diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7188d73 --- /dev/null +++ b/.clang-format @@ -0,0 +1,15 @@ +BasedOnStyle: LLVM + +Language: Cpp +Standard: c++17 +PointerAlignment: Left + +IncludeCategories: + - Regex: '^"[^/]+\"' + Priority: 10 + - Regex: '^"clang/' + Priority: 30 + - Regex: '^"llvm/' + Priority: 40 + - Regex: '^<' + Priority: 50 diff --git a/etc/cppinterop-clang21-valgrind.supp b/.github/valgrind/cppinterop-clang21-valgrind.supp similarity index 100% rename from etc/cppinterop-clang21-valgrind.supp rename to .github/valgrind/cppinterop-clang21-valgrind.supp diff --git a/etc/cppinterop-clang22-valgrind.supp b/.github/valgrind/cppinterop-clang22-valgrind.supp similarity index 100% rename from etc/cppinterop-clang22-valgrind.supp rename to .github/valgrind/cppinterop-clang22-valgrind.supp diff --git a/etc/valgrind-cppyy-cling.supp b/.github/valgrind/valgrind-cppjit-cling.supp similarity index 99% rename from etc/valgrind-cppyy-cling.supp rename to .github/valgrind/valgrind-cppjit-cling.supp index 9a1588c..30ab362 100644 --- a/etc/valgrind-cppyy-cling.supp +++ b/.github/valgrind/valgrind-cppjit-cling.supp @@ -1,5 +1,5 @@ # -# Cling/LLVM code generation: since cppyy is a python module, there is no +# Cling/LLVM code generation: since cppjit is a python module, there is no # proper control over offload ands since functions may be used during atexit() # calls, we'll just leak all JITed code rather than risk spurious crashes in # hard-to-reproduce cases. diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a1cb6f8..424a0f8 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -27,7 +27,8 @@ jobs: matrix: # Superset of the PR matrix + breadth (py 3.12/3.13, C++17, LLVM 20, arm) # and one cling cell. valgrind only where a supp is vendored: the LLVM 21 - # cells (clang21) and the arm LLVM 22 cell (clang22), both under etc/. + # cells (clang21) and the arm LLVM 22 cell (clang22), both under + # .github/valgrind/. include: - { os: ubuntu-24.04, llvm: '20', flavor: system, py: '3.12', cxx: '17' } - { os: ubuntu-24.04, llvm: '21', flavor: system, py: '3.13', cxx: '20', vg: true } diff --git a/.gitignore b/.gitignore index 0fee936..2013091 100644 --- a/.gitignore +++ b/.gitignore @@ -1,134 +1,31 @@ -# Byte-compiled / optimized / DLL files +# Byte-compiled __pycache__/ -*.py[codz] -*$py.class +*.py[cod] -# C extensions +# Built test dictionaries and extension modules *.so -# Distribution / packaging -.Python +# Packaging build/ -develop-eggs/ dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ *.egg-info/ -.installed.cfg -*.egg -MANIFEST +wheelhouse/ -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ +# Test and coverage artifacts +.pytest_cache/ .coverage .coverage.* -.cache -nosetests.xml coverage.xml -*.cover -*.py.cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -poetry.lock -poetry.toml - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control -pdm.lock -pdm.toml -.pdm-python -.pdm-build/ - -# pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. -# pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ +htmlcov/ -# Environments +# Environments and local config +.venv/ +venv/ .env .envrc -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# mkdocs documentation -/site - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ +.python-version -# IDEs and editors +# Editors / OS .idea/ .vscode/ +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c220d3..1f9c414 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,77 +74,85 @@ execute_process( OUTPUT_STRIP_TRAILING_WHITESPACE ) if(_python_platlib) - set(CPPINTEROP_INSTALL_DIR "${_python_platlib}/cppjit/cppyy_backend") + set(CPPINTEROP_INSTALL_DIR "${_python_platlib}/cppjit_backend") else() - set(CPPINTEROP_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/cppjit/cppyy_backend") + set(CPPINTEROP_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/cppjit_backend") endif() # Include cmake for CppInterOp config and build using ExternalProject. include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddCppInterOp.cmake) cppjit_add_cppinterop() -# this libcppyy.so merges both CPyCppyy and clingwrapper -file(GLOB CPYCPPYY_SOURCES CONFIGURE_DEPENDS src/CPyCppyy/src/*.cxx) -set(BACKEND_SOURCES - src/backend/clingwrapper.cxx - src/backend/cppinterop_dispatch.cxx +# this libcppjit.so merges both cpyrt and the interop wrapper +file(GLOB CPYRT_SOURCES CONFIGURE_DEPENDS src/cpyrt/*.cxx) +set(INTEROP_SOURCES + src/interop/interop_wrapper.cxx + src/interop/cppinterop_dispatch.cxx ) -add_library(cppyy SHARED ${CPYCPPYY_SOURCES} ${BACKEND_SOURCES}) -add_dependencies(cppyy CppInterOp) +add_library(cppjit SHARED ${CPYRT_SOURCES} ${INTEROP_SOURCES}) +add_dependencies(cppjit CppInterOp) -target_compile_definitions(cppyy PRIVATE +target_compile_definitions(cppjit PRIVATE CPPINTEROP_DIR="${CPPINTEROP_INSTALL_DIR}" CMAKE_SHARED_LIBRARY_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}" ) -target_include_directories(cppyy PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/CPyCppyy/include - ${CMAKE_CURRENT_SOURCE_DIR}/src/CPyCppyy/src - ${CMAKE_CURRENT_SOURCE_DIR}/src/backend +target_include_directories(cppjit PRIVATE + # src/ itself resolves the public "cpyrt/*.h" spellings against the + # flattened src/cpyrt/ directory + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/cpyrt + ${CMAKE_CURRENT_SOURCE_DIR}/src/interop ${CPPINTEROP_INSTALL_DIR}/include ${Python_INCLUDE_DIRS} ) -target_compile_options(cppyy PRIVATE +target_compile_options(cppjit PRIVATE -Wall -Wno-strict-aliasing -Wno-register ) if(CMAKE_COMPILER_IS_GNUCXX) - target_link_options(cppyy PRIVATE -Wl,-Bsymbolic-functions) + target_link_options(cppjit PRIVATE -Wl,-Bsymbolic-functions) endif() # macOS needs to allow undefined Python symbols if(APPLE) - set_target_properties(cppyy PROPERTIES + set_target_properties(cppjit PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" SUFFIX ".so" ) endif() -set_target_properties(cppyy PROPERTIES - OUTPUT_NAME "cppyy" +set_target_properties(cppjit PROPERTIES + OUTPUT_NAME "cppjit" PREFIX "lib" ) -# libcppyy.so is installed in cppjit/ root (import cppjit.libcppyy) -install(TARGETS cppyy - LIBRARY DESTINATION cppjit +# libcppjit.so is installed at the site-packages root (import libcppjit) +install(TARGETS cppjit + LIBRARY DESTINATION . ) # install CppInterOp libraries and headers install(CODE " file(GLOB _interop_libs \"${CPPINTEROP_INSTALL_DIR}/lib/libclangCppInterOp*\") foreach(_lib \${_interop_libs}) - file(INSTALL \${_lib} DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit/cppyy_backend/lib) + file(INSTALL \${_lib} DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit_backend/lib) endforeach() ") install(CODE " - file(INSTALL \"${CPPINTEROP_INSTALL_DIR}/include/\" DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit/cppyy_backend/include) + file(INSTALL \"${CPPINTEROP_INSTALL_DIR}/include/\" DESTINATION \${CMAKE_INSTALL_PREFIX}/cppjit_backend/include) ") -install(DIRECTORY src/CPyCppyy/include/CPyCppyy - DESTINATION cppjit/cppyy_backend/include +# the public cpyrt API headers keep their installed cpyrt/ prefix +install(FILES + src/cpyrt/API.h + src/cpyrt/CommonDefs.h + src/cpyrt/DispatchPtr.h + src/cpyrt/PyException.h + src/cpyrt/Reflex.h + DESTINATION cppjit_backend/include/cpyrt ) diff --git a/pyproject.toml b/pyproject.toml index ed91768..d7501d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,25 +4,50 @@ build-backend = "scikit_build_core.build" [project] name = "cppjit" -version = "0.1.0" +dynamic = ["version"] description = "CppJIT: fast and automatic Python-C++ interoperability" license = {text = "LBNL BSD"} -requires-python = ">=3.8" +requires-python = ">=3.12" authors = [ {name = "Aaron Jomy"}, {name = "Baidyanath Kundu"}, {name = "Jonas Rembser"}, {name = "Vassil Vassilev"}, {name = "Vipul Cariappa"}, - {name = "Wim Lavrijsen"}, - {name = "compiler-research"} + {name = "Wim Lavrijsen"} +] +maintainers = [ + {name = "Aaron Jomy", email = "aaronjomyjoseph@gmail.com"} ] [tool.scikit-build] wheel.install-dir = "." -wheel.packages = ["python/cppjit"] +wheel.packages = ["python/cppjit", "python/cppjit_backend"] cmake.build-type = "Release" +metadata.version.provider = "scikit_build_core.metadata.regex" +metadata.version.input = "python/cppjit/_version.py" [tool.pytest.ini_options] testpaths = ["test"] pythonpath = ["test"] + +[tool.ruff] +show-fixes = true + +[tool.ruff.lint] +# pyflakes/pycodestyle errors plus import sorting; wider rule sets are a +# post-squash cleanup +select = ["E4", "E7", "E9", "F", "I"] +ignore = [ + "E722", # bare excepts deliberately swallow C++-originated exceptions +] + +[tool.ruff.lint.per-file-ignores] +# the star import is load-bearing: it pulls the merged extension's names into +# the package namespace, and everything below it depends on them +"python/cppjit/__init__.py" = ["F403", "F405"] +"test/assert_interactive.py" = ["F403", "F405"] +# binding-test idioms: == True/False and type(x) == y assert proxy comparison +# semantics, fixtures use short upstream names, and unused locals hold +# references to control C++ object lifetimes +"test/**" = ["E712", "E721", "E741", "E742", "F841"] diff --git a/python/cppjit/__init__.py b/python/cppjit/__init__.py index fa2853e..bf9978f 100644 --- a/python/cppjit/__init__.py +++ b/python/cppjit/__init__.py @@ -1,2 +1,528 @@ -from .cppyy import * -from .cppyy import gbl, _backend +"""Dynamic C++ bindings generator. + +This module provides dynamic bindings to C++ through Cling, the LLVM-based C++ +interpreter, allowing interactive mixing of Python and C++. Example: + + >>> import cppjit + >>> cppjit.cppdef(\"\"\" + ... class MyClass { + ... public: + ... MyClass(int i) : m_data(i) {} + ... int m_data; + ... };\"\"\") + True + >>> from cppjit.gbl import MyClass + >>> m = MyClass(42) + >>> cppjit.cppdef(\"\"\" + ... void say_hello(MyClass* m) { + ... std::cout << "Hello, the number is: " << m->m_data << std::endl; + ... }\"\"\") + True + >>> MyClass.say_hello = cppjit.gbl.say_hello + >>> m.say_hello() + Hello, the number is: 42 + >>> m.m_data = 13 + >>> m.say_hello() + Hello, the number is: 13 + >>> + +For full documentation, see: + https://cppyy.readthedocs.io/ + +""" + +__author__ = "Wim Lavrijsen " + +__all__ = [ + "cppdef", # declare C++ source to Cling + "cppexec", # execute a C++ statement + "macro", # attempt to evaluate a cpp macro + "include", # load and jit a header file + "c_include", # load and jit a C header file + "load_library", # load a shared library + "nullptr", # unique pointer representing NULL + "sizeof", # size of a C++ type + "typeid", # typeid of a C++ type + "multi", # helper for multiple inheritance + "add_include_path", # add a path to search for headers + "add_library_path", # add a path to search for libraries + "add_autoload_map", # explicitly include an autoload map + "set_debug", # enable/disable debug output +] + +import ctypes +import os +import sys +import sysconfig +import warnings + +try: + import __pypy__ + + del __pypy__ + ispypy = True +except ImportError: + ispypy = False + +from . import _typemap +from ._version import __version__ as __version__ + +# import separately instead of in the above try/except block for easier to +# understand tracebacks +if ispypy: + raise ImportError("cppjit requires CPython; PyPy is not supported") +from ._cpython_cppjit import * + +# - allow importing from gbl -------------------------------------------------- +sys.modules["cppjit.gbl"] = gbl +sys.modules["cppjit.gbl.std"] = gbl.std + + +# - force creation of std.exception ------------------------------------------------------- +_e = gbl.std.exception + + +# - enable auto-loading ------------------------------------------------------- +try: + gbl.cling.runtime.gCling.EnableAutoLoading() +except: + pass + + +# - external typemap ---------------------------------------------------------- +_typemap.initialize(_backend) # also creates (u)int8_t mapper + +try: + gbl.std.int8_t = gbl.int8_t # ensures same _integer_ type + gbl.std.uint8_t = gbl.uint8_t +except (AttributeError, TypeError): + pass + + +# - pythonization factories --------------------------------------------------- +from . import _pythonization as py # noqa: E402 + +py._set_backend(_backend) + + +def _standard_pythonizations(pyclass, name): + # pythonization of tuple; TODO: placed here for convenience, but a custom case + # for tuples on each platform can be made much more performant ... + if name.find("tuple<", 0, 6) == 0: + import cppjit + + pyclass._tuple_len = cppjit.gbl.std.tuple_size(pyclass).value + + def tuple_len(self): + return self.__class__._tuple_len + + pyclass.__len__ = tuple_len + + def tuple_getitem(self, idx, get=cppjit.gbl.std.get): + if idx < self.__class__._tuple_len: + res = get[idx](self) + try: + res.__life_line = self + except Exception: + pass + return res + raise IndexError(idx) + + pyclass.__getitem__ = tuple_getitem + + # pythonization of std::basic_string; placed here because it's simpler to write the + # custom "npos" object (to allow easy result checking of find/rfind) in Python + elif pyclass.__cpp_name__ == "std::basic_string": + + class NPOS(int): + def __init__(self, npos): + self.__cpp_npos = npos + + def __eq__(self, other): + return other == -1 or other == self.__cpp_npos + + def __ne__(self, other): + return other != -1 and other != self.__cpp_npos + + if hasattr(pyclass.__class__, "npos"): + del pyclass.__class__.npos # drop b/c is const data + pyclass.npos = NPOS(pyclass.npos) + + return True + + +if not ispypy: + py.add_pythonization(_standard_pythonizations, "std") +# TODO: PyPy still has the old-style pythonizations, which require the full +# class name (not possible for std::tuple ...) + + +# std::make_shared/unique create needless templates: rely on Python's introspection +# instead. This also allows Python derived classes to be handled correctly. +class py_make_smartptr(object): + __slots__ = ["cls", "ptrcls"] + + def __init__(self, cls, ptrcls): + self.cls = cls + self.ptrcls = ptrcls + + def __call__(self, *args): + if len(args) == 1 and type(args[0]) == self.cls: # noqa: E721 + obj = args[0] + else: + obj = self.cls(*args) + return self.ptrcls[self.cls](obj) # C++ takes ownership + + +class make_smartptr(object): + __slots__ = ["ptrcls", "maker"] + + def __init__(self, ptrcls, maker): + self.ptrcls = ptrcls + self.maker = maker + + def __call__(self, ptr): + return py_make_smartptr(type(ptr), self.ptrcls)(ptr) + + def __getitem__(self, cls): + try: + if not cls.__module__ == int.__module__: + return py_make_smartptr(cls, self.ptrcls) + except AttributeError: + pass + if isinstance(cls, str) and cls not in ("int", "float"): + return py_make_smartptr(getattr(gbl, cls), self.ptrcls) + return self.maker[cls] + + +gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared) +gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique) +del make_smartptr + + +# --- interface to Cling ------------------------------------------------------ +class _stderr_capture(object): + def __init__(self): + self._capture = not gbl.Cpp.IsDebugOutputEnabled() + self.err = "" + + def __enter__(self): + if self._capture: + _begin_capture_stderr() + return self + + def __exit__(self, tp, val, trace): + if self._capture: + self.err = _end_capture_stderr() + + +def cppdef(src, verbose=True): + """Declare C++ source to Cling.""" + with _stderr_capture() as err: + errcode = gbl.Cpp.Declare(src, not verbose) + if not errcode == 0 or err.err: + if "warning" in err.err.lower() and "error" not in err.err.lower(): + warnings.warn(err.err, SyntaxWarning) + return True + raise SyntaxError("Failed to parse the given C++ code%s" % err.err) + return True + + +def cppexec(stmt): + """Execute C++ statement in Cling's global scope.""" + if stmt and stmt[-1] != ";": + stmt += ";" + + # capture stderr, but note that Process could legitimately be writing to + # std::cerr, in which case the captured output needs to be printed as normal + with _stderr_capture() as err: + errcode = ctypes.c_int(0) + try: + errcode = gbl.Cpp.Process(stmt) + except Exception as e: + sys.stderr.write("%s\n\n" % str(e)) + if not errcode.value: + errcode.value = 1 + + if not errcode == 0: + raise SyntaxError("Failed to parse the given C++ code%s" % err.err) + elif err.err and err.err[1:] != "\n": + sys.stderr.write(err.err[1:]) + + return True + + +def evaluate(input): + box = gbl.Cpp.Evaluate(input) + # Truthy sentinel: skips Box::convertTo's UB-on-K_Unspecified arm. + if box.getKind() == gbl.Cpp.Box.K_Unspecified: + return ~0 + return box.convertTo["long"]() + + +def macro(cppm): + """Attempt to evalute a C/C++ pre-processor macro as a constant""" + + try: + macro_val = getattr(getattr(gbl, "__cppjit_macros", None), cppm + "_", None) + if macro_val is None: + cppdef("namespace __cppjit_macros { auto %s_ = %s; }" % (cppm, cppm)) + return getattr(getattr(gbl, "__cppjit_macros"), cppm + "_") + except Exception: + pass + + raise ValueError("Failed to evaluate macro %s", cppm) + + +def load_library(name): + """Explicitly load a shared library.""" + with _stderr_capture() as err: + result = gbl.Cpp.LoadLibrary(name, True) + if result == False: # noqa: E712 + raise RuntimeError('Could not load library "%s": %s' % (name, err.err)) + + return True + + +def include(header): + """Load (and JIT) header file
into Cling.""" + with _stderr_capture() as err: + errcode = gbl.Cpp.Declare('#include "%s"' % header, False) + if not errcode == 0: + raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) + return True + + +def c_include(header): + """Load (and JIT) header file
into Cling.""" + with _stderr_capture() as err: + errcode = gbl.Cpp.Declare( + """extern "C" { + #include "%s" + }""" + % header, + False, + ) + if not errcode == 0: + raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) + return True + + +def add_include_path(path): + """Add a path to the include paths available to Cling.""" + if not os.path.isdir(path): + raise OSError("No such directory: %s" % path) + gbl.Cpp.AddIncludePath(path) + + +def add_library_path(path): + """Add a path to the library search paths available to Cling.""" + if not os.path.isdir(path): + raise OSError("No such directory: %s" % path) + gbl.Cpp.AddSearchPath(path, True, False) + + +# add access to Python C-API headers +apipath = sysconfig.get_path( + "include", "posix_prefix" if os.name == "posix" else os.name +) +if os.path.exists(apipath): + add_include_path(apipath) +elif ispypy: + # possibly structured without 'pythonx.y' in path + apipath = os.path.dirname(apipath) + if os.path.exists(apipath) and os.path.exists(os.path.join(apipath, "Python.h")): + add_include_path(apipath) + +# add access to extra headers for dispatcher (cpyrt only (?)) +if not ispypy: + try: + apipath_extra = os.environ["CPPJIT_API_PATH"] + if os.path.basename(apipath_extra) == "cpyrt": + apipath_extra = os.path.dirname(apipath_extra) + except KeyError: + apipath_extra = None + + if apipath_extra is None: + try: + import pkg_resources as pr + + d = pr.get_distribution("cpyrt") + for line in d.get_metadata_lines("RECORD"): + if "API.h" in line: + part = line[0 : line.find(",")] + + ape = os.path.join(d.location, part) + if os.path.exists(ape): + apipath_extra = os.path.dirname(os.path.dirname(ape)) + + del part, d, pr + except Exception: + pass + + if apipath_extra is None: + # for the monorepo: headers are at cppjit_backend/include/ + _cppjit_inc = os.path.join( + os.path.dirname(os.path.dirname(__file__)), "cppjit_backend", "include" + ) + if os.path.exists(os.path.join(_cppjit_inc, "cpyrt")): + apipath_extra = _cppjit_inc + del _cppjit_inc + + if apipath_extra is None: + ldversion = sysconfig.get_config_var("LDVERSION") + if not ldversion: + ldversion = sys.version[:3] + + apipath_extra = os.path.join( + os.path.dirname(apipath), "site", "python" + ldversion + ) + if not os.path.exists(os.path.join(apipath_extra, "cpyrt")): + import glob + + import libcppjit + + ape = os.path.dirname(libcppjit.__file__) + # a "normal" structure finds the include directory up to 3 levels up, + # ie. dropping lib/pythonx.y[md]/site-packages + for i in range(3): + if os.path.exists(os.path.join(ape, "include")): + break + ape = os.path.dirname(ape) + + ape = os.path.join(ape, "include") + if os.path.exists(os.path.join(ape, "cpyrt")): + apipath_extra = ape + else: + # add back pythonx.y or site/pythonx.y if present + for p in glob.glob( + os.path.join(ape, "python" + sys.version[:3] + "*") + ) + glob.glob(os.path.join(ape, "*", "python" + sys.version[:3] + "*")): + if os.path.exists(os.path.join(p, "cpyrt")): + apipath_extra = p + break + + if apipath_extra.lower() != "none": + if not os.path.exists(os.path.join(apipath_extra, "cpyrt")): + warnings.warn( + "cpyrt API not found (tried: %s); set CPPJIT_API_PATH envar to the 'cpyrt' API directory to fix" + % apipath_extra + ) + else: + add_include_path(apipath_extra) + + del apipath_extra + +if os.getenv("CONDA_PREFIX"): + # MacOS, Linux + include_path = os.path.join(os.getenv("CONDA_PREFIX"), "include") + if os.path.exists(include_path): + add_include_path(include_path) + + # Windows + include_path = os.path.join(os.getenv("CONDA_PREFIX"), "Library", "include") + if os.path.exists(include_path): + add_include_path(include_path) + +# assuming that we are in PREFIX/lib/python/site-packages/cppjit, add PREFIX/include to the search path +include_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), *(4 * [os.path.pardir] + ["include"])) +) +if os.path.exists(include_path): + add_include_path(include_path) + +del include_path, apipath, ispypy + + +def add_autoload_map(fname): + """Add the entries from a autoload (.rootmap) file to Cling.""" + if not os.path.isfile(fname): + raise OSError("no such file: %s" % fname) + gbl.cling.runtime.gCling.LoadLibraryMap(fname) + + +def set_debug(enable=True): + """Enable/disable debug output.""" + gbl.Cpp.EnableDebugOutput(enable) + + +def _get_name(tt): + if isinstance(tt, str): + return tt + try: + ttname = tt.__cpp_name__ + except AttributeError: + ttname = tt.__name__ + return ttname + + +_sizes = {} + + +def sizeof(tt): + """Returns the storage size (in chars) of C++ type .""" + if not isinstance(tt, type) and not isinstance(tt, str): + tt = type(tt) + try: + return _sizes[tt] + except KeyError: + try: + sz = ctypes.sizeof(tt) + except TypeError: + # Route through evaluate() so the Box-returning Cpp::Evaluate + # is unboxed in one place (see the shim above). Cpp::SizeOf + # would be faster but its sibling Cpp::GetNamed does not + # traverse `Foo::Bar`-style qualified names, so handing it a + # nested `tt.__cpp_name__` resolves to a null scope and the + # subsequent SizeOf hangs/aborts. Stick with the legacy + # interpreter round-trip until we add a qualified-name + # resolver (or an overload of SizeOf that takes a name). + sz = evaluate("sizeof(%s)" % (_get_name(tt),)) + # scope = gbl.Cpp.GetNamed(_get_name(tt)) + # sz = gbl.Cpp.SizeOf(scope) + _sizes[tt] = sz + return sz + + +_typeids = {} + + +def typeid(tt): + """Returns the C++ runtime type information for type .""" + if not isinstance(tt, type): + tt = type(tt) + try: + return _typeids[tt] + except KeyError: + tidname = "typeid_" + str(len(_typeids)) + cppexec( + "namespace _cppjit_internal { auto* %s = &typeid(%s); }" + % ( + tidname, + _get_name(tt), + ) + ) + tid = getattr(gbl._cppjit_internal, tidname) + _typeids[tt] = tid + return tid + + +def multi(*bases): # after six, see also _typemap.py + """Resolve metaclasses for multiple inheritance.""" + # contruct a "no conflict" meta class; the '_meta' is needed by convention + nc_meta = type.__new__( + type, "cppjit_nc_meta", tuple(type(b) for b in bases if type(b) is not type), {} + ) + + class faux_meta(type): + def __new__(mcs, name, this_bases, d): + return nc_meta(name, bases, d) + + return type.__new__(faux_meta, "faux_meta", (), {}) + + +# - workaround (TODO: may not be needed with Clang9) -------------------------- +if "win32" in sys.platform: + cppdef("""template<> + std::basic_ostream>& __cdecl std::endl>( + std::basic_ostream>&);""") diff --git a/python/cppjit/_cpython_cppjit.py b/python/cppjit/_cpython_cppjit.py new file mode 100644 index 0000000..0f26343 --- /dev/null +++ b/python/cppjit/_cpython_cppjit.py @@ -0,0 +1,248 @@ +"""CPython-specific touch-ups""" + +import ctypes +import sys + +from . import _stdcpp_fix # noqa: F401 + +__all__ = [ + "gbl", + "load_reflection_info", + "addressof", + "bind_object", + "nullptr", + "default", + "_backend", + "_begin_capture_stderr", + "_end_capture_stderr", +] + +# the merged libcppjit extension is the backend: importing it loads the +# C++ runtime (no separate loader.load_cpp_backend() step, which would +# initialize the interpreter twice) +import libcppjit as _backend + +# explicitly expose APIs from libcppjit +_w = ctypes.CDLL(_backend.__file__, ctypes.RTLD_GLOBAL) + + +# some beautification for inspect (only on p2) +if sys.hexversion < 0x3000000: + # TODO: this reliese on CPPOverload cooking up a func_code object, which atm + # is simply not implemented for p3 :/ + + # convince inspect that cppjit method proxies are possible drop-ins for python + # methods and classes for pydoc + import inspect + + inspect._old_isfunction = inspect.isfunction + + def isfunction(object): + if isinstance(object, _backend.CPPOverload) and not object.im_class: + return True + return inspect._old_isfunction(object) + + inspect.isfunction = isfunction + + inspect._old_ismethod = inspect.ismethod + + def ismethod(object): + if isinstance(object, _backend.CPPOverload): + return True + return inspect._old_ismethod(object) + + inspect.ismethod = ismethod + del isfunction, ismethod + + +### template support --------------------------------------------------------- +class Template(object): # expected/used by ProxyWrappers.cxx in cpyrt + stl_sequence_types = ["std::vector", "std::list", "std::set", "std::deque"] + stl_unrolled_types = ["std::pair"] + stl_fixed_size_types = ["std::array"] + stl_mapping_types = ["std::map", "std::unordered_map"] + + def __init__(self, name, scope): + self.__name__ = name + self.__cpp_name__ = name + self._instantiations = dict() + self.__scope__ = scope + + def __repr__(self): + return "" % (self.__name__, hex(id(self))) + + def __getitem__(self, *args): + # multi-argument to [] becomes a single tuple argument + if args and isinstance(args[0], tuple): + args = args[0] + + # if already instantiated, return the existing class + try: + return self._instantiations[args] + except KeyError: + pass + + # construct the type name from the types or their string representation + newargs = [self.__scope__] + for arg in args: + if isinstance(arg, str): + arg = ",".join(map(lambda x: x.strip(), arg.split(","))) + newargs.append(arg) + pyclass = _backend.MakeCppTemplateClass(*newargs) + + # memoize the class to prevent spurious lookups/re-pythonizations + self._instantiations[args] = pyclass + + # special case pythonization (builtin_map is not available from the C-API) + if "push_back" in pyclass.__dict__ and "__iadd__" not in pyclass.__dict__: + if "reserve" in pyclass.__dict__: + + def iadd(self, ll): + self.reserve(len(ll)) + for x in ll: + self.push_back(x) + return self + else: + + def iadd(self, ll): + for x in ll: + self.push_back(x) + return self + + pyclass.__iadd__ = iadd + + # back-pointer for reflection + pyclass.__cpp_template__ = self + + return pyclass + + def __call__(self, *args): + # for C++17, we're required to derive the type when using initializer syntax + # (i.e. a tuple or list); not sure how to do that in general, but below the + # most common cases are covered + if args: + args0 = args[0] + if args0 and isinstance(args0, (tuple, list)): + t = type(args0[0]) + if t is float: + t = "double" + + if self.__name__ in self.stl_sequence_types: + return self[t](*args) + if self.__name__ in self.stl_fixed_size_types: + return self[t, len(args0)](*args) + if self.__name__ in self.stl_unrolled_types: + return self[tuple(type(a) for a in args0)](*args0) + + if args0 and isinstance(args0, dict): + if self.__name__ in self.stl_mapping_types: + try: + pair = args0.items().__iter__().__next__() + except AttributeError: + pair = args0.items()[0] + t1 = type(pair[0]) + if t1 is float: + t1 = "double" + t2 = type(pair[1]) + if t2 is float: + t2 = "double" + return self[t1, t2](*args) + + return self.__getitem__(*(type(a) for a in args0))(*args) + + # old 'metaclass-style' template instantiations + return self.__getitem__(*args) + + +_backend.Template = Template + + +# - :: and std:: namespaces --------------------------------------------------- +gbl = _backend.CreateScopeProxy("") +gbl.__class__.__repr__ = lambda cls: "" % id(cls) +gbl.std = _backend.CreateScopeProxy("std") +# for move, we want our "pythonized" one, not the C++ template +gbl.std.move = _backend.move +# CppInterOp proxy object to access its API +Cpp = gbl.Cpp + +# - add to the dynamic path as needed ----------------------------------------- +import os # noqa: E402 + + +def add_default_paths(): + if os.getenv("CONDA_PREFIX"): + # MacOS, Linux + lib_path = os.path.join(os.getenv("CONDA_PREFIX"), "lib") + if os.path.exists(lib_path): + Cpp.AddSearchPath(lib_path, True, False) + + # Windows + lib_path = os.path.join(os.getenv("CONDA_PREFIX"), "Library", "lib") + if os.path.exists(lib_path): + Cpp.AddSearchPath(lib_path, True, False) + + # assuming that we are in PREFIX/lib/python/site-packages/cppjit, add PREFIX/lib to the search path + lib_path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), os.path.pardir, os.path.pardir, os.path.pardir + ) + ) + if os.path.exists(lib_path): + Cpp.AddSearchPath(lib_path, True, False) + + try: + with open("/etc/ld.so.conf") as ldconf: + for line in ldconf: + f = line.strip() + if os.path.exists(f): + Cpp.AddSearchPath(f, True, False) + except IOError: + pass + + +add_default_paths() +del add_default_paths + + +# - exports ------------------------------------------------------------------- +addressof = _backend.addressof +bind_object = _backend.bind_object +nullptr = _backend.nullptr +default = _backend.default + + +def load_reflection_info(name): + # with _stderr_capture() as err: + # FIXME: Remove the .so and add logic in libcppinterop + name = name + ".so" + result = Cpp.LoadLibrary(name, True) + if name.endswith("Dict.so"): + header = name[:-7] + ".h" + Cpp.Declare('#include "' + header + '"', False) + + if result == False: # noqa: E712 + raise RuntimeError('Could not load library "%s"' % (name)) + + return True + + +def _begin_capture_stderr(): + _backend._begin_capture_stderr() + + +def _end_capture_stderr(): + err = _backend._end_capture_stderr() + if err: + try: + return "\n%s" % err + except UnicodeDecodeError as e: + original_error = e + try: + return "\n%s" % err.decode("gbk") # not guaranteed, but common + except UnicodeDecodeError: + pass + return "C++ issued an error message that could not be decoded (%s)" % str( + original_error + ) + return "" diff --git a/python/cppjit/cppyy/_pythonization.py b/python/cppjit/_pythonization.py similarity index 82% rename from python/cppjit/cppyy/_pythonization.py rename to python/cppjit/_pythonization.py index a45c136..a3d1e74 100644 --- a/python/cppjit/cppyy/_pythonization.py +++ b/python/cppjit/_pythonization.py @@ -1,14 +1,14 @@ -""" Pythonization API. -""" +"""Pythonization API.""" import re __all__ = [ - 'add_pythonization', - 'remove_pythonization', - 'pin_type', - 'add_type_reducer', - ] + "add_pythonization", + "remove_pythonization", + "pin_type", + "add_type_reducer", +] + def _set_backend(backend): global _backend @@ -16,16 +16,16 @@ def _set_backend(backend): # user-provided, general pythonizations -def add_pythonization(pythonizor, scope = ''): +def add_pythonization(pythonizor, scope=""): """ should be a callable taking two arguments: a class proxy, and its C++ name. It is called each time a named class from (the global one by default, but a relevant C++ namespace is recommended) is bound. """ return _backend.add_pythonization(pythonizor, scope) -def remove_pythonization(pythonizor, scope = ''): - """Remove previously registered from . - """ + +def remove_pythonization(pythonizor, scope=""): + """Remove previously registered from .""" return _backend.remove_pythonization(pythonizor, scope) @@ -36,8 +36,7 @@ def pin_type(klass): # mapper to reduce template expression trees def add_type_reducer(reducable, reduced): - """Reduce to type on returns from function calls. - """ + """Reduce to type on returns from function calls.""" return _backend._add_type_reducer(reducable, reduced) @@ -46,16 +45,19 @@ def add_exception_mapping(cpp_exception, py_exception): _backend.UserExceptions[cpp_exception] = py_exception +# --- Pythonization factories -------------------------------------------- -#--- Pythonization factories -------------------------------------------- def set_gil_policy(match_class, match_method, release_gil=True): - return set_method_property(match_class, match_method, '__release_gil__', int(release_gil)) + return set_method_property( + match_class, match_method, "__release_gil__", int(release_gil) + ) def set_ownership_policy(match_class, match_method, python_owns_result): - return set_method_property(match_class, match_method, - '__creates__', int(python_owns_result)) + return set_method_property( + match_class, match_method, "__creates__", int(python_owns_result) + ) # NB: Ideally, we'd use the version commented out below, but for now, we @@ -65,18 +67,21 @@ class attribute_pythonizor(object): class getter(object): def __init__(self, attr): self.attr = attr + def __call__(self, obj): return getattr(obj, self.attr) class setter(object): def __init__(self, attr): self.attr = attr + def __call__(self, obj, value): return setattr(obj, self.attr, value) class deleter(object): def __init__(self, attr): self.attr = attr + def __call__(self, obj): return delattr(obj, self.attr) @@ -89,13 +94,15 @@ def __init__(self, match_class, orig_attribute, new_attribute, keep_orig): def __call__(self, obj, name): if not self.match_class.match(name): return - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: if self.match_attr.match(k): tmp = property(self.getter(k), self.setter(k), self.deleter(k)) setattr(obj, self.new_attr, tmp) - #if not self.keep_orig: delattr(obj, k) + # if not self.keep_orig: delattr(obj, k) + return attribute_pythonizor(match_class, orig_attribute, new_attribute, keep_orig) + # def rename_attribute(match_class, orig_attribute, new_attribute, keep_orig=False): # class method_pythonizor: # def __init__(self, match_class, orig_attribute, new_attribute, keep_orig): @@ -122,6 +129,7 @@ def __call__(self, obj, name): # Shared with PyPy: + def add_overload(match_class, match_method, overload): class method_pythonizor(object): def __init__(self, match_class, match_method, overload): @@ -132,15 +140,17 @@ def __init__(self, match_class, match_method, overload): def __call__(self, obj, name): if not self.match_class.match(name): return - for k in dir(obj): #.__dict__: - try: - tmp = getattr(obj, k) - except: - continue - if self.match_method.match(k): - try: - tmp.__add_overload__(overload) - except AttributeError: pass + for k in dir(obj): # .__dict__: + try: + tmp = getattr(obj, k) + except: + continue + if self.match_method.match(k): + try: + tmp.__add_overload__(overload) + except AttributeError: + pass + return method_pythonizor(match_class, match_method, overload) @@ -162,12 +172,16 @@ def __call__(self, obj, name): f = getattr(obj, k) except: continue + def make_fun(f, g): def h(self, *args, **kwargs): return g(self, f(self, *args, **kwargs)) + return h + h = make_fun(f, g) setattr(obj, k, h) + return composition_pythonizor(match_class, match_method, g) @@ -182,17 +196,20 @@ def __init__(self, match_class, match_method, prop, value): def __call__(self, obj, name): if not self.match_class.match(name): return - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: try: tmp = getattr(obj, k) except: continue if self.match_method.match(k): setattr(tmp, self.prop, self.value) + return method_pythonizor(match_class, match_method, prop, value) -def make_property(match_class, match_get, match_set=None, match_del=None, prop_name=None): +def make_property( + match_class, match_get, match_set=None, match_del=None, prop_name=None +): class property_pythonizor(object): def __init__(self, match_class, match_get, match_set, match_del, prop_name): self.match_class = re.compile(match_class) @@ -204,7 +221,7 @@ def __init__(self, match_class, match_get, match_set, match_del, prop_name): self.match_set = re.compile(match_set) match_many_setters = self.match_set.groups == 1 if match_many_getters ^ match_many_setters: - raise ValueError('Must match getters and setters equally') + raise ValueError("Must match getters and setters equally") else: self.match_set = None @@ -212,16 +229,20 @@ def __init__(self, match_class, match_get, match_set, match_del, prop_name): self.match_del = re.compile(match_del) match_many_deleters = self.match_del.groups == 1 if match_many_getters ^ match_many_deleters: - raise ValueError('Must match getters and deleters equally') + raise ValueError("Must match getters and deleters equally") else: self.match_del = None self.match_many = match_many_getters if not (self.match_many or prop_name): - raise ValueError("If not matching properties by regex, need a property name with exactly one substitution field") + raise ValueError( + "If not matching properties by regex, need a property name with exactly one substitution field" + ) if self.match_many and prop_name: - if prop_name.format(').!:(') == prop_name: - raise ValueError("If matching properties by regex and providing a property name, the name needs exactly one substitution field") + if prop_name.format(").!:(") == prop_name: + raise ValueError( + "If matching properties by regex and providing a property name, the name needs exactly one substitution field" + ) self.prop_name = prop_name @@ -232,6 +253,7 @@ def __init__(self, getter): def __call__(self, obj): return getattr(obj, self.getter)() + return proxy(getter) def make_set_proxy(self, setter): @@ -241,6 +263,7 @@ def __init__(self, setter): def __call__(self, obj, arg): return getattr(obj, self.setter)(arg) + return proxy(setter) def __call__(self, obj, name): @@ -255,13 +278,13 @@ def __call__(self, obj, name): if not self.match_many: fget, fset, fdel = None, None, None - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: match = self.match_get.match(k) try: tmp = getattr(obj, k) except: continue - if match and hasattr(tmp, '__call__'): + if match and hasattr(tmp, "__call__"): if self.match_many: name = match.group(1) named_getters[name] = k @@ -270,13 +293,13 @@ def __call__(self, obj, name): break if self.match_set: - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: match = self.match_set.match(k) try: tmp = getattr(obj, k) except: continue - if match and hasattr(tmp, '__call__'): + if match and hasattr(tmp, "__call__"): if self.match_many: name = match.group(1) named_setters[name] = k @@ -285,13 +308,13 @@ def __call__(self, obj, name): break if self.match_del: - for k in dir(obj): #.__dict__: + for k in dir(obj): # .__dict__: match = self.match_del.match(k) try: tmp = getattr(obj, k) except: continue - if match and hasattr(tmp, '__call__'): + if match and hasattr(tmp, "__call__"): if self.match_many: name = match.group(1) named_deleters[name] = k @@ -309,7 +332,7 @@ def __call__(self, obj, name): names += list(named_deleters.keys()) names = set(names) - properties = [] + properties = [] # noqa: F841 for name in names: if name in named_getters: fget = self.make_get_del_proxy(named_getters[name]) @@ -335,4 +358,3 @@ def __call__(self, obj, name): setattr(obj, prop_name, new_prop) return property_pythonizor(match_class, match_get, match_set, match_del, prop_name) - diff --git a/python/cppjit/cppyy/_stdcpp_fix.py b/python/cppjit/_stdcpp_fix.py similarity index 77% rename from python/cppjit/cppyy/_stdcpp_fix.py rename to python/cppjit/_stdcpp_fix.py index 90c3687..7b66014 100644 --- a/python/cppjit/cppyy/_stdcpp_fix.py +++ b/python/cppjit/_stdcpp_fix.py @@ -3,11 +3,12 @@ # It may be that the interpreter (wether python or pypy-c) was not linked # with C++; force its loading before doing anything else (note that not # linking with C++ spells trouble anyway for any C++ libraries ...) -if 'linux' in sys.platform and 'GCC' in sys.version: +if "linux" in sys.platform and "GCC" in sys.version: # TODO: check executable to see whether linking indeed didn't happen import ctypes + try: - stdcpp = ctypes.CDLL('libstdc++.so', ctypes.RTLD_GLOBAL) + stdcpp = ctypes.CDLL("libstdc++.so", ctypes.RTLD_GLOBAL) except Exception: pass # TODO: what if Linux/clang and what if Mac? diff --git a/python/cppjit/cppyy/_typemap.py b/python/cppjit/_typemap.py similarity index 69% rename from python/cppjit/cppyy/_typemap.py rename to python/cppjit/_typemap.py index 549dab7..4a182bf 100644 --- a/python/cppjit/cppyy/_typemap.py +++ b/python/cppjit/_typemap.py @@ -1,24 +1,28 @@ -""" Externally provided types: get looked up if all else fails, e.g. - for typedef-ed C++ builtin types. +"""Externally provided types: get looked up if all else fails, e.g. +for typedef-ed C++ builtin types. """ import ctypes import sys import types + def _create_mapper(cls, extra_dct=None): def mapper(name, scope): if scope: - cppname = scope+'::'+name - modname = 'cppyy.gbl.'+scope + cppname = scope + "::" + name + modname = "cppjit.gbl." + scope else: cppname = name - modname = 'cppyy.gbl' - dct = {'__cpp_name__' : cppname, '__module__' : modname} - if extra_dct: dct.update(extra_dct) + modname = "cppjit.gbl" + dct = {"__cpp_name__": cppname, "__module__": modname} + if extra_dct: + dct.update(extra_dct) return type(name, (cls,), dct) + return mapper + # from six.py --- # Copyright (c) 2010-2017 Benjamin Peterson # @@ -40,20 +44,21 @@ def mapper(name, scope): # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. + def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. class metaclass(type): - def __new__(cls, name, this_bases, d): if sys.version_info[:2] >= (3, 7): # This version introduced PEP 560 that requires a bit # of extra care (we mimic what is done by __build_class__). resolved_bases = types.resolve_bases(bases) if resolved_bases is not bases: - d['__orig_bases__'] = bases + d["__orig_bases__"] = bases else: resolved_bases = bases return meta(name, resolved_bases, d) @@ -61,57 +66,74 @@ def __new__(cls, name, this_bases, d): @classmethod def __prepare__(cls, name, this_bases): return meta.__prepare__(name, bases) - return type.__new__(metaclass, 'temporary_class', (), {}) + + return type.__new__(metaclass, "temporary_class", (), {}) + + # --- end from six.py + class _BoolMeta(type): - def __call__(self, val = bool()): - if val: return True - else: return False + def __call__(self, val=bool()): + if val: + return True + else: + return False + class _Bool(with_metaclass(_BoolMeta, object)): pass def initialize(backend): - if not hasattr(backend, 'type_map'): + if not hasattr(backend, "type_map"): return tm = backend.type_map # boolean type (builtin type bool can nog be subclassed) - tm['bool'] = _create_mapper(_Bool) + tm["bool"] = _create_mapper(_Bool) # char types str_tm = _create_mapper(str) - for tp in ['char', 'unsigned char', 'signed char']: + for tp in ["char", "unsigned char", "signed char"]: tm[tp] = str_tm if sys.hexversion < 0x3000000: - tm['wchar_t'] = _create_mapper(unicode) + tm["wchar_t"] = _create_mapper(unicode) # noqa: F821 else: - tm['wchar_t'] = str_tm + tm["wchar_t"] = str_tm # integer types int_tm = _create_mapper(int) - for tp in ['int8_t', 'uint8_t', 'short', 'unsigned short', 'int']: + for tp in ["int8_t", "uint8_t", "short", "unsigned short", "int"]: tm[tp] = int_tm if sys.hexversion < 0x3000000: - long_tm = _create_mapper(long) + long_tm = _create_mapper(long) # noqa: F821 else: - long_tm = tm['int'] - for tp in ['unsigned int', 'long', 'unsigned long', 'long long', 'unsigned long long']: + long_tm = tm["int"] + for tp in [ + "unsigned int", + "long", + "unsigned long", + "long long", + "unsigned long long", + ]: tm[tp] = long_tm # floating point types float_tm = _create_mapper(float) - for tp in ['float', 'double', 'long double']: + for tp in ["float", "double", "long double"]: tm[tp] = float_tm # void* def voidp_init(self, arg=0): - from cppjit import cppyy import ctypes - if arg == cppyy.nullptr: arg = 0 + + import cppjit + + if arg == cppjit.nullptr: + arg = 0 ctypes.c_void_p.__init__(self, arg) - tm['void *'] = _create_mapper(ctypes.c_void_p, {'__init__' : voidp_init}) + + tm["void *"] = _create_mapper(ctypes.c_void_p, {"__init__": voidp_init}) diff --git a/python/cppjit/_version.py b/python/cppjit/_version.py new file mode 100644 index 0000000..3dc1f76 --- /dev/null +++ b/python/cppjit/_version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/python/cppjit/cppyy/__init__.py b/python/cppjit/cppyy/__init__.py deleted file mode 100644 index 9ca8349..0000000 --- a/python/cppjit/cppyy/__init__.py +++ /dev/null @@ -1,465 +0,0 @@ -"""Dynamic C++ bindings generator. - -This module provides dynamic bindings to C++ through Cling, the LLVM-based C++ -interpreter, allowing interactive mixing of Python and C++. Example: - - >>> import cppyy - >>> cppyy.cppdef(\"\"\" - ... class MyClass { - ... public: - ... MyClass(int i) : m_data(i) {} - ... int m_data; - ... };\"\"\") - True - >>> from cppyy.gbl import MyClass - >>> m = MyClass(42) - >>> cppyy.cppdef(\"\"\" - ... void say_hello(MyClass* m) { - ... std::cout << "Hello, the number is: " << m->m_data << std::endl; - ... }\"\"\") - True - >>> MyClass.say_hello = cppyy.gbl.say_hello - >>> m.say_hello() - Hello, the number is: 42 - >>> m.m_data = 13 - >>> m.say_hello() - Hello, the number is: 13 - >>> - -For full documentation, see: - https://cppyy.readthedocs.io/ - -""" - -__author__ = 'Wim Lavrijsen ' - -__all__ = [ - 'cppdef', # declare C++ source to Cling - 'cppexec', # execute a C++ statement - 'macro', # attempt to evaluate a cpp macro - 'include', # load and jit a header file - 'c_include', # load and jit a C header file - 'load_library', # load a shared library - 'nullptr', # unique pointer representing NULL - 'sizeof', # size of a C++ type - 'typeid', # typeid of a C++ type - 'multi', # helper for multiple inheritance - 'add_include_path', # add a path to search for headers - 'add_library_path', # add a path to search for libraries - 'add_autoload_map', # explicitly include an autoload map - 'set_debug', # enable/disable debug output - ] - -import ctypes, os, sys, sysconfig, warnings - -if not 'CLING_STANDARD_PCH' in os.environ: - def _set_pch(): - try: - from .. import cppyy_backend as cpb - local_pch = os.path.join(os.path.dirname(__file__), 'allDict.cxx.pch.'+str(cpb.__version__)) - if os.path.exists(local_pch): - os.putenv('CLING_STANDARD_PCH', local_pch) - os.environ['CLING_STANDARD_PCH'] = local_pch - except (ImportError, AttributeError): - pass - _set_pch(); del _set_pch - -try: - import __pypy__ - del __pypy__ - ispypy = True -except ImportError: - ispypy = False - -from . import _typemap -from ._version import __version__ - -# import separately instead of in the above try/except block for easier to -# understand tracebacks -if ispypy: - from ._pypy_cppyy import * -else: - from ._cpython_cppyy import * - - -#- allow importing from gbl -------------------------------------------------- -sys.modules['cppyy.gbl'] = gbl -sys.modules['cppyy.gbl.std'] = gbl.std - - -#- force creation of std.exception ------------------------------------------------------- -_e = gbl.std.exception - - -#- enable auto-loading ------------------------------------------------------- -try: gbl.cling.runtime.gCling.EnableAutoLoading() -except: pass - - -#- external typemap ---------------------------------------------------------- -_typemap.initialize(_backend) # also creates (u)int8_t mapper - -try: - gbl.std.int8_t = gbl.int8_t # ensures same _integer_ type - gbl.std.uint8_t = gbl.uint8_t -except (AttributeError, TypeError): - pass - - -#- pythonization factories --------------------------------------------------- -from . import _pythonization as py -py._set_backend(_backend) - -def _standard_pythonizations(pyclass, name): - # pythonization of tuple; TODO: placed here for convenience, but a custom case - # for tuples on each platform can be made much more performant ... - if name.find('tuple<', 0, 6) == 0: - from cppjit import cppyy - pyclass._tuple_len = cppyy.gbl.std.tuple_size(pyclass).value - def tuple_len(self): - return self.__class__._tuple_len - pyclass.__len__ = tuple_len - def tuple_getitem(self, idx, get=cppyy.gbl.std.get): - if idx < self.__class__._tuple_len: - res = get[idx](self) - try: - res.__life_line = self - except Exception: - pass - return res - raise IndexError(idx) - pyclass.__getitem__ = tuple_getitem - - # pythonization of std::basic_string; placed here because it's simpler to write the - # custom "npos" object (to allow easy result checking of find/rfind) in Python - elif pyclass.__cpp_name__ == "std::basic_string": - class NPOS(int): - def __init__(self, npos): - self.__cpp_npos = npos - def __eq__(self, other): - return other == -1 or other == self.__cpp_npos - def __ne__(self, other): - return other != -1 and other != self.__cpp_npos - if hasattr(pyclass.__class__, 'npos'): - del pyclass.__class__.npos # drop b/c is const data - pyclass.npos = NPOS(pyclass.npos) - - return True - -if not ispypy: - py.add_pythonization(_standard_pythonizations, "std") -# TODO: PyPy still has the old-style pythonizations, which require the full -# class name (not possible for std::tuple ...) - -# std::make_shared/unique create needless templates: rely on Python's introspection -# instead. This also allows Python derived classes to be handled correctly. -class py_make_smartptr(object): - __slots__ = ['cls', 'ptrcls'] - def __init__(self, cls, ptrcls): - self.cls = cls - self.ptrcls = ptrcls - def __call__(self, *args): - if len(args) == 1 and type(args[0]) == self.cls: - obj = args[0] - else: - obj = self.cls(*args) - return self.ptrcls[self.cls](obj) # C++ takes ownership - -class make_smartptr(object): - __slots__ = ['ptrcls', 'maker'] - def __init__(self, ptrcls, maker): - self.ptrcls = ptrcls - self.maker = maker - def __call__(self, ptr): - return py_make_smartptr(type(ptr), self.ptrcls)(ptr) - def __getitem__(self, cls): - try: - if not cls.__module__ == int.__module__: - return py_make_smartptr(cls, self.ptrcls) - except AttributeError: - pass - if isinstance(cls, str) and not cls in ('int', 'float'): - return py_make_smartptr(getattr(gbl, cls), self.ptrcls) - return self.maker[cls] - -gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared) -gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique) -del make_smartptr - - -#--- interface to Cling ------------------------------------------------------ -class _stderr_capture(object): - def __init__(self): - self._capture = not gbl.Cpp.IsDebugOutputEnabled() - self.err = "" - - def __enter__(self): - if self._capture: - _begin_capture_stderr() - return self - - def __exit__(self, tp, val, trace): - if self._capture: - self.err = _end_capture_stderr() - -def cppdef(src, verbose = True): - """Declare C++ source to Cling.""" - with _stderr_capture() as err: - errcode = gbl.Cpp.Declare(src, not verbose) - if not errcode == 0 or err.err: - if 'warning' in err.err.lower() and not 'error' in err.err.lower(): - warnings.warn(err.err, SyntaxWarning) - return True - raise SyntaxError('Failed to parse the given C++ code%s' % err.err) - return True - -def cppexec(stmt): - """Execute C++ statement in Cling's global scope.""" - if stmt and stmt[-1] != ';': - stmt += ';' - - # capture stderr, but note that Process could legitimately be writing to - # std::cerr, in which case the captured output needs to be printed as normal - with _stderr_capture() as err: - errcode = ctypes.c_int(0) - try: - errcode = gbl.Cpp.Process(stmt) - except Exception as e: - sys.stderr.write("%s\n\n" % str(e)) - if not errcode.value: errcode.value = 1 - - if not errcode == 0: - raise SyntaxError('Failed to parse the given C++ code%s' % err.err) - elif err.err and err.err[1:] != '\n': - sys.stderr.write(err.err[1:]) - - return True - -def evaluate(input): - box = gbl.Cpp.Evaluate(input) - # Truthy sentinel: skips Box::convertTo's UB-on-K_Unspecified arm. - if box.getKind() == gbl.Cpp.Box.K_Unspecified: - return ~0 - return box.convertTo['long']() - -def macro(cppm): - """Attempt to evalute a C/C++ pre-processor macro as a constant""" - - try: - macro_val = getattr(getattr(gbl, '__cppyy_macros', None), cppm+'_', None) - if macro_val is None: - cppdef("namespace __cppyy_macros { auto %s_ = %s; }" % (cppm, cppm)) - return getattr(getattr(gbl, '__cppyy_macros'), cppm+'_') - except Exception: - pass - - raise ValueError('Failed to evaluate macro %s', cppm) - - -def load_library(name): - """Explicitly load a shared library.""" - with _stderr_capture() as err: - result = gbl.Cpp.LoadLibrary(name, True) - if result == False: - raise RuntimeError('Could not load library "%s": %s' % (name, err.err)) - - return True - -def include(header): - """Load (and JIT) header file
into Cling.""" - with _stderr_capture() as err: - errcode = gbl.Cpp.Declare('#include "%s"' % header, False) - if not errcode == 0: - raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) - return True - -def c_include(header): - """Load (and JIT) header file
into Cling.""" - with _stderr_capture() as err: - errcode = gbl.Cpp.Declare("""extern "C" { - #include "%s" - }""" % header, False) - if not errcode == 0: - raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) - return True - -def add_include_path(path): - """Add a path to the include paths available to Cling.""" - if not os.path.isdir(path): - raise OSError('No such directory: %s' % path) - gbl.Cpp.AddIncludePath(path) - -def add_library_path(path): - """Add a path to the library search paths available to Cling.""" - if not os.path.isdir(path): - raise OSError('No such directory: %s' % path) - gbl.Cpp.AddSearchPath(path, True, False) - -# add access to Python C-API headers -apipath = sysconfig.get_path('include', 'posix_prefix' if os.name == 'posix' else os.name) -if os.path.exists(apipath): - add_include_path(apipath) -elif ispypy: - # possibly structured without 'pythonx.y' in path - apipath = os.path.dirname(apipath) - if os.path.exists(apipath) and os.path.exists(os.path.join(apipath, 'Python.h')): - add_include_path(apipath) - -# add access to extra headers for dispatcher (CPyCppyy only (?)) -if not ispypy: - try: - apipath_extra = os.environ['CPPYY_API_PATH'] - if os.path.basename(apipath_extra) == 'CPyCppyy': - apipath_extra = os.path.dirname(apipath_extra) - except KeyError: - apipath_extra = None - - if apipath_extra is None: - try: - import pkg_resources as pr - - d = pr.get_distribution('CPyCppyy') - for line in d.get_metadata_lines('RECORD'): - if 'API.h' in line: - part = line[0:line.find(',')] - - ape = os.path.join(d.location, part) - if os.path.exists(ape): - apipath_extra = os.path.dirname(os.path.dirname(ape)) - - del part, d, pr - except Exception: - pass - - if apipath_extra is None: - # for the monorepo: headers are at cppjit/cppyy_backend/include/ - _cppjit_inc = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'cppyy_backend', 'include') - if os.path.exists(os.path.join(_cppjit_inc, 'CPyCppyy')): - apipath_extra = _cppjit_inc - del _cppjit_inc - - if apipath_extra is None: - ldversion = sysconfig.get_config_var('LDVERSION') - if not ldversion: ldversion = sys.version[:3] - - apipath_extra = os.path.join(os.path.dirname(apipath), 'site', 'python'+ldversion) - if not os.path.exists(os.path.join(apipath_extra, 'CPyCppyy')): - import glob - from .. import libcppyy - ape = os.path.dirname(libcppyy.__file__) - # a "normal" structure finds the include directory up to 3 levels up, - # ie. dropping lib/pythonx.y[md]/site-packages - for i in range(3): - if os.path.exists(os.path.join(ape, 'include')): - break - ape = os.path.dirname(ape) - - ape = os.path.join(ape, 'include') - if os.path.exists(os.path.join(ape, 'CPyCppyy')): - apipath_extra = ape - else: - # add back pythonx.y or site/pythonx.y if present - for p in glob.glob(os.path.join(ape, 'python'+sys.version[:3]+'*'))+\ - glob.glob(os.path.join(ape, '*', 'python'+sys.version[:3]+'*')): - if os.path.exists(os.path.join(p, 'CPyCppyy')): - apipath_extra = p - break - - if apipath_extra.lower() != 'none': - if not os.path.exists(os.path.join(apipath_extra, 'CPyCppyy')): - warnings.warn("CPyCppyy API not found (tried: %s); set CPPYY_API_PATH envar to the 'CPyCppyy' API directory to fix" % apipath_extra) - else: - add_include_path(apipath_extra) - - del apipath_extra - -if os.getenv('CONDA_PREFIX'): - # MacOS, Linux - include_path = os.path.join(os.getenv('CONDA_PREFIX'), 'include') - if os.path.exists(include_path): add_include_path(include_path) - - # Windows - include_path = os.path.join(os.getenv('CONDA_PREFIX'), 'Library', 'include') - if os.path.exists(include_path): add_include_path(include_path) - -# assuming that we are in PREFIX/lib/python/site-packages/cppyy, add PREFIX/include to the search path -include_path = os.path.abspath(os.path.join(os.path.dirname(__file__), *(4*[os.path.pardir]+['include']))) -if os.path.exists(include_path): add_include_path(include_path) - -del include_path, apipath, ispypy - -def add_autoload_map(fname): - """Add the entries from a autoload (.rootmap) file to Cling.""" - if not os.path.isfile(fname): - raise OSError("no such file: %s" % fname) - gbl.cling.runtime.gCling.LoadLibraryMap(fname) - -def set_debug(enable=True): - """Enable/disable debug output.""" - gbl.Cpp.EnableDebugOutput(enable) - -def _get_name(tt): - if isinstance(tt, str): - return tt - try: - ttname = tt.__cpp_name__ - except AttributeError: - ttname = tt.__name__ - return ttname - -_sizes = {} -def sizeof(tt): - """Returns the storage size (in chars) of C++ type .""" - if not isinstance(tt, type) and not isinstance(tt, str): - tt = type(tt) - try: - return _sizes[tt] - except KeyError: - try: - sz = ctypes.sizeof(tt) - except TypeError: - # Route through evaluate() so the Box-returning Cpp::Evaluate - # is unboxed in one place (see the shim above). Cpp::SizeOf - # would be faster but its sibling Cpp::GetNamed does not - # traverse `Foo::Bar`-style qualified names, so handing it a - # nested `tt.__cpp_name__` resolves to a null scope and the - # subsequent SizeOf hangs/aborts. Stick with the legacy - # interpreter round-trip until we add a qualified-name - # resolver (or an overload of SizeOf that takes a name). - sz = evaluate("sizeof(%s)" % (_get_name(tt),)) - #scope = gbl.Cpp.GetNamed(_get_name(tt)) - #sz = gbl.Cpp.SizeOf(scope) - _sizes[tt] = sz - return sz - -_typeids = {} -def typeid(tt): - """Returns the C++ runtime type information for type .""" - if not isinstance(tt, type): - tt = type(tt) - try: - return _typeids[tt] - except KeyError: - tidname = 'typeid_'+str(len(_typeids)) - cppexec( - "namespace _cppyy_internal { auto* %s = &typeid(%s); }" %\ - (tidname, _get_name(tt),)) - tid = getattr(gbl._cppyy_internal, tidname) - _typeids[tt] = tid - return tid - -def multi(*bases): # after six, see also _typemap.py - """Resolve metaclasses for multiple inheritance.""" - # contruct a "no conflict" meta class; the '_meta' is needed by convention - nc_meta = type.__new__(type, 'cppyy_nc_meta', tuple(type(b) for b in bases if type(b) is not type), {}) - class faux_meta(type): - def __new__(mcs, name, this_bases, d): - return nc_meta(name, bases, d) - return type.__new__(faux_meta, 'faux_meta', (), {}) - - -#- workaround (TODO: may not be needed with Clang9) -------------------------- -if 'win32' in sys.platform: - cppdef("""template<> - std::basic_ostream>& __cdecl std::endl>( - std::basic_ostream>&);""") diff --git a/python/cppjit/cppyy/__pyinstaller/__init__.py b/python/cppjit/cppyy/__pyinstaller/__init__.py deleted file mode 100644 index 84cb94e..0000000 --- a/python/cppjit/cppyy/__pyinstaller/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -__all__ = ['get_hook_dirs'] - -def get_hook_dirs(): - return [os.path.dirname(__file__)] diff --git a/python/cppjit/cppyy/__pyinstaller/hook-cppyy.py b/python/cppjit/cppyy/__pyinstaller/hook-cppyy.py deleted file mode 100644 index e7560ef..0000000 --- a/python/cppjit/cppyy/__pyinstaller/hook-cppyy.py +++ /dev/null @@ -1,37 +0,0 @@ -# PyInstaller hooks to declare the "data files" (libraries, headers, etc.) of -# cppyy-backend. Placed here rather then in cppyy-backend to guarantee that any -# packaging of top-level "cppyy" picks up the backend as well. -# -# See also setup.cfg. - -__all__ = ['data'] - - -def _backend_files(): - from cppjit import cppyy_backend - import glob, os - - all_files = glob.glob(os.path.join( - os.path.dirname(cppyy_backend.__file__), '*')) - - def datafile(path): - return path, os.path.join('cppyy_backend', os.path.basename(path)) - - return [datafile(filename) for filename in all_files if os.path.isdir(filename)] - -def _api_files(): - from cppjit import cppyy - import os - - # FIXME: We should add an interface in InterOp. - paths = str(cppyy.gbl.runtime.gCling.GetIncludePath()).split('-I') - for p in paths: - if not p: continue - - apipath = os.path.join(p.strip()[1:-1], 'CPyCppyy') - if os.path.exists(apipath): - return [(apipath, os.path.join('include', 'CPyCppyy'))] - - return [] - -datas = _backend_files()+_api_files() diff --git a/python/cppjit/cppyy/_cpython_cppyy.py b/python/cppjit/cppyy/_cpython_cppyy.py deleted file mode 100644 index 543cda5..0000000 --- a/python/cppjit/cppyy/_cpython_cppyy.py +++ /dev/null @@ -1,222 +0,0 @@ -""" CPython-specific touch-ups -""" - -import ctypes -import sys - -from . import _stdcpp_fix -from ..cppyy_backend import loader - -__all__ = [ - 'gbl', - 'load_reflection_info', - 'addressof', - 'bind_object', - 'nullptr', - 'default', - '_backend', - '_begin_capture_stderr', - '_end_capture_stderr' - ] - -# first load the dependency libraries of the backend, then pull in the -# libcppyy extension module -# c = loader.load_cpp_backend() -from .. import libcppyy as _backend -# _backend._cpp_backend = c - -# explicitly expose APIs from libcppyy -_w = ctypes.CDLL(_backend.__file__, ctypes.RTLD_GLOBAL) - - -# some beautification for inspect (only on p2) -if sys.hexversion < 0x3000000: - # TODO: this reliese on CPPOverload cooking up a func_code object, which atm - # is simply not implemented for p3 :/ - - # convince inspect that cppyy method proxies are possible drop-ins for python - # methods and classes for pydoc - import inspect - - inspect._old_isfunction = inspect.isfunction - def isfunction(object): - if isinstance(object, _backend.CPPOverload) and not object.im_class: - return True - return inspect._old_isfunction(object) - inspect.isfunction = isfunction - - inspect._old_ismethod = inspect.ismethod - def ismethod(object): - if isinstance(object, _backend.CPPOverload): - return True - return inspect._old_ismethod(object) - inspect.ismethod = ismethod - del isfunction, ismethod - - -### template support --------------------------------------------------------- -class Template(object): # expected/used by ProxyWrappers.cxx in CPyCppyy - stl_sequence_types = ['std::vector', 'std::list', 'std::set', 'std::deque'] - stl_unrolled_types = ['std::pair'] - stl_fixed_size_types = ['std::array'] - stl_mapping_types = ['std::map', 'std::unordered_map'] - - def __init__(self, name, scope): - self.__name__ = name - self.__cpp_name__ = name - self._instantiations = dict() - self.__scope__ = scope - - def __repr__(self): - return "" % (self.__name__, hex(id(self))) - - def __getitem__(self, *args): - # multi-argument to [] becomes a single tuple argument - if args and isinstance(args[0], tuple): - args = args[0] - - # if already instantiated, return the existing class - try: - return self._instantiations[args] - except KeyError: - pass - - # construct the type name from the types or their string representation - newargs = [self.__scope__] - for arg in args: - if isinstance(arg, str): - arg = ','.join(map(lambda x: x.strip(), arg.split(','))) - newargs.append(arg) - pyclass = _backend.MakeCppTemplateClass(*newargs) - - # memoize the class to prevent spurious lookups/re-pythonizations - self._instantiations[args] = pyclass - - # special case pythonization (builtin_map is not available from the C-API) - if 'push_back' in pyclass.__dict__ and not '__iadd__' in pyclass.__dict__: - if 'reserve' in pyclass.__dict__: - def iadd(self, ll): - self.reserve(len(ll)) - for x in ll: self.push_back(x) - return self - else: - def iadd(self, ll): - for x in ll: self.push_back(x) - return self - pyclass.__iadd__ = iadd - - # back-pointer for reflection - pyclass.__cpp_template__ = self - - return pyclass - - def __call__(self, *args): - # for C++17, we're required to derive the type when using initializer syntax - # (i.e. a tuple or list); not sure how to do that in general, but below the - # most common cases are covered - if args: - args0 = args[0] - if args0 and isinstance(args0, (tuple, list)): - t = type(args0[0]) - if t is float: t = 'double' - - if self.__name__ in self.stl_sequence_types: - return self[t](*args) - if self.__name__ in self.stl_fixed_size_types: - return self[t, len(args0)](*args) - if self.__name__ in self.stl_unrolled_types: - return self[tuple(type(a) for a in args0)](*args0) - - if args0 and isinstance(args0, dict): - if self.__name__ in self.stl_mapping_types: - try: - pair = args0.items().__iter__().__next__() - except AttributeError: - pair = args0.items()[0] - t1 = type(pair[0]) - if t1 is float: t1 = 'double' - t2 = type(pair[1]) - if t2 is float: t2 = 'double' - return self[t1, t2](*args) - - return self.__getitem__(*(type(a) for a in args0))(*args) - - # old 'metaclass-style' template instantiations - return self.__getitem__(*args) - -_backend.Template = Template - - -#- :: and std:: namespaces --------------------------------------------------- -gbl = _backend.CreateScopeProxy('') -gbl.__class__.__repr__ = lambda cls : '' % id(cls) -gbl.std = _backend.CreateScopeProxy('std') -# for move, we want our "pythonized" one, not the C++ template -gbl.std.move = _backend.move -# CppInterOp proxy object to access its API -Cpp = gbl.Cpp - -#- add to the dynamic path as needed ----------------------------------------- -import os -def add_default_paths(): - if os.getenv('CONDA_PREFIX'): - # MacOS, Linux - lib_path = os.path.join(os.getenv('CONDA_PREFIX'), 'lib') - if os.path.exists(lib_path): Cpp.AddSearchPath(lib_path, True, False) - - # Windows - lib_path = os.path.join(os.getenv('CONDA_PREFIX'), 'Library', 'lib') - if os.path.exists(lib_path): Cpp.AddSearchPath(lib_path, True, False) - - # assuming that we are in PREFIX/lib/python/site-packages/cppyy, add PREFIX/lib to the search path - lib_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir, os.path.pardir)) - if os.path.exists(lib_path): Cpp.AddSearchPath(lib_path, True, False) - - try: - with open('/etc/ld.so.conf') as ldconf: - for line in ldconf: - f = line.strip() - if (os.path.exists(f)): - Cpp.AddSearchPath(f, True, False) - except IOError: - pass -add_default_paths() -del add_default_paths - - -#- exports ------------------------------------------------------------------- -addressof = _backend.addressof -bind_object = _backend.bind_object -nullptr = _backend.nullptr -default = _backend.default - -def load_reflection_info(name): -# with _stderr_capture() as err: - #FIXME: Remove the .so and add logic in libcppinterop - name = name + ".so" - result = Cpp.LoadLibrary(name, True) - if name.endswith("Dict.so"): - header = name[:-7] + ".h" - Cpp.Declare('#include "' + header +'"', False) - - if result == False: - raise RuntimeError('Could not load library "%s"' % (name)) - - return True - -def _begin_capture_stderr(): - _backend._begin_capture_stderr() - -def _end_capture_stderr(): - err = _backend._end_capture_stderr() - if err: - try: - return "\n%s" % err - except UnicodeDecodeError as e: - original_error = e - try: - return "\n%s" % err.decode('gbk') # not guaranteed, but common - except UnicodeDecodeError: - pass - return "C++ issued an error message that could not be decoded (%s)" % str(original_error) - return "" diff --git a/python/cppjit/cppyy/_pypy_cppyy.py b/python/cppjit/cppyy/_pypy_cppyy.py deleted file mode 100644 index e6e237e..0000000 --- a/python/cppjit/cppyy/_pypy_cppyy.py +++ /dev/null @@ -1,73 +0,0 @@ -""" PyPy-specific touch-ups -""" - -from . import _stdcpp_fix - -import os, sys -from ..cppyy_backend import loader - -__all__ = [ - 'gbl', - 'addressof', - 'bind_object', - 'nullptr', - ] - -# first load the dependency libraries of the backend, then -# pull in the built-in low-level cppyy -try: - c = loader.load_cpp_backend() -except RuntimeError: - import sysconfig - os.environ['CPPYY_BACKEND_LIBRARY'] = "libcppyy_backend"+sysconfig.get_config_var("SO") - c = loader.load_cpp_backend() -os.environ['CPPYY_BACKEND_LIBRARY'] = c._name - -# some older versions can be fixed up through a compatibility -# module on the python side; load it, if available -try: - import cppyy_compat -except ImportError: - pass - -import _cppyy as _backend # built-in module -try: - _backend._post_import_startup() -except AttributeError: - pass -_backend._cpp_backend = c - -def fixup_legacy(): - version = sys.pypy_version_info - if version[0] < 7 or (version[0] == 7 and version[1] <= 3 and version[2] <= 3): - _backend.gbl.CppyyLegacy = _backend.gbl -fixup_legacy() -del fixup_legacy - - -#- exports ------------------------------------------------------------------- -_thismodule = sys.modules[__name__] -for name in __all__: - try: - setattr(_thismodule, name, getattr(_backend, name)) - except AttributeError: - pass -del name -nullptr = _backend.nullptr - -def load_reflection_info(name): - sc = _backend.gbl.gSystem.Load(name) - if sc == -1: - raise RuntimeError("Unable to load reflection library "+name) - -def _begin_capture_stderr(): - pass - -def _end_capture_stderr(): - return "" - -# add other exports to all -__all__.append('load_reflection_info') -__all__.append('_backend') -__all__.append('_begin_capture_stderr') -__all__.append('_end_capture_stderr') diff --git a/python/cppjit/cppyy/_version.py b/python/cppjit/cppyy/_version.py deleted file mode 100644 index 4eb28e3..0000000 --- a/python/cppjit/cppyy/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '3.0.0' diff --git a/python/cppjit/cppyy/interactive.py b/python/cppjit/cppyy/interactive.py deleted file mode 100644 index ef9893b..0000000 --- a/python/cppjit/cppyy/interactive.py +++ /dev/null @@ -1,37 +0,0 @@ -import sys -try: - import __pypy__ - del __pypy__ - ispypy = True -except ImportError: - ispypy = False - - -#- fake namespace for interactive lazy lookups ------------------------------- -class InteractiveLazy(object): - def __init__(self, hook_okay): - self._hook_okay = hook_okay - - def __getattr__(self, attr): - from cppjit import cppyy - - if attr == '__all__': - # copy all exported items from cppyy itself - for v in cppyy.__all__: - self.__dict__[v] = getattr(cppyy, v) - - # add the lookup hook into cppyy.gbl if legal, or put it under 'g' - # if not (PyPy and IPython for now) - if self._hook_okay: - caller = sys.modules[sys._getframe(1).f_globals['__name__']] - cppyy._backend._set_cpp_lazy_lookup(caller.__dict__) - return cppyy.__all__ - else: - self.__dict__['g'] = cppyy.gbl - self.__dict__['std'] = cppyy.gbl.std - return ['g', 'std']+cppyy.__all__ - return getattr(cppyy, attr) - -sys.modules['cppyy.interactive'] = InteractiveLazy(\ - not ispypy and not (hasattr(__builtins__, '__IPYTHON__') or 'IPython' in sys.modules)) -del InteractiveLazy, ispypy diff --git a/python/cppjit/cppyy/ll.py b/python/cppjit/cppyy/ll.py deleted file mode 100644 index 58612ee..0000000 --- a/python/cppjit/cppyy/ll.py +++ /dev/null @@ -1,160 +0,0 @@ -""" Low-level utilities, to be used for "emergencies only". -""" - -from cppjit import cppyy -import ctypes -import sys -import warnings - -try: - import __pypy__ - del __pypy__ - ispypy = True -except ImportError: - ispypy = False - -__all__ = [ - 'argv', - 'argc', - 'cast', - 'static_cast', - 'reinterpret_cast', - 'dynamic_cast', - 'malloc', - 'free', - 'array_new', - 'array_delete', - 'signals_as_exception', - 'set_signals_as_exception', - 'FatalError', - 'BusError', - 'SegmentationViolation', - 'IllegalInstruction', - 'AbortSignal', - ] - - -# convenience functions to create C-style argv/argc -def argv(): - argc = len(sys.argv) - cargsv = (ctypes.c_char_p * len(sys.argv))(*(x.encode() for x in sys.argv)) - return ctypes.POINTER(ctypes.c_char_p)(cargsv) - -def argc(): - return len(sys.argv) - -# import low-level python converters -for _name in ['addressof', 'as_cobject', 'as_capsule', 'as_ctypes', 'as_memoryview']: - try: - exec('%s = cppyy._backend.%s' % (_name, _name)) - __all__.append(_name) - except AttributeError: - pass -del _name - -# create low-level helpers once -if not hasattr(cppyy.gbl, "__cppyy_internal") or \ - not hasattr(cppyy.gbl.__cppyy_internal, "cppyy_cast"): - cppyy.cppdef("""namespace __cppyy_internal { - // type casting - template - T cppyy_cast(U val) { return (T)val; } - - template - T cppyy_static_cast(U val) { return static_cast(val); } - - template - T cppyy_reinterpret_cast(U val) { return reinterpret_cast(val); } - - template - T* cppyy_dynamic_cast(S* obj) { return dynamic_cast(obj); } - - // memory allocation/free-ing - template - T* cppyy_malloc(size_t count=1) { return (T*)malloc(sizeof(T*)*count); } - - template - T* cppyy_array_new(size_t count) { return new T[count]; } - - template - void cppyy_array_delete(T* ptr) { delete[] ptr; } - }""") - -# helper for sizing arrays -class ArraySizer(object): - def __init__(self, func): - self.func = func - def __getitem__(self, t): - self.array_type = t - return self - def __call__(self, size, managed=False): - res = self.func[self.array_type](size) - try: - res.reshape((size,)+res.shape[1:]) - if managed: res.__python_owns__ = True - except AttributeError: - res.__reshape__((size,)) - if managed: - warnings.warn("managed low-level arrays of instances not supported") - return res - -class CArraySizer(ArraySizer): - def __call__(self, size, managed=False): - res = ArraySizer.__call__(self, size, managed) - res.__cpp_array__ = False - return res - - -# import casting helpers -cast = cppyy.gbl.__cppyy_internal.cppyy_cast -static_cast = cppyy.gbl.__cppyy_internal.cppyy_static_cast -reinterpret_cast = cppyy.gbl.__cppyy_internal.cppyy_reinterpret_cast -dynamic_cast = cppyy.gbl.__cppyy_internal.cppyy_dynamic_cast - -# import memory allocation/free-ing helpers -malloc = CArraySizer(cppyy.gbl.__cppyy_internal.cppyy_malloc) -free = cppyy.gbl.free # for symmetry -array_new = ArraySizer(cppyy.gbl.__cppyy_internal.cppyy_array_new) -array_delete = cppyy.gbl.__cppyy_internal.cppyy_array_delete - -# signals as exceptions -if not ispypy: - FatalError = cppyy._backend.FatalError - BusError = cppyy._backend.BusError - SegmentationViolation = cppyy._backend.SegmentationViolation - IllegalInstruction = cppyy._backend.IllegalInstruction - AbortSignal = cppyy._backend.AbortSignal - - class signals_as_exception: - def __enter__(self): - cppyy._backend.SetGlobalSignalPolicy(1) - - def __exit__(self, type, value, traceback): - cppyy._backend.SetGlobalSignalPolicy(0) - - set_signals_as_exception = cppyy._backend.SetGlobalSignalPolicy - -else: - class FatalError(Exception): - pass - class BusError(FatalError): - pass - class SegmentationViolation(FatalError): - pass - class IllegalInstruction(FatalError): - pass - class AbortSignal(FatalError): - pass - - class signals_as_exception: - def __enter__(self): - pass # not yet implemented - - def __exit__(self, type, value, traceback): - pass # not yet implemented - - def set_signals_as_exception(seton): - return False - -del ispypy - diff --git a/python/cppjit/cppyy/reflex.py b/python/cppjit/cppyy/reflex.py deleted file mode 100644 index c8063b8..0000000 --- a/python/cppjit/cppyy/reflex.py +++ /dev/null @@ -1,28 +0,0 @@ -""" cppyy reflection -""" - -from cppjit import cppyy - -try: - import __pypy__ - __all__ = [ - - ] - -except ImportError: - __all__ = [ - 'RETURN_TYPE', - ] - - cppyy.include("CPyCppyy/Reflex.h") - - IS_NAMESPACE = cppyy.gbl.Cppyy.Reflex.IS_NAMESPACE - IS_AGGREGATE = cppyy.gbl.Cppyy.Reflex.IS_AGGREGATE - - OFFSET = cppyy.gbl.Cppyy.Reflex.OFFSET - RETURN_TYPE = cppyy.gbl.Cppyy.Reflex.RETURN_TYPE - TYPE = cppyy.gbl.Cppyy.Reflex.TYPE - - OPTIMAL = cppyy.gbl.Cppyy.Reflex.OPTIMAL - AS_TYPE = cppyy.gbl.Cppyy.Reflex.AS_TYPE - AS_STRING = cppyy.gbl.Cppyy.Reflex.AS_STRING diff --git a/python/cppjit/cppyy/types.py b/python/cppjit/cppyy/types.py deleted file mode 100644 index 5055cf6..0000000 --- a/python/cppjit/cppyy/types.py +++ /dev/null @@ -1,35 +0,0 @@ -""" C++ proxy types. -""" - -from cppjit import cppyy - -bck = cppyy._backend -Instance = bck.CPPInstance - -try: - import __pypy__ - __all__ = [ - 'Instance' - ] - -except ImportError: - __all__ = [ - 'DataMember', - 'Instance', - 'Function', - 'Method' - 'Scope', - 'InstanceArray', - 'LowLevelView', - 'Template' - ] - - DataMember = bck.CPPDataMember - Function = bck.CPPOverload - Method = bck.CPPOverload - Scope = bck.CPPScope - InstanceArray = bck.InstanceArray - LowLevelView = bck.LowLevelView - Template = bck.TemplateProxy - -del bck diff --git a/python/cppjit/cppyy_backend/__init__.py b/python/cppjit/cppyy_backend/__init__.py deleted file mode 100644 index 90c0d6c..0000000 --- a/python/cppjit/cppyy_backend/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ._version import __version__ - -from . import loader diff --git a/python/cppjit/cppyy_backend/_cling_config.py b/python/cppjit/cppyy_backend/_cling_config.py deleted file mode 100755 index d226d75..0000000 --- a/python/cppjit/cppyy_backend/_cling_config.py +++ /dev/null @@ -1,100 +0,0 @@ -from __future__ import print_function -import os, sys, subprocess - -MYHOME = os.path.dirname(__file__) - -def main(): - if len(sys.argv) == 2: - options = sys.argv[1] - - if options == '--cmake': - print(os.path.join(MYHOME, "cmake")) - return 0 - - if options == '--cppflags': - options = '--cflags' - - if options != '--help': - rcfg = os.path.join(MYHOME, 'bin', 'root-config') - try: - cli_arg = subprocess.check_output( - [os.path.join(MYHOME, 'bin', 'root-config'), options], - stderr=subprocess.STDOUT) - out = cli_arg.decode("utf-8").strip() - if 'flags' in options and 'STDCXX' in os.environ and '-std=' in out: - req = os.environ['STDCXX'] - true_flags = None - if req == '17': - true_flags = '-std=c++1z' - elif req == '20': - true_flags = '-std=c++2a' - else: - true_flags = '-std=c++'+req - if true_flags: - pos = out.find('std=') - out = out[:pos] + true_flags + out[pos+9:] - print(out) - return 0 - except OSError: - if not os.path.exists(rcfg) or not 'win32' in sys.platform: - raise - - # happens on Windows b/c root-config is a bash script; the - # following covers the most important options until that - # gets fixed upstream - - def get_include_dir(): - return os.path.join(MYHOME, 'include') - - def get_library_dir(): - return os.path.join(MYHOME, 'lib') - - def get_basic_cppflags(): - flags = '-Zc:__cplusplus ' - if 'STDCXX' in os.environ: - return flags + '/std:c++'+os.environ['STDCXX'] - else: - for line in open(rcfg): - if 'cxxversion' in line: - if '11' in line: - return flags+'/std:c++11' - elif '14' in line: - return flags+'/std:c++14' - elif '17' in line: - return flags+'/std:c++17' - else: - # includes C++20, which won't get it's own flag until - # it is feature-complete - return flags+'/std:c++latest' - raise - - if options == '--incdir': - print(get_include_dir()) - return 0 - - elif options == '--libdir': - print(get_library_dir()) - return 0 - - elif options == '--auxcflags': - # most important is get the C++ version flag right - print(get_basic_cppflags()) - return 0 - - elif options == '--cflags': - # most important are C++ flag and include directory - print(get_basic_cppflags(), '/I'+get_include_dir(), '/FIw32pragma.h') - return 0 - - elif options == '--ldflags': - print('/LIBPATH:'+get_library_dir(), 'libCoreLegacy.lib', 'libRIOLegacy.lib') - return 0 - - except subprocess.CalledProcessError: - pass - - print('Usage: cling-config [--cflags] [--cppflags] [--cmake]') - return 1 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/python/cppjit/cppyy_backend/_version.py b/python/cppjit/cppyy_backend/_version.py deleted file mode 100644 index 12486b3..0000000 --- a/python/cppjit/cppyy_backend/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '6.28.0' diff --git a/python/cppjit/cppyy_backend/loader.py b/python/cppjit/cppyy_backend/loader.py deleted file mode 100644 index c8f79d9..0000000 --- a/python/cppjit/cppyy_backend/loader.py +++ /dev/null @@ -1,241 +0,0 @@ -from __future__ import print_function -""" cppyy_backend loader -""" - -__all__ = [ - 'load_cpp_backend', # load libcppyy_backend - 'set_cling_compile_options', # set EXTRA_CLING_ARGS envar - 'ensure_precompiled_header' # build precompiled header as necessary -] - -import ctypes -import os -import platform -import re -import subprocess -import sys -import sysconfig -import warnings - -if 'win32' in sys.platform: - soext = '.dll' -elif 'linux' in sys.platform: - soext = '.so' -else: - soext = '.dylib' - -soabi = sysconfig.get_config_var("SOABI") -soext2 = sysconfig.get_config_var("EXT_SUFFIX") -if not soext2: - soext2 = sysconfig.get_config_var("SO") - -# Load the dl library (on Unix-like systems) -dl = ctypes.CDLL(None) - -# Function to check if a shared object is already loaded -def is_shared_object_loaded(lib_path): - try: - # Check if somebody else has dlopen-ed libclangCppInterOp. - dl.CreateInterpreter - return True - except: - return False # Library was not loaded. - -def _load_helper(bkname): - errors = set() - - # normal load, allowing for user overrides of LD_LIBRARY_PATH - try: - return ctypes.CDLL(bkname, ctypes.RTLD_GLOBAL), errors - except OSError as e: - errors.add(str(e)) - - # failed ... load dependencies explicitly - try: - pkgpath = os.path.dirname(bkname) - if not pkgpath: - pkgpath = os.path.dirname(__file__) - elif os.path.basename(pkgpath) in ['lib', 'bin']: - pkgpath = os.path.dirname(pkgpath) - return ctypes.CDLL(os.path.join(pkgpath, 'lib', bkname), ctypes.RTLD_GLOBAL), errors - except OSError as e: - errors.add(str(e)) - - return None, errors - - -_precompiled_header_ensured = False -def load_cpp_backend(): - set_cling_compile_options() - - if not _precompiled_header_ensured: - # the precompiled header of standard and system headers is not part of the - # distribution as there are too many varieties; create it now if needed - ensure_precompiled_header() - - names = list() - try: - bkname = os.environ['CPPYY_BACKEND_LIBRARY'] - if bkname.rfind(soext) < 0: - bkname += soext - names.append(bkname) - except KeyError: - names.append('libcppyy-backend'+soext) - if soabi: - names.append('libcppyy-backend.'+soabi+soext) - if soext2: - names.append('libcppyy-backend'+soext2) - - err = set() - for name in names: - c, err2 = _load_helper(name) - if c: - break - err = err.union(err2) - - if not c: - raise RuntimeError("could not load cppyy_backend library, details:\n%s" % - '\n'.join([' '+x for x in err])) - - return c - - -def set_cling_compile_options(add_defaults = False): - # extra optimization flags for Cling - if not 'EXTRA_CLING_ARGS' in os.environ: - CURRENT_ARGS = '' - add_defaults = True - else: - CURRENT_ARGS = os.environ['EXTRA_CLING_ARGS'] - - enable_cuda = os.environ.get('CLING_ENABLE_CUDA', '0') - if enable_cuda != '0' and enable_cuda.lower() != 'false': - try: - cuda_version = subprocess.check_output(['nvcc', '--version']).decode("utf-8") - cuda_version = re.search(r"release (\d+\.\d+)", cuda_version).groups()[0] - if float(cuda_version) <= 10.2: - CURRENT_ARGS += ' -x cuda -D__CUDA__' - else: - warnings.warn("CUDA version %s not supported" % cuda_version) - except Exception as e: - warnings.warn("CUDA requested, but no nvcc found") - - if add_defaults: - # M1 does not support -march=native until LLVM 15 - if sys.platform != sys.platform or not 'arm64' in platform.machine(): - CURRENT_ARGS += ' -O2 -march=native' - else: - CURRENT_ARGS += ' -O2' - - # py2.7 uses the register storage class, which is no longer allowed with C++17 - if sys.hexversion < 0x3000000: - CURRENT_ARGS += ' -Wno-register' - - os.putenv('EXTRA_CLING_ARGS', CURRENT_ARGS) - os.environ['EXTRA_CLING_ARGS'] = CURRENT_ARGS - - -def _disable_pch(): - os.putenv('CLING_STANDARD_PCH', 'none') - -def _warn_no_pch(msg, pchname=None): - if pchname is None or not os.path.exists(pchname): - _disable_pch() - warnings.warn('No precompiled header available (%s); this may impact performance.' % msg) - else: - warnings.warn('Precompiled header may be out of date (%s).' % msg) - -def _is_uptodate(pchname, incpath): - # check for forced rebuild - force_rebuild = os.environ.get('CLING_REBUILD_PCH', '') - if force_rebuild == '1' or force_rebuild.lower() == 'true': - return False - - # it's unlikely there's a C++ compiler available when distributed as a - # frozen bundle, so accept the PCH if any exists - if getattr(sys, 'frozen', False): - # okay if PCH is available, no point to rebuild if include dir is not - return os.path.exists(pchname) or not os.path.exists(incpath) - - # test whether the pch is older than the include directory as a crude way - # to find if it's up to date - try: - return os.stat(pchname).st_mtime >= os.stat(incpath).st_mtime - except Exception: - if not os.path.exists(incpath): - return True # no point in updating as it will fail - return False - -def ensure_precompiled_header(pchdir = '', pchname = ''): - # the precompiled header of standard and system headers is not part of the - # distribution as there are too many varieties; create it now if needed - global _precompiled_header_ensured - _precompiled_header_ensured = True # only ever call once, even if failed - - olddir = os.getcwd() - try: - pkgpath = os.path.abspath(os.path.dirname(__file__)) - cling_args = os.environ.get('EXTRA_CLING_ARGS', '') - if 'CLING_STANDARD_PCH' in os.environ: - pchname = os.environ['CLING_STANDARD_PCH'] - if pchname.lower() == 'none': # magic keyword to disable pch - _disable_pch() - os.chdir(olddir) - return # quiet - pchdir = os.path.dirname(pchname) - else: - if not pchdir: - pchdir = os.path.join(pkgpath, 'etc') - if not pchname: - pchname = 'allDict.cxx.pch.' - if 'native' in cling_args: pchname += 'native.' - if 'openmp' in cling_args: pchname += 'omp.' - if 'cuda' in cling_args: pchname += 'cuda.' - from ._version import __version__ - pchname += str(__version__) - pchname = os.path.join(pchdir, pchname) - os.environ['CLING_STANDARD_PCH'] = pchname - - specialize = [('', '')] - if 'cuda' in cling_args: - cuda_path = os.environ.get('CLING_CUDA_PATH', '') - if cuda_path: cuda_path = ' --cuda-path='+cuda_path - cuda_arch = os.environ.get('CLING_CUDA_ARCH', '') - if cuda_arch: cuda_arch = ' --cuda-gpu-arch='+cuda_arch - specialize = [('', ' --cuda-host-only'), - ('.device', ' --cuda-device-only' + cuda_path + cuda_arch)] - - os.chdir(pkgpath) - incpath = os.path.join(pkgpath, 'include') - - for ext, ext_flags in specialize: - pchname1 = pchname+ext - pch_exists = os.path.exists(pchname1) - if not pch_exists or not _is_uptodate(pchname1, incpath): - if os.access(pchdir, os.R_OK|os.W_OK): - if ext_flags: - eca_old1 = eca_old = os.environ.get('EXTRA_CLING_ARGS', '') - if 'device' in ext_flags: # TODO: find cleaner way - eca_old1 = eca_old1.replace(' -march=native', '') - os.environ['EXTRA_CLING_ARGS'] = eca_old1 + ext_flags - print('(Re-)building pre-compiled headers (options:%s); this may take a minute ...' % os.environ.get('EXTRA_CLING_ARGS', ' none')) - makepch = os.path.join(pkgpath, 'etc', 'dictpch', 'makepch.py') - pyexe = sys.executable - if getattr(sys, 'frozen', False) or not ('python' in pyexe.lower() or 'pypy' in pyexe.lower()): - # either frozen, or a high chance of being embedded; and the actual version - # of python used doesn't matter per se, as long as it is functional - pyexe = 'python' - if subprocess.call([pyexe, makepch, pchname1, '-I'+incpath]) != 0: - _warn_no_pch('failed to build', pchname1) - if ext_flags: - os.environ['EXTRA_CLING_ARGS'] = eca_old - elif not pch_exists: - # accept that the file may be out of date; since the location is not writable, - # the most likely cause is that it is managed by some packager, which in that - # case is responsible for the PCH, so only warn if it doesn't exist - _warn_no_pch('%s not writable, set CLING_STANDARD_PCH' % pchdir, pchname1) - - except Exception as e: - _warn_no_pch(str(e)) - finally: - os.chdir(olddir) diff --git a/python/cppjit/interactive.py b/python/cppjit/interactive.py new file mode 100644 index 0000000..426d8b6 --- /dev/null +++ b/python/cppjit/interactive.py @@ -0,0 +1,42 @@ +import sys + +try: + import __pypy__ + + del __pypy__ + ispypy = True +except ImportError: + ispypy = False + + +# - fake namespace for interactive lazy lookups ------------------------------- +class InteractiveLazy(object): + def __init__(self, hook_okay): + self._hook_okay = hook_okay + + def __getattr__(self, attr): + import cppjit + + if attr == "__all__": + # copy all exported items from cppjit itself + for v in cppjit.__all__: + self.__dict__[v] = getattr(cppjit, v) + + # add the lookup hook into cppjit.gbl if legal, or put it under 'g' + # if not (PyPy and IPython for now) + if self._hook_okay: + caller = sys.modules[sys._getframe(1).f_globals["__name__"]] + cppjit._backend._set_cpp_lazy_lookup(caller.__dict__) + return cppjit.__all__ + else: + self.__dict__["g"] = cppjit.gbl + self.__dict__["std"] = cppjit.gbl.std + return ["g", "std"] + cppjit.__all__ + return getattr(cppjit, attr) + + +sys.modules["cppjit.interactive"] = InteractiveLazy( + not ispypy + and not (hasattr(__builtins__, "__IPYTHON__") or "IPython" in sys.modules) +) +del InteractiveLazy, ispypy diff --git a/python/cppjit/ll.py b/python/cppjit/ll.py new file mode 100644 index 0000000..9621e9f --- /dev/null +++ b/python/cppjit/ll.py @@ -0,0 +1,174 @@ +"""Low-level utilities, to be used for "emergencies only".""" + +import ctypes +import sys +import warnings + +import cppjit + +try: + import __pypy__ + + del __pypy__ + ispypy = True +except ImportError: + ispypy = False + +__all__ = [ + "argv", + "argc", + "cast", + "static_cast", + "reinterpret_cast", + "dynamic_cast", + "malloc", + "free", + "array_new", + "array_delete", + "signals_as_exception", + "set_signals_as_exception", + "FatalError", + "BusError", + "SegmentationViolation", + "IllegalInstruction", + "AbortSignal", +] + + +# convenience functions to create C-style argv/argc +def argv(): + argc = len(sys.argv) # noqa: F841 + cargsv = (ctypes.c_char_p * len(sys.argv))(*(x.encode() for x in sys.argv)) + return ctypes.POINTER(ctypes.c_char_p)(cargsv) + + +def argc(): + return len(sys.argv) + + +# import low-level python converters +for _name in ["addressof", "as_cobject", "as_capsule", "as_ctypes", "as_memoryview"]: + try: + exec("%s = cppjit._backend.%s" % (_name, _name)) + __all__.append(_name) + except AttributeError: + pass +del _name + +# create low-level helpers once +if not hasattr(cppjit.gbl, "__cppjit_internal") or not hasattr( + cppjit.gbl.__cppjit_internal, "cppjit_cast" +): + cppjit.cppdef("""namespace __cppjit_internal { + // type casting + template + T cppjit_cast(U val) { return (T)val; } + + template + T cppjit_static_cast(U val) { return static_cast(val); } + + template + T cppjit_reinterpret_cast(U val) { return reinterpret_cast(val); } + + template + T* cppjit_dynamic_cast(S* obj) { return dynamic_cast(obj); } + + // memory allocation/free-ing + template + T* cppjit_malloc(size_t count=1) { return (T*)malloc(sizeof(T*)*count); } + + template + T* cppjit_array_new(size_t count) { return new T[count]; } + + template + void cppjit_array_delete(T* ptr) { delete[] ptr; } + }""") + + +# helper for sizing arrays +class ArraySizer(object): + def __init__(self, func): + self.func = func + + def __getitem__(self, t): + self.array_type = t + return self + + def __call__(self, size, managed=False): + res = self.func[self.array_type](size) + try: + res.reshape((size,) + res.shape[1:]) + if managed: + res.__python_owns__ = True + except AttributeError: + res.__reshape__((size,)) + if managed: + warnings.warn("managed low-level arrays of instances not supported") + return res + + +class CArraySizer(ArraySizer): + def __call__(self, size, managed=False): + res = ArraySizer.__call__(self, size, managed) + res.__cpp_array__ = False + return res + + +# import casting helpers +cast = cppjit.gbl.__cppjit_internal.cppjit_cast +static_cast = cppjit.gbl.__cppjit_internal.cppjit_static_cast +reinterpret_cast = cppjit.gbl.__cppjit_internal.cppjit_reinterpret_cast +dynamic_cast = cppjit.gbl.__cppjit_internal.cppjit_dynamic_cast + +# import memory allocation/free-ing helpers +malloc = CArraySizer(cppjit.gbl.__cppjit_internal.cppjit_malloc) +free = cppjit.gbl.free # for symmetry +array_new = ArraySizer(cppjit.gbl.__cppjit_internal.cppjit_array_new) +array_delete = cppjit.gbl.__cppjit_internal.cppjit_array_delete + +# signals as exceptions +if not ispypy: + FatalError = cppjit._backend.FatalError + BusError = cppjit._backend.BusError + SegmentationViolation = cppjit._backend.SegmentationViolation + IllegalInstruction = cppjit._backend.IllegalInstruction + AbortSignal = cppjit._backend.AbortSignal + + class signals_as_exception: + def __enter__(self): + cppjit._backend.SetGlobalSignalPolicy(1) + + def __exit__(self, type, value, traceback): + cppjit._backend.SetGlobalSignalPolicy(0) + + set_signals_as_exception = cppjit._backend.SetGlobalSignalPolicy + +else: + + class FatalError(Exception): + pass + + class BusError(FatalError): + pass + + class SegmentationViolation(FatalError): + pass + + class IllegalInstruction(FatalError): + pass + + class AbortSignal(FatalError): + pass + + class signals_as_exception: + def __enter__(self): + pass # not yet implemented + + def __exit__(self, type, value, traceback): + pass # not yet implemented + + def set_signals_as_exception(seton): + return False + + +del ispypy diff --git a/python/cppjit/cppyy/numba_ext.py b/python/cppjit/numba_ext.py similarity index 67% rename from python/cppjit/cppyy/numba_ext.py rename to python/cppjit/numba_ext.py index e5d5f9a..0f4a556 100644 --- a/python/cppjit/cppyy/numba_ext.py +++ b/python/cppjit/numba_ext.py @@ -1,23 +1,22 @@ -""" cppyy extensions for numba -""" +"""cppjit extensions for numba""" -from cppjit import cppyy -from cppjit.cppyy import types as cpp_types -from cppjit.cppyy import reflex as cpp_refl +import re import numba -import numba.extending as nb_ext import numba.core.cgutils as nb_cgu import numba.core.datamodel as nb_dm import numba.core.imputils as nb_iutils import numba.core.registry as nb_reg -import numba.core.typing.templates as nb_tmpl import numba.core.types as nb_types import numba.core.typing as nb_typing - +import numba.core.typing.templates as nb_tmpl +import numba.extending as nb_ext from llvmlite import ir -from numba.extending import make_attribute_wrapper -import re + +import cppjit +import cppjit.reflex as cpp_refl +import cppjit.types as cpp_types + # setuptools entry point for Numba def _init_extension(): @@ -25,49 +24,53 @@ def _init_extension(): class Qualified: - default = 0 - value = 1 - instance = 2 + default = 0 + value = 1 + instance = 2 + -ir_byte = ir.IntType(8) -ir_voidptr = ir.PointerType(ir_byte) # by convention -ir_byteptr = ir_voidptr # for clarity -ir_intptr_t = ir.IntType(cppyy.sizeof('void*')*8) +ir_byte = ir.IntType(8) +ir_voidptr = ir.PointerType(ir_byte) # by convention +ir_byteptr = ir_voidptr # for clarity +ir_intptr_t = ir.IntType(cppjit.sizeof("void*") * 8) # special case access to unboxing/boxing APIs -cppyy_as_voidptr = cppyy.addressof('Instance_AsVoidPtr') -cppyy_from_voidptr = cppyy.addressof('Instance_FromVoidPtr') +cppjit_as_voidptr = cppjit.addressof("Instance_AsVoidPtr") +cppjit_from_voidptr = cppjit.addressof("Instance_FromVoidPtr") _cpp2numba = { - 'void' : nb_types.void, - 'void*' : nb_types.voidptr, - 'int8_t' : nb_types.int8, - 'uint8_t' : nb_types.uint8, - 'short' : nb_types.short, - 'unsigned short' : nb_types.ushort, - 'int' : nb_types.intc, - 'unsigned int' : nb_types.uintc, - 'int32_t' : nb_types.int32, - 'uint32_t' : nb_types.uint32, - 'int64_t' : nb_types.int64, - 'uint64_t' : nb_types.uint64, - 'long' : nb_types.long_, - 'unsigned long' : nb_types.ulong, - 'long long' : nb_types.longlong, - 'unsigned long long' : nb_types.ulonglong, - 'float' : nb_types.float32, - 'double' : nb_types.float64, - 'char' : nb_types.char, - 'unsigned char' : nb_types.uchar, - 'char*' : nb_types.unicode_type + "void": nb_types.void, + "void*": nb_types.voidptr, + "int8_t": nb_types.int8, + "uint8_t": nb_types.uint8, + "short": nb_types.short, + "unsigned short": nb_types.ushort, + "int": nb_types.intc, + "unsigned int": nb_types.uintc, + "int32_t": nb_types.int32, + "uint32_t": nb_types.uint32, + "int64_t": nb_types.int64, + "uint64_t": nb_types.uint64, + "long": nb_types.long_, + "unsigned long": nb_types.ulong, + "long long": nb_types.longlong, + "unsigned long long": nb_types.ulonglong, + "float": nb_types.float32, + "double": nb_types.float64, + "char": nb_types.char, + "unsigned char": nb_types.uchar, + "char*": nb_types.unicode_type, } + def resolve_std_vector(val): - return re.match(r'std::vector<(.+?)>', val).group(1) + return re.match(r"std::vector<(.+?)>", val).group(1) + def resolve_const_types(val): - return re.match(r'const\s+(.+)\s*\*', val).group(1) + return re.match(r"const\s+(.+)\s*\*", val).group(1) + def cpp2numba(val): if not isinstance(val, str): @@ -78,24 +81,26 @@ def cpp2numba(val): elif val.startswith("std::vector"): type_arr = getattr(numba, str(cpp2numba(resolve_std_vector(val))))[:] return type_arr - elif val[-1] == '*' or val[-1] == '&': - if val.startswith('const'): + elif val[-1] == "*" or val[-1] == "&": + if val.startswith("const"): return nb_types.CPointer(cpp2numba(resolve_const_types(val))) return nb_types.CPointer(_cpp2numba[val[:-2]]) return _cpp2numba[val] + _numba2cpp = dict() for key, value in _cpp2numba.items(): _numba2cpp[value] = key # prefer "int" in the case of intc over "int32_t" -_numba2cpp[nb_types.intc] = 'int' +_numba2cpp[nb_types.intc] = "int" + def numba2cpp(val): - if hasattr(val, 'literal_type'): + if hasattr(val, "literal_type"): val = val.literal_type - if val == nb_types.int64: # Python int + if val == nb_types.int64: # Python int # TODO: this is only necessary until "best matching" is in place - val = nb_types.intc # more likely match candidate + val = nb_types.intc # more likely match candidate elif isinstance(val, numba.types.CPointer): return _numba2cpp[val.dtype] elif isinstance(val, numba.types.RawPointer): @@ -110,6 +115,7 @@ def numba2cpp(val): except: raise RuntimeError("Type mapping failed from Numba to C++ for ", val) + def numba_arg_convertor(args): args_cpp = [] for arg in list(args): @@ -124,34 +130,37 @@ def numba_arg_convertor(args): args_cpp.append(numba2cpp(arg)) return tuple(args_cpp) + def to_ref(type_list): ref_list = [] - for l in type_list: - ref_list.append(l + ' &') + for l in type_list: # noqa: E741 + ref_list.append(l + " &") return ref_list + # TODO: looks like Numba treats unsigned types as signed when lowering, # which seems to work as they're just reinterpret_casts _cpp2ir = { - 'char *' : ir_byteptr, - 'int8_t' : ir.IntType(8), - 'uint8_t' : ir.IntType(8), - 'short' : ir.IntType(nb_types.short.bitwidth), - 'unsigned short' : ir.IntType(nb_types.ushort.bitwidth), - 'int' : ir.IntType(nb_types.intc.bitwidth), - 'unsigned int' : ir.IntType(nb_types.uintc.bitwidth), - 'int32_t' : ir.IntType(32), - 'uint32_t' : ir.IntType(32), - 'int64_t' : ir.IntType(64), - 'uint64_t' : ir.IntType(64), - 'long' : ir.IntType(nb_types.long_.bitwidth), - 'unsigned long' : ir.IntType(nb_types.ulong.bitwidth), - 'long long' : ir.IntType(nb_types.longlong.bitwidth), - 'unsigned long long' : ir.IntType(nb_types.ulonglong.bitwidth), - 'float' : ir.FloatType(), - 'double' : ir.DoubleType(), + "char *": ir_byteptr, + "int8_t": ir.IntType(8), + "uint8_t": ir.IntType(8), + "short": ir.IntType(nb_types.short.bitwidth), + "unsigned short": ir.IntType(nb_types.ushort.bitwidth), + "int": ir.IntType(nb_types.intc.bitwidth), + "unsigned int": ir.IntType(nb_types.uintc.bitwidth), + "int32_t": ir.IntType(32), + "uint32_t": ir.IntType(32), + "int64_t": ir.IntType(64), + "uint64_t": ir.IntType(64), + "long": ir.IntType(nb_types.long_.bitwidth), + "unsigned long": ir.IntType(nb_types.ulong.bitwidth), + "long long": ir.IntType(nb_types.longlong.bitwidth), + "unsigned long long": ir.IntType(nb_types.ulonglong.bitwidth), + "float": ir.FloatType(), + "double": ir.DoubleType(), } + def cpp2ir(val): try: return _cpp2ir[val] @@ -161,11 +170,12 @@ def cpp2ir(val): type_arr = ir.VectorType(cpp2ir(resolve_std_vector(val)), 3) return type_arr elif val != "char *" and val[-1] == "*": - if val.startswith('const'): + if val.startswith("const"): return ir.PointerType(cpp2ir(resolve_const_types(val))) type_2 = _cpp2ir[val[:-2]] return ir.PointerType(type_2) + # # C++ function pointer -> Numba # @@ -174,7 +184,7 @@ class CppFunctionNumbaType(nb_types.Callable): requires_gil = False def __init__(self, func, is_method=False): - super(CppFunctionNumbaType, self).__init__('CppFunction(%s)' % str(func)) + super(CppFunctionNumbaType, self).__init__("CppFunction(%s)" % str(func)) self.sig = None self._func = func @@ -186,7 +196,7 @@ def __init__(self, func, is_method=False): self.ret_type = None def is_precise(self): - return True # by definition + return True # by definition def get_call_type(self, context, args, kwds): try: @@ -195,7 +205,8 @@ def get_call_type(self, context, args, kwds): pass ol = CppFunctionNumbaType( - self._func.__overload__(numba_arg_convertor(args)), self._is_method) + self._func.__overload__(numba_arg_convertor(args)), self._is_method + ) thistype = None if self._is_method: @@ -203,28 +214,34 @@ def get_call_type(self, context, args, kwds): self.ret_type = cpp2numba(ol._func.__cpp_reflex__(cpp_refl.RETURN_TYPE)) ol.sig = nb_typing.Signature( - return_type=self.ret_type, - args=args, - recvr=thistype) + return_type=self.ret_type, args=args, recvr=thistype + ) extsig = ol.sig if self._is_method: self.ret_type = ol.sig.return_type args = (nb_types.voidptr, *args) extsig = nb_typing.Signature( - return_type=ol.sig.return_type, args=args, recvr=None) + return_type=ol.sig.return_type, args=args, recvr=None + ) self._impl_keys[args] = ol self._arg_set_matched = numba_arg_convertor(args) - @nb_iutils.lower_builtin(ol, *args) - def lower_external_call(context, builder, sig, args, - ty=nb_types.ExternalFunctionPointer(extsig, ol.get_pointer), - pyval=self._func, is_method=self._is_method): + def lower_external_call( + context, + builder, + sig, + args, + ty=nb_types.ExternalFunctionPointer(extsig, ol.get_pointer), + pyval=self._func, + is_method=self._is_method, + ): ptrty = context.get_function_pointer_type(ty) ptrval = context.add_dynamic_addr( - builder, ty.get_pointer(pyval), info=str(pyval)) + builder, ty.get_pointer(pyval), info=str(pyval) + ) fptr = builder.bitcast(ptrval, ptrty) return context.call_function_pointer(builder, fptr, args) @@ -244,7 +261,7 @@ def get_pointer(self, func): ol = func.__overload__(numba_arg_convertor(self.sig.args)) - address = cppyy.addressof(ol) + address = cppjit.addressof(ol) if not address: raise RuntimeError("unresolved address for %s" % str(ol)) return address @@ -258,20 +275,23 @@ def key(self): def typeof_function(val, c): return CppFunctionNumbaType(val) + @nb_ext.typeof_impl.register(cpp_types.Template) def typeof_template(val, c): - if hasattr(val, '__overload__'): + if hasattr(val, "__overload__"): return CppFunctionNumbaType(val) raise RuntimeError("only function templates supported") + @nb_ext.register_model(CppFunctionNumbaType) class CppFunctionModel(nb_dm.models.PrimitiveModel): def __init__(self, dmm, fe_type): - # the function pointer of this overload can not be exactly typed, but - # only the storage size is relevant, so simply use a void* + # the function pointer of this overload can not be exactly typed, but + # only the storage size is relevant, so simply use a void* be_type = ir.PointerType(dmm.lookup(nb_types.void).get_value_type()) super(CppFunctionModel, self).__init__(dmm, fe_type, be_type) + @nb_iutils.lower_constant(CppFunctionNumbaType) def constant_function_pointer(context, builder, ty, pyval): # TODO: needs to exist for the proper flow, but why? The lowering of the @@ -283,10 +303,10 @@ def constant_function_pointer(context, builder, ty, pyval): # C++ method / data member -> Numba # class CppDataMemberInfo(object): - __slots__ = ['f_name', 'f_offset', 'f_nbtype', 'f_irtype'] + __slots__ = ["f_name", "f_offset", "f_nbtype", "f_irtype"] def __init__(self, name, offset, cpptype): - self.f_name = name + self.f_name = name self.f_offset = offset self.f_nbtype = cpp2numba(cpptype) self.f_irtype = cpp2ir(cpptype) @@ -300,13 +320,13 @@ def __init__(self, scope, qualifier): addr = None cppinstance_val = None if qualifier == Qualified.instance: - addr = cppyy.addressof(scope) + addr = cppjit.addressof(scope) cppinstance_val = scope scope = type(scope) qualifier = Qualified.default super(CppClassNumbaType, self).__init__(scope.__init__) - self.name = 'CppClass(%s)' % scope.__cpp_name__ # overrides value in Type - self._scope = scope + self.name = "CppClass(%s)" % scope.__cpp_name__ # overrides value in Type + self._scope = scope self._qualifier = qualifier self._cppinstanceval = cppinstance_val self._addr = addr @@ -359,12 +379,15 @@ def generic_resolve(self, typ, attr): return ft + @nb_iutils.lower_getattr_generic(CppClassNumbaType) def cppclass_getattr_impl(context, builder, typ, val, attr): # TODO: the following relies on the fact that numba will first lower the # field access, then immediately lower the call; and that the `val` loads # the struct representing the C++ object. Neither need be stable. - if attr in typ._scope.__dict__ and isinstance(typ._scope.__dict__[attr], cpp_types.DataMember): + if attr in typ._scope.__dict__ and isinstance( + typ._scope.__dict__[attr], cpp_types.DataMember + ): dm = typ._scope.__dict__[attr] ct = dm.__cpp_reflex__(cpp_refl.TYPE) offset = dm.__cpp_reflex__(cpp_refl.OFFSET) @@ -389,7 +412,7 @@ def cppclass_getattr_impl(context, builder, typ, val, attr): pf = builder.bitcast(pfc, ir.PointerType(cpp2ir(ct))) return builder.load(pf) - # assume this is a method + # assume this is a method q = typ.get_qualifier() if q == Qualified.default: return builder.bitcast(val, ir_voidptr) @@ -408,8 +431,8 @@ def get(self, builder, val, pos): if isinstance(pos, str): pos = self.get_field_position(pos) - # Use the offsets for direct addressing, rather than getting the elements - # from the struct type. + # Use the offsets for direct addressing, rather than getting the elements + # from the struct type. dmi = self._data_members[pos] stack = nb_cgu.alloca_once(builder, self.get_data_type()) @@ -421,34 +444,37 @@ def get(self, builder, val, pos): return builder.load(pf) + class ImplClassValueModel(ImplAggregateValueModel): - # TODO : Should the address have to be passed here and stored in meminfo - # value: representation inside function body. Maybe stored in stack. - # The representation here are flexible. + # TODO : Should the address have to be passed here and stored in meminfo + # value: representation inside function body. Maybe stored in stack. + # The representation here are flexible. def get_value_type(self): return self.get_data_type() - # data: representation used when storing into containers (e.g. arrays). + # data: representation used when storing into containers (e.g. arrays). def get_data_type(self): - # The struct model relies on data being a POD, but for C++ objects, there - # can be hidden data (e.g. vtable, thunks, or simply private members), and - # the alignment of Cling and Numba also need not be the same. Therefore, the - # struct is split in a series of byte members to get the total size right - # and to allow addressing at the correct offsets. + # The struct model relies on data being a POD, but for C++ objects, there + # can be hidden data (e.g. vtable, thunks, or simply private members), and + # the alignment of Cling and Numba also need not be the same. Therefore, the + # struct is split in a series of byte members to get the total size right + # and to allow addressing at the correct offsets. if self._data_type is None: - self._data_type = \ - ir.LiteralStructType([ir_byte for i in range(self._sizeof)], packed=True) + self._data_type = ir.LiteralStructType( + [ir_byte for i in range(self._sizeof)], packed=True + ) return self._data_type - # return: representation used for return argument. + # return: representation used for return argument. def get_return_type(self): return self.get_data_type() scope_numbatypes = (dict(), dict()) + @nb_ext.typeof_impl.register(cpp_types.Scope) -def typeof_scope(val, c, q = Qualified.default): +def typeof_scope(val, c, q=Qualified.default): is_instance = False cppinstance_val = None if q == Qualified.instance: @@ -479,35 +505,40 @@ class ImplClassType(CppClassNumbaType): scope_numbatypes[q][val] = cnt - # declare data members to Numba + # declare data members to Numba data_members = list() member_methods = dict() for name, field in val.__dict__.items(): if isinstance(field, cpp_types.DataMember): - data_members.append(CppDataMemberInfo( - name, field.__cpp_reflex__(cpp_refl.OFFSET), field.__cpp_reflex__(cpp_refl.TYPE)) + data_members.append( + CppDataMemberInfo( + name, + field.__cpp_reflex__(cpp_refl.OFFSET), + field.__cpp_reflex__(cpp_refl.TYPE), + ) ) elif isinstance(field, cpp_types.Function): member_methods[name] = field.__cpp_reflex__(cpp_refl.RETURN_TYPE) - # TODO: this refresh is needed b/c the scope type is registered as a - # callable after the tracing started; no idea of the side-effects ... + # TODO: this refresh is needed b/c the scope type is registered as a + # callable after the tracing started; no idea of the side-effects ... nb_reg.cpu_target.typing_context.refresh() - # create a model description for Numba + # create a model description for Numba if q == Qualified.default: + @nb_ext.register_model(ImplClassType) class ImplClassModel(nb_dm.models.StructModel): def __init__(self, dmm, fe_type): self._data_members = data_members self._member_methods = member_methods - # TODO: eventually we need not derive from StructModel + # TODO: eventually we need not derive from StructModel members = [(dmi.f_name, dmi.f_nbtype) for dmi in data_members] nb_dm.models.StructModel.__init__(self, dmm, fe_type, members) - # proxies are always accessed by pointer, which are not composites + # proxies are always accessed by pointer, which are not composites def traverse(self, builder): return [] @@ -515,25 +546,25 @@ def traverse_models(self): return [] def traverse_types(self): - return [self._fe_type] # from StructModel + return [self._fe_type] # from StructModel - # data: representation used when storing into containers (e.g. arrays). + # data: representation used when storing into containers (e.g. arrays). # TODO ... - # value: representation inside function body. Maybe stored in stack. - # The representation here are flexible. + # value: representation inside function body. Maybe stored in stack. + # The representation here are flexible. def get_value_type(self): - # the C++ object, b/c through a proxy, is always accessed by pointer; it is - # represented as a pointer to POD to allow indexing by Numba for data member - # type checking, but the address offsetting for loading data member values is - # independent (see get(), below), so the exact layout need not match a POD + # the C++ object, b/c through a proxy, is always accessed by pointer; it is + # represented as a pointer to POD to allow indexing by Numba for data member + # type checking, but the address offsetting for loading data member values is + # independent (see get(), below), so the exact layout need not match a POD - # TODO: this doesn't work for real PODs, b/c those are unpacked into their elements - # and passed through registers + # TODO: this doesn't work for real PODs, b/c those are unpacked into their elements + # and passed through registers return ir.PointerType(super(ImplClassModel, self).get_value_type()) - # argument: representation used for function argument. Needs to be builtin type, - # but unlike other Numba composites, C++ proxies are not flattened. + # argument: representation used for function argument. Needs to be builtin type, + # but unlike other Numba composites, C++ proxies are not flattened. def get_argument_type(self): return self.get_value_type() @@ -543,10 +574,10 @@ def as_argument(self, builder, value): def from_argument(self, builder, value): return value - # return: representation used for return argument. + # return: representation used for return argument. # TODO ... - # access to public data members + # access to public data members def get(self, builder, val, pos): """Get a field at the given position/field name""" @@ -563,21 +594,23 @@ def get(self, builder, val, pos): elif q == Qualified.value: if val.__cpp_reflex__(cpp_refl.IS_AGGREGATE): + @nb_ext.register_model(ImplClassType) class ImplClassModel(ImplAggregateValueModel): pass else: + @nb_ext.register_model(ImplClassType) class ImplClassModel(ImplClassValueModel): pass - def init(self, dmm, fe_type, sz = cppyy.sizeof(val)): + def init(self, dmm, fe_type, sz=cppjit.sizeof(val)): self._data_members = data_members self._member_methods = member_methods self._sizeof = sz - # TODO: this code exists purely to be able to use the indexing and hierarchy - # of the base class StructModel, which isn't much of a reason + # TODO: this code exists purely to be able to use the indexing and hierarchy + # of the base class StructModel, which isn't much of a reason members = [(dmi.f_name, dmi.f_nbtype) for dmi in data_members] nb_dm.models.StructModel.__init__(self, dmm, fe_type, members) @@ -586,13 +619,15 @@ def init(self, dmm, fe_type, sz = cppyy.sizeof(val)): else: assert not "unknown qualified type" - # Python proxy unwrapping for arguments into the Numba trace + # Python proxy unwrapping for arguments into the Numba trace @nb_ext.unbox(ImplClassType) def unbox_instance(typ, obj, c): - global cppyy_as_voidptr + global cppjit_as_voidptr ptrty = ir.PointerType(ir.FunctionType(ir_voidptr, [ir_voidptr])) - ptrval = c.context.add_dynamic_addr(c.builder, cppyy_as_voidptr, info='Instance_AsVoidPtr') + ptrval = c.context.add_dynamic_addr( + c.builder, cppjit_as_voidptr, info="Instance_AsVoidPtr" + ) fp = c.builder.bitcast(ptrval, ptrty) vptr = c.context.call_function_pointer(c.builder, fp, [obj]) @@ -604,33 +639,33 @@ def unbox_instance(typ, obj, c): def make_implclass(context, builder, typ, **kwargs): return nb_cgu.create_struct_proxy(typ)(context, builder, **kwargs) - # C++ object to Python proxy wrapping for returns from Numba trace + # C++ object to Python proxy wrapping for returns from Numba trace @nb_ext.box(ImplClassType) def box_instance(typ, val, c): assert not "requires object model and passing of intact object, not memberwise copy" - global cppyy_from_voidptr + global cppjit_from_voidptr if isinstance(val, ir.Constant): if val.constant == ir.Undefined: assert not "Value passed to instance boxing is undefined" - return NULL + return NULL # noqa: F821 implclass = make_implclass(c.context, c.builder, typ) classobj = c.pyapi.unserialize(c.pyapi.serialize_object(cpp_types.Instance)) box_list = [] - model = implclass._datamodel + model = implclass._datamodel # noqa: F841 cfr = CppClassFieldResolver(c.context) for i in typ._scope.__dict__: if isinstance(cfr.generic_resolve(typ, i), nb_types.Type): - box_list.append(c.box(cfr.generic_resolve(typ, i), getattr(implclass, i))) + box_list.append( + c.box(cfr.generic_resolve(typ, i), getattr(implclass, i)) + ) - box_res = c.pyapi.call_function_objargs( - classobj, tuple(box_list) - ) + box_res = c.pyapi.call_function_objargs(classobj, tuple(box_list)) # Required for nopython mode, numba nrt requres each member box call to decref # since it steals the reference for i in box_list: @@ -640,6 +675,7 @@ def box_instance(typ, val, c): return cnt + # # C++ instance -> Numba # diff --git a/python/cppjit/reflex.py b/python/cppjit/reflex.py new file mode 100644 index 0000000..70be356 --- /dev/null +++ b/python/cppjit/reflex.py @@ -0,0 +1,26 @@ +"""cppjit reflection""" + +import cppjit + +try: + import __pypy__ # noqa: F401 + + __all__ = [] + +except ImportError: + __all__ = [ + "RETURN_TYPE", + ] + + cppjit.include("cpyrt/Reflex.h") + + IS_NAMESPACE = cppjit.gbl.cppjit.interop.Reflex.IS_NAMESPACE + IS_AGGREGATE = cppjit.gbl.cppjit.interop.Reflex.IS_AGGREGATE + + OFFSET = cppjit.gbl.cppjit.interop.Reflex.OFFSET + RETURN_TYPE = cppjit.gbl.cppjit.interop.Reflex.RETURN_TYPE + TYPE = cppjit.gbl.cppjit.interop.Reflex.TYPE + + OPTIMAL = cppjit.gbl.cppjit.interop.Reflex.OPTIMAL + AS_TYPE = cppjit.gbl.cppjit.interop.Reflex.AS_TYPE + AS_STRING = cppjit.gbl.cppjit.interop.Reflex.AS_STRING diff --git a/python/cppjit/types.py b/python/cppjit/types.py new file mode 100644 index 0000000..418ea1b --- /dev/null +++ b/python/cppjit/types.py @@ -0,0 +1,32 @@ +"""C++ proxy types.""" + +import cppjit + +bck = cppjit._backend +Instance = bck.CPPInstance + +try: + import __pypy__ # noqa: F401 + + __all__ = ["Instance"] + +except ImportError: + __all__ = [ + "DataMember", + "Instance", + "Function", + "MethodScope", # noqa: F822 + "InstanceArray", + "LowLevelView", + "Template", + ] + + DataMember = bck.CPPDataMember + Function = bck.CPPOverload + Method = bck.CPPOverload + Scope = bck.CPPScope + InstanceArray = bck.InstanceArray + LowLevelView = bck.LowLevelView + Template = bck.TemplateProxy + +del bck diff --git a/python/cppjit_backend/__init__.py b/python/cppjit_backend/__init__.py new file mode 100644 index 0000000..aab79a8 --- /dev/null +++ b/python/cppjit_backend/__init__.py @@ -0,0 +1 @@ +from ._version import __version__ as __version__ diff --git a/python/cppjit_backend/_version.py b/python/cppjit_backend/_version.py new file mode 100644 index 0000000..3dc1f76 --- /dev/null +++ b/python/cppjit_backend/_version.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/src/CPyCppyy/include/CPyCppyy/API.h b/src/CPyCppyy/include/CPyCppyy/API.h deleted file mode 100644 index 8f17b0f..0000000 --- a/src/CPyCppyy/include/CPyCppyy/API.h +++ /dev/null @@ -1,242 +0,0 @@ -#ifndef CPYCPPYY_API_H -#define CPYCPPYY_API_H - -// -// Access to the python interpreter and API onto CPyCppyy. -// - -// Python -#ifdef _WIN32 -#pragma warning (disable : 4275) -#pragma warning (disable : 4251) -#pragma warning (disable : 4800) -#endif -#if defined(linux) -#include -#ifdef _POSIX_C_SOURCE -#undef _POSIX_C_SOURCE -#endif -#ifdef _FILE_OFFSET_BITS -#undef _FILE_OFFSET_BITS -#endif -#ifdef _XOPEN_SOURCE -#undef _XOPEN_SOURCE -#endif -#endif -#include "Python.h" - -#define CPYCPPYY_VERSION_HEX 0x011200 - -// Cppyy types -#ifndef CPYCPPYY_INTERNAL - -namespace Cpp { -struct DeclRef; -struct TypeRef; -struct FuncRef; -struct ObjectRef; -} // namespace Cpp - -namespace Cppyy { -typedef Cpp::DeclRef TCppScope_t; -typedef Cpp::TypeRef TCppType_t; -typedef Cpp::ObjectRef TCppObject_t; -typedef Cpp::FuncRef TCppMethod_t; -typedef size_t TCppIndex_t; -typedef void* TCppFuncAddr_t; -} // namespace Cppyy -#endif - -// Bindings -#include "CPyCppyy/CommonDefs.h" - -// Standard -#include -#include - - -namespace CPyCppyy { - -//- type conversion --------------------------------------------------------- - -#ifndef CPYCPPYY_PARAMETER -#define CPYCPPYY_PARAMETER -// generic function argument type -struct Parameter { - union Value { - bool fBool; - int8_t fInt8; - uint8_t fUInt8; - short fShort; - unsigned short fUShort; - int fInt; - unsigned int fUInt; - long fLong; - intptr_t fIntPtr; - unsigned long fULong; - long long fLLong; - unsigned long long fULLong; - int64_t fInt64; - uint64_t fUInt64; - float fFloat; - double fDouble; - long double fLDouble; - void* fVoidp; - } fValue; - void* fRef; - char fTypeCode; -}; -#endif // CPYCPPYY_PARAMETER - -// CallContext is not currently exposed -struct CallContext; - -// Dimensions class not currently exposed -#ifndef CPYCPPYY_DIMENSIONS_H -#define CPYCPPYY_DIMENSIONS_H -typedef Py_ssize_t dim_t; - -class Dimensions { // Windows note: NOT exported/imported - dim_t* fDims; - -public: - Dimensions(dim_t /*ndim*/ = 0, dim_t* /*dims*/ = nullptr) : fDims(nullptr) {} - ~Dimensions() { delete [] fDims; } - -public: - operator bool() const { return (bool)fDims; } -}; - -typedef Dimensions dims_t; -typedef const dims_t& cdims_t; -#endif // !CPYCPPYY_DIMENSIONS_H - -// type converter base class -class CPYCPPYY_CLASS_EXTERN Converter { -public: - virtual ~Converter(); - -// convert the python object and add store it on the parameter - virtual bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) = 0; - -// convert a C++ object from memory to a Python object - virtual PyObject* FromMemory(void* address); - -// convert a Python object to a C++ object and store it on address - virtual bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr); - -// if a converter has state, it will be unique per function, shared otherwise - virtual bool HasState() { return false; } -}; - -// create a converter based on its full type name and dimensions -CPYCPPYY_EXTERN Converter* CreateConverter(const std::string& name, cdims_t = 0); -CPYCPPYY_EXTERN Converter* CreateConverter(Cppyy::TCppType_t type, cdims_t = 0); - -// delete a previously created converter -CPYCPPYY_EXTERN void DestroyConverter(Converter* p); - -// register a custom converter -typedef Converter* (*ConverterFactory_t)(cdims_t); -CPYCPPYY_EXTERN bool RegisterConverter(const std::string& name, ConverterFactory_t); - -// register a custom converter that is a reference to an existing converter -CPYCPPYY_EXTERN bool RegisterConverterAlias(const std::string& name, const std::string& target); - -// remove a custom converter -CPYCPPYY_EXTERN bool UnregisterConverter(const std::string& name); - - -// function executor base class -class CPYCPPYY_CLASS_EXTERN Executor { -public: - virtual ~Executor(); - -// callback when executing a function from Python - virtual PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) = 0; - -// if an executor has state, it will be unique per function, shared otherwise - virtual bool HasState() { return false; } -}; - -// create an executor based on its full type name -CPYCPPYY_EXTERN Executor* CreateExecutor(const std::string& name, cdims_t = 0); -CPYCPPYY_EXTERN Executor* CreateExecutor(Cppyy::TCppType_t type, cdims_t = 0); - -// delete a previously created executor -CPYCPPYY_EXTERN void DestroyConverter(Converter* p); - -// register a custom executor -typedef Executor* (*ExecutorFactory_t)(cdims_t); -CPYCPPYY_EXTERN bool RegisterExecutor(const std::string& name, ExecutorFactory_t); - -// register a custom executor that is a reference to an existing converter -CPYCPPYY_EXTERN bool RegisterExecutorAlias(const std::string& name, const std::string& target); - -// remove a custom executor -CPYCPPYY_EXTERN bool UnregisterExecutor(const std::string& name); - -// helper for calling into C++ from a custom executor -CPYCPPYY_EXTERN void* CallVoidP(Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*); - - -//- C++ access to cppyy objects --------------------------------------------- - -// Get C++ Instance (python object proxy) name. -// Sets a TypeError and returns an empty string if the pyobject is not a CPPInstance. -CPYCPPYY_EXTERN std::string Instance_GetScopedFinalName(PyObject* pyobject); - -// C++ Instance (python object proxy) to void* conversion -CPYCPPYY_EXTERN void* Instance_AsVoidPtr(PyObject* pyobject); - -// void* to C++ Instance (python object proxy) conversion, returns a new reference -CPYCPPYY_EXTERN PyObject* Instance_FromVoidPtr( - void* addr, const std::string& classname, bool python_owns = false); -CPYCPPYY_EXTERN PyObject* Instance_FromVoidPtr( - void* addr, Cppyy::TCppScope_t klass_scope, bool python_owns = false); -// type verifiers for C++ Scope -CPYCPPYY_EXTERN bool Scope_Check(PyObject* pyobject); -CPYCPPYY_EXTERN bool Scope_CheckExact(PyObject* pyobject); - -// type verifiers for C++ Instance -CPYCPPYY_EXTERN bool Instance_Check(PyObject* pyobject); -CPYCPPYY_EXTERN bool Instance_CheckExact(PyObject* pyobject); - -// memory management: ownership of the underlying C++ object -CPYCPPYY_EXTERN void Instance_SetPythonOwns(PyObject* pyobject); -CPYCPPYY_EXTERN void Instance_SetCppOwns(PyObject* pyobject); - -// type verifier for sequences -CPYCPPYY_EXTERN bool Sequence_Check(PyObject* pyobject); - -// helper to verify expected safety of moving an instance into C++ -CPYCPPYY_EXTERN bool Instance_IsLively(PyObject* pyobject); - -// type verifiers for C++ Overload -CPYCPPYY_EXTERN bool Overload_Check(PyObject* pyobject); -CPYCPPYY_EXTERN bool Overload_CheckExact(PyObject* pyobject); - -// Sets the __reduce__ method for the CPPInstance class, which is by default not -// implemented by cppyy but might make sense to implement by frameworks that -// support IO of arbitrary C++ objects, like ROOT. -CPYCPPYY_EXTERN void Instance_SetReduceMethod(PyCFunction reduceMethod); - - -//- access to the python interpreter ---------------------------------------- - -// import a python module, making its classes available to Cling -CPYCPPYY_EXTERN bool Import(const std::string& name); - -// execute a python statement (e.g. "import sys") -CPYCPPYY_EXTERN bool Exec(const std::string& cmd); - -// execute a python stand-alone script, with argv CLI arguments -CPYCPPYY_EXTERN void ExecScript(const std::string& name, const std::vector& args); - -// enter an interactive python session (exit with ^D) -CPYCPPYY_EXTERN void Prompt(); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_API_H diff --git a/src/CPyCppyy/include/CPyCppyy/CommonDefs.h b/src/CPyCppyy/include/CPyCppyy/CommonDefs.h deleted file mode 100644 index af2fa60..0000000 --- a/src/CPyCppyy/include/CPyCppyy/CommonDefs.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef CPYCPPYY_COMMONDEFS_H -#define CPYCPPYY_COMMONDEFS_H - -// export macros for our own API -// import/export (after precommondefs.h from PyPy) -#ifdef _MSC_VER -// Windows requires symbols to be explicitly exported -#define CPYCPPYY_EXPORT extern __declspec(dllexport) -#define CPYCPPYY_IMPORT extern __declspec(dllimport) -#define CPYCPPYY_CLASS_EXPORT __declspec(dllexport) - -// CPYCPPYY_EXTERN is dual use in the public API -#ifndef CPYCPPYY_INTERNAL -#define CPYCPPYY_EXTERN extern __declspec(dllexport) -#define CPYCPPYY_CLASS_EXTERN __declspec(dllexport) -#else -#define CPYCPPYY_EXTERN extern __declspec(dllimport) -#define CPYCPPYY_CLASS_EXTERN __declspec(dllimport) -#endif - -#define CPYCPPYY_STATIC - -#else -// Linux, Mac, etc. -#define CPYCPPYY_EXPORT extern -#define CPYCPPYY_IMPORT extern -#define CPYCPPYY_CLASS_EXPORT -#define CPYCPPYY_EXTERN extern -#define CPYCPPYY_CLASS_EXTERN -#define CPYCPPYY_STATIC static - -#endif - -#endif // !CPYCPPYY_COMMONDEFS_H diff --git a/src/CPyCppyy/include/CPyCppyy/DispatchPtr.h b/src/CPyCppyy/include/CPyCppyy/DispatchPtr.h deleted file mode 100644 index 1cef6fa..0000000 --- a/src/CPyCppyy/include/CPyCppyy/DispatchPtr.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef CPYCPPYY_DISPATCHPTR_H -#define CPYCPPYY_DISPATCHPTR_H - -////////////////////////////////////////////////////////////////////////////// -// // -// DispatchPtr // -// // -// Smart pointer for reference management and C++ instance tracking when // -// cross-inheriting. The carried pointer is always expected to be derived // -// from CPPInstance, and the DispatchPtr to be embedded in the C++ instance // -// derived dispatcher to which it points (ownership is two-way; life-times // -// are equal). The C++ dispatcher then uses the DispatchPtr to call Python // -// functions for virtual methods. // -// // -////////////////////////////////////////////////////////////////////////////// - -// Bindings -#include "CPyCppyy/CommonDefs.h" -#include - -namespace CPyCppyy { -class PythonGILRAII { - PyGILState_STATE state; - -public: - PythonGILRAII() : state(PyGILState_Ensure()) {} - ~PythonGILRAII() { PyGILState_Release(state); } -}; - -class CPYCPPYY_CLASS_EXTERN DispatchPtr { -public: -// Default constructor: only ever created from C++, as by definition, creation -// from the Python side makes the relevant Python instance available. Calls to -// the default ctor happen eg. in STL containers. It is expected that the -// pointer to the Python object is filled in later, eg. through assign(). - DispatchPtr() : fPyHardRef(nullptr), fPyWeakRef(nullptr) {} - -// Conversion constructor: called with C++ object construction when the PyObject -// is known (eg. when instantiating from Python), with pyobj the Python-side -// representation of the C++ object. - explicit DispatchPtr(PyObject* pyobj, bool strong = false); - -// Copy constructor: only ever called from C++. The Python object needs to be -// copied, in case it has added state, and rebound to the new C++ instance. - DispatchPtr(const DispatchPtr& other, void* cppinst); - -// Assignment: only ever called from C++. Similarly to the copy constructor, the -// Pythonb object needs to be copied and rebound. - DispatchPtr& assign(const DispatchPtr& other, void* cppinst); - -// Do not otherwise allow straight copies/assignment. - DispatchPtr(DispatchPtr&) = delete; - DispatchPtr(DispatchPtr&&) = delete; - DispatchPtr& operator=(const DispatchPtr& other) = delete; - -// lifetime is directly bound to the lifetime of the dispatcher object - ~DispatchPtr(); - -// either C++ owns the Python object through a reference count (on fPyHardRef) or -// Python owns the C++ object and we only have a weak reference (through fPyWeakRef) - void PythonOwns(); - void CppOwns(); - -// access to underlying object: cast and dereferencing - operator PyObject*() const { - return Get(); - } - - PyObject* operator->() const { - return Get(); - } - -private: - PyObject* Get(bool borrowed=true) const; - -private: - PyObject* fPyHardRef; - PyObject* fPyWeakRef; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DISPATCHPTR_H diff --git a/src/CPyCppyy/include/CPyCppyy/Reflex.h b/src/CPyCppyy/include/CPyCppyy/Reflex.h deleted file mode 100644 index b37b9ee..0000000 --- a/src/CPyCppyy/include/CPyCppyy/Reflex.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef CPYCPPYY_REFLEX_H -#define CPYCPPYY_REFLEX_H - -// -// Access to the C++ reflection information -// - -namespace Cppyy { - -namespace Reflex { - -typedef int RequestId_t; - -const RequestId_t IS_NAMESPACE = 1; -const RequestId_t IS_AGGREGATE = 2; - -const RequestId_t OFFSET = 16; -const RequestId_t RETURN_TYPE = 17; -const RequestId_t TYPE = 18; - -typedef int FormatId_t; -const FormatId_t OPTIMAL = 1; -const FormatId_t AS_TYPE = 2; -const FormatId_t AS_STRING = 3; - -} // namespace Reflex - -} // namespace Cppyy - -#endif // !CPYCPPYY_REFLEX_H diff --git a/src/CPyCppyy/src/API.cxx b/src/CPyCppyy/src/API.cxx deleted file mode 100644 index 7c54dcf..0000000 --- a/src/CPyCppyy/src/API.cxx +++ /dev/null @@ -1,481 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Cppyy.h" -#define CPYCPPYY_INTERNAL 1 -#include "CPyCppyy/API.h" -#undef CPYCPPYY_INTERNAL - -#include "CPPInstance.h" -#include "CPPOverload.h" -#include "CPPScope.h" -#include "CPyCppyy/DispatchPtr.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" - -// Standard -#include -#include -#include - -//______________________________________________________________________________ -// CPyCppyy API: Interpreter and Proxy Access -// ========================================== -// -// Access to cppyy Python objects from Cling and C++: allows conversion for -// instances and type checking for scopes, instances, etc. -// Adds a few convenience functions to call Python from Cling and expose Python -// classes to Cling for use in inheritance etc. - - -//- data --------------------------------------------------------------------- -static PyObject* gMainDict = nullptr; - -namespace CPyCppyy { - extern PyObject* gThisModule; -} - - -//- private helpers ---------------------------------------------------------- -namespace { - -static bool Initialize() -{ -// Private initialization method: setup the python interpreter and load the -// cppyy module. - static bool isInitialized = false; - if (isInitialized) - return true; - - if (!Py_IsInitialized()) { - // this happens if Cling comes in first - PyConfig config; - PyConfig_InitPythonConfig(&config); - PyConfig_SetString(&config, &config.program_name, L"cppyy"); - Py_InitializeFromConfig(&config); - - // try again to see if the interpreter is initialized - if (!Py_IsInitialized()) { - // give up ... - std::cerr << "Error: python has not been initialized; returning." << std::endl; - return false; - } - - // force loading of the cppyy module - PyRun_SimpleString(const_cast("import cppyy")); - } - - if (!gMainDict) { - CPyCppyy::PythonGILRAII python_gil_raii; - // retrieve the main dictionary - gMainDict = PyModule_GetDict( - PyImport_AddModule(const_cast("__main__"))); - // The gMainDict is borrowed, i.e. we are not calling Py_INCREF(gMainDict). - // Like this, we avoid unexpectedly affecting how long __main__ is kept - // alive. The gMainDict is only used in Exec(), ExecScript(), and Eval(), - // which should not be called after __main__ is garbage collected anyway. - } - -// declare success ... - isInitialized = true; - return true; -} - -} // unnamed namespace - - -//- C++ access to cppyy objects --------------------------------------------- -std::string CPyCppyy::Instance_GetScopedFinalName(PyObject* pyobject) -{ - if (!Instance_Check(pyobject)) { - PyErr_SetString(PyExc_TypeError, "Instance_GetScopedFinalName : object is not a C++ instance"); - return ""; - } - - Cppyy::TCppScope_t pyobjectClass = ((CPPInstance *)pyobject)->ObjectIsA(); - return Cppyy::GetScopedFinalName(pyobjectClass); -} - -//----------------------------------------------------------------------------- -void* CPyCppyy::Instance_AsVoidPtr(PyObject* pyobject) -{ -// Extract the object pointer held by the CPPInstance pyobject. - if (!Initialize()) - return nullptr; - - PythonGILRAII python_gil_raii; - -// check validity of cast - if (!CPPInstance_Check(pyobject)) - return nullptr; - -// get held object (may be null) - return ((CPPInstance*)pyobject)->GetObject(); -} - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::Instance_FromVoidPtr( - void* addr, const std::string& classname, bool python_owns) -{ -// Bind the addr to a python object of class defined by classname. - if (!Initialize()) - return nullptr; - - PythonGILRAII python_gil_raii; - -// perform cast (the call will check TClass and addr, and set python errors) - PyObject* pyobject = BindCppObjectNoCast(addr, Cppyy::GetScope(classname), false); - -// give ownership, for ref-counting, to the python side, if so requested - if (python_owns && CPPInstance_Check(pyobject)) - ((CPPInstance*)pyobject)->PythonOwns(); - - return pyobject; -} - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::Instance_FromVoidPtr( - void* addr, Cppyy::TCppScope_t klass_scope, bool python_owns) -{ -// Bind the addr to a python object of class defined by classname. - if (!Initialize()) - return nullptr; - - PythonGILRAII python_gil_raii; - -// perform cast (the call will check TClass and addr, and set python errors) - PyObject* pyobject = BindCppObjectNoCast(addr, klass_scope, false); - -// give ownership, for ref-counting, to the python side, if so requested - if (python_owns && CPPInstance_Check(pyobject)) - ((CPPInstance*)pyobject)->PythonOwns(); - - return pyobject; -} -namespace CPyCppyy { -// version with C type arguments only for use with Numba -PyObject* Instance_FromVoidPtr(void* addr, const char* classname, int python_owns) { - return Instance_FromVoidPtr(addr, std::string(classname), (bool)python_owns); -} -} // namespace CPyCppyy - -//----------------------------------------------------------------------------- -bool CPyCppyy::Scope_Check(PyObject* pyobject) -{ -// Test if the given object is of a CPPScope derived type. - if (!Initialize()) - return false; - - PythonGILRAII python_gil_raii; - return CPPScope_Check(pyobject); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Scope_CheckExact(PyObject* pyobject) -{ -// Test if the given object is of a CPPScope type. - if (!Initialize()) - return false; - - PythonGILRAII python_gil_raii; - return CPPScope_CheckExact(pyobject); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Instance_Check(PyObject* pyobject) -{ -// Test if the given pyobject is of CPPInstance derived type. - if (!Initialize()) - return false; - - PythonGILRAII python_gil_raii; -// detailed walk through inheritance hierarchy - return CPPInstance_Check(pyobject); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Instance_CheckExact(PyObject* pyobject) -{ -// Test if the given pyobject is of CPPInstance type. - if (!Initialize()) - return false; - - PythonGILRAII python_gil_raii; -// direct pointer comparison of type member - return CPPInstance_CheckExact(pyobject); -} - -//----------------------------------------------------------------------------- -void CPyCppyy::Instance_SetPythonOwns(PyObject* pyobject) -{ - if (!Initialize()) - return; - -// check validity of cast - if (!CPPInstance_Check(pyobject)) - return; - - ((CPPInstance *)pyobject)->PythonOwns(); -} - -//----------------------------------------------------------------------------- -void CPyCppyy::Instance_SetCppOwns(PyObject* pyobject) -{ - if (!Initialize()) - return; - -// check validity of cast - if (!CPPInstance_Check(pyobject)) - return; - - ((CPPInstance *)pyobject)->CppOwns(); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Sequence_Check(PyObject* pyobject) -{ - PythonGILRAII python_gil_raii; -// Extends on PySequence_Check() to determine whether an object can be iterated -// over (technically, all objects can b/c of C++ pointer arithmetic, hence this -// check isn't 100% accurate, but neither is PySequence_Check()). - -// Note: simply having the iterator protocol does not constitute a sequence, bc -// PySequence_GetItem() would fail. - -// default to PySequence_Check() if called with a non-C++ object - if (!CPPInstance_Check(pyobject)) - return (bool)PySequence_Check(pyobject); - -// all C++ objects should have sq_item defined, but a user-derived class may -// have deleted it, in which case this is not a sequence - PyTypeObject* t = Py_TYPE(pyobject); - if (!t->tp_as_sequence || !t->tp_as_sequence->sq_item) - return false; - -// if this is the default getitem, it is only a sequence if it's an array type - if (t->tp_as_sequence->sq_item == CPPInstance_Type.tp_as_sequence->sq_item) { - if (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsArray) - return true; - return false; - } - -// TODO: could additionally verify whether __len__ is supported and/or whether -// operator()[] takes an int argument type - - return true; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Instance_IsLively(PyObject* pyobject) -{ - PythonGILRAII python_gil_raii; -// Test whether the given instance can safely return to C++ - if (!CPPInstance_Check(pyobject)) - return true; // simply don't know - -// the instance fails the lively test if it owns the C++ object while having a -// reference count of 1 (meaning: it could delete the C++ instance any moment) - if (Py_REFCNT(pyobject) <= 1 && (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsOwner)) - return false; - - return true; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Overload_Check(PyObject* pyobject) -{ -// Test if the given pyobject is of CPPOverload derived type. - if (!Initialize()) - return false; - - PythonGILRAII python_gil_raii; -// detailed walk through inheritance hierarchy - return CPPOverload_Check(pyobject); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Overload_CheckExact(PyObject* pyobject) -{ -// Test if the given pyobject is of CPPOverload type. - if (!Initialize()) - return false; - - PythonGILRAII python_gil_raii; -// direct pointer comparison of type member - return CPPOverload_CheckExact(pyobject); -} - -//----------------------------------------------------------------------------- -void CPyCppyy::Instance_SetReduceMethod(PyCFunction reduceMethod) -{ - CPPInstance::ReduceMethod() = reduceMethod; -} - -//- access to the python interpreter ---------------------------------------- -bool CPyCppyy::Import(const std::string& mod_name) -{ -// Import the named python module and create Cling equivalents for its classes. - if (!Initialize()) - return false; - - PythonGILRAII python_gil_raii; - - PyObject* mod = PyImport_ImportModule(mod_name.c_str()); - if (!mod) { - PyErr_Print(); - return false; - } - -// allow finding to prevent creation of a python proxy for the C++ proxy - Py_INCREF(mod); - PyModule_AddObject(gThisModule, mod_name.c_str(), mod); - -// force creation of the module as a namespace -// TODO: the following is broken (and should live in Cppyy.cxx) -// TClass::GetClass(mod_name, true); - - PyObject* dct = PyModule_GetDict(mod); - -// create Cling classes for all new python classes - PyObject* values = PyDict_Values(dct); - for (int i = 0; i < PyList_GET_SIZE(values); ++i) { - PyObject* value = PyList_GET_ITEM(values, i); - Py_INCREF(value); - - // collect classes - if (PyClass_Check(value) || PyObject_HasAttr(value, PyStrings::gBases)) { - // get full class name (including module) - PyObject* pyClName = PyObject_GetAttr(value, PyStrings::gName); - if (PyErr_Occurred()) - PyErr_Clear(); - - // build full, qualified name - std::string fullname = mod_name; - fullname += "."; - fullname += CPyCppyy_PyText_AsString(pyClName); - - Py_XDECREF(pyClName); - } - - Py_DECREF(value); - } - - Py_DECREF(values); - -// TODO: mod "leaks" here - if (PyErr_Occurred()) - return false; - return true; -} - -//----------------------------------------------------------------------------- -void CPyCppyy::ExecScript(const std::string& name, const std::vector& args) -{ -// Execute a python stand-alone script, with argv CLI arguments. -// -// example of use: -// CPyCppyy::ExecScript("test.py", {"1", "2", "3"}); - - if (!Initialize()) - return; - - PythonGILRAII python_gil_raii; - -// verify arguments - if (name.empty()) { - std::cerr << "Error: no file name specified." << std::endl; - return; - } - - FILE* fp = fopen(name.c_str(), "r"); - if (!fp) { - std::cerr << "Error: could not open file \"" << name << "\"." << std::endl; - return; - } - -// store a copy of the old cli for restoration - PyObject* oldargv = PySys_GetObject("argv"); // borrowed - if (oldargv) { - PyObject* copy = PyList_GetSlice(oldargv, 0, PyList_Size(oldargv)); - oldargv = copy; // now owned - } else { - PyErr_Clear(); - } - -// build new argv - const int argc = (int)args.size() + 1; - std::vector wargv(argc); - wargv[0] = Py_DecodeLocale(name.c_str(), nullptr); - - for (int i = 1; i < argc; ++i) { - wargv[i] = Py_DecodeLocale(args[i - 1].c_str(), nullptr); - } - -// set sys.argv - PyObject* sysmod = PyImport_ImportModule("sys"); // new reference - if (sysmod) { - PyObject* argv_obj = PyList_New(argc); - for (int i = 0; i < argc; ++i) { - PyList_SET_ITEM(argv_obj, i, PyUnicode_FromWideChar(wargv[i], -1)); - } - PyObject_SetAttrString(sysmod, "argv", argv_obj); - Py_DECREF(argv_obj); - Py_DECREF(sysmod); - } else { - PyErr_Print(); - } - -// actual script execution - PyObject* gbl = PyDict_Copy(gMainDict); - PyObject* result = // PyRun_FileEx closes fp (b/c of last argument "1") - PyRun_FileEx(fp, const_cast(name.c_str()), Py_file_input, gbl, gbl, 1); - - if (!result) - PyErr_Print(); - - Py_XDECREF(result); - Py_DECREF(gbl); - -// restore original command line - if (oldargv) { - PySys_SetObject("argv", oldargv); - Py_DECREF(oldargv); - } - -// free memory from Py_DecodeLocale - for (auto ptr : wargv) - PyMem_RawFree(ptr); -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::Exec(const std::string& cmd) -{ -// Execute a python statement (e.g. "import noddy"). - if (!Initialize()) - return false; - - PythonGILRAII python_gil_raii; -// execute the command - PyObject* result = - PyRun_String(const_cast(cmd.c_str()), Py_file_input, gMainDict, gMainDict); - -// test for error - if (result) { - Py_DECREF(result); - return true; - } - - PyErr_Print(); - return false; -} - -//----------------------------------------------------------------------------- -void CPyCppyy::Prompt() { -// Enter an interactive python session (exit with ^D). State is preserved -// between successive calls. - if (!Initialize()) - return; - - PythonGILRAII python_gil_raii; -// enter i/o interactive mode - PyRun_InteractiveLoop(stdin, const_cast("\0")); -} diff --git a/src/CPyCppyy/src/CPPClassMethod.cxx b/src/CPyCppyy/src/CPPClassMethod.cxx deleted file mode 100644 index 9134d5a..0000000 --- a/src/CPyCppyy/src/CPPClassMethod.cxx +++ /dev/null @@ -1,53 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPClassMethod.h" -#include "CPPInstance.h" - - -//- public members -------------------------------------------------------------- -PyObject *CPyCppyy::CPPClassMethod::Call(CPPInstance *&self, CPyCppyy_PyArgs_t args, - size_t nargsf, PyObject *kwds, CallContext *ctxt) -{ -// preliminary check in case keywords are accidently used (they are ignored otherwise) - if (kwds && ((PyDict_Check(kwds) && PyDict_Size(kwds)) || - (PyTuple_CheckExact(kwds) && PyTuple_GET_SIZE(kwds)))) { - PyErr_SetString(PyExc_TypeError, "keyword arguments are not yet supported"); - return nullptr; - } - -// setup as necessary - if (!this->Initialize(ctxt)) - return nullptr; - -// translate the arguments -// TODO: The following is not robust and should be revisited e.g. by making CPPOverloads -// that have only CPPClassMethods be true Python classmethods? Note that the original -// implementation wasn't 100% correct either (e.g. static size() mapped to len()). -// -// As-is, if no suitable `self` is given (normal case), but the type of the first argument -// matches the enclosing scope of the class method and it isn't needed for the call, then -// assume that the method was (incorrectly) bound and so drop that instance from args. - int nargs = (int)CPyCppyy_PyArgs_GET_SIZE(args, nargsf); - if ((!self || (PyObject*)self == Py_None) && nargs) { - PyObject* arg0 = CPyCppyy_PyArgs_GET_ITEM(args, 0); - if (CPPInstance_Check(arg0) && fArgsRequired <= nargs - 1 && - Cppyy::IsSubclass(reinterpret_cast(arg0)->ObjectIsA(), GetScope())) { - args += 1; // drops first argument - nargsf -= 1; - } - } - - if (!this->ConvertAndSetArgs(args, nargsf, ctxt)) - return nullptr; - -// execute function - return this->Execute(nullptr, 0, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPClassMethod::GetTypeName() -{ - PyObject* cppname = CPyCppyy_PyText_FromString((GetReturnTypeName() + " (*)").c_str()); - CPyCppyy_PyText_AppendAndDel(&cppname, GetSignature(false /* show_formalargs */)); - return cppname; -} diff --git a/src/CPyCppyy/src/CPPClassMethod.h b/src/CPyCppyy/src/CPPClassMethod.h deleted file mode 100644 index c8c3e85..0000000 --- a/src/CPyCppyy/src/CPPClassMethod.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef CPYCPPYY_CPPCLASSMETHOD_H -#define CPYCPPYY_CPPCLASSMETHOD_H - -// Bindings -#include "CPPMethod.h" - - -namespace CPyCppyy { - -class CPPClassMethod : public CPPMethod { -public: - using CPPMethod::CPPMethod; - -public: - PyObject* GetTypeName() override; - -public: - PyCallable* Clone() override { return new CPPClassMethod(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPCLASSMETHOD_H diff --git a/src/CPyCppyy/src/CPPConstructor.cxx b/src/CPyCppyy/src/CPPConstructor.cxx deleted file mode 100644 index 7b6170f..0000000 --- a/src/CPyCppyy/src/CPPConstructor.cxx +++ /dev/null @@ -1,373 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPConstructor.h" -#include "CPPInstance.h" -#include "Executors.h" -#include "MemoryRegulator.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" - -// Standard -#include -#include - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - extern PyObject* gNullPtrObject; - void* Instance_AsVoidPtr(PyObject* pyobject); - PyObject* Instance_FromVoidPtr(void* addr, Cppyy::TCppScope_t klass_scope, bool python_owns); -} - - -//- protected members -------------------------------------------------------- -bool CPyCppyy::CPPConstructor::InitExecutor_(Executor*& executor, CallContext*) -{ -// pick up special case new object executor - executor = CreateExecutor("__init__"); - return true; -} - -//- public members ----------------------------------------------------------- -PyObject* CPyCppyy::CPPConstructor::GetDocString() -{ -// GetMethod() may return an empty function if this is just a special case place holder - const std::string& clName = Cppyy::GetFinalName(this->GetScope()); - return CPyCppyy_PyText_FromFormat("%s::%s%s", - clName.c_str(), clName.c_str(), this->GetMethod() ? this->GetSignatureString().c_str() : "()"); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPConstructor::Reflex( - Cppyy::Reflex::RequestId_t request, Cppyy::Reflex::FormatId_t format) -{ -// C++ reflection tooling for constructors. - - if (request == Cppyy::Reflex::RETURN_TYPE) { - std::string fn = Cppyy::GetScopedFinalName(this->GetScope()); - if (format == Cppyy::Reflex::OPTIMAL || format == Cppyy::Reflex::AS_TYPE) - return CreateScopeProxy(this->GetScope()); - else if (format == Cppyy::Reflex::AS_STRING) - return CPyCppyy_PyText_FromString(fn.c_str()); - } - - return PyCallable::Reflex(request, format); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt) -{ -// setup as necessary - if (fArgsRequired == -1 && !this->Initialize(ctxt)) - return nullptr; // important: 0, not Py_None - -// fetch self, verify, and put the arguments in usable order - PyCallArgs cargs{self, args, nargsf, kwds}; - if (!this->ProcessArgs(cargs)) - return nullptr; - -// verify existence of self (i.e. tp_new called) - if (!self) { - PyErr_SetString(PyExc_ReferenceError, "no python object allocated"); - return nullptr; - } - - if (self->GetObject()) { - PyErr_SetString(PyExc_ReferenceError, - "object already constructed; use __assign__ instead of __init__"); - return nullptr; - } - - const auto cppScopeFlags = ((CPPScope*)Py_TYPE(self))->fFlags; - -// Do nothing if the constructor is explicit and we are in an implicit -// conversion context. See also the ConvertImplicit() helper in Converters.cxx. - if((cppScopeFlags & CPPScope::kActiveImplicitCall) && Cppyy::IsExplicit(GetMethod())) { - // FIXME: Cases with explicit marked std::complex constructors where we expect implicit conversionss - if (Cppyy::GetMethodSignature(GetMethod(), true).find("std::complex") == std::string::npos) - return nullptr; - } - -// self provides the python context for lifelines - if (!ctxt->fPyContext) - ctxt->fPyContext = (PyObject*)cargs.fSelf; // no Py_INCREF as no ownership - -// perform the call, nullptr 'this' makes the other side allocate the memory - Cppyy::TCppScope_t disp = self->ObjectIsA(false /* check_smart */); - intptr_t address = 0; - if (GetScope() != disp) { - // happens for Python derived types (which have a dispatcher inserted that - // is not otherwise user-visible: call it instead) and C++ derived classes - // without public constructors - - // first, check whether we at least had a proper meta class, or whether that - // was also replaced user-side - if (!GetScope() || !disp) { - PyErr_SetString(PyExc_TypeError, "can not construct incomplete C++ class"); - return nullptr; - } - - // get the dispatcher class and verify - PyObject* dispproxy = CPyCppyy::GetScopeProxy(disp); - if (!dispproxy) { - PyErr_SetString(PyExc_TypeError, "dispatcher proxy was never created"); - return nullptr; - } - - if (!(((CPPClass*)dispproxy)->fFlags & CPPScope::kIsPython)) { - PyErr_SetString(PyExc_TypeError, const_cast(( - "constructor for " + Cppyy::GetScopedFinalName(disp) + " is not a dispatcher").c_str())); - return nullptr; - } - - PyObject* pyobj = CPyCppyy_PyObject_Call(dispproxy, cargs.fArgs, cargs.fNArgsf, kwds); - if (!pyobj) - return nullptr; - - // retrieve the actual pointer, take over control, and set set _internal_self - address = (intptr_t)((CPPInstance*)pyobj)->GetObject(); - if (address) { - ((CPPInstance*)pyobj)->CppOwns(); // b/c self will control the object on address - PyObject* res = PyObject_CallMethodObjArgs( - dispproxy, PyStrings::gDispInit, pyobj, (PyObject*)self, nullptr); - Py_XDECREF(res); - } - Py_DECREF(dispproxy); - - } else { - // translate the arguments - if (cppScopeFlags & CPPScope::kActiveImplicitCall) - ctxt->fFlags |= CallContext::kNoImplicit; - if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) - return nullptr; - - address = (intptr_t)this->Execute(nullptr, 0, ctxt); - } - -// return object if successful, lament if not - if (address) { - Py_INCREF(self); - - // note: constructors are no longer set to take ownership by default; instead that is - // decided by the method proxy (which carries a creator flag) upon return - self->Set((void*)address); - - // mark as actual to prevent needless auto-casting and register on its class - self->fFlags |= CPPInstance::kIsActual; - if (!(((CPPClass*)Py_TYPE(self))->fFlags & CPPScope::kIsSmart)) - MemoryRegulator::RegisterPyObject(self, Cppyy::TCppObject_t((void*)address)); - - // handling smart types this way is deeply fugly, but if CPPInstance sets the proper - // types in op_new first, then the wrong init is called - if (((CPPClass*)Py_TYPE(self))->fFlags & CPPScope::kIsSmart) { - PyObject* pyclass = CreateScopeProxy(((CPPSmartClass*)Py_TYPE(self))->fUnderlyingType); - if (pyclass) { - self->SetSmart((PyObject*)Py_TYPE(self)); - Py_DECREF((PyObject*)Py_TYPE(self)); - Py_SET_TYPE(self, (PyTypeObject*)pyclass); - } - } - - // done with self - Py_DECREF(self); - - Py_RETURN_NONE; // by definition - } - - if (!PyErr_Occurred()) // should be set, otherwise write a generic error msg - PyErr_SetString(PyExc_TypeError, const_cast( - (Cppyy::GetScopedFinalName(GetScope()) + " constructor failed").c_str())); - -// do not throw an exception, nullptr might trigger the overload handler to -// choose a different constructor, which if all fails will throw an exception - return nullptr; -} - - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMultiConstructor::CPPMultiConstructor(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method) : - CPPConstructor(scope, method) -{ - fNumBases = Cppyy::GetNumBases(scope); -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMultiConstructor::CPPMultiConstructor(const CPPMultiConstructor& s) : - CPPConstructor(s), fNumBases(s.fNumBases) -{ -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMultiConstructor& CPyCppyy::CPPMultiConstructor::operator=(const CPPMultiConstructor& s) -{ - if (this != &s) { - CPPConstructor::operator=(s); - fNumBases = s.fNumBases; - } - return *this; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMultiConstructor::Call(CPPInstance*& self, - CPyCppyy_PyArgs_t argsin, size_t nargsf, PyObject* kwds, CallContext* ctxt) -{ -// By convention, initialization parameters of multiple base classes are grouped -// by target base class. Here, we disambiguate and put in "sentinel" parameters -// that allow the dispatcher to propagate them. - -// Three options supported: -// 0. empty args: default constructor call -// 1. fNumBases tuples, each handed to individual constructors -// 2. less than fNumBases, assuming (void) for the missing base constructors -// 3. normal arguments, going to the first base only - -// TODO: this way of forwarding is expensive as the loop is external to this call; -// it would be more efficient to have the argument handling happen beforehand - -// fetch self, verify, and put the arguments in usable order (if self is not handled -// first, arguments can not be reordered with sentinels in place) - PyCallArgs cargs{self, argsin, nargsf, kwds}; - if (!this->ProcessArgs(cargs)) - return nullptr; - -// to re-use the argument handling, simply change the argument array into a tuple (the -// benefits of not allocating the tuple are relatively minor in this case) - Py_ssize_t nargs = CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); - PyObject* args = PyTuple_New(nargs); - for (Py_ssize_t i = 0; i < nargs; ++i) { - Py_INCREF(cargs.fArgs[i]); - PyTuple_SET_ITEM(args, i, cargs.fArgs[i]); - } - -// copy out self as it may have been updated - self = cargs.fSelf; - - if (PyTuple_CheckExact(args) && PyTuple_GET_SIZE(args)) { // case 0. falls through - Py_ssize_t nArgs = PyTuple_GET_SIZE(args); - - bool isAllTuples = true; - Py_ssize_t nArgsTot = 0; - for (Py_ssize_t i = 0; i < nArgs; ++i) { - PyObject* argi = PyTuple_GET_ITEM(args, i); - if (!PyTuple_CheckExact(argi)) { - isAllTuples = false; - break; - } - nArgsTot += PyTuple_GET_SIZE(argi); - } - - if (isAllTuples) { - // copy over the arguments, while filling in the sentinels (case 1. & 2.), with - // just sentinels for the remaining (void) calls (case 2.) - PyObject* newArgs = PyTuple_New(nArgsTot + fNumBases - 1); - Py_ssize_t idx = 0; - for (Py_ssize_t i = 0; i < nArgs; ++i) { - if (i != 0) { - // add sentinel - Py_INCREF(gNullPtrObject); - PyTuple_SET_ITEM(newArgs, idx, gNullPtrObject); - idx += 1; - } - - PyObject* argi = PyTuple_GET_ITEM(args, i); - for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(argi); ++j) { - PyObject* item = PyTuple_GET_ITEM(argi, j); - Py_INCREF(item); - PyTuple_SET_ITEM(newArgs, idx, item); - idx += 1; - } - } - - // add final sentinels as needed - while (idx < (nArgsTot+fNumBases-1)) { - Py_INCREF(gNullPtrObject); - PyTuple_SET_ITEM(newArgs, idx, gNullPtrObject); - idx += 1; - } - - Py_DECREF(args); - args = newArgs; - } else { // case 3. add sentinels - // copy arguments as-is, then add sentinels at the end - PyObject* newArgs = PyTuple_New(PyTuple_GET_SIZE(args) + fNumBases - 1); - for (Py_ssize_t i = 0; i < nArgs; ++i) { - PyObject* item = PyTuple_GET_ITEM(args, i); - Py_INCREF(item); - PyTuple_SET_ITEM(newArgs, i, item); - } - for (Py_ssize_t i = 0; i < fNumBases - 1; ++i) { - Py_INCREF(gNullPtrObject); - PyTuple_SET_ITEM(newArgs, i+nArgs, gNullPtrObject); - } - Py_DECREF(args); - args = newArgs; - } - } - - nargs = PyTuple_GET_SIZE(args); - -// now unroll the new args tuple into a vector of objects - auto argsu = std::unique_ptr{new PyObject*[nargs]}; - for (Py_ssize_t i = 0; i < nargs; ++i) - argsu[i] = PyTuple_GET_ITEM(args, i); - CPyCppyy_PyArgs_t _args = argsu.get(); - - PyObject* result = CPPConstructor::Call(self, _args, nargs, kwds, ctxt); - Py_DECREF(args); - - return result; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPAbstractClassConstructor::Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt) -{ -// do not allow instantiation of abstract classes - if ((self && GetScope() != self->ObjectIsA() - ) || (!self && !(ctxt->fFlags & CallContext::kFromDescr) && \ - CPyCppyy_PyArgs_GET_SIZE(args, nargsf) && CPPInstance_Check(args[0]) && \ - GetScope() != ((CPPInstance*)args[0])->ObjectIsA() - )) { - // happens if a dispatcher is inserted; allow constructor call - return CPPConstructor::Call(self, args, nargsf, kwds, ctxt); - } - - PyErr_Format(PyExc_TypeError, "cannot instantiate abstract class \'%s\'" - " (from derived classes, use super() instead)", - Cppyy::GetScopedFinalName(this->GetScope()).c_str()); - return nullptr; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPNamespaceConstructor::Call( - CPPInstance*&, CPyCppyy_PyArgs_t, size_t, PyObject*, CallContext*) -{ -// do not allow instantiation of namespaces - PyErr_Format(PyExc_TypeError, "cannot instantiate namespace \'%s\'", - Cppyy::GetScopedFinalName(this->GetScope()).c_str()); - return nullptr; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPIncompleteClassConstructor::Call( - CPPInstance*&, CPyCppyy_PyArgs_t, size_t, PyObject*, CallContext*) -{ -// do not allow instantiation of incomplete (forward declared) classes) - PyErr_Format(PyExc_TypeError, "cannot instantiate incomplete class \'%s\'", - Cppyy::GetScopedFinalName(this->GetScope()).c_str()); - return nullptr; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPAllPrivateClassConstructor::Call( - CPPInstance*&, CPyCppyy_PyArgs_t, size_t, PyObject*, CallContext*) -{ -// do not allow instantiation of classes with only private constructors - PyErr_Format(PyExc_TypeError, "cannot instantiate class \'%s\' that has no public constructors", - Cppyy::GetScopedFinalName(this->GetScope()).c_str()); - return nullptr; -} diff --git a/src/CPyCppyy/src/CPPConstructor.h b/src/CPyCppyy/src/CPPConstructor.h deleted file mode 100644 index 6313fb9..0000000 --- a/src/CPyCppyy/src/CPPConstructor.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef CPYCPPYY_CPPCONSTRUCTOR_H -#define CPYCPPYY_CPPCONSTRUCTOR_H - -// Bindings -#include "CPPMethod.h" - - -namespace CPyCppyy { - -class CPPConstructor : public CPPMethod { -public: - using CPPMethod::CPPMethod; - -public: - PyObject* GetDocString() override; - PyObject* Reflex(Cppyy::Reflex::RequestId_t, - Cppyy::Reflex::FormatId_t = Cppyy::Reflex::OPTIMAL) override; - - PyCallable* Clone() override { return new CPPConstructor(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; - -protected: - bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr) override; -}; - - -// specialization for multiple inheritance disambiguation -class CPPMultiConstructor : public CPPConstructor { -public: - CPPMultiConstructor(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method); - CPPMultiConstructor(const CPPMultiConstructor&); - CPPMultiConstructor& operator=(const CPPMultiConstructor&); - -public: - PyCallable* Clone() override { return new CPPMultiConstructor(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; - -private: - Py_ssize_t fNumBases; -}; - - -// specializations of prohibiting constructors -class CPPAbstractClassConstructor : public CPPConstructor { -public: - using CPPConstructor::CPPConstructor; - -public: - PyCallable* Clone() override { return new CPPAbstractClassConstructor(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; -}; - -class CPPNamespaceConstructor : public CPPConstructor { -public: - using CPPConstructor::CPPConstructor; - -public: - PyCallable* Clone() override { return new CPPNamespaceConstructor(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; -}; - -class CPPIncompleteClassConstructor : public CPPConstructor { -public: - using CPPConstructor::CPPConstructor; - -public: - PyCallable* Clone() override { return new CPPIncompleteClassConstructor(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; -}; - -class CPPAllPrivateClassConstructor : public CPPConstructor { -public: - using CPPConstructor::CPPConstructor; - -public: - PyCallable* Clone() override { return new CPPAllPrivateClassConstructor(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPCONSTRUCTOR_H diff --git a/src/CPyCppyy/src/CPPDataMember.cxx b/src/CPyCppyy/src/CPPDataMember.cxx deleted file mode 100644 index ae2e742..0000000 --- a/src/CPyCppyy/src/CPPDataMember.cxx +++ /dev/null @@ -1,433 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPyCppyy/Reflex.h" -#include "Cppyy.h" -#include "PyStrings.h" -#include "CPPDataMember.h" -#include "CPPInstance.h" -#include "CPPEnum.h" -#include "Dimensions.h" -#include "LowLevelViews.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include - -namespace CPyCppyy { - -enum ETypeDetails { - kNone = 0x0000, - kIsStaticData = 0x0001, - kIsConstData = 0x0002, - kIsArrayType = 0x0004, - kIsEnumPrep = 0x0008, - kIsEnumType = 0x0010, - kIsCachable = 0x0020 -}; - -//= CPyCppyy data member as Python property behavior ========================= -static PyObject* dm_get(CPPDataMember* dm, CPPInstance* pyobj, PyObject* /* kls */) -{ -// cache lookup for low level views - if (pyobj && dm->fFlags & kIsCachable) { - CPyCppyy::CI_DatamemberCache_t& cache = pyobj->GetDatamemberCache(); - for (auto it = cache.begin(); it != cache.end(); ++it) { - if (it->first == dm->fOffset) { - if (it->second) { - Py_INCREF(it->second); - return it->second; - } else - cache.erase(it); - break; - } - } - } - - - if (dm->fFlags & (kIsEnumPrep | kIsEnumType)) { - if (dm->fFlags & kIsEnumPrep) { - // still need to do lookup; only ever try this once, then fallback on converter - dm->fFlags &= ~kIsEnumPrep; - - // fDescription contains the full name of the actual enum value object - const Cppyy::TCppScope_t enum_type = Cppyy::GetParentScope(dm->fScope); - const Cppyy::TCppScope_t enum_scope = Cppyy::GetParentScope(enum_type); - - PyObject* pyscope = CreateScopeProxy(enum_scope); - if (pyscope) { - PyObject* pyEnumType = - PyObject_GetAttrString(pyscope, Cppyy::GetFinalName(enum_type).c_str()); - if (pyEnumType) { - PyObject* pyval = - PyObject_GetAttrString(pyEnumType, Cppyy::GetFinalName(dm->fScope).c_str()); - Py_DECREF(pyEnumType); - if (pyval) { - Py_DECREF(dm->fDescription); - dm->fDescription = pyval; - dm->fFlags |= kIsEnumType; - } - } - Py_DECREF(pyscope); - } - if (!(dm->fFlags & kIsEnumType)) - PyErr_Clear(); - } - - if (dm->fFlags & kIsEnumType) { - Py_INCREF(dm->fDescription); - return dm->fDescription; - } - - if (Cppyy::IsEnumConstant(dm->fScope)) { - // anonymous enum - return pyval_from_enum(Cppyy::ResolveEnum(dm->fScope), nullptr, nullptr, dm->fScope); - } - } -// non-initialized or public data accesses through class (e.g. by help()) - void* address = dm->GetAddress(pyobj); - if (!address || (intptr_t)address == -1 /* Cling error */) - return nullptr; - - if (dm->fConverter != 0) { - PyObject* result = dm->fConverter->FromMemory((dm->fFlags & kIsArrayType) ? &address : address); - if (!result) - return result; - - // low level views are expensive to create, so cache them on the object instead - bool isLLView = LowLevelView_CheckExact(result); - if (isLLView && CPPInstance_Check(pyobj)) { - Py_INCREF(result); - pyobj->GetDatamemberCache().push_back(std::make_pair(dm->fOffset, result)); - dm->fFlags |= kIsCachable; - } - - // ensure that the encapsulating class does not go away for the duration - // of the data member's lifetime, if it is a bound type (it doesn't matter - // for builtin types, b/c those are copied over into python types and thus - // end up being "stand-alone") - // TODO: should be done for LLViews as well - else if (pyobj && !(dm->fFlags & kIsStaticData) && CPPInstance_Check(result)) { - if (PyObject_SetAttr(result, PyStrings::gLifeLine, (PyObject*)pyobj) == -1) - PyErr_Clear(); // ignored - } - - return result; - } - - PyErr_Format(PyExc_NotImplementedError, - "no converter available for \"%s\"", dm->GetName().c_str()); - return nullptr; -} - -//----------------------------------------------------------------------------- -static int dm_set(CPPDataMember* dm, CPPInstance* pyobj, PyObject* value) -{ -// Set the value of the C++ datum held. - const int errret = -1; - - if (!value) { - // we're being deleted; fine for namespaces (redo lookup next time), but makes - // no sense for classes/structs - if (!Cppyy::IsNamespace(dm->fEnclosingScope)) { - PyErr_SetString(PyExc_TypeError, "data member deletion is not supported"); - return errret; - } - - // deletion removes the proxy, with the idea that a fresh lookup can be made, - // to support Cling's shadowing of declarations (TODO: the use case here is - // redeclared variables, for which fDescription is indeed th ename; it might - // fail for enums). - return PyObject_DelAttr((PyObject*)Py_TYPE(pyobj), dm->fDescription); - } - -// filter const objects to prevent changing their values - if (dm->fFlags & kIsConstData) { - PyErr_SetString(PyExc_TypeError, "assignment to const data not allowed"); - return errret; - } - -// remove cached low level view, if any (will be restored upon reaeding) - if (dm->fFlags & kIsCachable) { - CPyCppyy::CI_DatamemberCache_t& cache = pyobj->GetDatamemberCache(); - for (auto it = cache.begin(); it != cache.end(); ++it) { - if (it->first == dm->fOffset) { - Py_XDECREF(it->second); - cache.erase(it); - break; - } - } - } - - intptr_t address = (intptr_t)dm->GetAddress(pyobj); - if (!address || address == -1 /* Cling error */) - return errret; - -// for fixed size arrays - void* ptr = (void*)address; - if (dm->fFlags & kIsArrayType) - ptr = &address; - -// actual conversion; return on success - if (dm->fConverter && dm->fConverter->ToMemory(value, ptr, (PyObject*)pyobj)) - return 0; - -// set a python error, if not already done - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_RuntimeError, "property type mismatch or assignment not allowed"); - -// failure ... - return errret; -} - -//= CPyCppyy data member construction/destruction =========================== -static CPPDataMember* dm_new(PyTypeObject* pytype, PyObject*, PyObject*) -{ -// Create and initialize a new property descriptor. - CPPDataMember* dm = (CPPDataMember*)pytype->tp_alloc(pytype, 0); - - dm->fOffset = 0; - dm->fFlags = 0; - dm->fConverter = nullptr; - dm->fEnclosingScope = nullptr; - dm->fDescription = nullptr; - dm->fDoc = nullptr; - - new (&dm->fFullType) std::string{}; - - return dm; -} - -//---------------------------------------------------------------------------- -static void dm_dealloc(CPPDataMember* dm) -{ -// Deallocate memory held by this descriptor. - using namespace std; - if (dm->fConverter && dm->fConverter->HasState()) delete dm->fConverter; - Py_XDECREF(dm->fDescription); // never exposed so no GC necessary - Py_XDECREF(dm->fDoc); - - dm->fFullType.~string(); - - Py_TYPE(dm)->tp_free((PyObject*)dm); -} - -static PyMemberDef dm_members[] = { - {(char*)"__doc__", T_OBJECT, offsetof(CPPDataMember, fDoc), 0, - (char*)"writable documentation"}, - {NULL, 0, 0, 0, nullptr} /* Sentinel */ -}; - -//= CPyCppyy datamember proxy access to internals ============================ -static PyObject* dm_reflex(CPPDataMember* dm, PyObject* args) -{ -// Provide the requested reflection information. - Cppyy::Reflex::RequestId_t request = -1; - Cppyy::Reflex::FormatId_t format = Cppyy::Reflex::OPTIMAL; - if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, &format)) - return nullptr; - - if (request == Cppyy::Reflex::TYPE) { - if (format == Cppyy::Reflex::OPTIMAL || format == Cppyy::Reflex::AS_STRING) - return CPyCppyy_PyText_FromString(dm->fFullType.c_str()); - } else if (request == Cppyy::Reflex::OFFSET) { - if (format == Cppyy::Reflex::OPTIMAL) - return PyLong_FromLong(dm->fOffset); - } - - PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", request, format); - return nullptr; -} - -//---------------------------------------------------------------------------- -static PyMethodDef dm_methods[] = { - {(char*)"__cpp_reflex__", (PyCFunction)dm_reflex, METH_VARARGS, - (char*)"C++ datamember reflection information" }, - {(char*)nullptr, nullptr, 0, nullptr } -}; - - -//= CPyCppyy data member type ================================================ -PyTypeObject CPPDataMember_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.CPPDataMember", // tp_name - sizeof(CPPDataMember), // tp_basicsize - 0, // tp_itemsize - (destructor)dm_dealloc, // tp_dealloc - 0, // tp_vectorcall_offset / tp_print - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / 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, // tp_flags - (char*)"cppyy data member (internal)", // tp_doc - 0, // tp_traverse - 0, // tp_clear - 0, // tp_richcompare - 0, // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - dm_methods, // tp_methods - dm_members, // tp_members - 0, // tp_getset - 0, // tp_base - 0, // tp_dict - (descrgetfunc)dm_get, // tp_descr_get - (descrsetfunc)dm_set, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - (newfunc)dm_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 - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy - - -//- public members ----------------------------------------------------------- -void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, Cppyy::TCppScope_t data) -{ - if (Cppyy::IsLambdaClass(Cppyy::GetDatamemberType(data))) { - fScope = Cppyy::WrapLambdaFromVariable(data); - } else { - fScope = data; - } - - fEnclosingScope = scope; - fOffset = Cppyy::GetDatamemberOffset(fScope, fScope == data ? scope : Cppyy::GetScope("__cppyy_internal_wrap_g")); // XXX: Check back here // TODO: make lazy - fFlags = Cppyy::IsStaticDatamember(fScope) ? kIsStaticData : 0; - - const std::string name = Cppyy::GetFinalName(fScope); - Cppyy::TCppType_t type; - - - if (Cppyy::IsEnumConstant(fScope)) { - type = Cppyy::GetEnumConstantType(fScope); - fFullType = Cppyy::GetTypeAsString(type); - if (fFullType.find("(anonymous)") == std::string::npos && - fFullType.find("(unnamed)") == std::string::npos) { - // repurpose fDescription for lazy lookup of the enum later - fDescription = CPyCppyy_PyText_FromString((fFullType + "::" + name).c_str()); - fFlags |= kIsEnumPrep; - } - type = Cppyy::ResolveType(type); - fFlags |= kIsConstData; - } else { - type = Cppyy::GetDatamemberType(fScope); - fFullType = Cppyy::GetTypeAsString(type); - - // Get the integer type if it's an enum - if (Cppyy::IsEnumType(type)) - type = Cppyy::ResolveType(type); - - if (Cppyy::IsConstVar(fScope)) - fFlags |= kIsConstData; - } - - auto ldims = Cppyy::GetDimensions(type); - std::vector dims(ldims.begin(), ldims.end()); - - if (!dims.empty()) - fFlags |= kIsArrayType; - - if (dims.empty()) - fConverter = CreateConverter(type, 0); - else - fConverter = CreateConverter(type, {(dim_t)dims.size(), dims.data()}); - - if (!(fFlags & kIsEnumPrep)) - fDescription = CPyCppyy_PyText_FromString(name.c_str()); -} - -//----------------------------------------------------------------------------- -void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, const std::string& name, void* address) -{ - fEnclosingScope = scope; - fDescription = CPyCppyy_PyText_FromString(name.c_str()); - fOffset = (intptr_t)address; - fFlags = kIsStaticData | kIsConstData; - fConverter = CreateConverter("internal_enum_type_t"); - fFullType = "unsigned int"; -} - - -//----------------------------------------------------------------------------- -void* CPyCppyy::CPPDataMember::GetAddress(CPPInstance* pyobj) -{ -// class attributes, global properties - if (fFlags & kIsStaticData) - return (void*)fOffset; - -// special case: non-static lookup through class - if (!pyobj) { - PyErr_SetString(PyExc_AttributeError, "attribute access requires an instance"); - return nullptr; - } - -// instance attributes; requires valid object for full address - if (!CPPInstance_Check(pyobj)) { - PyErr_Format(PyExc_TypeError, - "object instance required for access to property \"%s\"", GetName().c_str()); - return nullptr; - } - - void* obj = pyobj->GetObject(); - if (!obj) { - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); - return nullptr; - } - -// the proxy's internal offset is calculated from the enclosing class - ptrdiff_t offset = 0; - Cppyy::TCppScope_t oisa = pyobj->ObjectIsA(); - if (oisa != fEnclosingScope) - offset = Cppyy::GetBaseOffset(oisa, fEnclosingScope, obj, 1 /* up-cast */); - - return (void*)((intptr_t)obj + offset + fOffset); -} - - -//----------------------------------------------------------------------------- -std::string CPyCppyy::CPPDataMember::GetName() -{ - if (fFlags & kIsEnumType) { - PyObject* repr = PyObject_Repr(fDescription); - if (repr) { - std::string res = CPyCppyy_PyText_AsString(repr); - Py_DECREF(repr); - return res; - } - PyErr_Clear(); - return ""; - } else if (fFlags & kIsEnumPrep) { - std::string fullName = CPyCppyy_PyText_AsString(fDescription); - return fullName.substr(fullName.rfind("::")+2, std::string::npos); - } - - return CPyCppyy_PyText_AsString(fDescription); -} diff --git a/src/CPyCppyy/src/CPPDataMember.h b/src/CPyCppyy/src/CPPDataMember.h deleted file mode 100644 index 9241b4d..0000000 --- a/src/CPyCppyy/src/CPPDataMember.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef CPYCPPYY_CPPDATAMEMBER_H -#define CPYCPPYY_CPPDATAMEMBER_H - -// Bindings -#include "Converters.h" - -// Standard -#include - - -namespace CPyCppyy { - -class CPPInstance; - -class CPPDataMember { -public: - void Set(Cppyy::TCppScope_t scope, Cppyy::TCppScope_t var); - void Set(Cppyy::TCppScope_t scope, const std::string& name, void* address); - - std::string GetName(); - void* GetAddress(CPPInstance* pyobj /* owner */); - -public: // public, as the python C-API works with C structs - PyObject_HEAD - intptr_t fOffset; - long fFlags; - Converter* fConverter; - Cppyy::TCppScope_t fScope; - Cppyy::TCppScope_t fEnclosingScope; - PyObject* fDescription; - PyObject* fDoc; - - // TODO: data members should have a unique identifier, just like methods, - // so that reflection information can be recovered post-initialization - std::string fFullType; - -private: // private, as the python C-API will handle creation - CPPDataMember() = delete; -}; - - -//- property proxy for C++ data members, type and type verification ---------- -extern PyTypeObject CPPDataMember_Type; - -template -inline bool CPPDataMember_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &CPPDataMember_Type); -} - -template -inline bool CPPDataMember_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &CPPDataMember_Type; -} - -//- creation ----------------------------------------------------------------- -inline CPPDataMember* CPPDataMember_New( - Cppyy::TCppScope_t scope, Cppyy::TCppScope_t var) -{ -// Create an initialize a new property descriptor, given the C++ datum. - CPPDataMember* pyprop = - (CPPDataMember*)CPPDataMember_Type.tp_new(&CPPDataMember_Type, nullptr, nullptr); - pyprop->Set(scope, var); - return pyprop; -} - -inline CPPDataMember* CPPDataMember_NewConstant( - Cppyy::TCppScope_t scope, const std::string& name, void* address) -{ -// Create an initialize a new property descriptor, given the C++ datum. - CPPDataMember* pyprop = - (CPPDataMember*)CPPDataMember_Type.tp_new(&CPPDataMember_Type, nullptr, nullptr); - pyprop->Set(scope, name, address); - return pyprop; -} - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPDATAMEMBER_H diff --git a/src/CPyCppyy/src/CPPEnum.cxx b/src/CPyCppyy/src/CPPEnum.cxx deleted file mode 100644 index 3012442..0000000 --- a/src/CPyCppyy/src/CPPEnum.cxx +++ /dev/null @@ -1,241 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPEnum.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" -#include - - -//- private helpers ---------------------------------------------------------- -static PyObject* pytype_from_enum_type(const std::string& enum_type) -{ - if (enum_type == "char") - return (PyObject*)&CPyCppyy_PyText_Type; - else if (enum_type == "bool") - return (PyObject*)&PyInt_Type; // can't use PyBool_Type as base - else if (strstr("long", enum_type.c_str())) - return (PyObject*)&PyLong_Type; - return (PyObject*)&PyInt_Type; // covers most cases -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::pyval_from_enum(const std::string& enum_type, PyObject* pytype, - PyObject* btype, Cppyy::TCppScope_t enum_constant) { - long long llval = Cppyy::GetEnumDataValue(enum_constant); - - if (enum_type == "bool" && !(pytype && btype)) { - // no enum class to instantiate; the singletons can not carry attributes - PyObject* result = (bool)llval ? Py_True : Py_False; - Py_INCREF(result); - return result; - } - - PyObject* bval; - if (enum_type == "char") { - char val = (char)llval; - bval = PyUnicode_FromOrdinal((int)val); - } else if (enum_type == "int" || enum_type == "unsigned int") - bval = PyInt_FromLong((long)llval); - else - bval = PyLong_FromLongLong(llval); - - if (!bval) - return nullptr; // e.g. when out of range for small integers - - if (pytype && btype) { - PyObject* args = PyTuple_New(1); - PyTuple_SET_ITEM(args, 0, bval); - bval = ((PyTypeObject*)btype)->tp_new((PyTypeObject*)pytype, args, nullptr); - Py_DECREF(args); - } - return bval; -} - -//- enum methods ------------------------------------------------------------- -static int enum_setattro(PyObject* /* pyclass */, PyObject* /* pyname */, PyObject* /* pyval */) -{ -// Helper to make enums read-only. - PyErr_SetString(PyExc_TypeError, "enum values are read-only"); - return -1; -} - -//---------------------------------------------------------------------------- -static PyObject* enum_repr(PyObject* self) -{ - using namespace CPyCppyy; - - PyObject* kls_scope = PyObject_GetAttr((PyObject*)Py_TYPE(self), PyStrings::gThisModule); - if (!kls_scope) PyErr_Clear(); - PyObject* kls_cppname = PyObject_GetAttr((PyObject*)Py_TYPE(self), PyStrings::gCppName); - if (!kls_cppname) PyErr_Clear(); - PyObject* obj_cppname = PyObject_GetAttr(self, PyStrings::gCppName); - if (!obj_cppname) PyErr_Clear(); - PyObject* obj_str = Py_TYPE(self)->tp_str(self); - - PyObject* repr = nullptr; - if (kls_cppname && obj_cppname && obj_str) { - const std::string resolved = Cppyy::ResolveEnum(PyLong_AsVoidPtr(kls_scope)); - repr = CPyCppyy_PyText_FromFormat("(%s::%s) : (%s) %s", - CPyCppyy_PyText_AsString(kls_cppname), CPyCppyy_PyText_AsString(obj_cppname), - resolved.c_str(), CPyCppyy_PyText_AsString(obj_str)); - } - Py_XDECREF(obj_cppname); - Py_XDECREF(kls_cppname); - - if (repr) { - Py_DECREF(obj_str); - return repr; - } - - return obj_str; -} - - -//---------------------------------------------------------------------------- -// TODO: factor the following lookup with similar codes in Convertes and TemplateProxy.cxx - -static std::unordered_map gCTypesNames = { - {"bool", "c_bool"}, - {"char", "c_char"}, {"wchar_t", "c_wchar"}, - {"std::byte", "c_byte"}, {"int8_t", "c_byte"}, {"uint8_t", "c_ubyte"}, - {"short", "c_short"}, {"int16_t", "c_int16"}, {"unsigned short", "c_ushort"}, {"uint16_t", "c_uint16"}, - {"int", "c_int"}, {"unsigned int", "c_uint"}, - {"long", "c_long"}, {"unsigned long", "c_ulong"}, - {"long long", "c_longlong"}, {"unsigned long long", "c_ulonglong"}}; - -// Both GetCTypesType and GetCTypesPtrType, rely on the ctypes module itself -// caching the types (thus also making them unique), so no ref-count is needed. -// Further, by keeping a ref-count on the module, it won't be off-loaded until -// the 2nd cleanup cycle. -static PyTypeObject* GetCTypesType(const std::string& cppname) -{ - static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept - if (!ctmod) - return nullptr; - - auto nn = gCTypesNames.find(cppname); - if (nn == gCTypesNames.end()) { - PyErr_Format(PyExc_TypeError, "Can not find ctypes type for \"%s\"", cppname.c_str()); - return nullptr; - } - - return (PyTypeObject*)PyObject_GetAttrString(ctmod, nn->second.c_str()); -} - -static PyObject* enum_ctype(PyObject* cls, PyObject* args, PyObject* kwds) -{ - PyObject* pyres = PyObject_GetAttr(cls, CPyCppyy::PyStrings::gUnderlying); - if (!pyres) PyErr_Clear(); - - std::string underlying = pyres ? CPyCppyy_PyText_AsString(pyres) : "int"; - PyTypeObject* ct = GetCTypesType(underlying); - if (!ct) - return nullptr; - - return PyType_Type.tp_call((PyObject*)ct, args, kwds); -} - - -//- creation ----------------------------------------------------------------- -CPyCppyy::CPPEnum* CPyCppyy::CPPEnum_New(const std::string& name, Cppyy::TCppScope_t scope) -{ -// Create a new enum type based on the actual C++ type. Enum values are added to -// the type but may also live in the enclosing scope. - - CPPEnum* pyenum = nullptr; - - Cppyy::TCppScope_t etype = scope; - const std::string& ename = Cppyy::GetScopedFinalName(scope); - if (etype) { - // create new enum type with labeled values in place, with a meta-class - // to make sure the enum values are read-only - const std::string& resolved = Cppyy::ResolveEnum(etype); - PyObject* pyside_type = pytype_from_enum_type(resolved); - PyObject* pymetabases = PyTuple_New(1); - PyObject* btype = (PyObject*)Py_TYPE(pyside_type); - Py_INCREF(btype); - PyTuple_SET_ITEM(pymetabases, 0, btype); - - PyObject* args = Py_BuildValue((char*)"sO{}", (name+"_meta").c_str(), pymetabases); - Py_DECREF(pymetabases); - PyObject* pymeta = PyType_Type.tp_new(Py_TYPE(pyside_type), args, nullptr); - Py_DECREF(args); - - // prepare the base class - PyObject* pybases = PyTuple_New(1); - Py_INCREF(pyside_type); - PyTuple_SET_ITEM(pybases, 0, (PyObject*)pyside_type); - - // create the __cpp_name__ for templates - PyObject* dct = PyDict_New(); - PyObject* pycppname = CPyCppyy_PyText_FromString(ename.c_str()); - PyObject* pycppscope = PyLong_FromVoidPtr(etype.data); - PyDict_SetItem(dct, PyStrings::gCppName, pycppname); - PyDict_SetItem(dct, PyStrings::gThisModule, pycppscope); - Py_DECREF(pycppname); - PyObject* pyresolved = CPyCppyy_PyText_FromString(resolved.c_str()); - PyDict_SetItem(dct, PyStrings::gUnderlying, pyresolved); - Py_DECREF(pyresolved); - - // add the __module__ to allow pickling - std::string modname = TypeManip::extract_namespace(ename); - TypeManip::cppscope_to_pyscope(modname); // :: -> . - if (!modname.empty()) modname = "."+modname; - PyObject* pymodname = CPyCppyy_PyText_FromString(("cppyy.gbl"+modname).c_str()); - PyDict_SetItem(dct, PyStrings::gModule, pymodname); - Py_DECREF(pymodname); - - // create the actual enum class - args = Py_BuildValue((char*)"sOO", name.c_str(), pybases, dct); - Py_DECREF(pybases); - Py_DECREF(dct); - pyenum = ((PyTypeObject*)pymeta)->tp_new((PyTypeObject*)pymeta, args, nullptr); - - // add pythonizations - Utility::AddToClass( - (PyObject*)Py_TYPE(pyenum), "__ctype__", (PyCFunction)enum_ctype, METH_VARARGS | METH_KEYWORDS); - ((PyTypeObject*)pyenum)->tp_repr = enum_repr; - ((PyTypeObject*)pyenum)->tp_str = ((PyTypeObject*)pyside_type)->tp_repr; - - // collect the enum values - std::vector econstants = Cppyy::GetEnumConstants(etype); - bool values_ok = true; - for (auto econstant : econstants) { - PyObject* val = pyval_from_enum(resolved, pyenum, pyside_type, econstant); - if (!val) { - values_ok = false; - break; - } - const std::string& dname = Cppyy::GetFinalName(econstant); - PyObject* pydname = CPyCppyy_PyText_FromString(dname.c_str()); - PyObject_SetAttr(pyenum, pydname, val); - Py_DECREF(pydname); - PyObject* pydcppname = CPyCppyy_PyText_FromString((ename.empty() ? dname : (ename+"::"+dname)).c_str()); - PyObject_SetAttr(val, PyStrings::gCppName, pydcppname); - Py_DECREF(pydcppname); - Py_DECREF(val); - } - - // disable writing onto enum values - ((PyTypeObject*)pymeta)->tp_setattro = enum_setattro; - - // final cleanup - Py_DECREF(args); - Py_DECREF(pymeta); - - if (!values_ok) { - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_ValueError, "could not set some of the enum values"); - Py_DECREF(pyenum); - return nullptr; - } - - } else { - // presumably not a class enum; simply pretend int - Py_INCREF(&PyInt_Type); - pyenum = (PyObject*)&PyInt_Type; - } - - return pyenum; -} diff --git a/src/CPyCppyy/src/CPPEnum.h b/src/CPyCppyy/src/CPPEnum.h deleted file mode 100644 index d1d0e9e..0000000 --- a/src/CPyCppyy/src/CPPEnum.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef CPYCPPYY_CPPENUM_H -#define CPYCPPYY_CPPENUM_H - - -namespace CPyCppyy { - -// CPPEnum does not carry any additional C-side data for now, but can be of -// several types, based on the declared or compile-dependent types chosen. -typedef PyObject CPPEnum; - -//- creation ----------------------------------------------------------------- -CPPEnum* CPPEnum_New(const std::string& name, Cppyy::TCppScope_t scope); - -PyObject* pyval_from_enum(const std::string& enum_type, PyObject* pytype, - PyObject* btype, Cppyy::TCppScope_t enum_constant); -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPENUM_H diff --git a/src/CPyCppyy/src/CPPExcInstance.cxx b/src/CPyCppyy/src/CPPExcInstance.cxx deleted file mode 100644 index 07270fd..0000000 --- a/src/CPyCppyy/src/CPPExcInstance.cxx +++ /dev/null @@ -1,260 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPExcInstance.h" -#include "PyStrings.h" - - -//______________________________________________________________________________ -// Python-side exception proxy objects -// =================================== -// -// Python exceptions need to derive from PyException as it expects a set of -// specific data members. This class exists so that general CPPInstances need -// not carry that overhead. In use, it forwards to the embedded CPPInstance. - - -namespace CPyCppyy { - -//= CPyCppyy exception object proxy construction/destruction ================= -static PyObject* ep_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) -{ -// Create a new exception object proxy (holder only). - PyObject* pyobj = ((PyTypeObject*)PyExc_Exception)->tp_new(subtype, nullptr, nullptr); - if (!pyobj) - return nullptr; - - CPPExcInstance* excobj = (CPPExcInstance*)pyobj; - excobj->fTopMessage = nullptr; - if (args) { - PyObject* ulc = PyObject_GetAttr((PyObject*)subtype, PyStrings::gUnderlying); - excobj->fCppInstance = PyType_Type.tp_call(ulc, args, kwds); - if (!excobj->fCppInstance) { - // if this fails, then the construction may have been attempted from a string - // (e.g. from PyErr_Format); if so, drop the proxy and use fTopMessage instead - PyErr_Clear(); - if (PyTuple_GET_SIZE(args) == 1) { - PyObject* msg = PyTuple_GET_ITEM(args, 0); - if (CPyCppyy_PyText_Check(msg)) { - Py_INCREF(msg); - excobj->fTopMessage = msg; - } - } - } - Py_DECREF(ulc); - } else - excobj->fCppInstance = nullptr; - - return pyobj; -} - -//---------------------------------------------------------------------------- -static int ep_traverse(CPPExcInstance* pyobj, visitproc visit, void* args) -{ -// Garbage collector traverse of held python member objects. - ((PyTypeObject*)PyExc_Exception)->tp_traverse((PyObject*)pyobj, visit, args); - - if (pyobj->fCppInstance) - visit(pyobj->fCppInstance, args); - - if (pyobj->fTopMessage) - visit(pyobj->fTopMessage, args); - - return 0; -} - -//---------------------------------------------------------------------------- -static PyObject* ep_str(CPPExcInstance* self) -{ - if (self->fCppInstance) { - PyObject* what = PyObject_CallMethod((PyObject*)self, (char*)"what", nullptr); - if (what) { - if (self->fTopMessage) { - Py_INCREF(self->fTopMessage); - PyObject* top = self->fTopMessage; - CPyCppyy_PyText_Append(&top, what); - Py_DECREF(what); - what = top; - } - return what; - } - PyErr_Clear(); - return PyObject_Str(self->fCppInstance); - } - - if (self->fTopMessage) { - Py_INCREF(self->fTopMessage); - return self->fTopMessage; - } - - return PyType_Type.tp_str((PyObject*)self); -} - -//---------------------------------------------------------------------------- -static PyObject* ep_repr(CPPExcInstance* self) -{ - if (self->fCppInstance) - return PyObject_Repr(self->fCppInstance); - return PyType_Type.tp_repr((PyObject*)self); -} - -//---------------------------------------------------------------------------- -static void ep_dealloc(CPPExcInstance* pyobj) -{ - PyObject* tmp1 = pyobj->fCppInstance; - PyObject* tmp2 = pyobj->fTopMessage; - - ((PyTypeObject*)PyExc_Exception)->tp_dealloc((PyObject*)pyobj); - Py_CLEAR(tmp1); - Py_CLEAR(tmp2); -} - -//---------------------------------------------------------------------------- -static int ep_clear(CPPExcInstance* pyobj) -{ -// Garbage collector clear of held python member objects. - ((PyTypeObject*)PyExc_Exception)->tp_clear((PyObject*)pyobj); - - Py_CLEAR(pyobj->fCppInstance); - Py_CLEAR(pyobj->fTopMessage); - - return 0; -} - -//= forwarding methods ======================================================= -static PyObject* ep_getattro(CPPExcInstance* self, PyObject* attr) -{ - if (self->fCppInstance) { - PyObject* res = PyObject_GetAttr(self->fCppInstance, attr); - if (res) return res; - PyErr_Clear(); - } - - return ((PyTypeObject*)PyExc_Exception)->tp_getattro((PyObject*)self, attr); -} - -//---------------------------------------------------------------------------- -static int ep_setattro(CPPExcInstance* self, PyObject* attr, PyObject* value) -{ - if (self->fCppInstance) { - int res = PyObject_SetAttr(self->fCppInstance, attr, value); - if (!res) return res; - PyErr_Clear(); - } - - return ((PyTypeObject*)PyExc_Exception)->tp_setattro((PyObject*)self, attr, value); -} - -//---------------------------------------------------------------------------- -static PyObject* ep_richcompare(CPPExcInstance* self, PyObject* other, int op) -{ - return PyObject_RichCompare(self->fCppInstance, other, op); -} - -//---------------------------------------------------------------------------- -static int ep_nonzero(CPPExcInstance* self) -{ - if (self->fCppInstance) - return PyObject_IsTrue(self->fCppInstance); - - return 0; -} - -//----------------------------------------------------------------------------- -static PyNumberMethods ep_as_number = { - 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 - (inquiry)ep_nonzero, // nb_bool (nb_nonzero in p2) - 0, // nb_invert - 0, // nb_lshift - 0, // nb_rshift - 0, // nb_and - 0, // nb_xor - 0, // nb_or - 0, // nb_int - 0, // nb_long (nb_reserved in p3) - 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 -}; - -//= CPyCppyy exception object proxy type ====================================== -PyTypeObject CPPExcInstance_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.CPPExcInstance", // tp_name - sizeof(CPPExcInstance), // tp_basicsize - 0, // tp_itemsize - (destructor)ep_dealloc, // tp_dealloc - 0, // tp_vectorcall_offset / tp_print - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / tp_compare - (reprfunc)ep_repr, // tp_repr - &ep_as_number, // tp_as_number - 0, // tp_as_sequence - 0, // tp_as_mapping - 0, // tp_hash - 0, // tp_call - (reprfunc)ep_str, // tp_str - (getattrofunc)ep_getattro, // tp_getattro - (setattrofunc)ep_setattro, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_BASE_EXC_SUBCLASS | - Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_CHECKTYPES, // tp_flags - (char*)"cppyy exception object proxy (internal)", // tp_doc - (traverseproc)ep_traverse, // tp_traverse - (inquiry)ep_clear, // tp_clear - (richcmpfunc)ep_richcompare, // tp_richcompare - 0, // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - 0, // tp_methods - 0, // tp_members - 0, // tp_getset - (PyTypeObject*)PyExc_Exception, // tp_base - 0, // tp_dict - 0, // tp_descr_get - 0, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - (newfunc)ep_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 - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/src/CPyCppyy/src/CPPExcInstance.h b/src/CPyCppyy/src/CPPExcInstance.h deleted file mode 100644 index 8dd7175..0000000 --- a/src/CPyCppyy/src/CPPExcInstance.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef CPYCPPYY_CPPEXCINSTANCE_H -#define CPYCPPYY_CPPEXCINSTANCE_H - -////////////////////////////////////////////////////////////////////////////// -// // -// CpyCppyy::CPPExceptionInstance // -// // -// Python-side proxy, encapsulaties a C++ exception object. // -// // -////////////////////////////////////////////////////////////////////////////// - -namespace CPyCppyy { - -class CPPExcInstance { -public: - PyBaseExceptionObject fBase; - PyObject* fCppInstance; - PyObject* fTopMessage; -}; - - -//- object proxy type and type verification ---------------------------------- -CPYCPPYY_IMPORT PyTypeObject CPPExcInstance_Type; - -template -inline bool CPPExcInstance_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &CPPExcInstance_Type); -} - -template -inline bool CPPExcInstance_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &CPPExcInstance_Type; -} - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPEXCINSTANCE_H diff --git a/src/CPyCppyy/src/CPPFunction.cxx b/src/CPyCppyy/src/CPPFunction.cxx deleted file mode 100644 index 5cd8956..0000000 --- a/src/CPyCppyy/src/CPPFunction.cxx +++ /dev/null @@ -1,133 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPFunction.h" -#include "CPPInstance.h" - -// Standard -#include - - -//- CFunction helpers ----------------------------------------------------------- -bool CPyCppyy::AdjustSelf(PyCallArgs& cargs) -{ - if (cargs.fNArgsf & PY_VECTORCALL_ARGUMENTS_OFFSET) { // mutation allowed? - std::swap(((PyObject**)cargs.fArgs-1)[0], (PyObject*&)cargs.fSelf); - cargs.fFlags |= PyCallArgs::kSelfSwap; - cargs.fArgs -= 1; - cargs.fNArgsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; - cargs.fNArgsf += 1; - } else { - Py_ssize_t nkwargs = cargs.fKwds ? PyTuple_GET_SIZE(cargs.fKwds) : 0; - Py_ssize_t totalargs = PyVectorcall_NARGS(cargs.fNArgsf)+nkwargs; - PyObject** newArgs = (PyObject**)PyMem_Malloc((totalargs+1) * sizeof(PyObject*)); - if (!newArgs) - return false; - - newArgs[0] = (PyObject*)cargs.fSelf; - if (0 < totalargs) - memcpy((void*)&newArgs[1], cargs.fArgs, totalargs * sizeof(PyObject*)); - cargs.fArgs = newArgs; - cargs.fFlags |= PyCallArgs::kDoFree; - cargs.fNArgsf += 1; - } - return true; -} - -bool CPyCppyy::CPPFunction::ProcessArgs(PyCallArgs& cargs) -{ -// add self as part of the function arguments (means bound member) - if (cargs.fKwds) - return this->ProcessKwds((PyObject*)cargs.fSelf, cargs); - return AdjustSelf(cargs); -} - -//- CPPFunction public members -------------------------------------------------- -PyObject* CPyCppyy::CPPFunction::Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt) -{ -// setup as necessary - if (fArgsRequired == -1 && !this->Initialize(ctxt)) - return nullptr; - -// if function was attached to a class, self will be non-zero and should be -// the first function argument, so reorder - PyCallArgs cargs{self, args, nargsf, kwds}; - if (self || kwds) { - if (!this->ProcessArgs(cargs)) - return nullptr; - } - -// special case, if this method was inserted as a constructor, then self is nullptr -// and it will be the first argument and needs to be used as Python context - if (IsConstructor(ctxt->fFlags) && !ctxt->fPyContext && \ - CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf)) { - ctxt->fPyContext = cargs.fArgs[0]; - } - -// translate the arguments as normal - if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) - return nullptr; - -// execute function - PyObject* result = this->Execute(nullptr, 0, ctxt); - -// special case, if this method was inserted as a constructor, then if no self was -// provided, it will be the first argument and may have been updated - if (IsConstructor(ctxt->fFlags) && result && !cargs.fSelf && \ - CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) && CPPInstance_Check(cargs.fArgs[0])) { - self = (CPPInstance*)cargs.fArgs[0]; - Py_INCREF(self); - } - - return result; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPFunction::GetTypeName() -{ - PyObject* cppname = CPyCppyy_PyText_FromString((GetReturnTypeName() + " (*)").c_str()); - CPyCppyy_PyText_AppendAndDel(&cppname, GetSignature(false /* show_formalargs */)); - return cppname; -} - - -//- CPPReverseBinary private helper --------------------------------------------- -bool CPyCppyy::CPPReverseBinary::ProcessArgs(PyCallArgs& cargs) -{ - if (cargs.fSelf || cargs.fKwds) { - // add self as part of the function arguments (means bound member) - if (!this->CPPFunction::ProcessArgs(cargs)) - return false; - } - -// swap the arguments - std::swap(((PyObject**)cargs.fArgs)[0], ((PyObject**)cargs.fArgs)[1]); - cargs.fFlags |= PyCallArgs::kArgsSwap; - - return true; -} - -//- CPPReverseBinary public members --------------------------------------------- -PyObject* CPyCppyy::CPPReverseBinary::Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt) -{ -// This Call() function is very similar to the one of CPPFunction: only difference is -// that ProcessArgs() is always called. - -// setup as necessary - if (fArgsRequired == -1 && !this->Initialize(ctxt)) - return nullptr; - -// if function was attached to a class, self will be non-zero and should be -// the first function argument, further, the arguments needs swapping - PyCallArgs cargs{self, args, nargsf, kwds}; - if (!this->ProcessArgs(cargs)) - return nullptr; - -// translate the arguments as normal - if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) - return nullptr; - -// execute function - return this->Execute(nullptr, 0, ctxt); -} diff --git a/src/CPyCppyy/src/CPPFunction.h b/src/CPyCppyy/src/CPPFunction.h deleted file mode 100644 index f126f19..0000000 --- a/src/CPyCppyy/src/CPPFunction.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef CPYCPPYY_CPPFUNCTION_H -#define CPYCPPYY_CPPFUNCTION_H - -// Bindings -#include "CPPMethod.h" - - -namespace CPyCppyy { - -// Wrapper for global free/static C++ functions -class CPPFunction : public CPPMethod { -public: - using CPPMethod::CPPMethod; - -public: - PyObject* GetTypeName() override; - -public: - PyCallable* Clone() override { return new CPPFunction(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; - -protected: - bool ProcessArgs(PyCallArgs& args) override; -}; - -// Wrapper for global binary operators that swap arguments -class CPPReverseBinary : public CPPFunction { -public: - using CPPFunction::CPPFunction; - - PyCallable* Clone() override { return new CPPFunction(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; - -protected: - bool ProcessArgs(PyCallArgs& args) override; -}; - -// Helper to add self to the arguments tuple if rebound -bool AdjustSelf(PyCallArgs& cargs); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPFUNCTION_H diff --git a/src/CPyCppyy/src/CPPGetSetItem.cxx b/src/CPyCppyy/src/CPPGetSetItem.cxx deleted file mode 100644 index 5f4eff1..0000000 --- a/src/CPyCppyy/src/CPPGetSetItem.cxx +++ /dev/null @@ -1,104 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPGetSetItem.h" -#include "Executors.h" - - -//- private helpers ----------------------------------------------------------- -static inline -void unroll(CPyCppyy_PyArgs_t packed_args, CPyCppyy_PyArgs_t unrolled, Py_ssize_t nArgs) -{ -// Unroll up to nArgs arguments from packed_args into unrolled. - for (int i = 0, iur = 0; i < nArgs; ++i, ++iur) { - PyObject* item = CPyCppyy_PyArgs_GET_ITEM(packed_args, i); - if (PyTuple_Check(item)) { - for (int j = 0; j < PyTuple_GET_SIZE(item); ++j, ++iur) { - PyObject* subitem = PyTuple_GET_ITEM(item, j); - Py_INCREF(subitem); - CPyCppyy_PyArgs_SET_ITEM(unrolled, iur, subitem); - } - } else { - Py_INCREF(item); - CPyCppyy_PyArgs_SET_ITEM(unrolled, iur, item); - } - } -} - -//- protected members --------------------------------------------------------- -bool CPyCppyy::CPPSetItem::InitExecutor_(Executor*& executor, CallContext*) -{ -// basic call will do - if (!CPPMethod::InitExecutor_(executor)) - return false; - -// check to make sure we're dealing with a RefExecutor - if (!dynamic_cast(executor)) { - PyErr_Format(PyExc_NotImplementedError, - "no __setitem__ handler for return type (%s)", - this->GetReturnTypeName().c_str()); - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::CPPSetItem::ProcessArgs(PyCallArgs& cargs) -{ -// Prepare executor with a buffer for the return value. - Py_ssize_t nArgs = CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); - if (nArgs <= 1) { - PyErr_SetString(PyExc_TypeError, "insufficient arguments to __setitem__"); - return false; - } - -// use the last element of args for assignment upon return, then slice it from -// the (unrolled) actual arguments - ((RefExecutor*)this->GetExecutor())->SetAssignable(CPyCppyy_PyArgs_GET_ITEM(cargs.fArgs, nArgs-1)); - -// see whether any of the arguments is a tuple to be unrolled - Py_ssize_t realsize = 0; - for (Py_ssize_t i = 0; i < nArgs-1; ++i) { - PyObject* item = CPyCppyy_PyArgs_GET_ITEM(cargs.fArgs, i); - realsize += PyTuple_Check(item) ? PyTuple_GET_SIZE(item) : 1; - } - -// unroll any tuples, if present in the arguments - if (realsize != nArgs-1) { - CPyCppyy_PyArgs_t unrolled = (PyObject**)PyMem_Malloc(realsize * sizeof(PyObject*)); - unroll(cargs.fArgs, unrolled, nArgs-1); - cargs.fArgs = unrolled; - cargs.fFlags |= PyCallArgs::kDoFree; - } - cargs.fNArgsf = realsize; - -// continue normal method processing - return CPPMethod::ProcessArgs(cargs); -} - - -//----------------------------------------------------------------------------- -bool CPyCppyy::CPPGetItem::ProcessArgs(PyCallArgs& cargs) -{ -// Unroll tuples for call, otherwise just like regular CPPMethod of __getitem__. - Py_ssize_t nArgs = CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); - -// see whether any of the arguments is a tuple to be unrolled - Py_ssize_t realsize = 0; - for (Py_ssize_t i = 0; i < nArgs; ++i) { - PyObject* item = CPyCppyy_PyArgs_GET_ITEM(cargs.fArgs, i); - realsize += PyTuple_Check(item) ? PyTuple_GET_SIZE(item) : 1; - } - -// unroll any tuples, if present in the arguments - if (realsize != nArgs) { - CPyCppyy_PyArgs_t packed_args = cargs.fArgs; - cargs.fArgs = (PyObject**)PyMem_Malloc(realsize * sizeof(PyObject*)); - cargs.fFlags |= PyCallArgs::kDoFree; - cargs.fNArgsf = realsize; - unroll(packed_args, cargs.fArgs, nArgs); - } - -// continue normal method processing - return CPPMethod::ProcessArgs(cargs); -} diff --git a/src/CPyCppyy/src/CPPGetSetItem.h b/src/CPyCppyy/src/CPPGetSetItem.h deleted file mode 100644 index 3b6f42e..0000000 --- a/src/CPyCppyy/src/CPPGetSetItem.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef CPYCPPYY_CPPGETSETITEM_H -#define CPYCPPYY_CPPGETSETITEM_H - -// Bindings -#include "CPPMethod.h" - - -namespace CPyCppyy { - -class CPPSetItem : public CPPMethod { -public: - using CPPMethod::CPPMethod; - -public: - PyCallable* Clone() override { return new CPPSetItem(*this); } - -protected: - bool ProcessArgs(PyCallArgs& args) override; - bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr) override; -}; - -class CPPGetItem : public CPPMethod { -public: - using CPPMethod::CPPMethod; - -public: - PyCallable* Clone() override { return new CPPGetItem(*this); } - -protected: - bool ProcessArgs(PyCallArgs& args) override; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPGETSETITEM_H diff --git a/src/CPyCppyy/src/CPPInstance.cxx b/src/CPyCppyy/src/CPPInstance.cxx deleted file mode 100644 index a7f0141..0000000 --- a/src/CPyCppyy/src/CPPInstance.cxx +++ /dev/null @@ -1,1106 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPInstance.h" -#include "CPPScope.h" -#include "CPPOverload.h" -#include "Cppyy.h" -#include "MemoryRegulator.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - -#include "CPyCppyy/DispatchPtr.h" - -// Standard -#include -#include - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - extern PyObject* gNullPtrObject; -} - -//______________________________________________________________________________ -// Python-side proxy objects -// ========================= -// -// C++ objects are represented in Python by CPPInstances, which encapsulate -// them using either a pointer (normal), pointer-to-pointer (kIsReference set), -// or as an owned value (kIsValue set). Objects held as reference are never -// owned, otherwise the object is owned if kIsOwner is set. -// -// In addition to encapsulation, CPPInstance offers rudimentary comparison -// operators (based on pointer value and class comparisons); stubs (with lazy -// lookups) for numeric operators; and a representation that prints the C++ -// pointer values, rather than the PyObject* ones as is the default. -// -// Smart pointers have the underlying type as the Python type, but store the -// pointer to the smart pointer. They carry a pointer to the Python-sode smart -// class for dereferencing to get to the actual instance pointer. - - -//- private helpers ---------------------------------------------------------- -namespace { - -// Several specific use cases require extra data in a CPPInstance, but can not -// be a new type. E.g. cross-inheritance derived types are by definition added -// a posterio, and caching of datamembers is up to the datamember, not the -// instance type. To not have normal use of CPPInstance take extra memory, this -// extended data can slot in place of fObject for those use cases. - -struct ExtendedData { - ExtendedData() : fObject(nullptr), fSmartClass(nullptr), fDispatchPtr(nullptr), fArraySize(0) {} - ~ExtendedData() { - for (auto& pc : fDatamemberCache) - Py_XDECREF(pc.second); - fDatamemberCache.clear(); - } - -// the original object reference it replaces (Note: has to be first data member, see usage -// in GetObjectRaw(), e.g. for ptr-ptr passing) - void* fObject; - -// for caching expensive-to-create data member representations - CPyCppyy::CI_DatamemberCache_t fDatamemberCache; - -// for smart pointer types - CPyCppyy::CPPSmartClass* fSmartClass; - -// for back-referencing from Python-derived instances - CPyCppyy::DispatchPtr* fDispatchPtr; - -// for representing T* as a low-level array - Py_ssize_t fArraySize; -}; - -} // unnamed namespace - -#define EXT_OBJECT(pyobj) ((ExtendedData*)((pyobj)->fObject))->fObject -#define DATA_CACHE(pyobj) ((ExtendedData*)((pyobj)->fObject))->fDatamemberCache -#define SMART_CLS(pyobj) ((ExtendedData*)((pyobj)->fObject))->fSmartClass -#define SMART_TYPE(pyobj) SMART_CLS(pyobj)->fCppType -#define DISPATCHPTR(pyobj) ((ExtendedData*)((pyobj)->fObject))->fDispatchPtr -#define ARRAY_SIZE(pyobj) ((ExtendedData*)((pyobj)->fObject))->fArraySize - -inline void CPyCppyy::CPPInstance::CreateExtension() { - if (fFlags & kIsExtended) - return; - void* obj = fObject; - fObject = (void*)new ExtendedData{}; - EXT_OBJECT(this) = obj; - fFlags |= kIsExtended; -} - -void* CPyCppyy::CPPInstance::GetExtendedObject() -{ - if (IsSmart()) { - // We get the raw pointer from the smart pointer each time, in case it has - // changed or has been freed. - return Cppyy::CallR(SMART_CLS(this)->fDereferencer, EXT_OBJECT(this), 0, nullptr); - } - return EXT_OBJECT(this); -} - - -//- public methods ----------------------------------------------------------- -CPyCppyy::CPPInstance* CPyCppyy::CPPInstance::Copy(void* cppinst, PyTypeObject* target) -{ -// create a fresh instance; args and kwds are not used by op_new (see below) - PyObject* self = (PyObject*)this; - if (!target) target = Py_TYPE(self); - PyObject* newinst = target->tp_new(target, nullptr, nullptr); - -// set the C++ instance as given - ((CPPInstance*)newinst)->fObject = cppinst; - -// look for user-provided __cpp_copy__ (not reusing __copy__ b/c of differences -// in semantics: need to pass in the new instance) ... - PyObject* cpy = PyObject_GetAttrString(self, (char*)"__cpp_copy__"); - if (cpy && PyCallable_Check(cpy)) { - PyObject* args = PyTuple_New(1); - Py_INCREF(newinst); - PyTuple_SET_ITEM(args, 0, newinst); - PyObject* res = PyObject_CallObject(cpy, args); - Py_DECREF(args); - Py_DECREF(cpy); - if (res) { - Py_DECREF(res); - return (CPPInstance*)newinst; - } - - // error already set, but need to return nullptr - Py_DECREF(newinst); - return nullptr; - } else if (cpy) - Py_DECREF(cpy); - else - PyErr_Clear(); - -// ... otherwise, shallow copy any Python-side dictionary items - PyObject* selfdct = PyObject_GetAttr(self, PyStrings::gDict); - PyObject* newdct = PyObject_GetAttr(newinst, PyStrings::gDict); - bool bMergeOk = PyDict_Merge(newdct, selfdct, 1) == 0; - Py_DECREF(newdct); - Py_DECREF(selfdct); - - if (!bMergeOk) { - // presume error already set - Py_DECREF(newinst); - return nullptr; - } - - MemoryRegulator::RegisterPyObject((CPPInstance*)newinst, cppinst); - return (CPPInstance*)newinst; -} - - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPInstance::PythonOwns() -{ - fFlags |= kIsOwner; - if ((fFlags & kIsExtended) && DISPATCHPTR(this)) - DISPATCHPTR(this)->PythonOwns(); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPInstance::CppOwns() -{ - fFlags &= ~kIsOwner; - if ((fFlags & kIsExtended) && DISPATCHPTR(this)) - DISPATCHPTR(this)->CppOwns(); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPInstance::SetSmart(PyObject* smart_type) -{ - CreateExtension(); - Py_INCREF(smart_type); - SMART_CLS(this) = (CPPSmartClass*)smart_type; - fFlags |= kIsSmartPtr; -} - -//---------------------------------------------------------------------------- -Cppyy::TCppScope_t CPyCppyy::CPPInstance::GetSmartIsA() const -{ - if (!IsSmart()) return Cppyy::TCppScope_t{}; - return SMART_TYPE(this); -} - -//---------------------------------------------------------------------------- -Cppyy::TCppScope_t CPyCppyy::CPPInstance::GetSmartUnderlyingType() const -{ -// Declared underlying type of the embedded smart pointer ('Base' for a -// std::unique_ptr), independent of any downcast of the dereferenced -// object. - if (!IsSmart()) return Cppyy::TCppScope_t{}; - return SMART_CLS(this)->fUnderlyingType; -} - -//---------------------------------------------------------------------------- -CPyCppyy::CI_DatamemberCache_t& CPyCppyy::CPPInstance::GetDatamemberCache() -{ -// Return the cache for expensive data objects (and make extended as necessary) - CreateExtension(); - return DATA_CACHE(this); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPInstance::SetDispatchPtr(void* ptr) -{ -// Set up the dispatch pointer for memory management - CreateExtension(); - DISPATCHPTR(this) = (DispatchPtr*)ptr; -} - - -//---------------------------------------------------------------------------- -void CPyCppyy::op_dealloc_nofree(CPPInstance* pyobj) { -// Destroy the held C++ object, if owned; does not deallocate the proxy. - - Cppyy::TCppScope_t klass = pyobj->ObjectIsA(false /* check_smart */); - void*& cppobj = pyobj->GetObjectRaw(); - - if (pyobj->fFlags & CPPInstance::kIsRegulated) - MemoryRegulator::UnregisterPyObject(pyobj, (PyObject*)Py_TYPE((PyObject*)pyobj)); - - if (cppobj && (pyobj->fFlags & CPPInstance::kIsOwner)) { - if (pyobj->fFlags & CPPInstance::kIsValue) { - Cppyy::CallDestructor(klass, cppobj); - Cppyy::Deallocate(klass, cppobj); - } else - Cppyy::Destruct(klass, cppobj); - } - cppobj = nullptr; - - if (pyobj->IsExtended()) delete (ExtendedData*)pyobj->fObject; - pyobj->fFlags = CPPInstance::kNoWrapConv; -} - - -namespace CPyCppyy { - -//---------------------------------------------------------------------------- -static int op_traverse(CPPInstance* /*pyobj*/, visitproc /*visit*/, void* /*arg*/) -{ - return 0; -} - - -//= CPyCppyy object proxy null-ness checking ================================= -static int op_nonzero(CPPInstance* self) -{ -// Null of the proxy is determined by null-ness of the held C++ object. - if (!self->GetObject()) - return 0; - -// If the object is valid, then the normal Python behavior is to allow __len__ -// to determine truth. However, that function is defined in typeobject.c and only -// installed if tp_as_number exists w/o the nb_nonzero/nb_bool slot filled in, so -// it can not be called directly. Instead, since we're only ever dealing with -// CPPInstance derived objects, ignore length from sequence or mapping and call -// the __len__ method, if any, directly. - - PyObject* pylen = PyObject_CallMethodObjArgs((PyObject*)self, PyStrings::gLen, NULL); - if (!pylen) { - PyErr_Clear(); - return 1; // since it's still a valid object - } - - int result = PyObject_IsTrue(pylen); - Py_DECREF(pylen); - return result; -} - -//= CPyCppyy object explicit destruction ===================================== -static PyObject* op_destruct(CPPInstance* self) -{ -// User access to force deletion of the object. Needed in case of a true -// garbage collector (like in PyPy), to allow the user control over when -// the C++ destructor is called. This method requires that the C++ object -// is owned (no-op otherwise). - op_dealloc_nofree(self); - Py_RETURN_NONE; -} - -//= CPyCppyy object dispatch support ========================================= -static PyObject* op_dispatch(PyObject* self, PyObject* args, PyObject* /* kdws */) -{ -// User-side __dispatch__ method to allow selection of a specific overloaded -// method. The actual selection is in the __overload__() method of CPPOverload. - PyObject *mname = nullptr, *sigarg = nullptr; - if (!PyArg_ParseTuple(args, const_cast("O!O!:__dispatch__"), - &CPyCppyy_PyText_Type, &mname, &CPyCppyy_PyText_Type, &sigarg)) - return nullptr; - -// get the named overload - PyObject* pymeth = PyObject_GetAttr(self, mname); - if (!pymeth) - return nullptr; - -// get the '__overload__' method to allow overload selection - PyObject* pydisp = PyObject_GetAttrString(pymeth, const_cast("__overload__")); - if (!pydisp) { - Py_DECREF(pymeth); - return nullptr; - } - -// finally, call dispatch to get the specific overload - PyObject* oload = PyObject_CallFunctionObjArgs(pydisp, sigarg, nullptr); - Py_DECREF(pydisp); - Py_DECREF(pymeth); - return oload; -} - -//= CPyCppyy smart pointer support =========================================== -static PyObject* op_get_smartptr(CPPInstance* self) -{ - if (!self->IsSmart()) { - // TODO: more likely should raise - Py_RETURN_NONE; - } - - return CPyCppyy::BindCppObjectNoCast(self->GetSmartObject(), SMART_TYPE(self), CPPInstance::kNoWrapConv); -} - -//= pointer-as-array support for legacy C code =============================== -void CPyCppyy::CPPInstance::CastToArray(Py_ssize_t sz) -{ - CreateExtension(); - fFlags |= kIsArray; - ARRAY_SIZE(this) = sz; -} - -Py_ssize_t CPyCppyy::CPPInstance::ArrayLength() { - if (!(fFlags & kIsArray)) - return -1; - return (Py_ssize_t)ARRAY_SIZE(this); -} - -static PyObject* op_reshape(CPPInstance* self, PyObject* shape) -{ -// Allow the user to fix up the actual (type-strided) size of the buffer. - if (!PyTuple_Check(shape) || PyTuple_GET_SIZE(shape) != 1) { - PyErr_SetString(PyExc_TypeError, "tuple object of size 1 expected"); - return nullptr; - } - - long sz = PyLong_AsLong(PyTuple_GET_ITEM(shape, 0)); - if (sz <= 0) { - PyErr_SetString(PyExc_ValueError, "array length must be positive"); - return nullptr; - } - - self->CastToArray(sz); - - Py_RETURN_NONE; -} - -static PyObject* op_item(CPPInstance* self, Py_ssize_t idx) -{ -// In C, it is common to represent an array of structs as a pointer to the first -// object in the array. If the caller indexes a pointer to an object that does not -// define indexing, then highly likely such C-style indexing is the goal. Just -// like C, this is potentially unsafe, so caveat emptor. - - if (!(self->fFlags & (CPPInstance::kIsReference | CPPInstance::kIsArray))) { - PyErr_Format(PyExc_TypeError, "%s object does not support indexing", Py_TYPE(self)->tp_name); - return nullptr; - } - - if (idx < 0) { - // this is debatable, and probably should not care, but the use case is pretty - // circumscribed anyway, so might as well keep the functionality simple - PyErr_SetString(PyExc_IndexError, "negative indices not supported for array of structs"); - return nullptr; - } - - if (self->fFlags & CPPInstance::kIsArray) { - Py_ssize_t maxidx = ARRAY_SIZE(self); - if (0 <= maxidx && maxidx <= idx) { - PyErr_SetString(PyExc_IndexError, "index out of range"); - return nullptr; - } - } - - unsigned flags = 0; size_t sz = sizeof(void*); - if (self->fFlags & CPPInstance::kIsPtrPtr) { - flags = CPPInstance::kIsReference; - } else { - sz = Cppyy::SizeOf(((CPPClass*)Py_TYPE(self))->fCppType); - } - - uintptr_t address = (uintptr_t)(flags ? self->GetObjectRaw() : self->GetObject()); - void* indexed_obj = (void*)(address+(uintptr_t)(idx*sz)); - - return BindCppObjectNoCast(indexed_obj, ((CPPClass*)Py_TYPE(self))->fCppType, flags); -} - -//- sequence methods -------------------------------------------------------- -static PySequenceMethods op_as_sequence = { - 0, // sq_length - 0, // sq_concat - 0, // sq_repeat - (ssizeargfunc)op_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 -}; - -PyCFunction &CPPInstance::ReduceMethod() { - static PyCFunction reducer = nullptr; - return reducer; -} - -PyObject *op_reduce(PyObject *self, PyObject *args) -{ - auto &reducer = CPPInstance::ReduceMethod(); - if (!reducer) { - PyErr_SetString(PyExc_NotImplementedError, ""); - return nullptr; - } - return reducer(self, args); -} - - -//---------------------------------------------------------------------------- -static PyMethodDef op_methods[] = { - {(char*)"__destruct__", (PyCFunction)op_destruct, METH_NOARGS, - (char*)"call the C++ destructor"}, - {(char*)"__dispatch__", (PyCFunction)op_dispatch, METH_VARARGS, - (char*)"dispatch to selected overload"}, - {(char*)"__smartptr__", (PyCFunction)op_get_smartptr, METH_NOARGS, - (char*)"get associated smart pointer, if any"}, - {(char*)"__reduce__", (PyCFunction)op_reduce, METH_NOARGS, - (char*)"reduce method for serialization"}, - {(char*)"__reshape__", (PyCFunction)op_reshape, METH_O, - (char*)"cast pointer to 1D array type"}, - {(char*)nullptr, nullptr, 0, nullptr} -}; - - -//= CPyCppyy object proxy construction/destruction =========================== -static CPPInstance* op_new(PyTypeObject* subtype, PyObject*, PyObject*) -{ -// Create a new object proxy (holder only). - CPPInstance* pyobj = (CPPInstance*)subtype->tp_alloc(subtype, 0); - pyobj->fObject = nullptr; - pyobj->fFlags = CPPInstance::kNoWrapConv; - - return pyobj; -} - -//---------------------------------------------------------------------------- -static void op_dealloc(CPPInstance* pyobj) -{ -// Remove (Python-side) memory held by the object proxy. - PyObject_GC_UnTrack((PyObject*)pyobj); - op_dealloc_nofree(pyobj); - PyObject_GC_Del((PyObject*)pyobj); -} - -//---------------------------------------------------------------------------- -static int op_clear(CPPInstance* pyobj) -{ -// Garbage collector clear of held python member objects; this is a good time -// to safely remove this object from the memory regulator. - if (pyobj->fFlags & CPPInstance::kIsRegulated) - MemoryRegulator::UnregisterPyObject(pyobj, (PyObject*)Py_TYPE((PyObject*)pyobj)); - - return 0; -} - -//---------------------------------------------------------------------------- -static inline PyObject* eqneq_binop(CPPClass* klass, PyObject* self, PyObject* obj, int op) -{ - using namespace Utility; - -// special case for C++11 style nullptr - if (obj == gNullPtrObject) { - void* rawcpp = ((CPPInstance*)self)->GetObjectRaw(); - switch (op) { - case Py_EQ: - if (rawcpp == nullptr) Py_RETURN_TRUE; - Py_RETURN_FALSE; - case Py_NE: - if (rawcpp != nullptr) Py_RETURN_TRUE; - Py_RETURN_FALSE; - default: - return nullptr; // not implemented - } - } - - if (!klass->fOperators) - klass->fOperators = new PyOperators{}; - - bool flipit = false; - PyObject* binop = op == Py_EQ ? klass->fOperators->fEq : klass->fOperators->fNe; - if (!binop) { - binop = op == Py_EQ ? klass->fOperators->fNe : klass->fOperators->fEq; - if (binop) flipit = true; - } - if (!binop) { - const char* cppop = op == Py_EQ ? "==" : "!="; - PyCallable* pyfunc = FindBinaryOperator(self, obj, cppop); - if (pyfunc) binop = (PyObject*)CPPOverload_New(cppop, pyfunc); - else { - Py_INCREF(Py_None); - binop = Py_None; - } - // sets the operator to Py_None if not found, indicating that search was done - if (op == Py_EQ) klass->fOperators->fEq = binop; - else klass->fOperators->fNe = binop; - } - - if (!binop || binop == Py_None) return nullptr; - - PyObject* args = PyTuple_New(1); - Py_INCREF(obj); PyTuple_SET_ITEM(args, 0, obj); -// since this overload is "ours", don't have to worry about rebinding - ((CPPOverload*)binop)->fSelf = (CPPInstance*)self; - PyObject* result = CPPOverload_Type.tp_call(binop, args, nullptr); - ((CPPOverload*)binop)->fSelf = nullptr; - Py_DECREF(args); - - if (!result) { - PyErr_Clear(); - return nullptr; - } - -// successful result, but may need to reverse the outcome - if (!flipit) return result; - - int istrue = PyObject_IsTrue(result); - Py_DECREF(result); - if (istrue) { - Py_RETURN_FALSE; - } - Py_RETURN_TRUE; -} - -static inline void* cast_actual(void* obj) { - void* address = ((CPPInstance*)obj)->GetObject(); - if (((CPPInstance*)obj)->fFlags & CPPInstance::kIsActual) - return address; - - Cppyy::TCppScope_t klass = ((CPPClass*)Py_TYPE((PyObject*)obj))->fCppType; - Cppyy::TCppScope_t clActual = klass /* XXX: Cppyy::GetActualClass(klass, address) */; - if (clActual && clActual != klass) { - intptr_t offset = Cppyy::GetBaseOffset( - clActual, klass, address, -1 /* down-cast */, true /* report errors */); - if (offset != -1) address = (void*)((intptr_t)address + offset); - } - - return address; -} - - -#define CPYCPPYY_ORDERED_OPERATOR_STUB(op, ometh, label) \ - if (!ometh) { \ - PyCallable* pyfunc = Utility::FindBinaryOperator((PyObject*)self, other, #op);\ - if (pyfunc) \ - ometh = (PyObject*)CPPOverload_New(#label, pyfunc); \ - } \ - meth = ometh; - -static PyObject* op_richcompare(CPPInstance* self, PyObject* other, int op) -{ -// Rich set of comparison objects; currently supported: -// == : Py_EQ -// != : Py_NE -// -// < : Py_LT -// <= : Py_LE -// > : Py_GT -// >= : Py_GE -// - -// associative comparison operators - if (op == Py_EQ || op == Py_NE) { - // special case for None to compare True to a null-pointer - if ((PyObject*)other == Py_None && !self->fObject) { - const char *msg = - "\nComparison of C++ nullptr objects with `None` is no longer supported." - "\n\nPreviously, `None` was treated as equivalent to a null C++ pointer, " - "but this led to confusing behavior where `x == None` could be True even though `x is None` was False." - "\n\nTo test whether a C++ object is null or not, check its truth value instead:" - "\n if not x: ..." - "\nor use `x is None` to explicitly check for Python None." - "\n"; - - PyErr_SetString(PyExc_TypeError, msg); - return NULL; // stop execution, raise TypeError - } - - // use C++-side operators if available - PyObject* result = eqneq_binop((CPPClass*)Py_TYPE(self), (PyObject*)self, other, op); - if (!result && CPPInstance_Check(other)) - result = eqneq_binop((CPPClass*)Py_TYPE(other), other, (PyObject*)self, op); - if (result) return result; - - // default behavior: type + held pointer value defines identity; if both are - // CPPInstance objects, perform an additional autocast if need be - bool bIsEq = false; - - if ((Py_TYPE(self) == Py_TYPE(other) && \ - self->GetObject() == ((CPPInstance*)other)->GetObject())) { - // direct match - bIsEq = true; - } else if (CPPInstance_Check(other)) { - // try auto-cast match - void* addr1 = cast_actual(self); - void* addr2 = cast_actual(other); - bIsEq = addr1 && addr2 && (addr1 == addr2); - } - - if ((op == Py_EQ && bIsEq) || (op == Py_NE && !bIsEq)) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; - } - -// ordered comparison operators - else if (op == Py_LT || op == Py_LE || op == Py_GT || op == Py_GE) { - CPPClass* klass = (CPPClass*)Py_TYPE(self); - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators{}; - PyObject* meth = nullptr; - - switch (op) { - case Py_LT: - CPYCPPYY_ORDERED_OPERATOR_STUB(<, klass->fOperators->fLt, __lt__) - break; - case Py_LE: - CPYCPPYY_ORDERED_OPERATOR_STUB(<=, klass->fOperators->fLe, __le__) - break; - case Py_GT: - CPYCPPYY_ORDERED_OPERATOR_STUB(>, klass->fOperators->fGt, __gt__) - break; - case Py_GE: - CPYCPPYY_ORDERED_OPERATOR_STUB(>=, klass->fOperators->fGe, __ge__) - break; - } - - if (!meth) { - PyErr_SetString(PyExc_NotImplementedError, ""); - return nullptr; - } - - return PyObject_CallFunctionObjArgs(meth, (PyObject*)self, other, nullptr); - } - - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; -} - -//---------------------------------------------------------------------------- -static PyObject* op_repr(CPPInstance* self) -{ -// Build a representation string of the object proxy that shows the address -// of the C++ object that is held, as well as its type. - PyObject* pyclass = (PyObject*)Py_TYPE(self); - if (CPPScope_Check(pyclass) && (((CPPScope*)pyclass)->fFlags & CPPScope::kIsPython)) - return PyBaseObject_Type.tp_repr((PyObject*)self); - PyObject* modname = PyObject_GetAttr(pyclass, PyStrings::gModule); - - Cppyy::TCppScope_t klass = self->ObjectIsA(); - std::string clName = klass ? Cppyy::GetFinalName(klass) : ""; - if (self->fFlags & CPPInstance::kIsPtrPtr) - clName.append("**"); - else if (self->fFlags & CPPInstance::kIsReference) - clName.append("*"); - - PyObject* repr = nullptr; - if (self->IsSmart()) { - std::string smartPtrName = Cppyy::GetScopedFinalName(SMART_TYPE(self)); - repr = CPyCppyy_PyText_FromFormat( - const_cast("<%s.%s object at %p held by %s at %p>"), - CPyCppyy_PyText_AsString(modname), clName.c_str(), - self->GetObject(), smartPtrName.c_str(), self->GetObjectRaw()); - } else { - repr = CPyCppyy_PyText_FromFormat(const_cast("<%s.%s object at %p>"), - CPyCppyy_PyText_AsString(modname), clName.c_str(), self->GetObject()); - } - - Py_DECREF(modname); - return repr; -} - -//---------------------------------------------------------------------------- -static inline Py_hash_t CPyCppyy_PyLong_AsHash_t(PyObject* obj) -{ -// Cannot use PyLong_AsSize_t here, as it cuts of at PY_SSIZE_T_MAX, which is -// only half of the max of std::size_t returned by the hash. - if (sizeof(unsigned long) >= sizeof(size_t)) - return (Py_hash_t)PyLong_AsUnsignedLong(obj); - return (Py_hash_t)PyLong_AsUnsignedLongLong(obj); -} - -static Py_hash_t op_hash(CPPInstance* self) -{ -// Try to locate an std::hash for this type and use that if it exists - CPPClass* klass = (CPPClass*)Py_TYPE(self); - if (klass->fOperators && klass->fOperators->fHash) { - Py_hash_t h = 0; - PyObject* hashval = PyObject_CallFunctionObjArgs(klass->fOperators->fHash, (PyObject*)self, nullptr); - if (hashval) { - h = CPyCppyy_PyLong_AsHash_t(hashval); - Py_DECREF(hashval); - } - return h; - } - - Cppyy::TCppScope_t stdhash = Cppyy::GetFullScope("std::hash<"+Cppyy::GetScopedFinalName(self->ObjectIsA())+">"); - if (stdhash) { - PyObject* hashcls = CreateScopeProxy(stdhash); - PyObject* dct = PyObject_GetAttr(hashcls, PyStrings::gDict); - bool isValid = PyMapping_HasKeyString(dct, (char*)"__call__"); - Py_DECREF(dct); - if (isValid) { - PyObject* hashobj = PyObject_CallObject(hashcls, nullptr); - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators{}; - klass->fOperators->fHash = hashobj; - Py_DECREF(hashcls); - - Py_hash_t h = 0; - PyObject* hashval = PyObject_CallFunctionObjArgs(hashobj, (PyObject*)self, nullptr); - if (hashval) { - h = CPyCppyy_PyLong_AsHash_t(hashval); - Py_DECREF(hashval); - } - return h; - } - Py_DECREF(hashcls); - } - -// if not valid, simply reset the hash function so as to not kill performance - ((PyTypeObject*)Py_TYPE(self))->tp_hash = PyBaseObject_Type.tp_hash; - return PyBaseObject_Type.tp_hash((PyObject*)self); -} - -//---------------------------------------------------------------------------- -static PyObject* op_str_internal(PyObject* pyobj, PyObject* lshift, bool isBound) -{ - static Cppyy::TCppScope_t sOStringStreamID = Cppyy::GetFullScope("std::ostringstream"); - std::ostringstream s; - PyObject* pys = BindCppObjectNoCast(&s, sOStringStreamID); - Py_INCREF(pys); -// for py3 and later, a ref-count of 2 is okay to consider the object temporary, but -// in this case, we can't lose our existing ostrinstring (otherwise, we'd have to peel -// it out of the return value, if moves are used - Py_INCREF(pys); - - PyObject* res; - if (isBound) res = PyObject_CallFunctionObjArgs(lshift, pys, NULL); - else res = PyObject_CallFunctionObjArgs(lshift, pys, pyobj, NULL); - - Py_DECREF(pys); - Py_DECREF(pys); - - if (res) { - Py_DECREF(res); - return CPyCppyy_PyText_FromString(s.str().c_str()); - } - - return nullptr; -} - - -static inline bool ScopeFlagCheck(CPPInstance* self, CPPScope::EFlags flag) { - return ((CPPScope*)Py_TYPE((PyObject*)self))->fFlags & flag; -} - -static inline void ScopeFlagSet(CPPInstance* self, CPPScope::EFlags flag) { - ((CPPScope*)Py_TYPE((PyObject*)self))->fFlags |= flag; -} - -static PyObject* op_str(CPPInstance* self) -{ -// There are three possible options here: -// 1. Available operator<< to convert through an ostringstream -// 2. Cling's pretty printing -// 3. Generic printing as done in op_repr -// -// Additionally, there may be a mapped __str__ from the C++ type defining `operator char*` -// or `operator const char*`. Results are memoized for performance reasons. - -// 0. Protect against trying to print a typed nullptr object through an insertion operator - if (!self->GetObject()) - return op_repr(self); - -// 1. Available operator<< to convert through an ostringstream - if (!ScopeFlagCheck(self, CPPScope::kNoOSInsertion)) { - for (PyObject* pyname : {PyStrings::gLShift, PyStrings::gLShiftC, (PyObject*)0x01, (PyObject*)0x02}) { - if (pyname == PyStrings::gLShift && ScopeFlagCheck(self, CPPScope::kGblOSInsertion)) - continue; - - else if (pyname == (PyObject*)0x01) { - // normal lookup failed; attempt lazy install of global operator<<(ostream&, type&) - std::string rcname = Utility::ClassName((PyObject*)self); - Cppyy::TCppScope_t rnsID = Cppyy::GetScope(TypeManip::extract_namespace(rcname)); - PyCallable* pyfunc = Utility::FindBinaryOperator("std::ostream&", rcname, "<<", rnsID); - if (!pyfunc) - continue; - - Utility::AddToClass((PyObject*)Py_TYPE((PyObject*)self), "__lshiftc__", pyfunc); - - pyname = PyStrings::gLShiftC; - ScopeFlagSet(self, CPPScope::kGblOSInsertion); - - } else if (pyname == (PyObject*)0x02) { - // TODO: the only reason this still exists, is b/c friend functions are otherwise not found - // TODO: ToString() still leaks ... - const std::string& pretty = Cppyy::ToString(self->ObjectIsA(), self->GetObject()); - if (!pretty.empty()) - return CPyCppyy_PyText_FromString(pretty.c_str()); - continue; - } - - PyObject* lshift = PyObject_GetAttr( - pyname == PyStrings::gLShift ? (PyObject*)self : (PyObject*)Py_TYPE((PyObject*)self), pyname); - - if (lshift) { - PyObject* result = op_str_internal((PyObject*)self, lshift, pyname == PyStrings::gLShift); - Py_DECREF(lshift); - if (result) - return result; - } - - PyErr_Clear(); - } - - // failed ostream printing; don't try again - ScopeFlagSet(self, CPPScope::kNoOSInsertion); - } - -// 2. Cling's pretty printing (not done through backend for performance reasons) - if (!ScopeFlagCheck(self, CPPScope::kNoPrettyPrint)) { - static PyObject* printValue = nullptr; - if (!printValue) { - PyObject* gbl = PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppyy.gbl"); - PyObject* cl = PyObject_GetAttrString(gbl, (char*)"cling"); - printValue = PyObject_GetAttrString(cl, (char*)"printValue"); - Py_DECREF(cl); - // gbl is borrowed - if (printValue) { - Py_DECREF(printValue); // make borrowed - if (!PyCallable_Check(printValue)) - printValue = nullptr; // unusable ... - } - if (!printValue) // unlikely - ScopeFlagSet(self, CPPScope::kNoPrettyPrint); - } - - if (printValue) { - // as printValue only works well for templates taking pointer arguments, we'll - // have to force the issue by working with a by-ptr object - Cppyy::TCppObject_t cppobj = self->GetObjectRaw(); - PyObject* byref = (PyObject*)self; - if (!(self->fFlags & CPPInstance::kIsReference)) { - byref = BindCppObjectNoCast((Cppyy::TCppObject_t)&cppobj, - self->ObjectIsA(), CPPInstance::kIsReference | CPPInstance::kNoMemReg); - } else { - Py_INCREF(byref); - } - - // explicit template lookup - PyObject* clName = CPyCppyy_PyText_FromString(Utility::ClassName((PyObject*)self).c_str()); - PyObject* OL = PyObject_GetItem(printValue, clName); - Py_DECREF(clName); - - PyObject* pretty = OL ? PyObject_CallFunctionObjArgs(OL, byref, nullptr) : nullptr; - Py_XDECREF(OL); - Py_DECREF(byref); - - PyObject* result = nullptr; - if (pretty) { - const std::string& pv = *(std::string*)((CPPInstance*)pretty)->GetObject(); - if (!pv.empty() && pv.find("@0x") == std::string::npos) - result = CPyCppyy_PyText_FromString(pv.c_str()); - Py_DECREF(pretty); - if (result) return result; - } - - PyErr_Clear(); - } - - // if not available/specialized, don't try again - ScopeFlagSet(self, CPPScope::kNoPrettyPrint); - } - -// 3. Generic printing as done in op_repr - return op_repr(self); -} - -//----------------------------------------------------------------------------- -static PyObject* op_getownership(CPPInstance* pyobj, void*) -{ - return PyBool_FromLong((long)(pyobj->fFlags & CPPInstance::kIsOwner)); -} - -//----------------------------------------------------------------------------- -static int op_setownership(CPPInstance* pyobj, PyObject* value, void*) -{ -// Set the ownership (True is python-owns) for the given object. - long shouldown = PyLong_AsLong(value); - if (shouldown == -1 && PyErr_Occurred()) { - PyErr_SetString(PyExc_ValueError, "__python_owns__ should be either True or False"); - return -1; - } - - (bool)shouldown ? pyobj->PythonOwns() : pyobj->CppOwns(); - - return 0; -} - - -//----------------------------------------------------------------------------- -static PyGetSetDef op_getset[] = { - {(char*)"__python_owns__", (getter)op_getownership, (setter)op_setownership, - (char*)"If true, python manages the life time of this object", nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} -}; - - -//= CPyCppyy type number stubs to allow dynamic overrides ===================== -#define CPYCPPYY_STUB_BODY(name, op) \ - bool previously_resolved_overload = (bool)meth; \ - if (!meth) { \ - PyErr_Clear(); \ - PyCallable* pyfunc = Utility::FindBinaryOperator(left, right, #op); \ - if (pyfunc) meth = (PyObject*)CPPOverload_New(#name, pyfunc); \ - else { \ - PyErr_SetString(PyExc_NotImplementedError, ""); \ - return nullptr; \ - } \ - } \ - PyObject* res = PyObject_CallFunctionObjArgs(meth, cppobj, other, nullptr);\ - if (!res && previously_resolved_overload) { \ - /* try again, in case (left, right) are different types than before */ \ - PyErr_Clear(); \ - PyCallable* pyfunc = Utility::FindBinaryOperator(left, right, #op); \ - if (pyfunc) ((CPPOverload*&)meth)->AdoptMethod(pyfunc); \ - else { \ - PyErr_SetString(PyExc_NotImplementedError, ""); \ - return nullptr; \ - } \ - /* use same overload with newly added function */ \ - res = PyObject_CallFunctionObjArgs(meth, cppobj, other, nullptr); \ - } \ - return res; - - -#define CPYCPPYY_OPERATOR_STUB(name, op, ometh) \ -static PyObject* op_##name##_stub(PyObject* left, PyObject* right) \ -{ \ -/* placeholder to lazily install and forward to 'ometh' if available */ \ - CPPClass* klass = (CPPClass*)Py_TYPE(left); \ - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators{}; \ - PyObject*& meth = ometh; \ - PyObject *cppobj = left, *other = right; \ - CPYCPPYY_STUB_BODY(name, op) \ -} - -#define CPYCPPYY_ASSOCIATIVE_OPERATOR_STUB(name, op, lmeth, rmeth) \ -static PyObject* op_##name##_stub(PyObject* left, PyObject* right) \ -{ \ -/* placeholder to lazily install and forward do '(l/r)meth' if available */ \ - CPPClass* klass; PyObject** pmeth; \ - PyObject *cppobj, *other; \ - if (CPPInstance_Check(left)) { \ - klass = (CPPClass*)Py_TYPE(left); \ - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators{};\ - pmeth = &lmeth; cppobj = left; other = right; \ - } else if (CPPInstance_Check(right)) { \ - klass = (CPPClass*)Py_TYPE(right); \ - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators{};\ - pmeth = &rmeth; cppobj = right; other = left; \ - } else { \ - PyErr_SetString(PyExc_NotImplementedError, ""); \ - return nullptr; \ - } \ - PyObject*& meth = *pmeth; \ - CPYCPPYY_STUB_BODY(name, op) \ -} - -#define CPYCPPYY_UNARY_OPERATOR(name, op, label) \ -static PyObject* op_##name##_stub(PyObject* pyobj) \ -{ \ -/* placeholder to lazily install unary operators */ \ - PyCallable* pyfunc = Utility::FindUnaryOperator((PyObject*)Py_TYPE(pyobj), #op);\ - if (pyfunc && Utility::AddToClass((PyObject*)Py_TYPE(pyobj), #label, pyfunc))\ - return PyObject_CallMethod(pyobj, (char*)#label, nullptr); \ - PyErr_SetString(PyExc_NotImplementedError, ""); \ - return nullptr; \ -} - -CPYCPPYY_ASSOCIATIVE_OPERATOR_STUB(add, +, klass->fOperators->fLAdd, klass->fOperators->fRAdd) -CPYCPPYY_OPERATOR_STUB( sub, -, klass->fOperators->fSub) -CPYCPPYY_ASSOCIATIVE_OPERATOR_STUB(mul, *, klass->fOperators->fLMul, klass->fOperators->fRMul) -CPYCPPYY_OPERATOR_STUB( div, /, klass->fOperators->fDiv) -CPYCPPYY_UNARY_OPERATOR(neg, -, __neg__) -CPYCPPYY_UNARY_OPERATOR(pos, +, __pos__) -CPYCPPYY_UNARY_OPERATOR(invert, ~, __invert__) - -//----------------------------------------------------------------------------- -static PyNumberMethods op_as_number = { - (binaryfunc)op_add_stub, // nb_add - (binaryfunc)op_sub_stub, // nb_subtract - (binaryfunc)op_mul_stub, // nb_multiply - 0, // nb_remainder - 0, // nb_divmod - 0, // nb_power - (unaryfunc)op_neg_stub, // nb_negative - (unaryfunc)op_pos_stub, // nb_positive - 0, // nb_absolute - (inquiry)op_nonzero, // nb_bool (nb_nonzero in p2) - (unaryfunc)op_invert_stub, // nb_invert - 0, // nb_lshift - 0, // nb_rshift - 0, // nb_and - 0, // nb_xor - 0, // nb_or - 0, // nb_int - 0, // nb_long (nb_reserved in p3) - 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 - (binaryfunc)op_div_stub, // 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 -}; - - -//= CPyCppyy object proxy type =============================================== -PyTypeObject CPPInstance_Type = { - PyVarObject_HEAD_INIT(&CPPScope_Type, 0) - (char*)"cppyy.CPPInstance", // tp_name - sizeof(CPPInstance), // tp_basicsize - 0, // tp_itemsize - (destructor)op_dealloc, // tp_dealloc - 0, // tp_vectorcall_offset / tp_print - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / tp_compare - (reprfunc)op_repr, // tp_repr - &op_as_number, // tp_as_number - &op_as_sequence, // tp_as_sequence - 0, // tp_as_mapping - (hashfunc)op_hash, // tp_hash - 0, // tp_call - (reprfunc)op_str, // tp_str - 0, // tp_getattro - 0, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_HAVE_GC, // tp_flags - (char*)"cppyy object proxy (internal)", // tp_doc - (traverseproc)op_traverse, // tp_traverse - (inquiry)op_clear, // tp_clear - (richcmpfunc)op_richcompare, // tp_richcompare - 0, // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - op_methods, // tp_methods - 0, // tp_members - op_getset, // 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 - (newfunc)op_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 - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/src/CPyCppyy/src/CPPInstance.h b/src/CPyCppyy/src/CPPInstance.h deleted file mode 100644 index 3482cd0..0000000 --- a/src/CPyCppyy/src/CPPInstance.h +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef CPYCPPYY_CPPINSTANCE_H -#define CPYCPPYY_CPPINSTANCE_H - -////////////////////////////////////////////////////////////////////////////// -// // -// CpyCppyy::CPPInstance // -// // -// Python-side proxy, encapsulaties a C++ object. // -// // -////////////////////////////////////////////////////////////////////////////// - -// Bindings -#ifndef Py_LIMITED_API -#include "CPPScope.h" -#endif -#include "Cppyy.h" -#include "CallContext.h" // for Parameter - -// Standard -#include -#include - - -namespace CPyCppyy { - -typedef std::vector> CI_DatamemberCache_t; - -class CPPInstance { -public: - enum EFlags { - kDefault = 0x0000, - kNoWrapConv = 0x0001, // use type as-is (eg. no smart ptr wrap) - kIsOwner = 0x0002, // Python instance owns C++ object/memory - kIsExtended = 0x0004, // has extended data - kIsValue = 0x0008, // was created from a by-value return - kIsReference = 0x0010, // represents one indirection - kIsArray = 0x0020, // represents an array of objects - kIsSmartPtr = 0x0040, // is or embeds a smart pointer - kIsPtrPtr = 0x0080, // represents two indirections - kIsRValue = 0x0100, // can be used as an r-value - kIsLValue = 0x0200, // can be used as an l-value - kNoMemReg = 0x0400, // do not register with memory regulator - kIsRegulated = 0x0800, // is registered with memory regulator - kIsActual = 0x1000, // has been downcasted to actual type - kHasLifeLine = 0x2000, // has a life line set - }; - -public: // public, as the python C-API works with C structs - PyObject_HEAD - void* fObject; - uint32_t fFlags; - -public: -// construction (never done directly) - CPPInstance() = delete; - - void Set(void* address, EFlags flags = kDefault); - CPPInstance* Copy(void* cppinst, PyTypeObject* target = nullptr); - -// state checking - bool IsExtended() const { return fFlags & kIsExtended; } - bool IsSmart() const { return fFlags & kIsSmartPtr; } - -// access to C++ pointer and type - void* GetObject(); - void*& GetObjectRaw() { return IsExtended() ? *(void**) fObject : fObject; } - Cppyy::TCppScope_t ObjectIsA(bool check_smart = true) const; - -// memory management: ownership of the underlying C++ object - void PythonOwns(); - void CppOwns(); - -// data member cache - CI_DatamemberCache_t& GetDatamemberCache(); - -// smart pointer management - void SetSmart(PyObject* smart_type); - void* GetSmartObject() { return GetObjectRaw(); } - Cppyy::TCppScope_t GetSmartIsA() const; - Cppyy::TCppScope_t GetSmartUnderlyingType() const; - -// cross-inheritance dispatch - void SetDispatchPtr(void*); - -// redefine pointer to object as fixed-size array - void CastToArray(Py_ssize_t sz); - Py_ssize_t ArrayLength(); - -// implementation of the __reduce__ method: doesn't wrap any function by -// default but can be re-assigned by libraries that add C++ object -// serialization support, like ROOT - static PyCFunction &ReduceMethod(); - - -private: - void CreateExtension(); - void* GetExtendedObject(); -}; - - -//- public methods ----------------------------------------------------------- -inline void CPPInstance::Set(void* address, EFlags flags) -{ -// Initialize the proxy with the pointer value 'address.' - if (flags != kDefault) fFlags = flags; - GetObjectRaw() = address; -} - -//---------------------------------------------------------------------------- -inline void* CPPInstance::GetObject() -{ -// Retrieve a pointer to the held C++ object. - if (!IsExtended()) { - if (fObject && (fFlags & kIsReference)) - return *(reinterpret_cast(fObject)); - else - return fObject; // may be null - } else - return GetExtendedObject(); -} - -#ifndef Py_LIMITED_API -//---------------------------------------------------------------------------- -inline Cppyy::TCppScope_t CPPInstance::ObjectIsA(bool check_smart) const -{ -// Retrieve the C++ type identifier (or raw type if smart). - if (check_smart || !IsSmart()) return ((CPPClass*)Py_TYPE(this))->fCppType; - return GetSmartIsA(); -} -#endif - -//- object proxy type and type verification ---------------------------------- -CPYCPPYY_IMPORT PyTypeObject CPPInstance_Type; - -#ifndef Py_LIMITED_API -template -inline bool CPPInstance_Check(T* object) -{ -// Short-circuit the type check by checking tp_new which all generated subclasses -// of CPPInstance inherit. - return object && \ - (Py_TYPE(object)->tp_new == CPPInstance_Type.tp_new || \ - PyObject_TypeCheck(object, &CPPInstance_Type)); -} -#endif - -template -inline bool CPPInstance_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &CPPInstance_Type; -} - - -//- helper for memory regulation (no PyTypeObject equiv. member in p2.2) ----- -void op_dealloc_nofree(CPPInstance*); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPINSTANCE_H diff --git a/src/CPyCppyy/src/CPPMethod.cxx b/src/CPyCppyy/src/CPPMethod.cxx deleted file mode 100644 index 5bbf30c..0000000 --- a/src/CPyCppyy/src/CPPMethod.cxx +++ /dev/null @@ -1,1132 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPMethod.h" -#include "CPPExcInstance.h" -#include "CPPInstance.h" -#include "Converters.h" -#include "Cppyy.h" -#include "Executors.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "SignalTryCatch.h" -#include "Utility.h" - -#include "CPyCppyy/PyException.h" - -// Standard -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -//- data and local helpers --------------------------------------------------- -namespace CPyCppyy { - extern PyObject* gThisModule; - extern PyObject* gBusException; - extern PyObject* gSegvException; - extern PyObject* gIllException; - extern PyObject* gAbrtException; -} - - -//- public helper ------------------------------------------------------------ -CPyCppyy::PyCallArgs::~PyCallArgs() { - if (fFlags & kSelfSwap) // if self swap, fArgs has been offset by -1 - std::swap((PyObject*&)fSelf, ((PyObject**)fArgs)[0]); - - if (fFlags & kIsOffset) fArgs -= 1; - - if (fFlags & kDoItemDecref) { - for (Py_ssize_t iarg = 0; iarg < CPyCppyy_PyArgs_GET_SIZE(fArgs, fNArgsf); ++iarg) - Py_DECREF(fArgs[iarg]); - } - - if (fFlags & kDoFree) - PyMem_Free((void*)fArgs); - else if (fFlags & kArgsSwap) { - // if self swap, fArgs has been offset by -1 - int offset = (fFlags & kSelfSwap) ? 1 : 0; - std::swap(((PyObject**)fArgs+offset)[0], ((PyObject**)fArgs+offset)[1]); - } -} - - -//- private helpers ---------------------------------------------------------- -inline bool CPyCppyy::CPPMethod::VerifyArgCount_(Py_ssize_t actual) -{ -// actual number of arguments must be between required and max args - Py_ssize_t maxargs = (Py_ssize_t)fConverters.size(); - - if (maxargs != actual) { - if (actual < (Py_ssize_t)fArgsRequired) { - SetPyError_(CPyCppyy_PyText_FromFormat( - "takes at least %d arguments (%zd given)", fArgsRequired, actual)); - return false; - } else if (maxargs < actual) { - SetPyError_(CPyCppyy_PyText_FromFormat( - "takes at most %zd arguments (%zd given)", maxargs, actual)); - return false; - } - } - return true; -} - -//---------------------------------------------------------------------------- -inline void CPyCppyy::CPPMethod::Copy_(const CPPMethod& /* other */) -{ -// fScope and fMethod handled separately - -// do not copy caches - fExecutor = nullptr; - fArgIndices = nullptr; - fArgsRequired = -1; -} - -//---------------------------------------------------------------------------- -inline void CPyCppyy::CPPMethod::Destroy_() -{ -// destroy executor and argument converters - if (fExecutor && fExecutor->HasState()) delete fExecutor; - fExecutor = nullptr; - - for (auto p : fConverters) { - if (p && p->HasState()) delete p; - } - fConverters.clear(); - - delete fArgIndices; fArgIndices = nullptr; - fArgsRequired = -1; -} - -//---------------------------------------------------------------------------- -inline PyObject* CPyCppyy::CPPMethod::ExecuteFast( - void* self, ptrdiff_t offset, CallContext* ctxt) -{ -// call into C++ through fExecutor; abstracted out from Execute() to prevent some -// code duplication with ProtectedCall() - PyObject* result = nullptr; - - try { // C++ try block - result = fExecutor->Execute(fMethod, Cppyy::TCppObject_t((void*)((intptr_t)self+offset)), ctxt); - } catch (PyException&) { - ctxt->fFlags |= CallContext::kPyException; - result = nullptr; // error already set - } catch (std::exception& e) { - // attempt to set the exception to the actual type, to allow catching with the Python C++ type - static Cppyy::TCppScope_t exc_type = Cppyy::GetFullScope("std::exception"); - - ctxt->fFlags |= CallContext::kCppException; - - PyObject* pyexc_type = nullptr; - PyObject* pyexc_obj = nullptr; - - // TODO: factor this code with the same in ProxyWrappers (and cache it there to be able to - // look up based on TCppType_t): - Cppyy::TCppScope_t actual = Cppyy::GetActualClass(exc_type, &e); - const std::string& finalname = Cppyy::GetScopedFinalName(actual); - const std::string& parentname = TypeManip::extract_namespace(finalname); - PyObject* parent = CreateScopeProxy(parentname); - if (parent) { - pyexc_type = PyObject_GetAttrString(parent, - parentname.empty() ? finalname.c_str() : finalname.substr(parentname.size()+2, std::string::npos).c_str()); - Py_DECREF(parent); - } - - if (pyexc_type) { - // create a copy of the exception (TODO: factor this code with the same in ProxyWrappers) - PyObject* pyclass = CPyCppyy::GetScopeProxy(actual); - PyObject* source = BindCppObjectNoCast(&e, actual); - PyObject* pyexc_copy = PyObject_CallFunctionObjArgs(pyclass, source, nullptr); - Py_DECREF(source); - Py_DECREF(pyclass); - if (pyexc_copy) { - pyexc_obj = CPPExcInstance_Type.tp_new((PyTypeObject*)pyexc_type, nullptr, nullptr); - ((CPPExcInstance*)pyexc_obj)->fCppInstance = (PyObject*)pyexc_copy; - } else - PyErr_Clear(); - } else - PyErr_Clear(); - - if (pyexc_type && pyexc_obj) { - PyErr_SetObject(pyexc_type, pyexc_obj); - Py_DECREF(pyexc_obj); - Py_DECREF(pyexc_type); - } else { - PyErr_Format(PyExc_Exception, "%s (C++ exception)", e.what()); - Py_XDECREF(pyexc_obj); - Py_XDECREF(pyexc_type); - } - - result = nullptr; - } catch (...) { - // don't set the kCppException flag here, as there is basically no useful - // extra information to be had and caller has to catch Exception either way - PyErr_SetString(PyExc_Exception, "unhandled, unknown C++ exception"); - result = nullptr; - } - -// TODO: covers the PyException throw case, which does not seem to work on Windows, so -// instead leaves the error be -#ifdef _WIN32 - if (PyErr_Occurred()) { - Py_XDECREF(result); - result = nullptr; - } -#endif - - return result; -} - -//---------------------------------------------------------------------------- -inline PyObject* CPyCppyy::CPPMethod::ExecuteProtected( - void* self, ptrdiff_t offset, CallContext* ctxt) -{ -// helper code to prevent some code duplication; this code embeds a "try/catch" -// block that saves the call environment for restoration in case of an otherwise -// fatal signal - PyObject* result = 0; - - CLING_EXCEPTION_TRY { // copy call environment to be able to jump back on signal - result = ExecuteFast(self, offset, ctxt); - } CLING_EXCEPTION_CATCH(excode) { - // report any outstanding Python exceptions first - if (PyErr_Occurred()) { - std::cerr << "Python exception outstanding during C++ longjmp:" << std::endl; - PyErr_Print(); - std::cerr << std::endl; - } - - // unfortunately, the excodes are not the ones from signal.h, but enums from TSysEvtHandler.h - if (excode == 0) - PyErr_SetString(gBusException, "bus error in C++; program state was reset"); - else if (excode == 1) - PyErr_SetString(gSegvException, "segfault in C++; program state was reset"); - else if (excode == 4) - PyErr_SetString(gIllException, "illegal instruction in C++; program state was reset"); - else if (excode == 5) - PyErr_SetString(gAbrtException, "abort from C++; program state was reset"); - else if (excode == 12) - PyErr_SetString(PyExc_FloatingPointError, "floating point exception in C++; program state was reset"); - else - PyErr_SetString(PyExc_SystemError, "problem in C++; program state was reset"); - result = 0; - } CLING_EXCEPTION_ENDTRY; - - return result; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::InitConverters_() -{ -// build buffers for argument dispatching - const size_t nArgs = Cppyy::GetMethodNumArgs(fMethod); - fConverters.resize(nArgs); - -// setup the dispatch cache - for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - Cppyy::TCppType_t fullType = Cppyy::GetMethodArgType(fMethod, iarg); - Converter* conv = CreateConverter(fullType); - if (!conv) { - PyErr_Format(PyExc_TypeError, "argument type %s not handled", - Cppyy::GetTypeAsString(fullType).c_str()); - return false; - } - - fConverters[iarg] = conv; - } - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::InitExecutor_(Executor*& executor, CallContext* /* ctxt */) -{ -// install executor conform to the return type - executor = - (bool)fMethod == true ? CreateExecutor(Cppyy::GetMethodReturnType(fMethod)) \ - : CreateExecutor(Cppyy::GetScopedFinalName(fScope)); - - if (!executor) - return false; - - return true; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::CPPMethod::GetSignatureString(bool fa) -{ -// built a signature representation (used for doc strings) - return Cppyy::GetMethodSignature(fMethod, fa); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) -{ -// Helper to report errors in a consistent format (derefs msg). -// -// Handles three cases: -// 1. No Python error occured yet: -// Set a new TypeError with the message "msg" and the docstring of this -// C++ method to give some context. -// 2. A C++ exception has occured: -// Augment the exception message with the docstring of this method -// 3. A Python exception has occured with a traceback: -// Do nothing, Python exceptions are already informative enough -// 4. If the Python exception has no traceback hinting to an internally set error stack, -// extract its message and wrap it with C++ method docstring context. - -#if PY_VERSION_HEX >= 0x030c0000 - PyObject *evalue = PyErr_Occurred() ? PyErr_GetRaisedException() : nullptr; - PyObject *etype = evalue ? (PyObject *)Py_TYPE(evalue) : nullptr; -#else - PyObject *etype = nullptr; - PyObject *evalue = nullptr; - PyObject *etrace = nullptr; - - if (PyErr_Occurred()) { - PyErr_Fetch(&etype, &evalue, &etrace); - } -#endif - - const bool isCppExc = evalue && PyType_IsSubtype((PyTypeObject*)etype, &CPPExcInstance_Type); - std::string details; - - // If the error is not a CPPExcInstance and has a traceback, the error from - // Python itself is already complete and messing with it would only make it - // less informative. - // Just restore and return. - if (evalue && !isCppExc) { -#if PY_VERSION_HEX >= 0x030c0000 - PyObject* tb = PyException_GetTraceback(evalue); - if (tb) { - Py_DECREF(tb); - PyErr_SetRaisedException(evalue); - return; - } -#else - if (etrace) { - PyErr_Restore(etype, evalue, etrace); - return; - } -#endif - // no traceback, extract its message and fall through - PyObject* descr = PyObject_Str(evalue); - if (descr) { - details = CPyCppyy_PyText_AsString(descr); - Py_DECREF(descr); - } - } - - PyObject* doc = GetDocString(); - const char* cdoc = CPyCppyy_PyText_AsString(doc); - const char* cmsg = msg ? CPyCppyy_PyText_AsString(msg) : nullptr; - PyObject* errtype = etype ? etype : PyExc_TypeError; - PyObject* pyname = PyObject_GetAttr(errtype, PyStrings::gName); - const char* cname = pyname ? CPyCppyy_PyText_AsString(pyname) : "Exception"; - - if (!isCppExc) { - // this is the case where no Python error has occured yet, or an internal - // one without traceback set a new error with context - if (details.empty()) { - PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, cmsg ? cmsg : ""); - } else if (cmsg) { - PyErr_Format(errtype, "%s =>\n %s: %s (%s)", cdoc, cname, cmsg, details.c_str()); - } else { - PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, details.c_str()); - } - } else { - // augment the top message with context information - PyObject *&topMessage = ((CPPExcInstance*)evalue)->fTopMessage; - Py_XDECREF(topMessage); - if (msg) { - topMessage = CPyCppyy_PyText_FromFormat("%s =>\n %s: %s | ", cdoc, cname, cmsg); - } else { - topMessage = CPyCppyy_PyText_FromFormat("%s =>\n %s: ", cdoc, cname); - } - // restore the updated error -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(evalue); -#else - PyErr_Restore(etype, evalue, etrace); -#endif - } - - Py_XDECREF(pyname); - Py_DECREF(doc); - Py_XDECREF(msg); -} - -extern std::unordered_map TypeReductionMap; - -//- constructors and destructor ---------------------------------------------- -CPyCppyy::CPPMethod::CPPMethod( - Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method) : - fMethod(method), fScope(scope), fExecutor(nullptr), fArgIndices(nullptr), - fArgsRequired(-1) -{ - Cppyy::TCppType_t result = Cppyy::ResolveType(Cppyy::GetMethodReturnType(fMethod)); - if (TypeReductionMap.find(result) != TypeReductionMap.end()) - fMethod = Cppyy::ReduceReturnType(fMethod, TypeReductionMap[result]); - if (result && Cppyy::IsLambdaClass(result)) - fMethod = Cppyy::AdaptFunctionForLambdaReturn(fMethod); -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMethod::CPPMethod(const CPPMethod& other) : - PyCallable(other), fMethod(other.fMethod), fScope(other.fScope) -{ - Copy_(other); -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMethod& CPyCppyy::CPPMethod::operator=(const CPPMethod& other) -{ - if (this != &other) { - Destroy_(); - Copy_(other); - fScope = other.fScope; - fMethod = other.fMethod; - } - - return *this; -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPMethod::~CPPMethod() -{ - Destroy_(); -} - - -//- public members ----------------------------------------------------------- -/** - * @brief Construct a Python string from the method's prototype - * - * @param fa Show formal arguments of the method - * @return PyObject* A Python string with the full method prototype, namespaces included. - * - * For example, given: - * - * int foo(int x); - * - * namespace a { - * namespace b { - * namespace c { - * int foo(int x); - * }}} - * - * This function returns: - * - * 'int foo(int x)' - * 'int a::b::c::foo(int x)' - */ -PyObject* CPyCppyy::CPPMethod::GetPrototype(bool fa) -{ - // Gather the fully qualified final scope of the method. This includes - // all namespaces up to the one where the method is declared, for example: - // namespace a { namespace b { void foo(); }} - // gives - // a::b - std::string finalscope = Cppyy::GetScopedFinalName(fScope); - return CPyCppyy_PyText_FromFormat("%s%s %s%s", - (Cppyy::IsStaticMethod(fMethod) ? "static " : ""), - Cppyy::GetMethodReturnTypeAsString(fMethod).c_str(), - Cppyy::GetScopedFinalName(Cppyy::TCppScope_t(fMethod.data)).c_str(), - GetSignatureString(fa).c_str()); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::GetTypeName() -{ - PyObject* cppname = CPyCppyy_PyText_FromString( - (GetReturnTypeName() + \ - " (" + (fScope ? Cppyy::GetScopedFinalName(fScope) + "::*)" : "*)")).c_str()); - CPyCppyy_PyText_AppendAndDel(&cppname, GetSignature(false /* show_formalargs */)); - return cppname; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::Reflex(Cppyy::Reflex::RequestId_t request, Cppyy::Reflex::FormatId_t format) -{ -// C++ reflection tooling for methods. - - if (request == Cppyy::Reflex::RETURN_TYPE) { - std::string rtn = GetReturnTypeName(); - Cppyy::TCppScope_t scope = nullptr; - if (format == Cppyy::Reflex::OPTIMAL || format == Cppyy::Reflex::AS_TYPE) - scope = Cppyy::GetScope(rtn); - - if (format == Cppyy::Reflex::AS_STRING || (format == Cppyy::Reflex::OPTIMAL && !scope)) - return CPyCppyy_PyText_FromString(rtn.c_str()); - else if (format == Cppyy::Reflex::AS_TYPE || format == Cppyy::Reflex::OPTIMAL) { - if (scope) return CreateScopeProxy(scope); - /* TODO: builtins as type */ - } - } - - return PyCallable::Reflex(request, format); -} - -//---------------------------------------------------------------------------- -int CPyCppyy::CPPMethod::GetPriority() -{ -// To help with overload selection, methods are given a priority based on the -// affinity of Python and C++ types. Priority only matters for methods that have -// an equal number of arguments and types that are possible substitutes (the -// normal selection mechanisms would simply distinguish them otherwise). - -// The following types are ordered, in favor (variants implicit): -// -// bool >> long >> int >> short -// double >> long double >> float -// const char* >> char -// -// Further, all integer types are preferred over floating point b/c int to float -// is allowed implicitly, float to int is not. -// -// Special cases that are disliked include void* and unknown/incomplete types. -// Also, moves are preferred over references. std::initializer_list is not a nice -// conversion candidate either, but needs to be higher priority to mix well with -// implicit conversions. -// TODO: extend this to favour classes that are not bases. -// TODO: profile this method (it's expensive, but should be called too often) - - int priority = 0; - - const size_t nArgs = Cppyy::GetMethodNumArgs(fMethod); - for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - const std::string aname = Cppyy::GetMethodArgTypeAsString(fMethod, iarg); - // FIXME: convert the string comparisons with comparison to the underlying - // type: - // Cppyy::TCppType_t type = Cppyy::GetMethodArgType(fMethod, iarg); - - if (Cppyy::IsBuiltin(aname)) { - // complex type (note: double penalty: for complex and the template type) - if (strstr(aname.c_str(), "std::complex")) - priority -= 10; // prefer double, float, etc. over conversion - - // integer types - if (strstr(aname.c_str(), "bool")) - priority += 1; // bool over int (does accept 1 and 0) - else if (strstr(aname.c_str(), "long long")) - priority += -5; // will very likely fit - else if (strstr(aname.c_str(), "long")) - priority += -10; // most affine integer type - // no need to compare with int; leave at zero - else if (strstr(aname.c_str(), "short")) - priority += -50; // not really relevant as a type - - // floating point types (note all numbers lower than integer types) - else if (strstr(aname.c_str(), "float")) - priority += -100; // not really relevant as a type - else if (strstr(aname.c_str(), "long double")) - priority += -90; // fits double with least loss of precision - else if (strstr(aname.c_str(), "double")) - priority += -80; // most affine floating point type - - // string/char types - else if (strstr(aname.c_str(), "char") && aname[aname.size()-1] != '*') - priority += -60; // prefer (const) char* over char - - // oddball - else if (strstr(aname.c_str(), "void*")) - priority -= 1000; // void*/void** shouldn't be too greedy - - } else { - // This is a user-defined type (class, struct, enum, etc.). - - // There's a bit of hysteresis here for templates: once GetScope() is called, their - // IsComplete() succeeds, the other way around it does not. Since GetPriority() is - // likely called several times in a sort, the GetScope() _must_ come first, or - // different GetPriority() calls may return different results (since the 2nd time, - // GetScope() will have been called from the first), killing the stable_sort. - - // prefer more derived classes - const std::string& clean_name = TypeManip::clean_type(aname, false); - Cppyy::TCppScope_t scope = Cppyy::GetScope(clean_name); - if (scope) - priority += static_cast(Cppyy::GetNumBasesLongestBranch(scope)); - - if (Cppyy::IsEnumScope(scope)) - priority -= 100; - - // a couple of special cases as explained above - if (aname.find("initializer_list") != std::string::npos) { - priority += 150; // needed for proper implicit conversion rules - } else if (aname.rfind("&&", aname.size()-2) != std::string::npos) { - priority += 100; // prefer moves over other ref/ptr - } else if (scope && !Cppyy::IsComplete(scope)) { - // class is known, but no dictionary available, 2 more cases: * and & - if (aname[aname.size() - 1] == '&') - priority += -5000; - else - priority += -2000; // prefer pointer passing over reference - } - } - } - -// prefer methods w/o optional arguments b/c ones with optional arguments are easier to -// select by providing the optional arguments explicitly - priority += ((int)Cppyy::GetMethodReqArgs(fMethod) - (int)nArgs); - -// add a small penalty to prefer non-const methods over const ones for get/setitem - if (Cppyy::IsConstMethod(fMethod) && Cppyy::GetName(Cppyy::TCppScope_t(fMethod.data)) == "operator[]") - priority += -10; - - // constructors are prefered - if (Cppyy::IsConstructor(fMethod)) - priority += 100; - - return priority; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::IsGreedy() -{ -// Methods will all void*-like arguments should be sorted after template -// instanstations, so that they don't greedily take over pointers to object. -// GetPriority() is too heavy-handed, as it will pull in all the argument -// types, so use this cheaper check. - const size_t nArgs = Cppyy::GetMethodReqArgs(fMethod); - if (!nArgs) return false; - - for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - const std::string aname = Cppyy::GetMethodArgTypeAsString(fMethod, iarg); - if (aname.find("void *") != 0) - return false; - } - return true; -} - - -//---------------------------------------------------------------------------- -int CPyCppyy::CPPMethod::GetMaxArgs() -{ - return (int)Cppyy::GetMethodNumArgs(fMethod); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::GetCoVarNames() -{ -// Build a tuple of the argument types/names. - int co_argcount = (int)GetMaxArgs() /* +1 for self */; - -// TODO: static methods need no 'self' (but is harmless otherwise) - - PyObject* co_varnames = PyTuple_New(co_argcount+1 /* self */); - PyTuple_SET_ITEM(co_varnames, 0, CPyCppyy_PyText_FromString("self")); - for (int iarg = 0; iarg < co_argcount; ++iarg) { - std::string argrep = Cppyy::GetMethodArgTypeAsString(fMethod, iarg); - const std::string& parname = Cppyy::GetMethodArgName(fMethod, iarg); - if (!parname.empty()) { - argrep += " "; - argrep += parname; - } - - PyObject* pyspec = CPyCppyy_PyText_FromString(argrep.c_str()); - PyTuple_SET_ITEM(co_varnames, iarg+1, pyspec); - } - - return co_varnames; -} - -PyObject* CPyCppyy::CPPMethod::GetArgDefault(int iarg, bool silent) -{ -// get and evaluate the default value (if any) of argument iarg of this method - if (iarg >= (int)GetMaxArgs()) - return nullptr; - -// borrowed reference to cppyy.gbl module to use its dictionary to eval in - static PyObject* gbl = PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppyy.gbl"); - - std::string defvalue = Cppyy::GetMethodArgDefault(fMethod, iarg); - if (!defvalue.empty()) { - PyObject** dctptr = _PyObject_GetDictPtr(gbl); - if (!(dctptr && *dctptr)) - return nullptr; - - PyObject* gdct = *dctptr; - PyObject* scope = nullptr; - - if (defvalue.rfind('(') != std::string::npos) { // constructor-style call - // try to tickle scope creation, just in case, first look in the scope where - // the function lives, then in the global scope - std::string possible_scope = defvalue.substr(0, defvalue.rfind('(')); - if (!Cppyy::IsBuiltin(possible_scope)) { - std::string cand_scope = Cppyy::GetScopedFinalName(fScope)+"::"+possible_scope; - scope = CreateScopeProxy(cand_scope); - if (!scope) { - PyErr_Clear(); - // search within the global scope instead - scope = CreateScopeProxy(possible_scope); - if (!scope) PyErr_Clear(); - } else { - // re-scope the scope; alternatively, the expression could be - // compiled in the dictionary of the function's namespace, but - // that would affect arguments passed to the constructor, too - defvalue = cand_scope + defvalue.substr(defvalue.rfind('('), std::string::npos); - } - } - } - - // replace '::' -> '.' - TypeManip::cppscope_to_pyscope(defvalue); - - if (!scope) { - // a couple of common cases that python doesn't like (technically, 'L' is okay with older - // pythons, but C long will always fit in Python int, so no need to bother) - char c = defvalue.back(); - if (c == 'F' || c == 'D' || c == 'L') { - int offset = 1; - if (2 < defvalue.size() && defvalue[defvalue.size()-2] == 'U') - offset = 2; - defvalue = defvalue.substr(0, defvalue.size()-offset); - } else if (defvalue == "true") { - defvalue = "True"; - } else if (defvalue == "false") { - defvalue = "False"; - } - } - - // attempt to evaluate the string representation (compilation is first to code to allow - // the error message to indicate where it's coming from) - PyObject* pyval = nullptr; - - PyObject* pycode = Py_CompileString((char*)defvalue.c_str(), "cppyy_default_compiler", Py_eval_input); - if (pycode) { - pyval = PyEval_EvalCode(pycode, gdct, gdct); - Py_DECREF(pycode); - } - - if (!pyval && PyErr_Occurred() && silent) { - PyErr_Clear(); - pyval = CPyCppyy_PyText_FromString(defvalue.c_str()); // allows continuation, but is likely to fail - } - - Py_XDECREF(scope); - return pyval; // may be nullptr - } - - PyErr_Format(PyExc_TypeError, "Could not construct default value for: %s", Cppyy::GetMethodArgName(fMethod, iarg).c_str()); - return nullptr; -} - - -bool CPyCppyy::CPPMethod::IsConst() { - return Cppyy::IsConstMethod(GetMethod()); -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::GetScopeProxy() -{ -// Get or build the scope of this method. - return CreateScopeProxy(fScope); -} - - -//---------------------------------------------------------------------------- -Cppyy::TCppFuncAddr_t CPyCppyy::CPPMethod::GetFunctionAddress() -{ -// Return the C++ pointer of this function - return Cppyy::GetFunctionAddress(fMethod, false /* don't check fast path envar */); -} - -//---------------------------------------------------------------------------- -int CPyCppyy::CPPMethod::GetArgMatchScore(PyObject* args_tuple) -{ - Py_ssize_t n = PyTuple_Size(args_tuple); - - int req_args = Cppyy::GetMethodReqArgs(fMethod); - - // Not enough arguments supplied: no match - if (req_args > n) - return INT_MAX; - - size_t score = 0; - for (int i = 0; i < n; i++) { - PyObject *pItem = PyTuple_GetItem(args_tuple, i); - if(!CPyCppyy_PyText_Check(pItem)) { - PyErr_SetString(PyExc_TypeError, "argument types should be in string format"); - return INT_MAX; - } - std::string req_type(CPyCppyy_PyText_AsString(pItem)); - - size_t arg_score = Cppyy::CompareMethodArgType(fMethod, i, req_type); - - // Method is not compatible if even one argument does not match - if (arg_score >= 10) { - score = INT_MAX; - break; - } - - score += arg_score; - } - - return score; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::Initialize(CallContext* ctxt) -{ -// done if cache is already setup - if (fArgsRequired != -1) - return true; - - if (!InitConverters_()) - return false; - - if (!InitExecutor_(fExecutor, ctxt)) - return false; - -// minimum number of arguments when calling - fArgsRequired = (int)((bool)fMethod == true ? Cppyy::GetMethodReqArgs(fMethod) : 0); - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::ProcessKwds(PyObject* self_in, PyCallArgs& cargs) -{ - if (!PyTuple_CheckExact(cargs.fKwds)) { - SetPyError_(CPyCppyy_PyText_FromString("received unknown keyword names object")); - return false; - } - Py_ssize_t nKeys = PyTuple_GET_SIZE(cargs.fKwds); - - if (nKeys == 0 && !self_in) - return true; - - if (!fArgIndices) { - fArgIndices = new std::unordered_map{}; - for (int iarg = 0; iarg < (int)Cppyy::GetMethodNumArgs(fMethod); ++iarg) - (*fArgIndices)[Cppyy::GetMethodArgName(fMethod, iarg)] = iarg; - } - - Py_ssize_t nArgs = CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) + (self_in ? 1 : 0); - if (!VerifyArgCount_(nArgs+nKeys)) - return false; - - std::vector vArgs{fConverters.size()}; - -// next, insert the keyword values - PyObject *key, *value; - Py_ssize_t maxpos = -1; - - Py_ssize_t npos_args = CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); - for (Py_ssize_t ikey = 0; ikey < nKeys; ++ikey) { - key = PyTuple_GET_ITEM(cargs.fKwds, ikey); - value = cargs.fArgs[npos_args+ikey]; - const char* ckey = CPyCppyy_PyText_AsStringChecked(key); - if (!ckey) - return false; - - auto p = fArgIndices->find(ckey); - if (p == fArgIndices->end()) { - SetPyError_(CPyCppyy_PyText_FromFormat("%s::%s got an unexpected keyword argument \'%s\'", - Cppyy::GetFinalName(fScope).c_str(), Cppyy::GetName(Cppyy::TCppScope_t(fMethod.data)).c_str(), ckey)); - return false; - } - - maxpos = p->second > maxpos ? p->second : maxpos; - vArgs[p->second] = value; // no INCREF yet for simple cleanup in case of error - } - -// if maxpos < nArgs, it will be detected & reported as a duplicate below - Py_ssize_t maxargs = maxpos + 1; - CPyCppyy_PyArgs_t newArgs = CPyCppyy_PyArgs_New(maxargs); - -// set all values to zero to be able to check them later (this also guarantees normal -// cleanup by the tuple deallocation) - for (Py_ssize_t i = 0; i < maxargs; ++i) - CPyCppyy_PyArgs_SET_ITEM(newArgs, i, nullptr); - -// fill out the positional arguments - Py_ssize_t start = 0; - if (self_in) { - Py_INCREF(self_in); - CPyCppyy_PyArgs_SET_ITEM(newArgs, 0, self_in); - start = 1; - } - - for (Py_ssize_t i = start; i < nArgs; ++i) { - if (vArgs[i]) { - SetPyError_(CPyCppyy_PyText_FromFormat("%s::%s got multiple values for argument %d", - Cppyy::GetFinalName(fScope).c_str(), Cppyy::GetName(Cppyy::TCppScope_t(fMethod.data)).c_str(), (int)i+1)); - CPyCppyy_PyArgs_DEL(newArgs); - return false; - } - - PyObject* item = CPyCppyy_PyArgs_GET_ITEM(cargs.fArgs, i); - Py_INCREF(item); - CPyCppyy_PyArgs_SET_ITEM(newArgs, i, item); - } - -// fill out the keyword arguments - for (Py_ssize_t i = nArgs; i < maxargs; ++i) { - PyObject* item = vArgs[i]; - if (item) { - Py_INCREF(item); - CPyCppyy_PyArgs_SET_ITEM(newArgs, i, item); - } else { - // try retrieving the default - item = GetArgDefault((int)i, false /* i.e. not silent */); - if (!item) { - CPyCppyy_PyArgs_DEL(newArgs); - return false; - } - CPyCppyy_PyArgs_SET_ITEM(newArgs, i, item); - } - } - - if (cargs.fFlags & PyCallArgs::kDoFree) { - if (cargs.fFlags & PyCallArgs::kIsOffset) - cargs.fArgs -= 1; - CPyCppyy_PyArgs_DEL(cargs.fArgs); - } - - cargs.fArgs = newArgs; - cargs.fNArgsf = maxargs; - cargs.fFlags = PyCallArgs::kDoFree | PyCallArgs::kDoItemDecref; - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::ProcessArgs(PyCallArgs& cargs) -{ -// verify existence of self, return if ok - if (cargs.fSelf) { - if (cargs.fKwds) { return ProcessKwds(nullptr, cargs); } - return true; - } - -// otherwise, check for a suitable 'self' in args and update accordingly - if (CPyCppyy_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) != 0) { - CPPInstance* pyobj = (CPPInstance*)CPyCppyy_PyArgs_GET_ITEM(cargs.fArgs, 0); - - // demand CPyCppyy object, and an argument that may match down the road - if (CPPInstance_Check(pyobj)) { - Cppyy::TCppScope_t oisa = pyobj->ObjectIsA(); - if (fScope == Cppyy::GetGlobalScope() || // free global - oisa == nullptr || // null pointer or ctor call - oisa == fScope || // matching types - Cppyy::IsSubclass(oisa, fScope)) { // id. - - // reset self - Py_INCREF(pyobj); // corresponding Py_DECREF is in CPPOverload - cargs.fSelf = pyobj; - - // offset args by 1 - cargs.fArgs += 1; - cargs.fFlags |= PyCallArgs::kIsOffset; - cargs.fNArgsf -= 1; - - // put the keywords, if any, in their places in the arguments array - if (cargs.fKwds) - return ProcessKwds(nullptr, cargs); - return true; - } - } - } - -// no self, set error and lament - SetPyError_(CPyCppyy_PyText_FromFormat( - "unbound method %s::%s must be called with a %s instance as first argument", - Cppyy::GetFinalName(fScope).c_str(), Cppyy::GetName(Cppyy::TCppScope_t(fMethod.data)).c_str(), - Cppyy::GetFinalName(fScope).c_str())); - return false; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CPPMethod::ConvertAndSetArgs(CPyCppyy_PyArgs_t args, size_t nargsf, CallContext* ctxt) -{ - Py_ssize_t argc = CPyCppyy_PyArgs_GET_SIZE(args, nargsf); - if (!VerifyArgCount_(argc)) - return false; - -// pass current scope for which the call is made - ctxt->fCurScope = fScope; - - if (argc == 0) - return true; - -// convert the arguments to the method call array - bool isOK = true; - Parameter* cppArgs = ctxt->GetArgs(argc); - for (int i = 0; i < (int)argc; ++i) { - if (!fConverters[i]->SetArg(CPyCppyy_PyArgs_GET_ITEM(args, i), cppArgs[i], ctxt)) { - SetPyError_(CPyCppyy_PyText_FromFormat("could not convert argument %d: %s", i+1, fConverters[i]->GetFailureMsg().c_str())); - isOK = false; - break; - } - } - - return isOK; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::Execute(void* self, ptrdiff_t offset, CallContext* ctxt) -{ -// call the interface method - PyObject* result = 0; - - if (CallContext::sSignalPolicy != CallContext::kProtected && \ - !(ctxt->fFlags & CallContext::kProtected)) { - // bypasses try block (i.e. segfaults will abort) - result = ExecuteFast(self, offset, ctxt); - } else { - // at the cost of ~10% performance, don't abort the interpreter on any signal - result = ExecuteProtected(self, offset, ctxt); - } - - if (!result && PyErr_Occurred()) - SetPyError_(0); - - return result; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt) -{ -// setup as necessary - if (fArgsRequired == -1 && !Initialize(ctxt)) - return nullptr; - -// fetch self, verify, and put the arguments in usable order - PyCallArgs cargs{self, args, nargsf, kwds}; - if (!ProcessArgs(cargs)) - return nullptr; - -// self provides the python context for lifelines - if (!ctxt->fPyContext) - ctxt->fPyContext = (PyObject*)cargs.fSelf; // no Py_INCREF as no ownership - -// translate the arguments - if (fArgsRequired || CPyCppyy_PyArgs_GET_SIZE(args, cargs.fNArgsf)) { - if (!ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) - return nullptr; - } - -// get the C++ object that this object proxy is a handle for - void* object = self->GetObject(); - -// validity check that should not fail - if (!object) { - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); - return nullptr; - } - -// get its class - Cppyy::TCppScope_t derived = self->ObjectIsA(); - -// calculate offset: the generated wrapper casts 'this' to the method's actual -// declaring class, which may differ from fScope. In particular, a method -// brought into fScope through a using-declaration (e.g. `using Base::meth;`) -// is still declared in the base, so 'this' has to be adjusted to that base's -// subobject. Using fScope here would yield a zero offset and corrupt memory. - Cppyy::TCppScope_t declaring = Cppyy::GetParentScope(Cppyy::TCppScope_t(fMethod.data)); - if (!declaring) - declaring = fScope; - - ptrdiff_t offset = 0; - if (derived && derived != declaring) - offset = Cppyy::GetBaseOffset(derived, declaring, object, 1 /* up-cast */); - -// actual call; recycle self instead of returning new object for same address objects - CPPInstance* pyobj = (CPPInstance*)Execute(object, offset, ctxt); - if (CPPInstance_Check(pyobj) && - derived && pyobj->ObjectIsA() == derived && - pyobj->GetObject() == object) { - Py_INCREF((PyObject*)self); - Py_DECREF(pyobj); - return (PyObject*)self; - } - - return (PyObject*)pyobj; -} - -//- protected members -------------------------------------------------------- -PyObject* CPyCppyy::CPPMethod::GetSignature(bool fa) -{ -// construct python string from the method's signature - return CPyCppyy_PyText_FromString(GetSignatureString(fa).c_str()); -} - -/** - * @brief Returns a tuple with the names of the input parameters of this method. - * - * For example given a function with prototype: - * - * double foo(int a, float b, double c) - * - * this function returns: - * - * ('a', 'b', 'c') - */ -PyObject *CPyCppyy::CPPMethod::GetSignatureNames() -{ - // Build a tuple of the argument names for this signature. - int argcount = GetMaxArgs(); - PyObject *signature_names = PyTuple_New(argcount); - - for (int iarg = 0; iarg < argcount; ++iarg) { - const std::string &argname_cpp = Cppyy::GetMethodArgName(fMethod, iarg); - PyObject *argname_py = CPyCppyy_PyText_FromString(argname_cpp.c_str()); - PyTuple_SET_ITEM(signature_names, iarg, argname_py); - } - - return signature_names; -} - -/** - * @brief Returns a dictionary with the types of the signature of this method. - * - * This dictionary will store both the return type and the input parameter - * types of this method, respectively with keys "return_type" and - * "input_types", for example given a function with prototype: - * - * double foo(int a, float b, double c) - * - * this function returns: - * - * {'input_types': ('int', 'float', 'double'), 'return_type': 'double'} - */ -PyObject *CPyCppyy::CPPMethod::GetSignatureTypes() -{ - - PyObject *signature_types_dict = PyDict_New(); - - // Insert the return type first - std::string return_type = GetReturnTypeName(); - PyObject *return_type_py = CPyCppyy_PyText_FromString(return_type.c_str()); - PyDict_SetItem(signature_types_dict, CPyCppyy_PyText_FromString("return_type"), return_type_py); - - // Build a tuple of the argument types for this signature. - int argcount = GetMaxArgs(); - PyObject *parameter_types = PyTuple_New(argcount); - - for (int iarg = 0; iarg < argcount; ++iarg) { - const std::string &argtype_cpp = Cppyy::GetMethodArgTypeAsString(fMethod, iarg); - PyObject *argtype_py = CPyCppyy_PyText_FromString(argtype_cpp.c_str()); - PyTuple_SET_ITEM(parameter_types, iarg, argtype_py); - } - - PyDict_SetItem(signature_types_dict, CPyCppyy_PyText_FromString("input_types"), parameter_types); - - return signature_types_dict; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::CPPMethod::GetReturnTypeName() -{ - return Cppyy::GetMethodReturnTypeAsString(fMethod); -} diff --git a/src/CPyCppyy/src/CPPMethod.h b/src/CPyCppyy/src/CPPMethod.h deleted file mode 100644 index e13a8da..0000000 --- a/src/CPyCppyy/src/CPPMethod.h +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef CPYCPPYY_CPPMETHOD_H -#define CPYCPPYY_CPPMETHOD_H - -// Bindings -#include "PyCallable.h" - -// Standard -#include -#include -#include - - -namespace CPyCppyy { - -class Executor; -class Converter; - -class PyCallArgs { -public: - PyCallArgs(CPPInstance*& self, CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds) - : fSelf(self), fArgs(args), fNArgsf(nargsf), fKwds(kwds), fFlags(kNone) {} - ~PyCallArgs(); - - enum ECleanupFlags { - kNone = 0x0000, - kIsOffset = 0x0001, // args were offset by 1 to drop self - kSelfSwap = 0x0002, // args[-1] and self need swapping - kArgsSwap = 0x0004, // args[0] and args[1] need swapping - kDoFree = 0x0008, // args need to be free'd (vector call only) - kDoItemDecref = 0x0010 // items in args need a decref (vector call only) - }; - -public: - CPPInstance*& fSelf; - CPyCppyy_PyArgs_t fArgs; - size_t fNArgsf; - PyObject* fKwds; - int fFlags; -}; - -class CPPMethod : public PyCallable { -public: - CPPMethod(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method); - CPPMethod(const CPPMethod&); - CPPMethod& operator=(const CPPMethod&); - virtual ~CPPMethod(); - -public: - PyObject* GetSignature(bool show_formalargs = true) override; - PyObject* GetSignatureNames() override; - PyObject* GetSignatureTypes() override; - PyObject* GetPrototype(bool show_formalargs = true) override; - PyObject* GetTypeName() override; - PyObject* Reflex(Cppyy::Reflex::RequestId_t request, - Cppyy::Reflex::FormatId_t = Cppyy::Reflex::OPTIMAL) override; - - int GetPriority() override; - bool IsGreedy() override; - - int GetMaxArgs() override; - PyObject* GetCoVarNames() override; - PyObject* GetArgDefault(int iarg, bool silent=true) override; - bool IsConst() override; - - PyObject* GetScopeProxy() override; - Cppyy::TCppFuncAddr_t GetFunctionAddress() override; - - PyCallable* Clone() override { return new CPPMethod(*this); } - - int GetArgMatchScore(PyObject* args_tuple) override; - -public: - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; - -protected: - virtual bool ProcessArgs(PyCallArgs& args); - - bool Initialize(CallContext* ctxt = nullptr); - bool ProcessKwds(PyObject* self_in, PyCallArgs& args); - bool ConvertAndSetArgs(CPyCppyy_PyArgs_t, size_t nargsf, CallContext* ctxt = nullptr); - PyObject* Execute(void* self, ptrdiff_t offset, CallContext* ctxt = nullptr); - - Cppyy::TCppMethod_t GetMethod() { return fMethod; } - Cppyy::TCppScope_t GetScope() { return fScope; } - Executor* GetExecutor() { return fExecutor; } - std::string GetSignatureString(bool show_formalargs = true); - std::string GetReturnTypeName(); - - virtual bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr); - -private: - void Copy_(const CPPMethod&); - void Destroy_(); - bool VerifyArgCount_(Py_ssize_t); - - PyObject* ExecuteFast(void*, ptrdiff_t, CallContext*); - PyObject* ExecuteProtected(void*, ptrdiff_t, CallContext*); - - bool InitConverters_(); - - void SetPyError_(PyObject* msg); - -private: -// representation - Cppyy::TCppMethod_t fMethod; - Cppyy::TCppScope_t fScope; - Executor* fExecutor; - -// call dispatch buffers - std::vector fConverters; - std::unordered_map* fArgIndices; - -protected: -// cached value that doubles as initialized flag (uninitialized if -1) - int fArgsRequired; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPMETHOD_H diff --git a/src/CPyCppyy/src/CPPOperator.cxx b/src/CPyCppyy/src/CPPOperator.cxx deleted file mode 100644 index bf2aa60..0000000 --- a/src/CPyCppyy/src/CPPOperator.cxx +++ /dev/null @@ -1,58 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPOperator.h" -#include "CPPInstance.h" -#include "Utility.h" - - -//- constructor -------------------------------------------------------------- -CPyCppyy::CPPOperator::CPPOperator( - Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method, const std::string& name) - : CPPMethod(scope, method) -{ -// a bit silly but doing it this way allows decoupling the initialization order - if (name == "__mul__") - fStub = CPPInstance_Type.tp_as_number->nb_multiply; - else if (name == CPPYY__div__) - fStub = CPPInstance_Type.tp_as_number->nb_true_divide; - else if (name == "__add__") - fStub = CPPInstance_Type.tp_as_number->nb_add; - else if (name == "__sub__") - fStub = CPPInstance_Type.tp_as_number->nb_subtract; - else - fStub = nullptr; -} - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPOperator::Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt) -{ -// some operators can be a mix of global and class overloads; this method will -// first try class overloads (the existence of this method means that such were -// defined) and if failed, fall back on the global stubs -// TODO: the fact that this is a method and not an overload means that the global -// ones are tried for each method that fails during the overload resolution - PyObject* result = this->CPPMethod::Call(self, args, nargsf, kwds, ctxt); - if (result || !fStub || !self) - return result; - - Py_ssize_t idx_other = 0; - if (CPyCppyy_PyArgs_GET_SIZE(args, nargsf) != 1) { - if ((CPyCppyy_PyArgs_GET_SIZE(args, nargsf) == 2 && CPyCppyy_PyArgs_GET_ITEM(args, 0) == (PyObject*)self)) - idx_other = 1; - else - return result; - } - -// fetch the current error, resetting the error buffer - auto error = CPyCppyy::Utility::FetchPyError(); - - result = fStub((PyObject*)self, CPyCppyy_PyArgs_GET_ITEM(args, idx_other)); - -// if there was still a problem, restore the Python error buffer - if (!result) { - CPyCppyy::Utility::RestorePyError(error); - } - - return result; -} diff --git a/src/CPyCppyy/src/CPPOperator.h b/src/CPyCppyy/src/CPPOperator.h deleted file mode 100644 index e260267..0000000 --- a/src/CPyCppyy/src/CPPOperator.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef CPYCPPYY_CPPOPERATOR_H -#define CPYCPPYY_CPPOPERATOR_H - -// Bindings -#include "CPPMethod.h" - -// Standard -#include - - -namespace CPyCppyy { - -class CPPOperator : public CPPMethod { -public: - CPPOperator(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method, const std::string& name); - -public: - PyCallable* Clone() override { return new CPPOperator(*this); } - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; - -private: - binaryfunc fStub; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPOPERATOR_H diff --git a/src/CPyCppyy/src/CPPOverload.cxx b/src/CPyCppyy/src/CPPOverload.cxx deleted file mode 100644 index b4fb1da..0000000 --- a/src/CPyCppyy/src/CPPOverload.cxx +++ /dev/null @@ -1,1150 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPyCppyy/Reflex.h" -#include "structmember.h" // from Python -#if PY_VERSION_HEX < 0x030b0000 -#include "code.h" // from Python -#endif -#ifndef CO_NOFREE -// python2.2 does not have CO_NOFREE defined -#define CO_NOFREE 0x0040 -#endif -#include "CPPOverload.h" -#include "CPPInstance.h" -#include "CallContext.h" -#include "PyStrings.h" -#include "Utility.h" - -// Standard -#include -#include -#include - - -namespace CPyCppyy { - -namespace { - -// from CPython's instancemethod: Free list for method objects to safe malloc/free overhead -// The fSelf field is used to chain the elements. -static CPPOverload* free_list; -static int numfree = 0; -#ifndef CPPOverload_MAXFREELIST -#define CPPOverload_MAXFREELIST 32 -#endif - - -// TODO: only used in pythonizations to add Python-side overloads to existing -// C++ overloads, but may be better off integrated with Pythonize.cxx callbacks -class TPythonCallback : public PyCallable { -public: - PyObject* fCallable; - - TPythonCallback(PyObject* callable) : fCallable(nullptr) - { - if (!PyCallable_Check(callable)) { - PyErr_SetString(PyExc_TypeError, "parameter must be callable"); - return; - } - Py_INCREF(callable); - fCallable = callable; - } - - ~TPythonCallback() override { - Py_DECREF(fCallable); - fCallable = nullptr; - } - - PyObject* GetSignature(bool /*show_formalargs*/ = true) override { - return CPyCppyy_PyText_FromString("*args, **kwargs"); - } - PyObject* GetSignatureNames() override { - return PyTuple_New(0); - } - PyObject* GetSignatureTypes() override { - return PyTuple_New(0); - } - PyObject* GetPrototype(bool /*show_formalargs*/ = true) override { - return CPyCppyy_PyText_FromString(""); - } - PyObject* GetDocString() override { - if (PyObject_HasAttrString(fCallable, "__doc__")) { - return PyObject_GetAttrString(fCallable, "__doc__"); - } else { - return GetPrototype(); - } - } - - int GetPriority() override { return 100; }; - bool IsGreedy() override { return false; }; - - int GetMaxArgs() override { return 100; }; - PyObject* GetCoVarNames() override { // TODO: pick these up from the callable - Py_RETURN_NONE; - } - PyObject* GetArgDefault(int /* iarg */, bool /* silent */ =true) override { - Py_RETURN_NONE; // TODO: pick these up from the callable - } - - PyObject* GetScopeProxy() override { // should this be the module ?? - Py_RETURN_NONE; - } - - Cppyy::TCppFuncAddr_t GetFunctionAddress() override { - return (Cppyy::TCppFuncAddr_t)nullptr; - } - - PyCallable* Clone() override { return new TPythonCallback(*this); } - - PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* /* ctxt = 0 */) override { - - if (self) { - if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) { // mutation allowed? - std::swap(((PyObject**)args-1)[0], (PyObject*&)self); - nargsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; - args = args-1; - } else { - Py_ssize_t nkwargs = kwds ? PyTuple_GET_SIZE(kwds) : 0; - Py_ssize_t totalargs = PyVectorcall_NARGS(nargsf)+nkwargs; - PyObject** newArgs = (PyObject**)PyMem_Malloc((totalargs+1) * sizeof(PyObject*)); - if (!newArgs) - return nullptr; - - newArgs[0] = (PyObject*)self; - if (0 < totalargs) - memcpy((void*)&newArgs[1], args, totalargs * sizeof(PyObject*)); - args = newArgs; - } - nargsf += 1; - } - - PyObject* result = CPyCppyy_PyObject_Call(fCallable, args, nargsf, kwds); - if (self) { - if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) - std::swap(((PyObject**)args-1)[0], (PyObject*&)self); - else PyMem_Free((void*)args); - } - return result; - } -}; - -// helper to test whether a method is used in a pseudo-function modus -static inline bool IsPseudoFunc(CPPOverload* pymeth) -{ - return pymeth->fMethodInfo->fFlags & CallContext::kIsPseudoFunc; -} - -// helper to sort on method priority -static int PriorityCmp(const std::pair& left, const std::pair& right) -{ - return left.first > right.first; -} - -// return helper -static inline void ResetCallState(CPPInstance* descr_self, CPPInstance*& im_self) -{ -// reset self if needed, allowing simple re-use - if (descr_self != im_self) { - Py_XDECREF(im_self); - im_self = descr_self; - } -} - -// helper to factor out return logic of mp_call / mp_vectorcall -static inline PyObject* HandleReturn( - CPPOverload* pymeth, CPPInstance* im_self, PyObject* result) -{ -// special case for python exceptions, propagated through C++ layer - if (result) { - CPPInstance* cppres = (CPPInstance*)(CPPInstance_Check(result) ? result : nullptr); - - // if this method creates new objects, always take ownership - if (IsCreator(pymeth->fMethodInfo->fFlags)) { - - // either be a constructor with a fresh object proxy self ... - if (IsConstructor(pymeth->fMethodInfo->fFlags)) { - if (im_self) - im_self->PythonOwns(); - } - - // ... or be a regular method with an object proxy return value - else if (cppres) - cppres->PythonOwns(); - } - - // if this new object falls inside self, make sure its lifetime is proper - if (!(pymeth->fMethodInfo->fFlags & CallContext::kNeverLifeLine)) { - int ll_action = 0; - if ((PyObject*)im_self != result) { - if (pymeth->fMethodInfo->fFlags & CallContext::kSetLifeLine) - ll_action = 1; - else if (cppres && CPPInstance_Check(im_self)) { - // if self was a by-value return and result is not, pro-actively protect result; - // else if the return value falls within the memory of 'this', force a lifeline - if (!(cppres->fFlags & CPPInstance::kIsValue)) { // no need if the result is temporary - if (im_self->fFlags & CPPInstance::kIsValue) - ll_action = 2; - else if (im_self->fFlags & CPPInstance::kHasLifeLine) - ll_action = 3; - else { - ptrdiff_t offset = (ptrdiff_t)cppres->GetObject() - (ptrdiff_t)im_self->GetObject(); - if (0 <= offset && offset < (ptrdiff_t)Cppyy::SizeOf(im_self->ObjectIsA())) - ll_action = 4; - } - } - } - } - - if (!ll_action) - pymeth->fMethodInfo->fFlags |= CallContext::kNeverLifeLine; // assume invariant semantics - else { - if (PyObject_SetAttr(result, PyStrings::gLifeLine, (PyObject*)im_self) == -1) - PyErr_Clear(); // ignored - if (cppres) cppres->fFlags |= CPPInstance::kHasLifeLine; // for chaining - pymeth->fMethodInfo->fFlags |= CallContext::kSetLifeLine; // for next time - } - } - } - -// reset self as necessary to allow re-use of the CPPOverload - ResetCallState(pymeth->fSelf, im_self); - - return result; -} - - -//= CPyCppyy method proxy object behaviour =================================== -static PyObject* mp_name(CPPOverload* pymeth, void*) -{ - return CPyCppyy_PyText_FromString(pymeth->GetName().c_str()); -} - -//---------------------------------------------------------------------------- -static PyObject* mp_module(CPPOverload* /* pymeth */, void*) -{ - Py_INCREF(PyStrings::gThisModule); - return PyStrings::gThisModule; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_doc(CPPOverload* pymeth, void*) -{ - if (pymeth->fMethodInfo->fDoc) { - Py_INCREF(pymeth->fMethodInfo->fDoc); - return pymeth->fMethodInfo->fDoc; - } - -// Build python document string ('__doc__') from all C++-side overloads. - CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; - -// collect doc strings - CPPOverload::Methods_t::size_type nMethods = methods.size(); - if (nMethods == 0) // from template proxy with no instantiations - return nullptr; - PyObject* doc = methods[0]->GetDocString(); - -// simple case - if (nMethods == 1) - return doc; - -// overloaded method - PyObject* separator = CPyCppyy_PyText_FromString("\n"); - for (CPPOverload::Methods_t::size_type i = 1; i < nMethods; ++i) { - CPyCppyy_PyText_Append(&doc, separator); - CPyCppyy_PyText_AppendAndDel(&doc, methods[i]->GetDocString()); - } - Py_DECREF(separator); - - return doc; -} - -static int mp_doc_set(CPPOverload* pymeth, PyObject *val, void *) -{ - Py_XDECREF(pymeth->fMethodInfo->fDoc); - Py_INCREF(val); - pymeth->fMethodInfo->fDoc = val; - return 0; -} - -/** - * @brief Returns a dictionary with the input parameter names for all overloads. - * - * This dictionary may look like: - * - * {'double ::foo(int a, float b, double c)': ('a', 'b', 'c'), - * 'float ::foo(float b)': ('b',), - * 'int ::foo(int a)': ('a',), - * 'int ::foo(int a, float b)': ('a', 'b')} - */ -static PyObject *mp_func_overloads_names(CPPOverload *pymeth) -{ - - const CPPOverload::Methods_t &methods = pymeth->fMethodInfo->fMethods; - - PyObject *overloads_names_dict = PyDict_New(); - - for (PyCallable *method : methods) { - PyDict_SetItem(overloads_names_dict, method->GetPrototype(), method->GetSignatureNames()); - } - - return overloads_names_dict; -} - -/** - * @brief Returns a dictionary with the types of all overloads. - * - * This dictionary may look like: - * - * {'double ::foo(int a, float b, double c)': {'input_types': ('int', 'float', 'double'), 'return_type': 'double'}, - * 'float ::foo(float b)': {'input_types': ('float',), 'return_type': 'float'}, - * 'int ::foo(int a)': {'input_types': ('int',), 'return_type': 'int'}, - * 'int ::foo(int a, float b)': {'input_types': ('int', 'float'), 'return_type': 'int'}} - */ -static PyObject *mp_func_overloads_types(CPPOverload *pymeth) -{ - - const CPPOverload::Methods_t &methods = pymeth->fMethodInfo->fMethods; - - PyObject *overloads_types_dict = PyDict_New(); - - for (PyCallable *method : methods) { - PyDict_SetItem(overloads_types_dict, method->GetPrototype(), method->GetSignatureTypes()); - } - - return overloads_types_dict; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_meth_func(CPPOverload* pymeth, void*) -{ -// Create a new method proxy to be returned. - CPPOverload* newPyMeth = (CPPOverload*)CPPOverload_Type.tp_alloc(&CPPOverload_Type, 0); - -// method info is shared, as it contains the collected overload knowledge - *pymeth->fMethodInfo->fRefCount += 1; - newPyMeth->fMethodInfo = pymeth->fMethodInfo; - -// new method is unbound, track whether this proxy is used in the capacity of a -// method or a function (which normally is a CPPFunction) - newPyMeth->fMethodInfo->fFlags |= CallContext::kIsPseudoFunc; - - return (PyObject*)newPyMeth; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_meth_self(CPPOverload* pymeth, void*) -{ -// Return the bound self, if any; in case of pseudo-function role, pretend -// that the data member im_self does not exist. - if (IsPseudoFunc(pymeth)) { - PyErr_Format(PyExc_AttributeError, - "function %s has no attribute \'im_self\'", pymeth->fMethodInfo->fName.c_str()); - return nullptr; - } else if (pymeth->fSelf != 0) { - Py_INCREF((PyObject*)pymeth->fSelf); - return (PyObject*)pymeth->fSelf; - } - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_meth_class(CPPOverload* pymeth, void*) -{ -// Return scoping class; in case of pseudo-function role, pretend that there -// is no encompassing class (i.e. global scope). - if (!IsPseudoFunc(pymeth) && pymeth->fMethodInfo->fMethods.size()) { - PyObject* pyclass = pymeth->fMethodInfo->fMethods[0]->GetScopeProxy(); - if (!pyclass) - PyErr_Format(PyExc_AttributeError, - "function %s has no attribute \'im_class\'", pymeth->fMethodInfo->fName.c_str()); - return pyclass; - } - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_func_closure(CPPOverload* /* pymeth */, void*) -{ -// Stub only, to fill out the python function interface. - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_func_code(CPPOverload*, void*) -{ -// Code details are used in module inspect to fill out interactive help() -// not important for functioning of most code, so not implemented for p3 for now (TODO) - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_func_defaults(CPPOverload* pymeth, void*) -{ -// Create a tuple of default values, if there is only one method (otherwise -// leave undefined: this is only used by inspect for interactive help()) - CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; - - if (methods.size() != 1) - return PyTuple_New(0); - - int maxarg = methods[0]->GetMaxArgs(); - - PyObject* defaults = PyTuple_New(maxarg); - - int itup = 0; - for (int iarg = 0; iarg < maxarg; ++iarg) { - PyObject* defvalue = methods[0]->GetArgDefault(iarg); - if (defvalue) - PyTuple_SET_ITEM(defaults, itup++, defvalue); - else - PyErr_Clear(); - } - _PyTuple_Resize(&defaults, itup); - - return defaults; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_func_globals(CPPOverload* /* pymeth */, void*) -{ -// Return this function's global dict (hard-wired to be the cppyy module); used -// for lookup of names from co_code indexing into co_names. - PyObject* pyglobal = PyModule_GetDict(PyImport_AddModule((char*)"cppyy")); - Py_XINCREF(pyglobal); - return pyglobal; -} - -//---------------------------------------------------------------------------- -static inline int set_flag(CPPOverload* pymeth, PyObject* value, CallContext::ECallFlags flag, const char* name) -{ -// Generic setter of a (boolean) flag. - if (!value) { // accept as false (delete) - pymeth->fMethodInfo->fFlags &= ~flag; - return 0; - } - - long istrue = PyLong_AsLong(value); - if (istrue == -1 && PyErr_Occurred()) { - PyErr_Format(PyExc_ValueError, "a boolean 1 or 0 is required for %s", name); - return -1; - } - - if (istrue) - pymeth->fMethodInfo->fFlags |= flag; - else - pymeth->fMethodInfo->fFlags &= ~flag; - - return 0; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_getcreates(CPPOverload* pymeth, void*) -{ -// Get '__creates__' boolean, which determines ownership of return values. - return PyInt_FromLong((long)IsCreator(pymeth->fMethodInfo->fFlags)); -} - -//---------------------------------------------------------------------------- -static int mp_setcreates(CPPOverload* pymeth, PyObject* value, void*) -{ -// Set '__creates__' boolean, which determines ownership of return values. - return set_flag(pymeth, value, CallContext::kIsCreator, "__creates__"); -} - -//---------------------------------------------------------------------------- -static PyObject* mp_getmempolicy(CPPOverload* pymeth, void*) -{ -// Get '_mempolicy' enum, which determines ownership of call arguments. - if (pymeth->fMethodInfo->fFlags & CallContext::kUseHeuristics) - return PyInt_FromLong(CallContext::kUseHeuristics); - - if (pymeth->fMethodInfo->fFlags & CallContext::kUseStrict) - return PyInt_FromLong(CallContext::kUseStrict); - - return PyInt_FromLong(-1); -} - -//---------------------------------------------------------------------------- -static int mp_setmempolicy(CPPOverload* pymeth, PyObject* value, void*) -{ -// Set '_mempolicy' enum, which determines ownership of call arguments. - long mempolicy = PyLong_AsLong(value); - if (mempolicy == CallContext::kUseHeuristics) { - pymeth->fMethodInfo->fFlags |= CallContext::kUseHeuristics; - pymeth->fMethodInfo->fFlags &= ~CallContext::kUseStrict; - } else if (mempolicy == CallContext::kUseStrict) { - pymeth->fMethodInfo->fFlags |= CallContext::kUseStrict; - pymeth->fMethodInfo->fFlags &= ~CallContext::kUseHeuristics; - } else { - PyErr_SetString(PyExc_ValueError, - "expected kMemoryStrict or kMemoryHeuristics as value for __mempolicy__"); - return -1; - } - - return 0; -} - - -//---------------------------------------------------------------------------- -#define CPPYY_BOOLEAN_PROPERTY(name, flag, label) \ -static PyObject* mp_get##name(CPPOverload* pymeth, void*) { \ - if (pymeth->fMethodInfo->fFlags & flag) { \ - Py_RETURN_TRUE; \ - } \ - Py_RETURN_FALSE; \ -} \ - \ -static int mp_set##name(CPPOverload* pymeth, PyObject* value, void*) { \ - return set_flag(pymeth, value, flag, label); \ -} - -CPPYY_BOOLEAN_PROPERTY(lifeline, CallContext::kSetLifeLine, "__set_lifeline__") -CPPYY_BOOLEAN_PROPERTY(threaded, CallContext::kReleaseGIL, "__release_gil__") -CPPYY_BOOLEAN_PROPERTY(useffi, CallContext::kUseFFI, "__useffi__") -CPPYY_BOOLEAN_PROPERTY(sig2exc, CallContext::kProtected, "__sig2exc__") - -static PyObject* mp_getcppname(CPPOverload* pymeth, void*) -{ - if ((void*)pymeth == (void*)&CPPOverload_Type) - return CPyCppyy_PyText_FromString("CPPOverload_Type"); - - auto& methods = pymeth->fMethodInfo->fMethods; - if (methods.empty()) - return CPyCppyy_PyText_FromString("void (*)()"); // debatable - - if (methods.size() == 1) - return methods[0]->GetTypeName(); - - return CPyCppyy_PyText_FromString("void* (*)(...)"); // id. -} - - -//---------------------------------------------------------------------------- -static PyGetSetDef mp_getset[] = { - {(char*)"__name__", (getter)mp_name, nullptr, nullptr, nullptr}, - {(char*)"__module__", (getter)mp_module, nullptr, nullptr, nullptr}, - {(char*)"__doc__", (getter)mp_doc, (setter)mp_doc_set, nullptr, nullptr}, - -// to be more python-like, where these are duplicated as well; to actually -// derive from the python method or function type is too memory-expensive, -// given that most of the members of those types would not be used - {(char*)"im_func", (getter)mp_meth_func, nullptr, nullptr, nullptr}, - {(char*)"im_self", (getter)mp_meth_self, nullptr, nullptr, nullptr}, - {(char*)"im_class", (getter)mp_meth_class, nullptr, nullptr, nullptr}, - - {(char*)"func_closure", (getter)mp_func_closure, nullptr, nullptr, nullptr}, - {(char*)"func_code", (getter)mp_func_code, nullptr, nullptr, nullptr}, - {(char*)"func_defaults", (getter)mp_func_defaults, nullptr, nullptr, nullptr}, - {(char*)"func_globals", (getter)mp_func_globals, nullptr, nullptr, nullptr}, - {(char*)"func_doc", (getter)mp_doc, (setter)mp_doc_set, nullptr, nullptr}, - {(char*)"func_name", (getter)mp_name, nullptr, nullptr, nullptr}, - {(char*)"func_overloads_types", (getter)mp_func_overloads_types, nullptr, nullptr, nullptr}, - {(char*)"func_overloads_names", (getter)mp_func_overloads_names, nullptr, nullptr, nullptr}, - - -// flags to control behavior - {(char*)"__creates__", (getter)mp_getcreates, (setter)mp_setcreates, - (char*)"For ownership rules of result: if true, objects are python-owned", nullptr}, - {(char*)"__mempolicy__", (getter)mp_getmempolicy, (setter)mp_setmempolicy, - (char*)"For argument ownership rules: like global, either heuristic or strict", nullptr}, - {(char*)"__set_lifeline__", (getter)mp_getlifeline, (setter)mp_setlifeline, - (char*)"If true, set a lifeline from the return value onto self", nullptr}, - {(char*)"__release_gil__", (getter)mp_getthreaded, (setter)mp_setthreaded, - (char*)"If true, releases GIL on call into C++", nullptr}, - {(char*)"__useffi__", (getter)mp_getuseffi, (setter)mp_setuseffi, - (char*)"not implemented", nullptr}, - {(char*)"__sig2exc__", (getter)mp_getsig2exc, (setter)mp_setsig2exc, - (char*)"If true, turn signals into Python exceptions", nullptr}, - -// basic reflection information - {(char*)"__cpp_name__", (getter)mp_getcppname, nullptr, nullptr, nullptr}, - - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} -}; - -//= CPyCppyy method proxy function behavior ================================== -static PyObject* mp_vectorcall( - CPPOverload* pymeth, PyObject* const *args, size_t nargsf, PyObject* kwds) -{ -// Call the appropriate overload of this method. - -// If called from a descriptor, then this could be a bound function with -// non-zero self; otherwise pymeth->fSelf is expected to always be nullptr. - - CPPInstance* im_self = pymeth->fSelf; - -// get local handles to proxy internals - auto& methods = pymeth->fMethodInfo->fMethods; - - CPPOverload::Methods_t::size_type nMethods = methods.size(); - - CallContext ctxt{}; - const auto mflags = pymeth->fMethodInfo->fFlags; - const auto mempolicy = (mflags & (CallContext::kUseHeuristics | CallContext::kUseStrict)); - ctxt.fFlags |= mempolicy ? mempolicy : (uint64_t)CallContext::sMemoryPolicy; - ctxt.fFlags |= (mflags & CallContext::kReleaseGIL); - ctxt.fFlags |= (mflags & CallContext::kProtected); - if (IsConstructor(pymeth->fMethodInfo->fFlags)) ctxt.fFlags |= CallContext::kIsConstructor; - ctxt.fFlags |= (pymeth->fFlags & (CallContext::kCallDirect | CallContext::kFromDescr)); - ctxt.fPyContext = (PyObject*)im_self; // no Py_INCREF as no ownership - -// check implicit conversions status (may be disallowed to prevent recursion) - ctxt.fFlags |= (pymeth->fFlags & CallContext::kNoImplicit); - -// simple case - if (nMethods == 1) { - if (!NoImplicit(&ctxt)) ctxt.fFlags |= CallContext::kAllowImplicit; // no two rounds needed - PyObject* result = methods[0]->Call(im_self, args, nargsf, kwds, &ctxt); - return HandleReturn(pymeth, im_self, result); - } - -// otherwise, handle overloading - uint64_t sighash = HashSignature(args, nargsf); - -// look for known signatures ... - auto& dispatchMap = pymeth->fMethodInfo->fDispatchMap; - PyCallable* memoized_pc = nullptr; - for (const auto& p : dispatchMap) { - if (p.first == sighash) { - memoized_pc = p.second; - break; - } - } - if (memoized_pc) { - // it is necessary to enable implicit conversions as the memoized call may be from - // such a conversion case; if the call fails, the implicit flag is reset below - if (!NoImplicit(&ctxt)) ctxt.fFlags |= CallContext::kAllowImplicit; - PyObject* result = memoized_pc->Call(im_self, args, nargsf, kwds, &ctxt); - if (result) - return HandleReturn(pymeth, im_self, result); - - // fall through: python is dynamic, and so, the hashing isn't infallible - ctxt.fFlags &= ~CallContext::kAllowImplicit; - PyErr_Clear(); - ResetCallState(pymeth->fSelf, im_self); - } - -// ... otherwise loop over all methods and find the one that does not fail - if (!IsSorted(mflags)) { - // sorting is based on priority, which is not stored on the method as it is used - // only once, so copy the vector of methods into one where the priority can be - // stored during sorting - std::vector> pm; pm.reserve(methods.size()); - for (auto ptr : methods) - pm.emplace_back(ptr->GetPriority(), ptr); - std::stable_sort(pm.begin(), pm.end(), PriorityCmp); - for (CPPOverload::Methods_t::size_type i = 0; i < methods.size(); ++i) - methods[i] = pm[i].second; - pymeth->fMethodInfo->fFlags |= CallContext::kIsSorted; - } - - std::vector errors; - std::vector implicit_possible(methods.size()); - for (int stage = 0; stage < 2; ++stage) { - bool bHaveImplicit = false; - for (CPPOverload::Methods_t::size_type i = 0; i < nMethods; ++i) { - if (stage && !implicit_possible[i]) - continue; // did not set implicit conversion, so don't try again - - PyObject* result = methods[i]->Call(im_self, args, nargsf, kwds, &ctxt); - if (result) { - // success: update the dispatch map for subsequent calls - if (!memoized_pc) - dispatchMap.push_back(std::make_pair(sighash, methods[i])); - else { - // debatable: apparently there are two methods that map onto the same sighash - // and preferring the latest may result in "ping pong." - for (auto& p : dispatchMap) { - if (p.first == sighash) { - p.second = methods[i]; - break; - } - } - } - - return HandleReturn(pymeth, im_self, result); - } - - // else failure .. - if (stage != 0) { - PyErr_Clear(); // first stage errors should be the more informative - ResetCallState(pymeth->fSelf, im_self); - continue; - } - - // collect error message/trace (automatically clears exception, too) - if (!PyErr_Occurred()) { - // this should not happen; set an error to prevent core dump and report - PyObject* sig = methods[i]->GetPrototype(); - PyErr_Format(PyExc_SystemError, "%s =>\n %s", - CPyCppyy_PyText_AsString(sig), (char*)"nullptr result without error in overload call"); - Py_DECREF(sig); - } - - // retrieve, store, and clear errors - bool callee_error = ctxt.fFlags & (CallContext::kPyException | CallContext::kCppException); - ctxt.fFlags &= ~(CallContext::kPyException | CallContext::kCppException); - Utility::FetchError(errors, callee_error); - - if (HaveImplicit(&ctxt)) { - bHaveImplicit = true; - implicit_possible[i] = true; - ctxt.fFlags &= ~CallContext::kHaveImplicit; - } else - implicit_possible[i] = false; - ResetCallState(pymeth->fSelf, im_self); - } - - // only move forward if implicit conversions are available - if (!bHaveImplicit) - break; - - ctxt.fFlags |= CallContext::kAllowImplicit; - } - -// first summarize, then add details - PyObject* topmsg = CPyCppyy_PyText_FromFormat( - "none of the %d overloaded methods succeeded. Full details:", (int)nMethods); - SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); - -// report failure - return nullptr; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_str(CPPOverload* cppinst) -{ -// Print a description that includes the C++ name - std::ostringstream s; - s << "fMethodInfo->fName << "\" at " << (void*)cppinst << ">"; - return CPyCppyy_PyText_FromString(s.str().c_str()); -} - -//---------------------------------------------------------------------------- -static CPPOverload* mp_descr_get(CPPOverload* pymeth, CPPInstance* pyobj, PyObject*) -{ -// Descriptor; create and return a new, possibly bound, method proxy. This method -// has evolved with versions of python as follows: -// -// Python version | Action -// <- py2.7 | bound methods need to be first-class objects, so create a new -// | method object if self is not nullptr or Py_None -// py3.0-py3.7 | bound methods are no longer a language requirement, but -// | still supported: for convenience, retain old behavior -// py3.8 <= | vector calls no longer call the descriptor, so when it is -// | called, the method is likely stored, so should be new object - -// create a new method object - bool gc_track = false; - CPPOverload* newPyMeth = free_list; - if (newPyMeth != NULL) { - free_list = (CPPOverload*)(newPyMeth->fSelf); - (void)PyObject_INIT(newPyMeth, &CPPOverload_Type); - numfree--; - } else { - newPyMeth = PyObject_GC_New(CPPOverload, &CPPOverload_Type); - if (!newPyMeth) - return nullptr; - gc_track = true; - } - -// method info is shared, as it contains the collected overload knowledge - *pymeth->fMethodInfo->fRefCount += 1; - newPyMeth->fMethodInfo = pymeth->fMethodInfo; - - newPyMeth->fVectorCall = pymeth->fVectorCall; - - if (pyobj && (PyObject*)pyobj != Py_None) { - Py_INCREF((PyObject*)pyobj); - newPyMeth->fSelf = pyobj; - newPyMeth->fFlags = CallContext::kNone; - } else { - newPyMeth->fSelf = nullptr; - newPyMeth->fFlags = CallContext::kCallDirect; - } - -// vector calls don't get here, unless a method is looked up on an instance, for -// e.g. class methods (C++ static); notify downstream to expect a 'self' - newPyMeth->fFlags |= CallContext::kFromDescr; - - if (gc_track) - PyObject_GC_Track(newPyMeth); - - return newPyMeth; -} - - -//= CPyCppyy method proxy construction/destruction =========================== -static CPPOverload* mp_new(PyTypeObject*, PyObject*, PyObject*) -{ -// Create a new method proxy object. - CPPOverload* pymeth = PyObject_GC_New(CPPOverload, &CPPOverload_Type); - pymeth->fSelf = nullptr; - pymeth->fFlags = CallContext::kNone; - pymeth->fMethodInfo = new CPPOverload::MethodInfo_t; - - PyObject_GC_Track(pymeth); - return pymeth; -} - -//---------------------------------------------------------------------------- -static void mp_dealloc(CPPOverload* pymeth) -{ -// Deallocate memory held by method proxy object. - PyObject_GC_UnTrack(pymeth); - - Py_CLEAR(pymeth->fSelf); - - if (--(*pymeth->fMethodInfo->fRefCount) <= 0) { - delete pymeth->fMethodInfo; - } - - if (numfree < CPPOverload_MAXFREELIST) { - pymeth->fSelf = (CPyCppyy::CPPInstance*)free_list; - free_list = pymeth; - numfree++; - } else { - PyObject_GC_Del(pymeth); - } -} - -//---------------------------------------------------------------------------- -static Py_ssize_t mp_hash(CPPOverload* pymeth) -{ -// Hash of method proxy object for insertion into dictionaries; with actual -// method (fMethodInfo) shared, its address is best suited. -#if PY_VERSION_HEX >= 0x030d0000 - return Py_HashPointer(pymeth->fMethodInfo); -#else - return _Py_HashPointer(pymeth->fMethodInfo); -#endif -} - -//---------------------------------------------------------------------------- -static int mp_traverse(CPPOverload* pymeth, visitproc visit, void* args) -{ -// Garbage collector traverse of held python member objects. - if (pymeth->fSelf) - return visit((PyObject*)pymeth->fSelf, args); - - return 0; -} - -//---------------------------------------------------------------------------- -static int mp_clear(CPPOverload* pymeth) -{ -// Garbage collector clear of held python member objects. - Py_CLEAR(pymeth->fSelf); - - return 0; -} - -//---------------------------------------------------------------------------- -static PyObject* mp_richcompare(CPPOverload* self, CPPOverload* other, int op) -{ -// Rich set of comparison objects; only equals is defined. - if (op != Py_EQ) - return PyType_Type.tp_richcompare((PyObject*)self, (PyObject*)other, op); - -// defined by type + (shared) MethodInfo + bound self, with special case for -// fSelf (i.e. pseudo-function) - if ((Py_TYPE(self) == Py_TYPE(other) && self->fMethodInfo == other->fMethodInfo) && \ - ((IsPseudoFunc(self) && IsPseudoFunc(other)) || self->fSelf == other->fSelf)) { - Py_RETURN_TRUE; - } - Py_RETURN_FALSE; -} - - -//= CPyCppyy method proxy access to internals ================================ -static PyObject* mp_overload(CPPOverload* pymeth, PyObject* args) -{ -// Select and call a specific C++ overload, based on its signature. - const char* sigarg = nullptr; - PyObject* sigarg_tuple = nullptr; - int want_const = -1; - Py_ssize_t args_size = PyTuple_GET_SIZE(args); - if (args_size && - PyArg_ParseTuple(args, const_cast("s|i:__overload__"), &sigarg, &want_const)) { - want_const = args_size == 1 ? -1 : want_const; - return pymeth->FindOverload(sigarg ? sigarg : "", want_const); - } else if (args_size && - PyArg_ParseTuple(args, const_cast("O|i:__overload__"), &sigarg_tuple, &want_const)) { - PyErr_Clear(); - want_const = args_size == 1 ? -1 : want_const; - return pymeth->FindOverload(sigarg_tuple, want_const); - } else { - PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__"); - return nullptr; - } -} - -static PyObject* mp_add_overload(CPPOverload* pymeth, PyObject* new_overload) -{ - TPythonCallback* cb = new TPythonCallback(new_overload); - pymeth->AdoptMethod(cb); - Py_RETURN_NONE; -} - -static PyObject* mp_reflex(CPPOverload* pymeth, PyObject* args) -{ -// Provide the requested reflection information. - Cppyy::Reflex::RequestId_t request = -1; - Cppyy::Reflex::FormatId_t format = Cppyy::Reflex::OPTIMAL; - if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, &format)) - return nullptr; - - return pymeth->fMethodInfo->fMethods[0]->Reflex(request, format); -} - -//---------------------------------------------------------------------------- -static PyMethodDef mp_methods[] = { - {(char*)"__overload__", (PyCFunction)mp_overload, METH_VARARGS, - (char*)"select overload for dispatch" }, - {(char*)"__add_overload__", (PyCFunction)mp_add_overload, METH_O, - (char*)"add a new overload" }, - {(char*)"__cpp_reflex__", (PyCFunction)mp_reflex, METH_VARARGS, - (char*)"C++ overload reflection information" }, - {(char*)nullptr, nullptr, 0, nullptr } -}; - -} // unnamed namespace - - -//= CPyCppyy method proxy type =============================================== -PyTypeObject CPPOverload_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.CPPOverload", // tp_name - sizeof(CPPOverload), // tp_basicsize - 0, // tp_itemsize - (destructor)mp_dealloc, // tp_dealloc - offsetof(CPPOverload, fVectorCall), - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / tp_compare - 0, // tp_repr - 0, // tp_as_number - 0, // tp_as_sequence - 0, // tp_as_mapping - (hashfunc)mp_hash, // tp_hash - (ternaryfunc)PyVectorcall_Call, // tp_call - (reprfunc)mp_str, // tp_str - 0, // tp_getattro - 0, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC - | Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_METHOD_DESCRIPTOR, // tp_flags - (char*)"cppyy method proxy (internal)", // tp_doc - (traverseproc)mp_traverse, // tp_traverse - (inquiry)mp_clear, // tp_clear - (richcmpfunc)mp_richcompare, // tp_richcompare - 0, // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - mp_methods, // tp_methods - 0, // tp_members - mp_getset, // tp_getset - 0, // tp_base - 0, // tp_dict - (descrgetfunc)mp_descr_get, // tp_descr_get - 0, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - (newfunc)mp_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 - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy - - -//- public members ----------------------------------------------------------- -void CPyCppyy::CPPOverload::Set(const std::string& name, std::vector& methods) -{ -// Fill in the data of a freshly created method proxy. - fMethodInfo->fName = name; - fMethodInfo->fMethods.swap(methods); - fMethodInfo->fFlags &= ~CallContext::kIsSorted; - -// special case: all constructors are considered creators by default - if (name == "__init__") - fMethodInfo->fFlags |= (CallContext::kIsCreator | CallContext::kIsConstructor); - -// special case, in heuristics mode also tag *Clone* methods as creators - if (CallContext::sMemoryPolicy == CallContext::kUseHeuristics && \ - name.find("Clone") != std::string::npos) - fMethodInfo->fFlags |= CallContext::kIsCreator; - - fVectorCall = (vectorcallfunc)mp_vectorcall; -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPOverload::AdoptMethod(PyCallable* pc) -{ -// Fill in the data of a freshly created method proxy. - fMethodInfo->fMethods.push_back(pc); - fMethodInfo->fFlags &= ~CallContext::kIsSorted; -} - -//---------------------------------------------------------------------------- -void CPyCppyy::CPPOverload::MergeOverload(CPPOverload* meth) -{ - if (!HasMethods()) // if fresh method being filled: also copy flags - fMethodInfo->fFlags = meth->fMethodInfo->fFlags; - fMethodInfo->fMethods.insert(fMethodInfo->fMethods.end(), - meth->fMethodInfo->fMethods.begin(), meth->fMethodInfo->fMethods.end()); - fMethodInfo->fFlags &= ~CallContext::kIsSorted; - meth->fMethodInfo->fDispatchMap.clear(); - meth->fMethodInfo->fMethods.clear(); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CPPOverload::FindOverload(const std::string& signature, int want_const) -{ - bool accept_any = signature == ":any:"; - CPPOverload* newmeth = nullptr; - - std::string sig1{"("}; - if (!accept_any) { - sig1.append(signature); sig1.append(")"); - sig1.erase(std::remove(sig1.begin(), sig1.end(), ' '), std::end(sig1)); - } - - CPPOverload::Methods_t& methods = fMethodInfo->fMethods; - for (auto& meth : methods) { - bool found = accept_any; - if (!found) { - PyObject* pysig2 = meth->GetSignature(false); - std::string sig2(CPyCppyy_PyText_AsString(pysig2)); - sig2.erase(std::remove(sig2.begin(), sig2.end(), ' '), std::end(sig2)); - Py_DECREF(pysig2); - if (sig1 == sig2) found = true; - - if (!found) { - pysig2 = meth->GetSignature(true); - std::string sig3(CPyCppyy_PyText_AsString(pysig2)); - sig3.erase(std::remove(sig3.begin(), sig3.end(), ' '), std::end(sig3)); - Py_DECREF(pysig2); - if (sig1 == sig3) found = true; - } - } - - if (found && 0 <= want_const) { - bool isconst = meth->IsConst(); - if (!((want_const && isconst) || (!want_const && !isconst))) - found = false; - } - - if (found) { - if (!newmeth) { - newmeth = mp_new(nullptr, nullptr, nullptr); - CPPOverload::Methods_t vec; vec.push_back(meth->Clone()); - newmeth->Set(fMethodInfo->fName, vec); - - if (fSelf) { - Py_INCREF(fSelf); - newmeth->fSelf = fSelf; - } - newmeth->fMethodInfo->fFlags = fMethodInfo->fFlags; - } else - newmeth->AdoptMethod(meth->Clone()); - - if (!accept_any) - return (PyObject*)newmeth; - } - } - - if (!newmeth) - PyErr_Format(PyExc_LookupError, "signature \"%s\" not found", signature.c_str()); - - return (PyObject*)newmeth; -} - -PyObject* CPyCppyy::CPPOverload::FindOverload(PyObject *args_tuple, int want_const) -{ - Py_ssize_t n = PyTuple_Size(args_tuple); - - CPPOverload::Methods_t& methods = fMethodInfo->fMethods; - - // This value is set based on the maximum penalty in Cppyy::CompareMethodArgType - Py_ssize_t min_score = INT_MAX; - bool found = false; - size_t best_method = 0, method_index = 0; - - for (auto& meth : methods) { - if (0 <= want_const) { - bool isconst = meth->IsConst(); - if (!((want_const && isconst) || (!want_const && !isconst))) - continue; - } - - int score = meth->GetArgMatchScore(args_tuple); - - if (score < min_score) { - found = true; - min_score = score; - best_method = method_index; - } - - method_index++; - } - - if (!found) { - std::string sigargs("("); - - for (int i = 0; i < n; i++) { - PyObject *pItem = PyTuple_GetItem(args_tuple, i); - if(!CPyCppyy_PyText_Check(pItem)) { - PyErr_Format(PyExc_LookupError, "argument types should be in string format"); - return (PyObject*) nullptr; - } - std::string arg_type(CPyCppyy_PyText_AsString(pItem)); - sigargs += arg_type + ", "; - } - sigargs += ")"; - - PyErr_Format(PyExc_LookupError, "signature with arguments \"%s\" not found", sigargs.c_str()); - return (PyObject*) nullptr; - } - - CPPOverload* newmeth = mp_new(nullptr, nullptr, nullptr); - CPPOverload::Methods_t vec; - vec.push_back(methods[best_method]->Clone()); - newmeth->Set(fMethodInfo->fName, vec); - - if (fSelf) { - Py_INCREF(fSelf); - newmeth->fSelf = fSelf; - } - newmeth->fMethodInfo->fFlags = fMethodInfo->fFlags; - - return (PyObject*) newmeth; -} - -//---------------------------------------------------------------------------- -CPyCppyy::CPPOverload::MethodInfo_t::~MethodInfo_t() -{ -// Destructor (this object is reference counted). - for (Methods_t::iterator it = fMethods.begin(); it != fMethods.end(); ++it) { - delete *it; - } - fMethods.clear(); - delete fRefCount; - Py_XDECREF(fDoc); -} - -// TODO: something like PyMethod_Fini to clear up the free_list diff --git a/src/CPyCppyy/src/CPPOverload.h b/src/CPyCppyy/src/CPPOverload.h deleted file mode 100644 index 65c54b4..0000000 --- a/src/CPyCppyy/src/CPPOverload.h +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef CPYCPPYY_CPPOVERLOAD_H -#define CPYCPPYY_CPPOVERLOAD_H - -// Bindings -#include "PyCallable.h" - -// Standard -#include -#include -#include -#include - - -namespace CPyCppyy { - -// signature hashes are also used by TemplateProxy -inline uint64_t HashSignature(CPyCppyy_PyArgs_t args, size_t nargsf) -{ -// Build a hash from the types of the given python function arguments. - uint64_t hash = 0; - - Py_ssize_t nargs = CPyCppyy_PyArgs_GET_SIZE(args, nargsf); - for (Py_ssize_t i = 0; i < nargs; ++i) { - // TODO: hashing in the ref-count is for moves; resolve this together with the - // improved overloads for implicit conversions - PyObject* pyobj = CPyCppyy_PyArgs_GET_ITEM(args, i); - hash += (uint64_t)Py_TYPE(pyobj); -#if PY_VERSION_HEX >= 0x030e0000 - hash += (uint64_t)(PyUnstable_Object_IsUniqueReferencedTemporary(pyobj) ? 1 : 0); -#else - hash += (uint64_t)(Py_REFCNT(pyobj) == 1 ? 1 : 0); -#endif - hash += (hash << 10); hash ^= (hash >> 6); - } - - hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); - - return hash; -} - -class CPPOverload { -public: - typedef std::vector> DispatchMap_t; - typedef std::vector Methods_t; - - struct MethodInfo_t { - MethodInfo_t() : fDoc(nullptr), fFlags(CallContext::kNone) - { fRefCount = new int(1); } - ~MethodInfo_t(); - - std::string fName; - CPPOverload::DispatchMap_t fDispatchMap; - CPPOverload::Methods_t fMethods; - PyObject* fDoc; - uint32_t fFlags; - - int* fRefCount; - - private: - MethodInfo_t(const MethodInfo_t&) = delete; - MethodInfo_t& operator=(const MethodInfo_t&) = delete; - }; - -public: - void Set(const std::string& name, std::vector& methods); - void AdoptMethod(PyCallable* pc); - void MergeOverload(CPPOverload* meth); - - const std::string& GetName() const { return fMethodInfo->fName; } - bool HasMethods() const { return !fMethodInfo->fMethods.empty(); } - -// find a method based on the provided signature - PyObject* FindOverload(const std::string& signature, int want_const = -1); - PyObject* FindOverload(PyObject *args_tuple, int want_const = -1); - -public: // public, as the python C-API works with C structs - PyObject_HEAD - CPPInstance* fSelf; // must be first (same layout as TemplateProxy) - MethodInfo_t* fMethodInfo; - uint32_t fFlags; - vectorcallfunc fVectorCall; - -private: - CPPOverload() = delete; -}; - - -//- method proxy type and type verification ---------------------------------- -CPYCPPYY_IMPORT PyTypeObject CPPOverload_Type; - -template -inline bool CPPOverload_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &CPPOverload_Type); -} - -template -inline bool CPPOverload_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &CPPOverload_Type; -} - -//- creation ----------------------------------------------------------------- -inline CPPOverload* CPPOverload_New( - const std::string& name, std::vector& methods) -{ -// Create and initialize a new method proxy from the overloads. - CPPOverload* pymeth = (CPPOverload*)CPPOverload_Type.tp_new(&CPPOverload_Type, nullptr, nullptr); - pymeth->Set(name, methods); - return pymeth; -} - -inline CPPOverload* CPPOverload_New(const std::string& name, PyCallable* method) -{ -// Create and initialize a new method proxy from the method. - std::vector p; - p.push_back(method); - return CPPOverload_New(name, p); -} - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPOVERLOAD_H diff --git a/src/CPyCppyy/src/CPPScope.cxx b/src/CPyCppyy/src/CPPScope.cxx deleted file mode 100644 index 841f4bf..0000000 --- a/src/CPyCppyy/src/CPPScope.cxx +++ /dev/null @@ -1,706 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CPPScope.h" -#include "CPPDataMember.h" -#include "CPPEnum.h" -#include "CPPFunction.h" -#include "CPPOverload.h" -#include "Cppyy.h" -#include "CustomPyTypes.h" -#include "Dispatcher.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TemplateProxy.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include // for for_each -#include -#include -#include - - -namespace CPyCppyy { - -extern PyTypeObject CPPInstance_Type; - -//- helpers ------------------------------------------------------------------ -static inline PyObject* add_template(PyObject* pyclass, - const std::string& name, std::vector* overloads = nullptr) -{ -// If templated, the user-facing function must be the template proxy, but the -// specific lookup must be the current overload, if already found. - - const std::string& ncl = TypeManip::clean_type(name); - PyObject* pyncl = CPyCppyy_PyText_FromString(ncl.c_str()); - TemplateProxy* pytmpl = (TemplateProxy*)PyType_Type.tp_getattro(pyclass, pyncl); - if (!pytmpl) { - PyErr_Clear(); - pytmpl = TemplateProxy_New(ncl, ncl, pyclass); - // cache the template on its clean name - PyType_Type.tp_setattro(pyclass, pyncl, (PyObject*)pytmpl); - Py_DECREF(pyncl); - } else if (!TemplateProxy_CheckExact((PyObject*)pytmpl)) { - Py_DECREF(pytmpl); - return nullptr; - } - - if (overloads) { - // adopt the new overloads - if (ncl != name) - for (auto clb : *overloads) pytmpl->AdoptTemplate(clb); - else - for (auto clb : *overloads) pytmpl->AdoptMethod(clb); - } - -// the caller expects a method matching the full name, thus is a specialization -// was requested, do not return the template yet - if (ncl == name) - return (PyObject*)pytmpl; - - Py_DECREF(pytmpl); - return nullptr; // so that caller caches the method on full name -} - - -//= CPyCppyy type proxy construction/destruction ============================= -static PyObject* meta_alloc(PyTypeObject* meta, Py_ssize_t nitems) -{ -// pure memory allocation; object initialization is in pt_new - return PyType_Type.tp_alloc(meta, nitems); -} - -//---------------------------------------------------------------------------- -static void meta_dealloc(CPPScope* scope) -{ - if (scope->fFlags & CPPScope::kIsNamespace) { - if (scope->fImp.fUsing) { - for (auto pyobj : *scope->fImp.fUsing) Py_DECREF(pyobj); - delete scope->fImp.fUsing; scope->fImp.fUsing = nullptr; - } - } else if (!(scope->fFlags & CPPScope::kIsPython)) { - delete scope->fImp.fCppObjects; scope->fImp.fCppObjects = nullptr; - } - delete scope->fOperators; - free(scope->fModuleName); - return PyType_Type.tp_dealloc((PyObject*)scope); -} - -//----------------------------------------------------------------------------- -static PyObject* meta_getcppname(CPPScope* scope, void*) -{ - if ((void*)scope == (void*)&CPPInstance_Type) - return CPyCppyy_PyText_FromString("CPPInstance_Type"); - return CPyCppyy_PyText_FromString(Cppyy::GetScopedFinalName(scope->fCppType).c_str()); -} - -//----------------------------------------------------------------------------- -static PyObject* meta_getmodule(CPPScope* scope, void*) -{ - if ((void*)scope == (void*)&CPPInstance_Type) - return CPyCppyy_PyText_FromString("cppyy.gbl"); - - if (scope->fModuleName) - return CPyCppyy_PyText_FromString(scope->fModuleName); - -// get C++ representation of outer scope - Cppyy::TCppScope_t parent_scope = Cppyy::GetParentScope(scope->fCppType); - if (parent_scope == Cppyy::GetGlobalScope()) - return CPyCppyy_PyText_FromString(const_cast("cppyy.gbl")); - -// now peel scopes one by one, pulling in the python naming (which will -// simply recurse if not overridden in python) - PyObject* pymodule = nullptr; - PyObject* pyscope = CPyCppyy::GetScopeProxy(parent_scope); - if (pyscope) { - // get the module of our module - pymodule = PyObject_GetAttr(pyscope, PyStrings::gModule); - if (pymodule) { - // append name of our module - PyObject* pymodname = PyObject_GetAttr(pyscope, PyStrings::gName); - if (pymodname) { - CPyCppyy_PyText_AppendAndDel(&pymodule, CPyCppyy_PyText_FromString(".")); - CPyCppyy_PyText_AppendAndDel(&pymodule, pymodname); - } - } - Py_DECREF(pyscope); - } - - if (pymodule) - return pymodule; - PyErr_Clear(); - -// lookup through python failed, so simply cook up a '::' -> '.' replacement - std::string modname = Cppyy::GetScopedFinalName(parent_scope); - TypeManip::cppscope_to_pyscope(modname); - return CPyCppyy_PyText_FromString(("cppyy.gbl."+modname).c_str()); -} - -//----------------------------------------------------------------------------- -static int meta_setmodule(CPPScope* scope, PyObject* value, void*) -{ - if ((void*)scope == (void*)&CPPInstance_Type) { - PyErr_SetString(PyExc_AttributeError, - "attribute \'__module__\' of 'cppyy.CPPScope\' objects is not writable"); - return -1; - } - - const char* newname = CPyCppyy_PyText_AsStringChecked(value); - if (!value) - return -1; - - free(scope->fModuleName); - Py_ssize_t sz = CPyCppyy_PyText_GET_SIZE(value); - scope->fModuleName = (char*)malloc(sz+1); - memcpy(scope->fModuleName, newname, sz+1); - - return 0; -} - -//---------------------------------------------------------------------------- -static PyObject* meta_repr(CPPScope* scope) -{ -// Specialized b/c type_repr expects __module__ to live in the dictionary, -// whereas it is a property (to save memory). - if ((void*)scope == (void*)&CPPInstance_Type) - return CPyCppyy_PyText_FromFormat( - const_cast(""), scope); - - if (scope->fFlags & (CPPScope::kIsMeta | CPPScope::kIsPython)) { - // either meta type or Python-side derived class: use default type printing - return PyType_Type.tp_repr((PyObject*)scope); - } - -// skip in case some Python-side derived meta class - if (!CPPScope_Check(scope) || !scope->fCppType) - return PyType_Type.tp_repr((PyObject*)scope); - -// printing of C++ classes - PyObject* modname = meta_getmodule(scope, nullptr); - std::string clName = Cppyy::GetFinalName(scope->fCppType); - const char* kind = (scope->fFlags & CPPScope::kIsNamespace) ? "namespace" : "class"; - - PyObject* repr = CPyCppyy_PyText_FromFormat("<%s %s.%s at %p>", - kind, CPyCppyy_PyText_AsString(modname), clName.c_str(), scope); - - Py_DECREF(modname); - return repr; -} - - -//= CPyCppyy type metaclass behavior ========================================= -static PyObject* pt_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) -{ -// Called when CPPScope acts as a metaclass; since type_new always resets -// tp_alloc, and since it does not call tp_init on types, the metaclass is -// being fixed up here, and the class is initialized here as well. - -// fixup of metaclass (left permanent, and in principle only called once b/c -// cppyy caches python classes) - subtype->tp_alloc = (allocfunc)meta_alloc; - subtype->tp_dealloc = (destructor)meta_dealloc; - -// creation of the python-side class; extend the size if this is a smart ptr -// -// A namespace can never be a smart pointer, so skip the check for namespaces. -// This matters beyond performance: GetSmartPtrInfo() resolves the scope name -// through a slow interpreter lookup that triggers autoloading of the library -// providing the scope, which we don't want to do unnecessarily. - Cppyy::TCppScope_t raw; - Cppyy::TCppMethod_t deref; - if (CPPScope_CheckExact(subtype) && !Cppyy::IsNamespace(((CPPScope*)subtype)->fCppType)) { - if (Cppyy::GetSmartPtrInfo(Cppyy::GetScopedFinalName(((CPPScope*)subtype)->fCppType), &raw, &deref)) - subtype->tp_basicsize = sizeof(CPPSmartClass); - } - - CPPScope* result = (CPPScope*)PyType_Type.tp_new(subtype, args, kwds); - if (!CPPScope_Check(result)) { - // either failed or custom user-side metaclass that can't be handled here - return nullptr; - } - - result->fFlags = CPPScope::kNone; - result->fOperators = nullptr; - result->fModuleName = nullptr; - - if (raw && deref) { - result->fFlags |= CPPScope::kIsSmart; - ((CPPSmartClass*)result)->fUnderlyingType = raw; - ((CPPSmartClass*)result)->fDereferencer = deref; - } - -// initialization of class (based on metatype) - if (!CPPScope_CheckExact(subtype) || !strstr(subtype->tp_name, "_meta") /* convention */) { - // there has been a user meta class override in a derived class, so do - // the consistent thing, thus allowing user control over naming - result->fCppType = Cppyy::GetScope( - CPyCppyy_PyText_AsString(PyTuple_GET_ITEM(args, 0))); - } else { - // coming here from cppyy or from sub-classing in python; take the - // C++ type from the meta class to make sure that the latter category - // has fCppType properly set (it inherits the meta class, but has an - // otherwise unknown (or wrong) C++ type) - result->fCppType = ((CPPScope*)subtype)->fCppType; - - // the following is not robust, but by design, C++ classes get their - // dictionaries filled after creation (chicken & egg problem as they - // can return themselves in methods), whereas a derived Python class - // with method overrides will have a non-empty dictionary (even if it - // has no methods, it will at least have a module name) - if (3 <= PyTuple_GET_SIZE(args)) { - PyObject* dct = PyTuple_GET_ITEM(args, 2); - Py_ssize_t sz = PyDict_Size(dct); - if (0 < sz && !Cppyy::IsNamespace(result->fCppType)) { - result->fFlags |= CPPScope::kIsPython; - if (1 < PyTuple_GET_SIZE(PyTuple_GET_ITEM(args, 1))) - result->fFlags |= CPPScope::kIsMultiCross; - std::ostringstream errmsg; - if (!InsertDispatcher(result, PyTuple_GET_ITEM(args, 1), dct, errmsg)) { - PyErr_Format(PyExc_TypeError, "no python-side overrides supported (%s)", errmsg.str().c_str()); - return nullptr; - } else { - // the direct base can be useful for some templates, such as shared_ptrs, - // so make it accessible (the __cpp_cross__ data member also signals that - // this is a cross-inheritance class) - PyObject* bname = CPyCppyy_PyText_FromString(Cppyy::GetBaseName(result->fCppType, 0).c_str()); - if (PyObject_SetAttrString((PyObject*)result, "__cpp_cross__", bname) == -1) - PyErr_Clear(); - Py_DECREF(bname); - } - } else if (sz == (Py_ssize_t)-1) - PyErr_Clear(); - } - } - -// if the user messed with the metaclass, then we may not have a C++ type, -// simply return here before more damage gets done - if (!result->fCppType) - return (PyObject*)result; - -// maps for using namespaces and tracking objects - if (!Cppyy::IsNamespace(result->fCppType)) { - static Cppyy::TCppScope_t exc_type = Cppyy::GetScope("exception", Cppyy::GetScope("std")); - if (Cppyy::IsSubclass(result->fCppType, exc_type)) - result->fFlags |= CPPScope::kIsException; - if (!(result->fFlags & CPPScope::kIsPython)) - result->fImp.fCppObjects = new CppToPyMap_t; - else { - // special case: the C++ objects should be stored with the associated C++, not Python, type - CPPClass* kls = (CPPClass*)GetScopeProxy(result->fCppType); - if (kls) { - result->fImp.fCppObjects = kls->fImp.fCppObjects; - Py_DECREF(kls); - } else - result->fImp.fCppObjects = nullptr; - } - } else { - result->fImp.fUsing = nullptr; - result->fFlags |= CPPScope::kIsNamespace; - } - - if (PyErr_Occurred()) { - Py_DECREF((PyObject*)result); - return nullptr; - } - - return (PyObject*)result; -} - - -//---------------------------------------------------------------------------- -static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) -{ -// normal type-based lookup - PyObject* attr = PyType_Type.tp_getattro(pyclass, pyname); - if (pyclass == (PyObject*)&CPPInstance_Type) - return attr; - - PyObject* possibly_shadowed = nullptr; - if (attr) { - if (CPPScope_Check(attr) && CPPScope_Check(pyclass) && !(((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace)) { - // TODO: the goal here is to prevent typedefs that are shadowed in subclasses - // to be found as from the base class. The better approach would be to find - // all typedefs at class creation and insert placeholders to flag here. The - // current typedef loop in gInterpreter won't do, however. - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - if (dct) { - PyObject* attr_from_dict = PyObject_GetItem(dct, pyname); - Py_DECREF(dct); - if (attr_from_dict) { - Py_DECREF(attr); - return attr_from_dict; - } - possibly_shadowed = attr; - attr = nullptr; - } - PyErr_Clear(); - } else if (CPPScope_Check(attr) && CPPScope_Check(pyclass) && ((CPPScope*)attr)->fFlags & CPPScope::kIsException) { - return CreateExcScopeProxy(attr, pyname, pyclass); - } else - return attr; - } - - if (!CPyCppyy_PyText_CheckExact(pyname) || !CPPScope_Check(pyclass)) - return possibly_shadowed; - -// filter for python specials - std::string name = CPyCppyy_PyText_AsString(pyname); - if (name.size() >= 5 && name.compare(0, 2, "__") == 0 && - name.compare(name.size()-2, name.size(), "__") == 0) - return possibly_shadowed; - -// more elaborate search in case of failure (eg. for inner classes on demand) - std::vector errors; - Utility::FetchError(errors); - attr = CreateScopeProxy(name, pyclass); - - if (CPPScope_Check(attr) && (((CPPScope*)attr)->fFlags & CPPScope::kIsException)) { - // Instead of the CPPScope, return a fresh exception class derived from CPPExcInstance. - return CreateExcScopeProxy(attr, pyname, pyclass); - } - - bool templated_functions_checked = false; - CPPScope* klass = ((CPPScope*)pyclass); - if (!attr) { - Utility::FetchError(errors); - Cppyy::TCppScope_t scope = klass->fCppType; - - // namespaces may have seen updates in their list of global functions, which - // are available as "methods" even though they're not really that - if ((klass->fFlags & CPPScope::kIsNamespace) || - scope == Cppyy::GetGlobalScope()) { - // tickle lazy lookup of functions - const std::vector methods = - Cppyy::GetMethodsFromName(scope, name); - if (!methods.empty()) { - // function exists, now collect overloads - std::vector overloads; - for (auto method : methods) { - overloads.push_back(new CPPFunction(scope, method)); - } - - // Note: can't re-use Utility::AddClass here, as there's the risk of - // a recursive call. Simply add method directly, as we're guaranteed - // that it doesn't exist yet. - if (Cppyy::ExistsMethodTemplate(scope, name)) - attr = add_template(pyclass, name, &overloads); - else - attr = (PyObject*)CPPOverload_New(name, overloads); - templated_functions_checked = true; - } - } - - if (!attr) { - Cppyy::TCppScope_t lookup_result = Cppyy::GetNamed(name, scope); - if (Cppyy::IsVariable(lookup_result) || Cppyy::IsEnumConstant(lookup_result)) { - attr = (PyObject*)CPPDataMember_New(scope, lookup_result); - } else if (Cppyy::IsTypedefed(lookup_result)) { - Cppyy::TCppType_t resolved_type = Cppyy::ResolveType(Cppyy::GetTypeFromScope(lookup_result)); - const std::string& cpd = TypeManip::compound(Cppyy::GetTypeAsString(resolved_type)); - if (cpd == "*") { - Cppyy::TCppScope_t tcl = Cppyy::GetScopeFromType(Cppyy::ResolveType(resolved_type)); - if (tcl) { - typedefpointertoclassobject* tpc = - PyObject_New(typedefpointertoclassobject, &TypedefPointerToClass_Type); - tpc->fCppType = tcl; - attr = (PyObject*)tpc; - } - } - } - } - - // function templates that have not been instantiated (namespaces _may_ have already - // been taken care of, by their general function lookup above) - if (!attr && !templated_functions_checked) { - if (Cppyy::ExistsMethodTemplate(scope, name)) - attr = add_template(pyclass, name); - else { - // for completeness in error reporting - PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ template", name.c_str()); - Utility::FetchError(errors); - } - } - - // enums types requested as type (rather than the constants) - if (!attr) { - Cppyy::TCppScope_t enumerator = Cppyy::GetUnderlyingScope(Cppyy::GetNamed(name, scope)); - if (Cppyy::IsEnumScope(enumerator)) { - // enum types (incl. named and class enums) - attr = (PyObject*)CPPEnum_New(name, enumerator); - } else { - // for completeness in error reporting - PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ enum", name.c_str()); - Utility::FetchError(errors); - } - } - - if (attr) { - // cache the result - if (CPPDataMember_Check(attr)) { - PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pyname, attr); - - Py_DECREF(attr); - // The call below goes through "dm_get" - attr = PyType_Type.tp_getattro(pyclass, pyname); - if (!attr && PyErr_Occurred()) - Utility::FetchError(errors); - } else - PyType_Type.tp_setattro(pyclass, pyname, attr); - - } else { - Utility::FetchError(errors); - } - } - - if (!attr && (klass->fFlags & CPPScope::kIsNamespace)) { - // refresh using list as necessary - const std::vector& uv = Cppyy::GetUsingNamespaces(klass->fCppType); - if (!klass->fImp.fUsing || uv.size() != klass->fImp.fUsing->size()) { - if (klass->fImp.fUsing) { - for (auto pyref : *klass->fImp.fUsing) Py_DECREF(pyref); - klass->fImp.fUsing->clear(); - } else - klass->fImp.fUsing = new std::vector; - - // reload and reset weak refs - for (auto uid : uv) { - std::string uname = Cppyy::GetScopedFinalName(uid); - PyObject* pyuscope = CreateScopeProxy(uname); - if (pyuscope) { - klass->fImp.fUsing->push_back(PyWeakref_NewRef(pyuscope, nullptr)); - // the namespace may not otherwise be held, so tie the lifetimes - PyObject* llname = CPyCppyy_PyText_FromString(("__lifeline_"+uname).c_str()); - PyType_Type.tp_setattro(pyclass, llname, pyuscope); - Py_DECREF(llname); - Py_DECREF(pyuscope); - } else { - PyErr_Clear(); - } - } - } - - // try all outstanding using namespaces in turn to find the attribute (will cache - // locally later; TODO: doing so may cause pathological cases) - for (auto pyref : *klass->fImp.fUsing) { - PyObject* pyuscope = CPyCppyy_GetWeakRef(pyref); - if (pyuscope) { - attr = PyObject_GetAttr(pyuscope, pyname); - if (!attr) PyErr_Clear(); - Py_DECREF(pyuscope); - } - if (attr) - break; - } - } - -// if the attribute was not found but could possibly have been shadowed, insert it into -// the dict now, to short-circuit future lookups (TODO: this is part of the workaround -// described above of which the true solution is to loop over all typedefs at creation -// time for the class) - if (possibly_shadowed) { - if (attr) { - Py_DECREF(possibly_shadowed); - } else { - attr = possibly_shadowed; - PyType_Type.tp_setattro(pyclass, pyname, attr); - } - } - - if (attr) { - PyErr_Clear(); - } else { - // not found: prepare a full error report - PyObject* topmsg = nullptr; - PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); - PyObject* sklass = PyObject_Str(pyclass); - PyErr_Restore(pytype, pyvalue, pytrace); - if (sklass) { - topmsg = CPyCppyy_PyText_FromFormat("%s has no attribute \'%s\'. Full details:", - CPyCppyy_PyText_AsString(sklass), CPyCppyy_PyText_AsString(pyname)); - Py_DECREF(sklass); - } else { - topmsg = CPyCppyy_PyText_FromFormat("no such attribute \'%s\'. Full details:", - CPyCppyy_PyText_AsString(pyname)); - } - SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_AttributeError /* default error */); - } - - return attr; -} - -//---------------------------------------------------------------------------- -static int meta_setattro(PyObject* pyclass, PyObject* pyname, PyObject* pyval) -{ -// Global data and static data in namespaces is found lazily, thus if the first -// use is setting of the global data by the user, it will not be reflected on -// the C++ side, b/c there is no descriptor yet. This triggers the creation for -// for such data as necessary. The many checks to narrow down the specific case -// are needed to prevent unnecessary lookups and recursion. - // skip if the given pyval is a descriptor already, or an unassignable class - if (((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace - && !CPyCppyy::CPPDataMember_Check(pyval) - && !CPyCppyy::CPPScope_Check(pyval)) { - std::string name = CPyCppyy_PyText_AsString(pyname); - if (Cppyy::GetNamed(name, ((CPPScope*)pyclass)->fCppType)) - meta_getattro(pyclass, pyname); // triggers creation - } - - return PyType_Type.tp_setattro(pyclass, pyname, pyval); -} - - -//---------------------------------------------------------------------------- -static PyObject* meta_reflex(CPPScope* klass, PyObject* args) -{ -// Provide the requested reflection information. - Cppyy::Reflex::RequestId_t request = -1; - Cppyy::Reflex::FormatId_t format = Cppyy::Reflex::OPTIMAL; - if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, &format)) - return nullptr; - - switch (request) { - case Cppyy::Reflex::IS_NAMESPACE: - if (klass->fFlags & CPPScope::kIsNamespace) - Py_RETURN_TRUE; - Py_RETURN_FALSE; - break; - case Cppyy::Reflex::IS_AGGREGATE: - // this is not the strict C++ definition of aggregates, but is closer to what - // is needed for Numba and C calling conventions (TODO: probably have to check - // for all public data types, too, and maybe for no padding?) - if (Cppyy::IsAggregate(klass->fCppType) || !Cppyy::HasVirtualDestructor(klass->fCppType)) - Py_RETURN_TRUE; - Py_RETURN_FALSE; - break; - } - - PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", request, format); - return nullptr; -} - -//---------------------------------------------------------------------------- -// p2.7 does not have a __dir__ in object, and object.__dir__ in p3 does not -// quite what I'd expected of it, so the following pulls in the internal code -#include "PyObjectDir27.inc" - -static PyObject* meta_dir(CPPScope* klass) -{ -// Collect a list of everything (currently) available in the namespace. -// The backend can filter by returning empty strings. Special care is -// taken for functions, which need not be unique (overloading). - using namespace Cppyy; - - if ((void*)klass == (void*)&CPPInstance_Type) - return PyList_New(0); - - if (!CPyCppyy::CPPScope_Check((PyObject*)klass)) { - PyErr_SetString(PyExc_TypeError, "C++ proxy scope expected"); - return nullptr; - } - - PyObject* dirlist = _generic_dir((PyObject*)klass); - if (!(klass->fFlags & CPPScope::kIsNamespace)) - return dirlist; - - std::set cppnames; - Cppyy::GetAllCppNames(klass->fCppType, cppnames); - -// cleanup names - std::set dir_cppnames; - for (const std::string& name : cppnames) { - if (name.find("__", 0, 2) != std::string::npos || \ - name.find("<") != std::string::npos || \ - name.find("operator", 0, 8) != std::string::npos) continue; - dir_cppnames.insert(name); - } - -// get rid of duplicates - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(dirlist); ++i) - dir_cppnames.insert(CPyCppyy_PyText_AsString(PyList_GET_ITEM(dirlist, i))); - - Py_DECREF(dirlist); - dirlist = PyList_New(dir_cppnames.size()); - -// copy total onto python list - Py_ssize_t i = 0; - for (const auto& name : dir_cppnames) { - PyList_SET_ITEM(dirlist, i++, CPyCppyy_PyText_FromString(name.c_str())); - } - return dirlist; -} - -//----------------------------------------------------------------------------- -static PyMethodDef meta_methods[] = { - {(char*)"__cpp_reflex__", (PyCFunction)meta_reflex, METH_VARARGS, - (char*)"C++ datamember reflection information" }, - {(char*)"__dir__", (PyCFunction)meta_dir, METH_NOARGS, nullptr}, - {(char*)nullptr, nullptr, 0, nullptr} -}; - - -//----------------------------------------------------------------------------- -static PyGetSetDef meta_getset[] = { - {(char*)"__cpp_name__", (getter)meta_getcppname, nullptr, nullptr, nullptr}, - {(char*)"__module__", (getter)meta_getmodule, (setter)meta_setmodule, nullptr, nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} -}; - - -//= CPyCppyy object proxy type type ========================================== -PyTypeObject CPPScope_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.CPPScope", // tp_name - sizeof(CPyCppyy::CPPScope), // tp_basicsize - 0, // tp_itemsize - 0, // tp_dealloc - 0, // tp_vectorcall_offset / tp_print - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / tp_compare - (reprfunc)meta_repr, // tp_repr - 0, // tp_as_number - 0, // tp_as_sequence - 0, // tp_as_mapping - 0, // tp_hash - 0, // tp_call - 0, // tp_str - (getattrofunc)meta_getattro, // tp_getattro - (setattrofunc)meta_setattro, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS, // tp_flags - (char*)"CPyCppyy metatype (internal)", // tp_doc - 0, // tp_traverse - 0, // tp_clear - 0, // tp_richcompare - 0, // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - meta_methods, // tp_methods - 0, // tp_members - meta_getset, // tp_getset - &PyType_Type, // tp_base - 0, // tp_dict - 0, // tp_descr_get - 0, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - (newfunc)pt_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 - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/src/CPyCppyy/src/CPPScope.h b/src/CPyCppyy/src/CPPScope.h deleted file mode 100644 index 355dfae..0000000 --- a/src/CPyCppyy/src/CPPScope.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef CPYCPPYY_CPPSCOPE_H -#define CPYCPPYY_CPPSCOPE_H - -#include "Python.h" -#include "Cppyy.h" -#include - -#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION == 2 - -// In p2.2, PyHeapTypeObject is not yet part of the interface -#include "structmember.h" - -typedef struct { - PyTypeObject type; - PyNumberMethods as_number; - PySequenceMethods as_sequence; - PyMappingMethods as_mapping; - PyBufferProcs as_buffer; - PyObject *name, *slots; - PyMemberDef members[1]; -} PyHeapTypeObject; - -#endif - -// Standard -#include -#include - - -namespace CPyCppyy { - -/** Type object to hold class reference (this is only semantically a presentation - of CPPScope instances, not in a C++ sense) - @author WLAV - @date 07/06/2017 - @version 2.0 - */ - -typedef std::unordered_map CppToPyMap_t; -namespace Utility { struct PyOperators; } - -class CPPScope { -public: - enum EFlags { - kNone = 0x0, - kIsMeta = 0x0001, - kIsNamespace = 0x0002, - kIsException = 0x0004, - kIsSmart = 0x0008, - kIsPython = 0x0010, - kIsMultiCross = 0x0020, - kIsInComplete = 0x0040, - kActiveImplicitCall = 0x0080, - kNoOSInsertion = 0x0100, - kGblOSInsertion = 0x0200, - kNoPrettyPrint = 0x0400 }; - -public: - PyHeapTypeObject fType; - Cppyy::TCppScope_t fCppType; - uint32_t fFlags; - union { - CppToPyMap_t* fCppObjects; // classes only - std::vector* fUsing; // namespaces only - } fImp; - Utility::PyOperators* fOperators; - char* fModuleName; - -private: - CPPScope() = delete; -}; - -typedef CPPScope CPPClass; - -class CPPSmartClass : public CPPClass { -public: - Cppyy::TCppScope_t fUnderlyingType; - Cppyy::TCppMethod_t fDereferencer; -}; - - -//- metatype type and type verification -------------------------------------- -extern PyTypeObject CPPScope_Type; - -template -inline bool CPPScope_Check(T* object) -{ -// Short-circuit the type check by checking tp_new which all generated subclasses -// of CPPScope inherit. - return object && \ - (Py_TYPE(object)->tp_new == CPPScope_Type.tp_new || \ - PyObject_TypeCheck(object, &CPPScope_Type)); -} - -template -inline bool CPPScope_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &CPPScope_Type; -} - -//- creation ----------------------------------------------------------------- -inline CPPScope* CPPScopeMeta_New(Cppyy::TCppScope_t klass, PyObject* args) -{ -// Create and initialize a new scope meta class - CPPScope* pymeta = (CPPScope*)PyType_Type.tp_new(&CPPScope_Type, args, nullptr); - if (!pymeta) return pymeta; - -// set the klass id, for instances and Python-side derived classes to pick up - pymeta->fCppType = klass; - pymeta->fFlags = CPPScope::kIsMeta; - pymeta->fImp.fCppObjects = nullptr; - pymeta->fOperators = nullptr; - pymeta->fModuleName = nullptr; - - return pymeta; -} - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CPPSCOPE_H diff --git a/src/CPyCppyy/src/CPyCppyy.h b/src/CPyCppyy/src/CPyCppyy.h deleted file mode 100644 index 8cf3e35..0000000 --- a/src/CPyCppyy/src/CPyCppyy.h +++ /dev/null @@ -1,223 +0,0 @@ -#ifndef CPYCPPYY_CPYCPPYY_H -#define CPYCPPYY_CPYCPPYY_H - -#ifdef _WIN32 -// Disable warning C4275: non dll-interface class -#pragma warning (disable : 4275) -// Disable warning C4251: needs to have dll-interface to be used by clients -#pragma warning (disable : 4251) -// Disable warning C4800: 'int' : forcing value to bool -#pragma warning (disable : 4800) -// Avoid that pyconfig.h decides using a #pragma what library python library to use -//#define MS_NO_COREDLL 1 -#endif - -// to prevent problems with fpos_t and redefinition warnings -#if defined(linux) - -#include - -#ifdef _POSIX_C_SOURCE -#undef _POSIX_C_SOURCE -#endif - -#ifdef _FILE_OFFSET_BITS -#undef _FILE_OFFSET_BITS -#endif - -#ifdef _XOPEN_SOURCE -#undef _XOPEN_SOURCE -#endif - -#endif // linux - -#define PY_SSIZE_T_CLEAN -#include "Python.h" -#include - -namespace CPyCppyy { - typedef Py_ssize_t dim_t; -} // namespace CPyCppyy - -#if PY_VERSION_HEX >= 0x030b0000 - typedef Py_ssize_t (*dict_lookup_func)( - PyDictObject*, PyObject*, Py_hash_t, PyObject**); -#else - typedef Py_ssize_t (*dict_lookup_func)( - PyDictObject*, PyObject*, Py_hash_t, PyObject***, Py_ssize_t*); -#endif - -// for 3.0 support (backwards compatibility, really) -#define CPyCppyy_PyText_Check PyUnicode_Check -#define CPyCppyy_PyText_CheckExact PyUnicode_CheckExact -#define CPyCppyy_PyText_AsString PyUnicode_AsUTF8 -#define CPyCppyy_PyText_AsStringChecked PyUnicode_AsUTF8 -#define CPyCppyy_PyText_GetSize PyUnicode_GetSize -#define CPyCppyy_PyText_GET_SIZE PyUnicode_GET_LENGTH -#define CPyCppyy_PyUnicode_GET_SIZE PyUnicode_GET_LENGTH -#define CPyCppyy_PyText_FromFormat PyUnicode_FromFormat -#define CPyCppyy_PyText_FromString PyUnicode_FromString -#define CPyCppyy_PyText_InternFromString PyUnicode_InternFromString -#define CPyCppyy_PyText_Append PyUnicode_Append -#define CPyCppyy_PyText_AppendAndDel PyUnicode_AppendAndDel -#define CPyCppyy_PyText_FromStringAndSize PyUnicode_FromStringAndSize - -#define _CPyCppyy_PyText_AsStringAndSize PyUnicode_AsUTF8AndSize - -static inline const char* CPyCppyy_PyText_AsStringAndSize(PyObject* pystr, Py_ssize_t* size) -{ - const char* cstr = _CPyCppyy_PyText_AsStringAndSize(pystr, size); - if (!cstr && PyBytes_CheckExact(pystr)) { - PyErr_Clear(); - PyBytes_AsStringAndSize(pystr, (char**)&cstr, size); - } - return cstr; -} - -#define CPyCppyy_PyText_Type PyUnicode_Type - -#define PyIntObject PyLongObject -#define PyInt_Check PyLong_Check -#define PyInt_AsLong PyLong_AsLong -#define PyInt_AS_LONG PyLong_AsLong -#define PyInt_AsSsize_t PyLong_AsSsize_t -#define PyInt_CheckExact PyLong_CheckExact -#define PyInt_FromLong PyLong_FromLong -#define PyInt_FromSsize_t PyLong_FromSsize_t - -#define PyInt_Type PyLong_Type - -#define CPyCppyy_PyCapsule_New PyCapsule_New -#define CPyCppyy_PyCapsule_CheckExact PyCapsule_CheckExact -#define CPyCppyy_PyCapsule_GetPointer PyCapsule_GetPointer - -#define CPPYY__long__ "__int__" -#define CPPYY__idiv__ "__itruediv__" -#define CPPYY__div__ "__truediv__" -#define CPPYY__next__ "__next__" - -#define Py_TPFLAGS_HAVE_RICHCOMPARE 0 -#define Py_TPFLAGS_CHECKTYPES 0 - -#define PyClass_Check PyType_Check - -#define PyBuffer_Type PyMemoryView_Type - -#define CPyCppyy_PySliceCast PyObject* -#define PyUnicode_GetSize PyUnicode_GetLength - -#define CPyCppyy_PyUnicode_AsWideChar PyUnicode_AsWideChar - -#ifdef R__MACOSX -# if SIZEOF_SIZE_T == SIZEOF_INT -# if defined(MAC_OS_X_VERSION_10_4) -# define PY_SSIZE_T_FORMAT "%ld" -# else -# define PY_SSIZE_T_FORMAT "%d" -# endif -# elif SIZEOF_SIZE_T == SIZEOF_LONG -# define PY_SSIZE_T_FORMAT "%ld" -# endif -#else -# define PY_SSIZE_T_FORMAT "%zd" -#endif - -#ifndef Py_RETURN_NONE -#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None -#endif - -#ifndef Py_RETURN_TRUE -#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True -#endif - -#ifndef Py_RETURN_FALSE -#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False -#endif - -// vector call support -#define CPyCppyy_PyCFunction_Call PyObject_Call - -// vector call support -typedef PyObject* const* CPyCppyy_PyArgs_t; -static inline PyObject* CPyCppyy_PyArgs_GET_ITEM(CPyCppyy_PyArgs_t args, Py_ssize_t i) { - return args[i]; -} -static inline PyObject* CPyCppyy_PyArgs_SET_ITEM(CPyCppyy_PyArgs_t args, Py_ssize_t i, PyObject* item) { - return ((PyObject**)args)[i] = item; -} -static inline Py_ssize_t CPyCppyy_PyArgs_GET_SIZE(CPyCppyy_PyArgs_t, size_t nargsf) { - return PyVectorcall_NARGS(nargsf); -} -static inline CPyCppyy_PyArgs_t CPyCppyy_PyArgs_New(Py_ssize_t N) { - return (CPyCppyy_PyArgs_t)PyMem_Malloc(N*sizeof(PyObject*)); -} -static inline void CPyCppyy_PyArgs_DEL(CPyCppyy_PyArgs_t args) { - PyMem_Free((void*)args); -} -#define CPyCppyy_PyObject_Call PyObject_Vectorcall -inline PyObject* CPyCppyy_tp_call( - PyObject* cb, CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds) { - Py_ssize_t offset = Py_TYPE(cb)->tp_vectorcall_offset; - vectorcallfunc func = *(vectorcallfunc*)(((char*)cb) + offset); - return func(cb, args, nargsf, kwds); -} - -#ifndef Py_TPFLAGS_HAVE_VECTORCALL -#define Py_TPFLAGS_HAVE_VECTORCALL _Py_TPFLAGS_HAVE_VECTORCALL -#endif - -// weakref forced strong reference -#if PY_VERSION_HEX < 0x30d0000 -static inline PyObject* CPyCppyy_GetWeakRef(PyObject* ref) { - PyObject* pyobject = PyWeakref_GetObject(ref); - if (!pyobject || pyobject == Py_None) - return nullptr; - Py_INCREF(pyobject); - return pyobject; -} -#else -static inline PyObject* CPyCppyy_GetWeakRef(PyObject* ref) { - PyObject* pyobject = nullptr; - if (PyWeakref_GetRef(ref, &pyobject) != -1) - return pyobject; - return nullptr; -} -#endif - -// C++ version of the cppyy API -#include "Cppyy.h" - -// export macros for our own API -#include "CPyCppyy/CommonDefs.h" - -// --- reusable PyTypeObject initializer tail ------------------------------- -// Members appended to PyTypeObject in newer CPython releases. Every CPyCppyy -// type leaves all of these zero/null-initialized, so they share one tail. -// To support a future Python version, add one block below and one line to -// CPYCPPYY_PYTYPE_TAIL. - -#if PY_VERSION_HEX >= 0x030c0000 -#define CPYCPPYY_TP_WATCHED , 0 /* tp_watched (>= 3.12) */ -#else -#define CPYCPPYY_TP_WATCHED -#endif - -#if PY_VERSION_HEX >= 0x030d0000 -#define CPYCPPYY_TP_VERSIONS_USED , 0 /* tp_versions_used(>= 3.13) */ -#else -#define CPYCPPYY_TP_VERSIONS_USED -#endif - -#if PY_VERSION_HEX >= 0x030f0000 -#define CPYCPPYY_TP_ITERITEM , nullptr /* _tp_iteritem (>= 3.15) */ -#else -#define CPYCPPYY_TP_ITERITEM -#endif - -#define CPYCPPYY_PYTYPE_TAIL \ - CPYCPPYY_TP_WATCHED \ - CPYCPPYY_TP_VERSIONS_USED \ - CPYCPPYY_TP_ITERITEM -// -------------------------------------------------------------------------- - -#endif // !CPYCPPYY_CPYCPPYY_H diff --git a/src/CPyCppyy/src/CPyCppyyModule.cxx b/src/CPyCppyy/src/CPyCppyyModule.cxx deleted file mode 100644 index 21b0232..0000000 --- a/src/CPyCppyy/src/CPyCppyyModule.cxx +++ /dev/null @@ -1,1119 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CallContext.h" -#include "Converters.h" -#include "CPPDataMember.h" -#include "CPPExcInstance.h" -#include "CPPInstance.h" -#include "CPPOverload.h" -#include "CPPScope.h" -#include "Cppyy.h" -#include "CustomPyTypes.h" -#include "LowLevelViews.h" -#include "MemoryRegulator.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TemplateProxy.h" -#include "TupleOfInstances.h" -#include "Utility.h" -#include - -#define CPYCPPYY_INTERNAL 1 -#include "CPyCppyy/DispatchPtr.h" -namespace CPyCppyy { -void* Instance_AsVoidPtr(PyObject* pyobject); -PyObject* Instance_FromVoidPtr( - void* addr, const std::string& classname, bool python_owns = false); -} // namespace CPyCppyy -#undef CPYCPPYY_INTERNAL - -// Standard -#include -#include -#include -#include -#include -#include -#include -#include - -#if PY_VERSION_HEX < 0x030b0000 -namespace CPyCppyy { -extern dict_lookup_func gDictLookupOrg; -dict_lookup_func gDictLookupOrg = nullptr; -} // namespace CPyCppyy -#endif - -std::unordered_map TypeReductionMap; - -// Note: as of py3.11, dictionary objects no longer carry a function pointer for -// the lookup, so it can no longer be shimmed and "from cppyy.interactive import *" -// thus no longer works. -#if PY_VERSION_HEX < 0x030b0000 - -//- from Python's dictobject.c ------------------------------------------------- - typedef struct PyDictKeyEntry { - /* Cached hash code of me_key. */ - Py_hash_t me_hash; - PyObject *me_key; - PyObject *me_value; /* This field is only meaningful for combined tables */ - } PyDictEntry; - - typedef struct _dictkeysobject { - Py_ssize_t dk_refcnt; - Py_ssize_t dk_size; - dict_lookup_func dk_lookup; - Py_ssize_t dk_usable; - Py_ssize_t dk_nentries; - union { - int8_t as_1[8]; - int16_t as_2[4]; - int32_t as_4[2]; -#if SIZEOF_VOID_P > 4 - int64_t as_8[1]; -#endif - } dk_indices; - } PyDictKeysObject; - -#define CPYCPPYY_GET_DICT_LOOKUP(mp) \ - ((dict_lookup_func&)mp->ma_keys->dk_lookup) - -#endif // PY_VERSION_HEX < 0x030b0000 - -//- data ----------------------------------------------------------------------- -static PyObject* nullptr_repr(PyObject*) -{ - return CPyCppyy_PyText_FromString("nullptr"); -} - -static void nullptr_dealloc(PyObject*) -{ - Py_FatalError("deallocating nullptr"); -} - -static int nullptr_nonzero(PyObject*) -{ - return 0; -} - -static PyNumberMethods nullptr_as_number = { - 0, 0, 0, - 0, 0, 0, 0, 0, 0, - (inquiry)nullptr_nonzero, // tp_nonzero (nb_bool in p3) - 0, 0, 0, 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 0, // nb_floor_divide - 0, // nb_true_divide - 0, 0, - 0, // nb_index - 0, // nb_matrix_multiply - 0 // nb_inplace_matrix_multiply -}; - -static PyTypeObject PyNullPtr_t_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - "nullptr_t", // tp_name - sizeof(PyObject), // tp_basicsize - 0, // tp_itemsize - nullptr_dealloc, // tp_dealloc (never called) - 0, 0, 0, 0, - nullptr_repr, // tp_repr - &nullptr_as_number, // tp_as_number - 0, 0, -#if PY_VERSION_HEX >= 0x030d0000 - (hashfunc)Py_HashPointer, // tp_hash -#else - (hashfunc)_Py_HashPointer, // tp_hash -#endif - 0, 0, 0, 0, 0, Py_TPFLAGS_DEFAULT, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // tp_del - 0, // tp_version_tag - 0, // tp_finalize - 0 // tp_vectorcall - CPYCPPYY_PYTYPE_TAIL -}; - - -static PyObject* default_repr(PyObject*) -{ - return CPyCppyy_PyText_FromString("type default"); -} - -static void default_dealloc(PyObject*) -{ - Py_FatalError("deallocating default"); -} - -static PyTypeObject PyDefault_t_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - "default_t", // tp_name - sizeof(PyObject), // tp_basicsize - 0, // tp_itemsize - default_dealloc, // tp_dealloc (never called) - 0, 0, 0, 0, - default_repr, // tp_repr - 0, 0, 0, -#if PY_VERSION_HEX >= 0x030d0000 - (hashfunc)Py_HashPointer, // tp_hash -#else - (hashfunc)_Py_HashPointer, // tp_hash -#endif - 0, 0, 0, 0, 0, Py_TPFLAGS_DEFAULT, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // tp_del - 0, // tp_version_tag - 0, // tp_finalize - 0 // tp_vectorcall - CPYCPPYY_PYTYPE_TAIL -}; - -namespace { - -struct { - PyObject_HEAD -} _CPyCppyy_NullPtrStruct{PyObject_HEAD_INIT(&PyNullPtr_t_Type)}; - -struct { - PyObject_HEAD -} _CPyCppyy_DefaultStruct{PyObject_HEAD_INIT(&PyDefault_t_Type)}; - -// TODO: refactor with Converters.cxx -struct CPyCppyy_tagCDataObject { // non-public (but stable) - PyObject_HEAD - char* b_ptr; - int b_needsfree; -}; - -} // unnamed namespace - -namespace CPyCppyy { - PyObject* gThisModule = nullptr; - PyObject* gPyTypeMap = nullptr; - PyObject* gNullPtrObject = nullptr; - PyObject* gDefaultObject = nullptr; - PyObject* gBusException = nullptr; - PyObject* gSegvException = nullptr; - PyObject* gIllException = nullptr; - PyObject* gAbrtException = nullptr; - std::unordered_set gPinnedTypes; - std::ostringstream gCapturedError; - std::streambuf* gOldErrorBuffer = nullptr; - - std::unordered_map> &pythonizations() - { - static std::unordered_map> pyzMap; - return pyzMap; - } -} - - -//- private helpers ------------------------------------------------------------ -namespace { - -using namespace CPyCppyy; - - -//---------------------------------------------------------------------------- -#if PY_VERSION_HEX < 0x030b0000 -namespace { - -class GblGetter { -public: - GblGetter() { - PyObject* cppyy = PyImport_AddModule((char*)"cppyy"); - fGbl = PyObject_GetAttrString(cppyy, (char*)"gbl"); - } - ~GblGetter() { Py_DECREF(fGbl); } - - PyObject* operator*() { return fGbl; } - -private: - PyObject* fGbl; -}; - -} // unnamed namespace - -inline Py_ssize_t OrgDictLookup(PyDictObject* mp, PyObject* key, - Py_hash_t hash, PyObject*** value_addr, Py_ssize_t* hashpos) -{ - return (*gDictLookupOrg)(mp, key, hash, value_addr, hashpos); -} -#define CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos) \ - OrgDictLookup(mp, key, hash, value_addr, hashpos) - -Py_ssize_t CPyCppyyLookDictString(PyDictObject* mp, PyObject* key, - Py_hash_t hash, PyObject*** value_addr, Py_ssize_t* hashpos) -{ - static GblGetter gbl; - Py_ssize_t ep; - -// first search dictionary itself - ep = CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); - if (gDictLookupActive) - return ep; - - if (ep >= 0) - return ep; - -// filter for builtins - if (PyDict_GetItem(PyEval_GetBuiltins(), key) != 0) - return ep; - -// normal lookup failed, attempt to get C++ enum/global/class from top-level - gDictLookupActive = true; - -// attempt to get C++ enum/global/class from top-level - PyObject* val = PyObject_GetAttr(*gbl, key); - - if (val) { - // success ... - - if (CPPDataMember_CheckExact(val)) { - // don't want to add to dictionary (the proper place would be the - // dictionary of the (meta)class), but modifying ep will be noticed no - // matter what; just return the actual value and live with the copy in - // the dictionary (mostly, this is correct) - PyObject* actual_val = Py_TYPE(val)->tp_descr_get(val, nullptr, nullptr); - Py_DECREF(val); - val = actual_val; - } - - // add reference to C++ entity in the given dictionary - CPYCPPYY_GET_DICT_LOOKUP(mp) = gDictLookupOrg; // prevent recursion - if (PyDict_SetItem((PyObject*)mp, key, val) == 0) { - ep = CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); - } else { - ep = -1; - } - CPYCPPYY_GET_DICT_LOOKUP(mp) = CPyCppyyLookDictString; // restore - - // done with val - Py_DECREF(val); - } else - PyErr_Clear(); - - if (mp->ma_keys->dk_usable <= 0) { - // big risk that this lookup will result in a resize, so force it here - // to be able to reset the lookup function; of course, this is nowhere - // near fool-proof, but should cover interactive usage ... - CPYCPPYY_GET_DICT_LOOKUP(mp) = gDictLookupOrg; - const int maxinsert = 5; - PyObject* buf[maxinsert]; - for (int varmax = 1; varmax <= maxinsert; ++varmax) { - for (int ivar = 0; ivar < varmax; ++ivar) { - buf[ivar] = CPyCppyy_PyText_FromFormat("__CPYCPPYY_FORCE_RESIZE_%d", ivar); - PyDict_SetItem((PyObject*)mp, buf[ivar], Py_None); - } - for (int ivar = 0; ivar < varmax; ++ivar) { - PyDict_DelItem((PyObject*)mp, buf[ivar]); - Py_DECREF(buf[ivar]); - } - if (0 < mp->ma_keys->dk_usable) - break; - } - - // make sure the entry pointer is still valid by re-doing the lookup - ep = CPYCPPYY_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); - - // full reset of all lookup functions - gDictLookupOrg = CPYCPPYY_GET_DICT_LOOKUP(mp); - CPYCPPYY_GET_DICT_LOOKUP(mp) = CPyCppyyLookDictString; // restore - } - -// stopped calling into the reflection system - gDictLookupActive = false; - return ep; -} - -#endif // PY_VERSION_HEX < 0x030b0000 - -//---------------------------------------------------------------------------- -static PyObject* SetCppLazyLookup(PyObject*, PyObject* args) -{ -#if PY_VERSION_HEX < 0x030b0000 -// Modify the given dictionary to install the lookup function that also -// tries the global C++ namespace before failing. Called on a module's dictionary, -// this allows for lazy lookups. This works fine for p3.2 and earlier, but should -// not be used beyond interactive code for p3.3 and later b/c resizing causes the -// lookup function to revert to the default (lookdict_unicode_nodummy). - PyDictObject* dict = nullptr; - if (!PyArg_ParseTuple(args, const_cast("O!"), &PyDict_Type, &dict)) - return nullptr; - - CPYCPPYY_GET_DICT_LOOKUP(dict) = CPyCppyyLookDictString; -#else -// As of py3.11, there is no longer a lookup function pointer in the dict object -// to replace. Since this feature is not widely advertised, it's simply dropped - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"lazy lookup is no longer supported", 1) < 0) - return nullptr; - (void)args; // avoid warning about unused parameter -#endif - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* MakeCppTemplateClass(PyObject* /* self */, PyObject* args) -{ -// Create a binding for a templated class instantiation. - -// args is class name + template arguments; build full instantiation - Py_ssize_t nArgs = PyTuple_GET_SIZE(args); - if (nArgs < 2) { - PyErr_Format(PyExc_TypeError, "too few arguments for template instantiation"); - return nullptr; - } - PyObject *cppscope = PyTuple_GET_ITEM(args, 0); - void * tmpl = PyLong_AsVoidPtr(cppscope); - -// build "< type, type, ... >" part of class name (modifies pyname) - std::vector types = - Utility::GetTemplateArgsTypes(cppscope, args, nullptr, Utility::kNone, 1); - if (PyErr_Occurred()) - return nullptr; - - Cppyy::TCppScope_t scope = - Cppyy::InstantiateTemplate(tmpl, types.data(), types.size()); - for (Cpp::TemplateArgInfo i: types) { - if (i.m_IntegralValue) - std::free((void*)i.m_IntegralValue); - } - - if (!scope) { - PyErr_Format(PyExc_TypeError, - "Template instantiation failed: '%s' with args: '%s\n'", - Cppyy::GetScopedFinalName(tmpl).c_str(), - CPyCppyy_PyText_AsString(PyObject_Repr(args))); - return nullptr; - } - - return CreateScopeProxy(scope); -} - -//---------------------------------------------------------------------------- -static char* GCIA_kwlist[] = {(char*)"instance", (char*)"field", (char*)"byref", NULL}; -static void* GetCPPInstanceAddress(const char* fname, PyObject* args, PyObject* kwds) -{ -// Helper to get the address (address-of-address) of various object proxy types. - CPPInstance* pyobj = 0; PyObject* pyname = 0; int byref = 0; - if (PyArg_ParseTupleAndKeywords(args, kwds, const_cast("O|O!b"), GCIA_kwlist, - &pyobj, &CPyCppyy_PyText_Type, &pyname, &byref)) { - - if (CPPInstance_Check(pyobj)) { - if (pyname != 0) { - // locate property proxy for offset info - CPPDataMember* pyprop = nullptr; - - PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)pyobj); - PyObject* dict = PyObject_GetAttr(pyclass, PyStrings::gDict); - pyprop = (CPPDataMember*)PyObject_GetItem(dict, pyname); - Py_DECREF(dict); - - if (CPPDataMember_Check(pyprop)) { - // this is an address of a value (i.e. &myobj->prop) - void* addr = (void*)pyprop->GetAddress(pyobj); - Py_DECREF(pyprop); - return addr; - } - - Py_XDECREF(pyprop); - - PyErr_Format(PyExc_TypeError, - "%s is not a valid data member", CPyCppyy_PyText_AsString(pyname)); - return nullptr; - } - - // this is an address of an address (i.e. &myobj, with myobj of type MyObj*) - // note that the return result may be null - if (!byref) return ((CPPInstance*)pyobj)->GetObject(); - return &((CPPInstance*)pyobj)->GetObjectRaw(); - - } else if (CPyCppyy_PyText_Check(pyobj)) { - // special cases for access to the CPyCppyy API - std::string req = CPyCppyy_PyText_AsString((PyObject*)pyobj); - if (req == "Instance_AsVoidPtr") - return (void*)&Instance_AsVoidPtr; - else if (req == "Instance_FromVoidPtr") - return (void*)&Instance_FromVoidPtr; - } - } - - if (!PyErr_Occurred()) - PyErr_Format(PyExc_ValueError, "invalid argument for %s", fname); - return nullptr; -} - -//---------------------------------------------------------------------------- -static PyObject* addressof(PyObject* /* dummy */, PyObject* args, PyObject* kwds) -{ -// Return object proxy address as a value (cppyy-style), or the same for an array. - void* addr = GetCPPInstanceAddress("addressof", args, kwds); - if (addr) - return PyLong_FromLongLong((intptr_t)addr); - else if (!PyErr_Occurred()) { - return PyLong_FromLong(0); - } else if (PyTuple_CheckExact(args) && PyTuple_GET_SIZE(args) == 1) { - PyErr_Clear(); - PyObject* arg0 = PyTuple_GET_ITEM(args, 0); - - // nullptr special case - if (arg0 == gNullPtrObject || (PyInt_Check(arg0) && PyInt_AsLong(arg0) == 0)) - return PyLong_FromLong(0); - - // overload if unambiguous - if (CPPOverload_CheckExact(arg0)) { - const auto& methods = ((CPPOverload*)arg0)->fMethodInfo->fMethods; - if (methods.size() != 1) { - PyErr_SetString(PyExc_TypeError, "overload is not unambiguous"); - return nullptr; - } - - Cppyy::TCppFuncAddr_t caddr = methods[0]->GetFunctionAddress(); - return PyLong_FromLongLong((intptr_t)caddr); - } - - // C functions (incl. ourselves) - if (PyCFunction_Check(arg0)) { - void* caddr = (void*)PyCFunction_GetFunction(arg0); - return PyLong_FromLongLong((intptr_t)caddr); - } - - // LowLevelViews - if (LowLevelView_CheckExact(arg0)) { - auto *llv = (LowLevelView*)arg0; - return PyLong_FromLongLong((intptr_t)llv->get_buf()); - } - - // final attempt: any type of buffer - Utility::GetBuffer(arg0, '*', 1, addr, false); - if (addr) return PyLong_FromLongLong((intptr_t)addr); - } - -// error message if not already set - if (!PyErr_Occurred()) { - if (PyTuple_CheckExact(args) && PyTuple_GET_SIZE(args)) { - PyObject* str = PyObject_Str(PyTuple_GET_ITEM(args, 0)); - if (str && CPyCppyy_PyText_Check(str)) - PyErr_Format(PyExc_TypeError, "unknown object %s", CPyCppyy_PyText_AsString(str)); - else - PyErr_Format(PyExc_TypeError, "unknown object at %p", (void*)PyTuple_GET_ITEM(args, 0)); - Py_XDECREF(str); - } - } - return nullptr; -} - -//---------------------------------------------------------------------------- -static PyObject* AsCObject(PyObject* /* unused */, PyObject* args, PyObject* kwds) -{ -// Return object proxy as an opaque CObject. - void* addr = GetCPPInstanceAddress("as_cobject", args, kwds); - if (addr) - return CPyCppyy_PyCapsule_New((void*)addr, nullptr, nullptr); - return nullptr; -} - -//---------------------------------------------------------------------------- -static PyObject* AsCapsule(PyObject* /* unused */, PyObject* args, PyObject* kwds) -{ -// Return object proxy as an opaque PyCapsule. - void* addr = GetCPPInstanceAddress("as_capsule", args, kwds); - if (addr) - return PyCapsule_New(addr, nullptr, nullptr); - return nullptr; -} - -//---------------------------------------------------------------------------- -static PyObject* AsCTypes(PyObject* /* unused */, PyObject* args, PyObject* kwds) -{ -// Return object proxy as a ctypes c_void_p - void* addr = GetCPPInstanceAddress("as_ctypes", args, kwds); - if (!addr) - return nullptr; - -// TODO: refactor code below with converters code - static PyTypeObject* ct_cvoidp = nullptr; - if (!ct_cvoidp) { - PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept - if (!ctmod) return nullptr; - - ct_cvoidp = (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_void_p"); - Py_DECREF(ctmod); - if (!ct_cvoidp) return nullptr; - Py_DECREF(ct_cvoidp); // module keeps a reference - } - - PyObject* ref = ct_cvoidp->tp_new(ct_cvoidp, nullptr, nullptr); - *(void**)((CPyCppyy_tagCDataObject*)ref)->b_ptr = addr; - ((CPyCppyy_tagCDataObject*)ref)->b_needsfree = 0; - return ref; -} - -//---------------------------------------------------------------------------- -static PyObject* AsMemoryView(PyObject* /* unused */, PyObject* pyobject) -{ -// Return a raw memory view on arrays of PODs. - if (!CPPInstance_Check(pyobject)) { - PyErr_SetString(PyExc_TypeError, "C++ object proxy expected"); - return nullptr; - } - - CPPInstance* pyobj = (CPPInstance*)pyobject; - Cppyy::TCppScope_t klass = ((CPPClass*)Py_TYPE(pyobject))->fCppType; - - Py_ssize_t array_len = pyobj->ArrayLength(); - - if (array_len < 0 || !Cppyy::IsAggregate(klass)) { - PyErr_SetString( - PyExc_TypeError, "object is not a proxy to an array of PODs of known size"); - return nullptr; - } - - Py_buffer view; - - view.obj = pyobject; - view.buf = pyobj->GetObject(); - view.itemsize = Cppyy::SizeOf(klass); - view.len = view.itemsize * array_len; - view.readonly = 0; - view.format = NULL; // i.e. "B" assumed - view.ndim = 1; - view.shape = NULL; - view.strides = NULL; - view.suboffsets = NULL; - view.internal = NULL; - - return PyMemoryView_FromBuffer(&view); -} - -//---------------------------------------------------------------------------- -static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) -{ -// From a long representing an address or a PyCapsule/CObject, bind to a class. - Py_ssize_t argc = PyTuple_GET_SIZE(args); - if (argc != 2) { - PyErr_Format(PyExc_TypeError, - "bind_object takes 2 positional arguments but (" PY_SSIZE_T_FORMAT " were given)", argc); - return nullptr; - } - -// convert 2nd argument first (used for both pointer value and instance cases) - Cppyy::TCppScope_t cast_type = nullptr; - PyObject* arg1 = PyTuple_GET_ITEM(args, 1); - if (!CPyCppyy_PyText_Check(arg1)) { // not string, then class - if (CPPScope_Check(arg1)) - cast_type = ((CPPClass*)arg1)->fCppType; - else - arg1 = PyObject_GetAttr(arg1, PyStrings::gName); - } else - Py_INCREF(arg1); - - if (!cast_type && arg1) { - cast_type = (Cppyy::TCppScope_t)Cppyy::GetScope(CPyCppyy_PyText_AsString(arg1)); - Py_DECREF(arg1); - } - - if (!cast_type) { - PyErr_SetString(PyExc_TypeError, - "bind_object expects a valid class or class name as an argument"); - return nullptr; - } - -// next, convert the first argument, some pointer value or a pre-existing instance - PyObject* arg0 = PyTuple_GET_ITEM(args, 0); - - if (CPPInstance_Check(arg0)) { - // if this instance's class has a relation to the requested one, calculate the - // offset, erase if from any caches, and update the pointer and type - CPPInstance* arg0_pyobj = (CPPInstance*)arg0; - Cppyy::TCppScope_t cur_type = arg0_pyobj->ObjectIsA(false /* check_smart */); - - bool isPython = CPPScope_Check(arg1) && \ - (((CPPClass*)arg1)->fFlags & CPPScope::kIsPython); - - if (cur_type == cast_type && !isPython) { - Py_INCREF(arg0); // nothing to do - return arg0; - } - - int direction = 0; - Cppyy::TCppScope_t base = nullptr, derived = nullptr; - if (Cppyy::IsSubclass(cast_type, cur_type)) { - derived = cast_type; - base = cur_type; - direction = -1; // down-cast - } else if (Cppyy::IsSubclass(cur_type, cast_type)) { - base = cast_type; - derived = cur_type; - direction = 1; // up-cast - } else { - PyErr_SetString(PyExc_TypeError, - "provided instance and provided target type are unrelated"); - return nullptr; - } - - Cppyy::TCppObject_t address = (Cppyy::TCppObject_t)arg0_pyobj->GetObject(); - ptrdiff_t offset = Cppyy::GetBaseOffset(derived, base, address, direction); - - // it's debatable whether a new proxy should be created rather than updating - // the old, but changing the old object would be changing the behavior of all - // code that has a reference to it, which may not be the intention if the cast - // is on a C++ data member; this probably is the "least surprise" option - - // ownership is taken over as needed, again following the principle of "least - // surprise" as most likely only the cast object will be retained - bool owns = arg0_pyobj->fFlags & CPPInstance::kIsOwner; - - if (!isPython) { - // ordinary C++ class - PyObject* pyobj = BindCppObjectNoCast( - Cppyy::TCppObject_t((void*)((intptr_t)address.data + offset)), cast_type, owns ? CPPInstance::kIsOwner : 0); - if (owns && pyobj) arg0_pyobj->CppOwns(); - return pyobj; - - } else { - // rebinding to a Python-side class, create a fresh instance first to be able to - // perform a lookup of the original dispatch object and if found, return original - void* cast_address = (void*)((intptr_t)address.data + offset); - PyObject* pyobj = ((PyTypeObject*)arg1)->tp_new((PyTypeObject*)arg1, nullptr, nullptr); - ((CPPInstance*)pyobj)->GetObjectRaw() = cast_address; - - PyObject* dispproxy = CPyCppyy::GetScopeProxy(cast_type); - PyObject* res = PyObject_CallMethodOneArg(dispproxy, PyStrings::gDispGet, pyobj); - /* Note: the resultant object is borrowed */ - if (CPPInstance_Check(res) && ((CPPInstance*)res)->GetObject() == cast_address) { - ((CPPInstance*)pyobj)->CppOwns(); // make sure C++ object isn't deleted - Py_DECREF(pyobj); // on DECREF (is default, but still) - pyobj = res; - } else { - if (res) Py_DECREF(res); // most likely Py_None - else PyErr_Clear(); // should not happen - } - Py_DECREF(dispproxy); - - if (pyobj && owns) { - arg0_pyobj->CppOwns(); - ((CPPInstance*)pyobj)->PythonOwns(); - } - - return pyobj; - } - } - -// not a pre-existing object; get the address and bind - void* addr = nullptr; - if (arg0 != gNullPtrObject) { - addr = CPyCppyy_PyCapsule_GetPointer(arg0, nullptr); - if (PyErr_Occurred()) { - PyErr_Clear(); - - addr = PyLong_AsVoidPtr(arg0); - if (PyErr_Occurred()) { - PyErr_Clear(); - - // last chance, perhaps it's a buffer/array (return from void*) - Py_ssize_t buflen = Utility::GetBuffer(PyTuple_GetItem(args, 0), '*', 1, addr, false); - if (!addr || !buflen) { - PyErr_SetString(PyExc_TypeError, - "bind_object requires a CObject/Capsule, long integer, buffer, or instance as first argument"); - return nullptr; - } - } - } - } - - bool do_cast = false; - if (kwds) { - PyObject* cast = PyDict_GetItemString(kwds, "cast"); - do_cast = cast && PyObject_IsTrue(cast); - } - - if (do_cast) - return BindCppObject(addr, cast_type); - - return BindCppObjectNoCast(addr, cast_type); -} - -//---------------------------------------------------------------------------- -static PyObject* Move(PyObject*, PyObject* pyobject) -{ -// Prepare the given C++ object for moving. - if (!CPPInstance_Check(pyobject)) { - PyErr_SetString(PyExc_TypeError, "C++ object expected"); - return nullptr; - } - - ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; - Py_INCREF(pyobject); - return pyobject; -} - - -//---------------------------------------------------------------------------- -static PyObject* AddPythonization(PyObject*, PyObject* args) -{ -// Remove a previously registered pythonizor from the given scope. - PyObject* pythonizor = nullptr; const char* scope; - if (!PyArg_ParseTuple(args, const_cast("Os"), &pythonizor, &scope)) - return nullptr; - - if (!PyCallable_Check(pythonizor)) { - PyObject* pystr = PyObject_Str(pythonizor); - PyErr_Format(PyExc_TypeError, - "given \'%s\' object is not callable", CPyCppyy_PyText_AsString(pystr)); - Py_DECREF(pystr); - return nullptr; - } - - Py_INCREF(pythonizor); - pythonizations()[scope].push_back(pythonizor); - - Py_RETURN_NONE; -} - - -//---------------------------------------------------------------------------- -static PyObject* RemovePythonization(PyObject*, PyObject* args) -{ -// Remove a previously registered pythonizor from the given scope. - PyObject* pythonizor = nullptr; const char* scope; - if (!PyArg_ParseTuple(args, const_cast("Os"), &pythonizor, &scope)) - return nullptr; - - auto &pyzMap = pythonizations(); - auto p1 = pyzMap.find(scope); - if (p1 != pyzMap.end()) { - auto p2 = std::find(p1->second.begin(), p1->second.end(), pythonizor); - if (p2 != p1->second.end()) { - p1->second.erase(p2); - Py_RETURN_TRUE; - } - } - - Py_RETURN_FALSE; -} - -//---------------------------------------------------------------------------- -static PyObject* PinType(PyObject*, PyObject* pyclass) -{ -// Add a pinning so that objects of type `derived' are interpreted as -// objects of type `base'. - if (!CPPScope_Check(pyclass)) { - PyErr_SetString(PyExc_TypeError, "C++ class expected"); - return nullptr; - } - - gPinnedTypes.insert(((CPPClass*)pyclass)->fCppType); - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* AddTypeReducer(PyObject*, PyObject* args) -{ -// Add a type reducer to map type2 to type2 on function returns. - const char *reducable, *reduced; - if (!PyArg_ParseTuple(args, const_cast("ss"), &reducable, &reduced)) - return nullptr; - - Cppyy::TCppType_t reducable_type = Cppyy::GetTypeFromScope(Cppyy::GetScope(reducable)); - Cppyy::TCppType_t reduced_type = Cppyy::GetTypeFromScope(Cppyy::GetScope(reduced)); - TypeReductionMap[reducable_type] = reduced_type; - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* SetMemoryPolicy(PyObject*, PyObject* args) -{ -// Set the global memory policy, which affects object ownership when objects -// are passed as function arguments. - PyObject* policy = nullptr; - if (!PyArg_ParseTuple(args, const_cast("O!"), &PyInt_Type, &policy)) - return nullptr; - - long old = (long)CallContext::sMemoryPolicy; - - long l = PyInt_AS_LONG(policy); - if (CallContext::SetMemoryPolicy((CallContext::ECallFlags)l)) { - return PyInt_FromLong(old); - } - - PyErr_Format(PyExc_ValueError, "Unknown policy %ld", l); - return nullptr; -} - -//---------------------------------------------------------------------------- -static PyObject* SetGlobalSignalPolicy(PyObject*, PyObject* args) -{ -// Set the global signal policy, which determines whether a jmp address -// should be saved to return to after a C++ segfault. - PyObject* setProtected = 0; - if (!PyArg_ParseTuple(args, const_cast("O"), &setProtected)) - return nullptr; - - if (CallContext::SetGlobalSignalPolicy(PyObject_IsTrue(setProtected))) { - Py_RETURN_TRUE; - } - - Py_RETURN_FALSE; -} - -//---------------------------------------------------------------------------- -static PyObject* SetOwnership(PyObject*, PyObject* args) -{ -// Set the ownership (True is python-owns) for the given object. - CPPInstance* pyobj = nullptr; PyObject* pykeep = nullptr; - if (!PyArg_ParseTuple(args, const_cast("O!O!"), - &CPPInstance_Type, (void*)&pyobj, &PyInt_Type, &pykeep)) - return nullptr; - - (bool)PyLong_AsLong(pykeep) ? pyobj->PythonOwns() : pyobj->CppOwns(); - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* AddSmartPtrType(PyObject*, PyObject* args) -{ -// Add a smart pointer to the list of known smart pointer types. - const char* type_name; - if (!PyArg_ParseTuple(args, const_cast("s"), &type_name)) - return nullptr; - - // Cppyy::AddSmartPtrType(type_name); - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* BeginCaptureStderr(PyObject*, PyObject*) -{ - gOldErrorBuffer = std::cerr.rdbuf(); - std::cerr.rdbuf(gCapturedError.rdbuf()); - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -static PyObject* EndCaptureStderr(PyObject*, PyObject*) -{ -// restore old rdbuf and return captured result - std::cerr.rdbuf(gOldErrorBuffer); - gOldErrorBuffer = nullptr; - - std::string capturedError = std::move(gCapturedError).str(); - - gCapturedError.str(""); - gCapturedError.clear(); - - return Py_BuildValue("s", capturedError.c_str()); -} -} // unnamed namespace - - -//- data ----------------------------------------------------------------------- -static PyMethodDef gCPyCppyyMethods[] = { - {(char*) "CreateScopeProxy", (PyCFunction)CPyCppyy::CreateScopeProxy, - METH_VARARGS, (char*)"cppyy internal function"}, - {(char*) "MakeCppTemplateClass", (PyCFunction)MakeCppTemplateClass, - METH_VARARGS, (char*)"cppyy internal function"}, - {(char*) "_set_cpp_lazy_lookup", (PyCFunction)SetCppLazyLookup, - METH_VARARGS, (char*)"cppyy internal function"}, - {(char*) "_DestroyPyStrings", (PyCFunction)CPyCppyy::DestroyPyStrings, - METH_NOARGS, (char*)"cppyy internal function"}, - {(char*) "addressof", (PyCFunction)addressof, - METH_VARARGS | METH_KEYWORDS, (char*)"Retrieve address of proxied object or field as a value."}, - {(char*) "as_cobject", (PyCFunction)AsCObject, - METH_VARARGS | METH_KEYWORDS, (char*)"Retrieve address of proxied object or field in a CObject."}, - {(char*) "as_capsule", (PyCFunction)AsCapsule, - METH_VARARGS | METH_KEYWORDS, (char*)"Retrieve address of proxied object or field in a PyCapsule."}, - {(char*) "as_ctypes", (PyCFunction)AsCTypes, - METH_VARARGS | METH_KEYWORDS, (char*)"Retrieve address of proxied object or field in a ctypes c_void_p."}, - {(char*) "as_memoryview", (PyCFunction)AsMemoryView, - METH_O, (char*)"Represent an array of objects as raw memory."}, - {(char*)"bind_object", (PyCFunction)BindObject, - METH_VARARGS | METH_KEYWORDS, (char*) "Create an object of given type, from given address."}, - {(char*) "move", (PyCFunction)Move, - METH_O, (char*)"Cast the C++ object to become movable."}, - {(char*) "add_pythonization", (PyCFunction)AddPythonization, - METH_VARARGS, (char*)"Add a pythonizor."}, - {(char*) "remove_pythonization", (PyCFunction)RemovePythonization, - METH_VARARGS, (char*)"Remove a pythonizor."}, - {(char*) "_pin_type", (PyCFunction)PinType, - METH_O, (char*)"Install a type pinning."}, - {(char*) "_add_type_reducer", (PyCFunction)AddTypeReducer, - METH_VARARGS, (char*)"Add a type reducer."}, - {(char*) "SetMemoryPolicy", (PyCFunction)SetMemoryPolicy, - METH_VARARGS, (char*)"Determines object ownership model."}, - {(char*) "SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, - METH_VARARGS, (char*)"Trap signals in safe mode to prevent interpreter abort."}, - {(char*) "SetOwnership", (PyCFunction)SetOwnership, - METH_VARARGS, (char*)"Modify held C++ object ownership."}, - {(char*) "AddSmartPtrType", (PyCFunction)AddSmartPtrType, - METH_VARARGS, (char*) "Add a smart pointer to the list of known smart pointer types."}, - {(char*) "_begin_capture_stderr", (PyCFunction)BeginCaptureStderr, - METH_NOARGS, (char*) "Begin capturing stderr to a in memory buffer."}, - {(char*) "_end_capture_stderr", (PyCFunction)EndCaptureStderr, - METH_NOARGS, (char*) "End capturing stderr and returns the captured buffer."}, - {nullptr, nullptr, 0, nullptr} -}; - - -struct module_state { - PyObject *error; -}; - -#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) - -static int cpycppyymodule_traverse(PyObject* m, visitproc visit, void* arg) -{ - Py_VISIT(GETSTATE(m)->error); - return 0; -} - -static int cpycppyymodule_clear(PyObject* m) -{ - Py_CLEAR(GETSTATE(m)->error); - return 0; -} - - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "libcppyy", - nullptr, - sizeof(struct module_state), - gCPyCppyyMethods, - nullptr, - cpycppyymodule_traverse, - cpycppyymodule_clear, - nullptr -}; - -namespace CPyCppyy { - -//---------------------------------------------------------------------------- -extern "C" PyObject* PyInit_libcppyy() -{ -// Initialization of extension module libcppyy. - -// load commonly used python strings - if (!CPyCppyy::CreatePyStrings()) - return nullptr; - -// setup interpreter - -#if PY_VERSION_HEX < 0x030b0000 -// prepare for laziness (the insert is needed to capture the most generic lookup -// function, just in case ...) - PyObject* dict = PyDict_New(); - PyObject* notstring = PyInt_FromLong(5); - PyDict_SetItem(dict, notstring, notstring); - Py_DECREF(notstring); - gDictLookupOrg = (dict_lookup_func)((PyDictObject*)dict)->ma_keys->dk_lookup; - Py_DECREF(dict); -#endif // PY_VERSION_HEX < 0x030b0000 - -// setup this module - gThisModule = PyModule_Create(&moduledef); - if (!gThisModule) - return nullptr; - -// keep gThisModule, but do not increase its reference count even as it is borrowed, -// or a self-referencing cycle would be created - -// external types - gPyTypeMap = PyDict_New(); - PyModule_AddObject(gThisModule, "type_map", gPyTypeMap); // steals reference - -// Pythonizations ... - PyModule_AddObject(gThisModule, "UserExceptions", PyDict_New()); - -// inject meta type - if (!Utility::InitProxy(gThisModule, &CPPScope_Type, "CPPScope")) - return nullptr; - -// inject object proxy type - if (!Utility::InitProxy(gThisModule, &CPPInstance_Type, "CPPInstance")) - return nullptr; - -// inject exception object proxy type - if (!Utility::InitProxy(gThisModule, &CPPExcInstance_Type, "CPPExcInstance")) - return nullptr; - -// inject method proxy type - if (!Utility::InitProxy(gThisModule, &CPPOverload_Type, "CPPOverload")) - return nullptr; - -// inject template proxy type - if (!Utility::InitProxy(gThisModule, &TemplateProxy_Type, "TemplateProxy")) - return nullptr; - -// inject property proxy type - if (!Utility::InitProxy(gThisModule, &CPPDataMember_Type, "CPPDataMember")) - return nullptr; - - if (!Utility::InitProxy(gThisModule, &CustomInstanceMethod_Type, "InstanceMethod")) - return nullptr; - - if (!Utility::InitProxy(gThisModule, &TupleOfInstances_Type, "InstanceArray")) - return nullptr; - - if (!Utility::InitProxy(gThisModule, &LowLevelView_Type, "LowLevelView")) - return nullptr; - - if (!Utility::InitProxy(gThisModule, &PyNullPtr_t_Type, "nullptr_t")) - return nullptr; - -// custom iterators - if (PyType_Ready(&InstanceArrayIter_Type) < 0) - return nullptr; - - if (PyType_Ready(&IndexIter_Type) < 0) - return nullptr; - - if (PyType_Ready(&VectorIter_Type) < 0) - return nullptr; - -// inject identifiable nullptr and default - gNullPtrObject = (PyObject*)&_CPyCppyy_NullPtrStruct; - Py_INCREF(gNullPtrObject); - PyModule_AddObject(gThisModule, (char*)"nullptr", gNullPtrObject); - - gDefaultObject = (PyObject*)&_CPyCppyy_DefaultStruct; - Py_INCREF(gDefaultObject); - PyModule_AddObject(gThisModule, (char*)"default", gDefaultObject); - -// C++-specific exceptions - PyObject* cppfatal = PyErr_NewException((char*)"cppyy.ll.FatalError", nullptr, nullptr); - PyModule_AddObject(gThisModule, (char*)"FatalError", cppfatal); - - gBusException = PyErr_NewException((char*)"cppyy.ll.BusError", cppfatal, nullptr); - PyModule_AddObject(gThisModule, (char*)"BusError", gBusException); - gSegvException = PyErr_NewException((char*)"cppyy.ll.SegmentationViolation", cppfatal, nullptr); - PyModule_AddObject(gThisModule, (char*)"SegmentationViolation", gSegvException); - gIllException = PyErr_NewException((char*)"cppyy.ll.IllegalInstruction", cppfatal, nullptr); - PyModule_AddObject(gThisModule, (char*)"IllegalInstruction", gIllException); - gAbrtException = PyErr_NewException((char*)"cppyy.ll.AbortSignal", cppfatal, nullptr); - PyModule_AddObject(gThisModule, (char*)"AbortSignal", gAbrtException); - -// policy labels - PyModule_AddObject(gThisModule, (char*)"kMemoryHeuristics", - PyInt_FromLong((int)CallContext::kUseHeuristics)); - PyModule_AddObject(gThisModule, (char*)"kMemoryStrict", - PyInt_FromLong((int)CallContext::kUseStrict)); - -// gbl namespace is injected in cppyy.py - -// create the memory regulator - static MemoryRegulator s_memory_regulator; - - Py_INCREF(gThisModule); - return gThisModule; -} - -} // namespace CPyCppyy diff --git a/src/CPyCppyy/src/CallContext.cxx b/src/CPyCppyy/src/CallContext.cxx deleted file mode 100644 index 759765b..0000000 --- a/src/CPyCppyy/src/CallContext.cxx +++ /dev/null @@ -1,61 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CallContext.h" - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - - CallContext::ECallFlags CallContext::sMemoryPolicy = CallContext::kUseStrict; -// this is just a data holder for linking; actual value is set in CPyCppyyModule.cxx - CallContext::ECallFlags CallContext::sSignalPolicy = CallContext::kNone; - -} // namespace CPyCppyy - -//----------------------------------------------------------------------------- -void CPyCppyy::CallContext::AddTemporary(PyObject* pyobj) { - if (pyobj) { - if (!fTemps) - fTemps = new Temporary{pyobj, nullptr}; - else { - Temporary* tmp = fTemps; - while (tmp->fNext) tmp = tmp->fNext; - tmp->fNext = new Temporary{pyobj, nullptr}; - } - } -} - -//----------------------------------------------------------------------------- -void CPyCppyy::CallContext::Cleanup() { - Temporary* tmp = fTemps; - while (tmp) { - Py_DECREF(tmp->fPyObject); - Temporary* tmp2 = tmp->fNext; - delete tmp; - tmp = tmp2; - } - fTemps = nullptr; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::CallContext::SetMemoryPolicy(ECallFlags e) -{ -// Set the global memory policy, which affects object ownership when objects -// are passed as function arguments. - if (kUseHeuristics == e || e == kUseStrict) { - sMemoryPolicy = e; - return true; - } - return false; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::CallContext::SetGlobalSignalPolicy(bool setProtected) -{ -// Set the global signal policy, which determines whether a jmp address -// should be saved to return to after a C++ segfault. - bool old = sSignalPolicy == kProtected; - sSignalPolicy = setProtected ? kProtected : kNone; - return old; -} - diff --git a/src/CPyCppyy/src/CallContext.h b/src/CPyCppyy/src/CallContext.h deleted file mode 100644 index f093959..0000000 --- a/src/CPyCppyy/src/CallContext.h +++ /dev/null @@ -1,182 +0,0 @@ -#ifndef CPYCPPYY_CALLCONTEXT_H -#define CPYCPPYY_CALLCONTEXT_H - -#include "Python.h" -#include "Cppyy.h" - -// Standard -#include -#include - -#include - - -namespace CPyCppyy { - -// small number that allows use of stack for argument passing -const int SMALL_ARGS_N = 8; - -// convention to pass flag for direct calls (similar to Python's vector calls) -#define DIRECT_CALL ((size_t)1 << (8 * sizeof(size_t) - 1)) - -#ifndef CPYCPPYY_PARAMETER -#define CPYCPPYY_PARAMETER -// general place holder for function parameters -struct Parameter { - union Value { - bool fBool; - int8_t fInt8; - uint8_t fUInt8; - short fShort; - unsigned short fUShort; - int fInt; - unsigned int fUInt; - long fLong; - intptr_t fIntPtr; - unsigned long fULong; - long long fLLong; - unsigned long long fULLong; - int64_t fInt64; - uint64_t fUInt64; - float fFloat; - double fDouble; - long double fLDouble; - void* fVoidp; - } fValue; - void* fRef; - char fTypeCode; -}; -#endif // CPYCPPYY_PARAMETER - -// extra call information -struct CallContext { - CallContext() : fCurScope(nullptr), fPyContext(nullptr), fFlags(0), - fArgsVec(nullptr), fNArgs(0), fTemps(nullptr) {} - CallContext(const CallContext&) = delete; - CallContext& operator=(const CallContext&) = delete; - ~CallContext() { if (fTemps) Cleanup(); delete fArgsVec; } - - enum ECallFlags { - kNone = 0x000000, - kIsSorted = 0x000001, // if method overload priority determined - kIsCreator = 0x000002, // if method creates python-owned objects - kIsConstructor = 0x000004, // if method is a C++ constructor - kHaveImplicit = 0x000008, // indicate that implicit converters are available - kAllowImplicit = 0x000010, // indicate that implicit conversions are allowed - kNoImplicit = 0x000020, // disable implicit to prevent recursion - kCallDirect = 0x000040, // call wrapped method directly, no inheritance - kFromDescr = 0x000080, // initiated from a descriptor - kUseHeuristics = 0x000100, // if method applies heuristics memory policy - kImplicitSmartPtrConversion = 0x000200, // enable implicit conversion to smart pointers - kReleaseGIL = 0x000400, // if method should release the GIL - kSetLifeLine = 0x000800, // if return value is part of 'this' - kNeverLifeLine = 0x001000, // if the return value is never part of 'this' - kPyException = 0x002000, // Python exception during method execution - kCppException = 0x004000, // C++ exception during method execution - kProtected = 0x008000, // if method should return on signals - kUseFFI = 0x010000, // not implemented - kIsPseudoFunc = 0x020000, // internal, used for introspection - kUseStrict = 0x040000, // if method applies strict memory policy - }; - -// memory handling - static ECallFlags sMemoryPolicy; - static bool SetMemoryPolicy(ECallFlags e); - - void AddTemporary(PyObject* pyobj); - void Cleanup(); - -// signal safety - static ECallFlags sSignalPolicy; - static bool SetGlobalSignalPolicy(bool setProtected); - - Parameter* GetArgs(size_t sz) { - if (sz != (size_t)-1) fNArgs = sz; - if (fNArgs <= SMALL_ARGS_N) return fArgs; - if (!fArgsVec) fArgsVec = new std::vector(); - fArgsVec->resize(fNArgs); - return fArgsVec->data(); - } - - Parameter* GetArgs() { - if (fNArgs <= SMALL_ARGS_N) return fArgs; - return fArgsVec->data(); - } - - size_t GetSize() { return fNArgs; } - size_t GetEncodedSize() { return fNArgs | ((fFlags & kCallDirect) ? DIRECT_CALL : 0); } - -public: -// info/status - Cppyy::TCppScope_t fCurScope; - PyObject* fPyContext; - uint32_t fFlags; - -private: - struct Temporary { PyObject* fPyObject; Temporary* fNext; }; - -// payload - Parameter fArgs[SMALL_ARGS_N]; - std::vector* fArgsVec; - size_t fNArgs; - Temporary* fTemps; -}; - -inline bool IsSorted(uint64_t flags) { - return flags & CallContext::kIsSorted; -} - -inline bool IsCreator(uint64_t flags) { - return flags & CallContext::kIsCreator; -} - -inline bool IsConstructor(uint64_t flags) { - return flags & CallContext::kIsConstructor; -} - -inline bool HaveImplicit(CallContext* ctxt) { - return ctxt ? (!(ctxt->fFlags & CallContext::kNoImplicit) && (ctxt->fFlags & CallContext::kHaveImplicit)) : false; -} - -inline bool AllowImplicit(CallContext* ctxt) { - return ctxt ? (!(ctxt->fFlags & CallContext::kNoImplicit) && (ctxt->fFlags & CallContext::kAllowImplicit)) : false; -} - -inline bool NoImplicit(CallContext* ctxt) { - return ctxt ? (ctxt->fFlags & CallContext::kNoImplicit) : false; -} - -inline bool ReleasesGIL(CallContext* ctxt) { - return ctxt ? (ctxt->fFlags & CallContext::kReleaseGIL) : false; -} - -inline bool UseStrictOwnership(CallContext* ctxt) { - if (ctxt && (ctxt->fFlags & CallContext::kUseStrict)) - return true; - if (ctxt && (ctxt->fFlags & CallContext::kUseHeuristics)) - return false; - - return CallContext::sMemoryPolicy == CallContext::kUseStrict; -} - -template -class CallContextRAII { -public: - CallContextRAII(CallContext* ctxt) : fCtxt(ctxt) { - fPrior = fCtxt->fFlags & F; - fCtxt->fFlags |= F; - } - CallContextRAII(const CallContextRAII&) = delete; - CallContextRAII& operator=(const CallContextRAII&) = delete; - ~CallContextRAII() { - if (!fPrior) fCtxt->fFlags &= ~F; - } - -private: - CallContext* fCtxt; - bool fPrior; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CALLCONTEXT_H diff --git a/src/CPyCppyy/src/Converters.cxx b/src/CPyCppyy/src/Converters.cxx deleted file mode 100644 index c302129..0000000 --- a/src/CPyCppyy/src/Converters.cxx +++ /dev/null @@ -1,3949 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Cppyy.h" -#include "DeclareConverters.h" -#include "CallContext.h" -#include "CPPExcInstance.h" -#include "CPPInstance.h" -#include "CPPOverload.h" -#include "CustomPyTypes.h" -#include "LowLevelViews.h" -#include "MemoryRegulator.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TemplateProxy.h" -#include "TupleOfInstances.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include // for ptrdiff_t -#include -#include -#include -#include // for wstring_convert -#include -#include -#include -#include -#include -#if __cplusplus >= 202002L -#include -#endif - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - -// factories - typedef std::unordered_map ConvFactories_t; - static ConvFactories_t gConvFactories; - -// special objects - extern PyObject* gNullPtrObject; - extern PyObject* gDefaultObject; - -// regular expression for matching function pointer - static std::regex s_fnptr("\\((\\w*:*)*\\*&*\\)"); -} - -// Define our own PyUnstable_Object_IsUniqueReferencedTemporary function if the -// Python version is lower than 3.14, the version where that function got introduced. -#if PY_VERSION_HEX < 0x030e0000 -const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; -inline bool PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *pyobject) { - return Py_REFCNT(pyobject) <= MOVE_REFCOUNT_CUTOFF; -} -#endif - -//- pretend-ctypes helpers --------------------------------------------------- -struct CPyCppyy_tagCDataObject { // non-public (but stable) - PyObject_HEAD - char* b_ptr; - int b_needsfree; -}; - -struct CPyCppyy_tagPyCArgObject { // not public (but stable; note that older - PyObject_HEAD // Pythons protect 'D' with HAVE_LONG_LONG) - void* pffi_type; - char tag; - union { // for convenience, kept only relevant vals - char c; - char b; - short h; - int i; - long l; - long long q; - long double g; - void *p; -#if PY_VERSION_HEX >= 0x030e0000 - double D[2]; - float F[2]; - long double G[2]; -#endif - } value; - PyObject* obj; -#if PY_VERSION_HEX >= 0x030e0000 - Py_ssize_t size; -#endif -}; - -// indices of ctypes types into the array caches (note that c_complex and c_fcomplex -// do not exist as types in ctypes) -#define ct_c_bool 0 -#define ct_c_char 1 -#define ct_c_shar 1 -#define ct_c_wchar 2 -#define ct_c_byte 3 -#define ct_c_int8 3 -#define ct_c_ubyte 4 -#define ct_c_uchar 4 -#define ct_c_uint8 4 -#define ct_c_short 5 -#define ct_c_ushort 6 -#define ct_c_uint16 7 -#define ct_c_int 8 -#define ct_c_uint 9 -#define ct_c_uint32 10 -#define ct_c_long 11 -#define ct_c_ulong 12 -#define ct_c_longlong 13 -#define ct_c_ulonglong 14 -#define ct_c_float 15 -#define ct_c_double 16 -#define ct_c_longdouble 17 -#define ct_c_char_p 18 -#define ct_c_wchar_p 19 -#define ct_c_void_p 20 -#define ct_c_fcomplex 21 -#define ct_c_complex 22 -#define ct_c_pointer 23 -#define ct_c_funcptr 24 -#define NTYPES 25 - -static std::array gCTypesNames = { - "c_bool", "c_char", "c_wchar", "c_byte", "c_ubyte", "c_short", "c_ushort", "c_uint16", - "c_int", "c_uint", "c_uint32", "c_long", "c_ulong", "c_longlong", "c_ulonglong", - "c_float", "c_double", "c_longdouble", - "c_char_p", "c_wchar_p", "c_void_p", "c_fcomplex", "c_complex", - "_Pointer", "_CFuncPtr" }; -static std::array gCTypesTypes; -static std::array gCTypesPtrTypes; - -// Both GetCTypesType and GetCTypesPtrType, rely on the ctypes module itself -// caching the types (thus also making them unique), so no ref-count is needed. -// Further, by keeping a ref-count on the module, it won't be off-loaded until -// the 2nd cleanup cycle. -static PyTypeObject* GetCTypesType(int nidx) -{ - static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept - if (!ctmod) { - PyErr_Clear(); - return nullptr; - } - PyTypeObject* ct_t = gCTypesTypes[nidx]; - if (!ct_t) { - ct_t = (PyTypeObject*)PyObject_GetAttrString(ctmod, gCTypesNames[nidx]); - if (!ct_t) PyErr_Clear(); - else { - gCTypesTypes[nidx] = ct_t; - Py_DECREF(ct_t); - } - } - return ct_t; -} - -static PyTypeObject* GetCTypesPtrType(int nidx) -{ - static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept - if (!ctmod) { - PyErr_Clear(); - return nullptr; - } - PyTypeObject* cpt_t = gCTypesPtrTypes[nidx]; - if (!cpt_t) { - if (strcmp(gCTypesNames[nidx], "c_char") == 0) { - cpt_t = (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_char_p"); - } else { - PyObject* ct_t = (PyObject*)GetCTypesType(nidx); - if (ct_t) { - PyObject* ptrcreat = PyObject_GetAttrString(ctmod, "POINTER"); - cpt_t = (PyTypeObject*)PyObject_CallFunctionObjArgs(ptrcreat, ct_t, NULL); - Py_DECREF(ptrcreat); - } - } - if (cpt_t) { - gCTypesPtrTypes[nidx] = cpt_t; - Py_DECREF(cpt_t); - } - } - return cpt_t; -} - -static bool IsPyCArgObject(PyObject* pyobject) -{ - static PyTypeObject* pycarg_type = nullptr; - if (!pycarg_type) { - PyObject* ctmod = PyImport_ImportModule("ctypes"); - if (!ctmod) PyErr_Clear(); - else { - PyTypeObject* ct_t = (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_int"); - PyObject* cobj = ct_t->tp_new(ct_t, nullptr, nullptr); - PyObject* byref = PyObject_GetAttrString(ctmod, "byref"); - PyObject* pyptr = PyObject_CallFunctionObjArgs(byref, cobj, NULL); - Py_DECREF(byref); Py_DECREF(cobj); Py_DECREF(ct_t); - pycarg_type = Py_TYPE(pyptr); // static, no ref-count needed - Py_DECREF(pyptr); - Py_DECREF(ctmod); - } - } - return Py_TYPE(pyobject) == pycarg_type; -} - -#if PY_VERSION_HEX < 0x30d0000 -static bool IsCTypesArrayOrPointer(PyObject* pyobject) -{ - static PyTypeObject* cstgdict_type = nullptr; - if (!cstgdict_type) { - // get any pointer type to initialize the extended dictionary type - PyTypeObject* ct_int = GetCTypesType(ct_c_int); - if (ct_int && ct_int->tp_dict) { - cstgdict_type = Py_TYPE(ct_int->tp_dict); - } - } - - PyTypeObject* pytype = Py_TYPE(pyobject); - if (pytype->tp_dict && Py_TYPE(pytype->tp_dict) == cstgdict_type) - return true; - return false; -} -#else -// the internals of ctypes have been redone, requiring a more complex checking -namespace { - -typedef struct { - PyTypeObject *DictRemover_Type; - PyTypeObject *PyCArg_Type; - PyTypeObject *PyCField_Type; - PyTypeObject *PyCThunk_Type; - PyTypeObject *StructParam_Type; - PyTypeObject *PyCType_Type; - PyTypeObject *PyCStructType_Type; - PyTypeObject *UnionType_Type; - PyTypeObject *PyCPointerType_Type; -// ... unused fields omitted ... -} _cppyy_ctypes_state; - -} // unnamed namespace - -static bool IsCTypesArrayOrPointer(PyObject* pyobject) -{ - static _cppyy_ctypes_state* state = nullptr; - if (!state) { - PyObject* ctmod = PyImport_AddModule("_ctypes"); // the extension module, not the Python one - if (ctmod) - state = (_cppyy_ctypes_state*)PyModule_GetState(ctmod); - } - - // verify for object types that have a C payload - if (state && (PyObject_IsInstance((PyObject*)Py_TYPE(pyobject), (PyObject*)state->PyCType_Type) || - PyObject_IsInstance((PyObject*)Py_TYPE(pyobject), (PyObject*)state->PyCPointerType_Type))) { - return true; - } - - return false; -} -#endif - - -//- helper to establish life lines ------------------------------------------- -static inline bool SetLifeLine(PyObject* holder, PyObject* target, intptr_t ref) -{ -// set a lifeline from on the holder to the target, using the ref as label - if (!holder) return false; - -// 'ref' is expected to be the converter address or data memory location, so -// that the combination of holder and ref is unique, but also identifiable for -// reuse when the C++ side is being overwritten - std::ostringstream attr_name; - attr_name << "__" << ref; - auto res = PyObject_SetAttrString(holder, (char*)attr_name.str().c_str(), target); - return res != -1; -} - -static bool HasLifeLine(PyObject* holder, intptr_t ref) -{ -// determine if a lifeline was previously set for the ref on the holder - if (!holder) return false; - - std::ostringstream attr_name; - attr_name << "__" << ref; - PyObject* res = PyObject_GetAttrString(holder, (char*)attr_name.str().c_str()); - - if (res) { - Py_DECREF(res); - return true; - } - - PyErr_Clear(); - return false; -} - - -//- helper to work with both CPPInstance and CPPExcInstance ------------------ -static inline CPyCppyy::CPPInstance* GetCppInstance( - PyObject* pyobject, Cppyy::TCppScope_t klass = Cppyy::TCppScope_t{}, bool accept_rvalue = false) -{ - using namespace CPyCppyy; - if (CPPInstance_Check(pyobject)) - return (CPPInstance*)pyobject; - if (CPPExcInstance_Check(pyobject)) - return (CPPInstance*)((CPPExcInstance*)pyobject)->fCppInstance; - -// this is not a C++ proxy; allow custom cast to C++ - PyObject* castobj = PyObject_CallMethodNoArgs(pyobject, PyStrings::gCastCpp); - if (castobj) { - if (CPPInstance_Check(castobj)) - return (CPPInstance*)castobj; - else if (klass && PyTuple_CheckExact(castobj)) { - // allow implicit conversion from a tuple of arguments - PyObject* pyclass = GetScopeProxy(klass); - if (pyclass) { - CPPInstance* pytmp = (CPPInstance*)PyObject_Call(pyclass, castobj, NULL); - Py_DECREF(pyclass); - if (CPPInstance_Check(pytmp)) { - if (accept_rvalue) - pytmp->fFlags |= CPPInstance::kIsRValue; - Py_DECREF(castobj); - return pytmp; - } - Py_XDECREF(pytmp); - } - } - - Py_DECREF(castobj); - return nullptr; - } - - PyErr_Clear(); - return nullptr; -} - - -//- custom helpers to check ranges ------------------------------------------- -static inline bool ImplicitBool(PyObject* pyobject, CPyCppyy::CallContext* ctxt) -{ - using namespace CPyCppyy; - if (!AllowImplicit(ctxt) && PyBool_Check(pyobject)) { - if (!NoImplicit(ctxt)) ctxt->fFlags |= CallContext::kHaveImplicit; - return false; - } - return true; -} - -static inline bool StrictBool(PyObject* pyobject, CPyCppyy::CallContext* ctxt) -{ - using namespace CPyCppyy; - if (!AllowImplicit(ctxt) && !PyBool_Check(pyobject)) { - if (!NoImplicit(ctxt)) ctxt->fFlags |= CallContext::kHaveImplicit; - return false; - } - return true; -} - -static inline bool CPyCppyy_PyLong_AsBool(PyObject* pyobject) -{ -// range-checking python integer to C++ bool conversion - long l = PyLong_AsLong(pyobject); -// fail to pass float -> bool; the problem is rounding (0.1 -> 0 -> False) - if (!(l == 0|| l == 1) || PyFloat_Check(pyobject)) { - PyErr_SetString(PyExc_ValueError, "boolean value should be bool, or integer 1 or 0"); - return (bool)-1; - } - return (bool)l; -} - - -// range-checking python integer to C++ integer conversion (prevents p2.7 silent conversions) -#define CPPYY_PYLONG_AS_TYPE(name, type, limit_low, limit_high) \ -static inline type CPyCppyy_PyLong_As##name(PyObject* pyobject) \ -{ \ - if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { \ - if (pyobject == CPyCppyy::gDefaultObject) \ - return (type)0; \ - PyErr_SetString(PyExc_TypeError, #type" conversion expects an integer object");\ - return (type)-1; \ - } \ - long l = PyLong_AsLong(pyobject); \ - if (l < limit_low || limit_high < l) { \ - PyErr_Format(PyExc_ValueError, "integer %ld out of range for "#type, l);\ - return (type)-1; \ - } \ - return (type)l; \ -} - -CPPYY_PYLONG_AS_TYPE(UInt8, uint8_t, 0, UCHAR_MAX) -CPPYY_PYLONG_AS_TYPE(Int8, int8_t, SCHAR_MIN, SCHAR_MAX) -CPPYY_PYLONG_AS_TYPE(UShort, unsigned short, 0, USHRT_MAX) -CPPYY_PYLONG_AS_TYPE(Short, short, SHRT_MIN, SHRT_MAX) -CPPYY_PYLONG_AS_TYPE(StrictInt, int, INT_MIN, INT_MAX) - -static inline long CPyCppyy_PyLong_AsStrictLong(PyObject* pyobject) -{ -// strict python integer to C++ long integer conversion - -// prevent float -> long (see CPyCppyy_PyLong_AsStrictInt) - if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { - if (pyobject == CPyCppyy::gDefaultObject) - return (long)0; - PyErr_SetString(PyExc_TypeError, "int/long conversion expects an integer object"); - return (long)-1; - } - - return (long)PyLong_AsLong(pyobject); // already does long range check -} - -static inline PY_LONG_LONG CPyCppyy_PyLong_AsStrictLongLong(PyObject* pyobject) -{ -// strict python integer to C++ long long integer conversion - -// prevent float -> long (see CPyCppyy_PyLong_AsStrictInt) - if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { - if (pyobject == CPyCppyy::gDefaultObject) - return (PY_LONG_LONG)0; - PyErr_SetString(PyExc_TypeError, "int/long conversion expects an integer object"); - return (PY_LONG_LONG)-1; - } - - return PyLong_AsLongLong(pyobject); // already does long range check -} - - -//- helper for pointer/array/reference conversions --------------------------- -static inline bool CArraySetArg( - PyObject* pyobject, CPyCppyy::Parameter& para, char tc, int size, bool check=true) -{ -// Case of LowLevelView. In general, they also implement the buffer protocol, -// but for views around nullptr or C-style arrays without size info the buffer -// protocol implementation is incomplete and PyObject_GetBuffer will fail. - if (CPyCppyy::LowLevelView_Check(pyobject)) { - auto llview = ((CPyCppyy::LowLevelView*)pyobject); - if (llview->fBufInfo.itemsize != size || !strchr(llview->fBufInfo.format, tc)) { - PyErr_Format(PyExc_TypeError, - "could not convert argument to buffer or nullptr"); - return false; - } - - para.fValue.fVoidp = llview->get_buf(); - } -// general case of loading a C array pointer (void* + type code) as function argument - else if (pyobject == CPyCppyy::gNullPtrObject || pyobject == CPyCppyy::gDefaultObject) - para.fValue.fVoidp = nullptr; - else { - Py_ssize_t buflen = CPyCppyy::Utility::GetBuffer(pyobject, tc, size, para.fValue.fVoidp, check); - if (!buflen) { - // stuck here as it's the least common - if (CPyCppyy_PyLong_AsStrictInt(pyobject) == 0) - para.fValue.fVoidp = nullptr; - else { - PyErr_Format(PyExc_TypeError, // ValueError? - "could not convert argument to buffer or nullptr"); - return false; - } - } - } - para.fTypeCode = 'p'; - return true; -} - - -//- helper for implicit conversions ------------------------------------------ -static inline CPyCppyy::CPPInstance* ConvertImplicit(Cppyy::TCppScope_t klass, - PyObject* pyobject, CPyCppyy::Parameter& para, CPyCppyy::CallContext* ctxt, bool manage=true) -{ - using namespace CPyCppyy; - -// filter out copy and move constructors - if (IsConstructor(ctxt->fFlags) && klass == ctxt->fCurScope && ctxt->GetSize() == 1) - return nullptr; - -// only proceed if implicit conversions are allowed (in "round 2") or if the -// argument is exactly a tuple or list, as these are the equivalent of -// initializer lists and thus "syntax" not a conversion - if (!AllowImplicit(ctxt)) { - PyTypeObject* pytype = (PyTypeObject*)Py_TYPE(pyobject); - if (!(pytype == &PyList_Type || pytype == &PyTuple_Type)) {// || !CPPInstance_Check(pyobject))) { - if (!NoImplicit(ctxt)) ctxt->fFlags |= CallContext::kHaveImplicit; - return nullptr; - } - } - -// exercise implicit conversion - PyObject* pyscope = CreateScopeProxy(klass); - if (!CPPScope_Check(pyscope)) { - Py_XDECREF(pyscope); - return nullptr; - } - -// call constructor of argument type to attempt implicit conversion (disallow any -// implicit conversions by the scope's constructor itself) - PyObject* args = PyTuple_New(1); - Py_INCREF(pyobject); PyTuple_SET_ITEM(args, 0, pyobject); - - ((CPPScope*)pyscope)->fFlags |= CPPScope::kActiveImplicitCall; - CPPInstance* pytmp = (CPPInstance*)PyObject_Call(pyscope, args, NULL); - if (!pytmp && PyTuple_CheckExact(pyobject)) { - // special case: allow implicit conversion from given set of arguments in tuple - PyErr_Clear(); - pytmp = (CPPInstance*)PyObject_Call(pyscope, pyobject, NULL); - } - ((CPPScope*)pyscope)->fFlags &= ~CPPScope::kActiveImplicitCall; - - Py_DECREF(args); - Py_DECREF(pyscope); - - if (pytmp) { - // implicit conversion succeeded! - if (manage) ctxt->AddTemporary((PyObject*)pytmp); - para.fValue.fVoidp = pytmp->GetObjectRaw(); - para.fTypeCode = 'V'; - return pytmp; - } - - PyErr_Clear(); - return nullptr; -} - - -//- base converter implementation -------------------------------------------- -CPyCppyy::Converter::~Converter() -{ - /* empty */ -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Converter::FromMemory(void*) -{ -// could happen if no derived class override - PyErr_SetString(PyExc_TypeError, "C++ type cannot be converted from memory"); - return nullptr; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Converter::ToMemory(PyObject*, void*, PyObject* /* ctxt */) -{ -// could happen if no derived class override - PyErr_SetString(PyExc_TypeError, "C++ type cannot be converted to memory"); - return false; -} - - -//- helper macro's ----------------------------------------------------------- -#define CPPYY_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc)\ -/* convert to C++ 'type', set arg for call */ \ - type val = (type)F2(pyobject); \ - if (val == (type)-1 && PyErr_Occurred()) { \ - static PyTypeObject* ctypes_type = nullptr; \ - if (!ctypes_type) { \ - auto error = CPyCppyy::Utility::FetchPyError(); \ - ctypes_type = GetCTypesType(ct_##ctype); \ - CPyCppyy::Utility::RestorePyError(error); \ - } \ - if (Py_TYPE(pyobject) == ctypes_type) { \ - PyErr_Clear(); \ - val = *((type*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr); \ - } else if (pyobject == CPyCppyy::gDefaultObject) { \ - PyErr_Clear(); \ - val = (type)0; \ - } else \ - return false; \ - } \ - para.fValue.f##name = val; \ - para.fTypeCode = tc; \ - return true; - -#define CPPYY_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) \ -PyObject* CPyCppyy::name##Converter::FromMemory(void* address) \ -{ \ - return F1((stype)*((type*)address)); \ -} \ - \ -bool CPyCppyy::name##Converter::ToMemory( \ - PyObject* value, void* address, PyObject* /* ctxt */) \ -{ \ - type s = (type)F2(value); \ - if (s == (type)-1 && PyErr_Occurred()) { \ - if (value == CPyCppyy::gDefaultObject) { \ - PyErr_Clear(); \ - s = (type)0; \ - } else \ - return false; \ - } \ - *((type*)address) = (type)s; \ - return true; \ -} - -#define CPPYY_IMPL_BASIC_CONVERTER_NI(name, type, stype, ctype, F1, F2, tc) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* ctxt) \ -{ \ - if (!StrictBool(pyobject, ctxt)) \ - return false; \ - CPPYY_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ -} \ -CPPYY_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) - -#define CPPYY_IMPL_BASIC_CONVERTER_IB(name, type, stype, ctype, F1, F2, tc) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* ctxt) \ -{ \ - if (!ImplicitBool(pyobject, ctxt)) \ - return false; \ - CPPYY_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ -} \ -CPPYY_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) - -#define CPPYY_IMPL_BASIC_CONVERTER_NB(name, type, stype, ctype, F1, F2, tc) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /*ctxt*/) \ -{ \ - if (PyBool_Check(pyobject)) \ - return false; \ - CPPYY_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ -} \ -CPPYY_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) - -//---------------------------------------------------------------------------- -static inline int ExtractChar(PyObject* pyobject, const char* tname, int low, int high) -{ - int lchar = -1; - if (PyBytes_Check(pyobject)) { - if (PyBytes_GET_SIZE(pyobject) == 1) - lchar = (int)(PyBytes_AsString(pyobject)[0]); - else - PyErr_Format(PyExc_ValueError, "%s expected, got bytes of size " PY_SSIZE_T_FORMAT, - tname, PyBytes_GET_SIZE(pyobject)); - } else if (CPyCppyy_PyText_Check(pyobject)) { - if (CPyCppyy_PyText_GET_SIZE(pyobject) == 1) - lchar = (int)(CPyCppyy_PyText_AsString(pyobject)[0]); - else - PyErr_Format(PyExc_ValueError, "%s expected, got str of size " PY_SSIZE_T_FORMAT, - tname, CPyCppyy_PyText_GET_SIZE(pyobject)); - } else if (pyobject == CPyCppyy::gDefaultObject) { - lchar = (int)'\0'; - } else if (!PyFloat_Check(pyobject)) { // don't allow truncating conversion - lchar = (int)PyLong_AsLong(pyobject); - if (lchar == -1 && PyErr_Occurred()) - ; // empty, as error already set - else if (!(low <= lchar && lchar <= high)) { - PyErr_Format(PyExc_ValueError, - "integer to character: value %d not in range [%d,%d]", lchar, low, high); - lchar = -1; - } - } else - PyErr_SetString(PyExc_TypeError, "char or small int type expected"); - - return lchar; -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(name, ctype) \ -PyObject* CPyCppyy::name##RefConverter::FromMemory(void* ptr) \ -{ \ -/* convert a reference to int to Python through ctypes pointer object */ \ - PyTypeObject* ctypes_type = GetCTypesType(ct_##ctype); \ - if (!ctypes_type) { \ - PyErr_SetString(PyExc_RuntimeError, "no ctypes available"); \ - return nullptr; \ - } \ - PyObject* ref = ctypes_type->tp_new(ctypes_type, nullptr, nullptr); \ - ((CPyCppyy_tagCDataObject*)ref)->b_ptr = (char*)ptr; \ - ((CPyCppyy_tagCDataObject*)ref)->b_needsfree = 0; \ - return ref; \ -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_BASIC_CONST_REFCONVERTER(name, type, ctype, F1) \ -bool CPyCppyy::Const##name##RefConverter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) \ -{ \ - type val = (type)F1(pyobject); \ - if (val == (type)-1 && PyErr_Occurred()) { \ - if (pyobject == CPyCppyy::gDefaultObject) { \ - PyErr_Clear(); \ - val = (type)0; \ - } else \ - return false; \ - } \ - para.fValue.f##name = val; \ - para.fRef = ¶.fValue.f##name; \ - para.fTypeCode = 'r'; \ - return true; \ -} \ -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Const##name, ctype) - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_BASIC_CONST_CHAR_REFCONVERTER(name, type, ctype, low, high)\ -bool CPyCppyy::Const##name##RefConverter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) \ -{ \ -/* convert to C++ <>, set arg for call, allow int -> char */\ - type val = (type)ExtractChar(pyobject, #type, low, high); \ - if (val == (type)-1 && PyErr_Occurred()) \ - return false; \ - para.fValue.fLong = val; \ - para.fTypeCode = 'l'; \ - return true; \ -} \ -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Const##name, ctype) - - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_BASIC_CHAR_CONVERTER(name, type, low, high) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) \ -{ \ -/* convert to C++ <>, set arg for call, allow int -> char */\ - long val = ExtractChar(pyobject, #type, low, high); \ - if (val == -1 && PyErr_Occurred()) \ - return false; \ - para.fValue.fLong = val; \ - para.fTypeCode = 'l'; \ - return true; \ -} \ - \ -PyObject* CPyCppyy::name##Converter::FromMemory(void* address) \ -{ \ - /* return char in "native" str type as that's more natural in use */ \ - return CPyCppyy_PyText_FromFormat("%c", *((type*)address)); \ -} \ - \ -bool CPyCppyy::name##Converter::ToMemory( \ - PyObject* value, void* address, PyObject* /* ctxt */) \ -{ \ - Py_ssize_t len; \ - const char* cstr = nullptr; \ - if (PyBytes_Check(value)) \ - PyBytes_AsStringAndSize(value, (char**)&cstr, &len); \ - else \ - cstr = CPyCppyy_PyText_AsStringAndSize(value, &len); \ - if (cstr) { \ - if (len != 1) { \ - PyErr_Format(PyExc_TypeError, #type" expected, got string of size %zd", len);\ - return false; \ - } \ - *((type*)address) = (type)cstr[0]; \ - } else { \ - PyErr_Clear(); \ - long l = PyLong_AsLong(value); \ - if (l == -1 && PyErr_Occurred()) { \ - if (value == CPyCppyy::gDefaultObject) { \ - PyErr_Clear(); \ - l = (long)0; \ - } else \ - return false; \ - } \ - if (!(low <= l && l <= high)) { \ - PyErr_Format(PyExc_ValueError, \ - "integer to character: value %ld not in range [%d,%d]", l, low, high);\ - return false; \ - } \ - *((type*)address) = (type)l; \ - } \ - return true; \ -} - - -//- converters for built-ins ------------------------------------------------- -CPPYY_IMPL_BASIC_CONVERTER_IB(Long, long, long, c_long, PyLong_FromLong, CPyCppyy_PyLong_AsStrictLong, 'l') - -//---------------------------------------------------------------------------- -bool CPyCppyy::LongRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ long&, set arg for call - - if (Py_TYPE(pyobject) == GetCTypesType(ct_c_long)) { - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - } - - if (CArraySetArg(pyobject, para, 'l', sizeof(long))) { - para.fTypeCode = 'V'; - return true; - } - - PyErr_SetString(PyExc_TypeError, "use ctypes.c_long for pass-by-ref of longs"); - return false; -} - -//---------------------------------------------------------------------------- -CPPYY_IMPL_BASIC_CONST_CHAR_REFCONVERTER(Char, char, c_char, CHAR_MIN, CHAR_MAX) -CPPYY_IMPL_BASIC_CONST_CHAR_REFCONVERTER(UChar, unsigned char, c_uchar, 0, UCHAR_MAX) - -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Bool, bool, c_bool, CPyCppyy_PyLong_AsBool) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int8, int8_t, c_int8, CPyCppyy_PyLong_AsInt8) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt8, uint8_t, c_uint8, CPyCppyy_PyLong_AsUInt8) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Short, short, c_short, CPyCppyy_PyLong_AsShort) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UShort, unsigned short, c_ushort, CPyCppyy_PyLong_AsUShort) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int, int, c_int, CPyCppyy_PyLong_AsStrictInt) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt, unsigned int, c_uint, PyLongOrInt_AsULong) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Long, long, c_long, CPyCppyy_PyLong_AsStrictLong) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(ULong, unsigned long, c_ulong, PyLongOrInt_AsULong) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(LLong, PY_LONG_LONG, c_longlong, CPyCppyy_PyLong_AsStrictLongLong) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(ULLong, PY_ULONG_LONG, c_ulonglong, PyLongOrInt_AsULong64) - -//---------------------------------------------------------------------------- -bool CPyCppyy::IntRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ (pseudo)int&, set arg for call - - if (Py_TYPE(pyobject) == GetCTypesType(ct_c_int)) { - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - } - -// alternate, pass pointer from buffer - Py_ssize_t buflen = Utility::GetBuffer(pyobject, 'i', sizeof(int), para.fValue.fVoidp); - if (para.fValue.fVoidp && buflen) { - para.fTypeCode = 'V'; - return true; - }; - - PyErr_SetString(PyExc_TypeError, "use ctypes.c_int for pass-by-ref of ints"); - return false; -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_REFCONVERTER(name, ctype, type, code) \ -bool CPyCppyy::name##RefConverter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) \ -{ \ -/* convert a reference to int to Python through ctypes pointer object */ \ - if (Py_TYPE(pyobject) == GetCTypesType(ct_##ctype)) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'V'; \ - return true; \ - } \ - bool res = CArraySetArg(pyobject, para, code, sizeof(type)); \ - if (!res) { \ - PyErr_SetString(PyExc_TypeError, "use ctypes."#ctype" for pass-by-ref of "#type);\ - return false; \ - } \ - para.fTypeCode = 'V'; \ - return res; \ -} \ -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(name, ctype) - -CPPYY_IMPL_REFCONVERTER(Bool, c_bool, bool, '?'); -CPPYY_IMPL_REFCONVERTER(Char, c_char, char, 'b'); -CPPYY_IMPL_REFCONVERTER(WChar, c_wchar, wchar_t, 'u'); -CPPYY_IMPL_REFCONVERTER(Char16, c_uint16, char16_t, 'H'); -CPPYY_IMPL_REFCONVERTER(Char32, c_uint32, char32_t, 'I'); -CPPYY_IMPL_REFCONVERTER(SChar, c_byte, signed char, 'b'); -CPPYY_IMPL_REFCONVERTER(UChar, c_ubyte, unsigned char, 'B'); -CPPYY_IMPL_REFCONVERTER(Int8, c_int8, int8_t, 'b'); -CPPYY_IMPL_REFCONVERTER(UInt8, c_uint8, uint8_t, 'B'); -CPPYY_IMPL_REFCONVERTER(Short, c_short, short, 'h'); -CPPYY_IMPL_REFCONVERTER(UShort, c_ushort, unsigned short, 'H'); -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Int, c_int); -CPPYY_IMPL_REFCONVERTER(UInt, c_uint, unsigned int, 'I'); -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Long, c_long); -CPPYY_IMPL_REFCONVERTER(ULong, c_ulong, unsigned long, 'L'); -CPPYY_IMPL_REFCONVERTER(LLong, c_longlong, long long, 'q'); -CPPYY_IMPL_REFCONVERTER(ULLong, c_ulonglong, unsigned long long, 'Q'); -CPPYY_IMPL_REFCONVERTER(Float, c_float, float, 'f'); -CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Double, c_double); -CPPYY_IMPL_REFCONVERTER(LDouble, c_longdouble, PY_LONG_DOUBLE, 'g'); - - -//---------------------------------------------------------------------------- -// convert to C++ bool, allow int/long -> bool, set arg for call -CPPYY_IMPL_BASIC_CONVERTER_NI( - Bool, bool, long, c_bool, PyBool_FromLong, CPyCppyy_PyLong_AsBool, 'l') - -//---------------------------------------------------------------------------- -CPPYY_IMPL_BASIC_CHAR_CONVERTER(Char, char, CHAR_MIN, CHAR_MAX) -CPPYY_IMPL_BASIC_CHAR_CONVERTER(UChar, unsigned char, 0, UCHAR_MAX) - -PyObject* CPyCppyy::SCharAsIntConverter::FromMemory(void* address) -{ -// special case to be used with arrays: return a Python int instead of str -// (following the same convention as module array.array) - return PyInt_FromLong((long)*((signed char*)address)); -} - -PyObject* CPyCppyy::UCharAsIntConverter::FromMemory(void* address) -{ -// special case to be used with arrays: return a Python int instead of str -// (following the same convention as module array.array) - return PyInt_FromLong((long)*((unsigned char*)address)); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::WCharConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ , set arg for call - if (!PyUnicode_Check(pyobject) || CPyCppyy_PyUnicode_GET_SIZE(pyobject) != 1) { - PyErr_SetString(PyExc_ValueError, "single wchar_t character expected"); - return false; - } - wchar_t val; - Py_ssize_t res = CPyCppyy_PyUnicode_AsWideChar(pyobject, &val, 1); - if (res == -1) - return false; - para.fValue.fLong = (long)val; - para.fTypeCode = 'U'; - return true; -} - -PyObject* CPyCppyy::WCharConverter::FromMemory(void* address) -{ - return PyUnicode_FromWideChar((const wchar_t*)address, 1); -} - -bool CPyCppyy::WCharConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ - if (!PyUnicode_Check(value) || CPyCppyy_PyUnicode_GET_SIZE(value) != 1) { - PyErr_SetString(PyExc_ValueError, "single wchar_t character expected"); - return false; - } - wchar_t val; - Py_ssize_t res = CPyCppyy_PyUnicode_AsWideChar(value, &val, 1); - if (res == -1) - return false; - *((wchar_t*)address) = val; - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Char16Converter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ , set arg for call - if (!PyUnicode_Check(pyobject) || CPyCppyy_PyUnicode_GET_SIZE(pyobject) != 1) { - PyErr_SetString(PyExc_ValueError, "single char16_t character expected"); - return false; - } - - PyObject* bstr = PyUnicode_AsUTF16String(pyobject); - if (!bstr) return false; - - char16_t val = *(char16_t*)(PyBytes_AS_STRING(bstr) + sizeof(char16_t) /*BOM*/); - Py_DECREF(bstr); - para.fValue.fLong = (long)val; - para.fTypeCode = 'U'; - return true; -} - -PyObject* CPyCppyy::Char16Converter::FromMemory(void* address) -{ - return PyUnicode_DecodeUTF16((const char*)address, sizeof(char16_t), nullptr, nullptr); -} - -bool CPyCppyy::Char16Converter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ - if (!PyUnicode_Check(value) || CPyCppyy_PyUnicode_GET_SIZE(value) != 1) { - PyErr_SetString(PyExc_ValueError, "single char16_t character expected"); - return false; - } - - PyObject* bstr = PyUnicode_AsUTF16String(value); - if (!bstr) return false; - - *((char16_t*)address) = *(char16_t*)(PyBytes_AS_STRING(bstr) + sizeof(char16_t) /*BOM*/); - Py_DECREF(bstr); - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Char32Converter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ , set arg for call - if (!PyUnicode_Check(pyobject) || 2 < CPyCppyy_PyUnicode_GET_SIZE(pyobject)) { - PyErr_SetString(PyExc_ValueError, "single char32_t character expected"); - return false; - } - - PyObject* bstr = PyUnicode_AsUTF32String(pyobject); - if (!bstr) return false; - - char32_t val = *(char32_t*)(PyBytes_AS_STRING(bstr) + sizeof(char32_t) /*BOM*/); - Py_DECREF(bstr); - para.fValue.fLong = (long)val; - para.fTypeCode = 'U'; - return true; -} - -PyObject* CPyCppyy::Char32Converter::FromMemory(void* address) -{ - return PyUnicode_DecodeUTF32((const char*)address, sizeof(char32_t), nullptr, nullptr); -} - -bool CPyCppyy::Char32Converter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ - if (!PyUnicode_Check(value) || 2 < CPyCppyy_PyUnicode_GET_SIZE(value)) { - PyErr_SetString(PyExc_ValueError, "single char32_t character expected"); - return false; - } - - PyObject* bstr = PyUnicode_AsUTF32String(value); - if (!bstr) return false; - - *((char32_t*)address) = *(char32_t*)(PyBytes_AS_STRING(bstr) + sizeof(char32_t) /*BOM*/); - Py_DECREF(bstr); - return true; -} - -//---------------------------------------------------------------------------- -CPPYY_IMPL_BASIC_CONVERTER_IB( - Int8, int8_t, long, c_int8, PyInt_FromLong, CPyCppyy_PyLong_AsInt8, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - UInt8, uint8_t, long, c_uint8, PyInt_FromLong, CPyCppyy_PyLong_AsUInt8, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - Short, short, long, c_short, PyInt_FromLong, CPyCppyy_PyLong_AsShort, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - UShort, unsigned short, long, c_ushort, PyInt_FromLong, CPyCppyy_PyLong_AsUShort, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - Int, int, long, c_uint, PyInt_FromLong, CPyCppyy_PyLong_AsStrictInt, 'l') - -//---------------------------------------------------------------------------- -bool CPyCppyy::ULongConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ unsigned long, set arg for call - if (!ImplicitBool(pyobject, ctxt)) - return false; - - para.fValue.fULong = PyLongOrInt_AsULong(pyobject); - if (para.fValue.fULong == (unsigned long)-1 && PyErr_Occurred()) - return false; - para.fTypeCode = 'L'; - return true; -} - -PyObject* CPyCppyy::ULongConverter::FromMemory(void* address) -{ -// construct python object from C++ unsigned long read at
- return PyLong_FromUnsignedLong(*((unsigned long*)address)); -} - -bool CPyCppyy::ULongConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ unsigned long, write it at
- unsigned long u = PyLongOrInt_AsULong(value); - if (u == (unsigned long)-1 && PyErr_Occurred()) { - if (value == CPyCppyy::gDefaultObject) { - PyErr_Clear(); - u = (unsigned long)0; - } else - return false; - } - *((unsigned long*)address) = u; - return true; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::UIntConverter::FromMemory(void* address) -{ -// construct python object from C++ unsigned int read at
- return PyLong_FromUnsignedLong(*((unsigned int*)address)); -} - -bool CPyCppyy::UIntConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ unsigned int, write it at
- unsigned long u = PyLongOrInt_AsULong(value); - if (u == (unsigned long)-1 && PyErr_Occurred()) - return false; - - if (u > (unsigned long)UINT_MAX) { - PyErr_SetString(PyExc_OverflowError, "value too large for unsigned int"); - return false; - } - - *((unsigned int*)address) = (unsigned int)u; - return true; -} - -//- floating point converters ------------------------------------------------ -CPPYY_IMPL_BASIC_CONVERTER_NB( - Float, float, double, c_float, PyFloat_FromDouble, PyFloat_AsDouble, 'f') -CPPYY_IMPL_BASIC_CONVERTER_NB( - Double, double, double, c_double, PyFloat_FromDouble, PyFloat_AsDouble, 'd') - -CPPYY_IMPL_BASIC_CONVERTER_NB( - LDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, c_longdouble, PyFloat_FromDouble, PyFloat_AsDouble, 'g') - -CPyCppyy::ComplexDConverter::ComplexDConverter(bool keepControl) : - InstanceConverter(Cppyy::GetFullScope("std::complex"), keepControl) {} - -// special case for std::complex, maps it to/from Python's complex -bool CPyCppyy::ComplexDConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ - const Py_complex& pc = PyComplex_AsCComplex(pyobject); - if (pc.real != -1.0 || !PyErr_Occurred()) { - fBuffer.real(pc.real); - fBuffer.imag(pc.imag); - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - - return this->InstanceConverter::SetArg(pyobject, para, ctxt); -} - -PyObject* CPyCppyy::ComplexDConverter::FromMemory(void* address) -{ - std::complex* dc = (std::complex*)address; - return PyComplex_FromDoubles(dc->real(), dc->imag()); -} - -bool CPyCppyy::ComplexDConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ - const Py_complex& pc = PyComplex_AsCComplex(value); - if (pc.real != -1.0 || !PyErr_Occurred()) { - std::complex* dc = (std::complex*)address; - dc->real(pc.real); - dc->imag(pc.imag); - return true; - } - return this->InstanceConverter::ToMemory(value, address, ctxt); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::DoubleRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ double&, set arg for call - - if (Py_TYPE(pyobject) == GetCTypesType(ct_c_double)) { - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - } - -// alternate, pass pointer from buffer - Py_ssize_t buflen = Utility::GetBuffer(pyobject, 'd', sizeof(double), para.fValue.fVoidp); - if (buflen && para.fValue.fVoidp) { - para.fTypeCode = 'V'; - return true; - } - - PyErr_SetString(PyExc_TypeError, "use ctypes.c_double for pass-by-ref of doubles"); - return false; -} - -//---------------------------------------------------------------------------- -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Float, float, c_float, PyFloat_AsDouble) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Double, double, c_double, PyFloat_AsDouble) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(LDouble, PY_LONG_DOUBLE, c_longdouble, PyFloat_AsDouble) - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidConverter::SetArg(PyObject*, Parameter&, CallContext*) -{ -// can't happen (unless a type is mapped wrongly), but implemented for completeness - PyErr_SetString(PyExc_SystemError, "void/unknown arguments can\'t be set"); - return false; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::LLongConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ long long, set arg for call - if (!ImplicitBool(pyobject, ctxt)) - return false; - - para.fValue.fLLong = CPyCppyy_PyLong_AsStrictLongLong(pyobject); - if (PyErr_Occurred()) - return false; - para.fTypeCode = 'q'; - return true; -} - -PyObject* CPyCppyy::LLongConverter::FromMemory(void* address) -{ -// construct python object from C++ long long read at
- return PyLong_FromLongLong(*(PY_LONG_LONG*)address); -} - -bool CPyCppyy::LLongConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ long long, write it at
- PY_LONG_LONG ll = PyLong_AsLongLong(value); - if (ll == -1 && PyErr_Occurred()) { - if (value == CPyCppyy::gDefaultObject) { - PyErr_Clear(); - ll = (PY_LONG_LONG)0; - } else - return false; - } - *((PY_LONG_LONG*)address) = ll; - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::ULLongConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ unsigned long long, set arg for call - if (!ImplicitBool(pyobject, ctxt)) - return false; - - para.fValue.fULLong = PyLongOrInt_AsULong64(pyobject); - if (PyErr_Occurred()) - return false; - para.fTypeCode = 'Q'; - return true; -} - -PyObject* CPyCppyy::ULLongConverter::FromMemory(void* address) -{ -// construct python object from C++ unsigned long long read at
- return PyLong_FromUnsignedLongLong(*(PY_ULONG_LONG*)address); -} - -bool CPyCppyy::ULLongConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ unsigned long long, write it at
- PY_ULONG_LONG ull = PyLongOrInt_AsULong64(value); - if (PyErr_Occurred()) { - if (value == CPyCppyy::gDefaultObject) { - PyErr_Clear(); - ull = (PY_ULONG_LONG)0; - } else - return false; - } - *((PY_ULONG_LONG*)address) = ull; - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::CStringConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// construct a new string and copy it in new memory - Py_ssize_t len; - const char* cstr = CPyCppyy_PyText_AsStringAndSize(pyobject, &len); - if (!cstr) { - // special case: allow ctypes c_char_p - auto error = CPyCppyy::Utility::FetchPyError(); - if (Py_TYPE(pyobject) == GetCTypesType(ct_c_char_p)) { - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - } - CPyCppyy::Utility::RestorePyError(error); - return false; - } - -// verify (too long string will cause truncation, no crash) - if (fMaxSize != std::string::npos && fMaxSize < fBuffer.size()) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)", 1) < 0) - return false; - - if (!ctxt->fPyContext) { - // use internal buffer as workaround - fBuffer = std::string(cstr, len); - if (fMaxSize != std::string::npos) - fBuffer.resize(fMaxSize, '\0'); // pad remainder of buffer as needed - cstr = fBuffer.c_str(); - } else - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); - -// set the value and declare success - para.fValue.fVoidp = (void*)cstr; - para.fTypeCode = 'p'; - return true; -} - -PyObject* CPyCppyy::CStringConverter::FromMemory(void* address) -{ -// construct python object from C++ const char* read at
- if (address && *(void**)address) { - if (fMaxSize != std::string::npos) // need to prevent reading beyond boundary - return CPyCppyy_PyText_FromStringAndSize(*(char**)address, (Py_ssize_t)fMaxSize); - - if (*(void**)address == (void*)fBuffer.data()) // if we're buffering, we know the size - return CPyCppyy_PyText_FromStringAndSize((char*)fBuffer.data(), fBuffer.size()); - - // no idea about lentgth: cut on \0 - return CPyCppyy_PyText_FromString(*(char**)address); - } - -// empty string in case there's no address - Py_INCREF(PyStrings::gEmptyString); - return PyStrings::gEmptyString; -} - -bool CPyCppyy::CStringConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ -// convert to C++ const char*, write it at
- Py_ssize_t len; - const char* cstr = CPyCppyy_PyText_AsStringAndSize(value, &len); - if (!cstr) return false; - -// verify (too long string will cause truncation, no crash) - if (fMaxSize != std::string::npos && fMaxSize < (std::string::size_type)len) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)", 1) < 0) - return false; - -// if address is available, and it wasn't set by this converter, assume a byte-wise copy; -// otherwise assume a pointer copy (this relies on the converter to be used for properties, -// or for argument passing, but not both at the same time; this is currently the case) - void* ptrval = *(void**)address; - if (ptrval == (void*)fBuffer.data()) { - fBuffer = std::string(cstr, len); - *(void**)address = (void*)fBuffer.data(); - return true; - } else if (ptrval && HasLifeLine(ctxt, (intptr_t)ptrval)) { - ptrval = nullptr; - // fall through; ptrval is nullptr means we're managing it - } - -// the string is (going to be) managed by us: assume pointer copy - if (!ptrval) { - SetLifeLine(ctxt, value, (intptr_t)address); - *(void**)address = (void*)cstr; - return true; - } - -// the pointer value is non-zero and not ours: assume byte copy - if (fMaxSize != std::string::npos) - strncpy(*(char**)address, cstr, fMaxSize); // pads remainder - else - // coverity[secure_coding] - can't help it, it's intentional. - strcpy(*(char**)address, cstr); - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::WCStringConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// construct a new string and copy it in new memory - Py_ssize_t len = PyUnicode_GetSize(pyobject); - if (len == (Py_ssize_t)-1 && PyErr_Occurred()) - return false; - - fBuffer = (wchar_t*)realloc(fBuffer, sizeof(wchar_t)*(len+1)); - Py_ssize_t res = CPyCppyy_PyUnicode_AsWideChar(pyobject, fBuffer, len); - if (res == -1) - return false; // could free the buffer here - -// set the value and declare success - fBuffer[len] = L'\0'; - para.fValue.fVoidp = (void*)fBuffer; - para.fTypeCode = 'p'; - return true; -} - -PyObject* CPyCppyy::WCStringConverter::FromMemory(void* address) -{ -// construct python object from C++ wchar_t* read at
- if (address && *(wchar_t**)address) { - if (fMaxSize != std::wstring::npos) // need to prevent reading beyond boundary - return PyUnicode_FromWideChar(*(wchar_t**)address, (Py_ssize_t)fMaxSize); - // with unknown size - return PyUnicode_FromWideChar(*(wchar_t**)address, wcslen(*(wchar_t**)address)); - } - -// empty string in case there's no valid address - wchar_t w = L'\0'; - return PyUnicode_FromWideChar(&w, 0); -} - -bool CPyCppyy::WCStringConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ wchar_t*, write it at
- Py_ssize_t len = PyUnicode_GetSize(value); - if (len == (Py_ssize_t)-1 && PyErr_Occurred()) - return false; - -// verify (too long string will cause truncation, no crash) - if (fMaxSize != std::wstring::npos && fMaxSize < (std::wstring::size_type)len) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for wchar_t array (truncated)", 1) < 0) - return false; - - Py_ssize_t res = -1; - if (fMaxSize != std::wstring::npos) - res = CPyCppyy_PyUnicode_AsWideChar(value, *(wchar_t**)address, (Py_ssize_t)fMaxSize); - else - // coverity[secure_coding] - can't help it, it's intentional. - res = CPyCppyy_PyUnicode_AsWideChar(value, *(wchar_t**)address, len); - - if (res == -1) return false; - return true; -} - -//---------------------------------------------------------------------------- -#define CPYCPPYY_WIDESTRING_CONVERTER(name, type, encode, decode, snull) \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) \ -{ \ -/* change string encoding and copy into local buffer */ \ - PyObject* bstr = encode(pyobject); \ - if (!bstr) return false; \ - \ - Py_ssize_t len = PyBytes_GET_SIZE(bstr) - sizeof(type) /*BOM*/; \ - fBuffer = (type*)realloc(fBuffer, len + sizeof(type)); \ - memcpy(fBuffer, PyBytes_AS_STRING(bstr) + sizeof(type) /*BOM*/, len); \ - Py_DECREF(bstr); \ - \ - fBuffer[len/sizeof(type)] = snull; \ - para.fValue.fVoidp = (void*)fBuffer; \ - para.fTypeCode = 'p'; \ - return true; \ -} \ - \ -PyObject* CPyCppyy::name##Converter::FromMemory(void* address) \ -{ \ -/* construct python object from C++ * read at
*/ \ - if (address && *(type**)address) { \ - if (fMaxSize != std::wstring::npos) \ - return decode(*(const char**)address, (Py_ssize_t)fMaxSize*sizeof(type), nullptr, nullptr);\ - return decode(*(const char**)address, \ - std::char_traits::length(*(type**)address)*sizeof(type), nullptr, nullptr);\ - } \ - \ -/* empty string in case there's no valid address */ \ - type w = snull; \ - return decode((const char*)&w, 0, nullptr, nullptr); \ -} \ - \ -bool CPyCppyy::name##Converter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */)\ -{ \ -/* convert to C++ *, write it at
*/ \ - PyObject* bstr = encode(value); \ - if (!bstr) return false; \ - \ - Py_ssize_t len = PyBytes_GET_SIZE(bstr) - sizeof(type) /*BOM*/; \ - Py_ssize_t maxbytes = (Py_ssize_t)fMaxSize*sizeof(type); \ - \ -/* verify (too long string will cause truncation, no crash) */ \ - if (fMaxSize != std::wstring::npos && maxbytes < len) { \ - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for "#type" array (truncated)", 1) < 0) { \ - Py_DECREF(bstr); \ - return false; \ - } \ - len = maxbytes; \ - } \ - \ - memcpy(*((void**)address), PyBytes_AS_STRING(bstr) + sizeof(type) /*BOM*/, len);\ - Py_DECREF(bstr); \ -/* debatable, but probably more convenient in most cases to null-terminate if enough space */\ - if (len/sizeof(type) < fMaxSize) (*(type**)address)[len/sizeof(type)] = snull;\ - return true; \ -} - -CPYCPPYY_WIDESTRING_CONVERTER(CString16, char16_t, PyUnicode_AsUTF16String, PyUnicode_DecodeUTF16, u'\0') -CPYCPPYY_WIDESTRING_CONVERTER(CString32, char32_t, PyUnicode_AsUTF32String, PyUnicode_DecodeUTF32, U'\0') - -//---------------------------------------------------------------------------- -bool CPyCppyy::NonConstCStringConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// attempt base class first (i.e. passing a string), but if that fails, try a buffer - if (this->CStringConverter::SetArg(pyobject, para, ctxt)) - return true; - -// apparently failed, try char buffer - PyErr_Clear(); - return CArraySetArg(pyobject, para, 'c', sizeof(char)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::NonConstCStringConverter::FromMemory(void* address) -{ -// assume this is a buffer access if the size is known; otherwise assume string - if (fMaxSize != std::string::npos) - return CPyCppyy_PyText_FromStringAndSize(*(char**)address, (Py_ssize_t)fMaxSize); - return this->CStringConverter::FromMemory(address); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidArrayConverter::GetAddressSpecialCase(PyObject* pyobject, void*& address) -{ -// (1): C++11 style "null pointer" - if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { - address = nullptr; - return true; - } - -// (2): allow integer zero to act as a null pointer (C NULL), no deriveds - if (PyInt_CheckExact(pyobject) || PyLong_CheckExact(pyobject)) { - intptr_t val = (intptr_t)PyLong_AsLongLong(pyobject); - if (val == 0l) { - address = (void*)val; - return true; - } - - return false; - } - -// (3): opaque PyCapsule (CObject in older pythons) from somewhere - if (CPyCppyy_PyCapsule_CheckExact(pyobject)) { - address = (void*)CPyCppyy_PyCapsule_GetPointer(pyobject, nullptr); - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidArrayConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// just convert pointer if it is a C++ object - CPPInstance* pyobj = GetCppInstance(pyobject); - para.fValue.fVoidp = nullptr; - if (pyobj) { - // depending on memory policy, some objects are no longer owned when passed to C++ - if (!fKeepControl && !UseStrictOwnership(ctxt)) - pyobj->CppOwns(); - - // set pointer (may be null) and declare success - para.fValue.fVoidp = pyobj->GetObject(); - para.fTypeCode = 'p'; - return true; - } - -// handle special cases - if (GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { - para.fTypeCode = 'p'; - return true; - } - -// allow ctypes voidp (which if got as a buffer will return void**, not void*); use -// isintance instead of an exact check, b/c c_void_p is the type mapper for typedefs -// of void* (typically opaque handles) - if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_void_p))) { - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - } - -// allow any other ctypes pointer type - if (IsCTypesArrayOrPointer(pyobject)) { - void** payload = (void**)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - if (payload) { - para.fValue.fVoidp = *payload; - para.fTypeCode = 'p'; - return true; - } - } - -// final try: attempt to get buffer - Py_ssize_t buflen = Utility::GetBuffer(pyobject, '*', 1, para.fValue.fVoidp, false); - -// ok if buffer exists (can't perform any useful size checks) - if (para.fValue.fVoidp && buflen != 0) { - para.fTypeCode = 'p'; - return true; - } - -// give up - return false; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::VoidArrayConverter::FromMemory(void* address) -{ -// nothing sensible can be done, just return
as pylong - if (!address || *(uintptr_t*)address == 0) { - Py_INCREF(gNullPtrObject); - return gNullPtrObject; - } - return CreatePointerView(*(uintptr_t**)address); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidArrayConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// just convert pointer if it is a C++ object - CPPInstance* pyobj = GetCppInstance(value); - if (pyobj) { - // depending on memory policy, some objects are no longer owned when passed to C++ - if (!fKeepControl && CallContext::sMemoryPolicy != CallContext::kUseStrict) - pyobj->CppOwns(); - - // set pointer (may be null) and declare success - *(void**)address = pyobj->GetObject(); - return true; - } - -// handle special cases - void* ptr = nullptr; - if (GetAddressSpecialCase(value, ptr)) { - *(void**)address = ptr; - return true; - } - -// final try: attempt to get buffer - void* buf = nullptr; - Py_ssize_t buflen = Utility::GetBuffer(value, '*', 1, buf, false); - if (!buf || buflen == 0) - return false; - - *(void**)address = buf; - return true; -} - -#if __cplusplus >= 202002L - -namespace CPyCppyy { - -class StdSpanConverter : public InstanceConverter { -public: - StdSpanConverter(std::string const &typeName, Cppyy::TCppScope_t klass, bool keepControl = false) - : InstanceConverter{klass, keepControl}, fTypeName{typeName} - { - } - - ~StdSpanConverter() - { - if (fHasBuffer) { - PyBuffer_Release(&fBufinfo); - } - } - - bool SetArg(PyObject *, Parameter &, CallContext * = nullptr) override; - bool HasState() override { return true; } - -private: - std::string fTypeName; - std::span fBuffer; - bool fHasBuffer = false; - Py_buffer fBufinfo; -}; - -} // namespace CPyCppyy - -//---------------------------------------------------------------------------- -bool CPyCppyy::StdSpanConverter::SetArg(PyObject *pyobject, Parameter ¶, CallContext *ctxt) -{ - auto typecodeFound = Utility::TypecodeMap().find(fTypeName); - -// attempt to get buffer if the C++ type maps to a buffer type - if (typecodeFound == Utility::TypecodeMap().end() || !PyObject_CheckBuffer(pyobject)) { - // Fall back to regular InstanceConverter - return this->InstanceConverter::SetArg(pyobject, para, ctxt); - } - - Py_ssize_t buflen = 0; - char typecode = typecodeFound->second; - memset(&fBufinfo, 0, sizeof(Py_buffer)); - - if (PyObject_GetBuffer(pyobject, &fBufinfo, PyBUF_FORMAT) == 0) { - if (!strchr(fBufinfo.format, typecode)) { - PyErr_Format(PyExc_TypeError, - "buffer has incompatible type: expected '%c' for C++ type '%s', but got format '%s'", typecode, - fTypeName.c_str(), fBufinfo.format ? fBufinfo.format : ""); - PyBuffer_Release(&fBufinfo); - return false; - } - buflen = Utility::GetBuffer(pyobject, typecode, 1, para.fValue.fVoidp, false); - } - -// ok if buffer exists (can't perform any useful size checks) - if (para.fValue.fVoidp && buflen != 0) { - // We assume the layout for any std::span is the same, and just use - // std::span as a placeholder. Not elegant, but works. - fBuffer = std::span{(std::size_t *)para.fValue.fVoidp, static_cast(buflen)}; - fHasBuffer = true; - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - - return false; -} - -#endif // __cplusplus >= 202002L - - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ - CPyCppyy::name##ArrayConverter::name##ArrayConverter(cdims_t dims) \ - : fShape(dims) { \ - fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; \ - } \ - \ - bool CPyCppyy::name##ArrayConverter::SetArg( \ - PyObject *pyobject, Parameter ¶, CallContext *ctxt) { \ - /* filter ctypes first b/c their buffer conversion will be wrong */ \ - bool convOk = false; \ - \ - /* 2-dim case: ptr-ptr types */ \ - if (!convOk && fShape.ndim() == 2) { \ - if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ - para.fValue.fVoidp = \ - (void *)((CPyCppyy_tagCDataObject *)pyobject)->b_ptr; \ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (Py_TYPE(pyobject) == GetCTypesType(ct_c_void_p)) { \ - /* special case: pass address of c_void_p buffer to return the address \ - */ \ - para.fValue.fVoidp = \ - (void *)((CPyCppyy_tagCDataObject *)pyobject)->b_ptr; \ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (LowLevelView_Check(pyobject) && \ - ((LowLevelView *)pyobject)->fBufInfo.ndim == 2 && \ - strchr(((LowLevelView *)pyobject)->fBufInfo.format, code)) { \ - para.fValue.fVoidp = ((LowLevelView *)pyobject)->get_buf(); \ - para.fTypeCode = 'p'; \ - convOk = true; \ - } \ - } \ - \ - /* 1-dim (accept pointer), or unknown (accept pointer as cast) */ \ - if (!convOk) { \ - PyTypeObject *ctypes_type = GetCTypesType(ct_##ctype); \ - if (Py_TYPE(pyobject) == ctypes_type) { \ - para.fValue.fVoidp = \ - (void *)((CPyCppyy_tagCDataObject *)pyobject)->b_ptr; \ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ - para.fValue.fVoidp = \ - (void *)((CPyCppyy_tagCDataObject *)pyobject)->b_ptr; \ - para.fTypeCode = 'V'; \ - convOk = true; \ - } else if (IsPyCArgObject(pyobject)) { \ - CPyCppyy_tagPyCArgObject *carg = (CPyCppyy_tagPyCArgObject *)pyobject; \ - if (carg->obj && Py_TYPE(carg->obj) == ctypes_type) { \ - para.fValue.fVoidp = \ - (void *)((CPyCppyy_tagCDataObject *)carg->obj)->b_ptr; \ - para.fTypeCode = 'p'; \ - convOk = true; \ - } \ - } else if (LowLevelView_Check(pyobject) && \ - strchr(((LowLevelView *)pyobject)->fBufInfo.format, code)) { \ - para.fValue.fVoidp = ((LowLevelView *)pyobject)->get_buf(); \ - para.fTypeCode = 'p'; \ - convOk = true; \ - } \ - } \ - \ - /* cast pointer type */ \ - if (!convOk) { \ - bool ismulti = fShape.ndim() > 1; \ - convOk = CArraySetArg(pyobject, para, code, \ - ismulti ? sizeof(void *) : sizeof(type), true); \ - } \ - \ - /* memory management and offsetting */ \ - if (convOk) \ - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); \ - \ - return convOk; \ - } \ - \ - PyObject *CPyCppyy::name##ArrayConverter::FromMemory(void *address) { \ - if (!fIsFixed) \ - return CreateLowLevelView##suffix((type **)address, fShape); \ - return CreateLowLevelView##suffix(*(type **)address, fShape); \ - } \ - \ - bool CPyCppyy::name##ArrayConverter::ToMemory( \ - PyObject *value, void *address, PyObject *ctxt) { \ - if (fShape.ndim() <= 1 || fIsFixed) { \ - void *buf = nullptr; \ - Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(type), buf); \ - if (buflen == 0) \ - return false; \ - \ - Py_ssize_t oldsz = 1; \ - for (Py_ssize_t idim = 0; idim < fShape.ndim(); ++idim) { \ - if (fShape[idim] == UNKNOWN_SIZE) { \ - oldsz = -1; \ - break; \ - } \ - oldsz *= fShape[idim]; \ - } \ - if (fShape.ndim() != UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { \ - PyErr_SetString(PyExc_ValueError, "buffer too large for value"); \ - return false; \ - } \ - \ - if (fIsFixed) \ - memcpy(*(type **)address, buf, \ - (0 < buflen ? buflen : 1) * sizeof(type)); \ - else { \ - *(type **)address = (type *)buf; \ - fShape.ndim(1); \ - fShape[0] = buflen; \ - SetLifeLine(ctxt, value, (intptr_t)address); \ - } \ - \ - } else { /* multi-dim, non-flat array; assume structure matches */ \ - void *buf = nullptr; /* TODO: GetBuffer() assumes flat? */ \ - Py_ssize_t buflen = \ - Utility::GetBuffer(value, code, sizeof(void *), buf); \ - if (buflen == 0) \ - return false; \ - *(type **)address = (type *)buf; \ - SetLifeLine(ctxt, value, (intptr_t)address); \ - } \ - return true; \ - } - -//---------------------------------------------------------------------------- -CPPYY_IMPL_ARRAY_CONVERTER(Bool, c_bool, bool, '?', ) -CPPYY_IMPL_ARRAY_CONVERTER(SChar, c_char, signed char, 'b', ) -CPPYY_IMPL_ARRAY_CONVERTER(UChar, c_ubyte, unsigned char, 'B', ) -CPPYY_IMPL_ARRAY_CONVERTER(Byte, c_ubyte, std::byte, 'B', ) -CPPYY_IMPL_ARRAY_CONVERTER(Int8, c_byte, int8_t, 'b', _i8) -CPPYY_IMPL_ARRAY_CONVERTER(UInt8, c_ubyte, uint8_t, 'B', _i8) -CPPYY_IMPL_ARRAY_CONVERTER(Short, c_short, short, 'h', ) -CPPYY_IMPL_ARRAY_CONVERTER(UShort, c_ushort, unsigned short, 'H', ) -CPPYY_IMPL_ARRAY_CONVERTER(Int, c_int, int, 'i', ) -CPPYY_IMPL_ARRAY_CONVERTER(UInt, c_uint, unsigned int, 'I', ) -CPPYY_IMPL_ARRAY_CONVERTER(Long, c_long, long, 'l', ) -CPPYY_IMPL_ARRAY_CONVERTER(ULong, c_ulong, unsigned long, 'L', ) -CPPYY_IMPL_ARRAY_CONVERTER(LLong, c_longlong, long long, 'q', ) -CPPYY_IMPL_ARRAY_CONVERTER(ULLong, c_ulonglong, unsigned long long, 'Q', ) -CPPYY_IMPL_ARRAY_CONVERTER(Float, c_float, float, 'f', ) -CPPYY_IMPL_ARRAY_CONVERTER(Double, c_double, double, 'd', ) -CPPYY_IMPL_ARRAY_CONVERTER(LDouble, c_longdouble, long double, 'g', ) -CPPYY_IMPL_ARRAY_CONVERTER(ComplexF, c_fcomplex, std::complex, 'z', ) -CPPYY_IMPL_ARRAY_CONVERTER(ComplexD, c_complex, std::complex, 'Z', ) - - -//---------------------------------------------------------------------------- -bool CPyCppyy::CStringArrayConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ - if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_c_char_p) || \ - (1 < fShape.ndim() && PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_pointer)))) { - // 2nd predicate is ebatable: it's a catch-all for ctypes-styled multi-dimensional objects, - // which at this point does not check further dimensionality - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - para.fTypeCode = 'V'; - return true; - - } else if (PySequence_Check(pyobject) && !CPyCppyy_PyText_Check(pyobject) - && !PyBytes_Check(pyobject) - ) { - //for (auto& p : fBuffer) free(p); - fBuffer.clear(); - - size_t len = (size_t)PySequence_Size(pyobject); - if (len == (size_t)-1) { - PyErr_SetString(PyExc_ValueError, "can not convert sequence object of unknown length"); - return false; - } - - fBuffer.reserve(len); - for (size_t i = 0; i < len; ++i) { - PyObject* item = PySequence_GetItem(pyobject, i); - if (item) { - Py_ssize_t sz; - const char* p = CPyCppyy_PyText_AsStringAndSize(item, &sz); - Py_DECREF(item); - - if (p) fBuffer.push_back(p); - else { - PyErr_Format(PyExc_TypeError, "could not convert item %d to string", (int)i); - return false; - } - - } else - return false; - } - - para.fValue.fVoidp = (void*)fBuffer.data(); - para.fTypeCode = 'p'; - return true; - } - - return SCharArrayConverter::SetArg(pyobject, para, ctxt); -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CStringArrayConverter::FromMemory(void* address) -{ - if (fIsFixed) - return CreateLowLevelView(*(char**)address, fShape); - else if (fShape[0] == UNKNOWN_SIZE) - return CreateLowLevelViewString((const char**)address, fShape); - return CreateLowLevelViewString(*(const char***)address, fShape); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::NonConstCStringArrayConverter::FromMemory(void* address) -{ - if (fIsFixed) - return CreateLowLevelView(*(char**)address, fShape); - else if (fShape[0] == UNKNOWN_SIZE) - return CreateLowLevelViewString((char**)address, fShape); - return CreateLowLevelViewString(*(char***)address, fShape); -} - -//- converters for special cases --------------------------------------------- -bool CPyCppyy::NullptrConverter::SetArg(PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// Only allow C++11 style nullptr to pass - if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { - para.fValue.fVoidp = nullptr; - para.fTypeCode = 'p'; - return true; - } - return false; -} - - -//---------------------------------------------------------------------------- -template -static inline bool CPyCppyy_PyUnicodeAsBytes2Buffer(PyObject* pyobject, T& buffer) { - PyObject* pybytes = nullptr; - if (PyBytes_Check(pyobject)) { - Py_INCREF(pyobject); - pybytes = pyobject; - } else if (PyUnicode_Check(pyobject)) { - pybytes = PyUnicode_AsUTF8String(pyobject); - } - - if (pybytes) { - Py_ssize_t len; - const char* cstr = nullptr; - PyBytes_AsStringAndSize(pybytes, (char**)&cstr, &len); - if (cstr) buffer = T{cstr, (typename T::size_type)len}; - Py_DECREF(pybytes); - return (bool)cstr; - } - - return false; -} - -#define CPPYY_IMPL_STRING_AS_PRIMITIVE_CONVERTER(name, type, F1, F2) \ -CPyCppyy::name##Converter::name##Converter(bool keepControl) : \ - InstanceConverter(Cppyy::GetFullScope(#type), keepControl) {} \ - \ -bool CPyCppyy::name##Converter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* ctxt) \ -{ \ - if (CPyCppyy_PyUnicodeAsBytes2Buffer(pyobject, fBuffer)) { \ - para.fValue.fVoidp = &fBuffer; \ - para.fTypeCode = 'V'; \ - return true; \ - } \ - \ - PyErr_Clear(); \ - if (!(PyInt_Check(pyobject) || PyLong_Check(pyobject))) { \ - bool result = InstanceConverter::SetArg(pyobject, para, ctxt); \ - para.fTypeCode = 'V'; \ - return result; \ - } \ - \ - return false; \ -} \ - \ -PyObject* CPyCppyy::name##Converter::FromMemory(void* address) \ -{ \ - if (address) \ - return InstanceConverter::FromMemory(address); \ - auto* empty = new type(); \ - return BindCppObjectNoCast(Cppyy::TCppObject_t((void*)empty), fClass, CPPInstance::kIsOwner);\ -} \ - \ -bool CPyCppyy::name##Converter::ToMemory( \ - PyObject* value, void* address, PyObject* ctxt) \ -{ \ - if (CPyCppyy_PyUnicodeAsBytes2Buffer(value, *((type*)address))) \ - return true; \ - return InstanceConverter::ToMemory(value, address, ctxt); \ -} - -CPPYY_IMPL_STRING_AS_PRIMITIVE_CONVERTER(STLString, std::string, c_str, size) - - -CPyCppyy::STLWStringConverter::STLWStringConverter(bool keepControl) : - InstanceConverter(Cppyy::GetFullScope("std::wstring"), keepControl) {} - -bool CPyCppyy::STLWStringConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ - if (PyUnicode_Check(pyobject)) { - Py_ssize_t len = CPyCppyy_PyUnicode_GET_SIZE(pyobject); - fBuffer.resize(len); - CPyCppyy_PyUnicode_AsWideChar(pyobject, &fBuffer[0], len); - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - - if (!(PyInt_Check(pyobject) || PyLong_Check(pyobject))) { - bool result = InstancePtrConverter::SetArg(pyobject, para, ctxt); - para.fTypeCode = 'V'; - return result; - } - - return false; -} - -PyObject* CPyCppyy::STLWStringConverter::FromMemory(void* address) -{ - if (address) - return PyUnicode_FromWideChar(((std::wstring*)address)->c_str(), ((std::wstring*)address)->size()); - wchar_t w = L'\0'; - return PyUnicode_FromWideChar(&w, 0); -} - -bool CPyCppyy::STLWStringConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ - if (PyUnicode_Check(value)) { - Py_ssize_t len = CPyCppyy_PyUnicode_GET_SIZE(value); - wchar_t* buf = new wchar_t[len+1]; - CPyCppyy_PyUnicode_AsWideChar(value, buf, len); - *((std::wstring*)address) = std::wstring(buf, len); - delete[] buf; - return true; - } - return InstanceConverter::ToMemory(value, address, ctxt); -} - -CPyCppyy::STLStringViewConverter::STLStringViewConverter(bool keepControl) : - InstanceConverter(Cppyy::GetFullScope("std::string_view"), keepControl) {} - -bool CPyCppyy::STLStringViewConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// normal instance convertion (eg. string_view object passed) - if (!PyInt_Check(pyobject) && !PyLong_Check(pyobject)) { - CallContextRAII noimp(ctxt); - if (InstanceConverter::SetArg(pyobject, para, ctxt)) { - para.fTypeCode = 'V'; - return true; - } else - PyErr_Clear(); - } - -// passing of a Python string; buffering done Python-side b/c str is immutable - Py_ssize_t len; - const char* cstr = CPyCppyy_PyText_AsStringAndSize(pyobject, &len); - if (cstr) { - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); - fBuffer = std::string_view(cstr, (std::string_view::size_type)len); - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - - if (!CPPInstance_Check(pyobject)) - return false; - -// special case of a C++ std::string object; life-time management is left to -// the caller to ensure any external changes propagate correctly - if (CPPInstance_Check(pyobject)) { - static Cppyy::TCppScope_t sStringID = Cppyy::GetUnderlyingScope(Cppyy::GetFullScope("std::string")); - CPPInstance* pyobj = (CPPInstance*)pyobject; - if (pyobj->ObjectIsA() == sStringID) { - void* ptr = pyobj->GetObject(); - if (!ptr) - return false; // leaves prior conversion error for report - - PyErr_Clear(); - - fBuffer = *((std::string*)ptr); - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - } - - return false; -} - -PyObject* CPyCppyy::STLStringViewConverter::FromMemory(void* address) -{ - if (address) - return InstanceConverter::FromMemory(address); - auto* empty = new std::string_view(); - return BindCppObjectNoCast(empty, fClass, CPPInstance::kIsOwner); -} - -bool CPyCppyy::STLStringViewConverter::ToMemory( - PyObject* value, void* address, PyObject* ctxt) -{ -// common case of simple object assignment - if (InstanceConverter::ToMemory(value, address, ctxt)) - return true; - -// assignment of a Python string; buffering done Python-side b/c str is immutable - Py_ssize_t len; - const char* cstr = CPyCppyy_PyText_AsStringAndSize(value, &len); - if (cstr) { - SetLifeLine(ctxt, value, (intptr_t)this); - *reinterpret_cast(address) = \ - std::string_view(cstr, (std::string_view::size_type)len); - return true; - } - - return false; -} - - -bool CPyCppyy::STLStringMoveConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ std::string&&, set arg for call - int moveit_reason = 3; // move on temporary fBuffer - if (CPPInstance_Check(pyobject)) { - CPPInstance* pyobj = (CPPInstance*)pyobject; - if (pyobj->fFlags & CPPInstance::kIsRValue) { - pyobj->fFlags &= ~CPPInstance::kIsRValue; - moveit_reason = 2; - } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { - moveit_reason = 1; - } else - moveit_reason = 0; - } - - if (moveit_reason) { - bool result = this->STLStringConverter::SetArg(pyobject, para, ctxt); - if (!result && moveit_reason == 2) // restore the movability flag? - ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; - return result; - } - - PyErr_SetString(PyExc_ValueError, "object is not an rvalue"); - return false; // not a temporary or movable object -} - - -//---------------------------------------------------------------------------- -template -bool CPyCppyy::InstancePtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance*, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject, ISCONST ? fClass : Cppyy::TCppScope_t{}); - if (!pyobj) { - if (GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { - para.fTypeCode = 'p'; // allow special cases such as nullptr - return true; - } - - // not a cppyy object (TODO: handle SWIG etc.) - return false; - } - - // smart pointers should only extract the pointer if this is NOT an implicit - // conversion to another smart pointer - if (pyobj->IsSmart() && IsConstructor(ctxt->fFlags) && Cppyy::IsSmartPtr(ctxt->fCurScope)) - return false; - - Cppyy::TCppScope_t oisa = pyobj->ObjectIsA(); - if (oisa && (oisa == fClass || Cppyy::IsSubclass(oisa, fClass))) { - // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership(ctxt)) - pyobj->CppOwns(); - - // calculate offset between formal and actual arguments - para.fValue.fVoidp = pyobj->GetObject(); - if (oisa != fClass) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - oisa, fClass, para.fValue.fVoidp, 1 /* up-cast */); - } - - // set pointer (may be null) and declare success - para.fTypeCode = 'p'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -template -PyObject* CPyCppyy::InstancePtrConverter::FromMemory(void* address) -{ -// construct python object from C++ instance read at
- if (ISCONST) - return BindCppObject(*(void**)address, fClass); // by pointer value - return BindCppObject(address, fClass, CPPInstance::kIsReference); // modifiable -} - -//---------------------------------------------------------------------------- -template -bool CPyCppyy::InstancePtrConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ instance, write it at
- CPPInstance* pyobj = GetCppInstance(value, ISCONST ? fClass : Cppyy::TCppScope_t{}); - if (!pyobj) { - void* ptr = nullptr; - if (GetAddressSpecialCase(value, ptr)) { - *(void**)address = ptr; // allow special cases such as nullptr - return true; - } - - // not a cppyy object (TODO: handle SWIG etc.) - return false; - } - - if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { - // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict) - ((CPPInstance*)value)->CppOwns(); - - *(void**)address = pyobj->GetObject(); - return true; - } - - return false; -} - -// TODO: CONSOLIDATE Instance, InstanceRef, InstancePtr ... - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject, fClass); - if (pyobj) { - auto oisa = pyobj->ObjectIsA(); - if (oisa && ((oisa == (Cppyy::IsTypedefed(fClass) - ? Cppyy::GetUnderlyingScope(fClass) - : fClass)) || - Cppyy::IsSubclass(oisa, fClass))) { - // calculate offset between formal and actual arguments - para.fValue.fVoidp = pyobj->GetObject(); - if (!para.fValue.fVoidp) - return false; - - if (oisa != fClass) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - pyobj->ObjectIsA(), fClass, para.fValue.fVoidp, 1 /* up-cast */); - } - - para.fTypeCode = 'V'; - return true; - } - } - - return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceConverter::FromMemory(void* address) -{ -// This should not need a cast (ie. BindCppObjectNoCast), but performing the cast -// here means callbacks receive down-casted object when passed by-ptr, which is -// needed for object identity. The latter case is assumed to be more common than -// conversion of (global) objects. - return BindCppObject((Cppyy::TCppObject_t)address, fClass); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// assign value to C++ instance living at
through assignment operator - PyObject* pyobj = BindCppObjectNoCast(address, fClass); - PyObject* result = PyObject_CallMethodOneArg(pyobj, PyStrings::gAssign, value); - Py_DECREF(pyobj); - - if (result) { - Py_DECREF(result); - return true; - } - return false; -} - - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance&, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject, fIsConst ? fClass : Cppyy::TCppScope_t{}); - if (pyobj) { - - // reject moves - if (pyobj->fFlags & CPPInstance::kIsRValue) - return false; - - // smart pointers can end up here in case of a move, so preferentially match - // the smart type directly - bool argset = false; - Cppyy::TCppScope_t cls; - if (pyobj->IsSmart()) { - cls = pyobj->ObjectIsA(false); - if (cls && Cppyy::IsSubclass(cls, fClass)) { - para.fValue.fVoidp = pyobj->GetObjectRaw(); - argset = true; - } - } - - if (!argset) { - cls = pyobj->ObjectIsA(); - if (cls && Cppyy::IsSubclass(cls, fClass)) { - para.fValue.fVoidp = pyobj->GetObject(); - argset = true; - } - } - - if (argset) { - // do not allow null pointers through references - if (!para.fValue.fVoidp) { - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); - return false; - } - - // calculate offset between formal and actual arguments - if (cls != fClass) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - cls, fClass, para.fValue.fVoidp, 1 /* up-cast */); - } - - para.fTypeCode = 'V'; - return true; - } - } - - if (!fIsConst) // no implicit conversion possible - return false; - - return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceRefConverter::FromMemory(void* address) -{ - return BindCppObjectNoCast((Cppyy::TCppObject_t)address, fClass, CPPInstance::kIsReference); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceMoveConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance&&, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject, fClass, true /* accept_rvalue */); - if (!pyobj || (pyobj->fFlags & CPPInstance::kIsLValue)) { - // implicit conversion is fine as the temporary by definition is moveable - return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); - } - -// moving is same as by-ref, but have to check that move is allowed - int moveit_reason = 0; - if (pyobj->fFlags & CPPInstance::kIsRValue) { - pyobj->fFlags &= ~CPPInstance::kIsRValue; - moveit_reason = 2; - } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { - moveit_reason = 1; - } - - if (moveit_reason) { - bool result = this->InstanceRefConverter::SetArg(pyobject, para, ctxt); - if (!result && moveit_reason == 2) // restore the movability flag? - ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; - return result; - } - - PyErr_SetString(PyExc_ValueError, "object is not an rvalue"); - return false; // not a temporary or movable object -} - -//---------------------------------------------------------------------------- -template -bool CPyCppyy::InstancePtrPtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// convert to C++ instance**, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject); - if (!pyobj) { - if (!ISREFERENCE && (pyobject == gNullPtrObject || pyobject == gDefaultObject)) { - // allow nullptr as a special case - para.fValue.fVoidp = nullptr; - para.fTypeCode = 'p'; - return true; - } - return false; // not a cppyy object (TODO: handle SWIG etc.) - } - - if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { - // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership(ctxt)) - pyobj->CppOwns(); - - // set pointer (may be null) and declare success - if (pyobj->fFlags & CPPInstance::kIsReference) // already a ptr to object? - para.fValue.fVoidp = pyobj->GetObjectRaw(); - else - para.fValue.fVoidp = &pyobj->GetObjectRaw(); - para.fTypeCode = ISREFERENCE ? 'V' : 'p'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -template -PyObject* CPyCppyy::InstancePtrPtrConverter::FromMemory(void* address) -{ -// construct python object from C++ instance* read at
- return BindCppObject(*(void**)address, fClass, CPPInstance::kIsReference | CPPInstance::kIsPtrPtr); -} - -//---------------------------------------------------------------------------- -template -bool CPyCppyy::InstancePtrPtrConverter::ToMemory( - PyObject* value, void* address, PyObject* /* ctxt */) -{ -// convert to C++ instance*, write it at
- CPPInstance* pyobj = GetCppInstance(value); - if (!pyobj) { - if (value == gNullPtrObject || value == gDefaultObject) { - // allow nullptr as a special case - *(void**)address = nullptr; - return true; - } - return false; // not a cppyy object (TODO: handle SWIG etc.) - } - - if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { - // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict) - pyobj->CppOwns(); - - // register the value for potential recycling - MemoryRegulator::RegisterPyObject(pyobj, pyobj->GetObject()); - - // set pointer (may be null) and declare success - *(void**)address = pyobj->GetObject(); - return true; - } - - return false; -} - - -namespace CPyCppyy { -// Instantiate the templates - template class CPyCppyy::InstancePtrConverter; - template class CPyCppyy::InstancePtrConverter; - template class CPyCppyy::InstancePtrPtrConverter; - template class CPyCppyy::InstancePtrPtrConverter; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceArrayConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* txt */) -{ -// convert to C++ instance**, set arg for call - while (PyTuple_Check(pyobject) && !TupleOfInstances_CheckExact(pyobject)) { - if (PyTuple_Size(pyobject) > 0) - pyobject = PyTuple_GetItem(pyobject, 0); - else - return false; - } - if (!TupleOfInstances_CheckExact(pyobject)) - return false; // no guarantee that the tuple is okay - -// treat the first instance of the tuple as the start of the array, and pass it -// by pointer (TODO: store and check sizes) - if (PyTuple_Size(pyobject) < 1) - return false; - - PyObject* first = PyTuple_GetItem(pyobject, 0); - if (!CPPInstance_Check(first)) - return false; // should not happen - - if (Cppyy::IsSubclass(((CPPInstance*)first)->ObjectIsA(), fClass)) { - // no memory policies supported; set pointer (may be null) and declare success - para.fValue.fVoidp = ((CPPInstance*)first)->GetObject(); - para.fTypeCode = 'p'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceArrayConverter::FromMemory(void* address) -{ -// construct python tuple of instances from C++ array read at
- return BindCppObjectArray(*(char**)address, fClass, fShape); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::InstanceArrayConverter::ToMemory( - PyObject* /* value */, void* /* address */, PyObject* /* ctxt */) -{ -// convert to C++ array of instances, write it at
- -// TODO: need to have size both for the array and from the input - PyErr_SetString(PyExc_NotImplementedError, - "access to C-arrays of objects not yet implemented!"); - return false; -} - -//___________________________________________________________________________ -// Cling WORKAROUND -- classes for STL iterators are completely undefined in that -// they come in a bazillion different guises, so just do whatever -bool CPyCppyy::STLIteratorConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ - if (!CPPInstance_Check(pyobject)) - return false; - -// just set the pointer value, no check - CPPInstance* pyobj = (CPPInstance*)pyobject; - para.fValue.fVoidp = pyobj->GetObject(); - para.fTypeCode = 'V'; - return true; -} -// -- END Cling WORKAROUND - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidPtrRefConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ void*&, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject); - if (pyobj) { - para.fValue.fVoidp = &pyobj->GetObjectRaw(); - para.fTypeCode = 'V'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -CPyCppyy::VoidPtrPtrConverter::VoidPtrPtrConverter(cdims_t dims, const std::string &failureMsg) : - fShape(dims), fFailureMsg (failureMsg) { - fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::VoidPtrPtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// convert to C++ void**, set arg for call - CPPInstance* pyobj = GetCppInstance(pyobject); - if (pyobj) { - // this is a C++ object, take and set its address - para.fValue.fVoidp = &pyobj->GetObjectRaw(); - para.fTypeCode = 'p'; - return true; - } else if (IsPyCArgObject(pyobject)) { - CPyCppyy_tagPyCArgObject* carg = (CPyCppyy_tagPyCArgObject*)pyobject; - if (carg->obj) { - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)carg->obj)->b_ptr; - para.fTypeCode = 'p'; - return true; - } - } - -// buffer objects are allowed under "user knows best" (this includes the buffer -// interface to ctypes.c_void_p, which results in a void**) - Py_ssize_t buflen = Utility::GetBuffer(pyobject, '*', 1, para.fValue.fVoidp, false); - -// ok if buffer exists (can't perform any useful size checks) - if (para.fValue.fVoidp && buflen != 0) { - para.fTypeCode = 'p'; - return true; - } - - return false; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::VoidPtrPtrConverter::FromMemory(void* address) -{ -// read a void** from address; since this is unknown, uintptr_t is used (user can cast) - if (!address || *(ptrdiff_t*)address == 0) { - Py_INCREF(gNullPtrObject); - return gNullPtrObject; - } - if (!fIsFixed) - return CreatePointerView((uintptr_t**)address, fShape); - return CreatePointerView(*(uintptr_t**)address, fShape); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::PyObjectConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) -{ -// by definition: set and declare success - para.fValue.fVoidp = pyobject; - para.fTypeCode = 'p'; - return true; -} - -PyObject* CPyCppyy::PyObjectConverter::FromMemory(void* address) -{ -// construct python object from C++ PyObject* read at
- PyObject* pyobject = *((PyObject**)address); - - if (!pyobject) { - Py_RETURN_NONE; - } - - Py_INCREF(pyobject); - return pyobject; -} - -bool CPyCppyy::PyObjectConverter::ToMemory(PyObject* value, void* address, PyObject* /* ctxt */) -{ -// no conversion needed, write at
- Py_INCREF(value); - Py_XDECREF(*((PyObject**)address)); - *((PyObject**)address) = value; - return true; -} - - -//- function pointer converter ----------------------------------------------- -static unsigned int sWrapperCounter = 0; -// cache mapping signature/return type to python callable and corresponding wrapper -typedef std::string RetSigKey_t; -static std::unordered_map> sWrapperFree; -static std::unordered_map> sWrapperLookup; -static std::unordered_map> sWrapperWeakRefs; -static std::unordered_map sWrapperReference; - -static PyObject* WrapperCacheEraser(PyObject*, PyObject* pyref) -{ - auto ipos = sWrapperWeakRefs.find(pyref); - if (ipos != sWrapperWeakRefs.end()) { - auto key = ipos->second.second; - - // disable this callback and store on free list for possible re-use - void* wpraddress = ipos->second.first; - PyObject** oldref = sWrapperReference[wpraddress]; - const auto& lookup = sWrapperLookup.find(key); - if (lookup != sWrapperLookup.end()) lookup->second.erase(*oldref); - *oldref = nullptr; // to detect deletions - sWrapperFree[ipos->second.second].push_back(wpraddress); - - // clean up and remove weak reference from admin - Py_DECREF(ipos->first); - sWrapperWeakRefs.erase(ipos); - } - - Py_RETURN_NONE; -} -static PyMethodDef gWrapperCacheEraserMethodDef = { - const_cast("internal_WrapperCacheEraser"), - (PyCFunction)WrapperCacheEraser, - METH_O, nullptr -}; - -static void* PyFunction_AsCPointer(PyObject* pyobject, - const std::string& rettype, const std::string& signature, bool allowCppInstance) -{ -// Convert a bound C++ function pointer or callable python object to a C-style -// function pointer. The former is direct, the latter involves a JIT-ed wrapper. - static PyObject* sWrapperCacheEraser = PyCFunction_New(&gWrapperCacheEraserMethodDef, nullptr); - - // FIXME: avoid string comparisons and parsing - std::string true_signature = signature; - - if (true_signature.rfind("(void)") != std::string::npos) - true_signature = true_signature.substr(0, true_signature.size() - 6) + "()"; - - using namespace CPyCppyy; - - if (CPPOverload_Check(pyobject)) { - CPPOverload* ol = (CPPOverload*)pyobject; - if (!ol->fMethodInfo || ol->fMethodInfo->fMethods.empty()) - return nullptr; - - // find the overload with matching signature - for (auto& m : ol->fMethodInfo->fMethods) { - PyObject* sig = m->GetSignature(false); - bool found = true_signature == CPyCppyy_PyText_AsString(sig); - Py_DECREF(sig); - if (found) { - void* fptr = (void*)m->GetFunctionAddress(); - if (fptr) return fptr; - break; // fall-through, with calling through Python - } - } - // FIXME: maybe we should try BestOverloadFunctionMatch before failing - // FIXME: Should we fall-through, with calling through Python - return nullptr; - } - - if (TemplateProxy_Check(pyobject)) { - // get the actual underlying template matching the signature - TemplateProxy* pytmpl = (TemplateProxy*)pyobject; - std::string fullname = pytmpl->fTI->fCppName; - if (pytmpl->fTemplateArgs) - fullname += CPyCppyy_PyText_AsString(pytmpl->fTemplateArgs); - Cppyy::TCppScope_t scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - Cppyy::TCppMethod_t cppmeth = Cppyy::GetMethodTemplate(scope, fullname, true_signature); - if (cppmeth) { - void* fptr = (void*)Cppyy::GetFunctionAddress(cppmeth, false); - if (fptr) return fptr; - } - // FIXME: Should we fall-through, with calling through Python - return nullptr; - } - - if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_funcptr))) { - // ctypes function pointer - void* fptr = *(void**)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; - return fptr; - } - - if (PyCallable_Check(pyobject) && (allowCppInstance || !CPPInstance_Check(pyobject))) { - // generic python callable: create a C++ wrapper function - // Sometimes we don't want to take this branch if the object is a C++ - // instance, because C++ doesn't allow converting functor objects to - // function pointers, but only to std::function. - void* wpraddress = nullptr; - - // re-use existing wrapper if possible - auto key = rettype+true_signature; - const auto& lookup = sWrapperLookup.find(key); - if (lookup != sWrapperLookup.end()) { - const auto& existing = lookup->second.find(pyobject); - if (existing != lookup->second.end() && *sWrapperReference[existing->second] == pyobject) - wpraddress = existing->second; - } - - // check for a pre-existing, unused, wrapper if not found - if (!wpraddress) { - const auto& freewrap = sWrapperFree.find(key); - if (freewrap != sWrapperFree.end() && !freewrap->second.empty()) { - wpraddress = freewrap->second.back(); - freewrap->second.pop_back(); - *(sWrapperReference[wpraddress]) = pyobject; - sWrapperLookup[key][pyobject] = wpraddress; - PyObject* wref = PyWeakref_NewRef(pyobject, sWrapperCacheEraser); - if (wref) sWrapperWeakRefs[wref] = std::make_pair(wpraddress, key); - else PyErr_Clear(); // happens for builtins which don't need this - } - } - - // create wrapper if no re-use possible - if (!wpraddress) { - if (!Utility::IncludePython()) - return nullptr; - - // extract argument types - const std::vector& argtypes = TypeManip::extract_arg_types(true_signature); - int nArgs = (int)argtypes.size(); - - // wrapper name - std::ostringstream wname; - wname << "fptr_wrap" << ++sWrapperCounter; - - // build wrapper function code - std::ostringstream code; - code << "namespace __cppyy_internal {\n " - << rettype << " " << wname.str() << "("; - for (int i = 0; i < nArgs; ++i) { - code << argtypes[i] << " arg" << i; - if (i != nArgs-1) code << ", "; - } - code << ") {\n" - << " CPyCppyy::PythonGILRAII python_gil_raii;\n"; - - // start function body - Utility::ConstructCallbackPreamble(rettype, argtypes, code); - - // create a referenceable pointer - PyObject** ref = new PyObject*{pyobject}; - - // function call itself and cleanup - code << " PyObject** ref = (PyObject**)" << (intptr_t)ref << ";\n" - " PyObject* pyresult = nullptr;\n" - " if (*ref) pyresult = PyObject_CallFunctionObjArgs(*ref"; - for (int i = 0; i < nArgs; ++i) - code << ", pyargs[" << i << "]"; - code << ", NULL);\n" - " else PyErr_SetString(PyExc_TypeError, \"callable was deleted\");\n"; - - // close - Utility::ConstructCallbackReturn(rettype, nArgs, code); - - // end of namespace - code << "}"; - - // finally, compile the code - if (!Cppyy::Compile(code.str())) - return nullptr; - - // TODO: is there no easier way? - static Cppyy::TCppScope_t scope = Cppyy::GetScope("__cppyy_internal"); - const auto& methods = Cppyy::GetMethodsFromName(scope, wname.str()); - wpraddress = Cppyy::GetFunctionAddress(methods[0], false); - sWrapperReference[wpraddress] = ref; - - // cache the new wrapper - sWrapperLookup[key][pyobject] = wpraddress; - PyObject* wref = PyWeakref_NewRef(pyobject, sWrapperCacheEraser); - if (wref) sWrapperWeakRefs[wref] = std::make_pair(wpraddress, key); - else PyErr_Clear(); // happens for builtins which don't need this - } - - // now pass the pointer to the wrapper function (may be null) - return wpraddress; - } - - return nullptr; -} - -bool CPyCppyy::FunctionPointerConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// special case: allow nullptr singleton: - if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { - para.fValue.fVoidp = nullptr; - para.fTypeCode = 'p'; - return true; - } - -// normal case, get a function pointer - void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance); - if (fptr) { - SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); - para.fValue.fVoidp = fptr; - para.fTypeCode = 'p'; - return true; - } - - return false; -} - -PyObject* CPyCppyy::FunctionPointerConverter::FromMemory(void* address) -{ -// A function pointer in clang is represented by a Type, not a FunctionDecl and it's -// not possible to get the latter from the former: the backend will need to support -// both. Since that is far in the future, we'll use a std::function instead. - if (address) - return Utility::FuncPtr2StdFunction(fRetType, fSignature, *(void**)address); - PyErr_SetString(PyExc_TypeError, "can not convert null function pointer"); - return nullptr; -} - -bool CPyCppyy::FunctionPointerConverter::ToMemory( - PyObject* pyobject, void* address, PyObject* ctxt) -{ -// special case: allow nullptr singleton: - if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { - *((void**)address) = nullptr; - return true; - } - -// normal case, get a function pointer - void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance); - if (fptr) { - SetLifeLine(ctxt, pyobject, (intptr_t)address); - *((void**)address) = fptr; - return true; - } - - return false; -} - - -//- std::function converter -------------------------------------------------- -bool CPyCppyy::StdFunctionConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -// prefer normal "object" conversion - CallContextRAII noimp(ctxt); - if (fConverter->SetArg(pyobject, para, ctxt)) - return true; - - PyErr_Clear(); - -// else create a wrapper function - if (this->FunctionPointerConverter::SetArg(pyobject, para, ctxt)) { - // retrieve the wrapper pointer and capture it in a temporary std::function, - // then try normal conversion a second time - PyObject* func = this->FunctionPointerConverter::FromMemory(¶.fValue.fVoidp); - if (func) { - SetLifeLine(ctxt->fPyContext, func, (intptr_t)this); - bool result = fConverter->SetArg(func, para, ctxt); - if (result) ctxt->AddTemporary(func); - else Py_DECREF(func); - return result; - } - } - - return false; -} - -PyObject* CPyCppyy::StdFunctionConverter::FromMemory(void* address) -{ - return fConverter->FromMemory(address); -} - -bool CPyCppyy::StdFunctionConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ -// if the value is not an std::function<> but a generic Python callable, the -// conversion is done through the assignment, which may involve a temporary - if (address) SetLifeLine(ctxt, value, (intptr_t)address); - return fConverter->ToMemory(value, address, ctxt); -} - - -//- smart pointer converters ------------------------------------------------- -bool CPyCppyy::SmartPtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ - char typeCode = fIsRef ? 'p' : 'V'; - - if (!CPPInstance_Check(pyobject)) { - // TODO: not sure how this is correct for pass-by-ref nor does it help with - // implicit conversions for pass-by-value - if (fIsRef && GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { - para.fTypeCode = typeCode; // allow special cases such as nullptr - return true; - } - - return false; - } - - CPPInstance* pyobj = (CPPInstance*)pyobject; - Cppyy::TCppScope_t oisa = pyobj->ObjectIsA(); - -// for the case where we have a 'hidden' smart pointer: - if (Cppyy::TCppScope_t tsmart = pyobj->GetSmartIsA()) { - if (Cppyy::IsSubclass(tsmart, fSmartPtrType)) { - // depending on memory policy, some objects need releasing when passed into functions - if (!fKeepControl && !UseStrictOwnership(ctxt)) - ((CPPInstance*)pyobject)->CppOwns(); - - // calculate offset between formal and actual arguments - para.fValue.fVoidp = pyobj->GetSmartObject(); - if (tsmart != fSmartPtrType) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - tsmart, fSmartPtrType, para.fValue.fVoidp, 1 /* up-cast */); - } - - // set pointer (may be null) and declare success - para.fTypeCode = typeCode; - return true; - } - } - -// for the case where we have an 'exposed' smart pointer: - if (!pyobj->IsSmart() && Cppyy::IsSubclass(oisa, fSmartPtrType)) { - // calculate offset between formal and actual arguments - para.fValue.fVoidp = pyobj->GetObject(); - if (oisa != fSmartPtrType) { - para.fValue.fIntPtr += Cppyy::GetBaseOffset( - oisa, fSmartPtrType, para.fValue.fVoidp, 1 /* up-cast */); - } - - // set pointer (may be null) and declare success - para.fTypeCode = typeCode; - return true; - } - -// for the case where we have an ordinary object to convert - if (!pyobj->IsSmart() && Cppyy::IsSubclass(oisa, fUnderlyingType)) { - // create the relevant smart pointer and make the pyobject "smart" - CPPInstance* pysmart = (CPPInstance*)ConvertImplicit(fSmartPtrType, pyobject, para, ctxt, false); - if (!CPPInstance_Check(pysmart)) { - Py_XDECREF(pysmart); - return false; - } - - // copy internals from the fresh smart object to the original, making it smart - pyobj->GetObjectRaw() = pysmart->GetSmartObject(); - pyobj->SetSmart(CreateScopeProxy(fSmartPtrType)); //(PyObject*)Py_TYPE(pysmart)); - pyobj->PythonOwns(); - pysmart->CppOwns(); - Py_DECREF(pysmart); - - return true; - } - -// final option, try mapping pointer types held (TODO: do not allow for non-const ref) -// Match on the declared underlying type, not the (possibly downcast) -// dereferenced object: a unique_ptr holding a Derived is still a -// unique_ptr and must not pass where a unique_ptr is expected. - if (pyobj->IsSmart() && Cppyy::IsSubclass(pyobj->GetSmartUnderlyingType(), fUnderlyingType)) { - para.fValue.fVoidp = ((CPPInstance*)pyobject)->GetSmartObject(); - para.fTypeCode = 'V'; - return true; - } - - return false; -} - -PyObject* CPyCppyy::SmartPtrConverter::FromMemory(void* address) -{ - if (!address || !fSmartPtrType) - return nullptr; - - return BindCppObjectNoCast(address, fSmartPtrType); -} - -bool CPyCppyy::SmartPtrConverter::ToMemory(PyObject* value, void* address, PyObject*) -{ -// assign value to C++ instance living at
through assignment operator (this -// is similar to InstanceConverter::ToMemory, but prevents wrapping the smart ptr) - PyObject* pyobj = BindCppObjectNoCast(address, fSmartPtrType, CPPInstance::kNoWrapConv); - PyObject* result = PyObject_CallMethodOneArg(pyobj, PyStrings::gAssign, value); - Py_DECREF(pyobj); - - if (result) { - Py_DECREF(result); - return true; - } - return false; -} - - -//---------------------------------------------------------------------------- -namespace { - -// clang libcpp and gcc use the same structure (ptr, size) -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) -struct faux_initlist -{ - typedef size_t size_type; - typedef void* iterator; - iterator _M_array; - size_type _M_len; -}; -#elif defined (_MSC_VER) -struct faux_initlist -{ - typedef char* iterator; - iterator _M_array; // ie. _First; - iterator _Last; -}; -#else -#define NO_KNOWN_INITIALIZER_LIST 1 -#endif - -} // unnamed namespace - -CPyCppyy::InitializerListConverter::InitializerListConverter(Cppyy::TCppScope_t klass, std::string const &value_type) - - : InstanceConverter{klass}, - fValueTypeName{value_type}, - fValueType{Cppyy::GetScope(value_type)}, - fValueSize{Cppyy::SizeOfType(Cppyy::GetType(value_type, true))} -{ -} - -CPyCppyy::InitializerListConverter::~InitializerListConverter() -{ - for (Converter *converter : fConverters) { - if (converter && converter->HasState()) delete converter; - } - if (fBuffer) Clear(); -} - -void CPyCppyy::InitializerListConverter::Clear() { - if (fValueType) { - faux_initlist* fake = (faux_initlist*)fBuffer; -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) - for (faux_initlist::size_type i = 0; i < fake->_M_len; ++i) { -#elif defined (_MSC_VER) - for (size_t i = 0; (fake->_M_array+i*fValueSize) != fake->_Last; ++i) { -#endif - void* memloc = (char*)fake->_M_array + i*fValueSize; - Cppyy::CallDestructor(fValueType, (Cppyy::TCppObject_t)memloc); - } - } - - free(fBuffer); - fBuffer = nullptr; -} - -bool CPyCppyy::InitializerListConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* ctxt) -{ -#ifdef NO_KNOWN_INITIALIZER_LIST - return false; -#else - if (fBuffer) Clear(); - -// convert the given argument to an initializer list temporary; this is purely meant -// to be a syntactic thing, so only _python_ sequences are allowed; bound C++ proxies -// (likely explicitly created std::initializer_list, go through an instance converter - if (!PySequence_Check(pyobject) || CPyCppyy_PyText_Check(pyobject) || PyBytes_Check(pyobject) - ) - return false; - - if (CPPInstance_Check(pyobject)) - return this->InstanceConverter::SetArg(pyobject, para, ctxt); - - void* buf = nullptr; - Py_ssize_t buflen = Utility::GetBuffer(pyobject, '*', (int)fValueSize, buf, true); - faux_initlist* fake = nullptr; - size_t entries = 0; - if (buf && buflen) { - // dealing with an array here, pass on whole-sale - fake = (faux_initlist*)malloc(sizeof(faux_initlist)); - fBuffer = (void*)fake; - fake->_M_array = (faux_initlist::iterator)buf; -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) - fake->_M_len = (faux_initlist::size_type)buflen; -#elif defined (_MSC_VER) - fake->_Last = fake->_M_array+buflen*fValueSize; -#endif - } else if (fValueSize) { - // Remove any errors set by GetBuffer(); note that if the argument was an array - // that failed to extract because of a type mismatch, the following will perform - // a (rather inefficient) copy. No warning is issued b/c e.g. numpy doesn't do - // so either. - PyErr_Clear(); - - // Can only construct empty lists, so use a fake initializer list. For that we - // need to construct default objects. Fail early if that cannot work. - if (fValueType && !Cppyy::IsDefaultConstructable(fValueType)) { - PyErr_SetString(PyExc_TypeError, "default constructor needed for initializer list of objects"); - return false; - } - - size_t len = (size_t)PySequence_Size(pyobject); - fake = (faux_initlist*)malloc(sizeof(faux_initlist)+fValueSize*len); - fBuffer = (void*)fake; - fake->_M_array = (faux_initlist::iterator)((char*)fake+sizeof(faux_initlist)); -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) - fake->_M_len = (faux_initlist::size_type)len; - for (faux_initlist::size_type i = 0; i < fake->_M_len; ++i) { -#elif defined (_MSC_VER) - fake->_Last = fake->_M_array+len*fValueSize; - for (size_t i = 0; (fake->_M_array+i*fValueSize) != fake->_Last; ++i) { -#endif - PyObject* item = PySequence_GetItem(pyobject, i); - bool convert_ok = false; - if (item) { - Converter *converter = CreateConverter(fValueTypeName); - if (!converter) { - if (CPPInstance_Check(item)) { - // by convention, use byte copy - memcpy((char*)fake->_M_array + i*fValueSize, - ((CPPInstance*)item)->GetObject(), fValueSize); - convert_ok = true; - } - } else { - void* memloc = (char*)fake->_M_array + i*fValueSize; - if (fValueType) { - // we need to construct a default object for the constructor to assign into; this is - // clunky, but the use of a copy constructor isn't much better as the Python object - // need not be a C++ object - memloc = (void*)Cppyy::Construct(fValueType, memloc).data; - // We checked above that we are able to construct default objects of fValueType. - assert(memloc && ("failed to default construct object for type " + fValueTypeName).c_str()); - entries += 1; - } - if (memloc) { - convert_ok = converter->ToMemory(item, memloc); - } - fConverters.emplace_back(converter); - } - - - Py_DECREF(item); - } else - PyErr_Format(PyExc_TypeError, "failed to get item %d from sequence", (int)i); - - if (!convert_ok) { -#if defined (_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) - fake->_M_len = (faux_initlist::size_type)entries; -#elif defined (_MSC_VER) - fake->_Last = fake->_M_array+entries*fValueSize; -#endif - Clear(); - return false; - } - } - } - - if (!fake) // no buffer and value size indeterminate - return false; - - para.fValue.fVoidp = (void*)fake; - para.fTypeCode = 'V'; // means ptr that backend has to free after call - return true; -#endif -} - -namespace CPyCppyy { - -// raising converter to take out overloads -class NotImplementedConverter : public Converter { -public: - NotImplementedConverter(PyObject *errorType, std::string const &message) : fErrorType{errorType}, fMessage{message} {} - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -private: - PyObject *fErrorType; - std::string fMessage; -}; - -} // namespace CPyCppyy - -//---------------------------------------------------------------------------- -bool CPyCppyy::NotImplementedConverter::SetArg(PyObject*, Parameter&, CallContext*) -{ -// raise a NotImplemented exception to take a method out of overload resolution - PyErr_SetString(fErrorType, fMessage.c_str()); - return false; -} - - -//- helper to refactor some code from CreateConverter ------------------------ -static inline CPyCppyy::Converter* selectInstanceCnv(Cppyy::TCppScope_t klass, - const std::string& cpd, CPyCppyy::cdims_t dims, bool isConst, bool control) -{ - using namespace CPyCppyy; - Converter* result = nullptr; - - if (cpd == "**" || cpd == "*[]" || cpd == "&*") - result = new InstancePtrPtrConverter(klass, control); - else if (cpd == "*&") - result = new InstancePtrPtrConverter(klass, control); - else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { - if (isConst) result = new InstancePtrConverter(klass, control); - else result = new InstancePtrConverter(klass, control); - } - else if (cpd == "&") - result = new InstanceRefConverter(klass, isConst); - else if (cpd == "&&") - result = new InstanceMoveConverter(klass); - else if (cpd == "[]" || dims) - result = new InstanceArrayConverter(klass, dims, false); - else if (cpd == "") // by value - result = new InstanceConverter(klass, true); - - return result; -} - -//- factories ---------------------------------------------------------------- -CPYCPPYY_EXPORT -CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdims_t dims) -{ -// The matching of the fulltype to a converter factory goes through up to five levels: -// 1) full, exact match -// 2) match of decorated, unqualified type -// 3) accept const ref as by value -// 4) accept ref as pointer -// 5) generalized cases (covers basically all C++ classes) -// -// If all fails, void is used, which will generate a run-time warning when used. - -// an exactly matching converter is best - ConvFactories_t::iterator h = gConvFactories.find(fullType); - if (h != gConvFactories.end()) { - return (h->second)(dims); - } - -// resolve typedefs etc. - const std::string& resolvedType = Cppyy::ResolveName(fullType); - -// a full, qualified matching converter is preferred - if (resolvedType != fullType) { - h = gConvFactories.find(resolvedType); - if (h != gConvFactories.end()) - return (h->second)(dims); - } - -//-- nothing? ok, collect information about the type and possible qualifiers/decorators - bool isConst = strncmp(resolvedType.c_str(), "const", 5) == 0; - const std::string& cpd = TypeManip::compound(resolvedType); - std::string realType = TypeManip::clean_type(resolvedType, false, true); - -// accept unqualified type (as python does not know about qualifiers) - h = gConvFactories.find((isConst ? "const " : "") + realType + cpd); - if (h != gConvFactories.end()) - return (h->second)(dims); - -// mutable pointer references (T*&) are incompatible with Python's object model - if (!isConst && cpd == "*&") { - return new NotImplementedConverter{PyExc_TypeError, - "argument type '" + resolvedType + "' is not supported: non-const references to pointers (T*&) allow a" - " function to replace the pointer itself. Python cannot represent this safely. Consider changing the" - " C++ API to return the new pointer or use a wrapper"}; - } - -// drop const, as that is mostly meaningless to python (with the exception -// of c-strings, but those are specialized in the converter map) - if (isConst) { - h = gConvFactories.find(realType + cpd); - if (h != gConvFactories.end()) - return (h->second)(dims); - } - -//-- still nothing? try pointer instead of array (for builtins) - if (cpd.compare(0, 3, "*[]") == 0) { - // special case, array of pointers - h = gConvFactories.find(realType + " ptr"); - if (h != gConvFactories.end()) { - // upstream treats the pointer type as the array element type, but that pointer is - // treated as a low-level view as well, unless it's a void*/char* so adjust the dims - if (realType != "void" && realType != "char") { - dim_t newdim = dims.ndim() == UNKNOWN_SIZE ? 2 : dims.ndim()+1; - dims_t newdims = dims_t(newdim); - // TODO: sometimes the array size is known and can thus be verified; however, - // currently the meta layer does not provide this information - newdims[0] = dims ? dims[0] : UNKNOWN_SIZE; // the array - newdims[1] = UNKNOWN_SIZE; // the pointer - if (2 < newdim) { - for (int i = 2; i < (newdim-1); ++i) - newdims[i] = dims[i-1]; - } - - return (h->second)(newdims); - } - return (h->second)(dims); - } - - } else if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '*') == cpd.size()) { - // simple array; set or resize as necessary - h = gConvFactories.find(realType + " ptr"); - if (h != gConvFactories.end()) - return (h->second)((!dims && 1 < cpd.size()) ? dims_t(cpd.size()) : dims); - - } else if (2 <= cpd.size() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '[') == cpd.size() / 2) { - // fixed array, dims will have size if available - h = gConvFactories.find(realType + " ptr"); - if (h != gConvFactories.end()) - return (h->second)(dims); - } - -//-- special case: initializer list - if (realType.compare(0, 21, "std::initializer_list") == 0) { - // get the type of the list and create a converter (TODO: get hold of value_type?) - auto pos = realType.find('<'); - std::string value_type = realType.substr(pos+1, realType.size()-pos-2); - return new InitializerListConverter(Cppyy::GetScope(realType), value_type); - } - -//-- still nothing? use a generalized converter - bool control = cpd == "&" || isConst; - -//-- special case: std::function - auto pos = resolvedType.find("std::function<"); - if (pos == 0 /* std:: */ || pos == 6 /* const std:: */ ) { - - // get actual converter for normal passing - Converter* cnv = selectInstanceCnv( - Cppyy::GetScope(realType), cpd, dims, isConst, control); - - if (cnv) { - // get the type of the underlying (TODO: use target_type?) - auto pos1 = resolvedType.find("(", pos+14); - auto pos2 = resolvedType.rfind(")"); - if (pos1 != std::string::npos && pos2 != std::string::npos) { - auto sz1 = pos1-pos-14; - if (resolvedType[pos+14+sz1-1] == ' ') sz1 -= 1; - - return new StdFunctionConverter(cnv, - resolvedType.substr(pos+14, sz1), resolvedType.substr(pos1, pos2-pos1+1)); - } else if (cnv->HasState()) - delete cnv; - } - } - -#if __cplusplus >= 202002L -//-- special case: std::span - pos = resolvedType.find("span<"); - if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || - pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) { - - // std::span::value_type == std::remove_cv_t: look it up - Cppyy::TCppScope_t span_scope = Cppyy::GetScope(realType); - Cppyy::TCppType_t vtype = Cppyy::ResolveType( - Cppyy::GetTypeFromScope(Cppyy::GetNamed("value_type", span_scope))); - if (span_scope && vtype) { - std::string value_type = Cppyy::GetTypeAsString(vtype); - return new StdSpanConverter{value_type, span_scope}; - } - } -#endif - -// converters for known C++ classes and default (void*) - Converter* result = nullptr; - if (Cppyy::TCppScope_t klass = Cppyy::GetFullScope(realType)) { - Cppyy::TCppScope_t raw; - if (Cppyy::GetSmartPtrInfo(realType, &raw, nullptr)) { - if (cpd == "") { - result = new SmartPtrConverter(klass, raw, control); - } else if (cpd == "&") { - result = new SmartPtrConverter(klass, raw); - } else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { - result = new SmartPtrConverter(klass, raw, control, true); - } - } - - if (!result) { - // Cling WORKAROUND -- special case for STL iterators - if (Utility::IsSTLIterator(realType)) { - static STLIteratorConverter c; - result = &c; - } else - // -- Cling WORKAROUND - result = selectInstanceCnv(klass, cpd, dims, isConst, control); - } - } else { - std::smatch sm; - if (std::regex_search(resolvedType, sm, s_fnptr)) { - // this is a function pointer - auto pos1 = sm.position(0); - auto pos2 = resolvedType.rfind(')'); - result = new FunctionPointerConverter( - resolvedType.substr(0, pos1), resolvedType.substr(pos1+sm.length(), pos2-1)); - } - } - const std::string failure_msg("Failed to convert type: " + fullType + "; resolved: " + resolvedType + "; real: " + realType + "; cpd: " + cpd); - - if (!result && cpd == "&&") { - // for builtin, can use const-ref for r-ref - h = gConvFactories.find("const " + realType + "&"); - if (h != gConvFactories.end()) - return (h->second)(dims); - // else, unhandled moves - result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; - } - - if (!result && h != gConvFactories.end()) - // converter factory available, use it to create converter - result = (h->second)(dims); - else if (!result) { - // default to something reasonable, assuming "user knows best" - if (cpd.size() == 2 && cpd != "&&") // "**", "*[]", "*&" - result = new VoidPtrPtrConverter(dims.ndim(), failure_msg); - else if (!cpd.empty()) - result = new VoidArrayConverter(/* keepControl= */ true, failure_msg); // "user knows best" - else - // fails on use - result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; - } - - return result; -} - -CPYCPPYY_EXPORT -CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t dims) -{ -// The matching of the fulltype to a converter factory goes through up to five levels: -// 1) full, exact match -// 2) match of decorated, unqualified type -// 3) accept const ref as by value -// 4) accept ref as pointer -// 5) generalized cases (covers basically all C++ classes) -// -// If all fails, void is used, which will generate a run-time warning when used. - -// an exactly matching converter is best - std::string fullType = Cppyy::GetTypeAsString(type); - ConvFactories_t::iterator h = gConvFactories.find(fullType); - if (h != gConvFactories.end()) { - return (h->second)(dims); - } - -// resolve typedefs etc. - Cppyy::TCppType_t resolvedType = Cppyy::ResolveType(type); - const std::string& resolvedTypeStr = Cppyy::GetTypeAsString(resolvedType); - -// a full, qualified matching converter is preferred - if (resolvedTypeStr != fullType) { - h = gConvFactories.find(resolvedTypeStr); - if (h != gConvFactories.end()) { - return (h->second)(dims); - } - } - -//-- nothing? ok, collect information about the type and possible qualifiers/decorators - bool isConst = strncmp(resolvedTypeStr.c_str(), "const", 5) == 0; - const std::string& cpd = TypeManip::compound(resolvedTypeStr); - Cppyy::TCppType_t realType = Cppyy::ResolveType(Cppyy::GetRealType(type)); - std::string realTypeStr = Cppyy::GetTypeAsString(realType); - std::string realUnresolvedTypeStr = TypeManip::clean_type(fullType, false, true); - -// accept unqualified type (as python does not know about qualifiers) - h = gConvFactories.find((isConst ? "const " : "") + realTypeStr + cpd); - if (h != gConvFactories.end()) - return (h->second)(dims); - -// mutable pointer references (T*&) are incompatible with Python's object model - if (!isConst && cpd == "*&") { - return new NotImplementedConverter{PyExc_TypeError, - "argument type '" + resolvedTypeStr + "' is not supported: non-const references to pointers (T*&) allow a" - " function to replace the pointer itself. Python cannot represent this safely. Consider changing the" - " C++ API to return the new pointer or use a wrapper"}; - } - -// drop const, as that is mostly meaningless to python (with the exception -// of c-strings, but those are specialized in the converter map) - if (isConst) { - h = gConvFactories.find(realTypeStr + cpd); - if (h != gConvFactories.end()) { - return (h->second)(dims); - } - } - -//-- still nothing? try pointer instead of array (for builtins) - if (cpd.compare(0, 3, "*[]") == 0) { - // special case, array of pointers - h = gConvFactories.find(realTypeStr + " ptr"); - if (h != gConvFactories.end()) { - // upstream treats the pointer type as the array element type, but that pointer is - // treated as a low-level view as well, unless it's a void*/char* so adjust the dims - if (realTypeStr != "void" && realTypeStr != "char") { - dim_t newdim = dims.ndim() == UNKNOWN_SIZE ? 2 : dims.ndim()+1; - dims_t newdims = dims_t(newdim); - // TODO: sometimes the array size is known and can thus be verified; however, - // currently the meta layer does not provide this information - newdims[0] = dims ? dims[0] : UNKNOWN_SIZE; // the array - newdims[1] = UNKNOWN_SIZE; // the pointer - if (2 < newdim) { - for (int i = 2; i < (newdim-1); ++i) - newdims[i] = dims[i-1]; - } - - return (h->second)(newdims); - } - return (h->second)(dims); - } - - } else if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '*') == cpd.size()) { - // simple array; set or resize as necessary - h = gConvFactories.find(realTypeStr + " ptr"); - if (h != gConvFactories.end()) - return (h->second)((!dims && 1 < cpd.size()) ? dims_t(cpd.size()) : dims); - - } else if (2 <= cpd.size() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '[') == cpd.size() / 2) { - // fixed array, dims will have size if available - h = gConvFactories.find(realTypeStr + " ptr"); - if (h != gConvFactories.end()) - return (h->second)(dims); - } - -//-- special case: initializer list - if (realTypeStr.compare(0, 21, "std::initializer_list") == 0) { - // get the type of the list and create a converter (TODO: get hold of value_type?) - auto pos = realTypeStr.find('<'); - std::string value_type = realTypeStr.substr(pos+1, realTypeStr.size()-pos-2); - Converter* cnv = nullptr; bool use_byte_cnv = false; - if (cpd == "" && Cppyy::GetScope(value_type)) { - // initializer list of object values does not work as the target is raw - // memory; simply use byte copies - - // by convention, leave cnv as nullptr - use_byte_cnv = true; - } else - cnv = CreateConverter(value_type); - if (cnv || use_byte_cnv) - return new InitializerListConverter(Cppyy::GetScopeFromType(realType), value_type); - } - -//-- still nothing? use a generalized converter - bool control = cpd == "&" || isConst; - -//-- special case: std::function - auto pos = resolvedTypeStr.find("std::function<"); - if (pos == 0 /* std:: */ || pos == 6 /* const std:: */ ) { - - // get actual converter for normal passing - Converter* cnv = selectInstanceCnv( - Cppyy::GetScopeFromType(realType), cpd, dims, isConst, control); - - if (cnv) { - // get the type of the underlying (TODO: use target_type?) - auto pos1 = resolvedTypeStr.find("(", pos+14); - auto pos2 = resolvedTypeStr.rfind(")"); - if (pos1 != std::string::npos && pos2 != std::string::npos) { - auto sz1 = pos1-pos-14; - if (resolvedTypeStr[pos+14+sz1-1] == ' ') sz1 -= 1; - - const std::string &argsStr = resolvedTypeStr.substr(pos1, pos2-pos1+1).c_str(); - return new StdFunctionConverter(cnv, - resolvedTypeStr.substr(pos+14, sz1), argsStr == "(void)"? "()" : argsStr); - } else if (cnv->HasState()) - delete cnv; - } - } - -#if __cplusplus >= 202002L -//-- special case: std::span - pos = resolvedTypeStr.find("span<"); - if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || - pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) { - - // std::span::value_type == std::remove_cv_t: look it up - Cppyy::TCppScope_t span_scope = Cppyy::GetScopeFromType(realType); - Cppyy::TCppType_t vtype = Cppyy::ResolveType( - Cppyy::GetTypeFromScope(Cppyy::GetNamed("value_type", span_scope))); - if (span_scope && vtype) { - std::string value_type = Cppyy::GetTypeAsString(vtype); - return new StdSpanConverter{value_type, span_scope}; - } - } -#endif - -// converters for known C++ classes and default (void*) - Converter* result = nullptr; - Cppyy::TCppScope_t klass = Cppyy::GetScopeFromType(realType); - if (resolvedTypeStr.find("(*)") != std::string::npos || - (resolvedTypeStr.find("::*)") != std::string::npos)) { - // this is a function function pointer - // TODO: find better way of finding the type - auto pos1 = resolvedTypeStr.find('('); - auto pos2 = resolvedTypeStr.find("*)"); - auto pos3 = resolvedTypeStr.rfind(')'); - std::string return_type = resolvedTypeStr.substr(0, pos1); - result = new FunctionPointerConverter( - return_type.erase(return_type.find_last_not_of(" ") + 1), resolvedTypeStr.substr(pos2+2, pos3-pos2-1)); - } else if ((realTypeStr != "std::byte") && (klass || (klass = Cppyy::GetFullScope(realTypeStr)))) { - // std::byte is a special enum class used to access raw memory - Cppyy::TCppScope_t raw; - if (Cppyy::GetSmartPtrInfo(realTypeStr, &raw, nullptr)) { - if (cpd == "") { - result = new SmartPtrConverter(klass, raw, control); - } else if (cpd == "&") { - result = new SmartPtrConverter(klass, raw); - } else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { - result = new SmartPtrConverter(klass, raw, control, true); - } - } - - if (!result) { - // Cling WORKAROUND -- special case for STL iterators - if (realTypeStr.rfind("__gnu_cxx::__normal_iterator", 0) /* vector */ == 0 -#ifdef __APPLE__ - || realTypeStr.rfind("__wrap_iter", 0) == 0 -#endif - // TODO: Windows? - ) { - static STLIteratorConverter c; - result = &c; - } else if(realTypeStr != "int8_t" and realTypeStr != "uint8_t") { - // -- Cling WORKAROUND - result = selectInstanceCnv(klass, cpd, dims, isConst, control); - } - } - } - const std::string failure_msg("Failed to convert type: " + fullType + "; resolved: " + resolvedTypeStr + "; real: " + realTypeStr + "; realUnresolvedType: " + realUnresolvedTypeStr + "; cpd: " + cpd); - - if (!result && cpd == "&&") { - // for builtin, can use const-ref for r-ref - h = gConvFactories.find("const " + realTypeStr + "&"); - if (h != gConvFactories.end()) - return (h->second)(dims); - h = gConvFactories.find("const " + realUnresolvedTypeStr + "&"); - if (h != gConvFactories.end()) - return (h->second)(dims); - // else, unhandled moves - result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; - } - - if (!result && h != gConvFactories.end()) { - // converter factory available, use it to create converter - result = (h->second)(dims); - } else if (!result) { - // default to something reasonable, assuming "user knows best" - if (cpd.size() == 2 && cpd != "&&") {// "**", "*[]", "*&" - result = new VoidPtrPtrConverter(dims.ndim(), failure_msg); - } else if (!cpd.empty()) { - result = new VoidArrayConverter(/* keepControl= */ true, failure_msg); // "user knows best" - } else { - // fails on use - result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; - } - } - - return result; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -void CPyCppyy::DestroyConverter(Converter* p) -{ - if (p && p->HasState()) - delete p; // state-less converters are always shared -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::RegisterConverter(const std::string& name, cf_t fac) -{ -// register a custom converter - auto f = gConvFactories.find(name); - if (f != gConvFactories.end()) - return false; - - gConvFactories[name] = fac; - return true; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::RegisterConverterAlias(const std::string& name, const std::string& target) -{ -// register a custom converter that is a reference to an existing converter - auto f = gConvFactories.find(name); - if (f != gConvFactories.end()) - return false; - - auto t = gConvFactories.find(target); - if (t == gConvFactories.end()) - return false; - - gConvFactories[name] = t->second; - return true; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::UnregisterConverter(const std::string& name) -{ -// remove a custom converter - auto f = gConvFactories.find(name); - if (f != gConvFactories.end()) { - gConvFactories.erase(f); - return true; - } - return false; -} - - -//---------------------------------------------------------------------------- -namespace { - -using namespace CPyCppyy; - -inline static -std::string::size_type dims2stringsz(cdims_t d) { - return (d && d.ndim() != UNKNOWN_SIZE) ? d[0] : std::string::npos; -} - -#define STRINGVIEW "std::basic_string_view" -#define WSTRING1 "std::basic_string" -#define WSTRING2 "std::basic_string,std::allocator>" - -//-- aliasing special case: C complex (is binary compatible with C++ std::complex) -#ifndef _WIN32 -#define CCOMPLEX_D "_Complex double" -#define CCOMPLEX_F "_Complex float" -#else -#define CCOMPLEX_D "_C_double_complex" -#define CCOMPLEX_F "_C_float_complex" -#endif - -static struct InitConvFactories_t { -public: - InitConvFactories_t() { - // load all converter factories in the global map 'gConvFactories' - CPyCppyy::ConvFactories_t& gf = gConvFactories; - - // factories for built-ins - gf["bool"] = (cf_t)+[](cdims_t) { static BoolConverter c{}; return &c; }; - gf["const bool&"] = (cf_t)+[](cdims_t) { static ConstBoolRefConverter c{}; return &c; }; - gf["bool&"] = (cf_t)+[](cdims_t) { static BoolRefConverter c{}; return &c; }; - gf["char"] = (cf_t)+[](cdims_t) { static CharConverter c{}; return &c; }; - gf["const char&"] = (cf_t)+[](cdims_t) { static ConstCharRefConverter c{}; return &c; }; - gf["char&"] = (cf_t)+[](cdims_t) { static CharRefConverter c{}; return &c; }; - gf["signed char&"] = (cf_t)+[](cdims_t) { static SCharRefConverter c{}; return &c; }; - gf["unsigned char"] = (cf_t)+[](cdims_t) { static UCharConverter c{}; return &c; }; - gf["const unsigned char&"] = (cf_t)+[](cdims_t) { static ConstUCharRefConverter c{}; return &c; }; - gf["unsigned char&"] = (cf_t)+[](cdims_t) { static UCharRefConverter c{}; return &c; }; - gf["SCharAsInt"] = (cf_t)+[](cdims_t) { static SCharAsIntConverter c{}; return &c; }; - gf["UCharAsInt"] = (cf_t)+[](cdims_t) { static UCharAsIntConverter c{}; return &c; }; - gf["wchar_t"] = (cf_t)+[](cdims_t) { static WCharConverter c{}; return &c; }; - gf["char16_t"] = (cf_t)+[](cdims_t) { static Char16Converter c{}; return &c; }; - gf["char32_t"] = (cf_t)+[](cdims_t) { static Char32Converter c{}; return &c; }; - gf["wchar_t&"] = (cf_t)+[](cdims_t) { static WCharRefConverter c{}; return &c; }; - gf["char16_t&"] = (cf_t)+[](cdims_t) { static Char16RefConverter c{}; return &c; }; - gf["char32_t&"] = (cf_t)+[](cdims_t) { static Char32RefConverter c{}; return &c; }; - gf["int8_t"] = (cf_t)+[](cdims_t) { static Int8Converter c{}; return &c; }; - gf["const int8_t&"] = (cf_t)+[](cdims_t) { static ConstInt8RefConverter c{}; return &c; }; - gf["int8_t&"] = (cf_t)+[](cdims_t) { static Int8RefConverter c{}; return &c; }; - gf["uint8_t"] = (cf_t)+[](cdims_t) { static UInt8Converter c{}; return &c; }; - gf["const uint8_t&"] = (cf_t)+[](cdims_t) { static ConstUInt8RefConverter c{}; return &c; }; - gf["uint8_t&"] = (cf_t)+[](cdims_t) { static UInt8RefConverter c{}; return &c; }; - gf["short"] = (cf_t)+[](cdims_t) { static ShortConverter c{}; return &c; }; - gf["const short&"] = (cf_t)+[](cdims_t) { static ConstShortRefConverter c{}; return &c; }; - gf["short&"] = (cf_t)+[](cdims_t) { static ShortRefConverter c{}; return &c; }; - gf["unsigned short"] = (cf_t)+[](cdims_t) { static UShortConverter c{}; return &c; }; - gf["const unsigned short&"] = (cf_t)+[](cdims_t) { static ConstUShortRefConverter c{}; return &c; }; - gf["unsigned short&"] = (cf_t)+[](cdims_t) { static UShortRefConverter c{}; return &c; }; - gf["int"] = (cf_t)+[](cdims_t) { static IntConverter c{}; return &c; }; - gf["int&"] = (cf_t)+[](cdims_t) { static IntRefConverter c{}; return &c; }; - gf["const int&"] = (cf_t)+[](cdims_t) { static ConstIntRefConverter c{}; return &c; }; - gf["unsigned int"] = (cf_t)+[](cdims_t) { static UIntConverter c{}; return &c; }; - gf["const unsigned int&"] = (cf_t)+[](cdims_t) { static ConstUIntRefConverter c{}; return &c; }; - gf["unsigned int&"] = (cf_t)+[](cdims_t) { static UIntRefConverter c{}; return &c; }; - gf["long"] = (cf_t)+[](cdims_t) { static LongConverter c{}; return &c; }; - gf["long&"] = (cf_t)+[](cdims_t) { static LongRefConverter c{}; return &c; }; - gf["const long&"] = (cf_t)+[](cdims_t) { static ConstLongRefConverter c{}; return &c; }; - gf["unsigned long"] = (cf_t)+[](cdims_t) { static ULongConverter c{}; return &c; }; - gf["const unsigned long&"] = (cf_t)+[](cdims_t) { static ConstULongRefConverter c{}; return &c; }; - gf["unsigned long&"] = (cf_t)+[](cdims_t) { static ULongRefConverter c{}; return &c; }; - gf["long long"] = (cf_t)+[](cdims_t) { static LLongConverter c{}; return &c; }; - gf["const long long&"] = (cf_t)+[](cdims_t) { static ConstLLongRefConverter c{}; return &c; }; - gf["long long&"] = (cf_t)+[](cdims_t) { static LLongRefConverter c{}; return &c; }; - gf["unsigned long long"] = (cf_t)+[](cdims_t) { static ULLongConverter c{}; return &c; }; - gf["const unsigned long long&"] = (cf_t)+[](cdims_t) { static ConstULLongRefConverter c{}; return &c; }; - gf["unsigned long long&"] = (cf_t)+[](cdims_t) { static ULLongRefConverter c{}; return &c; }; - - gf["float"] = (cf_t)+[](cdims_t) { static FloatConverter c{}; return &c; }; - gf["const float&"] = (cf_t)+[](cdims_t) { static ConstFloatRefConverter c{}; return &c; }; - gf["float&"] = (cf_t)+[](cdims_t) { static FloatRefConverter c{}; return &c; }; - gf["double"] = (cf_t)+[](cdims_t) { static DoubleConverter c{}; return &c; }; - gf["double&"] = (cf_t)+[](cdims_t) { static DoubleRefConverter c{}; return &c; }; - gf["const double&"] = (cf_t)+[](cdims_t) { static ConstDoubleRefConverter c{}; return &c; }; - gf["long double"] = (cf_t)+[](cdims_t) { static LDoubleConverter c{}; return &c; }; - gf["const long double&"] = (cf_t)+[](cdims_t) { static ConstLDoubleRefConverter c{}; return &c; }; - gf["long double&"] = (cf_t)+[](cdims_t) { static LDoubleRefConverter c{}; return &c; }; - gf["std::complex"] = (cf_t)+[](cdims_t) { return new ComplexDConverter{}; }; - gf["const std::complex&"] = (cf_t)+[](cdims_t) { return new ComplexDConverter{}; }; - gf["void"] = (cf_t)+[](cdims_t) { static VoidConverter c{}; return &c; }; - - // pointer/array factories - gf["bool ptr"] = (cf_t)+[](cdims_t d) { return new BoolArrayConverter{d}; }; - gf["signed char ptr"] = (cf_t)+[](cdims_t d) { return new SCharArrayConverter{d}; }; - gf["signed char**"] = (cf_t)+[](cdims_t) { return new SCharArrayConverter{{UNKNOWN_SIZE, UNKNOWN_SIZE}}; }; - gf["const unsigned char*"] = (cf_t)+[](cdims_t d) { return new UCharArrayConverter{d}; }; - gf["unsigned char ptr"] = (cf_t)+[](cdims_t d) { return new UCharArrayConverter{d}; }; - gf["SCharAsInt*"] = gf["signed char ptr"]; - gf["SCharAsInt[]"] = gf["signed char ptr"]; - gf["UCharAsInt*"] = gf["unsigned char ptr"]; - gf["UCharAsInt[]"] = gf["unsigned char ptr"]; - gf["std::byte ptr"] = (cf_t)+[](cdims_t d) { return new ByteArrayConverter{d}; }; - gf["int8_t ptr"] = (cf_t)+[](cdims_t d) { return new Int8ArrayConverter{d}; }; - gf["uint8_t ptr"] = (cf_t)+[](cdims_t d) { return new UInt8ArrayConverter{d}; }; - gf["short ptr"] = (cf_t)+[](cdims_t d) { return new ShortArrayConverter{d}; }; - gf["unsigned short ptr"] = (cf_t)+[](cdims_t d) { return new UShortArrayConverter{d}; }; - gf["int ptr"] = (cf_t)+[](cdims_t d) { return new IntArrayConverter{d}; }; - gf["unsigned int ptr"] = (cf_t)+[](cdims_t d) { return new UIntArrayConverter{d}; }; - gf["long ptr"] = (cf_t)+[](cdims_t d) { return new LongArrayConverter{d}; }; - gf["unsigned long ptr"] = (cf_t)+[](cdims_t d) { return new ULongArrayConverter{d}; }; - gf["long long ptr"] = (cf_t)+[](cdims_t d) { return new LLongArrayConverter{d}; }; - gf["unsigned long long ptr"] = (cf_t)+[](cdims_t d) { return new ULLongArrayConverter{d}; }; - gf["float ptr"] = (cf_t)+[](cdims_t d) { return new FloatArrayConverter{d}; }; - gf["double ptr"] = (cf_t)+[](cdims_t d) { return new DoubleArrayConverter{d}; }; - gf["long double ptr"] = (cf_t)+[](cdims_t d) { return new LDoubleArrayConverter{d}; }; - gf["std::complex ptr"] = (cf_t)+[](cdims_t d) { return new ComplexFArrayConverter{d}; }; - gf["std::complex ptr"] = (cf_t)+[](cdims_t d) { return new ComplexDArrayConverter{d}; }; - gf["void*"] = (cf_t)+[](cdims_t d) { return new VoidArrayConverter{(bool)d}; }; - - // aliases - gf["signed char"] = gf["char"]; - gf["const signed char&"] = gf["const char&"]; - gf["std::byte"] = gf["uint8_t"]; - gf["const std::byte&"] = gf["const uint8_t&"]; - gf["std::byte&"] = gf["uint8_t&"]; - gf["std::int8_t"] = gf["int8_t"]; - gf["const std::int8_t&"] = gf["const int8_t&"]; - gf["std::int8_t&"] = gf["int8_t&"]; - gf["std::uint8_t"] = gf["uint8_t"]; - gf["const std::uint8_t&"] = gf["const uint8_t&"]; - gf["std::uint8_t&"] = gf["uint8_t&"]; - gf["internal_enum_type_t"] = gf["int"]; - gf["internal_enum_type_t&"] = gf["int&"]; - gf["const internal_enum_type_t&"] = gf["const int&"]; - gf["internal_enum_type_t ptr"] = gf["int ptr"]; -#ifdef _WIN32 - gf["__int64"] = gf["long long"]; - gf["const __int64&"] = gf["const long long&"]; - gf["__int64&"] = gf["long long&"]; - gf["__int64 ptr"] = gf["long long ptr"]; - gf["unsigned __int64"] = gf["unsigned long long"]; - gf["const unsigned __int64&"] = gf["const unsigned long long&"]; - gf["unsigned __int64&"] = gf["unsigned long long&"]; - gf["unsigned __int64 ptr"] = gf["unsigned long long ptr"]; -#endif - gf[CCOMPLEX_D] = gf["std::complex"]; - gf["const " CCOMPLEX_D "&"] = gf["const std::complex&"]; - gf[CCOMPLEX_F " ptr"] = gf["std::complex ptr"]; - gf[CCOMPLEX_D " ptr"] = gf["std::complex ptr"]; - - // We always need these converters when cppyy is based on an unpatched - // ROOT, because the "long long" types are always converted to Long64_t - // and ULong64_t already at the ROOT Meta level. - // See https://github.com/root-project/root/issues/15872#issuecomment-2174092763 - gf["Long64_t"] = gf["long long"]; - gf["Long64_t ptr"] = gf["long long ptr"]; - gf["Long64_t&"] = gf["long long&"]; - gf["const Long64_t&"] = gf["const long long&"]; - gf["ULong64_t"] = gf["unsigned long long"]; - gf["ULong64_t ptr"] = gf["unsigned long long ptr"]; - gf["ULong64_t&"] = gf["unsigned long long&"]; - gf["const ULong64_t&"] = gf["const unsigned long long&"]; - - // factories for special cases - gf["nullptr_t"] = (cf_t)+[](cdims_t) { static NullptrConverter c{}; return &c;}; - gf["const char*"] = (cf_t)+[](cdims_t) { return new CStringConverter{}; }; - gf["const signed char*"] = gf["const char*"]; - gf["const char*&&"] = gf["const char*"]; - gf["const char[]"] = (cf_t)+[](cdims_t) { return new CStringConverter{}; }; - gf["char*"] = (cf_t)+[](cdims_t d) { return new NonConstCStringConverter{dims2stringsz(d)}; }; - gf["char[]"] = (cf_t)+[](cdims_t d) { return new NonConstCStringArrayConverter{d, true}; }; - gf["signed char*"] = gf["char*"]; - gf["wchar_t*"] = (cf_t)+[](cdims_t) { return new WCStringConverter{}; }; - // TODO: The libc++ library that is used most prominently by Apple for - // macOS is using the _Nonnull Clang attribute for some of its string - // constructors (_LIBCPP_DIAGNOSE_NULLPTR is a preprocessor macro that - // resolves to _Nullable). The cppyy backend doesn't strip away such Clang - // attributes, so the wchar_t* converter fails to match. To make the - // construction of std::wstring work on macOS, we are explicitly creating - // a converter for the wchar_t*_Nonnull type here, but we should try to - // find a more general solution for dealing with Clang attributes. - // [1] https://clang.llvm.org/docs/AttributeReference.html#id656 - // [2] https://github.com/llvm/llvm-project/blob/2078da43e25a4623cab2d0d60decddf709aaea28/libcxx/include/string#L1061 - gf["wchar_t*_Nonnull"] = (cf_t)+[](cdims_t) { return new WCStringConverter{}; }; - gf["char16_t*"] = (cf_t)+[](cdims_t) { return new CString16Converter{}; }; - gf["char16_t[]"] = (cf_t)+[](cdims_t d) { return new CString16Converter{dims2stringsz(d)}; }; - gf["char32_t*"] = (cf_t)+[](cdims_t) { return new CString32Converter{}; }; - gf["char32_t[]"] = (cf_t)+[](cdims_t d) { return new CString32Converter{dims2stringsz(d)}; }; - // TODO: the following are handled incorrectly upstream (char16_t** where char16_t* intended)?! - gf["char16_t**"] = gf["char16_t*"]; - gf["char32_t**"] = gf["char32_t*"]; - gf["const char**"] = (cf_t)+[](cdims_t) { return new CStringArrayConverter{{UNKNOWN_SIZE, UNKNOWN_SIZE}, false}; }; - gf["char**"] = gf["const char**"]; - gf["const char*[]"] = (cf_t)+[](cdims_t d) { return new CStringArrayConverter{d, false}; }; - gf["char*[]"] = (cf_t)+[](cdims_t d) { return new NonConstCStringArrayConverter{d, false}; }; - gf["char ptr"] = gf["char*[]"]; - gf["std::basic_string"] = (cf_t)+[](cdims_t) { return new STLStringConverter{}; }; - gf["const std::basic_string&"] = gf["std::basic_string"]; - gf["std::basic_string&&"] = (cf_t)+[](cdims_t) { return new STLStringMoveConverter{}; }; - gf["const std::basic_string &"] = gf["std::basic_string"]; - gf["std::basic_string &&"] = (cf_t)+[](cdims_t) { return new STLStringMoveConverter{}; }; - gf["std::basic_string_view"] = (cf_t)+[](cdims_t) { return new STLStringViewConverter{}; }; - gf[STRINGVIEW] = gf["std::basic_string_view"]; - gf["std::basic_string_view&"] = gf["std::basic_string_view"]; - gf["const " STRINGVIEW "&"] = gf["std::basic_string_view"]; - gf["std::basic_string_view &"] = gf["std::basic_string_view"]; - gf["const " STRINGVIEW " &"] = gf["std::basic_string_view"]; - gf["std::wstring"] = (cf_t)+[](cdims_t) { return new STLWStringConverter{}; }; - gf[WSTRING1] = gf["std::wstring"]; - gf[WSTRING2] = gf["std::wstring"]; - gf["const std::wstring&"] = gf["std::wstring"]; - gf["const " WSTRING1 "&"] = gf["std::wstring"]; - gf["const " WSTRING2 "&"] = gf["std::wstring"]; - gf["const std::wstring &"] = gf["std::wstring"]; - gf["const " WSTRING1 " &"] = gf["std::wstring"]; - gf["const " WSTRING2 " &"] = gf["std::wstring"]; - // VoidPtrRefConverter should only be used for const references to pointers - gf["const void*&"] = (cf_t)+[](cdims_t) { static VoidPtrRefConverter c{}; return &c; }; - gf["void**"] = (cf_t)+[](cdims_t d) { return new VoidPtrPtrConverter{d}; }; - gf["void ptr"] = gf["void**"]; - gf["PyObject*"] = (cf_t)+[](cdims_t) { static PyObjectConverter c{}; return &c; }; - gf["_object*"] = gf["PyObject*"]; - gf["FILE*"] = (cf_t)+[](cdims_t) { return new VoidArrayConverter{}; }; - } -} initConvFactories_; - -} // unnamed namespace diff --git a/src/CPyCppyy/src/Converters.h b/src/CPyCppyy/src/Converters.h deleted file mode 100644 index 6560e79..0000000 --- a/src/CPyCppyy/src/Converters.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef CPYCPPYY_CONVERTERS_H -#define CPYCPPYY_CONVERTERS_H - -// Bindings -#include "Dimensions.h" - -// Standard -#include - - -namespace CPyCppyy { - -struct Parameter; -struct CallContext; - -class CPYCPPYY_CLASS_EXPORT Converter { -public: - virtual ~Converter(); - - Converter() = default; - - Converter(Converter const& other) = delete; - Converter(Converter && other) = delete; - Converter& operator=(Converter const& other) = delete; - Converter& operator=(Converter && other) = delete; - -public: - virtual bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) = 0; - virtual PyObject* FromMemory(void* address); - virtual bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr); - virtual bool HasState() { return false; } - virtual std::string GetFailureMsg() { return "[Converter]"; } -}; - -// create/destroy converter from fully qualified type (public API) -CPYCPPYY_EXPORT Converter* CreateConverter(const std::string& fullType, cdims_t dims = 0); -CPYCPPYY_EXPORT Converter* CreateConverter(Cppyy::TCppType_t type, cdims_t dims = 0); -CPYCPPYY_EXPORT void DestroyConverter(Converter* p); -typedef Converter* (*cf_t)(cdims_t d); -CPYCPPYY_EXPORT bool RegisterConverter(const std::string& name, cf_t fac); -CPYCPPYY_EXPORT bool RegisterConverterAlias(const std::string& name, const std::string& target); -CPYCPPYY_EXPORT bool UnregisterConverter(const std::string& name); - - -// converters for special cases (only here b/c of external use of StrictInstancePtrConverter) -class VoidArrayConverter : public Converter { -public: - VoidArrayConverter(bool keepControl = true, const std::string &failureMsg = std::string()) - : fFailureMsg(failureMsg) { fKeepControl = keepControl; } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[VoidArrayConverter] " + fFailureMsg; } - -protected: - virtual bool GetAddressSpecialCase(PyObject* pyobject, void*& address); - bool KeepControl() { return fKeepControl; } - const std::string fFailureMsg; - -private: - bool fKeepControl; -}; - -template -class InstancePtrConverter : public VoidArrayConverter { -public: - InstancePtrConverter(Cppyy::TCppScope_t klass, bool keepControl = false, const std::string &failureMsg = std::string()) : - VoidArrayConverter(keepControl, failureMsg), fClass(Cppyy::GetUnderlyingScope(klass)) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr) override; - -protected: - Cppyy::TCppScope_t fClass; -}; - -class StrictInstancePtrConverter : public InstancePtrConverter { -public: - using InstancePtrConverter::InstancePtrConverter; - -protected: - bool GetAddressSpecialCase(PyObject*, void*&) override { return false; } -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CONVERTERS_H diff --git a/src/CPyCppyy/src/Cppyy.h b/src/CPyCppyy/src/Cppyy.h deleted file mode 100644 index 440270c..0000000 --- a/src/CPyCppyy/src/Cppyy.h +++ /dev/null @@ -1,451 +0,0 @@ -#ifndef CPYCPPYY_CPPYY_H -#define CPYCPPYY_CPPYY_H - -// Standard -#include -#include -#include -#include -#include - -// import/export (after precommondefs.h from PyPy) -#ifdef _MSC_VER -#define CPPYY_IMPORT extern __declspec(dllimport) -#else -#define CPPYY_IMPORT extern -#endif - -// some more types; assumes Cppyy.h follows Python.h -#ifndef PY_LONG_LONG -#ifdef _WIN32 -typedef __int64 PY_LONG_LONG; -#else -typedef long long PY_LONG_LONG; -#endif -#endif - -#ifndef PY_ULONG_LONG -#ifdef _WIN32 -typedef unsigned __int64 PY_ULONG_LONG; -#else -typedef unsigned long long PY_ULONG_LONG; -#endif -#endif - -#ifndef PY_LONG_DOUBLE -typedef long double PY_LONG_DOUBLE; -#endif - -// FIXME: We should not duplicate these definitions here and in CppInterOp.h -// The current setup relies on finding an identical symbol definition in -// libcppyybackend.so which is fragile and requires updating both locations when -// changing. Ideally we should have the ability to set/get the template arg info -// provided through some factory methods in CppInterOp API, so the clients can -// rely completely on opaque pointers like we do for the rest of the argument -// types. -struct TemplateArgInfo { - void* m_Type; - const char* m_IntegralValue; - TemplateArgInfo(void* type, const char* integral_value = nullptr) - : m_Type(type), m_IntegralValue(integral_value) {} -}; - -namespace Cpp { -using TemplateArgInfo = ::TemplateArgInfo; - -struct DeclRef { - void* data; - DeclRef() : data(nullptr) {} - DeclRef(void* P) : data(P) {} - DeclRef(decltype(nullptr)) : data(nullptr) {} - explicit operator bool() const { return data != nullptr; } - friend bool operator==(DeclRef a, DeclRef b) { return a.data == b.data; } - friend bool operator!=(DeclRef a, DeclRef b) { return !(a == b); } -}; - -struct TypeRef { - void* data; - TypeRef() : data(nullptr) {} - TypeRef(void* P) : data(P) {} - TypeRef(decltype(nullptr)) : data(nullptr) {} - explicit operator bool() const { return data != nullptr; } - friend bool operator==(TypeRef a, TypeRef b) { return a.data == b.data; } - friend bool operator!=(TypeRef a, TypeRef b) { return !(a == b); } -}; - -struct FuncRef { - void* data; - FuncRef() : data(nullptr) {} - FuncRef(void* P) : data(P) {} - FuncRef(decltype(nullptr)) : data(nullptr) {} - explicit operator bool() const { return data != nullptr; } - friend bool operator==(FuncRef a, FuncRef b) { return a.data == b.data; } - friend bool operator!=(FuncRef a, FuncRef b) { return !(a == b); } -}; - -struct ObjectRef { - void* data; - ObjectRef() : data(nullptr) {} - ObjectRef(void* P) : data(P) {} - ObjectRef(decltype(nullptr)) : data(nullptr) {} - explicit operator bool() const { return data != nullptr; } - friend bool operator==(ObjectRef a, ObjectRef b) { return a.data == b.data; } - friend bool operator!=(ObjectRef a, ObjectRef b) { return !(a == b); } -}; -} // namespace Cpp - -template <> struct std::hash { - std::size_t operator()(const Cpp::DeclRef &obj) const { - return std::hash{}(obj.data); - } -}; -template <> struct std::hash { - std::size_t operator()(const Cpp::TypeRef &obj) const { - return std::hash{}(obj.data); - } -}; -template <> struct std::hash { - std::size_t operator()(const Cpp::FuncRef &obj) const { - return std::hash{}(obj.data); - } -}; -template <> struct std::hash { - std::size_t operator()(const Cpp::ObjectRef &obj) const { - return std::hash{}(obj.data); - } -}; - -namespace Cppyy { - typedef Cpp::DeclRef TCppScope_t; - typedef Cpp::TypeRef TCppType_t; - typedef Cpp::ObjectRef TCppObject_t; - typedef Cpp::FuncRef TCppMethod_t; - typedef size_t TCppIndex_t; - typedef void* TCppFuncAddr_t; - -// direct interpreter access ------------------------------------------------- - CPPYY_IMPORT - bool Compile(const std::string& code, bool silent = false); - CPPYY_IMPORT - std::string ToString(TCppScope_t klass, TCppObject_t obj); - -// name to opaque C++ scope representation ----------------------------------- - CPPYY_IMPORT - std::string ResolveName(const std::string& cppitem_name); - CPPYY_IMPORT - TCppType_t ResolveType(TCppType_t cppitem_name); - CPPYY_IMPORT - TCppType_t ResolveEnumReferenceType(TCppType_t type); - CPPYY_IMPORT - TCppType_t ResolveEnumPointerType(TCppType_t type); - CPPYY_IMPORT - TCppType_t GetRealType(TCppType_t type); - CPPYY_IMPORT - TCppType_t GetPointerType(TCppType_t type); - CPPYY_IMPORT - TCppType_t GetReferencedType(TCppType_t type, bool rvalue = false); - CPPYY_IMPORT - std::string ResolveEnum(TCppScope_t enum_scope); - CPPYY_IMPORT - bool IsLValueReferenceType(TCppType_t type); - CPPYY_IMPORT - bool IsRValueReferenceType(TCppType_t type); - CPPYY_IMPORT - bool IsClassType(TCppType_t type); - CPPYY_IMPORT - bool IsIntegerType(TCppType_t type, bool* is_signed = nullptr); - CPPYY_IMPORT - bool IsPointerType(TCppType_t type); - CPPYY_IMPORT - bool IsFunctionPointerType(TCppType_t type); - CPPYY_IMPORT - TCppType_t GetType(const std::string &name, bool enable_slow_lookup = false); - CPPYY_IMPORT - bool AppendTypesSlow(const std::string &name, - std::vector& types, Cppyy::TCppScope_t parent = nullptr); - CPPYY_IMPORT - TCppType_t GetComplexType(const std::string &element_type); - CPPYY_IMPORT - TCppScope_t GetScope(const std::string& scope_name, - TCppScope_t parent_scope = TCppScope_t{}); - CPPYY_IMPORT - TCppScope_t GetUnderlyingScope(TCppScope_t scope); - CPPYY_IMPORT - TCppScope_t GetFullScope(const std::string& scope_name); - CPPYY_IMPORT - TCppScope_t GetTypeScope(TCppScope_t klass); - CPPYY_IMPORT - TCppScope_t GetNamed(const std::string& scope_name, - TCppScope_t parent_scope = TCppScope_t{}); - CPPYY_IMPORT - TCppScope_t GetParentScope(TCppScope_t scope); - CPPYY_IMPORT - TCppScope_t GetScopeFromType(TCppType_t type); - CPPYY_IMPORT - TCppType_t GetTypeFromScope(TCppScope_t klass); - CPPYY_IMPORT - TCppScope_t GetGlobalScope(); - CPPYY_IMPORT - TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); - CPPYY_IMPORT - size_t SizeOf(TCppScope_t klass); - CPPYY_IMPORT - size_t SizeOfType(TCppType_t type); - - CPPYY_IMPORT - bool IsBuiltin(const std::string& type_name); - - CPPYY_IMPORT - bool IsBuiltin(TCppType_t type); - - CPPYY_IMPORT - bool IsComplete(TCppScope_t type); - -// memory management --------------------------------------------------------- - CPPYY_IMPORT - TCppObject_t Allocate(TCppScope_t scope); - CPPYY_IMPORT - void Deallocate(TCppScope_t scope, TCppObject_t instance); - CPPYY_IMPORT - TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); - CPPYY_IMPORT - void Destruct(TCppScope_t scope, TCppObject_t instance); - -// method/function dispatching ----------------------------------------------- - CPPYY_IMPORT - void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - unsigned char CallB(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - char CallC(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - short CallH(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - int CallI(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - long CallL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - PY_LONG_LONG CallLL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - float CallF(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - double CallD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - PY_LONG_DOUBLE CallLD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - - CPPYY_IMPORT - void* CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - CPPYY_IMPORT - char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length); - CPPYY_IMPORT - TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, size_t nargs, void* args); - CPPYY_IMPORT - void CallDestructor(TCppScope_t type, TCppObject_t self); - CPPYY_IMPORT - TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, TCppType_t result_type); - - CPPYY_IMPORT - TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, bool check_enabled=true); - -// handling of function argument buffer -------------------------------------- - CPPYY_IMPORT - void* AllocateFunctionArgs(size_t nargs); - CPPYY_IMPORT - void DeallocateFunctionArgs(void* args); - CPPYY_IMPORT - size_t GetFunctionArgSizeof(); - CPPYY_IMPORT - size_t GetFunctionArgTypeoffset(); - -// scope reflection information ---------------------------------------------- - CPPYY_IMPORT - bool IsNamespace(TCppScope_t scope); - CPPYY_IMPORT - bool IsClass(TCppScope_t scope); - CPPYY_IMPORT - bool IsTemplate(TCppScope_t scope); - CPPYY_IMPORT - bool IsTemplateInstantiation(TCppScope_t scope); - CPPYY_IMPORT - bool IsTypedefed(TCppScope_t scope); - CPPYY_IMPORT - bool IsAbstract(TCppScope_t scope); - CPPYY_IMPORT - bool IsEnumScope(TCppScope_t scope); - CPPYY_IMPORT - bool IsEnumConstant(TCppScope_t scope); - CPPYY_IMPORT - bool IsEnumType(TCppType_t type); - CPPYY_IMPORT - bool IsAggregate(TCppScope_t type); - CPPYY_IMPORT - bool IsDefaultConstructable(TCppScope_t scope); - CPPYY_IMPORT - bool IsVariable(TCppScope_t scope); - - CPPYY_IMPORT - void GetAllCppNames(TCppScope_t scope, std::set& cppnames); - -// namespace reflection information ------------------------------------------ - CPPYY_IMPORT - std::vector GetUsingNamespaces(TCppScope_t); - -// class reflection information ---------------------------------------------- - CPPYY_IMPORT - std::string GetFinalName(TCppScope_t type); - CPPYY_IMPORT - std::string GetScopedFinalName(TCppScope_t type); - CPPYY_IMPORT - bool HasVirtualDestructor(TCppScope_t type); - CPPYY_IMPORT - TCppIndex_t GetNumBases(TCppScope_t klass); - CPPYY_IMPORT - TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass); - CPPYY_IMPORT - std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase); - CPPYY_IMPORT - TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase); - CPPYY_IMPORT - bool IsSubclass(TCppScope_t derived, TCppScope_t base); - CPPYY_IMPORT - bool IsSmartPtr(TCppScope_t klass); - CPPYY_IMPORT - bool GetSmartPtrInfo(const std::string&, TCppScope_t* raw, TCppMethod_t* deref); -// calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 - CPPYY_IMPORT - ptrdiff_t GetBaseOffset( - TCppScope_t derived, TCppScope_t base, TCppObject_t address, int direction, bool rerror = false); - -// method/function reflection information ------------------------------------ - CPPYY_IMPORT - void GetClassMethods(TCppScope_t scope, std::vector &methods); - CPPYY_IMPORT - std::vector GetMethodsFromName(TCppScope_t scope, - const std::string& name); - CPPYY_IMPORT - std::string GetName(TCppScope_t); - CPPYY_IMPORT - std::string GetFullName(TCppScope_t); - CPPYY_IMPORT - TCppType_t GetMethodReturnType(TCppMethod_t); - CPPYY_IMPORT - std::string GetMethodReturnTypeAsString(TCppMethod_t); - CPPYY_IMPORT - TCppIndex_t GetMethodNumArgs(TCppMethod_t); - CPPYY_IMPORT - TCppIndex_t GetMethodReqArgs(TCppMethod_t); - CPPYY_IMPORT - std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); - CPPYY_IMPORT - TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); - CPPYY_IMPORT - TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, const std::string &req_type); - CPPYY_IMPORT - std::string GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg); - CPPYY_IMPORT - std::string GetMethodArgCanonTypeAsString(TCppMethod_t method, TCppIndex_t iarg); - CPPYY_IMPORT - std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); - CPPYY_IMPORT - std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, TCppIndex_t max_args = (TCppIndex_t)-1); - // GetMethodPrototype is unused. - CPPYY_IMPORT - std::string GetMethodPrototype(TCppMethod_t, bool show_formal_args); - CPPYY_IMPORT - std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); - CPPYY_IMPORT - bool IsConstMethod(TCppMethod_t); -// Templated method/function reflection information ------------------------------------ - CPPYY_IMPORT - void GetTemplatedMethods(TCppScope_t scope, std::vector &methods); - CPPYY_IMPORT - TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace = false); - CPPYY_IMPORT - std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); - CPPYY_IMPORT - bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); - CPPYY_IMPORT - bool IsTemplatedMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsStaticTemplate(TCppScope_t scope, const std::string& name); - CPPYY_IMPORT - TCppMethod_t GetMethodTemplate( - TCppScope_t scope, const std::string& name, const std::string& proto); - CPPYY_IMPORT - void GetClassOperators(Cppyy::TCppScope_t klass, const std::string& opname, - std::vector& operators); - CPPYY_IMPORT - TCppMethod_t GetGlobalOperator( - TCppScope_t scope, const std::string& lc, const std::string& rc, const std::string& op); - -// method properties --------------------------------------------------------- - CPPYY_IMPORT - bool IsDeletedMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsPublicMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsProtectedMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsPrivateMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsConstructor(TCppMethod_t method); - CPPYY_IMPORT - bool IsDestructor(TCppMethod_t method); - CPPYY_IMPORT - bool IsStaticMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsExplicit(TCppMethod_t method); - -// data member reflection information ---------------------------------------- - CPPYY_IMPORT - void GetDatamembers(TCppScope_t scope, std::vector& datamembers); - CPPYY_IMPORT - bool IsLambdaClass(TCppType_t type); - CPPYY_IMPORT - TCppScope_t WrapLambdaFromVariable(TCppScope_t var); - CPPYY_IMPORT - TCppMethod_t AdaptFunctionForLambdaReturn(TCppMethod_t fn); - CPPYY_IMPORT - TCppType_t GetDatamemberType(TCppScope_t data); - CPPYY_IMPORT - std::string GetDatamemberTypeAsString(TCppScope_t var); - CPPYY_IMPORT - std::string GetTypeAsString(TCppType_t type); - CPPYY_IMPORT - intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); - CPPYY_IMPORT - bool CheckDatamember(TCppScope_t scope, const std::string& name); - -// // data member properties ---------------------------------------------------- - CPPYY_IMPORT - bool IsPublicData(TCppScope_t var); - CPPYY_IMPORT - bool IsProtectedData(TCppScope_t var); - CPPYY_IMPORT - bool IsPrivateData(TCppScope_t var); - CPPYY_IMPORT - bool IsStaticDatamember(TCppScope_t var); - CPPYY_IMPORT - bool IsConstVar(TCppScope_t var); - CPPYY_IMPORT - TCppMethod_t ReduceReturnType(TCppMethod_t fn, TCppType_t reduce); - CPPYY_IMPORT - std::vector GetDimensions(TCppType_t type); - -// enum properties ----------------------------------------------------------- - CPPYY_IMPORT - std::vector GetEnumConstants(TCppScope_t scope); - CPPYY_IMPORT - TCppType_t GetEnumConstantType(TCppScope_t scope); - CPPYY_IMPORT - TCppIndex_t GetEnumDataValue(TCppScope_t scope); - - CPPYY_IMPORT - TCppScope_t InstantiateTemplate( - TCppScope_t tmpl, Cpp::TemplateArgInfo* args, size_t args_size); - - CPPYY_IMPORT - void DumpScope(TCppScope_t scope); -} // namespace Cppyy - -#endif // !CPYCPPYY_CPPYY_H diff --git a/src/CPyCppyy/src/CustomPyTypes.cxx b/src/CPyCppyy/src/CustomPyTypes.cxx deleted file mode 100644 index 4038947..0000000 --- a/src/CPyCppyy/src/CustomPyTypes.cxx +++ /dev/null @@ -1,361 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "CustomPyTypes.h" -#include "CPPInstance.h" -#include "Converters.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" - -// As of Python 3.12, we can't use the PyMethod_GET_FUNCTION and -// PyMethod_GET_SELF macros anymore, as the contain asserts that check if the -// Python type is actually PyMethod_Type. If the Python type is -// CustomInstanceMethod_Type, we need our own macros. Technically they do they -// same, because the actual C++ type of the PyObject is PyMethodObject anyway. -#define CustomInstanceMethod_GET_SELF(meth) reinterpret_cast(meth)->im_self -#define CustomInstanceMethod_GET_FUNCTION(meth) reinterpret_cast(meth)->im_func -// TODO: this will break functionality -#define CustomInstanceMethod_GET_CLASS(meth) Py_None - -namespace CPyCppyy { - -//- custom type representing typedef to pointer of class --------------------- -static PyObject* tptc_call(typedefpointertoclassobject* self, PyObject* args, PyObject* /* kwds */) -{ - long long addr = 0; - if (!PyArg_ParseTuple(args, const_cast("|L"), &addr)) - return nullptr; - return BindCppObjectNoCast(Cppyy::TCppObject_t((void*)addr), self->fCppType); -} - -//----------------------------------------------------------------------------- -static PyObject* tptc_getcppname(typedefpointertoclassobject* self, void*) -{ - return CPyCppyy_PyText_FromString( - (Cppyy::GetScopedFinalName(self->fCppType)+"*").c_str()); -} - -//----------------------------------------------------------------------------- -static PyObject* tptc_name(typedefpointertoclassobject* self, void*) -{ - PyObject* pyclass = CPyCppyy::GetScopeProxy(self->fCppType); - if (pyclass) { - PyObject* pyname = PyObject_GetAttr(pyclass, PyStrings::gName); - Py_DECREF(pyclass); - return pyname; - } - - return CPyCppyy_PyText_FromString("*"); -} - -//----------------------------------------------------------------------------- -static PyGetSetDef tptc_getset[] = { - {(char*)"__name__", (getter)tptc_name, nullptr, nullptr, nullptr}, - {(char*)"__cpp_name__", (getter)tptc_getcppname, nullptr, nullptr, nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} -}; - - -PyTypeObject TypedefPointerToClass_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.TypedefPointerToClass",// tp_name - sizeof(typedefpointertoclassobject), // tp_basicsize - 0, // tp_itemsize - 0, // tp_dealloc - 0, // tp_vectorcall_offset - 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 - (ternaryfunc)tptc_call, // tp_call - 0, // tp_str - PyObject_GenericGetAttr, // tp_getattro - PyObject_GenericSetAttr, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT, // 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 - tptc_getset, // 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 - CPYCPPYY_PYTYPE_TAIL -}; - -//= instancemethod object with a more efficient call function ================ -static PyMethodObject* free_list; -static int numfree = 0; -#ifndef PyMethod_MAXFREELIST -#define PyMethod_MAXFREELIST 256 -#endif - -//----------------------------------------------------------------------------- -PyObject* CustomInstanceMethod_New(PyObject* func, PyObject* self, PyObject*) -{ -// from instancemethod, but with custom type (at issue is that instancemethod is not -// meant to be derived from) - PyMethodObject* im; - if (!PyCallable_Check(func)) { - PyErr_Format(PyExc_SystemError, - "%s:%d: bad argument to internal function", __FILE__, __LINE__); - return nullptr; - } - - im = free_list; - if (im != nullptr) { - free_list = (PyMethodObject*)(im->im_self); - (void)PyObject_INIT(im, &CustomInstanceMethod_Type); - } - else { - im = PyObject_GC_New(PyMethodObject, &CustomInstanceMethod_Type); - if (im == nullptr) - return nullptr; - } - - im->im_weakreflist = nullptr; - Py_INCREF(func); - im->im_func = func; - Py_XINCREF(self); - im->im_self = self; - PyObject_GC_Track(im); - return (PyObject*)im; -} - -//----------------------------------------------------------------------------- -static void im_dealloc(PyMethodObject* im) -{ -// from instancemethod, but with custom type (at issue is that instancemethod is not -// meant to be derived from) - PyObject_GC_UnTrack(im); - - if (im->im_weakreflist != nullptr) - PyObject_ClearWeakRefs((PyObject*)im); - - Py_DECREF(im->im_func); - Py_XDECREF(im->im_self); - - if (numfree < PyMethod_MAXFREELIST) { - im->im_self = (PyObject*)free_list; - free_list = im; - numfree++; - } else { - PyObject_GC_Del(im); - } -} - -//----------------------------------------------------------------------------- -static PyObject* im_call(PyObject* meth, PyObject* args, PyObject* kw) -{ -// The mapping from a method to a function involves reshuffling of self back -// into the list of arguments. However, the pythonized methods will then have -// to undo that shuffling, which is inefficient. This method is the same as -// the one for the instancemethod object, except for the shuffling. - PyObject* self = CustomInstanceMethod_GET_SELF(meth); - - if (!self) { - // unbound methods must be called with an instance of the class (or a - // derived class) as first argument - Py_ssize_t argc = PyTuple_GET_SIZE(args); - PyObject* pyclass = CustomInstanceMethod_GET_CLASS(meth); - if (1 <= argc && PyObject_IsInstance(PyTuple_GET_ITEM(args, 0), pyclass) == 1) { - self = PyTuple_GET_ITEM(args, 0); - - PyObject* newArgs = PyTuple_New(argc-1); - for (int i = 1; i < argc; ++i) { - PyObject* v = PyTuple_GET_ITEM(args, i); - Py_INCREF(v); - PyTuple_SET_ITEM(newArgs, i-1, v); - } - - args = newArgs; - - } else - return PyMethod_Type.tp_call(meth, args, kw); // will set proper error msg - - } else - Py_INCREF(args); - - PyCFunctionObject* func = (PyCFunctionObject*)CustomInstanceMethod_GET_FUNCTION(meth); - -// the function is globally shared, so set and reset its "self" (ok, b/c of GIL) - Py_INCREF(self); - func->m_self = self; - PyObject* result = CPyCppyy_PyCFunction_Call((PyObject*)func, args, kw); - func->m_self = nullptr; - Py_DECREF(self); - Py_DECREF(args); - return result; -} - -//----------------------------------------------------------------------------- -static PyObject* im_descr_get(PyObject* meth, PyObject* obj, PyObject* pyclass) -{ -// from instancemethod: don't rebind an already bound method, or an unbound method -// of a class that's not a base class of pyclass - if (CustomInstanceMethod_GET_SELF(meth)) { - Py_INCREF(meth); - return meth; - } - - if (obj == Py_None) - obj = nullptr; - - return CustomInstanceMethod_New(CustomInstanceMethod_GET_FUNCTION(meth), obj, pyclass); -} - -//= CPyCppyy custom instance method type ===================================== -PyTypeObject CustomInstanceMethod_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.InstanceMethod", // tp_name - 0, 0, - (destructor)im_dealloc, // tp_dealloc - 0, 0, 0, 0, 0, 0, 0, 0, 0, - im_call, // tp_call - 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE, // tp_flags - (char*)"CPyCppyy custom instance method (internal)", // tp_doc - 0, 0, 0, 0, 0, 0, 0, 0, 0, - &PyMethod_Type, // tp_base - 0, - im_descr_get, // tp_descr_get - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // tp_del - 0, // tp_version_tag - 0, // tp_finalize - 0 // tp_vectorcall - CPYCPPYY_PYTYPE_TAIL -}; - - -//= CPyCppyy custom iterator for performance ================================= -static void indexiter_dealloc(indexiterobject* ii) { - PyObject_GC_UnTrack(ii); - Py_XDECREF(ii->ii_container); - PyObject_GC_Del(ii); -} - -static int indexiter_traverse(indexiterobject* ii, visitproc visit, void* arg) { - Py_VISIT(ii->ii_container); - return 0; -} - -static PyObject* indexiter_iternext(indexiterobject* ii) { - if (ii->ii_pos >= ii->ii_len) - return nullptr; - - PyObject* pyindex = PyLong_FromSsize_t(ii->ii_pos); - PyObject* result = PyObject_CallMethodOneArg((PyObject*)ii->ii_container, PyStrings::gGetItem, pyindex); - Py_DECREF(pyindex); - - ii->ii_pos += 1; - return result; -} - -PyTypeObject IndexIter_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.indexiter", // tp_name - sizeof(indexiterobject), // tp_basicsize - 0, - (destructor)indexiter_dealloc, // tp_dealloc - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_GC, // tp_flags - 0, - (traverseproc)indexiter_traverse, // tp_traverse - 0, 0, 0, - PyObject_SelfIter, // tp_iter - (iternextfunc)indexiter_iternext, // tp_iternext - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // tp_del - 0, // tp_version_tag - 0, // tp_finalize - 0 // tp_vectorcall - CPYCPPYY_PYTYPE_TAIL -}; - - -static void vectoriter_dealloc(vectoriterobject* vi) { - if (vi->vi_converter && vi->vi_converter->HasState()) delete vi->vi_converter; - indexiter_dealloc(vi); -} - -static PyObject* vectoriter_iternext(vectoriterobject* vi) { - if (vi->ii_pos >= vi->ii_len) - return nullptr; - - PyObject* result = nullptr; - - if (vi->vi_data && vi->vi_converter) { - void* location = (void*)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos); - result = vi->vi_converter->FromMemory(location); - } else if (vi->vi_data && vi->vi_klass) { - // The CPPInstance::kNoMemReg by-passes the memory regulator; the assumption here is - // that objects in vectors are simple and thus do not need to maintain object identity - // (or at least not during the loop anyway). This gains 2x in performance. - Cppyy::TCppObject_t cppobj((void*)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos)); - if (vi->vi_flags & vectoriterobject::kIsPolymorphic) - result = CPyCppyy::BindCppObject(*(void**)cppobj.data, vi->vi_klass, CPyCppyy::CPPInstance::kNoMemReg); - else - result = CPyCppyy::BindCppObjectNoCast(cppobj, vi->vi_klass, CPyCppyy::CPPInstance::kNoMemReg); - if ((vi->vi_flags & vectoriterobject::kNeedLifeLine) && result) - PyObject_SetAttr(result, PyStrings::gLifeLine, vi->ii_container); - } else { - PyObject* pyindex = PyLong_FromSsize_t(vi->ii_pos); - result = PyObject_CallMethodOneArg((PyObject*)vi->ii_container, PyStrings::gGetNoCheck, pyindex); - Py_DECREF(pyindex); - } - - vi->ii_pos += 1; - return result; -} - -PyTypeObject VectorIter_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.vectoriter", // tp_name - sizeof(vectoriterobject), // tp_basicsize - 0, - (destructor)vectoriter_dealloc, // tp_dealloc - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_GC, // tp_flags - 0, - (traverseproc)indexiter_traverse, // tp_traverse - 0, 0, 0, - PyObject_SelfIter, // tp_iter - (iternextfunc)vectoriter_iternext, // tp_iternext - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // tp_del - 0, // tp_version_tag - 0, // tp_finalize - 0 // tp_vectorcall - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/src/CPyCppyy/src/CustomPyTypes.h b/src/CPyCppyy/src/CustomPyTypes.h deleted file mode 100644 index 6e72ac8..0000000 --- a/src/CPyCppyy/src/CustomPyTypes.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef CPYCPPYY_CUSTOMPYTYPES_H -#define CPYCPPYY_CUSTOMPYTYPES_H - -#include "Python.h" -#include "Cppyy.h" - -namespace CPyCppyy { - -/** Custom "builtins," detectable by type, for pass by ref and improved - performance. - */ - -//- custom type representing typedef to pointer of class --------------------- -struct typedefpointertoclassobject { - PyObject_HEAD - Cppyy::TCppScope_t fCppType; -}; - -extern PyTypeObject TypedefPointerToClass_Type; - -template -inline bool TypedefPointerToClass_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &TypedefPointerToClass_Type); -} - -template -inline bool TypedefPointerToClass_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &TypedefPointerToClass_Type; -} - -//- custom instance method object type and type verification ----------------- -extern PyTypeObject CustomInstanceMethod_Type; - -template -inline bool CustomInstanceMethod_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &CustomInstanceMethod_Type); -} - -template -inline bool CustomInstanceMethod_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &CustomInstanceMethod_Type; -} - -PyObject* CustomInstanceMethod_New(PyObject* func, PyObject* self, PyObject* pyclass); - -//- custom iterator for high performance std::vector iteration --------------- -struct indexiterobject { - PyObject_HEAD - PyObject* ii_container; - Py_ssize_t ii_pos; - Py_ssize_t ii_len; -}; - -extern PyTypeObject IndexIter_Type; - -class Converter; -struct vectoriterobject : public indexiterobject { - void* vi_data; - Py_ssize_t vi_stride; - CPyCppyy::Converter* vi_converter; - Cppyy::TCppScope_t vi_klass; - int vi_flags; - - enum EFlags { - kDefault = 0x0000, - kNeedLifeLine = 0x0001, - kIsPolymorphic = 0x0002, - }; -}; - -extern PyTypeObject VectorIter_Type; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CUSTOMPYTYPES_H diff --git a/src/CPyCppyy/src/DeclareConverters.h b/src/CPyCppyy/src/DeclareConverters.h deleted file mode 100644 index 478f816..0000000 --- a/src/CPyCppyy/src/DeclareConverters.h +++ /dev/null @@ -1,502 +0,0 @@ -#ifndef CPYCPPYY_DECLARECONVERTERS_H -#define CPYCPPYY_DECLARECONVERTERS_H - -// Bindings -#include "Converters.h" -#include "Cppyy.h" -#include "Dimensions.h" - -// Standard -#include -#include - - -namespace CPyCppyy { - -namespace { - -#define CPPYY_DECLARE_BASIC_CONVERTER(name) \ -class name##Converter : public Converter { \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - std::string GetFailureMsg() override { return "[" #name "Converter]"; } \ -}; \ - \ -class Const##name##RefConverter : public Converter { \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - std::string GetFailureMsg() override { return "[Const" #name "RefConverter]"; }\ -} - - -#define CPPYY_DECLARE_BASIC_CONVERTER2(name, base) \ -class name##Converter : public base##Converter { \ -public: \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - std::string GetFailureMsg() override { return "[" #name "Converter]"; } \ -}; \ - \ -class Const##name##RefConverter : public Converter { \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - std::string GetFailureMsg() override { return "[Const" #name "RefConverter]"; }\ -} - -#define CPPYY_DECLARE_REFCONVERTER(name) \ -class name##RefConverter : public Converter { \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - std::string GetFailureMsg() override { return "[" #name "RefConverter]"; }\ -}; - -#define CPPYY_DECLARE_ARRAY_CONVERTER(name) \ -class name##ArrayConverter : public Converter { \ -public: \ - name##ArrayConverter(cdims_t dims); \ - name##ArrayConverter(const name##ArrayConverter&) = delete; \ - name##ArrayConverter& operator=(const name##ArrayConverter&) = delete; \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - bool HasState() override { return true; } \ - std::string GetFailureMsg() override { return "[" #name "ArrayConverter]"; }\ -protected: \ - dims_t fShape; \ - bool fIsFixed; \ -}; - - -// converters for built-ins -CPPYY_DECLARE_BASIC_CONVERTER(Long); -CPPYY_DECLARE_BASIC_CONVERTER(Bool); -CPPYY_DECLARE_BASIC_CONVERTER(Char); -class SCharAsIntConverter : public CharConverter { -public: - using CharConverter::CharConverter; - PyObject* FromMemory(void*) override; -}; -CPPYY_DECLARE_BASIC_CONVERTER(UChar); -class UCharAsIntConverter : public UCharConverter { -public: - using UCharConverter::UCharConverter; - PyObject* FromMemory(void*) override; -}; -CPPYY_DECLARE_BASIC_CONVERTER(WChar); -CPPYY_DECLARE_BASIC_CONVERTER(Char16); -CPPYY_DECLARE_BASIC_CONVERTER(Char32); -CPPYY_DECLARE_BASIC_CONVERTER(Int8); -CPPYY_DECLARE_BASIC_CONVERTER(UInt8); -CPPYY_DECLARE_BASIC_CONVERTER(Short); -CPPYY_DECLARE_BASIC_CONVERTER(UShort); -CPPYY_DECLARE_BASIC_CONVERTER(Int); -CPPYY_DECLARE_BASIC_CONVERTER(ULong); -CPPYY_DECLARE_BASIC_CONVERTER2(UInt, ULong); -CPPYY_DECLARE_BASIC_CONVERTER(LLong); -CPPYY_DECLARE_BASIC_CONVERTER(ULLong); -CPPYY_DECLARE_BASIC_CONVERTER(Double); -CPPYY_DECLARE_BASIC_CONVERTER(Float); -CPPYY_DECLARE_BASIC_CONVERTER(LDouble); - -CPPYY_DECLARE_REFCONVERTER(Bool); -CPPYY_DECLARE_REFCONVERTER(Char); -CPPYY_DECLARE_REFCONVERTER(WChar); -CPPYY_DECLARE_REFCONVERTER(Char16); -CPPYY_DECLARE_REFCONVERTER(Char32); -CPPYY_DECLARE_REFCONVERTER(SChar); -CPPYY_DECLARE_REFCONVERTER(UChar); -CPPYY_DECLARE_REFCONVERTER(Int8); -CPPYY_DECLARE_REFCONVERTER(UInt8); -CPPYY_DECLARE_REFCONVERTER(Short); -CPPYY_DECLARE_REFCONVERTER(UShort); -CPPYY_DECLARE_REFCONVERTER(UInt); -CPPYY_DECLARE_REFCONVERTER(Int); -CPPYY_DECLARE_REFCONVERTER(Long); -CPPYY_DECLARE_REFCONVERTER(ULong); -CPPYY_DECLARE_REFCONVERTER(LLong); -CPPYY_DECLARE_REFCONVERTER(ULLong); -CPPYY_DECLARE_REFCONVERTER(Float); -CPPYY_DECLARE_REFCONVERTER(Double); -CPPYY_DECLARE_REFCONVERTER(LDouble); - -class VoidConverter : public Converter { -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -}; - -class CStringConverter : public Converter { -public: - CStringConverter(std::string::size_type maxSize = std::string::npos) : fMaxSize(maxSize) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[CStringConverter]"; } - -protected: - std::string fBuffer; - std::string::size_type fMaxSize; -}; - -class NonConstCStringConverter : public CStringConverter { -public: - using CStringConverter::CStringConverter; - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - std::string GetFailureMsg() override { return "[NonConstCStringConverter]"; } -}; - -class WCStringConverter : public Converter { -public: - WCStringConverter(std::wstring::size_type maxSize = std::wstring::npos) : - fBuffer(nullptr), fMaxSize(maxSize) {} - WCStringConverter(const WCStringConverter&) = delete; - WCStringConverter& operator=(const WCStringConverter&) = delete; - virtual ~WCStringConverter() { free(fBuffer); } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[WCStringConverter]"; }; - -protected: - wchar_t* fBuffer; - std::wstring::size_type fMaxSize; -}; - -class CString16Converter : public Converter { -public: - CString16Converter(std::wstring::size_type maxSize = std::wstring::npos) : - fBuffer(nullptr), fMaxSize(maxSize) {} - CString16Converter(const CString16Converter&) = delete; - CString16Converter& operator=(const CString16Converter&) = delete; - virtual ~CString16Converter() { free(fBuffer); } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[CString16Converter]"; }; - -protected: - char16_t* fBuffer; - std::wstring::size_type fMaxSize; -}; - -class CString32Converter : public Converter { -public: - CString32Converter(std::wstring::size_type maxSize = std::wstring::npos) : - fBuffer(nullptr), fMaxSize(maxSize) {} - CString32Converter(const CString32Converter&) = delete; - CString32Converter& operator=(const CString32Converter&) = delete; - virtual ~CString32Converter() { free(fBuffer); } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[CString32Converter]"; }; - -protected: - char32_t* fBuffer; - std::wstring::size_type fMaxSize; -}; - -// pointer/array conversions -CPPYY_DECLARE_ARRAY_CONVERTER(Bool); -CPPYY_DECLARE_ARRAY_CONVERTER(SChar); -CPPYY_DECLARE_ARRAY_CONVERTER(UChar); -CPPYY_DECLARE_ARRAY_CONVERTER(Byte); -CPPYY_DECLARE_ARRAY_CONVERTER(Int8); -CPPYY_DECLARE_ARRAY_CONVERTER(UInt8); -CPPYY_DECLARE_ARRAY_CONVERTER(Short); -CPPYY_DECLARE_ARRAY_CONVERTER(UShort); -CPPYY_DECLARE_ARRAY_CONVERTER(Int); -CPPYY_DECLARE_ARRAY_CONVERTER(UInt); -CPPYY_DECLARE_ARRAY_CONVERTER(Long); -CPPYY_DECLARE_ARRAY_CONVERTER(ULong); -CPPYY_DECLARE_ARRAY_CONVERTER(LLong); -CPPYY_DECLARE_ARRAY_CONVERTER(ULLong); -CPPYY_DECLARE_ARRAY_CONVERTER(Float); -CPPYY_DECLARE_ARRAY_CONVERTER(Double); -CPPYY_DECLARE_ARRAY_CONVERTER(LDouble); -CPPYY_DECLARE_ARRAY_CONVERTER(ComplexF); -CPPYY_DECLARE_ARRAY_CONVERTER(ComplexD); - -class CStringArrayConverter : public SCharArrayConverter { -public: - CStringArrayConverter(cdims_t dims, bool fixed) : SCharArrayConverter(dims) { - fIsFixed = fixed; // overrides SCharArrayConverter decision - } - using SCharArrayConverter::SCharArrayConverter; - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - std::string GetFailureMsg() override { return "[CStringArrayConverter]"; }; - -private: - std::vector fBuffer; -}; - -class NonConstCStringArrayConverter : public CStringArrayConverter { -public: - using CStringArrayConverter::CStringArrayConverter; - PyObject* FromMemory(void* address) override; - std::string GetFailureMsg() override { return "[NonConstCStringArrayConverter]"; }; -}; - -// converters for special cases -class NullptrConverter : public Converter { -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -}; - -class InstanceConverter : public StrictInstancePtrConverter { -public: - using StrictInstancePtrConverter::StrictInstancePtrConverter; - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void*) override; - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; - std::string GetFailureMsg() override { return "[InstanceConverter]"; }; -}; - -class InstanceRefConverter : public Converter { -public: - InstanceRefConverter(Cppyy::TCppScope_t klass, bool isConst) : - fClass(klass), fIsConst(isConst) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[InstanceRefConverter]"; }; - -protected: - Cppyy::TCppScope_t fClass; - bool fIsConst; -}; - -class InstanceMoveConverter : public InstanceRefConverter { -public: - InstanceMoveConverter(Cppyy::TCppScope_t klass) : InstanceRefConverter(klass, true) {} - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - std::string GetFailureMsg() override { return "[InstanceMoveConverter]"; }; -}; - -template -class InstancePtrPtrConverter : public InstancePtrConverter { -public: - using InstancePtrConverter::InstancePtrConverter; - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - std::string GetFailureMsg() override { return "[InstancePtrPtrConverter]"; }; -}; - -class InstanceArrayConverter : public InstancePtrConverter { -public: - InstanceArrayConverter(Cppyy::TCppScope_t klass, cdims_t dims, bool keepControl = false) : - InstancePtrConverter(klass, keepControl), fShape(dims) { } - InstanceArrayConverter(const InstanceArrayConverter&) = delete; - InstanceArrayConverter& operator=(const InstanceArrayConverter&) = delete; - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - std::string GetFailureMsg() override { return "[InstanceArrayConverter]"; }; - -protected: - dims_t fShape; -}; - - -class ComplexDConverter: public InstanceConverter { -public: - ComplexDConverter(bool keepControl = false); - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[ComplexDConverter]"; }; - -private: - std::complex fBuffer; -}; - - -// Cling WORKAROUND -- classes for STL iterators are completely undefined in that -// they come in a bazillion different guises, so just do whatever -class STLIteratorConverter : public Converter { -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - std::string GetFailureMsg() override { return "[STLIteratorConverter]"; }; -}; -// -- END Cling WORKAROUND - - -class VoidPtrRefConverter : public Converter { -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - std::string GetFailureMsg() override { return "[VoidPtrRefConverter]"; }; -}; - -class VoidPtrPtrConverter : public Converter { -public: - VoidPtrPtrConverter(cdims_t dims, const std::string &failureMsg = std::string()); - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[VoidPtrPtrConverter] " + fFailureMsg; } - -protected: - dims_t fShape; - bool fIsFixed; - const std::string fFailureMsg; -}; - -CPPYY_DECLARE_BASIC_CONVERTER(PyObject); - - -#define CPPYY_DECLARE_STRING_CONVERTER(name, strtype) \ -class name##Converter : public InstanceConverter { \ -public: \ - name##Converter(bool keepControl = true); \ - \ -public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void* address) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - bool HasState() override { return true; } \ - std::string GetFailureMsg() override { return "[" #name "Converter]"; }; \ -protected: \ - strtype fBuffer; \ -} - -CPPYY_DECLARE_STRING_CONVERTER(STLString, std::string); -CPPYY_DECLARE_STRING_CONVERTER(STLWString, std::wstring); -CPPYY_DECLARE_STRING_CONVERTER(STLStringView, std::string_view); - -class STLStringMoveConverter : public STLStringConverter { -public: - using STLStringConverter::STLStringConverter; - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - std::string GetFailureMsg() override { return "[STLStringMoveConverter]"; }; -}; - - -// function pointers -class FunctionPointerConverter : public Converter { -public: - FunctionPointerConverter(const std::string& ret, const std::string& sig) : - fRetType(ret), fSignature(sig) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[FunctionPointerConverter]"; }; - -protected: - std::string fRetType; - std::string fSignature; - bool fAllowCppInstance = false; -}; - -// std::function -class StdFunctionConverter : public FunctionPointerConverter { -public: - StdFunctionConverter(Converter* cnv, const std::string& ret, const std::string& sig) : - FunctionPointerConverter(ret, sig), fConverter(cnv) { - fAllowCppInstance = true; - } - StdFunctionConverter(const StdFunctionConverter&) = delete; - StdFunctionConverter& operator=(const StdFunctionConverter&) = delete; - virtual ~StdFunctionConverter() { delete fConverter; } - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; - std::string GetFailureMsg() override { return "[StdFunctionConverter]"; }; - -protected: - Converter* fConverter; -}; - - -// smart pointer converter -class SmartPtrConverter : public Converter { -public: - SmartPtrConverter(Cppyy::TCppScope_t smart, - Cppyy::TCppScope_t underlying, - bool keepControl = false, - bool isRef = false) - : fSmartPtrType(smart), fUnderlyingType(underlying), - fKeepControl(keepControl), fIsRef(isRef) {} - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[SmartPtrConverter]"; }; - -protected: - virtual bool GetAddressSpecialCase(PyObject*, void*&) { return false; } - - Cppyy::TCppScope_t fSmartPtrType; - Cppyy::TCppScope_t fUnderlyingType; - bool fKeepControl; - bool fIsRef; -}; - - -// initializer lists -class InitializerListConverter : public InstanceConverter { -public: - InitializerListConverter(Cppyy::TCppScope_t klass, std::string const& value_type); - InitializerListConverter(const InitializerListConverter&) = delete; - InitializerListConverter& operator=(const InitializerListConverter&) = delete; - virtual ~InitializerListConverter(); - -public: - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; - bool HasState() override { return true; } - std::string GetFailureMsg() override { return "[FunctionPointerConverter]"; }; - -protected: - void Clear(); - -protected: - void* fBuffer = nullptr; - std::vector fConverters; - std::string fValueTypeName; - Cppyy::TCppScope_t fValueType; - size_t fValueSize; -}; - -} // unnamed namespace - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DECLARECONVERTERS_H diff --git a/src/CPyCppyy/src/DeclareExecutors.h b/src/CPyCppyy/src/DeclareExecutors.h deleted file mode 100644 index 19dea66..0000000 --- a/src/CPyCppyy/src/DeclareExecutors.h +++ /dev/null @@ -1,204 +0,0 @@ -#ifndef CPYCPPYY_DECLAREEXECUTORS_H -#define CPYCPPYY_DECLAREEXECUTORS_H - -#include "Python.h" -#include "Cppyy.h" - -// Bindings -#include "Executors.h" -#include "CallContext.h" -#include "Dimensions.h" - -// Standard -#include - - -namespace CPyCppyy { - -namespace { - -#define CPPYY_DECL_EXEC(name) \ -class name##Executor : public Executor { \ -public: \ - PyObject* Execute( \ - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; \ -} - -// executors for built-ins -CPPYY_DECL_EXEC(Bool); -CPPYY_DECL_EXEC(BoolConstRef); -CPPYY_DECL_EXEC(Char); -CPPYY_DECL_EXEC(CharConstRef); -CPPYY_DECL_EXEC(UChar); -CPPYY_DECL_EXEC(UCharConstRef); -CPPYY_DECL_EXEC(WChar); -CPPYY_DECL_EXEC(Char16); -CPPYY_DECL_EXEC(Char32); -CPPYY_DECL_EXEC(Int8); -CPPYY_DECL_EXEC(Int8ConstRef); -CPPYY_DECL_EXEC(UInt8); -CPPYY_DECL_EXEC(UInt8ConstRef); -CPPYY_DECL_EXEC(Short); -CPPYY_DECL_EXEC(Int); -CPPYY_DECL_EXEC(Long); -CPPYY_DECL_EXEC(ULong); -CPPYY_DECL_EXEC(LongLong); -CPPYY_DECL_EXEC(ULongLong); -CPPYY_DECL_EXEC(Float); -CPPYY_DECL_EXEC(Double); -CPPYY_DECL_EXEC(LongDouble); -CPPYY_DECL_EXEC(Void); -CPPYY_DECL_EXEC(CString); -CPPYY_DECL_EXEC(CStringRef); -CPPYY_DECL_EXEC(WCString); -CPPYY_DECL_EXEC(CString16); -CPPYY_DECL_EXEC(CString32); - -// pointer/array executors -#define CPPYY_ARRAY_DECL_EXEC(name) \ -class name##ArrayExecutor : public Executor { \ - dims_t fShape; \ -public: \ - name##ArrayExecutor(dims_t dims) : fShape(dims) {} \ - PyObject* Execute( \ - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; \ - bool HasState() override { return true; } \ -} -CPPYY_ARRAY_DECL_EXEC(Void); -CPPYY_ARRAY_DECL_EXEC(Bool); -CPPYY_ARRAY_DECL_EXEC(SChar); -CPPYY_ARRAY_DECL_EXEC(UChar); -CPPYY_ARRAY_DECL_EXEC(Byte); -CPPYY_ARRAY_DECL_EXEC(Int8); -CPPYY_ARRAY_DECL_EXEC(UInt8); -CPPYY_ARRAY_DECL_EXEC(Short); -CPPYY_ARRAY_DECL_EXEC(UShort); -CPPYY_ARRAY_DECL_EXEC(Int); -CPPYY_ARRAY_DECL_EXEC(UInt); -CPPYY_ARRAY_DECL_EXEC(Long); -CPPYY_ARRAY_DECL_EXEC(ULong); -CPPYY_ARRAY_DECL_EXEC(LLong); -CPPYY_ARRAY_DECL_EXEC(ULLong); -CPPYY_ARRAY_DECL_EXEC(Float); -CPPYY_ARRAY_DECL_EXEC(Double); -CPPYY_ARRAY_DECL_EXEC(LDouble); -CPPYY_ARRAY_DECL_EXEC(ComplexF); -CPPYY_ARRAY_DECL_EXEC(ComplexD); -CPPYY_ARRAY_DECL_EXEC(ComplexI); -CPPYY_ARRAY_DECL_EXEC(ComplexL); - -// special cases -CPPYY_DECL_EXEC(ComplexD); -CPPYY_DECL_EXEC(STLString); -CPPYY_DECL_EXEC(STLWString); - -class InstancePtrExecutor : public Executor { -public: - InstancePtrExecutor(Cppyy::TCppScope_t klass) : fClass(klass) {} - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - bool HasState() override { return true; } - -protected: - Cppyy::TCppScope_t fClass; -}; - -class InstanceExecutor : public Executor { -public: - InstanceExecutor(Cppyy::TCppScope_t klass); - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - bool HasState() override { return true; } - -protected: - Cppyy::TCppScope_t fClass; - uint32_t fFlags; -}; - -class IteratorExecutor : public InstanceExecutor { -public: - IteratorExecutor(Cppyy::TCppScope_t klass); -}; - -CPPYY_DECL_EXEC(Constructor); -CPPYY_DECL_EXEC(PyObject); - -#define CPPYY_DECL_REFEXEC(name) \ -class name##RefExecutor : public RefExecutor { \ -public: \ - PyObject* Execute( \ - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; \ -} - -CPPYY_DECL_REFEXEC(Bool); -CPPYY_DECL_REFEXEC(Char); -CPPYY_DECL_REFEXEC(UChar); -CPPYY_DECL_REFEXEC(Int8); -CPPYY_DECL_REFEXEC(UInt8); -CPPYY_DECL_REFEXEC(Short); -CPPYY_DECL_REFEXEC(UShort); -CPPYY_DECL_REFEXEC(Int); -CPPYY_DECL_REFEXEC(UInt); -CPPYY_DECL_REFEXEC(Long); -CPPYY_DECL_REFEXEC(ULong); -CPPYY_DECL_REFEXEC(LongLong); -CPPYY_DECL_REFEXEC(ULongLong); -CPPYY_DECL_REFEXEC(Float); -CPPYY_DECL_REFEXEC(Double); -CPPYY_DECL_REFEXEC(LongDouble); -CPPYY_DECL_REFEXEC(ComplexD); -CPPYY_DECL_REFEXEC(STLString); - -// special cases -class InstanceRefExecutor : public RefExecutor { -public: - InstanceRefExecutor(Cppyy::TCppScope_t klass) : fClass(klass) {} - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - -protected: - Cppyy::TCppScope_t fClass; -}; - -class InstancePtrPtrExecutor : public InstanceRefExecutor { -public: - using InstanceRefExecutor::InstanceRefExecutor; - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; -}; - -class InstancePtrRefExecutor : public InstanceRefExecutor { -public: - using InstanceRefExecutor::InstanceRefExecutor; - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; -}; - -class InstanceArrayExecutor : public InstancePtrExecutor { -public: - InstanceArrayExecutor(Cppyy::TCppScope_t klass, dim_t array_size) - : InstancePtrExecutor(klass), fSize(array_size) {} - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - -protected: - dim_t fSize; -}; - -class FunctionPointerExecutor : public Executor { -public: - FunctionPointerExecutor(const std::string& ret, const std::string& sig) : - fRetType(ret), fSignature(sig) {} - PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; - -protected: - std::string fRetType; - std::string fSignature; -}; - -} // unnamed namespace - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DECLAREEXECUTORS_H diff --git a/src/CPyCppyy/src/Dimensions.h b/src/CPyCppyy/src/Dimensions.h deleted file mode 100644 index 1f29ae0..0000000 --- a/src/CPyCppyy/src/Dimensions.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef CPYCPPYY_DIMENSIONS_H -#define CPYCPPYY_DIMENSIONS_H - -#include "Python.h" -#include "Cppyy.h" - -// Bindings -#include "CPyCppyy/CommonDefs.h" - -// Standard -#include -#include - - -namespace CPyCppyy { - -static const dim_t UNKNOWN_SIZE = (dim_t)-1; - -class CPYCPPYY_CLASS_EXPORT Dimensions { - dim_t* fDims; - -public: - Dimensions(dim_t ndim = 0, dim_t* dims = nullptr) : fDims(nullptr) { - if (ndim && ndim != UNKNOWN_SIZE) { - fDims = new dim_t[ndim+1]; - fDims[0] = ndim; - if (dims) std::copy(dims, dims+ndim, fDims+1); - else std::fill_n(fDims+1, ndim, UNKNOWN_SIZE); - } - } - Dimensions(std::initializer_list l) { - fDims = new dim_t[l.size()+1]; - fDims[0] = l.size(); - std::copy(l.begin(), l.end(), fDims+1); - } - Dimensions(const Dimensions& d) : fDims(nullptr) { - if (d.fDims) { - fDims = new dim_t[d.fDims[0]+1]; - std::copy(d.fDims, d.fDims+d.fDims[0]+1, fDims); - } - } - Dimensions(Dimensions&& d) : fDims(d.fDims) { - d.fDims = nullptr; - } - Dimensions& operator=(const Dimensions& d) { - if (this != &d) { - if (!d.fDims) { - delete [] fDims; - fDims = nullptr; - } else { - if (!fDims || (fDims && fDims[0] != d.fDims[0])) { - delete [] fDims; - fDims = new dim_t[d.fDims[0]+1]; - } - std::copy(d.fDims, d.fDims+d.fDims[0]+1, fDims); - } - } - return *this; - } - ~Dimensions() { - delete [] fDims; - } - -public: - operator bool() const { return (bool)fDims; } - - dim_t ndim() const { return fDims ? fDims[0] : UNKNOWN_SIZE; } - void ndim(dim_t d) { - if (fDims) { - if (fDims[0] == d) return; - delete [] fDims; - } - - fDims = new dim_t[d+1]; - fDims[0] = d; - std::fill_n(fDims+1, d, UNKNOWN_SIZE); - } - - dim_t operator[](dim_t i) const { return fDims[i+1]; } - dim_t& operator[](dim_t i) { return fDims[i+1]; } - - Dimensions sub() const { return fDims ? Dimensions(fDims[0]-1, fDims+2) : Dimensions(); } -}; - -typedef Dimensions dims_t; -typedef const dims_t& cdims_t; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DIMENSIONS_H diff --git a/src/CPyCppyy/src/DispatchPtr.cxx b/src/CPyCppyy/src/DispatchPtr.cxx deleted file mode 100644 index da1682d..0000000 --- a/src/CPyCppyy/src/DispatchPtr.cxx +++ /dev/null @@ -1,107 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#define CPYCPPYY_INTERNAL 1 -#include "CPyCppyy/DispatchPtr.h" -#undef CPYCPPYY_INTERNAL -#include "CPPInstance.h" -#include "CPPScope.h" - - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::DispatchPtr::Get(bool borrowed) const -{ - PythonGILRAII python_gil_raii; - if (fPyHardRef) { - if (!borrowed) Py_INCREF(fPyHardRef); - return fPyHardRef; - } - if (fPyWeakRef) { - PyObject* disp = CPyCppyy_GetWeakRef(fPyWeakRef); - if (disp) { // dispatcher object disappeared? - if (borrowed) Py_DECREF(disp); - return disp; - } - } - return nullptr; -} - -//----------------------------------------------------------------------------- -CPyCppyy::DispatchPtr::DispatchPtr(PyObject* pyobj, bool strong) : fPyHardRef(nullptr) -{ - PythonGILRAII python_gil_raii; - if (strong) { - Py_INCREF(pyobj); - fPyHardRef = pyobj; - fPyWeakRef = nullptr; - } else { - fPyHardRef = nullptr; - fPyWeakRef = PyWeakref_NewRef(pyobj, nullptr); - } - ((CPPInstance*)pyobj)->SetDispatchPtr(this); -} - -//----------------------------------------------------------------------------- -CPyCppyy::DispatchPtr::DispatchPtr(const DispatchPtr& other, void* cppinst) : fPyWeakRef(nullptr) -{ - PythonGILRAII python_gil_raii; - PyObject* pyobj = other.Get(false /* not borrowed */); - fPyHardRef = pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; - if (fPyHardRef) ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); - Py_XDECREF(pyobj); -} - -//----------------------------------------------------------------------------- -CPyCppyy::DispatchPtr::~DispatchPtr() { -// if we're holding a hard reference, or holding weak reference while being part -// of a dispatcher intermediate, then this delete is from the C++ side, and Python -// is "notified" by nulling out the reference and an exception will be raised on -// continued access - PythonGILRAII python_gil_raii; - if (fPyWeakRef) { - PyObject* pyobj = CPyCppyy_GetWeakRef(fPyWeakRef); - if (pyobj && ((CPPScope*)Py_TYPE(pyobj))->fFlags & CPPScope::kIsPython) - ((CPPInstance*)pyobj)->GetObjectRaw() = nullptr; - Py_XDECREF(pyobj); - Py_DECREF(fPyWeakRef); - } else if (fPyHardRef) { - ((CPPInstance*)fPyHardRef)->GetObjectRaw() = nullptr; - Py_DECREF(fPyHardRef); - } -} - -//----------------------------------------------------------------------------- -CPyCppyy::DispatchPtr& CPyCppyy::DispatchPtr::assign(const DispatchPtr& other, void* cppinst) -{ - PythonGILRAII python_gil_raii; - if (this != &other) { - Py_XDECREF(fPyWeakRef); fPyWeakRef = nullptr; - Py_XDECREF(fPyHardRef); - PyObject* pyobj = other.Get(false /* not borrowed */); - fPyHardRef = pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; - if (fPyHardRef) ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); - Py_XDECREF(pyobj); - } - return *this; -} - -//----------------------------------------------------------------------------- -void CPyCppyy::DispatchPtr::PythonOwns() -{ - PythonGILRAII python_gil_raii; -// Python maintains the hardref, so only allowed a weakref here - if (fPyHardRef) { - fPyWeakRef = PyWeakref_NewRef(fPyHardRef, nullptr); - Py_DECREF(fPyHardRef); fPyHardRef = nullptr; - } -} - -//----------------------------------------------------------------------------- -void CPyCppyy::DispatchPtr::CppOwns() -{ - PythonGILRAII python_gil_raii; -// C++ maintains the hardref, keeping the PyObject alive w/o outstanding ref - if (fPyWeakRef) { - fPyHardRef = CPyCppyy_GetWeakRef(fPyWeakRef); - Py_DECREF(fPyWeakRef); fPyWeakRef = nullptr; - } -} diff --git a/src/CPyCppyy/src/Dispatcher.cxx b/src/CPyCppyy/src/Dispatcher.cxx deleted file mode 100644 index 12b8ee4..0000000 --- a/src/CPyCppyy/src/Dispatcher.cxx +++ /dev/null @@ -1,505 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Dispatcher.h" -#include "CPPScope.h" -#include "Cppyy.h" -#include "PyStrings.h" -#include "ProxyWrappers.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include - - -//---------------------------------------------------------------------------- -static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& mtCppName, std::ostringstream& code) -{ -// inject implementation for an overridden method - using namespace CPyCppyy; - -// method declaration - std::string retType = Cppyy::GetMethodReturnTypeAsString(method); - code << " " << retType << " " << mtCppName << "("; - -// build out the signature with predictable formal names - Cppyy::TCppIndex_t nArgs = Cppyy::GetMethodNumArgs(method); - std::vector argtypes; argtypes.reserve(nArgs); - for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { - argtypes.push_back(Cppyy::GetMethodArgCanonTypeAsString(method, i)); - if (i != 0) code << ", "; - code << argtypes.back() << " arg" << i; - } - code << ") "; - if (Cppyy::IsConstMethod(method)) code << "const "; - code << "{\n"; - -// on destruction, the Python object may go first, in which case provide a diagnostic -// warning (raising a PyException may not be possible as this could happen during -// program shutdown); note that this means that the actual result will be the default -// and the caller may need to act on that, but that's still an improvement over a -// possible crash - code << " CPyCppyy::PythonGILRAII python_gil_raii;\n" // acquire GIL - " PyObject* iself = (PyObject*)_internal_self;\n" - " if (!iself || iself == Py_None) {\n" - " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on " - "deleted python-side proxy\");\n" - " return"; - if (retType != "void") { - if (retType.back() != '*') - code << " (" << CPyCppyy::TypeManip::remove_const(retType) << "){}"; - else - code << " nullptr"; - } - code << ";\n" - " }\n" - " Py_INCREF(iself);\n"; - -// start actual function body - Utility::ConstructCallbackPreamble(retType, argtypes, code); - -// perform actual method call - code << " PyObject* mtPyName = PyUnicode_FromString(\"" << mtCppName << "\");\n" - " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, mtPyName"; - for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) - code << ", pyargs[" << i << "]"; - code << ", NULL);\n Py_DECREF(mtPyName);\n Py_DECREF(iself);\n"; - -// close - Utility::ConstructCallbackReturn(retType, (int)nArgs, code); -} - -//---------------------------------------------------------------------------- -namespace { - struct BaseInfo { - BaseInfo(Cppyy::TCppScope_t t, std::string&& bn, std::string&& bns) : - btype(t), bname(bn), bname_scoped(bns) {} - Cppyy::TCppScope_t btype; - std::string bname; - std::string bname_scoped; - }; - - typedef std::vector BaseInfos_t; - typedef std::vector Ctors_t; - typedef std::vector AllCtors_t; - typedef std::vector> CtorInfos_t; -} // unnamed namespace - -static void build_constructors( - const std::string& derivedName, const BaseInfos_t& base_infos, const AllCtors_t& ctors, - std::ostringstream& code, const CtorInfos_t& methods = CtorInfos_t{}, size_t idx = 0) -{ - if (idx < ctors.size()) { - for (const auto& method : ctors[idx]) { - size_t argsmin = (size_t)Cppyy::GetMethodReqArgs(method); - size_t argsmax = (size_t)Cppyy::GetMethodNumArgs(method); - for (size_t i = argsmin; i <= argsmax; ++i) { - CtorInfos_t methods1{methods}; - methods1.emplace_back(method, i); - build_constructors(derivedName, base_infos, ctors, code, methods1, idx+1); - } - } - } else { - // this is as deep as we go; start writing - code << " " << derivedName << "("; - - // declare arguments - std::vector arg_tots; arg_tots.reserve(methods.size()); - for (Ctors_t::size_type i = 0; i < methods.size(); ++i) { - const auto& cinfo = methods[i]; - if (i != 0 && (arg_tots.back() || 1 < arg_tots.size())) code << ", "; - size_t nArgs = cinfo.second; - arg_tots.push_back(i == 0 ? nArgs : nArgs+arg_tots.back()); - - if (i != 0) code << "__cppyy_internal::Sep*"; - size_t offset = (i != 0 ? arg_tots[i-1] : 0); - for (size_t j = 0; j < nArgs; ++j) { - if (i != 0 || j != 0) code << ", "; - code << Cppyy::GetMethodArgCanonTypeAsString(cinfo.first, j) << " a" << (j+offset); - } - } - code << ") : "; - - // pass arguments to base constructors - for (BaseInfos_t::size_type i = 0; i < base_infos.size(); ++i) { - if (i != 0) code << ", "; - code << base_infos[i].bname << "("; - size_t first = (i != 0 ? arg_tots[i-1] : 0); - for (size_t j = first; j < arg_tots[i]; ++j) { - if (j != first) code << ", "; - bool isRValue = CPyCppyy::TypeManip::compound(\ - Cppyy::GetMethodArgCanonTypeAsString(methods[i].first, j-first)) == "&&"; - if (isRValue) code << "std::move("; - code << "a" << j; - if (isRValue) code << ")"; - } - code << ")"; - } - code << " {}\n"; - } -} - -namespace { - -using namespace Cppyy; - -static inline -std::vector FindBaseMethod(TCppScope_t tbase, const std::string mtCppName) -{ -// Recursively walk the inheritance tree to find the overloads of the named method - std::vector result; - result = GetMethodsFromName(tbase, mtCppName); - if (result.empty()) { - for (TCppIndex_t ibase = 0; ibase < GetNumBases(tbase); ++ibase) { - TCppScope_t b = Cppyy::GetBaseScope(tbase, ibase); - result = FindBaseMethod(b, mtCppName); - if (!result.empty()) - break; - } - } - return result; -} - -} // unnamed namespace - -bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, std::ostringstream& err) -{ -// Scan all methods in dct and where it overloads base methods in klass, create -// dispatchers on the C++ side. Then interject the dispatcher class. - - if (!PyTuple_Check(bases) || !PyTuple_GET_SIZE(bases) || !dct || !PyDict_Check(dct)) { - err << "internal error: expected tuple of bases and proper dictionary"; - return false; - } - - if (!Utility::IncludePython()) { - err << "failed to include Python.h"; - return false; - } - -// collect all bases, error checking the hierarchy along the way - const Py_ssize_t nBases = PyTuple_GET_SIZE(bases); - BaseInfos_t base_infos; base_infos.reserve(nBases); - for (Py_ssize_t ibase = 0; ibase < nBases; ++ibase) { - if (!CPPScope_Check(PyTuple_GET_ITEM(bases, ibase))) - continue; - - Cppyy::TCppScope_t basetype = ((CPPScope*)PyTuple_GET_ITEM(bases, ibase))->fCppType; - - if (!basetype) { - err << "base class is incomplete"; - break; - } - - if (Cppyy::IsNamespace(basetype)) { - err << Cppyy::GetScopedFinalName(basetype) << " is a namespace"; - break; - } - - if (!Cppyy::HasVirtualDestructor(basetype)) { - const std::string& bname = Cppyy::GetScopedFinalName(basetype); - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)("class \""+bname+"\" has no virtual destructor").c_str(), 1) < 0) - return false; - } - - base_infos.emplace_back( - basetype, TypeManip::template_base(Cppyy::GetFinalName(basetype)), Cppyy::GetScopedFinalName(basetype)); - } - -// TODO: check deep hierarchy for multiple inheritance - bool isDeepHierarchy = klass->fCppType && base_infos.front().btype != klass->fCppType; - -// once classes can be extended, should consider re-use; for now, since derived -// python classes can differ in what they override, simply use different shims - static int counter = 0; - std::ostringstream osname; - osname << "Dispatcher" << ++counter; - const std::string& derivedName = osname.str(); - -// generate proxy class with the relevant method dispatchers - std::ostringstream code; - -// start class declaration - code << "namespace __cppyy_internal {\n" - << "class " << derivedName << " : "; - for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { - if (ibase != 0) code << ", "; - code << "public ::" << base_infos[ibase].bname_scoped; - } - code << " {\n"; - if (!isDeepHierarchy) - code << "protected:\n CPyCppyy::DispatchPtr _internal_self;\n"; - code << "public:\n"; - -// add a virtual destructor for good measure, which is allowed to be "overridden" by -// the conventional __destruct__ method (note that __del__ is always called, too, if -// provided, but only when the Python object goes away; furthermore, if the Python -// object goes before the C++ one, only __del__ is called) - if (PyMapping_HasKeyString(dct, (char*)"__destruct__")) { - code << " virtual ~" << derivedName << "() {\n" - " CPyCppyy::PythonGILRAII python_gil_raii;\n" - " PyObject* iself = (PyObject*)_internal_self;\n" - " if (!iself || iself == Py_None)\n" - " return;\n" // safe, as destructor always returns void - " Py_INCREF(iself);\n" - " PyObject* mtPyName = PyUnicode_FromString(\"__destruct__\");\n" - " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, mtPyName, NULL);\n" - " Py_DECREF(mtPyName);\n Py_DECREF(iself);\n"; - - // this being a destructor, print on exception rather than propagate using the - // magic C++ exception ... - code << " if (!pyresult) PyErr_Print();\n" - " else { Py_DECREF(pyresult); }\n" - " }\n"; - } else - code << " virtual ~" << derivedName << "() {}\n"; - -// methods: first collect all callables, then get overrides from base classes, for -// those that are still missing, search the hierarchy - PyObject* clbs = PyDict_New(); - PyObject* items = PyDict_Items(dct); - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); ++i) { - PyObject* value = PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 1); - if (PyCallable_Check(value)) - PyDict_SetItem(clbs, PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 0), value); - } - Py_DECREF(items); - if (PyDict_DelItem(clbs, PyStrings::gInit) != 0) - PyErr_Clear(); - -// protected methods and data need their access changed in the C++ trampoline and then -// exposed on the Python side; so, collect their names as we go along - std::set protected_names; - -// simple case: methods from current class (collect constructors along the way) - int has_default = 0, has_cctor = 0, has_ctors = 0, has_tmpl_ctors = 0; - AllCtors_t ctors{base_infos.size()}; - for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { - const auto& binfo = base_infos[ibase]; - - std::vector methods; - Cppyy::GetClassMethods(binfo.btype, methods); - bool cctor_found = false, default_found = false, any_ctor_found = false; - for (auto &method : methods) { - if (Cppyy::IsConstructor(method)) { - any_ctor_found = true; - if ((Cppyy::IsPublicMethod(method) || Cppyy::IsProtectedMethod(method)) && - !Cppyy::IsDeletedMethod(method)) { - Cppyy::TCppIndex_t nreq = Cppyy::GetMethodReqArgs(method); - if (nreq == 0) default_found = true; - else if (!cctor_found && nreq == 1) { - const std::string& argtype = Cppyy::GetMethodArgCanonTypeAsString(method, 0); - if (TypeManip::compound(argtype) == "&" && TypeManip::clean_type(argtype, false) == binfo.bname_scoped) - cctor_found = true; - } - ctors[ibase].push_back(method); - } - continue; - } - - std::string mtCppName = Cppyy::GetName(Cppyy::TCppScope_t(method.data)); - PyObject* key = CPyCppyy_PyText_FromString(mtCppName.c_str()); - int contains = PyDict_Contains(dct, key); - if (contains == -1) PyErr_Clear(); - if (contains != 1) { - Py_DECREF(key); - - // if the method is protected, we expose it through re-declaration and forwarding (using - // does not work here b/c there may be private overloads) - if (Cppyy::IsProtectedMethod(method)) { - protected_names.insert(mtCppName); - - code << " " << Cppyy::GetMethodReturnTypeAsString(method) << " " << mtCppName << "("; - Cppyy::TCppIndex_t nArgs = Cppyy::GetMethodNumArgs(method); - for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { - if (i != 0) code << ", "; - code << Cppyy::GetMethodArgCanonTypeAsString(method, i) << " arg" << i; - } - code << ") "; - if (Cppyy::IsConstMethod(method)) code << "const "; - code << "{\n return " << binfo.bname << "::" << mtCppName << "("; - for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { - if (i != 0) code << ", "; - if (Cppyy::IsRValueReferenceType(Cppyy::GetMethodArgType(method, i))) - code << "std::move(arg" << i << ")"; - else - code << "arg" << i; - } - code << ");\n }\n"; - } - - continue; - } - - InjectMethod(method, mtCppName, code); - - if (PyDict_DelItem(clbs, key) != 0) - PyErr_Clear(); // happens for overloads - Py_DECREF(key); - } - - // support for templated ctors in single inheritance (TODO: also multi possible?) - if (base_infos.size() == 1) { - std::vector templ_methods; - Cppyy::GetTemplatedMethods(binfo.btype, templ_methods); - for (auto &method : templ_methods) { - if (Cppyy::IsConstructor(method)) { - any_ctor_found = true; - has_tmpl_ctors += 1; - break; // one suffices to map as argument packs are used - } - } - } - - // count the cctors and default ctors to determine whether each base has one - if (cctor_found || (!cctor_found && !any_ctor_found)) has_cctor += 1; - if (default_found || (!default_found && !any_ctor_found)) has_default += 1; - if (any_ctor_found && !has_tmpl_ctors) has_ctors += 1; - } - -// try to locate left-overs in base classes - for (const auto& binfo : base_infos) { - if (PyDict_Size(clbs)) { - size_t nbases = Cppyy::GetNumBases(binfo.btype); - for (size_t ibase = 0; ibase < nbases; ++ibase) { - Cppyy::TCppScope_t tbase = - (Cppyy::TCppScope_t) Cppyy::GetBaseScope(binfo.btype, ibase); - - PyObject* keys = PyDict_Keys(clbs); - for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); ++i) { - // TODO: should probably invert this looping; but that makes handling overloads clunky - PyObject* key = PyList_GET_ITEM(keys, i); - std::string mtCppName = CPyCppyy_PyText_AsString(key); - const auto& methods = FindBaseMethod(tbase, mtCppName); - for (auto method : methods) - InjectMethod(method, mtCppName, code); - if (!methods.empty()) { - if (PyDict_DelItem(clbs, key) != 0) PyErr_Clear(); - } - } - Py_DECREF(keys); - } - } - } - Py_DECREF(clbs); - -// constructors: build up from the argument types of the base class, for use by the Python -// derived class (inheriting with/ "using" does not work b/c base class constructors may -// have been deleted), - build_constructors(derivedName, base_infos, ctors, code); - -// for working with C++ templates, additional constructors are needed to make -// sure the python object is properly carried, but they can only be generated -// if the base class supports them - if (1 < nBases && (!has_ctors || has_default == nBases)) - code << " " << derivedName << "() {}\n"; - if (has_cctor == nBases) { - code << " " << derivedName << "(const " << derivedName << "& other) : "; - for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { - if (ibase != 0) code << ", "; - code << base_infos[ibase].bname << "(other)"; - } - if (!isDeepHierarchy) - code << ", _internal_self(other._internal_self, this)"; - code << " {}\n"; - } - if (has_tmpl_ctors && base_infos.size() == 1) { - // support for templated ctors in single inheritance (TODO: also multi possible?) - code << " template\n " << derivedName << "(Args... args) : " - << base_infos[0].bname << "(args...) {}\n"; - } - -// destructor: default is fine - -// pull in data members that are protected - bool setPublic = false; - for (const auto& binfo : base_infos) { - std::vector datamems; - Cppyy::GetDatamembers(binfo.btype, datamems); - for (auto data : datamems) { - if (Cppyy::IsProtectedData(data)) { - const std::string dm_name = Cppyy::GetFinalName(data); - if (dm_name != "_internal_self") { - const std::string& dname = Cppyy::GetFinalName(data); - protected_names.insert(dname); - if (!setPublic) { - code << "public:\n"; - setPublic = true; - } - code << " using " << binfo.bname << "::" << dname << ";\n"; - } - } - } - } - -// initialize the dispatch pointer for all direct bases that have one - BaseInfos_t::size_type disp_inited = 0; - code << "public:\n static void _init_dispatchptr(" << derivedName << "* inst, PyObject* self) {\n"; - if (1 < base_infos.size()) { - for (const auto& binfo : base_infos) { - if (Cppyy::CheckDatamember(binfo.btype, "_internal_self")) { - code << " " << binfo.bname << "::_init_dispatchptr(inst, self);\n"; - disp_inited += 1; - } - } - } -// The dispatch initializer is only used in constructors, and C++ object start out -// as owned by C++, with Python ownership explicitly set only later. To match, the -// dispatch pointer needs to start out with a hard reference, i.e. C++ ownership of -// the dispatch object. If the constructor has __creates__ set to True (default), -// then a call to PythonOwns() will switch the hard ref to a weak ref, preventing -// accidental circular references. - if (disp_inited != base_infos.size()) - code << " new ((void*)&inst->_internal_self) CPyCppyy::DispatchPtr{self, true};\n"; - code << " }"; - -// provide an accessor to re-initialize after round-tripping from C++ (internal) - code << "\n static PyObject* _get_dispatch(" << derivedName << "* inst) {\n" - " CPyCppyy::PythonGILRAII python_gil_raii;\n" - " PyObject* res = (PyObject*)inst->_internal_self;\n" - " Py_XINCREF(res); return res;\n }"; - -// finish class declaration - code << "};\n}"; - -// finally, compile the code - if (!Cppyy::Compile(code.str())) { - err << "failed to compile the dispatcher code"; - return false; - } - -// keep track internally of the actual C++ type (this is used in -// CPPConstructor to call the dispatcher's one instead of the base) - Cppyy::TCppScope_t disp = Cppyy::GetFullScope("__cppyy_internal::"+derivedName); - if (!disp) { - err << "failed to retrieve the internal dispatcher"; - return false; - } - klass->fCppType = disp; - -// at this point, the dispatcher only lives in C++, as opposed to regular classes -// that are part of the hierarchy in Python, so create it, which will cache it for -// later use by e.g. the MemoryRegulator - unsigned int flags = (unsigned int)(klass->fFlags & CPPScope::kIsMultiCross); - PyObject* disp_proxy = CPyCppyy::CreateScopeProxy(disp, 0, flags); - if (flags) ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsMultiCross; - ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsPython; - -// finally, to expose protected members, copy them over from the C++ dispatcher base -// to the Python dictionary (the C++ dispatcher's Python proxy is not a base of the -// Python class to keep the inheritance tree intact) - for (const auto& name : protected_names) { - PyObject* disp_dct = PyObject_GetAttr(disp_proxy, PyStrings::gDict); - PyObject* pyf = PyMapping_GetItemString(disp_dct, (char*)name.c_str()); - if (pyf) { - PyObject_SetAttrString((PyObject*)klass, (char*)name.c_str(), pyf); - Py_DECREF(pyf); - } - Py_DECREF(disp_dct); - } - - Py_XDECREF(disp_proxy); - - return true; -} diff --git a/src/CPyCppyy/src/Dispatcher.h b/src/CPyCppyy/src/Dispatcher.h deleted file mode 100644 index f8d4dc5..0000000 --- a/src/CPyCppyy/src/Dispatcher.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef CPYCPPYY_DISPATCHER_H -#define CPYCPPYY_DISPATCHER_H - -// Standard -#include - -namespace CPyCppyy { - -class CPPScope; - -// helper that inserts dispatchers for virtual methods -bool InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, std::ostringstream& err); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_DISPATCHER_H diff --git a/src/CPyCppyy/src/Executors.cxx b/src/CPyCppyy/src/Executors.cxx deleted file mode 100644 index ce86a9d..0000000 --- a/src/CPyCppyy/src/Executors.cxx +++ /dev/null @@ -1,1264 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Cppyy.h" -#include "DeclareExecutors.h" -#include "CPPInstance.h" -#include "LowLevelViews.h" -#include "ProxyWrappers.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include -#include -#include -#include - - -//- data _____________________________________________________________________ -namespace CPyCppyy { - typedef std::unordered_map ExecFactories_t; - static ExecFactories_t gExecFactories; - - extern PyObject* gNullPtrObject; - - extern std::set gIteratorTypes; -} - - -//- helpers ------------------------------------------------------------------ -namespace { - -#ifdef WITH_THREAD - class GILControl { - public: - GILControl() : fSave(PyEval_SaveThread()) { } - ~GILControl() { - PyEval_RestoreThread(fSave); - } - private: - PyThreadState* fSave; - }; -#endif - -} // unnamed namespace - -#ifdef WITH_THREAD -#define CPPYY_IMPL_GILCALL(rtype, tcode) \ -static inline rtype GILCall##tcode( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CPyCppyy::CallContext* ctxt)\ -{ \ - if (!ReleasesGIL(ctxt)) \ - return Cppyy::Call##tcode(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs());\ - GILControl gc{}; \ - return Cppyy::Call##tcode(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs());\ -} -#else -#define CPPYY_IMPL_GILCALL(rtype, tcode) \ -static inline rtype GILCall##tcode( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CPyCppyy::CallContext* ctxt)\ -{ \ - return Cppyy::Call##tcode(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs());\ -} -#endif - -CPPYY_IMPL_GILCALL(void, V) -CPPYY_IMPL_GILCALL(unsigned char, B) -CPPYY_IMPL_GILCALL(char, C) -CPPYY_IMPL_GILCALL(short, H) -CPPYY_IMPL_GILCALL(int, I) -CPPYY_IMPL_GILCALL(long, L) -CPPYY_IMPL_GILCALL(PY_LONG_LONG, LL) -CPPYY_IMPL_GILCALL(float, F) -CPPYY_IMPL_GILCALL(double, D) -CPPYY_IMPL_GILCALL(PY_LONG_DOUBLE, LD) -CPPYY_IMPL_GILCALL(void*, R) - -static inline Cppyy::TCppObject_t GILCallO(Cppyy::TCppMethod_t method, - Cppyy::TCppObject_t self, CPyCppyy::CallContext* ctxt, Cppyy::TCppScope_t klass) -{ - Cppyy::TCppType_t klass_ty = Cppyy::GetTypeFromScope(klass); -#ifdef WITH_THREAD - if (!ReleasesGIL(ctxt)) -#endif - return Cppyy::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), klass_ty); -#ifdef WITH_THREAD - GILControl gc{}; - return Cppyy::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), klass_ty); -#endif -} - -static inline Cppyy::TCppObject_t GILCallConstructor( - Cppyy::TCppMethod_t method, Cppyy::TCppScope_t klass, CPyCppyy::CallContext* ctxt) -{ -#ifdef WITH_THREAD - if (!ReleasesGIL(ctxt)) -#endif - return Cppyy::CallConstructor(method, klass, ctxt->GetEncodedSize(), ctxt->GetArgs()); -#ifdef WITH_THREAD - GILControl gc{}; - return Cppyy::CallConstructor(method, klass, ctxt->GetEncodedSize(), ctxt->GetArgs()); -#endif -} - -static inline PyObject* CPyCppyy_PyText_FromLong(long cl) -{ -// python chars are range(256) - if (cl < -256 || cl > 255) { - PyErr_SetString(PyExc_ValueError, "char conversion out of range"); - return nullptr; - } - int c = (int)cl; - if (c < 0) return CPyCppyy_PyText_FromFormat("%c", 256 - std::abs(c)); - return CPyCppyy_PyText_FromFormat("%c", c); -} - -static inline PyObject* CPyCppyy_PyText_FromULong(unsigned long uc) -{ -// TODO: range check here? - if (255 < uc) { - PyErr_SetString(PyExc_ValueError, "char conversion out of range"); - return nullptr; - } - int c = (int)uc; - return CPyCppyy_PyText_FromFormat("%c", c); -} - -static inline PyObject* CPyCppyy_PyBool_FromLong(long b) -{ - PyObject* result = (bool)b ? Py_True : Py_False; - Py_INCREF(result); - return result; -} - - -//- base executor implementation --------------------------------------------- -CPyCppyy::Executor::~Executor() -{ - /* empty */ -} - -//- executors for built-ins -------------------------------------------------- -PyObject* CPyCppyy::BoolExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python bool return value - bool retval = GILCallB(method, self, ctxt); - PyObject* result = retval ? Py_True : Py_False; - Py_INCREF(result); - return result; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::BoolConstRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python bool return value - return CPyCppyy_PyBool_FromLong(*((bool*)GILCallR(method, self, ctxt))); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CharExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute , construct python string return value -// with the single char - return CPyCppyy_PyText_FromLong((int)GILCallC(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CharConstRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char - return CPyCppyy_PyText_FromLong(*((char*)GILCallR(method, self, ctxt))); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::UCharExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char - return CPyCppyy_PyText_FromLong((unsigned char)GILCallB(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::UCharConstRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char from the pointer return - return CPyCppyy_PyText_FromLong(*((unsigned char*)GILCallR(method, self, ctxt))); -} - -//---------------------------------------------------------------------------- -PyObject *CPyCppyy::Int8ConstRefExecutor::Execute(Cppyy::TCppMethod_t method, - Cppyy::TCppObject_t self, - CallContext *ctxt) { - return PyInt_FromLong(*((int8_t *)GILCallR(method, self, ctxt))); -} - -//---------------------------------------------------------------------------- -PyObject *CPyCppyy::UInt8ConstRefExecutor::Execute(Cppyy::TCppMethod_t method, - Cppyy::TCppObject_t self, - CallContext *ctxt) { - return PyInt_FromLong(*((uint8_t *)GILCallR(method, self, ctxt))); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::WCharExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single wide char - wchar_t res = (wchar_t)GILCallL(method, self, ctxt); - return PyUnicode_FromWideChar(&res, 1); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Char16Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char16 - char16_t res = (char16_t)GILCallL(method, self, ctxt); - return PyUnicode_DecodeUTF16((const char*)&res, sizeof(char16_t), nullptr, nullptr); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Char32Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value -// with the single char32 - char32_t res = (char32_t)GILCallL(method, self, ctxt); - return PyUnicode_DecodeUTF32((const char*)&res, sizeof(char32_t), nullptr, nullptr); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::IntExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python int return value - return PyInt_FromLong((int)GILCallI(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Int8Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python int return value - return PyInt_FromLong((int8_t)GILCallC(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::UInt8Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python int return value - return PyInt_FromLong((uint8_t)GILCallB(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::ShortExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python int return value - return PyInt_FromLong((short)GILCallH(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::LongExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python long return value - return PyLong_FromLong((long)GILCallL(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::ULongExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unsigned long return value - return PyLong_FromUnsignedLong((unsigned long)GILCallLL(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::LongLongExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python long long return value - PY_LONG_LONG result = GILCallLL(method, self, ctxt); - return PyLong_FromLongLong(result); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::ULongLongExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unsigned long long return value - PY_ULONG_LONG result = (PY_ULONG_LONG)GILCallLL(method, self, ctxt); - return PyLong_FromUnsignedLongLong(result); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::FloatExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python float return value - return PyFloat_FromDouble((double)GILCallF(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::DoubleExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python float return value - return PyFloat_FromDouble((double)GILCallD(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::LongDoubleExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python float return value - return PyFloat_FromDouble((double)GILCallLD(method, self, ctxt)); -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::RefExecutor::SetAssignable(PyObject* pyobject) -{ -// prepare "buffer" for by-ref returns, used with __setitem__ - if (pyobject) { - Py_INCREF(pyobject); - fAssignable = pyobject; - return true; - } - - fAssignable = nullptr; - return false; -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_REFEXEC(name, type, stype, F1, F2) \ -PyObject* CPyCppyy::name##RefExecutor::Execute( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) \ -{ \ - type* ref = (type*)GILCallR(method, self, ctxt); \ - if (!ref) { /* can happen if wrapper compilation fails */ \ - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");\ - return nullptr; \ - } \ - if (!fAssignable) \ - return F1((stype)*ref); \ - else { \ - *ref = (type)F2(fAssignable); \ - Py_DECREF(fAssignable); \ - fAssignable = nullptr; \ - if (*ref == (type)-1 && PyErr_Occurred()) \ - return nullptr; \ - Py_INCREF(Py_None); \ - return Py_None; \ - } \ -} - -CPPYY_IMPL_REFEXEC(Bool, bool, long, CPyCppyy_PyBool_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(Char, char, long, CPyCppyy_PyText_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(UChar, unsigned char, unsigned long, CPyCppyy_PyText_FromULong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(Int8, int8_t, long, PyInt_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(UInt8, uint8_t, unsigned long, PyInt_FromLong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(Short, short, long, PyInt_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(UShort, unsigned short, unsigned long, PyInt_FromLong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(Int, int, long, PyInt_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(UInt, unsigned int, unsigned long, PyLong_FromUnsignedLong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(Long, long, long, PyLong_FromLong, PyLong_AsLong) -CPPYY_IMPL_REFEXEC(ULong, unsigned long, unsigned long, PyLong_FromUnsignedLong, PyLongOrInt_AsULong) -CPPYY_IMPL_REFEXEC(LongLong, PY_LONG_LONG, PY_LONG_LONG, PyLong_FromLongLong, PyLong_AsLongLong) -CPPYY_IMPL_REFEXEC(ULongLong, PY_ULONG_LONG, PY_ULONG_LONG, PyLong_FromUnsignedLongLong, PyLongOrInt_AsULong64) -CPPYY_IMPL_REFEXEC(Float, float, double, PyFloat_FromDouble, PyFloat_AsDouble) -CPPYY_IMPL_REFEXEC(Double, double, double, PyFloat_FromDouble, PyFloat_AsDouble) -CPPYY_IMPL_REFEXEC(LongDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, PyFloat_FromDouble, PyFloat_AsDouble) - -template -static inline PyObject* PyComplex_FromComplex(const std::complex& c) { - return PyComplex_FromDoubles(c.real(), c.imag()); -} - -template -static inline std::complex PyComplex_AsComplex(PyObject* pycplx) { - Py_complex cplx = PyComplex_AsCComplex(pycplx); - return std::complex(cplx.real, cplx.imag); -} - -CPPYY_IMPL_REFEXEC(ComplexD, std::complex, - std::complex, PyComplex_FromComplex, PyComplex_AsComplex) - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::STLStringRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , return python string return value - static Cppyy::TCppScope_t sSTLStringScope = Cppyy::GetFullScope("std::string"); - - std::string* result = (std::string*)GILCallR(method, self, ctxt); - if (!fAssignable) { - std::string* rescp = new std::string{*result}; - return BindCppObjectNoCast((void*)rescp, sSTLStringScope, CPPInstance::kIsOwner); - } - - if (!CPyCppyy_PyText_Check(fAssignable)) { - PyErr_Format(PyExc_TypeError, "wrong type in assignment (string expected)"); - return nullptr; - } - - *result = std::string( - CPyCppyy_PyText_AsString(fAssignable), CPyCppyy_PyText_GET_SIZE(fAssignable)); - - Py_DECREF(fAssignable); - fAssignable = nullptr; - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::VoidExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , return None - GILCallV(method, self, ctxt); - if (PyErr_Occurred()) return nullptr; - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CStringExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value - char* result = (char*)GILCallR(method, self, ctxt); - if (!result) { - Py_INCREF(PyStrings::gEmptyString); - return PyStrings::gEmptyString; - } - - return CPyCppyy_PyText_FromString(result); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CStringRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value - char** result = (char**)GILCallR(method, self, ctxt); - if (!result || !*result) { - Py_INCREF(PyStrings::gEmptyString); - return PyStrings::gEmptyString; - } - - return CPyCppyy_PyText_FromString(*result); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::WCStringExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unicode return value - wchar_t* result = (wchar_t*)GILCallR(method, self, ctxt); - if (!result) { - wchar_t w = L'\0'; - return PyUnicode_FromWideChar(&w, 0); - } - - return PyUnicode_FromWideChar(result, wcslen(result)); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CString16Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unicode return value - char16_t* result = (char16_t*)GILCallR(method, self, ctxt); - if (!result) { - char16_t w = u'\0'; - return PyUnicode_DecodeUTF16((const char*)&w, 0, nullptr, nullptr); - } - - return PyUnicode_DecodeUTF16((const char*)result, - std::char_traits::length(result)*sizeof(char16_t), nullptr, nullptr); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CString32Executor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python unicode return value - char32_t* result = (char32_t*)GILCallR(method, self, ctxt); - if (!result) { - char32_t w = U'\0'; - return PyUnicode_DecodeUTF32((const char*)&w, 0, nullptr, nullptr); - } - - return PyUnicode_DecodeUTF32((const char*)result, - std::char_traits::length(result)*sizeof(char32_t), nullptr, nullptr); -} - - -//- pointer/array executors -------------------------------------------------- -PyObject* CPyCppyy::VoidArrayExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python long return value - intptr_t* result = (intptr_t*)GILCallR(method, self, ctxt); - if (!result) { - Py_INCREF(gNullPtrObject); - return gNullPtrObject; - } - return CreatePointerView(result, fShape); -} - -//---------------------------------------------------------------------------- -#define CPPYY_IMPL_ARRAY_EXEC(name, type, suffix) \ -PyObject* CPyCppyy::name##ArrayExecutor::Execute( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) \ -{ \ - return CreateLowLevelView##suffix((type*)GILCallR(method, self, ctxt), fShape); \ -} - -CPPYY_IMPL_ARRAY_EXEC(Bool, bool, ) -CPPYY_IMPL_ARRAY_EXEC(SChar, signed char, ) -CPPYY_IMPL_ARRAY_EXEC(UChar, unsigned char, ) -CPPYY_IMPL_ARRAY_EXEC(Byte, std::byte, ) -CPPYY_IMPL_ARRAY_EXEC(Int8, int8_t, _i8) -CPPYY_IMPL_ARRAY_EXEC(UInt8, uint8_t, _i8) -CPPYY_IMPL_ARRAY_EXEC(Short, short, ) -CPPYY_IMPL_ARRAY_EXEC(UShort, unsigned short, ) -CPPYY_IMPL_ARRAY_EXEC(Int, int, ) -CPPYY_IMPL_ARRAY_EXEC(UInt, unsigned int, ) -CPPYY_IMPL_ARRAY_EXEC(Long, long, ) -CPPYY_IMPL_ARRAY_EXEC(ULong, unsigned long, ) -CPPYY_IMPL_ARRAY_EXEC(LLong, long long, ) -CPPYY_IMPL_ARRAY_EXEC(ULLong, unsigned long long, ) -CPPYY_IMPL_ARRAY_EXEC(Float, float, ) -CPPYY_IMPL_ARRAY_EXEC(Double, double, ) -CPPYY_IMPL_ARRAY_EXEC(LDouble, long double, ) -CPPYY_IMPL_ARRAY_EXEC(ComplexF, std::complex, ) -CPPYY_IMPL_ARRAY_EXEC(ComplexD, std::complex, ) -CPPYY_IMPL_ARRAY_EXEC(ComplexI, std::complex, ) -CPPYY_IMPL_ARRAY_EXEC(ComplexL, std::complex, ) - - -//- special cases ------------------------------------------------------------ -#define CPPYY_COMPLEX_EXEC(code, type) \ -PyObject* CPyCppyy::Complex##code##Executor::Execute( \ - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) \ -{ \ - static Cppyy::TCppScope_t scopeid = Cppyy::GetScope("std::complex<"#type">");\ - std::complex* result = \ - (std::complex*)GILCallO(method, self, ctxt, scopeid).data; \ - if (!result) { \ - PyErr_SetString(PyExc_ValueError, "NULL result where temporary expected");\ - return nullptr; \ - } \ - \ - PyObject* pyres = PyComplex_FromDoubles(result->real(), result->imag()); \ - ::operator delete(result); /* Cppyy::CallO calls ::operator new */ \ - return pyres; \ -} - -CPPYY_COMPLEX_EXEC(D, double) - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::STLStringExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value - -// TODO: make use of GILLCallS (?!) - static Cppyy::TCppScope_t sSTLStringScope = Cppyy::GetFullScope("std::string"); - std::string* result = (std::string*)GILCallO(method, self, ctxt, sSTLStringScope).data; - if (!result) - result = new std::string{}; - else if (PyErr_Occurred()) { - delete result; - return nullptr; - } - - return BindCppObjectNoCast((void*)result, sSTLStringScope, CPPInstance::kIsOwner); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::STLWStringExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python string return value - static Cppyy::TCppScope_t sSTLWStringScope = Cppyy::GetFullScope("std::wstring"); - std::wstring* result = (std::wstring*)GILCallO(method, self, ctxt, sSTLWStringScope).data; - if (!result) { - wchar_t w = L'\0'; - return PyUnicode_FromWideChar(&w, 0); - } - - PyObject* pyresult = PyUnicode_FromWideChar(result->c_str(), result->size()); - delete result; // Cppyy::CallO allocates and constructs a string, so it must be properly destroyed - - return pyresult; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstancePtrExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python proxy object return value - return BindCppObject((void*)GILCallR(method, self, ctxt), fClass); -} - -//---------------------------------------------------------------------------- -CPyCppyy::InstanceExecutor::InstanceExecutor(Cppyy::TCppScope_t klass) : - fClass(klass), fFlags(CPPInstance::kIsValue | CPPInstance::kIsOwner) -{ - /* empty */ -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execution will bring a temporary in existence - Cppyy::TCppObject_t value = GILCallO(method, self, ctxt, fClass); - - if (!value) { - if (!PyErr_Occurred()) // callee may have set a python error itself - PyErr_SetString(PyExc_ValueError, "nullptr result where temporary expected"); - return nullptr; - } - -// the result can then be bound - PyObject* pyobj = BindCppObjectNoCast(value, fClass, fFlags); - if (!pyobj) - return nullptr; - -// python ref counting will now control this object's life span; it will be -// deleted b/c it is marked as a by-value object owned by python (from fFlags) - return pyobj; -} - - -//---------------------------------------------------------------------------- -CPyCppyy::IteratorExecutor::IteratorExecutor(Cppyy::TCppScope_t klass) : - InstanceExecutor(klass) -{ - fFlags |= CPPInstance::kNoMemReg | CPPInstance::kNoWrapConv; // adds to flags from base class -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// executor binds the result to the left-hand side, overwriting if an old object - PyObject* result = BindCppObject((void*)GILCallR(method, self, ctxt), fClass); - if (!result || !fAssignable) - return result; - else { - // this generic code is quite slow compared to its C++ equivalent ... - PyObject* assign = PyObject_GetAttr(result, PyStrings::gAssign); - if (!assign) { - PyErr_Clear(); - PyObject* descr = PyObject_Str(result); - if (descr && CPyCppyy_PyText_CheckExact(descr)) { - PyErr_Format(PyExc_TypeError, "cannot assign to return object (%s)", - CPyCppyy_PyText_AsString(descr)); - } else { - PyErr_SetString(PyExc_TypeError, "cannot assign to result"); - } - Py_XDECREF(descr); - Py_DECREF(result); - Py_DECREF(fAssignable); fAssignable = nullptr; - return nullptr; - } - - PyObject* res2 = PyObject_CallFunction(assign, const_cast("O"), fAssignable); - - Py_DECREF(assign); - Py_DECREF(result); - Py_DECREF(fAssignable); fAssignable = nullptr; - - if (res2) { - Py_DECREF(res2); // typically, *this from operator=() - Py_RETURN_NONE; - } - - return nullptr; - } -} - -//---------------------------------------------------------------------------- -static inline PyObject* SetInstanceCheckError(PyObject* pyobj) { - PyObject* pystr = PyObject_Str(pyobj); - if (pystr) { - PyErr_Format(PyExc_TypeError, - "C++ object expected, got %s", CPyCppyy_PyText_AsString(pystr)); - Py_DECREF(pystr); - } else - PyErr_SetString(PyExc_TypeError, "C++ object expected"); - return nullptr; -} - -PyObject* CPyCppyy::InstancePtrPtrExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python C++ proxy object -// return ptr value - if (fAssignable && !CPPInstance_Check(fAssignable)) - return SetInstanceCheckError(fAssignable); - - void** result = (void**)GILCallR(method, self, ctxt); - if (!fAssignable) - return BindCppObject((void*)result, fClass, - CPPInstance::kIsPtrPtr | CPPInstance::kIsReference); - - CPPInstance* cppinst = (CPPInstance*)fAssignable; - *result = cppinst->GetObject(); - - Py_DECREF(fAssignable); - fAssignable = nullptr; - - Py_RETURN_NONE; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstancePtrRefExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct python C++ proxy object -// ignoring ref) return ptr value - if (fAssignable && !CPPInstance_Check(fAssignable)) - return SetInstanceCheckError(fAssignable); - - void** result = (void**)GILCallR(method, self, ctxt); - if (!fAssignable) - return BindCppObject(*result, fClass); - - CPPInstance* cppinst = (CPPInstance*)fAssignable; - *result = cppinst->GetObject(); - - Py_DECREF(fAssignable); - fAssignable = nullptr; - - Py_RETURN_NONE; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::InstanceArrayExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , construct TupleOfInstances from -// return value - return BindCppObjectArray((void*)GILCallR(method, self, ctxt), fClass, {fSize}); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::ConstructorExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t klass, CallContext* ctxt) -{ -// package return address in PyObject* for caller to handle appropriately (see -// CPPConstructor for the actual build of the PyObject) - return (PyObject*)GILCallConstructor(method, Cppyy::TCppScope_t(klass.data), ctxt).data; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::PyObjectExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , return python object - return (PyObject*)GILCallR(method, self, ctxt); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::FunctionPointerExecutor::Execute( - Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) -{ -// execute with argument , return std::function from func ptr - -// A function pointer in clang is represented by a Type, not a FunctionDecl and it's -// not possible to get the latter from the former: the backend will need to support -// both. Since that is far in the future, we'll use a std::function instead. - void* address = (void*)GILCallR(method, self, ctxt); - if (address) - return Utility::FuncPtr2StdFunction(fRetType, fSignature, address); - -// There's currently now way to return a typed function nullptr, but given that -// this is a return value (thus can not be set) and overloading function based -// on function pointer return type is likely uncommon ... - Py_INCREF(gNullPtrObject); - return gNullPtrObject; -} - -//- factories ---------------------------------------------------------------- -CPyCppyy::Executor* CPyCppyy::CreateExecutor(const std::string& fullType, cdims_t dims) -{ -// The matching of the fulltype to an executor factory goes through up to 4 levels: -// 1) full, qualified match -// 2) drop '&' as by ref/full type is often pretty much the same python-wise -// 3) C++ classes, either by ref/ptr or by value -// 4) additional special case for enums -// -// If all fails, void is used, which will cause the return type to be ignored on use - - if (fullType.empty()) - return nullptr; - -// an exactly matching executor is best - ExecFactories_t::iterator h = gExecFactories.find(fullType); - if (h != gExecFactories.end()) - return (h->second)(dims); - -// resolve typedefs etc. - const std::string resolvedType = Cppyy::ResolveName(fullType); - -// a full, qualified matching executor is preferred - if (resolvedType != fullType) { - h = gExecFactories.find(resolvedType); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -//-- nothing? ok, collect information about the type and possible qualifiers/decorators - bool isConst = strncmp(resolvedType.c_str(), "const", 5) == 0; - const std::string& cpd = TypeManip::compound(resolvedType); - std::string realType = TypeManip::clean_type(resolvedType, false); - -// accept unqualified type (as python does not know about qualifiers) - h = gExecFactories.find(realType + cpd); - if (h != gExecFactories.end()) - return (h->second)(dims); - -// drop const, as that is mostly meaningless to python (with the exception -// of c-strings, but those are specialized in the converter map) - if (isConst) { - realType = TypeManip::remove_const(realType); - h = gExecFactories.find(realType + cpd); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -// simple array types - if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '*') == cpd.size()) { - h = gExecFactories.find(realType + " ptr"); - if (h != gExecFactories.end()) - return (h->second)((!dims || dims.ndim() < (dim_t)cpd.size()) ? dims_t(cpd.size()) : dims); - } - -//-- still nothing? try pointer instead of array (for builtins) - if (cpd == "[]") { - h = gExecFactories.find(realType + "*"); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -// C++ classes and special cases - Executor* result = 0; - if (Cppyy::TCppScope_t klass = Cppyy::GetFullScope(realType)) { - if (Utility::IsSTLIterator(realType) || gIteratorTypes.find(fullType) != gIteratorTypes.end()) { - if (cpd == "") - return new IteratorExecutor(klass); - } - - if (cpd == "") - result = new InstanceExecutor(klass); - else if (cpd == "&") - result = new InstanceRefExecutor(klass); - else if (cpd == "**" || cpd == "*[]" || cpd == "&*") - result = new InstancePtrPtrExecutor(klass); - else if (cpd == "*&") - result = new InstancePtrRefExecutor(klass); - else if (cpd == "[]") { - Py_ssize_t asize = TypeManip::array_size(resolvedType); - if (0 < asize) - result = new InstanceArrayExecutor(klass, asize); - else - result = new InstancePtrRefExecutor(klass); - } else - result = new InstancePtrExecutor(klass); - } else if (resolvedType.find("(*)") != std::string::npos || - (resolvedType.find("::*)") != std::string::npos)) { - // this is a function pointer - // TODO: find better way of finding the type - auto pos1 = resolvedType.find('('); - auto pos2 = resolvedType.find("*)"); - auto pos3 = resolvedType.rfind(')'); - result = new FunctionPointerExecutor( - resolvedType.substr(0, pos1), resolvedType.substr(pos2+2, pos3-pos2-1)); - } else { - // unknown: void* may work ("user knows best"), void will fail on use of return value - h = (cpd == "") ? gExecFactories.find("void") : gExecFactories.find("void ptr"); - } - - if (!result && h != gExecFactories.end()) - // executor factory available, use it to create executor - result = (h->second)(dims); - - return result; // may still be null -} - -CPyCppyy::Executor* CPyCppyy::CreateExecutor(Cppyy::TCppType_t type, cdims_t dims) -{ -// The matching of the fulltype to an executor factory goes through up to 4 levels: -// 1) full, qualified match -// 2) drop '&' as by ref/full type is often pretty much the same python-wise -// 3) C++ classes, either by ref/ptr or by value -// 4) additional special case for enums -// -// If all fails, void is used, which will cause the return type to be ignored on use - -// an exactly matching executor is best - std::string fullType = Cppyy::GetTypeAsString(type); - if (fullType.size() >= 2 && fullType.compare(fullType.size() - 2, 2, " &") == 0) - fullType = fullType.substr(0, fullType.size() - 2) + "&"; - - ExecFactories_t::iterator h = gExecFactories.find(fullType); - if (h != gExecFactories.end()) - return (h->second)(dims); - -// resolve typedefs etc. - Cppyy::TCppType_t resolvedType = Cppyy::ResolveType(type); - { - // if resolvedType is a reference to enum - // then it should be reduced to reference - // to the underlying interger - resolvedType = Cppyy::ResolveEnumReferenceType(resolvedType); - // similarly for pointers - resolvedType = Cppyy::ResolveEnumPointerType(resolvedType); - } - // FIXME: avoid string comparisons and parsing - std::string resolvedTypeStr = Cppyy::GetTypeAsString(resolvedType); - if (Cppyy::IsFunctionPointerType(resolvedType)) { - resolvedTypeStr.erase(std::remove(resolvedTypeStr.begin(), resolvedTypeStr.end(), ' '), resolvedTypeStr.end()); - if (resolvedTypeStr.rfind("(void)") != std::string::npos) - resolvedTypeStr = resolvedTypeStr.substr(0, resolvedTypeStr.size() - 6) + "()"; - } - -// a full, qualified matching executor is preferred - if (resolvedTypeStr != fullType) { - h = gExecFactories.find(resolvedTypeStr); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -//-- nothing? ok, collect information about the type and possible qualifiers/decorators - bool isConst = strncmp(resolvedTypeStr.c_str(), "const", 5) == 0; - const std::string& cpd = TypeManip::compound(resolvedTypeStr); - Cppyy::TCppType_t realType = Cppyy::IsFunctionPointerType(resolvedType) ? resolvedType : Cppyy::GetRealType(resolvedType); - std::string realTypeStr = Cppyy::IsFunctionPointerType(resolvedType) ? resolvedTypeStr : Cppyy::GetTypeAsString(realType); - const std::string compounded = cpd.empty() ? realTypeStr : realTypeStr + cpd; - -// accept unqualified type (as python does not know about qualifiers) - h = gExecFactories.find(compounded); - if (h != gExecFactories.end()) - return (h->second)(dims); - -// drop const, as that is mostly meaningless to python (with the exception -// of c-strings, but those are specialized in the converter map) - if (isConst) { - realTypeStr = TypeManip::remove_const(realTypeStr); - h = gExecFactories.find(compounded); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -// simple array types - if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '*') == cpd.size()) { - h = gExecFactories.find(realTypeStr + " ptr"); - if (h != gExecFactories.end()) - return (h->second)((!dims || dims.ndim() < (dim_t)cpd.size()) ? dims_t(cpd.size()) : dims); - } - -//-- still nothing? try pointer instead of array (for builtins) - if (cpd == "[]") { - h = gExecFactories.find(realTypeStr + "*"); - if (h != gExecFactories.end()) - return (h->second)(dims); - } - -// C++ classes and special cases - Executor* result = 0; - if (Cppyy::IsClassType(realType)) { - Cppyy::TCppScope_t klass = Cppyy::GetScopeFromType(realType); - if (resolvedTypeStr.find("iterator") != std::string::npos || gIteratorTypes.find(fullType) != gIteratorTypes.end()) { - if (cpd == "") - return new IteratorExecutor(klass); - } - - if (cpd == "") - result = new InstanceExecutor(klass); - else if (cpd == "&") - result = new InstanceRefExecutor(klass); - else if (cpd == "**" || cpd == "*[]" || cpd == "&*") - result = new InstancePtrPtrExecutor(klass); - else if (cpd == "*&") - result = new InstancePtrRefExecutor(klass); - else if (cpd == "[]") { - Py_ssize_t asize = TypeManip::array_size(resolvedTypeStr); - if (0 < asize) - result = new InstanceArrayExecutor(klass, asize); - else - result = new InstancePtrRefExecutor(klass); - } else - result = new InstancePtrExecutor(klass); - } else if (realTypeStr.find("(*)") != std::string::npos || - (realTypeStr.find("::*)") != std::string::npos)) { - // this is a function pointer - // TODO: find better way of finding the type - auto pos1 = realTypeStr.find('('); - auto pos2 = realTypeStr.find("*)"); - auto pos3 = realTypeStr.rfind(')'); - result = new FunctionPointerExecutor( - realTypeStr.substr(0, pos1), realTypeStr.substr(pos2+2, pos3-pos2-1)); - } else { - // unknown: void* may work ("user knows best"), void will fail on use of return value - h = (cpd == "") ? gExecFactories.find("void") : gExecFactories.find("void ptr"); - } - - if (!result && h != gExecFactories.end()) - // executor factory available, use it to create executor - result = (h->second)(dims); - - return result; // may still be null -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -void CPyCppyy::DestroyExecutor(Executor* p) -{ - if (p && p->HasState()) - delete p; // state-less executors are always shared -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::RegisterExecutor(const std::string& name, ef_t fac) -{ -// register a custom executor - auto f = gExecFactories.find(name); - if (f != gExecFactories.end()) - return false; - - gExecFactories[name] = fac; - return true; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::RegisterExecutorAlias(const std::string& name, const std::string& target) -{ -// register a custom executor that is a reference to an existing converter - auto f = gExecFactories.find(name); - if (f != gExecFactories.end()) - return false; - - auto t = gExecFactories.find(target); - if (t == gExecFactories.end()) - return false; - - gExecFactories[name] = t->second; - return true; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -bool CPyCppyy::UnregisterExecutor(const std::string& name) -{ -// remove a custom executor - auto f = gExecFactories.find(name); - if (f != gExecFactories.end()) { - gExecFactories.erase(f); - return true; - } - return false; -} - -//---------------------------------------------------------------------------- -CPYCPPYY_EXPORT -void* CPyCppyy::CallVoidP(Cppyy::TCppMethod_t meth, Cppyy::TCppObject_t obj, CallContext* ctxt) -{ - return GILCallR(meth, obj, ctxt); -} - - -//---------------------------------------------------------------------------- -namespace { - -using namespace CPyCppyy; - -#define WSTRING1 "std::basic_string" -#define WSTRING2 "std::basic_string,std::allocator>" - -//-- aliasing special case: C complex (is binary compatible with C++ std::complex) -#ifndef _WIN32 -#define CCOMPLEX_D "_Complex double" -#define CCOMPLEX_F "_Complex float" -#else -#define CCOMPLEX_D "_C_double_complex" -#define CCOMPLEX_F "_C_float_complex" -#endif - -struct InitExecFactories_t { -public: - InitExecFactories_t() { - // load all executor factories in the global map 'gExecFactories' - CPyCppyy::ExecFactories_t& gf = gExecFactories; - - // factories for built-ins - gf["bool"] = (ef_t)+[](cdims_t) { static BoolExecutor e{}; return &e; }; - gf["bool&"] = (ef_t)+[](cdims_t) { return new BoolRefExecutor{}; }; - gf["const bool&"] = (ef_t)+[](cdims_t) { static BoolConstRefExecutor e{}; return &e; }; - gf["char"] = (ef_t)+[](cdims_t) { static CharExecutor e{}; return &e; }; - gf["signed char"] = gf["char"]; - gf["unsigned char"] = (ef_t)+[](cdims_t) { static UCharExecutor e{}; return &e; }; - gf["char&"] = (ef_t)+[](cdims_t) { return new CharRefExecutor{}; }; - gf["signed char&"] = gf["char&"]; - gf["unsigned char&"] = (ef_t)+[](cdims_t) { return new UCharRefExecutor{}; }; - gf["const char&"] = (ef_t)+[](cdims_t) { static CharConstRefExecutor e{}; return &e; }; - gf["const signed char&"] = gf["const char&"]; - gf["const unsigned char&"] = (ef_t)+[](cdims_t) { static UCharConstRefExecutor e{}; return &e; }; - gf["wchar_t"] = (ef_t)+[](cdims_t) { static WCharExecutor e{}; return &e; }; - gf["char16_t"] = (ef_t)+[](cdims_t) { static Char16Executor e{}; return &e; }; - gf["char32_t"] = (ef_t)+[](cdims_t) { static Char32Executor e{}; return &e; }; - gf["int8_t"] = (ef_t)+[](cdims_t) { static Int8Executor e{}; return &e; }; - gf["int8_t&"] = (ef_t)+[](cdims_t) { return new Int8RefExecutor{}; }; - gf["const int8_t&"] = (ef_t) + [](cdims_t) { - static Int8ConstRefExecutor e{}; - return &e; - }; - gf["uint8_t"] = (ef_t)+[](cdims_t) { static UInt8Executor e{}; return &e; }; - gf["uint8_t&"] = (ef_t)+[](cdims_t) { return new UInt8RefExecutor{}; }; - gf["const uint8_t&"] = (ef_t) + [](cdims_t) { - static UInt8ConstRefExecutor e{}; - return &e; - }; - gf["short"] = (ef_t)+[](cdims_t) { static ShortExecutor e{}; return &e; }; - gf["short&"] = (ef_t)+[](cdims_t) { return new ShortRefExecutor{}; }; - gf["int"] = (ef_t)+[](cdims_t) { static IntExecutor e{}; return &e; }; - gf["int&"] = (ef_t)+[](cdims_t) { return new IntRefExecutor{}; }; - gf["unsigned short"] = gf["int"]; - gf["unsigned short&"] = (ef_t)+[](cdims_t) { return new UShortRefExecutor{}; }; - gf["unsigned long"] = (ef_t)+[](cdims_t) { static ULongExecutor e{}; return &e; }; - gf["unsigned long&"] = (ef_t)+[](cdims_t) { return new ULongRefExecutor{}; }; - gf["unsigned int"] = gf["unsigned long"]; - gf["unsigned int&"] = (ef_t)+[](cdims_t) { return new UIntRefExecutor{}; }; - gf["long"] = (ef_t)+[](cdims_t) { static LongExecutor e{}; return &e; }; - gf["long&"] = (ef_t)+[](cdims_t) { return new LongRefExecutor{}; }; - gf["unsigned long"] = (ef_t)+[](cdims_t) { static ULongExecutor e{}; return &e; }; - gf["unsigned long&"] = (ef_t)+[](cdims_t) { return new ULongRefExecutor{}; }; - gf["long long"] = (ef_t)+[](cdims_t) { static LongLongExecutor e{}; return &e; }; - gf["long long&"] = (ef_t)+[](cdims_t) { return new LongLongRefExecutor{}; }; - gf["unsigned long long"] = (ef_t)+[](cdims_t) { static ULongLongExecutor e{}; return &e; }; - gf["unsigned long long&"] = (ef_t)+[](cdims_t) { return new ULongLongRefExecutor{}; }; - - gf["float"] = (ef_t)+[](cdims_t) { static FloatExecutor e{}; return &e; }; - gf["float&"] = (ef_t)+[](cdims_t) { return new FloatRefExecutor{}; }; - gf["double"] = (ef_t)+[](cdims_t) { static DoubleExecutor e{}; return &e; }; - gf["double&"] = (ef_t)+[](cdims_t) { return new DoubleRefExecutor{}; }; - gf["long double"] = (ef_t)+[](cdims_t) { static LongDoubleExecutor e{}; return &e; }; // TODO: lost precision - gf["long double&"] = (ef_t)+[](cdims_t) { return new LongDoubleRefExecutor{}; }; - gf["std::complex"] = (ef_t)+[](cdims_t) { static ComplexDExecutor e{}; return &e; }; - gf["std::complex&"] = (ef_t)+[](cdims_t) { return new ComplexDRefExecutor{}; }; - gf["void"] = (ef_t)+[](cdims_t) { static VoidExecutor e{}; return &e; }; - - // pointer/array factories - gf["void ptr"] = (ef_t)+[](cdims_t d) { return new VoidArrayExecutor{d}; }; - gf["bool ptr"] = (ef_t)+[](cdims_t d) { return new BoolArrayExecutor{d}; }; - gf["unsigned char ptr"] = (ef_t)+[](cdims_t d) { return new UCharArrayExecutor{d}; }; - gf["const unsigned char ptr"] = gf["unsigned char ptr"]; - gf["std::byte ptr"] = (ef_t)+[](cdims_t d) { return new ByteArrayExecutor{d}; }; - gf["const std::byte ptr"] = gf["std::byte ptr"]; - gf["int8_t ptr"] = (ef_t)+[](cdims_t d) { return new Int8ArrayExecutor{d}; }; - gf["uint8_t ptr"] = (ef_t)+[](cdims_t d) { return new UInt8ArrayExecutor{d}; }; - gf["short ptr"] = (ef_t)+[](cdims_t d) { return new ShortArrayExecutor{d}; }; - gf["unsigned short ptr"] = (ef_t)+[](cdims_t d) { return new UShortArrayExecutor{d}; }; - gf["int ptr"] = (ef_t)+[](cdims_t d) { return new IntArrayExecutor{d}; }; - gf["unsigned int ptr"] = (ef_t)+[](cdims_t d) { return new UIntArrayExecutor{d}; }; - gf["long ptr"] = (ef_t)+[](cdims_t d) { return new LongArrayExecutor{d}; }; - gf["unsigned long ptr"] = (ef_t)+[](cdims_t d) { return new ULongArrayExecutor{d}; }; - gf["long long ptr"] = (ef_t)+[](cdims_t d) { return new LLongArrayExecutor{d}; }; - gf["unsigned long long ptr"] = (ef_t)+[](cdims_t d) { return new ULLongArrayExecutor{d}; }; - gf["float ptr"] = (ef_t)+[](cdims_t d) { return new FloatArrayExecutor{d}; }; - gf["double ptr"] = (ef_t)+[](cdims_t d) { return new DoubleArrayExecutor{d}; }; - gf["long double ptr"] = (ef_t)+[](cdims_t d) { return new LDoubleArrayExecutor{d}; }; - gf["std::complex ptr"] = (ef_t)+[](cdims_t d) { return new ComplexFArrayExecutor{d}; }; - gf["std::complex ptr"] = (ef_t)+[](cdims_t d) { return new ComplexDArrayExecutor{d}; }; - gf["std::complex ptr"] = (ef_t)+[](cdims_t d) { return new ComplexIArrayExecutor{d}; }; - gf["std::complex ptr"] = (ef_t)+[](cdims_t d) { return new ComplexLArrayExecutor{d}; }; - - // aliases - gf["internal_enum_type_t"] = gf["int"]; - gf["internal_enum_type_t&"] = gf["int&"]; - gf["internal_enum_type_t ptr"] = gf["int ptr"]; - gf["std::byte"] = gf["uint8_t"]; - gf["std::byte&"] = gf["uint8_t&"]; - gf["const std::byte&"] = gf["const uint8_t&"]; - gf["const byte&"] = gf["const uint8_t&"]; - gf["std::int8_t"] = gf["int8_t"]; - gf["std::int8_t&"] = gf["int8_t&"]; - gf["const std::int8_t&"] = gf["const int8_t&"]; - gf["std::int8_t ptr"] = gf["int8_t ptr"]; - gf["std::uint8_t"] = gf["uint8_t"]; - gf["std::uint8_t&"] = gf["uint8_t&"]; - gf["const std::uint8_t&"] = gf["const uint8_t&"]; - gf["std::uint8_t ptr"] = gf["uint8_t ptr"]; -#ifdef _WIN32 - gf["__int64"] = gf["long long"]; - gf["__int64&"] = gf["long long&"]; - gf["__int64 ptr"] = gf["long long ptr"]; - gf["unsigned __int64"] = gf["unsigned long long"]; - gf["unsigned __int64&"] = gf["unsigned long long&"]; - gf["unsigned __int64 ptr"] = gf["unsigned long long ptr"]; -#endif - gf[CCOMPLEX_D] = gf["std::complex"]; - gf[CCOMPLEX_D "&"] = gf["std::complex&"]; - gf[CCOMPLEX_F " ptr"] = gf["std::complex ptr"]; - gf[CCOMPLEX_D " ptr"] = gf["std::complex ptr"]; - - // We always need these executors when cppyy is based on an unpatched - // ROOT, because the "long long" types are always converted to Long64_t - // and ULong64_t already at the ROOT Meta level. - // See https://github.com/root-project/root/issues/15872#issuecomment-2174092763 - gf["Long64_t"] = gf["long long"]; - gf["Long64_t&"] = gf["long long&"]; - gf["Long64_t ptr"] = gf["long long ptr"]; - gf["ULong64_t"] = gf["unsigned long long"]; - gf["ULong64_t&"] = gf["unsigned long long&"]; - gf["ULong64_t ptr"] = gf["unsigned long long ptr"]; - - // factories for special cases - gf["const char*"] = (ef_t)+[](cdims_t) { static CStringExecutor e{}; return &e; }; - gf["char*"] = gf["const char*"]; - gf["const char*&"] = (ef_t)+[](cdims_t) { static CStringRefExecutor e{}; return &e; }; - gf["char*&"] = gf["const char*&"]; - gf["const signed char*"] = gf["const char*"]; - //gf["signed char*"] = gf["char*"]; - gf["signed char ptr"] = (ef_t)+[](cdims_t d) { return new SCharArrayExecutor{d}; }; - gf["wchar_t*"] = (ef_t)+[](cdims_t) { static WCStringExecutor e{}; return &e;}; - gf["char16_t*"] = (ef_t)+[](cdims_t) { static CString16Executor e{}; return &e;}; - gf["char32_t*"] = (ef_t)+[](cdims_t) { static CString32Executor e{}; return &e;}; - gf["std::basic_string"] = (ef_t)+[](cdims_t) { static STLStringExecutor e{}; return &e; }; - gf["std::basic_string&"] = (ef_t)+[](cdims_t) { return new STLStringRefExecutor{}; }; - gf["std::basic_string"] = (ef_t)+[](cdims_t) { static STLWStringExecutor e{}; return &e; }; - gf[WSTRING1] = gf["std::basic_string"]; - gf[WSTRING2] = gf["std::basic_string"]; - gf["__init__"] = (ef_t)+[](cdims_t) { static ConstructorExecutor e{}; return &e; }; - gf["PyObject*"] = (ef_t)+[](cdims_t) { static PyObjectExecutor e{}; return &e; }; - gf["_object*"] = gf["PyObject*"]; - gf["FILE*"] = gf["void ptr"]; - } -} initExecvFactories_; - -} // unnamed namespace diff --git a/src/CPyCppyy/src/Executors.h b/src/CPyCppyy/src/Executors.h deleted file mode 100644 index c081a43..0000000 --- a/src/CPyCppyy/src/Executors.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef CPYCPPYY_EXECUTORS_H -#define CPYCPPYY_EXECUTORS_H - -#include "Python.h" -#include "Cppyy.h" - -// Bindings -#include "Dimensions.h" - -// Standard -#include - - -namespace CPyCppyy { - -struct CallContext; - -class CPYCPPYY_CLASS_EXPORT Executor { -public: - virtual ~Executor(); - virtual PyObject* Execute( - Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) = 0; - virtual bool HasState() { return false; } -}; - -// special case needed for CPPSetItem -class RefExecutor : public Executor { -public: - RefExecutor() : fAssignable(nullptr) {} - virtual bool SetAssignable(PyObject*); - bool HasState() override { return true; } - -protected: - PyObject* fAssignable; -}; - -// create/destroy executor from fully qualified type (public API) -CPYCPPYY_EXPORT Executor* CreateExecutor(const std::string& fullType, cdims_t = 0); -CPYCPPYY_EXPORT Executor* CreateExecutor(Cppyy::TCppType_t type, cdims_t = 0); -CPYCPPYY_EXPORT void DestroyExecutor(Executor* p); -typedef Executor* (*ef_t) (cdims_t); -CPYCPPYY_EXPORT bool RegisterExecutor(const std::string& name, ef_t fac); -CPYCPPYY_EXPORT bool RegisterExecutorAlias(const std::string& name, const std::string& target); -CPYCPPYY_EXPORT bool UnregisterExecutor(const std::string& name); - -// helper for the actual call -CPYCPPYY_EXPORT void* CallVoidP(Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_EXECUTORS_H diff --git a/src/CPyCppyy/src/LowLevelViews.cxx b/src/CPyCppyy/src/LowLevelViews.cxx deleted file mode 100644 index 870a992..0000000 --- a/src/CPyCppyy/src/LowLevelViews.cxx +++ /dev/null @@ -1,1155 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "LowLevelViews.h" -#include "Converters.h" -#include "CustomPyTypes.h" -#include "PyStrings.h" - -// Standard -#include -#include -#include -#include - - -//= memoryview-like object =================================================== -// This is largely setup just like Python builtin memory view objects, with -// the exceptions that there is no need of a "base" object (it views on C++ -// memory, not a Python object with a buffer interface), it uses the CPyCppyy -// converters, and typed results and assignments are supported. Code reused -// under PSF License Version 2. - - -//- helpers ------------------------------------------------------------------ -static inline void set_strides(Py_buffer& view, size_t itemsize, bool isfix) { - if (isfix) { - Py_ssize_t stride = itemsize; - for (Py_ssize_t idim = view.ndim-1; 0 <= idim; --idim) { - view.strides[idim] = stride; - stride *= view.shape[idim]; - } - } else { - view.strides[view.ndim-1] = itemsize; - for (Py_ssize_t idim = 0; idim < view.ndim-1; ++idim) - view.strides[idim] = view.itemsize; - } -} - - -//= CPyCppyy low level view construction/destruction ========================= -static CPyCppyy::LowLevelView* ll_new(PyTypeObject* subtype, PyObject*, PyObject*) -{ -// Create a new low level ptr type - CPyCppyy::LowLevelView* pyobj = (CPyCppyy::LowLevelView*)subtype->tp_alloc(subtype, 0); - if (!pyobj) - return nullptr; - - memset(&pyobj->fBufInfo, 0, sizeof(Py_buffer)); - (intptr_t&)pyobj->fBufInfo.internal |= CPyCppyy::LowLevelView::kIsCppArray; - pyobj->fBuf = nullptr; - pyobj->fConverter = nullptr; - pyobj->fElemCnv = nullptr; - - return pyobj; -} - -//---------------------------------------------------------------------------- -static void ll_dealloc(CPyCppyy::LowLevelView* pyobj) -{ -// Destruction requires the deletion of the converter (if any) - PyMem_Free(pyobj->fBufInfo.shape); - PyMem_Free(pyobj->fBufInfo.strides); - if ((intptr_t)pyobj->fBufInfo.internal & CPyCppyy::LowLevelView::kIsOwner) { - if ((intptr_t)pyobj->fBufInfo.internal & CPyCppyy::LowLevelView::kIsCppArray) - delete [] pyobj->fBuf; - else - free(pyobj->fBuf); - } - - if (pyobj->fElemCnv != pyobj->fConverter &&\ - pyobj->fElemCnv && pyobj->fElemCnv->HasState()) - delete pyobj->fElemCnv; - - if (pyobj->fConverter && pyobj->fConverter->HasState()) - delete pyobj->fConverter; - - Py_TYPE(pyobj)->tp_free((PyObject*)pyobj); -} - - -//---------------------------------------------------------------------------- -#define CPYCPPYY_LL_FLAG_GETSET(name, flag, doc) \ -static PyObject* ll_get##name(CPyCppyy::LowLevelView* pyobj) \ -{ \ - return PyBool_FromLong((long)((intptr_t)pyobj->fBufInfo.internal & flag));\ -} \ - \ -static int ll_set##name(CPyCppyy::LowLevelView* pyobj, PyObject* value, void*)\ -{ \ - long settrue = PyLong_AsLong(value); \ - if (settrue == -1 && PyErr_Occurred()) { \ - PyErr_SetString(PyExc_ValueError, #doc" should be either True or False");\ - return -1; \ - } \ - \ - if ((bool)settrue) \ - (intptr_t&)pyobj->fBufInfo.internal |= flag; \ - else \ - (intptr_t&)pyobj->fBufInfo.internal &= ~flag; \ - \ - return 0; \ -} - -CPYCPPYY_LL_FLAG_GETSET(ownership, CPyCppyy::LowLevelView::kIsOwner, __python_owns__) -CPYCPPYY_LL_FLAG_GETSET(cpparray, CPyCppyy::LowLevelView::kIsCppArray, __cpp_array__) - -//--------------------------------------------------------------------------- -static PyObject* ll_typecode(CPyCppyy::LowLevelView* self, void*) -{ - return CPyCppyy_PyText_FromString((char*)self->fBufInfo.format); -} - - -//- Copy memoryview buffers ================================================= - -// The functions in this section take a source and a destination buffer -// with the same logical structure: format, itemsize, ndim and shape -// are identical, with ndim > 0. - -// Check for the presence of suboffsets in the first dimension. -#define HAVE_PTR(suboffsets, dim) (suboffsets && suboffsets[dim] >= 0) -// Adjust ptr if suboffsets are present. -#define ADJUST_PTR(ptr, suboffsets, dim) \ - (HAVE_PTR(suboffsets, dim) ? *((char**)ptr) + suboffsets[dim] : ptr) - -// Assumptions: ndim >= 1. The macro tests for a corner case that should -// perhaps be explicitly forbidden in the PEP. -#define HAVE_SUBOFFSETS_IN_LAST_DIM(view) \ - (view->suboffsets && view->suboffsets[dest->ndim-1] >= 0) - -//--------------------------------------------------------------------------- -static inline int last_dim_is_contiguous(const Py_buffer *dest, const Py_buffer *src) -{ - assert(dest->ndim > 0 && src->ndim > 0); - return (!HAVE_SUBOFFSETS_IN_LAST_DIM(dest) && - !HAVE_SUBOFFSETS_IN_LAST_DIM(src) && - dest->strides[dest->ndim-1] == dest->itemsize && - src->strides[src->ndim-1] == src->itemsize); -} - -//--------------------------------------------------------------------------- -static inline bool equiv_shape(const Py_buffer* dest, const Py_buffer* src) -{ -// Two shapes are equivalent if they are either equal or identical up -// to a zero element at the same position. For example, in NumPy arrays -// the shapes [1, 0, 5] and [1, 0, 7] are equivalent. - if (dest->ndim != src->ndim) - return false; - - for (int i = 0; i < dest->ndim; i++) { - if (dest->shape[i] != src->shape[i]) - return 0; - if (dest->shape[i] == 0) - break; - } - - return true; -} - -//--------------------------------------------------------------------------- -static bool equiv_structure(const Py_buffer* dest, const Py_buffer* src) -{ -// Check that the logical structure of the destination and source buffers -// is identical. - if (strcmp(dest->format, src->format) != 0 || dest->itemsize != src->itemsize || - !equiv_shape(dest, src)) { - PyErr_SetString(PyExc_ValueError, - "low level pointer assignment: lvalue and rvalue have different structures"); - return false; - } - - return true; -} - -//--------------------------------------------------------------------------- -static void copy_base(const Py_ssize_t* shape, Py_ssize_t itemsize, - char* dptr, const Py_ssize_t* dstrides, const Py_ssize_t* dsuboffsets, - char* sptr, const Py_ssize_t* sstrides, const Py_ssize_t* ssuboffsets, - char* mem) -{ -// Base case for recursive multi-dimensional copying. Contiguous arrays are -// copied with very little overhead. Assumptions: ndim == 1, mem == nullptr or -// sizeof(mem) == shape[0] * itemsize. - if (!mem) { // contiguous - Py_ssize_t size = shape[0] * itemsize; - if (dptr + size < sptr || sptr + size < dptr) - memcpy(dptr, sptr, size); // no overlapping - else - memmove(dptr, sptr, size); - } - else { - char *p; - Py_ssize_t i; - for (i=0, p=mem; i < shape[0]; p+=itemsize, sptr+=sstrides[0], i++) { - char *xsptr = ADJUST_PTR(sptr, ssuboffsets, 0); - memcpy(p, xsptr, itemsize); - } - for (i=0, p=mem; i < shape[0]; p+=itemsize, dptr+=dstrides[0], i++) { - char *xdptr = ADJUST_PTR(dptr, dsuboffsets, 0); - memcpy(xdptr, p, itemsize); - } - } - -} - -//--------------------------------------------------------------------------- -static int copy_single(Py_buffer* dest, Py_buffer* src) -{ -// Faster copying of one-dimensional arrays. - char* mem = nullptr; - - assert(dest->ndim == 1); - - if (!equiv_structure(dest, src)) - return -1; - - if (!last_dim_is_contiguous(dest, src)) { - mem = (char*)PyMem_Malloc(dest->shape[0] * dest->itemsize); - if (!mem) { - PyErr_NoMemory(); - return -1; - } - } - - copy_base(dest->shape, dest->itemsize, - (char*)dest->buf, dest->strides, dest->suboffsets, - (char*)src->buf, src->strides, src->suboffsets, - mem); - - if (mem) - PyMem_Free(mem); - - return 0; -} - - -//- Indexing and slicing ---------------------------------------------------- -static char* lookup_dimension(Py_buffer& view, char* ptr, int dim, Py_ssize_t index) -{ - Py_ssize_t nitems; // items in the given dimension - - assert(view.shape); - assert(view.strides); - - nitems = view.shape[dim]; - if (index < 0) { - if (nitems != CPyCppyy::UNKNOWN_SIZE) - index += nitems; - else { - PyErr_Format(PyExc_IndexError, - "negative index not supported on dimension %d with unknown size", dim + 1); - return nullptr; - } - } - - if (view.strides[dim] == CPyCppyy::UNKNOWN_SIZE) { - PyErr_Format(PyExc_IndexError, - "multi index not supported on dimension %d with unknown stride", dim + 1); - return nullptr; - } - - if (nitems != CPyCppyy::UNKNOWN_SIZE && (index < 0 || index >= nitems)) { - PyErr_Format(PyExc_IndexError, - "index out of bounds on dimension %d", dim + 1); - return nullptr; - } - - ptr += view.strides[dim] * index; - ptr = ADJUST_PTR(ptr, view.suboffsets, dim); - - return ptr; -} - -// Get the pointer to the item at index. -//--------------------------------------------------------------------------- -static inline void* ptr_from_index(CPyCppyy::LowLevelView* llview, Py_ssize_t index) -{ - Py_buffer& view = llview->fBufInfo; - return lookup_dimension(view, (char*)llview->get_buf(), 0, index); -} - -// Get the pointer to the item at tuple. -//--------------------------------------------------------------------------- -static void* ptr_from_tuple(CPyCppyy::LowLevelView* llview, PyObject* tup) -{ - Py_buffer& view = llview->fBufInfo; - - Py_ssize_t nindices = PyTuple_GET_SIZE(tup); - if (nindices > view.ndim) { - PyErr_Format(PyExc_TypeError, - "cannot index %d-dimension view with %zd-element tuple", view.ndim, nindices); - return nullptr; - } - - char* ptr = (char*)llview->get_buf(); - for (Py_ssize_t dim = 0; dim < nindices; dim++) { - Py_ssize_t index; - index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(tup, dim), - PyExc_IndexError); - if (index == -1 && PyErr_Occurred()) - return nullptr; - - ptr = lookup_dimension(view, ptr, (int)dim, index); - if (!ptr) - return nullptr; - - if (!((intptr_t)view.internal & CPyCppyy::LowLevelView::kIsFixed) && dim != view.ndim-1) - ptr = *(char**)ptr; - } - return ptr; -} - - -//= mapping methods ========================================================= -static Py_ssize_t ll_length(CPyCppyy::LowLevelView* self) -{ - if (!self->get_buf()) - return 0; - return self->fBufInfo.ndim == 0 ? 1 : self->fBufInfo.shape[0]; -} - -//--------------------------------------------------------------------------- -static inline int init_slice(Py_buffer* base, PyObject* _key, int dim) -{ - Py_ssize_t start, stop, step, slicelength; - - PyObject* key = _key; - - if (PySlice_GetIndicesEx(key, base->shape[dim], &start, &stop, &step, &slicelength) < 0) - return -1; - - if (!base->suboffsets || dim == 0) { - adjust_buf: - base->buf = (char *)base->buf + base->strides[dim] * start; - } - else { - Py_ssize_t n = dim-1; - while (n >= 0 && base->suboffsets[n] < 0) - n--; - if (n < 0) - goto adjust_buf; // all suboffsets are negative - base->suboffsets[n] = base->suboffsets[n] + base->strides[dim] * start; - } - base->shape[dim] = slicelength; - base->strides[dim] = base->strides[dim] * step; - - return 0; -} - -//--------------------------------------------------------------------------- -static bool is_multislice(PyObject* key) -{ - if (!PyTuple_Check(key)) - return false; - - Py_ssize_t size = PyTuple_GET_SIZE(key); - if (size == 0) - return false; - - for (Py_ssize_t i = 0; i < size; i++) { - PyObject *x = PyTuple_GET_ITEM(key, i); - if (!PySlice_Check(x)) - return false; - } - return true; -} - -//--------------------------------------------------------------------------- -static Py_ssize_t is_multiindex(PyObject* key) -{ - if (!PyTuple_Check(key)) - return 0; - - Py_ssize_t size = PyTuple_GET_SIZE(key); - for (Py_ssize_t i = 0; i < size; i++) { - PyObject *x = PyTuple_GET_ITEM(key, i); - if (!PyIndex_Check(x)) - return 0; - } - return 1; -} - - -// Return the item at index. In a one-dimensional view, this is an object -// with the type specified by view->format. Otherwise, the item is a sub-view. -// The function is used in ll_subscript() and ll_as_sequence. -//--------------------------------------------------------------------------- -static PyObject* ll_item(CPyCppyy::LowLevelView* self, Py_ssize_t index) -{ - Py_buffer& view = self->fBufInfo; - - if (!self->get_buf()) { - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); - return nullptr; - } - - if (view.ndim == 0) { - PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory"); - return nullptr; - } - - void* ptr = ptr_from_index(self, index); - if (ptr) { - bool isfix = (intptr_t)view.internal & CPyCppyy::LowLevelView::kIsFixed; - if (self->fBufInfo.ndim == 1 || !isfix) - return self->fConverter->FromMemory(ptr); - return self->fConverter->FromMemory((void*)&ptr); - } - - return nullptr; // error already set by lookup_dimension -} - -// Return the item at position *key* (a tuple of indices). -//--------------------------------------------------------------------------- -static PyObject* ll_item_multi(CPyCppyy::LowLevelView* self, PyObject *tup) -{ - Py_buffer& view = self->fBufInfo; - Py_ssize_t nindices = PyTuple_GET_SIZE(tup); - - if (nindices < view.ndim) { - // TODO: implement - PyErr_SetString(PyExc_NotImplementedError, - "sub-views are not implemented"); - return nullptr; - } - - void* ptr = ptr_from_tuple(self, tup); - -// if there's an error, it was already set by lookup_dimension - return ptr ? self->fElemCnv->FromMemory(ptr) : nullptr; -} - - -// llp[obj] returns an object holding the data for one element if obj -// fully indexes the lowlevelptr or another lowlevelptr object if it -// does not. -// -// 0-d lowlevelptr objects can be referenced using llp[...] or llp[()] -// but not with anything else. -//--------------------------------------------------------------------------- -static PyObject* ll_subscript(CPyCppyy::LowLevelView* self, PyObject* key) -{ - Py_buffer& view = self->fBufInfo; - - if (view.ndim == 0) { - if (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0) { - return self->fConverter->FromMemory(self->get_buf()); - } - else if (key == Py_Ellipsis) { - Py_INCREF(self); - return (PyObject*)self; - } - else { - PyErr_SetString(PyExc_TypeError, - "invalid indexing of 0-dim memory"); - return nullptr; - } - } - - if (PyIndex_Check(key)) { - Py_ssize_t index = PyNumber_AsSsize_t(key, PyExc_IndexError); - if (index == -1 && PyErr_Occurred()) - return nullptr; - return ll_item(self, index); - } - else if (PySlice_Check(key)) { - if (view.ndim == 1) { - Py_ssize_t start, stop, step, slicelen; - if (PySlice_Unpack(key, &start, &stop, &step) < 0) - return nullptr; - - slicelen = PySlice_AdjustIndices(view.shape[0], &start, &stop, step); - if (slicelen <= 0) - slicelen = view.shape[0]; - - char* buf = (char*)self->get_buf(); - char* slice_buf = new char[slicelen*view.itemsize]; - size_t isize = view.itemsize; - for (size_t i=0, cur=0; i < (size_t)slicelen; cur += step, ++i) { - for (size_t j=0; j < isize; ++j) - slice_buf[i*isize+j] = buf[(start+cur)*isize + j]; - } - - CPyCppyy::LowLevelView* ll = self->fCreator(slice_buf, {1, slicelen}); - if (!ll) - delete [] slice_buf; - else - (intptr_t&)ll->fBufInfo.internal |= CPyCppyy::LowLevelView::kIsOwner; - - return (PyObject*)ll; - - } else { - // TODO: handle slicing. This should be simpler than the memoryview - // case as there is no Python object holding the buffer. - PyErr_SetString(PyExc_NotImplementedError, - "multi-dimensional slicing is not implemented"); - return nullptr; - } - } - else if (is_multiindex(key)) { - return ll_item_multi(self, key); - } - else if (is_multislice(key)) { - PyErr_SetString(PyExc_NotImplementedError, - "multi-dimensional slicing is not implemented"); - return nullptr; - } - - PyErr_SetString(PyExc_TypeError, "invalid slice key"); - return nullptr; -} - -//--------------------------------------------------------------------------- -static int ll_ass_sub(CPyCppyy::LowLevelView* self, PyObject* key, PyObject* value) -{ - Py_buffer& view = self->fBufInfo; - Py_buffer src; - - if (view.readonly) { - PyErr_SetString(PyExc_TypeError, "cannot modify read-only memory"); - return -1; - } - - if (value == nullptr) { - PyErr_SetString(PyExc_TypeError, "cannot delete memory"); - return -1; - } - - if (view.ndim == 0) { - if (key == Py_Ellipsis || - (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0)) { - return self->fConverter->ToMemory(value, self->get_buf()) ? 0 : -1; - } - else { - PyErr_SetString(PyExc_TypeError, - "invalid indexing of 0-dim memory"); - return -1; - } - } - - if (PyIndex_Check(key)) { - Py_ssize_t index; - if (1 < view.ndim) { - PyErr_SetString(PyExc_NotImplementedError, - "sub-views are not implemented"); - return -1; - } - index = PyNumber_AsSsize_t(key, PyExc_IndexError); - if (index == -1 && PyErr_Occurred()) - return -1; - void* ptr = ptr_from_index(self, index); - if (ptr == nullptr) - return -1; - return self->fConverter->ToMemory(value, ptr) ? 0 : -1; - } - - // one-dimensional: fast path - if (PySlice_Check(key) && view.ndim == 1) { - Py_buffer dest; // sliced view - Py_ssize_t arrays[3]; - int ret = -1; - - // rvalue must be an exporter - if (PyObject_GetBuffer(value, &src, PyBUF_FULL_RO) < 0) { - if (src.obj) PyBuffer_Release(&src); - return ret; - } - - dest = view; - dest.shape = &arrays[0]; dest.shape[0] = view.shape[0]; - dest.strides = &arrays[1]; dest.strides[0] = view.strides[0]; - if (view.suboffsets) { - dest.suboffsets = &arrays[2]; dest.suboffsets[0] = view.suboffsets[0]; - } - - if (init_slice(&dest, key, 0) < 0) - return -1; - dest.len = dest.shape[0] * dest.itemsize; - - ret = copy_single(&dest, &src); - PyBuffer_Release(&src); - return ret; - } - - if (is_multiindex(key)) { - // TODO: implement - if (PyTuple_GET_SIZE(key) < view.ndim) { - PyErr_SetString(PyExc_NotImplementedError, - "sub-views are not implemented"); - return -1; - } - void* ptr = ptr_from_tuple(self, key); - if (ptr == nullptr) - return -1; - return self->fElemCnv->ToMemory(value, ptr) ? 0 : -1; - } - - if (PySlice_Check(key) || is_multislice(key)) { - // TODO: implement - PyErr_SetString(PyExc_NotImplementedError, - "LowLevelView slice assignments are currently restricted " - "to ndim = 1"); - return -1; - } - - PyErr_SetString(PyExc_TypeError, "invalid slice key"); - return -1; -} - -//--------------------------------------------------------------------------- -static int ll_getbuf(CPyCppyy::LowLevelView* self, Py_buffer* view, int flags) -{ -// Simplified from memoryobject, as we're always dealing with C arrays. - -// start with full copy - *view = self->fBufInfo; - - if (!(flags & PyBUF_FORMAT)) { - /* NULL indicates that the buffer's data type has been cast to 'B'. - view->itemsize is the _previous_ itemsize. If shape is present, - the equality product(shape) * itemsize = len still holds at this - point. The equality calcsize(format) = itemsize does _not_ hold - from here on! */ - view->format = NULL; - } - - if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) { - PyErr_SetString(PyExc_BufferError, - "underlying buffer is not Fortran contiguous"); - return -1; - } - - if (!(flags & PyBUF_FORMAT)) { - /* PyBUF_SIMPLE or PyBUF_WRITABLE: at this point buf is C-contiguous, - so base->buf = ndbuf->data. */ - if (view->format != NULL) { - /* PyBUF_SIMPLE|PyBUF_FORMAT and PyBUF_WRITABLE|PyBUF_FORMAT do - not make sense. */ - PyErr_Format(PyExc_BufferError, - "cannot cast to unsigned bytes if the format flag is present"); - return -1; - } - /* product(shape) * itemsize = len and calcsize(format) = itemsize - do _not_ hold from here on! */ - view->ndim = 1; - view->shape = NULL; - } - - view->obj = (PyObject*)self; - Py_INCREF(view->obj); - - return 0; -} - - -//= iterator protocol ======================================================= -static PyObject* ll_iter(PyObject* self) { -// The index iterator indexes through getitem, just like python would do by -// default, except that it checks the size externally to raise StopIteration, -// rather than geitem failing. - - using namespace CPyCppyy; - - indexiterobject* ii = PyObject_GC_New(indexiterobject, &IndexIter_Type); - if (!ii) return nullptr; - - Py_INCREF(self); - ii->ii_container = self; - ii->ii_pos = 0; - ii->ii_len = ll_length((LowLevelView*)self); - - PyObject_GC_Track(ii); - return (PyObject*)ii; -} - - -//- mapping methods --------------------------------------------------------- -static PyMappingMethods ll_as_mapping = { - (lenfunc) ll_length, // mp_length - (binaryfunc) ll_subscript, // mp_subscript - (objobjargproc)ll_ass_sub, // mp_ass_subscript -}; - -//- sequence methods -------------------------------------------------------- -static PySequenceMethods ll_as_sequence = { - (lenfunc)ll_length, // sq_length - 0, // sq_concat - 0, // sq_repeat - (ssizeargfunc)ll_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 -}; - -//- buffer methods ---------------------------------------------------------- -static PyBufferProcs ll_as_buffer = { - (getbufferproc)ll_getbuf, // bf_getbuffer - 0, // bf_releasebuffer -}; - - - -//--------------------------------------------------------------------------- -static PyObject* ll_shape(CPyCppyy::LowLevelView* self) -{ - Py_buffer& view = self->fBufInfo; - - PyObject* shape = PyTuple_New(view.ndim); - for (Py_ssize_t idim = 0; idim < view.ndim; ++idim) - PyTuple_SET_ITEM(shape, idim, PyInt_FromSsize_t(view.shape[idim])); - - return shape; -} - -//--------------------------------------------------------------------------- -static PyObject* ll_reshape(CPyCppyy::LowLevelView* self, PyObject* shape) -{ -// Allow the user to fix up the actual (type-strided) size of the buffer. - if (!PyTuple_Check(shape)) { - if (shape) { - PyObject* pystr = PyObject_Str(shape); - if (pystr) { - PyErr_Format(PyExc_TypeError, "tuple object expected, received %s", - CPyCppyy_PyText_AsStringChecked(pystr)); - Py_DECREF(pystr); - return nullptr; - } - } - PyErr_SetString(PyExc_TypeError, "tuple object expected"); - return nullptr; - } - - Py_buffer& view = self->fBufInfo; - -// verify size match - Py_ssize_t oldsz = 0; - for (Py_ssize_t idim = 0; idim < view.ndim; ++idim) { - Py_ssize_t nlen = view.shape[idim]; - if (nlen == CPyCppyy::UNKNOWN_SIZE || nlen == INT_MAX/view.itemsize /* fake 'max' */) { - oldsz = -1; // meaning, unable to check size match - break; - } - oldsz += view.shape[idim]; - } - - if (0 < oldsz) { - Py_ssize_t newsz = 0; - for (Py_ssize_t idim = 0; idim < PyTuple_GET_SIZE(shape); ++idim) - newsz += PyInt_AsSsize_t(PyTuple_GET_ITEM(shape, idim)); - if (oldsz != newsz) { - PyObject* tas = PyObject_Str(shape); - PyErr_Format(PyExc_ValueError, - "cannot reshape array of size %ld into shape %s", (long)oldsz, CPyCppyy_PyText_AsString(tas)); - Py_DECREF(tas); - return nullptr; - } - } - -// reshape - size_t itemsize = view.strides[view.ndim-1]; - if (view.ndim != PyTuple_GET_SIZE(shape)) { - PyMem_Free(view.shape); - PyMem_Free(view.strides); - - view.ndim = (int)PyTuple_GET_SIZE(shape); - view.shape = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); - view.strides = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); - } - - for (Py_ssize_t idim = 0; idim < PyTuple_GET_SIZE(shape); ++idim) { - Py_ssize_t nlen = PyInt_AsSsize_t(PyTuple_GET_ITEM(shape, idim)); - if (nlen == -1 && PyErr_Occurred()) - return nullptr; - - if (idim == 0) view.len = nlen * view.itemsize; - - view.shape[idim] = nlen; - } - - set_strides(view, itemsize, false /* by definition not fixed */); - - Py_RETURN_NONE; -} - - -//--------------------------------------------------------------------------- -static PyObject* ll_array(CPyCppyy::LowLevelView* self, PyObject* args, PyObject* kwds) -{ -// Construct a numpy array from the lowlevelview (w/o copy if possible); this -// uses the Python methods to avoid depending on numpy directly - -// Expect as most a dtype from the arguments; - static PyObject* npmod = PyImport_ImportModule("numpy"); // ref-count kept - if (!npmod) - return nullptr; - - bool docopy = false; - if (kwds) { - PyObject* pycp = PyObject_GetItem(kwds, CPyCppyy::PyStrings::gCopy); - if (!pycp) { - PyErr_SetString(PyExc_TypeError, "__array__ only supports the \"copy\" keyword"); - return nullptr; - } - - docopy = PyObject_IsTrue(pycp); - Py_DECREF(pycp); - } - - if (!docopy) { // view requested - // expect possible dtype from the arguments, otherwise take it from the type code - PyObject* dtype; - if (!args || PyTuple_GET_SIZE(args) != 1) { - PyObject* npdtype = PyObject_GetAttr(npmod, CPyCppyy::PyStrings::gDType); - PyObject* typecode = ll_typecode(self, nullptr); - dtype = PyObject_CallFunctionObjArgs(npdtype, typecode, nullptr); - Py_DECREF(typecode); - Py_DECREF(npdtype); - } else { - dtype = PyTuple_GET_ITEM(args, 0); - Py_INCREF(dtype); - } - - if (!dtype) - return nullptr; - - PyObject* npfrombuf = PyObject_GetAttr(npmod, CPyCppyy::PyStrings::gFromBuffer); - PyObject* view = PyObject_CallFunctionObjArgs(npfrombuf, (PyObject*)self, dtype, nullptr); - Py_DECREF(dtype); - Py_DECREF(npfrombuf); - - return view; - - } else { // copy requested - PyObject* npcopy = PyObject_GetAttr(npmod, CPyCppyy::PyStrings::gCopy); - PyObject* newarr = PyObject_CallFunctionObjArgs(npcopy, (PyObject*)self, nullptr); - Py_DECREF(npcopy); - - return newarr; - } - -// never get here - return nullptr; -} - - -//--------------------------------------------------------------------------- -static PyObject* ll_as_string(CPyCppyy::LowLevelView* self) -{ -// Interpret memory as a null-terminated char string. - Py_buffer& view = self->fBufInfo; - - if (strcmp(view.format, "b") != 0 || view.ndim != 1) { - PyErr_Format(PyExc_TypeError, - "as_string only supported for 1-dim char strings (format: %s, dim: %d)", - view.format, (int)view.ndim); - return nullptr; - } - - char* buf = (char*)self->get_buf(); - size_t sz = strnlen(buf, (size_t)view.shape[0]); - return CPyCppyy_PyText_FromStringAndSize(buf, sz); -} - -//--------------------------------------------------------------------------- -static PyMethodDef ll_methods[] = { - {(char*)"reshape", (PyCFunction)ll_reshape, METH_O, - (char*)"change the shape (not layout) of the low level view"}, - {(char*)"as_string", (PyCFunction)ll_as_string, METH_NOARGS, - (char*)"interpret memory as a null-terminated char string and return Python str"}, - {(char*)"__array__", (PyCFunction)ll_array, METH_VARARGS | METH_KEYWORDS, - (char*)"return a numpy array from the low level view"}, - {(char*)nullptr, nullptr, 0, nullptr} -}; - -//--------------------------------------------------------------------------- -static PyGetSetDef ll_getset[] = { - {(char*)"__python_owns__", (getter)ll_getownership, (setter)ll_setownership, - (char*)"If true, python manages the life time of this buffer", nullptr}, - {(char*)"__cpp_array__", (getter)ll_getcpparray, (setter)ll_setcpparray, - (char*)"If true, this array was allocated with C++\'s new[]", nullptr}, - {(char*)"format", (getter)ll_typecode, nullptr, nullptr, nullptr}, - {(char*)"typecode", (getter)ll_typecode, nullptr, nullptr, nullptr}, - {(char*)"shape", (getter)ll_shape, (setter)ll_reshape, nullptr, nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr } -}; - - -namespace CPyCppyy { - -//= CPyCppyy low level view type ============================================ -PyTypeObject LowLevelView_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.LowLevelView", // tp_name - sizeof(CPyCppyy::LowLevelView),// tp_basicsize - 0, // tp_itemsize - (destructor)ll_dealloc, // tp_dealloc - 0, // tp_vectorcall_offset / tp_print - 0, // tp_getattr - 0, // tp_setattr - 0, // itp_as_async / tp_compare - 0, // tp_repr - 0, // tp_as_number - &ll_as_sequence, // tp_as_sequence - &ll_as_mapping, // tp_as_mapping - 0, // tp_hash - 0, // tp_call - 0, // tp_str - 0, // tp_getattro - 0, // tp_setattro - &ll_as_buffer, // tp_as_buffer - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE, // tp_flags - (char*)"memory view on C++ pointer", // tp_doc - 0, // tp_traverse - 0, // tp_clear - 0, // tp_richcompare - 0, // tp_weaklistoffset - (getiterfunc)ll_iter, // tp_iter - 0, // tp_iternext - ll_methods, // tp_methods - 0, // tp_members - ll_getset, // 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 - (newfunc)ll_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 - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy - -namespace { - -template struct typecode_traits {}; -template<> struct typecode_traits { - static constexpr const char* format = "?"; static constexpr const char* name = "bool"; }; -template<> struct typecode_traits { - static constexpr const char* format = "b"; static constexpr const char* name = "char"; }; -template<> struct typecode_traits { - static constexpr const char* format = "b"; static constexpr const char* name = "SCharAsInt"; }; -template<> struct typecode_traits { - static constexpr const char* format = "B"; static constexpr const char* name = "UCharAsInt"; }; -template<> struct typecode_traits { - static constexpr const char* format = "B"; static constexpr const char* name = "UCharAsInt"; }; -template<> struct typecode_traits { - static constexpr const char* format = "b"; static constexpr const char* name = "char*"; }; -template<> struct typecode_traits { - static constexpr const char* format = "b"; static constexpr const char* name = "const char*"; }; -template<> struct typecode_traits { - static constexpr const char* format = "h"; static constexpr const char* name = "short"; }; -template<> struct typecode_traits { - static constexpr const char* format = "H"; static constexpr const char* name = "unsigned short"; }; -template<> struct typecode_traits { - static constexpr const char* format = "i"; static constexpr const char* name = "int"; }; -template<> struct typecode_traits { - static constexpr const char* format = "I"; static constexpr const char* name = "unsigned int"; }; -template<> struct typecode_traits { - static constexpr const char* format = "l"; static constexpr const char* name = "long"; }; -template<> struct typecode_traits { - static constexpr const char* format = "L"; static constexpr const char* name = "unsigned long"; }; -template<> struct typecode_traits { - static constexpr const char* format = "q"; static constexpr const char* name = "long long"; }; -template<> struct typecode_traits { - static constexpr const char* format = "Q"; static constexpr const char* name = "unsigned long long"; }; -template<> struct typecode_traits { - static constexpr const char* format = "f"; static constexpr const char* name = "float"; }; -template<> struct typecode_traits { - static constexpr const char* format = "d"; static constexpr const char* name = "double"; }; -template<> struct typecode_traits { - static constexpr const char* format = "D"; static constexpr const char* name = "long double"; }; -template<> struct typecode_traits> { - static constexpr const char* format = "Zf"; static constexpr const char* name = "std::complex"; }; -template<> struct typecode_traits> { - static constexpr const char* format = "Zd"; static constexpr const char* name = "std::complex"; }; -template<> struct typecode_traits> { - static constexpr const char* format = "Zi"; static constexpr const char* name = "std::complex"; }; -template<> struct typecode_traits> { - static constexpr const char* format = "Zl"; static constexpr const char* name = "std::complex"; }; - -} // unnamed namespace - - -//--------------------------------------------------------------------------- -bool CPyCppyy::LowLevelView::resize(size_t sz) -{ - Py_buffer& bi = this->fBufInfo; - if (bi.ndim == 1 && bi.shape) { - bi.len = sz * bi.itemsize; - bi.shape[0] = sz; - return true; - } - - return false; -} - -//--------------------------------------------------------------------------- -template -static inline CPyCppyy::LowLevelView* CreateLowLevelViewT( - T* address, CPyCppyy::cdims_t shape, const char* format = nullptr, const char* name = nullptr, Py_ssize_t itemsize = -1) -{ - using namespace CPyCppyy; - Py_ssize_t nx = (shape.ndim() != UNKNOWN_SIZE) ? shape[0] : INT_MAX/sizeof(T); - if (nx == UNKNOWN_SIZE) nx = INT_MAX/sizeof(T); - PyObject* args = PyTuple_New(0); - LowLevelView* llp = - (LowLevelView*)LowLevelView_Type.tp_new(&LowLevelView_Type, args, nullptr); - Py_DECREF(args); - - Py_buffer& view = llp->fBufInfo; - view.buf = address; - view.obj = nullptr; - view.readonly = 0; - view.format = (char*)(format ? format : typecode_traits::format); - view.ndim = int(shape.ndim() != UNKNOWN_SIZE ? shape.ndim() : 1); - view.shape = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); - view.shape[0] = nx; // view.len / view.itemsize - view.strides = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); - view.suboffsets = nullptr; - (intptr_t&)view.internal = CPyCppyy::LowLevelView::kIsCppArray; // assumed - bool isfix = shape.ndim() != UNKNOWN_SIZE; - if (isfix) { - for (int i = 0; i < shape.ndim(); ++i) - isfix = isfix && (shape[i] != UNKNOWN_SIZE); - if (isfix) (intptr_t&)view.internal |= CPyCppyy::LowLevelView::kIsFixed; - } - - llp->fElemCnv = CreateConverter(name ? name : typecode_traits::name); - if (view.ndim == 1) { - // simple 1-dim array of the declared type - view.len = nx * sizeof(T); - view.itemsize = (itemsize > 0 ? (size_t)itemsize : sizeof(T)); - llp->fConverter = llp->fElemCnv; - } else { - // multi-dim array; sub-views are projected by using more LLViews - const size_t elemsize = isfix ? sizeof(T) : sizeof(void*); - view.len = nx * elemsize; - view.itemsize = elemsize; - for (Py_ssize_t idim = 1; idim < view.ndim; ++idim) - view.shape[idim] = shape[idim]; - - // peel off one dimension and create a new LLView converter - std::string tname{name ? name : typecode_traits::name}; - tname.append("[]"); // make sure to ask for another array - // TODO: although this will work, it means that "naive" loops are expensive - llp->fConverter = CreateConverter(tname, shape.sub()); - } - - set_strides(view, sizeof(T), isfix); - - return llp; -} - -//--------------------------------------------------------------------------- -template -static inline CPyCppyy::LowLevelView* CreateLowLevelViewT( - T** address, CPyCppyy::cdims_t shape, const char* format = nullptr, const char* name = nullptr) -{ - using namespace CPyCppyy; - LowLevelView* llp = (LowLevelView*)CreateLowLevelViewT((T*)address, shape, format, name); - llp->set_buf((void**)address); - return llp; -} - -//--------------------------------------------------------------------------- -#define CPPYY_RET_W_CREATOR(type, fname) \ - PyObject* (*c)(type, cdims_t) = &fname; \ - ll->fCreator = (LowLevelView::Creator_t)c; \ - return (PyObject*)ll - -#define CPPYY_IMPL_VIEW_CREATOR(type) \ -PyObject* CPyCppyy::CreateLowLevelView(type* address, cdims_t shape) { \ - LowLevelView* ll = CreateLowLevelViewT(address, shape); \ - CPPYY_RET_W_CREATOR(type*, CreateLowLevelView); \ -} \ -PyObject* CPyCppyy::CreateLowLevelView(type** address, cdims_t shape) { \ - LowLevelView* ll = CreateLowLevelViewT(address, shape); \ - CPPYY_RET_W_CREATOR(type**, CreateLowLevelView); \ -} - -CPPYY_IMPL_VIEW_CREATOR(bool); -CPPYY_IMPL_VIEW_CREATOR(signed char); -CPPYY_IMPL_VIEW_CREATOR(unsigned char); -CPPYY_IMPL_VIEW_CREATOR(std::byte); -CPPYY_IMPL_VIEW_CREATOR(short); -CPPYY_IMPL_VIEW_CREATOR(unsigned short); -CPPYY_IMPL_VIEW_CREATOR(int); -CPPYY_IMPL_VIEW_CREATOR(unsigned int); -CPPYY_IMPL_VIEW_CREATOR(long); -CPPYY_IMPL_VIEW_CREATOR(unsigned long); -CPPYY_IMPL_VIEW_CREATOR(long long); -CPPYY_IMPL_VIEW_CREATOR(unsigned long long); -CPPYY_IMPL_VIEW_CREATOR(float); -CPPYY_IMPL_VIEW_CREATOR(double); -CPPYY_IMPL_VIEW_CREATOR(long double); -CPPYY_IMPL_VIEW_CREATOR(std::complex); -CPPYY_IMPL_VIEW_CREATOR(std::complex); -CPPYY_IMPL_VIEW_CREATOR(std::complex); -CPPYY_IMPL_VIEW_CREATOR(std::complex); - -PyObject* CPyCppyy::CreateLowLevelView(char* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape); - CPPYY_RET_W_CREATOR(char*, CreateLowLevelView); -} - -PyObject* CPyCppyy::CreateLowLevelView(char** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape); - CPPYY_RET_W_CREATOR(char**, CreateLowLevelView); -} - -PyObject* CPyCppyy::CreateLowLevelViewString(char** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, nullptr, nullptr, sizeof(char)); - CPPYY_RET_W_CREATOR(char**, CreateLowLevelViewString); -} - -PyObject* CPyCppyy::CreateLowLevelViewString(const char** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, nullptr, nullptr, sizeof(char)); - CPPYY_RET_W_CREATOR(const char**, CreateLowLevelViewString); -} - -PyObject* CPyCppyy::CreateLowLevelView_i8(int8_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "b", "int8_t"); - CPPYY_RET_W_CREATOR(int8_t*, CreateLowLevelView_i8); -} - -PyObject* CPyCppyy::CreateLowLevelView_i8(int8_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "b", "int8_t"); - CPPYY_RET_W_CREATOR(int8_t**, CreateLowLevelView_i8); -} - -PyObject* CPyCppyy::CreateLowLevelView_i8(uint8_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "B", "uint8_t"); - CPPYY_RET_W_CREATOR(uint8_t*, CreateLowLevelView_i8); -} - -PyObject* CPyCppyy::CreateLowLevelView_i8(uint8_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "B", "uint8_t"); - CPPYY_RET_W_CREATOR(uint8_t**, CreateLowLevelView_i8); -} diff --git a/src/CPyCppyy/src/LowLevelViews.h b/src/CPyCppyy/src/LowLevelViews.h deleted file mode 100644 index 0edb329..0000000 --- a/src/CPyCppyy/src/LowLevelViews.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef CPYCPPYY_LOWLEVELVIEWS_H -#define CPYCPPYY_LOWLEVELVIEWS_H - -// Bindings -#include "Dimensions.h" - -// Standard -#include -#include -#include - - -namespace CPyCppyy { - -class Converter; - -class LowLevelView { -public: - enum EFlags { - kDefault = 0x0000, - kIsCppArray = 0x0001, // allocated with new[] - kIsFixed = 0x0002, // fixed size array (assumed flat) - kIsOwner = 0x0004 }; // Python owns - -public: - PyObject_HEAD - Py_buffer fBufInfo; - void** fBuf; - Converter* fConverter; - Converter* fElemCnv; - - typedef LowLevelView* (*Creator_t)(void*, cdims_t); - Creator_t fCreator; // for slicing, which requires copying - -public: - void* get_buf() { return fBuf ? *fBuf : fBufInfo.buf; } - void set_buf(void** buf) { fBuf = buf; fBufInfo.buf = get_buf(); } - - bool resize(size_t sz); -}; - -#define CPPYY_DECL_VIEW_CREATOR(type) \ - PyObject* CreateLowLevelView(type*, cdims_t shape); \ - PyObject* CreateLowLevelView(type**, cdims_t shape) - -CPPYY_DECL_VIEW_CREATOR(bool); -CPPYY_DECL_VIEW_CREATOR(char); -CPPYY_DECL_VIEW_CREATOR(signed char); -CPPYY_DECL_VIEW_CREATOR(unsigned char); -CPPYY_DECL_VIEW_CREATOR(std::byte); -PyObject* CreateLowLevelView_i8(int8_t*, cdims_t shape); -PyObject* CreateLowLevelView_i8(int8_t**, cdims_t shape); -PyObject* CreateLowLevelView_i8(uint8_t*, cdims_t shape); -PyObject* CreateLowLevelView_i8(uint8_t**, cdims_t shape); -CPPYY_DECL_VIEW_CREATOR(short); -CPPYY_DECL_VIEW_CREATOR(unsigned short); -CPPYY_DECL_VIEW_CREATOR(int); -CPPYY_DECL_VIEW_CREATOR(unsigned int); -CPPYY_DECL_VIEW_CREATOR(long); -CPPYY_DECL_VIEW_CREATOR(unsigned long); -CPPYY_DECL_VIEW_CREATOR(long long); -CPPYY_DECL_VIEW_CREATOR(unsigned long long); -CPPYY_DECL_VIEW_CREATOR(float); -CPPYY_DECL_VIEW_CREATOR(double); -CPPYY_DECL_VIEW_CREATOR(long double); -CPPYY_DECL_VIEW_CREATOR(std::complex); -CPPYY_DECL_VIEW_CREATOR(std::complex); -CPPYY_DECL_VIEW_CREATOR(std::complex); -CPPYY_DECL_VIEW_CREATOR(std::complex); - -PyObject* CreateLowLevelViewString(char**, cdims_t shape); -PyObject* CreateLowLevelViewString(const char**, cdims_t shape); - -inline PyObject* CreatePointerView(void* ptr, cdims_t shape = 0) { - return CreateLowLevelView((uintptr_t*)ptr, shape); -} - -//- low level view type and type verification -------------------------------- -extern PyTypeObject LowLevelView_Type; - -template -inline bool LowLevelView_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &LowLevelView_Type); -} - -template -inline bool LowLevelView_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &LowLevelView_Type; -} - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_LOWLEVELVIEWS_H diff --git a/src/CPyCppyy/src/MemoryRegulator.cxx b/src/CPyCppyy/src/MemoryRegulator.cxx deleted file mode 100644 index 4b6d22f..0000000 --- a/src/CPyCppyy/src/MemoryRegulator.cxx +++ /dev/null @@ -1,255 +0,0 @@ -// Bindings -#include "Cppyy.h" -#include "CPyCppyy.h" -#include "MemoryRegulator.h" -#include "CPPInstance.h" -#include "ProxyWrappers.h" - -// Standard -#include -#include -#include - - -//= pseudo-None type for masking out objects on the python side =============== -static PyTypeObject CPyCppyy_NoneType; - -//----------------------------------------------------------------------------- -static Py_ssize_t AlwaysNullLength(PyObject*) -{ - return 0; -} - -//----------------------------------------------------------------------------- -static PyMappingMethods CPyCppyy_NoneType_mapping = { - AlwaysNullLength, - (binaryfunc) 0, - (objobjargproc) 0 -}; - -// silence warning about some cast operations -#if defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && ((__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ >= 1) || (__GNUC_MINOR__ >= 3)))) && !__INTEL_COMPILER -#pragma GCC diagnostic ignored "-Wstrict-aliasing" -#endif - - -//----------------------------------------------------------------------------- -namespace { - -struct InitCPyCppyy_NoneType_t { - InitCPyCppyy_NoneType_t() { - // create a CPyCppyy NoneType (for references that went dodo) from NoneType - memset(&CPyCppyy_NoneType, 0, sizeof(CPyCppyy_NoneType)); - - ((PyObject&)CPyCppyy_NoneType).ob_type = &PyType_Type; - Py_SET_REFCNT((PyObject*)&CPyCppyy_NoneType, 1); - ((PyVarObject&)CPyCppyy_NoneType).ob_size = 0; - - CPyCppyy_NoneType.tp_name = const_cast("CPyCppyy_NoneType"); - CPyCppyy_NoneType.tp_flags = Py_TPFLAGS_HAVE_RICHCOMPARE; - - CPyCppyy_NoneType.tp_traverse = (traverseproc)0; - CPyCppyy_NoneType.tp_clear = (inquiry)0; - CPyCppyy_NoneType.tp_dealloc = (destructor)&InitCPyCppyy_NoneType_t::DeAlloc; - CPyCppyy_NoneType.tp_repr = Py_TYPE(Py_None)->tp_repr; - CPyCppyy_NoneType.tp_richcompare = (richcmpfunc)&InitCPyCppyy_NoneType_t::RichCompare; - CPyCppyy_NoneType.tp_hash = (hashfunc)&InitCPyCppyy_NoneType_t::PtrHash; - - CPyCppyy_NoneType.tp_as_mapping = &CPyCppyy_NoneType_mapping; - - PyType_Ready(&CPyCppyy_NoneType); - } - - static void DeAlloc(PyObject* pyobj) { Py_TYPE(pyobj)->tp_free(pyobj); } - static int PtrHash(PyObject* pyobj) { return (int)ptrdiff_t(pyobj); } - - static PyObject* RichCompare(PyObject*, PyObject* other, int opid) { - return PyObject_RichCompare(other, Py_None, opid); - } - - static int Compare(PyObject*, PyObject* other) { - // TODO the following isn't correct as it doesn't order, but will do for now ... - return !PyObject_RichCompareBool(other, Py_None, Py_EQ); - } -}; - -} // unnamed namespace - -// Memory regulation hooks -CPyCppyy::MemHook_t CPyCppyy::MemoryRegulator::registerHook = nullptr; -CPyCppyy::MemHook_t CPyCppyy::MemoryRegulator::unregisterHook = nullptr; - - -//- ctor/dtor ---------------------------------------------------------------- -CPyCppyy::MemoryRegulator::MemoryRegulator() -{ -// setup NoneType for referencing and create weakref cache - static InitCPyCppyy_NoneType_t initCPyCppyy_NoneType; -} - - -//- public members ----------------------------------------------------------- -bool CPyCppyy::MemoryRegulator::RecursiveRemove( - Cppyy::TCppObject_t cppobj, Cppyy::TCppScope_t klass) -{ -// if registered by the framework, called whenever a cppobj gets destroyed - if (!cppobj) - return false; - - PyObject* pyscope = GetScopeProxy(klass); - if (!CPPScope_Check(pyscope)) { - Py_XDECREF(pyscope); - return false; - } - - CppToPyMap_t* cppobjs = ((CPPClass*)pyscope)->fImp.fCppObjects; - if (!cppobjs) { // table may have been deleted on shutdown - Py_DECREF(pyscope); - return false; - } - -// see whether we're tracking this object - CppToPyMap_t::iterator ppo = cppobjs->find(cppobj); - - if (ppo != cppobjs->end()) { - // get the tracked object - CPPInstance* pyobj = (CPPInstance*)ppo->second; - - // erase the object from tracking - pyobj->fFlags &= ~CPPInstance::kIsRegulated; - cppobjs->erase(ppo); - - // nullify the object - if (!CPyCppyy_NoneType.tp_traverse) { - // take a reference as we're copying its function pointers - Py_INCREF(Py_TYPE(pyobj)); - - // all object that arrive here are expected to be of the same type ("instance") - CPyCppyy_NoneType.tp_traverse = Py_TYPE(pyobj)->tp_traverse; - CPyCppyy_NoneType.tp_clear = Py_TYPE(pyobj)->tp_clear; - CPyCppyy_NoneType.tp_free = Py_TYPE(pyobj)->tp_free; - CPyCppyy_NoneType.tp_flags |= Py_TYPE(pyobj)->tp_flags; - } else if (CPyCppyy_NoneType.tp_traverse != Py_TYPE(pyobj)->tp_traverse) { - // TODO: SystemError? - std::cerr << "in CPyCppyy::MemoryRegulater, unexpected object of type: " - << Py_TYPE(pyobj)->tp_name << std::endl; - - // drop object and leave before too much damage is done - Py_DECREF(pyscope); - return false; - } - - // notify any other weak referents by playing dead - Py_ssize_t refcnt = Py_REFCNT((PyObject*)pyobj); - Py_SET_REFCNT((PyObject*)pyobj, 0); - PyObject_ClearWeakRefs((PyObject*)pyobj); - Py_SET_REFCNT((PyObject*)pyobj, refcnt); - - // cleanup object internals - pyobj->CppOwns(); // held object is out of scope now anyway - op_dealloc_nofree(pyobj); // normal object cleanup, while keeping memory - - // reset type object - Py_INCREF((PyObject*)(void*)&CPyCppyy_NoneType); - Py_DECREF(Py_TYPE(pyobj)); - ((PyObject*)pyobj)->ob_type = &CPyCppyy_NoneType; - - Py_DECREF(pyscope); - return true; - } - -// unregulated cppobj - Py_DECREF(pyscope); - return false; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::MemoryRegulator::RegisterPyObject( - CPPInstance* pyobj, Cppyy::TCppObject_t cppobj) -{ -// start tracking proxied by - if (!(pyobj && cppobj)) - return false; - - if (registerHook) { - auto res = registerHook(cppobj, pyobj->ObjectIsA(false)); - if (!res.second) return res.first; - } - - CppToPyMap_t* cppobjs = ((CPPClass*)Py_TYPE(pyobj))->fImp.fCppObjects; - if (!cppobjs) - return false; - -// if an address was already associated with a different object, then stop following -// the old and force insert the new proxy for following - const auto& res = cppobjs->insert(std::make_pair(cppobj, (PyObject*)pyobj)); - if (!res.second) { - ((CPPInstance*)res.first->second)->fFlags &= ~CPPInstance::kIsRegulated; - (*cppobjs)[cppobj] = (PyObject*)pyobj; - } - - pyobj->fFlags |= CPPInstance::kIsRegulated; - return true; -} - -//----------------------------------------------------------------------------- -bool CPyCppyy::MemoryRegulator::UnregisterPyObject(CPPInstance* pyobj, PyObject* pyclass) -{ -// called when the proxy is garbage collected or is about delete the C++ instance - if (!(pyobj && pyclass)) - return false; - - Cppyy::TCppObject_t cppobj = pyobj->IsSmart() ? pyobj->GetObjectRaw() : pyobj->GetObject(); - if (!cppobj) - return false; - - if (unregisterHook) { - auto res = unregisterHook(cppobj, ((CPPClass*)pyclass)->fCppType); - if (!res.second) return res.first; - } - - CppToPyMap_t* cppobjs = ((CPPClass*)pyclass)->fImp.fCppObjects; - if (!cppobjs) - return false; - -// erase if tracked - if (cppobjs->erase(cppobj)) { - pyobj->fFlags &= ~CPPInstance::kIsRegulated; - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::MemoryRegulator::RetrievePyObject(Cppyy::TCppObject_t cppobj, PyObject* pyclass) -{ -// lookup to see if a C++ address is already known, return old proxy if tracked - if (!(cppobj && pyclass)) - return nullptr; - - CppToPyMap_t* cppobjs = ((CPPClass*)pyclass)->fImp.fCppObjects; - if (!cppobjs) - return nullptr; - - CppToPyMap_t::iterator ppo = cppobjs->find(cppobj); - if (ppo != cppobjs->end()) { - Py_INCREF(ppo->second); - return ppo->second; - } - - return nullptr; -} - - -//----------------------------------------------------------------------------- -void CPyCppyy::MemoryRegulator::SetRegisterHook(MemHook_t h) { -// Set custom register hook; called when a regulated object is to be tracked - registerHook = h; -} - -//----------------------------------------------------------------------------- -void CPyCppyy::MemoryRegulator::SetUnregisterHook(MemHook_t h) { -// Set custom unregister hook; called when a regulated object is to be untracked - unregisterHook = h; -} diff --git a/src/CPyCppyy/src/MemoryRegulator.h b/src/CPyCppyy/src/MemoryRegulator.h deleted file mode 100644 index 6096f37..0000000 --- a/src/CPyCppyy/src/MemoryRegulator.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef CPYCPPYY_MEMORYREGULATOR_H -#define CPYCPPYY_MEMORYREGULATOR_H - -#include "Python.h" -#include "Cppyy.h" - -#include -#include - -namespace CPyCppyy { - -class CPPInstance; - -typedef std::function(Cppyy::TCppObject_t, Cppyy::TCppScope_t)> MemHook_t; - -class MemoryRegulator { -private: - static MemHook_t registerHook, unregisterHook; - -public: - MemoryRegulator(); - -// callback from C++-side frameworks - static bool RecursiveRemove(Cppyy::TCppObject_t cppobj, Cppyy::TCppScope_t klass); - -// called when a new python proxy object is created - static bool RegisterPyObject(CPPInstance* pyobj, Cppyy::TCppObject_t cppobj); - -// called when a the python proxy object is about to be garbage collected or when it is -// about to delete the proxied C++ object, if owned - static bool UnregisterPyObject(CPPInstance* pyobj, PyObject* pyclass); - -// new reference to python object matching cppobj, or 0 on failure - static PyObject* RetrievePyObject(Cppyy::TCppObject_t cppobj, PyObject* pyclass); - -// set hooks for custom memory regulation - static void SetRegisterHook(MemHook_t h); - static void SetUnregisterHook(MemHook_t h); -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_MEMORYREGULATOR_H diff --git a/src/CPyCppyy/src/ProxyWrappers.cxx b/src/CPyCppyy/src/ProxyWrappers.cxx deleted file mode 100644 index e03a4d2..0000000 --- a/src/CPyCppyy/src/ProxyWrappers.cxx +++ /dev/null @@ -1,979 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "ProxyWrappers.h" -#include "CPPClassMethod.h" -#include "CPPConstructor.h" -#include "CPPDataMember.h" -#include "CPPExcInstance.h" -#include "CPPFunction.h" -#include "CPPGetSetItem.h" -#include "CPPInstance.h" -#include "CPPMethod.h" -#include "CPPOperator.h" -#include "CPPOverload.h" -#include "CPPScope.h" -#include "Cppyy.h" -#include "MemoryRegulator.h" -#include "PyStrings.h" -#include "Pythonize.h" -#include "TemplateProxy.h" -#include "TupleOfInstances.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include -#include -#include - - -//- data _______________________________________________________________________ -namespace CPyCppyy { - extern PyObject* gThisModule; - extern PyObject* gPyTypeMap; - extern std::unordered_set gPinnedTypes; -} - -// to prevent having to walk scopes, track python classes by C++ class -typedef std::unordered_map PyClassMap_t; -static PyClassMap_t gPyClasses; - - -//- helpers -------------------------------------------------------------------- - -namespace CPyCppyy { - -typedef struct { - PyObject_HEAD - PyObject *dict; -} proxyobject; - -// helper for creating new C++ proxy python types -static PyObject* CreateNewCppProxyClass(Cppyy::TCppScope_t klass, PyObject* pybases) -{ -// Create a new python shadow class with the required hierarchy and meta-classes. - PyObject* pymetabases = PyTuple_New(PyTuple_GET_SIZE(pybases)); - for (int i = 0; i < PyTuple_GET_SIZE(pybases); ++i) { - PyObject* btype = (PyObject*)Py_TYPE(PyTuple_GetItem(pybases, i)); - Py_INCREF(btype); - PyTuple_SET_ITEM(pymetabases, i, btype); - } - - std::string name = Cppyy::GetFinalName(klass); - -// create meta-class, add a dummy __module__ to pre-empt the default setting - PyObject* args = Py_BuildValue((char*)"sO{}", (name+"_meta").c_str(), pymetabases); - PyDict_SetItem(PyTuple_GET_ITEM(args, 2), PyStrings::gModule, Py_True); - Py_DECREF(pymetabases); - - PyObject* pymeta = (PyObject*)CPPScopeMeta_New(klass, args); - Py_DECREF(args); - if (!pymeta) - return nullptr; - -// alright, and now we really badly want to get rid of the dummy ... - PyObject* dictproxy = PyObject_GetAttr(pymeta, PyStrings::gDict); - PyDict_DelItem(((proxyobject*)dictproxy)->dict, PyStrings::gModule); - -// create actual class - args = Py_BuildValue((char*)"sO{}", name.c_str(), pybases); - PyObject* pyclass = - ((PyTypeObject*)pymeta)->tp_new((PyTypeObject*)pymeta, args, nullptr); - - Py_DECREF(args); - Py_DECREF(pymeta); - - return pyclass; -} - -static inline -void AddPropertyToClass(PyObject* pyclass, - Cppyy::TCppScope_t scope, Cppyy::TCppScope_t data) -{ - CPyCppyy::CPPDataMember* property = CPyCppyy::CPPDataMember_New(scope, data); - PyObject* pname = CPyCppyy_PyText_InternFromString(const_cast(property->GetName().c_str())); - -// allow access at the instance level - PyType_Type.tp_setattro(pyclass, pname, (PyObject*)property); - if (PyErr_Occurred()) { - // CPPDataMember.tp_descr_set raises an error - // when all of the following conditions are met - // 1. data is static const - // 2. scope is a derived class - // 3. data is defined in both parent and base class (using parent::attribute;) - PyErr_Clear(); - } - -// allow access at the class level (always add after setting instance level) - if (Cppyy::IsStaticDatamember(data) || Cppyy::IsEnumConstant(data)) - PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pname, (PyObject*)property); - -// cleanup - Py_DECREF(pname); - Py_DECREF(property); -} - -static inline -void AddScopeToParent(PyObject* parent, const std::string& name, PyObject* newscope) -{ - PyObject* pyname = CPyCppyy_PyText_InternFromString((char*)name.c_str()); - if (CPPScope_Check(parent)) PyType_Type.tp_setattro(parent, pyname, newscope); - else PyObject_SetAttr(parent, pyname, newscope); - Py_DECREF(pyname); -} - -static inline -PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { -// get an attribute without causing getattr lookups - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - if (dct) { - PyObject* attr = PyObject_GetItem(dct, pyname); - Py_DECREF(dct); - return attr; - } - return nullptr; -} - -} // namespace CPyCppyy - - -//- public functions --------------------------------------------------------- -namespace CPyCppyy { - -static inline void sync_templates( - PyObject* pyclass, const std::string& mtCppName, const std::string& mtName) -{ - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - PyObject* pyname = CPyCppyy_PyText_FromString(const_cast(mtName.c_str())); - PyObject* attr = PyObject_GetItem(dct, pyname); - if (!attr) PyErr_Clear(); - Py_DECREF(dct); - if (!TemplateProxy_Check(attr)) { - TemplateProxy* pytmpl = TemplateProxy_New(mtCppName, mtName, pyclass); - if (CPPOverload_Check(attr)) pytmpl->MergeOverload((CPPOverload*)attr); - PyType_Type.tp_setattro(pyclass, pyname, (PyObject*)pytmpl); - Py_DECREF(pytmpl); - } - Py_XDECREF(attr); - Py_DECREF(pyname); -} - -static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, const unsigned int flags) -{ -// Collect methods and data for the given scope, and add them to the given python -// proxy object. - -// some properties that'll affect building the dictionary - bool isNamespace = Cppyy::IsNamespace(scope); - bool isComplete = Cppyy::IsComplete(scope); - bool hasConstructor = false; - Cppyy::TCppMethod_t potGetItem = nullptr; - -// load all public methods and data members - typedef std::vector Callables_t; - typedef std::unordered_map CallableCache_t; - CallableCache_t cache; - -// bypass custom __getattr__ for efficiency - getattrofunc oldgetattro = Py_TYPE(pyclass)->tp_getattro; - Py_TYPE(pyclass)->tp_getattro = PyType_Type.tp_getattro; - -// functions in namespaces are properly found through lazy lookup, so do not -// create them until needed (the same is not true for data members) - std::vector methods; - if (isComplete) Cppyy::GetClassMethods(scope, methods); - - for (auto &method : methods) { - - // do not expose non-public methods as the Cling wrappers as those won't compile - if (!Cppyy::IsPublicMethod(method)) - continue; - - // process the method based on its name - std::string mtCppName = Cppyy::GetName(Cppyy::TCppScope_t(method.data)); - - // special case trackers - bool setupSetItem = false; - bool isConstructor = Cppyy::IsConstructor(method); - - // Here isTemplate means to ignore templated constructors, that is handled in the next loop. - bool isTemplate = Cppyy::IsTemplatedMethod(method) && !isConstructor; - bool isStubbedOperator = false; - - // filter empty names (happens for namespaces, is bug?) - if (mtCppName == "") - continue; - - // filter C++ destructors - if (mtCppName[0] == '~') - continue; - - // translate operators - std::string mtName = Utility::MapOperatorName( - mtCppName, Cppyy::GetMethodNumArgs(method), &isStubbedOperator); - if (mtName.empty()) - continue; - - // operator[]/() returning a reference type will be used for __setitem__ - bool isCall = mtName == "__call__"; - if (isCall || mtName == "__getitem__") { - const std::string& qual_return = Cppyy::GetMethodReturnTypeAsString(method); - const std::string& cpd = TypeManip::compound(qual_return); - if (!cpd.empty() && cpd[cpd.size()-1] == '&' && \ - qual_return.find("const", 0, 5) == std::string::npos) { - if (isCall && !potGetItem) potGetItem = method; - setupSetItem = true; // will add methods as overloads - } else if (isCall && 1 < Cppyy::GetMethodNumArgs(method)) { - // not a non-const by-ref return, thus better __getitem__ candidate; the - // requirement for multiple arguments is that there is otherwise no benefit - // over the use of normal __getitem__ (this allows multi-indexing arguments, - // which is clean in Python, but not allowed in C++) - potGetItem = method; - } - } - - // template members; handled by adding a dispatcher to the class - bool storeOnTemplate = isTemplate; - if (storeOnTemplate) { - sync_templates(pyclass, mtCppName, mtName); - // continue processing to actually add the method so that the proxy can find - // it on the class when called explicitly - } - - // construct the holder - PyCallable* pycall = nullptr; - if (Cppyy::IsStaticMethod(method)) // class method - pycall = new CPPClassMethod(scope, method); - else if (isNamespace) // free function - pycall = new CPPFunction(scope, method); - else if (isConstructor) { // ctor - mtName = "__init__"; - hasConstructor = true; - if (!Cppyy::IsAbstract(scope)) { - if (flags & CPPScope::kIsMultiCross) { - pycall = new CPPMultiConstructor(scope, method); - } else - pycall = new CPPConstructor(scope, method); - } else - pycall = new CPPAbstractClassConstructor(scope, method); - } else if (isStubbedOperator) { - pycall = new CPPOperator(scope, method, mtName); - } else // member function - pycall = new CPPMethod(scope, method); - - if (storeOnTemplate) { - // template proxy was already created in sync_templates call above, so - // add only here, not to the cache of collected methods - PyObject* attr = PyObject_GetAttrString(pyclass, const_cast(mtName.c_str())); - if (isTemplate) ((TemplateProxy*)attr)->AdoptTemplate(pycall); - else ((TemplateProxy*)attr)->AdoptMethod(pycall); - Py_DECREF(attr); - - // for operator[]/() that returns by ref, also add __setitem__ - if (setupSetItem) { - TemplateProxy* pysi = (TemplateProxy*)GetAttrDirect(pyclass, PyStrings::gSetItem); - if (!TemplateProxy_Check(pysi)) { - CPPOverload* precursor = (CPPOverload_Check(pysi)) ? (CPPOverload*)pysi : nullptr; - if (pysi && !precursor) Py_DECREF(pysi); // something unknown, just drop it - pysi = TemplateProxy_New(mtCppName, "__setitem__", pyclass); - if (precursor) pysi->MergeOverload(precursor); - Py_XDECREF(precursor); - PyObject_SetAttrString(pyclass, const_cast("__setitem__"), (PyObject*)pysi); - } - if (isTemplate) pysi->AdoptTemplate(new CPPSetItem(scope, method)); - else pysi->AdoptMethod(new CPPSetItem(scope, method)); - Py_XDECREF(pysi); - } - - } else { - // lookup method dispatcher and store method - Callables_t& md = (*(cache.insert( - std::make_pair(mtName, Callables_t())).first)).second; - md.push_back(pycall); - - // special case for operator[]/() that returns by ref, use for getitem/call and setitem - if (setupSetItem) { - Callables_t& setitem = (*(cache.insert( - std::make_pair(std::string("__setitem__"), Callables_t())).first)).second; - setitem.push_back(new CPPSetItem(scope, method)); - } - } - } - - -// add proxies for un-instantiated/non-overloaded templated methods - std::vector templ_methods; - Cppyy::GetTemplatedMethods(scope, templ_methods); - for (auto &method : templ_methods) { - const std::string mtCppName = Cppyy::GetName(Cppyy::TCppScope_t(method.data)); - // the number of arguments isn't known until instantiation and as far as C++ is concerned, all - // same-named operators are simply overloads; so will pre-emptively add both names if with and - // without arguments differ, letting the normal overload mechanism resolve on call - bool isConstructor = Cppyy::IsConstructor(method); - - // first add with no arguments - std::string mtName0 = isConstructor ? "__init__" : Utility::MapOperatorName(mtCppName, false); - sync_templates(pyclass, mtCppName, mtName0); - - // then add when taking arguments, if this method is different - if (!isConstructor) { - std::string mtName1 = Utility::MapOperatorName(mtCppName, true); - if (mtName0 != mtName1) - sync_templates(pyclass, mtCppName, mtName1); - } - } - -// add a pseudo-default ctor, if none defined - if (!hasConstructor) { - PyCallable* defctor = nullptr; - if (!isComplete) { - ((CPPScope*)pyclass)->fFlags |= CPPScope::kIsInComplete; - defctor = new CPPIncompleteClassConstructor(scope, Cppyy::TCppMethod_t{}); - } else if (Cppyy::IsAbstract(scope)) { - defctor = new CPPAbstractClassConstructor(scope, Cppyy::TCppMethod_t{}); - } else if (isNamespace) { - defctor = new CPPNamespaceConstructor(scope, Cppyy::TCppMethod_t{}); - } else { - defctor = new CPPAllPrivateClassConstructor(scope, Cppyy::TCppMethod_t{}); - } - - if (defctor) - cache["__init__"].push_back(defctor); - } - -// map __call__ to __getitem__ if also mapped to __setitem__ - if (potGetItem) { - Callables_t& getitem = (*(cache.insert( - std::make_pair(std::string("__getitem__"), Callables_t())).first)).second; - getitem.push_back(new CPPGetItem(scope, potGetItem)); - } - -// add the methods to the class dictionary - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - for (CallableCache_t::iterator imd = cache.begin(); imd != cache.end(); ++imd) { - // in order to prevent removing templated editions of this method (which were set earlier, - // above, as a different proxy object), we'll check and add this method flagged as a generic - // one (to be picked up by the templated one as appropriate) if a template exists - PyObject* pyname = CPyCppyy_PyText_FromString(const_cast(imd->first.c_str())); - PyObject* attr = PyObject_GetItem(dct, pyname); - Py_DECREF(pyname); - if (TemplateProxy_Check(attr)) { - // template exists, supply it with the non-templated method overloads - for (auto cit : imd->second) - ((TemplateProxy*)attr)->AdoptMethod(cit); - } else { - if (!attr) PyErr_Clear(); - // normal case, add a new method - CPPOverload* method = CPPOverload_New(imd->first, imd->second); - PyObject* pymname = CPyCppyy_PyText_InternFromString(const_cast(method->GetName().c_str())); - PyType_Type.tp_setattro(pyclass, pymname, (PyObject*)method); - Py_DECREF(pymname); - Py_DECREF(method); - } - - Py_XDECREF(attr); // could have been found in base class or non-existent - } - Py_DECREF(dct); - - // collect data members (including enums) - std::vector datamembers; - Cppyy::GetDatamembers(scope, datamembers); - for (auto &datamember : datamembers) { - // allow only public members - if (!Cppyy::IsPublicData(datamember)) - continue; - - // enum datamembers (this in conjunction with previously collected enums above) - if (Cppyy::IsEnumType(Cppyy::GetDatamemberType(datamember)) && Cppyy::IsEnumConstant(datamember)) { - // two options: this is a static variable, or it is the enum value, the latter - // already exists, so check for it and move on if set - PyObject* eset = PyObject_GetAttrString(pyclass, - const_cast(Cppyy::GetFinalName(datamember).c_str())); - if (eset) { - Py_DECREF(eset); - continue; - } - - PyErr_Clear(); - - // it could still be that this is an anonymous enum, which is not in the list - // provided by the class - if (strstr(Cppyy::GetDatamemberTypeAsString(datamember).c_str(), "(anonymous)") != 0 || - strstr(Cppyy::GetDatamemberTypeAsString(datamember).c_str(), "(unnamed)") != 0) { - AddPropertyToClass(pyclass, scope, datamember); - continue; - } - } - - // properties (aka public (static) data members) - AddPropertyToClass(pyclass, scope, datamember); - } - -// restore custom __getattr__ - Py_TYPE(pyclass)->tp_getattro = oldgetattro; - -// all ok, done - return 0; -} - -//---------------------------------------------------------------------------- -static void CollectUniqueBases(Cppyy::TCppScope_t klass, std::deque& uqb) -{ -// collect bases in acceptable mro order, while removing duplicates (this may -// break the overload resolution in esoteric cases, but otherwise the class can -// not be used at all, as CPython will refuse the mro). - size_t nbases = Cppyy::GetNumBases(klass); - - for (size_t ibase = 0; ibase < nbases; ++ibase) { - Cppyy::TCppScope_t tp = Cppyy::GetBaseScope(klass, ibase); - int decision = 2; - if (!tp) continue; // means this base with not be available Python-side - for (size_t ibase2 = 0; ibase2 < uqb.size(); ++ibase2) { - if (uqb[ibase2] == tp) { // not unique ... skip - decision = 0; - break; - } - - if (Cppyy::IsSubclass(tp, uqb[ibase2])) { - // mro requirement: sub-type has to follow base - decision = 1; - break; - } - } - - if (decision == 1) { - uqb.push_front(tp); - } else if (decision == 2) { - uqb.push_back(tp); - } - // skipped if decision == 0 (not unique) - } -} - -static PyObject* BuildCppClassBases(Cppyy::TCppScope_t klass) -{ -// Build a tuple of python proxy classes of all the bases of the given 'klass'. - std::deque uqb; - CollectUniqueBases(klass, uqb); - -// allocate a tuple for the base classes, special case for first base - size_t nbases = uqb.size(); - - PyObject* pybases = PyTuple_New(nbases ? nbases : 1); - if (!pybases) - return nullptr; - -// build all the bases - if (nbases == 0) { - Py_INCREF((PyObject*)(void*)&CPPInstance_Type); - PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPInstance_Type); - } else { - for (std::deque::size_type ibase = 0; ibase < nbases; ++ibase) { - PyObject* pyclass = CreateScopeProxy(uqb[ibase]); - if (!pyclass) { - Py_DECREF(pybases); - return nullptr; - } - - PyTuple_SET_ITEM(pybases, ibase, pyclass); - } - - // special case, if true python types enter the hierarchy, make sure that - // the first base seen is still the CPPInstance_Type - if (!PyObject_IsSubclass(PyTuple_GET_ITEM(pybases, 0), (PyObject*)&CPPInstance_Type)) { - PyObject* newpybases = PyTuple_New(nbases+1); - Py_INCREF((PyObject*)(void*)&CPPInstance_Type); - PyTuple_SET_ITEM(newpybases, 0, (PyObject*)(void*)&CPPInstance_Type); - for (int ibase = 0; ibase < (int)nbases; ++ibase) { - PyObject* pyclass = PyTuple_GET_ITEM(pybases, ibase); - Py_INCREF(pyclass); - PyTuple_SET_ITEM(newpybases, ibase+1, pyclass); - } - Py_DECREF(pybases); - pybases = newpybases; - } - } - - return pybases; -} - -} // namespace CPyCppyy - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::GetScopeProxy(Cppyy::TCppScope_t scope) -{ -// Retrieve scope proxy from the known ones. - PyClassMap_t::iterator pci = gPyClasses.find(scope); - if (pci != gPyClasses.end()) { - PyObject* pyclass = CPyCppyy_GetWeakRef(pci->second); - if (pyclass) - return pyclass; - } - - return nullptr; -} -namespace CPyCppyy { -PyObject *CppType_To_PyObject(Cppyy::TCppType_t type, std::string name, Cppyy::TCppScope_t parent_scope, PyObject *parent) { - Cppyy::TCppType_t resolved_type = Cppyy::ResolveType(type); - if (gPyTypeMap) { - const std::string& resolved = Cppyy::GetTypeAsString(resolved_type); - PyObject* tc = PyDict_GetItemString(gPyTypeMap, resolved.c_str()); // borrowed - if (tc && PyCallable_Check(tc)) { - const std::string& scName = Cppyy::GetScopedFinalName(parent_scope); - PyObject* nt = PyObject_CallFunction(tc, (char*)"ss", name.c_str(), scName != "" ? scName.c_str() : ""); - if (nt) { - if (parent) { - AddScopeToParent(parent, name, nt); - Py_DECREF(parent); - } - return nt; - } - PyErr_Clear(); - } - } - return nullptr; -} -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateScopeProxy(PyObject*, PyObject* args) -{ -// Build a python shadow class for the named C++ class. - std::string cname = CPyCppyy_PyText_AsString(PyTuple_GetItem(args, 0)); - if (PyErr_Occurred()) - return nullptr; - - return CreateScopeProxy(cname); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateScopeProxy(const std::string& name, PyObject* parent, const unsigned flags) -{ -// Build a python shadow class for the named C++ class or namespace. - -// determine complete scope name, if a python parent has been given - Cppyy::TCppScope_t parent_scope; - if (parent) { - if (CPPScope_Check(parent)) - parent_scope = ((CPPScope*)parent)->fCppType; - else { - PyObject* parname = PyObject_GetAttr(parent, PyStrings::gName); - if (!parname) { - PyErr_Format(PyExc_SystemError, "given scope has no name for %s", name.c_str()); - return nullptr; - } - - // should be a string - std::string scName = CPyCppyy_PyText_AsString(parname); - Py_DECREF(parname); - if (PyErr_Occurred()) - return nullptr; - parent_scope = Cppyy::GetScope(scName); - } - - Py_INCREF(parent); - } - -// retrieve C++ class (this verifies name, and is therefore done first) - if (name == "") { - Cppyy::TCppScope_t klass = Cppyy::GetGlobalScope(); - Py_INCREF(gThisModule); - parent = gThisModule; - return CreateScopeProxy(klass, parent, flags); - } else if (Cppyy::TCppScope_t klass = Cppyy::GetScope(name, parent_scope)) { - if (Cppyy::IsTypedefed(klass) && - Cppyy::IsPointerType(Cppyy::GetTypeFromScope(klass)) && - Cppyy::IsClass(Cppyy::GetUnderlyingScope(klass))) - return nullptr; // this is handled by the caller; typedef to class pointer - return CreateScopeProxy(klass, parent, flags); - } else if (Cppyy::IsBuiltin(name)) { - Cppyy::TCppType_t type = Cppyy::GetType(name); - PyObject *result = nullptr; - if (type) - result = CppType_To_PyObject(type, name, parent_scope, parent); - if (result) - return result; - } - // all options have been exhausted: it doesn't exist as such - PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", name.c_str()); - Py_XDECREF(parent); - return nullptr; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateScopeProxy(Cppyy::TCppScope_t scope, PyObject* parent, const unsigned flags) -{ -// Convenience function with a lookup first through the known existing proxies. - PyObject* pyclass = GetScopeProxy(scope); - if (pyclass) - return pyclass; - - Cppyy::TCppScope_t parent_scope = nullptr; - if (CPPScope_Check(parent)) - parent_scope = ((CPPScope*)parent)->fCppType; - if (!parent_scope) - parent_scope = Cppyy::GetParentScope(scope); - - if (!parent) { - if (parent_scope) - parent = CreateScopeProxy(parent_scope); - else { - Py_INCREF(gThisModule); - parent = gThisModule; - } - } - - std::string name = Cppyy::GetFinalName(scope); - bool is_typedef = false; - std::string typedefed_name = ""; - if (Cppyy::IsTypedefed(scope)) { - is_typedef = true; - typedefed_name = Cppyy::GetFullName(scope); - Cppyy::TCppScope_t underlying_scope = Cppyy::GetUnderlyingScope(scope); - if ((underlying_scope) && (underlying_scope != scope)) { - scope = underlying_scope; - } else { - if (PyObject *result = CppType_To_PyObject(Cppyy::GetTypeFromScope(scope), name, parent_scope, parent)) - return result; - - PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", Cppyy::GetScopedFinalName(scope).c_str()); - Py_XDECREF(parent); - return nullptr; - } - } - - if (Cppyy::IsTemplate(scope)) { - // a "naked" templated class is requested: return callable proxy for instantiations - PyObject* pytcl = PyObject_GetAttr(gThisModule, PyStrings::gTemplate); - PyObject* cppscope = PyLong_FromVoidPtr(scope.data); - PyObject* pytemplate = PyObject_CallFunction( - pytcl, const_cast("sO"), - const_cast(Cppyy::GetScopedFinalName(scope).c_str()), - cppscope); - Py_DECREF(pytcl); - - // cache the result - AddScopeToParent(parent, name, pytemplate); - - // done, next step should be a call into this template - Py_XDECREF(parent); - return pytemplate; - } - - if (Cppyy::IsEnumScope(scope)) - return nullptr; - -// locate class by ID, if possible, to prevent parsing scopes/templates anew - PyObject* pyscope = GetScopeProxy(scope); - if (pyscope) { - if (parent) { - AddScopeToParent(parent, name, pyscope); - Py_DECREF(parent); - } - return pyscope; - } - - // if the scope was earlier found as actual, then we're done already, otherwise - // build a new scope proxy - if (!pyscope) { - // construct the base classes - PyObject* pybases = BuildCppClassBases(scope); - if (pybases != 0) { - // create a fresh Python class, given bases, name, and empty dictionary - pyscope = CreateNewCppProxyClass(scope, pybases); - Py_DECREF(pybases); - } - - // fill the dictionary, if successful - if (pyscope) { - if (BuildScopeProxyDict(scope, pyscope, flags)) { - // something failed in building the dictionary - Py_DECREF(pyscope); - pyscope = nullptr; - } - } - - // store a ref from cppyy scope id to new python class - if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { - gPyClasses[scope] = PyWeakref_NewRef(pyscope, nullptr); - - if (!(((CPPScope*)pyscope)->fFlags & CPPScope::kIsNamespace)) { - // add python-style features to classes only - if (!Pythonize(pyscope, scope)) { - Py_DECREF(pyscope); - pyscope = nullptr; - } - } else { - // add to sys.modules to allow importing from this namespace - PyObject* pyfullname = PyObject_GetAttr(pyscope, PyStrings::gModule); - CPyCppyy_PyText_AppendAndDel(&pyfullname, CPyCppyy_PyText_FromString(".")); - CPyCppyy_PyText_AppendAndDel(&pyfullname, PyObject_GetAttr(pyscope, PyStrings::gName)); - PyObject* modules = PySys_GetObject(const_cast("modules")); - if (modules && PyDict_Check(modules)) - PyDict_SetItem(modules, pyfullname, pyscope); - Py_DECREF(pyfullname); - } - } - } - -// store on parent if found/created and complete - if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { - // FIXME: This is to mimic original behaviour. Still required? - if (Cppyy::IsTemplateInstantiation(scope)) - name = Cppyy::GetScopedFinalName(scope); - if (is_typedef && !typedefed_name.empty()) - AddScopeToParent(parent, typedefed_name, pyscope); - AddScopeToParent(parent, name, pyscope); - } - Py_DECREF(parent); - -// all done - return pyscope; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyObject* parent) -{ -// To allow use of C++ exceptions in lieue of Python exceptions, they need to -// derive from BaseException, which can not mix with the normal CPPInstance and -// use of the meta-class. Instead, encapsulate them in a forwarding class that -// derives from Pythons Exception class - -// start with creation of CPPExcInstance type base classes - std::deque uqb; - CollectUniqueBases(((CPPScope*)pyscope)->fCppType, uqb); - size_t nbases = uqb.size(); - -// Support for multiple bases actually can not actually work as-is: the reason -// for deriving from BaseException is to guarantee the layout needed for storing -// traces. If some other base is std::exception (as e.g. boost::bad_any_cast) or -// also derives from std::exception, then there are two trace locations. OTOH, -// if the other class is a non-exception type, then the exception class does not -// need to derive from it because it can never be caught as that type forwarding -// to the proxy will work as expected, through, which is good enough). -// -// The code below restricts the hierarchy to a single base class, picking the -// "best" by filtering std::exception and non-exception bases. - - PyObject* pybases = PyTuple_New(1); - if (nbases == 0) { - Py_INCREF((PyObject*)(void*)&CPPExcInstance_Type); - PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPExcInstance_Type); - } else { - PyObject* best_base = nullptr; - - for (std::deque::size_type ibase = 0; ibase < nbases; ++ibase) { - // retrieve bases through their enclosing scope to guarantee treatment as - // exception classes and proper caching - Cppyy::TCppScope_t parent_scope = Cppyy::GetParentScope(uqb[ibase]); - PyObject* base_parent = CreateScopeProxy(parent_scope); - if (!base_parent) { - Py_DECREF(pybases); - return nullptr; - } - - PyObject* excbase = PyObject_GetAttrString(base_parent, - Cppyy::GetFinalName(uqb[ibase]).c_str()); - Py_DECREF(base_parent); - if (!excbase) { - Py_DECREF(pybases); - return nullptr; - } - - if (PyType_IsSubtype((PyTypeObject*)excbase, &CPPExcInstance_Type)) { - Py_XDECREF(best_base); - best_base = excbase; - - if (Cppyy::GetScopedFinalName(uqb[ibase]) != "std::exception") - break; - } else { - // just skip: there will be at least one exception derived base class - Py_DECREF(excbase); - } - } - - PyTuple_SET_ITEM(pybases, 0, best_base); - } - - PyObject* args = Py_BuildValue((char*)"OO{}", pyname, pybases); - -// meta-class attributes (__cpp_name__, etc.) can not be resolved lazily so add -// them directly instead in case they are needed - PyObject* dct = PyTuple_GET_ITEM(args, 2); - PyDict_SetItem(dct, PyStrings::gUnderlying, pyscope); - PyDict_SetItem(dct, PyStrings::gName, PyObject_GetAttr(pyscope, PyStrings::gName)); - PyDict_SetItem(dct, PyStrings::gCppName, PyObject_GetAttr(pyscope, PyStrings::gCppName)); - PyDict_SetItem(dct, PyStrings::gModule, PyObject_GetAttr(pyscope, PyStrings::gModule)); - -// create the actual exception class - PyObject* exc_pyscope = PyType_Type.tp_new(&PyType_Type, args, nullptr); - Py_DECREF(args); - Py_DECREF(pybases); - -// cache the result for future lookups and return - PyType_Type.tp_setattro(parent, pyname, exc_pyscope); - return exc_pyscope; -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::BindCppObjectNoCast(Cppyy::TCppObject_t address, - Cppyy::TCppScope_t klass, const unsigned flags) -{ -// only known or knowable objects will be bound (null object is ok) - if (!klass) { - PyErr_SetString(PyExc_TypeError, "attempt to bind C++ object w/o class"); - return nullptr; - } - -// retrieve python class - PyObject* pyclass = CreateScopeProxy(klass); - if (!pyclass) - return nullptr; // error has been set in CreateScopeProxy - - bool noReg = flags & (CPPInstance::kNoMemReg|CPPInstance::kNoWrapConv); - bool isRef = flags & CPPInstance::kIsReference; - void* r_address = isRef ? (address ? *(void**)address.data : nullptr) : address.data; - -// check whether the object to be bound is a smart pointer that needs embedding - PyObject* smart_type = (!(flags & CPPInstance::kNoWrapConv) && \ - (((CPPClass*)pyclass)->fFlags & CPPScope::kIsSmart)) ? pyclass : nullptr; - if (smart_type) { - Cppyy::TCppScope_t underlying = ((CPPSmartClass*)smart_type)->fUnderlyingType; - - // Downcast the underlying object to its actual (most derived) class, as - // BindCppObject does for raw pointers, but only when the cast is safe: - // * the actual class must really derive from the declared one; - // GetActualClass() can report a base (e.g. when the actual class has no - // dictionary of its own), and such an up-cast must be rejected. - // * the cast must need no pointer adjustment: the dereferencer always - // returns a pointer to the declared class, so a non-zero offset could - // not be applied consistently on later member access (multiple - // inheritance). - if (address && !isRef) { - if (void* deref = Cppyy::CallR( - ((CPPSmartClass*)smart_type)->fDereferencer, address, 0, nullptr)) { - Cppyy::TCppScope_t clActual = Cppyy::GetActualClass(underlying, deref); - if (clActual && clActual != underlying && - Cppyy::IsSubclass(clActual, underlying) && - Cppyy::GetBaseOffset(clActual, underlying, deref, -1 /* down-cast */) == 0) - underlying = clActual; - } - } - - pyclass = CreateScopeProxy(underlying); - if (!pyclass) { - // simply restore and expose as the actual smart pointer class - pyclass = smart_type; - smart_type = nullptr; - } - } - -// TODO: make sure that a consistent address is used (may have to be done in BindCppObject) - if (address && !(flags & CPPInstance::kIsValue) /* always fresh */ && !noReg) { - PyObject* oldPyObject = MemoryRegulator::RetrievePyObject(r_address, pyclass); - - // embedded smart pointers are registered with the class of the underlying type, b/c - // there is no Python proxy for the smart pointer itself in that case; check whether - // the result found matches the smart-ness and if so, whether the smart pointer found - // is the same as the one requested (note: ptr-ptr requires old object to be a - // reference to enable re-use) - if (oldPyObject) { - CPPInstance* o_pyobj = ((CPPInstance*)oldPyObject); - - if ((bool)smart_type == o_pyobj->IsSmart() && \ - r_address == (smart_type ? o_pyobj->GetObjectRaw() : o_pyobj->GetObject()) && \ - (!(flags & CPPInstance::kIsPtrPtr) || (o_pyobj->fFlags & CPPInstance::kIsReference))) { - Py_DECREF(pyclass); - return oldPyObject; - } else { // reference or naked v.s. smart pointer mismatch - Py_DECREF(oldPyObject); - } - } - } - -// instantiate an object of this class - PyObject* args = PyTuple_New(0); - CPPInstance* pyobj = - (CPPInstance*)((PyTypeObject*)pyclass)->tp_new((PyTypeObject*)pyclass, args, nullptr); - Py_DECREF(args); - -// bind, register and return if successful - if (pyobj != 0) { // fill proxy value? - unsigned objflags = flags & \ - (CPPInstance::kIsReference | CPPInstance::kIsPtrPtr | CPPInstance::kIsValue | CPPInstance::kIsOwner | CPPInstance::kIsActual); - pyobj->Set(address.data, (CPPInstance::EFlags)objflags); - - if (smart_type) - pyobj->SetSmart(smart_type); - - // do not register null pointers, references (regulated objects can be referenced, but - // referenced objects can not be regulated), or direct usage of smart pointers or iterators - if (address && !isRef && !noReg) - MemoryRegulator::RegisterPyObject(pyobj, smart_type ? pyobj->GetObjectRaw() : pyobj->GetObject()); - } - -// successful completion; wrap exception options to make them raiseable, normal return otherwise - if (((CPPClass*)pyclass)->fFlags & CPPScope::kIsException) { - PyObject* exc_obj = CPPExcInstance_Type.tp_new(&CPPExcInstance_Type, nullptr, nullptr); - ((CPPExcInstance*)exc_obj)->fCppInstance = (PyObject*)pyobj; - Py_DECREF(pyclass); - return exc_obj; - } - - Py_DECREF(pyclass); - return (PyObject*)pyobj; -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::BindCppObject(Cppyy::TCppObject_t address, - Cppyy::TCppScope_t klass, const unsigned flags) -{ -// if the object is a null pointer, return a typed one (as needed for overloading) - if (!address) - return BindCppObjectNoCast(address, klass, flags); - -// only known or knowable objects will be bound - if (!klass) { - PyErr_SetString(PyExc_TypeError, "attempt to bind C++ object w/o class"); - return nullptr; - } - - bool isRef = flags & CPPInstance::kIsReference; - -// downcast to real class for object returns, unless pinned -// TODO: should the memory regulator for klass be searched first, so that if -// successful, no down-casting is attempted? -// TODO: optimize for final classes - unsigned new_flags = flags; - if (!isRef && (gPinnedTypes.empty() || gPinnedTypes.find(klass) == gPinnedTypes.end())) { - Cppyy::TCppScope_t clActual = Cppyy::GetActualClass(klass, address); - - if (clActual) { - if (clActual != klass) { - intptr_t offset = Cppyy::GetBaseOffset( - clActual, klass, address, -1 /* down-cast */, true /* report errors */); - if (offset != -1) { // may fail if clActual not fully defined - address = (void*)((intptr_t)address.data + offset); - klass = clActual; - } - } - new_flags |= CPPInstance::kIsActual; - } - } - -// actual binding (returned object may be zero w/ a python exception set) - return BindCppObjectNoCast(address, klass, new_flags); -} - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::BindCppObjectArray( - Cppyy::TCppObject_t address, Cppyy::TCppScope_t klass, cdims_t dims) -{ -// TODO: this function exists for symmetry; need to figure out if it's useful - return TupleOfInstances_New(address, klass, dims); -} diff --git a/src/CPyCppyy/src/ProxyWrappers.h b/src/CPyCppyy/src/ProxyWrappers.h deleted file mode 100644 index 7a074f8..0000000 --- a/src/CPyCppyy/src/ProxyWrappers.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef CPYCPPYY_PROXYWRAPPERS_H -#define CPYCPPYY_PROXYWRAPPERS_H - -// Bindings -#include "Dimensions.h" - -// Standard -#include - - -namespace CPyCppyy { - -// construct a Python shadow class for the named C++ class -PyObject* GetScopeProxy(Cppyy::TCppScope_t); -PyObject* CreateScopeProxy(PyObject*, PyObject* args); -PyObject* CreateScopeProxy( - const std::string& scope_name, PyObject* parent = nullptr, const unsigned flags = 0); - -PyObject* CreateScopeProxy(Cppyy::TCppScope_t scope, PyObject* parent = nullptr, const unsigned flags = 0); -// C++ exceptions form a special case b/c they have to derive from BaseException -PyObject* CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyObject* parent); - -// bind a C++ object into a Python proxy object (flags are CPPInstance::Default) -PyObject* BindCppObjectNoCast(Cppyy::TCppObject_t object, - Cppyy::TCppScope_t klass, const unsigned flags = 0); -PyObject* BindCppObject(Cppyy::TCppObject_t object, - Cppyy::TCppScope_t klass, const unsigned flags = 0); -PyObject* BindCppObjectArray( - Cppyy::TCppObject_t address, Cppyy::TCppScope_t klass, cdims_t dims); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PROXYWRAPPERS_H diff --git a/src/CPyCppyy/src/PyCallable.h b/src/CPyCppyy/src/PyCallable.h deleted file mode 100644 index d2a072c..0000000 --- a/src/CPyCppyy/src/PyCallable.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef CPYCPPYY_PYCALLABLE_H -#define CPYCPPYY_PYCALLABLE_H - -#include - -// Bindings -#include "CPyCppyy/Reflex.h" -#include "CallContext.h" - - -namespace CPyCppyy { - -class CPPInstance; - -class PyCallable { -public: - virtual ~PyCallable() {} - -public: - virtual PyObject* GetSignature(bool show_formalargs = true) = 0; - virtual PyObject* GetSignatureNames() = 0; - virtual PyObject* GetSignatureTypes() = 0; - virtual PyObject* GetPrototype(bool show_formalargs = true) = 0; - virtual PyObject* GetTypeName() { return GetPrototype(false); } - virtual PyObject* GetDocString() { return GetPrototype(); } - virtual PyObject* Reflex(Cppyy::Reflex::RequestId_t request, - Cppyy::Reflex::FormatId_t format = Cppyy::Reflex::OPTIMAL) { - PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", request, format); - return nullptr; - }; - - virtual int GetPriority() = 0; - virtual bool IsGreedy() = 0; - - virtual int GetMaxArgs() = 0; - virtual PyObject* GetCoVarNames() = 0; - virtual PyObject* GetArgDefault(int /* iarg */, bool silent=true) = 0; - virtual bool IsConst() { return false; } - - virtual PyObject* GetScopeProxy() = 0; - virtual Cppyy::TCppFuncAddr_t GetFunctionAddress() = 0; - - virtual PyCallable* Clone() = 0; - - virtual int GetArgMatchScore(PyObject* /* args_tuple */) { return INT_MAX; } - -public: - virtual PyObject* Call(CPPInstance*& self, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) = 0; -}; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PYCALLABLE_H diff --git a/src/CPyCppyy/src/PyException.cxx b/src/CPyCppyy/src/PyException.cxx deleted file mode 100644 index df4b664..0000000 --- a/src/CPyCppyy/src/PyException.cxx +++ /dev/null @@ -1,141 +0,0 @@ -// Standard -#include - -// Bindings -#include "CPyCppyy.h" -#define CPYCPPYY_INTERNAL 1 -#include "CPyCppyy/DispatchPtr.h" -#include "CPyCppyy/PyException.h" -#undef CPYCPPYY_INTERNAL - - -//______________________________________________________________________________ -// C++ exception for throwing python exceptions -// ============================================ -// Purpose: A C++ exception class for throwing python exceptions -// through C++ code. -// Created: Apr, 2004, Scott Snyder, from the version in D0's python_util. -// -// Note: Don't be tempted to declare the virtual functions defined here -// as inline. -// If you do, you may not be able to properly throw these -// exceptions across shared libraries. - - -//- constructors/destructor -------------------------------------------------- -CPyCppyy::PyException::PyException() -{ -#ifdef WITH_THREAD - PythonGILRAII python_gil_raii; -#endif - -#if PY_VERSION_HEX >= 0x030c0000 - PyObject *pyvalue = PyErr_GetRaisedException(); - PyObject *pytype = pyvalue ? (PyObject *)Py_TYPE(pyvalue) : nullptr; - PyObject* traceback = pyvalue ? PyException_GetTraceback(pyvalue) : nullptr; -#else - PyObject* pytype = nullptr, *pyvalue = nullptr, *pytrace = nullptr; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); - PyObject* traceback = pytrace; // to keep the original unchanged - Py_XINCREF(traceback); -#endif - - if (pytype && pyvalue) { - const char* tname = PyExceptionClass_Name(pytype); - if (tname) { - char* dot = strrchr((char*)tname, '.'); - if (dot) tname = dot+1; - fMsg += tname; - fMsg += ": "; - } - - PyObject* msg = PyObject_Str(pyvalue); - if (msg) { - fMsg += CPyCppyy_PyText_AsString(msg); - Py_DECREF(msg); - } - } - - std::string locName; - std::string locFile; - int locLine = 0; - - while (traceback && traceback != Py_None) { - PyObject* frame = PyObject_GetAttrString(traceback, "tb_frame"); - PyObject* code = PyObject_GetAttrString(frame, "f_code"); - Py_DECREF(frame); - - PyObject* filename = PyObject_GetAttrString(code, "co_filename"); - Py_DECREF(code); - - PyObject* filenameStr = PyObject_Str(filename); - locFile = CPyCppyy_PyText_AsString(filenameStr); - Py_DECREF(filenameStr); - Py_DECREF(filename); - - PyObject* name = PyObject_GetAttrString(code, "co_name"); - PyObject* nameStr = PyObject_Str(name); - locName = CPyCppyy_PyText_AsString(nameStr); - Py_DECREF(nameStr); - Py_DECREF(name); - - PyObject* lineno = PyObject_GetAttrString(traceback, "tb_lineno"); - locLine = PyLong_AsLong(lineno); - Py_DECREF(lineno); - - if (locFile == "") { // these are not that useful, skipping - PyObject* nextTraceback = PyObject_GetAttrString(traceback, "tb_next"); - Py_DECREF(traceback); - traceback = nextTraceback; - continue; - } - - break; - } - - Py_XDECREF(traceback); - -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(pyvalue); -#else - PyErr_Restore(pytype, pyvalue, pytrace); -#endif - - if (fMsg.empty()) - fMsg = "python exception"; - - if (!locFile.empty()) { - - // only keeping the filename, not the full path - locFile = locFile.substr(locFile.find_last_of("/\\") + 1); - - fMsg += " (at " + locFile + ":" + std::to_string(locLine); - - if (locName != "") - fMsg += " in " + locName; - - fMsg += ")"; - } -} - -CPyCppyy::PyException::~PyException() noexcept -{ -// destructor -} - - -//- public members ----------------------------------------------------------- -const char* CPyCppyy::PyException::what() const noexcept -{ -// Return reason for throwing this exception: a python exception was raised. - return fMsg.c_str(); -} - -void CPyCppyy::PyException::clear() const noexcept -{ -#ifdef WITH_THREAD - PythonGILRAII python_gil_raii; -#endif -// clear Python error, to allow full error handling C++ side - PyErr_Clear(); -} diff --git a/src/CPyCppyy/src/PyStrings.cxx b/src/CPyCppyy/src/PyStrings.cxx deleted file mode 100644 index 7142d24..0000000 --- a/src/CPyCppyy/src/PyStrings.cxx +++ /dev/null @@ -1,241 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "PyStrings.h" - - -//- data _____________________________________________________________________ -PyObject* CPyCppyy::PyStrings::gAssign = nullptr; -PyObject* CPyCppyy::PyStrings::gBases = nullptr; -PyObject* CPyCppyy::PyStrings::gBase = nullptr; -PyObject* CPyCppyy::PyStrings::gContains = nullptr; -PyObject* CPyCppyy::PyStrings::gCopy = nullptr; -PyObject* CPyCppyy::PyStrings::gCppBool = nullptr; -PyObject* CPyCppyy::PyStrings::gCppName = nullptr; -PyObject* CPyCppyy::PyStrings::gAnnotations = nullptr; -PyObject* CPyCppyy::PyStrings::gCastCpp = nullptr; -PyObject* CPyCppyy::PyStrings::gCType = nullptr; -PyObject* CPyCppyy::PyStrings::gDeref = nullptr; -PyObject* CPyCppyy::PyStrings::gPreInc = nullptr; -PyObject* CPyCppyy::PyStrings::gPostInc = nullptr; -PyObject* CPyCppyy::PyStrings::gDict = nullptr; -PyObject* CPyCppyy::PyStrings::gEmptyString = nullptr; -PyObject* CPyCppyy::PyStrings::gEq = nullptr; -PyObject* CPyCppyy::PyStrings::gFollow = nullptr; -PyObject* CPyCppyy::PyStrings::gGetItem = nullptr; -PyObject* CPyCppyy::PyStrings::gGetNoCheck = nullptr; -PyObject* CPyCppyy::PyStrings::gSetItem = nullptr; -PyObject* CPyCppyy::PyStrings::gInit = nullptr; -PyObject* CPyCppyy::PyStrings::gIter = nullptr; -PyObject* CPyCppyy::PyStrings::gLen = nullptr; -PyObject* CPyCppyy::PyStrings::gLifeLine = nullptr; -PyObject* CPyCppyy::PyStrings::gModule = nullptr; -PyObject* CPyCppyy::PyStrings::gMRO = nullptr; -PyObject* CPyCppyy::PyStrings::gName = nullptr; -PyObject* CPyCppyy::PyStrings::gNe = nullptr; -PyObject* CPyCppyy::PyStrings::gRepr = nullptr; -PyObject* CPyCppyy::PyStrings::gCppRepr = nullptr; -PyObject* CPyCppyy::PyStrings::gStr = nullptr; -PyObject* CPyCppyy::PyStrings::gCppStr = nullptr; -PyObject* CPyCppyy::PyStrings::gTypeCode = nullptr; -PyObject* CPyCppyy::PyStrings::gCTypesType = nullptr; - -PyObject* CPyCppyy::PyStrings::gUnderlying = nullptr; -PyObject* CPyCppyy::PyStrings::gRealInit = nullptr; - -PyObject* CPyCppyy::PyStrings::gAdd = nullptr; -PyObject* CPyCppyy::PyStrings::gSub = nullptr; -PyObject* CPyCppyy::PyStrings::gMul = nullptr; -PyObject* CPyCppyy::PyStrings::gDiv = nullptr; - -PyObject* CPyCppyy::PyStrings::gLShift = nullptr; -PyObject* CPyCppyy::PyStrings::gLShiftC = nullptr; - -PyObject* CPyCppyy::PyStrings::gAt = nullptr; -PyObject* CPyCppyy::PyStrings::gBegin = nullptr; -PyObject* CPyCppyy::PyStrings::gEnd = nullptr; -PyObject* CPyCppyy::PyStrings::gFirst = nullptr; -PyObject* CPyCppyy::PyStrings::gSecond = nullptr; -PyObject* CPyCppyy::PyStrings::gSize = nullptr; -PyObject* CPyCppyy::PyStrings::gTemplate = nullptr; -PyObject* CPyCppyy::PyStrings::gVectorAt = nullptr; -PyObject* CPyCppyy::PyStrings::gInsert = nullptr; -PyObject* CPyCppyy::PyStrings::gValueType = nullptr; -PyObject* CPyCppyy::PyStrings::gValueTypePtr = nullptr; -PyObject* CPyCppyy::PyStrings::gValueSize = nullptr; - -PyObject* CPyCppyy::PyStrings::gCppReal = nullptr; -PyObject* CPyCppyy::PyStrings::gCppImag = nullptr; - -PyObject* CPyCppyy::PyStrings::gThisModule = nullptr; - -PyObject* CPyCppyy::PyStrings::gDispInit = nullptr; -PyObject* CPyCppyy::PyStrings::gDispGet = nullptr; - -PyObject* CPyCppyy::PyStrings::gExPythonize = nullptr; -PyObject* CPyCppyy::PyStrings::gPythonize = nullptr; - -PyObject* CPyCppyy::PyStrings::gArray = nullptr; -PyObject* CPyCppyy::PyStrings::gDType = nullptr; -PyObject* CPyCppyy::PyStrings::gFromBuffer = nullptr; - - -//----------------------------------------------------------------------------- -#define CPPYY_INITIALIZE_STRING(var, str) \ - if (!(PyStrings::var = CPyCppyy_PyText_InternFromString((char*)#str))) \ - return false - -bool CPyCppyy::CreatePyStrings() { -// Build cache of commonly used python strings (the cache is python intern, so -// all strings are shared python-wide, not just in cppyy). - CPPYY_INITIALIZE_STRING(gAssign, __assign__); - CPPYY_INITIALIZE_STRING(gBases, __bases__); - CPPYY_INITIALIZE_STRING(gBase, __base__); - CPPYY_INITIALIZE_STRING(gContains, contains); - CPPYY_INITIALIZE_STRING(gCopy, copy); - CPPYY_INITIALIZE_STRING(gCppBool, __cpp_bool__); - CPPYY_INITIALIZE_STRING(gCppName, __cpp_name__); - CPPYY_INITIALIZE_STRING(gAnnotations, __annotations__); - CPPYY_INITIALIZE_STRING(gCastCpp, __cast_cpp__); - CPPYY_INITIALIZE_STRING(gCType, __ctype__); - CPPYY_INITIALIZE_STRING(gDeref, __deref__); - CPPYY_INITIALIZE_STRING(gPreInc, __preinc__); - CPPYY_INITIALIZE_STRING(gPostInc, __postinc__); - CPPYY_INITIALIZE_STRING(gDict, __dict__); - if (!(PyStrings::gEmptyString = CPyCppyy_PyText_FromString((char*)""))) - return false; - CPPYY_INITIALIZE_STRING(gEq, __eq__); - CPPYY_INITIALIZE_STRING(gFollow, __follow__); - CPPYY_INITIALIZE_STRING(gGetItem, __getitem__); - CPPYY_INITIALIZE_STRING(gGetNoCheck, _getitem__unchecked); - CPPYY_INITIALIZE_STRING(gSetItem, __setitem__); - CPPYY_INITIALIZE_STRING(gInit, __init__); - CPPYY_INITIALIZE_STRING(gIter, __iter__); - CPPYY_INITIALIZE_STRING(gLen, __len__); - CPPYY_INITIALIZE_STRING(gLifeLine, __lifeline); - CPPYY_INITIALIZE_STRING(gModule, __module__); - CPPYY_INITIALIZE_STRING(gMRO, __mro__); - CPPYY_INITIALIZE_STRING(gName, __name__); - CPPYY_INITIALIZE_STRING(gNe, __ne__); - CPPYY_INITIALIZE_STRING(gRepr, __repr__); - CPPYY_INITIALIZE_STRING(gCppRepr, __cpp_repr); - CPPYY_INITIALIZE_STRING(gStr, __str__); - CPPYY_INITIALIZE_STRING(gCppStr, __cpp_str); - CPPYY_INITIALIZE_STRING(gTypeCode, typecode); - CPPYY_INITIALIZE_STRING(gCTypesType, _type_); - - CPPYY_INITIALIZE_STRING(gUnderlying, __underlying); - CPPYY_INITIALIZE_STRING(gRealInit, __real_init); - - CPPYY_INITIALIZE_STRING(gAdd, __add__); - CPPYY_INITIALIZE_STRING(gSub, __sub__); - CPPYY_INITIALIZE_STRING(gMul, __mul__); - CPPYY_INITIALIZE_STRING(gDiv, CPPYY__div__); - - CPPYY_INITIALIZE_STRING(gLShift, __lshift__); - CPPYY_INITIALIZE_STRING(gLShiftC, __lshiftc__); - - CPPYY_INITIALIZE_STRING(gAt, at); - CPPYY_INITIALIZE_STRING(gBegin, begin); - CPPYY_INITIALIZE_STRING(gEnd, end); - CPPYY_INITIALIZE_STRING(gFirst, first); - CPPYY_INITIALIZE_STRING(gSecond, second); - CPPYY_INITIALIZE_STRING(gSize, size); - CPPYY_INITIALIZE_STRING(gTemplate, Template); - CPPYY_INITIALIZE_STRING(gVectorAt, _vector__at); - CPPYY_INITIALIZE_STRING(gInsert, insert); - CPPYY_INITIALIZE_STRING(gValueType, value_type); - CPPYY_INITIALIZE_STRING(gValueTypePtr, _value_type); - CPPYY_INITIALIZE_STRING(gValueSize, value_size); - - CPPYY_INITIALIZE_STRING(gCppReal, __cpp_real); - CPPYY_INITIALIZE_STRING(gCppImag, __cpp_imag); - - CPPYY_INITIALIZE_STRING(gThisModule, cppyy); - - CPPYY_INITIALIZE_STRING(gDispInit, _init_dispatchptr); - CPPYY_INITIALIZE_STRING(gDispGet, _get_dispatch); - - CPPYY_INITIALIZE_STRING(gExPythonize, __cppyy_explicit_pythonize__); - CPPYY_INITIALIZE_STRING(gPythonize, __cppyy_pythonize__); - - CPPYY_INITIALIZE_STRING(gArray, __array__); - CPPYY_INITIALIZE_STRING(gDType, dtype); - CPPYY_INITIALIZE_STRING(gFromBuffer, frombuffer); - - return true; -} - - -//----------------------------------------------------------------------------- -PyObject* CPyCppyy::DestroyPyStrings() { -// Remove all cached python strings. - Py_DECREF(PyStrings::gBases); PyStrings::gBases = nullptr; - Py_DECREF(PyStrings::gBase); PyStrings::gBase = nullptr; - Py_DECREF(PyStrings::gContains); PyStrings::gContains = nullptr; - Py_DECREF(PyStrings::gCopy); PyStrings::gCopy = nullptr; - Py_DECREF(PyStrings::gCppBool); PyStrings::gCppBool = nullptr; - Py_DECREF(PyStrings::gCppName); PyStrings::gCppName = nullptr; - Py_DECREF(PyStrings::gAnnotations); PyStrings::gAnnotations = nullptr; - Py_DECREF(PyStrings::gCType); PyStrings::gCType = nullptr; - Py_DECREF(PyStrings::gDeref); PyStrings::gDeref = nullptr; - Py_DECREF(PyStrings::gPreInc); PyStrings::gPreInc = nullptr; - Py_DECREF(PyStrings::gPostInc); PyStrings::gPostInc = nullptr; - Py_DECREF(PyStrings::gDict); PyStrings::gDict = nullptr; - Py_DECREF(PyStrings::gEmptyString); PyStrings::gEmptyString = nullptr; - Py_DECREF(PyStrings::gEq); PyStrings::gEq = nullptr; - Py_DECREF(PyStrings::gFollow); PyStrings::gFollow = nullptr; - Py_DECREF(PyStrings::gGetItem); PyStrings::gGetItem = nullptr; - Py_DECREF(PyStrings::gGetNoCheck); PyStrings::gGetNoCheck = nullptr; - Py_DECREF(PyStrings::gSetItem); PyStrings::gSetItem = nullptr; - Py_DECREF(PyStrings::gInit); PyStrings::gInit = nullptr; - Py_DECREF(PyStrings::gIter); PyStrings::gIter = nullptr; - Py_DECREF(PyStrings::gLen); PyStrings::gLen = nullptr; - Py_DECREF(PyStrings::gLifeLine); PyStrings::gLifeLine = nullptr; - Py_DECREF(PyStrings::gModule); PyStrings::gModule = nullptr; - Py_DECREF(PyStrings::gMRO); PyStrings::gMRO = nullptr; - Py_DECREF(PyStrings::gName); PyStrings::gName = nullptr; - Py_DECREF(PyStrings::gNe); PyStrings::gNe = nullptr; - Py_DECREF(PyStrings::gTypeCode); PyStrings::gTypeCode = nullptr; - Py_DECREF(PyStrings::gCTypesType); PyStrings::gCTypesType = nullptr; - - Py_DECREF(PyStrings::gUnderlying); PyStrings::gUnderlying = nullptr; - Py_DECREF(PyStrings::gRealInit); PyStrings::gRealInit = nullptr; - - Py_DECREF(PyStrings::gAdd); PyStrings::gAdd = nullptr; - Py_DECREF(PyStrings::gSub); PyStrings::gSub = nullptr; - Py_DECREF(PyStrings::gMul); PyStrings::gMul = nullptr; - Py_DECREF(PyStrings::gDiv); PyStrings::gDiv = nullptr; - - Py_DECREF(PyStrings::gLShift); PyStrings::gLShift = nullptr; - Py_DECREF(PyStrings::gLShiftC); PyStrings::gLShiftC = nullptr; - - Py_DECREF(PyStrings::gAt); PyStrings::gAt = nullptr; - Py_DECREF(PyStrings::gBegin); PyStrings::gBegin = nullptr; - Py_DECREF(PyStrings::gEnd); PyStrings::gEnd = nullptr; - Py_DECREF(PyStrings::gFirst); PyStrings::gFirst = nullptr; - Py_DECREF(PyStrings::gSecond); PyStrings::gSecond = nullptr; - Py_DECREF(PyStrings::gSize); PyStrings::gSize = nullptr; - Py_DECREF(PyStrings::gTemplate); PyStrings::gTemplate = nullptr; - Py_DECREF(PyStrings::gVectorAt); PyStrings::gVectorAt = nullptr; - Py_DECREF(PyStrings::gInsert); PyStrings::gInsert = nullptr; - Py_DECREF(PyStrings::gValueType); PyStrings::gValueType = nullptr; - Py_DECREF(PyStrings::gValueTypePtr);PyStrings::gValueTypePtr= nullptr; - Py_DECREF(PyStrings::gValueSize); PyStrings::gValueSize = nullptr; - - Py_DECREF(PyStrings::gCppReal); PyStrings::gCppReal = nullptr; - Py_DECREF(PyStrings::gCppImag); PyStrings::gCppImag = nullptr; - - Py_DECREF(PyStrings::gThisModule); PyStrings::gThisModule = nullptr; - - Py_DECREF(PyStrings::gDispInit); PyStrings::gDispInit = nullptr; - Py_DECREF(PyStrings::gDispGet); PyStrings::gDispGet = nullptr; - - Py_DECREF(PyStrings::gExPythonize); PyStrings::gExPythonize = nullptr; - Py_DECREF(PyStrings::gPythonize); PyStrings::gPythonize = nullptr; - - Py_DECREF(PyStrings::gArray); PyStrings::gArray = nullptr; - Py_DECREF(PyStrings::gDType); PyStrings::gDType = nullptr; - Py_DECREF(PyStrings::gFromBuffer); PyStrings::gFromBuffer = nullptr; - - Py_RETURN_NONE; -} diff --git a/src/CPyCppyy/src/PyStrings.h b/src/CPyCppyy/src/PyStrings.h deleted file mode 100644 index ed6a677..0000000 --- a/src/CPyCppyy/src/PyStrings.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef CPYCPPYY_PYSTRINGS_H -#define CPYCPPYY_PYSTRINGS_H - -namespace CPyCppyy { - -// python strings kept for performance reasons - -namespace PyStrings { - - extern PyObject* gAssign; - extern PyObject* gBases; - extern PyObject* gBase; - extern PyObject* gContains; - extern PyObject* gCopy; - extern PyObject* gCppBool; - extern PyObject* gCppName; - extern PyObject* gAnnotations; - extern PyObject* gCastCpp; - extern PyObject* gCType; - extern PyObject* gDeref; - extern PyObject* gPreInc; - extern PyObject* gPostInc; - extern PyObject* gDict; - extern PyObject* gEmptyString; - extern PyObject* gEq; - extern PyObject* gFollow; - extern PyObject* gGetItem; - extern PyObject* gGetNoCheck; - extern PyObject* gSetItem; - extern PyObject* gInit; - extern PyObject* gIter; - extern PyObject* gLen; - extern PyObject* gLifeLine; - extern PyObject* gModule; - extern PyObject* gMRO; - extern PyObject* gName; - extern PyObject* gNe; - extern PyObject* gRepr; - extern PyObject* gCppRepr; - extern PyObject* gStr; - extern PyObject* gCppStr; - extern PyObject* gTypeCode; - extern PyObject* gCTypesType; - - extern PyObject* gUnderlying; - extern PyObject* gRealInit; - - extern PyObject* gAdd; - extern PyObject* gSub; - extern PyObject* gMul; - extern PyObject* gDiv; - - extern PyObject* gLShift; - extern PyObject* gLShiftC; - - extern PyObject* gAt; - extern PyObject* gBegin; - extern PyObject* gEnd; - extern PyObject* gFirst; - extern PyObject* gSecond; - extern PyObject* gSize; - extern PyObject* gTemplate; - extern PyObject* gVectorAt; - extern PyObject* gInsert; - extern PyObject* gValueType; - extern PyObject* gValueTypePtr; - extern PyObject* gValueSize; - - extern PyObject* gCppReal; - extern PyObject* gCppImag; - - extern PyObject* gThisModule; - - extern PyObject* gDispInit; - extern PyObject* gDispGet; - - extern PyObject* gExPythonize; - extern PyObject* gPythonize; - - extern PyObject* gArray; - extern PyObject* gDType; - extern PyObject* gFromBuffer; - -} // namespace PyStrings - -bool CreatePyStrings(); -PyObject* DestroyPyStrings(); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PYSTRINGS_H diff --git a/src/CPyCppyy/src/Pythonize.cxx b/src/CPyCppyy/src/Pythonize.cxx deleted file mode 100644 index 7580fbd..0000000 --- a/src/CPyCppyy/src/Pythonize.cxx +++ /dev/null @@ -1,2043 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Pythonize.h" -#include "Converters.h" -#include "CPPInstance.h" -#include "CPPFunction.h" -#include "CPPOverload.h" -#include "Cppyy.h" -#include "CustomPyTypes.h" -#include "LowLevelViews.h" -#include "ProxyWrappers.h" -#include "PyCallable.h" -#include "PyStrings.h" -#include "TypeManip.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include -#include -#include -#include - - -//- data and local helpers --------------------------------------------------- -namespace CPyCppyy { - extern PyObject* gThisModule; - std::unordered_map> &pythonizations(); -} - -namespace { - -// for convenience -using namespace CPyCppyy; - -//----------------------------------------------------------------------------- -bool HasAttrDirect(PyObject* pyclass, PyObject* pyname, bool mustBeCPyCppyy = false) { -// prevents calls to Py_TYPE(pyclass)->tp_getattr, which is unnecessary for our -// purposes here and could tickle problems w/ spurious lookups into ROOT meta - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - if (dct) { - PyObject* attr = PyObject_GetItem(dct, pyname); - Py_DECREF(dct); - if (attr) { - bool ret = !mustBeCPyCppyy || CPPOverload_Check(attr); - Py_DECREF(attr); - return ret; - } - } - PyErr_Clear(); - return false; -} - -PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { -// get an attribute without causing getattr lookups - PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); - if (dct) { - PyObject* attr = PyObject_GetItem(dct, pyname); - Py_DECREF(dct); - return attr; - } - return nullptr; -} - -//----------------------------------------------------------------------------- -inline bool IsTemplatedSTLClass(const std::string& name, const std::string& klass) { -// Scan the name of the class and determine whether it is a template instantiation. - auto pos = name.find(klass); - return pos == 5 && name.rfind("std::", 0, 5) == 0 && name.find("::", name.rfind(">")) == std::string::npos; -} - -// to prevent compiler warnings about const char* -> char* -inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth) -{ -// Helper; call method with signature: obj->meth(). - Py_INCREF(obj); - PyObject* result = PyObject_CallMethod(obj, const_cast(meth), const_cast("")); - Py_DECREF(obj); - return result; -} - -//----------------------------------------------------------------------------- -inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth, PyObject* arg1) -{ -// Helper; call method with signature: obj->meth(arg1). - Py_INCREF(obj); - PyObject* result = PyObject_CallMethod( - obj, const_cast(meth), const_cast("O"), arg1); - Py_DECREF(obj); - return result; -} - -//----------------------------------------------------------------------------- -PyObject* PyStyleIndex(PyObject* self, PyObject* index) -{ -// Helper; converts python index into straight C index. - Py_ssize_t idx = PyInt_AsSsize_t(index); - if (idx == (Py_ssize_t)-1 && PyErr_Occurred()) - return nullptr; - - Py_ssize_t size = PySequence_Size(self); - if (idx >= size || (idx < 0 && idx < -size)) { - PyErr_SetString(PyExc_IndexError, "index out of range"); - return nullptr; - } - - PyObject* pyindex = nullptr; - if (idx >= 0) { - Py_INCREF(index); - pyindex = index; - } else - pyindex = PyLong_FromSsize_t(size+idx); - - return pyindex; -} - -//----------------------------------------------------------------------------- -inline bool AdjustSlice(const Py_ssize_t nlen, Py_ssize_t& start, Py_ssize_t& stop, Py_ssize_t& step) -{ -// Helper; modify slice range to match the container. - if ((step > 0 && stop <= start) || (step < 0 && start <= stop)) - return false; - - if (start < 0) start = 0; - if (start >= nlen) start = nlen-1; - if (step >= nlen) step = nlen; - - stop = step > 0 ? std::min(nlen, stop) : (stop >= 0 ? stop : -1); - return true; -} - -//----------------------------------------------------------------------------- -inline PyObject* CallSelfIndex(CPPInstance* self, PyObject* idx, PyObject* pymeth) -{ -// Helper; call method with signature: meth(pyindex). - Py_INCREF((PyObject*)self); - PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); - if (!pyindex) { - Py_DECREF((PyObject*)self); - return nullptr; - } - - PyObject* result = PyObject_CallMethodOneArg((PyObject*)self, pymeth, pyindex); - Py_DECREF(pyindex); - Py_DECREF((PyObject*)self); - return result; -} - -//- "smart pointer" behavior --------------------------------------------------- -static PyObject* smart_follow(PyObject* self, PyObject* name, PyObject* method) -{ - if (!CPyCppyy_PyText_Check(name)) - PyErr_SetString(PyExc_TypeError, "getattr(): attribute name must be string"); - - PyObject* pyptr = PyObject_CallMethodNoArgs(self, method); - if (!pyptr) - return nullptr; - -// prevent a potential infinite loop - if (Py_TYPE(pyptr) == Py_TYPE(self)) { - PyObject* val1 = PyObject_Str((PyObject*)Py_TYPE(self)); - PyObject* val2 = PyObject_Str(name); - PyErr_Format(PyExc_AttributeError, "%s object has no attribute \'%s\'", - CPyCppyy_PyText_AsString(val1), CPyCppyy_PyText_AsString(val2)); - Py_DECREF(val2); - Py_DECREF(val1); - - Py_DECREF(pyptr); - return nullptr; - } - - PyObject* result = PyObject_GetAttr(pyptr, name); - Py_DECREF(pyptr); - return result; -} - -PyObject* DeRefGetAttr(PyObject* self, PyObject* name) -{ -// Follow operator*() if present (available in python as __deref__), so that -// smart pointers behave as expected. - if (name == PyStrings::gTypeCode || name == PyStrings::gCTypesType) { - // TODO: these calls come from TemplateProxy and are unlikely to be needed in practice, - // whereas as-is, they can accidentally dereference the result of end() on some STL - // containers. Obviously, this is a dumb hack that should be resolved more fundamentally. - PyErr_SetString(PyExc_AttributeError, CPyCppyy_PyText_AsString(name)); - return nullptr; - } - - return smart_follow(self, name, PyStrings::gDeref); -} - -//----------------------------------------------------------------------------- -PyObject* FollowGetAttr(PyObject* self, PyObject* name) -{ -// Follow operator->() if present (available in python as __follow__), so that -// smart pointers behave as expected. - return smart_follow(self, name, PyStrings::gFollow); -} - -//- pointer checking bool converter ------------------------------------------- -PyObject* NullCheckBool(PyObject* self) -{ - if (!CPPInstance_Check(self)) { - PyErr_SetString(PyExc_TypeError, "C++ object proxy expected"); - return nullptr; - } - - if (!((CPPInstance*)self)->GetObject()) - Py_RETURN_FALSE; - - return PyObject_CallMethodNoArgs(self, PyStrings::gCppBool); -} - -//- vector behavior as primitives ---------------------------------------------- - -// TODO: can probably use the below getters in the InitializerListConverter -struct ItemGetter { - ItemGetter(PyObject* pyobj) : fPyObject(pyobj) { Py_INCREF(fPyObject); } - virtual ~ItemGetter() { Py_DECREF(fPyObject); } - virtual Py_ssize_t size() = 0; - virtual PyObject* get() = 0; - PyObject* fPyObject; -}; - -struct CountedItemGetter : public ItemGetter { - CountedItemGetter(PyObject* pyobj) : ItemGetter(pyobj), fCur(0) {} - Py_ssize_t fCur; -}; - -struct TupleItemGetter : public CountedItemGetter { - using CountedItemGetter::CountedItemGetter; - Py_ssize_t size() override { return PyTuple_GET_SIZE(fPyObject); } - PyObject* get() override { - if (fCur < PyTuple_GET_SIZE(fPyObject)) { - PyObject* item = PyTuple_GET_ITEM(fPyObject, fCur++); - Py_INCREF(item); - return item; - } - PyErr_SetString(PyExc_StopIteration, "end of tuple"); - return nullptr; - } -}; - -struct ListItemGetter : public CountedItemGetter { - using CountedItemGetter::CountedItemGetter; - Py_ssize_t size() override { return PyList_GET_SIZE(fPyObject); } - PyObject* get() override { - if (fCur < PyList_GET_SIZE(fPyObject)) { - PyObject* item = PyList_GET_ITEM(fPyObject, fCur++); - Py_INCREF(item); - return item; - } - PyErr_SetString(PyExc_StopIteration, "end of list"); - return nullptr; - } -}; - -struct SequenceItemGetter : public CountedItemGetter { - using CountedItemGetter::CountedItemGetter; - Py_ssize_t size() override { - Py_ssize_t sz = PySequence_Size(fPyObject); - if (sz < 0) { - PyErr_Clear(); - return PyObject_LengthHint(fPyObject, 8); - } - return sz; - } - PyObject* get() override { return PySequence_GetItem(fPyObject, fCur++); } -}; - -struct IterItemGetter : public ItemGetter { - using ItemGetter::ItemGetter; - Py_ssize_t size() override { return PyObject_LengthHint(fPyObject, 8); } - PyObject* get() override { return (*(Py_TYPE(fPyObject)->tp_iternext))(fPyObject); } -}; - -static ItemGetter* GetGetter(PyObject* args) -{ -// Create an ItemGetter to loop over the iterable argument, if any. - ItemGetter* getter = nullptr; - - if (PyTuple_GET_SIZE(args) == 1) { - PyObject* fi = PyTuple_GET_ITEM(args, 0); - if (CPyCppyy_PyText_Check(fi) || PyBytes_Check(fi)) - return nullptr; // do not accept string to fill std::vector - - // TODO: this only tests for new-style buffers, which is too strict, but a - // generic check for Py_TYPE(fi)->tp_as_buffer is too loose (note that the - // main use case is numpy, which offers the new interface) - if (PyObject_CheckBuffer(fi)) - return nullptr; - - if (PyTuple_CheckExact(fi)) - getter = new TupleItemGetter(fi); - else if (PyList_CheckExact(fi)) - getter = new ListItemGetter(fi); - else if (PySequence_Check(fi)) - getter = new SequenceItemGetter(fi); - else { - PyObject* iter = PyObject_GetIter(fi); - if (iter) { - getter = new IterItemGetter{iter}; - Py_DECREF(iter); - } - else PyErr_Clear(); - } - } - - return getter; -} - -static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter) -{ - Py_ssize_t sz = getter->size(); - if (sz < 0) - return false; - -// reserve memory as applicable - if (0 < sz) { - PyObject* res = PyObject_CallMethod(vecin, (char*)"reserve", (char*)"n", sz); - Py_DECREF(res); - } else // i.e. sz == 0, so empty container: done - return true; - - bool fill_ok = true; - -// two main options: a list of lists (or tuples), or a list of objects; the former -// are emplace_back'ed, the latter push_back'ed - PyObject* fi = PySequence_GetItem(PyTuple_GET_ITEM(args, 0), 0); - if (!fi) PyErr_Clear(); - if (fi && (PyTuple_CheckExact(fi) || PyList_CheckExact(fi))) { - // use emplace_back to construct the vector entries one by one - PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back"); - PyObject* vtype = GetAttrDirect((PyObject*)Py_TYPE(vecin), PyStrings::gValueTypePtr); - bool value_is_vector = false; - if (vtype && PyLong_Check(vtype)) { - // if the value_type is a vector, then allow for initialization from sequences - if (Cppyy::GetTypeAsString(PyLong_AsVoidPtr(vtype)).rfind("std::vector", 0) != std::string::npos) - value_is_vector = true; - } else - PyErr_Clear(); - Py_XDECREF(vtype); - - if (eb_call) { - PyObject* eb_args; - for (int i = 0; /* until break */; ++i) { - PyObject* item = getter->get(); - if (item) { - if (value_is_vector && PySequence_Check(item)) { - eb_args = PyTuple_New(1); - PyTuple_SET_ITEM(eb_args, 0, item); - } else if (PyTuple_CheckExact(item)) { - eb_args = item; - } else if (PyList_CheckExact(item)) { - Py_ssize_t isz = PyList_GET_SIZE(item); - eb_args = PyTuple_New(isz); - for (Py_ssize_t j = 0; j < isz; ++j) { - PyObject* iarg = PyList_GET_ITEM(item, j); - Py_INCREF(iarg); - PyTuple_SET_ITEM(eb_args, j, iarg); - } - Py_DECREF(item); - } else { - Py_DECREF(item); - PyErr_Format(PyExc_TypeError, "argument %d is not a tuple or list", i); - fill_ok = false; - break; - } - PyObject* ebres = PyObject_CallObject(eb_call, eb_args); - Py_DECREF(eb_args); - if (!ebres) { - fill_ok = false; - break; - } - Py_DECREF(ebres); - } else { - if (PyErr_Occurred()) { - if (!(PyErr_ExceptionMatches(PyExc_IndexError) || - PyErr_ExceptionMatches(PyExc_StopIteration))) - fill_ok = false; - else { PyErr_Clear(); } - } - break; - } - } - Py_DECREF(eb_call); - } - } else { - // use push_back to add the vector entries one by one - PyObject* pb_call = PyObject_GetAttrString(vecin, (char*)"push_back"); - if (pb_call) { - for (;;) { - PyObject* item = getter->get(); - if (item) { - PyObject* pbres = PyObject_CallFunctionObjArgs(pb_call, item, nullptr); - Py_DECREF(item); - if (!pbres) { - fill_ok = false; - break; - } - Py_DECREF(pbres); - } else { - if (PyErr_Occurred()) { - if (!(PyErr_ExceptionMatches(PyExc_IndexError) || - PyErr_ExceptionMatches(PyExc_StopIteration))) - fill_ok = false; - else { PyErr_Clear(); } - } - break; - } - } - Py_DECREF(pb_call); - } - } - Py_XDECREF(fi); - - return fill_ok; -} - -PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// Implement fast __iadd__ on std::vector (generic __iadd__ is in Python) - ItemGetter* getter = GetGetter(args); - - if (getter) { - bool fill_ok = FillVector(self, args, getter); - delete getter; - - if (!fill_ok) - return nullptr; - - Py_INCREF(self); - return self; - } - -// if no getter, it could still be b/c we have a buffer (e.g. numpy); looping over -// a buffer here is slow, so use insert() instead - if (PyTuple_GET_SIZE(args) == 1) { - PyObject* fi = PyTuple_GET_ITEM(args, 0); - if (PyObject_CheckBuffer(fi) && !(CPyCppyy_PyText_Check(fi) || PyBytes_Check(fi))) { - PyObject* vend = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); - if (vend) { - PyObject* result = PyObject_CallMethodObjArgs(self, PyStrings::gInsert, vend, fi, nullptr); - Py_DECREF(vend); - return result; - } - } - } - - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_TypeError, "argument is not iterable"); - return nullptr; // error already set -} - - -PyObject* VectorInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// Specialized vector constructor to allow construction from containers; allowing -// such construction from initializer_list instead would possible, but can be -// error-prone. This use case is common enough for std::vector to implement it -// directly, except for arrays (which can be passed wholesale) and strings (which -// won't convert properly as they'll be seen as buffers) - - ItemGetter* getter = GetGetter(args); - - if (getter) { - // construct an empty vector, then back-fill it - PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); - if (!result) { - delete getter; - return nullptr; - } - - bool fill_ok = FillVector(self, args, getter); - delete getter; - - if (!fill_ok) { - Py_DECREF(result); - return nullptr; - } - - return result; - } - -// The given argument wasn't iterable: simply forward to regular constructor - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - return result; - } - - return nullptr; -} - -//--------------------------------------------------------------------------- -PyObject* VectorData(PyObject* self, PyObject*) -{ - PyObject* pydata = CallPyObjMethod(self, "__real_data"); - if (!LowLevelView_Check(pydata) && !CPPInstance_Check(pydata)) - return pydata; - - PyObject* pylen = PyObject_CallMethodNoArgs(self, PyStrings::gSize); - if (!pylen) { - PyErr_Clear(); - return pydata; - } - - long clen = PyInt_AsLong(pylen); - Py_DECREF(pylen); - - if (CPPInstance_Check(pydata)) { - ((CPPInstance*)pydata)->CastToArray(clen); - return pydata; - } - - ((LowLevelView*)pydata)->resize((size_t)clen); - return pydata; -} - - -//--------------------------------------------------------------------------- -PyObject* VectorArray(PyObject* self, PyObject* args, PyObject* kwargs) -{ - PyObject* pydata = VectorData(self, nullptr); - PyObject* arrcall = PyObject_GetAttr(pydata, PyStrings::gArray); - PyObject* newarr = PyObject_Call(arrcall, args, kwargs); - Py_DECREF(arrcall); - Py_DECREF(pydata); - return newarr; -} - - -//----------------------------------------------------------------------------- -static PyObject* vector_iter(PyObject* v) { - vectoriterobject* vi = PyObject_GC_New(vectoriterobject, &VectorIter_Type); - if (!vi) return nullptr; - - Py_INCREF(v); - vi->ii_container = v; - -// tell the iterator code to set a life line if this container is a temporary - vi->vi_flags = vectoriterobject::kDefault; -#if PY_VERSION_HEX >= 0x030e0000 - if (PyUnstable_Object_IsUniqueReferencedTemporary(v) || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) -#else - if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) -#endif - vi->vi_flags = vectoriterobject::kNeedLifeLine; - - Py_INCREF(v); - - PyObject* pyvalue_type = PyObject_GetAttr((PyObject*)Py_TYPE(v), PyStrings::gValueTypePtr); - if (pyvalue_type) { - PyObject* pyvalue_size = GetAttrDirect((PyObject*)Py_TYPE(v), PyStrings::gValueSize); - if (pyvalue_size) { - vi->vi_stride = PyLong_AsLong(pyvalue_size); - Py_DECREF(pyvalue_size); - } else { - PyErr_Clear(); - vi->vi_stride = 0; - } - - if (PyLong_Check(pyvalue_type)) { - Cppyy::TCppType_t value_type = PyLong_AsVoidPtr(pyvalue_type); - value_type = Cppyy::ResolveType(value_type); - vi->vi_klass = Cppyy::GetScopeFromType(value_type); - if (!vi->vi_klass) { - // look for a special case of pointer to a class type (which is a builtin, but it - // is more useful to treat it polymorphically by allowing auto-downcasts) - const std::string& clean_type = TypeManip::clean_type(Cppyy::GetTypeAsString(value_type), false, false); - Cppyy::TCppScope_t c = Cppyy::GetScope(clean_type); - if (c && TypeManip::compound(Cppyy::GetTypeAsString(value_type)) == "*") { - vi->vi_klass = c; - vi->vi_flags = vectoriterobject::kIsPolymorphic; - } - } - if (Cppyy::IsPointerType(value_type)) - vi->vi_flags = vectoriterobject::kIsPolymorphic; - if (vi->vi_klass) { - vi->vi_converter = nullptr; - if (!vi->vi_flags) { - value_type = Cppyy::ResolveType(value_type); - if (Cppyy::GetTypeAsString(value_type).back() != '*') // meaning, object stored by-value - vi->vi_flags = vectoriterobject::kNeedLifeLine; - } - } else - vi->vi_converter = CPyCppyy::CreateConverter(value_type); - if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOfType(value_type); - - } else if (CPPScope_Check(pyvalue_type)) { - vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType; - vi->vi_converter = nullptr; - if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(vi->vi_klass); - if (!vi->vi_flags) vi->vi_flags = vectoriterobject::kNeedLifeLine; - } - - PyObject* pydata = CallPyObjMethod(v, "__real_data"); - if (!pydata || Utility::GetBuffer(pydata, '*', 1, vi->vi_data, false) == 0) - vi->vi_data = CPPInstance_Check(pydata) ? ((CPPInstance*)pydata)->GetObjectRaw() : nullptr; - Py_XDECREF(pydata); - - } else { - PyErr_Clear(); - vi->vi_data = nullptr; - vi->vi_stride = 0; - vi->vi_converter = nullptr; - vi->vi_klass = nullptr; - vi->vi_flags = 0; - } - - Py_XDECREF(pyvalue_type); - - vi->ii_pos = 0; - vi->ii_len = PySequence_Size(v); - - PyObject_GC_Track(vi); - return (PyObject*)vi; -} - -PyObject* VectorGetItem(CPPInstance* self, PySliceObject* index) -{ -// Implement python's __getitem__ for std::vector<>s. - if (PySlice_Check(index)) { - if (!self->GetObject()) { - PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); - return nullptr; - } - - PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)self); - PyObject* nseq = PyObject_CallObject(pyclass, nullptr); - - Py_ssize_t start, stop, step; - PySlice_GetIndices((CPyCppyy_PySliceCast)index, PyObject_Length((PyObject*)self), &start, &stop, &step); - - const Py_ssize_t nlen = PySequence_Size((PyObject*)self); - if (!AdjustSlice(nlen, start, stop, step)) - return nseq; - - const Py_ssize_t sign = step < 0 ? -1 : 1; - for (Py_ssize_t i = start; i*sign < stop*sign; i += step) { - PyObject* pyidx = PyInt_FromSsize_t(i); - PyObject* item = PyObject_CallMethodOneArg((PyObject*)self, PyStrings::gGetNoCheck, pyidx); - CallPyObjMethod(nseq, "push_back", item); - Py_DECREF(item); - Py_DECREF(pyidx); - } - - return nseq; - } - - return CallSelfIndex(self, (PyObject*)index, PyStrings::gGetNoCheck); -} - - -static Cppyy::TCppScope_t sVectorBoolTypeID; - -PyObject* VectorBoolGetItem(CPPInstance* self, PyObject* idx) -{ -// std::vector is a special-case in C++, and its return type depends on -// the compiler: treat it special here as well - if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) { - PyErr_Format(PyExc_TypeError, - "require object of type std::vector, but %s given", - Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str()); - return nullptr; - } - - if (!self->GetObject()) { - PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); - return nullptr; - } - - if (PySlice_Check(idx)) { - PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)self); - PyObject* nseq = PyObject_CallObject(pyclass, nullptr); - - Py_ssize_t start, stop, step; - PySlice_GetIndices((CPyCppyy_PySliceCast)idx, PyObject_Length((PyObject*)self), &start, &stop, &step); - const Py_ssize_t nlen = PySequence_Size((PyObject*)self); - if (!AdjustSlice(nlen, start, stop, step)) - return nseq; - - const Py_ssize_t sign = step < 0 ? -1 : 1; - for (Py_ssize_t i = start; i*sign < stop*sign; i += step) { - PyObject* pyidx = PyInt_FromSsize_t(i); - PyObject* item = PyObject_CallMethodOneArg((PyObject*)self, PyStrings::gGetItem, pyidx); - CallPyObjMethod(nseq, "push_back", item); - Py_DECREF(item); - Py_DECREF(pyidx); - } - - return nseq; - } - - PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); - if (!pyindex) - return nullptr; - - int index = (int)PyLong_AsLong(pyindex); - Py_DECREF(pyindex); - -// get hold of the actual std::vector (no cast, as vector is never a base) - std::vector* vb = (std::vector*)self->GetObject(); - -// finally, return the value - if (bool((*vb)[index])) - Py_RETURN_TRUE; - Py_RETURN_FALSE; -} - -PyObject* VectorBoolSetItem(CPPInstance* self, PyObject* args) -{ -// std::vector is a special-case in C++, and its return type depends on -// the compiler: treat it special here as well - if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) { - PyErr_Format(PyExc_TypeError, - "require object of type std::vector, but %s given", - Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str()); - return nullptr; - } - - if (!self->GetObject()) { - PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); - return nullptr; - } - - int bval = 0; PyObject* idx = nullptr; - if (!PyArg_ParseTuple(args, const_cast("Oi:__setitem__"), &idx, &bval)) - return nullptr; - - PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); - if (!pyindex) - return nullptr; - - int index = (int)PyLong_AsLong(pyindex); - Py_DECREF(pyindex); - -// get hold of the actual std::vector (no cast, as vector is never a base) - std::vector* vb = (std::vector*)self->GetObject(); - -// finally, set the value - (*vb)[index] = (bool)bval; - - Py_RETURN_NONE; -} - - -//- array behavior as primitives ---------------------------------------------- -PyObject* ArrayInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// std::array is normally only constructed using aggregate initialization, which -// is a concept that does not exist in python, so use this custom constructor to -// to fill the array using setitem - - if (args && PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) { - // construct the empty array, then fill it - PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); - if (!result) - return nullptr; - - PyObject* items = PyTuple_GET_ITEM(args, 0); - Py_ssize_t fillsz = PySequence_Size(items); - if (PySequence_Size(self) != fillsz) { - PyErr_Format(PyExc_ValueError, "received sequence of size %zd where %zd expected", - fillsz, PySequence_Size(self)); - Py_DECREF(result); - return nullptr; - } - - PyObject* si_call = PyObject_GetAttr(self, PyStrings::gSetItem); - for (Py_ssize_t i = 0; i < fillsz; ++i) { - PyObject* item = PySequence_GetItem(items, i); - PyObject* index = PyInt_FromSsize_t(i); - PyObject* sires = PyObject_CallFunctionObjArgs(si_call, index, item, nullptr); - Py_DECREF(index); - Py_DECREF(item); - if (!sires) { - Py_DECREF(si_call); - Py_DECREF(result); - return nullptr; - } else - Py_DECREF(sires); - } - Py_DECREF(si_call); - - return result; - } else - PyErr_Clear(); - -// The given argument wasn't iterable: simply forward to regular constructor - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - return result; - } - - return nullptr; -} - - -//- map behavior as primitives ------------------------------------------------ -static PyObject* MapFromPairs(PyObject* self, PyObject* pairs) -{ -// construct an empty map, then fill it with the key, value pairs - PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); - if (!result) - return nullptr; - - PyObject* si_call = PyObject_GetAttr(self, PyStrings::gSetItem); - for (Py_ssize_t i = 0; i < PySequence_Size(pairs); ++i) { - PyObject* pair = PySequence_GetItem(pairs, i); - PyObject* sires = nullptr; - if (pair && PySequence_Check(pair) && PySequence_Size(pair) == 2) { - PyObject* key = PySequence_GetItem(pair, 0); - PyObject* value = PySequence_GetItem(pair, 1); - sires = PyObject_CallFunctionObjArgs(si_call, key, value, nullptr); - Py_DECREF(value); - Py_DECREF(key); - } - Py_DECREF(pair); - if (!sires) { - Py_DECREF(si_call); - Py_DECREF(result); - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_TypeError, "Failed to fill map (argument not a dict or sequence of pairs)"); - return nullptr; - } else - Py_DECREF(sires); - } - Py_DECREF(si_call); - - return result; -} - -PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// Specialized map constructor to allow construction from mapping containers and -// from tuples of pairs ("initializer_list style"). - -// PyMapping_Check is not very discriminatory, as it basically only checks for the -// existence of __getitem__, hence the most common cases of tuple and list are -// dropped straight-of-the-bat (the PyMapping_Items call will fail on them). - if (PyTuple_GET_SIZE(args) == 1 && PyMapping_Check(PyTuple_GET_ITEM(args, 0)) && \ - !(PyTuple_Check(PyTuple_GET_ITEM(args, 0)) || PyList_Check(PyTuple_GET_ITEM(args, 0)))) { - PyObject* assoc = PyTuple_GET_ITEM(args, 0); - // in p3, PyMapping_Items isn't a macro, but a function that short-circuits dict - PyObject* items = PyMapping_Items(assoc); - if (items && PySequence_Check(items)) { - PyObject* result = MapFromPairs(self, items); - Py_DECREF(items); - return result; - } - - Py_XDECREF(items); - PyErr_Clear(); - - // okay to fall through as long as 'self' has not been created (is done in MapFromPairs) - } - -// tuple of pairs case (some mapping types are sequences) - if (PyTuple_GET_SIZE(args) == 1 && PySequence_Check(PyTuple_GET_ITEM(args, 0))) - return MapFromPairs(self, PyTuple_GET_ITEM(args, 0)); - -// The given argument wasn't a mapping or tuple of pairs: forward to regular constructor - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - return result; - } - - return nullptr; -} - -PyObject* STLContainsWithFind(PyObject* self, PyObject* obj) -{ -// Implement python's __contains__ for std::map/std::set - PyObject* result = nullptr; - - PyObject* iter = CallPyObjMethod(self, "find", obj); - if (CPPInstance_Check(iter)) { - PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); - if (CPPInstance_Check(end)) { - if (!PyObject_RichCompareBool(iter, end, Py_EQ)) { - Py_INCREF(Py_True); - result = Py_True; - } - } - Py_XDECREF(end); - } - Py_XDECREF(iter); - - if (!result) { - PyErr_Clear(); // e.g. wrong argument type, which should always lead to False - Py_INCREF(Py_False); - result = Py_False; - } - - return result; -} - - -//- set behavior as primitives ------------------------------------------------ -PyObject* SetInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// Specialized set constructor to allow construction from Python sets. - if (PyTuple_GET_SIZE(args) == 1 && PySet_Check(PyTuple_GET_ITEM(args, 0))) { - PyObject* pyset = PyTuple_GET_ITEM(args, 0); - - // construct an empty set, then fill it - PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); - if (!result) - return nullptr; - - PyObject* iter = PyObject_GetIter(pyset); - if (iter) { - PyObject* ins_call = PyObject_GetAttrString(self, (char*)"insert"); - - IterItemGetter getter{iter}; - Py_DECREF(iter); - - PyObject* item = getter.get(); - while (item) { - PyObject* insres = PyObject_CallFunctionObjArgs(ins_call, item, nullptr); - Py_DECREF(item); - if (!insres) { - Py_DECREF(ins_call); - Py_DECREF(result); - return nullptr; - } else - Py_DECREF(insres); - item = getter.get(); - } - Py_DECREF(ins_call); - } - - return result; - } - -// The given argument wasn't iterable: simply forward to regular constructor - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - return result; - } - - return nullptr; -} - - -//- STL container iterator support -------------------------------------------- -static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash -static const ptrdiff_t PS_FLAG_ADDR = 11; // id. -static const ptrdiff_t PS_COLL_ADDR = 13; // id. - -PyObject* STLIterNext(PyObject* self); // defined below; used by STLSequenceIter - -PyObject* LLSequenceIter(PyObject* self) -{ -// Implement python's __iter__ for low level views used through STL-type begin()/end() - PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gBegin); - - if (LowLevelView_Check(iter)) { - // builtin pointer iteration: can only succeed if a size is available - Py_ssize_t sz = PySequence_Size(self); - if (sz == -1) { - Py_DECREF(iter); - return nullptr; - } - PyObject* lliter = Py_TYPE(iter)->tp_iter(iter); - ((indexiterobject*)lliter)->ii_len = sz; - Py_DECREF(iter); - return lliter; - } - - if (iter) { - Py_DECREF(iter); - PyErr_SetString(PyExc_TypeError, "unrecognized iterator type for low level views"); - } - - return nullptr; -} - -PyObject* STLSequenceIter(PyObject* self) -{ -// Implement python's __iter__ for std::iterator<>s - PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gBegin); - if (iter) { - PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); - if (end) { - if (CPPInstance_Check(iter)) { - // Guarantee the returned iterator implements Python's iterator protocol. - // The deferred, name-based __next__ install can miss when an iterator's - // canonical return-type spelling and its scoped-final-name diverge - // (e.g libc++'s std::__1::__wrap_iter on macOS) leaving the type without - // __next__, so iter() rejects it. begin()/end() resolving here proves - // this is an STL forward iterator, so install the protocol now if it is still absent - PyTypeObject* itype = Py_TYPE(iter); - if (!PyIter_Check(iter)) { // no tp_iternext, or the - // _PyObject_NextNotImplemented sentinel - itype->tp_iternext = (iternextfunc)STLIterNext; - Utility::AddToClass((PyObject*)itype, CPPYY__next__, - (PyCFunction)STLIterNext, METH_NOARGS); - if (!itype->tp_iter) { - itype->tp_iter = (getiterfunc)PyObject_SelfIter; - Utility::AddToClass((PyObject*)itype, "__iter__", - (PyCFunction)PyObject_SelfIter, METH_NOARGS); - } - PyType_Modified(itype); - } - // use the data member cache to store extra state on the iterator object, - // without it being visible on the Python side - auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache(); - dmc.push_back(std::make_pair(PS_END_ADDR, end)); - - // set a flag, indicating first iteration (reset in __next__) - Py_INCREF(Py_False); - dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False)); - - // make sure the iterated over collection remains alive for the duration - Py_INCREF(self); - dmc.push_back(std::make_pair(PS_COLL_ADDR, self)); - } else { - // could store "end" on the object's dictionary anyway, but if end() returns - // a user-customized object, then its __next__ is probably custom, too - Py_DECREF(end); - } - } - } - return iter; -} - -//- generic iterator support over a sequence with operator[] and size --------- -//----------------------------------------------------------------------------- -static PyObject* index_iter(PyObject* c) { - indexiterobject* ii = PyObject_GC_New(indexiterobject, &IndexIter_Type); - if (!ii) return nullptr; - - Py_INCREF(c); - ii->ii_container = c; - ii->ii_pos = 0; - ii->ii_len = PySequence_Size(c); - - PyObject_GC_Track(ii); - return (PyObject*)ii; -} - - -//- safe indexing for STL-like vector w/o iterator dictionaries --------------- -/* replaced by indexiterobject iteration, but may still have some future use ... -PyObject* CheckedGetItem(PyObject* self, PyObject* obj) -{ -// Implement a generic python __getitem__ for STL-like classes that are missing the -// reflection info for their iterators. This is then used for iteration by means of -// consecutive indices, it such index is of integer type. - Py_ssize_t size = PySequence_Size(self); - Py_ssize_t idx = PyInt_AsSsize_t(obj); - if ((size == (Py_ssize_t)-1 || idx == (Py_ssize_t)-1) && PyErr_Occurred()) { - // argument conversion problem: let method itself resolve anew and report - PyErr_Clear(); - return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj); - } - - bool inbounds = false; - if (idx < 0) idx += size; - if (0 <= idx && 0 <= size && idx < size) - inbounds = true; - - if (inbounds) - return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj); - else - PyErr_SetString( PyExc_IndexError, "index out of range" ); - - return nullptr; -}*/ - - -//- pair as sequence to allow tuple unpacking -------------------------------- -PyObject* PairUnpack(PyObject* self, PyObject* pyindex) -{ -// For std::map<> iteration, unpack std::pair<>s into tuples for the loop. - long idx = PyLong_AsLong(pyindex); - if (idx == -1 && PyErr_Occurred()) - return nullptr; - - if (!CPPInstance_Check(self) || !((CPPInstance*)self)->GetObject()) { - PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); - return nullptr; - } - - if ((int)idx == 0) - return PyObject_GetAttr(self, PyStrings::gFirst); - else if ((int)idx == 1) - return PyObject_GetAttr(self, PyStrings::gSecond); - -// still here? Trigger stop iteration - PyErr_SetString(PyExc_IndexError, "out of bounds"); - return nullptr; -} - -//- simplistic len() functions ----------------------------------------------- -PyObject* ReturnTwo(CPPInstance*, PyObject*) { - return PyInt_FromLong(2); -} - - -//- shared/unique_ptr behavior ----------------------------------------------- -PyObject* SmartPtrInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// since the shared/unique pointer will take ownership, we need to relinquish it - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject* result = PyObject_Call(realInit, args, nullptr); - Py_DECREF(realInit); - if (result && PyTuple_GET_SIZE(args) == 1 && CPPInstance_Check(PyTuple_GET_ITEM(args, 0))) { - CPPInstance* cppinst = (CPPInstance*)PyTuple_GET_ITEM(args, 0); - if (!(cppinst->fFlags & CPPInstance::kIsSmartPtr)) cppinst->CppOwns(); - } - return result; - } - return nullptr; -} - - -//- string behavior as primitives -------------------------------------------- -// TODO: this is wrong, b/c it doesn't order -static int PyObject_Compare(PyObject* one, PyObject* other) { - return !PyObject_RichCompareBool(one, other, Py_EQ); -} -static inline -PyObject* CPyCppyy_PyString_FromCppString(std::string_view s, bool native=true) { - if (native) - return PyBytes_FromStringAndSize(s.data(), s.size()); - return CPyCppyy_PyText_FromStringAndSize(s.data(), s.size()); -} - -static inline -PyObject* CPyCppyy_PyString_FromCppString(std::wstring_view s, bool native=true) { - PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size()); - if (pyobj && native) { - PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict"); - Py_DECREF(pyobj); - pyobj = pybytes; - } - return pyobj; -} - -#define CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \ -static inline \ -PyObject* name##StringGetData(PyObject* self, bool native=true) \ -{ \ - if (CPyCppyy::CPPInstance_Check(self)) { \ - type* obj = ((type*)((CPPInstance*)self)->GetObject()); \ - if (obj) return CPyCppyy_PyString_FromCppString(*obj, native); \ - } \ - PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \ - return nullptr; \ -} \ - \ -PyObject* name##StringStr(PyObject* self) \ -{ \ - PyObject* pyobj = name##StringGetData(self, false); \ - if (!pyobj) { \ - /* do a native conversion to make printing possible (debatable) */ \ - PyErr_Clear(); \ - PyObject* pybytes = name##StringGetData(self, true); \ - if (pybytes) { /* should not fail */ \ - pyobj = PyObject_Str(pybytes); \ - Py_DECREF(pybytes); \ - } \ - } \ - return pyobj; \ -} \ - \ -PyObject* name##StringBytes(PyObject* self) \ -{ \ - return name##StringGetData(self, true); \ -} \ - \ -PyObject* name##StringRepr(PyObject* self) \ -{ \ - PyObject* data = name##StringGetData(self, true); \ - if (data) { \ - PyObject* repr = PyObject_Repr(data); \ - Py_DECREF(data); \ - return repr; \ - } \ - return nullptr; \ -} \ - \ -PyObject* name##StringIsEqual(PyObject* self, PyObject* obj) \ -{ \ - PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ - if (data) { \ - PyObject* result = PyObject_RichCompare(data, obj, Py_EQ); \ - Py_DECREF(data); \ - return result; \ - } \ - return nullptr; \ -} \ - \ -PyObject* name##StringIsNotEqual(PyObject* self, PyObject* obj) \ -{ \ - PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ - if (data) { \ - PyObject* result = PyObject_RichCompare(data, obj, Py_NE); \ - Py_DECREF(data); \ - return result; \ - } \ - return nullptr; \ -} - -// Only define STLStringCompare: -#define CPPYY_IMPL_STRING_PYTHONIZATION_CMP(type, name) \ -CPPYY_IMPL_STRING_PYTHONIZATION(type, name) \ -PyObject* name##StringCompare(PyObject* self, PyObject* obj) \ -{ \ - PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ - int result = 0; \ - if (data) { \ - result = PyObject_Compare(data, obj); \ - Py_DECREF(data); \ - } \ - if (PyErr_Occurred()) \ - return nullptr; \ - return PyInt_FromLong(result); \ -} - -CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::string, STL) -CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::wstring, STLW) -CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::string_view, STLView) - -static inline std::string* GetSTLString(CPPInstance* self) { - if (!CPPInstance_Check(self)) { - PyErr_SetString(PyExc_TypeError, "std::string object expected"); - return nullptr; - } - - std::string* obj = (std::string*)self->GetObject(); - if (!obj) - PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); - - return obj; -} - -PyObject* STLStringDecode(CPPInstance* self, PyObject* args, PyObject* kwds) -{ - std::string* obj = GetSTLString(self); - if (!obj) - return nullptr; - - char* keywords[] = {(char*)"encoding", (char*)"errors", (char*)nullptr}; - const char* encoding = nullptr; const char* errors = nullptr; - if (!PyArg_ParseTupleAndKeywords(args, kwds, - const_cast("s|s"), keywords, &encoding, &errors)) - return nullptr; - - return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors); -} - -PyObject* STLStringContains(CPPInstance* self, PyObject* pyobj) -{ - std::string* obj = GetSTLString(self); - if (!obj) - return nullptr; - - const char* needle = CPyCppyy_PyText_AsString(pyobj); - if (!needle) - return nullptr; - - if (obj->find(needle) != std::string::npos) { - Py_RETURN_TRUE; - } - - Py_RETURN_FALSE; -} - -PyObject* STLStringReplace(CPPInstance* self, PyObject* args, PyObject* /*kwds*/) -{ - std::string* obj = GetSTLString(self); - if (!obj) - return nullptr; - -// both str and std::string have a method "replace", but the Python version only -// accepts strings and takes no keyword arguments, whereas the C++ version has no -// overload that takes a string - - if (2 <= PyTuple_GET_SIZE(args) && CPyCppyy_PyText_Check(PyTuple_GET_ITEM(args, 0))) { - PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size()); - PyObject* meth = PyObject_GetAttrString(pystr, (char*)"replace"); - Py_DECREF(pystr); - PyObject* result = PyObject_CallObject(meth, args); - Py_DECREF(meth); - return result; - } - - PyObject* cppreplace = PyObject_GetAttrString((PyObject*)self, (char*)"__cpp_replace"); - if (cppreplace) { - PyObject* result = PyObject_Call(cppreplace, args, nullptr); - Py_DECREF(cppreplace); - return result; - } - - PyErr_SetString(PyExc_AttributeError, "\'std::string\' object has no attribute \'replace\'"); - return nullptr; -} - -#define CPYCPPYY_STRING_FINDMETHOD(name, cppname, pyname) \ -PyObject* STLString##name(CPPInstance* self, PyObject* args, PyObject* /*kwds*/) \ -{ \ - std::string* obj = GetSTLString(self); \ - if (!obj) \ - return nullptr; \ - \ - PyObject* cppmeth = PyObject_GetAttrString((PyObject*)self, (char*)#cppname);\ - if (cppmeth) { \ - PyObject* result = PyObject_Call(cppmeth, args, nullptr); \ - Py_DECREF(cppmeth); \ - if (result) { \ - if (PyLongOrInt_AsULong64(result) == (PY_ULONG_LONG)std::string::npos) {\ - Py_DECREF(result); \ - return PyInt_FromLong(-1); \ - } \ - return result; \ - } \ - PyErr_Clear(); \ - } \ - \ - PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size());\ - PyObject* pymeth = PyObject_GetAttrString(pystr, (char*)#pyname); \ - Py_DECREF(pystr); \ - PyObject* result = PyObject_CallObject(pymeth, args); \ - Py_DECREF(pymeth); \ - return result; \ -} - -// both str and std::string have method "find" and "rfin"; try the C++ version first -// and fall back on the Python one in case of failure -CPYCPPYY_STRING_FINDMETHOD( Find, __cpp_find, find) -CPYCPPYY_STRING_FINDMETHOD(RFind, __cpp_rfind, rfind) - -PyObject* STLStringGetAttr(CPPInstance* self, PyObject* attr_name) -{ - std::string* obj = GetSTLString(self); - if (!obj) - return nullptr; - - PyObject* pystr = CPyCppyy_PyText_FromStringAndSize(obj->data(), obj->size()); - PyObject* attr = PyObject_GetAttr(pystr, attr_name); - Py_DECREF(pystr); - return attr; -} - - -PyObject* UTF8Repr(PyObject* self) -{ -// force C++ string types conversion to Python str per Python __repr__ requirements - PyObject* res = PyObject_CallMethodNoArgs(self, PyStrings::gCppRepr); - if (!res || CPyCppyy_PyText_Check(res)) - return res; - PyObject* str_res = PyObject_Str(res); - Py_DECREF(res); - return str_res; -} - -PyObject* UTF8Str(PyObject* self) -{ -// force C++ string types conversion to Python str per Python __str__ requirements - PyObject* res = PyObject_CallMethodNoArgs(self, PyStrings::gCppStr); - if (!res || CPyCppyy_PyText_Check(res)) - return res; - PyObject* str_res = PyObject_Str(res); - Py_DECREF(res); - return str_res; -} - -Py_hash_t STLStringHash(PyObject* self) -{ -// std::string objects hash to the same values as Python strings to allow -// matches in dictionaries etc. - PyObject* data = STLStringGetData(self, false); - Py_hash_t h = CPyCppyy_PyText_Type.tp_hash(data); - Py_DECREF(data); - return h; -} - - -//- string_view behavior as primitive ---------------------------------------- -PyObject* StringViewInit(PyObject* self, PyObject* args, PyObject* /* kwds */) -{ -// if constructed from a Python unicode object, the constructor will convert it -// to a temporary byte string, which is likely to go out of scope too soon; so -// buffer it as needed - PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); - if (realInit) { - PyObject *strbuf = nullptr, *newArgs = nullptr; - if (PyTuple_GET_SIZE(args) == 1) { - PyObject* arg0 = PyTuple_GET_ITEM(args, 0); - if (PyUnicode_Check(arg0)) { - // convert to the expected bytes array to control the temporary - strbuf = PyUnicode_AsEncodedString(arg0, "UTF-8", "strict"); - newArgs = PyTuple_New(1); - Py_INCREF(strbuf); - PyTuple_SET_ITEM(newArgs, 0, strbuf); - } else if (PyBytes_Check(arg0)) { - // tie the life time of the provided string to the string_view - Py_INCREF(arg0); - strbuf = arg0; - } - } - - PyObject* result = PyObject_Call(realInit, newArgs ? newArgs : args, nullptr); - - Py_XDECREF(newArgs); - Py_DECREF(realInit); - - // if construction was successful and a string buffer was used, add a - // life line to it from the string_view bound object - if (result && self && strbuf) - PyObject_SetAttr(self, PyStrings::gLifeLine, strbuf); - Py_XDECREF(strbuf); - - return result; - } - return nullptr; -} - - -//- STL iterator behavior ---------------------------------------------------- -PyObject* STLIterNext(PyObject* self) -{ -// Python iterator protocol __next__ for STL forward iterators. - bool mustIncrement = true; - PyObject* last = nullptr; - if (CPPInstance_Check(self)) { - auto& dmc = ((CPPInstance*)self)->GetDatamemberCache(); - for (auto& p: dmc) { - if (p.first == PS_END_ADDR) { - last = p.second; - Py_INCREF(last); - } else if (p.first == PS_FLAG_ADDR) { - mustIncrement = p.second == Py_True; - if (!mustIncrement) { - Py_DECREF(p.second); - Py_INCREF(Py_True); - p.second = Py_True; - } - } - } - } - - PyObject* next = nullptr; - if (last) { - // handle special case of empty container (i.e. self is end) - if (!PyObject_RichCompareBool(last, self, Py_EQ)) { - bool iter_valid = true; - if (mustIncrement) { - // prefer preinc, but allow post-inc; in both cases, it is "self" that has - // the updated state to dereference - PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gPreInc); - if (!iter) { - PyErr_Clear(); - static PyObject* dummy = PyInt_FromLong(1l); - iter = PyObject_CallMethodOneArg(self, PyStrings::gPostInc, dummy); - } - iter_valid = iter && PyObject_RichCompareBool(last, self, Py_NE); - Py_XDECREF(iter); - } - - if (iter_valid) { - next = PyObject_CallMethodNoArgs(self, PyStrings::gDeref); - if (!next) PyErr_Clear(); - } - } - Py_DECREF(last); - } - - if (!next) PyErr_SetString(PyExc_StopIteration, ""); - return next; -} - - -//- STL complex behavior -------------------------------------------------- -#define COMPLEX_METH_GETSET(name, cppname) \ -static PyObject* name##ComplexGet(PyObject* self, void*) { \ - return PyObject_CallMethodNoArgs(self, cppname); \ -} \ -static int name##ComplexSet(PyObject* self, PyObject* value, void*) { \ - PyObject* result = PyObject_CallMethodOneArg(self, cppname, value); \ - if (result) { \ - Py_DECREF(result); \ - return 0; \ - } \ - return -1; \ -} \ -PyGetSetDef name##Complex{(char*)#name, (getter)name##ComplexGet, (setter)name##ComplexSet, nullptr, nullptr}; - -COMPLEX_METH_GETSET(real, PyStrings::gCppReal) -COMPLEX_METH_GETSET(imag, PyStrings::gCppImag) - -static PyObject* ComplexComplex(PyObject* self) { - PyObject* real = PyObject_CallMethodNoArgs(self, PyStrings::gCppReal); - if (!real) return nullptr; - double r = PyFloat_AsDouble(real); - Py_DECREF(real); - if (r == -1. && PyErr_Occurred()) - return nullptr; - - PyObject* imag = PyObject_CallMethodNoArgs(self, PyStrings::gCppImag); - if (!imag) return nullptr; - double i = PyFloat_AsDouble(imag); - Py_DECREF(imag); - if (i == -1. && PyErr_Occurred()) - return nullptr; - - return PyComplex_FromDoubles(r, i); -} - -static PyObject* ComplexRepr(PyObject* self) { - PyObject* real = PyObject_CallMethodNoArgs(self, PyStrings::gCppReal); - if (!real) return nullptr; - double r = PyFloat_AsDouble(real); - Py_DECREF(real); - if (r == -1. && PyErr_Occurred()) - return nullptr; - - PyObject* imag = PyObject_CallMethodNoArgs(self, PyStrings::gCppImag); - if (!imag) return nullptr; - double i = PyFloat_AsDouble(imag); - Py_DECREF(imag); - if (i == -1. && PyErr_Occurred()) - return nullptr; - - std::ostringstream s; - s << '(' << r << '+' << i << "j)"; - return CPyCppyy_PyText_FromString(s.str().c_str()); -} - -static PyObject* ComplexDRealGet(CPPInstance* self, void*) -{ - return PyFloat_FromDouble(((std::complex*)self->GetObject())->real()); -} - -static int ComplexDRealSet(CPPInstance* self, PyObject* value, void*) -{ - double d = PyFloat_AsDouble(value); - if (d == -1.0 && PyErr_Occurred()) - return -1; - ((std::complex*)self->GetObject())->real(d); - return 0; -} - -PyGetSetDef ComplexDReal{(char*)"real", (getter)ComplexDRealGet, (setter)ComplexDRealSet, nullptr, nullptr}; - - -static PyObject* ComplexDImagGet(CPPInstance* self, void*) -{ - return PyFloat_FromDouble(((std::complex*)self->GetObject())->imag()); -} - -static int ComplexDImagSet(CPPInstance* self, PyObject* value, void*) -{ - double d = PyFloat_AsDouble(value); - if (d == -1.0 && PyErr_Occurred()) - return -1; - ((std::complex*)self->GetObject())->imag(d); - return 0; -} - -PyGetSetDef ComplexDImag{(char*)"imag", (getter)ComplexDImagGet, (setter)ComplexDImagSet, nullptr, nullptr}; - -static PyObject* ComplexDComplex(CPPInstance* self) -{ - double r = ((std::complex*)self->GetObject())->real(); - double i = ((std::complex*)self->GetObject())->imag(); - return PyComplex_FromDoubles(r, i); -} - - -} // unnamed namespace - - -//- public functions --------------------------------------------------------- -namespace CPyCppyy { - std::set gIteratorTypes; -} - -static inline -bool run_pythonizors(PyObject* pyclass, PyObject* pyname, const std::vector& v) -{ - PyObject* args = PyTuple_New(2); - Py_INCREF(pyclass); PyTuple_SET_ITEM(args, 0, pyclass); - Py_INCREF(pyname); PyTuple_SET_ITEM(args, 1, pyname); - - bool pstatus = true; - for (auto pythonizor : v) { - PyObject* result = PyObject_CallObject(pythonizor, args); - if (!result) { - pstatus = false; // TODO: detail the error handling - break; - } - Py_DECREF(result); - } - Py_DECREF(args); - - return pstatus; -} - -bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) -{ - const std::string& name = Cppyy::GetScopedFinalName(scope); - -// Add pre-defined pythonizations (for STL and ROOT) to classes based on their -// signature and/or class name. - if (!pyclass) - return false; - - CPPScope* klass = (CPPScope*)pyclass; - -//- method name based pythonization ------------------------------------------ - -// for smart pointer style classes that are otherwise not known as such; would -// prefer operator-> as that returns a pointer (which is simpler since it never -// has to deal with ref-assignment), but operator* plays better with STL iters -// and algorithms - if (HasAttrDirect(pyclass, PyStrings::gDeref) && !Cppyy::IsSmartPtr(klass->fCppType)) - Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)DeRefGetAttr, METH_O); - else if (HasAttrDirect(pyclass, PyStrings::gFollow) && !Cppyy::IsSmartPtr(klass->fCppType)) - Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)FollowGetAttr, METH_O); - -// for pre-check of nullptr for boolean types - if (HasAttrDirect(pyclass, PyStrings::gCppBool)) { - const char* pybool_name = "__bool__"; - Utility::AddToClass(pyclass, pybool_name, (PyCFunction)NullCheckBool, METH_NOARGS); - } - -// for STL containers, and user classes modeled after them -// the attribute must be a CPyCppyy overload, otherwise the check gives false -// positives in the case where the class has a non-function attribute that is -// called "size". - if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBeCPyCppyy=*/ true)) { - Utility::AddToClass(pyclass, "__len__", "size"); - } - - if (HasAttrDirect(pyclass, PyStrings::gContains)) { - Utility::AddToClass(pyclass, "__contains__", "contains"); - } - - if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below - !((PyTypeObject*)pyclass)->tp_iter) { - if (HasAttrDirect(pyclass, PyStrings::gBegin) && HasAttrDirect(pyclass, PyStrings::gEnd)) { - // obtain the name of the return type - const auto& methods = Cppyy::GetMethodsFromName(klass->fCppType, "begin"); - if (!methods.empty()) { - // check return type; if not explicitly an iterator, add it to the "known" return - // types to add the "next" method on use - Cppyy::TCppMethod_t meth = methods[0]; - const std::string& resname = Cppyy::GetMethodReturnTypeAsString(meth); - bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end(); - if (!isIterator && Cppyy::GetScope(resname)) { - if (resname.find("iterator") == std::string::npos) - gIteratorTypes.insert(resname); - isIterator = true; - } - - if (isIterator) { - // install iterator protocol a la STL - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)STLSequenceIter; - Utility::AddToClass(pyclass, "__iter__", (PyCFunction)STLSequenceIter, METH_NOARGS); - } else { - // still okay if this is some pointer type of builtin persuasion (general class - // won't work: the return type needs to understand the iterator protocol) - std::string resolved = Cppyy::ResolveName(resname); - if (resolved.back() == '*' && Cppyy::IsBuiltin(resolved.substr(0, resolved.size()-1))) { - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)LLSequenceIter; - Utility::AddToClass(pyclass, "__iter__", (PyCFunction)LLSequenceIter, METH_NOARGS); - } - } - } - } - if (!((PyTypeObject*)pyclass)->tp_iter && // no iterator resolved - HasAttrDirect(pyclass, PyStrings::gGetItem) && PyObject_HasAttr(pyclass, PyStrings::gLen)) { - // Python will iterate over __getitem__ using integers, but C++ operator[] will never raise - // a StopIteration. A checked getitem (raising IndexError if beyond size()) works in some - // cases but would mess up if operator[] is meant to implement an associative container. So, - // this has to be implemented as an iterator protocol. - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)index_iter; - Utility::AddToClass(pyclass, "__iter__", (PyCFunction)index_iter, METH_NOARGS); - } - } - -// operator==/!= are used in op_richcompare of CPPInstance, which subsequently allows -// comparisons to None; if no operator is available, a hook is installed for lazy -// lookups in the global and/or class namespace - if (HasAttrDirect(pyclass, PyStrings::gEq, true) && \ - Cppyy::GetMethodsFromName(klass->fCppType, "__eq__").empty()) { - PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gEq); - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators(); - klass->fOperators->fEq = cppol; - // re-insert the forwarding __eq__ from the CPPInstance in case there was a Python-side - // override in the base class - static PyObject* top_eq = nullptr; - if (!top_eq) { - PyObject* top_cls = PyObject_GetAttrString(gThisModule, "CPPInstance"); - top_eq = PyObject_GetAttr(top_cls, PyStrings::gEq); - Py_DECREF(top_eq); // make it borrowed - Py_DECREF(top_cls); - } - PyObject_SetAttr(pyclass, PyStrings::gEq, top_eq); - } - - if (HasAttrDirect(pyclass, PyStrings::gNe, true) && \ - Cppyy::GetMethodsFromName(klass->fCppType, "__ne__").empty()) { - PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gNe); - if (!klass->fOperators) klass->fOperators = new Utility::PyOperators(); - klass->fOperators->fNe = cppol; - // re-insert the forwarding __ne__ (same reason as above for __eq__) - static PyObject* top_ne = nullptr; - if (!top_ne) { - PyObject* top_cls = PyObject_GetAttrString(gThisModule, "CPPInstance"); - top_ne = PyObject_GetAttr(top_cls, PyStrings::gNe); - Py_DECREF(top_ne); // make it borrowed - Py_DECREF(top_cls); - } - PyObject_SetAttr(pyclass, PyStrings::gNe, top_ne); - } - - if (HasAttrDirect(pyclass, PyStrings::gRepr, true)) { - // guarantee that the result of __repr__ is a Python string - Utility::AddToClass(pyclass, "__cpp_repr", "__repr__"); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)UTF8Repr, METH_NOARGS); - } - - if (HasAttrDirect(pyclass, PyStrings::gStr, true)) { - // guarantee that the result of __str__ is a Python string - Utility::AddToClass(pyclass, "__cpp_str", "__str__"); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)UTF8Str, METH_NOARGS); - } - - if (Cppyy::IsAggregate(((CPPClass*)pyclass)->fCppType) && name.compare(0, 5, "std::", 5) != 0) { - // create a pseudo-constructor to allow initializer-style object creation - Cppyy::TCppScope_t kls = ((CPPClass*)pyclass)->fCppType; - std::vector datamems; - Cppyy::GetDatamembers(kls, datamems); - if (!datamems.empty()) { - std::string rname = name; - TypeManip::cppscope_to_legalname(rname); - - std::ostringstream initdef; - initdef << "namespace __cppyy_internal {\n" - << "void init_" << rname << "(" << name << "** self"; - bool codegen_ok = true; - std::vector arg_types, arg_names, arg_defaults; - const int ndata = datamems.size(); - arg_types.reserve(ndata); arg_names.reserve(ndata); arg_defaults.reserve(ndata); - for (auto data : datamems) { - if (Cppyy::IsStaticDatamember(data) || !Cppyy::IsPublicData(data)) - continue; - - Cppyy::TCppType_t datammember_type = - Cppyy::GetDatamemberType(data); - const std::string &res = - Cppyy::IsEnumType(datammember_type) - ? Cppyy::GetScopedFinalName( - Cppyy::GetScopeFromType(datammember_type)) - : Cppyy::GetTypeAsString( - Cppyy::ResolveType(datammember_type)); - const std::string& cpd = TypeManip::compound(res); - std::string res_clean = TypeManip::clean_type(res, false, true); - - if (res_clean == "internal_enum_type_t") - res_clean = res; // restore (properly scoped name) - - if (res.rfind(']') == std::string::npos && res.rfind(')') == std::string::npos) { - if (!cpd.empty()) arg_types.push_back(res_clean+cpd); - else arg_types.push_back("const "+res_clean+"&"); - arg_names.push_back(Cppyy::GetFinalName(data)); - if ((!cpd.empty() && cpd.back() == '*') || Cppyy::IsBuiltin(res_clean)) - arg_defaults.push_back("0"); - else { - Cppyy::TCppScope_t klsid = Cppyy::GetScope(res_clean); - if (Cppyy::IsDefaultConstructable(klsid)) arg_defaults.push_back(res_clean+"{}"); - } - } else { - codegen_ok = false; // TODO: how to support arrays, anonymous enums, etc? - break; - } - } - - if (codegen_ok && !arg_types.empty()) { - bool defaults_ok = arg_defaults.size() == arg_types.size(); - for (std::vector::size_type i = 0; i < arg_types.size(); ++i) { - initdef << ", " << arg_types[i] << " " << arg_names[i]; - if (defaults_ok) initdef << " = " << arg_defaults[i]; - } - initdef << ") {\n *self = new " << name << "{"; - for (std::vector::size_type i = 0; i < arg_names.size(); ++i) { - if (i != 0) initdef << ", "; - initdef << arg_names[i]; - } - initdef << "};\n} }"; - - if (Cppyy::Compile(initdef.str(), true /* silent */)) { - Cppyy::TCppScope_t cis = Cppyy::GetScope("__cppyy_internal"); - const auto& methods = Cppyy::GetMethodsFromName(cis, "init_" + rname); - if (methods.size()) { - if (!Utility::AddToClass(pyclass, "__init__", - new CPPFunction(cis, methods[0]))) - PyErr_Clear(); - } - } - } - } - } - - -//- class name based pythonization ------------------------------------------- - - if (IsTemplatedSTLClass(name, "vector")) { - - // std::vector is a special case in C++ - if (!sVectorBoolTypeID) sVectorBoolTypeID = Cppyy::GetScope("std::vector"); - if (klass->fCppType == sVectorBoolTypeID) { - Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)VectorBoolGetItem, METH_O); - Utility::AddToClass(pyclass, "__setitem__", (PyCFunction)VectorBoolSetItem); - } else { - // constructor that takes python collections - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)VectorInit, METH_VARARGS | METH_KEYWORDS); - - // data with size - Utility::AddToClass(pyclass, "__real_data", "data"); - Utility::AddToClass(pyclass, "data", (PyCFunction)VectorData); - - // numpy array conversion - Utility::AddToClass(pyclass, "__array__", (PyCFunction)VectorArray, METH_VARARGS | METH_KEYWORDS /* unused */); - - // checked getitem - if (HasAttrDirect(pyclass, PyStrings::gLen)) { - Utility::AddToClass(pyclass, "_getitem__unchecked", "__getitem__"); - Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)VectorGetItem, METH_O); - } - - // vector-optimized iterator protocol - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)vector_iter; - - // optimized __iadd__ - Utility::AddToClass(pyclass, "__iadd__", (PyCFunction)VectorIAdd, METH_VARARGS | METH_KEYWORDS); - - // helpers for iteration - Cppyy::TCppType_t value_type = Cppyy::GetTypeFromScope(Cppyy::GetNamed("value_type", scope)); - Cppyy::TCppType_t vtype = Cppyy::ResolveType(value_type); - if (vtype) { // actually resolved? - PyObject* pyvalue_type = PyLong_FromVoidPtr(vtype.data); - PyObject_SetAttr(pyclass, PyStrings::gValueTypePtr, pyvalue_type); - Py_DECREF(pyvalue_type); - pyvalue_type = PyUnicode_FromString(Cppyy::GetTypeAsString(vtype).c_str()); - PyObject_SetAttr(pyclass, PyStrings::gValueType, pyvalue_type); - Py_DECREF(pyvalue_type); - } - - size_t typesz = Cppyy::SizeOfType(vtype); - if (typesz) { - PyObject* pyvalue_size = PyLong_FromSsize_t(typesz); - PyObject_SetAttr(pyclass, PyStrings::gValueSize, pyvalue_size); - Py_DECREF(pyvalue_size); - } - } - } - - else if (IsTemplatedSTLClass(name, "array")) { - // constructor that takes python associative collections - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)ArrayInit, METH_VARARGS | METH_KEYWORDS); - } - - else if (IsTemplatedSTLClass(name, "map") || IsTemplatedSTLClass(name, "unordered_map")) { - // constructor that takes python associative collections - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)MapInit, METH_VARARGS | METH_KEYWORDS); - // From C++20, std::map/unordered_map have a native contains() that the generic - // contains->__contains__ mapping above will pick up. Strong-types reflection - // does not always expose it, so fall back to a find()-based __contains__ when - // none was reflected (still O(log n)/O(1), never iterating). - if (!HasAttrDirect(pyclass, PyStrings::gContains)) - Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLContainsWithFind, METH_O); - } - - else if (IsTemplatedSTLClass(name, "set")) { - // constructor that takes python associative collections - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)SetInit, METH_VARARGS | METH_KEYWORDS); - // From C++20, std::set has a native contains() that the generic - // contains->__contains__ mapping above will pick up. Strong-types reflection - // does not always expose it, so fall back to a find()-based __contains__ when - // none was reflected (still O(log n), never iterating). - if (!HasAttrDirect(pyclass, PyStrings::gContains)) - Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLContainsWithFind, METH_O); - } - - else if (IsTemplatedSTLClass(name, "pair")) { - Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)PairUnpack, METH_O); - Utility::AddToClass(pyclass, "__len__", (PyCFunction)ReturnTwo, METH_NOARGS); - } - - if (IsTemplatedSTLClass(name, "shared_ptr") || IsTemplatedSTLClass(name, "unique_ptr")) { - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)SmartPtrInit, METH_VARARGS | METH_KEYWORDS); - } - - else if (!((PyTypeObject*)pyclass)->tp_iter && \ - (name.find("iterator") != std::string::npos || gIteratorTypes.find(name) != gIteratorTypes.end())) { - ((PyTypeObject*)pyclass)->tp_iternext = (iternextfunc)STLIterNext; - Utility::AddToClass(pyclass, CPPYY__next__, (PyCFunction)STLIterNext, METH_NOARGS); - ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)PyObject_SelfIter; - Utility::AddToClass(pyclass, "__iter__", (PyCFunction)PyObject_SelfIter, METH_NOARGS); - } - - else if (name == "std::basic_string" || - name == "std::__1::basic_string" || // libc++ inline namespace - name == "std::string") { // typedef preserved by GetScopedFinalName on libc++ - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLStringRepr, METH_NOARGS); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLStringStr, METH_NOARGS); - Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLStringBytes, METH_NOARGS); - Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLStringCompare, METH_O); - Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLStringIsEqual, METH_O); - Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLStringIsNotEqual, METH_O); - // Python's `in` operator needs __contains__ on the proxy regardless of the - // C++ standard CPyCppyy is built with; it never dispatches to C++'s - // std::string::contains (C++23+). The old `__cplusplus <= 202302L` guard - // wrongly dropped it when built with -std=c++2c (__cplusplus == 202400L). - Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLStringContains, METH_O); - Utility::AddToClass(pyclass, "decode", (PyCFunction)STLStringDecode, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__cpp_find", "find"); - Utility::AddToClass(pyclass, "find", (PyCFunction)STLStringFind, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__cpp_rfind", "rfind"); - Utility::AddToClass(pyclass, "rfind", (PyCFunction)STLStringRFind, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__cpp_replace", "replace"); - Utility::AddToClass(pyclass, "replace", (PyCFunction)STLStringReplace, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)STLStringGetAttr, METH_O); - - // to allow use of std::string in dictionaries and findable with str - ((PyTypeObject*)pyclass)->tp_hash = (hashfunc)STLStringHash; - } - - else if (name == "std::basic_string_view" || - name == "std::__1::basic_string_view" || // libc++ inline namespace - name == "std::string_view") { // typedef preserved by GetScopedFinalName on libc++ - Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLViewStringBytes, METH_NOARGS); - Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLViewStringCompare, METH_O); - Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLViewStringIsEqual, METH_O); - Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLViewStringIsNotEqual, METH_O); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLViewStringRepr, METH_NOARGS); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, METH_NOARGS); - } - - else if (name == "std::basic_string,std::allocator >" || - name == "std::__1::basic_string,std::__1::allocator >" || - name == "std::wstring") { - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLWStringRepr, METH_NOARGS); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLWStringStr, METH_NOARGS); - Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLWStringBytes, METH_NOARGS); - Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLWStringCompare, METH_O); - Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLWStringIsEqual, METH_O); - Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLWStringIsNotEqual, METH_O); - } - - else if (name == "complex" || name == "std::complex") { - Utility::AddToClass(pyclass, "__cpp_real", "real"); - PyObject_SetAttrString(pyclass, "real", PyDescr_NewGetSet((PyTypeObject*)pyclass, &ComplexDReal)); - Utility::AddToClass(pyclass, "__cpp_imag", "imag"); - PyObject_SetAttrString(pyclass, "imag", PyDescr_NewGetSet((PyTypeObject*)pyclass, &ComplexDImag)); - Utility::AddToClass(pyclass, "__complex__", (PyCFunction)ComplexDComplex, METH_NOARGS); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)ComplexRepr, METH_NOARGS); - } - - else if (IsTemplatedSTLClass(name, "complex")) { - Utility::AddToClass(pyclass, "__cpp_real", "real"); - PyObject_SetAttrString(pyclass, "real", PyDescr_NewGetSet((PyTypeObject*)pyclass, &realComplex)); - Utility::AddToClass(pyclass, "__cpp_imag", "imag"); - PyObject_SetAttrString(pyclass, "imag", PyDescr_NewGetSet((PyTypeObject*)pyclass, &imagComplex)); - Utility::AddToClass(pyclass, "__complex__", (PyCFunction)ComplexComplex, METH_NOARGS); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)ComplexRepr, METH_NOARGS); - } - -// direct user access; there are two calls here: -// - explicit pythonization: won't fall through to the base classes and is preferred if present -// - normal pythonization: only called if explicit isn't present, falls through to base classes - bool bUserOk = true; PyObject* res = nullptr; - PyObject* pyname = CPyCppyy_PyText_FromString(name.c_str()); - if (HasAttrDirect(pyclass, PyStrings::gExPythonize)) { - res = PyObject_CallMethodObjArgs(pyclass, PyStrings::gExPythonize, pyclass, pyname, nullptr); - bUserOk = (bool)res; - } else { - PyObject* func = PyObject_GetAttr(pyclass, PyStrings::gPythonize); - if (func) { - res = PyObject_CallFunctionObjArgs(func, pyclass, pyname, nullptr); - Py_DECREF(func); - bUserOk = (bool)res; - } else - PyErr_Clear(); - } - if (!bUserOk) { - Py_DECREF(pyname); - return false; - } else { - Py_XDECREF(res); - // pyname handed to args tuple below - } - -// call registered pythonizors, if any: first run the namespace-specific pythonizors, then -// the global ones (the idea is to allow writing a pythonizor that see all classes) - bool pstatus = true; - std::string outer_scope = TypeManip::extract_namespace(name); - auto &pyzMap = pythonizations(); - if (!outer_scope.empty()) { - auto p = pyzMap.find(outer_scope); - if (p != pyzMap.end()) { - PyObject* subname = CPyCppyy_PyText_FromString( - name.substr(outer_scope.size()+2, std::string::npos).c_str()); - pstatus = run_pythonizors(pyclass, subname, p->second); - Py_DECREF(subname); - } - } - - if (pstatus) { - auto p = pyzMap.find(""); - if (p != pyzMap.end()) - pstatus = run_pythonizors(pyclass, pyname, p->second); - } - - Py_DECREF(pyname); - -// phew! all done ... - return pstatus; -} diff --git a/src/CPyCppyy/src/Pythonize.h b/src/CPyCppyy/src/Pythonize.h deleted file mode 100644 index 673f574..0000000 --- a/src/CPyCppyy/src/Pythonize.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef CPYCPPYY_PYTHONIZE_H -#define CPYCPPYY_PYTHONIZE_H - -// Standard -#include - - -namespace CPyCppyy { - -// make the named C++ class more python-like -bool Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_PYTHONIZE_H diff --git a/src/CPyCppyy/src/SignalTryCatch.h b/src/CPyCppyy/src/SignalTryCatch.h deleted file mode 100644 index 14742b8..0000000 --- a/src/CPyCppyy/src/SignalTryCatch.h +++ /dev/null @@ -1,80 +0,0 @@ -// Partial reproduction of ROOT's TException.h - -/************************************************************************* - * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers. * - * All rights reserved. * - * * - * For the licensing terms see $ROOTSYS/LICENSE. * - * For the list of contributors see $ROOTSYS/README/CREDITS. * - *************************************************************************/ - -#ifndef CPYCPPYY_SIGNALTRYCATCH_H -#define CPYCPPYY_SIGNALTRYCATCH_H - -#include -#include "CPyCppyy/CommonDefs.h" - -#ifndef _WIN32 -#define NEED_SIGJMP 1 -#endif - -// By default, the ExceptionContext_t class is expected in the namespace -// CppyyLegacy, If it is expected in no namespace, one can explicitly define -// NO_CPPYY_LEGACY_NAMESPACE at build time (e.g. if one wants to use ROOT). - -#ifndef NO_CPPYY_LEGACY_NAMESPACE -namespace CppyyLegacy { -#endif -struct ExceptionContext_t { -#ifdef NEED_SIGJMP - sigjmp_buf fBuf; -#else - jmp_buf fBuf; -#endif -}; -#ifndef NO_CPPYY_LEGACY_NAMESPACE -} - -using CppyyExceptionContext_t = CppyyLegacy::ExceptionContext_t; -#else -using CppyyExceptionContext_t = ExceptionContext_t; -#endif - -// FIXME: This is a dummy, replace with cling equivalent of gException -static CppyyExceptionContext_t DummyException; -static CppyyExceptionContext_t *gException = &DummyException; - -#ifdef NEED_SIGJMP -# define CLING_EXCEPTION_SETJMP(buf) sigsetjmp(buf,1) -#else -# define CLING_EXCEPTION_SETJMP(buf) setjmp(buf) -#endif - -#define CLING_EXCEPTION_RETRY \ - { \ - static CppyyExceptionContext_t R__curr, *R__old = gException; \ - int R__code; \ - gException = &R__curr; \ - R__code = CLING_EXCEPTION_SETJMP(gException->fBuf); if (R__code) { }; { - -#define CLING_EXCEPTION_TRY \ - { \ - static CppyyExceptionContext_t R__curr, *R__old = gException; \ - int R__code; \ - gException = &R__curr; \ - if ((R__code = CLING_EXCEPTION_SETJMP(gException->fBuf)) == 0) { - -#define CLING_EXCEPTION_CATCH(n) \ - gException = R__old; \ - } else { \ - int n = R__code; \ - gException = R__old; - -#define CLING_EXCEPTION_ENDTRY \ - } \ - gException = R__old; \ - } - -CPYCPPYY_IMPORT CppyyExceptionContext_t *gException; - -#endif diff --git a/src/CPyCppyy/src/TemplateProxy.cxx b/src/CPyCppyy/src/TemplateProxy.cxx deleted file mode 100644 index b42a1b0..0000000 --- a/src/CPyCppyy/src/TemplateProxy.cxx +++ /dev/null @@ -1,923 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "TemplateProxy.h" -#include "CPPClassMethod.h" -#include "CPPConstructor.h" -#include "CPPFunction.h" -#include "CPPMethod.h" -#include "CPPOverload.h" -#include "Cppyy.h" -#include "PyCallable.h" -#include "PyStrings.h" -#include "Utility.h" - -// Standard -#include - - -namespace CPyCppyy { - -static inline std::string targs2str(TemplateProxy* pytmpl) -{ - if (!pytmpl || !pytmpl->fTemplateArgs) return ""; - return CPyCppyy_PyText_AsString(pytmpl->fTemplateArgs); -} - -//---------------------------------------------------------------------------- -TemplateInfo::TemplateInfo() : fPyClass(nullptr), fNonTemplated(nullptr), - fTemplated(nullptr), fLowPriority(nullptr), fDoc(nullptr) -{ - /* empty */ -} - -//---------------------------------------------------------------------------- -TemplateInfo::~TemplateInfo() -{ - Py_XDECREF(fPyClass); - - Py_XDECREF(fDoc); - Py_DECREF(fNonTemplated); - Py_DECREF(fTemplated); - Py_DECREF(fLowPriority); - - for (const auto& p : fDispatchMap) { - for (const auto& c : p.second) { - Py_DECREF(c.second); - } - } -} - - -//---------------------------------------------------------------------------- -void TemplateProxy::MergeOverload(CPPOverload* mp) { -// Store overloads of this templated method. - bool isGreedy = false; - for (auto pc : mp->fMethodInfo->fMethods) { - if (pc->IsGreedy()) { - isGreedy = true; - break; - } - } - - CPPOverload* cppol = isGreedy ? fTI->fLowPriority : fTI->fNonTemplated; - cppol->MergeOverload(mp); -} - -void TemplateProxy::AdoptMethod(PyCallable* pc) { -// Store overload of this templated method. - CPPOverload* cppol = pc->IsGreedy() ? fTI->fLowPriority : fTI->fNonTemplated; - cppol->AdoptMethod(pc); -} - -void TemplateProxy::AdoptTemplate(PyCallable* pc) -{ -// Store known template methods. - fTI->fTemplated->AdoptMethod(pc); -} - -//---------------------------------------------------------------------------- -PyObject* TemplateProxy::Instantiate(const std::string& fname, - CPyCppyy_PyArgs_t args, size_t nargsf, Utility::ArgPreference pref, int* pcnt) -{ -// Instantiate (and cache) templated methods, return method if any - std::string proto = ""; - -// adjust arguments for self if this is a rebound global function - bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); - if (!isNS && CPyCppyy_PyArgs_GET_SIZE(args, nargsf) && \ - (!fSelf || - (fSelf == Py_None && !Cppyy::IsStaticTemplate(((CPPScope*)fTI->fPyClass)->fCppType, fname)))) { - args += 1; - nargsf -= 1; - } - - Py_ssize_t argc = CPyCppyy_PyArgs_GET_SIZE(args, nargsf); - if (argc != 0) { - PyObject* tpArgs = PyTuple_New(argc); - for (Py_ssize_t i = 0; i < argc; ++i) { - PyObject* itemi = CPyCppyy_PyArgs_GET_ITEM(args, i); - - bool bArgSet = false; - - // special case for arrays - if (TemplateProxy_CheckExact(itemi)) { - TemplateProxy *tn = (TemplateProxy*)itemi; - PyObject *f = PyUnicode_FromFormat("%s%s", tn->fTI->fCppName.c_str(), targs2str(tn).c_str()); - PyTuple_SET_ITEM(tpArgs, i, f); - bArgSet = true; - } - PyObject* pytc = nullptr; - if (!bArgSet && (pytc = PyObject_GetAttr(itemi, PyStrings::gTypeCode))) { - Py_buffer bufinfo; - memset(&bufinfo, 0, sizeof(Py_buffer)); - std::string ptrdef; - if (PyObject_GetBuffer(itemi, &bufinfo, PyBUF_FORMAT) == 0) { - for (int j = 0; j < bufinfo.ndim; ++j) ptrdef += "*"; - PyBuffer_Release(&bufinfo); - } else { - ptrdef += "*"; - PyErr_Clear(); - } - - PyObject* pyptrname = Utility::CT2CppName(pytc, ptrdef.c_str(), true); - if (pyptrname) { - PyTuple_SET_ITEM(tpArgs, i, pyptrname); - bArgSet = true; - // string added, but not counted towards nStrings - } - Py_DECREF(pytc); pytc = nullptr; - } else - PyErr_Clear(); - - // if not arg set, try special case for ctypes - if (!bArgSet) pytc = PyObject_GetAttr(itemi, PyStrings::gCTypesType); - - if (!bArgSet && pytc) { - PyObject* pyactname = Utility::CT2CppName(pytc, "&", false); - if (!pyactname) { - // _type_ of a pointer to c_type is that type, which will have a type - PyObject* newpytc = PyObject_GetAttr(pytc, PyStrings::gCTypesType); - Py_DECREF(pytc); - pytc = newpytc; - if (pytc) { - pyactname = Utility::CT2CppName(pytc, "*", false); - } else - PyErr_Clear(); - } - Py_XDECREF(pytc); pytc = nullptr; - if (pyactname) { - PyTuple_SET_ITEM(tpArgs, i, pyactname); - bArgSet = true; - // string added, but not counted towards nStrings - } - } else - PyErr_Clear(); - - if (!bArgSet) { - // normal case (may well fail) - PyErr_Clear(); - PyObject* tp = (PyObject*)Py_TYPE(itemi); - Py_INCREF(tp); - PyTuple_SET_ITEM(tpArgs, i, tp); - } - } - - PyObject* pyargs = PyTuple_New(argc); - for (Py_ssize_t i = 0; i < argc; ++i) { - PyObject* item = CPyCppyy_PyArgs_GET_ITEM(args, i); - Py_INCREF(item); - PyTuple_SET_ITEM(pyargs, i, item); - } - const std::string& name_v1 = \ - Utility::ConstructTemplateArgs(nullptr, tpArgs, pyargs, pref, 0, pcnt); - - Py_DECREF(pyargs); - Py_DECREF(tpArgs); - - // Propagate the error that occurs if we can't construct the C++ name - // from the provided template argument - if (PyErr_Occurred()) { - return nullptr; - } - - if (name_v1.size()) - proto = name_v1.substr(1, name_v1.size()-2); - } - -// the following causes instantiation as necessary - Cppyy::TCppScope_t scope = ((CPPClass*)fTI->fPyClass)->fCppType; - Cppyy::TCppMethod_t cppmeth = Cppyy::GetMethodTemplate(scope, fname, proto); - if (cppmeth) { // overload stops here - // A successful instantiation needs to be cached to pre-empt future instantiations. There - // are two names involved, the original asked (which may be partial) and the received. - // - // Caching scheme: if the match is exact, simply add the overload to the pre-existing - // one, or create a new overload for later lookups. If the match is not exact, do the - // same, but also create an alias. Only add exact matches to the set of known template - // instantiations, to prevent piling on from different partial instantiations. - // - // TODO: this caches the lookup method before the call, meaning that failing overloads - // can add already existing overloads to the set of methods. - - std::string resname = Cppyy::GetFullName(Cppyy::TCppScope_t(cppmeth.data)); - - // An initializer_list is preferred for the argument types, but should not leak into - // the argument types. If it did, replace with vector and lookup anew. - if (resname.find("initializer_list") != std::string::npos) { - auto pos = proto.find("initializer_list"); - while (pos != std::string::npos) { - proto.replace(pos, 16, "vector"); - pos = proto.find("initializer_list", pos + 6); - } - - Cppyy::TCppMethod_t m2 = Cppyy::GetMethodTemplate(scope, fname, proto); - if (m2 && m2 != cppmeth) { - // replace if the new method with vector was found; otherwise just continue - // with the previously found method with initializer_list. - cppmeth = m2; - resname = Cppyy::GetFullName(Cppyy::TCppScope_t(cppmeth.data)); - } - } - - bool bExactMatch = fname == resname; - - // lookup on existing name in case this was an overload, not a caching, failure - PyObject* dct = PyObject_GetAttr(fTI->fPyClass, PyStrings::gDict); - PyObject* pycachename = CPyCppyy_PyText_InternFromString(fname.c_str()); - PyObject* pyol = PyObject_GetItem(dct, pycachename); - if (!pyol) PyErr_Clear(); - bool bIsCppOL = CPPOverload_Check(pyol); - bool bIsCppTP = TemplateProxy_Check(pyol); - - // find the full name if the requested one was partial - PyObject* exact = nullptr; - PyObject* pyresname = CPyCppyy_PyText_FromString(resname.c_str()); - if (!bExactMatch) { - exact = PyObject_GetItem(dct, pyresname); - if (!exact) PyErr_Clear(); - } - Py_DECREF(dct); - - bool bIsConstructor = false, bNeedsRebind = true; - - PyCallable* meth = nullptr; - if (Cppyy::IsNamespace(scope)) { - meth = new CPPFunction(scope, cppmeth); - bNeedsRebind = false; - } else if (Cppyy::IsStaticMethod(cppmeth)) { - meth = new CPPClassMethod(scope, cppmeth); - bNeedsRebind = false; - } else if (Cppyy::IsConstructor(cppmeth)) { - bIsConstructor = true; - meth = new CPPConstructor(scope, cppmeth); - } else - meth = new CPPMethod(scope, cppmeth); - - // Case 1/2: method simply did not exist before - if (!pyol) { - // actual overload to use (now owns meth) - pyol = (PyObject*)CPPOverload_New(fname, meth); - if (bIsConstructor) { - // TODO: this is an ugly hack :( - ((CPPOverload*)pyol)->fMethodInfo->fFlags |= \ - CallContext::kIsCreator | CallContext::kIsConstructor; - } - - // add to class dictionary - PyType_Type.tp_setattro(fTI->fPyClass, pycachename, pyol); - } - - // Case 3/4: pre-existing method that was either not found b/c the full - // templated name was constructed in this call or it failed as overload - else if (bIsCppOL) { - // TODO: see above, since the call hasn't happened yet, this overload may - // already exist and fail again. - ((CPPOverload*)pyol)->AdoptMethod(meth); // takes ownership - } - - // Case 5: must be a template proxy, meaning that current template name is not - // a template overload - else if (bIsCppTP) { - ((TemplateProxy*)pyol)->AdoptTemplate(meth->Clone()); - Py_DECREF(pyol); - pyol = (PyObject*)CPPOverload_New(fname, meth); // takes ownership - } - // Case 6: pre-existing object is not a CPPOverload nor TemplateProxy - // we do not cache it, as this might be a pythonization (monkey-patched func/method) - else { - Py_DECREF(pyol); - pyol = (PyObject*)CPPOverload_New(fname, meth); - } - - // Special Case if name was aliased (e.g. typedef in template instantiation) - if (!exact && !bExactMatch) { - PyType_Type.tp_setattro(fTI->fPyClass, pyresname, pyol); - } - - // cleanup - Py_XDECREF(exact); - Py_DECREF(pyresname); - Py_DECREF(pycachename); - - // retrieve fresh (for boundedness) and call - PyObject* pymeth = - CPPOverload_Type.tp_descr_get(pyol, bNeedsRebind ? fSelf : nullptr, (PyObject*)&CPPOverload_Type); - Py_DECREF(pyol); - return pymeth; - } - - PyErr_Format(PyExc_TypeError, "Failed to instantiate \"%s(%s)\"", fname.c_str(), proto.c_str()); - return nullptr; -} - - -//= CPyCppyy template proxy construction/destruction ========================= -static TemplateProxy* tpp_new(PyTypeObject*, PyObject*, PyObject*) -{ -// Create a new empty template method proxy. - TemplateProxy* pytmpl = PyObject_GC_New(TemplateProxy, &TemplateProxy_Type); - pytmpl->fSelf = nullptr; - pytmpl->fTemplateArgs = nullptr; - pytmpl->fWeakrefList = nullptr; - new (&pytmpl->fTI) TP_TInfo_t{}; - pytmpl->fTI = std::make_shared(); - - PyObject_GC_Track(pytmpl); - return pytmpl; -} - -//---------------------------------------------------------------------------- -static Py_hash_t tpp_hash(TemplateProxy* self) -{ - return (Py_hash_t)self; -} - -//---------------------------------------------------------------------------- -static PyObject* tpp_richcompare(TemplateProxy* self, PyObject* other, int op) -{ - if (op == Py_EQ || op == Py_NE) { - if (!TemplateProxy_CheckExact(other)) - Py_RETURN_FALSE; - - if (self->fTI == ((TemplateProxy*)other)->fTI) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; - } - - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; -} - -//---------------------------------------------------------------------------- -static int tpp_clear(TemplateProxy* pytmpl) -{ -// Garbage collector clear of held python member objects. - Py_CLEAR(pytmpl->fSelf); - Py_CLEAR(pytmpl->fTemplateArgs); - - return 0; -} - -//---------------------------------------------------------------------------- -static void tpp_dealloc(TemplateProxy* pytmpl) -{ -// Destroy the given template method proxy. - if (pytmpl->fWeakrefList) - PyObject_ClearWeakRefs((PyObject*)pytmpl); - PyObject_GC_UnTrack(pytmpl); - tpp_clear(pytmpl); - pytmpl->fTI.~TP_TInfo_t(); - PyObject_GC_Del(pytmpl); -} - -//---------------------------------------------------------------------------- -static int tpp_traverse(TemplateProxy* pytmpl, visitproc visit, void* arg) -{ -// Garbage collector traverse of held python member objects. - Py_VISIT(pytmpl->fSelf); - Py_VISIT(pytmpl->fTemplateArgs); - - return 0; -} - -//---------------------------------------------------------------------------- -static PyObject* tpp_doc(TemplateProxy* pytmpl, void*) -{ - if (pytmpl->fTI->fDoc) { - Py_INCREF(pytmpl->fTI->fDoc); - return pytmpl->fTI->fDoc; - } - -// Forward to method proxies to doc all overloads - PyObject* doc = nullptr; - if (pytmpl->fTI->fNonTemplated->HasMethods()) - doc = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fNonTemplated, "__doc__"); - if (pytmpl->fTI->fTemplated->HasMethods()) { - PyObject* doc2 = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fTemplated, "__doc__"); - if (doc && doc2) { - CPyCppyy_PyText_AppendAndDel(&doc, CPyCppyy_PyText_FromString("\n")); - CPyCppyy_PyText_AppendAndDel(&doc, doc2); - } else if (!doc && doc2) { - doc = doc2; - } - } - if (pytmpl->fTI->fLowPriority->HasMethods()) { - PyObject* doc2 = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fLowPriority, "__doc__"); - if (doc && doc2) { - CPyCppyy_PyText_AppendAndDel(&doc, CPyCppyy_PyText_FromString("\n")); - CPyCppyy_PyText_AppendAndDel(&doc, doc2); - } else if (!doc && doc2) { - doc = doc2; - } - } - - if (doc) - return doc; - - return CPyCppyy_PyText_FromString(TemplateProxy_Type.tp_doc); -} - -static int tpp_doc_set(TemplateProxy* pytmpl, PyObject *val, void *) -{ - Py_XDECREF(pytmpl->fTI->fDoc); - Py_INCREF(val); - pytmpl->fTI->fDoc = val; - return 0; -} - -//---------------------------------------------------------------------------- - -//= CPyCppyy template proxy callable behavior ================================ - -#define TPPCALL_RETURN \ -{ errors.clear(); \ - return result; } - -static inline void UpdateDispatchMap(TemplateProxy* pytmpl, bool use_targs, uint64_t sighash, CPPOverload* pymeth) -{ -// Memoize a method in the dispatch map after successful call; replace old if need be (may be -// with the same CPPOverload, just with more methods). - bool bInserted = false; - auto& v = pytmpl->fTI->fDispatchMap[use_targs ? targs2str(pytmpl) : ""]; - - Py_INCREF(pymeth); - for (auto& p : v) { - if (p.first == sighash) { - Py_DECREF(p.second); - p.second = pymeth; - bInserted = true; - } - } - if (!bInserted) v.push_back(std::make_pair(sighash, pymeth)); -} - -static inline PyObject* SelectAndForward(TemplateProxy* pytmpl, CPPOverload* pymeth, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, - bool implicitOkay, bool use_targs, uint64_t sighash, std::vector& errors) -{ -// Forward a call to known overloads, if any. - if (pymeth->HasMethods()) { - PyObject* pycall = CPPOverload_Type.tp_descr_get( - (PyObject*)pymeth, pytmpl->fSelf, (PyObject*)&CPPOverload_Type); - - if (!implicitOkay) - ((CPPOverload*)pycall)->fFlags |= CallContext::kNoImplicit; - - // now call the method with the arguments (loops internally) - PyObject* result = CPyCppyy_tp_call(pycall, args, nargsf, kwds); - Py_DECREF(pycall); - if (result) { - UpdateDispatchMap(pytmpl, use_targs, sighash, pymeth); - TPPCALL_RETURN; - } - Utility::FetchError(errors); - } - - return nullptr; -} - -static inline PyObject* CallMethodImp(TemplateProxy* pytmpl, PyObject*& pymeth, - CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, bool impOK, uint64_t sighash) -{ -// Actual call of a given overload: takes care of handlign of "self" and -// dereferences the overloaded method after use. - - PyObject* result; - if (!impOK && CPPOverload_Check(pymeth)) - ((CPPOverload*)pymeth)->fFlags |= CallContext::kNoImplicit; - bool isNS = (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); - if (isNS && pytmpl->fSelf && pytmpl->fSelf != Py_None) { - // this is a global method added a posteriori to the class - PyCallArgs cargs{(CPPInstance*&)pytmpl->fSelf, args, nargsf, kwds}; - AdjustSelf(cargs); - result = CPyCppyy_tp_call(pymeth, cargs.fArgs, cargs.fNArgsf, cargs.fKwds); - } else { - if (!pytmpl->fSelf && CPPOverload_Check(pymeth)) - ((CPPOverload*)pymeth)->fFlags &= ~CallContext::kFromDescr; - result = CPyCppyy_tp_call(pymeth, args, nargsf, kwds); - } - - if (result) { - Py_XDECREF(((CPPOverload*)pymeth)->fSelf); ((CPPOverload*)pymeth)->fSelf = nullptr; // unbind - UpdateDispatchMap(pytmpl, true, sighash, (CPPOverload*)pymeth); - } - - Py_DECREF(pymeth); pymeth = nullptr; - return result; -} - -static PyObject* tpp_vectorcall( - TemplateProxy* pytmpl, PyObject* const *args, size_t nargsf, PyObject* kwds) -{ -// Dispatcher to the actual member method, several uses possible; in order: -// -// case 1: explicit template previously selected through subscript -// -// case 2: select known non-template overload -// -// obj.method(a0, a1, ...) -// => obj->method(a0, a1, ...) // non-template -// -// case 3: select known template overload -// -// obj.method(a0, a1, ...) -// => obj->method(a0, a1, ...) // all known templates -// -// case 4: auto-instantiation from types of arguments -// -// obj.method(a0, a1, ...) -// => obj->method(a0, a1, ...) -// -// Note: explicit instantiation needs to use [] syntax: -// -// obj.method[type, type, ...](a0, a1, ...) -// -// case 5: low priority methods, such as ones that take void* arguments -// - -// TODO: should previously instantiated templates be considered first? - - PyObject *pymeth = nullptr, *result = nullptr; - -// short-cut through memoization map - Py_ssize_t argc = CPyCppyy_PyArgs_GET_SIZE(args, nargsf); - uint64_t sighash = HashSignature(args, argc); - - CPPOverload* ol = nullptr; - if (!pytmpl->fTemplateArgs) { - // look for known signatures ... - auto& v = pytmpl->fTI->fDispatchMap[""]; - for (const auto& p : v) { - if (p.first == sighash) { - ol = p.second; - break; - } - } - - if (ol != nullptr) { - if (!pytmpl->fSelf || pytmpl->fSelf == Py_None) { - result = CPyCppyy_tp_call((PyObject*)ol, args, nargsf, kwds); - } else { - pymeth = CPPOverload_Type.tp_descr_get( - (PyObject*)ol, pytmpl->fSelf, (PyObject*)&CPPOverload_Type); - result = CPyCppyy_tp_call(pymeth, args, nargsf, kwds); - Py_DECREF(pymeth); pymeth = nullptr; - } - if (result) - return result; - } - } - -// container for collecting errors - std::vector errors; - if (ol) Utility::FetchError(errors); - -// case 1: explicit template previously selected through subscript - if (pytmpl->fTemplateArgs) { - // instantiate explicitly - PyObject* pyfullname = CPyCppyy_PyText_FromString(pytmpl->fTI->fCppName.c_str()); - CPyCppyy_PyText_Append(&pyfullname, pytmpl->fTemplateArgs); - - // first, lookup by full name, if previously stored - bool isNS = (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); - if (pytmpl->fSelf && pytmpl->fSelf != Py_None && !isNS) - pymeth = PyObject_GetAttr(pytmpl->fSelf, pyfullname); - else // by-passes custom scope getattr that searches into Cling - pymeth = PyType_Type.tp_getattro(pytmpl->fTI->fPyClass, pyfullname); - - // attempt call if found (this may fail if there are specializations) - if (CPPOverload_Check(pymeth)) { - // since the template args are fully explicit, allow implicit conversion of arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, sighash); - if (result) { - Py_DECREF(pyfullname); - TPPCALL_RETURN; - } - Utility::FetchError(errors); - } else if (pymeth && PyCallable_Check(pymeth)) { - // something different (user provided?) - result = CPyCppyy_PyObject_Call(pymeth, args, nargsf, kwds); - Py_DECREF(pymeth); - if (result) { - Py_DECREF(pyfullname); - TPPCALL_RETURN; - } - Utility::FetchError(errors); - } else if (!pymeth) - PyErr_Clear(); - - // not cached or failed call; try instantiation - pymeth = pytmpl->Instantiate( - CPyCppyy_PyText_AsString(pyfullname), args, nargsf, Utility::kNone); - if (pymeth) { - // attempt actual call; same as above, allow implicit conversion of arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, sighash); - if (result) { - Py_DECREF(pyfullname); - TPPCALL_RETURN; - } - } - - // no drop through if failed (if implicit was desired, don't provide template args) - Utility::FetchError(errors); - PyObject* topmsg = CPyCppyy_PyText_FromFormat( - "Could not find \"%s\" (set cppyy.set_debug() for C++ errors):", CPyCppyy_PyText_AsString(pyfullname)); - Py_DECREF(pyfullname); - Utility::SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); - - return nullptr; - } - -// case 2: select known non-template overload - result = SelectAndForward(pytmpl, pytmpl->fTI->fNonTemplated, args, nargsf, kwds, - true /* implicitOkay */, false /* use_targs */, sighash, errors); - if (result) - TPPCALL_RETURN; - -// case 3: select known template overload - result = SelectAndForward(pytmpl, pytmpl->fTI->fTemplated, args, nargsf, kwds, - false /* implicitOkay */, true /* use_targs */, sighash, errors); - if (result) - TPPCALL_RETURN; - -// case 4: auto-instantiation from types of arguments - for (auto pref : {Utility::kReference, Utility::kPointer, Utility::kValue}) { - // TODO: no need to loop if there are no non-instance arguments; also, should any - // failed lookup be removed? - int pcnt = 0; - pymeth = pytmpl->Instantiate(pytmpl->fTI->fCppName, args, nargsf, pref, &pcnt); - if (pymeth) { - // attempt actual call; even if argument based, allow implicit conversions, for example for non-template arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, sighash); - if (result) TPPCALL_RETURN; - } - Utility::FetchError(errors); - if (!pcnt) break; // preference never used; no point trying others - } - -// case 5: low priority methods, such as ones that take void* arguments - result = SelectAndForward(pytmpl, pytmpl->fTI->fLowPriority, args, nargsf, kwds, - false /* implicitOkay */, false /* use_targs */, sighash, errors); - if (result) - TPPCALL_RETURN; - -// error reporting is fraud, given the numerous steps taken, but more details seems better - if (!errors.empty()) { - PyObject* topmsg = CPyCppyy_PyText_FromString("Template method resolution failed:"); - Utility::SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); - } else { - PyErr_Format(PyExc_TypeError, "cannot resolve method template call for \'%s\'", - pytmpl->fTI->fCppName.c_str()); - } - - return nullptr; -} - -//---------------------------------------------------------------------------- -static TemplateProxy* tpp_descr_get(TemplateProxy* pytmpl, PyObject* pyobj, PyObject*) -{ -// create and use a new template proxy (language requirement) - TemplateProxy* newPyTmpl = (TemplateProxy*)TemplateProxy_Type.tp_alloc(&TemplateProxy_Type, 0); - -// new method is to be bound to current object (may be nullptr) - if (pyobj) { - Py_INCREF(pyobj); - newPyTmpl->fSelf = pyobj; - } else { - Py_INCREF(Py_None); - newPyTmpl->fSelf = Py_None; - } - - Py_XINCREF(pytmpl->fTemplateArgs); - newPyTmpl->fTemplateArgs = pytmpl->fTemplateArgs; - -// copy name, class, etc. pointers - new (&newPyTmpl->fTI) std::shared_ptr{pytmpl->fTI}; - - newPyTmpl->fVectorCall = pytmpl->fVectorCall; - - return newPyTmpl; -} - - -//---------------------------------------------------------------------------- -static PyObject* tpp_subscript(TemplateProxy* pytmpl, PyObject* args) -{ -// Explicit template member lookup/instantiation; works by re-bounding. This method can -// not cache overloads as instantiations need not be unique for the argument types due -// to template specializations. - TemplateProxy* typeBoundMethod = tpp_descr_get(pytmpl, pytmpl->fSelf, nullptr); - Py_XDECREF(typeBoundMethod->fTemplateArgs); - typeBoundMethod->fTemplateArgs = CPyCppyy_PyText_FromString( - Utility::ConstructTemplateArgs(nullptr, args).c_str()); -// Propagate the error that occurs if we can't construct the C++ name -// from the provided template argument - if (PyErr_Occurred()) { - return nullptr; - } - return (PyObject*)typeBoundMethod; -} - -//----------------------------------------------------------------------------- -static PyObject* tpp_getuseffi(CPPOverload*, void*) -{ - return PyInt_FromLong(0); // dummy (__useffi__ unused) -} - -//----------------------------------------------------------------------------- -static int tpp_setuseffi(CPPOverload*, PyObject*, void*) -{ - return 0; // dummy (__useffi__ unused) -} - - -//---------------------------------------------------------------------------- -static PyMappingMethods tpp_as_mapping = { - nullptr, (binaryfunc)tpp_subscript, nullptr -}; - -static PyGetSetDef tpp_getset[] = { - {(char*)"__doc__", (getter)tpp_doc, (setter)tpp_doc_set, nullptr, nullptr}, - {(char*)"__useffi__", (getter)tpp_getuseffi, (setter)tpp_setuseffi, - (char*)"unused", nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} -}; - - -//---------------------------------------------------------------------------- -void TemplateProxy::Set(const std::string& cppname, const std::string& pyname, PyObject* pyclass) -{ -// Initialize the proxy for the given 'pyclass.' - fSelf = nullptr; - fTemplateArgs = nullptr; - - fTI->fCppName = cppname; - Py_XINCREF(pyclass); - fTI->fPyClass = pyclass; - - std::vector dummy; - fTI->fNonTemplated = CPPOverload_New(pyname, dummy); - fTI->fTemplated = CPPOverload_New(pyname, dummy); - fTI->fLowPriority = CPPOverload_New(pyname, dummy); - - fVectorCall = (vectorcallfunc)tpp_vectorcall; -} - - -//= CPyCppyy method proxy access to internals ================================ -static PyObject* tpp_overload(TemplateProxy* pytmpl, PyObject* args) -{ -// Select and call a specific C++ overload, based on its signature. - const char* sigarg = nullptr; - PyObject* sigarg_tuple = nullptr; - int want_const = -1; - - Cppyy::TCppScope_t scope = nullptr; - Cppyy::TCppMethod_t cppmeth = nullptr; - std::string proto; - - if (PyArg_ParseTuple(args, const_cast("s|i:__overload__"), &sigarg, &want_const)) { - want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; - - // check existing overloads in order - PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg, want_const); - if (ol) return ol; - PyErr_Clear(); - ol = pytmpl->fTI->fTemplated->FindOverload(sigarg, want_const); - if (ol) return ol; - PyErr_Clear(); - ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg, want_const); - if (ol) return ol; - - proto = Utility::ConstructTemplateArgs(nullptr, args); - // Propagate the error that occurs if we can't construct the C++ name - // from the provided template argument - if (PyErr_Occurred()) { - return nullptr; - } - - scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - cppmeth = Cppyy::GetMethodTemplate( - scope, pytmpl->fTI->fCppName, proto.substr(1, proto.size()-2)); - } else if (PyArg_ParseTuple(args, const_cast("O|i:__overload__"), &sigarg_tuple, &want_const)) { - PyErr_Clear(); - want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; - - // check existing overloads in order - PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg_tuple, want_const); - if (ol) return ol; - PyErr_Clear(); - ol = pytmpl->fTI->fTemplated->FindOverload(sigarg_tuple, want_const); - if (ol) return ol; - PyErr_Clear(); - ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg_tuple, want_const); - if (ol) return ol; - - proto.reserve(128); - proto.push_back('<'); - Py_ssize_t n = PyTuple_Size(sigarg_tuple); - for (int i = 0; i < n; i++) { - PyObject *pItem = PyTuple_GetItem(sigarg_tuple, i); - if(!CPyCppyy_PyText_Check(pItem)) { - PyErr_Format(PyExc_LookupError, "argument types should be in string format"); - return (PyObject*) nullptr; - } - proto.append(CPyCppyy_PyText_AsString(pItem)); - if (i < n - 1) - proto.push_back(','); - } - proto.push_back('>'); - - scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - cppmeth = Cppyy::GetMethodTemplate( - scope, pytmpl->fTI->fCppName, proto.substr(1, proto.size()-2)); - } else { - PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__"); - return nullptr; - } - -// else attempt instantiation - if (!cppmeth) { - return nullptr; - } - - PyErr_Clear(); - - // TODO: the next step should be consolidated with Instantiate() - PyCallable* meth = nullptr; - if (Cppyy::IsNamespace(scope)) { - meth = new CPPFunction(scope, cppmeth); - } else if (Cppyy::IsStaticMethod(cppmeth)) { - meth = new CPPClassMethod(scope, cppmeth); - } else if (Cppyy::IsConstructor(cppmeth)) { - meth = new CPPConstructor(scope, cppmeth); - } else - meth = new CPPMethod(scope, cppmeth); - - return (PyObject*)CPPOverload_New(pytmpl->fTI->fCppName+proto, meth); -} - -static PyMethodDef tpp_methods[] = { - {(char*)"__overload__", (PyCFunction)tpp_overload, METH_VARARGS, - (char*)"select overload for dispatch" }, - {(char*)nullptr, nullptr, 0, nullptr } -}; - - -//= CPyCppyy template proxy type ============================================= -PyTypeObject TemplateProxy_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.TemplateProxy", // tp_name - sizeof(TemplateProxy), // tp_basicsize - 0, // tp_itemsize - (destructor)tpp_dealloc, // tp_dealloc - offsetof(TemplateProxy, fVectorCall), - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / tp_compare - 0, // tp_repr - 0, // tp_as_number - 0, // tp_as_sequence - &tpp_as_mapping, // tp_as_mapping - (hashfunc)tpp_hash, // tp_hash - (ternaryfunc)PyVectorcall_Call, // tp_call - 0, // tp_str - 0, // tp_getattro - 0, // tp_setattro - 0, // tp_as_buffer - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC - | Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_METHOD_DESCRIPTOR, // tp_flags - (char*)"cppyy template proxy (internal)", // tp_doc - (traverseproc)tpp_traverse, // tp_traverse - (inquiry)tpp_clear, // tp_clear - (richcmpfunc)tpp_richcompare, // tp_richcompare - offsetof(TemplateProxy, fWeakrefList), // tp_weaklistoffset - 0, // tp_iter - 0, // tp_iternext - tpp_methods, // tp_methods - 0, // tp_members - tpp_getset, // tp_getset - 0, // tp_base - 0, // tp_dict - (descrgetfunc)tpp_descr_get, // tp_descr_get - 0, // tp_descr_set - 0, // tp_dictoffset - 0, // tp_init - 0, // tp_alloc - (newfunc)tpp_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 - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/src/CPyCppyy/src/TemplateProxy.h b/src/CPyCppyy/src/TemplateProxy.h deleted file mode 100644 index 79c7402..0000000 --- a/src/CPyCppyy/src/TemplateProxy.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef CPYCPPYY_TEMPLATEPROXY_H -#define CPYCPPYY_TEMPLATEPROXY_H - -// Bindings -#include "CPPScope.h" -#include "Utility.h" - -// Standard -#include -#include -#include -#include -#include - - -namespace CPyCppyy { - -class PyCallable; -class CPPOverload; - -/** Template proxy object to return functions and methods - */ - -typedef std::pair TP_DispatchEntry_t; -typedef std::unordered_map> TP_DispatchMap_t; - -class TemplateInfo { -public: - TemplateInfo(); - TemplateInfo(const TemplateInfo&) = delete; - TemplateInfo& operator=(const TemplateInfo&) = delete; - ~TemplateInfo(); - -public: - std::string fCppName; - PyObject* fPyClass; - CPPOverload* fNonTemplated; // holder for non-template overloads - CPPOverload* fTemplated; // holder for templated overloads - CPPOverload* fLowPriority; // low priority overloads such as void*/void** - - TP_DispatchMap_t fDispatchMap; - PyObject* fDoc; -}; - -typedef std::shared_ptr TP_TInfo_t; - -class TemplateProxy { -private: - friend TemplateProxy* TemplateProxy_New( - const std::string& cppname, const std::string& pyname, PyObject* pyclass); - void Set(const std::string& cppname, const std::string& pyname, PyObject* pyclass); - -public: // public, as the python C-API works with C structs - PyObject_HEAD - PyObject* fSelf; // must be first (same layout as CPPOverload) - PyObject* fTemplateArgs; - PyObject* fWeakrefList; - vectorcallfunc fVectorCall; - TP_TInfo_t fTI; - -public: - void MergeOverload(CPPOverload* mp); - void AdoptMethod(PyCallable* pc); - void AdoptTemplate(PyCallable* pc); - PyObject* Instantiate(const std::string& fname, - CPyCppyy_PyArgs_t tmplArgs, size_t nargsf, Utility::ArgPreference, int* pcnt = nullptr); - -private: // private, as the python C-API will handle creation - TemplateProxy() = delete; -}; - - -//- template proxy type and type verification -------------------------------- -extern PyTypeObject TemplateProxy_Type; - -template -inline bool TemplateProxy_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &TemplateProxy_Type); -} - -template -inline bool TemplateProxy_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &TemplateProxy_Type; -} - -//- creation ----------------------------------------------------------------- -inline TemplateProxy* TemplateProxy_New( - const std::string& cppname, const std::string& pyname, PyObject* pyclass) -{ -// Create and initialize a new template method proxy for the class. - if (!CPPScope_Check(pyclass)) return nullptr; - - TemplateProxy* pytmpl = - (TemplateProxy*)TemplateProxy_Type.tp_new(&TemplateProxy_Type, nullptr, nullptr); - pytmpl->Set(cppname, pyname, pyclass); - return pytmpl; -} - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_TEMPLATEPROXY_H diff --git a/src/CPyCppyy/src/TupleOfInstances.cxx b/src/CPyCppyy/src/TupleOfInstances.cxx deleted file mode 100644 index e7308ab..0000000 --- a/src/CPyCppyy/src/TupleOfInstances.cxx +++ /dev/null @@ -1,245 +0,0 @@ -#include "Python.h" -#include "Cppyy.h" - -// Bindings -#include "CPyCppyy.h" -#include "TupleOfInstances.h" -#include "ProxyWrappers.h" - - -namespace { - -typedef struct { - PyObject_HEAD - Cppyy::TCppScope_t ia_klass; - void* ia_array_start; - Py_ssize_t ia_pos; - Py_ssize_t ia_len; - Py_ssize_t ia_stride; -} ia_iterobject; - -static PyObject* ia_iternext(ia_iterobject* ia) { - if (ia->ia_len != (Py_ssize_t)-1 && ia->ia_pos >= ia->ia_len) { - ia->ia_pos = 0; // debatable, but since the iterator is cached, this - return nullptr; // allows for multiple conversions to e.g. a tuple - } else if (ia->ia_stride == 0 && ia->ia_pos != 0) { - PyErr_SetString(PyExc_ReferenceError, "no stride available for indexing"); - return nullptr; - } - PyObject* result = CPyCppyy::BindCppObjectNoCast( - (char*)ia->ia_array_start + ia->ia_pos*ia->ia_stride, ia->ia_klass); - ia->ia_pos += 1; - return result; -} - -static int ia_traverse(ia_iterobject*, visitproc, void*) { - return 0; -} - -static PyObject* ia_getsize(ia_iterobject* ia, void*) { - return PyInt_FromSsize_t(ia->ia_len); -} - -static int ia_setsize(ia_iterobject* ia, PyObject* pysize, void*) { - Py_ssize_t size = PyInt_AsSsize_t(pysize); - if (size == (Py_ssize_t)-1 && PyErr_Occurred()) - return -1; - ia->ia_len = size; - return 0; -} - -static PyGetSetDef ia_getset[] = { - {(char*)"size", (getter)ia_getsize, (setter)ia_setsize, - (char*)"set size of array to which this iterator refers", nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} -}; - - -static Py_ssize_t ia_length(ia_iterobject* ia) -{ - return ia->ia_len; -} - -static PyObject* ia_subscript(ia_iterobject* ia, PyObject* pyidx) -{ -// Subscripting the iterator allows direct access through indexing on arrays -// that do not have a defined length. This way, the return from accessing such -// an array as a data member can both be used in a loop and directly. - Py_ssize_t idx = PyInt_AsSsize_t(pyidx); - if (idx == (Py_ssize_t)-1 && PyErr_Occurred()) - return nullptr; - - if (ia->ia_len != (Py_ssize_t)-1 && (idx < 0 || ia->ia_len <= idx)) { - PyErr_SetString(PyExc_IndexError, "index out of range"); - return nullptr; - } - - return CPyCppyy::BindCppObjectNoCast( - (char*)ia->ia_array_start + ia->ia_pos*ia->ia_stride, ia->ia_klass); -} - -static PyMappingMethods ia_as_mapping = { - (lenfunc) ia_length, // mp_length - (binaryfunc) ia_subscript, // mp_subscript - (objobjargproc)nullptr, // mp_ass_subscript -}; - -} // unnamed namespace - - -namespace CPyCppyy { - -PyTypeObject InstanceArrayIter_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.instancearrayiter", // tp_name - sizeof(ia_iterobject), // tp_basicsize - 0, - (destructor)PyObject_GC_Del, // tp_dealloc - 0, 0, 0, 0, 0, 0, 0, - &ia_as_mapping, // tp_as_mapping - 0, 0, 0, 0, 0, 0, - Py_TPFLAGS_DEFAULT | - Py_TPFLAGS_HAVE_GC, // tp_flags - 0, - (traverseproc)ia_traverse, // tp_traverse - 0, 0, 0, - PyObject_SelfIter, // tp_iter - (iternextfunc)ia_iternext, // tp_iternext - 0, 0, - ia_getset, // tp_getset - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, // tp_del - 0, // tp_version_tag - 0, // tp_finalize - 0 // tp_vectorcall - CPYCPPYY_PYTYPE_TAIL -}; - - -//= support for C-style arrays of objects ==================================== -PyObject* TupleOfInstances_New( - Cppyy::TCppObject_t address, Cppyy::TCppScope_t klass, cdims_t dims) -{ -// recursively set up tuples of instances on all dimensions - if (dims.ndim() == UNKNOWN_SIZE || dims[0] == UNKNOWN_SIZE /* unknown shape or size */) { - // no known length ... return an iterable object and let the user figure it out - ia_iterobject* ia = PyObject_GC_New(ia_iterobject, &InstanceArrayIter_Type); - if (!ia) return nullptr; - - ia->ia_klass = klass; - ia->ia_array_start = address.data; - ia->ia_pos = 0; - ia->ia_len = -1; - ia->ia_stride = Cppyy::SizeOf(klass); - - PyObject_GC_Track(ia); - return (PyObject*)ia; - } else if (1 < dims.ndim()) { - // not the innermost dimension, descend one level - size_t block_size = 1; - for (Py_ssize_t i = 1; i < dims.ndim(); ++i) { - if (dims[i] != 0) - block_size *= (size_t)dims[i]; - } - block_size *= Cppyy::SizeOf(klass); - - Py_ssize_t nelems = dims[0]; - PyObject* tup = PyTuple_New(nelems); - for (Py_ssize_t i = 0; i < nelems; ++i) { - PyTuple_SetItem(tup, i, TupleOfInstances_New( - ((char*)address.data) + i*block_size, klass, dims.sub())); - } - return tup; - } else { - // innermost dimension: construct tuple - int nelems = (int)dims[0]; - size_t block_size = Cppyy::SizeOf(klass); - if (block_size == 0) { - PyErr_Format(PyExc_TypeError, - "can not determine size of type \"%s\" for array indexing", - Cppyy::GetScopedFinalName(klass).c_str()); - return nullptr; - } - - // TODO: the extra copy is inefficient, but it appears that the only way to - // initialize a subclass of a tuple is through a sequence - PyObject* tup = PyTuple_New(nelems); - for (int i = 0; i < nelems; ++i) { - // TODO: there's an assumption here that there is no padding, which is bound - // to be incorrect in certain cases - PyTuple_SetItem(tup, i, - BindCppObjectNoCast(((char*)address.data) + i*block_size, klass)); - // Note: objects are bound as pointers, yet since the pointer value stays in - // place, updates propagate just as if they were bound by-reference - } - - PyObject* args = PyTuple_New(1); - Py_INCREF(tup); PyTuple_SET_ITEM(args, 0, tup); - PyObject* arr = PyTuple_Type.tp_new(&TupleOfInstances_Type, args, nullptr); - - Py_DECREF(args); - // tup ref eaten by SET_ITEM on args - - return arr; - } - -// never get here - return nullptr; -} - -//= CPyCppyy custom tuple-like array type ==================================== -PyTypeObject TupleOfInstances_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - (char*)"cppyy.InstancesArray", // tp_name - 0, // tp_basicsize - 0, // tp_itemsize - 0, // tp_dealloc - 0, // tp_vectorcall_offset / tp_print - 0, // tp_getattr - 0, // tp_setattr - 0, // tp_as_async / 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_CHECKTYPES | - Py_TPFLAGS_BASETYPE, // tp_flags - (char*)"array of C++ instances", // 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 - &PyTuple_Type, // 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 - CPYCPPYY_PYTYPE_TAIL -}; - -} // namespace CPyCppyy diff --git a/src/CPyCppyy/src/TupleOfInstances.h b/src/CPyCppyy/src/TupleOfInstances.h deleted file mode 100644 index d903872..0000000 --- a/src/CPyCppyy/src/TupleOfInstances.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef CPYCPPYY_TUPLEOFINSTANCES_H -#define CPYCPPYY_TUPLEOFINSTANCES_H - -// Bindings -#include "Dimensions.h" - - -namespace CPyCppyy { - -/** Representation of C-style array of instances - @author WLAV - @date 02/10/2014 - @version 1.0 - */ - -//- custom tuple type that can pass through C-style arrays ------------------- -extern PyTypeObject TupleOfInstances_Type; -extern PyTypeObject InstanceArrayIter_Type; - -template -inline bool TupleOfInstances_Check(T* object) -{ - return object && PyObject_TypeCheck(object, &TupleOfInstances_Type); -} - -template -inline bool TupleOfInstances_CheckExact(T* object) -{ - return object && Py_TYPE(object) == &TupleOfInstances_Type; -} - -PyObject* TupleOfInstances_New( - Cppyy::TCppObject_t address, Cppyy::TCppScope_t klass, cdims_t dims); - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_TUPLEOFINSTANCES_H diff --git a/src/CPyCppyy/src/TypeManip.cxx b/src/CPyCppyy/src/TypeManip.cxx deleted file mode 100644 index bfead0b..0000000 --- a/src/CPyCppyy/src/TypeManip.cxx +++ /dev/null @@ -1,280 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "TypeManip.h" - -// Standard -#include -#include -#include - - -//- helpers ------------------------------------------------------------------ -static inline -bool is_varchar(char c) { - return isalnum((int)c) || c == '_' || c == ')' || c == '(' /* for (anonymous)/(unnamed) */; -} - -static inline -std::string::size_type find_qualifier_index(const std::string& name) -{ -// Find the first location that is not part of the class name proper. - std::string::size_type i = name.size() - 1; - bool arr_open = false; - for ( ; 0 < i; --i) { - std::string::value_type c = name[i]; - if (!arr_open && (is_varchar(c) || c == '>')) { - if (c == 't' && 6 < i && !is_varchar(name[i-5]) && name.substr(i-4, 5) == "const") - i -= 4; // this skips 'const' on a pointer type - else - break; - } else if (c == ']') { - arr_open = true; - } else if (c == '[') { - arr_open = false; - } - } - - return i+1; -} - -static inline void erase_const(std::string& name) -{ -// Find and remove all occurrence of 'const'. - if (name.empty()) - return; - - std::string::size_type spos = std::string::npos; - std::string::size_type start = 0; - while ((spos = name.find("const", start)) != std::string::npos) { - // make sure not to erase 'const' as part of the name: if it is - // connected, before or after, to a variable name, then keep it - std::string::size_type after = spos+5; - if (after < name.size() && is_varchar(name[after])) { - start = after; - continue; - } else if (after == name.size()) { - if (spos > 0 && is_varchar(name[spos - 1])) - break; - } - - std::string::size_type i = 5; - while (name[spos+i] == ' ') ++i; - name.swap(name.erase(spos, i)); - } -} - -static inline void rstrip(std::string& name) -{ -// Remove space from the right side of name. - std::string::size_type i = name.size(); - for ( ; 0 < i; --i) { - if (!isspace(name[i-1])) - break; - } - - if (i != name.size()) - name = name.substr(0, i); -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::remove_const(const std::string& cppname) -{ -// Remove 'const' qualifiers from the given C++ name. - std::string::size_type tmplt_start = cppname.find('<'); - std::string::size_type type_stop = cppname.rfind('>'); - if (cppname.find("::", type_stop+1) != std::string::npos) // e.g. klass::some_typedef - type_stop = cppname.find(' ', type_stop+1); - if (tmplt_start != std::string::npos && cppname[tmplt_start+1] != '<') { - // only replace const qualifying cppname, not in template parameters - std::string pre = cppname.substr(0, tmplt_start); - erase_const(pre); - std::string post = ""; - if (type_stop != std::string::npos && type_stop != cppname.size()-1) { - post = cppname.substr(type_stop+1, std::string::npos); - erase_const(post); - } - - return pre + cppname.substr(tmplt_start, type_stop+1-tmplt_start) + post; - } - - std::string clean_name = cppname; - erase_const(clean_name); - return clean_name; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::clean_type( - const std::string& cppname, bool template_strip, bool const_strip) -{ -// Strip C++ name from all qualifiers and compounds. - std::string::size_type i = find_qualifier_index(cppname); - std::string name = cppname.substr(0, i); - rstrip(name); - - if (name.back() == ']') { // array type? - // TODO: this fails templates instantiated on arrays (not common) - name = name.substr(0, name.find('[')); - } else if (template_strip && name.back() == '>') { - name = name.substr(0, name.find('<')); - } - - if (const_strip) { - if (template_strip) - erase_const(name); - else - name = remove_const(name); - } - - return name; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::template_base(const std::string& cppname) -{ -// If this is a template, return the underlying template name w/o arguments - if (cppname.empty() || cppname.back() != '>') - return cppname; - - int tpl_open = 0; - for (std::string::size_type pos = cppname.size()-1; 0 < pos; --pos) { - std::string::value_type c = cppname[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '>') - --tpl_open; - else if (c == '<' && cppname[pos+1] != '<') - ++tpl_open; - - if (tpl_open == 0) - return cppname.substr(0, pos); - } - - return cppname; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::compound(const std::string& name) -{ -// FIXME: temporary fix for translation unit decl being passed in -// CreateExecutor from InitExecutor_ (run test10_object_identity), remove later - if (name.empty()) return ""; -// Break down the compound of a fully qualified type name. - std::string cleanName = remove_const(name); - auto idx = find_qualifier_index(cleanName); - - std::string cpd = cleanName.substr(idx, std::string::npos); - -// for easy identification of fixed size arrays - if (!cpd.empty() && cpd.back() == ']') { - if (cpd.front() == '[') - return "[]"; // fixed array any; dimensions handled separately - - std::ostringstream scpd; - scpd << cpd.substr(0, cpd.find('[')) << "[]"; - cpd = scpd.str(); - } - - // XXX: remove this hack - if (!cpd.empty() && cpd[0] == ' ') return cpd.substr(1, cpd.length() - 1); - - return cpd; -} - -//---------------------------------------------------------------------------- -void CPyCppyy::TypeManip::cppscope_to_pyscope(std::string& cppscope) -{ -// Change '::' in C++ scope into '.' as in a Python scope. - std::string::size_type pos = 0; - while ((pos = cppscope.find("::", pos)) != std::string::npos) { - cppscope.replace(pos, 2, "."); - pos += 1; - } -} - -//---------------------------------------------------------------------------- -void CPyCppyy::TypeManip::cppscope_to_legalname(std::string& cppscope) -{ -// Change characters illegal in a variable name into '_' to form a legal name. - for (char& c : cppscope) { - for (char needle : {':', '>', '<', ' ', ',', '&', '=', '*', '-'}) - if (c == needle) c = '_'; - } -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::TypeManip::extract_namespace(const std::string& name) -{ -// Find the namespace the named class lives in, take care of templates - if (name.empty()) - return name; - - int tpl_open = 0; - for (std::string::size_type pos = name.size()-1; 0 < pos; --pos) { - std::string::value_type c = name[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '>') - --tpl_open; - else if (c == '<' && name[pos+1] != '<') - ++tpl_open; - - // collect name up to "::" - else if (tpl_open == 0 && c == ':' && name[pos-1] == ':') { - // found the extend of the scope ... done - return name.substr(0, pos-1); - } - } - -// no namespace; assume outer scope - return ""; -} - -//---------------------------------------------------------------------------- -std::vector CPyCppyy::TypeManip::extract_arg_types(const std::string& sig) -{ -// break out the argument types from the signature string - std::vector result; - - if (sig.empty() || sig == "()") - return result; - - int tpl_open = 0; - std::string::size_type start = 1; - for (std::string::size_type pos = 1; pos < sig.size()-1; ++pos) { - std::string::value_type c = sig[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '>') - --tpl_open; - else if (c == '<' && sig[pos+1] != '<') - ++tpl_open; - - // collect type name up to ',' or end ')' - else if (tpl_open == 0 && c == ',') { - // found the extend of the scope ... done - result.push_back(sig.substr(start, pos-start)); - start = pos+1; - } - } - -// add last type - result.push_back(sig.substr(start, sig.rfind(")")-start)); - - return result; -} - -//---------------------------------------------------------------------------- -Py_ssize_t CPyCppyy::TypeManip::array_size(const std::string& name) -{ -// Extract the array size from a given type name (assumes 1D arrays) - std::string cleanName = remove_const(name); - if (cleanName[cleanName.size()-1] == ']') { - std::string::size_type idx = cleanName.rfind('['); - if (idx != std::string::npos) { - const std::string asize = cleanName.substr(idx+1, cleanName.size()-2); - return strtoul(asize.c_str(), nullptr, 0); - } - } - - return -1; -} diff --git a/src/CPyCppyy/src/TypeManip.h b/src/CPyCppyy/src/TypeManip.h deleted file mode 100644 index 4fb8bd0..0000000 --- a/src/CPyCppyy/src/TypeManip.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef CPYCPPYY_TYPEMANIP_H -#define CPYCPPYY_TYPEMANIP_H - -#include -#include - - -namespace CPyCppyy { - -namespace TypeManip { - - std::string remove_const(const std::string& cppname); - std::string clean_type(const std::string& cppname, - bool template_strip = true, bool const_strip = true); - std::string template_base(const std::string& cppname); - std::string compound(const std::string& name); - - void cppscope_to_pyscope(std::string& cppscope); - void cppscope_to_legalname(std::string& cppscope); - std::string extract_namespace(const std::string& name); - - std::vector extract_arg_types(const std::string& sig); - Py_ssize_t array_size(const std::string& name); - -} // namespace TypeManip - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_TYPEMANIP_H diff --git a/src/CPyCppyy/src/Utility.cxx b/src/CPyCppyy/src/Utility.cxx deleted file mode 100644 index bbe9d16..0000000 --- a/src/CPyCppyy/src/Utility.cxx +++ /dev/null @@ -1,1489 +0,0 @@ -// Bindings -#include "CPyCppyy.h" -#include "Utility.h" -#include "CPPFunction.h" -#include "CPPInstance.h" -#include "CPPOverload.h" -#include "CPyCppyy/DispatchPtr.h" -#include "ProxyWrappers.h" -#include "PyCallable.h" -#include "PyStrings.h" -#include "CustomPyTypes.h" -#include "TemplateProxy.h" -#include "TypeManip.h" - -// Standard -#include -#include -#include -#include -#include -#include -#include -#include - - -//- data _____________________________________________________________________ -#if PY_VERSION_HEX < 0x030b0000 -bool CPyCppyy::gDictLookupActive = false; -#endif - -typedef std::unordered_map TC2POperatorMapping_t; -static TC2POperatorMapping_t gC2POperatorMapping; -static std::set gOpSkip; -static std::set gOpRemove; - -namespace CPyCppyy { -// special objects - extern PyObject* gNullPtrObject; - extern PyObject* gDefaultObject; -} - -namespace { - - using namespace CPyCppyy::Utility; - - struct InitOperatorMapping_t { - public: - InitOperatorMapping_t() { - // Initialize the global map of operator names C++ -> python. - - gOpSkip.insert("[]"); // __s/getitem__, depends on return type - gOpSkip.insert("+"); // __add__, depends on # of args (see __pos__) - gOpSkip.insert("-"); // __sub__, id. (eq. __neg__) - gOpSkip.insert("*"); // __mul__ or __deref__ - gOpSkip.insert("++"); // __postinc__ or __preinc__ - gOpSkip.insert("--"); // __postdec__ or __predec__ - - gOpRemove.insert("new"); // this and the following not handled at all - gOpRemove.insert("new[]"); - gOpRemove.insert("delete"); - gOpRemove.insert("delete[]"); - - gC2POperatorMapping["[]"] = "__getitem__"; - gC2POperatorMapping["()"] = "__call__"; - gC2POperatorMapping["%"] = "__mod__"; - gC2POperatorMapping["**"] = "__pow__"; - gC2POperatorMapping["<<"] = "__lshift__"; - gC2POperatorMapping[">>"] = "__rshift__"; - gC2POperatorMapping["&"] = "__and__"; - gC2POperatorMapping["&&"] = "__dand__"; - gC2POperatorMapping["|"] = "__or__"; - gC2POperatorMapping["||"] = "__dor__"; - gC2POperatorMapping["^"] = "__xor__"; - gC2POperatorMapping["~"] = "__invert__"; - gC2POperatorMapping[","] = "__comma__"; - gC2POperatorMapping["+="] = "__iadd__"; - gC2POperatorMapping["-="] = "__isub__"; - gC2POperatorMapping["*="] = "__imul__"; - gC2POperatorMapping["/="] = CPPYY__idiv__; - gC2POperatorMapping["%="] = "__imod__"; - gC2POperatorMapping["**="] = "__ipow__"; - gC2POperatorMapping["<<="] = "__ilshift__"; - gC2POperatorMapping[">>="] = "__irshift__"; - gC2POperatorMapping["&="] = "__iand__"; - gC2POperatorMapping["|="] = "__ior__"; - gC2POperatorMapping["^="] = "__ixor__"; - gC2POperatorMapping["=="] = "__eq__"; - gC2POperatorMapping["!="] = "__ne__"; - gC2POperatorMapping[">"] = "__gt__"; - gC2POperatorMapping["<"] = "__lt__"; - gC2POperatorMapping[">="] = "__ge__"; - gC2POperatorMapping["<="] = "__le__"; - - // the following type mappings are "exact" - gC2POperatorMapping["const char*"] = "__str__"; - gC2POperatorMapping["char*"] = "__str__"; - gC2POperatorMapping["const char *"] = gC2POperatorMapping["const char*"]; - gC2POperatorMapping["char *"] = gC2POperatorMapping["char*"]; - gC2POperatorMapping["int"] = "__int__"; - gC2POperatorMapping["long"] = CPPYY__long__; - gC2POperatorMapping["double"] = "__float__"; - - // the following type mappings are "okay"; the assumption is that they - // are not mixed up with the ones above or between themselves (and if - // they are, that it is done consistently) - gC2POperatorMapping["short"] = "__int__"; - gC2POperatorMapping["unsigned short"] = "__int__"; - gC2POperatorMapping["unsigned int"] = CPPYY__long__; - gC2POperatorMapping["unsigned long"] = CPPYY__long__; - gC2POperatorMapping["long long"] = CPPYY__long__; - gC2POperatorMapping["unsigned long long"] = CPPYY__long__; - gC2POperatorMapping["float"] = "__float__"; - - gC2POperatorMapping["->"] = "__follow__"; // not an actual python operator - gC2POperatorMapping["="] = "__assign__"; // id. - - gC2POperatorMapping["bool"] = "__cpp_bool__"; - } - } initOperatorMapping_; - - inline std::string full_scope(const std::string& tpname) { - return tpname[0] == ':' ? tpname : "::"+tpname; - } - -} // unnamed namespace - - -//- public functions --------------------------------------------------------- -unsigned long CPyCppyy::PyLongOrInt_AsULong(PyObject* pyobject) -{ -// Convert to C++ unsigned long, with bounds checking, allow int -> ulong. - if (PyFloat_Check(pyobject)) { - PyErr_SetString(PyExc_TypeError, "can\'t convert float to unsigned long"); - return (unsigned long)-1; - } else if (pyobject == CPyCppyy::gDefaultObject) { - return (unsigned long)0; - } - - unsigned long ul = PyLong_AsUnsignedLong(pyobject); - if (ul == (unsigned long)-1 && PyErr_Occurred() && PyInt_Check(pyobject)) { - PyErr_Clear(); - long i = PyInt_AS_LONG(pyobject); - if (0 <= i) { - ul = (unsigned long)i; - } else { - PyErr_SetString(PyExc_ValueError, - "can\'t convert negative value to unsigned long"); - return (unsigned long)-1; - } - } - - return ul; -} - -//---------------------------------------------------------------------------- -PY_ULONG_LONG CPyCppyy::PyLongOrInt_AsULong64(PyObject* pyobject) -{ -// Convert to C++ unsigned long long, with bounds checking. - if (PyFloat_Check(pyobject)) { - PyErr_SetString(PyExc_TypeError, "can\'t convert float to unsigned long long"); - return -1; - } else if (pyobject == CPyCppyy::gDefaultObject) { - return (unsigned long)0; - } - - PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(pyobject); - if (PyErr_Occurred() && PyInt_Check(pyobject)) { - PyErr_Clear(); - long i = PyInt_AS_LONG(pyobject); - if (0 <= i) { - ull = (PY_ULONG_LONG)i; - } else { - PyErr_SetString(PyExc_ValueError, - "can\'t convert negative value to unsigned long long"); - } - } - - return ull; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Utility::AddToClass( - PyObject* pyclass, const char* label, PyCFunction cfunc, int flags) -{ -// Add the given function to the class under name 'label'. - -// use list for clean-up (.so's are unloaded only at interpreter shutdown) - static std::list s_pymeths; - - s_pymeths.push_back(PyMethodDef()); - PyMethodDef* pdef = &s_pymeths.back(); - pdef->ml_name = const_cast(label); - pdef->ml_meth = cfunc; - pdef->ml_flags = flags; - pdef->ml_doc = nullptr; - - PyObject* func = PyCFunction_New(pdef, nullptr); - PyObject* name = CPyCppyy_PyText_InternFromString(pdef->ml_name); - PyObject* method = CustomInstanceMethod_New(func, nullptr, pyclass); - PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); - bool isOk = PyType_Type.tp_setattro(pyclass, name, method) == 0; - PyErr_Restore(pytype, pyvalue, pytrace); - Py_DECREF(method); - Py_DECREF(name); - Py_DECREF(func); - - if (PyErr_Occurred()) - return false; - - if (!isOk) { - PyErr_Format(PyExc_TypeError, "could not add method %s", label); - return false; - } - - return true; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Utility::AddToClass(PyObject* pyclass, const char* label, const char* func) -{ -// Add the given function to the class under name 'label'. - PyObject* pyfunc = PyObject_GetAttrString(pyclass, const_cast(func)); - if (!pyfunc) - return false; - - PyObject* pylabel = CPyCppyy_PyText_InternFromString(const_cast(label)); - bool isOk = PyType_Type.tp_setattro(pyclass, pylabel, pyfunc) == 0; - Py_DECREF(pylabel); - - Py_DECREF(pyfunc); - return isOk; -} - -//---------------------------------------------------------------------------- -bool CPyCppyy::Utility::AddToClass(PyObject* pyclass, const char* label, PyCallable* pyfunc) -{ -// Add the given function to the class under name 'label'. - CPPOverload* method = - (CPPOverload*)PyObject_GetAttrString(pyclass, const_cast(label)); - - if (!method || !CPPOverload_Check(method)) { - // not adding to existing CPPOverload; add callable directly to the class - if (PyErr_Occurred()) - PyErr_Clear(); - Py_XDECREF((PyObject*)method); - method = CPPOverload_New(label, pyfunc); - PyObject* pylabel = CPyCppyy_PyText_InternFromString(const_cast(label)); - bool isOk = PyType_Type.tp_setattro(pyclass, pylabel, (PyObject*)method) == 0; - Py_DECREF(pylabel); - Py_DECREF(method); - return isOk; - } - - method->AdoptMethod(pyfunc); - - Py_DECREF(method); - return true; -} - - -//---------------------------------------------------------------------------- -static inline -CPyCppyy::PyCallable* BuildOperator(const std::string& lcname, const std::string& rcname, - const char* op, Cppyy::TCppScope_t scope, bool reverse=false) -{ -// Helper to find a function with matching signature in 'funcs'. - - Cppyy::TCppMethod_t meth = Cppyy::GetGlobalOperator(scope, lcname, rcname, op); - if (!meth) - return nullptr; - - if (!reverse) - return new CPyCppyy::CPPFunction(scope, meth); - return new CPyCppyy::CPPReverseBinary(scope, meth); -} - -//---------------------------------------------------------------------------- -CPyCppyy::PyCallable* CPyCppyy::Utility::FindUnaryOperator(PyObject* pyclass, const char* op) -{ -// Find a callable matching named operator (op) and klass arguments in the global -// namespace or the klass' namespace. - - if (!CPPScope_Check(pyclass)) - return nullptr; - - CPPClass* klass = (CPPClass*)pyclass; - const std::string& lcname = Cppyy::GetScopedFinalName(klass->fCppType); - Cppyy::TCppScope_t scope = Cppyy::GetScope(TypeManip::extract_namespace(lcname)); - return FindBinaryOperator(lcname, "", op, scope, false); -} - -//---------------------------------------------------------------------------- -CPyCppyy::PyCallable* CPyCppyy::Utility::FindBinaryOperator(PyObject* left, PyObject* right, - const char* op, Cppyy::TCppScope_t scope) -{ -// Find a callable matching the named operator (op) and the (left, right) -// arguments in the global or these objects' namespaces. - - bool reverse = false; - if (!CPPInstance_Check(left)) { - if (CPPInstance_Check(right)) - reverse = true; - else - return nullptr; - } - -// retrieve the class names to match the signature of any found global functions - const std::string& lcname = ClassName(left); - const std::string& rcname = ClassName(right); - return FindBinaryOperator(lcname, rcname, op, scope, reverse); -} - -//---------------------------------------------------------------------------- -CPyCppyy::PyCallable* CPyCppyy::Utility::FindBinaryOperator( - const std::string& lcname, const std::string& rcname, - const char* op, Cppyy::TCppScope_t scope, bool reverse) -{ -// Find a global function with a matching signature; search __gnu_cxx, std::__1, -// and __cppyy_internal pro-actively (as there's AFAICS no way to unearth 'using' -// information). - - if (rcname == "" || lcname == "") - return nullptr; - - PyCallable* pyfunc = 0; - - if (!scope) { - // TODO: the following should remain sync with what clingwrapper does in its - // type remapper; there must be a better way? - if (lcname == "str" || lcname == "unicode" || lcname == "complex" || lcname.find("std::") == 0) - scope = Cppyy::GetScope("std"); - } - if (scope) - pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); - if (!pyfunc) - if ((scope = Cppyy::GetScope(TypeManip::extract_namespace(lcname)))) - pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); - - if (!pyfunc && scope != Cppyy::GetGlobalScope())// search in global scope anyway - pyfunc = BuildOperator(lcname, rcname, op, Cppyy::GetGlobalScope(), reverse); - - if (!pyfunc) { - // For GNU on clang, search the internal __gnu_cxx namespace for binary operators (is - // typically the case for STL iterators operator==/!=. - // TODO: only look in __gnu_cxx for iterators (and more generally: do lookups in the - // namespace where the class is defined - static Cppyy::TCppScope_t gnucxx = Cppyy::GetScope("__gnu_cxx"); - if (gnucxx) - pyfunc = BuildOperator(lcname, rcname, op, gnucxx, reverse); - } - - if (!pyfunc) { - // Same for clang (on Mac only?). TODO: find proper pre-processor magic to only use those - // specific namespaces that are actually around; although to be sure, this isn't expensive. - static Cppyy::TCppScope_t std__1 = Cppyy::GetFullScope("std::__1"); - - if (std__1 -#ifdef __APPLE__ - && lcname.find("__wrap_iter") == std::string::npos // wrapper call does not compile -#endif - ) { - pyfunc = BuildOperator(lcname, rcname, op, std__1, reverse); - } - } - - if (!pyfunc) { - // One more, mostly for Mac, but again not sure whether this is not a general issue. Some - // operators are declared as friends only in classes, so then they're not found in the - // global namespace, so this helper let's the compiler resolve the operator. - static Cppyy::TCppScope_t s_intern = Cppyy::GetScope("__cppyy_internal"); - if (s_intern) { - std::stringstream fname, proto; - if (strncmp(op, "==", 2) == 0) { fname << "is_equal<"; } - else if (strncmp(op, "!=", 2) == 0) { fname << "is_not_equal<"; } - else { fname << "not_implemented<"; } - fname << lcname << ", " << rcname << ">"; - proto << "const " << lcname << "&, const " << rcname; - Cppyy::TCppMethod_t method = Cppyy::GetMethodTemplate(s_intern, fname.str(), proto.str()); - if (method) pyfunc = new CPPFunction(s_intern, method); - } - } - - return pyfunc; -} - -//---------------------------------------------------------------------------- -static inline std::string AnnotationAsText(PyObject* pyobj) -{ - if (!CPyCppyy_PyText_Check(pyobj)) { - PyObject* pystr = PyObject_GetAttr(pyobj, CPyCppyy::PyStrings::gName); - if (!pystr) { - PyErr_Clear(); - pystr = PyObject_Str(pyobj); - } - - std::string str = CPyCppyy_PyText_AsString(pystr); - Py_DECREF(pystr); - return str; - } - return CPyCppyy_PyText_AsString(pyobj); -} - -static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg, - CPyCppyy::Utility::ArgPreference pref, int* pcnt = nullptr) -{ -// Determine the appropriate C++ type for a given Python type; this is a helper because -// it can recurse if the type is list or tuple and needs matching on std::vector. - using namespace CPyCppyy; - using namespace CPyCppyy::Utility; - - if (tn == (PyObject*)&PyInt_Type) { - if (arg) { - PY_LONG_LONG ll = PyLong_AsLongLong(arg); - if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { - PyErr_Clear(); - PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); - if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { - PyErr_Clear(); - tmpl_name.append("int"); // still out of range, will fail later - } else - tmpl_name.append("unsigned long long"); // since already failed long long - } else - tmpl_name.append((ll < INT_MIN || INT_MAX < ll) ? \ - ((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long") : "int"); - } else - tmpl_name.append("int"); - - return true; - } - - if (tn == (PyObject*)&PyFloat_Type) { - // special case for floats (Python-speak for double) if from argument (only) - tmpl_name.append(arg ? "double" : "float"); - return true; - } - - if (tn == (PyObject*)&PyUnicode_Type) { - tmpl_name.append("std::string"); - return true; - } - - if (tn == (PyObject*)&PyList_Type || tn == (PyObject*)&PyTuple_Type) { - if (arg && PySequence_Size(arg)) { - std::string subtype{"std::initializer_list<"}; - PyObject* item = PySequence_GetItem(arg, 0); - ArgPreference subpref = pref == kValue ? kValue : kPointer; - bool ret = AddTypeName(subtype, (PyObject*)Py_TYPE(item), item, subpref); - if (ret) { - tmpl_name.append(subtype); - tmpl_name.append(">"); - } - Py_DECREF(item); - // Error occurred in inner call to AddTypeName, which means it has - // also set a TypeError. We return and let the error propagate. - if (!ret) { - return false; - } - } - - return true; - } - - if (CPPScope_Check(tn)) { - auto cpp_type = Cppyy::GetScopedFinalName(((CPPClass*)tn)->fCppType); - tmpl_name.append(full_scope(cpp_type)); - if (arg) { - // try to specialize the type match for the given object - CPPInstance* pyobj = (CPPInstance*)arg; - if (CPPInstance_Check(pyobj)) { - if (pyobj->fFlags & CPPInstance::kIsRValue) - tmpl_name.append("&&"); - else { - if (pcnt) *pcnt += 1; - if ((pyobj->fFlags & CPPInstance::kIsReference) || pref == kPointer) - tmpl_name.push_back('*'); - else if (pref != kValue) - tmpl_name.push_back('&'); - } - } - } - - return true; - } - - if (tn == (PyObject*)&CPPOverload_Type) { - PyObject* tpName = arg ? \ - PyObject_GetAttr(arg, PyStrings::gCppName) : \ - CPyCppyy_PyText_FromString("void* (*)(...)"); - tmpl_name.append(CPyCppyy_PyText_AsString(tpName)); - Py_DECREF(tpName); - - return true; - } - - if (arg && PyCallable_Check(arg)) { - // annotated/typed Python function - PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations); - if (annot) { - if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) { - PyObject* ret = PyDict_GetItemString(annot, "return"); - if (ret) { - // dict is ordered, with the last value being the return type - std::ostringstream tpn; - tpn << (CPPScope_Check(ret) ? ClassName(ret) : AnnotationAsText(ret)) - << " (*)("; - - PyObject* values = PyDict_Values(annot); - for (Py_ssize_t i = 0; i < (PyList_GET_SIZE(values)-1); ++i) { - if (i) tpn << ", "; - PyObject* item = PyList_GET_ITEM(values, i); - tpn << (CPPScope_Check(item) ? full_scope(ClassName(item)) : AnnotationAsText(item)); - } - Py_DECREF(values); - - tpn << ')'; - tmpl_name.append(tpn.str()); - - Py_DECREF(annot); - return true; - - } else - PyErr_Clear(); - } - Py_DECREF(annot); - } else - PyErr_Clear(); - - // ctypes function pointer - PyObject* argtypes = nullptr; - PyObject* ret = nullptr; - if ((argtypes = PyObject_GetAttrString(arg, "argtypes")) - && (ret = PyObject_GetAttrString(arg, "restype"))) { - std::ostringstream tpn; - PyObject* pytc = PyObject_GetAttr(ret, PyStrings::gCTypesType); - tpn << CT2CppNameS(pytc, false) - << " (*)("; - Py_DECREF(pytc); - - for (Py_ssize_t i = 0; i < PySequence_Length(argtypes); ++i) { - if (i) tpn << ", "; - PyObject* item = PySequence_GetItem(argtypes, i); - pytc = PyObject_GetAttr(item, PyStrings::gCTypesType); - tpn << CT2CppNameS(pytc, false); - Py_DECREF(pytc); - Py_DECREF(item); - } - - tpn << ')'; - tmpl_name.append(tpn.str()); - - Py_DECREF(ret); - Py_DECREF(argtypes); - - return true; - - } else { - PyErr_Clear(); - Py_XDECREF(ret); - Py_XDECREF(argtypes); - } - - // callable C++ type (e.g. std::function) - PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName); - if (tpName) { - const char* cname = CPyCppyy_PyText_AsString(tpName); - tmpl_name.append(CPPScope_Check(arg) ? full_scope(cname) : cname); - Py_DECREF(tpName); - return true; - } - PyErr_Clear(); - } - - for (auto nn : {PyStrings::gCppName, PyStrings::gName}) { - PyObject* tpName = PyObject_GetAttr(tn, nn); - if (tpName) { - tmpl_name.append(CPyCppyy_PyText_AsString(tpName)); - Py_DECREF(tpName); - return true; - } - PyErr_Clear(); - } - - if (PyInt_Check(tn) || PyLong_Check(tn) || PyFloat_Check(tn)) { - // last ditch attempt, works for things like int values; since this is a - // source of errors otherwise, it is limited to specific types and not - // generally used (str(obj) can print anything ...) - PyObject* pystr = PyObject_Str(tn); - tmpl_name.append(CPyCppyy_PyText_AsString(pystr)); - Py_DECREF(pystr); - return true; - } - -// Give up with a TypeError - -// Try to get a readable representation of the argument - PyObject *repr = PyObject_Repr(tn); - const char *repr_cstr = repr ? PyUnicode_AsUTF8(repr) : ""; - - PyErr_Format(PyExc_TypeError, "could not construct C++ name from template argument %s", repr_cstr); - - Py_XDECREF(repr); - return false; -} - -std::string CPyCppyy::Utility::ConstructTemplateArgs( - PyObject* pyname, PyObject* tpArgs, PyObject* args, ArgPreference pref, int argoff, int* pcnt) -{ -// Helper to construct the "" part of a templated name (either -// for a class or method lookup - bool justOne = !PyTuple_CheckExact(tpArgs); - -// Note: directly appending to string is a lot faster than stringstream - std::string tmpl_name; - tmpl_name.reserve(128); - if (pyname) - tmpl_name.append(CPyCppyy_PyText_AsString(pyname)); - tmpl_name.push_back('<'); - - if (pcnt) *pcnt = 0; // count number of times 'pref' is used - - Py_ssize_t nArgs = justOne ? 1 : PyTuple_GET_SIZE(tpArgs); - for (int i = argoff; i < nArgs; ++i) { - // add type as string to name - PyObject* tn = justOne ? tpArgs : PyTuple_GET_ITEM(tpArgs, i); - if (CPyCppyy_PyText_Check(tn)) { - tmpl_name.append(CPyCppyy_PyText_AsString(tn)); - // some common numeric types (separated out for performance: checking for - // __cpp_name__ and/or __name__ is rather expensive) - } else { - if (!AddTypeName(tmpl_name, tn, (args ? PyTuple_GET_ITEM(args, i) : nullptr), pref, pcnt)) { - PyErr_SetString(PyExc_SyntaxError, - "could not construct C++ name from provided template argument."); - return ""; - } - } - - // add a comma, as needed (no space as internally, final names don't have them) - if (i != nArgs-1) - tmpl_name.push_back(','); - } - -// close template name - tmpl_name.push_back('>'); - - return tmpl_name; -} - -//---------------------------------------------------------------------------- -static bool AddTypeName(std::vector& types, PyObject* tn, - PyObject* arg, CPyCppyy::Utility::ArgPreference pref, int* pcnt = nullptr) -{ -// Determine the appropriate C++ type for a given Python type; this is a helper because -// it can recurse if the type is list or tuple and needs matching on std::vector. - using namespace CPyCppyy; - using namespace CPyCppyy::Utility; - - if (tn == (PyObject*)&PyInt_Type) { - if (arg) { - PY_LONG_LONG ll = PyLong_AsLongLong(arg); - if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { - PyErr_Clear(); - PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); - if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { - PyErr_Clear(); - types.push_back(Cppyy::GetType("int").data); // still out of range, will fail later - } else - types.push_back(Cppyy::GetType("unsigned long long").data); // since already failed long long - } else - types.push_back(Cppyy::GetType((ll < INT_MIN || INT_MAX < ll) ? \ - ((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long") : "int").data); - } else { - types.push_back(Cppyy::GetType("int").data); - } - - return true; - } - - if (tn == (PyObject*)&PyFloat_Type) { - // special case for floats (Python-speak for double) if from argument (only) - types.push_back(Cppyy::GetType(arg ? "double" : "float").data); - return true; - } - - if (tn == (PyObject*)&PyUnicode_Type) { - types.push_back(Cppyy::GetType("std::string", /* enable_slow_lookup */ true).data); - return true; - } - - if (tn == (PyObject*)&PyList_Type || tn == (PyObject*)&PyTuple_Type) { - if (arg && PySequence_Size(arg)) { - std::string subtype{"std::initializer_list<"}; - PyObject* item = PySequence_GetItem(arg, 0); - ArgPreference subpref = pref == kValue ? kValue : kPointer; - if (AddTypeName(subtype, (PyObject*)Py_TYPE(item), item, subpref)) { - subtype.append(">"); - types.push_back(Cppyy::GetType(subtype).data); - } - Py_DECREF(item); - } - - return true; - } - - if (CPPScope_Check(tn)) { - auto cpp_type = Cppyy::GetTypeFromScope(((CPPClass*)tn)->fCppType); - if (arg) { - // try to specialize the type match for the given object - CPPInstance* pyobj = (CPPInstance*)arg; - if (CPPInstance_Check(pyobj)) { - if (pyobj->fFlags & CPPInstance::kIsRValue) - cpp_type = - Cppyy::GetReferencedType(cpp_type, /*rvalue=*/true); - else { - if (pcnt) *pcnt += 1; - if ((pyobj->fFlags & CPPInstance::kIsReference) || pref == kPointer) - cpp_type = Cppyy::GetPointerType(cpp_type); - else if (pref != kValue) - cpp_type = - Cppyy::GetReferencedType(cpp_type, /*rvalue=*/false); - } - } - } - types.push_back(cpp_type.data); - return true; - } - - if (tn == (PyObject*)&CPPOverload_Type) { - PyObject* tpName = arg ? \ - PyObject_GetAttr(arg, PyStrings::gCppName) : \ - CPyCppyy_PyText_FromString("void* (*)(...)"); - types.push_back(Cppyy::GetType(CPyCppyy_PyText_AsString(tpName), /* enable_slow_lookup */ true).data); - Py_DECREF(tpName); - - return true; - } - - if (arg && PyCallable_Check(arg)) { - PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations); - if (annot) { - if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) { - PyObject* ret = PyDict_GetItemString(annot, "return"); - if (ret) { - // dict is ordered, with the last value being the return type - std::ostringstream tpn; - tpn << (CPPScope_Check(ret) ? ClassName(ret) : CPyCppyy_PyText_AsString(ret)) - << " (*)("; - - PyObject* values = PyDict_Values(annot); - for (Py_ssize_t i = 0; i < (PyList_GET_SIZE(values)-1); ++i) { - if (i) tpn << ", "; - PyObject* item = PyList_GET_ITEM(values, i); - tpn << (CPPScope_Check(item) ? ClassName(item) : CPyCppyy_PyText_AsString(item)); - } - Py_DECREF(values); - - tpn << ')'; - // tmpl_name.append(tpn.str()); - // FIXME: find a way to add it to types - throw std::runtime_error( - "This path is not yet implemented (AddTypeName) \n"); - - return true; - - } else - PyErr_Clear(); - } - Py_DECREF(annot); - } else - PyErr_Clear(); - - PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName); - if (tpName) { - types.push_back(Cppyy::GetType(CPyCppyy_PyText_AsString(tpName), /* enable_slow_lookup */ true).data); - Py_DECREF(tpName); - return true; - } - PyErr_Clear(); - } - - for (auto nn : {PyStrings::gCppName, PyStrings::gName}) { - PyObject* tpName = PyObject_GetAttr(tn, nn); - if (tpName) { - Cppyy::TCppType_t type = Cppyy::GetType(CPyCppyy_PyText_AsString(tpName), /* enable_slow_lookup */ true); - if (Cppyy::IsEnumType(type)) { - PyObject *value_int = PyNumber_Index(tn); - if (!value_int) { - types.push_back(type.data); - PyErr_Clear(); - } else { - PyObject* pystr = PyObject_Str(tn); - std::string num = CPyCppyy_PyText_AsString(pystr); - types.push_back({type.data, strdup(num.c_str())}); - Py_DECREF(pystr); - Py_DECREF(value_int); - } - } else { - types.push_back(type.data); - } - Py_DECREF(tpName); - return true; - } - PyErr_Clear(); - } - - if (PyInt_Check(tn) || PyLong_Check(tn) || PyFloat_Check(tn)) { - // last ditch attempt, works for things like int values; since this is a - // source of errors otherwise, it is limited to specific types and not - // generally used (str(obj) can print anything ...) - PyObject* pystr = PyObject_Str(tn); - std::string num = CPyCppyy_PyText_AsString(pystr); - if (num == "True") - num = "1"; - else if (num == "False") - num = "0"; - types.push_back({Cppyy::GetType("int").data, strdup(num.c_str())}); - Py_DECREF(pystr); - return true; - } - - return false; -} - -std::vector CPyCppyy::Utility::GetTemplateArgsTypes( - PyObject* /*scope*/, PyObject* tpArgs, PyObject* args, ArgPreference pref, int argoff, int* pcnt) -{ -// Helper to construct the "" part of a templated name (either -// for a class or method lookup - bool justOne = !PyTuple_CheckExact(tpArgs); - -// Note: directly appending to string is a lot faster than stringstream - std::vector types; - types.reserve(8); - - if (pcnt) *pcnt = 0; // count number of times 'pref' is used - - Py_ssize_t nArgs = justOne ? 1 : PyTuple_GET_SIZE(tpArgs); - for (int i = argoff; i < nArgs; ++i) { - // add type as string to name - PyObject* tn = justOne ? tpArgs : PyTuple_GET_ITEM(tpArgs, i); - if (CPyCppyy_PyText_Check(tn)) { - const char * tn_string = CPyCppyy_PyText_AsString(tn); - - if (Cppyy::AppendTypesSlow(tn_string, types)) { - PyErr_Format(PyExc_TypeError, - "Cannot find Templated Arg: %s", tn_string); - return {}; - } - - // some commmon numeric types (separated out for performance: checking for - // __cpp_name__ and/or __name__ is rather expensive) - } else { - if (!AddTypeName(types, tn, (args ? PyTuple_GET_ITEM(args, i) : nullptr), pref, pcnt)) { - PyErr_SetString(PyExc_TypeError, - "could not construct C++ name from provided template argument."); - return {}; - } - } - } - - return types; -} - -std::string CPyCppyy::Utility::CT2CppNameS(PyObject* pytc, bool allow_voidp) -{ -// helper to convert ctypes' `_type_` info to the equivalent C++ name - const char* name = ""; - if (CPyCppyy_PyText_Check(pytc)) { - char tc = ((char*)CPyCppyy_PyText_AsString(pytc))[0]; - switch (tc) { - case '?': name = "bool"; break; - case 'c': name = "char"; break; - case 'b': name = "char"; break; - case 'B': name = "unsigned char"; break; - case 'h': name = "short"; break; - case 'H': name = "unsigned short"; break; - case 'i': name = "int"; break; - case 'I': name = "unsigned int"; break; - case 'l': name = "long"; break; - case 'L': name = "unsigned long"; break; - case 'q': name = "long long"; break; - case 'Q': name = "unsigned long long"; break; - case 'f': name = "float"; break; - case 'd': name = "double"; break; - case 'g': name = "long double"; break; - case 'z': name = "const char*"; break; - default: if (allow_voidp) name = "void*"; break; - } - } - - return name; -} - -//---------------------------------------------------------------------------- -static inline bool check_scope(const std::string& name) -{ - return (bool)Cppyy::GetScope(CPyCppyy::TypeManip::clean_type(name, false)); -} - -void CPyCppyy::Utility::ConstructCallbackPreamble(const std::string& retType, - const std::vector& argtypes, std::ostringstream& code) -{ -// Generate function setup to be used in callbacks (wrappers and overrides). - int nArgs = (int)argtypes.size(); - -// return value and argument type converters - bool isVoid = retType.find("void") == 0; // might contain trailing space - if (!isVoid) - code << " CPYCPPYY_STATIC std::unique_ptr> " - "retconv{CPyCppyy::CreateConverter(\"" - << retType << "\"), CPyCppyy::DestroyConverter};\n"; - std::vector arg_is_ptr; - if (nArgs) { - arg_is_ptr.resize(nArgs); - code << " CPYCPPYY_STATIC std::vector>> argcvs;\n" - << " if (argcvs.empty()) {\n" - << " argcvs.reserve(" << nArgs << ");\n"; - for (int i = 0; i < nArgs; ++i) { - arg_is_ptr[i] = false; - code << " argcvs.emplace_back(CPyCppyy::CreateConverter(\""; - const std::string& at = argtypes[i]; - const std::string& res_at = Cppyy::ResolveName(at); - const std::string& cpd = TypeManip::compound(res_at); - if (!cpd.empty() && check_scope(res_at)) { - // in case of a pointer, the original argument needs to be used to ensure - // the pointer-value remains comparable - // - // in case of a reference, there is no extra indirection on the C++ side as - // would be when converting a data member, so adjust the converter - arg_is_ptr[i] = cpd.back() == '*'; - if (arg_is_ptr[i] || cpd.back() == '&') { - code << res_at.substr(0, res_at.size()-1); - } else code << at; - } else - code << at; - code << "\"), CPyCppyy::DestroyConverter);\n"; - } - code << " }\n"; - } - -// declare return value (TODO: this does not work for most non-builtin values) - if (!isVoid) - code << " " << retType << " ret{};\n"; - -// build argument tuple if needed - if (nArgs) { - code << " std::vector pyargs;\n"; - code << " pyargs.reserve(" << nArgs << ");\n" - << " try {\n"; - for (int i = 0; i < nArgs; ++i) { - code << " pyargs.emplace_back(argcvs[" << i << "]->FromMemory((void*)"; - if (!arg_is_ptr[i]) code << '&'; - code << "arg" << i << "));\n" - << " if (!pyargs.back()) throw " << i << ";\n"; - } - code << " } catch(int) {\n" - << " for (auto pyarg : pyargs) Py_XDECREF(pyarg);\n" - << " CPyCppyy::PyException pyexc; throw pyexc;\n" - << " }\n"; - } -} - -void CPyCppyy::Utility::ConstructCallbackReturn(const std::string& retType, int nArgs, std::ostringstream& code) -{ -// Generate code for return value conversion and error handling. - bool isVoid = retType.find("void") == 0; // might contain trailing space - bool isPtr = Cppyy::ResolveName(retType).back() == '*'; - - if (nArgs) - code << " for (auto pyarg : pyargs) Py_DECREF(pyarg);\n"; - code << " bool cOk = (bool)pyresult;\n" - " if (pyresult) {\n"; - if (isPtr) { - // If the return type is a CPPInstance, owned by Python, and the ref-count down - // to 1, the return will hold a dangling pointer, so set it to nullptr instead. - code << " if (!CPyCppyy::Instance_IsLively(pyresult))\n" - " ret = nullptr;\n" - " else {\n"; - } - code << (isVoid ? "" : " cOk = retconv->ToMemory(pyresult, (void*)&ret);\n") - << " Py_DECREF(pyresult);\n }\n"; - if (isPtr) code << " }\n"; - code << " if (!cOk) {" // assume error set when converter failed -// TODO: On Windows, throwing a C++ exception here makes the code hang; leave -// the error be which allows at least one layer of propagation -#ifdef _WIN32 - " /* do nothing */ }\n" -#else - " CPyCppyy::PyException pyexc; throw pyexc; }\n" -#endif - " return"; - code << (isVoid ? ";\n }\n" : " ret;\n }\n"); -} - - -//---------------------------------------------------------------------------- -static std::unordered_map sStdFuncLookup; -static std::unordered_map sStdFuncMakerLookup; -PyObject* CPyCppyy::Utility::FuncPtr2StdFunction( - const std::string& retType, const std::string& signature, void* address) -{ -// Convert a function pointer to an equivalent std::function<> object. - static int maker_count = 0; - - auto pf = sStdFuncLookup.find(address); - if (pf != sStdFuncLookup.end()) { - Py_INCREF(pf->second); - return pf->second; - } - - PyObject* maker = nullptr; - - auto pm = sStdFuncMakerLookup.find(retType+signature); - if (pm == sStdFuncMakerLookup.end()) { - std::ostringstream fname; - fname << "ptr2func" << ++maker_count; - - std::ostringstream code; - code << "namespace __cppyy_internal { std::function<" - << retType << signature << "> " << fname.str() - << "(intptr_t faddr) { return (" << retType << "(*)" << signature << ")faddr;} }"; - - if (!Cppyy::Compile(code.str())) { - PyErr_SetString(PyExc_TypeError, "conversion to std::function failed"); - return nullptr; - } - - PyObject* pyscope = CreateScopeProxy("__cppyy_internal"); - maker = PyObject_GetAttrString(pyscope, fname.str().c_str()); - Py_DECREF(pyscope); - if (!maker) - return nullptr; - - // cache the new maker (TODO: does it make sense to use weakrefs?) - sStdFuncMakerLookup[retType+signature] = maker; - } else - maker = pm->second; - - PyObject* args = PyTuple_New(1); - PyTuple_SET_ITEM(args, 0, PyLong_FromLongLong((intptr_t)address)); - PyObject* func = PyObject_Call(maker, args, NULL); - Py_DECREF(args); - - if (func) { // prevent moving this func object, since then it can not be reused - ((CPPInstance*)func)->fFlags |= CPPInstance::kIsLValue; - Py_INCREF(func); // TODO: use weak? The C++ maker doesn't go away either - sStdFuncLookup[address] = func; - } - - return func; -} - - -//---------------------------------------------------------------------------- -bool CPyCppyy::Utility::InitProxy(PyObject* module, PyTypeObject* pytype, const char* name) -{ -// Initialize a proxy class for use by python, and add it to the module. - -// finalize proxy type - if (PyType_Ready(pytype) < 0) - return false; - -// add proxy type to the given module - Py_INCREF(pytype); // PyModule_AddObject steals reference - if (PyModule_AddObject(module, (char*)name, (PyObject*)pytype) < 0) { - Py_DECREF(pytype); - return false; - } - -// declare success - return true; -} - -//---------------------------------------------------------------------------- -std::unordered_map const &CPyCppyy::Utility::TypecodeMap() -{ - // See https://docs.python.org/3/library/array.html#array.array - static std::unordered_map typecodeMap{ - {"char", 'b'}, - {"unsigned char", 'B'}, -#if PY_VERSION_HEX >= 0x030d0000 - {"Py_UCS4", 'w'}, -#endif - {"short", 'h'}, - {"unsigned short", 'H'}, - {"int", 'i'}, - {"unsigned int", 'I'}, - {"long", 'l'}, - {"unsigned long", 'L'}, - {"long long", 'q'}, - {"unsigned long long", 'Q'}, - {"float", 'f'}, - {"double", 'd'} - }; - return typecodeMap; -} - -//---------------------------------------------------------------------------- -Py_ssize_t CPyCppyy::Utility::GetBuffer(PyObject* pyobject, char tc, int size, void*& buf, bool check) -{ -// Retrieve a linear buffer pointer from the given pyobject. - -// special case: don't handle character strings here (yes, they're buffers, but not quite) - if (PyBytes_Check(pyobject) || PyUnicode_Check(pyobject)) - return 0; - -// special case: bytes array - if ((!check || tc == '*' || tc == 'B') && PyByteArray_CheckExact(pyobject)) { - buf = PyByteArray_AS_STRING(pyobject); - return PyByteArray_GET_SIZE(pyobject); - } - -// new-style buffer interface - if (PyObject_CheckBuffer(pyobject)) { - if (PySequence_Check(pyobject) && !PySequence_Size(pyobject)) - return 0; // PyObject_GetBuffer() crashes on some platforms for some zero-sized seqeunces - if (PyErr_Occurred()) { - // PySequence_Size errored with - // TypeError: object of type 'LP_c_type' has no len() - PyErr_Clear(); - } - - Py_buffer bufinfo; - memset(&bufinfo, 0, sizeof(Py_buffer)); - if (PyObject_GetBuffer(pyobject, &bufinfo, PyBUF_FORMAT) == 0) { - if (tc == '*' || strchr(bufinfo.format, tc) - // if `long int` and `int` are the same size (on Windows and 32bit Linux, - // for example), `ctypes` isn't too picky about the type format, so make - // sure both integer types pass the type check - || (sizeof(long int) == sizeof(int) && ((tc == 'I' && strchr(bufinfo.format, 'L')) || - (tc == 'i' && strchr(bufinfo.format, 'l')))) - // complex float is 'Zf' in bufinfo.format, but 'z' in single char - || (tc == 'z' && strstr(bufinfo.format, "Zf")) - // allow 'signed char' ('b') from array to pass through '?' (bool as from struct) - || (tc == '?' && strchr(bufinfo.format, 'b')) - ) { - buf = bufinfo.buf; - - if (check && bufinfo.itemsize != size) { - PyErr_Format(PyExc_TypeError, - "buffer itemsize (%ld) does not match expected size (%d)", bufinfo.itemsize, size); - PyBuffer_Release(&bufinfo); - return 0; - } - - Py_ssize_t buflen = 0; - if (buf && bufinfo.ndim == 0) - buflen = bufinfo.len/bufinfo.itemsize; - else if (buf && bufinfo.ndim == 1) - buflen = bufinfo.shape ? bufinfo.shape[0] : bufinfo.len/bufinfo.itemsize; - PyBuffer_Release(&bufinfo); - if (buflen) - return buflen; - } else { - // have buf, but format mismatch: bail out now, otherwise the old - // code will return based on itemsize match - PyBuffer_Release(&bufinfo); - return 0; - } - } else if (bufinfo.obj) - PyBuffer_Release(&bufinfo); - PyErr_Clear(); - } - -// attempt to retrieve pointer through old-style buffer interface - PyBufferProcs* bufprocs = Py_TYPE(pyobject)->tp_as_buffer; - - PySequenceMethods* seqmeths = Py_TYPE(pyobject)->tp_as_sequence; - if (seqmeths != 0 && bufprocs != 0 - && bufprocs->bf_getbuffer != 0 - ) { - - // get the buffer - Py_buffer bufinfo; - (*(bufprocs->bf_getbuffer))(pyobject, &bufinfo, PyBUF_WRITABLE); - buf = (char*)bufinfo.buf; - Py_ssize_t buflen = bufinfo.len; - PyBuffer_Release(&bufinfo); - - if (buf && check == true) { - // determine buffer compatibility (use "buf" as a status flag) - PyObject* pytc = tc != '*' ? PyObject_GetAttr(pyobject, PyStrings::gTypeCode) : nullptr; - if (pytc != 0) { // for array objects - char cpytc = CPyCppyy_PyText_AsString(pytc)[0]; - if (!(cpytc == tc || (tc == '?' && cpytc == 'b'))) - buf = 0; // no match - Py_DECREF(pytc); - } else if (seqmeths->sq_length && - (int)(buflen/(*(seqmeths->sq_length))(pyobject)) == size) { - // this is a gamble ... may or may not be ok, but that's for the user - PyErr_Clear(); - } else if (buflen == size) { - // also a gamble, but at least 1 item will fit into the buffer, so very likely ok ... - PyErr_Clear(); - } else { - buf = 0; // not compatible - - // clarify error message - auto error = FetchPyError(); - PyObject* pyvalue2 = CPyCppyy_PyText_FromFormat( - (char*)"%s and given element size (%ld) do not match needed (%d)", - CPyCppyy_PyText_AsString(error.fValue.get()), - seqmeths->sq_length ? (long)(buflen/(*(seqmeths->sq_length))(pyobject)) : (long)buflen, - size); - error.fValue.reset(pyvalue2); - RestorePyError(error); - } - } - - if (!buf) return 0; - return buflen/(size ? size : 1); - } - - return 0; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::Utility::MapOperatorName(const std::string& name, bool bTakesParams, bool* stubbed) -{ -// Map the given C++ operator name on the python equivalent. - if (8 < name.size() && name.substr(0, 8) == "operator") { - std::string op = name.substr(8, std::string::npos); - - // stripping ... - std::string::size_type start = 0, end = op.size(); - while (start < end && isspace(op[start])) ++start; - while (start < end && isspace(op[end-1])) --end; - op = op.substr(start, end - start); - - // certain operators should be removed completely (e.g. operator delete & friends) - if (gOpRemove.find(op) != gOpRemove.end()) - return ""; - - TC2POperatorMapping_t::iterator pop = gC2POperatorMapping.find(op); - // map C++ operator to python equivalent, or made up name if no equivalent exists - if (pop != gC2POperatorMapping.end()) { - return pop->second; - - } else if (op == "*") { - // dereference v.s. multiplication of two instances - if (!bTakesParams) return "__deref__"; - if (stubbed) *stubbed = true; - return "__mul__"; - - } else if (op == "/") { - // no unary, but is stubbed - return CPPYY__div__; - - } else if (op == "+") { - // unary positive v.s. addition of two instances - if (!bTakesParams) return "__pos__"; - if (stubbed) *stubbed = true; - return "__add__"; - - } else if (op == "-") { - // unary negative v.s. subtraction of two instances - if (!bTakesParams) return "__neg__"; - if (stubbed) *stubbed = true; - return "__sub__"; - - } else if (op == "++") { - // prefix v.s. postfix increment - return bTakesParams ? "__postinc__" : "__preinc__"; - - } else if (op == "--") { - // prefix v.s. postfix decrement - return bTakesParams ? "__postdec__" : "__predec__"; - } - - } - -// might get here, as not all operator methods are handled (new, delete, etc.) - return name; -} - -//---------------------------------------------------------------------------- -std::string CPyCppyy::Utility::ClassName(PyObject* pyobj) -{ -// Retrieve the class name from the given Python instance. - std::string clname = ""; - PyObject* pyclass = (PyObject*)Py_TYPE(pyobj); - PyObject* pyname = PyObject_GetAttr(pyclass, PyStrings::gCppName); - if (!pyname) { - PyErr_Clear(); - pyname = PyObject_GetAttr(pyclass, PyStrings::gName); - } - - if (pyname) { - clname = CPyCppyy_PyText_AsString(pyname); - Py_DECREF(pyname); - } else - PyErr_Clear(); - return clname; -} - -//---------------------------------------------------------------------------- -static std::set sIteratorTypes; -bool CPyCppyy::Utility::IsSTLIterator(const std::string& classname) -{ -// attempt to recognize STL iterators (TODO: probably belongs in the backend), using -// a couple of common container classes with different iterator protocols (note that -// mapping iterators are handled separately in the pythonizations) as exemplars (the -// actual, resolved, names will be compiler-specific) that are picked b/c they are -// baked into the CoreLegacy dictionary - if (sIteratorTypes.empty()) { - std::string tt = "::"; - for (auto c : {"std::vector", "std::list", "std::deque"}) { - for (auto i : {"iterator", "const_iterator"}) { - const std::string& itname = Cppyy::ResolveName(c+tt+i); - auto pos = itname.find('<'); - if (pos != std::string::npos) - sIteratorTypes.insert(itname.substr(0, pos)); - } - } - } - - auto pos = classname.find('<'); - if (pos != std::string::npos) - return sIteratorTypes.find(classname.substr(0, pos)) != sIteratorTypes.end(); - return false; -} - - -//---------------------------------------------------------------------------- -CPyCppyy::Utility::PyOperators::~PyOperators() -{ - Py_XDECREF(fEq); - Py_XDECREF(fNe); - Py_XDECREF(fLAdd); Py_XDECREF(fRAdd); - Py_XDECREF(fSub); - Py_XDECREF(fLMul); Py_XDECREF(fRMul); - Py_XDECREF(fDiv); - Py_XDECREF(fHash); -} - - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::Utility::PyErr_Occurred_WithGIL() -{ -// Re-acquire the GIL before calling PyErr_Occurred() in case it has been -// released; note that the p2.2 code assumes that there are no callbacks in -// C++ to python (or at least none returning errors). - PythonGILRAII python_gil_raii; - PyObject* e = PyErr_Occurred(); - - return e; -} - - -//---------------------------------------------------------------------------- -CPyCppyy::Utility::PyError_t CPyCppyy::Utility::FetchPyError() -{ - // create a PyError_t RAII object that will capture and store the exception data - CPyCppyy::Utility::PyError_t error{}; -#if PY_VERSION_HEX >= 0x030c0000 - error.fValue.reset(PyErr_GetRaisedException()); -#else - PyObject *pytype = nullptr; - PyObject *pyvalue = nullptr; - PyObject *pytrace = nullptr; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); - error.fType.reset(pytype); - error.fValue.reset(pyvalue); - error.fTrace.reset(pytrace); -#endif - return error; -} - - -//---------------------------------------------------------------------------- -void CPyCppyy::Utility::RestorePyError(CPyCppyy::Utility::PyError_t &error) -{ -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(error.fValue.release()); -#else - PyErr_Restore(error.fType.release(), error.fValue.release(), error.fTrace.release()); -#endif -} - - -//---------------------------------------------------------------------------- -size_t CPyCppyy::Utility::FetchError(std::vector& errors, bool is_cpp) -{ -// Fetch the current python error, if any, and store it for future use. - if (PyErr_Occurred()) { - errors.emplace_back(FetchPyError()); - errors.back().fIsCpp = is_cpp; - } - return errors.size(); -} - -//---------------------------------------------------------------------------- -void CPyCppyy::Utility::SetDetailedException(std::vector&& errors, PyObject* topmsg, PyObject* defexc) -{ -// Use the collected exceptions to build up a detailed error log. - if (errors.empty()) { - // should not happen ... - PyErr_SetString(defexc, CPyCppyy_PyText_AsString(topmsg)); - Py_DECREF(topmsg); - return; - } - -// if a _single_ exception was thrown from C++, assume it has priority (see below) - PyError_t* unique_from_cpp = nullptr; - for (auto& e : errors) { - if (e.fIsCpp) { - if (!unique_from_cpp) - unique_from_cpp = &e; - else { - // two C++ exceptions, resort to default behavior - unique_from_cpp = nullptr; - break; - } - } - } - - if (unique_from_cpp) { - // report only this error; the idea here is that all other errors come from - // the bindings (e.g. argument conversion errors), while the exception from - // C++ means that it originated from an otherwise successful call - - // bind the original C++ object, rather than constructing from topmsg, as it - // is expected to have informative state - RestorePyError(*unique_from_cpp); - } else { - // try to consolidate Python exceptions, otherwise select default - PyObject* exc_type = nullptr; - for (auto& e : errors) { -#if PY_VERSION_HEX >= 0x030c0000 - PyObject* pytype = (PyObject*)Py_TYPE(e.fValue.get()); -#else - PyObject* pytype = e.fType.get(); -#endif - if (!exc_type) exc_type = pytype; - else if (exc_type != pytype) { - exc_type = defexc; - break; - } - } - - // add the details to the topmsg - PyObject* separator = CPyCppyy_PyText_FromString("\n "); - for (auto& e : errors) { - PyObject *pyvalue = e.fValue.get(); - CPyCppyy_PyText_Append(&topmsg, separator); - if (CPyCppyy_PyText_Check(pyvalue)) { - CPyCppyy_PyText_Append(&topmsg, pyvalue); - } else if (pyvalue) { - PyObject* excstr = PyObject_Str(pyvalue); - if (!excstr) { - PyErr_Clear(); - excstr = PyObject_Str((PyObject*)Py_TYPE(pyvalue)); - } - CPyCppyy_PyText_AppendAndDel(&topmsg, excstr); - } else { - CPyCppyy_PyText_AppendAndDel(&topmsg, - CPyCppyy_PyText_FromString("unknown exception")); - } - } - - Py_DECREF(separator); - - // set the python exception - PyErr_SetString(exc_type, CPyCppyy_PyText_AsString(topmsg)); - } - - Py_DECREF(topmsg); -} - - -//---------------------------------------------------------------------------- -static bool includesDone = false; -bool CPyCppyy::Utility::IncludePython() -{ -// setup Python API for callbacks - if (!includesDone) { - bool okay = Cppyy::Compile( - // basic API (converters etc.) - "#include \"CPyCppyy/API.h\"\n" - - // utilities from the CPyCppyy public API - "#include \"CPyCppyy/DispatchPtr.h\"\n" - "#include \"CPyCppyy/PyException.h\"\n" - ); - includesDone = okay; - } - - return includesDone; -} diff --git a/src/CPyCppyy/src/Utility.h b/src/CPyCppyy/src/Utility.h deleted file mode 100644 index cc4c2a5..0000000 --- a/src/CPyCppyy/src/Utility.h +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef CPYCPPYY_UTILITY_H -#define CPYCPPYY_UTILITY_H - -// Standard -#include -#include -#include -#include - -#include "Python.h" -#include "Cppyy.h" - -namespace CPyCppyy { - -class PyCallable; - -#if PY_VERSION_HEX < 0x030b0000 -extern bool gDictLookupActive; -#endif - -// additional converter functions -unsigned long PyLongOrInt_AsULong(PyObject* pyobject); -PY_ULONG_LONG PyLongOrInt_AsULong64(PyObject* pyobject); - -namespace Utility { - -// convenience functions for adding methods to classes -bool AddToClass(PyObject* pyclass, const char* label, PyCFunction cfunc, - int flags = METH_VARARGS); -bool AddToClass(PyObject* pyclass, const char* label, const char* func); -bool AddToClass(PyObject* pyclass, const char* label, PyCallable* pyfunc); - -// helpers for dynamically constructing operators -PyCallable* FindUnaryOperator(PyObject* pyclass, const char* op); -PyCallable* FindBinaryOperator(PyObject* left, PyObject* right, - const char* op, Cppyy::TCppScope_t scope = Cppyy::TCppScope_t{}); -PyCallable* FindBinaryOperator(const std::string& lcname, const std::string& rcname, - const char* op, Cppyy::TCppScope_t scope = Cppyy::TCppScope_t{}, bool reverse = false); - -// helper for template classes and methods -enum ArgPreference { kNone, kPointer, kReference, kValue }; -std::string ConstructTemplateArgs( - PyObject* pyname, PyObject* tpArgs, PyObject* args = nullptr, ArgPreference = kNone, int argoff = 0, int* pcnt = nullptr); -std::vector GetTemplateArgsTypes( - PyObject* scope, PyObject* tpArgs, PyObject* args = nullptr, ArgPreference = kNone, int argoff = 0, int* pcnt = nullptr); - -std::string CT2CppNameS(PyObject* pytc, bool allow_voidp); -inline PyObject* CT2CppName(PyObject* pytc, const char* cpd, bool allow_voidp) -{ - const std::string& name = CT2CppNameS(pytc, allow_voidp); - if (!name.empty()) { - if (name == "const char*") cpd = ""; - return PyUnicode_FromString((std::string{name}+cpd).c_str()); - } - return nullptr; -} - -// helper for generating callbacks -void ConstructCallbackPreamble(const std::string& retType, - const std::vector& argtypes, std::ostringstream& code); -void ConstructCallbackReturn(const std::string& retType, int nArgs, std::ostringstream& code); - -// helper for function pointer conversions -PyObject* FuncPtr2StdFunction(const std::string& retType, const std::string& signature, void* address); - -// initialize proxy type objects -bool InitProxy(PyObject* module, PyTypeObject* pytype, const char* name); - -std::unordered_map const &TypecodeMap(); - -// retrieve the memory buffer from pyobject, return buflength, tc (optional) is python -// array.array type code, size is type size, buf will point to buffer, and if check is -// true, some heuristics will be applied to check buffer compatibility with the type -Py_ssize_t GetBuffer(PyObject* pyobject, char tc, int size, void*& buf, bool check = true); - -// data/operator mappings -std::string MapOperatorName(const std::string& name, bool bTakesParames, bool* stubbed = nullptr); - -struct PyOperators { - PyOperators() : fEq(nullptr), fNe(nullptr), fLt(nullptr), fLe(nullptr), fGt(nullptr), fGe(nullptr), - fLAdd(nullptr), fRAdd(nullptr), fSub(nullptr), fLMul(nullptr), fRMul(nullptr), fDiv(nullptr), - fHash(nullptr) {} - ~PyOperators(); - - PyObject* fEq; - PyObject* fNe; - PyObject *fLt, *fLe; - PyObject *fGt, *fGe; - PyObject *fLAdd, *fRAdd; - PyObject* fSub; - PyObject *fLMul, *fRMul; - PyObject* fDiv; - PyObject* fHash; -}; - -// meta information -std::string ClassName(PyObject* pyobj); -bool IsSTLIterator(const std::string& classname); - -// for threading: save call to PyErr_Occurred() -PyObject* PyErr_Occurred_WithGIL(); - -// helpers for collecting/maintaining python exception data -struct PyError_t { - struct PyObjectDeleter { - void operator()(PyObject *obj) { Py_XDECREF(obj); } - }; -#if PY_VERSION_HEX < 0x030c0000 - std::unique_ptr fType; - std::unique_ptr fTrace; -#endif - std::unique_ptr fValue; - bool fIsCpp = false; -}; - -PyError_t FetchPyError(); -void RestorePyError(PyError_t &error); - -size_t FetchError(std::vector&, bool is_cpp = false); -void SetDetailedException( - std::vector&& errors /* clears */, PyObject* topmsg /* steals ref */, PyObject* defexc); - -// setup Python API for callbacks -bool IncludePython(); - -} // namespace Utility - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_UTILITY_H diff --git a/src/backend/callcontext.h b/src/backend/callcontext.h deleted file mode 100644 index b0ae54a..0000000 --- a/src/backend/callcontext.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef CPYCPPYY_CALLCONTEXT_H -#define CPYCPPYY_CALLCONTEXT_H - -// Standard -#include - - -namespace CPyCppyy { - -// general place holder for function parameters - struct Parameter { - union Value { - bool fBool; - int8_t fInt8; - uint8_t fUInt8; - short fShort; - unsigned short fUShort; - int fInt; - unsigned int fUInt; - long fLong; - intptr_t fIntPtr; - unsigned long fULong; - long long fLLong; - unsigned long long fULLong; - int64_t fInt64; - uint64_t fUInt64; - float fFloat; - double fDouble; - long double fLDouble; - void* fVoidp; - } fValue; - void* fRef; - char fTypeCode; - }; - -} // namespace CPyCppyy - -#endif // !CPYCPPYY_CALLCONTEXT_H diff --git a/src/backend/clingwrapper.cxx b/src/backend/clingwrapper.cxx deleted file mode 100644 index 4b89dab..0000000 --- a/src/backend/clingwrapper.cxx +++ /dev/null @@ -1,1923 +0,0 @@ -#ifndef _WIN32 -#ifndef _CRT_SECURE_NO_WARNINGS -// silence warnings about getenv, strncpy, etc. -#define _CRT_SECURE_NO_WARNINGS -#endif -#endif - -#include "precommondefs.h" // This defines several system feature macros and should be included before any system header. - - -// Bindings -#include "cpp_cppyy.h" -#include "callcontext.h" - -#ifndef _WIN32 -#include -#endif - -// Standard -#include -#include // for std::count, std::remove -#include -#include -#include -#include -#include -#include -#include -#include // for getenv -#include -#include -#include -#include -#include - -std::recursive_mutex InterOpMutex; - -// builtin types -static std::set g_builtins = - {"bool", "char", "signed char", "unsigned char", "wchar_t", "short", "unsigned short", - "int", "unsigned int", "long", "unsigned long", "long long", "unsigned long long", - "float", "double", "long double", "void"}; - -// to filter out ROOT names -static std::set gInitialNames; -static std::set gRootSOs; - -// configuration -static bool gEnableFastPath = true; - - -// global initialization ----------------------------------------------------- -namespace { - -//const int kMAXSIGNALS = 16; - -// names copied from TUnixSystem -#ifdef WIN32 -const int SIGBUS = 0; // simple placeholders for ones that don't exist -const int SIGSYS = 0; -const int SIGPIPE = 0; -const int SIGQUIT = 0; -const int SIGWINCH = 0; -const int SIGALRM = 0; -const int SIGCHLD = 0; -const int SIGURG = 0; -const int SIGUSR1 = 0; -const int SIGUSR2 = 0; -#endif - -#if 0 -static struct Signalmap_t { - int fCode; - const char *fSigName; -} gSignalMap[kMAXSIGNALS] = { // the order of the signals should be identical - { SIGBUS, "bus error" }, // to the one in TSysEvtHandler.h - { SIGSEGV, "segmentation violation" }, - { SIGSYS, "bad argument to system call" }, - { SIGPIPE, "write on a pipe with no one to read it" }, - { SIGILL, "illegal instruction" }, - { SIGABRT, "abort" }, - { SIGQUIT, "quit" }, - { SIGINT, "interrupt" }, - { SIGWINCH, "window size change" }, - { SIGALRM, "alarm clock" }, - { SIGCHLD, "death of a child" }, - { SIGURG, "urgent data arrived on an I/O channel" }, - { SIGFPE, "floating point exception" }, - { SIGTERM, "termination signal" }, - { SIGUSR1, "user-defined signal 1" }, - { SIGUSR2, "user-defined signal 2" } -}; -#endif - -static inline -void push_tokens_from_string(char *s, std::vector &tokens) { - char *token = strtok(s, " "); - - while (token) { - tokens.push_back(token); - token = strtok(NULL, " "); - } -} - -static inline -bool is_integral(std::string& s) -{ - if (s == "false") { s = "0"; return true; } - else if (s == "true") { s = "1"; return true; } - return !s.empty() && std::find_if(s.begin(), - s.end(), [](unsigned char c) { return !std::isdigit(c); }) == s.end(); -} - -class ApplicationStarter { - Cppyy::TInterp_t Interp; -public: - ApplicationStarter() { - std::lock_guard Lock(InterOpMutex); - if (!Cpp::LoadDispatchAPI( - CPPINTEROP_DIR - "/lib/libclangCppInterOp" CMAKE_SHARED_LIBRARY_SUFFIX)) { - std::cerr << "[cppyy-backend] Failed to load CppInterOp" << std::endl; - return; - } - // Check if somebody already loaded CppInterOp and created an - // interpreter for us. - if (auto existingInterp = Cpp::GetInterpreter()) { - Interp = existingInterp; - } - else { -#ifdef __arm64__ -#ifdef __APPLE__ - // If on apple silicon don't use -march=native - std::vector InterpArgs({"-std=c++17"}); -#else - std::vector InterpArgs( - {"-std=c++17", "-march=native"}); -#endif -#else - std::vector InterpArgs({"-std=c++17", "-march=native"}); -#endif - char *InterpArgString = getenv("CPPINTEROP_EXTRA_INTERPRETER_ARGS"); - - if (InterpArgString) - push_tokens_from_string(InterpArgString, InterpArgs); - -#ifdef __arm64__ -#ifdef __APPLE__ - // If on apple silicon don't use -march=native - Interp = Cpp::CreateInterpreter({"-std=c++17"}, /*GpuArgs=*/{}); -#else - Interp = Cpp::CreateInterpreter({"-std=c++17", "-march=native"}, - /*GpuArgs=*/{}); -#endif -#else - Interp = Cpp::CreateInterpreter({"-std=c++17", "-march=native"}, - /*GpuArgs=*/{}); -#endif - } - - // fill out the builtins - std::set bi{g_builtins}; - for (const auto& name : bi) { - for (const char* a : {"*", "&", "*&", "[]", "*[]"}) - g_builtins.insert(name+a); - } - - // disable fast path if requested - if (getenv("CPPYY_DISABLE_FASTPATH")) gEnableFastPath = false; - - // set opt level (default to 2 if not given; Cling itself defaults to 0) - int optLevel = 2; - - if (getenv("CPPYY_OPT_LEVEL")) optLevel = atoi(getenv("CPPYY_OPT_LEVEL")); - - if (optLevel != 0) { - std::ostringstream s; - s << "#pragma cling optimize " << optLevel; - Cpp::Process(s.str().c_str()); - } - - // This would give us something like: - // /home/vvassilev/workspace/builds/scratch/cling-build/builddir/lib/clang/13.0.0 - const char * ResourceDir = Cpp::GetResourceDir(); - std::string ClingSrc = std::string(ResourceDir) + "/../../../../cling-src"; - std::string ClingBuildDir = std::string(ResourceDir) + "/../../../"; - Cpp::AddIncludePath((ClingSrc + "/tools/cling/include").c_str()); - Cpp::AddIncludePath((ClingSrc + "/include").c_str()); - Cpp::AddIncludePath((ClingBuildDir + "/include").c_str()); - Cpp::AddIncludePath((std::string(CPPINTEROP_DIR) + "/include").c_str()); - Cpp::LoadLibrary("libstdc++", /* lookup= */ true); - - // load frequently used headers - const char* code = - "#include \n" - "#include \n" - "#include \n" - "#include \n" - "#include \n" // for strcpy - "#include \n" - // "#include \n" // defines R__EXTERN - "#include \n" - "#include \n" - "#include \n" - "#include \n" // for the dispatcher code to use - // std::function - "#include \n" // FIXME: Replace with modules - "#include \n" // FIXME: Replace with modules - "#include \n" // FIXME: Replace with modules - "#include \n" // FIXME: Replace with modules - "#include \n" // FIXME: Replace with modules - "#include \n" // FIXME: Replace with modules - "#include \n" // FIXME: Replace with modules - "#include \n" // FIXME: Replace with modules - "#include \n" // FIXME: Replace with modules - "#if __has_include()\n" - "#include \n" - "#endif\n" - "#include \n"; - Cpp::Process(code); - - // create helpers for comparing thingies - Cpp::Declare("namespace __cppyy_internal { template" - " bool is_equal(const C1& c1, const C2& c2) { return " - "(bool)(c1 == c2); } }", - /*silent=*/false); - Cpp::Declare("namespace __cppyy_internal { template" - " bool is_not_equal(const C1& c1, const C2& c2) { return " - "(bool)(c1 != c2); } }", - /*silent=*/false); - - // Define gCling when we run with clang-repl. - // FIXME: We should get rid of all the uses of gCling as this seems to - // break encapsulation. - std::stringstream InterpPtrSS; - InterpPtrSS << "#ifndef __CLING__\n" - << "namespace cling { namespace runtime {\n" - << "void* gCling=(void*)" << Interp.data - << ";\n }}\n" - << "#endif \n"; - Cpp::Process(InterpPtrSS.str().c_str()); - - // helper for multiple inheritance - Cpp::Declare("namespace __cppyy_internal { struct Sep; }", - /*silent=*/false); - - // std::string libInterOp = I->getDynamicLibraryManager()->lookupLibrary("libcling"); - // void *interopDL = dlopen(libInterOp.c_str(), RTLD_LAZY); - // if (!interopDL) { - // std::cerr << "libInterop could not be opened!\n"; - // exit(1); - // } - - // start off with a reasonable size placeholder for wrappers - // gWrapperHolder.reserve(1024); - - // create an exception handler to process signals - // gExceptionHandler = new TExceptionHandlerImp{}; - } - - ~ApplicationStarter() { - //Cpp::DeleteInterpreter(Interp); - // for (auto wrap : gWrapperHolder) - // delete wrap; - // delete gExceptionHandler; gExceptionHandler = nullptr; - } -} _applicationStarter; - -} // unnamed namespace - - -// local helpers ------------------------------------------------------------- -static inline -char* cppstring_to_cstring(const std::string& cppstr) -{ - char* cstr = (char*)malloc(cppstr.size()+1); - memcpy(cstr, cppstr.c_str(), cppstr.size()+1); - return cstr; -} - -// direct interpreter access ------------------------------------------------- -// Returns false on failure and true on success -bool Cppyy::Compile(const std::string& code, bool silent) -{ - std::lock_guard Lock(InterOpMutex); - // Declare returns an enum which equals 0 on success - return !Cpp::Declare(code.c_str(), silent); -} - -std::string Cppyy::ToString(TCppScope_t klass, TCppObject_t obj) -{ - std::lock_guard Lock(InterOpMutex); - if (klass && obj && !Cpp::IsNamespace(klass)) - return Cpp::ObjToString(Cpp::GetQualifiedCompleteName(klass).c_str(), obj.data); - return ""; -} - -// // name to opaque C++ scope representation ----------------------------------- -std::string Cppyy::ResolveName(const std::string& name) { - if (!name.empty()) { - if (Cppyy::TCppType_t type = - Cppyy::GetType(name, /*enable_slow_lookup=*/true)) - return Cppyy::GetTypeAsString(Cppyy::ResolveType(type)); - return name; - } - return ""; -} - -Cppyy::TCppType_t Cppyy::ResolveEnumReferenceType(TCppType_t type) { -std::lock_guard Lock(InterOpMutex); - if (Cpp::GetValueKind(type) != Cpp::ValueKind::LValue) - return type; - - TCppType_t nonReferenceType = Cpp::GetNonReferenceType(type); - if (Cpp::IsEnumType(nonReferenceType)) { - TCppType_t underlying_type = Cpp::GetIntegerTypeFromEnumType(nonReferenceType); - return Cpp::GetReferencedType(underlying_type, /*rvalue=*/false); - } - return type; -} - -Cppyy::TCppType_t Cppyy::ResolveEnumPointerType(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); - if (!Cpp::IsPointerType(type)) - return type; - - TCppType_t PointeeType = Cpp::GetPointeeType(type); - if (Cpp::IsEnumType(PointeeType)) { - TCppType_t underlying_type = Cpp::GetIntegerTypeFromEnumType(PointeeType); - return Cpp::GetPointerType(underlying_type); - } - return type; -} - -Cppyy::TCppType_t int_like_type(Cppyy::TCppType_t type) { - Cppyy::TCppType_t check_int_typedefs = type; - if (Cpp::IsPointerType(check_int_typedefs)) - check_int_typedefs = Cpp::GetPointeeType(check_int_typedefs); - if (Cpp::IsReferenceType(check_int_typedefs)) - check_int_typedefs = - Cpp::GetReferencedType(check_int_typedefs, /*rvalue=*/false); - - if (Cpp::GetTypeAsString(check_int_typedefs) == "int8_t" || Cpp::GetTypeAsString(check_int_typedefs) == "uint8_t") - return check_int_typedefs; - return nullptr; -} - -Cppyy::TCppType_t Cppyy::ResolveType(TCppType_t type) { - if (!type) return type; - - std::lock_guard Lock(InterOpMutex); - - TCppType_t check_int_typedefs = int_like_type(type); - if (check_int_typedefs) - return type; - - Cppyy::TCppType_t canonType = Cpp::GetCanonicalType(type); - - if (Cpp::IsEnumType(canonType)) { - if (Cpp::GetTypeAsString(type) != "std::byte") - return Cpp::GetIntegerTypeFromEnumType(canonType); - } - if (Cpp::HasTypeQualifier(canonType, Cpp::QualKind::Restrict)) { - return Cpp::RemoveTypeQualifier(canonType, Cpp::QualKind::Restrict); - } - - return canonType; -} - -Cppyy::TCppType_t Cppyy::GetRealType(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); - TCppType_t check_int_typedefs = int_like_type(type); - if (check_int_typedefs) - return check_int_typedefs; - return Cpp::GetUnderlyingType(type); -} - -Cppyy::TCppType_t Cppyy::GetPointerType(TCppType_t type) { - std::lock_guard Lock(InterOpMutex); - return Cpp::GetPointerType(type); -} - -Cppyy::TCppType_t Cppyy::GetReferencedType(TCppType_t type, bool rvalue) { - std::lock_guard Lock(InterOpMutex); - return Cpp::GetReferencedType(type, rvalue); -} - -bool Cppyy::IsRValueReferenceType(TCppType_t type) { - return Cpp::GetValueKind(type) == Cpp::ValueKind::RValue; -} - -bool Cppyy::IsLValueReferenceType(TCppType_t type) { - return Cpp::GetValueKind(type) == Cpp::ValueKind::LValue; -} - -bool Cppyy::IsClassType(TCppType_t type) { - return Cpp::IsRecordType(type); -} - -bool Cppyy::IsIntegerType(TCppType_t type, bool* is_signed /*= nullptr*/) { - if (is_signed) { - Cpp::Signedness sign; - bool res = Cpp::IsIntegerType(type, &sign); - *is_signed = (sign == Cpp::Signedness::kSigned); - return res; - } - return Cpp::IsIntegerType(type, nullptr); -} - -bool Cppyy::IsPointerType(TCppType_t type) { - return Cpp::IsPointerType(type); -} - -bool Cppyy::IsFunctionPointerType(TCppType_t type) { - return Cpp::IsFunctionPointerType(type); -} - -std::string trim(const std::string& line) -{ - if (line.empty()) return ""; - const char* WhiteSpace = " \t\v\r\n"; - std::size_t start = line.find_first_not_of(WhiteSpace); - std::size_t end = line.find_last_not_of(WhiteSpace); - return line.substr(start, end - start + 1); -} - -// returns false of angular brackets dont match, else true -bool split_comma_saparated_types(const std::string& name, - std::vector& types) { - std::string trimed_name = trim(name); - size_t start_pos = 0; - size_t end_pos = 0; - int matching_angular_brackets = 0; - while (end_pos < trimed_name.size()) { - switch (trimed_name[end_pos]) { - case ',': { - if (!matching_angular_brackets) { - if(end_pos > start_pos) - types.push_back( - trim(trimed_name.substr(start_pos, end_pos - start_pos))); - start_pos = end_pos + 1; - } - break; - } - case '<': { - matching_angular_brackets++; - break; - } - case '>': { - matching_angular_brackets--; - break; - } - } - end_pos++; - } - if (start_pos < trimed_name.size()) - types.push_back(trim(trimed_name.substr(start_pos, end_pos - start_pos))); - return true; -} - -Cppyy::TCppScope_t GetEnumFromCompleteName(const std::string &name) { - std::string delim = "::"; - size_t start = 0; - size_t end = name.find(delim); - Cppyy::TCppScope_t curr_scope; - while (end != std::string::npos) { - curr_scope = Cpp::GetNamed(name.substr(start, end - start), curr_scope); - start = end + delim.length(); - end = name.find(delim, start); - } - return Cpp::GetNamed(name.substr(start, end), curr_scope); -} -static bool is_identifier(std::string_view s) { - if (s.empty()) return false; - auto is_valid_start = [](unsigned char c) { - return std::isalpha(c) || c == '_'; - }; - auto is_valid_body = [](unsigned char c) { - return std::isalnum(c) || c == '_'; - }; - return is_valid_start(s[0]) && - std::all_of(s.begin() + 1, s.end(), is_valid_body); -}; - -// returns true if no new type was added. -bool Cppyy::AppendTypesSlow(const std::string& name, - std::vector& types, Cppyy::TCppScope_t parent) { - - // Add no new type if string is empty - if (name.empty()) - return true; - - // The ast printer gave us garbage. - if (name == "") - return true; - - auto replace_all = [](std::string& str, const std::string& from, const std::string& to) { - if(from.empty()) - return; - size_t start_pos = 0; - while((start_pos = str.find(from, start_pos)) != std::string::npos) { - str.replace(start_pos, from.length(), to); - start_pos += to.length(); - } - }; - - std::string resolved_name = name; - replace_all(resolved_name, "std::initializer_list<", "std::vector<"); // replace initializer_list with vector - - // If we have a single identifier, we don't need anything complicated. - // Try scoped lookup first (catches type aliases / nested types declared - // inside `parent`), then fall back to TU (catches typedefs declared - // outside the query scope, e.g. `typedef Foo Bar;` at TU consulted - // from a method on Foo). - if (is_identifier(name)) { - TCppType_t type = parent ? Cpp::GetType(name, parent) : nullptr; - if (!type) - type = Cpp::GetType(name); - if (type) { - types.emplace_back(type.data); - return false; - } - return true; - } - - std::lock_guard Lock(InterOpMutex); - - // We might have an entire expression such as int, double. - static unsigned long long struct_count = 0; - std::string code = "template struct __Cppyy_AppendTypesSlow {};\n"; - if (!struct_count) - Cpp::Declare(code.c_str(), /*silent=*/true); // initialize the trampoline - - // The trampoline declares its variable in the global scope, so a name - // written relative to a parent (e.g. "vector" looked up in std) - // won't resolve. Try the name as given, then qualified by the parent. - std::vector candidates = {resolved_name}; - if (parent && parent != Cpp::GetGlobalScope() && - (Cppyy::IsNamespace(parent) || Cppyy::IsClass(parent))) - candidates.push_back(Cpp::GetQualifiedCompleteName(parent) + "::" + resolved_name); - - for (const std::string& candidate : candidates) { - std::string var = "__Cppyy_s" + std::to_string(struct_count++); - // nodebug: with -g the variable's debug info would carry the full DIE - // tree of every template argument (all member declarations included) -- - // a large, uncacheable per-lookup cost on heavyweight types. - if (!Cpp::Declare(("__Cppyy_AppendTypesSlow<" + candidate + "> __attribute__((nodebug)) " + var + ";\n").c_str(), /*silent=*/true)) { - TCppType_t varN = - Cpp::GetVariableType(Cpp::GetNamed(var.c_str(), /*parent=*/nullptr)); - TCppScope_t instance_class = Cpp::GetScopeFromType(varN); - size_t oldSize = types.size(); - Cpp::GetClassTemplateInstantiationArgs(instance_class, types); - return oldSize == types.size(); - } - } - - // We split each individual types based on , and resolve it - // FIXME: see discussion on should we support template instantiation with string: - // https://github.com/compiler-research/cppyy-backend/pull/137#discussion_r2079357491 - // We should consider eliminating the `split_comma_saparated_types` and `is_integral` - // string parsing. - std::vector individual_types; - if (!split_comma_saparated_types(resolved_name, individual_types)) - return true; - - for (std::string& i : individual_types) { - // Try going via Cppyy::GetType first. - const char* integral_value = nullptr; - Cppyy::TCppType_t type = nullptr; - - type = GetType(i, /*enable_slow_lookup=*/true); - if (!type && parent && (Cppyy::IsNamespace(parent) || Cppyy::IsClass(parent))) { - type = Cppyy::GetTypeFromScope(Cppyy::GetNamed(resolved_name, parent)); - } - - if (!type) { - types.clear(); - return true; - } - - if (is_integral(i)) - integral_value = strdup(i.c_str()); - if (TCppScope_t scope = GetEnumFromCompleteName(i)) - if (Cpp::IsEnumConstant(scope)) - integral_value = - strdup(std::to_string(Cpp::GetEnumConstantValue(scope)).c_str()); - types.emplace_back(type.data, integral_value); - } - return false; -} - -Cppyy::TCppType_t Cppyy::GetType(const std::string &name, bool enable_slow_lookup /* = false */) { - // The ast printer gave us garbage. - if (name == "") - return nullptr; - std::lock_guard Lock(InterOpMutex); - - if (auto type = Cpp::GetType(name)) - return type; - - // Plain identifiers don't need the heavy __typeof__ trampoline: - // Cpp::GetType above already covers builtin types and named - // scopes. Exception: the three identifier-shaped C++ value- - // literals -- `true`, `false`, `nullptr` -- aren't reachable by - // name (no type called "false") but appear as non-type template - // args in libstdc++ types like _Node_iterator<..., false, false>; - // map them to their underlying type directly so the per-chunk - // fallback in AppendTypesSlow gets a real type without paying - // the trampoline cost. - if (is_identifier(name)) { - if (name == "true" || name == "false") - return Cpp::GetType("bool"); - if (name == "nullptr") - return Cpp::GetType("nullptr_t", Cpp::GetNamed("std")); - return nullptr; - } - - if (!enable_slow_lookup) { - if (name.find("::") != std::string::npos) - throw std::runtime_error("Calling Cppyy::GetType with qualified name '" - + name + "'\n"); - return nullptr; - } - - // Here we might need to deal with integral types such as 3.14. - - static unsigned long long var_count = 0; - std::string id = "__Cppyy_GetType_" + std::to_string(var_count++); - std::string using_clause = "using " + id + " = __typeof__(" + name + ");\n"; - - if (!Cpp::Declare(using_clause.c_str(), /*silent=*/true)) { - TCppScope_t lookup = Cpp::GetNamed(id); - TCppType_t lookup_ty = Cpp::GetTypeFromScope(lookup); - return Cpp::GetCanonicalType(lookup_ty); - } - return nullptr; -} - - -Cppyy::TCppType_t Cppyy::GetComplexType(const std::string &name) { - std::lock_guard Lock(InterOpMutex); - return Cpp::GetComplexType(Cpp::GetType(name)); -} - - -// //---------------------------------------------------------------------------- -// static std::string extract_namespace(const std::string& name) -// { -// // Find the namespace the named class lives in, take care of templates -// // Note: this code also lives in CPyCppyy (TODO: refactor?) -// if (name.empty()) -// return name; -// -// int tpl_open = 0; -// for (std::string::size_type pos = name.size()-1; 0 < pos; --pos) { -// std::string::value_type c = name[pos]; -// -// // count '<' and '>' to be able to skip template contents -// if (c == '>') -// ++tpl_open; -// else if (c == '<') -// --tpl_open; -// -// // collect name up to "::" -// else if (tpl_open == 0 && c == ':' && name[pos-1] == ':') { -// // found the extend of the scope ... done -// return name.substr(0, pos-1); -// } -// } -// -// // no namespace; assume outer scope -// return ""; -// } -// - -std::string Cppyy::ResolveEnum(TCppScope_t handle) -{ - std::lock_guard Lock(InterOpMutex); - std::string type = Cpp::GetTypeAsString( - Cpp::GetIntegerTypeFromEnumScope(handle)); - if (type == "signed char") - return "char"; - return type; -} - -Cppyy::TCppScope_t Cppyy::GetUnderlyingScope(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetUnderlyingScope(scope); -} - -Cppyy::TCppScope_t Cppyy::GetScope(const std::string& name, - TCppScope_t parent_scope) -{ - std::lock_guard Lock(InterOpMutex); - if (Cppyy::TCppScope_t scope = Cpp::GetScope(name, parent_scope)) - return scope; - if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) - if (Cppyy::TCppScope_t scope = Cpp::GetScopeFromCompleteName(name)) - return scope; - - // FIXME: avoid string parsing here - if (name.find('<') != std::string::npos) { - // Templated type; may need instantiation. Resolve the whole type - // expression (e.g. "std::array") and read back its scope. - // Splitting off the argument list and resolving it directly cannot - // represent non-type arguments such as the `3` in std::array. - std::vector types; - InterOpMutex.unlock(); // unlock to allow AppendTypesSlow - bool added_new_type = !Cppyy::AppendTypesSlow(name, types, /*parent=*/parent_scope); - std::lock_guard Lock(InterOpMutex); - if (added_new_type && types.size() == 1) { - // A pointer or reference spelling (e.g. "std::chrono::nanoseconds *", - // the return type of std::array::begin()) does not - // name a scope; GetScopeFromType would silently strip the pointer and - // return the pointee's scope, misclassifying the name. - if (Cpp::IsPointerType(types[0].m_Type) || - Cpp::IsReferenceType(types[0].m_Type)) - return nullptr; - TCppScope_t scope = Cpp::GetScopeFromType(types[0].m_Type); - // Naming the type as a template argument above does not instantiate - // it, so the specialization may still be declared-but-undefined. - // Force its definition: callers expect a complete scope, e.g. to - // walk its base classes. - if (scope) - Cpp::IsComplete(scope); - return scope; - } - } - return nullptr; -} - -Cppyy::TCppScope_t Cppyy::GetFullScope(const std::string& name) -{ - return Cppyy::GetScope(name); -} - -Cppyy::TCppScope_t Cppyy::GetTypeScope(TCppScope_t var) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetScopeFromType( - Cpp::GetVariableType(var)); -} - -Cppyy::TCppScope_t Cppyy::GetNamed(const std::string& name, - TCppScope_t parent_scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetNamed(name, parent_scope); -} - -Cppyy::TCppScope_t Cppyy::GetParentScope(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetParentScope(scope); -} - -Cppyy::TCppScope_t Cppyy::GetScopeFromType(TCppType_t type) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetScopeFromType(type); -} - -Cppyy::TCppType_t Cppyy::GetTypeFromScope(TCppScope_t klass) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetTypeFromScope(klass); -} - -Cppyy::TCppScope_t Cppyy::GetGlobalScope() -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetGlobalScope(); -} - -bool Cppyy::IsTemplate(TCppScope_t handle) -{ - return Cpp::IsTemplate(handle); -} - -bool Cppyy::IsTemplateInstantiation(TCppScope_t handle) -{ - return Cpp::IsTemplateSpecialization(handle); -} - -bool Cppyy::IsTypedefed(TCppScope_t handle) -{ - return Cpp::IsTypedefed(handle); -} - -namespace { -class AutoCastRTTI { -public: - virtual ~AutoCastRTTI() {} -}; -} // namespace - -Cppyy::TCppScope_t Cppyy::GetActualClass(TCppScope_t klass, TCppObject_t obj) { - std::lock_guard Lock(InterOpMutex); - - if (!Cpp::IsClassPolymorphic(klass)) - return klass; - - const std::type_info *typ = &typeid(*(AutoCastRTTI *)obj.data); - - std::string mangled_name = typ->name(); - std::string demangled_name = Cpp::Demangle(mangled_name); - - if (TCppScope_t scope = Cppyy::GetScope(demangled_name)) - return scope; - - return klass; -} - -size_t Cppyy::SizeOf(TCppScope_t klass) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::SizeOf(klass); -} - -size_t Cppyy::SizeOfType(TCppType_t klass) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetSizeOfType(klass); -} - -bool Cppyy::IsBuiltin(const std::string& type_name) -{ - static std::set s_builtins = - {"bool", "char", "signed char", "unsigned char", "wchar_t", "short", - "unsigned short", "int", "unsigned int", "long", "unsigned long", - "long long", "unsigned long long", "float", "double", "long double", - "void"}; - if (s_builtins.find(trim(type_name)) != s_builtins.end()) - return true; - - if (strstr(type_name.c_str(), "std::complex")) - return true; - - return false; -} - -bool Cppyy::IsBuiltin(TCppType_t type) -{ - return Cpp::IsBuiltin(type); - -} - -bool Cppyy::IsComplete(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::IsComplete(scope); -} - -// // memory management --------------------------------------------------------- -Cppyy::TCppObject_t Cppyy::Allocate(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::Allocate(scope, /*count=*/1); -} - -void Cppyy::Deallocate(TCppScope_t scope, TCppObject_t instance) -{ - std::lock_guard Lock(InterOpMutex); - Cpp::Deallocate(scope, instance, /*count=*/1); -} - -Cppyy::TCppObject_t Cppyy::Construct(TCppScope_t scope, void* arena/*=nullptr*/) -{ - std::lock_guard Lock(InterOpMutex); // TODO: this shouldn't locks the JIT call - return Cpp::Construct(scope, arena, /*count=*/1); -} - -void Cppyy::Destruct(TCppScope_t scope, TCppObject_t instance) -{ - std::lock_guard Lock(InterOpMutex); // TODO: this shouldn't locks the JIT call - Cpp::Destruct(instance, scope, true, /*count=*/0); -} - -static inline -bool copy_args(Parameter* args, size_t nargs, void** vargs) -{ - bool runRelease = false; - for (size_t i = 0; i < nargs; ++i) { - switch (args[i].fTypeCode) { - case 'X': /* (void*)type& with free */ - runRelease = true; - case 'V': /* (void*)type& */ - vargs[i] = args[i].fValue.fVoidp; - break; - case 'r': /* const type& */ - vargs[i] = args[i].fRef; - break; - default: /* all other types in union */ - vargs[i] = (void*)&args[i].fValue.fVoidp; - break; - } - } - return runRelease; -} - -static inline -void release_args(Parameter* args, size_t nargs) { - for (size_t i = 0; i < nargs; ++i) { - if (args[i].fTypeCode == 'X') - free(args[i].fValue.fVoidp); - } -} - -static inline -bool WrapperCall(Cppyy::TCppMethod_t method, size_t nargs, void* args_, void* self, void* result) -{ - Parameter* args = (Parameter*)args_; - //bool is_direct = nargs & DIRECT_CALL; - nargs = CALL_NARGS(nargs); - - // if (!is_ready(wrap, is_direct)) - // return false; // happens with compilation error - InterOpMutex.lock(); - if (Cpp::JitCall JC = Cpp::MakeFunctionCallable(method)) { - InterOpMutex.unlock(); - bool runRelease = false; - //const auto& fgen = /* is_direct ? faceptr.fDirect : */ faceptr; - if (nargs <= SMALL_ARGS_N) { - void* smallbuf[SMALL_ARGS_N]; - if (nargs) runRelease = copy_args(args, nargs, smallbuf); - // CLING_CATCH_UNCAUGHT_ - JC.Invoke(result, {smallbuf, nargs}, self); - // _CLING_CATCH_UNCAUGHT - } else { - std::vector buf(nargs); - runRelease = copy_args(args, nargs, buf.data()); - // CLING_CATCH_UNCAUGHT_ - JC.Invoke(result, {buf.data(), nargs}, self); - // _CLING_CATCH_UNCAUGHT - } - if (runRelease) release_args(args, nargs); - return true; - } - InterOpMutex.unlock(); - return false; -} - -template -static inline -T CallT(Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, size_t nargs, void* args) -{ - T t{}; - if (WrapperCall(method, nargs, args, self.data, &t)) - return t; - throw std::runtime_error("failed to resolve function"); - return (T)-1; -} - -#ifdef PRINT_DEBUG - #define _IMP_CALL_PRINT_STMT(type) \ - printf("IMP CALL with type: %s\n", #type); -#else - #define _IMP_CALL_PRINT_STMT(type) -#endif - -#define CPPYY_IMP_CALL(typecode, rtype) \ -rtype Cppyy::Call##typecode(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args)\ -{ \ - _IMP_CALL_PRINT_STMT(rtype) \ - return CallT(method, self, nargs, args); \ -} - -void Cppyy::CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args) -{ - if (!WrapperCall(method, nargs, args, self.data, nullptr)) - return /* TODO ... report error */; -} - -CPPYY_IMP_CALL(B, unsigned char) -CPPYY_IMP_CALL(C, char ) -CPPYY_IMP_CALL(H, short ) -CPPYY_IMP_CALL(I, int ) -CPPYY_IMP_CALL(L, long ) -CPPYY_IMP_CALL(LL, long long ) -CPPYY_IMP_CALL(F, float ) -CPPYY_IMP_CALL(D, double ) -CPPYY_IMP_CALL(LD, long double ) - -void* Cppyy::CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args) -{ - void* r = nullptr; - if (WrapperCall(method, nargs, args, self.data, &r)) - return r; - return nullptr; -} - -char* Cppyy::CallS( - TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length) -{ - char* cstr = nullptr; - // TClassRef cr("std::string"); // TODO: Why is this required? - std::string* cppresult = (std::string*)malloc(sizeof(std::string)); - if (WrapperCall(method, nargs, args, self.data, (void*)cppresult)) { - cstr = cppstring_to_cstring(*cppresult); - *length = cppresult->size(); - cppresult->std::string::~basic_string(); - } else - *length = 0; - free((void*)cppresult); - return cstr; -} - -Cppyy::TCppObject_t Cppyy::CallConstructor( - TCppMethod_t method, TCppScope_t /*klass*/, size_t nargs, void* args) -{ - void* obj = nullptr; - WrapperCall(method, nargs, args, nullptr, &obj); - return (TCppObject_t)obj; -} - -void Cppyy::CallDestructor(TCppScope_t scope, TCppObject_t self) -{ - std::lock_guard Lock(InterOpMutex); // TODO: this shouldn't locks the JIT call - Cpp::Destruct(self, scope, /*withFree=*/false, /*count=*/0); -} - -Cppyy::TCppObject_t Cppyy::CallO(TCppMethod_t method, - TCppObject_t self, size_t nargs, void* args, TCppType_t result_type) -{ - void* obj = ::operator new(Cppyy::SizeOfType(result_type)); - if (WrapperCall(method, nargs, args, self.data, obj)) - return (TCppObject_t)obj; - ::operator delete(obj); - return TCppObject_t{}; -} - -Cppyy::TCppFuncAddr_t Cppyy::GetFunctionAddress(TCppMethod_t method, bool /*check_enabled*/) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetFunctionAddress(method); -} - - -// handling of function argument buffer -------------------------------------- -void* Cppyy::AllocateFunctionArgs(size_t nargs) -{ - return new Parameter[nargs]; -} - -void Cppyy::DeallocateFunctionArgs(void* args) -{ - delete [] (Parameter*)args; -} - -size_t Cppyy::GetFunctionArgSizeof() -{ - return sizeof(Parameter); -} - -size_t Cppyy::GetFunctionArgTypeoffset() -{ - return offsetof(Parameter, fTypeCode); -} - - -// scope reflection information ---------------------------------------------- -bool Cppyy::IsNamespace(TCppScope_t scope) -{ - if (!scope) - return false; - - // Test if this scope represents a namespace. - std::lock_guard Lock(InterOpMutex); - return Cpp::IsNamespace(scope) || Cpp::GetGlobalScope() == scope; -} - -bool Cppyy::IsClass(TCppScope_t scope) -{ - // Test if this scope represents a namespace. - return Cpp::IsClass(scope); -} -// -bool Cppyy::IsAbstract(TCppScope_t scope) -{ - // Test if this type may not be instantiated. - return Cpp::IsAbstract(scope); -} - -bool Cppyy::IsEnumScope(TCppScope_t scope) -{ - return Cpp::IsEnumScope(scope); -} - -bool Cppyy::IsEnumConstant(TCppScope_t scope) -{ - return Cpp::IsEnumConstant(Cppyy::GetUnderlyingScope(scope)); -} - -bool Cppyy::IsEnumType(TCppType_t type) -{ - return Cpp::IsEnumType(type); -} - -bool Cppyy::IsAggregate(TCppScope_t type) -{ - // Test if this type is a "plain old data" type - return Cpp::IsAggregate(type); -} - -bool Cppyy::IsDefaultConstructable(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); -// Test if this type has a default constructor or is a "plain old data" type - return Cpp::HasDefaultConstructor(scope); -} - -bool Cppyy::IsVariable(TCppScope_t scope) -{ - return Cpp::IsVariable(scope); -} - -// // helpers for stripping scope names -// static -// std::string outer_with_template(const std::string& name) -// { -// // Cut down to the outer-most scope from , taking proper care of templates. -// int tpl_open = 0; -// for (std::string::size_type pos = 0; pos < name.size(); ++pos) { -// std::string::value_type c = name[pos]; -// -// // count '<' and '>' to be able to skip template contents -// if (c == '<') -// ++tpl_open; -// else if (c == '>') -// --tpl_open; -// -// // collect name up to "::" -// else if (tpl_open == 0 && \ -// c == ':' && pos+1 < name.size() && name[pos+1] == ':') { -// // found the extend of the scope ... done -// return name.substr(0, pos-1); -// } -// } -// -// // whole name is apparently a single scope -// return name; -// } -// -// static -// std::string outer_no_template(const std::string& name) -// { -// // Cut down to the outer-most scope from , drop templates -// std::string::size_type first_scope = name.find(':'); -// if (first_scope == std::string::npos) -// return name.substr(0, name.find('<')); -// std::string::size_type first_templ = name.find('<'); -// if (first_templ == std::string::npos) -// return name.substr(0, first_scope); -// return name.substr(0, std::min(first_templ, first_scope)); -// } -// -// #define FILL_COLL(type, filter) { \ -// TIter itr{coll}; \ -// type* obj = nullptr; \ -// while ((obj = (type*)itr.Next())) { \ -// const char* nm = obj->GetName(); \ -// if (nm && nm[0] != '_' && !(obj->Property() & (filter))) { \ -// if (gInitialNames.find(nm) == gInitialNames.end()) \ -// cppnames.insert(nm); \ -// }}} -// -// static inline -// void cond_add(Cppyy::TCppScope_t scope, const std::string& ns_scope, -// std::set& cppnames, const char* name, bool nofilter = false) -// { -// if (!name || name[0] == '_' || strstr(name, ".h") != 0 || strncmp(name, "operator", 8) == 0) -// return; -// -// if (scope == GLOBAL_HANDLE) { -// std::string to_add = outer_no_template(name); -// if (nofilter || gInitialNames.find(to_add) == gInitialNames.end()) -// cppnames.insert(outer_no_template(name)); -// } else if (scope == STD_HANDLE) { -// if (strncmp(name, "std::", 5) == 0) { -// name += 5; -// #ifdef __APPLE__ -// if (strncmp(name, "__1::", 5) == 0) name += 5; -// #endif -// } -// cppnames.insert(outer_no_template(name)); -// } else { -// if (strncmp(name, ns_scope.c_str(), ns_scope.size()) == 0) -// cppnames.insert(outer_with_template(name + ns_scope.size())); -// } -// } - -void Cppyy::GetAllCppNames(TCppScope_t scope, std::set& cppnames) -{ -// Collect all known names of C++ entities under scope. This is useful for IDEs -// employing tab-completion, for example. Note that functions names need not be -// unique as they can be overloaded. - std::lock_guard Lock(InterOpMutex); - Cpp::GetAllCppNames(scope, cppnames); -} - -// class reflection information ---------------------------------------------- -std::vector Cppyy::GetUsingNamespaces(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetUsingNamespaces(scope); -} - -// class reflection information ---------------------------------------------- -std::string Cppyy::GetFinalName(TCppScope_t klass) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetCompleteName(Cpp::GetUnderlyingScope(klass)); -} - -std::string Cppyy::GetScopedFinalName(TCppScope_t klass) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetQualifiedCompleteName(klass); -} - -bool Cppyy::HasVirtualDestructor(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - TCppMethod_t func = Cpp::GetDestructor(scope); - return Cpp::IsVirtualMethod(func); -} - -Cppyy::TCppIndex_t Cppyy::GetNumBases(TCppScope_t klass) -{ -// Get the total number of base classes that this class has. - std::lock_guard Lock(InterOpMutex); - return Cpp::GetNumBases(klass); -} - -//////////////////////////////////////////////////////////////////////////////// -/// \fn Cppyy::TCppIndex_t Cppyy::GetNumBasesLongestBranch(TCppScope_t klass) -/// \brief Retrieve number of base classes in the longest branch of the -/// inheritance tree of the input class. -/// \param[in] klass The class to start the retrieval process from. -/// -/// This is a helper function for Cppyy::GetNumBasesLongestBranch. -/// Given an inheritance tree, the function assigns weight 1 to each class that -/// has at least one base. Starting from the input class, the function is -/// called recursively on all the bases. For each base the return value is one -/// (the weight of the base itself) plus the maximum value retrieved for their -/// bases in turn. For example, given the following inheritance tree: -/// -/// ~~~{.cpp} -/// class A {}; class B: public A {}; -/// class X {}; class Y: public X {}; class Z: public Y {}; -/// class C: public B, Z {}; -/// ~~~ -/// -/// calling this function on an instance of `C` will return 3, the steps -/// required to go from C to X. -Cppyy::TCppIndex_t Cppyy::GetNumBasesLongestBranch(TCppScope_t klass) { - std::vector num; - for (TCppIndex_t ibase = 0; ibase < GetNumBases(klass); ++ibase) - num.push_back(GetNumBasesLongestBranch(Cppyy::GetBaseScope(klass, ibase))); - if (num.empty()) - return 0; - return *std::max_element(num.begin(), num.end()) + 1; -} - -std::string Cppyy::GetBaseName(TCppScope_t klass, TCppIndex_t ibase) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetName(Cpp::GetBaseClass(klass, ibase)); -} - -Cppyy::TCppScope_t Cppyy::GetBaseScope(TCppScope_t klass, TCppIndex_t ibase) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetBaseClass(klass, ibase); -} - -bool Cppyy::IsSubclass(TCppScope_t derived, TCppScope_t base) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::IsSubclass(derived, base); -} - -static std::set gSmartPtrTypes = - {"std::auto_ptr", "std::shared_ptr", "std::unique_ptr", "std::weak_ptr"}; - -bool Cppyy::IsSmartPtr(TCppScope_t klass) -{ - const std::string& rn = Cppyy::GetScopedFinalName(klass); - if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) != gSmartPtrTypes.end()) - return true; - return false; -} - -bool Cppyy::GetSmartPtrInfo( - const std::string& tname, TCppScope_t* raw, TCppMethod_t* deref) -{ - // TODO: We can directly accept scope instead of name - const std::string& rn = ResolveName(tname); - if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) == gSmartPtrTypes.end()) - return false; - - if (!raw && !deref) return true; - - TCppScope_t scope = Cppyy::GetScope(rn); - if (!scope) - return false; - - std::vector ops; - { - std::lock_guard Lock(InterOpMutex); - Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops, - /*kind=*/Cpp::OperatorArity::kBoth); - } - if (ops.size() != 1) - return false; - - if (deref) *deref = ops[0]; - if (raw) *raw = Cppyy::GetScopeFromType(Cppyy::GetMethodReturnType(ops[0])); - return (!deref || *deref) && (!raw || *raw); -} - -// type offsets -------------------------------------------------------------- -ptrdiff_t Cppyy::GetBaseOffset(TCppScope_t derived, TCppScope_t base, - TCppObject_t /*address*/, int direction, bool rerror) -{ - std::lock_guard Lock(InterOpMutex); - intptr_t offset = Cpp::GetBaseClassOffset(derived, base); - - if (offset == -1) // Cling error, treat silently - return rerror ? (ptrdiff_t)offset : 0; - - return (ptrdiff_t)(direction < 0 ? -offset : offset); -} - -// method/function reflection information ------------------------------------ -void Cppyy::GetClassMethods(TCppScope_t scope, std::vector &methods) -{ - std::lock_guard Lock(InterOpMutex); - Cpp::GetClassMethods(scope, methods); -} - -std::vector Cppyy::GetMethodsFromName( - TCppScope_t scope, const std::string& name) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetFunctionsUsingName(scope, name); -} - -std::string Cppyy::GetName(TCppScope_t method) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetName(method); -} - -std::string Cppyy::GetFullName(TCppScope_t method) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetCompleteName(method); -} - -Cppyy::TCppType_t Cppyy::GetMethodReturnType(TCppMethod_t method) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetFunctionReturnType(method); -} - -std::string Cppyy::GetMethodReturnTypeAsString(TCppMethod_t method) -{ - std::lock_guard Lock(InterOpMutex); - return - Cpp::GetTypeAsString( - Cpp::GetCanonicalType( - Cpp::GetFunctionReturnType(method))); -} - -Cppyy::TCppIndex_t Cppyy::GetMethodNumArgs(TCppMethod_t method) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetFunctionNumArgs(method); -} - -Cppyy::TCppIndex_t Cppyy::GetMethodReqArgs(TCppMethod_t method) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetFunctionRequiredArgs(method); -} - -std::string Cppyy::GetMethodArgName(TCppMethod_t method, TCppIndex_t iarg) -{ - if (!method) - return ""; - - std::lock_guard Lock(InterOpMutex); - return Cpp::GetFunctionArgName(method, iarg); -} - -Cppyy::TCppType_t Cppyy::GetMethodArgType(TCppMethod_t method, TCppIndex_t iarg) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetFunctionArgType(method, iarg); -} - -std::string Cppyy::GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier( - Cpp::GetFunctionArgType(method, iarg), Cpp::QualKind::Const)); -} - -std::string Cppyy::GetMethodArgCanonTypeAsString(TCppMethod_t method, TCppIndex_t iarg) -{ - std::lock_guard Lock(InterOpMutex); - return - Cpp::GetTypeAsString( - Cpp::GetCanonicalType( - Cpp::GetFunctionArgType(method, iarg))); -} - -std::string Cppyy::GetMethodArgDefault(TCppMethod_t method, TCppIndex_t iarg) -{ - if (!method) - return ""; - - std::lock_guard Lock(InterOpMutex); - return Cpp::GetFunctionArgDefault(method, iarg); -} - -Cppyy::TCppIndex_t Cppyy::CompareMethodArgType(TCppMethod_t /*method*/, TCppIndex_t iarg, const std::string &req_type) -{ - // if (method) { - // TFunction* f = m2f(method); - // TMethodArg* arg = (TMethodArg *)f->GetListOfMethodArgs()->At((int)iarg); - // void *argqtp = gInterpreter->TypeInfo_QualTypePtr(arg->GetTypeInfo()); - - // TypeInfo_t *reqti = gInterpreter->TypeInfo_Factory(req_type.c_str()); - // void *reqqtp = gInterpreter->TypeInfo_QualTypePtr(reqti); - - // if (ArgSimilarityScore(argqtp, reqqtp) < 10) { - // return ArgSimilarityScore(argqtp, reqqtp); - // } - // else { // Match using underlying types - // if(gInterpreter->IsPointerType(argqtp)) - // argqtp = gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetPointerType(argqtp)); - - // // Handles reference types and strips qualifiers - // TypeInfo_t *arg_ul = gInterpreter->GetNonReferenceType(argqtp); - // TypeInfo_t *req_ul = gInterpreter->GetNonReferenceType(reqqtp); - // argqtp = gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(arg_ul))); - // reqqtp = gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(req_ul))); - - // return ArgSimilarityScore(argqtp, reqqtp); - // } - // } - return 0; // Method is not valid -} - -std::string Cppyy::GetMethodSignature(TCppMethod_t method, bool show_formal_args, TCppIndex_t max_args) -{ - std::ostringstream sig; - sig << "("; - int nArgs = GetMethodNumArgs(method); - if (max_args != (TCppIndex_t)-1) nArgs = std::min(nArgs, (int)max_args); - for (int iarg = 0; iarg < nArgs; ++iarg) { - sig << Cppyy::GetMethodArgTypeAsString(method, iarg); - if (show_formal_args) { - std::string argname = Cppyy::GetMethodArgName(method, iarg); - if (!argname.empty()) sig << " " << argname; - std::string defvalue = Cppyy::GetMethodArgDefault(method, iarg); - if (!defvalue.empty()) sig << " = " << defvalue; - } - if (iarg != nArgs-1) sig << ", "; - } - sig << ")"; - return sig.str(); -} - -Cppyy::TCppType_t Cppyy::GetFnTypeFromStdFn(TCppType_t fn_type) { - fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type) : fn_type; - fn_type = Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type; - TCppScope_t scope = Cpp::GetScopeFromType(fn_type); - std::vector args; - Cpp::GetClassTemplateArgs(scope, args); - assert(args.size() == 1); - if (args.size() == 1) - return args[0].m_Type; - return nullptr; -} - -void Cppyy::GetFnTypeSig(TCppType_t fn_type, std::vector& arg_types) { - fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type) : fn_type; - fn_type = Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type; - Cpp::GetFnTypeSignature(fn_type, arg_types); -} - -bool Cppyy::IsSameType(TCppType_t typ1, TCppType_t typ2) { - return Cpp::IsSameType(typ1, typ2); -} - -bool Cppyy::IsFunctionType(TCppType_t typ) { - typ = Cpp::IsReferenceType(typ) ? Cpp::GetNonReferenceType(typ) : typ; - typ = Cpp::IsPointerType(typ) ? Cpp::GetPointeeType(typ) : typ; - return Cpp::IsFunctionProtoType(typ); -} - -bool Cppyy::IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2) { - typ1 = Cpp::IsReferenceType(typ1) ? Cpp::GetNonReferenceType(typ1) : typ1; - typ2 = Cpp::IsReferenceType(typ2) ? Cpp::GetNonReferenceType(typ2) : typ2; - typ1 = Cpp::IsPointerType(typ1) ? Cpp::GetPointeeType(typ1) : typ1; - typ2 = Cpp::IsPointerType(typ2) ? Cpp::GetPointeeType(typ2) : typ2; - return Cpp::IsSameType(typ1, typ2); -} - -std::string Cppyy::GetDoxygenComment(TCppScope_t scope, bool strip_markers) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetDoxygenComment(scope, strip_markers); -} - -bool Cppyy::IsConstMethod(TCppMethod_t method) -{ - if (!method) - return false; - std::lock_guard Lock(InterOpMutex); - return Cpp::IsConstMethod(method); -} - -void Cppyy::GetTemplatedMethods(TCppScope_t scope, std::vector &methods) -{ - std::lock_guard Lock(InterOpMutex); - Cpp::GetFunctionTemplatedDecls(scope, methods); -} - -Cppyy::TCppIndex_t Cppyy::GetNumTemplatedMethods(TCppScope_t scope, bool /*accept_namespace*/) -{ - std::lock_guard Lock(InterOpMutex); - std::vector mc; - Cpp::GetFunctionTemplatedDecls(scope, mc); - return mc.size(); -} - -std::string Cppyy::GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth) -{ - std::lock_guard Lock(InterOpMutex); - std::vector mc; - Cpp::GetFunctionTemplatedDecls(scope, mc); - - if (imeth < mc.size()) return Cpp::GetName(TCppScope_t(mc[imeth].data)); - - return ""; -} - -bool Cppyy::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::ExistsFunctionTemplate(name, scope); -} - -bool Cppyy::IsTemplatedMethod(TCppMethod_t method) -{ - return Cpp::IsTemplatedFunction(method); -} - -bool Cppyy::IsStaticTemplate(TCppScope_t scope, const std::string& name) -{ - std::vector candidate_methods; - Cpp::GetClassTemplatedMethods(name, scope, candidate_methods); - bool is_static = true; - for (auto i: candidate_methods) { - if (!Cpp::IsStaticMethod(i)) { - is_static = false; - break; - } - } - return is_static; -} - -Cppyy::TCppMethod_t Cppyy::GetMethodTemplate( - TCppScope_t scope, const std::string& name, const std::string& proto) -{ - std::string pureName; - std::string explicit_params; - - if ((name.find("operator<") != 0) && - (name.find('<') != std::string::npos)) { - pureName = name.substr(0, name.find('<')); - size_t start = name.find('<'); - size_t end = name.rfind('>'); - explicit_params = name.substr(start + 1, end - start - 1); - } else { - pureName = name; - } - - std::lock_guard Lock(InterOpMutex); - - std::vector unresolved_candidate_methods; - Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods); - if (unresolved_candidate_methods.empty() && name.find("operator") == 0) { - // try operators - Cppyy::GetClassOperators(scope, pureName, unresolved_candidate_methods); - } - - // CPyCppyy assumes that we attempt instantiation here - std::vector arg_types; - std::vector templ_params; - Cppyy::AppendTypesSlow(proto, arg_types, scope); - Cppyy::AppendTypesSlow(explicit_params, templ_params, scope); - Cppyy::TCppMethod_t cppmeth = nullptr; - cppmeth = Cpp::BestOverloadFunctionMatch( - unresolved_candidate_methods, templ_params, arg_types); - - // If overload resolution failed but explicit template arguments were - // supplied, fall back to direct template-argument substitution: ask Sema - // to instantiate each candidate with the explicit args. Sema's SFINAE - // rejects overloads whose substitution fails (e.g. the initializer_list - // form of std::make_any with non-init-list explicit args), so iterating - // gives back exactly the viable specialisation. The wrapper-side argument - // conversion then handles e.g. taking the address of an instance when the - // substituted parameter is a pointer. - if (!cppmeth && !templ_params.empty()) { - for (const auto& cand : unresolved_candidate_methods) { - if (Cpp::DeclRef spec = Cpp::InstantiateTemplate( - TCppScope_t(cand.data), templ_params.data(), - templ_params.size(), /*instantiate_body=*/false)) { - cppmeth = spec.data; - break; - } - } - } - - return TCppMethod_t(cppmeth.data); - // if it fails, use Sema to propogate info about why it failed (DeductionInfo) -} - -static inline std::string type_remap(const std::string& n1, - const std::string& n2) { - // Operator lookups of (C++ string, Python str) should succeed for the - // combos of string/str, wstring/str, string/unicode and wstring/unicode; - // since C++ does not have a operator+(std::string, std::wstring), we'll - // have to look up the same type and rely on the converters in - // CPyCppyy/_cppyy. - if (n1 == "str" || n1 == "unicode" || n1 == "std::basic_string") { - if (n2 == "std::basic_string") - return "std::basic_string&"; // match like for like - return "std::basic_string&"; // probably best bet - } else if (n1 == "std::basic_string") { - return "std::basic_string&"; - } else if (n1 == "complex") { - return "std::complex"; - } - return n1; -} - -void Cppyy::GetClassOperators(Cppyy::TCppScope_t klass, - const std::string& opname, - std::vector& operators) { - std::lock_guard Lock(InterOpMutex); - std::string op = opname.substr(8); - Cpp::GetOperator(klass, Cpp::GetOperatorFromSpelling(op), operators, - /*kind=*/Cpp::OperatorArity::kBoth); -} - -Cppyy::TCppMethod_t Cppyy::GetGlobalOperator( - TCppScope_t scope, const std::string& lc, const std::string& rc, const std::string& opname) -{ - std::string rc_type = type_remap(rc, lc); - std::string lc_type = type_remap(lc, rc); - - std::vector overloads; - Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads, - /*kind=*/Cpp::OperatorArity::kBoth); - - // Avoid pushing nullptr into arg_types which would crash - // BestOverloadFunctionMatch when it dereferences each entry's QualType. - auto resolve_arg_type = [](const std::string& name) -> Cppyy::TCppType_t { - if (auto s = Cppyy::GetScope(name)) - if (auto t = Cppyy::GetTypeFromScope(s)) - return Cppyy::GetReferencedType(t); - return Cppyy::GetType(name, /*enable_slow_lookup=*/true); - }; - - std::vector arg_types; - if (auto l = resolve_arg_type(lc_type)) - arg_types.emplace_back(l.data); - else - return nullptr; - - if (!rc_type.empty()) { - if (auto r = resolve_arg_type(rc_type)) - arg_types.emplace_back(r.data); - else - return nullptr; - } - Cppyy::TCppMethod_t cppmeth = Cpp::BestOverloadFunctionMatch( - overloads, {}, arg_types); - if (cppmeth) - return cppmeth; - return nullptr; -} - -// method properties --------------------------------------------------------- -bool Cppyy::IsDeletedMethod(TCppMethod_t method) -{ - return Cpp::IsFunctionDeleted(method); -} - -bool Cppyy::IsPublicMethod(TCppMethod_t method) -{ - return Cpp::IsPublicMethod(method); -} - -bool Cppyy::IsProtectedMethod(TCppMethod_t method) -{ - return Cpp::IsProtectedMethod(method); -} - -bool Cppyy::IsPrivateMethod(TCppMethod_t method) -{ - return Cpp::IsPrivateMethod(method); -} - -bool Cppyy::IsConstructor(TCppMethod_t method) -{ - return Cpp::IsConstructor(method); -} - -bool Cppyy::IsDestructor(TCppMethod_t method) -{ - return Cpp::IsDestructor(method); -} - -bool Cppyy::IsStaticMethod(TCppMethod_t method) -{ - return Cpp::IsStaticMethod(method); -} - -bool Cppyy::IsExplicit(TCppMethod_t method) -{ - return Cpp::IsExplicit(method); -} - -// data member reflection information ---------------------------------------- -void Cppyy::GetDatamembers(TCppScope_t scope, std::vector& datamembers) -{ - std::lock_guard Lock(InterOpMutex); - Cpp::GetDatamembers(scope, datamembers); - Cpp::GetStaticDatamembers(scope, datamembers); - Cpp::GetEnumConstantDatamembers(scope, datamembers, false); -} - -bool Cppyy::CheckDatamember(TCppScope_t scope, const std::string& name) { - std::lock_guard Lock(InterOpMutex); - return (bool) Cpp::LookupDatamember(name, scope); -} - -bool Cppyy::IsLambdaClass(TCppType_t type) { - return Cpp::IsLambdaClass(type); -} - -Cppyy::TCppScope_t Cppyy::WrapLambdaFromVariable(TCppScope_t var) { - std::lock_guard Lock(InterOpMutex); - std::ostringstream code; - std::string name = Cppyy::GetFinalName(var); - code << "namespace __cppyy_internal_wrap_g {\n" - << " " << "std::function " << name << " = ::" << Cpp::GetQualifiedName(var) << ";\n" - << "}\n"; - - if (Cppyy::Compile(code.str().c_str())) { - TCppScope_t res = Cpp::GetNamed( - name, Cpp::GetScope("__cppyy_internal_wrap_g", /*parent=*/nullptr)); - if (res) return res; - } - return var; -} - -Cppyy::TCppMethod_t Cppyy::AdaptFunctionForLambdaReturn(Cppyy::TCppMethod_t fn) { - std::lock_guard Lock(InterOpMutex); - - std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data)); - std::string signature = Cppyy::GetMethodSignature(fn, true); - - std::ostringstream call; - call << "("; - for (size_t i = 0, n = Cppyy::GetMethodNumArgs(fn); i < n; i++) { - call << Cppyy::GetMethodArgName(fn, i); - if (i != n - 1) - call << ", "; - } - call << ")"; - - std::ostringstream code; - static int i = 0; - std::string name = "lambda_return_convert_" + std::to_string(++i); - code << "namespace __cppyy_internal_wrap_g {\n" - << "auto " << name << signature << "{" << "return std::function(" << fn_name << call.str() << "); }\n" - << "}\n"; - if (Cppyy::Compile(code.str().c_str())) { - TCppScope_t res = Cpp::GetNamed( - name, Cpp::GetScope("__cppyy_internal_wrap_g", /*parent=*/nullptr)); - if (res) return TCppMethod_t(res.data); - } - return fn; -} - -Cppyy::TCppType_t Cppyy::GetDatamemberType(TCppScope_t var) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetVariableType(Cpp::GetUnderlyingScope(var)); -} - -std::string Cppyy::GetDatamemberTypeAsString(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetTypeAsString( - Cpp::GetVariableType(Cpp::GetUnderlyingScope(scope))); -} - -std::string Cppyy::GetTypeAsString(TCppType_t type) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetTypeAsString(type); -} - -intptr_t Cppyy::GetDatamemberOffset(TCppScope_t var, TCppScope_t klass) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetVariableOffset(Cpp::GetUnderlyingScope(var), klass); -} - -// data member properties ---------------------------------------------------- -bool Cppyy::IsPublicData(TCppScope_t datamem) -{ - return Cpp::IsPublicVariable(datamem); -} - -bool Cppyy::IsProtectedData(TCppScope_t datamem) -{ - return Cpp::IsProtectedVariable(datamem); -} - -bool Cppyy::IsPrivateData(TCppScope_t datamem) -{ - return Cpp::IsPrivateVariable(datamem); -} - -bool Cppyy::IsStaticDatamember(TCppScope_t var) -{ - return Cpp::IsStaticVariable(Cppyy::GetUnderlyingScope(var)); -} - -bool Cppyy::IsConstVar(TCppScope_t var) -{ - return Cpp::IsConstVariable(var); -} - -Cppyy::TCppMethod_t Cppyy::ReduceReturnType(TCppMethod_t fn, TCppType_t reduce) { - std::lock_guard Lock(InterOpMutex); - - std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data)); - std::string signature = Cppyy::GetMethodSignature(fn, true); - std::string result_type = Cppyy::GetTypeAsString(reduce); - - std::ostringstream call; - call << "("; - for (size_t i = 0, n = Cppyy::GetMethodNumArgs(fn); i < n; i++) { - call << Cppyy::GetMethodArgName(fn, i); - if (i != n - 1) - call << ", "; - } - call << ")"; - - std::ostringstream code; - static int i = 0; - std::string name = "reduced_function_" + std::to_string(++i); - code << "namespace __cppyy_internal_wrap_g {\n" - << result_type << " " << name << signature << "{" << "return (" << result_type << ")::" << fn_name << call.str() << "; }\n" - << "}\n"; - if (Cppyy::Compile(code.str().c_str())) { - TCppScope_t res = Cpp::GetNamed( - name, Cpp::GetScope("__cppyy_internal_wrap_g", /*parent=*/nullptr)); - if (res) return TCppMethod_t(res.data); - } - return fn; -} - -std::vector Cppyy::GetDimensions(TCppType_t type) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetDimensions(type); -} - -// enum properties ----------------------------------------------------------- -std::vector Cppyy::GetEnumConstants(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetEnumConstants(scope); -} - -Cppyy::TCppType_t Cppyy::GetEnumConstantType(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope)); -} - -Cppyy::TCppIndex_t Cppyy::GetEnumDataValue(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::GetEnumConstantValue(scope); -} - -Cppyy::TCppScope_t Cppyy::InstantiateTemplate( - TCppScope_t tmpl, Cpp::TemplateArgInfo* args, size_t args_size) -{ - std::lock_guard Lock(InterOpMutex); - return Cpp::InstantiateTemplate(tmpl, args, args_size, - /*instantiate_body=*/false); -} - -void Cppyy::DumpScope(TCppScope_t scope) -{ - std::lock_guard Lock(InterOpMutex); - Cpp::DumpScope(scope); -} diff --git a/src/backend/cpp_cppyy.h b/src/backend/cpp_cppyy.h deleted file mode 100644 index 8b2bbcf..0000000 --- a/src/backend/cpp_cppyy.h +++ /dev/null @@ -1,391 +0,0 @@ -#ifndef CPYCPPYY_CPPYY_H -#define CPYCPPYY_CPPYY_H - -#include -#include - -// Standard -#include -#include -#include -#include -#include -#include - -#include "precommondefs.h" -#include "callcontext.h" - -// some more types; assumes Cppyy.h follows Python.h -#ifndef PY_LONG_LONG -#ifdef _WIN32 -typedef __int64 PY_LONG_LONG; -#else -typedef long long PY_LONG_LONG; -#endif -#endif - -#ifndef PY_ULONG_LONG -#ifdef _WIN32 -typedef unsigned __int64 PY_ULONG_LONG; -#else -typedef unsigned long long PY_ULONG_LONG; -#endif -#endif - -#ifndef PY_LONG_DOUBLE -typedef long double PY_LONG_DOUBLE; -#endif - -typedef CPyCppyy::Parameter Parameter; - -// small number that allows use of stack for argument passing -const int SMALL_ARGS_N = 8; - -// convention to pass flag for direct calls (similar to Python's vector calls) -#define DIRECT_CALL ((size_t)1 << (8 * sizeof(size_t) - 1)) -static inline size_t CALL_NARGS(size_t nargs) { - return nargs & ~DIRECT_CALL; -} - -namespace Cppyy { - typedef Cpp::DeclRef TCppScope_t; - typedef Cpp::TypeRef TCppType_t; - typedef Cpp::ObjectRef TCppObject_t; - typedef Cpp::FuncRef TCppMethod_t; - typedef Cpp::InterpRef TInterp_t; - typedef size_t TCppIndex_t; - typedef void* TCppFuncAddr_t; - -// direct interpreter access ------------------------------------------------- - RPY_EXPORTED - bool Compile(const std::string& code, bool silent = false); - RPY_EXPORTED - std::string ToString(TCppScope_t klass, TCppObject_t obj); - -// name to opaque C++ scope representation ----------------------------------- - RPY_EXPORTED - std::string ResolveName(const std::string& cppitem_name); - RPY_EXPORTED - TCppType_t ResolveType(TCppType_t cppitem_name); - RPY_EXPORTED - TCppType_t ResolveEnumReferenceType(TCppType_t type); - RPY_EXPORTED - TCppType_t ResolveEnumPointerType(TCppType_t type); - RPY_EXPORTED - TCppType_t GetRealType(TCppType_t type); - RPY_EXPORTED - TCppType_t GetPointerType(TCppType_t type); - RPY_EXPORTED - TCppType_t GetReferencedType(TCppType_t type, bool rvalue = false); - RPY_EXPORTED - std::string ResolveEnum(TCppScope_t enum_scope); - RPY_EXPORTED - bool IsLValueReferenceType(TCppType_t type); - RPY_EXPORTED - bool IsRValueReferenceType(TCppType_t type); - RPY_EXPORTED - bool IsClassType(TCppType_t type); - RPY_EXPORTED - bool IsIntegerType(TCppType_t type, bool* is_signed = nullptr); - RPY_EXPORTED - bool IsPointerType(TCppType_t type); - RPY_EXPORTED - bool IsFunctionPointerType(TCppType_t type); - RPY_EXPORTED - TCppType_t GetType(const std::string &name, bool enable_slow_lookup = false); - RPY_EXPORTED - bool AppendTypesSlow(const std::string &name, - std::vector& types, Cppyy::TCppScope_t parent = nullptr); - RPY_EXPORTED - TCppType_t GetComplexType(const std::string &element_type); - RPY_EXPORTED - TCppScope_t GetScope(const std::string& scope_name, - TCppScope_t parent_scope = TCppScope_t{}); - RPY_EXPORTED - TCppScope_t GetUnderlyingScope(TCppScope_t scope); - RPY_EXPORTED - TCppScope_t GetFullScope(const std::string& scope_name); - RPY_EXPORTED - TCppScope_t GetTypeScope(TCppScope_t klass); - RPY_EXPORTED - TCppScope_t GetNamed(const std::string& scope_name, - TCppScope_t parent_scope = TCppScope_t{}); - RPY_EXPORTED - TCppScope_t GetParentScope(TCppScope_t scope); - RPY_EXPORTED - TCppScope_t GetScopeFromType(TCppType_t type); - RPY_EXPORTED - TCppType_t GetTypeFromScope(TCppScope_t klass); - RPY_EXPORTED - TCppScope_t GetGlobalScope(); - RPY_EXPORTED - TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); - RPY_EXPORTED - size_t SizeOf(TCppScope_t klass); - RPY_EXPORTED - size_t SizeOfType(TCppType_t type); - - RPY_EXPORTED - bool IsBuiltin(const std::string& type_name); - - RPY_EXPORTED - bool IsBuiltin(TCppType_t type); - - RPY_EXPORTED - bool IsComplete(TCppScope_t type); - -// memory management --------------------------------------------------------- - RPY_EXPORTED - TCppObject_t Allocate(TCppScope_t scope); - RPY_EXPORTED - void Deallocate(TCppScope_t scope, TCppObject_t instance); - RPY_EXPORTED - TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); - RPY_EXPORTED - void Destruct(TCppScope_t scope, TCppObject_t instance); - -// method/function dispatching ----------------------------------------------- - RPY_EXPORTED - void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - unsigned char CallB(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - char CallC(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - short CallH(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - int CallI(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - long CallL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - PY_LONG_LONG CallLL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - float CallF(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - double CallD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - PY_LONG_DOUBLE CallLD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - - RPY_EXPORTED - void* CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); - RPY_EXPORTED - char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length); - RPY_EXPORTED - TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, size_t nargs, void* args); - RPY_EXPORTED - void CallDestructor(TCppScope_t type, TCppObject_t self); - RPY_EXPORTED - TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, TCppType_t result_type); - - RPY_EXPORTED - TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, bool check_enabled=true); - -// handling of function argument buffer -------------------------------------- - RPY_EXPORTED - void* AllocateFunctionArgs(size_t nargs); - RPY_EXPORTED - void DeallocateFunctionArgs(void* args); - RPY_EXPORTED - size_t GetFunctionArgSizeof(); - RPY_EXPORTED - size_t GetFunctionArgTypeoffset(); - -// scope reflection information ---------------------------------------------- - RPY_EXPORTED - bool IsNamespace(TCppScope_t scope); - RPY_EXPORTED - bool IsClass(TCppScope_t scope); - RPY_EXPORTED - bool IsTemplate(TCppScope_t scope); - RPY_EXPORTED - bool IsTemplateInstantiation(TCppScope_t scope); - RPY_EXPORTED - bool IsTypedefed(TCppScope_t scope); - RPY_EXPORTED - bool IsAbstract(TCppScope_t scope); - RPY_EXPORTED - bool IsEnumScope(TCppScope_t scope); - RPY_EXPORTED - bool IsEnumConstant(TCppScope_t scope); - RPY_EXPORTED - bool IsEnumType(TCppType_t type); - RPY_EXPORTED - bool IsAggregate(TCppScope_t type); - RPY_EXPORTED - bool IsDefaultConstructable(TCppScope_t scope); - RPY_EXPORTED - bool IsVariable(TCppScope_t scope); - - RPY_EXPORTED - void GetAllCppNames(TCppScope_t scope, std::set& cppnames); - -// namespace reflection information ------------------------------------------ - RPY_EXPORTED - std::vector GetUsingNamespaces(TCppScope_t); - -// class reflection information ---------------------------------------------- - RPY_EXPORTED - std::string GetFinalName(TCppScope_t type); - RPY_EXPORTED - std::string GetScopedFinalName(TCppScope_t type); - RPY_EXPORTED - bool HasVirtualDestructor(TCppScope_t type); - RPY_EXPORTED - TCppIndex_t GetNumBases(TCppScope_t klass); - RPY_EXPORTED - TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass); - RPY_EXPORTED - std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase); - RPY_EXPORTED - TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase); - RPY_EXPORTED - bool IsSubclass(TCppScope_t derived, TCppScope_t base); - RPY_EXPORTED - bool IsSmartPtr(TCppScope_t klass); - RPY_EXPORTED - bool GetSmartPtrInfo(const std::string&, TCppScope_t* raw, TCppMethod_t* deref); -// calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 - RPY_EXPORTED - ptrdiff_t GetBaseOffset( - TCppScope_t derived, TCppScope_t base, TCppObject_t address, int direction, bool rerror = false); - -// method/function reflection information ------------------------------------ - RPY_EXPORTED - void GetClassMethods(TCppScope_t scope, std::vector &methods); - RPY_EXPORTED - std::vector GetMethodsFromName(TCppScope_t scope, - const std::string& name); - RPY_EXPORTED - std::string GetName(TCppScope_t); - RPY_EXPORTED - std::string GetFullName(TCppScope_t); - RPY_EXPORTED - TCppType_t GetMethodReturnType(TCppMethod_t); - RPY_EXPORTED - std::string GetMethodReturnTypeAsString(TCppMethod_t); - RPY_EXPORTED - TCppIndex_t GetMethodNumArgs(TCppMethod_t); - RPY_EXPORTED - TCppIndex_t GetMethodReqArgs(TCppMethod_t); - RPY_EXPORTED - std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); - RPY_EXPORTED - TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); - RPY_EXPORTED - TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, const std::string &req_type); - RPY_EXPORTED - std::string GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg); - RPY_EXPORTED - std::string GetMethodArgCanonTypeAsString(TCppMethod_t method, TCppIndex_t iarg); - RPY_EXPORTED - std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); - RPY_EXPORTED - std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, TCppIndex_t max_args = (TCppIndex_t)-1); - RPY_EXPORTED - bool IsFunctionType(TCppType_t typ); - RPY_EXPORTED - TCppType_t GetFnTypeFromStdFn(TCppType_t fn_type); - RPY_EXPORTED - void GetFnTypeSig(TCppType_t fn_type, std::vector& arg_types); - RPY_EXPORTED - bool IsSameType(TCppType_t typ1, TCppType_t typ2); - RPY_EXPORTED - bool IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2); - RPY_EXPORTED - std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); - RPY_EXPORTED - bool IsConstMethod(TCppMethod_t); -// Templated method/function reflection information ------------------------------------ - RPY_EXPORTED - void GetTemplatedMethods(TCppScope_t scope, std::vector &methods); - RPY_EXPORTED - TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace = false); - RPY_EXPORTED - std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); - RPY_EXPORTED - bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); - RPY_EXPORTED - bool IsTemplatedMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsStaticTemplate(TCppScope_t scope, const std::string& name); - RPY_EXPORTED - TCppMethod_t GetMethodTemplate( - TCppScope_t scope, const std::string& name, const std::string& proto); - RPY_EXPORTED - void GetClassOperators(Cppyy::TCppScope_t klass, const std::string& opname, - std::vector& operators); - RPY_EXPORTED - TCppMethod_t GetGlobalOperator( - TCppScope_t scope, const std::string& lc, const std::string& rc, const std::string& op); - -// method properties --------------------------------------------------------- - RPY_EXPORTED - bool IsDeletedMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsPublicMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsProtectedMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsPrivateMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsConstructor(TCppMethod_t method); - RPY_EXPORTED - bool IsDestructor(TCppMethod_t method); - RPY_EXPORTED - bool IsStaticMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsExplicit(TCppMethod_t method); - -// data member reflection information ---------------------------------------- - RPY_EXPORTED - void GetDatamembers(TCppScope_t scope, std::vector& datamembers); - RPY_EXPORTED - bool IsLambdaClass(TCppType_t type); - RPY_EXPORTED - TCppScope_t WrapLambdaFromVariable(TCppScope_t var); - RPY_EXPORTED - TCppMethod_t AdaptFunctionForLambdaReturn(TCppMethod_t fn); - RPY_EXPORTED - TCppType_t GetDatamemberType(TCppScope_t data); - RPY_EXPORTED - std::string GetDatamemberTypeAsString(TCppScope_t var); - RPY_EXPORTED - std::string GetTypeAsString(TCppType_t type); - RPY_EXPORTED - intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); - RPY_EXPORTED - bool CheckDatamember(TCppScope_t scope, const std::string& name); - -// // data member properties ---------------------------------------------------- - RPY_EXPORTED - bool IsPublicData(TCppScope_t var); - RPY_EXPORTED - bool IsProtectedData(TCppScope_t var); - RPY_EXPORTED - bool IsPrivateData(TCppScope_t var); - RPY_EXPORTED - bool IsStaticDatamember(TCppScope_t var); - RPY_EXPORTED - bool IsConstVar(TCppScope_t var); - RPY_EXPORTED - TCppMethod_t ReduceReturnType(TCppMethod_t fn, TCppType_t reduce); - RPY_EXPORTED - std::vector GetDimensions(TCppType_t type); - -// enum properties ----------------------------------------------------------- - RPY_EXPORTED - std::vector GetEnumConstants(TCppScope_t scope); - RPY_EXPORTED - TCppType_t GetEnumConstantType(TCppScope_t scope); - RPY_EXPORTED - TCppIndex_t GetEnumDataValue(TCppScope_t scope); - - RPY_EXPORTED - TCppScope_t InstantiateTemplate( - TCppScope_t tmpl, Cpp::TemplateArgInfo* args, size_t args_size); - - RPY_EXPORTED - void DumpScope(TCppScope_t scope); -} // namespace Cppyy - -#endif // !CPYCPPYY_CPPYY_H diff --git a/src/cpyrt/API.cxx b/src/cpyrt/API.cxx new file mode 100644 index 0000000..c5aea0a --- /dev/null +++ b/src/cpyrt/API.cxx @@ -0,0 +1,471 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "cppjit_interop.h" +#define CPYRT_INTERNAL 1 +#include "cpyrt/API.h" +#undef CPYRT_INTERNAL + +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "cpyrt/DispatchPtr.h" + +// Standard +#include +#include +#include + +//______________________________________________________________________________ +// cpyrt API: Interpreter and Proxy Access +// ========================================== +// +// Access to cppjit Python objects from Cling and C++: allows conversion for +// instances and type checking for scopes, instances, etc. +// Adds a few convenience functions to call Python from Cling and expose Python +// classes to Cling for use in inheritance etc. + +//- data --------------------------------------------------------------------- +static PyObject* gMainDict = nullptr; + +namespace cppjit::cpyrt { +extern PyObject* gThisModule; +} + +//- private helpers ---------------------------------------------------------- +namespace { + +static bool Initialize() { + // Private initialization method: setup the python interpreter and load the + // cppjit module. + static bool isInitialized = false; + if (isInitialized) + return true; + + if (!Py_IsInitialized()) { + // this happens if Cling comes in first + PyConfig config; + PyConfig_InitPythonConfig(&config); + PyConfig_SetString(&config, &config.program_name, L"cppjit"); + Py_InitializeFromConfig(&config); + + // try again to see if the interpreter is initialized + if (!Py_IsInitialized()) { + // give up ... + std::cerr << "Error: python has not been initialized; returning." + << std::endl; + return false; + } + + // force loading of the cppjit module + PyRun_SimpleString(const_cast("import cppjit")); + } + + if (!gMainDict) { + cppjit::cpyrt::PythonGILRAII python_gil_raii; + // retrieve the main dictionary + gMainDict = + PyModule_GetDict(PyImport_AddModule(const_cast("__main__"))); + // The gMainDict is borrowed, i.e. we are not calling Py_INCREF(gMainDict). + // Like this, we avoid unexpectedly affecting how long __main__ is kept + // alive. The gMainDict is only used in Exec(), ExecScript(), and Eval(), + // which should not be called after __main__ is garbage collected anyway. + } + + // declare success ... + isInitialized = true; + return true; +} + +} // unnamed namespace + +//- C++ access to cppjit objects --------------------------------------------- +std::string cpyrt::Instance_GetScopedFinalName(PyObject* pyobject) { + if (!Instance_Check(pyobject)) { + PyErr_SetString( + PyExc_TypeError, + "Instance_GetScopedFinalName : object is not a C++ instance"); + return ""; + } + + interop::TCppScope_t pyobjectClass = ((CPPInstance*)pyobject)->ObjectIsA(); + return interop::GetScopedFinalName(pyobjectClass); +} + +//----------------------------------------------------------------------------- +void* cpyrt::Instance_AsVoidPtr(PyObject* pyobject) { + // Extract the object pointer held by the CPPInstance pyobject. + if (!Initialize()) + return nullptr; + + PythonGILRAII python_gil_raii; + + // check validity of cast + if (!CPPInstance_Check(pyobject)) + return nullptr; + + // get held object (may be null) + return ((CPPInstance*)pyobject)->GetObject(); +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::Instance_FromVoidPtr(void* addr, const std::string& classname, + bool python_owns) { + // Bind the addr to a python object of class defined by classname. + if (!Initialize()) + return nullptr; + + PythonGILRAII python_gil_raii; + + // perform cast (the call will check TClass and addr, and set python errors) + PyObject* pyobject = + BindCppObjectNoCast(addr, interop::GetScope(classname), false); + + // give ownership, for ref-counting, to the python side, if so requested + if (python_owns && CPPInstance_Check(pyobject)) + ((CPPInstance*)pyobject)->PythonOwns(); + + return pyobject; +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::Instance_FromVoidPtr(void* addr, + interop::TCppScope_t klass_scope, + bool python_owns) { + // Bind the addr to a python object of class defined by classname. + if (!Initialize()) + return nullptr; + + PythonGILRAII python_gil_raii; + + // perform cast (the call will check TClass and addr, and set python errors) + PyObject* pyobject = BindCppObjectNoCast(addr, klass_scope, false); + + // give ownership, for ref-counting, to the python side, if so requested + if (python_owns && CPPInstance_Check(pyobject)) + ((CPPInstance*)pyobject)->PythonOwns(); + + return pyobject; +} +namespace cppjit::cpyrt { +// version with C type arguments only for use with Numba +PyObject* Instance_FromVoidPtr(void* addr, const char* classname, + int python_owns) { + return Instance_FromVoidPtr(addr, std::string(classname), (bool)python_owns); +} +} // namespace cppjit::cpyrt + +//----------------------------------------------------------------------------- +bool cpyrt::Scope_Check(PyObject* pyobject) { + // Test if the given object is of a CPPScope derived type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + return CPPScope_Check(pyobject); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Scope_CheckExact(PyObject* pyobject) { + // Test if the given object is of a CPPScope type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + return CPPScope_CheckExact(pyobject); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Instance_Check(PyObject* pyobject) { + // Test if the given pyobject is of CPPInstance derived type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // detailed walk through inheritance hierarchy + return CPPInstance_Check(pyobject); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Instance_CheckExact(PyObject* pyobject) { + // Test if the given pyobject is of CPPInstance type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // direct pointer comparison of type member + return CPPInstance_CheckExact(pyobject); +} + +//----------------------------------------------------------------------------- +void cpyrt::Instance_SetPythonOwns(PyObject* pyobject) { + if (!Initialize()) + return; + + // check validity of cast + if (!CPPInstance_Check(pyobject)) + return; + + ((CPPInstance*)pyobject)->PythonOwns(); +} + +//----------------------------------------------------------------------------- +void cpyrt::Instance_SetCppOwns(PyObject* pyobject) { + if (!Initialize()) + return; + + // check validity of cast + if (!CPPInstance_Check(pyobject)) + return; + + ((CPPInstance*)pyobject)->CppOwns(); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Sequence_Check(PyObject* pyobject) { + PythonGILRAII python_gil_raii; + // Extends on PySequence_Check() to determine whether an object can be + // iterated over (technically, all objects can b/c of C++ pointer arithmetic, + // hence this check isn't 100% accurate, but neither is PySequence_Check()). + + // Note: simply having the iterator protocol does not constitute a sequence, + // bc PySequence_GetItem() would fail. + + // default to PySequence_Check() if called with a non-C++ object + if (!CPPInstance_Check(pyobject)) + return (bool)PySequence_Check(pyobject); + + // all C++ objects should have sq_item defined, but a user-derived class may + // have deleted it, in which case this is not a sequence + PyTypeObject* t = Py_TYPE(pyobject); + if (!t->tp_as_sequence || !t->tp_as_sequence->sq_item) + return false; + + // if this is the default getitem, it is only a sequence if it's an array type + if (t->tp_as_sequence->sq_item == CPPInstance_Type.tp_as_sequence->sq_item) { + if (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsArray) + return true; + return false; + } + + // TODO: could additionally verify whether __len__ is supported and/or whether + // operator()[] takes an int argument type + + return true; +} + +//----------------------------------------------------------------------------- +bool cppjit::cpyrt::Instance_IsLively(PyObject* pyobject) { + PythonGILRAII python_gil_raii; + // Test whether the given instance can safely return to C++ + if (!CPPInstance_Check(pyobject)) + return true; // simply don't know + + // the instance fails the lively test if it owns the C++ object while having a + // reference count of 1 (meaning: it could delete the C++ instance any moment) + if (Py_REFCNT(pyobject) <= 1 && + (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsOwner)) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +bool cpyrt::Overload_Check(PyObject* pyobject) { + // Test if the given pyobject is of CPPOverload derived type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // detailed walk through inheritance hierarchy + return CPPOverload_Check(pyobject); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Overload_CheckExact(PyObject* pyobject) { + // Test if the given pyobject is of CPPOverload type. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // direct pointer comparison of type member + return CPPOverload_CheckExact(pyobject); +} + +//----------------------------------------------------------------------------- +void cpyrt::Instance_SetReduceMethod(PyCFunction reduceMethod) { + CPPInstance::ReduceMethod() = reduceMethod; +} + +//- access to the python interpreter ---------------------------------------- +bool cpyrt::Import(const std::string& mod_name) { + // Import the named python module and create Cling equivalents for its + // classes. + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + + PyObject* mod = PyImport_ImportModule(mod_name.c_str()); + if (!mod) { + PyErr_Print(); + return false; + } + + // allow finding to prevent creation of a python proxy for the C++ proxy + Py_INCREF(mod); + PyModule_AddObject(gThisModule, mod_name.c_str(), mod); + + // force creation of the module as a namespace + // TODO: the following is broken (and should live in cppjit_interop.cxx) + // TClass::GetClass(mod_name, true); + + PyObject* dct = PyModule_GetDict(mod); + + // create Cling classes for all new python classes + PyObject* values = PyDict_Values(dct); + for (int i = 0; i < PyList_GET_SIZE(values); ++i) { + PyObject* value = PyList_GET_ITEM(values, i); + Py_INCREF(value); + + // collect classes + if (PyClass_Check(value) || PyObject_HasAttr(value, PyStrings::gBases)) { + // get full class name (including module) + PyObject* pyClName = PyObject_GetAttr(value, PyStrings::gName); + if (PyErr_Occurred()) + PyErr_Clear(); + + // build full, qualified name + std::string fullname = mod_name; + fullname += "."; + fullname += cpyrt_PyText_AsString(pyClName); + + Py_XDECREF(pyClName); + } + + Py_DECREF(value); + } + + Py_DECREF(values); + + // TODO: mod "leaks" here + if (PyErr_Occurred()) + return false; + return true; +} + +//----------------------------------------------------------------------------- +void cpyrt::ExecScript(const std::string& name, + const std::vector& args) { + // Execute a python stand-alone script, with argv CLI arguments. + // + // example of use: + // cpyrt::ExecScript("test.py", {"1", "2", "3"}); + + if (!Initialize()) + return; + + PythonGILRAII python_gil_raii; + + // verify arguments + if (name.empty()) { + std::cerr << "Error: no file name specified." << std::endl; + return; + } + + FILE* fp = fopen(name.c_str(), "r"); + if (!fp) { + std::cerr << "Error: could not open file \"" << name << "\"." << std::endl; + return; + } + + // store a copy of the old cli for restoration + PyObject* oldargv = PySys_GetObject("argv"); // borrowed + if (oldargv) { + PyObject* copy = PyList_GetSlice(oldargv, 0, PyList_Size(oldargv)); + oldargv = copy; // now owned + } else { + PyErr_Clear(); + } + + // build new argv + const int argc = (int)args.size() + 1; + std::vector wargv(argc); + wargv[0] = Py_DecodeLocale(name.c_str(), nullptr); + + for (int i = 1; i < argc; ++i) { + wargv[i] = Py_DecodeLocale(args[i - 1].c_str(), nullptr); + } + + // set sys.argv + PyObject* sysmod = PyImport_ImportModule("sys"); // new reference + if (sysmod) { + PyObject* argv_obj = PyList_New(argc); + for (int i = 0; i < argc; ++i) { + PyList_SET_ITEM(argv_obj, i, PyUnicode_FromWideChar(wargv[i], -1)); + } + PyObject_SetAttrString(sysmod, "argv", argv_obj); + Py_DECREF(argv_obj); + Py_DECREF(sysmod); + } else { + PyErr_Print(); + } + + // actual script execution + PyObject* gbl = PyDict_Copy(gMainDict); + PyObject* result = // PyRun_FileEx closes fp (b/c of last argument "1") + PyRun_FileEx(fp, const_cast(name.c_str()), Py_file_input, gbl, gbl, + 1); + + if (!result) + PyErr_Print(); + + Py_XDECREF(result); + Py_DECREF(gbl); + + // restore original command line + if (oldargv) { + PySys_SetObject("argv", oldargv); + Py_DECREF(oldargv); + } + + // free memory from Py_DecodeLocale + for (auto ptr : wargv) + PyMem_RawFree(ptr); +} + +//----------------------------------------------------------------------------- +bool cpyrt::Exec(const std::string& cmd) { + // Execute a python statement (e.g. "import noddy"). + if (!Initialize()) + return false; + + PythonGILRAII python_gil_raii; + // execute the command + PyObject* result = PyRun_String(const_cast(cmd.c_str()), Py_file_input, + gMainDict, gMainDict); + + // test for error + if (result) { + Py_DECREF(result); + return true; + } + + PyErr_Print(); + return false; +} + +//----------------------------------------------------------------------------- +void cpyrt::Prompt() { + // Enter an interactive python session (exit with ^D). State is preserved + // between successive calls. + if (!Initialize()) + return; + + PythonGILRAII python_gil_raii; + // enter i/o interactive mode + PyRun_InteractiveLoop(stdin, const_cast("\0")); +} diff --git a/src/cpyrt/API.h b/src/cpyrt/API.h new file mode 100644 index 0000000..74facdf --- /dev/null +++ b/src/cpyrt/API.h @@ -0,0 +1,250 @@ +#ifndef CPYRT_API_H +#define CPYRT_API_H + +// +// Access to the python interpreter and API onto cpyrt. +// + +// Python +#ifdef _WIN32 +#pragma warning(disable : 4275) +#pragma warning(disable : 4251) +#pragma warning(disable : 4800) +#endif +#if defined(linux) +#include +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif +#ifdef _FILE_OFFSET_BITS +#undef _FILE_OFFSET_BITS +#endif +#ifdef _XOPEN_SOURCE +#undef _XOPEN_SOURCE +#endif +#endif +#include "Python.h" + +#define CPYRT_VERSION_HEX 0x011200 + +// cppjit_interop types +#ifndef CPYRT_INTERNAL + +namespace Cpp { +struct DeclRef; +struct TypeRef; +struct FuncRef; +struct ObjectRef; +} // namespace Cpp + +namespace cppjit::interop { +typedef Cpp::DeclRef TCppScope_t; +typedef Cpp::TypeRef TCppType_t; +typedef Cpp::ObjectRef TCppObject_t; +typedef Cpp::FuncRef TCppMethod_t; +typedef size_t TCppIndex_t; +typedef void* TCppFuncAddr_t; +} // namespace cppjit::interop +#endif + +// Bindings +#include "cpyrt/CommonDefs.h" + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +//- type conversion --------------------------------------------------------- + +#ifndef CPYRT_PARAMETER +#define CPYRT_PARAMETER +// generic function argument type +struct Parameter { + union Value { + bool fBool; + int8_t fInt8; + uint8_t fUInt8; + short fShort; + unsigned short fUShort; + int fInt; + unsigned int fUInt; + long fLong; + intptr_t fIntPtr; + unsigned long fULong; + long long fLLong; + unsigned long long fULLong; + int64_t fInt64; + uint64_t fUInt64; + float fFloat; + double fDouble; + long double fLDouble; + void* fVoidp; + } fValue; + void* fRef; + char fTypeCode; +}; +#endif // CPYRT_PARAMETER + +// CallContext is not currently exposed +struct CallContext; + +// Dimensions class not currently exposed +#ifndef CPYRT_DIMENSIONS_H +#define CPYRT_DIMENSIONS_H +typedef Py_ssize_t dim_t; + +class Dimensions { // Windows note: NOT exported/imported + dim_t* fDims; + +public: + Dimensions(dim_t /*ndim*/ = 0, dim_t* /*dims*/ = nullptr) : fDims(nullptr) {} + ~Dimensions() { delete[] fDims; } + +public: + operator bool() const { return (bool)fDims; } +}; + +typedef Dimensions dims_t; +typedef const dims_t& cdims_t; +#endif // !CPYRT_DIMENSIONS_H + +// type converter base class +class CPYRT_CLASS_EXTERN Converter { +public: + virtual ~Converter(); + + // convert the python object and add store it on the parameter + virtual bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) = 0; + + // convert a C++ object from memory to a Python object + virtual PyObject* FromMemory(void* address); + + // convert a Python object to a C++ object and store it on address + virtual bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr); + + // if a converter has state, it will be unique per function, shared otherwise + virtual bool HasState() { return false; } +}; + +// create a converter based on its full type name and dimensions +CPYRT_EXTERN Converter* CreateConverter(const std::string& name, cdims_t = 0); +CPYRT_EXTERN Converter* CreateConverter(cppjit::interop::TCppType_t type, + cdims_t = 0); + +// delete a previously created converter +CPYRT_EXTERN void DestroyConverter(Converter* p); + +// register a custom converter +typedef Converter* (*ConverterFactory_t)(cdims_t); +CPYRT_EXTERN bool RegisterConverter(const std::string& name, + ConverterFactory_t); + +// register a custom converter that is a reference to an existing converter +CPYRT_EXTERN bool RegisterConverterAlias(const std::string& name, + const std::string& target); + +// remove a custom converter +CPYRT_EXTERN bool UnregisterConverter(const std::string& name); + +// function executor base class +class CPYRT_CLASS_EXTERN Executor { +public: + virtual ~Executor(); + + // callback when executing a function from Python + virtual PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) = 0; + + // if an executor has state, it will be unique per function, shared otherwise + virtual bool HasState() { return false; } +}; + +// create an executor based on its full type name +CPYRT_EXTERN Executor* CreateExecutor(const std::string& name, cdims_t = 0); +CPYRT_EXTERN Executor* CreateExecutor(cppjit::interop::TCppType_t type, + cdims_t = 0); + +// delete a previously created executor +CPYRT_EXTERN void DestroyConverter(Converter* p); + +// register a custom executor +typedef Executor* (*ExecutorFactory_t)(cdims_t); +CPYRT_EXTERN bool RegisterExecutor(const std::string& name, ExecutorFactory_t); + +// register a custom executor that is a reference to an existing converter +CPYRT_EXTERN bool RegisterExecutorAlias(const std::string& name, + const std::string& target); + +// remove a custom executor +CPYRT_EXTERN bool UnregisterExecutor(const std::string& name); + +// helper for calling into C++ from a custom executor +CPYRT_EXTERN void* CallVoidP(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*); + +//- C++ access to cppjit objects --------------------------------------------- + +// Get C++ Instance (python object proxy) name. +// Sets a TypeError and returns an empty string if the pyobject is not a +// CPPInstance. +CPYRT_EXTERN std::string Instance_GetScopedFinalName(PyObject* pyobject); + +// C++ Instance (python object proxy) to void* conversion +CPYRT_EXTERN void* Instance_AsVoidPtr(PyObject* pyobject); + +// void* to C++ Instance (python object proxy) conversion, returns a new +// reference +CPYRT_EXTERN PyObject* Instance_FromVoidPtr(void* addr, + const std::string& classname, + bool python_owns = false); +CPYRT_EXTERN PyObject* +Instance_FromVoidPtr(void* addr, cppjit::interop::TCppScope_t klass_scope, + bool python_owns = false); +// type verifiers for C++ Scope +CPYRT_EXTERN bool Scope_Check(PyObject* pyobject); +CPYRT_EXTERN bool Scope_CheckExact(PyObject* pyobject); + +// type verifiers for C++ Instance +CPYRT_EXTERN bool Instance_Check(PyObject* pyobject); +CPYRT_EXTERN bool Instance_CheckExact(PyObject* pyobject); + +// memory management: ownership of the underlying C++ object +CPYRT_EXTERN void Instance_SetPythonOwns(PyObject* pyobject); +CPYRT_EXTERN void Instance_SetCppOwns(PyObject* pyobject); + +// type verifier for sequences +CPYRT_EXTERN bool Sequence_Check(PyObject* pyobject); + +// helper to verify expected safety of moving an instance into C++ +CPYRT_EXTERN bool Instance_IsLively(PyObject* pyobject); + +// type verifiers for C++ Overload +CPYRT_EXTERN bool Overload_Check(PyObject* pyobject); +CPYRT_EXTERN bool Overload_CheckExact(PyObject* pyobject); + +// Sets the __reduce__ method for the CPPInstance class, which is by default not +// implemented by cppjit but might make sense to implement by frameworks that +// support IO of arbitrary C++ objects, like ROOT. +CPYRT_EXTERN void Instance_SetReduceMethod(PyCFunction reduceMethod); + +//- access to the python interpreter ---------------------------------------- + +// import a python module, making its classes available to Cling +CPYRT_EXTERN bool Import(const std::string& name); + +// execute a python statement (e.g. "import sys") +CPYRT_EXTERN bool Exec(const std::string& cmd); + +// execute a python stand-alone script, with argv CLI arguments +CPYRT_EXTERN void ExecScript(const std::string& name, + const std::vector& args); + +// enter an interactive python session (exit with ^D) +CPYRT_EXTERN void Prompt(); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_API_H diff --git a/src/cpyrt/CPPClassMethod.cxx b/src/cpyrt/CPPClassMethod.cxx new file mode 100644 index 0000000..d6893a8 --- /dev/null +++ b/src/cpyrt/CPPClassMethod.cxx @@ -0,0 +1,60 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPClassMethod.h" +#include "CPPInstance.h" + +//- public members +//-------------------------------------------------------------- +PyObject* cpyrt::CPPClassMethod::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // preliminary check in case keywords are accidently used (they are ignored + // otherwise) + if (kwds && ((PyDict_Check(kwds) && PyDict_Size(kwds)) || + (PyTuple_CheckExact(kwds) && PyTuple_GET_SIZE(kwds)))) { + PyErr_SetString(PyExc_TypeError, "keyword arguments are not yet supported"); + return nullptr; + } + + // setup as necessary + if (!this->Initialize(ctxt)) + return nullptr; + + // translate the arguments + // TODO: The following is not robust and should be revisited e.g. by making + // CPPOverloads that have only CPPClassMethods be true Python classmethods? + // Note that the original implementation wasn't 100% correct either (e.g. + // static size() mapped to len()). + // + // As-is, if no suitable `self` is given (normal case), but the type of the + // first argument matches the enclosing scope of the class method and it isn't + // needed for the call, then assume that the method was (incorrectly) bound + // and so drop that instance from args. + int nargs = (int)cpyrt_PyArgs_GET_SIZE(args, nargsf); + if ((!self || (PyObject*)self == Py_None) && nargs) { + PyObject* arg0 = cpyrt_PyArgs_GET_ITEM(args, 0); + if (CPPInstance_Check(arg0) && fArgsRequired <= nargs - 1 && + interop::IsSubclass(reinterpret_cast(arg0)->ObjectIsA(), + GetScope())) { + args += 1; // drops first argument + nargsf -= 1; + } + } + + if (!this->ConvertAndSetArgs(args, nargsf, ctxt)) + return nullptr; + + // execute function + return this->Execute(nullptr, 0, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPClassMethod::GetTypeName() { + PyObject* cppname = + cpyrt_PyText_FromString((GetReturnTypeName() + " (*)").c_str()); + cpyrt_PyText_AppendAndDel(&cppname, + GetSignature(false /* show_formalargs */)); + return cppname; +} diff --git a/src/cpyrt/CPPClassMethod.h b/src/cpyrt/CPPClassMethod.h new file mode 100644 index 0000000..4ffe761 --- /dev/null +++ b/src/cpyrt/CPPClassMethod.h @@ -0,0 +1,24 @@ +#ifndef CPYRT_CPPCLASSMETHOD_H +#define CPYRT_CPPCLASSMETHOD_H + +// Bindings +#include "CPPMethod.h" + +namespace cppjit::cpyrt { + +class CPPClassMethod : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyObject* GetTypeName() override; + +public: + PyCallable* Clone() override { return new CPPClassMethod(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPCLASSMETHOD_H diff --git a/src/cpyrt/CPPConstructor.cxx b/src/cpyrt/CPPConstructor.cxx new file mode 100644 index 0000000..657f875 --- /dev/null +++ b/src/cpyrt/CPPConstructor.cxx @@ -0,0 +1,392 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPConstructor.h" +#include "CPPInstance.h" +#include "Executors.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" + +// Standard +#include +#include + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { +extern PyObject* gNullPtrObject; +void* Instance_AsVoidPtr(PyObject* pyobject); +PyObject* Instance_FromVoidPtr(void* addr, interop::TCppScope_t klass_scope, + bool python_owns); +} // namespace cppjit::cpyrt + +//- protected members -------------------------------------------------------- +bool cpyrt::CPPConstructor::InitExecutor_(Executor*& executor, CallContext*) { + // pick up special case new object executor + executor = CreateExecutor("__init__"); + return true; +} + +//- public members ----------------------------------------------------------- +PyObject* cpyrt::CPPConstructor::GetDocString() { + // GetMethod() may return an empty function if this is just a special case + // place holder + const std::string& clName = interop::GetFinalName(this->GetScope()); + return cpyrt_PyText_FromFormat( + "%s::%s%s", clName.c_str(), clName.c_str(), + this->GetMethod() ? this->GetSignatureString().c_str() : "()"); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPConstructor::Reflex(interop::Reflex::RequestId_t request, + interop::Reflex::FormatId_t format) { + // C++ reflection tooling for constructors. + + if (request == interop::Reflex::RETURN_TYPE) { + std::string fn = interop::GetScopedFinalName(this->GetScope()); + if (format == interop::Reflex::OPTIMAL || + format == interop::Reflex::AS_TYPE) + return CreateScopeProxy(this->GetScope()); + else if (format == interop::Reflex::AS_STRING) + return cpyrt_PyText_FromString(fn.c_str()); + } + + return PyCallable::Reflex(request, format); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPConstructor::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // setup as necessary + if (fArgsRequired == -1 && !this->Initialize(ctxt)) + return nullptr; // important: 0, not Py_None + + // fetch self, verify, and put the arguments in usable order + PyCallArgs cargs{self, args, nargsf, kwds}; + if (!this->ProcessArgs(cargs)) + return nullptr; + + // verify existence of self (i.e. tp_new called) + if (!self) { + PyErr_SetString(PyExc_ReferenceError, "no python object allocated"); + return nullptr; + } + + if (self->GetObject()) { + PyErr_SetString( + PyExc_ReferenceError, + "object already constructed; use __assign__ instead of __init__"); + return nullptr; + } + + const auto cppScopeFlags = ((CPPScope*)Py_TYPE(self))->fFlags; + + // Do nothing if the constructor is explicit and we are in an implicit + // conversion context. See also the ConvertImplicit() helper in + // Converters.cxx. + if ((cppScopeFlags & CPPScope::kActiveImplicitCall) && + interop::IsExplicit(GetMethod())) { + // FIXME: Cases with explicit marked std::complex constructors where we + // expect implicit conversionss + if (interop::GetMethodSignature(GetMethod(), true).find("std::complex") == + std::string::npos) + return nullptr; + } + + // self provides the python context for lifelines + if (!ctxt->fPyContext) + ctxt->fPyContext = (PyObject*)cargs.fSelf; // no Py_INCREF as no ownership + + // perform the call, nullptr 'this' makes the other side allocate the memory + interop::TCppScope_t disp = self->ObjectIsA(false /* check_smart */); + intptr_t address = 0; + if (GetScope() != disp) { + // happens for Python derived types (which have a dispatcher inserted that + // is not otherwise user-visible: call it instead) and C++ derived classes + // without public constructors + + // first, check whether we at least had a proper meta class, or whether that + // was also replaced user-side + if (!GetScope() || !disp) { + PyErr_SetString(PyExc_TypeError, + "can not construct incomplete C++ class"); + return nullptr; + } + + // get the dispatcher class and verify + PyObject* dispproxy = cpyrt::GetScopeProxy(disp); + if (!dispproxy) { + PyErr_SetString(PyExc_TypeError, "dispatcher proxy was never created"); + return nullptr; + } + + if (!(((CPPClass*)dispproxy)->fFlags & CPPScope::kIsPython)) { + PyErr_SetString(PyExc_TypeError, + const_cast(("constructor for " + + interop::GetScopedFinalName(disp) + + " is not a dispatcher") + .c_str())); + return nullptr; + } + + PyObject* pyobj = + cpyrt_PyObject_Call(dispproxy, cargs.fArgs, cargs.fNArgsf, kwds); + if (!pyobj) + return nullptr; + + // retrieve the actual pointer, take over control, and set set + // _internal_self + address = (intptr_t)((CPPInstance*)pyobj)->GetObject(); + if (address) { + ((CPPInstance*)pyobj) + ->CppOwns(); // b/c self will control the object on address + PyObject* res = PyObject_CallMethodObjArgs( + dispproxy, PyStrings::gDispInit, pyobj, (PyObject*)self, nullptr); + Py_XDECREF(res); + } + Py_DECREF(dispproxy); + + } else { + // translate the arguments + if (cppScopeFlags & CPPScope::kActiveImplicitCall) + ctxt->fFlags |= CallContext::kNoImplicit; + if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) + return nullptr; + + address = (intptr_t)this->Execute(nullptr, 0, ctxt); + } + + // return object if successful, lament if not + if (address) { + Py_INCREF(self); + + // note: constructors are no longer set to take ownership by default; + // instead that is decided by the method proxy (which carries a creator + // flag) upon return + self->Set((void*)address); + + // mark as actual to prevent needless auto-casting and register on its class + self->fFlags |= CPPInstance::kIsActual; + if (!(((CPPClass*)Py_TYPE(self))->fFlags & CPPScope::kIsSmart)) + MemoryRegulator::RegisterPyObject(self, + interop::TCppObject_t((void*)address)); + + // handling smart types this way is deeply fugly, but if CPPInstance sets + // the proper types in op_new first, then the wrong init is called + if (((CPPClass*)Py_TYPE(self))->fFlags & CPPScope::kIsSmart) { + PyObject* pyclass = + CreateScopeProxy(((CPPSmartClass*)Py_TYPE(self))->fUnderlyingType); + if (pyclass) { + self->SetSmart((PyObject*)Py_TYPE(self)); + Py_DECREF((PyObject*)Py_TYPE(self)); + Py_SET_TYPE(self, (PyTypeObject*)pyclass); + } + } + + // done with self + Py_DECREF(self); + + Py_RETURN_NONE; // by definition + } + + if (!PyErr_Occurred()) // should be set, otherwise write a generic error msg + PyErr_SetString(PyExc_TypeError, + const_cast((interop::GetScopedFinalName(GetScope()) + + " constructor failed") + .c_str())); + + // do not throw an exception, nullptr might trigger the overload handler to + // choose a different constructor, which if all fails will throw an exception + return nullptr; +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMultiConstructor::CPPMultiConstructor(interop::TCppScope_t scope, + interop::TCppMethod_t method) + : CPPConstructor(scope, method) { + fNumBases = interop::GetNumBases(scope); +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMultiConstructor::CPPMultiConstructor(const CPPMultiConstructor& s) + : CPPConstructor(s), fNumBases(s.fNumBases) {} + +//---------------------------------------------------------------------------- +cpyrt::CPPMultiConstructor& +cpyrt::CPPMultiConstructor::operator=(const CPPMultiConstructor& s) { + if (this != &s) { + CPPConstructor::operator=(s); + fNumBases = s.fNumBases; + } + return *this; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMultiConstructor::Call(CPPInstance*& self, + cpyrt_PyArgs_t argsin, size_t nargsf, + PyObject* kwds, CallContext* ctxt) { + // By convention, initialization parameters of multiple base classes are + // grouped by target base class. Here, we disambiguate and put in "sentinel" + // parameters that allow the dispatcher to propagate them. + + // Three options supported: + // 0. empty args: default constructor call + // 1. fNumBases tuples, each handed to individual constructors + // 2. less than fNumBases, assuming (void) for the missing base constructors + // 3. normal arguments, going to the first base only + + // TODO: this way of forwarding is expensive as the loop is external to this + // call; it would be more efficient to have the argument handling happen + // beforehand + + // fetch self, verify, and put the arguments in usable order (if self is not + // handled first, arguments can not be reordered with sentinels in place) + PyCallArgs cargs{self, argsin, nargsf, kwds}; + if (!this->ProcessArgs(cargs)) + return nullptr; + + // to re-use the argument handling, simply change the argument array into a + // tuple (the benefits of not allocating the tuple are relatively minor in + // this case) + Py_ssize_t nargs = cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); + PyObject* args = PyTuple_New(nargs); + for (Py_ssize_t i = 0; i < nargs; ++i) { + Py_INCREF(cargs.fArgs[i]); + PyTuple_SET_ITEM(args, i, cargs.fArgs[i]); + } + + // copy out self as it may have been updated + self = cargs.fSelf; + + if (PyTuple_CheckExact(args) && + PyTuple_GET_SIZE(args)) { // case 0. falls through + Py_ssize_t nArgs = PyTuple_GET_SIZE(args); + + bool isAllTuples = true; + Py_ssize_t nArgsTot = 0; + for (Py_ssize_t i = 0; i < nArgs; ++i) { + PyObject* argi = PyTuple_GET_ITEM(args, i); + if (!PyTuple_CheckExact(argi)) { + isAllTuples = false; + break; + } + nArgsTot += PyTuple_GET_SIZE(argi); + } + + if (isAllTuples) { + // copy over the arguments, while filling in the sentinels (case 1. & 2.), + // with just sentinels for the remaining (void) calls (case 2.) + PyObject* newArgs = PyTuple_New(nArgsTot + fNumBases - 1); + Py_ssize_t idx = 0; + for (Py_ssize_t i = 0; i < nArgs; ++i) { + if (i != 0) { + // add sentinel + Py_INCREF(gNullPtrObject); + PyTuple_SET_ITEM(newArgs, idx, gNullPtrObject); + idx += 1; + } + + PyObject* argi = PyTuple_GET_ITEM(args, i); + for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(argi); ++j) { + PyObject* item = PyTuple_GET_ITEM(argi, j); + Py_INCREF(item); + PyTuple_SET_ITEM(newArgs, idx, item); + idx += 1; + } + } + + // add final sentinels as needed + while (idx < (nArgsTot + fNumBases - 1)) { + Py_INCREF(gNullPtrObject); + PyTuple_SET_ITEM(newArgs, idx, gNullPtrObject); + idx += 1; + } + + Py_DECREF(args); + args = newArgs; + } else { // case 3. add sentinels + // copy arguments as-is, then add sentinels at the end + PyObject* newArgs = PyTuple_New(PyTuple_GET_SIZE(args) + fNumBases - 1); + for (Py_ssize_t i = 0; i < nArgs; ++i) { + PyObject* item = PyTuple_GET_ITEM(args, i); + Py_INCREF(item); + PyTuple_SET_ITEM(newArgs, i, item); + } + for (Py_ssize_t i = 0; i < fNumBases - 1; ++i) { + Py_INCREF(gNullPtrObject); + PyTuple_SET_ITEM(newArgs, i + nArgs, gNullPtrObject); + } + Py_DECREF(args); + args = newArgs; + } + } + + nargs = PyTuple_GET_SIZE(args); + + // now unroll the new args tuple into a vector of objects + auto argsu = std::unique_ptr{new PyObject*[nargs]}; + for (Py_ssize_t i = 0; i < nargs; ++i) + argsu[i] = PyTuple_GET_ITEM(args, i); + cpyrt_PyArgs_t _args = argsu.get(); + + PyObject* result = CPPConstructor::Call(self, _args, nargs, kwds, ctxt); + Py_DECREF(args); + + return result; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPAbstractClassConstructor::Call(CPPInstance*& self, + cpyrt_PyArgs_t args, + size_t nargsf, + PyObject* kwds, + CallContext* ctxt) { + // do not allow instantiation of abstract classes + if ((self && GetScope() != self->ObjectIsA()) || + (!self && !(ctxt->fFlags & CallContext::kFromDescr) && + cpyrt_PyArgs_GET_SIZE(args, nargsf) && CPPInstance_Check(args[0]) && + GetScope() != ((CPPInstance*)args[0])->ObjectIsA())) { + // happens if a dispatcher is inserted; allow constructor call + return CPPConstructor::Call(self, args, nargsf, kwds, ctxt); + } + + PyErr_Format(PyExc_TypeError, + "cannot instantiate abstract class \'%s\'" + " (from derived classes, use super() instead)", + interop::GetScopedFinalName(this->GetScope()).c_str()); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPNamespaceConstructor::Call(CPPInstance*&, cpyrt_PyArgs_t, + size_t, PyObject*, + CallContext*) { + // do not allow instantiation of namespaces + PyErr_Format(PyExc_TypeError, "cannot instantiate namespace \'%s\'", + interop::GetScopedFinalName(this->GetScope()).c_str()); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPIncompleteClassConstructor::Call(CPPInstance*&, + cpyrt_PyArgs_t, size_t, + PyObject*, CallContext*) { + // do not allow instantiation of incomplete (forward declared) classes) + PyErr_Format(PyExc_TypeError, "cannot instantiate incomplete class \'%s\'", + interop::GetScopedFinalName(this->GetScope()).c_str()); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPAllPrivateClassConstructor::Call(CPPInstance*&, + cpyrt_PyArgs_t, size_t, + PyObject*, CallContext*) { + // do not allow instantiation of classes with only private constructors + PyErr_Format( + PyExc_TypeError, + "cannot instantiate class \'%s\' that has no public constructors", + interop::GetScopedFinalName(this->GetScope()).c_str()); + return nullptr; +} diff --git a/src/cpyrt/CPPConstructor.h b/src/cpyrt/CPPConstructor.h new file mode 100644 index 0000000..0440d23 --- /dev/null +++ b/src/cpyrt/CPPConstructor.h @@ -0,0 +1,93 @@ +#ifndef CPYRT_CPPCONSTRUCTOR_H +#define CPYRT_CPPCONSTRUCTOR_H + +// Bindings +#include "CPPMethod.h" + +namespace cppjit::cpyrt { + +class CPPConstructor : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyObject* GetDocString() override; + PyObject* Reflex(cppjit::interop::Reflex::RequestId_t, + cppjit::interop::Reflex::FormatId_t = + cppjit::interop::Reflex::OPTIMAL) override; + + PyCallable* Clone() override { return new CPPConstructor(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +protected: + bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr) override; +}; + +// specialization for multiple inheritance disambiguation +class CPPMultiConstructor : public CPPConstructor { +public: + CPPMultiConstructor(cppjit::interop::TCppScope_t scope, + cppjit::interop::TCppMethod_t method); + CPPMultiConstructor(const CPPMultiConstructor&); + CPPMultiConstructor& operator=(const CPPMultiConstructor&); + +public: + PyCallable* Clone() override { return new CPPMultiConstructor(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +private: + Py_ssize_t fNumBases; +}; + +// specializations of prohibiting constructors +class CPPAbstractClassConstructor : public CPPConstructor { +public: + using CPPConstructor::CPPConstructor; + +public: + PyCallable* Clone() override { + return new CPPAbstractClassConstructor(*this); + } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +class CPPNamespaceConstructor : public CPPConstructor { +public: + using CPPConstructor::CPPConstructor; + +public: + PyCallable* Clone() override { return new CPPNamespaceConstructor(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +class CPPIncompleteClassConstructor : public CPPConstructor { +public: + using CPPConstructor::CPPConstructor; + +public: + PyCallable* Clone() override { + return new CPPIncompleteClassConstructor(*this); + } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +class CPPAllPrivateClassConstructor : public CPPConstructor { +public: + using CPPConstructor::CPPConstructor; + +public: + PyCallable* Clone() override { + return new CPPAllPrivateClassConstructor(*this); + } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPCONSTRUCTOR_H diff --git a/src/cpyrt/CPPDataMember.cxx b/src/cpyrt/CPPDataMember.cxx new file mode 100644 index 0000000..c2543dc --- /dev/null +++ b/src/cpyrt/CPPDataMember.cxx @@ -0,0 +1,442 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPDataMember.h" +#include "CPPEnum.h" +#include "CPPInstance.h" +#include "Dimensions.h" +#include "LowLevelViews.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" +#include "cpyrt/Reflex.h" + +// Standard +#include +#include +#include +#include + +namespace cppjit::cpyrt { + +enum ETypeDetails { + kNone = 0x0000, + kIsStaticData = 0x0001, + kIsConstData = 0x0002, + kIsArrayType = 0x0004, + kIsEnumPrep = 0x0008, + kIsEnumType = 0x0010, + kIsCachable = 0x0020 +}; + +//= cpyrt data member as Python property behavior ========================= +static PyObject* dm_get(CPPDataMember* dm, CPPInstance* pyobj, + PyObject* /* kls */) { + // cache lookup for low level views + if (pyobj && dm->fFlags & kIsCachable) { + cpyrt::CI_DatamemberCache_t& cache = pyobj->GetDatamemberCache(); + for (auto it = cache.begin(); it != cache.end(); ++it) { + if (it->first == dm->fOffset) { + if (it->second) { + Py_INCREF(it->second); + return it->second; + } else + cache.erase(it); + break; + } + } + } + + if (dm->fFlags & (kIsEnumPrep | kIsEnumType)) { + if (dm->fFlags & kIsEnumPrep) { + // still need to do lookup; only ever try this once, then fallback on + // converter + dm->fFlags &= ~kIsEnumPrep; + + // fDescription contains the full name of the actual enum value object + const interop::TCppScope_t enum_type = + interop::GetParentScope(dm->fScope); + const interop::TCppScope_t enum_scope = + interop::GetParentScope(enum_type); + + PyObject* pyscope = CreateScopeProxy(enum_scope); + if (pyscope) { + PyObject* pyEnumType = PyObject_GetAttrString( + pyscope, interop::GetFinalName(enum_type).c_str()); + if (pyEnumType) { + PyObject* pyval = PyObject_GetAttrString( + pyEnumType, interop::GetFinalName(dm->fScope).c_str()); + Py_DECREF(pyEnumType); + if (pyval) { + Py_DECREF(dm->fDescription); + dm->fDescription = pyval; + dm->fFlags |= kIsEnumType; + } + } + Py_DECREF(pyscope); + } + if (!(dm->fFlags & kIsEnumType)) + PyErr_Clear(); + } + + if (dm->fFlags & kIsEnumType) { + Py_INCREF(dm->fDescription); + return dm->fDescription; + } + + if (interop::IsEnumConstant(dm->fScope)) { + // anonymous enum + return pyval_from_enum(interop::ResolveEnum(dm->fScope), nullptr, nullptr, + dm->fScope); + } + } + // non-initialized or public data accesses through class (e.g. by help()) + void* address = dm->GetAddress(pyobj); + if (!address || (intptr_t)address == -1 /* Cling error */) + return nullptr; + + if (dm->fConverter != 0) { + PyObject* result = dm->fConverter->FromMemory( + (dm->fFlags & kIsArrayType) ? &address : address); + if (!result) + return result; + + // low level views are expensive to create, so cache them on the object + // instead + bool isLLView = LowLevelView_CheckExact(result); + if (isLLView && CPPInstance_Check(pyobj)) { + Py_INCREF(result); + pyobj->GetDatamemberCache().push_back( + std::make_pair(dm->fOffset, result)); + dm->fFlags |= kIsCachable; + } + + // ensure that the encapsulating class does not go away for the duration + // of the data member's lifetime, if it is a bound type (it doesn't matter + // for builtin types, b/c those are copied over into python types and thus + // end up being "stand-alone") + // TODO: should be done for LLViews as well + else if (pyobj && !(dm->fFlags & kIsStaticData) && + CPPInstance_Check(result)) { + if (PyObject_SetAttr(result, PyStrings::gLifeLine, (PyObject*)pyobj) == + -1) + PyErr_Clear(); // ignored + } + + return result; + } + + PyErr_Format(PyExc_NotImplementedError, "no converter available for \"%s\"", + dm->GetName().c_str()); + return nullptr; +} + +//----------------------------------------------------------------------------- +static int dm_set(CPPDataMember* dm, CPPInstance* pyobj, PyObject* value) { + // Set the value of the C++ datum held. + const int errret = -1; + + if (!value) { + // we're being deleted; fine for namespaces (redo lookup next time), but + // makes no sense for classes/structs + if (!interop::IsNamespace(dm->fEnclosingScope)) { + PyErr_SetString(PyExc_TypeError, "data member deletion is not supported"); + return errret; + } + + // deletion removes the proxy, with the idea that a fresh lookup can be + // made, to support Cling's shadowing of declarations (TODO: the use case + // here is redeclared variables, for which fDescription is indeed th ename; + // it might fail for enums). + return PyObject_DelAttr((PyObject*)Py_TYPE(pyobj), dm->fDescription); + } + + // filter const objects to prevent changing their values + if (dm->fFlags & kIsConstData) { + PyErr_SetString(PyExc_TypeError, "assignment to const data not allowed"); + return errret; + } + + // remove cached low level view, if any (will be restored upon reaeding) + if (dm->fFlags & kIsCachable) { + cpyrt::CI_DatamemberCache_t& cache = pyobj->GetDatamemberCache(); + for (auto it = cache.begin(); it != cache.end(); ++it) { + if (it->first == dm->fOffset) { + Py_XDECREF(it->second); + cache.erase(it); + break; + } + } + } + + intptr_t address = (intptr_t)dm->GetAddress(pyobj); + if (!address || address == -1 /* Cling error */) + return errret; + + // for fixed size arrays + void* ptr = (void*)address; + if (dm->fFlags & kIsArrayType) + ptr = &address; + + // actual conversion; return on success + if (dm->fConverter && dm->fConverter->ToMemory(value, ptr, (PyObject*)pyobj)) + return 0; + + // set a python error, if not already done + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_RuntimeError, + "property type mismatch or assignment not allowed"); + + // failure ... + return errret; +} + +//= cpyrt data member construction/destruction =========================== +static CPPDataMember* dm_new(PyTypeObject* pytype, PyObject*, PyObject*) { + // Create and initialize a new property descriptor. + CPPDataMember* dm = (CPPDataMember*)pytype->tp_alloc(pytype, 0); + + dm->fOffset = 0; + dm->fFlags = 0; + dm->fConverter = nullptr; + dm->fEnclosingScope = nullptr; + dm->fDescription = nullptr; + dm->fDoc = nullptr; + + new (&dm->fFullType) std::string{}; + + return dm; +} + +//---------------------------------------------------------------------------- +static void dm_dealloc(CPPDataMember* dm) { + // Deallocate memory held by this descriptor. + using namespace std; + if (dm->fConverter && dm->fConverter->HasState()) + delete dm->fConverter; + Py_XDECREF(dm->fDescription); // never exposed so no GC necessary + Py_XDECREF(dm->fDoc); + + dm->fFullType.~string(); + + Py_TYPE(dm)->tp_free((PyObject*)dm); +} + +static PyMemberDef dm_members[] = { + {(char*)"__doc__", T_OBJECT, offsetof(CPPDataMember, fDoc), 0, + (char*)"writable documentation"}, + {NULL, 0, 0, 0, nullptr} /* Sentinel */ +}; + +//= cpyrt datamember proxy access to internals ============================ +static PyObject* dm_reflex(CPPDataMember* dm, PyObject* args) { + // Provide the requested reflection information. + interop::Reflex::RequestId_t request = -1; + interop::Reflex::FormatId_t format = interop::Reflex::OPTIMAL; + if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, + &format)) + return nullptr; + + if (request == interop::Reflex::TYPE) { + if (format == interop::Reflex::OPTIMAL || + format == interop::Reflex::AS_STRING) + return cpyrt_PyText_FromString(dm->fFullType.c_str()); + } else if (request == interop::Reflex::OFFSET) { + if (format == interop::Reflex::OPTIMAL) + return PyLong_FromLong(dm->fOffset); + } + + PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", + request, format); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyMethodDef dm_methods[] = { + {(char*)"__cpp_reflex__", (PyCFunction)dm_reflex, METH_VARARGS, + (char*)"C++ datamember reflection information"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//= cpyrt data member type ================================================ +PyTypeObject CPPDataMember_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.CPPDataMember", // tp_name + sizeof(CPPDataMember), // tp_basicsize + 0, // tp_itemsize + (destructor)dm_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / 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, // tp_flags + (char*)"cppjit data member (internal)", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + dm_methods, // tp_methods + dm_members, // tp_members + 0, // tp_getset + 0, // tp_base + 0, // tp_dict + (descrgetfunc)dm_get, // tp_descr_get + (descrsetfunc)dm_set, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)dm_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 + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt + +//- public members ----------------------------------------------------------- +void cpyrt::CPPDataMember::Set(interop::TCppScope_t scope, + interop::TCppScope_t data) { + if (interop::IsLambdaClass(interop::GetDatamemberType(data))) { + fScope = interop::WrapLambdaFromVariable(data); + } else { + fScope = data; + } + + fEnclosingScope = scope; + fOffset = interop::GetDatamemberOffset( + fScope, fScope == data + ? scope + : interop::GetScope( + "__cppjit_internal_wrap_g")); // XXX: Check back here // + // TODO: make lazy + fFlags = interop::IsStaticDatamember(fScope) ? kIsStaticData : 0; + + const std::string name = interop::GetFinalName(fScope); + interop::TCppType_t type; + + if (interop::IsEnumConstant(fScope)) { + type = interop::GetEnumConstantType(fScope); + fFullType = interop::GetTypeAsString(type); + if (fFullType.find("(anonymous)") == std::string::npos && + fFullType.find("(unnamed)") == std::string::npos) { + // repurpose fDescription for lazy lookup of the enum later + fDescription = cpyrt_PyText_FromString((fFullType + "::" + name).c_str()); + fFlags |= kIsEnumPrep; + } + type = interop::ResolveType(type); + fFlags |= kIsConstData; + } else { + type = interop::GetDatamemberType(fScope); + fFullType = interop::GetTypeAsString(type); + + // Get the integer type if it's an enum + if (interop::IsEnumType(type)) + type = interop::ResolveType(type); + + if (interop::IsConstVar(fScope)) + fFlags |= kIsConstData; + } + + auto ldims = interop::GetDimensions(type); + std::vector dims(ldims.begin(), ldims.end()); + + if (!dims.empty()) + fFlags |= kIsArrayType; + + if (dims.empty()) + fConverter = CreateConverter(type, 0); + else + fConverter = CreateConverter(type, {(dim_t)dims.size(), dims.data()}); + + if (!(fFlags & kIsEnumPrep)) + fDescription = cpyrt_PyText_FromString(name.c_str()); +} + +//----------------------------------------------------------------------------- +void cpyrt::CPPDataMember::Set(interop::TCppScope_t scope, + const std::string& name, void* address) { + fEnclosingScope = scope; + fDescription = cpyrt_PyText_FromString(name.c_str()); + fOffset = (intptr_t)address; + fFlags = kIsStaticData | kIsConstData; + fConverter = CreateConverter("internal_enum_type_t"); + fFullType = "unsigned int"; +} + +//----------------------------------------------------------------------------- +void* cpyrt::CPPDataMember::GetAddress(CPPInstance* pyobj) { + // class attributes, global properties + if (fFlags & kIsStaticData) + return (void*)fOffset; + + // special case: non-static lookup through class + if (!pyobj) { + PyErr_SetString(PyExc_AttributeError, + "attribute access requires an instance"); + return nullptr; + } + + // instance attributes; requires valid object for full address + if (!CPPInstance_Check(pyobj)) { + PyErr_Format(PyExc_TypeError, + "object instance required for access to property \"%s\"", + GetName().c_str()); + return nullptr; + } + + void* obj = pyobj->GetObject(); + if (!obj) { + PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); + return nullptr; + } + + // the proxy's internal offset is calculated from the enclosing class + ptrdiff_t offset = 0; + interop::TCppScope_t oisa = pyobj->ObjectIsA(); + if (oisa != fEnclosingScope) + offset = + interop::GetBaseOffset(oisa, fEnclosingScope, obj, 1 /* up-cast */); + + return (void*)((intptr_t)obj + offset + fOffset); +} + +//----------------------------------------------------------------------------- +std::string cpyrt::CPPDataMember::GetName() { + if (fFlags & kIsEnumType) { + PyObject* repr = PyObject_Repr(fDescription); + if (repr) { + std::string res = cpyrt_PyText_AsString(repr); + Py_DECREF(repr); + return res; + } + PyErr_Clear(); + return ""; + } else if (fFlags & kIsEnumPrep) { + std::string fullName = cpyrt_PyText_AsString(fDescription); + return fullName.substr(fullName.rfind("::") + 2, std::string::npos); + } + + return cpyrt_PyText_AsString(fDescription); +} diff --git a/src/cpyrt/CPPDataMember.h b/src/cpyrt/CPPDataMember.h new file mode 100644 index 0000000..7dcf624 --- /dev/null +++ b/src/cpyrt/CPPDataMember.h @@ -0,0 +1,74 @@ +#ifndef CPYRT_CPPDATAMEMBER_H +#define CPYRT_CPPDATAMEMBER_H + +// Bindings +#include "Converters.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +class CPPInstance; + +class CPPDataMember { +public: + void Set(cppjit::interop::TCppScope_t scope, + cppjit::interop::TCppScope_t var); + void Set(cppjit::interop::TCppScope_t scope, const std::string& name, + void* address); + + std::string GetName(); + void* GetAddress(CPPInstance* pyobj /* owner */); + +public: // public, as the python C-API works with C structs + PyObject_HEAD intptr_t fOffset; + long fFlags; + Converter* fConverter; + cppjit::interop::TCppScope_t fScope; + cppjit::interop::TCppScope_t fEnclosingScope; + PyObject* fDescription; + PyObject* fDoc; + + // TODO: data members should have a unique identifier, just like methods, + // so that reflection information can be recovered post-initialization + std::string fFullType; + +private: // private, as the python C-API will handle creation + CPPDataMember() = delete; +}; + +//- property proxy for C++ data members, type and type verification ---------- +extern PyTypeObject CPPDataMember_Type; + +template inline bool CPPDataMember_Check(T* object) { + return object && PyObject_TypeCheck(object, &CPPDataMember_Type); +} + +template inline bool CPPDataMember_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPDataMember_Type; +} + +//- creation ----------------------------------------------------------------- +inline CPPDataMember* CPPDataMember_New(cppjit::interop::TCppScope_t scope, + cppjit::interop::TCppScope_t var) { + // Create an initialize a new property descriptor, given the C++ datum. + CPPDataMember* pyprop = (CPPDataMember*)CPPDataMember_Type.tp_new( + &CPPDataMember_Type, nullptr, nullptr); + pyprop->Set(scope, var); + return pyprop; +} + +inline CPPDataMember* +CPPDataMember_NewConstant(cppjit::interop::TCppScope_t scope, + const std::string& name, void* address) { + // Create an initialize a new property descriptor, given the C++ datum. + CPPDataMember* pyprop = (CPPDataMember*)CPPDataMember_Type.tp_new( + &CPPDataMember_Type, nullptr, nullptr); + pyprop->Set(scope, name, address); + return pyprop; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPDATAMEMBER_H diff --git a/src/cpyrt/CPPEnum.cxx b/src/cpyrt/CPPEnum.cxx new file mode 100644 index 0000000..6585265 --- /dev/null +++ b/src/cpyrt/CPPEnum.cxx @@ -0,0 +1,263 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPEnum.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include + +//- private helpers ---------------------------------------------------------- +static PyObject* pytype_from_enum_type(const std::string& enum_type) { + if (enum_type == "char") + return (PyObject*)&cpyrt_PyText_Type; + else if (enum_type == "bool") + return (PyObject*)&PyInt_Type; // can't use PyBool_Type as base + else if (strstr("long", enum_type.c_str())) + return (PyObject*)&PyLong_Type; + return (PyObject*)&PyInt_Type; // covers most cases +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::pyval_from_enum(const std::string& enum_type, PyObject* pytype, + PyObject* btype, + interop::TCppScope_t enum_constant) { + long long llval = interop::GetEnumDataValue(enum_constant); + + if (enum_type == "bool" && !(pytype && btype)) { + // no enum class to instantiate; the singletons can not carry attributes + PyObject* result = (bool)llval ? Py_True : Py_False; + Py_INCREF(result); + return result; + } + + PyObject* bval; + if (enum_type == "char") { + char val = (char)llval; + bval = PyUnicode_FromOrdinal((int)val); + } else if (enum_type == "int" || enum_type == "unsigned int") + bval = PyInt_FromLong((long)llval); + else + bval = PyLong_FromLongLong(llval); + + if (!bval) + return nullptr; // e.g. when out of range for small integers + + if (pytype && btype) { + PyObject* args = PyTuple_New(1); + PyTuple_SET_ITEM(args, 0, bval); + bval = ((PyTypeObject*)btype)->tp_new((PyTypeObject*)pytype, args, nullptr); + Py_DECREF(args); + } + return bval; +} + +//- enum methods ------------------------------------------------------------- +static int enum_setattro(PyObject* /* pyclass */, PyObject* /* pyname */, + PyObject* /* pyval */) { + // Helper to make enums read-only. + PyErr_SetString(PyExc_TypeError, "enum values are read-only"); + return -1; +} + +//---------------------------------------------------------------------------- +static PyObject* enum_repr(PyObject* self) { + using namespace cppjit::cpyrt; + + PyObject* kls_scope = + PyObject_GetAttr((PyObject*)Py_TYPE(self), PyStrings::gThisModule); + if (!kls_scope) + PyErr_Clear(); + PyObject* kls_cppname = + PyObject_GetAttr((PyObject*)Py_TYPE(self), PyStrings::gCppName); + if (!kls_cppname) + PyErr_Clear(); + PyObject* obj_cppname = PyObject_GetAttr(self, PyStrings::gCppName); + if (!obj_cppname) + PyErr_Clear(); + PyObject* obj_str = Py_TYPE(self)->tp_str(self); + + PyObject* repr = nullptr; + if (kls_cppname && obj_cppname && obj_str) { + const std::string resolved = + interop::ResolveEnum(PyLong_AsVoidPtr(kls_scope)); + repr = cpyrt_PyText_FromFormat( + "(%s::%s) : (%s) %s", cpyrt_PyText_AsString(kls_cppname), + cpyrt_PyText_AsString(obj_cppname), resolved.c_str(), + cpyrt_PyText_AsString(obj_str)); + } + Py_XDECREF(obj_cppname); + Py_XDECREF(kls_cppname); + + if (repr) { + Py_DECREF(obj_str); + return repr; + } + + return obj_str; +} + +//---------------------------------------------------------------------------- +// TODO: factor the following lookup with similar codes in Convertes and +// TemplateProxy.cxx + +static std::unordered_map gCTypesNames = { + {"bool", "c_bool"}, + {"char", "c_char"}, + {"wchar_t", "c_wchar"}, + {"std::byte", "c_byte"}, + {"int8_t", "c_byte"}, + {"uint8_t", "c_ubyte"}, + {"short", "c_short"}, + {"int16_t", "c_int16"}, + {"unsigned short", "c_ushort"}, + {"uint16_t", "c_uint16"}, + {"int", "c_int"}, + {"unsigned int", "c_uint"}, + {"long", "c_long"}, + {"unsigned long", "c_ulong"}, + {"long long", "c_longlong"}, + {"unsigned long long", "c_ulonglong"}}; + +// Both GetCTypesType and GetCTypesPtrType, rely on the ctypes module itself +// caching the types (thus also making them unique), so no ref-count is needed. +// Further, by keeping a ref-count on the module, it won't be off-loaded until +// the 2nd cleanup cycle. +static PyTypeObject* GetCTypesType(const std::string& cppname) { + static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept + if (!ctmod) + return nullptr; + + auto nn = gCTypesNames.find(cppname); + if (nn == gCTypesNames.end()) { + PyErr_Format(PyExc_TypeError, "Can not find ctypes type for \"%s\"", + cppname.c_str()); + return nullptr; + } + + return (PyTypeObject*)PyObject_GetAttrString(ctmod, nn->second.c_str()); +} + +static PyObject* enum_ctype(PyObject* cls, PyObject* args, PyObject* kwds) { + PyObject* pyres = PyObject_GetAttr(cls, cpyrt::PyStrings::gUnderlying); + if (!pyres) + PyErr_Clear(); + + std::string underlying = pyres ? cpyrt_PyText_AsString(pyres) : "int"; + PyTypeObject* ct = GetCTypesType(underlying); + if (!ct) + return nullptr; + + return PyType_Type.tp_call((PyObject*)ct, args, kwds); +} + +//- creation ----------------------------------------------------------------- +cpyrt::CPPEnum* cpyrt::CPPEnum_New(const std::string& name, + interop::TCppScope_t scope) { + // Create a new enum type based on the actual C++ type. Enum values are added + // to the type but may also live in the enclosing scope. + + CPPEnum* pyenum = nullptr; + + interop::TCppScope_t etype = scope; + const std::string& ename = interop::GetScopedFinalName(scope); + if (etype) { + // create new enum type with labeled values in place, with a meta-class + // to make sure the enum values are read-only + const std::string& resolved = interop::ResolveEnum(etype); + PyObject* pyside_type = pytype_from_enum_type(resolved); + PyObject* pymetabases = PyTuple_New(1); + PyObject* btype = (PyObject*)Py_TYPE(pyside_type); + Py_INCREF(btype); + PyTuple_SET_ITEM(pymetabases, 0, btype); + + PyObject* args = + Py_BuildValue((char*)"sO{}", (name + "_meta").c_str(), pymetabases); + Py_DECREF(pymetabases); + PyObject* pymeta = PyType_Type.tp_new(Py_TYPE(pyside_type), args, nullptr); + Py_DECREF(args); + + // prepare the base class + PyObject* pybases = PyTuple_New(1); + Py_INCREF(pyside_type); + PyTuple_SET_ITEM(pybases, 0, (PyObject*)pyside_type); + + // create the __cpp_name__ for templates + PyObject* dct = PyDict_New(); + PyObject* pycppname = cpyrt_PyText_FromString(ename.c_str()); + PyObject* pycppscope = PyLong_FromVoidPtr(etype.data); + PyDict_SetItem(dct, PyStrings::gCppName, pycppname); + PyDict_SetItem(dct, PyStrings::gThisModule, pycppscope); + Py_DECREF(pycppname); + PyObject* pyresolved = cpyrt_PyText_FromString(resolved.c_str()); + PyDict_SetItem(dct, PyStrings::gUnderlying, pyresolved); + Py_DECREF(pyresolved); + + // add the __module__ to allow pickling + std::string modname = TypeManip::extract_namespace(ename); + TypeManip::cppscope_to_pyscope(modname); // :: -> . + if (!modname.empty()) + modname = "." + modname; + PyObject* pymodname = + cpyrt_PyText_FromString(("cppjit.gbl" + modname).c_str()); + PyDict_SetItem(dct, PyStrings::gModule, pymodname); + Py_DECREF(pymodname); + + // create the actual enum class + args = Py_BuildValue((char*)"sOO", name.c_str(), pybases, dct); + Py_DECREF(pybases); + Py_DECREF(dct); + pyenum = + ((PyTypeObject*)pymeta)->tp_new((PyTypeObject*)pymeta, args, nullptr); + + // add pythonizations + Utility::AddToClass((PyObject*)Py_TYPE(pyenum), "__ctype__", + (PyCFunction)enum_ctype, METH_VARARGS | METH_KEYWORDS); + ((PyTypeObject*)pyenum)->tp_repr = enum_repr; + ((PyTypeObject*)pyenum)->tp_str = ((PyTypeObject*)pyside_type)->tp_repr; + + // collect the enum values + std::vector econstants = + interop::GetEnumConstants(etype); + bool values_ok = true; + for (auto econstant : econstants) { + PyObject* val = pyval_from_enum(resolved, pyenum, pyside_type, econstant); + if (!val) { + values_ok = false; + break; + } + const std::string& dname = interop::GetFinalName(econstant); + PyObject* pydname = cpyrt_PyText_FromString(dname.c_str()); + PyObject_SetAttr(pyenum, pydname, val); + Py_DECREF(pydname); + PyObject* pydcppname = cpyrt_PyText_FromString( + (ename.empty() ? dname : (ename + "::" + dname)).c_str()); + PyObject_SetAttr(val, PyStrings::gCppName, pydcppname); + Py_DECREF(pydcppname); + Py_DECREF(val); + } + + // disable writing onto enum values + ((PyTypeObject*)pymeta)->tp_setattro = enum_setattro; + + // final cleanup + Py_DECREF(args); + Py_DECREF(pymeta); + + if (!values_ok) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, + "could not set some of the enum values"); + Py_DECREF(pyenum); + return nullptr; + } + + } else { + // presumably not a class enum; simply pretend int + Py_INCREF(&PyInt_Type); + pyenum = (PyObject*)&PyInt_Type; + } + + return pyenum; +} diff --git a/src/cpyrt/CPPEnum.h b/src/cpyrt/CPPEnum.h new file mode 100644 index 0000000..7ac2e58 --- /dev/null +++ b/src/cpyrt/CPPEnum.h @@ -0,0 +1,19 @@ +#ifndef CPYRT_CPPENUM_H +#define CPYRT_CPPENUM_H + +namespace cppjit::cpyrt { + +// CPPEnum does not carry any additional C-side data for now, but can be of +// several types, based on the declared or compile-dependent types chosen. +typedef PyObject CPPEnum; + +//- creation ----------------------------------------------------------------- +CPPEnum* CPPEnum_New(const std::string& name, + cppjit::interop::TCppScope_t scope); + +PyObject* pyval_from_enum(const std::string& enum_type, PyObject* pytype, + PyObject* btype, + cppjit::interop::TCppScope_t enum_constant); +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPENUM_H diff --git a/src/cpyrt/CPPExcInstance.cxx b/src/cpyrt/CPPExcInstance.cxx new file mode 100644 index 0000000..6a668f0 --- /dev/null +++ b/src/cpyrt/CPPExcInstance.cxx @@ -0,0 +1,253 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPExcInstance.h" +#include "PyStrings.h" + +//______________________________________________________________________________ +// Python-side exception proxy objects +// =================================== +// +// Python exceptions need to derive from PyException as it expects a set of +// specific data members. This class exists so that general CPPInstances need +// not carry that overhead. In use, it forwards to the embedded CPPInstance. + +namespace cppjit::cpyrt { + +//= cpyrt exception object proxy construction/destruction ================= +static PyObject* ep_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) { + // Create a new exception object proxy (holder only). + PyObject* pyobj = + ((PyTypeObject*)PyExc_Exception)->tp_new(subtype, nullptr, nullptr); + if (!pyobj) + return nullptr; + + CPPExcInstance* excobj = (CPPExcInstance*)pyobj; + excobj->fTopMessage = nullptr; + if (args) { + PyObject* ulc = + PyObject_GetAttr((PyObject*)subtype, PyStrings::gUnderlying); + excobj->fCppInstance = PyType_Type.tp_call(ulc, args, kwds); + if (!excobj->fCppInstance) { + // if this fails, then the construction may have been attempted from a + // string (e.g. from PyErr_Format); if so, drop the proxy and use + // fTopMessage instead + PyErr_Clear(); + if (PyTuple_GET_SIZE(args) == 1) { + PyObject* msg = PyTuple_GET_ITEM(args, 0); + if (cpyrt_PyText_Check(msg)) { + Py_INCREF(msg); + excobj->fTopMessage = msg; + } + } + } + Py_DECREF(ulc); + } else + excobj->fCppInstance = nullptr; + + return pyobj; +} + +//---------------------------------------------------------------------------- +static int ep_traverse(CPPExcInstance* pyobj, visitproc visit, void* args) { + // Garbage collector traverse of held python member objects. + ((PyTypeObject*)PyExc_Exception)->tp_traverse((PyObject*)pyobj, visit, args); + + if (pyobj->fCppInstance) + visit(pyobj->fCppInstance, args); + + if (pyobj->fTopMessage) + visit(pyobj->fTopMessage, args); + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* ep_str(CPPExcInstance* self) { + if (self->fCppInstance) { + PyObject* what = + PyObject_CallMethod((PyObject*)self, (char*)"what", nullptr); + if (what) { + if (self->fTopMessage) { + Py_INCREF(self->fTopMessage); + PyObject* top = self->fTopMessage; + cpyrt_PyText_Append(&top, what); + Py_DECREF(what); + what = top; + } + return what; + } + PyErr_Clear(); + return PyObject_Str(self->fCppInstance); + } + + if (self->fTopMessage) { + Py_INCREF(self->fTopMessage); + return self->fTopMessage; + } + + return PyType_Type.tp_str((PyObject*)self); +} + +//---------------------------------------------------------------------------- +static PyObject* ep_repr(CPPExcInstance* self) { + if (self->fCppInstance) + return PyObject_Repr(self->fCppInstance); + return PyType_Type.tp_repr((PyObject*)self); +} + +//---------------------------------------------------------------------------- +static void ep_dealloc(CPPExcInstance* pyobj) { + PyObject* tmp1 = pyobj->fCppInstance; + PyObject* tmp2 = pyobj->fTopMessage; + + ((PyTypeObject*)PyExc_Exception)->tp_dealloc((PyObject*)pyobj); + Py_CLEAR(tmp1); + Py_CLEAR(tmp2); +} + +//---------------------------------------------------------------------------- +static int ep_clear(CPPExcInstance* pyobj) { + // Garbage collector clear of held python member objects. + ((PyTypeObject*)PyExc_Exception)->tp_clear((PyObject*)pyobj); + + Py_CLEAR(pyobj->fCppInstance); + Py_CLEAR(pyobj->fTopMessage); + + return 0; +} + +//= forwarding methods ======================================================= +static PyObject* ep_getattro(CPPExcInstance* self, PyObject* attr) { + if (self->fCppInstance) { + PyObject* res = PyObject_GetAttr(self->fCppInstance, attr); + if (res) + return res; + PyErr_Clear(); + } + + return ((PyTypeObject*)PyExc_Exception)->tp_getattro((PyObject*)self, attr); +} + +//---------------------------------------------------------------------------- +static int ep_setattro(CPPExcInstance* self, PyObject* attr, PyObject* value) { + if (self->fCppInstance) { + int res = PyObject_SetAttr(self->fCppInstance, attr, value); + if (!res) + return res; + PyErr_Clear(); + } + + return ((PyTypeObject*)PyExc_Exception) + ->tp_setattro((PyObject*)self, attr, value); +} + +//---------------------------------------------------------------------------- +static PyObject* ep_richcompare(CPPExcInstance* self, PyObject* other, int op) { + return PyObject_RichCompare(self->fCppInstance, other, op); +} + +//---------------------------------------------------------------------------- +static int ep_nonzero(CPPExcInstance* self) { + if (self->fCppInstance) + return PyObject_IsTrue(self->fCppInstance); + + return 0; +} + +//----------------------------------------------------------------------------- +static PyNumberMethods ep_as_number = { + 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 + (inquiry)ep_nonzero, // nb_bool (nb_nonzero in p2) + 0, // nb_invert + 0, // nb_lshift + 0, // nb_rshift + 0, // nb_and + 0, // nb_xor + 0, // nb_or + 0, // nb_int + 0, // nb_long (nb_reserved in p3) + 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 +}; + +//= cpyrt exception object proxy type ====================================== +PyTypeObject CPPExcInstance_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.CPPExcInstance", // tp_name + sizeof(CPPExcInstance), // tp_basicsize + 0, // tp_itemsize + (destructor)ep_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + (reprfunc)ep_repr, // tp_repr + &ep_as_number, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + (reprfunc)ep_str, // tp_str + (getattrofunc)ep_getattro, // tp_getattro + (setattrofunc)ep_setattro, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_BASE_EXC_SUBCLASS | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES, // tp_flags + (char*)"cppjit exception object proxy (internal)", // tp_doc + (traverseproc)ep_traverse, // tp_traverse + (inquiry)ep_clear, // tp_clear + (richcmpfunc)ep_richcompare, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + 0, // tp_methods + 0, // tp_members + 0, // tp_getset + (PyTypeObject*)PyExc_Exception, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)ep_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 + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/src/cpyrt/CPPExcInstance.h b/src/cpyrt/CPPExcInstance.h new file mode 100644 index 0000000..ee7bf59 --- /dev/null +++ b/src/cpyrt/CPPExcInstance.h @@ -0,0 +1,34 @@ +#ifndef CPYRT_CPPEXCINSTANCE_H +#define CPYRT_CPPEXCINSTANCE_H + +////////////////////////////////////////////////////////////////////////////// +// // +// Cpycppjit::interop::CPPExceptionInstance // +// // +// Python-side proxy, encapsulaties a C++ exception object. // +// // +////////////////////////////////////////////////////////////////////////////// + +namespace cppjit::cpyrt { + +class CPPExcInstance { +public: + PyBaseExceptionObject fBase; + PyObject* fCppInstance; + PyObject* fTopMessage; +}; + +//- object proxy type and type verification ---------------------------------- +CPYRT_IMPORT PyTypeObject CPPExcInstance_Type; + +template inline bool CPPExcInstance_Check(T* object) { + return object && PyObject_TypeCheck(object, &CPPExcInstance_Type); +} + +template inline bool CPPExcInstance_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPExcInstance_Type; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPEXCINSTANCE_H diff --git a/src/cpyrt/CPPFunction.cxx b/src/cpyrt/CPPFunction.cxx new file mode 100644 index 0000000..cc6e852 --- /dev/null +++ b/src/cpyrt/CPPFunction.cxx @@ -0,0 +1,138 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPFunction.h" +#include "CPPInstance.h" + +// Standard +#include + +//- CFunction helpers +//----------------------------------------------------------- +bool cpyrt::AdjustSelf(PyCallArgs& cargs) { + if (cargs.fNArgsf & PY_VECTORCALL_ARGUMENTS_OFFSET) { // mutation allowed? + std::swap(((PyObject**)cargs.fArgs - 1)[0], (PyObject*&)cargs.fSelf); + cargs.fFlags |= PyCallArgs::kSelfSwap; + cargs.fArgs -= 1; + cargs.fNArgsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + cargs.fNArgsf += 1; + } else { + Py_ssize_t nkwargs = cargs.fKwds ? PyTuple_GET_SIZE(cargs.fKwds) : 0; + Py_ssize_t totalargs = PyVectorcall_NARGS(cargs.fNArgsf) + nkwargs; + PyObject** newArgs = + (PyObject**)PyMem_Malloc((totalargs + 1) * sizeof(PyObject*)); + if (!newArgs) + return false; + + newArgs[0] = (PyObject*)cargs.fSelf; + if (0 < totalargs) + memcpy((void*)&newArgs[1], cargs.fArgs, totalargs * sizeof(PyObject*)); + cargs.fArgs = newArgs; + cargs.fFlags |= PyCallArgs::kDoFree; + cargs.fNArgsf += 1; + } + return true; +} + +bool cpyrt::CPPFunction::ProcessArgs(PyCallArgs& cargs) { + // add self as part of the function arguments (means bound member) + if (cargs.fKwds) + return this->ProcessKwds((PyObject*)cargs.fSelf, cargs); + return AdjustSelf(cargs); +} + +//- CPPFunction public members +//-------------------------------------------------- +PyObject* cpyrt::CPPFunction::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // setup as necessary + if (fArgsRequired == -1 && !this->Initialize(ctxt)) + return nullptr; + + // if function was attached to a class, self will be non-zero and should be + // the first function argument, so reorder + PyCallArgs cargs{self, args, nargsf, kwds}; + if (self || kwds) { + if (!this->ProcessArgs(cargs)) + return nullptr; + } + + // special case, if this method was inserted as a constructor, then self is + // nullptr and it will be the first argument and needs to be used as Python + // context + if (IsConstructor(ctxt->fFlags) && !ctxt->fPyContext && + cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf)) { + ctxt->fPyContext = cargs.fArgs[0]; + } + + // translate the arguments as normal + if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) + return nullptr; + + // execute function + PyObject* result = this->Execute(nullptr, 0, ctxt); + + // special case, if this method was inserted as a constructor, then if no self + // was provided, it will be the first argument and may have been updated + if (IsConstructor(ctxt->fFlags) && result && !cargs.fSelf && + cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) && + CPPInstance_Check(cargs.fArgs[0])) { + self = (CPPInstance*)cargs.fArgs[0]; + Py_INCREF(self); + } + + return result; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPFunction::GetTypeName() { + PyObject* cppname = + cpyrt_PyText_FromString((GetReturnTypeName() + " (*)").c_str()); + cpyrt_PyText_AppendAndDel(&cppname, + GetSignature(false /* show_formalargs */)); + return cppname; +} + +//- CPPReverseBinary private helper +//--------------------------------------------- +bool cpyrt::CPPReverseBinary::ProcessArgs(PyCallArgs& cargs) { + if (cargs.fSelf || cargs.fKwds) { + // add self as part of the function arguments (means bound member) + if (!this->CPPFunction::ProcessArgs(cargs)) + return false; + } + + // swap the arguments + std::swap(((PyObject**)cargs.fArgs)[0], ((PyObject**)cargs.fArgs)[1]); + cargs.fFlags |= PyCallArgs::kArgsSwap; + + return true; +} + +//- CPPReverseBinary public members +//--------------------------------------------- +PyObject* cpyrt::CPPReverseBinary::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // This Call() function is very similar to the one of CPPFunction: only + // difference is that ProcessArgs() is always called. + + // setup as necessary + if (fArgsRequired == -1 && !this->Initialize(ctxt)) + return nullptr; + + // if function was attached to a class, self will be non-zero and should be + // the first function argument, further, the arguments needs swapping + PyCallArgs cargs{self, args, nargsf, kwds}; + if (!this->ProcessArgs(cargs)) + return nullptr; + + // translate the arguments as normal + if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) + return nullptr; + + // execute function + return this->Execute(nullptr, 0, ctxt); +} diff --git a/src/cpyrt/CPPFunction.h b/src/cpyrt/CPPFunction.h new file mode 100644 index 0000000..9e356c8 --- /dev/null +++ b/src/cpyrt/CPPFunction.h @@ -0,0 +1,44 @@ +#ifndef CPYRT_CPPFUNCTION_H +#define CPYRT_CPPFUNCTION_H + +// Bindings +#include "CPPMethod.h" + +namespace cppjit::cpyrt { + +// Wrapper for global free/static C++ functions +class CPPFunction : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyObject* GetTypeName() override; + +public: + PyCallable* Clone() override { return new CPPFunction(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +protected: + bool ProcessArgs(PyCallArgs& args) override; +}; + +// Wrapper for global binary operators that swap arguments +class CPPReverseBinary : public CPPFunction { +public: + using CPPFunction::CPPFunction; + + PyCallable* Clone() override { return new CPPFunction(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +protected: + bool ProcessArgs(PyCallArgs& args) override; +}; + +// Helper to add self to the arguments tuple if rebound +bool AdjustSelf(PyCallArgs& cargs); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPFUNCTION_H diff --git a/src/cpyrt/CPPGetSetItem.cxx b/src/cpyrt/CPPGetSetItem.cxx new file mode 100644 index 0000000..bb132b5 --- /dev/null +++ b/src/cpyrt/CPPGetSetItem.cxx @@ -0,0 +1,103 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPGetSetItem.h" +#include "Executors.h" + +//- private helpers ----------------------------------------------------------- +static inline void unroll(cpyrt_PyArgs_t packed_args, cpyrt_PyArgs_t unrolled, + Py_ssize_t nArgs) { + // Unroll up to nArgs arguments from packed_args into unrolled. + for (int i = 0, iur = 0; i < nArgs; ++i, ++iur) { + PyObject* item = cpyrt_PyArgs_GET_ITEM(packed_args, i); + if (PyTuple_Check(item)) { + for (int j = 0; j < PyTuple_GET_SIZE(item); ++j, ++iur) { + PyObject* subitem = PyTuple_GET_ITEM(item, j); + Py_INCREF(subitem); + cpyrt_PyArgs_SET_ITEM(unrolled, iur, subitem); + } + } else { + Py_INCREF(item); + cpyrt_PyArgs_SET_ITEM(unrolled, iur, item); + } + } +} + +//- protected members --------------------------------------------------------- +bool cpyrt::CPPSetItem::InitExecutor_(Executor*& executor, CallContext*) { + // basic call will do + if (!CPPMethod::InitExecutor_(executor)) + return false; + + // check to make sure we're dealing with a RefExecutor + if (!dynamic_cast(executor)) { + PyErr_Format(PyExc_NotImplementedError, + "no __setitem__ handler for return type (%s)", + this->GetReturnTypeName().c_str()); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +bool cpyrt::CPPSetItem::ProcessArgs(PyCallArgs& cargs) { + // Prepare executor with a buffer for the return value. + Py_ssize_t nArgs = cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); + if (nArgs <= 1) { + PyErr_SetString(PyExc_TypeError, "insufficient arguments to __setitem__"); + return false; + } + + // use the last element of args for assignment upon return, then slice it from + // the (unrolled) actual arguments + ((RefExecutor*)this->GetExecutor()) + ->SetAssignable(cpyrt_PyArgs_GET_ITEM(cargs.fArgs, nArgs - 1)); + + // see whether any of the arguments is a tuple to be unrolled + Py_ssize_t realsize = 0; + for (Py_ssize_t i = 0; i < nArgs - 1; ++i) { + PyObject* item = cpyrt_PyArgs_GET_ITEM(cargs.fArgs, i); + realsize += PyTuple_Check(item) ? PyTuple_GET_SIZE(item) : 1; + } + + // unroll any tuples, if present in the arguments + if (realsize != nArgs - 1) { + cpyrt_PyArgs_t unrolled = + (PyObject**)PyMem_Malloc(realsize * sizeof(PyObject*)); + unroll(cargs.fArgs, unrolled, nArgs - 1); + cargs.fArgs = unrolled; + cargs.fFlags |= PyCallArgs::kDoFree; + } + cargs.fNArgsf = realsize; + + // continue normal method processing + return CPPMethod::ProcessArgs(cargs); +} + +//----------------------------------------------------------------------------- +bool cpyrt::CPPGetItem::ProcessArgs(PyCallArgs& cargs) { + // Unroll tuples for call, otherwise just like regular CPPMethod of + // __getitem__. + Py_ssize_t nArgs = cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); + + // see whether any of the arguments is a tuple to be unrolled + Py_ssize_t realsize = 0; + for (Py_ssize_t i = 0; i < nArgs; ++i) { + PyObject* item = cpyrt_PyArgs_GET_ITEM(cargs.fArgs, i); + realsize += PyTuple_Check(item) ? PyTuple_GET_SIZE(item) : 1; + } + + // unroll any tuples, if present in the arguments + if (realsize != nArgs) { + cpyrt_PyArgs_t packed_args = cargs.fArgs; + cargs.fArgs = (PyObject**)PyMem_Malloc(realsize * sizeof(PyObject*)); + cargs.fFlags |= PyCallArgs::kDoFree; + cargs.fNArgsf = realsize; + unroll(packed_args, cargs.fArgs, nArgs); + } + + // continue normal method processing + return CPPMethod::ProcessArgs(cargs); +} diff --git a/src/cpyrt/CPPGetSetItem.h b/src/cpyrt/CPPGetSetItem.h new file mode 100644 index 0000000..aad043e --- /dev/null +++ b/src/cpyrt/CPPGetSetItem.h @@ -0,0 +1,34 @@ +#ifndef CPYRT_CPPGETSETITEM_H +#define CPYRT_CPPGETSETITEM_H + +// Bindings +#include "CPPMethod.h" + +namespace cppjit::cpyrt { + +class CPPSetItem : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyCallable* Clone() override { return new CPPSetItem(*this); } + +protected: + bool ProcessArgs(PyCallArgs& args) override; + bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr) override; +}; + +class CPPGetItem : public CPPMethod { +public: + using CPPMethod::CPPMethod; + +public: + PyCallable* Clone() override { return new CPPGetItem(*this); } + +protected: + bool ProcessArgs(PyCallArgs& args) override; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPGETSETITEM_H diff --git a/src/cpyrt/CPPInstance.cxx b/src/cpyrt/CPPInstance.cxx new file mode 100644 index 0000000..9d0668c --- /dev/null +++ b/src/cpyrt/CPPInstance.cxx @@ -0,0 +1,1148 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +#include "cpyrt/DispatchPtr.h" + +// Standard +#include +#include + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { +extern PyObject* gNullPtrObject; +} + +//______________________________________________________________________________ +// Python-side proxy objects +// ========================= +// +// C++ objects are represented in Python by CPPInstances, which encapsulate +// them using either a pointer (normal), pointer-to-pointer (kIsReference set), +// or as an owned value (kIsValue set). Objects held as reference are never +// owned, otherwise the object is owned if kIsOwner is set. +// +// In addition to encapsulation, CPPInstance offers rudimentary comparison +// operators (based on pointer value and class comparisons); stubs (with lazy +// lookups) for numeric operators; and a representation that prints the C++ +// pointer values, rather than the PyObject* ones as is the default. +// +// Smart pointers have the underlying type as the Python type, but store the +// pointer to the smart pointer. They carry a pointer to the Python-sode smart +// class for dereferencing to get to the actual instance pointer. + +//- private helpers ---------------------------------------------------------- +namespace { + +// Several specific use cases require extra data in a CPPInstance, but can not +// be a new type. E.g. cross-inheritance derived types are by definition added +// a posterio, and caching of datamembers is up to the datamember, not the +// instance type. To not have normal use of CPPInstance take extra memory, this +// extended data can slot in place of fObject for those use cases. + +struct ExtendedData { + ExtendedData() + : fObject(nullptr), fSmartClass(nullptr), fDispatchPtr(nullptr), + fArraySize(0) {} + ~ExtendedData() { + for (auto& pc : fDatamemberCache) + Py_XDECREF(pc.second); + fDatamemberCache.clear(); + } + + // the original object reference it replaces (Note: has to be first data + // member, see usage in GetObjectRaw(), e.g. for ptr-ptr passing) + void* fObject; + + // for caching expensive-to-create data member representations + cpyrt::CI_DatamemberCache_t fDatamemberCache; + + // for smart pointer types + cpyrt::CPPSmartClass* fSmartClass; + + // for back-referencing from Python-derived instances + cppjit::cpyrt::DispatchPtr* fDispatchPtr; + + // for representing T* as a low-level array + Py_ssize_t fArraySize; +}; + +} // unnamed namespace + +#define EXT_OBJECT(pyobj) ((ExtendedData*)((pyobj)->fObject))->fObject +#define DATA_CACHE(pyobj) ((ExtendedData*)((pyobj)->fObject))->fDatamemberCache +#define SMART_CLS(pyobj) ((ExtendedData*)((pyobj)->fObject))->fSmartClass +#define SMART_TYPE(pyobj) SMART_CLS(pyobj)->fCppType +#define DISPATCHPTR(pyobj) ((ExtendedData*)((pyobj)->fObject))->fDispatchPtr +#define ARRAY_SIZE(pyobj) ((ExtendedData*)((pyobj)->fObject))->fArraySize + +inline void cpyrt::CPPInstance::CreateExtension() { + if (fFlags & kIsExtended) + return; + void* obj = fObject; + fObject = (void*)new ExtendedData{}; + EXT_OBJECT(this) = obj; + fFlags |= kIsExtended; +} + +void* cpyrt::CPPInstance::GetExtendedObject() { + if (IsSmart()) { + // We get the raw pointer from the smart pointer each time, in case it has + // changed or has been freed. + return interop::CallR(SMART_CLS(this)->fDereferencer, EXT_OBJECT(this), 0, + nullptr); + } + return EXT_OBJECT(this); +} + +//- public methods ----------------------------------------------------------- +cpyrt::CPPInstance* cpyrt::CPPInstance::Copy(void* cppinst, + PyTypeObject* target) { + // create a fresh instance; args and kwds are not used by op_new (see below) + PyObject* self = (PyObject*)this; + if (!target) + target = Py_TYPE(self); + PyObject* newinst = target->tp_new(target, nullptr, nullptr); + + // set the C++ instance as given + ((CPPInstance*)newinst)->fObject = cppinst; + + // look for user-provided __cpp_copy__ (not reusing __copy__ b/c of + // differences in semantics: need to pass in the new instance) ... + PyObject* cpy = PyObject_GetAttrString(self, (char*)"__cpp_copy__"); + if (cpy && PyCallable_Check(cpy)) { + PyObject* args = PyTuple_New(1); + Py_INCREF(newinst); + PyTuple_SET_ITEM(args, 0, newinst); + PyObject* res = PyObject_CallObject(cpy, args); + Py_DECREF(args); + Py_DECREF(cpy); + if (res) { + Py_DECREF(res); + return (CPPInstance*)newinst; + } + + // error already set, but need to return nullptr + Py_DECREF(newinst); + return nullptr; + } else if (cpy) + Py_DECREF(cpy); + else + PyErr_Clear(); + + // ... otherwise, shallow copy any Python-side dictionary items + PyObject* selfdct = PyObject_GetAttr(self, PyStrings::gDict); + PyObject* newdct = PyObject_GetAttr(newinst, PyStrings::gDict); + bool bMergeOk = PyDict_Merge(newdct, selfdct, 1) == 0; + Py_DECREF(newdct); + Py_DECREF(selfdct); + + if (!bMergeOk) { + // presume error already set + Py_DECREF(newinst); + return nullptr; + } + + MemoryRegulator::RegisterPyObject((CPPInstance*)newinst, cppinst); + return (CPPInstance*)newinst; +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPInstance::PythonOwns() { + fFlags |= kIsOwner; + if ((fFlags & kIsExtended) && DISPATCHPTR(this)) + DISPATCHPTR(this)->PythonOwns(); +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPInstance::CppOwns() { + fFlags &= ~kIsOwner; + if ((fFlags & kIsExtended) && DISPATCHPTR(this)) + DISPATCHPTR(this)->CppOwns(); +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPInstance::SetSmart(PyObject* smart_type) { + CreateExtension(); + Py_INCREF(smart_type); + SMART_CLS(this) = (CPPSmartClass*)smart_type; + fFlags |= kIsSmartPtr; +} + +//---------------------------------------------------------------------------- +interop::TCppScope_t cpyrt::CPPInstance::GetSmartIsA() const { + if (!IsSmart()) + return interop::TCppScope_t{}; + return SMART_TYPE(this); +} + +//---------------------------------------------------------------------------- +interop::TCppScope_t cpyrt::CPPInstance::GetSmartUnderlyingType() const { + // Declared underlying type of the embedded smart pointer ('Base' for a + // std::unique_ptr), independent of any downcast of the dereferenced + // object. + if (!IsSmart()) + return interop::TCppScope_t{}; + return SMART_CLS(this)->fUnderlyingType; +} + +//---------------------------------------------------------------------------- +cpyrt::CI_DatamemberCache_t& cpyrt::CPPInstance::GetDatamemberCache() { + // Return the cache for expensive data objects (and make extended as + // necessary) + CreateExtension(); + return DATA_CACHE(this); +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPInstance::SetDispatchPtr(void* ptr) { + // Set up the dispatch pointer for memory management + CreateExtension(); + DISPATCHPTR(this) = (DispatchPtr*)ptr; +} + +//---------------------------------------------------------------------------- +void cpyrt::op_dealloc_nofree(CPPInstance* pyobj) { + // Destroy the held C++ object, if owned; does not deallocate the proxy. + + interop::TCppScope_t klass = pyobj->ObjectIsA(false /* check_smart */); + void*& cppobj = pyobj->GetObjectRaw(); + + if (pyobj->fFlags & CPPInstance::kIsRegulated) + MemoryRegulator::UnregisterPyObject(pyobj, + (PyObject*)Py_TYPE((PyObject*)pyobj)); + + if (cppobj && (pyobj->fFlags & CPPInstance::kIsOwner)) { + if (pyobj->fFlags & CPPInstance::kIsValue) { + interop::CallDestructor(klass, cppobj); + interop::Deallocate(klass, cppobj); + } else + interop::Destruct(klass, cppobj); + } + cppobj = nullptr; + + if (pyobj->IsExtended()) + delete (ExtendedData*)pyobj->fObject; + pyobj->fFlags = CPPInstance::kNoWrapConv; +} + +namespace cppjit::cpyrt { + +//---------------------------------------------------------------------------- +static int op_traverse(CPPInstance* /*pyobj*/, visitproc /*visit*/, + void* /*arg*/) { + return 0; +} + +//= cpyrt object proxy null-ness checking ================================= +static int op_nonzero(CPPInstance* self) { + // Null of the proxy is determined by null-ness of the held C++ object. + if (!self->GetObject()) + return 0; + + // If the object is valid, then the normal Python behavior is to allow __len__ + // to determine truth. However, that function is defined in typeobject.c and + // only installed if tp_as_number exists w/o the nb_nonzero/nb_bool slot + // filled in, so it can not be called directly. Instead, since we're only ever + // dealing with CPPInstance derived objects, ignore length from sequence or + // mapping and call the __len__ method, if any, directly. + + PyObject* pylen = + PyObject_CallMethodObjArgs((PyObject*)self, PyStrings::gLen, NULL); + if (!pylen) { + PyErr_Clear(); + return 1; // since it's still a valid object + } + + int result = PyObject_IsTrue(pylen); + Py_DECREF(pylen); + return result; +} + +//= cpyrt object explicit destruction ===================================== +static PyObject* op_destruct(CPPInstance* self) { + // User access to force deletion of the object. Needed in case of a true + // garbage collector (like in PyPy), to allow the user control over when + // the C++ destructor is called. This method requires that the C++ object + // is owned (no-op otherwise). + op_dealloc_nofree(self); + Py_RETURN_NONE; +} + +//= cpyrt object dispatch support ========================================= +static PyObject* op_dispatch(PyObject* self, PyObject* args, + PyObject* /* kdws */) { + // User-side __dispatch__ method to allow selection of a specific overloaded + // method. The actual selection is in the __overload__() method of + // CPPOverload. + PyObject *mname = nullptr, *sigarg = nullptr; + if (!PyArg_ParseTuple(args, const_cast("O!O!:__dispatch__"), + &cpyrt_PyText_Type, &mname, &cpyrt_PyText_Type, + &sigarg)) + return nullptr; + + // get the named overload + PyObject* pymeth = PyObject_GetAttr(self, mname); + if (!pymeth) + return nullptr; + + // get the '__overload__' method to allow overload selection + PyObject* pydisp = + PyObject_GetAttrString(pymeth, const_cast("__overload__")); + if (!pydisp) { + Py_DECREF(pymeth); + return nullptr; + } + + // finally, call dispatch to get the specific overload + PyObject* oload = PyObject_CallFunctionObjArgs(pydisp, sigarg, nullptr); + Py_DECREF(pydisp); + Py_DECREF(pymeth); + return oload; +} + +//= cpyrt smart pointer support =========================================== +static PyObject* op_get_smartptr(CPPInstance* self) { + if (!self->IsSmart()) { + // TODO: more likely should raise + Py_RETURN_NONE; + } + + return cpyrt::BindCppObjectNoCast(self->GetSmartObject(), SMART_TYPE(self), + CPPInstance::kNoWrapConv); +} + +//= pointer-as-array support for legacy C code =============================== +void cpyrt::CPPInstance::CastToArray(Py_ssize_t sz) { + CreateExtension(); + fFlags |= kIsArray; + ARRAY_SIZE(this) = sz; +} + +Py_ssize_t cpyrt::CPPInstance::ArrayLength() { + if (!(fFlags & kIsArray)) + return -1; + return (Py_ssize_t)ARRAY_SIZE(this); +} + +static PyObject* op_reshape(CPPInstance* self, PyObject* shape) { + // Allow the user to fix up the actual (type-strided) size of the buffer. + if (!PyTuple_Check(shape) || PyTuple_GET_SIZE(shape) != 1) { + PyErr_SetString(PyExc_TypeError, "tuple object of size 1 expected"); + return nullptr; + } + + long sz = PyLong_AsLong(PyTuple_GET_ITEM(shape, 0)); + if (sz <= 0) { + PyErr_SetString(PyExc_ValueError, "array length must be positive"); + return nullptr; + } + + self->CastToArray(sz); + + Py_RETURN_NONE; +} + +static PyObject* op_item(CPPInstance* self, Py_ssize_t idx) { + // In C, it is common to represent an array of structs as a pointer to the + // first object in the array. If the caller indexes a pointer to an object + // that does not define indexing, then highly likely such C-style indexing is + // the goal. Just like C, this is potentially unsafe, so caveat emptor. + + if (!(self->fFlags & (CPPInstance::kIsReference | CPPInstance::kIsArray))) { + PyErr_Format(PyExc_TypeError, "%s object does not support indexing", + Py_TYPE(self)->tp_name); + return nullptr; + } + + if (idx < 0) { + // this is debatable, and probably should not care, but the use case is + // pretty circumscribed anyway, so might as well keep the functionality + // simple + PyErr_SetString(PyExc_IndexError, + "negative indices not supported for array of structs"); + return nullptr; + } + + if (self->fFlags & CPPInstance::kIsArray) { + Py_ssize_t maxidx = ARRAY_SIZE(self); + if (0 <= maxidx && maxidx <= idx) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return nullptr; + } + } + + unsigned flags = 0; + size_t sz = sizeof(void*); + if (self->fFlags & CPPInstance::kIsPtrPtr) { + flags = CPPInstance::kIsReference; + } else { + sz = interop::SizeOf(((CPPClass*)Py_TYPE(self))->fCppType); + } + + uintptr_t address = + (uintptr_t)(flags ? self->GetObjectRaw() : self->GetObject()); + void* indexed_obj = (void*)(address + (uintptr_t)(idx * sz)); + + return BindCppObjectNoCast(indexed_obj, ((CPPClass*)Py_TYPE(self))->fCppType, + flags); +} + +//- sequence methods -------------------------------------------------------- +static PySequenceMethods op_as_sequence = { + 0, // sq_length + 0, // sq_concat + 0, // sq_repeat + (ssizeargfunc)op_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 +}; + +PyCFunction& CPPInstance::ReduceMethod() { + static PyCFunction reducer = nullptr; + return reducer; +} + +PyObject* op_reduce(PyObject* self, PyObject* args) { + auto& reducer = CPPInstance::ReduceMethod(); + if (!reducer) { + PyErr_SetString(PyExc_NotImplementedError, ""); + return nullptr; + } + return reducer(self, args); +} + +//---------------------------------------------------------------------------- +static PyMethodDef op_methods[] = { + {(char*)"__destruct__", (PyCFunction)op_destruct, METH_NOARGS, + (char*)"call the C++ destructor"}, + {(char*)"__dispatch__", (PyCFunction)op_dispatch, METH_VARARGS, + (char*)"dispatch to selected overload"}, + {(char*)"__smartptr__", (PyCFunction)op_get_smartptr, METH_NOARGS, + (char*)"get associated smart pointer, if any"}, + {(char*)"__reduce__", (PyCFunction)op_reduce, METH_NOARGS, + (char*)"reduce method for serialization"}, + {(char*)"__reshape__", (PyCFunction)op_reshape, METH_O, + (char*)"cast pointer to 1D array type"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//= cpyrt object proxy construction/destruction =========================== +static CPPInstance* op_new(PyTypeObject* subtype, PyObject*, PyObject*) { + // Create a new object proxy (holder only). + CPPInstance* pyobj = (CPPInstance*)subtype->tp_alloc(subtype, 0); + pyobj->fObject = nullptr; + pyobj->fFlags = CPPInstance::kNoWrapConv; + + return pyobj; +} + +//---------------------------------------------------------------------------- +static void op_dealloc(CPPInstance* pyobj) { + // Remove (Python-side) memory held by the object proxy. + PyObject_GC_UnTrack((PyObject*)pyobj); + op_dealloc_nofree(pyobj); + PyObject_GC_Del((PyObject*)pyobj); +} + +//---------------------------------------------------------------------------- +static int op_clear(CPPInstance* pyobj) { + // Garbage collector clear of held python member objects; this is a good time + // to safely remove this object from the memory regulator. + if (pyobj->fFlags & CPPInstance::kIsRegulated) + MemoryRegulator::UnregisterPyObject(pyobj, + (PyObject*)Py_TYPE((PyObject*)pyobj)); + + return 0; +} + +//---------------------------------------------------------------------------- +static inline PyObject* eqneq_binop(CPPClass* klass, PyObject* self, + PyObject* obj, int op) { + using namespace Utility; + + // special case for C++11 style nullptr + if (obj == gNullPtrObject) { + void* rawcpp = ((CPPInstance*)self)->GetObjectRaw(); + switch (op) { + case Py_EQ: + if (rawcpp == nullptr) + Py_RETURN_TRUE; + Py_RETURN_FALSE; + case Py_NE: + if (rawcpp != nullptr) + Py_RETURN_TRUE; + Py_RETURN_FALSE; + default: + return nullptr; // not implemented + } + } + + if (!klass->fOperators) + klass->fOperators = new PyOperators{}; + + bool flipit = false; + PyObject* binop = + op == Py_EQ ? klass->fOperators->fEq : klass->fOperators->fNe; + if (!binop) { + binop = op == Py_EQ ? klass->fOperators->fNe : klass->fOperators->fEq; + if (binop) + flipit = true; + } + if (!binop) { + const char* cppop = op == Py_EQ ? "==" : "!="; + PyCallable* pyfunc = FindBinaryOperator(self, obj, cppop); + if (pyfunc) + binop = (PyObject*)CPPOverload_New(cppop, pyfunc); + else { + Py_INCREF(Py_None); + binop = Py_None; + } + // sets the operator to Py_None if not found, indicating that search was + // done + if (op == Py_EQ) + klass->fOperators->fEq = binop; + else + klass->fOperators->fNe = binop; + } + + if (!binop || binop == Py_None) + return nullptr; + + PyObject* args = PyTuple_New(1); + Py_INCREF(obj); + PyTuple_SET_ITEM(args, 0, obj); + // since this overload is "ours", don't have to worry about rebinding + ((CPPOverload*)binop)->fSelf = (CPPInstance*)self; + PyObject* result = CPPOverload_Type.tp_call(binop, args, nullptr); + ((CPPOverload*)binop)->fSelf = nullptr; + Py_DECREF(args); + + if (!result) { + PyErr_Clear(); + return nullptr; + } + + // successful result, but may need to reverse the outcome + if (!flipit) + return result; + + int istrue = PyObject_IsTrue(result); + Py_DECREF(result); + if (istrue) { + Py_RETURN_FALSE; + } + Py_RETURN_TRUE; +} + +static inline void* cast_actual(void* obj) { + void* address = ((CPPInstance*)obj)->GetObject(); + if (((CPPInstance*)obj)->fFlags & CPPInstance::kIsActual) + return address; + + interop::TCppScope_t klass = ((CPPClass*)Py_TYPE((PyObject*)obj))->fCppType; + interop::TCppScope_t clActual = + klass /* XXX: interop::GetActualClass(klass, address) */; + if (clActual && clActual != klass) { + intptr_t offset = interop::GetBaseOffset( + clActual, klass, address, -1 /* down-cast */, true /* report errors */); + if (offset != -1) + address = (void*)((intptr_t)address + offset); + } + + return address; +} + +#define CPYRT_ORDERED_OPERATOR_STUB(op, ometh, label) \ + if (!ometh) { \ + PyCallable* pyfunc = \ + Utility::FindBinaryOperator((PyObject*)self, other, #op); \ + if (pyfunc) \ + ometh = (PyObject*)CPPOverload_New(#label, pyfunc); \ + } \ + meth = ometh; + +static PyObject* op_richcompare(CPPInstance* self, PyObject* other, int op) { + // Rich set of comparison objects; currently supported: + // == : Py_EQ + // != : Py_NE + // + // < : Py_LT + // <= : Py_LE + // > : Py_GT + // >= : Py_GE + // + + // associative comparison operators + if (op == Py_EQ || op == Py_NE) { + // special case for None to compare True to a null-pointer + if ((PyObject*)other == Py_None && !self->fObject) { + const char* msg = + "\nComparison of C++ nullptr objects with `None` is no longer " + "supported." + "\n\nPreviously, `None` was treated as equivalent to a null C++ " + "pointer, " + "but this led to confusing behavior where `x == None` could be True " + "even though `x is None` was False." + "\n\nTo test whether a C++ object is null or not, check its truth " + "value instead:" + "\n if not x: ..." + "\nor use `x is None` to explicitly check for Python None." + "\n"; + + PyErr_SetString(PyExc_TypeError, msg); + return NULL; // stop execution, raise TypeError + } + + // use C++-side operators if available + PyObject* result = + eqneq_binop((CPPClass*)Py_TYPE(self), (PyObject*)self, other, op); + if (!result && CPPInstance_Check(other)) + result = + eqneq_binop((CPPClass*)Py_TYPE(other), other, (PyObject*)self, op); + if (result) + return result; + + // default behavior: type + held pointer value defines identity; if both are + // CPPInstance objects, perform an additional autocast if need be + bool bIsEq = false; + + if ((Py_TYPE(self) == Py_TYPE(other) && + self->GetObject() == ((CPPInstance*)other)->GetObject())) { + // direct match + bIsEq = true; + } else if (CPPInstance_Check(other)) { + // try auto-cast match + void* addr1 = cast_actual(self); + void* addr2 = cast_actual(other); + bIsEq = addr1 && addr2 && (addr1 == addr2); + } + + if ((op == Py_EQ && bIsEq) || (op == Py_NE && !bIsEq)) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; + } + + // ordered comparison operators + else if (op == Py_LT || op == Py_LE || op == Py_GT || op == Py_GE) { + CPPClass* klass = (CPPClass*)Py_TYPE(self); + if (!klass->fOperators) + klass->fOperators = new Utility::PyOperators{}; + PyObject* meth = nullptr; + + switch (op) { + case Py_LT: + CPYRT_ORDERED_OPERATOR_STUB(<, klass->fOperators->fLt, __lt__) + break; + case Py_LE: + CPYRT_ORDERED_OPERATOR_STUB(<=, klass->fOperators->fLe, __le__) + break; + case Py_GT: + CPYRT_ORDERED_OPERATOR_STUB(>, klass->fOperators->fGt, __gt__) + break; + case Py_GE: + CPYRT_ORDERED_OPERATOR_STUB(>=, klass->fOperators->fGe, __ge__) + break; + } + + if (!meth) { + PyErr_SetString(PyExc_NotImplementedError, ""); + return nullptr; + } + + return PyObject_CallFunctionObjArgs(meth, (PyObject*)self, other, nullptr); + } + + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +//---------------------------------------------------------------------------- +static PyObject* op_repr(CPPInstance* self) { + // Build a representation string of the object proxy that shows the address + // of the C++ object that is held, as well as its type. + PyObject* pyclass = (PyObject*)Py_TYPE(self); + if (CPPScope_Check(pyclass) && + (((CPPScope*)pyclass)->fFlags & CPPScope::kIsPython)) + return PyBaseObject_Type.tp_repr((PyObject*)self); + PyObject* modname = PyObject_GetAttr(pyclass, PyStrings::gModule); + + interop::TCppScope_t klass = self->ObjectIsA(); + std::string clName = klass ? interop::GetFinalName(klass) : ""; + if (self->fFlags & CPPInstance::kIsPtrPtr) + clName.append("**"); + else if (self->fFlags & CPPInstance::kIsReference) + clName.append("*"); + + PyObject* repr = nullptr; + if (self->IsSmart()) { + std::string smartPtrName = interop::GetScopedFinalName(SMART_TYPE(self)); + repr = cpyrt_PyText_FromFormat( + const_cast("<%s.%s object at %p held by %s at %p>"), + cpyrt_PyText_AsString(modname), clName.c_str(), self->GetObject(), + smartPtrName.c_str(), self->GetObjectRaw()); + } else { + repr = cpyrt_PyText_FromFormat(const_cast("<%s.%s object at %p>"), + cpyrt_PyText_AsString(modname), + clName.c_str(), self->GetObject()); + } + + Py_DECREF(modname); + return repr; +} + +//---------------------------------------------------------------------------- +static inline Py_hash_t cpyrt_PyLong_AsHash_t(PyObject* obj) { + // Cannot use PyLong_AsSize_t here, as it cuts of at PY_SSIZE_T_MAX, which is + // only half of the max of std::size_t returned by the hash. + if (sizeof(unsigned long) >= sizeof(size_t)) + return (Py_hash_t)PyLong_AsUnsignedLong(obj); + return (Py_hash_t)PyLong_AsUnsignedLongLong(obj); +} + +static Py_hash_t op_hash(CPPInstance* self) { + // Try to locate an std::hash for this type and use that if it exists + CPPClass* klass = (CPPClass*)Py_TYPE(self); + if (klass->fOperators && klass->fOperators->fHash) { + Py_hash_t h = 0; + PyObject* hashval = PyObject_CallFunctionObjArgs(klass->fOperators->fHash, + (PyObject*)self, nullptr); + if (hashval) { + h = cpyrt_PyLong_AsHash_t(hashval); + Py_DECREF(hashval); + } + return h; + } + + interop::TCppScope_t stdhash = interop::GetFullScope( + "std::hash<" + interop::GetScopedFinalName(self->ObjectIsA()) + ">"); + if (stdhash) { + PyObject* hashcls = CreateScopeProxy(stdhash); + PyObject* dct = PyObject_GetAttr(hashcls, PyStrings::gDict); + bool isValid = PyMapping_HasKeyString(dct, (char*)"__call__"); + Py_DECREF(dct); + if (isValid) { + PyObject* hashobj = PyObject_CallObject(hashcls, nullptr); + if (!klass->fOperators) + klass->fOperators = new Utility::PyOperators{}; + klass->fOperators->fHash = hashobj; + Py_DECREF(hashcls); + + Py_hash_t h = 0; + PyObject* hashval = + PyObject_CallFunctionObjArgs(hashobj, (PyObject*)self, nullptr); + if (hashval) { + h = cpyrt_PyLong_AsHash_t(hashval); + Py_DECREF(hashval); + } + return h; + } + Py_DECREF(hashcls); + } + + // if not valid, simply reset the hash function so as to not kill performance + ((PyTypeObject*)Py_TYPE(self))->tp_hash = PyBaseObject_Type.tp_hash; + return PyBaseObject_Type.tp_hash((PyObject*)self); +} + +//---------------------------------------------------------------------------- +static PyObject* op_str_internal(PyObject* pyobj, PyObject* lshift, + bool isBound) { + static interop::TCppScope_t sOStringStreamID = + interop::GetFullScope("std::ostringstream"); + std::ostringstream s; + PyObject* pys = BindCppObjectNoCast(&s, sOStringStreamID); + Py_INCREF(pys); + // for py3 and later, a ref-count of 2 is okay to consider the object + // temporary, but in this case, we can't lose our existing ostrinstring + // (otherwise, we'd have to peel it out of the return value, if moves are used + Py_INCREF(pys); + + PyObject* res; + if (isBound) + res = PyObject_CallFunctionObjArgs(lshift, pys, NULL); + else + res = PyObject_CallFunctionObjArgs(lshift, pys, pyobj, NULL); + + Py_DECREF(pys); + Py_DECREF(pys); + + if (res) { + Py_DECREF(res); + return cpyrt_PyText_FromString(s.str().c_str()); + } + + return nullptr; +} + +static inline bool ScopeFlagCheck(CPPInstance* self, CPPScope::EFlags flag) { + return ((CPPScope*)Py_TYPE((PyObject*)self))->fFlags & flag; +} + +static inline void ScopeFlagSet(CPPInstance* self, CPPScope::EFlags flag) { + ((CPPScope*)Py_TYPE((PyObject*)self))->fFlags |= flag; +} + +static PyObject* op_str(CPPInstance* self) { + // There are three possible options here: + // 1. Available operator<< to convert through an ostringstream + // 2. Cling's pretty printing + // 3. Generic printing as done in op_repr + // + // Additionally, there may be a mapped __str__ from the C++ type defining + // `operator char*` or `operator const char*`. Results are memoized for + // performance reasons. + + // 0. Protect against trying to print a typed nullptr object through an + // insertion operator + if (!self->GetObject()) + return op_repr(self); + + // 1. Available operator<< to convert through an ostringstream + if (!ScopeFlagCheck(self, CPPScope::kNoOSInsertion)) { + for (PyObject* pyname : {PyStrings::gLShift, PyStrings::gLShiftC, + (PyObject*)0x01, (PyObject*)0x02}) { + if (pyname == PyStrings::gLShift && + ScopeFlagCheck(self, CPPScope::kGblOSInsertion)) + continue; + + else if (pyname == (PyObject*)0x01) { + // normal lookup failed; attempt lazy install of global + // operator<<(ostream&, type&) + std::string rcname = Utility::ClassName((PyObject*)self); + interop::TCppScope_t rnsID = + interop::GetScope(TypeManip::extract_namespace(rcname)); + PyCallable* pyfunc = + Utility::FindBinaryOperator("std::ostream&", rcname, "<<", rnsID); + if (!pyfunc) + continue; + + Utility::AddToClass((PyObject*)Py_TYPE((PyObject*)self), "__lshiftc__", + pyfunc); + + pyname = PyStrings::gLShiftC; + ScopeFlagSet(self, CPPScope::kGblOSInsertion); + + } else if (pyname == (PyObject*)0x02) { + // TODO: the only reason this still exists, is b/c friend functions are + // otherwise not found + // TODO: ToString() still leaks ... + const std::string& pretty = + interop::ToString(self->ObjectIsA(), self->GetObject()); + if (!pretty.empty()) + return cpyrt_PyText_FromString(pretty.c_str()); + continue; + } + + PyObject* lshift = PyObject_GetAttr( + pyname == PyStrings::gLShift ? (PyObject*)self + : (PyObject*)Py_TYPE((PyObject*)self), + pyname); + + if (lshift) { + PyObject* result = op_str_internal((PyObject*)self, lshift, + pyname == PyStrings::gLShift); + Py_DECREF(lshift); + if (result) + return result; + } + + PyErr_Clear(); + } + + // failed ostream printing; don't try again + ScopeFlagSet(self, CPPScope::kNoOSInsertion); + } + + // 2. Cling's pretty printing (not done through backend for performance + // reasons) + if (!ScopeFlagCheck(self, CPPScope::kNoPrettyPrint)) { + static PyObject* printValue = nullptr; + if (!printValue) { + PyObject* gbl = + PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppjit.gbl"); + PyObject* cl = PyObject_GetAttrString(gbl, (char*)"cling"); + printValue = PyObject_GetAttrString(cl, (char*)"printValue"); + Py_DECREF(cl); + // gbl is borrowed + if (printValue) { + Py_DECREF(printValue); // make borrowed + if (!PyCallable_Check(printValue)) + printValue = nullptr; // unusable ... + } + if (!printValue) // unlikely + ScopeFlagSet(self, CPPScope::kNoPrettyPrint); + } + + if (printValue) { + // as printValue only works well for templates taking pointer arguments, + // we'll have to force the issue by working with a by-ptr object + interop::TCppObject_t cppobj = self->GetObjectRaw(); + PyObject* byref = (PyObject*)self; + if (!(self->fFlags & CPPInstance::kIsReference)) { + byref = BindCppObjectNoCast( + (interop::TCppObject_t)&cppobj, self->ObjectIsA(), + CPPInstance::kIsReference | CPPInstance::kNoMemReg); + } else { + Py_INCREF(byref); + } + + // explicit template lookup + PyObject* clName = + cpyrt_PyText_FromString(Utility::ClassName((PyObject*)self).c_str()); + PyObject* OL = PyObject_GetItem(printValue, clName); + Py_DECREF(clName); + + PyObject* pretty = + OL ? PyObject_CallFunctionObjArgs(OL, byref, nullptr) : nullptr; + Py_XDECREF(OL); + Py_DECREF(byref); + + PyObject* result = nullptr; + if (pretty) { + const std::string& pv = + *(std::string*)((CPPInstance*)pretty)->GetObject(); + if (!pv.empty() && pv.find("@0x") == std::string::npos) + result = cpyrt_PyText_FromString(pv.c_str()); + Py_DECREF(pretty); + if (result) + return result; + } + + PyErr_Clear(); + } + + // if not available/specialized, don't try again + ScopeFlagSet(self, CPPScope::kNoPrettyPrint); + } + + // 3. Generic printing as done in op_repr + return op_repr(self); +} + +//----------------------------------------------------------------------------- +static PyObject* op_getownership(CPPInstance* pyobj, void*) { + return PyBool_FromLong((long)(pyobj->fFlags & CPPInstance::kIsOwner)); +} + +//----------------------------------------------------------------------------- +static int op_setownership(CPPInstance* pyobj, PyObject* value, void*) { + // Set the ownership (True is python-owns) for the given object. + long shouldown = PyLong_AsLong(value); + if (shouldown == -1 && PyErr_Occurred()) { + PyErr_SetString(PyExc_ValueError, + "__python_owns__ should be either True or False"); + return -1; + } + + (bool)shouldown ? pyobj->PythonOwns() : pyobj->CppOwns(); + + return 0; +} + +//----------------------------------------------------------------------------- +static PyGetSetDef op_getset[] = { + {(char*)"__python_owns__", (getter)op_getownership, (setter)op_setownership, + (char*)"If true, python manages the life time of this object", nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +//= cpyrt type number stubs to allow dynamic overrides ===================== +#define CPYRT_STUB_BODY(name, op) \ + bool previously_resolved_overload = (bool)meth; \ + if (!meth) { \ + PyErr_Clear(); \ + PyCallable* pyfunc = Utility::FindBinaryOperator(left, right, #op); \ + if (pyfunc) \ + meth = (PyObject*)CPPOverload_New(#name, pyfunc); \ + else { \ + PyErr_SetString(PyExc_NotImplementedError, ""); \ + return nullptr; \ + } \ + } \ + PyObject* res = PyObject_CallFunctionObjArgs(meth, cppobj, other, nullptr); \ + if (!res && previously_resolved_overload) { \ + /* try again, in case (left, right) are different types than before */ \ + PyErr_Clear(); \ + PyCallable* pyfunc = Utility::FindBinaryOperator(left, right, #op); \ + if (pyfunc) \ + ((CPPOverload*&)meth)->AdoptMethod(pyfunc); \ + else { \ + PyErr_SetString(PyExc_NotImplementedError, ""); \ + return nullptr; \ + } \ + /* use same overload with newly added function */ \ + res = PyObject_CallFunctionObjArgs(meth, cppobj, other, nullptr); \ + } \ + return res; + +#define CPYRT_OPERATOR_STUB(name, op, ometh) \ + static PyObject* op_##name##_stub(PyObject* left, PyObject* right) { \ + /* placeholder to lazily install and forward to 'ometh' if available */ \ + CPPClass* klass = (CPPClass*)Py_TYPE(left); \ + if (!klass->fOperators) \ + klass->fOperators = new Utility::PyOperators{}; \ + PyObject*& meth = ometh; \ + PyObject *cppobj = left, *other = right; \ + CPYRT_STUB_BODY(name, op) \ + } + +#define CPYRT_ASSOCIATIVE_OPERATOR_STUB(name, op, lmeth, rmeth) \ + static PyObject* op_##name##_stub(PyObject* left, PyObject* right) { \ + /* placeholder to lazily install and forward do '(l/r)meth' if available \ + */ \ + CPPClass* klass; \ + PyObject** pmeth; \ + PyObject *cppobj, *other; \ + if (CPPInstance_Check(left)) { \ + klass = (CPPClass*)Py_TYPE(left); \ + if (!klass->fOperators) \ + klass->fOperators = new Utility::PyOperators{}; \ + pmeth = &lmeth; \ + cppobj = left; \ + other = right; \ + } else if (CPPInstance_Check(right)) { \ + klass = (CPPClass*)Py_TYPE(right); \ + if (!klass->fOperators) \ + klass->fOperators = new Utility::PyOperators{}; \ + pmeth = &rmeth; \ + cppobj = right; \ + other = left; \ + } else { \ + PyErr_SetString(PyExc_NotImplementedError, ""); \ + return nullptr; \ + } \ + PyObject*& meth = *pmeth; \ + CPYRT_STUB_BODY(name, op) \ + } + +#define CPYRT_UNARY_OPERATOR(name, op, label) \ + static PyObject* op_##name##_stub(PyObject* pyobj) { \ + /* placeholder to lazily install unary operators */ \ + PyCallable* pyfunc = \ + Utility::FindUnaryOperator((PyObject*)Py_TYPE(pyobj), #op); \ + if (pyfunc && \ + Utility::AddToClass((PyObject*)Py_TYPE(pyobj), #label, pyfunc)) \ + return PyObject_CallMethod(pyobj, (char*)#label, nullptr); \ + PyErr_SetString(PyExc_NotImplementedError, ""); \ + return nullptr; \ + } + +// clang-format off +CPYRT_ASSOCIATIVE_OPERATOR_STUB(add, +, klass->fOperators->fLAdd, klass->fOperators->fRAdd) +CPYRT_OPERATOR_STUB( sub, -, klass->fOperators->fSub) +CPYRT_ASSOCIATIVE_OPERATOR_STUB(mul, *, klass->fOperators->fLMul, klass->fOperators->fRMul) +CPYRT_OPERATOR_STUB( div, /, klass->fOperators->fDiv) +CPYRT_UNARY_OPERATOR(neg, -, __neg__) +CPYRT_UNARY_OPERATOR(pos, +, __pos__) +CPYRT_UNARY_OPERATOR(invert, ~, __invert__) +// clang-format on + +//----------------------------------------------------------------------------- +static PyNumberMethods op_as_number = { + (binaryfunc)op_add_stub, // nb_add + (binaryfunc)op_sub_stub, // nb_subtract + (binaryfunc)op_mul_stub, // nb_multiply + 0, // nb_remainder + 0, // nb_divmod + 0, // nb_power + (unaryfunc)op_neg_stub, // nb_negative + (unaryfunc)op_pos_stub, // nb_positive + 0, // nb_absolute + (inquiry)op_nonzero, // nb_bool (nb_nonzero in p2) + (unaryfunc)op_invert_stub, // nb_invert + 0, // nb_lshift + 0, // nb_rshift + 0, // nb_and + 0, // nb_xor + 0, // nb_or + 0, // nb_int + 0, // nb_long (nb_reserved in p3) + 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 + (binaryfunc)op_div_stub, // 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 +}; + +//= cpyrt object proxy type =============================================== +PyTypeObject CPPInstance_Type = { + PyVarObject_HEAD_INIT(&CPPScope_Type, + 0)(char*) "cppjit.CPPInstance", // tp_name + sizeof(CPPInstance), // tp_basicsize + 0, // tp_itemsize + (destructor)op_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + (reprfunc)op_repr, // tp_repr + &op_as_number, // tp_as_number + &op_as_sequence, // tp_as_sequence + 0, // tp_as_mapping + (hashfunc)op_hash, // tp_hash + 0, // tp_call + (reprfunc)op_str, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_HAVE_GC, // tp_flags + (char*)"cppjit object proxy (internal)", // tp_doc + (traverseproc)op_traverse, // tp_traverse + (inquiry)op_clear, // tp_clear + (richcmpfunc)op_richcompare, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + op_methods, // tp_methods + 0, // tp_members + op_getset, // 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 + (newfunc)op_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 + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/src/cpyrt/CPPInstance.h b/src/cpyrt/CPPInstance.h new file mode 100644 index 0000000..ea3831a --- /dev/null +++ b/src/cpyrt/CPPInstance.h @@ -0,0 +1,149 @@ +#ifndef CPYRT_CPPINSTANCE_H +#define CPYRT_CPPINSTANCE_H + +////////////////////////////////////////////////////////////////////////////// +// // +// Cpycppjit::interop::CPPInstance // +// // +// Python-side proxy, encapsulaties a C++ object. // +// // +////////////////////////////////////////////////////////////////////////////// + +// Bindings +#ifndef Py_LIMITED_API +#include "CPPScope.h" +#endif +#include "CallContext.h" // for Parameter +#include "cppjit_interop.h" + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +typedef std::vector> CI_DatamemberCache_t; + +class CPPInstance { +public: + enum EFlags { + kDefault = 0x0000, + kNoWrapConv = 0x0001, // use type as-is (eg. no smart ptr wrap) + kIsOwner = 0x0002, // Python instance owns C++ object/memory + kIsExtended = 0x0004, // has extended data + kIsValue = 0x0008, // was created from a by-value return + kIsReference = 0x0010, // represents one indirection + kIsArray = 0x0020, // represents an array of objects + kIsSmartPtr = 0x0040, // is or embeds a smart pointer + kIsPtrPtr = 0x0080, // represents two indirections + kIsRValue = 0x0100, // can be used as an r-value + kIsLValue = 0x0200, // can be used as an l-value + kNoMemReg = 0x0400, // do not register with memory regulator + kIsRegulated = 0x0800, // is registered with memory regulator + kIsActual = 0x1000, // has been downcasted to actual type + kHasLifeLine = 0x2000, // has a life line set + }; + +public: // public, as the python C-API works with C structs + PyObject_HEAD void* fObject; + uint32_t fFlags; + +public: + // construction (never done directly) + CPPInstance() = delete; + + void Set(void* address, EFlags flags = kDefault); + CPPInstance* Copy(void* cppinst, PyTypeObject* target = nullptr); + + // state checking + bool IsExtended() const { return fFlags & kIsExtended; } + bool IsSmart() const { return fFlags & kIsSmartPtr; } + + // access to C++ pointer and type + void* GetObject(); + void*& GetObjectRaw() { return IsExtended() ? *(void**)fObject : fObject; } + cppjit::interop::TCppScope_t ObjectIsA(bool check_smart = true) const; + + // memory management: ownership of the underlying C++ object + void PythonOwns(); + void CppOwns(); + + // data member cache + CI_DatamemberCache_t& GetDatamemberCache(); + + // smart pointer management + void SetSmart(PyObject* smart_type); + void* GetSmartObject() { return GetObjectRaw(); } + cppjit::interop::TCppScope_t GetSmartIsA() const; + cppjit::interop::TCppScope_t GetSmartUnderlyingType() const; + + // cross-inheritance dispatch + void SetDispatchPtr(void*); + + // redefine pointer to object as fixed-size array + void CastToArray(Py_ssize_t sz); + Py_ssize_t ArrayLength(); + + // implementation of the __reduce__ method: doesn't wrap any function by + // default but can be re-assigned by libraries that add C++ object + // serialization support, like ROOT + static PyCFunction& ReduceMethod(); + +private: + void CreateExtension(); + void* GetExtendedObject(); +}; + +//- public methods ----------------------------------------------------------- +inline void CPPInstance::Set(void* address, EFlags flags) { + // Initialize the proxy with the pointer value 'address.' + if (flags != kDefault) + fFlags = flags; + GetObjectRaw() = address; +} + +//---------------------------------------------------------------------------- +inline void* CPPInstance::GetObject() { + // Retrieve a pointer to the held C++ object. + if (!IsExtended()) { + if (fObject && (fFlags & kIsReference)) + return *(reinterpret_cast(fObject)); + else + return fObject; // may be null + } else + return GetExtendedObject(); +} + +#ifndef Py_LIMITED_API +//---------------------------------------------------------------------------- +inline cppjit::interop::TCppScope_t +CPPInstance::ObjectIsA(bool check_smart) const { + // Retrieve the C++ type identifier (or raw type if smart). + if (check_smart || !IsSmart()) + return ((CPPClass*)Py_TYPE(this))->fCppType; + return GetSmartIsA(); +} +#endif + +//- object proxy type and type verification ---------------------------------- +CPYRT_IMPORT PyTypeObject CPPInstance_Type; + +#ifndef Py_LIMITED_API +template inline bool CPPInstance_Check(T* object) { + // Short-circuit the type check by checking tp_new which all generated + // subclasses of CPPInstance inherit. + return object && (Py_TYPE(object)->tp_new == CPPInstance_Type.tp_new || + PyObject_TypeCheck(object, &CPPInstance_Type)); +} +#endif + +template inline bool CPPInstance_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPInstance_Type; +} + +//- helper for memory regulation (no PyTypeObject equiv. member in p2.2) ----- +void op_dealloc_nofree(CPPInstance*); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPINSTANCE_H diff --git a/src/cpyrt/CPPMethod.cxx b/src/cpyrt/CPPMethod.cxx new file mode 100644 index 0000000..761d0ed --- /dev/null +++ b/src/cpyrt/CPPMethod.cxx @@ -0,0 +1,1170 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPExcInstance.h" +#include "CPPInstance.h" +#include "CPPMethod.h" +#include "Converters.h" +#include "Executors.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "SignalTryCatch.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +#include "cpyrt/PyException.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//- data and local helpers --------------------------------------------------- +namespace cppjit::cpyrt { +extern PyObject* gThisModule; +extern PyObject* gBusException; +extern PyObject* gSegvException; +extern PyObject* gIllException; +extern PyObject* gAbrtException; +} // namespace cppjit::cpyrt + +//- public helper ------------------------------------------------------------ +cpyrt::PyCallArgs::~PyCallArgs() { + if (fFlags & kSelfSwap) // if self swap, fArgs has been offset by -1 + std::swap((PyObject*&)fSelf, ((PyObject**)fArgs)[0]); + + if (fFlags & kIsOffset) + fArgs -= 1; + + if (fFlags & kDoItemDecref) { + for (Py_ssize_t iarg = 0; iarg < cpyrt_PyArgs_GET_SIZE(fArgs, fNArgsf); + ++iarg) + Py_DECREF(fArgs[iarg]); + } + + if (fFlags & kDoFree) + PyMem_Free((void*)fArgs); + else if (fFlags & kArgsSwap) { + // if self swap, fArgs has been offset by -1 + int offset = (fFlags & kSelfSwap) ? 1 : 0; + std::swap(((PyObject**)fArgs + offset)[0], ((PyObject**)fArgs + offset)[1]); + } +} + +//- private helpers ---------------------------------------------------------- +inline bool cpyrt::CPPMethod::VerifyArgCount_(Py_ssize_t actual) { + // actual number of arguments must be between required and max args + Py_ssize_t maxargs = (Py_ssize_t)fConverters.size(); + + if (maxargs != actual) { + if (actual < (Py_ssize_t)fArgsRequired) { + SetPyError_(cpyrt_PyText_FromFormat( + "takes at least %d arguments (%zd given)", fArgsRequired, actual)); + return false; + } else if (maxargs < actual) { + SetPyError_(cpyrt_PyText_FromFormat( + "takes at most %zd arguments (%zd given)", maxargs, actual)); + return false; + } + } + return true; +} + +//---------------------------------------------------------------------------- +inline void cpyrt::CPPMethod::Copy_(const CPPMethod& /* other */) { + // fScope and fMethod handled separately + + // do not copy caches + fExecutor = nullptr; + fArgIndices = nullptr; + fArgsRequired = -1; +} + +//---------------------------------------------------------------------------- +inline void cpyrt::CPPMethod::Destroy_() { + // destroy executor and argument converters + if (fExecutor && fExecutor->HasState()) + delete fExecutor; + fExecutor = nullptr; + + for (auto p : fConverters) { + if (p && p->HasState()) + delete p; + } + fConverters.clear(); + + delete fArgIndices; + fArgIndices = nullptr; + fArgsRequired = -1; +} + +//---------------------------------------------------------------------------- +inline PyObject* cpyrt::CPPMethod::ExecuteFast(void* self, ptrdiff_t offset, + CallContext* ctxt) { + // call into C++ through fExecutor; abstracted out from Execute() to prevent + // some code duplication with ProtectedCall() + PyObject* result = nullptr; + + try { // C++ try block + result = fExecutor->Execute( + fMethod, interop::TCppObject_t((void*)((intptr_t)self + offset)), ctxt); + } catch (PyException&) { + ctxt->fFlags |= CallContext::kPyException; + result = nullptr; // error already set + } catch (std::exception& e) { + // attempt to set the exception to the actual type, to allow catching with + // the Python C++ type + static interop::TCppScope_t exc_type = + interop::GetFullScope("std::exception"); + + ctxt->fFlags |= CallContext::kCppException; + + PyObject* pyexc_type = nullptr; + PyObject* pyexc_obj = nullptr; + + // TODO: factor this code with the same in ProxyWrappers (and cache it there + // to be able to look up based on TCppType_t): + interop::TCppScope_t actual = interop::GetActualClass(exc_type, &e); + const std::string& finalname = interop::GetScopedFinalName(actual); + const std::string& parentname = TypeManip::extract_namespace(finalname); + PyObject* parent = CreateScopeProxy(parentname); + if (parent) { + pyexc_type = PyObject_GetAttrString( + parent, + parentname.empty() + ? finalname.c_str() + : finalname.substr(parentname.size() + 2, std::string::npos) + .c_str()); + Py_DECREF(parent); + } + + if (pyexc_type) { + // create a copy of the exception (TODO: factor this code with the same in + // ProxyWrappers) + PyObject* pyclass = cpyrt::GetScopeProxy(actual); + PyObject* source = BindCppObjectNoCast(&e, actual); + PyObject* pyexc_copy = + PyObject_CallFunctionObjArgs(pyclass, source, nullptr); + Py_DECREF(source); + Py_DECREF(pyclass); + if (pyexc_copy) { + pyexc_obj = CPPExcInstance_Type.tp_new((PyTypeObject*)pyexc_type, + nullptr, nullptr); + ((CPPExcInstance*)pyexc_obj)->fCppInstance = (PyObject*)pyexc_copy; + } else + PyErr_Clear(); + } else + PyErr_Clear(); + + if (pyexc_type && pyexc_obj) { + PyErr_SetObject(pyexc_type, pyexc_obj); + Py_DECREF(pyexc_obj); + Py_DECREF(pyexc_type); + } else { + PyErr_Format(PyExc_Exception, "%s (C++ exception)", e.what()); + Py_XDECREF(pyexc_obj); + Py_XDECREF(pyexc_type); + } + + result = nullptr; + } catch (...) { + // don't set the kCppException flag here, as there is basically no useful + // extra information to be had and caller has to catch Exception either way + PyErr_SetString(PyExc_Exception, "unhandled, unknown C++ exception"); + result = nullptr; + } + +// TODO: covers the PyException throw case, which does not seem to work on +// Windows, so instead leaves the error be +#ifdef _WIN32 + if (PyErr_Occurred()) { + Py_XDECREF(result); + result = nullptr; + } +#endif + + return result; +} + +//---------------------------------------------------------------------------- +inline PyObject* cpyrt::CPPMethod::ExecuteProtected(void* self, + ptrdiff_t offset, + CallContext* ctxt) { + // helper code to prevent some code duplication; this code embeds a + // "try/catch" block that saves the call environment for restoration in case + // of an otherwise fatal signal + PyObject* result = 0; + + CLING_EXCEPTION_TRY { // copy call environment to be able to jump back on + // signal + result = ExecuteFast(self, offset, ctxt); + } + CLING_EXCEPTION_CATCH(excode) { + // report any outstanding Python exceptions first + if (PyErr_Occurred()) { + std::cerr << "Python exception outstanding during C++ longjmp:" + << std::endl; + PyErr_Print(); + std::cerr << std::endl; + } + + // unfortunately, the excodes are not the ones from signal.h, but enums from + // TSysEvtHandler.h + if (excode == 0) + PyErr_SetString(gBusException, + "bus error in C++; program state was reset"); + else if (excode == 1) + PyErr_SetString(gSegvException, + "segfault in C++; program state was reset"); + else if (excode == 4) + PyErr_SetString(gIllException, + "illegal instruction in C++; program state was reset"); + else if (excode == 5) + PyErr_SetString(gAbrtException, + "abort from C++; program state was reset"); + else if (excode == 12) + PyErr_SetString( + PyExc_FloatingPointError, + "floating point exception in C++; program state was reset"); + else + PyErr_SetString(PyExc_SystemError, + "problem in C++; program state was reset"); + result = 0; + } + CLING_EXCEPTION_ENDTRY; + + return result; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::InitConverters_() { + // build buffers for argument dispatching + const size_t nArgs = interop::GetMethodNumArgs(fMethod); + fConverters.resize(nArgs); + + // setup the dispatch cache + for (int iarg = 0; iarg < (int)nArgs; ++iarg) { + interop::TCppType_t fullType = interop::GetMethodArgType(fMethod, iarg); + Converter* conv = CreateConverter(fullType); + if (!conv) { + PyErr_Format(PyExc_TypeError, "argument type %s not handled", + interop::GetTypeAsString(fullType).c_str()); + return false; + } + + fConverters[iarg] = conv; + } + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::InitExecutor_(Executor*& executor, + CallContext* /* ctxt */) { + // install executor conform to the return type + executor = (bool)fMethod == true + ? CreateExecutor(interop::GetMethodReturnType(fMethod)) + : CreateExecutor(interop::GetScopedFinalName(fScope)); + + if (!executor) + return false; + + return true; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::CPPMethod::GetSignatureString(bool fa) { + // built a signature representation (used for doc strings) + return interop::GetMethodSignature(fMethod, fa); +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPMethod::SetPyError_(PyObject* msg) { + // Helper to report errors in a consistent format (derefs msg). + // + // Handles three cases: + // 1. No Python error occured yet: + // Set a new TypeError with the message "msg" and the docstring of this + // C++ method to give some context. + // 2. A C++ exception has occured: + // Augment the exception message with the docstring of this method + // 3. A Python exception has occured with a traceback: + // Do nothing, Python exceptions are already informative enough + // 4. If the Python exception has no traceback hinting to an internally set + // error stack, + // extract its message and wrap it with C++ method docstring context. + +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* evalue = PyErr_Occurred() ? PyErr_GetRaisedException() : nullptr; + PyObject* etype = evalue ? (PyObject*)Py_TYPE(evalue) : nullptr; +#else + PyObject* etype = nullptr; + PyObject* evalue = nullptr; + PyObject* etrace = nullptr; + + if (PyErr_Occurred()) { + PyErr_Fetch(&etype, &evalue, &etrace); + } +#endif + + const bool isCppExc = + evalue && PyType_IsSubtype((PyTypeObject*)etype, &CPPExcInstance_Type); + std::string details; + + // If the error is not a CPPExcInstance and has a traceback, the error from + // Python itself is already complete and messing with it would only make it + // less informative. + // Just restore and return. + if (evalue && !isCppExc) { +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* tb = PyException_GetTraceback(evalue); + if (tb) { + Py_DECREF(tb); + PyErr_SetRaisedException(evalue); + return; + } +#else + if (etrace) { + PyErr_Restore(etype, evalue, etrace); + return; + } +#endif + // no traceback, extract its message and fall through + PyObject* descr = PyObject_Str(evalue); + if (descr) { + details = cpyrt_PyText_AsString(descr); + Py_DECREF(descr); + } + } + + PyObject* doc = GetDocString(); + const char* cdoc = cpyrt_PyText_AsString(doc); + const char* cmsg = msg ? cpyrt_PyText_AsString(msg) : nullptr; + PyObject* errtype = etype ? etype : PyExc_TypeError; + PyObject* pyname = PyObject_GetAttr(errtype, PyStrings::gName); + const char* cname = pyname ? cpyrt_PyText_AsString(pyname) : "Exception"; + + if (!isCppExc) { + // this is the case where no Python error has occured yet, or an internal + // one without traceback set a new error with context + if (details.empty()) { + PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, cmsg ? cmsg : ""); + } else if (cmsg) { + PyErr_Format(errtype, "%s =>\n %s: %s (%s)", cdoc, cname, cmsg, + details.c_str()); + } else { + PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, details.c_str()); + } + } else { + // augment the top message with context information + PyObject*& topMessage = ((CPPExcInstance*)evalue)->fTopMessage; + Py_XDECREF(topMessage); + if (msg) { + topMessage = + cpyrt_PyText_FromFormat("%s =>\n %s: %s | ", cdoc, cname, cmsg); + } else { + topMessage = cpyrt_PyText_FromFormat("%s =>\n %s: ", cdoc, cname); + } + // restore the updated error +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(evalue); +#else + PyErr_Restore(etype, evalue, etrace); +#endif + } + + Py_XDECREF(pyname); + Py_DECREF(doc); + Py_XDECREF(msg); +} + +extern std::unordered_map + TypeReductionMap; + +//- constructors and destructor ---------------------------------------------- +cpyrt::CPPMethod::CPPMethod(interop::TCppScope_t scope, + interop::TCppMethod_t method) + : fMethod(method), fScope(scope), fExecutor(nullptr), fArgIndices(nullptr), + fArgsRequired(-1) { + interop::TCppType_t result = + interop::ResolveType(interop::GetMethodReturnType(fMethod)); + if (TypeReductionMap.find(result) != TypeReductionMap.end()) + fMethod = interop::ReduceReturnType(fMethod, TypeReductionMap[result]); + if (result && interop::IsLambdaClass(result)) + fMethod = interop::AdaptFunctionForLambdaReturn(fMethod); +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMethod::CPPMethod(const CPPMethod& other) + : PyCallable(other), fMethod(other.fMethod), fScope(other.fScope) { + Copy_(other); +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMethod& cpyrt::CPPMethod::operator=(const CPPMethod& other) { + if (this != &other) { + Destroy_(); + Copy_(other); + fScope = other.fScope; + fMethod = other.fMethod; + } + + return *this; +} + +//---------------------------------------------------------------------------- +cpyrt::CPPMethod::~CPPMethod() { Destroy_(); } + +//- public members ----------------------------------------------------------- +/** + * @brief Construct a Python string from the method's prototype + * + * @param fa Show formal arguments of the method + * @return PyObject* A Python string with the full method prototype, namespaces + * included. + * + * For example, given: + * + * int foo(int x); + * + * namespace a { + * namespace b { + * namespace c { + * int foo(int x); + * }}} + * + * This function returns: + * + * 'int foo(int x)' + * 'int a::b::c::foo(int x)' + */ +PyObject* cpyrt::CPPMethod::GetPrototype(bool fa) { + // Gather the fully qualified final scope of the method. This includes + // all namespaces up to the one where the method is declared, for example: + // namespace a { namespace b { void foo(); }} + // gives + // a::b + std::string finalscope = interop::GetScopedFinalName(fScope); + return cpyrt_PyText_FromFormat( + "%s%s %s%s", (interop::IsStaticMethod(fMethod) ? "static " : ""), + interop::GetMethodReturnTypeAsString(fMethod).c_str(), + interop::GetScopedFinalName(interop::TCppScope_t(fMethod.data)).c_str(), + GetSignatureString(fa).c_str()); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::GetTypeName() { + PyObject* cppname = cpyrt_PyText_FromString( + (GetReturnTypeName() + " (" + + (fScope ? interop::GetScopedFinalName(fScope) + "::*)" : "*)")) + .c_str()); + cpyrt_PyText_AppendAndDel(&cppname, + GetSignature(false /* show_formalargs */)); + return cppname; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::Reflex(interop::Reflex::RequestId_t request, + interop::Reflex::FormatId_t format) { + // C++ reflection tooling for methods. + + if (request == interop::Reflex::RETURN_TYPE) { + std::string rtn = GetReturnTypeName(); + interop::TCppScope_t scope = nullptr; + if (format == interop::Reflex::OPTIMAL || + format == interop::Reflex::AS_TYPE) + scope = interop::GetScope(rtn); + + if (format == interop::Reflex::AS_STRING || + (format == interop::Reflex::OPTIMAL && !scope)) + return cpyrt_PyText_FromString(rtn.c_str()); + else if (format == interop::Reflex::AS_TYPE || + format == interop::Reflex::OPTIMAL) { + if (scope) + return CreateScopeProxy(scope); + /* TODO: builtins as type */ + } + } + + return PyCallable::Reflex(request, format); +} + +//---------------------------------------------------------------------------- +int cpyrt::CPPMethod::GetPriority() { + // To help with overload selection, methods are given a priority based on the + // affinity of Python and C++ types. Priority only matters for methods that + // have an equal number of arguments and types that are possible substitutes + // (the normal selection mechanisms would simply distinguish them otherwise). + + // The following types are ordered, in favor (variants implicit): + // + // bool >> long >> int >> short + // double >> long double >> float + // const char* >> char + // + // Further, all integer types are preferred over floating point b/c int to + // float is allowed implicitly, float to int is not. + // + // Special cases that are disliked include void* and unknown/incomplete types. + // Also, moves are preferred over references. std::initializer_list is not a + // nice conversion candidate either, but needs to be higher priority to mix + // well with implicit conversions. + // TODO: extend this to favour classes that are not bases. + // TODO: profile this method (it's expensive, but should be called too often) + + int priority = 0; + + const size_t nArgs = interop::GetMethodNumArgs(fMethod); + for (int iarg = 0; iarg < (int)nArgs; ++iarg) { + const std::string aname = interop::GetMethodArgTypeAsString(fMethod, iarg); + // FIXME: convert the string comparisons with comparison to the underlying + // type: + // interop::TCppType_t type = interop::GetMethodArgType(fMethod, iarg); + + if (interop::IsBuiltin(aname)) { + // complex type (note: double penalty: for complex and the template type) + if (strstr(aname.c_str(), "std::complex")) + priority -= 10; // prefer double, float, etc. over conversion + + // integer types + if (strstr(aname.c_str(), "bool")) + priority += 1; // bool over int (does accept 1 and 0) + else if (strstr(aname.c_str(), "long long")) + priority += -5; // will very likely fit + else if (strstr(aname.c_str(), "long")) + priority += -10; // most affine integer type + // no need to compare with int; leave at zero + else if (strstr(aname.c_str(), "short")) + priority += -50; // not really relevant as a type + + // floating point types (note all numbers lower than integer types) + else if (strstr(aname.c_str(), "float")) + priority += -100; // not really relevant as a type + else if (strstr(aname.c_str(), "long double")) + priority += -90; // fits double with least loss of precision + else if (strstr(aname.c_str(), "double")) + priority += -80; // most affine floating point type + + // string/char types + else if (strstr(aname.c_str(), "char") && aname[aname.size() - 1] != '*') + priority += -60; // prefer (const) char* over char + + // oddball + else if (strstr(aname.c_str(), "void*")) + priority -= 1000; // void*/void** shouldn't be too greedy + + } else { + // This is a user-defined type (class, struct, enum, etc.). + + // There's a bit of hysteresis here for templates: once GetScope() is + // called, their IsComplete() succeeds, the other way around it does not. + // Since GetPriority() is likely called several times in a sort, the + // GetScope() _must_ come first, or different GetPriority() calls may + // return different results (since the 2nd time, GetScope() will have been + // called from the first), killing the stable_sort. + + // prefer more derived classes + const std::string& clean_name = TypeManip::clean_type(aname, false); + interop::TCppScope_t scope = interop::GetScope(clean_name); + if (scope) + priority += static_cast(interop::GetNumBasesLongestBranch(scope)); + + if (interop::IsEnumScope(scope)) + priority -= 100; + + // a couple of special cases as explained above + if (aname.find("initializer_list") != std::string::npos) { + priority += 150; // needed for proper implicit conversion rules + } else if (aname.rfind("&&", aname.size() - 2) != std::string::npos) { + priority += 100; // prefer moves over other ref/ptr + } else if (scope && !interop::IsComplete(scope)) { + // class is known, but no dictionary available, 2 more cases: * and & + if (aname[aname.size() - 1] == '&') + priority += -5000; + else + priority += -2000; // prefer pointer passing over reference + } + } + } + + // prefer methods w/o optional arguments b/c ones with optional arguments are + // easier to select by providing the optional arguments explicitly + priority += ((int)interop::GetMethodReqArgs(fMethod) - (int)nArgs); + + // add a small penalty to prefer non-const methods over const ones for + // get/setitem + if (interop::IsConstMethod(fMethod) && + interop::GetName(interop::TCppScope_t(fMethod.data)) == "operator[]") + priority += -10; + + // constructors are prefered + if (interop::IsConstructor(fMethod)) + priority += 100; + + return priority; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::IsGreedy() { + // Methods will all void*-like arguments should be sorted after template + // instanstations, so that they don't greedily take over pointers to object. + // GetPriority() is too heavy-handed, as it will pull in all the argument + // types, so use this cheaper check. + const size_t nArgs = interop::GetMethodReqArgs(fMethod); + if (!nArgs) + return false; + + for (int iarg = 0; iarg < (int)nArgs; ++iarg) { + const std::string aname = interop::GetMethodArgTypeAsString(fMethod, iarg); + if (aname.find("void *") != 0) + return false; + } + return true; +} + +//---------------------------------------------------------------------------- +int cpyrt::CPPMethod::GetMaxArgs() { + return (int)interop::GetMethodNumArgs(fMethod); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::GetCoVarNames() { + // Build a tuple of the argument types/names. + int co_argcount = (int)GetMaxArgs() /* +1 for self */; + + // TODO: static methods need no 'self' (but is harmless otherwise) + + PyObject* co_varnames = PyTuple_New(co_argcount + 1 /* self */); + PyTuple_SET_ITEM(co_varnames, 0, cpyrt_PyText_FromString("self")); + for (int iarg = 0; iarg < co_argcount; ++iarg) { + std::string argrep = interop::GetMethodArgTypeAsString(fMethod, iarg); + const std::string& parname = interop::GetMethodArgName(fMethod, iarg); + if (!parname.empty()) { + argrep += " "; + argrep += parname; + } + + PyObject* pyspec = cpyrt_PyText_FromString(argrep.c_str()); + PyTuple_SET_ITEM(co_varnames, iarg + 1, pyspec); + } + + return co_varnames; +} + +PyObject* cpyrt::CPPMethod::GetArgDefault(int iarg, bool silent) { + // get and evaluate the default value (if any) of argument iarg of this method + if (iarg >= (int)GetMaxArgs()) + return nullptr; + + // borrowed reference to cppjit.gbl module to use its dictionary to eval in + static PyObject* gbl = + PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppjit.gbl"); + + std::string defvalue = interop::GetMethodArgDefault(fMethod, iarg); + if (!defvalue.empty()) { + PyObject** dctptr = _PyObject_GetDictPtr(gbl); + if (!(dctptr && *dctptr)) + return nullptr; + + PyObject* gdct = *dctptr; + PyObject* scope = nullptr; + + if (defvalue.rfind('(') != std::string::npos) { // constructor-style call + // try to tickle scope creation, just in case, first look in the scope + // where the function lives, then in the global scope + std::string possible_scope = defvalue.substr(0, defvalue.rfind('(')); + if (!interop::IsBuiltin(possible_scope)) { + std::string cand_scope = + interop::GetScopedFinalName(fScope) + "::" + possible_scope; + scope = CreateScopeProxy(cand_scope); + if (!scope) { + PyErr_Clear(); + // search within the global scope instead + scope = CreateScopeProxy(possible_scope); + if (!scope) + PyErr_Clear(); + } else { + // re-scope the scope; alternatively, the expression could be + // compiled in the dictionary of the function's namespace, but + // that would affect arguments passed to the constructor, too + defvalue = cand_scope + + defvalue.substr(defvalue.rfind('('), std::string::npos); + } + } + } + + // replace '::' -> '.' + TypeManip::cppscope_to_pyscope(defvalue); + + if (!scope) { + // a couple of common cases that python doesn't like (technically, 'L' is + // okay with older pythons, but C long will always fit in Python int, so + // no need to bother) + char c = defvalue.back(); + if (c == 'F' || c == 'D' || c == 'L') { + int offset = 1; + if (2 < defvalue.size() && defvalue[defvalue.size() - 2] == 'U') + offset = 2; + defvalue = defvalue.substr(0, defvalue.size() - offset); + } else if (defvalue == "true") { + defvalue = "True"; + } else if (defvalue == "false") { + defvalue = "False"; + } + } + + // attempt to evaluate the string representation (compilation is first to + // code to allow the error message to indicate where it's coming from) + PyObject* pyval = nullptr; + + PyObject* pycode = Py_CompileString( + (char*)defvalue.c_str(), "cppjit_default_compiler", Py_eval_input); + if (pycode) { + pyval = PyEval_EvalCode(pycode, gdct, gdct); + Py_DECREF(pycode); + } + + if (!pyval && PyErr_Occurred() && silent) { + PyErr_Clear(); + pyval = cpyrt_PyText_FromString( + defvalue.c_str()); // allows continuation, but is likely to fail + } + + Py_XDECREF(scope); + return pyval; // may be nullptr + } + + PyErr_Format(PyExc_TypeError, "Could not construct default value for: %s", + interop::GetMethodArgName(fMethod, iarg).c_str()); + return nullptr; +} + +bool cpyrt::CPPMethod::IsConst() { return interop::IsConstMethod(GetMethod()); } + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::GetScopeProxy() { + // Get or build the scope of this method. + return CreateScopeProxy(fScope); +} + +//---------------------------------------------------------------------------- +interop::TCppFuncAddr_t cpyrt::CPPMethod::GetFunctionAddress() { + // Return the C++ pointer of this function + return interop::GetFunctionAddress(fMethod, + false /* don't check fast path envar */); +} + +//---------------------------------------------------------------------------- +int cpyrt::CPPMethod::GetArgMatchScore(PyObject* args_tuple) { + Py_ssize_t n = PyTuple_Size(args_tuple); + + int req_args = interop::GetMethodReqArgs(fMethod); + + // Not enough arguments supplied: no match + if (req_args > n) + return INT_MAX; + + size_t score = 0; + for (int i = 0; i < n; i++) { + PyObject* pItem = PyTuple_GetItem(args_tuple, i); + if (!cpyrt_PyText_Check(pItem)) { + PyErr_SetString(PyExc_TypeError, + "argument types should be in string format"); + return INT_MAX; + } + std::string req_type(cpyrt_PyText_AsString(pItem)); + + size_t arg_score = interop::CompareMethodArgType(fMethod, i, req_type); + + // Method is not compatible if even one argument does not match + if (arg_score >= 10) { + score = INT_MAX; + break; + } + + score += arg_score; + } + + return score; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::Initialize(CallContext* ctxt) { + // done if cache is already setup + if (fArgsRequired != -1) + return true; + + if (!InitConverters_()) + return false; + + if (!InitExecutor_(fExecutor, ctxt)) + return false; + + // minimum number of arguments when calling + fArgsRequired = + (int)((bool)fMethod == true ? interop::GetMethodReqArgs(fMethod) : 0); + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::ProcessKwds(PyObject* self_in, PyCallArgs& cargs) { + if (!PyTuple_CheckExact(cargs.fKwds)) { + SetPyError_( + cpyrt_PyText_FromString("received unknown keyword names object")); + return false; + } + Py_ssize_t nKeys = PyTuple_GET_SIZE(cargs.fKwds); + + if (nKeys == 0 && !self_in) + return true; + + if (!fArgIndices) { + fArgIndices = new std::unordered_map{}; + for (int iarg = 0; iarg < (int)interop::GetMethodNumArgs(fMethod); ++iarg) + (*fArgIndices)[interop::GetMethodArgName(fMethod, iarg)] = iarg; + } + + Py_ssize_t nArgs = + cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) + (self_in ? 1 : 0); + if (!VerifyArgCount_(nArgs + nKeys)) + return false; + + std::vector vArgs{fConverters.size()}; + + // next, insert the keyword values + PyObject *key, *value; + Py_ssize_t maxpos = -1; + + Py_ssize_t npos_args = cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf); + for (Py_ssize_t ikey = 0; ikey < nKeys; ++ikey) { + key = PyTuple_GET_ITEM(cargs.fKwds, ikey); + value = cargs.fArgs[npos_args + ikey]; + const char* ckey = cpyrt_PyText_AsStringChecked(key); + if (!ckey) + return false; + + auto p = fArgIndices->find(ckey); + if (p == fArgIndices->end()) { + SetPyError_(cpyrt_PyText_FromFormat( + "%s::%s got an unexpected keyword argument \'%s\'", + interop::GetFinalName(fScope).c_str(), + interop::GetName(interop::TCppScope_t(fMethod.data)).c_str(), ckey)); + return false; + } + + maxpos = p->second > maxpos ? p->second : maxpos; + vArgs[p->second] = + value; // no INCREF yet for simple cleanup in case of error + } + + // if maxpos < nArgs, it will be detected & reported as a duplicate below + Py_ssize_t maxargs = maxpos + 1; + cpyrt_PyArgs_t newArgs = cpyrt_PyArgs_New(maxargs); + + // set all values to zero to be able to check them later (this also guarantees + // normal cleanup by the tuple deallocation) + for (Py_ssize_t i = 0; i < maxargs; ++i) + cpyrt_PyArgs_SET_ITEM(newArgs, i, nullptr); + + // fill out the positional arguments + Py_ssize_t start = 0; + if (self_in) { + Py_INCREF(self_in); + cpyrt_PyArgs_SET_ITEM(newArgs, 0, self_in); + start = 1; + } + + for (Py_ssize_t i = start; i < nArgs; ++i) { + if (vArgs[i]) { + SetPyError_(cpyrt_PyText_FromFormat( + "%s::%s got multiple values for argument %d", + interop::GetFinalName(fScope).c_str(), + interop::GetName(interop::TCppScope_t(fMethod.data)).c_str(), + (int)i + 1)); + cpyrt_PyArgs_DEL(newArgs); + return false; + } + + PyObject* item = cpyrt_PyArgs_GET_ITEM(cargs.fArgs, i); + Py_INCREF(item); + cpyrt_PyArgs_SET_ITEM(newArgs, i, item); + } + + // fill out the keyword arguments + for (Py_ssize_t i = nArgs; i < maxargs; ++i) { + PyObject* item = vArgs[i]; + if (item) { + Py_INCREF(item); + cpyrt_PyArgs_SET_ITEM(newArgs, i, item); + } else { + // try retrieving the default + item = GetArgDefault((int)i, false /* i.e. not silent */); + if (!item) { + cpyrt_PyArgs_DEL(newArgs); + return false; + } + cpyrt_PyArgs_SET_ITEM(newArgs, i, item); + } + } + + if (cargs.fFlags & PyCallArgs::kDoFree) { + if (cargs.fFlags & PyCallArgs::kIsOffset) + cargs.fArgs -= 1; + cpyrt_PyArgs_DEL(cargs.fArgs); + } + + cargs.fArgs = newArgs; + cargs.fNArgsf = maxargs; + cargs.fFlags = PyCallArgs::kDoFree | PyCallArgs::kDoItemDecref; + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::ProcessArgs(PyCallArgs& cargs) { + // verify existence of self, return if ok + if (cargs.fSelf) { + if (cargs.fKwds) { + return ProcessKwds(nullptr, cargs); + } + return true; + } + + // otherwise, check for a suitable 'self' in args and update accordingly + if (cpyrt_PyArgs_GET_SIZE(cargs.fArgs, cargs.fNArgsf) != 0) { + CPPInstance* pyobj = (CPPInstance*)cpyrt_PyArgs_GET_ITEM(cargs.fArgs, 0); + + // demand cpyrt object, and an argument that may match down the road + if (CPPInstance_Check(pyobj)) { + interop::TCppScope_t oisa = pyobj->ObjectIsA(); + if (fScope == interop::GetGlobalScope() || // free global + oisa == nullptr || // null pointer or ctor call + oisa == fScope || // matching types + interop::IsSubclass(oisa, fScope)) { // id. + + // reset self + Py_INCREF(pyobj); // corresponding Py_DECREF is in CPPOverload + cargs.fSelf = pyobj; + + // offset args by 1 + cargs.fArgs += 1; + cargs.fFlags |= PyCallArgs::kIsOffset; + cargs.fNArgsf -= 1; + + // put the keywords, if any, in their places in the arguments array + if (cargs.fKwds) + return ProcessKwds(nullptr, cargs); + return true; + } + } + } + + // no self, set error and lament + SetPyError_(cpyrt_PyText_FromFormat( + "unbound method %s::%s must be called with a %s instance as first " + "argument", + interop::GetFinalName(fScope).c_str(), + interop::GetName(interop::TCppScope_t(fMethod.data)).c_str(), + interop::GetFinalName(fScope).c_str())); + return false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CPPMethod::ConvertAndSetArgs(cpyrt_PyArgs_t args, size_t nargsf, + CallContext* ctxt) { + Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf); + if (!VerifyArgCount_(argc)) + return false; + + // pass current scope for which the call is made + ctxt->fCurScope = fScope; + + if (argc == 0) + return true; + + // convert the arguments to the method call array + bool isOK = true; + Parameter* cppArgs = ctxt->GetArgs(argc); + for (int i = 0; i < (int)argc; ++i) { + if (!fConverters[i]->SetArg(cpyrt_PyArgs_GET_ITEM(args, i), cppArgs[i], + ctxt)) { + SetPyError_( + cpyrt_PyText_FromFormat("could not convert argument %d: %s", i + 1, + fConverters[i]->GetFailureMsg().c_str())); + isOK = false; + break; + } + } + + return isOK; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::Execute(void* self, ptrdiff_t offset, + CallContext* ctxt) { + // call the interface method + PyObject* result = 0; + + if (CallContext::sSignalPolicy != CallContext::kProtected && + !(ctxt->fFlags & CallContext::kProtected)) { + // bypasses try block (i.e. segfaults will abort) + result = ExecuteFast(self, offset, ctxt); + } else { + // at the cost of ~10% performance, don't abort the interpreter on any + // signal + result = ExecuteProtected(self, offset, ctxt); + } + + if (!result && PyErr_Occurred()) + SetPyError_(0); + + return result; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPMethod::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // setup as necessary + if (fArgsRequired == -1 && !Initialize(ctxt)) + return nullptr; + + // fetch self, verify, and put the arguments in usable order + PyCallArgs cargs{self, args, nargsf, kwds}; + if (!ProcessArgs(cargs)) + return nullptr; + + // self provides the python context for lifelines + if (!ctxt->fPyContext) + ctxt->fPyContext = (PyObject*)cargs.fSelf; // no Py_INCREF as no ownership + + // translate the arguments + if (fArgsRequired || cpyrt_PyArgs_GET_SIZE(args, cargs.fNArgsf)) { + if (!ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) + return nullptr; + } + + // get the C++ object that this object proxy is a handle for + void* object = self->GetObject(); + + // validity check that should not fail + if (!object) { + PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); + return nullptr; + } + + // get its class + interop::TCppScope_t derived = self->ObjectIsA(); + + // calculate offset: the generated wrapper casts 'this' to the method's actual + // declaring class, which may differ from fScope. In particular, a method + // brought into fScope through a using-declaration (e.g. `using Base::meth;`) + // is still declared in the base, so 'this' has to be adjusted to that base's + // subobject. Using fScope here would yield a zero offset and corrupt memory. + interop::TCppScope_t declaring = + interop::GetParentScope(interop::TCppScope_t(fMethod.data)); + if (!declaring) + declaring = fScope; + + ptrdiff_t offset = 0; + if (derived && derived != declaring) + offset = + interop::GetBaseOffset(derived, declaring, object, 1 /* up-cast */); + + // actual call; recycle self instead of returning new object for same address + // objects + CPPInstance* pyobj = (CPPInstance*)Execute(object, offset, ctxt); + if (CPPInstance_Check(pyobj) && derived && pyobj->ObjectIsA() == derived && + pyobj->GetObject() == object) { + Py_INCREF((PyObject*)self); + Py_DECREF(pyobj); + return (PyObject*)self; + } + + return (PyObject*)pyobj; +} + +//- protected members -------------------------------------------------------- +PyObject* cpyrt::CPPMethod::GetSignature(bool fa) { + // construct python string from the method's signature + return cpyrt_PyText_FromString(GetSignatureString(fa).c_str()); +} + +/** + * @brief Returns a tuple with the names of the input parameters of this method. + * + * For example given a function with prototype: + * + * double foo(int a, float b, double c) + * + * this function returns: + * + * ('a', 'b', 'c') + */ +PyObject* cpyrt::CPPMethod::GetSignatureNames() { + // Build a tuple of the argument names for this signature. + int argcount = GetMaxArgs(); + PyObject* signature_names = PyTuple_New(argcount); + + for (int iarg = 0; iarg < argcount; ++iarg) { + const std::string& argname_cpp = interop::GetMethodArgName(fMethod, iarg); + PyObject* argname_py = cpyrt_PyText_FromString(argname_cpp.c_str()); + PyTuple_SET_ITEM(signature_names, iarg, argname_py); + } + + return signature_names; +} + +/** + * @brief Returns a dictionary with the types of the signature of this method. + * + * This dictionary will store both the return type and the input parameter + * types of this method, respectively with keys "return_type" and + * "input_types", for example given a function with prototype: + * + * double foo(int a, float b, double c) + * + * this function returns: + * + * {'input_types': ('int', 'float', 'double'), 'return_type': 'double'} + */ +PyObject* cpyrt::CPPMethod::GetSignatureTypes() { + + PyObject* signature_types_dict = PyDict_New(); + + // Insert the return type first + std::string return_type = GetReturnTypeName(); + PyObject* return_type_py = cpyrt_PyText_FromString(return_type.c_str()); + PyDict_SetItem(signature_types_dict, cpyrt_PyText_FromString("return_type"), + return_type_py); + + // Build a tuple of the argument types for this signature. + int argcount = GetMaxArgs(); + PyObject* parameter_types = PyTuple_New(argcount); + + for (int iarg = 0; iarg < argcount; ++iarg) { + const std::string& argtype_cpp = + interop::GetMethodArgTypeAsString(fMethod, iarg); + PyObject* argtype_py = cpyrt_PyText_FromString(argtype_cpp.c_str()); + PyTuple_SET_ITEM(parameter_types, iarg, argtype_py); + } + + PyDict_SetItem(signature_types_dict, cpyrt_PyText_FromString("input_types"), + parameter_types); + + return signature_types_dict; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::CPPMethod::GetReturnTypeName() { + return interop::GetMethodReturnTypeAsString(fMethod); +} diff --git a/src/cpyrt/CPPMethod.h b/src/cpyrt/CPPMethod.h new file mode 100644 index 0000000..f66016c --- /dev/null +++ b/src/cpyrt/CPPMethod.h @@ -0,0 +1,124 @@ +#ifndef CPYRT_CPPMETHOD_H +#define CPYRT_CPPMETHOD_H + +// Bindings +#include "PyCallable.h" + +// Standard +#include +#include +#include + +namespace cppjit::cpyrt { + +class Executor; +class Converter; + +class PyCallArgs { +public: + PyCallArgs(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds) + : fSelf(self), fArgs(args), fNArgsf(nargsf), fKwds(kwds), fFlags(kNone) {} + ~PyCallArgs(); + + enum ECleanupFlags { + kNone = 0x0000, + kIsOffset = 0x0001, // args were offset by 1 to drop self + kSelfSwap = 0x0002, // args[-1] and self need swapping + kArgsSwap = 0x0004, // args[0] and args[1] need swapping + kDoFree = 0x0008, // args need to be free'd (vector call only) + kDoItemDecref = 0x0010 // items in args need a decref (vector call only) + }; + +public: + CPPInstance*& fSelf; + cpyrt_PyArgs_t fArgs; + size_t fNArgsf; + PyObject* fKwds; + int fFlags; +}; + +class CPPMethod : public PyCallable { +public: + CPPMethod(cppjit::interop::TCppScope_t scope, + cppjit::interop::TCppMethod_t method); + CPPMethod(const CPPMethod&); + CPPMethod& operator=(const CPPMethod&); + virtual ~CPPMethod(); + +public: + PyObject* GetSignature(bool show_formalargs = true) override; + PyObject* GetSignatureNames() override; + PyObject* GetSignatureTypes() override; + PyObject* GetPrototype(bool show_formalargs = true) override; + PyObject* GetTypeName() override; + PyObject* Reflex(cppjit::interop::Reflex::RequestId_t request, + cppjit::interop::Reflex::FormatId_t = + cppjit::interop::Reflex::OPTIMAL) override; + + int GetPriority() override; + bool IsGreedy() override; + + int GetMaxArgs() override; + PyObject* GetCoVarNames() override; + PyObject* GetArgDefault(int iarg, bool silent = true) override; + bool IsConst() override; + + PyObject* GetScopeProxy() override; + cppjit::interop::TCppFuncAddr_t GetFunctionAddress() override; + + PyCallable* Clone() override { return new CPPMethod(*this); } + + int GetArgMatchScore(PyObject* args_tuple) override; + +public: + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +protected: + virtual bool ProcessArgs(PyCallArgs& args); + + bool Initialize(CallContext* ctxt = nullptr); + bool ProcessKwds(PyObject* self_in, PyCallArgs& args); + bool ConvertAndSetArgs(cpyrt_PyArgs_t, size_t nargsf, + CallContext* ctxt = nullptr); + PyObject* Execute(void* self, ptrdiff_t offset, CallContext* ctxt = nullptr); + + cppjit::interop::TCppMethod_t GetMethod() { return fMethod; } + cppjit::interop::TCppScope_t GetScope() { return fScope; } + Executor* GetExecutor() { return fExecutor; } + std::string GetSignatureString(bool show_formalargs = true); + std::string GetReturnTypeName(); + + virtual bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr); + +private: + void Copy_(const CPPMethod&); + void Destroy_(); + bool VerifyArgCount_(Py_ssize_t); + + PyObject* ExecuteFast(void*, ptrdiff_t, CallContext*); + PyObject* ExecuteProtected(void*, ptrdiff_t, CallContext*); + + bool InitConverters_(); + + void SetPyError_(PyObject* msg); + +private: + // representation + cppjit::interop::TCppMethod_t fMethod; + cppjit::interop::TCppScope_t fScope; + Executor* fExecutor; + + // call dispatch buffers + std::vector fConverters; + std::unordered_map* fArgIndices; + +protected: + // cached value that doubles as initialized flag (uninitialized if -1) + int fArgsRequired; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPMETHOD_H diff --git a/src/cpyrt/CPPOperator.cxx b/src/cpyrt/CPPOperator.cxx new file mode 100644 index 0000000..b48a243 --- /dev/null +++ b/src/cpyrt/CPPOperator.cxx @@ -0,0 +1,62 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "CPPOperator.h" +#include "Utility.h" + +//- constructor -------------------------------------------------------------- +cpyrt::CPPOperator::CPPOperator(interop::TCppScope_t scope, + interop::TCppMethod_t method, + const std::string& name) + : CPPMethod(scope, method) { + // a bit silly but doing it this way allows decoupling the initialization + // order + if (name == "__mul__") + fStub = CPPInstance_Type.tp_as_number->nb_multiply; + else if (name == CPPJIT__div__) + fStub = CPPInstance_Type.tp_as_number->nb_true_divide; + else if (name == "__add__") + fStub = CPPInstance_Type.tp_as_number->nb_add; + else if (name == "__sub__") + fStub = CPPInstance_Type.tp_as_number->nb_subtract; + else + fStub = nullptr; +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::CPPOperator::Call(CPPInstance*& self, cpyrt_PyArgs_t args, + size_t nargsf, PyObject* kwds, + CallContext* ctxt) { + // some operators can be a mix of global and class overloads; this method will + // first try class overloads (the existence of this method means that such + // were defined) and if failed, fall back on the global stubs + // TODO: the fact that this is a method and not an overload means that the + // global ones are tried for each method that fails during the overload + // resolution + PyObject* result = this->CPPMethod::Call(self, args, nargsf, kwds, ctxt); + if (result || !fStub || !self) + return result; + + Py_ssize_t idx_other = 0; + if (cpyrt_PyArgs_GET_SIZE(args, nargsf) != 1) { + if ((cpyrt_PyArgs_GET_SIZE(args, nargsf) == 2 && + cpyrt_PyArgs_GET_ITEM(args, 0) == (PyObject*)self)) + idx_other = 1; + else + return result; + } + + // fetch the current error, resetting the error buffer + auto error = cpyrt::Utility::FetchPyError(); + + result = fStub((PyObject*)self, cpyrt_PyArgs_GET_ITEM(args, idx_other)); + + // if there was still a problem, restore the Python error buffer + if (!result) { + cpyrt::Utility::RestorePyError(error); + } + + return result; +} diff --git a/src/cpyrt/CPPOperator.h b/src/cpyrt/CPPOperator.h new file mode 100644 index 0000000..03e4f73 --- /dev/null +++ b/src/cpyrt/CPPOperator.h @@ -0,0 +1,28 @@ +#ifndef CPYRT_CPPOPERATOR_H +#define CPYRT_CPPOPERATOR_H + +// Bindings +#include "CPPMethod.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +class CPPOperator : public CPPMethod { +public: + CPPOperator(cppjit::interop::TCppScope_t scope, + cppjit::interop::TCppMethod_t method, const std::string& name); + +public: + PyCallable* Clone() override { return new CPPOperator(*this); } + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) override; + +private: + binaryfunc fStub; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPOPERATOR_H diff --git a/src/cpyrt/CPPOverload.cxx b/src/cpyrt/CPPOverload.cxx new file mode 100644 index 0000000..8bda467 --- /dev/null +++ b/src/cpyrt/CPPOverload.cxx @@ -0,0 +1,1157 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "structmember.h" // from Python +#include "cpyrt/Reflex.h" +#if PY_VERSION_HEX < 0x030b0000 +#include "code.h" // from Python +#endif +#ifndef CO_NOFREE +// python2.2 does not have CO_NOFREE defined +#define CO_NOFREE 0x0040 +#endif +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CallContext.h" +#include "PyStrings.h" +#include "Utility.h" + +// Standard +#include +#include +#include + +namespace cppjit::cpyrt { + +namespace { + +// from CPython's instancemethod: Free list for method objects to safe +// malloc/free overhead The fSelf field is used to chain the elements. +static CPPOverload* free_list; +static int numfree = 0; +#ifndef CPPOverload_MAXFREELIST +#define CPPOverload_MAXFREELIST 32 +#endif + +// TODO: only used in pythonizations to add Python-side overloads to existing +// C++ overloads, but may be better off integrated with Pythonize.cxx callbacks +class TPythonCallback : public PyCallable { +public: + PyObject* fCallable; + + TPythonCallback(PyObject* callable) : fCallable(nullptr) { + if (!PyCallable_Check(callable)) { + PyErr_SetString(PyExc_TypeError, "parameter must be callable"); + return; + } + Py_INCREF(callable); + fCallable = callable; + } + + ~TPythonCallback() override { + Py_DECREF(fCallable); + fCallable = nullptr; + } + + PyObject* GetSignature(bool /*show_formalargs*/ = true) override { + return cpyrt_PyText_FromString("*args, **kwargs"); + } + PyObject* GetSignatureNames() override { return PyTuple_New(0); } + PyObject* GetSignatureTypes() override { return PyTuple_New(0); } + PyObject* GetPrototype(bool /*show_formalargs*/ = true) override { + return cpyrt_PyText_FromString(""); + } + PyObject* GetDocString() override { + if (PyObject_HasAttrString(fCallable, "__doc__")) { + return PyObject_GetAttrString(fCallable, "__doc__"); + } else { + return GetPrototype(); + } + } + + int GetPriority() override { return 100; }; + bool IsGreedy() override { return false; }; + + int GetMaxArgs() override { return 100; }; + PyObject* GetCoVarNames() override { // TODO: pick these up from the callable + Py_RETURN_NONE; + } + PyObject* GetArgDefault(int /* iarg */, bool /* silent */ = true) override { + Py_RETURN_NONE; // TODO: pick these up from the callable + } + + PyObject* GetScopeProxy() override { // should this be the module ?? + Py_RETURN_NONE; + } + + interop::TCppFuncAddr_t GetFunctionAddress() override { + return (interop::TCppFuncAddr_t) nullptr; + } + + PyCallable* Clone() override { return new TPythonCallback(*this); } + + PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* /* ctxt = 0 */) override { + + if (self) { + if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) { // mutation allowed? + std::swap(((PyObject**)args - 1)[0], (PyObject*&)self); + nargsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET; + args = args - 1; + } else { + Py_ssize_t nkwargs = kwds ? PyTuple_GET_SIZE(kwds) : 0; + Py_ssize_t totalargs = PyVectorcall_NARGS(nargsf) + nkwargs; + PyObject** newArgs = + (PyObject**)PyMem_Malloc((totalargs + 1) * sizeof(PyObject*)); + if (!newArgs) + return nullptr; + + newArgs[0] = (PyObject*)self; + if (0 < totalargs) + memcpy((void*)&newArgs[1], args, totalargs * sizeof(PyObject*)); + args = newArgs; + } + nargsf += 1; + } + + PyObject* result = cpyrt_PyObject_Call(fCallable, args, nargsf, kwds); + if (self) { + if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) + std::swap(((PyObject**)args - 1)[0], (PyObject*&)self); + else + PyMem_Free((void*)args); + } + return result; + } +}; + +// helper to test whether a method is used in a pseudo-function modus +static inline bool IsPseudoFunc(CPPOverload* pymeth) { + return pymeth->fMethodInfo->fFlags & CallContext::kIsPseudoFunc; +} + +// helper to sort on method priority +static int PriorityCmp(const std::pair& left, + const std::pair& right) { + return left.first > right.first; +} + +// return helper +static inline void ResetCallState(CPPInstance* descr_self, + CPPInstance*& im_self) { + // reset self if needed, allowing simple re-use + if (descr_self != im_self) { + Py_XDECREF(im_self); + im_self = descr_self; + } +} + +// helper to factor out return logic of mp_call / mp_vectorcall +static inline PyObject* HandleReturn(CPPOverload* pymeth, CPPInstance* im_self, + PyObject* result) { + // special case for python exceptions, propagated through C++ layer + if (result) { + CPPInstance* cppres = + (CPPInstance*)(CPPInstance_Check(result) ? result : nullptr); + + // if this method creates new objects, always take ownership + if (IsCreator(pymeth->fMethodInfo->fFlags)) { + + // either be a constructor with a fresh object proxy self ... + if (IsConstructor(pymeth->fMethodInfo->fFlags)) { + if (im_self) + im_self->PythonOwns(); + } + + // ... or be a regular method with an object proxy return value + else if (cppres) + cppres->PythonOwns(); + } + + // if this new object falls inside self, make sure its lifetime is proper + if (!(pymeth->fMethodInfo->fFlags & CallContext::kNeverLifeLine)) { + int ll_action = 0; + if ((PyObject*)im_self != result) { + if (pymeth->fMethodInfo->fFlags & CallContext::kSetLifeLine) + ll_action = 1; + else if (cppres && CPPInstance_Check(im_self)) { + // if self was a by-value return and result is not, pro-actively + // protect result; else if the return value falls within the memory of + // 'this', force a lifeline + if (!(cppres->fFlags & + CPPInstance::kIsValue)) { // no need if the result is temporary + if (im_self->fFlags & CPPInstance::kIsValue) + ll_action = 2; + else if (im_self->fFlags & CPPInstance::kHasLifeLine) + ll_action = 3; + else { + ptrdiff_t offset = (ptrdiff_t)cppres->GetObject() - + (ptrdiff_t)im_self->GetObject(); + if (0 <= offset && + offset < (ptrdiff_t)interop::SizeOf(im_self->ObjectIsA())) + ll_action = 4; + } + } + } + } + + if (!ll_action) + pymeth->fMethodInfo->fFlags |= + CallContext::kNeverLifeLine; // assume invariant semantics + else { + if (PyObject_SetAttr(result, PyStrings::gLifeLine, + (PyObject*)im_self) == -1) + PyErr_Clear(); // ignored + if (cppres) + cppres->fFlags |= CPPInstance::kHasLifeLine; // for chaining + pymeth->fMethodInfo->fFlags |= + CallContext::kSetLifeLine; // for next time + } + } + } + + // reset self as necessary to allow re-use of the CPPOverload + ResetCallState(pymeth->fSelf, im_self); + + return result; +} + +//= cpyrt method proxy object behaviour =================================== +static PyObject* mp_name(CPPOverload* pymeth, void*) { + return cpyrt_PyText_FromString(pymeth->GetName().c_str()); +} + +//---------------------------------------------------------------------------- +static PyObject* mp_module(CPPOverload* /* pymeth */, void*) { + Py_INCREF(PyStrings::gThisModule); + return PyStrings::gThisModule; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_doc(CPPOverload* pymeth, void*) { + if (pymeth->fMethodInfo->fDoc) { + Py_INCREF(pymeth->fMethodInfo->fDoc); + return pymeth->fMethodInfo->fDoc; + } + + // Build python document string ('__doc__') from all C++-side overloads. + CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; + + // collect doc strings + CPPOverload::Methods_t::size_type nMethods = methods.size(); + if (nMethods == 0) // from template proxy with no instantiations + return nullptr; + PyObject* doc = methods[0]->GetDocString(); + + // simple case + if (nMethods == 1) + return doc; + + // overloaded method + PyObject* separator = cpyrt_PyText_FromString("\n"); + for (CPPOverload::Methods_t::size_type i = 1; i < nMethods; ++i) { + cpyrt_PyText_Append(&doc, separator); + cpyrt_PyText_AppendAndDel(&doc, methods[i]->GetDocString()); + } + Py_DECREF(separator); + + return doc; +} + +static int mp_doc_set(CPPOverload* pymeth, PyObject* val, void*) { + Py_XDECREF(pymeth->fMethodInfo->fDoc); + Py_INCREF(val); + pymeth->fMethodInfo->fDoc = val; + return 0; +} + +/** + * @brief Returns a dictionary with the input parameter names for all overloads. + * + * This dictionary may look like: + * + * {'double ::foo(int a, float b, double c)': ('a', 'b', 'c'), + * 'float ::foo(float b)': ('b',), + * 'int ::foo(int a)': ('a',), + * 'int ::foo(int a, float b)': ('a', 'b')} + */ +static PyObject* mp_func_overloads_names(CPPOverload* pymeth) { + + const CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; + + PyObject* overloads_names_dict = PyDict_New(); + + for (PyCallable* method : methods) { + PyDict_SetItem(overloads_names_dict, method->GetPrototype(), + method->GetSignatureNames()); + } + + return overloads_names_dict; +} + +/** + * @brief Returns a dictionary with the types of all overloads. + * + * This dictionary may look like: + * + * {'double ::foo(int a, float b, double c)': {'input_types': ('int', 'float', + * 'double'), 'return_type': 'double'}, 'float ::foo(float b)': {'input_types': + * ('float',), 'return_type': 'float'}, 'int ::foo(int a)': {'input_types': + * ('int',), 'return_type': 'int'}, 'int ::foo(int a, float b)': {'input_types': + * ('int', 'float'), 'return_type': 'int'}} + */ +static PyObject* mp_func_overloads_types(CPPOverload* pymeth) { + + const CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; + + PyObject* overloads_types_dict = PyDict_New(); + + for (PyCallable* method : methods) { + PyDict_SetItem(overloads_types_dict, method->GetPrototype(), + method->GetSignatureTypes()); + } + + return overloads_types_dict; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_meth_func(CPPOverload* pymeth, void*) { + // Create a new method proxy to be returned. + CPPOverload* newPyMeth = + (CPPOverload*)CPPOverload_Type.tp_alloc(&CPPOverload_Type, 0); + + // method info is shared, as it contains the collected overload knowledge + *pymeth->fMethodInfo->fRefCount += 1; + newPyMeth->fMethodInfo = pymeth->fMethodInfo; + + // new method is unbound, track whether this proxy is used in the capacity of + // a method or a function (which normally is a CPPFunction) + newPyMeth->fMethodInfo->fFlags |= CallContext::kIsPseudoFunc; + + return (PyObject*)newPyMeth; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_meth_self(CPPOverload* pymeth, void*) { + // Return the bound self, if any; in case of pseudo-function role, pretend + // that the data member im_self does not exist. + if (IsPseudoFunc(pymeth)) { + PyErr_Format(PyExc_AttributeError, + "function %s has no attribute \'im_self\'", + pymeth->fMethodInfo->fName.c_str()); + return nullptr; + } else if (pymeth->fSelf != 0) { + Py_INCREF((PyObject*)pymeth->fSelf); + return (PyObject*)pymeth->fSelf; + } + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_meth_class(CPPOverload* pymeth, void*) { + // Return scoping class; in case of pseudo-function role, pretend that there + // is no encompassing class (i.e. global scope). + if (!IsPseudoFunc(pymeth) && pymeth->fMethodInfo->fMethods.size()) { + PyObject* pyclass = pymeth->fMethodInfo->fMethods[0]->GetScopeProxy(); + if (!pyclass) + PyErr_Format(PyExc_AttributeError, + "function %s has no attribute \'im_class\'", + pymeth->fMethodInfo->fName.c_str()); + return pyclass; + } + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_func_closure(CPPOverload* /* pymeth */, void*) { + // Stub only, to fill out the python function interface. + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_func_code(CPPOverload*, void*) { + // Code details are used in module inspect to fill out interactive help() + // not important for functioning of most code, so not implemented for p3 for + // now (TODO) + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_func_defaults(CPPOverload* pymeth, void*) { + // Create a tuple of default values, if there is only one method (otherwise + // leave undefined: this is only used by inspect for interactive help()) + CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods; + + if (methods.size() != 1) + return PyTuple_New(0); + + int maxarg = methods[0]->GetMaxArgs(); + + PyObject* defaults = PyTuple_New(maxarg); + + int itup = 0; + for (int iarg = 0; iarg < maxarg; ++iarg) { + PyObject* defvalue = methods[0]->GetArgDefault(iarg); + if (defvalue) + PyTuple_SET_ITEM(defaults, itup++, defvalue); + else + PyErr_Clear(); + } + _PyTuple_Resize(&defaults, itup); + + return defaults; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_func_globals(CPPOverload* /* pymeth */, void*) { + // Return this function's global dict (hard-wired to be the cppjit module); + // used for lookup of names from co_code indexing into co_names. + PyObject* pyglobal = PyModule_GetDict(PyImport_AddModule((char*)"cppjit")); + Py_XINCREF(pyglobal); + return pyglobal; +} + +//---------------------------------------------------------------------------- +static inline int set_flag(CPPOverload* pymeth, PyObject* value, + CallContext::ECallFlags flag, const char* name) { + // Generic setter of a (boolean) flag. + if (!value) { // accept as false (delete) + pymeth->fMethodInfo->fFlags &= ~flag; + return 0; + } + + long istrue = PyLong_AsLong(value); + if (istrue == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_ValueError, "a boolean 1 or 0 is required for %s", name); + return -1; + } + + if (istrue) + pymeth->fMethodInfo->fFlags |= flag; + else + pymeth->fMethodInfo->fFlags &= ~flag; + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_getcreates(CPPOverload* pymeth, void*) { + // Get '__creates__' boolean, which determines ownership of return values. + return PyInt_FromLong((long)IsCreator(pymeth->fMethodInfo->fFlags)); +} + +//---------------------------------------------------------------------------- +static int mp_setcreates(CPPOverload* pymeth, PyObject* value, void*) { + // Set '__creates__' boolean, which determines ownership of return values. + return set_flag(pymeth, value, CallContext::kIsCreator, "__creates__"); +} + +//---------------------------------------------------------------------------- +static PyObject* mp_getmempolicy(CPPOverload* pymeth, void*) { + // Get '_mempolicy' enum, which determines ownership of call arguments. + if (pymeth->fMethodInfo->fFlags & CallContext::kUseHeuristics) + return PyInt_FromLong(CallContext::kUseHeuristics); + + if (pymeth->fMethodInfo->fFlags & CallContext::kUseStrict) + return PyInt_FromLong(CallContext::kUseStrict); + + return PyInt_FromLong(-1); +} + +//---------------------------------------------------------------------------- +static int mp_setmempolicy(CPPOverload* pymeth, PyObject* value, void*) { + // Set '_mempolicy' enum, which determines ownership of call arguments. + long mempolicy = PyLong_AsLong(value); + if (mempolicy == CallContext::kUseHeuristics) { + pymeth->fMethodInfo->fFlags |= CallContext::kUseHeuristics; + pymeth->fMethodInfo->fFlags &= ~CallContext::kUseStrict; + } else if (mempolicy == CallContext::kUseStrict) { + pymeth->fMethodInfo->fFlags |= CallContext::kUseStrict; + pymeth->fMethodInfo->fFlags &= ~CallContext::kUseHeuristics; + } else { + PyErr_SetString(PyExc_ValueError, + "expected kMemoryStrict or kMemoryHeuristics as value for " + "__mempolicy__"); + return -1; + } + + return 0; +} + +//---------------------------------------------------------------------------- +#define CPPJIT_BOOLEAN_PROPERTY(name, flag, label) \ + static PyObject* mp_get##name(CPPOverload* pymeth, void*) { \ + if (pymeth->fMethodInfo->fFlags & flag) { \ + Py_RETURN_TRUE; \ + } \ + Py_RETURN_FALSE; \ + } \ + \ + static int mp_set##name(CPPOverload* pymeth, PyObject* value, void*) { \ + return set_flag(pymeth, value, flag, label); \ + } + +// clang-format off +CPPJIT_BOOLEAN_PROPERTY(lifeline, CallContext::kSetLifeLine, "__set_lifeline__") +CPPJIT_BOOLEAN_PROPERTY(threaded, CallContext::kReleaseGIL, "__release_gil__") +CPPJIT_BOOLEAN_PROPERTY(useffi, CallContext::kUseFFI, "__useffi__") +CPPJIT_BOOLEAN_PROPERTY(sig2exc, CallContext::kProtected, "__sig2exc__") +// clang-format on + +static PyObject* mp_getcppname(CPPOverload* pymeth, void*) { + if ((void*)pymeth == (void*)&CPPOverload_Type) + return cpyrt_PyText_FromString("CPPOverload_Type"); + + auto& methods = pymeth->fMethodInfo->fMethods; + if (methods.empty()) + return cpyrt_PyText_FromString("void (*)()"); // debatable + + if (methods.size() == 1) + return methods[0]->GetTypeName(); + + return cpyrt_PyText_FromString("void* (*)(...)"); // id. +} + +//---------------------------------------------------------------------------- +static PyGetSetDef mp_getset[] = { + {(char*)"__name__", (getter)mp_name, nullptr, nullptr, nullptr}, + {(char*)"__module__", (getter)mp_module, nullptr, nullptr, nullptr}, + {(char*)"__doc__", (getter)mp_doc, (setter)mp_doc_set, nullptr, nullptr}, + + // to be more python-like, where these are duplicated as well; to actually + // derive from the python method or function type is too memory-expensive, + // given that most of the members of those types would not be used + {(char*)"im_func", (getter)mp_meth_func, nullptr, nullptr, nullptr}, + {(char*)"im_self", (getter)mp_meth_self, nullptr, nullptr, nullptr}, + {(char*)"im_class", (getter)mp_meth_class, nullptr, nullptr, nullptr}, + + {(char*)"func_closure", (getter)mp_func_closure, nullptr, nullptr, nullptr}, + {(char*)"func_code", (getter)mp_func_code, nullptr, nullptr, nullptr}, + {(char*)"func_defaults", (getter)mp_func_defaults, nullptr, nullptr, + nullptr}, + {(char*)"func_globals", (getter)mp_func_globals, nullptr, nullptr, nullptr}, + {(char*)"func_doc", (getter)mp_doc, (setter)mp_doc_set, nullptr, nullptr}, + {(char*)"func_name", (getter)mp_name, nullptr, nullptr, nullptr}, + {(char*)"func_overloads_types", (getter)mp_func_overloads_types, nullptr, + nullptr, nullptr}, + {(char*)"func_overloads_names", (getter)mp_func_overloads_names, nullptr, + nullptr, nullptr}, + + // flags to control behavior + {(char*)"__creates__", (getter)mp_getcreates, (setter)mp_setcreates, + (char*)"For ownership rules of result: if true, objects are python-owned", + nullptr}, + {(char*)"__mempolicy__", (getter)mp_getmempolicy, (setter)mp_setmempolicy, + (char*)"For argument ownership rules: like global, either heuristic or " + "strict", + nullptr}, + {(char*)"__set_lifeline__", (getter)mp_getlifeline, (setter)mp_setlifeline, + (char*)"If true, set a lifeline from the return value onto self", nullptr}, + {(char*)"__release_gil__", (getter)mp_getthreaded, (setter)mp_setthreaded, + (char*)"If true, releases GIL on call into C++", nullptr}, + {(char*)"__useffi__", (getter)mp_getuseffi, (setter)mp_setuseffi, + (char*)"not implemented", nullptr}, + {(char*)"__sig2exc__", (getter)mp_getsig2exc, (setter)mp_setsig2exc, + (char*)"If true, turn signals into Python exceptions", nullptr}, + + // basic reflection information + {(char*)"__cpp_name__", (getter)mp_getcppname, nullptr, nullptr, nullptr}, + + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +//= cpyrt method proxy function behavior ================================== +static PyObject* mp_vectorcall(CPPOverload* pymeth, PyObject* const* args, + size_t nargsf, PyObject* kwds) { + // Call the appropriate overload of this method. + + // If called from a descriptor, then this could be a bound function with + // non-zero self; otherwise pymeth->fSelf is expected to always be nullptr. + + CPPInstance* im_self = pymeth->fSelf; + + // get local handles to proxy internals + auto& methods = pymeth->fMethodInfo->fMethods; + + CPPOverload::Methods_t::size_type nMethods = methods.size(); + + CallContext ctxt{}; + const auto mflags = pymeth->fMethodInfo->fFlags; + const auto mempolicy = + (mflags & (CallContext::kUseHeuristics | CallContext::kUseStrict)); + ctxt.fFlags |= mempolicy ? mempolicy : (uint64_t)CallContext::sMemoryPolicy; + ctxt.fFlags |= (mflags & CallContext::kReleaseGIL); + ctxt.fFlags |= (mflags & CallContext::kProtected); + if (IsConstructor(pymeth->fMethodInfo->fFlags)) + ctxt.fFlags |= CallContext::kIsConstructor; + ctxt.fFlags |= + (pymeth->fFlags & (CallContext::kCallDirect | CallContext::kFromDescr)); + ctxt.fPyContext = (PyObject*)im_self; // no Py_INCREF as no ownership + + // check implicit conversions status (may be disallowed to prevent recursion) + ctxt.fFlags |= (pymeth->fFlags & CallContext::kNoImplicit); + + // simple case + if (nMethods == 1) { + if (!NoImplicit(&ctxt)) + ctxt.fFlags |= CallContext::kAllowImplicit; // no two rounds needed + PyObject* result = methods[0]->Call(im_self, args, nargsf, kwds, &ctxt); + return HandleReturn(pymeth, im_self, result); + } + + // otherwise, handle overloading + uint64_t sighash = HashSignature(args, nargsf); + + // look for known signatures ... + auto& dispatchMap = pymeth->fMethodInfo->fDispatchMap; + PyCallable* memoized_pc = nullptr; + for (const auto& p : dispatchMap) { + if (p.first == sighash) { + memoized_pc = p.second; + break; + } + } + if (memoized_pc) { + // it is necessary to enable implicit conversions as the memoized call may + // be from such a conversion case; if the call fails, the implicit flag is + // reset below + if (!NoImplicit(&ctxt)) + ctxt.fFlags |= CallContext::kAllowImplicit; + PyObject* result = memoized_pc->Call(im_self, args, nargsf, kwds, &ctxt); + if (result) + return HandleReturn(pymeth, im_self, result); + + // fall through: python is dynamic, and so, the hashing isn't infallible + ctxt.fFlags &= ~CallContext::kAllowImplicit; + PyErr_Clear(); + ResetCallState(pymeth->fSelf, im_self); + } + + // ... otherwise loop over all methods and find the one that does not fail + if (!IsSorted(mflags)) { + // sorting is based on priority, which is not stored on the method as it is + // used only once, so copy the vector of methods into one where the priority + // can be stored during sorting + std::vector> pm; + pm.reserve(methods.size()); + for (auto ptr : methods) + pm.emplace_back(ptr->GetPriority(), ptr); + std::stable_sort(pm.begin(), pm.end(), PriorityCmp); + for (CPPOverload::Methods_t::size_type i = 0; i < methods.size(); ++i) + methods[i] = pm[i].second; + pymeth->fMethodInfo->fFlags |= CallContext::kIsSorted; + } + + std::vector errors; + std::vector implicit_possible(methods.size()); + for (int stage = 0; stage < 2; ++stage) { + bool bHaveImplicit = false; + for (CPPOverload::Methods_t::size_type i = 0; i < nMethods; ++i) { + if (stage && !implicit_possible[i]) + continue; // did not set implicit conversion, so don't try again + + PyObject* result = methods[i]->Call(im_self, args, nargsf, kwds, &ctxt); + if (result) { + // success: update the dispatch map for subsequent calls + if (!memoized_pc) + dispatchMap.push_back(std::make_pair(sighash, methods[i])); + else { + // debatable: apparently there are two methods that map onto the same + // sighash and preferring the latest may result in "ping pong." + for (auto& p : dispatchMap) { + if (p.first == sighash) { + p.second = methods[i]; + break; + } + } + } + + return HandleReturn(pymeth, im_self, result); + } + + // else failure .. + if (stage != 0) { + PyErr_Clear(); // first stage errors should be the more informative + ResetCallState(pymeth->fSelf, im_self); + continue; + } + + // collect error message/trace (automatically clears exception, too) + if (!PyErr_Occurred()) { + // this should not happen; set an error to prevent core dump and report + PyObject* sig = methods[i]->GetPrototype(); + PyErr_Format(PyExc_SystemError, "%s =>\n %s", + cpyrt_PyText_AsString(sig), + (char*)"nullptr result without error in overload call"); + Py_DECREF(sig); + } + + // retrieve, store, and clear errors + bool callee_error = ctxt.fFlags & (CallContext::kPyException | + CallContext::kCppException); + ctxt.fFlags &= ~(CallContext::kPyException | CallContext::kCppException); + Utility::FetchError(errors, callee_error); + + if (HaveImplicit(&ctxt)) { + bHaveImplicit = true; + implicit_possible[i] = true; + ctxt.fFlags &= ~CallContext::kHaveImplicit; + } else + implicit_possible[i] = false; + ResetCallState(pymeth->fSelf, im_self); + } + + // only move forward if implicit conversions are available + if (!bHaveImplicit) + break; + + ctxt.fFlags |= CallContext::kAllowImplicit; + } + + // first summarize, then add details + PyObject* topmsg = cpyrt_PyText_FromFormat( + "none of the %d overloaded methods succeeded. Full details:", + (int)nMethods); + SetDetailedException(std::move(errors), topmsg /* steals */, + PyExc_TypeError /* default error */); + + // report failure + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_str(CPPOverload* cppinst) { + // Print a description that includes the C++ name + std::ostringstream s; + s << "fMethodInfo->fName << "\" at " + << (void*)cppinst << ">"; + return cpyrt_PyText_FromString(s.str().c_str()); +} + +//---------------------------------------------------------------------------- +static CPPOverload* mp_descr_get(CPPOverload* pymeth, CPPInstance* pyobj, + PyObject*) { + // Descriptor; create and return a new, possibly bound, method proxy. This + // method has evolved with versions of python as follows: + // + // Python version | Action + // <- py2.7 | bound methods need to be first-class objects, so + // create a new + // | method object if self is not nullptr or Py_None + // py3.0-py3.7 | bound methods are no longer a language requirement, + // but + // | still supported: for convenience, retain old behavior + // py3.8 <= | vector calls no longer call the descriptor, so when it + // is + // | called, the method is likely stored, so should be new + // object + + // create a new method object + bool gc_track = false; + CPPOverload* newPyMeth = free_list; + if (newPyMeth != NULL) { + free_list = (CPPOverload*)(newPyMeth->fSelf); + (void)PyObject_INIT(newPyMeth, &CPPOverload_Type); + numfree--; + } else { + newPyMeth = PyObject_GC_New(CPPOverload, &CPPOverload_Type); + if (!newPyMeth) + return nullptr; + gc_track = true; + } + + // method info is shared, as it contains the collected overload knowledge + *pymeth->fMethodInfo->fRefCount += 1; + newPyMeth->fMethodInfo = pymeth->fMethodInfo; + + newPyMeth->fVectorCall = pymeth->fVectorCall; + + if (pyobj && (PyObject*)pyobj != Py_None) { + Py_INCREF((PyObject*)pyobj); + newPyMeth->fSelf = pyobj; + newPyMeth->fFlags = CallContext::kNone; + } else { + newPyMeth->fSelf = nullptr; + newPyMeth->fFlags = CallContext::kCallDirect; + } + + // vector calls don't get here, unless a method is looked up on an instance, + // for e.g. class methods (C++ static); notify downstream to expect a 'self' + newPyMeth->fFlags |= CallContext::kFromDescr; + + if (gc_track) + PyObject_GC_Track(newPyMeth); + + return newPyMeth; +} + +//= cpyrt method proxy construction/destruction =========================== +static CPPOverload* mp_new(PyTypeObject*, PyObject*, PyObject*) { + // Create a new method proxy object. + CPPOverload* pymeth = PyObject_GC_New(CPPOverload, &CPPOverload_Type); + pymeth->fSelf = nullptr; + pymeth->fFlags = CallContext::kNone; + pymeth->fMethodInfo = new CPPOverload::MethodInfo_t; + + PyObject_GC_Track(pymeth); + return pymeth; +} + +//---------------------------------------------------------------------------- +static void mp_dealloc(CPPOverload* pymeth) { + // Deallocate memory held by method proxy object. + PyObject_GC_UnTrack(pymeth); + + Py_CLEAR(pymeth->fSelf); + + if (--(*pymeth->fMethodInfo->fRefCount) <= 0) { + delete pymeth->fMethodInfo; + } + + if (numfree < CPPOverload_MAXFREELIST) { + pymeth->fSelf = (cpyrt::CPPInstance*)free_list; + free_list = pymeth; + numfree++; + } else { + PyObject_GC_Del(pymeth); + } +} + +//---------------------------------------------------------------------------- +static Py_ssize_t mp_hash(CPPOverload* pymeth) { +// Hash of method proxy object for insertion into dictionaries; with actual +// method (fMethodInfo) shared, its address is best suited. +#if PY_VERSION_HEX >= 0x030d0000 + return Py_HashPointer(pymeth->fMethodInfo); +#else + return _Py_HashPointer(pymeth->fMethodInfo); +#endif +} + +//---------------------------------------------------------------------------- +static int mp_traverse(CPPOverload* pymeth, visitproc visit, void* args) { + // Garbage collector traverse of held python member objects. + if (pymeth->fSelf) + return visit((PyObject*)pymeth->fSelf, args); + + return 0; +} + +//---------------------------------------------------------------------------- +static int mp_clear(CPPOverload* pymeth) { + // Garbage collector clear of held python member objects. + Py_CLEAR(pymeth->fSelf); + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* mp_richcompare(CPPOverload* self, CPPOverload* other, int op) { + // Rich set of comparison objects; only equals is defined. + if (op != Py_EQ) + return PyType_Type.tp_richcompare((PyObject*)self, (PyObject*)other, op); + + // defined by type + (shared) MethodInfo + bound self, with special case for + // fSelf (i.e. pseudo-function) + if ((Py_TYPE(self) == Py_TYPE(other) && + self->fMethodInfo == other->fMethodInfo) && + ((IsPseudoFunc(self) && IsPseudoFunc(other)) || + self->fSelf == other->fSelf)) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +//= cpyrt method proxy access to internals ================================ +static PyObject* mp_overload(CPPOverload* pymeth, PyObject* args) { + // Select and call a specific C++ overload, based on its signature. + const char* sigarg = nullptr; + PyObject* sigarg_tuple = nullptr; + int want_const = -1; + Py_ssize_t args_size = PyTuple_GET_SIZE(args); + if (args_size && PyArg_ParseTuple(args, const_cast("s|i:__overload__"), + &sigarg, &want_const)) { + want_const = args_size == 1 ? -1 : want_const; + return pymeth->FindOverload(sigarg ? sigarg : "", want_const); + } else if (args_size && + PyArg_ParseTuple(args, const_cast("O|i:__overload__"), + &sigarg_tuple, &want_const)) { + PyErr_Clear(); + want_const = args_size == 1 ? -1 : want_const; + return pymeth->FindOverload(sigarg_tuple, want_const); + } else { + PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__"); + return nullptr; + } +} + +static PyObject* mp_add_overload(CPPOverload* pymeth, PyObject* new_overload) { + TPythonCallback* cb = new TPythonCallback(new_overload); + pymeth->AdoptMethod(cb); + Py_RETURN_NONE; +} + +static PyObject* mp_reflex(CPPOverload* pymeth, PyObject* args) { + // Provide the requested reflection information. + interop::Reflex::RequestId_t request = -1; + interop::Reflex::FormatId_t format = interop::Reflex::OPTIMAL; + if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, + &format)) + return nullptr; + + return pymeth->fMethodInfo->fMethods[0]->Reflex(request, format); +} + +//---------------------------------------------------------------------------- +static PyMethodDef mp_methods[] = { + {(char*)"__overload__", (PyCFunction)mp_overload, METH_VARARGS, + (char*)"select overload for dispatch"}, + {(char*)"__add_overload__", (PyCFunction)mp_add_overload, METH_O, + (char*)"add a new overload"}, + {(char*)"__cpp_reflex__", (PyCFunction)mp_reflex, METH_VARARGS, + (char*)"C++ overload reflection information"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +} // unnamed namespace + +//= cpyrt method proxy type =============================================== +PyTypeObject CPPOverload_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.CPPOverload", // tp_name + sizeof(CPPOverload), // tp_basicsize + 0, // tp_itemsize + (destructor)mp_dealloc, // tp_dealloc + offsetof(CPPOverload, fVectorCall), + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + (hashfunc)mp_hash, // tp_hash + (ternaryfunc)PyVectorcall_Call, // tp_call + (reprfunc)mp_str, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VECTORCALL | + Py_TPFLAGS_METHOD_DESCRIPTOR, // tp_flags + (char*)"cppjit method proxy (internal)", // tp_doc + (traverseproc)mp_traverse, // tp_traverse + (inquiry)mp_clear, // tp_clear + (richcmpfunc)mp_richcompare, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + mp_methods, // tp_methods + 0, // tp_members + mp_getset, // tp_getset + 0, // tp_base + 0, // tp_dict + (descrgetfunc)mp_descr_get, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)mp_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 + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt + +//- public members ----------------------------------------------------------- +void cpyrt::CPPOverload::Set(const std::string& name, + std::vector& methods) { + // Fill in the data of a freshly created method proxy. + fMethodInfo->fName = name; + fMethodInfo->fMethods.swap(methods); + fMethodInfo->fFlags &= ~CallContext::kIsSorted; + + // special case: all constructors are considered creators by default + if (name == "__init__") + fMethodInfo->fFlags |= + (CallContext::kIsCreator | CallContext::kIsConstructor); + + // special case, in heuristics mode also tag *Clone* methods as creators + if (CallContext::sMemoryPolicy == CallContext::kUseHeuristics && + name.find("Clone") != std::string::npos) + fMethodInfo->fFlags |= CallContext::kIsCreator; + + fVectorCall = (vectorcallfunc)mp_vectorcall; +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPOverload::AdoptMethod(PyCallable* pc) { + // Fill in the data of a freshly created method proxy. + fMethodInfo->fMethods.push_back(pc); + fMethodInfo->fFlags &= ~CallContext::kIsSorted; +} + +//---------------------------------------------------------------------------- +void cpyrt::CPPOverload::MergeOverload(CPPOverload* meth) { + if (!HasMethods()) // if fresh method being filled: also copy flags + fMethodInfo->fFlags = meth->fMethodInfo->fFlags; + fMethodInfo->fMethods.insert(fMethodInfo->fMethods.end(), + meth->fMethodInfo->fMethods.begin(), + meth->fMethodInfo->fMethods.end()); + fMethodInfo->fFlags &= ~CallContext::kIsSorted; + meth->fMethodInfo->fDispatchMap.clear(); + meth->fMethodInfo->fMethods.clear(); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CPPOverload::FindOverload(const std::string& signature, + int want_const) { + bool accept_any = signature == ":any:"; + CPPOverload* newmeth = nullptr; + + std::string sig1{"("}; + if (!accept_any) { + sig1.append(signature); + sig1.append(")"); + sig1.erase(std::remove(sig1.begin(), sig1.end(), ' '), std::end(sig1)); + } + + CPPOverload::Methods_t& methods = fMethodInfo->fMethods; + for (auto& meth : methods) { + bool found = accept_any; + if (!found) { + PyObject* pysig2 = meth->GetSignature(false); + std::string sig2(cpyrt_PyText_AsString(pysig2)); + sig2.erase(std::remove(sig2.begin(), sig2.end(), ' '), std::end(sig2)); + Py_DECREF(pysig2); + if (sig1 == sig2) + found = true; + + if (!found) { + pysig2 = meth->GetSignature(true); + std::string sig3(cpyrt_PyText_AsString(pysig2)); + sig3.erase(std::remove(sig3.begin(), sig3.end(), ' '), std::end(sig3)); + Py_DECREF(pysig2); + if (sig1 == sig3) + found = true; + } + } + + if (found && 0 <= want_const) { + bool isconst = meth->IsConst(); + if (!((want_const && isconst) || (!want_const && !isconst))) + found = false; + } + + if (found) { + if (!newmeth) { + newmeth = mp_new(nullptr, nullptr, nullptr); + CPPOverload::Methods_t vec; + vec.push_back(meth->Clone()); + newmeth->Set(fMethodInfo->fName, vec); + + if (fSelf) { + Py_INCREF(fSelf); + newmeth->fSelf = fSelf; + } + newmeth->fMethodInfo->fFlags = fMethodInfo->fFlags; + } else + newmeth->AdoptMethod(meth->Clone()); + + if (!accept_any) + return (PyObject*)newmeth; + } + } + + if (!newmeth) + PyErr_Format(PyExc_LookupError, "signature \"%s\" not found", + signature.c_str()); + + return (PyObject*)newmeth; +} + +PyObject* cpyrt::CPPOverload::FindOverload(PyObject* args_tuple, + int want_const) { + Py_ssize_t n = PyTuple_Size(args_tuple); + + CPPOverload::Methods_t& methods = fMethodInfo->fMethods; + + // This value is set based on the maximum penalty in + // interop::CompareMethodArgType + Py_ssize_t min_score = INT_MAX; + bool found = false; + size_t best_method = 0, method_index = 0; + + for (auto& meth : methods) { + if (0 <= want_const) { + bool isconst = meth->IsConst(); + if (!((want_const && isconst) || (!want_const && !isconst))) + continue; + } + + int score = meth->GetArgMatchScore(args_tuple); + + if (score < min_score) { + found = true; + min_score = score; + best_method = method_index; + } + + method_index++; + } + + if (!found) { + std::string sigargs("("); + + for (int i = 0; i < n; i++) { + PyObject* pItem = PyTuple_GetItem(args_tuple, i); + if (!cpyrt_PyText_Check(pItem)) { + PyErr_Format(PyExc_LookupError, + "argument types should be in string format"); + return (PyObject*)nullptr; + } + std::string arg_type(cpyrt_PyText_AsString(pItem)); + sigargs += arg_type + ", "; + } + sigargs += ")"; + + PyErr_Format(PyExc_LookupError, "signature with arguments \"%s\" not found", + sigargs.c_str()); + return (PyObject*)nullptr; + } + + CPPOverload* newmeth = mp_new(nullptr, nullptr, nullptr); + CPPOverload::Methods_t vec; + vec.push_back(methods[best_method]->Clone()); + newmeth->Set(fMethodInfo->fName, vec); + + if (fSelf) { + Py_INCREF(fSelf); + newmeth->fSelf = fSelf; + } + newmeth->fMethodInfo->fFlags = fMethodInfo->fFlags; + + return (PyObject*)newmeth; +} + +//---------------------------------------------------------------------------- +cpyrt::CPPOverload::MethodInfo_t::~MethodInfo_t() { + // Destructor (this object is reference counted). + for (Methods_t::iterator it = fMethods.begin(); it != fMethods.end(); ++it) { + delete *it; + } + fMethods.clear(); + delete fRefCount; + Py_XDECREF(fDoc); +} + +// TODO: something like PyMethod_Fini to clear up the free_list diff --git a/src/cpyrt/CPPOverload.h b/src/cpyrt/CPPOverload.h new file mode 100644 index 0000000..87019d2 --- /dev/null +++ b/src/cpyrt/CPPOverload.h @@ -0,0 +1,122 @@ +#ifndef CPYRT_CPPOVERLOAD_H +#define CPYRT_CPPOVERLOAD_H + +// Bindings +#include "PyCallable.h" + +// Standard +#include +#include +#include +#include + +namespace cppjit::cpyrt { + +// signature hashes are also used by TemplateProxy +inline uint64_t HashSignature(cpyrt_PyArgs_t args, size_t nargsf) { + // Build a hash from the types of the given python function arguments. + uint64_t hash = 0; + + Py_ssize_t nargs = cpyrt_PyArgs_GET_SIZE(args, nargsf); + for (Py_ssize_t i = 0; i < nargs; ++i) { + // TODO: hashing in the ref-count is for moves; resolve this together with + // the improved overloads for implicit conversions + PyObject* pyobj = cpyrt_PyArgs_GET_ITEM(args, i); + hash += (uint64_t)Py_TYPE(pyobj); +#if PY_VERSION_HEX >= 0x030e0000 + hash += + (uint64_t)(PyUnstable_Object_IsUniqueReferencedTemporary(pyobj) ? 1 + : 0); +#else + hash += (uint64_t)(Py_REFCNT(pyobj) == 1 ? 1 : 0); +#endif + hash += (hash << 10); + hash ^= (hash >> 6); + } + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash; +} + +class CPPOverload { +public: + typedef std::vector> DispatchMap_t; + typedef std::vector Methods_t; + + struct MethodInfo_t { + MethodInfo_t() : fDoc(nullptr), fFlags(CallContext::kNone) { + fRefCount = new int(1); + } + ~MethodInfo_t(); + + std::string fName; + CPPOverload::DispatchMap_t fDispatchMap; + CPPOverload::Methods_t fMethods; + PyObject* fDoc; + uint32_t fFlags; + + int* fRefCount; + + private: + MethodInfo_t(const MethodInfo_t&) = delete; + MethodInfo_t& operator=(const MethodInfo_t&) = delete; + }; + +public: + void Set(const std::string& name, std::vector& methods); + void AdoptMethod(PyCallable* pc); + void MergeOverload(CPPOverload* meth); + + const std::string& GetName() const { return fMethodInfo->fName; } + bool HasMethods() const { return !fMethodInfo->fMethods.empty(); } + + // find a method based on the provided signature + PyObject* FindOverload(const std::string& signature, int want_const = -1); + PyObject* FindOverload(PyObject* args_tuple, int want_const = -1); + +public: // public, as the python C-API works with C structs + PyObject_HEAD CPPInstance* + fSelf; // must be first (same layout as TemplateProxy) + MethodInfo_t* fMethodInfo; + uint32_t fFlags; + vectorcallfunc fVectorCall; + +private: + CPPOverload() = delete; +}; + +//- method proxy type and type verification ---------------------------------- +CPYRT_IMPORT PyTypeObject CPPOverload_Type; + +template inline bool CPPOverload_Check(T* object) { + return object && PyObject_TypeCheck(object, &CPPOverload_Type); +} + +template inline bool CPPOverload_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPOverload_Type; +} + +//- creation ----------------------------------------------------------------- +inline CPPOverload* CPPOverload_New(const std::string& name, + std::vector& methods) { + // Create and initialize a new method proxy from the overloads. + CPPOverload* pymeth = (CPPOverload*)CPPOverload_Type.tp_new(&CPPOverload_Type, + nullptr, nullptr); + pymeth->Set(name, methods); + return pymeth; +} + +inline CPPOverload* CPPOverload_New(const std::string& name, + PyCallable* method) { + // Create and initialize a new method proxy from the method. + std::vector p; + p.push_back(method); + return CPPOverload_New(name, p); +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPOVERLOAD_H diff --git a/src/cpyrt/CPPScope.cxx b/src/cpyrt/CPPScope.cxx new file mode 100644 index 0000000..fded0d5 --- /dev/null +++ b/src/cpyrt/CPPScope.cxx @@ -0,0 +1,730 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPDataMember.h" +#include "CPPEnum.h" +#include "CPPFunction.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "CustomPyTypes.h" +#include "Dispatcher.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include // for for_each +#include +#include +#include +#include + +namespace cppjit::cpyrt { + +extern PyTypeObject CPPInstance_Type; + +//- helpers ------------------------------------------------------------------ +static inline PyObject* +add_template(PyObject* pyclass, const std::string& name, + std::vector* overloads = nullptr) { + // If templated, the user-facing function must be the template proxy, but the + // specific lookup must be the current overload, if already found. + + const std::string& ncl = TypeManip::clean_type(name); + PyObject* pyncl = cpyrt_PyText_FromString(ncl.c_str()); + TemplateProxy* pytmpl = + (TemplateProxy*)PyType_Type.tp_getattro(pyclass, pyncl); + if (!pytmpl) { + PyErr_Clear(); + pytmpl = TemplateProxy_New(ncl, ncl, pyclass); + // cache the template on its clean name + PyType_Type.tp_setattro(pyclass, pyncl, (PyObject*)pytmpl); + Py_DECREF(pyncl); + } else if (!TemplateProxy_CheckExact((PyObject*)pytmpl)) { + Py_DECREF(pytmpl); + return nullptr; + } + + if (overloads) { + // adopt the new overloads + if (ncl != name) + for (auto clb : *overloads) + pytmpl->AdoptTemplate(clb); + else + for (auto clb : *overloads) + pytmpl->AdoptMethod(clb); + } + + // the caller expects a method matching the full name, thus is a + // specialization was requested, do not return the template yet + if (ncl == name) + return (PyObject*)pytmpl; + + Py_DECREF(pytmpl); + return nullptr; // so that caller caches the method on full name +} + +//= cpyrt type proxy construction/destruction ============================= +static PyObject* meta_alloc(PyTypeObject* meta, Py_ssize_t nitems) { + // pure memory allocation; object initialization is in pt_new + return PyType_Type.tp_alloc(meta, nitems); +} + +//---------------------------------------------------------------------------- +static void meta_dealloc(CPPScope* scope) { + if (scope->fFlags & CPPScope::kIsNamespace) { + if (scope->fImp.fUsing) { + for (auto pyobj : *scope->fImp.fUsing) + Py_DECREF(pyobj); + delete scope->fImp.fUsing; + scope->fImp.fUsing = nullptr; + } + } else if (!(scope->fFlags & CPPScope::kIsPython)) { + delete scope->fImp.fCppObjects; + scope->fImp.fCppObjects = nullptr; + } + delete scope->fOperators; + free(scope->fModuleName); + return PyType_Type.tp_dealloc((PyObject*)scope); +} + +//----------------------------------------------------------------------------- +static PyObject* meta_getcppname(CPPScope* scope, void*) { + if ((void*)scope == (void*)&CPPInstance_Type) + return cpyrt_PyText_FromString("CPPInstance_Type"); + return cpyrt_PyText_FromString( + interop::GetScopedFinalName(scope->fCppType).c_str()); +} + +//----------------------------------------------------------------------------- +static PyObject* meta_getmodule(CPPScope* scope, void*) { + if ((void*)scope == (void*)&CPPInstance_Type) + return cpyrt_PyText_FromString("cppjit.gbl"); + + if (scope->fModuleName) + return cpyrt_PyText_FromString(scope->fModuleName); + + // get C++ representation of outer scope + interop::TCppScope_t parent_scope = interop::GetParentScope(scope->fCppType); + if (parent_scope == interop::GetGlobalScope()) + return cpyrt_PyText_FromString(const_cast("cppjit.gbl")); + + // now peel scopes one by one, pulling in the python naming (which will + // simply recurse if not overridden in python) + PyObject* pymodule = nullptr; + PyObject* pyscope = cpyrt::GetScopeProxy(parent_scope); + if (pyscope) { + // get the module of our module + pymodule = PyObject_GetAttr(pyscope, PyStrings::gModule); + if (pymodule) { + // append name of our module + PyObject* pymodname = PyObject_GetAttr(pyscope, PyStrings::gName); + if (pymodname) { + cpyrt_PyText_AppendAndDel(&pymodule, cpyrt_PyText_FromString(".")); + cpyrt_PyText_AppendAndDel(&pymodule, pymodname); + } + } + Py_DECREF(pyscope); + } + + if (pymodule) + return pymodule; + PyErr_Clear(); + + // lookup through python failed, so simply cook up a '::' -> '.' replacement + std::string modname = interop::GetScopedFinalName(parent_scope); + TypeManip::cppscope_to_pyscope(modname); + return cpyrt_PyText_FromString(("cppjit.gbl." + modname).c_str()); +} + +//----------------------------------------------------------------------------- +static int meta_setmodule(CPPScope* scope, PyObject* value, void*) { + if ((void*)scope == (void*)&CPPInstance_Type) { + PyErr_SetString(PyExc_AttributeError, + "attribute \'__module__\' of 'cppjit.CPPScope\' objects is " + "not writable"); + return -1; + } + + const char* newname = cpyrt_PyText_AsStringChecked(value); + if (!value) + return -1; + + free(scope->fModuleName); + Py_ssize_t sz = cpyrt_PyText_GET_SIZE(value); + scope->fModuleName = (char*)malloc(sz + 1); + memcpy(scope->fModuleName, newname, sz + 1); + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* meta_repr(CPPScope* scope) { + // Specialized b/c type_repr expects __module__ to live in the dictionary, + // whereas it is a property (to save memory). + if ((void*)scope == (void*)&CPPInstance_Type) + return cpyrt_PyText_FromFormat( + const_cast(""), scope); + + if (scope->fFlags & (CPPScope::kIsMeta | CPPScope::kIsPython)) { + // either meta type or Python-side derived class: use default type printing + return PyType_Type.tp_repr((PyObject*)scope); + } + + // skip in case some Python-side derived meta class + if (!CPPScope_Check(scope) || !scope->fCppType) + return PyType_Type.tp_repr((PyObject*)scope); + + // printing of C++ classes + PyObject* modname = meta_getmodule(scope, nullptr); + std::string clName = interop::GetFinalName(scope->fCppType); + const char* kind = + (scope->fFlags & CPPScope::kIsNamespace) ? "namespace" : "class"; + + PyObject* repr = cpyrt_PyText_FromFormat("<%s %s.%s at %p>", kind, + cpyrt_PyText_AsString(modname), + clName.c_str(), scope); + + Py_DECREF(modname); + return repr; +} + +//= cpyrt type metaclass behavior ========================================= +static PyObject* pt_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) { + // Called when CPPScope acts as a metaclass; since type_new always resets + // tp_alloc, and since it does not call tp_init on types, the metaclass is + // being fixed up here, and the class is initialized here as well. + + // fixup of metaclass (left permanent, and in principle only called once b/c + // cppjit caches python classes) + subtype->tp_alloc = (allocfunc)meta_alloc; + subtype->tp_dealloc = (destructor)meta_dealloc; + + // creation of the python-side class; extend the size if this is a smart ptr + // + // A namespace can never be a smart pointer, so skip the check for namespaces. + // This matters beyond performance: GetSmartPtrInfo() resolves the scope name + // through a slow interpreter lookup that triggers autoloading of the library + // providing the scope, which we don't want to do unnecessarily. + interop::TCppScope_t raw; + interop::TCppMethod_t deref; + if (CPPScope_CheckExact(subtype) && + !interop::IsNamespace(((CPPScope*)subtype)->fCppType)) { + if (interop::GetSmartPtrInfo( + interop::GetScopedFinalName(((CPPScope*)subtype)->fCppType), &raw, + &deref)) + subtype->tp_basicsize = sizeof(CPPSmartClass); + } + + CPPScope* result = (CPPScope*)PyType_Type.tp_new(subtype, args, kwds); + if (!CPPScope_Check(result)) { + // either failed or custom user-side metaclass that can't be handled here + return nullptr; + } + + result->fFlags = CPPScope::kNone; + result->fOperators = nullptr; + result->fModuleName = nullptr; + + if (raw && deref) { + result->fFlags |= CPPScope::kIsSmart; + ((CPPSmartClass*)result)->fUnderlyingType = raw; + ((CPPSmartClass*)result)->fDereferencer = deref; + } + + // initialization of class (based on metatype) + if (!CPPScope_CheckExact(subtype) || + !strstr(subtype->tp_name, "_meta") /* convention */) { + // there has been a user meta class override in a derived class, so do + // the consistent thing, thus allowing user control over naming + result->fCppType = + interop::GetScope(cpyrt_PyText_AsString(PyTuple_GET_ITEM(args, 0))); + } else { + // coming here from cppjit or from sub-classing in python; take the + // C++ type from the meta class to make sure that the latter category + // has fCppType properly set (it inherits the meta class, but has an + // otherwise unknown (or wrong) C++ type) + result->fCppType = ((CPPScope*)subtype)->fCppType; + + // the following is not robust, but by design, C++ classes get their + // dictionaries filled after creation (chicken & egg problem as they + // can return themselves in methods), whereas a derived Python class + // with method overrides will have a non-empty dictionary (even if it + // has no methods, it will at least have a module name) + if (3 <= PyTuple_GET_SIZE(args)) { + PyObject* dct = PyTuple_GET_ITEM(args, 2); + Py_ssize_t sz = PyDict_Size(dct); + if (0 < sz && !interop::IsNamespace(result->fCppType)) { + result->fFlags |= CPPScope::kIsPython; + if (1 < PyTuple_GET_SIZE(PyTuple_GET_ITEM(args, 1))) + result->fFlags |= CPPScope::kIsMultiCross; + std::ostringstream errmsg; + if (!InsertDispatcher(result, PyTuple_GET_ITEM(args, 1), dct, errmsg)) { + PyErr_Format(PyExc_TypeError, + "no python-side overrides supported (%s)", + errmsg.str().c_str()); + return nullptr; + } else { + // the direct base can be useful for some templates, such as + // shared_ptrs, so make it accessible (the __cpp_cross__ data member + // also signals that this is a cross-inheritance class) + PyObject* bname = cpyrt_PyText_FromString( + interop::GetBaseName(result->fCppType, 0).c_str()); + if (PyObject_SetAttrString((PyObject*)result, "__cpp_cross__", + bname) == -1) + PyErr_Clear(); + Py_DECREF(bname); + } + } else if (sz == (Py_ssize_t)-1) + PyErr_Clear(); + } + } + + // if the user messed with the metaclass, then we may not have a C++ type, + // simply return here before more damage gets done + if (!result->fCppType) + return (PyObject*)result; + + // maps for using namespaces and tracking objects + if (!interop::IsNamespace(result->fCppType)) { + static interop::TCppScope_t exc_type = + interop::GetScope("exception", interop::GetScope("std")); + if (interop::IsSubclass(result->fCppType, exc_type)) + result->fFlags |= CPPScope::kIsException; + if (!(result->fFlags & CPPScope::kIsPython)) + result->fImp.fCppObjects = new CppToPyMap_t; + else { + // special case: the C++ objects should be stored with the associated C++, + // not Python, type + CPPClass* kls = (CPPClass*)GetScopeProxy(result->fCppType); + if (kls) { + result->fImp.fCppObjects = kls->fImp.fCppObjects; + Py_DECREF(kls); + } else + result->fImp.fCppObjects = nullptr; + } + } else { + result->fImp.fUsing = nullptr; + result->fFlags |= CPPScope::kIsNamespace; + } + + if (PyErr_Occurred()) { + Py_DECREF((PyObject*)result); + return nullptr; + } + + return (PyObject*)result; +} + +//---------------------------------------------------------------------------- +static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) { + // normal type-based lookup + PyObject* attr = PyType_Type.tp_getattro(pyclass, pyname); + if (pyclass == (PyObject*)&CPPInstance_Type) + return attr; + + PyObject* possibly_shadowed = nullptr; + if (attr) { + if (CPPScope_Check(attr) && CPPScope_Check(pyclass) && + !(((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace)) { + // TODO: the goal here is to prevent typedefs that are shadowed in + // subclasses to be found as from the base class. The better approach + // would be to find all typedefs at class creation and insert placeholders + // to flag here. The current typedef loop in gInterpreter won't do, + // however. + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + if (dct) { + PyObject* attr_from_dict = PyObject_GetItem(dct, pyname); + Py_DECREF(dct); + if (attr_from_dict) { + Py_DECREF(attr); + return attr_from_dict; + } + possibly_shadowed = attr; + attr = nullptr; + } + PyErr_Clear(); + } else if (CPPScope_Check(attr) && CPPScope_Check(pyclass) && + ((CPPScope*)attr)->fFlags & CPPScope::kIsException) { + return CreateExcScopeProxy(attr, pyname, pyclass); + } else + return attr; + } + + if (!cpyrt_PyText_CheckExact(pyname) || !CPPScope_Check(pyclass)) + return possibly_shadowed; + + // filter for python specials + std::string name = cpyrt_PyText_AsString(pyname); + if (name.size() >= 5 && name.compare(0, 2, "__") == 0 && + name.compare(name.size() - 2, name.size(), "__") == 0) + return possibly_shadowed; + + // more elaborate search in case of failure (eg. for inner classes on demand) + std::vector errors; + Utility::FetchError(errors); + attr = CreateScopeProxy(name, pyclass); + + if (CPPScope_Check(attr) && + (((CPPScope*)attr)->fFlags & CPPScope::kIsException)) { + // Instead of the CPPScope, return a fresh exception class derived from + // CPPExcInstance. + return CreateExcScopeProxy(attr, pyname, pyclass); + } + + bool templated_functions_checked = false; + CPPScope* klass = ((CPPScope*)pyclass); + if (!attr) { + Utility::FetchError(errors); + interop::TCppScope_t scope = klass->fCppType; + + // namespaces may have seen updates in their list of global functions, which + // are available as "methods" even though they're not really that + if ((klass->fFlags & CPPScope::kIsNamespace) || + scope == interop::GetGlobalScope()) { + // tickle lazy lookup of functions + const std::vector methods = + interop::GetMethodsFromName(scope, name); + if (!methods.empty()) { + // function exists, now collect overloads + std::vector overloads; + for (auto method : methods) { + overloads.push_back(new CPPFunction(scope, method)); + } + + // Note: can't re-use Utility::AddClass here, as there's the risk of + // a recursive call. Simply add method directly, as we're guaranteed + // that it doesn't exist yet. + if (interop::ExistsMethodTemplate(scope, name)) + attr = add_template(pyclass, name, &overloads); + else + attr = (PyObject*)CPPOverload_New(name, overloads); + templated_functions_checked = true; + } + } + + if (!attr) { + interop::TCppScope_t lookup_result = interop::GetNamed(name, scope); + if (interop::IsVariable(lookup_result) || + interop::IsEnumConstant(lookup_result)) { + attr = (PyObject*)CPPDataMember_New(scope, lookup_result); + } else if (interop::IsTypedefed(lookup_result)) { + interop::TCppType_t resolved_type = + interop::ResolveType(interop::GetTypeFromScope(lookup_result)); + const std::string& cpd = + TypeManip::compound(interop::GetTypeAsString(resolved_type)); + if (cpd == "*") { + interop::TCppScope_t tcl = + interop::GetScopeFromType(interop::ResolveType(resolved_type)); + if (tcl) { + typedefpointertoclassobject* tpc = PyObject_New( + typedefpointertoclassobject, &TypedefPointerToClass_Type); + tpc->fCppType = tcl; + attr = (PyObject*)tpc; + } + } + } + } + + // function templates that have not been instantiated (namespaces _may_ have + // already been taken care of, by their general function lookup above) + if (!attr && !templated_functions_checked) { + if (interop::ExistsMethodTemplate(scope, name)) + attr = add_template(pyclass, name); + else { + // for completeness in error reporting + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ template", + name.c_str()); + Utility::FetchError(errors); + } + } + + // enums types requested as type (rather than the constants) + if (!attr) { + interop::TCppScope_t enumerator = + interop::GetUnderlyingScope(interop::GetNamed(name, scope)); + if (interop::IsEnumScope(enumerator)) { + // enum types (incl. named and class enums) + attr = (PyObject*)CPPEnum_New(name, enumerator); + } else { + // for completeness in error reporting + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ enum", + name.c_str()); + Utility::FetchError(errors); + } + } + + if (attr) { + // cache the result + if (CPPDataMember_Check(attr)) { + PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pyname, attr); + + Py_DECREF(attr); + // The call below goes through "dm_get" + attr = PyType_Type.tp_getattro(pyclass, pyname); + if (!attr && PyErr_Occurred()) + Utility::FetchError(errors); + } else + PyType_Type.tp_setattro(pyclass, pyname, attr); + + } else { + Utility::FetchError(errors); + } + } + + if (!attr && (klass->fFlags & CPPScope::kIsNamespace)) { + // refresh using list as necessary + const std::vector& uv = + interop::GetUsingNamespaces(klass->fCppType); + if (!klass->fImp.fUsing || uv.size() != klass->fImp.fUsing->size()) { + if (klass->fImp.fUsing) { + for (auto pyref : *klass->fImp.fUsing) + Py_DECREF(pyref); + klass->fImp.fUsing->clear(); + } else + klass->fImp.fUsing = new std::vector; + + // reload and reset weak refs + for (auto uid : uv) { + std::string uname = interop::GetScopedFinalName(uid); + PyObject* pyuscope = CreateScopeProxy(uname); + if (pyuscope) { + klass->fImp.fUsing->push_back(PyWeakref_NewRef(pyuscope, nullptr)); + // the namespace may not otherwise be held, so tie the lifetimes + PyObject* llname = + cpyrt_PyText_FromString(("__lifeline_" + uname).c_str()); + PyType_Type.tp_setattro(pyclass, llname, pyuscope); + Py_DECREF(llname); + Py_DECREF(pyuscope); + } else { + PyErr_Clear(); + } + } + } + + // try all outstanding using namespaces in turn to find the attribute (will + // cache locally later; TODO: doing so may cause pathological cases) + for (auto pyref : *klass->fImp.fUsing) { + PyObject* pyuscope = cpyrt_GetWeakRef(pyref); + if (pyuscope) { + attr = PyObject_GetAttr(pyuscope, pyname); + if (!attr) + PyErr_Clear(); + Py_DECREF(pyuscope); + } + if (attr) + break; + } + } + + // if the attribute was not found but could possibly have been shadowed, + // insert it into the dict now, to short-circuit future lookups (TODO: this is + // part of the workaround described above of which the true solution is to + // loop over all typedefs at creation time for the class) + if (possibly_shadowed) { + if (attr) { + Py_DECREF(possibly_shadowed); + } else { + attr = possibly_shadowed; + PyType_Type.tp_setattro(pyclass, pyname, attr); + } + } + + if (attr) { + PyErr_Clear(); + } else { + // not found: prepare a full error report + PyObject* topmsg = nullptr; + PyObject *pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + PyObject* sklass = PyObject_Str(pyclass); + PyErr_Restore(pytype, pyvalue, pytrace); + if (sklass) { + topmsg = cpyrt_PyText_FromFormat( + "%s has no attribute \'%s\'. Full details:", + cpyrt_PyText_AsString(sklass), cpyrt_PyText_AsString(pyname)); + Py_DECREF(sklass); + } else { + topmsg = + cpyrt_PyText_FromFormat("no such attribute \'%s\'. Full details:", + cpyrt_PyText_AsString(pyname)); + } + SetDetailedException(std::move(errors), topmsg /* steals */, + PyExc_AttributeError /* default error */); + } + + return attr; +} + +//---------------------------------------------------------------------------- +static int meta_setattro(PyObject* pyclass, PyObject* pyname, PyObject* pyval) { + // Global data and static data in namespaces is found lazily, thus if the + // first use is setting of the global data by the user, it will not be + // reflected on the C++ side, b/c there is no descriptor yet. This triggers + // the creation for for such data as necessary. The many checks to narrow down + // the specific case are needed to prevent unnecessary lookups and recursion. + // skip if the given pyval is a descriptor already, or an unassignable class + if (((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace && + !cpyrt::CPPDataMember_Check(pyval) && !cpyrt::CPPScope_Check(pyval)) { + std::string name = cpyrt_PyText_AsString(pyname); + if (interop::GetNamed(name, ((CPPScope*)pyclass)->fCppType)) + meta_getattro(pyclass, pyname); // triggers creation + } + + return PyType_Type.tp_setattro(pyclass, pyname, pyval); +} + +//---------------------------------------------------------------------------- +static PyObject* meta_reflex(CPPScope* klass, PyObject* args) { + // Provide the requested reflection information. + interop::Reflex::RequestId_t request = -1; + interop::Reflex::FormatId_t format = interop::Reflex::OPTIMAL; + if (!PyArg_ParseTuple(args, const_cast("i|i:__cpp_reflex__"), &request, + &format)) + return nullptr; + + switch (request) { + case interop::Reflex::IS_NAMESPACE: + if (klass->fFlags & CPPScope::kIsNamespace) + Py_RETURN_TRUE; + Py_RETURN_FALSE; + break; + case interop::Reflex::IS_AGGREGATE: + // this is not the strict C++ definition of aggregates, but is closer to + // what is needed for Numba and C calling conventions (TODO: probably have + // to check for all public data types, too, and maybe for no padding?) + if (interop::IsAggregate(klass->fCppType) || + !interop::HasVirtualDestructor(klass->fCppType)) + Py_RETURN_TRUE; + Py_RETURN_FALSE; + break; + } + + PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", + request, format); + return nullptr; +} + +//---------------------------------------------------------------------------- +// p2.7 does not have a __dir__ in object, and object.__dir__ in p3 does not +// quite what I'd expected of it, so the following pulls in the internal code +#include "PyObjectDir27.inc" + +static PyObject* meta_dir(CPPScope* klass) { + // Collect a list of everything (currently) available in the namespace. + // The backend can filter by returning empty strings. Special care is + // taken for functions, which need not be unique (overloading). + using namespace cppjit::interop; + + if ((void*)klass == (void*)&CPPInstance_Type) + return PyList_New(0); + + if (!cpyrt::CPPScope_Check((PyObject*)klass)) { + PyErr_SetString(PyExc_TypeError, "C++ proxy scope expected"); + return nullptr; + } + + PyObject* dirlist = _generic_dir((PyObject*)klass); + if (!(klass->fFlags & CPPScope::kIsNamespace)) + return dirlist; + + std::set cppnames; + interop::GetAllCppNames(klass->fCppType, cppnames); + + // cleanup names + std::set dir_cppnames; + for (const std::string& name : cppnames) { + if (name.find("__", 0, 2) != std::string::npos || + name.find("<") != std::string::npos || + name.find("operator", 0, 8) != std::string::npos) + continue; + dir_cppnames.insert(name); + } + + // get rid of duplicates + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(dirlist); ++i) + dir_cppnames.insert(cpyrt_PyText_AsString(PyList_GET_ITEM(dirlist, i))); + + Py_DECREF(dirlist); + dirlist = PyList_New(dir_cppnames.size()); + + // copy total onto python list + Py_ssize_t i = 0; + for (const auto& name : dir_cppnames) { + PyList_SET_ITEM(dirlist, i++, cpyrt_PyText_FromString(name.c_str())); + } + return dirlist; +} + +//----------------------------------------------------------------------------- +static PyMethodDef meta_methods[] = { + {(char*)"__cpp_reflex__", (PyCFunction)meta_reflex, METH_VARARGS, + (char*)"C++ datamember reflection information"}, + {(char*)"__dir__", (PyCFunction)meta_dir, METH_NOARGS, nullptr}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//----------------------------------------------------------------------------- +static PyGetSetDef meta_getset[] = { + {(char*)"__cpp_name__", (getter)meta_getcppname, nullptr, nullptr, nullptr}, + {(char*)"__module__", (getter)meta_getmodule, (setter)meta_setmodule, + nullptr, nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +//= cpyrt object proxy type type ========================================== +PyTypeObject CPPScope_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0)(char*) "cppjit.CPPScope", // tp_name + sizeof(cpyrt::CPPScope), // tp_basicsize + 0, // tp_itemsize + 0, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + (reprfunc)meta_repr, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + 0, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + 0, // tp_str + (getattrofunc)meta_getattro, // tp_getattro + (setattrofunc)meta_setattro, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_TYPE_SUBCLASS, // tp_flags + (char*)"cpyrt metatype (internal)", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + meta_methods, // tp_methods + 0, // tp_members + meta_getset, // tp_getset + &PyType_Type, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)pt_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 + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/src/cpyrt/CPPScope.h b/src/cpyrt/CPPScope.h new file mode 100644 index 0000000..edf417b --- /dev/null +++ b/src/cpyrt/CPPScope.h @@ -0,0 +1,119 @@ +#ifndef CPYRT_CPPSCOPE_H +#define CPYRT_CPPSCOPE_H + +#include "Python.h" +#include "cppjit_interop.h" +#include + +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION == 2 + +// In p2.2, PyHeapTypeObject is not yet part of the interface +#include "structmember.h" + +typedef struct { + PyTypeObject type; + PyNumberMethods as_number; + PySequenceMethods as_sequence; + PyMappingMethods as_mapping; + PyBufferProcs as_buffer; + PyObject *name, *slots; + PyMemberDef members[1]; +} PyHeapTypeObject; + +#endif + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +/** Type object to hold class reference (this is only semantically a + presentation of CPPScope instances, not in a C++ sense) + @author WLAV + @date 07/06/2017 + @version 2.0 + */ + +typedef std::unordered_map + CppToPyMap_t; +namespace Utility { +struct PyOperators; +} + +class CPPScope { +public: + enum EFlags { + kNone = 0x0, + kIsMeta = 0x0001, + kIsNamespace = 0x0002, + kIsException = 0x0004, + kIsSmart = 0x0008, + kIsPython = 0x0010, + kIsMultiCross = 0x0020, + kIsInComplete = 0x0040, + kActiveImplicitCall = 0x0080, + kNoOSInsertion = 0x0100, + kGblOSInsertion = 0x0200, + kNoPrettyPrint = 0x0400 + }; + +public: + PyHeapTypeObject fType; + cppjit::interop::TCppScope_t fCppType; + uint32_t fFlags; + union { + CppToPyMap_t* fCppObjects; // classes only + std::vector* fUsing; // namespaces only + } fImp; + Utility::PyOperators* fOperators; + char* fModuleName; + +private: + CPPScope() = delete; +}; + +typedef CPPScope CPPClass; + +class CPPSmartClass : public CPPClass { +public: + cppjit::interop::TCppScope_t fUnderlyingType; + cppjit::interop::TCppMethod_t fDereferencer; +}; + +//- metatype type and type verification -------------------------------------- +extern PyTypeObject CPPScope_Type; + +template inline bool CPPScope_Check(T* object) { + // Short-circuit the type check by checking tp_new which all generated + // subclasses of CPPScope inherit. + return object && (Py_TYPE(object)->tp_new == CPPScope_Type.tp_new || + PyObject_TypeCheck(object, &CPPScope_Type)); +} + +template inline bool CPPScope_CheckExact(T* object) { + return object && Py_TYPE(object) == &CPPScope_Type; +} + +//- creation ----------------------------------------------------------------- +inline CPPScope* CPPScopeMeta_New(cppjit::interop::TCppScope_t klass, + PyObject* args) { + // Create and initialize a new scope meta class + CPPScope* pymeta = + (CPPScope*)PyType_Type.tp_new(&CPPScope_Type, args, nullptr); + if (!pymeta) + return pymeta; + + // set the klass id, for instances and Python-side derived classes to pick up + pymeta->fCppType = klass; + pymeta->fFlags = CPPScope::kIsMeta; + pymeta->fImp.fCppObjects = nullptr; + pymeta->fOperators = nullptr; + pymeta->fModuleName = nullptr; + + return pymeta; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CPPSCOPE_H diff --git a/src/cpyrt/CallContext.cxx b/src/cpyrt/CallContext.cxx new file mode 100644 index 0000000..f8d077e --- /dev/null +++ b/src/cpyrt/CallContext.cxx @@ -0,0 +1,61 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CallContext.h" + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { + +CallContext::ECallFlags CallContext::sMemoryPolicy = CallContext::kUseStrict; +// this is just a data holder for linking; actual value is set in +// cpyrtModule.cxx +CallContext::ECallFlags CallContext::sSignalPolicy = CallContext::kNone; + +} // namespace cppjit::cpyrt + +//----------------------------------------------------------------------------- +void cpyrt::CallContext::AddTemporary(PyObject* pyobj) { + if (pyobj) { + if (!fTemps) + fTemps = new Temporary{pyobj, nullptr}; + else { + Temporary* tmp = fTemps; + while (tmp->fNext) + tmp = tmp->fNext; + tmp->fNext = new Temporary{pyobj, nullptr}; + } + } +} + +//----------------------------------------------------------------------------- +void cpyrt::CallContext::Cleanup() { + Temporary* tmp = fTemps; + while (tmp) { + Py_DECREF(tmp->fPyObject); + Temporary* tmp2 = tmp->fNext; + delete tmp; + tmp = tmp2; + } + fTemps = nullptr; +} + +//----------------------------------------------------------------------------- +bool cpyrt::CallContext::SetMemoryPolicy(ECallFlags e) { + // Set the global memory policy, which affects object ownership when objects + // are passed as function arguments. + if (kUseHeuristics == e || e == kUseStrict) { + sMemoryPolicy = e; + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +bool cpyrt::CallContext::SetGlobalSignalPolicy(bool setProtected) { + // Set the global signal policy, which determines whether a jmp address + // should be saved to return to after a C++ segfault. + bool old = sSignalPolicy == kProtected; + sSignalPolicy = setProtected ? kProtected : kNone; + return old; +} diff --git a/src/cpyrt/CallContext.h b/src/cpyrt/CallContext.h new file mode 100644 index 0000000..4509983 --- /dev/null +++ b/src/cpyrt/CallContext.h @@ -0,0 +1,198 @@ +#ifndef CPYRT_CALLCONTEXT_H +#define CPYRT_CALLCONTEXT_H + +#include "Python.h" +#include "cppjit_interop.h" + +// Standard +#include +#include + +#include + +namespace cppjit::cpyrt { + +// small number that allows use of stack for argument passing +const int SMALL_ARGS_N = 8; + +// convention to pass flag for direct calls (similar to Python's vector calls) +#define DIRECT_CALL ((size_t)1 << (8 * sizeof(size_t) - 1)) + +#ifndef CPYRT_PARAMETER +#define CPYRT_PARAMETER +// general place holder for function parameters +struct Parameter { + union Value { + bool fBool; + int8_t fInt8; + uint8_t fUInt8; + short fShort; + unsigned short fUShort; + int fInt; + unsigned int fUInt; + long fLong; + intptr_t fIntPtr; + unsigned long fULong; + long long fLLong; + unsigned long long fULLong; + int64_t fInt64; + uint64_t fUInt64; + float fFloat; + double fDouble; + long double fLDouble; + void* fVoidp; + } fValue; + void* fRef; + char fTypeCode; +}; +#endif // CPYRT_PARAMETER + +// extra call information +struct CallContext { + CallContext() + : fCurScope(nullptr), fPyContext(nullptr), fFlags(0), fArgsVec(nullptr), + fNArgs(0), fTemps(nullptr) {} + CallContext(const CallContext&) = delete; + CallContext& operator=(const CallContext&) = delete; + ~CallContext() { + if (fTemps) + Cleanup(); + delete fArgsVec; + } + + enum ECallFlags { + kNone = 0x000000, + kIsSorted = 0x000001, // if method overload priority determined + kIsCreator = 0x000002, // if method creates python-owned objects + kIsConstructor = 0x000004, // if method is a C++ constructor + kHaveImplicit = 0x000008, // indicate that implicit converters are available + kAllowImplicit = 0x000010, // indicate that implicit conversions are allowed + kNoImplicit = 0x000020, // disable implicit to prevent recursion + kCallDirect = 0x000040, // call wrapped method directly, no inheritance + kFromDescr = 0x000080, // initiated from a descriptor + kUseHeuristics = 0x000100, // if method applies heuristics memory policy + kImplicitSmartPtrConversion = + 0x000200, // enable implicit conversion to smart pointers + kReleaseGIL = 0x000400, // if method should release the GIL + kSetLifeLine = 0x000800, // if return value is part of 'this' + kNeverLifeLine = 0x001000, // if the return value is never part of 'this' + kPyException = 0x002000, // Python exception during method execution + kCppException = 0x004000, // C++ exception during method execution + kProtected = 0x008000, // if method should return on signals + kUseFFI = 0x010000, // not implemented + kIsPseudoFunc = 0x020000, // internal, used for introspection + kUseStrict = 0x040000, // if method applies strict memory policy + }; + + // memory handling + static ECallFlags sMemoryPolicy; + static bool SetMemoryPolicy(ECallFlags e); + + void AddTemporary(PyObject* pyobj); + void Cleanup(); + + // signal safety + static ECallFlags sSignalPolicy; + static bool SetGlobalSignalPolicy(bool setProtected); + + Parameter* GetArgs(size_t sz) { + if (sz != (size_t)-1) + fNArgs = sz; + if (fNArgs <= SMALL_ARGS_N) + return fArgs; + if (!fArgsVec) + fArgsVec = new std::vector(); + fArgsVec->resize(fNArgs); + return fArgsVec->data(); + } + + Parameter* GetArgs() { + if (fNArgs <= SMALL_ARGS_N) + return fArgs; + return fArgsVec->data(); + } + + size_t GetSize() { return fNArgs; } + size_t GetEncodedSize() { + return fNArgs | ((fFlags & kCallDirect) ? DIRECT_CALL : 0); + } + +public: + // info/status + cppjit::interop::TCppScope_t fCurScope; + PyObject* fPyContext; + uint32_t fFlags; + +private: + struct Temporary { + PyObject* fPyObject; + Temporary* fNext; + }; + + // payload + Parameter fArgs[SMALL_ARGS_N]; + std::vector* fArgsVec; + size_t fNArgs; + Temporary* fTemps; +}; + +inline bool IsSorted(uint64_t flags) { return flags & CallContext::kIsSorted; } + +inline bool IsCreator(uint64_t flags) { + return flags & CallContext::kIsCreator; +} + +inline bool IsConstructor(uint64_t flags) { + return flags & CallContext::kIsConstructor; +} + +inline bool HaveImplicit(CallContext* ctxt) { + return ctxt ? (!(ctxt->fFlags & CallContext::kNoImplicit) && + (ctxt->fFlags & CallContext::kHaveImplicit)) + : false; +} + +inline bool AllowImplicit(CallContext* ctxt) { + return ctxt ? (!(ctxt->fFlags & CallContext::kNoImplicit) && + (ctxt->fFlags & CallContext::kAllowImplicit)) + : false; +} + +inline bool NoImplicit(CallContext* ctxt) { + return ctxt ? (ctxt->fFlags & CallContext::kNoImplicit) : false; +} + +inline bool ReleasesGIL(CallContext* ctxt) { + return ctxt ? (ctxt->fFlags & CallContext::kReleaseGIL) : false; +} + +inline bool UseStrictOwnership(CallContext* ctxt) { + if (ctxt && (ctxt->fFlags & CallContext::kUseStrict)) + return true; + if (ctxt && (ctxt->fFlags & CallContext::kUseHeuristics)) + return false; + + return CallContext::sMemoryPolicy == CallContext::kUseStrict; +} + +template class CallContextRAII { +public: + CallContextRAII(CallContext* ctxt) : fCtxt(ctxt) { + fPrior = fCtxt->fFlags & F; + fCtxt->fFlags |= F; + } + CallContextRAII(const CallContextRAII&) = delete; + CallContextRAII& operator=(const CallContextRAII&) = delete; + ~CallContextRAII() { + if (!fPrior) + fCtxt->fFlags &= ~F; + } + +private: + CallContext* fCtxt; + bool fPrior; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CALLCONTEXT_H diff --git a/src/cpyrt/CommonDefs.h b/src/cpyrt/CommonDefs.h new file mode 100644 index 0000000..0db7320 --- /dev/null +++ b/src/cpyrt/CommonDefs.h @@ -0,0 +1,34 @@ +#ifndef CPYRT_COMMONDEFS_H +#define CPYRT_COMMONDEFS_H + +// export macros for our own API +// import/export (after precommondefs.h from PyPy) +#ifdef _MSC_VER +// Windows requires symbols to be explicitly exported +#define CPYRT_EXPORT extern __declspec(dllexport) +#define CPYRT_IMPORT extern __declspec(dllimport) +#define CPYRT_CLASS_EXPORT __declspec(dllexport) + +// CPYRT_EXTERN is dual use in the public API +#ifndef CPYRT_INTERNAL +#define CPYRT_EXTERN extern __declspec(dllexport) +#define CPYRT_CLASS_EXTERN __declspec(dllexport) +#else +#define CPYRT_EXTERN extern __declspec(dllimport) +#define CPYRT_CLASS_EXTERN __declspec(dllimport) +#endif + +#define CPYRT_STATIC + +#else +// Linux, Mac, etc. +#define CPYRT_EXPORT extern +#define CPYRT_IMPORT extern +#define CPYRT_CLASS_EXPORT +#define CPYRT_EXTERN extern +#define CPYRT_CLASS_EXTERN +#define CPYRT_STATIC static + +#endif + +#endif // !CPYRT_COMMONDEFS_H diff --git a/src/cpyrt/Converters.cxx b/src/cpyrt/Converters.cxx new file mode 100644 index 0000000..31ab848 --- /dev/null +++ b/src/cpyrt/Converters.cxx @@ -0,0 +1,4291 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPExcInstance.h" +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CallContext.h" +#include "CustomPyTypes.h" +#include "DeclareConverters.h" +#include "LowLevelViews.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "TupleOfInstances.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include // for wstring_convert +#include +#include +#include // for ptrdiff_t +#include +#include +#include +#if __cplusplus >= 202002L +#include +#endif + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { + +// factories +typedef std::unordered_map ConvFactories_t; +static ConvFactories_t gConvFactories; + +// special objects +extern PyObject* gNullPtrObject; +extern PyObject* gDefaultObject; + +// regular expression for matching function pointer +static std::regex s_fnptr("\\((\\w*:*)*\\*&*\\)"); +} // namespace cppjit::cpyrt + +// Define our own PyUnstable_Object_IsUniqueReferencedTemporary function if the +// Python version is lower than 3.14, the version where that function got +// introduced. +#if PY_VERSION_HEX < 0x030e0000 +const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; +inline bool PyUnstable_Object_IsUniqueReferencedTemporary(PyObject* pyobject) { + return Py_REFCNT(pyobject) <= MOVE_REFCOUNT_CUTOFF; +} +#endif + +//- pretend-ctypes helpers --------------------------------------------------- +struct cpyrt_tagCDataObject { // non-public (but stable) + PyObject_HEAD char* b_ptr; + int b_needsfree; +}; + +struct cpyrt_tagPyCArgObject { // not public (but stable; note that older + PyObject_HEAD // Pythons protect 'D' with HAVE_LONG_LONG) + void* pffi_type; + char tag; + union { // for convenience, kept only relevant vals + char c; + char b; + short h; + int i; + long l; + long long q; + long double g; + void* p; +#if PY_VERSION_HEX >= 0x030e0000 + double D[2]; + float F[2]; + long double G[2]; +#endif + } value; + PyObject* obj; +#if PY_VERSION_HEX >= 0x030e0000 + Py_ssize_t size; +#endif +}; + +// indices of ctypes types into the array caches (note that c_complex and +// c_fcomplex do not exist as types in ctypes) +#define ct_c_bool 0 +#define ct_c_char 1 +#define ct_c_shar 1 +#define ct_c_wchar 2 +#define ct_c_byte 3 +#define ct_c_int8 3 +#define ct_c_ubyte 4 +#define ct_c_uchar 4 +#define ct_c_uint8 4 +#define ct_c_short 5 +#define ct_c_ushort 6 +#define ct_c_uint16 7 +#define ct_c_int 8 +#define ct_c_uint 9 +#define ct_c_uint32 10 +#define ct_c_long 11 +#define ct_c_ulong 12 +#define ct_c_longlong 13 +#define ct_c_ulonglong 14 +#define ct_c_float 15 +#define ct_c_double 16 +#define ct_c_longdouble 17 +#define ct_c_char_p 18 +#define ct_c_wchar_p 19 +#define ct_c_void_p 20 +#define ct_c_fcomplex 21 +#define ct_c_complex 22 +#define ct_c_pointer 23 +#define ct_c_funcptr 24 +#define NTYPES 25 + +static std::array gCTypesNames = { + "c_bool", "c_char", "c_wchar", "c_byte", "c_ubyte", + "c_short", "c_ushort", "c_uint16", "c_int", "c_uint", + "c_uint32", "c_long", "c_ulong", "c_longlong", "c_ulonglong", + "c_float", "c_double", "c_longdouble", "c_char_p", "c_wchar_p", + "c_void_p", "c_fcomplex", "c_complex", "_Pointer", "_CFuncPtr"}; +static std::array gCTypesTypes; +static std::array gCTypesPtrTypes; + +// Both GetCTypesType and GetCTypesPtrType, rely on the ctypes module itself +// caching the types (thus also making them unique), so no ref-count is needed. +// Further, by keeping a ref-count on the module, it won't be off-loaded until +// the 2nd cleanup cycle. +static PyTypeObject* GetCTypesType(int nidx) { + static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept + if (!ctmod) { + PyErr_Clear(); + return nullptr; + } + PyTypeObject* ct_t = gCTypesTypes[nidx]; + if (!ct_t) { + ct_t = (PyTypeObject*)PyObject_GetAttrString(ctmod, gCTypesNames[nidx]); + if (!ct_t) + PyErr_Clear(); + else { + gCTypesTypes[nidx] = ct_t; + Py_DECREF(ct_t); + } + } + return ct_t; +} + +static PyTypeObject* GetCTypesPtrType(int nidx) { + static PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept + if (!ctmod) { + PyErr_Clear(); + return nullptr; + } + PyTypeObject* cpt_t = gCTypesPtrTypes[nidx]; + if (!cpt_t) { + if (strcmp(gCTypesNames[nidx], "c_char") == 0) { + cpt_t = (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_char_p"); + } else { + PyObject* ct_t = (PyObject*)GetCTypesType(nidx); + if (ct_t) { + PyObject* ptrcreat = PyObject_GetAttrString(ctmod, "POINTER"); + cpt_t = + (PyTypeObject*)PyObject_CallFunctionObjArgs(ptrcreat, ct_t, NULL); + Py_DECREF(ptrcreat); + } + } + if (cpt_t) { + gCTypesPtrTypes[nidx] = cpt_t; + Py_DECREF(cpt_t); + } + } + return cpt_t; +} + +static bool IsPyCArgObject(PyObject* pyobject) { + static PyTypeObject* pycarg_type = nullptr; + if (!pycarg_type) { + PyObject* ctmod = PyImport_ImportModule("ctypes"); + if (!ctmod) + PyErr_Clear(); + else { + PyTypeObject* ct_t = + (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_int"); + PyObject* cobj = ct_t->tp_new(ct_t, nullptr, nullptr); + PyObject* byref = PyObject_GetAttrString(ctmod, "byref"); + PyObject* pyptr = PyObject_CallFunctionObjArgs(byref, cobj, NULL); + Py_DECREF(byref); + Py_DECREF(cobj); + Py_DECREF(ct_t); + pycarg_type = Py_TYPE(pyptr); // static, no ref-count needed + Py_DECREF(pyptr); + Py_DECREF(ctmod); + } + } + return Py_TYPE(pyobject) == pycarg_type; +} + +#if PY_VERSION_HEX < 0x30d0000 +static bool IsCTypesArrayOrPointer(PyObject* pyobject) { + static PyTypeObject* cstgdict_type = nullptr; + if (!cstgdict_type) { + // get any pointer type to initialize the extended dictionary type + PyTypeObject* ct_int = GetCTypesType(ct_c_int); + if (ct_int && ct_int->tp_dict) { + cstgdict_type = Py_TYPE(ct_int->tp_dict); + } + } + + PyTypeObject* pytype = Py_TYPE(pyobject); + if (pytype->tp_dict && Py_TYPE(pytype->tp_dict) == cstgdict_type) + return true; + return false; +} +#else +// the internals of ctypes have been redone, requiring a more complex checking +namespace { + +typedef struct { + PyTypeObject* DictRemover_Type; + PyTypeObject* PyCArg_Type; + PyTypeObject* PyCField_Type; + PyTypeObject* PyCThunk_Type; + PyTypeObject* StructParam_Type; + PyTypeObject* PyCType_Type; + PyTypeObject* PyCStructType_Type; + PyTypeObject* UnionType_Type; + PyTypeObject* PyCPointerType_Type; + // ... unused fields omitted ... +} _cppjit_ctypes_state; + +} // unnamed namespace + +static bool IsCTypesArrayOrPointer(PyObject* pyobject) { + static _cppjit_ctypes_state* state = nullptr; + if (!state) { + PyObject* ctmod = PyImport_AddModule( + "_ctypes"); // the extension module, not the Python one + if (ctmod) + state = (_cppjit_ctypes_state*)PyModule_GetState(ctmod); + } + + // verify for object types that have a C payload + if (state && (PyObject_IsInstance((PyObject*)Py_TYPE(pyobject), + (PyObject*)state->PyCType_Type) || + PyObject_IsInstance((PyObject*)Py_TYPE(pyobject), + (PyObject*)state->PyCPointerType_Type))) { + return true; + } + + return false; +} +#endif + +//- helper to establish life lines ------------------------------------------- +static inline bool SetLifeLine(PyObject* holder, PyObject* target, + intptr_t ref) { + // set a lifeline from on the holder to the target, using the ref as label + if (!holder) + return false; + + // 'ref' is expected to be the converter address or data memory location, so + // that the combination of holder and ref is unique, but also identifiable for + // reuse when the C++ side is being overwritten + std::ostringstream attr_name; + attr_name << "__" << ref; + auto res = + PyObject_SetAttrString(holder, (char*)attr_name.str().c_str(), target); + return res != -1; +} + +static bool HasLifeLine(PyObject* holder, intptr_t ref) { + // determine if a lifeline was previously set for the ref on the holder + if (!holder) + return false; + + std::ostringstream attr_name; + attr_name << "__" << ref; + PyObject* res = + PyObject_GetAttrString(holder, (char*)attr_name.str().c_str()); + + if (res) { + Py_DECREF(res); + return true; + } + + PyErr_Clear(); + return false; +} + +//- helper to work with both CPPInstance and CPPExcInstance ------------------ +static inline cpyrt::CPPInstance* +GetCppInstance(PyObject* pyobject, + interop::TCppScope_t klass = interop::TCppScope_t{}, + bool accept_rvalue = false) { + using namespace cppjit::cpyrt; + if (CPPInstance_Check(pyobject)) + return (CPPInstance*)pyobject; + if (CPPExcInstance_Check(pyobject)) + return (CPPInstance*)((CPPExcInstance*)pyobject)->fCppInstance; + + // this is not a C++ proxy; allow custom cast to C++ + PyObject* castobj = PyObject_CallMethodNoArgs(pyobject, PyStrings::gCastCpp); + if (castobj) { + if (CPPInstance_Check(castobj)) + return (CPPInstance*)castobj; + else if (klass && PyTuple_CheckExact(castobj)) { + // allow implicit conversion from a tuple of arguments + PyObject* pyclass = GetScopeProxy(klass); + if (pyclass) { + CPPInstance* pytmp = + (CPPInstance*)PyObject_Call(pyclass, castobj, NULL); + Py_DECREF(pyclass); + if (CPPInstance_Check(pytmp)) { + if (accept_rvalue) + pytmp->fFlags |= CPPInstance::kIsRValue; + Py_DECREF(castobj); + return pytmp; + } + Py_XDECREF(pytmp); + } + } + + Py_DECREF(castobj); + return nullptr; + } + + PyErr_Clear(); + return nullptr; +} + +//- custom helpers to check ranges ------------------------------------------- +static inline bool ImplicitBool(PyObject* pyobject, cpyrt::CallContext* ctxt) { + using namespace cppjit::cpyrt; + if (!AllowImplicit(ctxt) && PyBool_Check(pyobject)) { + if (!NoImplicit(ctxt)) + ctxt->fFlags |= CallContext::kHaveImplicit; + return false; + } + return true; +} + +static inline bool StrictBool(PyObject* pyobject, cpyrt::CallContext* ctxt) { + using namespace cppjit::cpyrt; + if (!AllowImplicit(ctxt) && !PyBool_Check(pyobject)) { + if (!NoImplicit(ctxt)) + ctxt->fFlags |= CallContext::kHaveImplicit; + return false; + } + return true; +} + +static inline bool cpyrt_PyLong_AsBool(PyObject* pyobject) { + // range-checking python integer to C++ bool conversion + long l = PyLong_AsLong(pyobject); + // fail to pass float -> bool; the problem is rounding (0.1 -> 0 -> False) + if (!(l == 0 || l == 1) || PyFloat_Check(pyobject)) { + PyErr_SetString(PyExc_ValueError, + "boolean value should be bool, or integer 1 or 0"); + return (bool)-1; + } + return (bool)l; +} + +// range-checking python integer to C++ integer conversion (prevents p2.7 silent +// conversions) +#define CPPJIT_PYLONG_AS_TYPE(name, type, limit_low, limit_high) \ + static inline type cpyrt_PyLong_As##name(PyObject* pyobject) { \ + if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { \ + if (pyobject == cpyrt::gDefaultObject) \ + return (type)0; \ + PyErr_SetString(PyExc_TypeError, \ + #type " conversion expects an integer object"); \ + return (type) - 1; \ + } \ + long l = PyLong_AsLong(pyobject); \ + if (l < limit_low || limit_high < l) { \ + PyErr_Format(PyExc_ValueError, "integer %ld out of range for " #type, \ + l); \ + return (type) - 1; \ + } \ + return (type)l; \ + } + +CPPJIT_PYLONG_AS_TYPE(UInt8, uint8_t, 0, UCHAR_MAX) +CPPJIT_PYLONG_AS_TYPE(Int8, int8_t, SCHAR_MIN, SCHAR_MAX) +CPPJIT_PYLONG_AS_TYPE(UShort, unsigned short, 0, USHRT_MAX) +CPPJIT_PYLONG_AS_TYPE(Short, short, SHRT_MIN, SHRT_MAX) +CPPJIT_PYLONG_AS_TYPE(StrictInt, int, INT_MIN, INT_MAX) + +static inline long cpyrt_PyLong_AsStrictLong(PyObject* pyobject) { + // strict python integer to C++ long integer conversion + + // prevent float -> long (see cpyrt_PyLong_AsStrictInt) + if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { + if (pyobject == cpyrt::gDefaultObject) + return (long)0; + PyErr_SetString(PyExc_TypeError, + "int/long conversion expects an integer object"); + return (long)-1; + } + + return (long)PyLong_AsLong(pyobject); // already does long range check +} + +static inline PY_LONG_LONG cpyrt_PyLong_AsStrictLongLong(PyObject* pyobject) { + // strict python integer to C++ long long integer conversion + + // prevent float -> long (see cpyrt_PyLong_AsStrictInt) + if (!(PyLong_Check(pyobject) || PyInt_Check(pyobject))) { + if (pyobject == cpyrt::gDefaultObject) + return (PY_LONG_LONG)0; + PyErr_SetString(PyExc_TypeError, + "int/long conversion expects an integer object"); + return (PY_LONG_LONG)-1; + } + + return PyLong_AsLongLong(pyobject); // already does long range check +} + +//- helper for pointer/array/reference conversions --------------------------- +static inline bool CArraySetArg(PyObject* pyobject, cpyrt::Parameter& para, + char tc, int size, bool check = true) { + // Case of LowLevelView. In general, they also implement the buffer protocol, + // but for views around nullptr or C-style arrays without size info the buffer + // protocol implementation is incomplete and PyObject_GetBuffer will fail. + if (cpyrt::LowLevelView_Check(pyobject)) { + auto llview = ((cpyrt::LowLevelView*)pyobject); + if (llview->fBufInfo.itemsize != size || + !strchr(llview->fBufInfo.format, tc)) { + PyErr_Format(PyExc_TypeError, + "could not convert argument to buffer or nullptr"); + return false; + } + + para.fValue.fVoidp = llview->get_buf(); + } + // general case of loading a C array pointer (void* + type code) as function + // argument + else if (pyobject == cpyrt::gNullPtrObject || + pyobject == cpyrt::gDefaultObject) + para.fValue.fVoidp = nullptr; + else { + Py_ssize_t buflen = cpyrt::Utility::GetBuffer(pyobject, tc, size, + para.fValue.fVoidp, check); + if (!buflen) { + // stuck here as it's the least common + if (cpyrt_PyLong_AsStrictInt(pyobject) == 0) + para.fValue.fVoidp = nullptr; + else { + PyErr_Format(PyExc_TypeError, // ValueError? + "could not convert argument to buffer or nullptr"); + return false; + } + } + } + para.fTypeCode = 'p'; + return true; +} + +//- helper for implicit conversions ------------------------------------------ +static inline cpyrt::CPPInstance* ConvertImplicit(interop::TCppScope_t klass, + PyObject* pyobject, + cpyrt::Parameter& para, + cpyrt::CallContext* ctxt, + bool manage = true) { + using namespace cppjit::cpyrt; + + // filter out copy and move constructors + if (IsConstructor(ctxt->fFlags) && klass == ctxt->fCurScope && + ctxt->GetSize() == 1) + return nullptr; + + // only proceed if implicit conversions are allowed (in "round 2") or if the + // argument is exactly a tuple or list, as these are the equivalent of + // initializer lists and thus "syntax" not a conversion + if (!AllowImplicit(ctxt)) { + PyTypeObject* pytype = (PyTypeObject*)Py_TYPE(pyobject); + if (!(pytype == &PyList_Type || + pytype == &PyTuple_Type)) { // || !CPPInstance_Check(pyobject))) { + if (!NoImplicit(ctxt)) + ctxt->fFlags |= CallContext::kHaveImplicit; + return nullptr; + } + } + + // exercise implicit conversion + PyObject* pyscope = CreateScopeProxy(klass); + if (!CPPScope_Check(pyscope)) { + Py_XDECREF(pyscope); + return nullptr; + } + + // call constructor of argument type to attempt implicit conversion (disallow + // any implicit conversions by the scope's constructor itself) + PyObject* args = PyTuple_New(1); + Py_INCREF(pyobject); + PyTuple_SET_ITEM(args, 0, pyobject); + + ((CPPScope*)pyscope)->fFlags |= CPPScope::kActiveImplicitCall; + CPPInstance* pytmp = (CPPInstance*)PyObject_Call(pyscope, args, NULL); + if (!pytmp && PyTuple_CheckExact(pyobject)) { + // special case: allow implicit conversion from given set of arguments in + // tuple + PyErr_Clear(); + pytmp = (CPPInstance*)PyObject_Call(pyscope, pyobject, NULL); + } + ((CPPScope*)pyscope)->fFlags &= ~CPPScope::kActiveImplicitCall; + + Py_DECREF(args); + Py_DECREF(pyscope); + + if (pytmp) { + // implicit conversion succeeded! + if (manage) + ctxt->AddTemporary((PyObject*)pytmp); + para.fValue.fVoidp = pytmp->GetObjectRaw(); + para.fTypeCode = 'V'; + return pytmp; + } + + PyErr_Clear(); + return nullptr; +} + +//- base converter implementation -------------------------------------------- +cppjit::cpyrt::Converter::~Converter() { /* empty */ } + +//---------------------------------------------------------------------------- +PyObject* cppjit::cpyrt::Converter::FromMemory(void*) { + // could happen if no derived class override + PyErr_SetString(PyExc_TypeError, "C++ type cannot be converted from memory"); + return nullptr; +} + +//---------------------------------------------------------------------------- +bool cppjit::cpyrt::Converter::ToMemory(PyObject*, void*, + PyObject* /* ctxt */) { + // could happen if no derived class override + PyErr_SetString(PyExc_TypeError, "C++ type cannot be converted to memory"); + return false; +} + +//- helper macro's ----------------------------------------------------------- +#define CPPJIT_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ + /* convert to C++ 'type', set arg for call */ \ + type val = (type)F2(pyobject); \ + if (val == (type) - 1 && PyErr_Occurred()) { \ + static PyTypeObject* ctypes_type = nullptr; \ + if (!ctypes_type) { \ + auto error = cpyrt::Utility::FetchPyError(); \ + ctypes_type = GetCTypesType(ct_##ctype); \ + cpyrt::Utility::RestorePyError(error); \ + } \ + if (Py_TYPE(pyobject) == ctypes_type) { \ + PyErr_Clear(); \ + val = *((type*)((cpyrt_tagCDataObject*)pyobject)->b_ptr); \ + } else if (pyobject == cpyrt::gDefaultObject) { \ + PyErr_Clear(); \ + val = (type)0; \ + } else \ + return false; \ + } \ + para.fValue.f##name = val; \ + para.fTypeCode = tc; \ + return true; + +#define CPPJIT_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) \ + PyObject* cpyrt::name##Converter::FromMemory(void* address) { \ + return F1((stype) * ((type*)address)); \ + } \ + \ + bool cpyrt::name##Converter::ToMemory(PyObject* value, void* address, \ + PyObject* /* ctxt */) { \ + type s = (type)F2(value); \ + if (s == (type) - 1 && PyErr_Occurred()) { \ + if (value == cpyrt::gDefaultObject) { \ + PyErr_Clear(); \ + s = (type)0; \ + } else \ + return false; \ + } \ + *((type*)address) = (type)s; \ + return true; \ + } + +#define CPPJIT_IMPL_BASIC_CONVERTER_NI(name, type, stype, ctype, F1, F2, tc) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* ctxt) { \ + if (!StrictBool(pyobject, ctxt)) \ + return false; \ + CPPJIT_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ + } \ + CPPJIT_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) + +#define CPPJIT_IMPL_BASIC_CONVERTER_IB(name, type, stype, ctype, F1, F2, tc) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* ctxt) { \ + if (!ImplicitBool(pyobject, ctxt)) \ + return false; \ + CPPJIT_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ + } \ + CPPJIT_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) + +#define CPPJIT_IMPL_BASIC_CONVERTER_NB(name, type, stype, ctype, F1, F2, tc) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* /*ctxt*/) { \ + if (PyBool_Check(pyobject)) \ + return false; \ + CPPJIT_IMPL_BASIC_CONVERTER_BODY(name, type, stype, ctype, F1, F2, tc) \ + } \ + CPPJIT_IMPL_BASIC_CONVERTER_METHODS(name, type, stype, ctype, F1, F2) + +//---------------------------------------------------------------------------- +static inline int ExtractChar(PyObject* pyobject, const char* tname, int low, + int high) { + int lchar = -1; + if (PyBytes_Check(pyobject)) { + if (PyBytes_GET_SIZE(pyobject) == 1) + lchar = (int)(PyBytes_AsString(pyobject)[0]); + else + PyErr_Format(PyExc_ValueError, + "%s expected, got bytes of size " PY_SSIZE_T_FORMAT, tname, + PyBytes_GET_SIZE(pyobject)); + } else if (cpyrt_PyText_Check(pyobject)) { + if (cpyrt_PyText_GET_SIZE(pyobject) == 1) + lchar = (int)(cpyrt_PyText_AsString(pyobject)[0]); + else + PyErr_Format(PyExc_ValueError, + "%s expected, got str of size " PY_SSIZE_T_FORMAT, tname, + cpyrt_PyText_GET_SIZE(pyobject)); + } else if (pyobject == cpyrt::gDefaultObject) { + lchar = (int)'\0'; + } else if (!PyFloat_Check(pyobject)) { // don't allow truncating conversion + lchar = (int)PyLong_AsLong(pyobject); + if (lchar == -1 && PyErr_Occurred()) + ; // empty, as error already set + else if (!(low <= lchar && lchar <= high)) { + PyErr_Format(PyExc_ValueError, + "integer to character: value %d not in range [%d,%d]", lchar, + low, high); + lchar = -1; + } + } else + PyErr_SetString(PyExc_TypeError, "char or small int type expected"); + + return lchar; +} + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(name, ctype) \ + PyObject* cpyrt::name##RefConverter::FromMemory(void* ptr) { \ + /* convert a reference to int to Python through ctypes pointer object */ \ + PyTypeObject* ctypes_type = GetCTypesType(ct_##ctype); \ + if (!ctypes_type) { \ + PyErr_SetString(PyExc_RuntimeError, "no ctypes available"); \ + return nullptr; \ + } \ + PyObject* ref = ctypes_type->tp_new(ctypes_type, nullptr, nullptr); \ + ((cpyrt_tagCDataObject*)ref)->b_ptr = (char*)ptr; \ + ((cpyrt_tagCDataObject*)ref)->b_needsfree = 0; \ + return ref; \ + } + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(name, type, ctype, F1) \ + bool cpyrt::Const##name##RefConverter::SetArg( \ + PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) { \ + type val = (type)F1(pyobject); \ + if (val == (type) - 1 && PyErr_Occurred()) { \ + if (pyobject == cpyrt::gDefaultObject) { \ + PyErr_Clear(); \ + val = (type)0; \ + } else \ + return false; \ + } \ + para.fValue.f##name = val; \ + para.fRef = ¶.fValue.f##name; \ + para.fTypeCode = 'r'; \ + return true; \ + } \ + CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Const##name, ctype) + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_BASIC_CONST_CHAR_REFCONVERTER(name, type, ctype, low, \ + high) \ + bool cpyrt::Const##name##RefConverter::SetArg( \ + PyObject* pyobject, Parameter& para, CallContext* /* ctxt */) { \ + /* convert to C++ <>, set arg for call, allow int -> char \ + */ \ + type val = (type)ExtractChar(pyobject, #type, low, high); \ + if (val == (type) - 1 && PyErr_Occurred()) \ + return false; \ + para.fValue.fLong = val; \ + para.fTypeCode = 'l'; \ + return true; \ + } \ + CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Const##name, ctype) + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_BASIC_CHAR_CONVERTER(name, type, low, high) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* /* ctxt */) { \ + /* convert to C++ <>, set arg for call, allow int -> char \ + */ \ + long val = ExtractChar(pyobject, #type, low, high); \ + if (val == -1 && PyErr_Occurred()) \ + return false; \ + para.fValue.fLong = val; \ + para.fTypeCode = 'l'; \ + return true; \ + } \ + \ + PyObject* cpyrt::name##Converter::FromMemory(void* address) { \ + /* return char in "native" str type as that's more natural in use */ \ + return cpyrt_PyText_FromFormat("%c", *((type*)address)); \ + } \ + \ + bool cpyrt::name##Converter::ToMemory(PyObject* value, void* address, \ + PyObject* /* ctxt */) { \ + Py_ssize_t len; \ + const char* cstr = nullptr; \ + if (PyBytes_Check(value)) \ + PyBytes_AsStringAndSize(value, (char**)&cstr, &len); \ + else \ + cstr = cpyrt_PyText_AsStringAndSize(value, &len); \ + if (cstr) { \ + if (len != 1) { \ + PyErr_Format(PyExc_TypeError, \ + #type " expected, got string of size %zd", len); \ + return false; \ + } \ + *((type*)address) = (type)cstr[0]; \ + } else { \ + PyErr_Clear(); \ + long l = PyLong_AsLong(value); \ + if (l == -1 && PyErr_Occurred()) { \ + if (value == cpyrt::gDefaultObject) { \ + PyErr_Clear(); \ + l = (long)0; \ + } else \ + return false; \ + } \ + if (!(low <= l && l <= high)) { \ + PyErr_Format(PyExc_ValueError, \ + "integer to character: value %ld not in range [%d,%d]", \ + l, low, high); \ + return false; \ + } \ + *((type*)address) = (type)l; \ + } \ + return true; \ + } + +//- converters for built-ins ------------------------------------------------- +CPPJIT_IMPL_BASIC_CONVERTER_IB(Long, long, long, c_long, PyLong_FromLong, + cpyrt_PyLong_AsStrictLong, 'l') + +//---------------------------------------------------------------------------- +bool cpyrt::LongRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ long&, set arg for call + + if (Py_TYPE(pyobject) == GetCTypesType(ct_c_long)) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + + if (CArraySetArg(pyobject, para, 'l', sizeof(long))) { + para.fTypeCode = 'V'; + return true; + } + + PyErr_SetString(PyExc_TypeError, + "use ctypes.c_long for pass-by-ref of longs"); + return false; +} + +//---------------------------------------------------------------------------- +// clang-format off +CPPJIT_IMPL_BASIC_CONST_CHAR_REFCONVERTER(Char, char, c_char, CHAR_MIN, CHAR_MAX) +CPPJIT_IMPL_BASIC_CONST_CHAR_REFCONVERTER(UChar, unsigned char, c_uchar, 0, UCHAR_MAX) + +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Bool, bool, c_bool, cpyrt_PyLong_AsBool) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Int8, int8_t, c_int8, cpyrt_PyLong_AsInt8) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(UInt8, uint8_t, c_uint8, cpyrt_PyLong_AsUInt8) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Short, short, c_short, cpyrt_PyLong_AsShort) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(UShort, unsigned short, c_ushort, cpyrt_PyLong_AsUShort) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Int, int, c_int, cpyrt_PyLong_AsStrictInt) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(UInt, unsigned int, c_uint, PyLongOrInt_AsULong) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Long, long, c_long, cpyrt_PyLong_AsStrictLong) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(ULong, unsigned long, c_ulong, PyLongOrInt_AsULong) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(LLong, PY_LONG_LONG, c_longlong, cpyrt_PyLong_AsStrictLongLong) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(ULLong, PY_ULONG_LONG, c_ulonglong, PyLongOrInt_AsULong64) +// clang-format on + +//---------------------------------------------------------------------------- +bool cpyrt::IntRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ (pseudo)int&, set arg for call + + if (Py_TYPE(pyobject) == GetCTypesType(ct_c_int)) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + + // alternate, pass pointer from buffer + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, 'i', sizeof(int), para.fValue.fVoidp); + if (para.fValue.fVoidp && buflen) { + para.fTypeCode = 'V'; + return true; + }; + + PyErr_SetString(PyExc_TypeError, "use ctypes.c_int for pass-by-ref of ints"); + return false; +} + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_REFCONVERTER(name, ctype, type, code) \ + bool cpyrt::name##RefConverter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* /* ctxt */) { \ + /* convert a reference to int to Python through ctypes pointer object */ \ + if (Py_TYPE(pyobject) == GetCTypesType(ct_##ctype)) { \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'V'; \ + return true; \ + } \ + bool res = CArraySetArg(pyobject, para, code, sizeof(type)); \ + if (!res) { \ + PyErr_SetString(PyExc_TypeError, \ + "use ctypes." #ctype " for pass-by-ref of " #type); \ + return false; \ + } \ + para.fTypeCode = 'V'; \ + return res; \ + } \ + CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(name, ctype) + +// clang-format off +CPPJIT_IMPL_REFCONVERTER(Bool, c_bool, bool, '?'); +CPPJIT_IMPL_REFCONVERTER(Char, c_char, char, 'b'); +CPPJIT_IMPL_REFCONVERTER(WChar, c_wchar, wchar_t, 'u'); +CPPJIT_IMPL_REFCONVERTER(Char16, c_uint16, char16_t, 'H'); +CPPJIT_IMPL_REFCONVERTER(Char32, c_uint32, char32_t, 'I'); +CPPJIT_IMPL_REFCONVERTER(SChar, c_byte, signed char, 'b'); +CPPJIT_IMPL_REFCONVERTER(UChar, c_ubyte, unsigned char, 'B'); +CPPJIT_IMPL_REFCONVERTER(Int8, c_int8, int8_t, 'b'); +CPPJIT_IMPL_REFCONVERTER(UInt8, c_uint8, uint8_t, 'B'); +CPPJIT_IMPL_REFCONVERTER(Short, c_short, short, 'h'); +CPPJIT_IMPL_REFCONVERTER(UShort, c_ushort, unsigned short, 'H'); +CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Int, c_int); +CPPJIT_IMPL_REFCONVERTER(UInt, c_uint, unsigned int, 'I'); +CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Long, c_long); +CPPJIT_IMPL_REFCONVERTER(ULong, c_ulong, unsigned long, 'L'); +CPPJIT_IMPL_REFCONVERTER(LLong, c_longlong, long long, 'q'); +CPPJIT_IMPL_REFCONVERTER(ULLong, c_ulonglong, unsigned long long, 'Q'); +CPPJIT_IMPL_REFCONVERTER(Float, c_float, float, 'f'); +CPPJIT_IMPL_REFCONVERTER_FROM_MEMORY(Double, c_double); +CPPJIT_IMPL_REFCONVERTER(LDouble, c_longdouble, PY_LONG_DOUBLE, 'g'); +// clang-format on + +//---------------------------------------------------------------------------- +// convert to C++ bool, allow int/long -> bool, set arg for call +CPPJIT_IMPL_BASIC_CONVERTER_NI(Bool, bool, long, c_bool, PyBool_FromLong, + cpyrt_PyLong_AsBool, 'l') + +//---------------------------------------------------------------------------- +CPPJIT_IMPL_BASIC_CHAR_CONVERTER(Char, char, CHAR_MIN, CHAR_MAX) +CPPJIT_IMPL_BASIC_CHAR_CONVERTER(UChar, unsigned char, 0, UCHAR_MAX) + +PyObject* cpyrt::SCharAsIntConverter::FromMemory(void* address) { + // special case to be used with arrays: return a Python int instead of str + // (following the same convention as module array.array) + return PyInt_FromLong((long)*((signed char*)address)); +} + +PyObject* cpyrt::UCharAsIntConverter::FromMemory(void* address) { + // special case to be used with arrays: return a Python int instead of str + // (following the same convention as module array.array) + return PyInt_FromLong((long)*((unsigned char*)address)); +} + +//---------------------------------------------------------------------------- +bool cpyrt::WCharConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ , set arg for call + if (!PyUnicode_Check(pyobject) || cpyrt_PyUnicode_GET_SIZE(pyobject) != 1) { + PyErr_SetString(PyExc_ValueError, "single wchar_t character expected"); + return false; + } + wchar_t val; + Py_ssize_t res = cpyrt_PyUnicode_AsWideChar(pyobject, &val, 1); + if (res == -1) + return false; + para.fValue.fLong = (long)val; + para.fTypeCode = 'U'; + return true; +} + +PyObject* cpyrt::WCharConverter::FromMemory(void* address) { + return PyUnicode_FromWideChar((const wchar_t*)address, 1); +} + +bool cpyrt::WCharConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + if (!PyUnicode_Check(value) || cpyrt_PyUnicode_GET_SIZE(value) != 1) { + PyErr_SetString(PyExc_ValueError, "single wchar_t character expected"); + return false; + } + wchar_t val; + Py_ssize_t res = cpyrt_PyUnicode_AsWideChar(value, &val, 1); + if (res == -1) + return false; + *((wchar_t*)address) = val; + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Char16Converter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ , set arg for call + if (!PyUnicode_Check(pyobject) || cpyrt_PyUnicode_GET_SIZE(pyobject) != 1) { + PyErr_SetString(PyExc_ValueError, "single char16_t character expected"); + return false; + } + + PyObject* bstr = PyUnicode_AsUTF16String(pyobject); + if (!bstr) + return false; + + char16_t val = + *(char16_t*)(PyBytes_AS_STRING(bstr) + sizeof(char16_t) /*BOM*/); + Py_DECREF(bstr); + para.fValue.fLong = (long)val; + para.fTypeCode = 'U'; + return true; +} + +PyObject* cpyrt::Char16Converter::FromMemory(void* address) { + return PyUnicode_DecodeUTF16((const char*)address, sizeof(char16_t), nullptr, + nullptr); +} + +bool cpyrt::Char16Converter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + if (!PyUnicode_Check(value) || cpyrt_PyUnicode_GET_SIZE(value) != 1) { + PyErr_SetString(PyExc_ValueError, "single char16_t character expected"); + return false; + } + + PyObject* bstr = PyUnicode_AsUTF16String(value); + if (!bstr) + return false; + + *((char16_t*)address) = + *(char16_t*)(PyBytes_AS_STRING(bstr) + sizeof(char16_t) /*BOM*/); + Py_DECREF(bstr); + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Char32Converter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ , set arg for call + if (!PyUnicode_Check(pyobject) || 2 < cpyrt_PyUnicode_GET_SIZE(pyobject)) { + PyErr_SetString(PyExc_ValueError, "single char32_t character expected"); + return false; + } + + PyObject* bstr = PyUnicode_AsUTF32String(pyobject); + if (!bstr) + return false; + + char32_t val = + *(char32_t*)(PyBytes_AS_STRING(bstr) + sizeof(char32_t) /*BOM*/); + Py_DECREF(bstr); + para.fValue.fLong = (long)val; + para.fTypeCode = 'U'; + return true; +} + +PyObject* cpyrt::Char32Converter::FromMemory(void* address) { + return PyUnicode_DecodeUTF32((const char*)address, sizeof(char32_t), nullptr, + nullptr); +} + +bool cpyrt::Char32Converter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + if (!PyUnicode_Check(value) || 2 < cpyrt_PyUnicode_GET_SIZE(value)) { + PyErr_SetString(PyExc_ValueError, "single char32_t character expected"); + return false; + } + + PyObject* bstr = PyUnicode_AsUTF32String(value); + if (!bstr) + return false; + + *((char32_t*)address) = + *(char32_t*)(PyBytes_AS_STRING(bstr) + sizeof(char32_t) /*BOM*/); + Py_DECREF(bstr); + return true; +} + +//---------------------------------------------------------------------------- +CPPJIT_IMPL_BASIC_CONVERTER_IB(Int8, int8_t, long, c_int8, PyInt_FromLong, + cpyrt_PyLong_AsInt8, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(UInt8, uint8_t, long, c_uint8, PyInt_FromLong, + cpyrt_PyLong_AsUInt8, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(Short, short, long, c_short, PyInt_FromLong, + cpyrt_PyLong_AsShort, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(UShort, unsigned short, long, c_ushort, + PyInt_FromLong, cpyrt_PyLong_AsUShort, 'l') +CPPJIT_IMPL_BASIC_CONVERTER_IB(Int, int, long, c_uint, PyInt_FromLong, + cpyrt_PyLong_AsStrictInt, 'l') + +//---------------------------------------------------------------------------- +bool cpyrt::ULongConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ unsigned long, set arg for call + if (!ImplicitBool(pyobject, ctxt)) + return false; + + para.fValue.fULong = PyLongOrInt_AsULong(pyobject); + if (para.fValue.fULong == (unsigned long)-1 && PyErr_Occurred()) + return false; + para.fTypeCode = 'L'; + return true; +} + +PyObject* cpyrt::ULongConverter::FromMemory(void* address) { + // construct python object from C++ unsigned long read at
+ return PyLong_FromUnsignedLong(*((unsigned long*)address)); +} + +bool cpyrt::ULongConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ unsigned long, write it at
+ unsigned long u = PyLongOrInt_AsULong(value); + if (u == (unsigned long)-1 && PyErr_Occurred()) { + if (value == cpyrt::gDefaultObject) { + PyErr_Clear(); + u = (unsigned long)0; + } else + return false; + } + *((unsigned long*)address) = u; + return true; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UIntConverter::FromMemory(void* address) { + // construct python object from C++ unsigned int read at
+ return PyLong_FromUnsignedLong(*((unsigned int*)address)); +} + +bool cpyrt::UIntConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ unsigned int, write it at
+ unsigned long u = PyLongOrInt_AsULong(value); + if (u == (unsigned long)-1 && PyErr_Occurred()) + return false; + + if (u > (unsigned long)UINT_MAX) { + PyErr_SetString(PyExc_OverflowError, "value too large for unsigned int"); + return false; + } + + *((unsigned int*)address) = (unsigned int)u; + return true; +} + +//- floating point converters ------------------------------------------------ +CPPJIT_IMPL_BASIC_CONVERTER_NB(Float, float, double, c_float, + PyFloat_FromDouble, PyFloat_AsDouble, 'f') +CPPJIT_IMPL_BASIC_CONVERTER_NB(Double, double, double, c_double, + PyFloat_FromDouble, PyFloat_AsDouble, 'd') + +CPPJIT_IMPL_BASIC_CONVERTER_NB(LDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, + c_longdouble, PyFloat_FromDouble, + PyFloat_AsDouble, 'g') + +cpyrt::ComplexDConverter::ComplexDConverter(bool keepControl) + : InstanceConverter(interop::GetFullScope("std::complex"), + keepControl) {} + +// special case for std::complex, maps it to/from Python's complex +bool cpyrt::ComplexDConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + const Py_complex& pc = PyComplex_AsCComplex(pyobject); + if (pc.real != -1.0 || !PyErr_Occurred()) { + fBuffer.real(pc.real); + fBuffer.imag(pc.imag); + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + return this->InstanceConverter::SetArg(pyobject, para, ctxt); +} + +PyObject* cpyrt::ComplexDConverter::FromMemory(void* address) { + std::complex* dc = (std::complex*)address; + return PyComplex_FromDoubles(dc->real(), dc->imag()); +} + +bool cpyrt::ComplexDConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + const Py_complex& pc = PyComplex_AsCComplex(value); + if (pc.real != -1.0 || !PyErr_Occurred()) { + std::complex* dc = (std::complex*)address; + dc->real(pc.real); + dc->imag(pc.imag); + return true; + } + return this->InstanceConverter::ToMemory(value, address, ctxt); +} + +//---------------------------------------------------------------------------- +bool cpyrt::DoubleRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ double&, set arg for call + + if (Py_TYPE(pyobject) == GetCTypesType(ct_c_double)) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + + // alternate, pass pointer from buffer + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, 'd', sizeof(double), para.fValue.fVoidp); + if (buflen && para.fValue.fVoidp) { + para.fTypeCode = 'V'; + return true; + } + + PyErr_SetString(PyExc_TypeError, + "use ctypes.c_double for pass-by-ref of doubles"); + return false; +} + +//---------------------------------------------------------------------------- +// clang-format off +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Float, float, c_float, PyFloat_AsDouble) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(Double, double, c_double, PyFloat_AsDouble) +CPPJIT_IMPL_BASIC_CONST_REFCONVERTER(LDouble, PY_LONG_DOUBLE, c_longdouble, PyFloat_AsDouble) +// clang-format on + +//---------------------------------------------------------------------------- +bool cpyrt::VoidConverter::SetArg(PyObject*, Parameter&, CallContext*) { + // can't happen (unless a type is mapped wrongly), but implemented for + // completeness + PyErr_SetString(PyExc_SystemError, "void/unknown arguments can\'t be set"); + return false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::LLongConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ long long, set arg for call + if (!ImplicitBool(pyobject, ctxt)) + return false; + + para.fValue.fLLong = cpyrt_PyLong_AsStrictLongLong(pyobject); + if (PyErr_Occurred()) + return false; + para.fTypeCode = 'q'; + return true; +} + +PyObject* cpyrt::LLongConverter::FromMemory(void* address) { + // construct python object from C++ long long read at
+ return PyLong_FromLongLong(*(PY_LONG_LONG*)address); +} + +bool cpyrt::LLongConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ long long, write it at
+ PY_LONG_LONG ll = PyLong_AsLongLong(value); + if (ll == -1 && PyErr_Occurred()) { + if (value == cpyrt::gDefaultObject) { + PyErr_Clear(); + ll = (PY_LONG_LONG)0; + } else + return false; + } + *((PY_LONG_LONG*)address) = ll; + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::ULLongConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ unsigned long long, set arg for call + if (!ImplicitBool(pyobject, ctxt)) + return false; + + para.fValue.fULLong = PyLongOrInt_AsULong64(pyobject); + if (PyErr_Occurred()) + return false; + para.fTypeCode = 'Q'; + return true; +} + +PyObject* cpyrt::ULLongConverter::FromMemory(void* address) { + // construct python object from C++ unsigned long long read at
+ return PyLong_FromUnsignedLongLong(*(PY_ULONG_LONG*)address); +} + +bool cpyrt::ULLongConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ unsigned long long, write it at
+ PY_ULONG_LONG ull = PyLongOrInt_AsULong64(value); + if (PyErr_Occurred()) { + if (value == cpyrt::gDefaultObject) { + PyErr_Clear(); + ull = (PY_ULONG_LONG)0; + } else + return false; + } + *((PY_ULONG_LONG*)address) = ull; + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::CStringConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // construct a new string and copy it in new memory + Py_ssize_t len; + const char* cstr = cpyrt_PyText_AsStringAndSize(pyobject, &len); + if (!cstr) { + // special case: allow ctypes c_char_p + auto error = cpyrt::Utility::FetchPyError(); + if (Py_TYPE(pyobject) == GetCTypesType(ct_c_char_p)) { + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + cpyrt::Utility::RestorePyError(error); + return false; + } + + // verify (too long string will cause truncation, no crash) + if (fMaxSize != std::string::npos && fMaxSize < fBuffer.size()) + if (PyErr_WarnEx(PyExc_RuntimeWarning, + (char*)"string too long for char array (truncated)", + 1) < 0) + return false; + + if (!ctxt->fPyContext) { + // use internal buffer as workaround + fBuffer = std::string(cstr, len); + if (fMaxSize != std::string::npos) + fBuffer.resize(fMaxSize, '\0'); // pad remainder of buffer as needed + cstr = fBuffer.c_str(); + } else + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); + + // set the value and declare success + para.fValue.fVoidp = (void*)cstr; + para.fTypeCode = 'p'; + return true; +} + +PyObject* cpyrt::CStringConverter::FromMemory(void* address) { + // construct python object from C++ const char* read at
+ if (address && *(void**)address) { + if (fMaxSize != + std::string::npos) // need to prevent reading beyond boundary + return cpyrt_PyText_FromStringAndSize(*(char**)address, + (Py_ssize_t)fMaxSize); + + if (*(void**)address == + (void*)fBuffer.data()) // if we're buffering, we know the size + return cpyrt_PyText_FromStringAndSize((char*)fBuffer.data(), + fBuffer.size()); + + // no idea about lentgth: cut on \0 + return cpyrt_PyText_FromString(*(char**)address); + } + + // empty string in case there's no address + Py_INCREF(PyStrings::gEmptyString); + return PyStrings::gEmptyString; +} + +bool cpyrt::CStringConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + // convert to C++ const char*, write it at
+ Py_ssize_t len; + const char* cstr = cpyrt_PyText_AsStringAndSize(value, &len); + if (!cstr) + return false; + + // verify (too long string will cause truncation, no crash) + if (fMaxSize != std::string::npos && fMaxSize < (std::string::size_type)len) + if (PyErr_WarnEx(PyExc_RuntimeWarning, + (char*)"string too long for char array (truncated)", + 1) < 0) + return false; + + // if address is available, and it wasn't set by this converter, assume a + // byte-wise copy; otherwise assume a pointer copy (this relies on the + // converter to be used for properties, or for argument passing, but not both + // at the same time; this is currently the case) + void* ptrval = *(void**)address; + if (ptrval == (void*)fBuffer.data()) { + fBuffer = std::string(cstr, len); + *(void**)address = (void*)fBuffer.data(); + return true; + } else if (ptrval && HasLifeLine(ctxt, (intptr_t)ptrval)) { + ptrval = nullptr; + // fall through; ptrval is nullptr means we're managing it + } + + // the string is (going to be) managed by us: assume pointer copy + if (!ptrval) { + SetLifeLine(ctxt, value, (intptr_t)address); + *(void**)address = (void*)cstr; + return true; + } + + // the pointer value is non-zero and not ours: assume byte copy + if (fMaxSize != std::string::npos) + strncpy(*(char**)address, cstr, fMaxSize); // pads remainder + else + // coverity[secure_coding] - can't help it, it's intentional. + strcpy(*(char**)address, cstr); + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::WCStringConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // construct a new string and copy it in new memory + Py_ssize_t len = PyUnicode_GetSize(pyobject); + if (len == (Py_ssize_t)-1 && PyErr_Occurred()) + return false; + + fBuffer = (wchar_t*)realloc(fBuffer, sizeof(wchar_t) * (len + 1)); + Py_ssize_t res = cpyrt_PyUnicode_AsWideChar(pyobject, fBuffer, len); + if (res == -1) + return false; // could free the buffer here + + // set the value and declare success + fBuffer[len] = L'\0'; + para.fValue.fVoidp = (void*)fBuffer; + para.fTypeCode = 'p'; + return true; +} + +PyObject* cpyrt::WCStringConverter::FromMemory(void* address) { + // construct python object from C++ wchar_t* read at
+ if (address && *(wchar_t**)address) { + if (fMaxSize != + std::wstring::npos) // need to prevent reading beyond boundary + return PyUnicode_FromWideChar(*(wchar_t**)address, (Py_ssize_t)fMaxSize); + // with unknown size + return PyUnicode_FromWideChar(*(wchar_t**)address, + wcslen(*(wchar_t**)address)); + } + + // empty string in case there's no valid address + wchar_t w = L'\0'; + return PyUnicode_FromWideChar(&w, 0); +} + +bool cpyrt::WCStringConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // convert to C++ wchar_t*, write it at
+ Py_ssize_t len = PyUnicode_GetSize(value); + if (len == (Py_ssize_t)-1 && PyErr_Occurred()) + return false; + + // verify (too long string will cause truncation, no crash) + if (fMaxSize != std::wstring::npos && fMaxSize < (std::wstring::size_type)len) + if (PyErr_WarnEx(PyExc_RuntimeWarning, + (char*)"string too long for wchar_t array (truncated)", + 1) < 0) + return false; + + Py_ssize_t res = -1; + if (fMaxSize != std::wstring::npos) + res = cpyrt_PyUnicode_AsWideChar(value, *(wchar_t**)address, + (Py_ssize_t)fMaxSize); + else + // coverity[secure_coding] - can't help it, it's intentional. + res = cpyrt_PyUnicode_AsWideChar(value, *(wchar_t**)address, len); + + if (res == -1) + return false; + return true; +} + +//---------------------------------------------------------------------------- +#define CPYRT_WIDESTRING_CONVERTER(name, type, encode, decode, snull) \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* /* ctxt */) { \ + /* change string encoding and copy into local buffer */ \ + PyObject* bstr = encode(pyobject); \ + if (!bstr) \ + return false; \ + \ + Py_ssize_t len = PyBytes_GET_SIZE(bstr) - sizeof(type) /*BOM*/; \ + fBuffer = (type*)realloc(fBuffer, len + sizeof(type)); \ + memcpy(fBuffer, PyBytes_AS_STRING(bstr) + sizeof(type) /*BOM*/, len); \ + Py_DECREF(bstr); \ + \ + fBuffer[len / sizeof(type)] = snull; \ + para.fValue.fVoidp = (void*)fBuffer; \ + para.fTypeCode = 'p'; \ + return true; \ + } \ + \ + PyObject* cpyrt::name##Converter::FromMemory(void* address) { \ + /* construct python object from C++ * read at
*/ \ + if (address && *(type**)address) { \ + if (fMaxSize != std::wstring::npos) \ + return decode(*(const char**)address, \ + (Py_ssize_t)fMaxSize * sizeof(type), nullptr, nullptr); \ + return decode(*(const char**)address, \ + std::char_traits::length(*(type**)address) * \ + sizeof(type), \ + nullptr, nullptr); \ + } \ + \ + /* empty string in case there's no valid address */ \ + type w = snull; \ + return decode((const char*)&w, 0, nullptr, nullptr); \ + } \ + \ + bool cpyrt::name##Converter::ToMemory(PyObject* value, void* address, \ + PyObject* /* ctxt */) { \ + /* convert to C++ *, write it at
*/ \ + PyObject* bstr = encode(value); \ + if (!bstr) \ + return false; \ + \ + Py_ssize_t len = PyBytes_GET_SIZE(bstr) - sizeof(type) /*BOM*/; \ + Py_ssize_t maxbytes = (Py_ssize_t)fMaxSize * sizeof(type); \ + \ + /* verify (too long string will cause truncation, no crash) */ \ + if (fMaxSize != std::wstring::npos && maxbytes < len) { \ + if (PyErr_WarnEx(PyExc_RuntimeWarning, \ + (char*)"string too long for " #type \ + " array (truncated)", \ + 1) < 0) { \ + Py_DECREF(bstr); \ + return false; \ + } \ + len = maxbytes; \ + } \ + \ + memcpy(*((void**)address), PyBytes_AS_STRING(bstr) + sizeof(type) /*BOM*/, \ + len); \ + Py_DECREF(bstr); \ + /* debatable, but probably more convenient in most cases to null-terminate \ + * if enough space */ \ + if (len / sizeof(type) < fMaxSize) \ + (*(type**)address)[len / sizeof(type)] = snull; \ + return true; \ + } + +CPYRT_WIDESTRING_CONVERTER(CString16, char16_t, PyUnicode_AsUTF16String, + PyUnicode_DecodeUTF16, u'\0') +CPYRT_WIDESTRING_CONVERTER(CString32, char32_t, PyUnicode_AsUTF32String, + PyUnicode_DecodeUTF32, U'\0') + +//---------------------------------------------------------------------------- +bool cpyrt::NonConstCStringConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { + // attempt base class first (i.e. passing a string), but if that fails, try a + // buffer + if (this->CStringConverter::SetArg(pyobject, para, ctxt)) + return true; + + // apparently failed, try char buffer + PyErr_Clear(); + return CArraySetArg(pyobject, para, 'c', sizeof(char)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::NonConstCStringConverter::FromMemory(void* address) { + // assume this is a buffer access if the size is known; otherwise assume + // string + if (fMaxSize != std::string::npos) + return cpyrt_PyText_FromStringAndSize(*(char**)address, + (Py_ssize_t)fMaxSize); + return this->CStringConverter::FromMemory(address); +} + +//---------------------------------------------------------------------------- +bool cpyrt::VoidArrayConverter::GetAddressSpecialCase(PyObject* pyobject, + void*& address) { + // (1): C++11 style "null pointer" + if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { + address = nullptr; + return true; + } + + // (2): allow integer zero to act as a null pointer (C NULL), no deriveds + if (PyInt_CheckExact(pyobject) || PyLong_CheckExact(pyobject)) { + intptr_t val = (intptr_t)PyLong_AsLongLong(pyobject); + if (val == 0l) { + address = (void*)val; + return true; + } + + return false; + } + + // (3): opaque PyCapsule (CObject in older pythons) from somewhere + if (cpyrt_PyCapsule_CheckExact(pyobject)) { + address = (void*)cpyrt_PyCapsule_GetPointer(pyobject, nullptr); + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::VoidArrayConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // just convert pointer if it is a C++ object + CPPInstance* pyobj = GetCppInstance(pyobject); + para.fValue.fVoidp = nullptr; + if (pyobj) { + // depending on memory policy, some objects are no longer owned when passed + // to C++ + if (!fKeepControl && !UseStrictOwnership(ctxt)) + pyobj->CppOwns(); + + // set pointer (may be null) and declare success + para.fValue.fVoidp = pyobj->GetObject(); + para.fTypeCode = 'p'; + return true; + } + + // handle special cases + if (GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { + para.fTypeCode = 'p'; + return true; + } + + // allow ctypes voidp (which if got as a buffer will return void**, not + // void*); use isintance instead of an exact check, b/c c_void_p is the type + // mapper for typedefs of void* (typically opaque handles) + if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_void_p))) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + } + + // allow any other ctypes pointer type + if (IsCTypesArrayOrPointer(pyobject)) { + void** payload = (void**)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + if (payload) { + para.fValue.fVoidp = *payload; + para.fTypeCode = 'p'; + return true; + } + } + + // final try: attempt to get buffer + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, '*', 1, para.fValue.fVoidp, false); + + // ok if buffer exists (can't perform any useful size checks) + if (para.fValue.fVoidp && buflen != 0) { + para.fTypeCode = 'p'; + return true; + } + + // give up + return false; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::VoidArrayConverter::FromMemory(void* address) { + // nothing sensible can be done, just return
as pylong + if (!address || *(uintptr_t*)address == 0) { + Py_INCREF(gNullPtrObject); + return gNullPtrObject; + } + return CreatePointerView(*(uintptr_t**)address); +} + +//---------------------------------------------------------------------------- +bool cpyrt::VoidArrayConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // just convert pointer if it is a C++ object + CPPInstance* pyobj = GetCppInstance(value); + if (pyobj) { + // depending on memory policy, some objects are no longer owned when passed + // to C++ + if (!fKeepControl && CallContext::sMemoryPolicy != CallContext::kUseStrict) + pyobj->CppOwns(); + + // set pointer (may be null) and declare success + *(void**)address = pyobj->GetObject(); + return true; + } + + // handle special cases + void* ptr = nullptr; + if (GetAddressSpecialCase(value, ptr)) { + *(void**)address = ptr; + return true; + } + + // final try: attempt to get buffer + void* buf = nullptr; + Py_ssize_t buflen = Utility::GetBuffer(value, '*', 1, buf, false); + if (!buf || buflen == 0) + return false; + + *(void**)address = buf; + return true; +} + +#if __cplusplus >= 202002L + +namespace cppjit::cpyrt { + +class StdSpanConverter : public InstanceConverter { +public: + StdSpanConverter(std::string const& typeName, interop::TCppScope_t klass, + bool keepControl = false) + : InstanceConverter{klass, keepControl}, fTypeName{typeName} {} + + ~StdSpanConverter() { + if (fHasBuffer) { + PyBuffer_Release(&fBufinfo); + } + } + + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + bool HasState() override { return true; } + +private: + std::string fTypeName; + std::span fBuffer; + bool fHasBuffer = false; + Py_buffer fBufinfo; +}; + +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +bool cpyrt::StdSpanConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + auto typecodeFound = Utility::TypecodeMap().find(fTypeName); + + // attempt to get buffer if the C++ type maps to a buffer type + if (typecodeFound == Utility::TypecodeMap().end() || + !PyObject_CheckBuffer(pyobject)) { + // Fall back to regular InstanceConverter + return this->InstanceConverter::SetArg(pyobject, para, ctxt); + } + + Py_ssize_t buflen = 0; + char typecode = typecodeFound->second; + memset(&fBufinfo, 0, sizeof(Py_buffer)); + + if (PyObject_GetBuffer(pyobject, &fBufinfo, PyBUF_FORMAT) == 0) { + if (!strchr(fBufinfo.format, typecode)) { + PyErr_Format(PyExc_TypeError, + "buffer has incompatible type: expected '%c' for C++ type " + "'%s', but got format '%s'", + typecode, fTypeName.c_str(), + fBufinfo.format ? fBufinfo.format : ""); + PyBuffer_Release(&fBufinfo); + return false; + } + buflen = + Utility::GetBuffer(pyobject, typecode, 1, para.fValue.fVoidp, false); + } + + // ok if buffer exists (can't perform any useful size checks) + if (para.fValue.fVoidp && buflen != 0) { + // We assume the layout for any std::span is the same, and just use + // std::span as a placeholder. Not elegant, but works. + fBuffer = std::span{(std::size_t*)para.fValue.fVoidp, + static_cast(buflen)}; + fHasBuffer = true; + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + return false; +} + +#endif // __cplusplus >= 202002L + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ + cpyrt::name##ArrayConverter::name##ArrayConverter(cdims_t dims) \ + : fShape(dims) { \ + fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; \ + } \ + \ + bool cpyrt::name##ArrayConverter::SetArg( \ + PyObject* pyobject, Parameter& para, CallContext* ctxt) { \ + /* filter ctypes first b/c their buffer conversion will be wrong */ \ + bool convOk = false; \ + \ + /* 2-dim case: ptr-ptr types */ \ + if (!convOk && fShape.ndim() == 2) { \ + if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (Py_TYPE(pyobject) == GetCTypesType(ct_c_void_p)) { \ + /* special case: pass address of c_void_p buffer to return the address \ + */ \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (LowLevelView_Check(pyobject) && \ + ((LowLevelView*)pyobject)->fBufInfo.ndim == 2 && \ + strchr(((LowLevelView*)pyobject)->fBufInfo.format, code)) { \ + para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } \ + \ + /* 1-dim (accept pointer), or unknown (accept pointer as cast) */ \ + if (!convOk) { \ + PyTypeObject* ctypes_type = GetCTypesType(ct_##ctype); \ + if (Py_TYPE(pyobject) == ctypes_type) { \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; \ + para.fTypeCode = 'V'; \ + convOk = true; \ + } else if (IsPyCArgObject(pyobject)) { \ + cpyrt_tagPyCArgObject* carg = (cpyrt_tagPyCArgObject*)pyobject; \ + if (carg->obj && Py_TYPE(carg->obj) == ctypes_type) { \ + para.fValue.fVoidp = \ + (void*)((cpyrt_tagCDataObject*)carg->obj)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } else if (LowLevelView_Check(pyobject) && \ + strchr(((LowLevelView*)pyobject)->fBufInfo.format, code)) { \ + para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } \ + \ + /* cast pointer type */ \ + if (!convOk) { \ + bool ismulti = fShape.ndim() > 1; \ + convOk = CArraySetArg(pyobject, para, code, \ + ismulti ? sizeof(void*) : sizeof(type), true); \ + } \ + \ + /* memory management and offsetting */ \ + if (convOk) \ + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); \ + \ + return convOk; \ + } \ + \ + PyObject* cpyrt::name##ArrayConverter::FromMemory(void* address) { \ + if (!fIsFixed) \ + return CreateLowLevelView##suffix((type**)address, fShape); \ + return CreateLowLevelView##suffix(*(type**)address, fShape); \ + } \ + \ + bool cpyrt::name##ArrayConverter::ToMemory(PyObject* value, void* address, \ + PyObject* ctxt) { \ + if (fShape.ndim() <= 1 || fIsFixed) { \ + void* buf = nullptr; \ + Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(type), buf); \ + if (buflen == 0) \ + return false; \ + \ + Py_ssize_t oldsz = 1; \ + for (Py_ssize_t idim = 0; idim < fShape.ndim(); ++idim) { \ + if (fShape[idim] == UNKNOWN_SIZE) { \ + oldsz = -1; \ + break; \ + } \ + oldsz *= fShape[idim]; \ + } \ + if (fShape.ndim() != UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { \ + PyErr_SetString(PyExc_ValueError, "buffer too large for value"); \ + return false; \ + } \ + \ + if (fIsFixed) \ + memcpy(*(type**)address, buf, \ + (0 < buflen ? buflen : 1) * sizeof(type)); \ + else { \ + *(type**)address = (type*)buf; \ + fShape.ndim(1); \ + fShape[0] = buflen; \ + SetLifeLine(ctxt, value, (intptr_t)address); \ + } \ + \ + } else { /* multi-dim, non-flat array; assume structure matches */ \ + void* buf = nullptr; /* TODO: GetBuffer() assumes flat? */ \ + Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(void*), buf); \ + if (buflen == 0) \ + return false; \ + *(type**)address = (type*)buf; \ + SetLifeLine(ctxt, value, (intptr_t)address); \ + } \ + return true; \ + } + +//---------------------------------------------------------------------------- +// clang-format off +CPPJIT_IMPL_ARRAY_CONVERTER(Bool, c_bool, bool, '?', ) +CPPJIT_IMPL_ARRAY_CONVERTER(SChar, c_char, signed char, 'b', ) +CPPJIT_IMPL_ARRAY_CONVERTER(UChar, c_ubyte, unsigned char, 'B', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Byte, c_ubyte, std::byte, 'B', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Int8, c_byte, int8_t, 'b', _i8) +CPPJIT_IMPL_ARRAY_CONVERTER(UInt8, c_ubyte, uint8_t, 'B', _i8) +CPPJIT_IMPL_ARRAY_CONVERTER(Short, c_short, short, 'h', ) +CPPJIT_IMPL_ARRAY_CONVERTER(UShort, c_ushort, unsigned short, 'H', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Int, c_int, int, 'i', ) +CPPJIT_IMPL_ARRAY_CONVERTER(UInt, c_uint, unsigned int, 'I', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Long, c_long, long, 'l', ) +CPPJIT_IMPL_ARRAY_CONVERTER(ULong, c_ulong, unsigned long, 'L', ) +CPPJIT_IMPL_ARRAY_CONVERTER(LLong, c_longlong, long long, 'q', ) +CPPJIT_IMPL_ARRAY_CONVERTER(ULLong, c_ulonglong, unsigned long long, 'Q', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Float, c_float, float, 'f', ) +CPPJIT_IMPL_ARRAY_CONVERTER(Double, c_double, double, 'd', ) +CPPJIT_IMPL_ARRAY_CONVERTER(LDouble, c_longdouble, long double, 'g', ) +CPPJIT_IMPL_ARRAY_CONVERTER(ComplexF, c_fcomplex, std::complex, 'z', ) +CPPJIT_IMPL_ARRAY_CONVERTER(ComplexD, c_complex, std::complex, 'Z', ) +// clang-format on + +//---------------------------------------------------------------------------- +bool cpyrt::CStringArrayConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_c_char_p) || + (1 < fShape.ndim() && + PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_pointer)))) { + // 2nd predicate is ebatable: it's a catch-all for ctypes-styled + // multi-dimensional objects, which at this point does not check further + // dimensionality + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + para.fTypeCode = 'V'; + return true; + + } else if (PySequence_Check(pyobject) && !cpyrt_PyText_Check(pyobject) && + !PyBytes_Check(pyobject)) { + // for (auto& p : fBuffer) free(p); + fBuffer.clear(); + + size_t len = (size_t)PySequence_Size(pyobject); + if (len == (size_t)-1) { + PyErr_SetString(PyExc_ValueError, + "can not convert sequence object of unknown length"); + return false; + } + + fBuffer.reserve(len); + for (size_t i = 0; i < len; ++i) { + PyObject* item = PySequence_GetItem(pyobject, i); + if (item) { + Py_ssize_t sz; + const char* p = cpyrt_PyText_AsStringAndSize(item, &sz); + Py_DECREF(item); + + if (p) + fBuffer.push_back(p); + else { + PyErr_Format(PyExc_TypeError, "could not convert item %d to string", + (int)i); + return false; + } + + } else + return false; + } + + para.fValue.fVoidp = (void*)fBuffer.data(); + para.fTypeCode = 'p'; + return true; + } + + return SCharArrayConverter::SetArg(pyobject, para, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CStringArrayConverter::FromMemory(void* address) { + if (fIsFixed) + return CreateLowLevelView(*(char**)address, fShape); + else if (fShape[0] == UNKNOWN_SIZE) + return CreateLowLevelViewString((const char**)address, fShape); + return CreateLowLevelViewString(*(const char***)address, fShape); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::NonConstCStringArrayConverter::FromMemory(void* address) { + if (fIsFixed) + return CreateLowLevelView(*(char**)address, fShape); + else if (fShape[0] == UNKNOWN_SIZE) + return CreateLowLevelViewString((char**)address, fShape); + return CreateLowLevelViewString(*(char***)address, fShape); +} + +//- converters for special cases --------------------------------------------- +bool cpyrt::NullptrConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // Only allow C++11 style nullptr to pass + if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { + para.fValue.fVoidp = nullptr; + para.fTypeCode = 'p'; + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +template +static inline bool cpyrt_PyUnicodeAsBytes2Buffer(PyObject* pyobject, + T& buffer) { + PyObject* pybytes = nullptr; + if (PyBytes_Check(pyobject)) { + Py_INCREF(pyobject); + pybytes = pyobject; + } else if (PyUnicode_Check(pyobject)) { + pybytes = PyUnicode_AsUTF8String(pyobject); + } + + if (pybytes) { + Py_ssize_t len; + const char* cstr = nullptr; + PyBytes_AsStringAndSize(pybytes, (char**)&cstr, &len); + if (cstr) + buffer = T{cstr, (typename T::size_type)len}; + Py_DECREF(pybytes); + return (bool)cstr; + } + + return false; +} + +#define CPPJIT_IMPL_STRING_AS_PRIMITIVE_CONVERTER(name, type, F1, F2) \ + cpyrt::name##Converter::name##Converter(bool keepControl) \ + : InstanceConverter(interop::GetFullScope(#type), keepControl) {} \ + \ + bool cpyrt::name##Converter::SetArg(PyObject* pyobject, Parameter& para, \ + CallContext* ctxt) { \ + if (cpyrt_PyUnicodeAsBytes2Buffer(pyobject, fBuffer)) { \ + para.fValue.fVoidp = &fBuffer; \ + para.fTypeCode = 'V'; \ + return true; \ + } \ + \ + PyErr_Clear(); \ + if (!(PyInt_Check(pyobject) || PyLong_Check(pyobject))) { \ + bool result = InstanceConverter::SetArg(pyobject, para, ctxt); \ + para.fTypeCode = 'V'; \ + return result; \ + } \ + \ + return false; \ + } \ + \ + PyObject* cpyrt::name##Converter::FromMemory(void* address) { \ + if (address) \ + return InstanceConverter::FromMemory(address); \ + auto* empty = new type(); \ + return BindCppObjectNoCast(interop::TCppObject_t((void*)empty), fClass, \ + CPPInstance::kIsOwner); \ + } \ + \ + bool cpyrt::name##Converter::ToMemory(PyObject* value, void* address, \ + PyObject* ctxt) { \ + if (cpyrt_PyUnicodeAsBytes2Buffer(value, *((type*)address))) \ + return true; \ + return InstanceConverter::ToMemory(value, address, ctxt); \ + } + +CPPJIT_IMPL_STRING_AS_PRIMITIVE_CONVERTER(STLString, std::string, c_str, size) + +cpyrt::STLWStringConverter::STLWStringConverter(bool keepControl) + : InstanceConverter(interop::GetFullScope("std::wstring"), keepControl) {} + +bool cpyrt::STLWStringConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + if (PyUnicode_Check(pyobject)) { + Py_ssize_t len = cpyrt_PyUnicode_GET_SIZE(pyobject); + fBuffer.resize(len); + cpyrt_PyUnicode_AsWideChar(pyobject, &fBuffer[0], len); + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + if (!(PyInt_Check(pyobject) || PyLong_Check(pyobject))) { + bool result = InstancePtrConverter::SetArg(pyobject, para, ctxt); + para.fTypeCode = 'V'; + return result; + } + + return false; +} + +PyObject* cpyrt::STLWStringConverter::FromMemory(void* address) { + if (address) + return PyUnicode_FromWideChar(((std::wstring*)address)->c_str(), + ((std::wstring*)address)->size()); + wchar_t w = L'\0'; + return PyUnicode_FromWideChar(&w, 0); +} + +bool cpyrt::STLWStringConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + if (PyUnicode_Check(value)) { + Py_ssize_t len = cpyrt_PyUnicode_GET_SIZE(value); + wchar_t* buf = new wchar_t[len + 1]; + cpyrt_PyUnicode_AsWideChar(value, buf, len); + *((std::wstring*)address) = std::wstring(buf, len); + delete[] buf; + return true; + } + return InstanceConverter::ToMemory(value, address, ctxt); +} + +cpyrt::STLStringViewConverter::STLStringViewConverter(bool keepControl) + : InstanceConverter(interop::GetFullScope("std::string_view"), + keepControl) {} + +bool cpyrt::STLStringViewConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // normal instance convertion (eg. string_view object passed) + if (!PyInt_Check(pyobject) && !PyLong_Check(pyobject)) { + CallContextRAII noimp(ctxt); + if (InstanceConverter::SetArg(pyobject, para, ctxt)) { + para.fTypeCode = 'V'; + return true; + } else + PyErr_Clear(); + } + + // passing of a Python string; buffering done Python-side b/c str is immutable + Py_ssize_t len; + const char* cstr = cpyrt_PyText_AsStringAndSize(pyobject, &len); + if (cstr) { + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); + fBuffer = std::string_view(cstr, (std::string_view::size_type)len); + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + if (!CPPInstance_Check(pyobject)) + return false; + + // special case of a C++ std::string object; life-time management is left to + // the caller to ensure any external changes propagate correctly + if (CPPInstance_Check(pyobject)) { + static interop::TCppScope_t sStringID = + interop::GetUnderlyingScope(interop::GetFullScope("std::string")); + CPPInstance* pyobj = (CPPInstance*)pyobject; + if (pyobj->ObjectIsA() == sStringID) { + void* ptr = pyobj->GetObject(); + if (!ptr) + return false; // leaves prior conversion error for report + + PyErr_Clear(); + + fBuffer = *((std::string*)ptr); + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + } + + return false; +} + +PyObject* cpyrt::STLStringViewConverter::FromMemory(void* address) { + if (address) + return InstanceConverter::FromMemory(address); + auto* empty = new std::string_view(); + return BindCppObjectNoCast(empty, fClass, CPPInstance::kIsOwner); +} + +bool cpyrt::STLStringViewConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + // common case of simple object assignment + if (InstanceConverter::ToMemory(value, address, ctxt)) + return true; + + // assignment of a Python string; buffering done Python-side b/c str is + // immutable + Py_ssize_t len; + const char* cstr = cpyrt_PyText_AsStringAndSize(value, &len); + if (cstr) { + SetLifeLine(ctxt, value, (intptr_t)this); + *reinterpret_cast(address) = + std::string_view(cstr, (std::string_view::size_type)len); + return true; + } + + return false; +} + +bool cpyrt::STLStringMoveConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ std::string&&, set arg for call + int moveit_reason = 3; // move on temporary fBuffer + if (CPPInstance_Check(pyobject)) { + CPPInstance* pyobj = (CPPInstance*)pyobject; + if (pyobj->fFlags & CPPInstance::kIsRValue) { + pyobj->fFlags &= ~CPPInstance::kIsRValue; + moveit_reason = 2; + } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { + moveit_reason = 1; + } else + moveit_reason = 0; + } + + if (moveit_reason) { + bool result = this->STLStringConverter::SetArg(pyobject, para, ctxt); + if (!result && moveit_reason == 2) // restore the movability flag? + ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; + return result; + } + + PyErr_SetString(PyExc_ValueError, "object is not an rvalue"); + return false; // not a temporary or movable object +} + +//---------------------------------------------------------------------------- +template +bool cpyrt::InstancePtrConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { + // convert to C++ instance*, set arg for call + CPPInstance* pyobj = + GetCppInstance(pyobject, ISCONST ? fClass : interop::TCppScope_t{}); + if (!pyobj) { + if (GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { + para.fTypeCode = 'p'; // allow special cases such as nullptr + return true; + } + + // not a cppjit object (TODO: handle SWIG etc.) + return false; + } + + // smart pointers should only extract the pointer if this is NOT an implicit + // conversion to another smart pointer + if (pyobj->IsSmart() && IsConstructor(ctxt->fFlags) && + interop::IsSmartPtr(ctxt->fCurScope)) + return false; + + interop::TCppScope_t oisa = pyobj->ObjectIsA(); + if (oisa && (oisa == fClass || interop::IsSubclass(oisa, fClass))) { + // depending on memory policy, some objects need releasing when passed into + // functions + if (!KeepControl() && !UseStrictOwnership(ctxt)) + pyobj->CppOwns(); + + // calculate offset between formal and actual arguments + para.fValue.fVoidp = pyobj->GetObject(); + if (oisa != fClass) { + para.fValue.fIntPtr += interop::GetBaseOffset( + oisa, fClass, para.fValue.fVoidp, 1 /* up-cast */); + } + + // set pointer (may be null) and declare success + para.fTypeCode = 'p'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +template +PyObject* cpyrt::InstancePtrConverter::FromMemory(void* address) { + // construct python object from C++ instance read at
+ if (ISCONST) + return BindCppObject(*(void**)address, fClass); // by pointer value + return BindCppObject(address, fClass, + CPPInstance::kIsReference); // modifiable +} + +//---------------------------------------------------------------------------- +template +bool cpyrt::InstancePtrConverter::ToMemory(PyObject* value, + void* address, + PyObject* /* ctxt */) { + // convert to C++ instance, write it at
+ CPPInstance* pyobj = + GetCppInstance(value, ISCONST ? fClass : interop::TCppScope_t{}); + if (!pyobj) { + void* ptr = nullptr; + if (GetAddressSpecialCase(value, ptr)) { + *(void**)address = ptr; // allow special cases such as nullptr + return true; + } + + // not a cppjit object (TODO: handle SWIG etc.) + return false; + } + + if (interop::IsSubclass(pyobj->ObjectIsA(), fClass)) { + // depending on memory policy, some objects need releasing when passed into + // functions + if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict) + ((CPPInstance*)value)->CppOwns(); + + *(void**)address = pyobj->GetObject(); + return true; + } + + return false; +} + +// TODO: CONSOLIDATE Instance, InstanceRef, InstancePtr ... + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ instance, set arg for call + CPPInstance* pyobj = GetCppInstance(pyobject, fClass); + if (pyobj) { + auto oisa = pyobj->ObjectIsA(); + if (oisa && ((oisa == (interop::IsTypedefed(fClass) + ? interop::GetUnderlyingScope(fClass) + : fClass)) || + interop::IsSubclass(oisa, fClass))) { + // calculate offset between formal and actual arguments + para.fValue.fVoidp = pyobj->GetObject(); + if (!para.fValue.fVoidp) + return false; + + if (oisa != fClass) { + para.fValue.fIntPtr += interop::GetBaseOffset( + pyobj->ObjectIsA(), fClass, para.fValue.fVoidp, 1 /* up-cast */); + } + + para.fTypeCode = 'V'; + return true; + } + } + + return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceConverter::FromMemory(void* address) { + // This should not need a cast (ie. BindCppObjectNoCast), but performing the + // cast here means callbacks receive down-casted object when passed by-ptr, + // which is needed for object identity. The latter case is assumed to be more + // common than conversion of (global) objects. + return BindCppObject((interop::TCppObject_t)address, fClass); +} + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // assign value to C++ instance living at
through assignment + // operator + PyObject* pyobj = BindCppObjectNoCast(address, fClass); + PyObject* result = + PyObject_CallMethodOneArg(pyobj, PyStrings::gAssign, value); + Py_DECREF(pyobj); + + if (result) { + Py_DECREF(result); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ instance&, set arg for call + CPPInstance* pyobj = + GetCppInstance(pyobject, fIsConst ? fClass : interop::TCppScope_t{}); + if (pyobj) { + + // reject moves + if (pyobj->fFlags & CPPInstance::kIsRValue) + return false; + + // smart pointers can end up here in case of a move, so preferentially match + // the smart type directly + bool argset = false; + interop::TCppScope_t cls; + if (pyobj->IsSmart()) { + cls = pyobj->ObjectIsA(false); + if (cls && interop::IsSubclass(cls, fClass)) { + para.fValue.fVoidp = pyobj->GetObjectRaw(); + argset = true; + } + } + + if (!argset) { + cls = pyobj->ObjectIsA(); + if (cls && interop::IsSubclass(cls, fClass)) { + para.fValue.fVoidp = pyobj->GetObject(); + argset = true; + } + } + + if (argset) { + // do not allow null pointers through references + if (!para.fValue.fVoidp) { + PyErr_SetString(PyExc_ReferenceError, + "attempt to access a null-pointer"); + return false; + } + + // calculate offset between formal and actual arguments + if (cls != fClass) { + para.fValue.fIntPtr += interop::GetBaseOffset( + cls, fClass, para.fValue.fVoidp, 1 /* up-cast */); + } + + para.fTypeCode = 'V'; + return true; + } + } + + if (!fIsConst) // no implicit conversion possible + return false; + + return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceRefConverter::FromMemory(void* address) { + return BindCppObjectNoCast((interop::TCppObject_t)address, fClass, + CPPInstance::kIsReference); +} + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceMoveConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // convert to C++ instance&&, set arg for call + CPPInstance* pyobj = + GetCppInstance(pyobject, fClass, true /* accept_rvalue */); + if (!pyobj || (pyobj->fFlags & CPPInstance::kIsLValue)) { + // implicit conversion is fine as the temporary by definition is moveable + return (bool)ConvertImplicit(fClass, pyobject, para, ctxt); + } + + // moving is same as by-ref, but have to check that move is allowed + int moveit_reason = 0; + if (pyobj->fFlags & CPPInstance::kIsRValue) { + pyobj->fFlags &= ~CPPInstance::kIsRValue; + moveit_reason = 2; + } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { + moveit_reason = 1; + } + + if (moveit_reason) { + bool result = this->InstanceRefConverter::SetArg(pyobject, para, ctxt); + if (!result && moveit_reason == 2) // restore the movability flag? + ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; + return result; + } + + PyErr_SetString(PyExc_ValueError, "object is not an rvalue"); + return false; // not a temporary or movable object +} + +//---------------------------------------------------------------------------- +template +bool cpyrt::InstancePtrPtrConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { + // convert to C++ instance**, set arg for call + CPPInstance* pyobj = GetCppInstance(pyobject); + if (!pyobj) { + if (!ISREFERENCE && + (pyobject == gNullPtrObject || pyobject == gDefaultObject)) { + // allow nullptr as a special case + para.fValue.fVoidp = nullptr; + para.fTypeCode = 'p'; + return true; + } + return false; // not a cppjit object (TODO: handle SWIG etc.) + } + + if (interop::IsSubclass(pyobj->ObjectIsA(), fClass)) { + // depending on memory policy, some objects need releasing when passed into + // functions + if (!KeepControl() && !UseStrictOwnership(ctxt)) + pyobj->CppOwns(); + + // set pointer (may be null) and declare success + if (pyobj->fFlags & CPPInstance::kIsReference) // already a ptr to object? + para.fValue.fVoidp = pyobj->GetObjectRaw(); + else + para.fValue.fVoidp = &pyobj->GetObjectRaw(); + para.fTypeCode = ISREFERENCE ? 'V' : 'p'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +template +PyObject* +cpyrt::InstancePtrPtrConverter::FromMemory(void* address) { + // construct python object from C++ instance* read at
+ return BindCppObject(*(void**)address, fClass, + CPPInstance::kIsReference | CPPInstance::kIsPtrPtr); +} + +//---------------------------------------------------------------------------- +template +bool cpyrt::InstancePtrPtrConverter::ToMemory( + PyObject* value, void* address, PyObject* /* ctxt */) { + // convert to C++ instance*, write it at
+ CPPInstance* pyobj = GetCppInstance(value); + if (!pyobj) { + if (value == gNullPtrObject || value == gDefaultObject) { + // allow nullptr as a special case + *(void**)address = nullptr; + return true; + } + return false; // not a cppjit object (TODO: handle SWIG etc.) + } + + if (interop::IsSubclass(pyobj->ObjectIsA(), fClass)) { + // depending on memory policy, some objects need releasing when passed into + // functions + if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict) + pyobj->CppOwns(); + + // register the value for potential recycling + MemoryRegulator::RegisterPyObject(pyobj, pyobj->GetObject()); + + // set pointer (may be null) and declare success + *(void**)address = pyobj->GetObject(); + return true; + } + + return false; +} + +namespace cppjit::cpyrt { +// Instantiate the templates +template class cpyrt::InstancePtrConverter; +template class cpyrt::InstancePtrConverter; +template class cpyrt::InstancePtrPtrConverter; +template class cpyrt::InstancePtrPtrConverter; +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceArrayConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* txt */) { + // convert to C++ instance**, set arg for call + while (PyTuple_Check(pyobject) && !TupleOfInstances_CheckExact(pyobject)) { + if (PyTuple_Size(pyobject) > 0) + pyobject = PyTuple_GetItem(pyobject, 0); + else + return false; + } + if (!TupleOfInstances_CheckExact(pyobject)) + return false; // no guarantee that the tuple is okay + + // treat the first instance of the tuple as the start of the array, and pass + // it by pointer (TODO: store and check sizes) + if (PyTuple_Size(pyobject) < 1) + return false; + + PyObject* first = PyTuple_GetItem(pyobject, 0); + if (!CPPInstance_Check(first)) + return false; // should not happen + + if (interop::IsSubclass(((CPPInstance*)first)->ObjectIsA(), fClass)) { + // no memory policies supported; set pointer (may be null) and declare + // success + para.fValue.fVoidp = ((CPPInstance*)first)->GetObject(); + para.fTypeCode = 'p'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceArrayConverter::FromMemory(void* address) { + // construct python tuple of instances from C++ array read at
+ return BindCppObjectArray(*(char**)address, fClass, fShape); +} + +//---------------------------------------------------------------------------- +bool cpyrt::InstanceArrayConverter::ToMemory(PyObject* /* value */, + void* /* address */, + PyObject* /* ctxt */) { + // convert to C++ array of instances, write it at
+ + // TODO: need to have size both for the array and from the input + PyErr_SetString(PyExc_NotImplementedError, + "access to C-arrays of objects not yet implemented!"); + return false; +} + +//___________________________________________________________________________ +// Cling WORKAROUND -- classes for STL iterators are completely undefined in +// that they come in a bazillion different guises, so just do whatever +bool cpyrt::STLIteratorConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + if (!CPPInstance_Check(pyobject)) + return false; + + // just set the pointer value, no check + CPPInstance* pyobj = (CPPInstance*)pyobject; + para.fValue.fVoidp = pyobj->GetObject(); + para.fTypeCode = 'V'; + return true; +} +// -- END Cling WORKAROUND + +//---------------------------------------------------------------------------- +bool cpyrt::VoidPtrRefConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ void*&, set arg for call + CPPInstance* pyobj = GetCppInstance(pyobject); + if (pyobj) { + para.fValue.fVoidp = &pyobj->GetObjectRaw(); + para.fTypeCode = 'V'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +cpyrt::VoidPtrPtrConverter::VoidPtrPtrConverter(cdims_t dims, + const std::string& failureMsg) + : fShape(dims), fFailureMsg(failureMsg) { + fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; +} + +//---------------------------------------------------------------------------- +bool cpyrt::VoidPtrPtrConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // convert to C++ void**, set arg for call + CPPInstance* pyobj = GetCppInstance(pyobject); + if (pyobj) { + // this is a C++ object, take and set its address + para.fValue.fVoidp = &pyobj->GetObjectRaw(); + para.fTypeCode = 'p'; + return true; + } else if (IsPyCArgObject(pyobject)) { + cpyrt_tagPyCArgObject* carg = (cpyrt_tagPyCArgObject*)pyobject; + if (carg->obj) { + para.fValue.fVoidp = (void*)((cpyrt_tagCDataObject*)carg->obj)->b_ptr; + para.fTypeCode = 'p'; + return true; + } + } + + // buffer objects are allowed under "user knows best" (this includes the + // buffer interface to ctypes.c_void_p, which results in a void**) + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, '*', 1, para.fValue.fVoidp, false); + + // ok if buffer exists (can't perform any useful size checks) + if (para.fValue.fVoidp && buflen != 0) { + para.fTypeCode = 'p'; + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::VoidPtrPtrConverter::FromMemory(void* address) { + // read a void** from address; since this is unknown, uintptr_t is used (user + // can cast) + if (!address || *(ptrdiff_t*)address == 0) { + Py_INCREF(gNullPtrObject); + return gNullPtrObject; + } + if (!fIsFixed) + return CreatePointerView((uintptr_t**)address, fShape); + return CreatePointerView(*(uintptr_t**)address, fShape); +} + +//---------------------------------------------------------------------------- +bool cpyrt::PyObjectConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* /* ctxt */) { + // by definition: set and declare success + para.fValue.fVoidp = pyobject; + para.fTypeCode = 'p'; + return true; +} + +PyObject* cpyrt::PyObjectConverter::FromMemory(void* address) { + // construct python object from C++ PyObject* read at
+ PyObject* pyobject = *((PyObject**)address); + + if (!pyobject) { + Py_RETURN_NONE; + } + + Py_INCREF(pyobject); + return pyobject; +} + +bool cpyrt::PyObjectConverter::ToMemory(PyObject* value, void* address, + PyObject* /* ctxt */) { + // no conversion needed, write at
+ Py_INCREF(value); + Py_XDECREF(*((PyObject**)address)); + *((PyObject**)address) = value; + return true; +} + +//- function pointer converter ----------------------------------------------- +static unsigned int sWrapperCounter = 0; +// cache mapping signature/return type to python callable and corresponding +// wrapper +typedef std::string RetSigKey_t; +static std::unordered_map> sWrapperFree; +static std::unordered_map> + sWrapperLookup; +static std::unordered_map> + sWrapperWeakRefs; +static std::unordered_map sWrapperReference; + +static PyObject* WrapperCacheEraser(PyObject*, PyObject* pyref) { + auto ipos = sWrapperWeakRefs.find(pyref); + if (ipos != sWrapperWeakRefs.end()) { + auto key = ipos->second.second; + + // disable this callback and store on free list for possible re-use + void* wpraddress = ipos->second.first; + PyObject** oldref = sWrapperReference[wpraddress]; + const auto& lookup = sWrapperLookup.find(key); + if (lookup != sWrapperLookup.end()) + lookup->second.erase(*oldref); + *oldref = nullptr; // to detect deletions + sWrapperFree[ipos->second.second].push_back(wpraddress); + + // clean up and remove weak reference from admin + Py_DECREF(ipos->first); + sWrapperWeakRefs.erase(ipos); + } + + Py_RETURN_NONE; +} +static PyMethodDef gWrapperCacheEraserMethodDef = { + const_cast("internal_WrapperCacheEraser"), + (PyCFunction)WrapperCacheEraser, METH_O, nullptr}; + +static void* PyFunction_AsCPointer(PyObject* pyobject, + const std::string& rettype, + const std::string& signature, + bool allowCppInstance) { + // Convert a bound C++ function pointer or callable python object to a C-style + // function pointer. The former is direct, the latter involves a JIT-ed + // wrapper. + static PyObject* sWrapperCacheEraser = + PyCFunction_New(&gWrapperCacheEraserMethodDef, nullptr); + + // FIXME: avoid string comparisons and parsing + std::string true_signature = signature; + + if (true_signature.rfind("(void)") != std::string::npos) + true_signature = true_signature.substr(0, true_signature.size() - 6) + "()"; + + using namespace cppjit::cpyrt; + + if (CPPOverload_Check(pyobject)) { + CPPOverload* ol = (CPPOverload*)pyobject; + if (!ol->fMethodInfo || ol->fMethodInfo->fMethods.empty()) + return nullptr; + + // find the overload with matching signature + for (auto& m : ol->fMethodInfo->fMethods) { + PyObject* sig = m->GetSignature(false); + bool found = true_signature == cpyrt_PyText_AsString(sig); + Py_DECREF(sig); + if (found) { + void* fptr = (void*)m->GetFunctionAddress(); + if (fptr) + return fptr; + break; // fall-through, with calling through Python + } + } + // FIXME: maybe we should try BestOverloadFunctionMatch before failing + // FIXME: Should we fall-through, with calling through Python + return nullptr; + } + + if (TemplateProxy_Check(pyobject)) { + // get the actual underlying template matching the signature + TemplateProxy* pytmpl = (TemplateProxy*)pyobject; + std::string fullname = pytmpl->fTI->fCppName; + if (pytmpl->fTemplateArgs) + fullname += cpyrt_PyText_AsString(pytmpl->fTemplateArgs); + interop::TCppScope_t scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; + interop::TCppMethod_t cppmeth = + interop::GetMethodTemplate(scope, fullname, true_signature); + if (cppmeth) { + void* fptr = (void*)interop::GetFunctionAddress(cppmeth, false); + if (fptr) + return fptr; + } + // FIXME: Should we fall-through, with calling through Python + return nullptr; + } + + if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_funcptr))) { + // ctypes function pointer + void* fptr = *(void**)((cpyrt_tagCDataObject*)pyobject)->b_ptr; + return fptr; + } + + if (PyCallable_Check(pyobject) && + (allowCppInstance || !CPPInstance_Check(pyobject))) { + // generic python callable: create a C++ wrapper function + // Sometimes we don't want to take this branch if the object is a C++ + // instance, because C++ doesn't allow converting functor objects to + // function pointers, but only to std::function. + void* wpraddress = nullptr; + + // re-use existing wrapper if possible + auto key = rettype + true_signature; + const auto& lookup = sWrapperLookup.find(key); + if (lookup != sWrapperLookup.end()) { + const auto& existing = lookup->second.find(pyobject); + if (existing != lookup->second.end() && + *sWrapperReference[existing->second] == pyobject) + wpraddress = existing->second; + } + + // check for a pre-existing, unused, wrapper if not found + if (!wpraddress) { + const auto& freewrap = sWrapperFree.find(key); + if (freewrap != sWrapperFree.end() && !freewrap->second.empty()) { + wpraddress = freewrap->second.back(); + freewrap->second.pop_back(); + *(sWrapperReference[wpraddress]) = pyobject; + sWrapperLookup[key][pyobject] = wpraddress; + PyObject* wref = PyWeakref_NewRef(pyobject, sWrapperCacheEraser); + if (wref) + sWrapperWeakRefs[wref] = std::make_pair(wpraddress, key); + else + PyErr_Clear(); // happens for builtins which don't need this + } + } + + // create wrapper if no re-use possible + if (!wpraddress) { + if (!Utility::IncludePython()) + return nullptr; + + // extract argument types + const std::vector& argtypes = + TypeManip::extract_arg_types(true_signature); + int nArgs = (int)argtypes.size(); + + // wrapper name + std::ostringstream wname; + wname << "fptr_wrap" << ++sWrapperCounter; + + // build wrapper function code + std::ostringstream code; + code << "namespace __cppjit_internal {\n " << rettype << " " + << wname.str() << "("; + for (int i = 0; i < nArgs; ++i) { + code << argtypes[i] << " arg" << i; + if (i != nArgs - 1) + code << ", "; + } + code << ") {\n" + << " cppjit::cpyrt::PythonGILRAII python_gil_raii;\n"; + + // start function body + Utility::ConstructCallbackPreamble(rettype, argtypes, code); + + // create a referenceable pointer + PyObject** ref = new PyObject* {pyobject}; + + // function call itself and cleanup + code << " PyObject** ref = (PyObject**)" << (intptr_t)ref + << ";\n" + " PyObject* pyresult = nullptr;\n" + " if (*ref) pyresult = PyObject_CallFunctionObjArgs(*ref"; + for (int i = 0; i < nArgs; ++i) + code << ", pyargs[" << i << "]"; + code << ", NULL);\n" + " else PyErr_SetString(PyExc_TypeError, \"callable was " + "deleted\");\n"; + + // close + Utility::ConstructCallbackReturn(rettype, nArgs, code); + + // end of namespace + code << "}"; + + // finally, compile the code + if (!interop::Compile(code.str())) + return nullptr; + + // TODO: is there no easier way? + static interop::TCppScope_t scope = + interop::GetScope("__cppjit_internal"); + const auto& methods = interop::GetMethodsFromName(scope, wname.str()); + wpraddress = interop::GetFunctionAddress(methods[0], false); + sWrapperReference[wpraddress] = ref; + + // cache the new wrapper + sWrapperLookup[key][pyobject] = wpraddress; + PyObject* wref = PyWeakref_NewRef(pyobject, sWrapperCacheEraser); + if (wref) + sWrapperWeakRefs[wref] = std::make_pair(wpraddress, key); + else + PyErr_Clear(); // happens for builtins which don't need this + } + + // now pass the pointer to the wrapper function (may be null) + return wpraddress; + } + + return nullptr; +} + +bool cpyrt::FunctionPointerConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { + // special case: allow nullptr singleton: + if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { + para.fValue.fVoidp = nullptr; + para.fTypeCode = 'p'; + return true; + } + + // normal case, get a function pointer + void* fptr = + PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance); + if (fptr) { + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); + para.fValue.fVoidp = fptr; + para.fTypeCode = 'p'; + return true; + } + + return false; +} + +PyObject* cpyrt::FunctionPointerConverter::FromMemory(void* address) { + // A function pointer in clang is represented by a Type, not a FunctionDecl + // and it's not possible to get the latter from the former: the backend will + // need to support both. Since that is far in the future, we'll use a + // std::function instead. + if (address) + return Utility::FuncPtr2StdFunction(fRetType, fSignature, *(void**)address); + PyErr_SetString(PyExc_TypeError, "can not convert null function pointer"); + return nullptr; +} + +bool cpyrt::FunctionPointerConverter::ToMemory(PyObject* pyobject, + void* address, PyObject* ctxt) { + // special case: allow nullptr singleton: + if (pyobject == gNullPtrObject || pyobject == gDefaultObject) { + *((void**)address) = nullptr; + return true; + } + + // normal case, get a function pointer + void* fptr = + PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance); + if (fptr) { + SetLifeLine(ctxt, pyobject, (intptr_t)address); + *((void**)address) = fptr; + return true; + } + + return false; +} + +//- std::function converter -------------------------------------------------- +bool cpyrt::StdFunctionConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + // prefer normal "object" conversion + CallContextRAII noimp(ctxt); + if (fConverter->SetArg(pyobject, para, ctxt)) + return true; + + PyErr_Clear(); + + // else create a wrapper function + if (this->FunctionPointerConverter::SetArg(pyobject, para, ctxt)) { + // retrieve the wrapper pointer and capture it in a temporary std::function, + // then try normal conversion a second time + PyObject* func = + this->FunctionPointerConverter::FromMemory(¶.fValue.fVoidp); + if (func) { + SetLifeLine(ctxt->fPyContext, func, (intptr_t)this); + bool result = fConverter->SetArg(func, para, ctxt); + if (result) + ctxt->AddTemporary(func); + else + Py_DECREF(func); + return result; + } + } + + return false; +} + +PyObject* cpyrt::StdFunctionConverter::FromMemory(void* address) { + return fConverter->FromMemory(address); +} + +bool cpyrt::StdFunctionConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + // if the value is not an std::function<> but a generic Python callable, the + // conversion is done through the assignment, which may involve a temporary + if (address) + SetLifeLine(ctxt, value, (intptr_t)address); + return fConverter->ToMemory(value, address, ctxt); +} + +//- smart pointer converters ------------------------------------------------- +bool cpyrt::SmartPtrConverter::SetArg(PyObject* pyobject, Parameter& para, + CallContext* ctxt) { + char typeCode = fIsRef ? 'p' : 'V'; + + if (!CPPInstance_Check(pyobject)) { + // TODO: not sure how this is correct for pass-by-ref nor does it help with + // implicit conversions for pass-by-value + if (fIsRef && GetAddressSpecialCase(pyobject, para.fValue.fVoidp)) { + para.fTypeCode = typeCode; // allow special cases such as nullptr + return true; + } + + return false; + } + + CPPInstance* pyobj = (CPPInstance*)pyobject; + interop::TCppScope_t oisa = pyobj->ObjectIsA(); + + // for the case where we have a 'hidden' smart pointer: + if (interop::TCppScope_t tsmart = pyobj->GetSmartIsA()) { + if (interop::IsSubclass(tsmart, fSmartPtrType)) { + // depending on memory policy, some objects need releasing when passed + // into functions + if (!fKeepControl && !UseStrictOwnership(ctxt)) + ((CPPInstance*)pyobject)->CppOwns(); + + // calculate offset between formal and actual arguments + para.fValue.fVoidp = pyobj->GetSmartObject(); + if (tsmart != fSmartPtrType) { + para.fValue.fIntPtr += interop::GetBaseOffset( + tsmart, fSmartPtrType, para.fValue.fVoidp, 1 /* up-cast */); + } + + // set pointer (may be null) and declare success + para.fTypeCode = typeCode; + return true; + } + } + + // for the case where we have an 'exposed' smart pointer: + if (!pyobj->IsSmart() && interop::IsSubclass(oisa, fSmartPtrType)) { + // calculate offset between formal and actual arguments + para.fValue.fVoidp = pyobj->GetObject(); + if (oisa != fSmartPtrType) { + para.fValue.fIntPtr += interop::GetBaseOffset( + oisa, fSmartPtrType, para.fValue.fVoidp, 1 /* up-cast */); + } + + // set pointer (may be null) and declare success + para.fTypeCode = typeCode; + return true; + } + + // for the case where we have an ordinary object to convert + if (!pyobj->IsSmart() && interop::IsSubclass(oisa, fUnderlyingType)) { + // create the relevant smart pointer and make the pyobject "smart" + CPPInstance* pysmart = (CPPInstance*)ConvertImplicit( + fSmartPtrType, pyobject, para, ctxt, false); + if (!CPPInstance_Check(pysmart)) { + Py_XDECREF(pysmart); + return false; + } + + // copy internals from the fresh smart object to the original, making it + // smart + pyobj->GetObjectRaw() = pysmart->GetSmartObject(); + pyobj->SetSmart( + CreateScopeProxy(fSmartPtrType)); //(PyObject*)Py_TYPE(pysmart)); + pyobj->PythonOwns(); + pysmart->CppOwns(); + Py_DECREF(pysmart); + + return true; + } + + // final option, try mapping pointer types held (TODO: do not allow for + // non-const ref) Match on the declared underlying type, not the (possibly + // downcast) dereferenced object: a unique_ptr holding a Derived is + // still a unique_ptr and must not pass where a unique_ptr is + // expected. + if (pyobj->IsSmart() && + interop::IsSubclass(pyobj->GetSmartUnderlyingType(), fUnderlyingType)) { + para.fValue.fVoidp = ((CPPInstance*)pyobject)->GetSmartObject(); + para.fTypeCode = 'V'; + return true; + } + + return false; +} + +PyObject* cpyrt::SmartPtrConverter::FromMemory(void* address) { + if (!address || !fSmartPtrType) + return nullptr; + + return BindCppObjectNoCast(address, fSmartPtrType); +} + +bool cpyrt::SmartPtrConverter::ToMemory(PyObject* value, void* address, + PyObject*) { + // assign value to C++ instance living at
through assignment + // operator (this is similar to InstanceConverter::ToMemory, but prevents + // wrapping the smart ptr) + PyObject* pyobj = + BindCppObjectNoCast(address, fSmartPtrType, CPPInstance::kNoWrapConv); + PyObject* result = + PyObject_CallMethodOneArg(pyobj, PyStrings::gAssign, value); + Py_DECREF(pyobj); + + if (result) { + Py_DECREF(result); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +namespace { + +// clang libcpp and gcc use the same structure (ptr, size) +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) +struct faux_initlist { + typedef size_t size_type; + typedef void* iterator; + iterator _M_array; + size_type _M_len; +}; +#elif defined(_MSC_VER) +struct faux_initlist { + typedef char* iterator; + iterator _M_array; // ie. _First; + iterator _Last; +}; +#else +#define NO_KNOWN_INITIALIZER_LIST 1 +#endif + +} // unnamed namespace + +cpyrt::InitializerListConverter::InitializerListConverter( + interop::TCppScope_t klass, std::string const& value_type) + + : InstanceConverter{klass}, fValueTypeName{value_type}, + fValueType{interop::GetScope(value_type)}, + fValueSize{interop::SizeOfType(interop::GetType(value_type, true))} {} + +cpyrt::InitializerListConverter::~InitializerListConverter() { + for (Converter* converter : fConverters) { + if (converter && converter->HasState()) + delete converter; + } + if (fBuffer) + Clear(); +} + +void cpyrt::InitializerListConverter::Clear() { + if (fValueType) { + faux_initlist* fake = (faux_initlist*)fBuffer; +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) + for (faux_initlist::size_type i = 0; i < fake->_M_len; ++i) { +#elif defined(_MSC_VER) + for (size_t i = 0; (fake->_M_array + i * fValueSize) != fake->_Last; ++i) { +#endif + void* memloc = (char*)fake->_M_array + i * fValueSize; + interop::CallDestructor(fValueType, (interop::TCppObject_t)memloc); + } + } + + free(fBuffer); + fBuffer = nullptr; +} + +bool cpyrt::InitializerListConverter::SetArg(PyObject* pyobject, + Parameter& para, + CallContext* ctxt) { +#ifdef NO_KNOWN_INITIALIZER_LIST + return false; +#else + if (fBuffer) + Clear(); + + // convert the given argument to an initializer list temporary; this is purely + // meant to be a syntactic thing, so only _python_ sequences are allowed; + // bound C++ proxies (likely explicitly created std::initializer_list, go + // through an instance converter + if (!PySequence_Check(pyobject) || cpyrt_PyText_Check(pyobject) || + PyBytes_Check(pyobject)) + return false; + + if (CPPInstance_Check(pyobject)) + return this->InstanceConverter::SetArg(pyobject, para, ctxt); + + void* buf = nullptr; + Py_ssize_t buflen = + Utility::GetBuffer(pyobject, '*', (int)fValueSize, buf, true); + faux_initlist* fake = nullptr; + size_t entries = 0; + if (buf && buflen) { + // dealing with an array here, pass on whole-sale + fake = (faux_initlist*)malloc(sizeof(faux_initlist)); + fBuffer = (void*)fake; + fake->_M_array = (faux_initlist::iterator)buf; +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) + fake->_M_len = (faux_initlist::size_type)buflen; +#elif defined(_MSC_VER) + fake->_Last = fake->_M_array + buflen * fValueSize; +#endif + } else if (fValueSize) { + // Remove any errors set by GetBuffer(); note that if the argument was an + // array that failed to extract because of a type mismatch, the following + // will perform a (rather inefficient) copy. No warning is issued b/c e.g. + // numpy doesn't do so either. + PyErr_Clear(); + + // Can only construct empty lists, so use a fake initializer list. For that + // we need to construct default objects. Fail early if that cannot work. + if (fValueType && !interop::IsDefaultConstructable(fValueType)) { + PyErr_SetString( + PyExc_TypeError, + "default constructor needed for initializer list of objects"); + return false; + } + + size_t len = (size_t)PySequence_Size(pyobject); + fake = (faux_initlist*)malloc(sizeof(faux_initlist) + fValueSize * len); + fBuffer = (void*)fake; + fake->_M_array = + (faux_initlist::iterator)((char*)fake + sizeof(faux_initlist)); +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) + fake->_M_len = (faux_initlist::size_type)len; + for (faux_initlist::size_type i = 0; i < fake->_M_len; ++i) { +#elif defined(_MSC_VER) + fake->_Last = fake->_M_array + len * fValueSize; + for (size_t i = 0; (fake->_M_array + i * fValueSize) != fake->_Last; ++i) { +#endif + PyObject* item = PySequence_GetItem(pyobject, i); + bool convert_ok = false; + if (item) { + Converter* converter = CreateConverter(fValueTypeName); + if (!converter) { + if (CPPInstance_Check(item)) { + // by convention, use byte copy + memcpy((char*)fake->_M_array + i * fValueSize, + ((CPPInstance*)item)->GetObject(), fValueSize); + convert_ok = true; + } + } else { + void* memloc = (char*)fake->_M_array + i * fValueSize; + if (fValueType) { + // we need to construct a default object for the constructor to + // assign into; this is clunky, but the use of a copy constructor + // isn't much better as the Python object need not be a C++ object + memloc = (void*)interop::Construct(fValueType, memloc).data; + // We checked above that we are able to construct default objects of + // fValueType. + assert(memloc && ("failed to default construct object for type " + + fValueTypeName) + .c_str()); + entries += 1; + } + if (memloc) { + convert_ok = converter->ToMemory(item, memloc); + } + fConverters.emplace_back(converter); + } + + Py_DECREF(item); + } else + PyErr_Format(PyExc_TypeError, "failed to get item %d from sequence", + (int)i); + + if (!convert_ok) { +#if defined(_LIBCPP_INITIALIZER_LIST) || defined(__GNUC__) + fake->_M_len = (faux_initlist::size_type)entries; +#elif defined(_MSC_VER) + fake->_Last = fake->_M_array + entries * fValueSize; +#endif + Clear(); + return false; + } + } + } + + if (!fake) // no buffer and value size indeterminate + return false; + + para.fValue.fVoidp = (void*)fake; + para.fTypeCode = 'V'; // means ptr that backend has to free after call + return true; +#endif +} + +namespace cppjit::cpyrt { + +// raising converter to take out overloads +class NotImplementedConverter : public Converter { +public: + NotImplementedConverter(PyObject* errorType, std::string const& message) + : fErrorType{errorType}, fMessage{message} {} + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + +private: + PyObject* fErrorType; + std::string fMessage; +}; + +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +bool cpyrt::NotImplementedConverter::SetArg(PyObject*, Parameter&, + CallContext*) { + // raise a NotImplemented exception to take a method out of overload + // resolution + PyErr_SetString(fErrorType, fMessage.c_str()); + return false; +} + +//- helper to refactor some code from CreateConverter ------------------------ +static inline cppjit::cpyrt::Converter* +selectInstanceCnv(interop::TCppScope_t klass, const std::string& cpd, + cpyrt::cdims_t dims, bool isConst, bool control) { + using namespace cppjit::cpyrt; + Converter* result = nullptr; + + if (cpd == "**" || cpd == "*[]" || cpd == "&*") + result = new InstancePtrPtrConverter(klass, control); + else if (cpd == "*&") + result = new InstancePtrPtrConverter(klass, control); + else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { + if (isConst) + result = new InstancePtrConverter(klass, control); + else + result = new InstancePtrConverter(klass, control); + } else if (cpd == "&") + result = new InstanceRefConverter(klass, isConst); + else if (cpd == "&&") + result = new InstanceMoveConverter(klass); + else if (cpd == "[]" || dims) + result = new InstanceArrayConverter(klass, dims, false); + else if (cpd == "") // by value + result = new InstanceConverter(klass, true); + + return result; +} + +//- factories ---------------------------------------------------------------- +CPYRT_EXPORT +cppjit::cpyrt::Converter* +cppjit::cpyrt::CreateConverter(const std::string& fullType, cdims_t dims) { + // The matching of the fulltype to a converter factory goes through up to five + // levels: + // 1) full, exact match + // 2) match of decorated, unqualified type + // 3) accept const ref as by value + // 4) accept ref as pointer + // 5) generalized cases (covers basically all C++ classes) + // + // If all fails, void is used, which will generate a run-time warning when + // used. + + // an exactly matching converter is best + ConvFactories_t::iterator h = gConvFactories.find(fullType); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + + // resolve typedefs etc. + const std::string& resolvedType = interop::ResolveName(fullType); + + // a full, qualified matching converter is preferred + if (resolvedType != fullType) { + h = gConvFactories.find(resolvedType); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + + //-- nothing? ok, collect information about the type and possible + // qualifiers/decorators + bool isConst = strncmp(resolvedType.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedType); + std::string realType = TypeManip::clean_type(resolvedType, false, true); + + // accept unqualified type (as python does not know about qualifiers) + h = gConvFactories.find((isConst ? "const " : "") + realType + cpd); + if (h != gConvFactories.end()) + return (h->second)(dims); + + // mutable pointer references (T*&) are incompatible with Python's object + // model + if (!isConst && cpd == "*&") { + return new NotImplementedConverter{ + PyExc_TypeError, + "argument type '" + resolvedType + + "' is not supported: non-const references to pointers (T*&) allow a" + " function to replace the pointer itself. Python cannot represent " + "this safely. Consider changing the" + " C++ API to return the new pointer or use a wrapper"}; + } + + // drop const, as that is mostly meaningless to python (with the exception + // of c-strings, but those are specialized in the converter map) + if (isConst) { + h = gConvFactories.find(realType + cpd); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + + //-- still nothing? try pointer instead of array (for builtins) + if (cpd.compare(0, 3, "*[]") == 0) { + // special case, array of pointers + h = gConvFactories.find(realType + " ptr"); + if (h != gConvFactories.end()) { + // upstream treats the pointer type as the array element type, but that + // pointer is treated as a low-level view as well, unless it's a + // void*/char* so adjust the dims + if (realType != "void" && realType != "char") { + dim_t newdim = dims.ndim() == UNKNOWN_SIZE ? 2 : dims.ndim() + 1; + dims_t newdims = dims_t(newdim); + // TODO: sometimes the array size is known and can thus be verified; + // however, currently the meta layer does not provide this information + newdims[0] = dims ? dims[0] : UNKNOWN_SIZE; // the array + newdims[1] = UNKNOWN_SIZE; // the pointer + if (2 < newdim) { + for (int i = 2; i < (newdim - 1); ++i) + newdims[i] = dims[i - 1]; + } + + return (h->second)(newdims); + } + return (h->second)(dims); + } + + } else if (!cpd.empty() && (std::string::size_type)std::count( + cpd.begin(), cpd.end(), '*') == cpd.size()) { + // simple array; set or resize as necessary + h = gConvFactories.find(realType + " ptr"); + if (h != gConvFactories.end()) + return (h->second)((!dims && 1 < cpd.size()) ? dims_t(cpd.size()) : dims); + + } else if (2 <= cpd.size() && + (std::string::size_type)std::count(cpd.begin(), cpd.end(), '[') == + cpd.size() / 2) { + // fixed array, dims will have size if available + h = gConvFactories.find(realType + " ptr"); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + + //-- special case: initializer list + if (realType.compare(0, 21, "std::initializer_list") == 0) { + // get the type of the list and create a converter (TODO: get hold of + // value_type?) + auto pos = realType.find('<'); + std::string value_type = + realType.substr(pos + 1, realType.size() - pos - 2); + return new InitializerListConverter(interop::GetScope(realType), + value_type); + } + + //-- still nothing? use a generalized converter + bool control = cpd == "&" || isConst; + + //-- special case: std::function + auto pos = resolvedType.find("std::function<"); + if (pos == 0 /* std:: */ || pos == 6 /* const std:: */) { + + // get actual converter for normal passing + Converter* cnv = selectInstanceCnv(interop::GetScope(realType), cpd, dims, + isConst, control); + + if (cnv) { + // get the type of the underlying (TODO: use target_type?) + auto pos1 = resolvedType.find("(", pos + 14); + auto pos2 = resolvedType.rfind(")"); + if (pos1 != std::string::npos && pos2 != std::string::npos) { + auto sz1 = pos1 - pos - 14; + if (resolvedType[pos + 14 + sz1 - 1] == ' ') + sz1 -= 1; + + return new StdFunctionConverter( + cnv, resolvedType.substr(pos + 14, sz1), + resolvedType.substr(pos1, pos2 - pos1 + 1)); + } else if (cnv->HasState()) + delete cnv; + } + } + +#if __cplusplus >= 202002L + //-- special case: std::span + pos = resolvedType.find("span<"); + if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || + pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */) { + + // std::span::value_type == std::remove_cv_t: look it up + interop::TCppScope_t span_scope = interop::GetScope(realType); + interop::TCppType_t vtype = interop::ResolveType( + interop::GetTypeFromScope(interop::GetNamed("value_type", span_scope))); + if (span_scope && vtype) { + std::string value_type = interop::GetTypeAsString(vtype); + return new StdSpanConverter{value_type, span_scope}; + } + } +#endif + + // converters for known C++ classes and default (void*) + Converter* result = nullptr; + if (interop::TCppScope_t klass = interop::GetFullScope(realType)) { + interop::TCppScope_t raw; + if (interop::GetSmartPtrInfo(realType, &raw, nullptr)) { + if (cpd == "") { + result = new SmartPtrConverter(klass, raw, control); + } else if (cpd == "&") { + result = new SmartPtrConverter(klass, raw); + } else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { + result = new SmartPtrConverter(klass, raw, control, true); + } + } + + if (!result) { + // Cling WORKAROUND -- special case for STL iterators + if (Utility::IsSTLIterator(realType)) { + static STLIteratorConverter c; + result = &c; + } else + // -- Cling WORKAROUND + result = selectInstanceCnv(klass, cpd, dims, isConst, control); + } + } else { + std::smatch sm; + if (std::regex_search(resolvedType, sm, s_fnptr)) { + // this is a function pointer + auto pos1 = sm.position(0); + auto pos2 = resolvedType.rfind(')'); + result = new FunctionPointerConverter( + resolvedType.substr(0, pos1), + resolvedType.substr(pos1 + sm.length(), pos2 - 1)); + } + } + const std::string failure_msg("Failed to convert type: " + fullType + + "; resolved: " + resolvedType + + "; real: " + realType + "; cpd: " + cpd); + + if (!result && cpd == "&&") { + // for builtin, can use const-ref for r-ref + h = gConvFactories.find("const " + realType + "&"); + if (h != gConvFactories.end()) + return (h->second)(dims); + // else, unhandled moves + result = new NotImplementedConverter{PyExc_NotImplementedError, + "this method cannot (yet) be called"}; + } + + if (!result && h != gConvFactories.end()) + // converter factory available, use it to create converter + result = (h->second)(dims); + else if (!result) { + // default to something reasonable, assuming "user knows best" + if (cpd.size() == 2 && cpd != "&&") // "**", "*[]", "*&" + result = new VoidPtrPtrConverter(dims.ndim(), failure_msg); + else if (!cpd.empty()) + result = new VoidArrayConverter(/* keepControl= */ true, + failure_msg); // "user knows best" + else + // fails on use + result = new NotImplementedConverter{ + PyExc_NotImplementedError, "this method cannot (yet) be called"}; + } + + return result; +} + +CPYRT_EXPORT +cppjit::cpyrt::Converter* +cppjit::cpyrt::CreateConverter(interop::TCppType_t type, cdims_t dims) { + // The matching of the fulltype to a converter factory goes through up to five + // levels: + // 1) full, exact match + // 2) match of decorated, unqualified type + // 3) accept const ref as by value + // 4) accept ref as pointer + // 5) generalized cases (covers basically all C++ classes) + // + // If all fails, void is used, which will generate a run-time warning when + // used. + + // an exactly matching converter is best + std::string fullType = interop::GetTypeAsString(type); + ConvFactories_t::iterator h = gConvFactories.find(fullType); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + + // resolve typedefs etc. + interop::TCppType_t resolvedType = interop::ResolveType(type); + const std::string& resolvedTypeStr = interop::GetTypeAsString(resolvedType); + + // a full, qualified matching converter is preferred + if (resolvedTypeStr != fullType) { + h = gConvFactories.find(resolvedTypeStr); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + } + + //-- nothing? ok, collect information about the type and possible + // qualifiers/decorators + bool isConst = strncmp(resolvedTypeStr.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedTypeStr); + interop::TCppType_t realType = + interop::ResolveType(interop::GetRealType(type)); + std::string realTypeStr = interop::GetTypeAsString(realType); + std::string realUnresolvedTypeStr = + TypeManip::clean_type(fullType, false, true); + + // accept unqualified type (as python does not know about qualifiers) + h = gConvFactories.find((isConst ? "const " : "") + realTypeStr + cpd); + if (h != gConvFactories.end()) + return (h->second)(dims); + + // mutable pointer references (T*&) are incompatible with Python's object + // model + if (!isConst && cpd == "*&") { + return new NotImplementedConverter{ + PyExc_TypeError, + "argument type '" + resolvedTypeStr + + "' is not supported: non-const references to pointers (T*&) allow a" + " function to replace the pointer itself. Python cannot represent " + "this safely. Consider changing the" + " C++ API to return the new pointer or use a wrapper"}; + } + + // drop const, as that is mostly meaningless to python (with the exception + // of c-strings, but those are specialized in the converter map) + if (isConst) { + h = gConvFactories.find(realTypeStr + cpd); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + } + + //-- still nothing? try pointer instead of array (for builtins) + if (cpd.compare(0, 3, "*[]") == 0) { + // special case, array of pointers + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) { + // upstream treats the pointer type as the array element type, but that + // pointer is treated as a low-level view as well, unless it's a + // void*/char* so adjust the dims + if (realTypeStr != "void" && realTypeStr != "char") { + dim_t newdim = dims.ndim() == UNKNOWN_SIZE ? 2 : dims.ndim() + 1; + dims_t newdims = dims_t(newdim); + // TODO: sometimes the array size is known and can thus be verified; + // however, currently the meta layer does not provide this information + newdims[0] = dims ? dims[0] : UNKNOWN_SIZE; // the array + newdims[1] = UNKNOWN_SIZE; // the pointer + if (2 < newdim) { + for (int i = 2; i < (newdim - 1); ++i) + newdims[i] = dims[i - 1]; + } + + return (h->second)(newdims); + } + return (h->second)(dims); + } + + } else if (!cpd.empty() && (std::string::size_type)std::count( + cpd.begin(), cpd.end(), '*') == cpd.size()) { + // simple array; set or resize as necessary + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) + return (h->second)((!dims && 1 < cpd.size()) ? dims_t(cpd.size()) : dims); + + } else if (2 <= cpd.size() && + (std::string::size_type)std::count(cpd.begin(), cpd.end(), '[') == + cpd.size() / 2) { + // fixed array, dims will have size if available + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + + //-- special case: initializer list + if (realTypeStr.compare(0, 21, "std::initializer_list") == 0) { + // get the type of the list and create a converter (TODO: get hold of + // value_type?) + auto pos = realTypeStr.find('<'); + std::string value_type = + realTypeStr.substr(pos + 1, realTypeStr.size() - pos - 2); + Converter* cnv = nullptr; + bool use_byte_cnv = false; + if (cpd == "" && interop::GetScope(value_type)) { + // initializer list of object values does not work as the target is raw + // memory; simply use byte copies + + // by convention, leave cnv as nullptr + use_byte_cnv = true; + } else + cnv = CreateConverter(value_type); + if (cnv || use_byte_cnv) + return new InitializerListConverter(interop::GetScopeFromType(realType), + value_type); + } + + //-- still nothing? use a generalized converter + bool control = cpd == "&" || isConst; + + //-- special case: std::function + auto pos = resolvedTypeStr.find("std::function<"); + if (pos == 0 /* std:: */ || pos == 6 /* const std:: */) { + + // get actual converter for normal passing + Converter* cnv = selectInstanceCnv(interop::GetScopeFromType(realType), cpd, + dims, isConst, control); + + if (cnv) { + // get the type of the underlying (TODO: use target_type?) + auto pos1 = resolvedTypeStr.find("(", pos + 14); + auto pos2 = resolvedTypeStr.rfind(")"); + if (pos1 != std::string::npos && pos2 != std::string::npos) { + auto sz1 = pos1 - pos - 14; + if (resolvedTypeStr[pos + 14 + sz1 - 1] == ' ') + sz1 -= 1; + + const std::string& argsStr = + resolvedTypeStr.substr(pos1, pos2 - pos1 + 1).c_str(); + return new StdFunctionConverter(cnv, + resolvedTypeStr.substr(pos + 14, sz1), + argsStr == "(void)" ? "()" : argsStr); + } else if (cnv->HasState()) + delete cnv; + } + } + +#if __cplusplus >= 202002L + //-- special case: std::span + pos = resolvedTypeStr.find("span<"); + if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || + pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */) { + + // std::span::value_type == std::remove_cv_t: look it up + interop::TCppScope_t span_scope = interop::GetScopeFromType(realType); + interop::TCppType_t vtype = interop::ResolveType( + interop::GetTypeFromScope(interop::GetNamed("value_type", span_scope))); + if (span_scope && vtype) { + std::string value_type = interop::GetTypeAsString(vtype); + return new StdSpanConverter{value_type, span_scope}; + } + } +#endif + + // converters for known C++ classes and default (void*) + Converter* result = nullptr; + interop::TCppScope_t klass = interop::GetScopeFromType(realType); + if (resolvedTypeStr.find("(*)") != std::string::npos || + (resolvedTypeStr.find("::*)") != std::string::npos)) { + // this is a function function pointer + // TODO: find better way of finding the type + auto pos1 = resolvedTypeStr.find('('); + auto pos2 = resolvedTypeStr.find("*)"); + auto pos3 = resolvedTypeStr.rfind(')'); + std::string return_type = resolvedTypeStr.substr(0, pos1); + result = new FunctionPointerConverter( + return_type.erase(return_type.find_last_not_of(" ") + 1), + resolvedTypeStr.substr(pos2 + 2, pos3 - pos2 - 1)); + } else if ((realTypeStr != "std::byte") && + (klass || (klass = interop::GetFullScope(realTypeStr)))) { + // std::byte is a special enum class used to access raw memory + interop::TCppScope_t raw; + if (interop::GetSmartPtrInfo(realTypeStr, &raw, nullptr)) { + if (cpd == "") { + result = new SmartPtrConverter(klass, raw, control); + } else if (cpd == "&") { + result = new SmartPtrConverter(klass, raw); + } else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { + result = new SmartPtrConverter(klass, raw, control, true); + } + } + + if (!result) { + // Cling WORKAROUND -- special case for STL iterators + if (realTypeStr.rfind("__gnu_cxx::__normal_iterator", 0) /* vector */ == 0 +#ifdef __APPLE__ + || realTypeStr.rfind("__wrap_iter", 0) == 0 +#endif + // TODO: Windows? + ) { + static STLIteratorConverter c; + result = &c; + } else if (realTypeStr != "int8_t" and realTypeStr != "uint8_t") { + // -- Cling WORKAROUND + result = selectInstanceCnv(klass, cpd, dims, isConst, control); + } + } + } + const std::string failure_msg( + "Failed to convert type: " + fullType + "; resolved: " + resolvedTypeStr + + "; real: " + realTypeStr + + "; realUnresolvedType: " + realUnresolvedTypeStr + "; cpd: " + cpd); + + if (!result && cpd == "&&") { + // for builtin, can use const-ref for r-ref + h = gConvFactories.find("const " + realTypeStr + "&"); + if (h != gConvFactories.end()) + return (h->second)(dims); + h = gConvFactories.find("const " + realUnresolvedTypeStr + "&"); + if (h != gConvFactories.end()) + return (h->second)(dims); + // else, unhandled moves + result = new NotImplementedConverter{PyExc_NotImplementedError, + "this method cannot (yet) be called"}; + } + + if (!result && h != gConvFactories.end()) { + // converter factory available, use it to create converter + result = (h->second)(dims); + } else if (!result) { + // default to something reasonable, assuming "user knows best" + if (cpd.size() == 2 && cpd != "&&") { // "**", "*[]", "*&" + result = new VoidPtrPtrConverter(dims.ndim(), failure_msg); + } else if (!cpd.empty()) { + result = new VoidArrayConverter(/* keepControl= */ true, + failure_msg); // "user knows best" + } else { + // fails on use + result = new NotImplementedConverter{ + PyExc_NotImplementedError, "this method cannot (yet) be called"}; + } + } + + return result; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +void cppjit::cpyrt::DestroyConverter(Converter* p) { + if (p && p->HasState()) + delete p; // state-less converters are always shared +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::RegisterConverter(const std::string& name, cf_t fac) { + // register a custom converter + auto f = gConvFactories.find(name); + if (f != gConvFactories.end()) + return false; + + gConvFactories[name] = fac; + return true; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::RegisterConverterAlias(const std::string& name, + const std::string& target) { + // register a custom converter that is a reference to an existing converter + auto f = gConvFactories.find(name); + if (f != gConvFactories.end()) + return false; + + auto t = gConvFactories.find(target); + if (t == gConvFactories.end()) + return false; + + gConvFactories[name] = t->second; + return true; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::UnregisterConverter(const std::string& name) { + // remove a custom converter + auto f = gConvFactories.find(name); + if (f != gConvFactories.end()) { + gConvFactories.erase(f); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +namespace { + +using namespace cppjit::cpyrt; + +inline static std::string::size_type dims2stringsz(cdims_t d) { + return (d && d.ndim() != UNKNOWN_SIZE) ? d[0] : std::string::npos; +} + +#define STRINGVIEW "std::basic_string_view" +#define WSTRING1 "std::basic_string" +#define WSTRING2 \ + "std::basic_string,std::allocator>" + +//-- aliasing special case: C complex (is binary compatible with C++ +// std::complex) +#ifndef _WIN32 +#define CCOMPLEX_D "_Complex double" +#define CCOMPLEX_F "_Complex float" +#else +#define CCOMPLEX_D "_C_double_complex" +#define CCOMPLEX_F "_C_float_complex" +#endif + +static struct InitConvFactories_t { +public: + InitConvFactories_t() { + // load all converter factories in the global map 'gConvFactories' + cpyrt::ConvFactories_t& gf = gConvFactories; + + // factories for built-ins + gf["bool"] = (cf_t) + [](cdims_t) { + static BoolConverter c{}; + return &c; + }; + gf["const bool&"] = (cf_t) + [](cdims_t) { + static ConstBoolRefConverter c{}; + return &c; + }; + gf["bool&"] = (cf_t) + [](cdims_t) { + static BoolRefConverter c{}; + return &c; + }; + gf["char"] = (cf_t) + [](cdims_t) { + static CharConverter c{}; + return &c; + }; + gf["const char&"] = (cf_t) + [](cdims_t) { + static ConstCharRefConverter c{}; + return &c; + }; + gf["char&"] = (cf_t) + [](cdims_t) { + static CharRefConverter c{}; + return &c; + }; + gf["signed char&"] = (cf_t) + [](cdims_t) { + static SCharRefConverter c{}; + return &c; + }; + gf["unsigned char"] = (cf_t) + [](cdims_t) { + static UCharConverter c{}; + return &c; + }; + gf["const unsigned char&"] = (cf_t) + [](cdims_t) { + static ConstUCharRefConverter c{}; + return &c; + }; + gf["unsigned char&"] = (cf_t) + [](cdims_t) { + static UCharRefConverter c{}; + return &c; + }; + gf["SCharAsInt"] = (cf_t) + [](cdims_t) { + static SCharAsIntConverter c{}; + return &c; + }; + gf["UCharAsInt"] = (cf_t) + [](cdims_t) { + static UCharAsIntConverter c{}; + return &c; + }; + gf["wchar_t"] = (cf_t) + [](cdims_t) { + static WCharConverter c{}; + return &c; + }; + gf["char16_t"] = (cf_t) + [](cdims_t) { + static Char16Converter c{}; + return &c; + }; + gf["char32_t"] = (cf_t) + [](cdims_t) { + static Char32Converter c{}; + return &c; + }; + gf["wchar_t&"] = (cf_t) + [](cdims_t) { + static WCharRefConverter c{}; + return &c; + }; + gf["char16_t&"] = (cf_t) + [](cdims_t) { + static Char16RefConverter c{}; + return &c; + }; + gf["char32_t&"] = (cf_t) + [](cdims_t) { + static Char32RefConverter c{}; + return &c; + }; + gf["int8_t"] = (cf_t) + [](cdims_t) { + static Int8Converter c{}; + return &c; + }; + gf["const int8_t&"] = (cf_t) + [](cdims_t) { + static ConstInt8RefConverter c{}; + return &c; + }; + gf["int8_t&"] = (cf_t) + [](cdims_t) { + static Int8RefConverter c{}; + return &c; + }; + gf["uint8_t"] = (cf_t) + [](cdims_t) { + static UInt8Converter c{}; + return &c; + }; + gf["const uint8_t&"] = (cf_t) + [](cdims_t) { + static ConstUInt8RefConverter c{}; + return &c; + }; + gf["uint8_t&"] = (cf_t) + [](cdims_t) { + static UInt8RefConverter c{}; + return &c; + }; + gf["short"] = (cf_t) + [](cdims_t) { + static ShortConverter c{}; + return &c; + }; + gf["const short&"] = (cf_t) + [](cdims_t) { + static ConstShortRefConverter c{}; + return &c; + }; + gf["short&"] = (cf_t) + [](cdims_t) { + static ShortRefConverter c{}; + return &c; + }; + gf["unsigned short"] = (cf_t) + [](cdims_t) { + static UShortConverter c{}; + return &c; + }; + gf["const unsigned short&"] = (cf_t) + [](cdims_t) { + static ConstUShortRefConverter c{}; + return &c; + }; + gf["unsigned short&"] = (cf_t) + [](cdims_t) { + static UShortRefConverter c{}; + return &c; + }; + gf["int"] = (cf_t) + [](cdims_t) { + static IntConverter c{}; + return &c; + }; + gf["int&"] = (cf_t) + [](cdims_t) { + static IntRefConverter c{}; + return &c; + }; + gf["const int&"] = (cf_t) + [](cdims_t) { + static ConstIntRefConverter c{}; + return &c; + }; + gf["unsigned int"] = (cf_t) + [](cdims_t) { + static UIntConverter c{}; + return &c; + }; + gf["const unsigned int&"] = (cf_t) + [](cdims_t) { + static ConstUIntRefConverter c{}; + return &c; + }; + gf["unsigned int&"] = (cf_t) + [](cdims_t) { + static UIntRefConverter c{}; + return &c; + }; + gf["long"] = (cf_t) + [](cdims_t) { + static LongConverter c{}; + return &c; + }; + gf["long&"] = (cf_t) + [](cdims_t) { + static LongRefConverter c{}; + return &c; + }; + gf["const long&"] = (cf_t) + [](cdims_t) { + static ConstLongRefConverter c{}; + return &c; + }; + gf["unsigned long"] = (cf_t) + [](cdims_t) { + static ULongConverter c{}; + return &c; + }; + gf["const unsigned long&"] = (cf_t) + [](cdims_t) { + static ConstULongRefConverter c{}; + return &c; + }; + gf["unsigned long&"] = (cf_t) + [](cdims_t) { + static ULongRefConverter c{}; + return &c; + }; + gf["long long"] = (cf_t) + [](cdims_t) { + static LLongConverter c{}; + return &c; + }; + gf["const long long&"] = (cf_t) + [](cdims_t) { + static ConstLLongRefConverter c{}; + return &c; + }; + gf["long long&"] = (cf_t) + [](cdims_t) { + static LLongRefConverter c{}; + return &c; + }; + gf["unsigned long long"] = (cf_t) + [](cdims_t) { + static ULLongConverter c{}; + return &c; + }; + gf["const unsigned long long&"] = (cf_t) + [](cdims_t) { + static ConstULLongRefConverter c{}; + return &c; + }; + gf["unsigned long long&"] = (cf_t) + [](cdims_t) { + static ULLongRefConverter c{}; + return &c; + }; + + gf["float"] = (cf_t) + [](cdims_t) { + static FloatConverter c{}; + return &c; + }; + gf["const float&"] = (cf_t) + [](cdims_t) { + static ConstFloatRefConverter c{}; + return &c; + }; + gf["float&"] = (cf_t) + [](cdims_t) { + static FloatRefConverter c{}; + return &c; + }; + gf["double"] = (cf_t) + [](cdims_t) { + static DoubleConverter c{}; + return &c; + }; + gf["double&"] = (cf_t) + [](cdims_t) { + static DoubleRefConverter c{}; + return &c; + }; + gf["const double&"] = (cf_t) + [](cdims_t) { + static ConstDoubleRefConverter c{}; + return &c; + }; + gf["long double"] = (cf_t) + [](cdims_t) { + static LDoubleConverter c{}; + return &c; + }; + gf["const long double&"] = (cf_t) + [](cdims_t) { + static ConstLDoubleRefConverter c{}; + return &c; + }; + gf["long double&"] = (cf_t) + [](cdims_t) { + static LDoubleRefConverter c{}; + return &c; + }; + gf["std::complex"] = + (cf_t) + [](cdims_t) { return new ComplexDConverter{}; }; + gf["const std::complex&"] = + (cf_t) + [](cdims_t) { return new ComplexDConverter{}; }; + gf["void"] = (cf_t) + [](cdims_t) { + static VoidConverter c{}; + return &c; + }; + + // pointer/array factories + gf["bool ptr"] = + (cf_t) + [](cdims_t d) { return new BoolArrayConverter{d}; }; + gf["signed char ptr"] = + (cf_t) + [](cdims_t d) { return new SCharArrayConverter{d}; }; + gf["signed char**"] = (cf_t) + [](cdims_t) { + return new SCharArrayConverter{{UNKNOWN_SIZE, UNKNOWN_SIZE}}; + }; + gf["const unsigned char*"] = + (cf_t) + [](cdims_t d) { return new UCharArrayConverter{d}; }; + gf["unsigned char ptr"] = + (cf_t) + [](cdims_t d) { return new UCharArrayConverter{d}; }; + gf["SCharAsInt*"] = gf["signed char ptr"]; + gf["SCharAsInt[]"] = gf["signed char ptr"]; + gf["UCharAsInt*"] = gf["unsigned char ptr"]; + gf["UCharAsInt[]"] = gf["unsigned char ptr"]; + gf["std::byte ptr"] = + (cf_t) + [](cdims_t d) { return new ByteArrayConverter{d}; }; + gf["int8_t ptr"] = + (cf_t) + [](cdims_t d) { return new Int8ArrayConverter{d}; }; + gf["uint8_t ptr"] = + (cf_t) + [](cdims_t d) { return new UInt8ArrayConverter{d}; }; + gf["short ptr"] = + (cf_t) + [](cdims_t d) { return new ShortArrayConverter{d}; }; + gf["unsigned short ptr"] = + (cf_t) + [](cdims_t d) { return new UShortArrayConverter{d}; }; + gf["int ptr"] = (cf_t) + [](cdims_t d) { return new IntArrayConverter{d}; }; + gf["unsigned int ptr"] = + (cf_t) + [](cdims_t d) { return new UIntArrayConverter{d}; }; + gf["long ptr"] = + (cf_t) + [](cdims_t d) { return new LongArrayConverter{d}; }; + gf["unsigned long ptr"] = + (cf_t) + [](cdims_t d) { return new ULongArrayConverter{d}; }; + gf["long long ptr"] = + (cf_t) + [](cdims_t d) { return new LLongArrayConverter{d}; }; + gf["unsigned long long ptr"] = + (cf_t) + [](cdims_t d) { return new ULLongArrayConverter{d}; }; + gf["float ptr"] = + (cf_t) + [](cdims_t d) { return new FloatArrayConverter{d}; }; + gf["double ptr"] = + (cf_t) + [](cdims_t d) { return new DoubleArrayConverter{d}; }; + gf["long double ptr"] = + (cf_t) + [](cdims_t d) { return new LDoubleArrayConverter{d}; }; + gf["std::complex ptr"] = + (cf_t) + [](cdims_t d) { return new ComplexFArrayConverter{d}; }; + gf["std::complex ptr"] = + (cf_t) + [](cdims_t d) { return new ComplexDArrayConverter{d}; }; + gf["void*"] = + (cf_t) + [](cdims_t d) { return new VoidArrayConverter{(bool)d}; }; + + // aliases + gf["signed char"] = gf["char"]; + gf["const signed char&"] = gf["const char&"]; + gf["std::byte"] = gf["uint8_t"]; + gf["const std::byte&"] = gf["const uint8_t&"]; + gf["std::byte&"] = gf["uint8_t&"]; + gf["std::int8_t"] = gf["int8_t"]; + gf["const std::int8_t&"] = gf["const int8_t&"]; + gf["std::int8_t&"] = gf["int8_t&"]; + gf["std::uint8_t"] = gf["uint8_t"]; + gf["const std::uint8_t&"] = gf["const uint8_t&"]; + gf["std::uint8_t&"] = gf["uint8_t&"]; + gf["internal_enum_type_t"] = gf["int"]; + gf["internal_enum_type_t&"] = gf["int&"]; + gf["const internal_enum_type_t&"] = gf["const int&"]; + gf["internal_enum_type_t ptr"] = gf["int ptr"]; +#ifdef _WIN32 + gf["__int64"] = gf["long long"]; + gf["const __int64&"] = gf["const long long&"]; + gf["__int64&"] = gf["long long&"]; + gf["__int64 ptr"] = gf["long long ptr"]; + gf["unsigned __int64"] = gf["unsigned long long"]; + gf["const unsigned __int64&"] = gf["const unsigned long long&"]; + gf["unsigned __int64&"] = gf["unsigned long long&"]; + gf["unsigned __int64 ptr"] = gf["unsigned long long ptr"]; +#endif + gf[CCOMPLEX_D] = gf["std::complex"]; + gf["const " CCOMPLEX_D "&"] = gf["const std::complex&"]; + gf[CCOMPLEX_F " ptr"] = gf["std::complex ptr"]; + gf[CCOMPLEX_D " ptr"] = gf["std::complex ptr"]; + + // We always need these converters when cppjit is based on an unpatched + // ROOT, because the "long long" types are always converted to Long64_t + // and ULong64_t already at the ROOT Meta level. + // See + // https://github.com/root-project/root/issues/15872#issuecomment-2174092763 + gf["Long64_t"] = gf["long long"]; + gf["Long64_t ptr"] = gf["long long ptr"]; + gf["Long64_t&"] = gf["long long&"]; + gf["const Long64_t&"] = gf["const long long&"]; + gf["ULong64_t"] = gf["unsigned long long"]; + gf["ULong64_t ptr"] = gf["unsigned long long ptr"]; + gf["ULong64_t&"] = gf["unsigned long long&"]; + gf["const ULong64_t&"] = gf["const unsigned long long&"]; + + // factories for special cases + gf["nullptr_t"] = (cf_t) + [](cdims_t) { + static NullptrConverter c{}; + return &c; + }; + gf["const char*"] = (cf_t) + [](cdims_t) { return new CStringConverter{}; }; + gf["const signed char*"] = gf["const char*"]; + gf["const char*&&"] = gf["const char*"]; + gf["const char[]"] = + (cf_t) + [](cdims_t) { return new CStringConverter{}; }; + gf["char*"] = (cf_t) + [](cdims_t d) { + return new NonConstCStringConverter{dims2stringsz(d)}; + }; + gf["char[]"] = (cf_t) + [](cdims_t d) { + return new NonConstCStringArrayConverter{d, true}; + }; + gf["signed char*"] = gf["char*"]; + gf["wchar_t*"] = (cf_t) + [](cdims_t) { return new WCStringConverter{}; }; + // TODO: The libc++ library that is used most prominently by Apple for + // macOS is using the _Nonnull Clang attribute for some of its string + // constructors (_LIBCPP_DIAGNOSE_NULLPTR is a preprocessor macro that + // resolves to _Nullable). The cppjit backend doesn't strip away such Clang + // attributes, so the wchar_t* converter fails to match. To make the + // construction of std::wstring work on macOS, we are explicitly creating + // a converter for the wchar_t*_Nonnull type here, but we should try to + // find a more general solution for dealing with Clang attributes. + // [1] https://clang.llvm.org/docs/AttributeReference.html#id656 + // [2] + // https://github.com/llvm/llvm-project/blob/2078da43e25a4623cab2d0d60decddf709aaea28/libcxx/include/string#L1061 + gf["wchar_t*_Nonnull"] = + (cf_t) + [](cdims_t) { return new WCStringConverter{}; }; + gf["char16_t*"] = (cf_t) + [](cdims_t) { return new CString16Converter{}; }; + gf["char16_t[]"] = (cf_t) + [](cdims_t d) { + return new CString16Converter{dims2stringsz(d)}; + }; + gf["char32_t*"] = (cf_t) + [](cdims_t) { return new CString32Converter{}; }; + gf["char32_t[]"] = (cf_t) + [](cdims_t d) { + return new CString32Converter{dims2stringsz(d)}; + }; + // TODO: the following are handled incorrectly upstream (char16_t** where + // char16_t* intended)?! + gf["char16_t**"] = gf["char16_t*"]; + gf["char32_t**"] = gf["char32_t*"]; + gf["const char**"] = (cf_t) + [](cdims_t) { + return new CStringArrayConverter{{UNKNOWN_SIZE, UNKNOWN_SIZE}, false}; + }; + gf["char**"] = gf["const char**"]; + gf["const char*[]"] = + (cf_t) + [](cdims_t d) { return new CStringArrayConverter{d, false}; }; + gf["char*[]"] = (cf_t) + [](cdims_t d) { + return new NonConstCStringArrayConverter{d, false}; + }; + gf["char ptr"] = gf["char*[]"]; + gf["std::basic_string"] = + (cf_t) + [](cdims_t) { return new STLStringConverter{}; }; + gf["const std::basic_string&"] = gf["std::basic_string"]; + gf["std::basic_string&&"] = + (cf_t) + [](cdims_t) { return new STLStringMoveConverter{}; }; + gf["const std::basic_string &"] = gf["std::basic_string"]; + gf["std::basic_string &&"] = + (cf_t) + [](cdims_t) { return new STLStringMoveConverter{}; }; + gf["std::basic_string_view"] = + (cf_t) + [](cdims_t) { return new STLStringViewConverter{}; }; + gf[STRINGVIEW] = gf["std::basic_string_view"]; + gf["std::basic_string_view&"] = gf["std::basic_string_view"]; + gf["const " STRINGVIEW "&"] = gf["std::basic_string_view"]; + gf["std::basic_string_view &"] = gf["std::basic_string_view"]; + gf["const " STRINGVIEW " &"] = gf["std::basic_string_view"]; + gf["std::wstring"] = + (cf_t) + [](cdims_t) { return new STLWStringConverter{}; }; + gf[WSTRING1] = gf["std::wstring"]; + gf[WSTRING2] = gf["std::wstring"]; + gf["const std::wstring&"] = gf["std::wstring"]; + gf["const " WSTRING1 "&"] = gf["std::wstring"]; + gf["const " WSTRING2 "&"] = gf["std::wstring"]; + gf["const std::wstring &"] = gf["std::wstring"]; + gf["const " WSTRING1 " &"] = gf["std::wstring"]; + gf["const " WSTRING2 " &"] = gf["std::wstring"]; + // VoidPtrRefConverter should only be used for const references to pointers + gf["const void*&"] = (cf_t) + [](cdims_t) { + static VoidPtrRefConverter c{}; + return &c; + }; + gf["void**"] = + (cf_t) + [](cdims_t d) { return new VoidPtrPtrConverter{d}; }; + gf["void ptr"] = gf["void**"]; + gf["PyObject*"] = (cf_t) + [](cdims_t) { + static PyObjectConverter c{}; + return &c; + }; + gf["_object*"] = gf["PyObject*"]; + gf["FILE*"] = (cf_t) + [](cdims_t) { return new VoidArrayConverter{}; }; + } +} initConvFactories_; + +} // unnamed namespace diff --git a/src/cpyrt/Converters.h b/src/cpyrt/Converters.h new file mode 100644 index 0000000..f2e42f6 --- /dev/null +++ b/src/cpyrt/Converters.h @@ -0,0 +1,104 @@ +#ifndef CPYRT_CONVERTERS_H +#define CPYRT_CONVERTERS_H + +// Bindings +#include "Dimensions.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +struct Parameter; +struct CallContext; + +class CPYRT_CLASS_EXPORT Converter { +public: + virtual ~Converter(); + + Converter() = default; + + Converter(Converter const& other) = delete; + Converter(Converter&& other) = delete; + Converter& operator=(Converter const& other) = delete; + Converter& operator=(Converter&& other) = delete; + +public: + virtual bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) = 0; + virtual PyObject* FromMemory(void* address); + virtual bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr); + virtual bool HasState() { return false; } + virtual std::string GetFailureMsg() { return "[Converter]"; } +}; + +// create/destroy converter from fully qualified type (public API) +CPYRT_EXPORT Converter* CreateConverter(const std::string& fullType, + cdims_t dims = 0); +CPYRT_EXPORT Converter* CreateConverter(cppjit::interop::TCppType_t type, + cdims_t dims = 0); +CPYRT_EXPORT void DestroyConverter(Converter* p); +typedef Converter* (*cf_t)(cdims_t d); +CPYRT_EXPORT bool RegisterConverter(const std::string& name, cf_t fac); +CPYRT_EXPORT bool RegisterConverterAlias(const std::string& name, + const std::string& target); +CPYRT_EXPORT bool UnregisterConverter(const std::string& name); + +// converters for special cases (only here b/c of external use of +// StrictInstancePtrConverter) +class VoidArrayConverter : public Converter { +public: + VoidArrayConverter(bool keepControl = true, + const std::string& failureMsg = std::string()) + : fFailureMsg(failureMsg) { + fKeepControl = keepControl; + } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { + return "[VoidArrayConverter] " + fFailureMsg; + } + +protected: + virtual bool GetAddressSpecialCase(PyObject* pyobject, void*& address); + bool KeepControl() { return fKeepControl; } + const std::string fFailureMsg; + +private: + bool fKeepControl; +}; + +template class InstancePtrConverter : public VoidArrayConverter { +public: + InstancePtrConverter(cppjit::interop::TCppScope_t klass, + bool keepControl = false, + const std::string& failureMsg = std::string()) + : VoidArrayConverter(keepControl, failureMsg), + fClass(cppjit::interop::GetUnderlyingScope(klass)) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, + PyObject* ctxt = nullptr) override; + +protected: + cppjit::interop::TCppScope_t fClass; +}; + +class StrictInstancePtrConverter : public InstancePtrConverter { +public: + using InstancePtrConverter::InstancePtrConverter; + +protected: + bool GetAddressSpecialCase(PyObject*, void*&) override { return false; } +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CONVERTERS_H diff --git a/src/cpyrt/CustomPyTypes.cxx b/src/cpyrt/CustomPyTypes.cxx new file mode 100644 index 0000000..dcb64b8 --- /dev/null +++ b/src/cpyrt/CustomPyTypes.cxx @@ -0,0 +1,456 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "Converters.h" +#include "CustomPyTypes.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" + +// As of Python 3.12, we can't use the PyMethod_GET_FUNCTION and +// PyMethod_GET_SELF macros anymore, as the contain asserts that check if the +// Python type is actually PyMethod_Type. If the Python type is +// CustomInstanceMethod_Type, we need our own macros. Technically they do they +// same, because the actual C++ type of the PyObject is PyMethodObject anyway. +#define CustomInstanceMethod_GET_SELF(meth) \ + reinterpret_cast(meth)->im_self +#define CustomInstanceMethod_GET_FUNCTION(meth) \ + reinterpret_cast(meth)->im_func +// TODO: this will break functionality +#define CustomInstanceMethod_GET_CLASS(meth) Py_None + +namespace cppjit::cpyrt { + +//- custom type representing typedef to pointer of class --------------------- +static PyObject* tptc_call(typedefpointertoclassobject* self, PyObject* args, + PyObject* /* kwds */) { + long long addr = 0; + if (!PyArg_ParseTuple(args, const_cast("|L"), &addr)) + return nullptr; + return BindCppObjectNoCast(interop::TCppObject_t((void*)addr), + self->fCppType); +} + +//----------------------------------------------------------------------------- +static PyObject* tptc_getcppname(typedefpointertoclassobject* self, void*) { + return cpyrt_PyText_FromString( + (interop::GetScopedFinalName(self->fCppType) + "*").c_str()); +} + +//----------------------------------------------------------------------------- +static PyObject* tptc_name(typedefpointertoclassobject* self, void*) { + PyObject* pyclass = cpyrt::GetScopeProxy(self->fCppType); + if (pyclass) { + PyObject* pyname = PyObject_GetAttr(pyclass, PyStrings::gName); + Py_DECREF(pyclass); + return pyname; + } + + return cpyrt_PyText_FromString("*"); +} + +//----------------------------------------------------------------------------- +static PyGetSetDef tptc_getset[] = { + {(char*)"__name__", (getter)tptc_name, nullptr, nullptr, nullptr}, + {(char*)"__cpp_name__", (getter)tptc_getcppname, nullptr, nullptr, nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +PyTypeObject TypedefPointerToClass_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.TypedefPointerToClass", // tp_name + sizeof(typedefpointertoclassobject), // tp_basicsize + 0, // tp_itemsize + 0, // tp_dealloc + 0, // tp_vectorcall_offset + 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 + (ternaryfunc)tptc_call, // tp_call + 0, // tp_str + PyObject_GenericGetAttr, // tp_getattro + PyObject_GenericSetAttr, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT, // 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 + tptc_getset, // 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 + CPYRT_PYTYPE_TAIL}; + +//= instancemethod object with a more efficient call function ================ +static PyMethodObject* free_list; +static int numfree = 0; +#ifndef PyMethod_MAXFREELIST +#define PyMethod_MAXFREELIST 256 +#endif + +//----------------------------------------------------------------------------- +PyObject* CustomInstanceMethod_New(PyObject* func, PyObject* self, PyObject*) { + // from instancemethod, but with custom type (at issue is that instancemethod + // is not meant to be derived from) + PyMethodObject* im; + if (!PyCallable_Check(func)) { + PyErr_Format(PyExc_SystemError, "%s:%d: bad argument to internal function", + __FILE__, __LINE__); + return nullptr; + } + + im = free_list; + if (im != nullptr) { + free_list = (PyMethodObject*)(im->im_self); + (void)PyObject_INIT(im, &CustomInstanceMethod_Type); + } else { + im = PyObject_GC_New(PyMethodObject, &CustomInstanceMethod_Type); + if (im == nullptr) + return nullptr; + } + + im->im_weakreflist = nullptr; + Py_INCREF(func); + im->im_func = func; + Py_XINCREF(self); + im->im_self = self; + PyObject_GC_Track(im); + return (PyObject*)im; +} + +//----------------------------------------------------------------------------- +static void im_dealloc(PyMethodObject* im) { + // from instancemethod, but with custom type (at issue is that instancemethod + // is not meant to be derived from) + PyObject_GC_UnTrack(im); + + if (im->im_weakreflist != nullptr) + PyObject_ClearWeakRefs((PyObject*)im); + + Py_DECREF(im->im_func); + Py_XDECREF(im->im_self); + + if (numfree < PyMethod_MAXFREELIST) { + im->im_self = (PyObject*)free_list; + free_list = im; + numfree++; + } else { + PyObject_GC_Del(im); + } +} + +//----------------------------------------------------------------------------- +static PyObject* im_call(PyObject* meth, PyObject* args, PyObject* kw) { + // The mapping from a method to a function involves reshuffling of self back + // into the list of arguments. However, the pythonized methods will then have + // to undo that shuffling, which is inefficient. This method is the same as + // the one for the instancemethod object, except for the shuffling. + PyObject* self = CustomInstanceMethod_GET_SELF(meth); + + if (!self) { + // unbound methods must be called with an instance of the class (or a + // derived class) as first argument + Py_ssize_t argc = PyTuple_GET_SIZE(args); + PyObject* pyclass = CustomInstanceMethod_GET_CLASS(meth); + if (1 <= argc && + PyObject_IsInstance(PyTuple_GET_ITEM(args, 0), pyclass) == 1) { + self = PyTuple_GET_ITEM(args, 0); + + PyObject* newArgs = PyTuple_New(argc - 1); + for (int i = 1; i < argc; ++i) { + PyObject* v = PyTuple_GET_ITEM(args, i); + Py_INCREF(v); + PyTuple_SET_ITEM(newArgs, i - 1, v); + } + + args = newArgs; + + } else + return PyMethod_Type.tp_call(meth, args, kw); // will set proper error msg + + } else + Py_INCREF(args); + + PyCFunctionObject* func = + (PyCFunctionObject*)CustomInstanceMethod_GET_FUNCTION(meth); + + // the function is globally shared, so set and reset its "self" (ok, b/c of + // GIL) + Py_INCREF(self); + func->m_self = self; + PyObject* result = cpyrt_PyCFunction_Call((PyObject*)func, args, kw); + func->m_self = nullptr; + Py_DECREF(self); + Py_DECREF(args); + return result; +} + +//----------------------------------------------------------------------------- +static PyObject* im_descr_get(PyObject* meth, PyObject* obj, + PyObject* pyclass) { + // from instancemethod: don't rebind an already bound method, or an unbound + // method of a class that's not a base class of pyclass + if (CustomInstanceMethod_GET_SELF(meth)) { + Py_INCREF(meth); + return meth; + } + + if (obj == Py_None) + obj = nullptr; + + return CustomInstanceMethod_New(CustomInstanceMethod_GET_FUNCTION(meth), obj, + pyclass); +} + +//= cpyrt custom instance method type ===================================== +PyTypeObject CustomInstanceMethod_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.InstanceMethod", // tp_name + 0, + 0, + (destructor)im_dealloc, // tp_dealloc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + im_call, // tp_call + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, // tp_flags + (char*)"cpyrt custom instance method (internal)", // tp_doc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + &PyMethod_Type, // tp_base + 0, + im_descr_get, // tp_descr_get + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +//= cpyrt custom iterator for performance ================================= +static void indexiter_dealloc(indexiterobject* ii) { + PyObject_GC_UnTrack(ii); + Py_XDECREF(ii->ii_container); + PyObject_GC_Del(ii); +} + +static int indexiter_traverse(indexiterobject* ii, visitproc visit, void* arg) { + Py_VISIT(ii->ii_container); + return 0; +} + +static PyObject* indexiter_iternext(indexiterobject* ii) { + if (ii->ii_pos >= ii->ii_len) + return nullptr; + + PyObject* pyindex = PyLong_FromSsize_t(ii->ii_pos); + PyObject* result = PyObject_CallMethodOneArg((PyObject*)ii->ii_container, + PyStrings::gGetItem, pyindex); + Py_DECREF(pyindex); + + ii->ii_pos += 1; + return result; +} + +PyTypeObject IndexIter_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0)(char*) "cppjit.indexiter", // tp_name + sizeof(indexiterobject), // tp_basicsize + 0, + (destructor)indexiter_dealloc, // tp_dealloc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags + 0, + (traverseproc)indexiter_traverse, // tp_traverse + 0, + 0, + 0, + PyObject_SelfIter, // tp_iter + (iternextfunc)indexiter_iternext, // tp_iternext + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +static void vectoriter_dealloc(vectoriterobject* vi) { + if (vi->vi_converter && vi->vi_converter->HasState()) + delete vi->vi_converter; + indexiter_dealloc(vi); +} + +static PyObject* vectoriter_iternext(vectoriterobject* vi) { + if (vi->ii_pos >= vi->ii_len) + return nullptr; + + PyObject* result = nullptr; + + if (vi->vi_data && vi->vi_converter) { + void* location = + (void*)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos); + result = vi->vi_converter->FromMemory(location); + } else if (vi->vi_data && vi->vi_klass) { + // The CPPInstance::kNoMemReg by-passes the memory regulator; the assumption + // here is that objects in vectors are simple and thus do not need to + // maintain object identity (or at least not during the loop anyway). This + // gains 2x in performance. + interop::TCppObject_t cppobj( + (void*)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos)); + if (vi->vi_flags & vectoriterobject::kIsPolymorphic) + result = cpyrt::BindCppObject(*(void**)cppobj.data, vi->vi_klass, + cpyrt::CPPInstance::kNoMemReg); + else + result = cpyrt::BindCppObjectNoCast(cppobj, vi->vi_klass, + cpyrt::CPPInstance::kNoMemReg); + if ((vi->vi_flags & vectoriterobject::kNeedLifeLine) && result) + PyObject_SetAttr(result, PyStrings::gLifeLine, vi->ii_container); + } else { + PyObject* pyindex = PyLong_FromSsize_t(vi->ii_pos); + result = PyObject_CallMethodOneArg((PyObject*)vi->ii_container, + PyStrings::gGetNoCheck, pyindex); + Py_DECREF(pyindex); + } + + vi->ii_pos += 1; + return result; +} + +PyTypeObject VectorIter_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.vectoriter", // tp_name + sizeof(vectoriterobject), // tp_basicsize + 0, + (destructor)vectoriter_dealloc, // tp_dealloc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags + 0, + (traverseproc)indexiter_traverse, // tp_traverse + 0, + 0, + 0, + PyObject_SelfIter, // tp_iter + (iternextfunc)vectoriter_iternext, // tp_iternext + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/src/cpyrt/CustomPyTypes.h b/src/cpyrt/CustomPyTypes.h new file mode 100644 index 0000000..1b28a5c --- /dev/null +++ b/src/cpyrt/CustomPyTypes.h @@ -0,0 +1,70 @@ +#ifndef CPYRT_CUSTOMPYTYPES_H +#define CPYRT_CUSTOMPYTYPES_H + +#include "Python.h" +#include "cppjit_interop.h" + +namespace cppjit::cpyrt { + +/** Custom "builtins," detectable by type, for pass by ref and improved + performance. + */ + +//- custom type representing typedef to pointer of class --------------------- +struct typedefpointertoclassobject { + PyObject_HEAD cppjit::interop::TCppScope_t fCppType; +}; + +extern PyTypeObject TypedefPointerToClass_Type; + +template inline bool TypedefPointerToClass_Check(T* object) { + return object && PyObject_TypeCheck(object, &TypedefPointerToClass_Type); +} + +template inline bool TypedefPointerToClass_CheckExact(T* object) { + return object && Py_TYPE(object) == &TypedefPointerToClass_Type; +} + +//- custom instance method object type and type verification ----------------- +extern PyTypeObject CustomInstanceMethod_Type; + +template inline bool CustomInstanceMethod_Check(T* object) { + return object && PyObject_TypeCheck(object, &CustomInstanceMethod_Type); +} + +template inline bool CustomInstanceMethod_CheckExact(T* object) { + return object && Py_TYPE(object) == &CustomInstanceMethod_Type; +} + +PyObject* CustomInstanceMethod_New(PyObject* func, PyObject* self, + PyObject* pyclass); + +//- custom iterator for high performance std::vector iteration --------------- +struct indexiterobject { + PyObject_HEAD PyObject* ii_container; + Py_ssize_t ii_pos; + Py_ssize_t ii_len; +}; + +extern PyTypeObject IndexIter_Type; + +class Converter; +struct vectoriterobject : public indexiterobject { + void* vi_data; + Py_ssize_t vi_stride; + cppjit::cpyrt::Converter* vi_converter; + cppjit::interop::TCppScope_t vi_klass; + int vi_flags; + + enum EFlags { + kDefault = 0x0000, + kNeedLifeLine = 0x0001, + kIsPolymorphic = 0x0002, + }; +}; + +extern PyTypeObject VectorIter_Type; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CUSTOMPYTYPES_H diff --git a/src/cpyrt/DeclareConverters.h b/src/cpyrt/DeclareConverters.h new file mode 100644 index 0000000..9ca27e4 --- /dev/null +++ b/src/cpyrt/DeclareConverters.h @@ -0,0 +1,509 @@ +#ifndef CPYRT_DECLARECONVERTERS_H +#define CPYRT_DECLARECONVERTERS_H + +// Bindings +#include "Converters.h" +#include "Dimensions.h" +#include "cppjit_interop.h" + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +namespace { + +#define CPPJIT_DECLARE_BASIC_CONVERTER(name) \ + class name##Converter : public Converter { \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + std::string GetFailureMsg() override { return "[" #name "Converter]"; } \ + }; \ + \ + class Const##name##RefConverter : public Converter { \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + std::string GetFailureMsg() override { \ + return "[Const" #name "RefConverter]"; \ + } \ + } + +#define CPPJIT_DECLARE_BASIC_CONVERTER2(name, base) \ + class name##Converter : public base##Converter { \ + public: \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + std::string GetFailureMsg() override { return "[" #name "Converter]"; } \ + }; \ + \ + class Const##name##RefConverter : public Converter { \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + std::string GetFailureMsg() override { \ + return "[Const" #name "RefConverter]"; \ + } \ + } + +#define CPPJIT_DECLARE_REFCONVERTER(name) \ + class name##RefConverter : public Converter { \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + std::string GetFailureMsg() override { return "[" #name "RefConverter]"; } \ + }; + +#define CPPJIT_DECLARE_ARRAY_CONVERTER(name) \ + class name##ArrayConverter : public Converter { \ + public: \ + name##ArrayConverter(cdims_t dims); \ + name##ArrayConverter(const name##ArrayConverter&) = delete; \ + name##ArrayConverter& operator=(const name##ArrayConverter&) = delete; \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + bool HasState() override { return true; } \ + std::string GetFailureMsg() override { \ + return "[" #name "ArrayConverter]"; \ + } \ + \ + protected: \ + dims_t fShape; \ + bool fIsFixed; \ + }; + +// converters for built-ins +CPPJIT_DECLARE_BASIC_CONVERTER(Long); +CPPJIT_DECLARE_BASIC_CONVERTER(Bool); +CPPJIT_DECLARE_BASIC_CONVERTER(Char); +class SCharAsIntConverter : public CharConverter { +public: + using CharConverter::CharConverter; + PyObject* FromMemory(void*) override; +}; +CPPJIT_DECLARE_BASIC_CONVERTER(UChar); +class UCharAsIntConverter : public UCharConverter { +public: + using UCharConverter::UCharConverter; + PyObject* FromMemory(void*) override; +}; +CPPJIT_DECLARE_BASIC_CONVERTER(WChar); +CPPJIT_DECLARE_BASIC_CONVERTER(Char16); +CPPJIT_DECLARE_BASIC_CONVERTER(Char32); +CPPJIT_DECLARE_BASIC_CONVERTER(Int8); +CPPJIT_DECLARE_BASIC_CONVERTER(UInt8); +CPPJIT_DECLARE_BASIC_CONVERTER(Short); +CPPJIT_DECLARE_BASIC_CONVERTER(UShort); +CPPJIT_DECLARE_BASIC_CONVERTER(Int); +CPPJIT_DECLARE_BASIC_CONVERTER(ULong); +CPPJIT_DECLARE_BASIC_CONVERTER2(UInt, ULong); +CPPJIT_DECLARE_BASIC_CONVERTER(LLong); +CPPJIT_DECLARE_BASIC_CONVERTER(ULLong); +CPPJIT_DECLARE_BASIC_CONVERTER(Double); +CPPJIT_DECLARE_BASIC_CONVERTER(Float); +CPPJIT_DECLARE_BASIC_CONVERTER(LDouble); + +CPPJIT_DECLARE_REFCONVERTER(Bool); +CPPJIT_DECLARE_REFCONVERTER(Char); +CPPJIT_DECLARE_REFCONVERTER(WChar); +CPPJIT_DECLARE_REFCONVERTER(Char16); +CPPJIT_DECLARE_REFCONVERTER(Char32); +CPPJIT_DECLARE_REFCONVERTER(SChar); +CPPJIT_DECLARE_REFCONVERTER(UChar); +CPPJIT_DECLARE_REFCONVERTER(Int8); +CPPJIT_DECLARE_REFCONVERTER(UInt8); +CPPJIT_DECLARE_REFCONVERTER(Short); +CPPJIT_DECLARE_REFCONVERTER(UShort); +CPPJIT_DECLARE_REFCONVERTER(UInt); +CPPJIT_DECLARE_REFCONVERTER(Int); +CPPJIT_DECLARE_REFCONVERTER(Long); +CPPJIT_DECLARE_REFCONVERTER(ULong); +CPPJIT_DECLARE_REFCONVERTER(LLong); +CPPJIT_DECLARE_REFCONVERTER(ULLong); +CPPJIT_DECLARE_REFCONVERTER(Float); +CPPJIT_DECLARE_REFCONVERTER(Double); +CPPJIT_DECLARE_REFCONVERTER(LDouble); + +class VoidConverter : public Converter { +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; +}; + +class CStringConverter : public Converter { +public: + CStringConverter(std::string::size_type maxSize = std::string::npos) + : fMaxSize(maxSize) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[CStringConverter]"; } + +protected: + std::string fBuffer; + std::string::size_type fMaxSize; +}; + +class NonConstCStringConverter : public CStringConverter { +public: + using CStringConverter::CStringConverter; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + std::string GetFailureMsg() override { return "[NonConstCStringConverter]"; } +}; + +class WCStringConverter : public Converter { +public: + WCStringConverter(std::wstring::size_type maxSize = std::wstring::npos) + : fBuffer(nullptr), fMaxSize(maxSize) {} + WCStringConverter(const WCStringConverter&) = delete; + WCStringConverter& operator=(const WCStringConverter&) = delete; + virtual ~WCStringConverter() { free(fBuffer); } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[WCStringConverter]"; }; + +protected: + wchar_t* fBuffer; + std::wstring::size_type fMaxSize; +}; + +class CString16Converter : public Converter { +public: + CString16Converter(std::wstring::size_type maxSize = std::wstring::npos) + : fBuffer(nullptr), fMaxSize(maxSize) {} + CString16Converter(const CString16Converter&) = delete; + CString16Converter& operator=(const CString16Converter&) = delete; + virtual ~CString16Converter() { free(fBuffer); } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[CString16Converter]"; }; + +protected: + char16_t* fBuffer; + std::wstring::size_type fMaxSize; +}; + +class CString32Converter : public Converter { +public: + CString32Converter(std::wstring::size_type maxSize = std::wstring::npos) + : fBuffer(nullptr), fMaxSize(maxSize) {} + CString32Converter(const CString32Converter&) = delete; + CString32Converter& operator=(const CString32Converter&) = delete; + virtual ~CString32Converter() { free(fBuffer); } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[CString32Converter]"; }; + +protected: + char32_t* fBuffer; + std::wstring::size_type fMaxSize; +}; + +// pointer/array conversions +CPPJIT_DECLARE_ARRAY_CONVERTER(Bool); +CPPJIT_DECLARE_ARRAY_CONVERTER(SChar); +CPPJIT_DECLARE_ARRAY_CONVERTER(UChar); +CPPJIT_DECLARE_ARRAY_CONVERTER(Byte); +CPPJIT_DECLARE_ARRAY_CONVERTER(Int8); +CPPJIT_DECLARE_ARRAY_CONVERTER(UInt8); +CPPJIT_DECLARE_ARRAY_CONVERTER(Short); +CPPJIT_DECLARE_ARRAY_CONVERTER(UShort); +CPPJIT_DECLARE_ARRAY_CONVERTER(Int); +CPPJIT_DECLARE_ARRAY_CONVERTER(UInt); +CPPJIT_DECLARE_ARRAY_CONVERTER(Long); +CPPJIT_DECLARE_ARRAY_CONVERTER(ULong); +CPPJIT_DECLARE_ARRAY_CONVERTER(LLong); +CPPJIT_DECLARE_ARRAY_CONVERTER(ULLong); +CPPJIT_DECLARE_ARRAY_CONVERTER(Float); +CPPJIT_DECLARE_ARRAY_CONVERTER(Double); +CPPJIT_DECLARE_ARRAY_CONVERTER(LDouble); +CPPJIT_DECLARE_ARRAY_CONVERTER(ComplexF); +CPPJIT_DECLARE_ARRAY_CONVERTER(ComplexD); + +class CStringArrayConverter : public SCharArrayConverter { +public: + CStringArrayConverter(cdims_t dims, bool fixed) : SCharArrayConverter(dims) { + fIsFixed = fixed; // overrides SCharArrayConverter decision + } + using SCharArrayConverter::SCharArrayConverter; + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + std::string GetFailureMsg() override { return "[CStringArrayConverter]"; }; + +private: + std::vector fBuffer; +}; + +class NonConstCStringArrayConverter : public CStringArrayConverter { +public: + using CStringArrayConverter::CStringArrayConverter; + PyObject* FromMemory(void* address) override; + std::string GetFailureMsg() override { + return "[NonConstCStringArrayConverter]"; + }; +}; + +// converters for special cases +class NullptrConverter : public Converter { +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; +}; + +class InstanceConverter : public StrictInstancePtrConverter { +public: + using StrictInstancePtrConverter::StrictInstancePtrConverter; + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void*) override; + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[InstanceConverter]"; }; +}; + +class InstanceRefConverter : public Converter { +public: + InstanceRefConverter(cppjit::interop::TCppScope_t klass, bool isConst) + : fClass(klass), fIsConst(isConst) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[InstanceRefConverter]"; }; + +protected: + cppjit::interop::TCppScope_t fClass; + bool fIsConst; +}; + +class InstanceMoveConverter : public InstanceRefConverter { +public: + InstanceMoveConverter(cppjit::interop::TCppScope_t klass) + : InstanceRefConverter(klass, true) {} + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + std::string GetFailureMsg() override { return "[InstanceMoveConverter]"; }; +}; + +template +class InstancePtrPtrConverter : public InstancePtrConverter { +public: + using InstancePtrConverter::InstancePtrConverter; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[InstancePtrPtrConverter]"; }; +}; + +class InstanceArrayConverter : public InstancePtrConverter { +public: + InstanceArrayConverter(cppjit::interop::TCppScope_t klass, cdims_t dims, + bool keepControl = false) + : InstancePtrConverter(klass, keepControl), fShape(dims) {} + InstanceArrayConverter(const InstanceArrayConverter&) = delete; + InstanceArrayConverter& operator=(const InstanceArrayConverter&) = delete; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[InstanceArrayConverter]"; }; + +protected: + dims_t fShape; +}; + +class ComplexDConverter : public InstanceConverter { +public: + ComplexDConverter(bool keepControl = false); + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[ComplexDConverter]"; }; + +private: + std::complex fBuffer; +}; + +// Cling WORKAROUND -- classes for STL iterators are completely undefined in +// that they come in a bazillion different guises, so just do whatever +class STLIteratorConverter : public Converter { +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + std::string GetFailureMsg() override { return "[STLIteratorConverter]"; }; +}; +// -- END Cling WORKAROUND + +class VoidPtrRefConverter : public Converter { +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + std::string GetFailureMsg() override { return "[VoidPtrRefConverter]"; }; +}; + +class VoidPtrPtrConverter : public Converter { +public: + VoidPtrPtrConverter(cdims_t dims, + const std::string& failureMsg = std::string()); + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { + return "[VoidPtrPtrConverter] " + fFailureMsg; + } + +protected: + dims_t fShape; + bool fIsFixed; + const std::string fFailureMsg; +}; + +CPPJIT_DECLARE_BASIC_CONVERTER(PyObject); + +#define CPPJIT_DECLARE_STRING_CONVERTER(name, strtype) \ + class name##Converter : public InstanceConverter { \ + public: \ + name##Converter(bool keepControl = true); \ + \ + public: \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void* address) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + bool HasState() override { return true; } \ + std::string GetFailureMsg() override { return "[" #name "Converter]"; }; \ + \ + protected: \ + strtype fBuffer; \ + } + +CPPJIT_DECLARE_STRING_CONVERTER(STLString, std::string); +CPPJIT_DECLARE_STRING_CONVERTER(STLWString, std::wstring); +CPPJIT_DECLARE_STRING_CONVERTER(STLStringView, std::string_view); + +class STLStringMoveConverter : public STLStringConverter { +public: + using STLStringConverter::STLStringConverter; + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + std::string GetFailureMsg() override { return "[STLStringMoveConverter]"; }; +}; + +// function pointers +class FunctionPointerConverter : public Converter { +public: + FunctionPointerConverter(const std::string& ret, const std::string& sig) + : fRetType(ret), fSignature(sig) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[FunctionPointerConverter]"; }; + +protected: + std::string fRetType; + std::string fSignature; + bool fAllowCppInstance = false; +}; + +// std::function +class StdFunctionConverter : public FunctionPointerConverter { +public: + StdFunctionConverter(Converter* cnv, const std::string& ret, + const std::string& sig) + : FunctionPointerConverter(ret, sig), fConverter(cnv) { + fAllowCppInstance = true; + } + StdFunctionConverter(const StdFunctionConverter&) = delete; + StdFunctionConverter& operator=(const StdFunctionConverter&) = delete; + virtual ~StdFunctionConverter() { delete fConverter; } + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + std::string GetFailureMsg() override { return "[StdFunctionConverter]"; }; + +protected: + Converter* fConverter; +}; + +// smart pointer converter +class SmartPtrConverter : public Converter { +public: + SmartPtrConverter(cppjit::interop::TCppScope_t smart, + cppjit::interop::TCppScope_t underlying, + bool keepControl = false, bool isRef = false) + : fSmartPtrType(smart), fUnderlyingType(underlying), + fKeepControl(keepControl), fIsRef(isRef) {} + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[SmartPtrConverter]"; }; + +protected: + virtual bool GetAddressSpecialCase(PyObject*, void*&) { return false; } + + cppjit::interop::TCppScope_t fSmartPtrType; + cppjit::interop::TCppScope_t fUnderlyingType; + bool fKeepControl; + bool fIsRef; +}; + +// initializer lists +class InitializerListConverter : public InstanceConverter { +public: + InitializerListConverter(cppjit::interop::TCppScope_t klass, + std::string const& value_type); + InitializerListConverter(const InitializerListConverter&) = delete; + InitializerListConverter& operator=(const InitializerListConverter&) = delete; + virtual ~InitializerListConverter(); + +public: + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + bool HasState() override { return true; } + std::string GetFailureMsg() override { return "[FunctionPointerConverter]"; }; + +protected: + void Clear(); + +protected: + void* fBuffer = nullptr; + std::vector fConverters; + std::string fValueTypeName; + cppjit::interop::TCppScope_t fValueType; + size_t fValueSize; +}; + +} // unnamed namespace + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DECLARECONVERTERS_H diff --git a/src/cpyrt/DeclareExecutors.h b/src/cpyrt/DeclareExecutors.h new file mode 100644 index 0000000..c994546 --- /dev/null +++ b/src/cpyrt/DeclareExecutors.h @@ -0,0 +1,204 @@ +#ifndef CPYRT_DECLAREEXECUTORS_H +#define CPYRT_DECLAREEXECUTORS_H + +#include "Python.h" +#include "cppjit_interop.h" + +// Bindings +#include "CallContext.h" +#include "Dimensions.h" +#include "Executors.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +namespace { + +#define CPPJIT_DECL_EXEC(name) \ + class name##Executor : public Executor { \ + public: \ + PyObject* Execute(cppjit::interop::TCppMethod_t, \ + cppjit::interop::TCppObject_t, CallContext*) override; \ + } + +// executors for built-ins +CPPJIT_DECL_EXEC(Bool); +CPPJIT_DECL_EXEC(BoolConstRef); +CPPJIT_DECL_EXEC(Char); +CPPJIT_DECL_EXEC(CharConstRef); +CPPJIT_DECL_EXEC(UChar); +CPPJIT_DECL_EXEC(UCharConstRef); +CPPJIT_DECL_EXEC(WChar); +CPPJIT_DECL_EXEC(Char16); +CPPJIT_DECL_EXEC(Char32); +CPPJIT_DECL_EXEC(Int8); +CPPJIT_DECL_EXEC(Int8ConstRef); +CPPJIT_DECL_EXEC(UInt8); +CPPJIT_DECL_EXEC(UInt8ConstRef); +CPPJIT_DECL_EXEC(Short); +CPPJIT_DECL_EXEC(Int); +CPPJIT_DECL_EXEC(Long); +CPPJIT_DECL_EXEC(ULong); +CPPJIT_DECL_EXEC(LongLong); +CPPJIT_DECL_EXEC(ULongLong); +CPPJIT_DECL_EXEC(Float); +CPPJIT_DECL_EXEC(Double); +CPPJIT_DECL_EXEC(LongDouble); +CPPJIT_DECL_EXEC(Void); +CPPJIT_DECL_EXEC(CString); +CPPJIT_DECL_EXEC(CStringRef); +CPPJIT_DECL_EXEC(WCString); +CPPJIT_DECL_EXEC(CString16); +CPPJIT_DECL_EXEC(CString32); + +// pointer/array executors +#define CPPJIT_ARRAY_DECL_EXEC(name) \ + class name##ArrayExecutor : public Executor { \ + dims_t fShape; \ + \ + public: \ + name##ArrayExecutor(dims_t dims) : fShape(dims) {} \ + PyObject* Execute(cppjit::interop::TCppMethod_t, \ + cppjit::interop::TCppObject_t, CallContext*) override; \ + bool HasState() override { return true; } \ + } +CPPJIT_ARRAY_DECL_EXEC(Void); +CPPJIT_ARRAY_DECL_EXEC(Bool); +CPPJIT_ARRAY_DECL_EXEC(SChar); +CPPJIT_ARRAY_DECL_EXEC(UChar); +CPPJIT_ARRAY_DECL_EXEC(Byte); +CPPJIT_ARRAY_DECL_EXEC(Int8); +CPPJIT_ARRAY_DECL_EXEC(UInt8); +CPPJIT_ARRAY_DECL_EXEC(Short); +CPPJIT_ARRAY_DECL_EXEC(UShort); +CPPJIT_ARRAY_DECL_EXEC(Int); +CPPJIT_ARRAY_DECL_EXEC(UInt); +CPPJIT_ARRAY_DECL_EXEC(Long); +CPPJIT_ARRAY_DECL_EXEC(ULong); +CPPJIT_ARRAY_DECL_EXEC(LLong); +CPPJIT_ARRAY_DECL_EXEC(ULLong); +CPPJIT_ARRAY_DECL_EXEC(Float); +CPPJIT_ARRAY_DECL_EXEC(Double); +CPPJIT_ARRAY_DECL_EXEC(LDouble); +CPPJIT_ARRAY_DECL_EXEC(ComplexF); +CPPJIT_ARRAY_DECL_EXEC(ComplexD); +CPPJIT_ARRAY_DECL_EXEC(ComplexI); +CPPJIT_ARRAY_DECL_EXEC(ComplexL); + +// special cases +CPPJIT_DECL_EXEC(ComplexD); +CPPJIT_DECL_EXEC(STLString); +CPPJIT_DECL_EXEC(STLWString); + +class InstancePtrExecutor : public Executor { +public: + InstancePtrExecutor(cppjit::interop::TCppScope_t klass) : fClass(klass) {} + PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) override; + bool HasState() override { return true; } + +protected: + cppjit::interop::TCppScope_t fClass; +}; + +class InstanceExecutor : public Executor { +public: + InstanceExecutor(cppjit::interop::TCppScope_t klass); + PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) override; + bool HasState() override { return true; } + +protected: + cppjit::interop::TCppScope_t fClass; + uint32_t fFlags; +}; + +class IteratorExecutor : public InstanceExecutor { +public: + IteratorExecutor(cppjit::interop::TCppScope_t klass); +}; + +CPPJIT_DECL_EXEC(Constructor); +CPPJIT_DECL_EXEC(PyObject); + +#define CPPJIT_DECL_REFEXEC(name) \ + class name##RefExecutor : public RefExecutor { \ + public: \ + PyObject* Execute(cppjit::interop::TCppMethod_t, \ + cppjit::interop::TCppObject_t, CallContext*) override; \ + } + +CPPJIT_DECL_REFEXEC(Bool); +CPPJIT_DECL_REFEXEC(Char); +CPPJIT_DECL_REFEXEC(UChar); +CPPJIT_DECL_REFEXEC(Int8); +CPPJIT_DECL_REFEXEC(UInt8); +CPPJIT_DECL_REFEXEC(Short); +CPPJIT_DECL_REFEXEC(UShort); +CPPJIT_DECL_REFEXEC(Int); +CPPJIT_DECL_REFEXEC(UInt); +CPPJIT_DECL_REFEXEC(Long); +CPPJIT_DECL_REFEXEC(ULong); +CPPJIT_DECL_REFEXEC(LongLong); +CPPJIT_DECL_REFEXEC(ULongLong); +CPPJIT_DECL_REFEXEC(Float); +CPPJIT_DECL_REFEXEC(Double); +CPPJIT_DECL_REFEXEC(LongDouble); +CPPJIT_DECL_REFEXEC(ComplexD); +CPPJIT_DECL_REFEXEC(STLString); + +// special cases +class InstanceRefExecutor : public RefExecutor { +public: + InstanceRefExecutor(cppjit::interop::TCppScope_t klass) : fClass(klass) {} + PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) override; + +protected: + cppjit::interop::TCppScope_t fClass; +}; + +class InstancePtrPtrExecutor : public InstanceRefExecutor { +public: + using InstanceRefExecutor::InstanceRefExecutor; + PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) override; +}; + +class InstancePtrRefExecutor : public InstanceRefExecutor { +public: + using InstanceRefExecutor::InstanceRefExecutor; + PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) override; +}; + +class InstanceArrayExecutor : public InstancePtrExecutor { +public: + InstanceArrayExecutor(cppjit::interop::TCppScope_t klass, dim_t array_size) + : InstancePtrExecutor(klass), fSize(array_size) {} + PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) override; + +protected: + dim_t fSize; +}; + +class FunctionPointerExecutor : public Executor { +public: + FunctionPointerExecutor(const std::string& ret, const std::string& sig) + : fRetType(ret), fSignature(sig) {} + PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) override; + +protected: + std::string fRetType; + std::string fSignature; +}; + +} // unnamed namespace + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DECLAREEXECUTORS_H diff --git a/src/cpyrt/Dimensions.h b/src/cpyrt/Dimensions.h new file mode 100644 index 0000000..a63e636 --- /dev/null +++ b/src/cpyrt/Dimensions.h @@ -0,0 +1,90 @@ +#ifndef CPYRT_DIMENSIONS_H +#define CPYRT_DIMENSIONS_H + +#include "Python.h" +#include "cppjit_interop.h" + +// Bindings +#include "cpyrt/CommonDefs.h" + +// Standard +#include +#include + +namespace cppjit::cpyrt { + +static const dim_t UNKNOWN_SIZE = (dim_t)-1; + +class CPYRT_CLASS_EXPORT Dimensions { + dim_t* fDims; + +public: + Dimensions(dim_t ndim = 0, dim_t* dims = nullptr) : fDims(nullptr) { + if (ndim && ndim != UNKNOWN_SIZE) { + fDims = new dim_t[ndim + 1]; + fDims[0] = ndim; + if (dims) + std::copy(dims, dims + ndim, fDims + 1); + else + std::fill_n(fDims + 1, ndim, UNKNOWN_SIZE); + } + } + Dimensions(std::initializer_list l) { + fDims = new dim_t[l.size() + 1]; + fDims[0] = l.size(); + std::copy(l.begin(), l.end(), fDims + 1); + } + Dimensions(const Dimensions& d) : fDims(nullptr) { + if (d.fDims) { + fDims = new dim_t[d.fDims[0] + 1]; + std::copy(d.fDims, d.fDims + d.fDims[0] + 1, fDims); + } + } + Dimensions(Dimensions&& d) : fDims(d.fDims) { d.fDims = nullptr; } + Dimensions& operator=(const Dimensions& d) { + if (this != &d) { + if (!d.fDims) { + delete[] fDims; + fDims = nullptr; + } else { + if (!fDims || (fDims && fDims[0] != d.fDims[0])) { + delete[] fDims; + fDims = new dim_t[d.fDims[0] + 1]; + } + std::copy(d.fDims, d.fDims + d.fDims[0] + 1, fDims); + } + } + return *this; + } + ~Dimensions() { delete[] fDims; } + +public: + operator bool() const { return (bool)fDims; } + + dim_t ndim() const { return fDims ? fDims[0] : UNKNOWN_SIZE; } + void ndim(dim_t d) { + if (fDims) { + if (fDims[0] == d) + return; + delete[] fDims; + } + + fDims = new dim_t[d + 1]; + fDims[0] = d; + std::fill_n(fDims + 1, d, UNKNOWN_SIZE); + } + + dim_t operator[](dim_t i) const { return fDims[i + 1]; } + dim_t& operator[](dim_t i) { return fDims[i + 1]; } + + Dimensions sub() const { + return fDims ? Dimensions(fDims[0] - 1, fDims + 2) : Dimensions(); + } +}; + +typedef Dimensions dims_t; +typedef const dims_t& cdims_t; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DIMENSIONS_H diff --git a/src/cpyrt/DispatchPtr.cxx b/src/cpyrt/DispatchPtr.cxx new file mode 100644 index 0000000..cc7c25c --- /dev/null +++ b/src/cpyrt/DispatchPtr.cxx @@ -0,0 +1,114 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#define CPYRT_INTERNAL 1 +#include "cpyrt/DispatchPtr.h" +#undef CPYRT_INTERNAL +#include "CPPInstance.h" +#include "CPPScope.h" + +//----------------------------------------------------------------------------- +PyObject* cppjit::cpyrt::DispatchPtr::Get(bool borrowed) const { + PythonGILRAII python_gil_raii; + if (fPyHardRef) { + if (!borrowed) + Py_INCREF(fPyHardRef); + return fPyHardRef; + } + if (fPyWeakRef) { + PyObject* disp = cpyrt_GetWeakRef(fPyWeakRef); + if (disp) { // dispatcher object disappeared? + if (borrowed) + Py_DECREF(disp); + return disp; + } + } + return nullptr; +} + +//----------------------------------------------------------------------------- +cppjit::cpyrt::DispatchPtr::DispatchPtr(PyObject* pyobj, bool strong) + : fPyHardRef(nullptr) { + PythonGILRAII python_gil_raii; + if (strong) { + Py_INCREF(pyobj); + fPyHardRef = pyobj; + fPyWeakRef = nullptr; + } else { + fPyHardRef = nullptr; + fPyWeakRef = PyWeakref_NewRef(pyobj, nullptr); + } + ((CPPInstance*)pyobj)->SetDispatchPtr(this); +} + +//----------------------------------------------------------------------------- +cppjit::cpyrt::DispatchPtr::DispatchPtr(const DispatchPtr& other, void* cppinst) + : fPyWeakRef(nullptr) { + PythonGILRAII python_gil_raii; + PyObject* pyobj = other.Get(false /* not borrowed */); + fPyHardRef = + pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; + if (fPyHardRef) + ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); + Py_XDECREF(pyobj); +} + +//----------------------------------------------------------------------------- +cppjit::cpyrt::DispatchPtr::~DispatchPtr() { + // if we're holding a hard reference, or holding weak reference while being + // part of a dispatcher intermediate, then this delete is from the C++ side, + // and Python is "notified" by nulling out the reference and an exception will + // be raised on continued access + PythonGILRAII python_gil_raii; + if (fPyWeakRef) { + PyObject* pyobj = cpyrt_GetWeakRef(fPyWeakRef); + if (pyobj && ((CPPScope*)Py_TYPE(pyobj))->fFlags & CPPScope::kIsPython) + ((CPPInstance*)pyobj)->GetObjectRaw() = nullptr; + Py_XDECREF(pyobj); + Py_DECREF(fPyWeakRef); + } else if (fPyHardRef) { + ((CPPInstance*)fPyHardRef)->GetObjectRaw() = nullptr; + Py_DECREF(fPyHardRef); + } +} + +//----------------------------------------------------------------------------- +cppjit::cpyrt::DispatchPtr& +cppjit::cpyrt::DispatchPtr::assign(const DispatchPtr& other, void* cppinst) { + PythonGILRAII python_gil_raii; + if (this != &other) { + Py_XDECREF(fPyWeakRef); + fPyWeakRef = nullptr; + Py_XDECREF(fPyHardRef); + PyObject* pyobj = other.Get(false /* not borrowed */); + fPyHardRef = + pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; + if (fPyHardRef) + ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); + Py_XDECREF(pyobj); + } + return *this; +} + +//----------------------------------------------------------------------------- +void cppjit::cpyrt::DispatchPtr::PythonOwns() { + PythonGILRAII python_gil_raii; + // Python maintains the hardref, so only allowed a weakref here + if (fPyHardRef) { + fPyWeakRef = PyWeakref_NewRef(fPyHardRef, nullptr); + Py_DECREF(fPyHardRef); + fPyHardRef = nullptr; + } +} + +//----------------------------------------------------------------------------- +void cppjit::cpyrt::DispatchPtr::CppOwns() { + PythonGILRAII python_gil_raii; + // C++ maintains the hardref, keeping the PyObject alive w/o outstanding ref + if (fPyWeakRef) { + fPyHardRef = cpyrt_GetWeakRef(fPyWeakRef); + Py_DECREF(fPyWeakRef); + fPyWeakRef = nullptr; + } +} diff --git a/src/cpyrt/DispatchPtr.h b/src/cpyrt/DispatchPtr.h new file mode 100644 index 0000000..75cbe9d --- /dev/null +++ b/src/cpyrt/DispatchPtr.h @@ -0,0 +1,80 @@ +#ifndef CPYRT_DISPATCHPTR_H +#define CPYRT_DISPATCHPTR_H + +////////////////////////////////////////////////////////////////////////////// +// // +// DispatchPtr // +// // +// Smart pointer for reference management and C++ instance tracking when // +// cross-inheriting. The carried pointer is always expected to be derived // +// from CPPInstance, and the DispatchPtr to be embedded in the C++ instance // +// derived dispatcher to which it points (ownership is two-way; life-times // +// are equal). The C++ dispatcher then uses the DispatchPtr to call Python // +// functions for virtual methods. // +// // +////////////////////////////////////////////////////////////////////////////// + +// Bindings +#include +#include "cpyrt/CommonDefs.h" + +namespace cppjit::cpyrt { +class PythonGILRAII { + PyGILState_STATE state; + +public: + PythonGILRAII() : state(PyGILState_Ensure()) {} + ~PythonGILRAII() { PyGILState_Release(state); } +}; + +class CPYRT_CLASS_EXTERN DispatchPtr { +public: + // Default constructor: only ever created from C++, as by definition, creation + // from the Python side makes the relevant Python instance available. Calls to + // the default ctor happen eg. in STL containers. It is expected that the + // pointer to the Python object is filled in later, eg. through assign(). + DispatchPtr() : fPyHardRef(nullptr), fPyWeakRef(nullptr) {} + + // Conversion constructor: called with C++ object construction when the + // PyObject is known (eg. when instantiating from Python), with pyobj the + // Python-side representation of the C++ object. + explicit DispatchPtr(PyObject* pyobj, bool strong = false); + + // Copy constructor: only ever called from C++. The Python object needs to be + // copied, in case it has added state, and rebound to the new C++ instance. + DispatchPtr(const DispatchPtr& other, void* cppinst); + + // Assignment: only ever called from C++. Similarly to the copy constructor, + // the Pythonb object needs to be copied and rebound. + DispatchPtr& assign(const DispatchPtr& other, void* cppinst); + + // Do not otherwise allow straight copies/assignment. + DispatchPtr(DispatchPtr&) = delete; + DispatchPtr(DispatchPtr&&) = delete; + DispatchPtr& operator=(const DispatchPtr& other) = delete; + + // lifetime is directly bound to the lifetime of the dispatcher object + ~DispatchPtr(); + + // either C++ owns the Python object through a reference count (on fPyHardRef) + // or Python owns the C++ object and we only have a weak reference (through + // fPyWeakRef) + void PythonOwns(); + void CppOwns(); + + // access to underlying object: cast and dereferencing + operator PyObject*() const { return Get(); } + + PyObject* operator->() const { return Get(); } + +private: + PyObject* Get(bool borrowed = true) const; + +private: + PyObject* fPyHardRef; + PyObject* fPyWeakRef; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DISPATCHPTR_H diff --git a/src/cpyrt/Dispatcher.cxx b/src/cpyrt/Dispatcher.cxx new file mode 100644 index 0000000..1fafb48 --- /dev/null +++ b/src/cpyrt/Dispatcher.cxx @@ -0,0 +1,567 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPScope.h" +#include "Dispatcher.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include + +//---------------------------------------------------------------------------- +static inline void InjectMethod(interop::TCppMethod_t method, + const std::string& mtCppName, + std::ostringstream& code) { + // inject implementation for an overridden method + using namespace cppjit::cpyrt; + + // method declaration + std::string retType = interop::GetMethodReturnTypeAsString(method); + code << " " << retType << " " << mtCppName << "("; + + // build out the signature with predictable formal names + interop::TCppIndex_t nArgs = interop::GetMethodNumArgs(method); + std::vector argtypes; + argtypes.reserve(nArgs); + for (interop::TCppIndex_t i = 0; i < nArgs; ++i) { + argtypes.push_back(interop::GetMethodArgCanonTypeAsString(method, i)); + if (i != 0) + code << ", "; + code << argtypes.back() << " arg" << i; + } + code << ") "; + if (interop::IsConstMethod(method)) + code << "const "; + code << "{\n"; + + // on destruction, the Python object may go first, in which case provide a + // diagnostic warning (raising a PyException may not be possible as this could + // happen during program shutdown); note that this means that the actual + // result will be the default and the caller may need to act on that, but + // that's still an improvement over a possible crash + code << " cppjit::cpyrt::PythonGILRAII python_gil_raii;\n" // acquire GIL + " PyObject* iself = (PyObject*)_internal_self;\n" + " if (!iself || iself == Py_None) {\n" + " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on " + "deleted python-side proxy\");\n" + " return"; + if (retType != "void") { + if (retType.back() != '*') + code << " (" << cpyrt::TypeManip::remove_const(retType) << "){}"; + else + code << " nullptr"; + } + code << ";\n" + " }\n" + " Py_INCREF(iself);\n"; + + // start actual function body + Utility::ConstructCallbackPreamble(retType, argtypes, code); + + // perform actual method call + code << " PyObject* mtPyName = PyUnicode_FromString(\"" << mtCppName + << "\");\n" + " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, mtPyName"; + for (interop::TCppIndex_t i = 0; i < nArgs; ++i) + code << ", pyargs[" << i << "]"; + code << ", NULL);\n Py_DECREF(mtPyName);\n Py_DECREF(iself);\n"; + + // close + Utility::ConstructCallbackReturn(retType, (int)nArgs, code); +} + +//---------------------------------------------------------------------------- +namespace { +struct BaseInfo { + BaseInfo(interop::TCppScope_t t, std::string&& bn, std::string&& bns) + : btype(t), bname(bn), bname_scoped(bns) {} + interop::TCppScope_t btype; + std::string bname; + std::string bname_scoped; +}; + +typedef std::vector BaseInfos_t; +typedef std::vector Ctors_t; +typedef std::vector AllCtors_t; +typedef std::vector> CtorInfos_t; +} // unnamed namespace + +static void build_constructors(const std::string& derivedName, + const BaseInfos_t& base_infos, + const AllCtors_t& ctors, + std::ostringstream& code, + const CtorInfos_t& methods = CtorInfos_t{}, + size_t idx = 0) { + if (idx < ctors.size()) { + for (const auto& method : ctors[idx]) { + size_t argsmin = (size_t)interop::GetMethodReqArgs(method); + size_t argsmax = (size_t)interop::GetMethodNumArgs(method); + for (size_t i = argsmin; i <= argsmax; ++i) { + CtorInfos_t methods1{methods}; + methods1.emplace_back(method, i); + build_constructors(derivedName, base_infos, ctors, code, methods1, + idx + 1); + } + } + } else { + // this is as deep as we go; start writing + code << " " << derivedName << "("; + + // declare arguments + std::vector arg_tots; + arg_tots.reserve(methods.size()); + for (Ctors_t::size_type i = 0; i < methods.size(); ++i) { + const auto& cinfo = methods[i]; + if (i != 0 && (arg_tots.back() || 1 < arg_tots.size())) + code << ", "; + size_t nArgs = cinfo.second; + arg_tots.push_back(i == 0 ? nArgs : nArgs + arg_tots.back()); + + if (i != 0) + code << "__cppjit_internal::Sep*"; + size_t offset = (i != 0 ? arg_tots[i - 1] : 0); + for (size_t j = 0; j < nArgs; ++j) { + if (i != 0 || j != 0) + code << ", "; + code << interop::GetMethodArgCanonTypeAsString(cinfo.first, j) << " a" + << (j + offset); + } + } + code << ") : "; + + // pass arguments to base constructors + for (BaseInfos_t::size_type i = 0; i < base_infos.size(); ++i) { + if (i != 0) + code << ", "; + code << base_infos[i].bname << "("; + size_t first = (i != 0 ? arg_tots[i - 1] : 0); + for (size_t j = first; j < arg_tots[i]; ++j) { + if (j != first) + code << ", "; + bool isRValue = + cpyrt::TypeManip::compound(interop::GetMethodArgCanonTypeAsString( + methods[i].first, j - first)) == "&&"; + if (isRValue) + code << "std::move("; + code << "a" << j; + if (isRValue) + code << ")"; + } + code << ")"; + } + code << " {}\n"; + } +} + +namespace { + +using namespace cppjit::interop; + +static inline std::vector +FindBaseMethod(TCppScope_t tbase, const std::string mtCppName) { + // Recursively walk the inheritance tree to find the overloads of the named + // method + std::vector result; + result = GetMethodsFromName(tbase, mtCppName); + if (result.empty()) { + for (TCppIndex_t ibase = 0; ibase < GetNumBases(tbase); ++ibase) { + TCppScope_t b = interop::GetBaseScope(tbase, ibase); + result = FindBaseMethod(b, mtCppName); + if (!result.empty()) + break; + } + } + return result; +} + +} // unnamed namespace + +bool cpyrt::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, + std::ostringstream& err) { + // Scan all methods in dct and where it overloads base methods in klass, + // create dispatchers on the C++ side. Then interject the dispatcher class. + + if (!PyTuple_Check(bases) || !PyTuple_GET_SIZE(bases) || !dct || + !PyDict_Check(dct)) { + err << "internal error: expected tuple of bases and proper dictionary"; + return false; + } + + if (!Utility::IncludePython()) { + err << "failed to include Python.h"; + return false; + } + + // collect all bases, error checking the hierarchy along the way + const Py_ssize_t nBases = PyTuple_GET_SIZE(bases); + BaseInfos_t base_infos; + base_infos.reserve(nBases); + for (Py_ssize_t ibase = 0; ibase < nBases; ++ibase) { + if (!CPPScope_Check(PyTuple_GET_ITEM(bases, ibase))) + continue; + + interop::TCppScope_t basetype = + ((CPPScope*)PyTuple_GET_ITEM(bases, ibase))->fCppType; + + if (!basetype) { + err << "base class is incomplete"; + break; + } + + if (interop::IsNamespace(basetype)) { + err << interop::GetScopedFinalName(basetype) << " is a namespace"; + break; + } + + if (!interop::HasVirtualDestructor(basetype)) { + const std::string& bname = interop::GetScopedFinalName(basetype); + if (PyErr_WarnEx( + PyExc_RuntimeWarning, + (char*)("class \"" + bname + "\" has no virtual destructor") + .c_str(), + 1) < 0) + return false; + } + + base_infos.emplace_back( + basetype, TypeManip::template_base(interop::GetFinalName(basetype)), + interop::GetScopedFinalName(basetype)); + } + + // TODO: check deep hierarchy for multiple inheritance + bool isDeepHierarchy = + klass->fCppType && base_infos.front().btype != klass->fCppType; + + // once classes can be extended, should consider re-use; for now, since + // derived python classes can differ in what they override, simply use + // different shims + static int counter = 0; + std::ostringstream osname; + osname << "Dispatcher" << ++counter; + const std::string& derivedName = osname.str(); + + // generate proxy class with the relevant method dispatchers + std::ostringstream code; + + // start class declaration + code << "namespace __cppjit_internal {\n" + << "class " << derivedName << " : "; + for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { + if (ibase != 0) + code << ", "; + code << "public ::" << base_infos[ibase].bname_scoped; + } + code << " {\n"; + if (!isDeepHierarchy) + code << "protected:\n cppjit::cpyrt::DispatchPtr _internal_self;\n"; + code << "public:\n"; + + // add a virtual destructor for good measure, which is allowed to be + // "overridden" by the conventional __destruct__ method (note that __del__ is + // always called, too, if provided, but only when the Python object goes away; + // furthermore, if the Python object goes before the C++ one, only __del__ is + // called) + if (PyMapping_HasKeyString(dct, (char*)"__destruct__")) { + code << " virtual ~" << derivedName + << "() {\n" + " cppjit::cpyrt::PythonGILRAII python_gil_raii;\n" + " PyObject* iself = (PyObject*)_internal_self;\n" + " if (!iself || iself == Py_None)\n" + " return;\n" // safe, as destructor always returns void + " Py_INCREF(iself);\n" + " PyObject* mtPyName = PyUnicode_FromString(\"__destruct__\");\n" + " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, " + "mtPyName, NULL);\n" + " Py_DECREF(mtPyName);\n Py_DECREF(iself);\n"; + + // this being a destructor, print on exception rather than propagate using + // the magic C++ exception ... + code << " if (!pyresult) PyErr_Print();\n" + " else { Py_DECREF(pyresult); }\n" + " }\n"; + } else + code << " virtual ~" << derivedName << "() {}\n"; + + // methods: first collect all callables, then get overrides from base classes, + // for those that are still missing, search the hierarchy + PyObject* clbs = PyDict_New(); + PyObject* items = PyDict_Items(dct); + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); ++i) { + PyObject* value = PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 1); + if (PyCallable_Check(value)) + PyDict_SetItem(clbs, PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 0), + value); + } + Py_DECREF(items); + if (PyDict_DelItem(clbs, PyStrings::gInit) != 0) + PyErr_Clear(); + + // protected methods and data need their access changed in the C++ trampoline + // and then exposed on the Python side; so, collect their names as we go along + std::set protected_names; + + // simple case: methods from current class (collect constructors along the + // way) + int has_default = 0, has_cctor = 0, has_ctors = 0, has_tmpl_ctors = 0; + AllCtors_t ctors{base_infos.size()}; + for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { + const auto& binfo = base_infos[ibase]; + + std::vector methods; + interop::GetClassMethods(binfo.btype, methods); + bool cctor_found = false, default_found = false, any_ctor_found = false; + for (auto& method : methods) { + if (interop::IsConstructor(method)) { + any_ctor_found = true; + if ((interop::IsPublicMethod(method) || + interop::IsProtectedMethod(method)) && + !interop::IsDeletedMethod(method)) { + interop::TCppIndex_t nreq = interop::GetMethodReqArgs(method); + if (nreq == 0) + default_found = true; + else if (!cctor_found && nreq == 1) { + const std::string& argtype = + interop::GetMethodArgCanonTypeAsString(method, 0); + if (TypeManip::compound(argtype) == "&" && + TypeManip::clean_type(argtype, false) == binfo.bname_scoped) + cctor_found = true; + } + ctors[ibase].push_back(method); + } + continue; + } + + std::string mtCppName = + interop::GetName(interop::TCppScope_t(method.data)); + PyObject* key = cpyrt_PyText_FromString(mtCppName.c_str()); + int contains = PyDict_Contains(dct, key); + if (contains == -1) + PyErr_Clear(); + if (contains != 1) { + Py_DECREF(key); + + // if the method is protected, we expose it through re-declaration and + // forwarding (using does not work here b/c there may be private + // overloads) + if (interop::IsProtectedMethod(method)) { + protected_names.insert(mtCppName); + + code << " " << interop::GetMethodReturnTypeAsString(method) << " " + << mtCppName << "("; + interop::TCppIndex_t nArgs = interop::GetMethodNumArgs(method); + for (interop::TCppIndex_t i = 0; i < nArgs; ++i) { + if (i != 0) + code << ", "; + code << interop::GetMethodArgCanonTypeAsString(method, i) << " arg" + << i; + } + code << ") "; + if (interop::IsConstMethod(method)) + code << "const "; + code << "{\n return " << binfo.bname << "::" << mtCppName << "("; + for (interop::TCppIndex_t i = 0; i < nArgs; ++i) { + if (i != 0) + code << ", "; + if (interop::IsRValueReferenceType( + interop::GetMethodArgType(method, i))) + code << "std::move(arg" << i << ")"; + else + code << "arg" << i; + } + code << ");\n }\n"; + } + + continue; + } + + InjectMethod(method, mtCppName, code); + + if (PyDict_DelItem(clbs, key) != 0) + PyErr_Clear(); // happens for overloads + Py_DECREF(key); + } + + // support for templated ctors in single inheritance (TODO: also multi + // possible?) + if (base_infos.size() == 1) { + std::vector templ_methods; + interop::GetTemplatedMethods(binfo.btype, templ_methods); + for (auto& method : templ_methods) { + if (interop::IsConstructor(method)) { + any_ctor_found = true; + has_tmpl_ctors += 1; + break; // one suffices to map as argument packs are used + } + } + } + + // count the cctors and default ctors to determine whether each base has one + if (cctor_found || (!cctor_found && !any_ctor_found)) + has_cctor += 1; + if (default_found || (!default_found && !any_ctor_found)) + has_default += 1; + if (any_ctor_found && !has_tmpl_ctors) + has_ctors += 1; + } + + // try to locate left-overs in base classes + for (const auto& binfo : base_infos) { + if (PyDict_Size(clbs)) { + size_t nbases = interop::GetNumBases(binfo.btype); + for (size_t ibase = 0; ibase < nbases; ++ibase) { + interop::TCppScope_t tbase = + (interop::TCppScope_t)interop::GetBaseScope(binfo.btype, ibase); + + PyObject* keys = PyDict_Keys(clbs); + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); ++i) { + // TODO: should probably invert this looping; but that makes handling + // overloads clunky + PyObject* key = PyList_GET_ITEM(keys, i); + std::string mtCppName = cpyrt_PyText_AsString(key); + const auto& methods = FindBaseMethod(tbase, mtCppName); + for (auto method : methods) + InjectMethod(method, mtCppName, code); + if (!methods.empty()) { + if (PyDict_DelItem(clbs, key) != 0) + PyErr_Clear(); + } + } + Py_DECREF(keys); + } + } + } + Py_DECREF(clbs); + + // constructors: build up from the argument types of the base class, for use + // by the Python derived class (inheriting with/ "using" does not work b/c + // base class constructors may have been deleted), + build_constructors(derivedName, base_infos, ctors, code); + + // for working with C++ templates, additional constructors are needed to make + // sure the python object is properly carried, but they can only be generated + // if the base class supports them + if (1 < nBases && (!has_ctors || has_default == nBases)) + code << " " << derivedName << "() {}\n"; + if (has_cctor == nBases) { + code << " " << derivedName << "(const " << derivedName << "& other) : "; + for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { + if (ibase != 0) + code << ", "; + code << base_infos[ibase].bname << "(other)"; + } + if (!isDeepHierarchy) + code << ", _internal_self(other._internal_self, this)"; + code << " {}\n"; + } + if (has_tmpl_ctors && base_infos.size() == 1) { + // support for templated ctors in single inheritance (TODO: also multi + // possible?) + code << " template\n " << derivedName + << "(Args... args) : " << base_infos[0].bname << "(args...) {}\n"; + } + + // destructor: default is fine + + // pull in data members that are protected + bool setPublic = false; + for (const auto& binfo : base_infos) { + std::vector datamems; + interop::GetDatamembers(binfo.btype, datamems); + for (auto data : datamems) { + if (interop::IsProtectedData(data)) { + const std::string dm_name = interop::GetFinalName(data); + if (dm_name != "_internal_self") { + const std::string& dname = interop::GetFinalName(data); + protected_names.insert(dname); + if (!setPublic) { + code << "public:\n"; + setPublic = true; + } + code << " using " << binfo.bname << "::" << dname << ";\n"; + } + } + } + } + + // initialize the dispatch pointer for all direct bases that have one + BaseInfos_t::size_type disp_inited = 0; + code << "public:\n static void _init_dispatchptr(" << derivedName + << "* inst, PyObject* self) {\n"; + if (1 < base_infos.size()) { + for (const auto& binfo : base_infos) { + if (interop::CheckDatamember(binfo.btype, "_internal_self")) { + code << " " << binfo.bname << "::_init_dispatchptr(inst, self);\n"; + disp_inited += 1; + } + } + } + // The dispatch initializer is only used in constructors, and C++ object start + // out as owned by C++, with Python ownership explicitly set only later. To + // match, the dispatch pointer needs to start out with a hard reference, i.e. + // C++ ownership of the dispatch object. If the constructor has __creates__ + // set to True (default), then a call to PythonOwns() will switch the hard ref + // to a weak ref, preventing accidental circular references. + if (disp_inited != base_infos.size()) + code << " new ((void*)&inst->_internal_self) " + "cppjit::cpyrt::DispatchPtr{self, true};\n"; + code << " }"; + + // provide an accessor to re-initialize after round-tripping from C++ + // (internal) + code << "\n static PyObject* _get_dispatch(" << derivedName + << "* inst) {\n" + " cppjit::cpyrt::PythonGILRAII python_gil_raii;\n" + " PyObject* res = (PyObject*)inst->_internal_self;\n" + " Py_XINCREF(res); return res;\n }"; + + // finish class declaration + code << "};\n}"; + + // finally, compile the code + if (!interop::Compile(code.str())) { + err << "failed to compile the dispatcher code"; + return false; + } + + // keep track internally of the actual C++ type (this is used in + // CPPConstructor to call the dispatcher's one instead of the base) + interop::TCppScope_t disp = + interop::GetFullScope("__cppjit_internal::" + derivedName); + if (!disp) { + err << "failed to retrieve the internal dispatcher"; + return false; + } + klass->fCppType = disp; + + // at this point, the dispatcher only lives in C++, as opposed to regular + // classes that are part of the hierarchy in Python, so create it, which will + // cache it for later use by e.g. the MemoryRegulator + unsigned int flags = (unsigned int)(klass->fFlags & CPPScope::kIsMultiCross); + PyObject* disp_proxy = cpyrt::CreateScopeProxy(disp, 0, flags); + if (flags) + ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsMultiCross; + ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsPython; + + // finally, to expose protected members, copy them over from the C++ + // dispatcher base to the Python dictionary (the C++ dispatcher's Python proxy + // is not a base of the Python class to keep the inheritance tree intact) + for (const auto& name : protected_names) { + PyObject* disp_dct = PyObject_GetAttr(disp_proxy, PyStrings::gDict); + PyObject* pyf = PyMapping_GetItemString(disp_dct, (char*)name.c_str()); + if (pyf) { + PyObject_SetAttrString((PyObject*)klass, (char*)name.c_str(), pyf); + Py_DECREF(pyf); + } + Py_DECREF(disp_dct); + } + + Py_XDECREF(disp_proxy); + + return true; +} diff --git a/src/cpyrt/Dispatcher.h b/src/cpyrt/Dispatcher.h new file mode 100644 index 0000000..3621ee8 --- /dev/null +++ b/src/cpyrt/Dispatcher.h @@ -0,0 +1,17 @@ +#ifndef CPYRT_DISPATCHER_H +#define CPYRT_DISPATCHER_H + +// Standard +#include + +namespace cppjit::cpyrt { + +class CPPScope; + +// helper that inserts dispatchers for virtual methods +bool InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, + std::ostringstream& err); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_DISPATCHER_H diff --git a/src/cpyrt/Executors.cxx b/src/cpyrt/Executors.cxx new file mode 100644 index 0000000..b2508e9 --- /dev/null +++ b/src/cpyrt/Executors.cxx @@ -0,0 +1,1469 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "DeclareExecutors.h" +#include "LowLevelViews.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include + +//- data _____________________________________________________________________ +namespace cppjit::cpyrt { +typedef std::unordered_map ExecFactories_t; +static ExecFactories_t gExecFactories; + +extern PyObject* gNullPtrObject; + +extern std::set gIteratorTypes; +} // namespace cppjit::cpyrt + +//- helpers ------------------------------------------------------------------ +namespace { + +#ifdef WITH_THREAD +class GILControl { +public: + GILControl() : fSave(PyEval_SaveThread()) {} + ~GILControl() { PyEval_RestoreThread(fSave); } + +private: + PyThreadState* fSave; +}; +#endif + +} // unnamed namespace + +#ifdef WITH_THREAD +#define CPPJIT_IMPL_GILCALL(rtype, tcode) \ + static inline rtype GILCall##tcode(interop::TCppMethod_t method, \ + interop::TCppObject_t self, \ + cpyrt::CallContext* ctxt) { \ + if (!ReleasesGIL(ctxt)) \ + return interop::Call##tcode(method, self, ctxt->GetEncodedSize(), \ + ctxt->GetArgs()); \ + GILControl gc{}; \ + return interop::Call##tcode(method, self, ctxt->GetEncodedSize(), \ + ctxt->GetArgs()); \ + } +#else +#define CPPJIT_IMPL_GILCALL(rtype, tcode) \ + static inline rtype GILCall##tcode(interop::TCppMethod_t method, \ + interop::TCppObject_t self, \ + cpyrt::CallContext* ctxt) { \ + return interop::Call##tcode(method, self, ctxt->GetEncodedSize(), \ + ctxt->GetArgs()); \ + } +#endif + +// clang-format off +CPPJIT_IMPL_GILCALL(void, V) +CPPJIT_IMPL_GILCALL(unsigned char, B) +CPPJIT_IMPL_GILCALL(char, C) +CPPJIT_IMPL_GILCALL(short, H) +CPPJIT_IMPL_GILCALL(int, I) +CPPJIT_IMPL_GILCALL(long, L) +CPPJIT_IMPL_GILCALL(PY_LONG_LONG, LL) +CPPJIT_IMPL_GILCALL(float, F) +CPPJIT_IMPL_GILCALL(double, D) +CPPJIT_IMPL_GILCALL(PY_LONG_DOUBLE, LD) +CPPJIT_IMPL_GILCALL(void*, R) +// clang-format on + +static inline interop::TCppObject_t GILCallO(interop::TCppMethod_t method, + interop::TCppObject_t self, + cpyrt::CallContext* ctxt, + interop::TCppScope_t klass) { + interop::TCppType_t klass_ty = interop::GetTypeFromScope(klass); +#ifdef WITH_THREAD + if (!ReleasesGIL(ctxt)) +#endif + return interop::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), + klass_ty); +#ifdef WITH_THREAD + GILControl gc{}; + return interop::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), + klass_ty); +#endif +} + +static inline interop::TCppObject_t +GILCallConstructor(interop::TCppMethod_t method, interop::TCppScope_t klass, + cpyrt::CallContext* ctxt) { +#ifdef WITH_THREAD + if (!ReleasesGIL(ctxt)) +#endif + return interop::CallConstructor(method, klass, ctxt->GetEncodedSize(), + ctxt->GetArgs()); +#ifdef WITH_THREAD + GILControl gc{}; + return interop::CallConstructor(method, klass, ctxt->GetEncodedSize(), + ctxt->GetArgs()); +#endif +} + +static inline PyObject* cpyrt_PyText_FromLong(long cl) { + // python chars are range(256) + if (cl < -256 || cl > 255) { + PyErr_SetString(PyExc_ValueError, "char conversion out of range"); + return nullptr; + } + int c = (int)cl; + if (c < 0) + return cpyrt_PyText_FromFormat("%c", 256 - std::abs(c)); + return cpyrt_PyText_FromFormat("%c", c); +} + +static inline PyObject* cpyrt_PyText_FromULong(unsigned long uc) { + // TODO: range check here? + if (255 < uc) { + PyErr_SetString(PyExc_ValueError, "char conversion out of range"); + return nullptr; + } + int c = (int)uc; + return cpyrt_PyText_FromFormat("%c", c); +} + +static inline PyObject* cpyrt_PyBool_FromLong(long b) { + PyObject* result = (bool)b ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +//- base executor implementation --------------------------------------------- +cpyrt::Executor::~Executor() { /* empty */ } + +//- executors for built-ins -------------------------------------------------- +PyObject* cpyrt::BoolExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python bool return + // value + bool retval = GILCallB(method, self, ctxt); + PyObject* result = retval ? Py_True : Py_False; + Py_INCREF(result); + return result; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::BoolConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python bool return + // value + return cpyrt_PyBool_FromLong(*((bool*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CharExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute , construct python string return + // value with the single char + return cpyrt_PyText_FromLong((int)GILCallC(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CharConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char + return cpyrt_PyText_FromLong(*((char*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UCharExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char + return cpyrt_PyText_FromLong((unsigned char)GILCallB(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UCharConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char from the pointer return + return cpyrt_PyText_FromLong(*((unsigned char*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Int8ConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + return PyInt_FromLong(*((int8_t*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UInt8ConstRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + return PyInt_FromLong(*((uint8_t*)GILCallR(method, self, ctxt))); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::WCharExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single wide char + wchar_t res = (wchar_t)GILCallL(method, self, ctxt); + return PyUnicode_FromWideChar(&res, 1); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Char16Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char16 + char16_t res = (char16_t)GILCallL(method, self, ctxt); + return PyUnicode_DecodeUTF16((const char*)&res, sizeof(char16_t), nullptr, + nullptr); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Char32Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value with the single char32 + char32_t res = (char32_t)GILCallL(method, self, ctxt); + return PyUnicode_DecodeUTF32((const char*)&res, sizeof(char32_t), nullptr, + nullptr); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::IntExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python int return + // value + return PyInt_FromLong((int)GILCallI(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Int8Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python int return + // value + return PyInt_FromLong((int8_t)GILCallC(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::UInt8Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python int return + // value + return PyInt_FromLong((uint8_t)GILCallB(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::ShortExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python int return + // value + return PyInt_FromLong((short)GILCallH(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::LongExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python long return + // value + return PyLong_FromLong((long)GILCallL(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::ULongExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unsigned long + // return value + return PyLong_FromUnsignedLong((unsigned long)GILCallLL(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::LongLongExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python long long + // return value + PY_LONG_LONG result = GILCallLL(method, self, ctxt); + return PyLong_FromLongLong(result); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::ULongLongExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unsigned long + // long return value + PY_ULONG_LONG result = (PY_ULONG_LONG)GILCallLL(method, self, ctxt); + return PyLong_FromUnsignedLongLong(result); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::FloatExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python float return + // value + return PyFloat_FromDouble((double)GILCallF(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::DoubleExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python float return + // value + return PyFloat_FromDouble((double)GILCallD(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::LongDoubleExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python float return + // value + return PyFloat_FromDouble((double)GILCallLD(method, self, ctxt)); +} + +//---------------------------------------------------------------------------- +bool cpyrt::RefExecutor::SetAssignable(PyObject* pyobject) { + // prepare "buffer" for by-ref returns, used with __setitem__ + if (pyobject) { + Py_INCREF(pyobject); + fAssignable = pyobject; + return true; + } + + fAssignable = nullptr; + return false; +} + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_REFEXEC(name, type, stype, F1, F2) \ + PyObject* cpyrt::name##RefExecutor::Execute(interop::TCppMethod_t method, \ + interop::TCppObject_t self, \ + CallContext* ctxt) { \ + type* ref = (type*)GILCallR(method, self, ctxt); \ + if (!ref) { /* can happen if wrapper compilation fails */ \ + PyErr_SetString(PyExc_ReferenceError, \ + "attempt to access a null-pointer"); \ + return nullptr; \ + } \ + if (!fAssignable) \ + return F1((stype) * ref); \ + else { \ + *ref = (type)F2(fAssignable); \ + Py_DECREF(fAssignable); \ + fAssignable = nullptr; \ + if (*ref == (type) - 1 && PyErr_Occurred()) \ + return nullptr; \ + Py_INCREF(Py_None); \ + return Py_None; \ + } \ + } + +// clang-format off +CPPJIT_IMPL_REFEXEC(Bool, bool, long, cpyrt_PyBool_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(Char, char, long, cpyrt_PyText_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(UChar, unsigned char, unsigned long, cpyrt_PyText_FromULong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(Int8, int8_t, long, PyInt_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(UInt8, uint8_t, unsigned long, PyInt_FromLong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(Short, short, long, PyInt_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(UShort, unsigned short, unsigned long, PyInt_FromLong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(Int, int, long, PyInt_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(UInt, unsigned int, unsigned long, PyLong_FromUnsignedLong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(Long, long, long, PyLong_FromLong, PyLong_AsLong) +CPPJIT_IMPL_REFEXEC(ULong, unsigned long, unsigned long, PyLong_FromUnsignedLong, PyLongOrInt_AsULong) +CPPJIT_IMPL_REFEXEC(LongLong, PY_LONG_LONG, PY_LONG_LONG, PyLong_FromLongLong, PyLong_AsLongLong) +CPPJIT_IMPL_REFEXEC(ULongLong, PY_ULONG_LONG, PY_ULONG_LONG, PyLong_FromUnsignedLongLong, PyLongOrInt_AsULong64) +CPPJIT_IMPL_REFEXEC(Float, float, double, PyFloat_FromDouble, PyFloat_AsDouble) +CPPJIT_IMPL_REFEXEC(Double, double, double, PyFloat_FromDouble, PyFloat_AsDouble) +CPPJIT_IMPL_REFEXEC(LongDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, PyFloat_FromDouble, PyFloat_AsDouble) +// clang-format on + +template +static inline PyObject* PyComplex_FromComplex(const std::complex& c) { + return PyComplex_FromDoubles(c.real(), c.imag()); +} + +template +static inline std::complex PyComplex_AsComplex(PyObject* pycplx) { + Py_complex cplx = PyComplex_AsCComplex(pycplx); + return std::complex(cplx.real, cplx.imag); +} + +CPPJIT_IMPL_REFEXEC(ComplexD, std::complex, std::complex, + PyComplex_FromComplex, PyComplex_AsComplex) + +//---------------------------------------------------------------------------- +PyObject* cpyrt::STLStringRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , return python string return + // value + static interop::TCppScope_t sSTLStringScope = + interop::GetFullScope("std::string"); + + std::string* result = (std::string*)GILCallR(method, self, ctxt); + if (!fAssignable) { + std::string* rescp = new std::string{*result}; + return BindCppObjectNoCast((void*)rescp, sSTLStringScope, + CPPInstance::kIsOwner); + } + + if (!cpyrt_PyText_Check(fAssignable)) { + PyErr_Format(PyExc_TypeError, "wrong type in assignment (string expected)"); + return nullptr; + } + + *result = std::string(cpyrt_PyText_AsString(fAssignable), + cpyrt_PyText_GET_SIZE(fAssignable)); + + Py_DECREF(fAssignable); + fAssignable = nullptr; + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::VoidExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , return None + GILCallV(method, self, ctxt); + if (PyErr_Occurred()) + return nullptr; + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CStringExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value + char* result = (char*)GILCallR(method, self, ctxt); + if (!result) { + Py_INCREF(PyStrings::gEmptyString); + return PyStrings::gEmptyString; + } + + return cpyrt_PyText_FromString(result); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CStringRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value + char** result = (char**)GILCallR(method, self, ctxt); + if (!result || !*result) { + Py_INCREF(PyStrings::gEmptyString); + return PyStrings::gEmptyString; + } + + return cpyrt_PyText_FromString(*result); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::WCStringExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unicode + // return value + wchar_t* result = (wchar_t*)GILCallR(method, self, ctxt); + if (!result) { + wchar_t w = L'\0'; + return PyUnicode_FromWideChar(&w, 0); + } + + return PyUnicode_FromWideChar(result, wcslen(result)); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CString16Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unicode + // return value + char16_t* result = (char16_t*)GILCallR(method, self, ctxt); + if (!result) { + char16_t w = u'\0'; + return PyUnicode_DecodeUTF16((const char*)&w, 0, nullptr, nullptr); + } + + return PyUnicode_DecodeUTF16((const char*)result, + std::char_traits::length(result) * + sizeof(char16_t), + nullptr, nullptr); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CString32Executor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python unicode + // return value + char32_t* result = (char32_t*)GILCallR(method, self, ctxt); + if (!result) { + char32_t w = U'\0'; + return PyUnicode_DecodeUTF32((const char*)&w, 0, nullptr, nullptr); + } + + return PyUnicode_DecodeUTF32((const char*)result, + std::char_traits::length(result) * + sizeof(char32_t), + nullptr, nullptr); +} + +//- pointer/array executors -------------------------------------------------- +PyObject* cpyrt::VoidArrayExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python long return + // value + intptr_t* result = (intptr_t*)GILCallR(method, self, ctxt); + if (!result) { + Py_INCREF(gNullPtrObject); + return gNullPtrObject; + } + return CreatePointerView(result, fShape); +} + +//---------------------------------------------------------------------------- +#define CPPJIT_IMPL_ARRAY_EXEC(name, type, suffix) \ + PyObject* cpyrt::name##ArrayExecutor::Execute(interop::TCppMethod_t method, \ + interop::TCppObject_t self, \ + CallContext* ctxt) { \ + return CreateLowLevelView##suffix((type*)GILCallR(method, self, ctxt), \ + fShape); \ + } + +// clang-format off +CPPJIT_IMPL_ARRAY_EXEC(Bool, bool, ) +CPPJIT_IMPL_ARRAY_EXEC(SChar, signed char, ) +CPPJIT_IMPL_ARRAY_EXEC(UChar, unsigned char, ) +CPPJIT_IMPL_ARRAY_EXEC(Byte, std::byte, ) +CPPJIT_IMPL_ARRAY_EXEC(Int8, int8_t, _i8) +CPPJIT_IMPL_ARRAY_EXEC(UInt8, uint8_t, _i8) +CPPJIT_IMPL_ARRAY_EXEC(Short, short, ) +CPPJIT_IMPL_ARRAY_EXEC(UShort, unsigned short, ) +CPPJIT_IMPL_ARRAY_EXEC(Int, int, ) +CPPJIT_IMPL_ARRAY_EXEC(UInt, unsigned int, ) +CPPJIT_IMPL_ARRAY_EXEC(Long, long, ) +CPPJIT_IMPL_ARRAY_EXEC(ULong, unsigned long, ) +CPPJIT_IMPL_ARRAY_EXEC(LLong, long long, ) +CPPJIT_IMPL_ARRAY_EXEC(ULLong, unsigned long long, ) +CPPJIT_IMPL_ARRAY_EXEC(Float, float, ) +CPPJIT_IMPL_ARRAY_EXEC(Double, double, ) +CPPJIT_IMPL_ARRAY_EXEC(LDouble, long double, ) +CPPJIT_IMPL_ARRAY_EXEC(ComplexF, std::complex, ) +CPPJIT_IMPL_ARRAY_EXEC(ComplexD, std::complex, ) +CPPJIT_IMPL_ARRAY_EXEC(ComplexI, std::complex, ) +CPPJIT_IMPL_ARRAY_EXEC(ComplexL, std::complex, ) +// clang-format on + +//- special cases ------------------------------------------------------------ +#define CPPJIT_COMPLEX_EXEC(code, type) \ + PyObject* cpyrt::Complex##code##Executor::Execute( \ + interop::TCppMethod_t method, interop::TCppObject_t self, \ + CallContext* ctxt) { \ + static interop::TCppScope_t scopeid = \ + interop::GetScope("std::complex<" #type ">"); \ + std::complex* result = \ + (std::complex*)GILCallO(method, self, ctxt, scopeid).data; \ + if (!result) { \ + PyErr_SetString(PyExc_ValueError, \ + "NULL result where temporary expected"); \ + return nullptr; \ + } \ + \ + PyObject* pyres = PyComplex_FromDoubles(result->real(), result->imag()); \ + ::operator delete(result); /* interop::CallO calls ::operator new */ \ + return pyres; \ + } + +CPPJIT_COMPLEX_EXEC(D, double) + +//---------------------------------------------------------------------------- +PyObject* cpyrt::STLStringExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value + + // TODO: make use of GILLCallS (?!) + static interop::TCppScope_t sSTLStringScope = + interop::GetFullScope("std::string"); + std::string* result = + (std::string*)GILCallO(method, self, ctxt, sSTLStringScope).data; + if (!result) + result = new std::string{}; + else if (PyErr_Occurred()) { + delete result; + return nullptr; + } + + return BindCppObjectNoCast((void*)result, sSTLStringScope, + CPPInstance::kIsOwner); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::STLWStringExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python string return + // value + static interop::TCppScope_t sSTLWStringScope = + interop::GetFullScope("std::wstring"); + std::wstring* result = + (std::wstring*)GILCallO(method, self, ctxt, sSTLWStringScope).data; + if (!result) { + wchar_t w = L'\0'; + return PyUnicode_FromWideChar(&w, 0); + } + + PyObject* pyresult = PyUnicode_FromWideChar(result->c_str(), result->size()); + delete result; // interop::CallO allocates and constructs a string, so it must + // be properly destroyed + + return pyresult; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstancePtrExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python proxy object + // return value + return BindCppObject((void*)GILCallR(method, self, ctxt), fClass); +} + +//---------------------------------------------------------------------------- +cpyrt::InstanceExecutor::InstanceExecutor(interop::TCppScope_t klass) + : fClass(klass), fFlags(CPPInstance::kIsValue | CPPInstance::kIsOwner) { + /* empty */ +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execution will bring a temporary in existence + interop::TCppObject_t value = GILCallO(method, self, ctxt, fClass); + + if (!value) { + if (!PyErr_Occurred()) // callee may have set a python error itself + PyErr_SetString(PyExc_ValueError, + "nullptr result where temporary expected"); + return nullptr; + } + + // the result can then be bound + PyObject* pyobj = BindCppObjectNoCast(value, fClass, fFlags); + if (!pyobj) + return nullptr; + + // python ref counting will now control this object's life span; it will be + // deleted b/c it is marked as a by-value object owned by python (from fFlags) + return pyobj; +} + +//---------------------------------------------------------------------------- +cpyrt::IteratorExecutor::IteratorExecutor(interop::TCppScope_t klass) + : InstanceExecutor(klass) { + fFlags |= CPPInstance::kNoMemReg | + CPPInstance::kNoWrapConv; // adds to flags from base class +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // executor binds the result to the left-hand side, overwriting if an old + // object + PyObject* result = BindCppObject((void*)GILCallR(method, self, ctxt), fClass); + if (!result || !fAssignable) + return result; + else { + // this generic code is quite slow compared to its C++ equivalent ... + PyObject* assign = PyObject_GetAttr(result, PyStrings::gAssign); + if (!assign) { + PyErr_Clear(); + PyObject* descr = PyObject_Str(result); + if (descr && cpyrt_PyText_CheckExact(descr)) { + PyErr_Format(PyExc_TypeError, "cannot assign to return object (%s)", + cpyrt_PyText_AsString(descr)); + } else { + PyErr_SetString(PyExc_TypeError, "cannot assign to result"); + } + Py_XDECREF(descr); + Py_DECREF(result); + Py_DECREF(fAssignable); + fAssignable = nullptr; + return nullptr; + } + + PyObject* res2 = + PyObject_CallFunction(assign, const_cast("O"), fAssignable); + + Py_DECREF(assign); + Py_DECREF(result); + Py_DECREF(fAssignable); + fAssignable = nullptr; + + if (res2) { + Py_DECREF(res2); // typically, *this from operator=() + Py_RETURN_NONE; + } + + return nullptr; + } +} + +//---------------------------------------------------------------------------- +static inline PyObject* SetInstanceCheckError(PyObject* pyobj) { + PyObject* pystr = PyObject_Str(pyobj); + if (pystr) { + PyErr_Format(PyExc_TypeError, "C++ object expected, got %s", + cpyrt_PyText_AsString(pystr)); + Py_DECREF(pystr); + } else + PyErr_SetString(PyExc_TypeError, "C++ object expected"); + return nullptr; +} + +PyObject* cpyrt::InstancePtrPtrExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python C++ proxy + // object return ptr value + if (fAssignable && !CPPInstance_Check(fAssignable)) + return SetInstanceCheckError(fAssignable); + + void** result = (void**)GILCallR(method, self, ctxt); + if (!fAssignable) + return BindCppObject((void*)result, fClass, + CPPInstance::kIsPtrPtr | CPPInstance::kIsReference); + + CPPInstance* cppinst = (CPPInstance*)fAssignable; + *result = cppinst->GetObject(); + + Py_DECREF(fAssignable); + fAssignable = nullptr; + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstancePtrRefExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct python C++ proxy + // object ignoring ref) return ptr value + if (fAssignable && !CPPInstance_Check(fAssignable)) + return SetInstanceCheckError(fAssignable); + + void** result = (void**)GILCallR(method, self, ctxt); + if (!fAssignable) + return BindCppObject(*result, fClass); + + CPPInstance* cppinst = (CPPInstance*)fAssignable; + *result = cppinst->GetObject(); + + Py_DECREF(fAssignable); + fAssignable = nullptr; + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::InstanceArrayExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , construct TupleOfInstances + // from return value + return BindCppObjectArray((void*)GILCallR(method, self, ctxt), fClass, + {fSize}); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::ConstructorExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t klass, + CallContext* ctxt) { + // package return address in PyObject* for caller to handle appropriately (see + // CPPConstructor for the actual build of the PyObject) + return (PyObject*)GILCallConstructor(method, interop::TCppScope_t(klass.data), + ctxt) + .data; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::PyObjectExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , return python object + return (PyObject*)GILCallR(method, self, ctxt); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::FunctionPointerExecutor::Execute(interop::TCppMethod_t method, + interop::TCppObject_t self, + CallContext* ctxt) { + // execute with argument , return std::function from func + // ptr + + // A function pointer in clang is represented by a Type, not a FunctionDecl + // and it's not possible to get the latter from the former: the backend will + // need to support both. Since that is far in the future, we'll use a + // std::function instead. + void* address = (void*)GILCallR(method, self, ctxt); + if (address) + return Utility::FuncPtr2StdFunction(fRetType, fSignature, address); + + // There's currently now way to return a typed function nullptr, but given + // that this is a return value (thus can not be set) and overloading function + // based on function pointer return type is likely uncommon ... + Py_INCREF(gNullPtrObject); + return gNullPtrObject; +} + +//- factories ---------------------------------------------------------------- +cpyrt::Executor* cpyrt::CreateExecutor(const std::string& fullType, + cdims_t dims) { + // The matching of the fulltype to an executor factory goes through up to 4 + // levels: + // 1) full, qualified match + // 2) drop '&' as by ref/full type is often pretty much the same python-wise + // 3) C++ classes, either by ref/ptr or by value + // 4) additional special case for enums + // + // If all fails, void is used, which will cause the return type to be ignored + // on use + + if (fullType.empty()) + return nullptr; + + // an exactly matching executor is best + ExecFactories_t::iterator h = gExecFactories.find(fullType); + if (h != gExecFactories.end()) + return (h->second)(dims); + + // resolve typedefs etc. + const std::string resolvedType = interop::ResolveName(fullType); + + // a full, qualified matching executor is preferred + if (resolvedType != fullType) { + h = gExecFactories.find(resolvedType); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + //-- nothing? ok, collect information about the type and possible + // qualifiers/decorators + bool isConst = strncmp(resolvedType.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedType); + std::string realType = TypeManip::clean_type(resolvedType, false); + + // accept unqualified type (as python does not know about qualifiers) + h = gExecFactories.find(realType + cpd); + if (h != gExecFactories.end()) + return (h->second)(dims); + + // drop const, as that is mostly meaningless to python (with the exception + // of c-strings, but those are specialized in the converter map) + if (isConst) { + realType = TypeManip::remove_const(realType); + h = gExecFactories.find(realType + cpd); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + // simple array types + if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), + '*') == cpd.size()) { + h = gExecFactories.find(realType + " ptr"); + if (h != gExecFactories.end()) + return (h->second)((!dims || dims.ndim() < (dim_t)cpd.size()) + ? dims_t(cpd.size()) + : dims); + } + + //-- still nothing? try pointer instead of array (for builtins) + if (cpd == "[]") { + h = gExecFactories.find(realType + "*"); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + // C++ classes and special cases + Executor* result = 0; + if (interop::TCppScope_t klass = interop::GetFullScope(realType)) { + if (Utility::IsSTLIterator(realType) || + gIteratorTypes.find(fullType) != gIteratorTypes.end()) { + if (cpd == "") + return new IteratorExecutor(klass); + } + + if (cpd == "") + result = new InstanceExecutor(klass); + else if (cpd == "&") + result = new InstanceRefExecutor(klass); + else if (cpd == "**" || cpd == "*[]" || cpd == "&*") + result = new InstancePtrPtrExecutor(klass); + else if (cpd == "*&") + result = new InstancePtrRefExecutor(klass); + else if (cpd == "[]") { + Py_ssize_t asize = TypeManip::array_size(resolvedType); + if (0 < asize) + result = new InstanceArrayExecutor(klass, asize); + else + result = new InstancePtrRefExecutor(klass); + } else + result = new InstancePtrExecutor(klass); + } else if (resolvedType.find("(*)") != std::string::npos || + (resolvedType.find("::*)") != std::string::npos)) { + // this is a function pointer + // TODO: find better way of finding the type + auto pos1 = resolvedType.find('('); + auto pos2 = resolvedType.find("*)"); + auto pos3 = resolvedType.rfind(')'); + result = new FunctionPointerExecutor( + resolvedType.substr(0, pos1), + resolvedType.substr(pos2 + 2, pos3 - pos2 - 1)); + } else { + // unknown: void* may work ("user knows best"), void will fail on use of + // return value + h = (cpd == "") ? gExecFactories.find("void") + : gExecFactories.find("void ptr"); + } + + if (!result && h != gExecFactories.end()) + // executor factory available, use it to create executor + result = (h->second)(dims); + + return result; // may still be null +} + +cpyrt::Executor* cpyrt::CreateExecutor(interop::TCppType_t type, cdims_t dims) { + // The matching of the fulltype to an executor factory goes through up to 4 + // levels: + // 1) full, qualified match + // 2) drop '&' as by ref/full type is often pretty much the same python-wise + // 3) C++ classes, either by ref/ptr or by value + // 4) additional special case for enums + // + // If all fails, void is used, which will cause the return type to be ignored + // on use + + // an exactly matching executor is best + std::string fullType = interop::GetTypeAsString(type); + if (fullType.size() >= 2 && + fullType.compare(fullType.size() - 2, 2, " &") == 0) + fullType = fullType.substr(0, fullType.size() - 2) + "&"; + + ExecFactories_t::iterator h = gExecFactories.find(fullType); + if (h != gExecFactories.end()) + return (h->second)(dims); + + // resolve typedefs etc. + interop::TCppType_t resolvedType = interop::ResolveType(type); + { + // if resolvedType is a reference to enum + // then it should be reduced to reference + // to the underlying interger + resolvedType = interop::ResolveEnumReferenceType(resolvedType); + // similarly for pointers + resolvedType = interop::ResolveEnumPointerType(resolvedType); + } + // FIXME: avoid string comparisons and parsing + std::string resolvedTypeStr = interop::GetTypeAsString(resolvedType); + if (interop::IsFunctionPointerType(resolvedType)) { + resolvedTypeStr.erase( + std::remove(resolvedTypeStr.begin(), resolvedTypeStr.end(), ' '), + resolvedTypeStr.end()); + if (resolvedTypeStr.rfind("(void)") != std::string::npos) + resolvedTypeStr = + resolvedTypeStr.substr(0, resolvedTypeStr.size() - 6) + "()"; + } + + // a full, qualified matching executor is preferred + if (resolvedTypeStr != fullType) { + h = gExecFactories.find(resolvedTypeStr); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + //-- nothing? ok, collect information about the type and possible + // qualifiers/decorators + bool isConst = strncmp(resolvedTypeStr.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedTypeStr); + interop::TCppType_t realType = interop::IsFunctionPointerType(resolvedType) + ? resolvedType + : interop::GetRealType(resolvedType); + std::string realTypeStr = interop::IsFunctionPointerType(resolvedType) + ? resolvedTypeStr + : interop::GetTypeAsString(realType); + const std::string compounded = cpd.empty() ? realTypeStr : realTypeStr + cpd; + + // accept unqualified type (as python does not know about qualifiers) + h = gExecFactories.find(compounded); + if (h != gExecFactories.end()) + return (h->second)(dims); + + // drop const, as that is mostly meaningless to python (with the exception + // of c-strings, but those are specialized in the converter map) + if (isConst) { + realTypeStr = TypeManip::remove_const(realTypeStr); + h = gExecFactories.find(compounded); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + // simple array types + if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), + '*') == cpd.size()) { + h = gExecFactories.find(realTypeStr + " ptr"); + if (h != gExecFactories.end()) + return (h->second)((!dims || dims.ndim() < (dim_t)cpd.size()) + ? dims_t(cpd.size()) + : dims); + } + + //-- still nothing? try pointer instead of array (for builtins) + if (cpd == "[]") { + h = gExecFactories.find(realTypeStr + "*"); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + + // C++ classes and special cases + Executor* result = 0; + if (interop::IsClassType(realType)) { + interop::TCppScope_t klass = interop::GetScopeFromType(realType); + if (resolvedTypeStr.find("iterator") != std::string::npos || + gIteratorTypes.find(fullType) != gIteratorTypes.end()) { + if (cpd == "") + return new IteratorExecutor(klass); + } + + if (cpd == "") + result = new InstanceExecutor(klass); + else if (cpd == "&") + result = new InstanceRefExecutor(klass); + else if (cpd == "**" || cpd == "*[]" || cpd == "&*") + result = new InstancePtrPtrExecutor(klass); + else if (cpd == "*&") + result = new InstancePtrRefExecutor(klass); + else if (cpd == "[]") { + Py_ssize_t asize = TypeManip::array_size(resolvedTypeStr); + if (0 < asize) + result = new InstanceArrayExecutor(klass, asize); + else + result = new InstancePtrRefExecutor(klass); + } else + result = new InstancePtrExecutor(klass); + } else if (realTypeStr.find("(*)") != std::string::npos || + (realTypeStr.find("::*)") != std::string::npos)) { + // this is a function pointer + // TODO: find better way of finding the type + auto pos1 = realTypeStr.find('('); + auto pos2 = realTypeStr.find("*)"); + auto pos3 = realTypeStr.rfind(')'); + result = new FunctionPointerExecutor( + realTypeStr.substr(0, pos1), + realTypeStr.substr(pos2 + 2, pos3 - pos2 - 1)); + } else { + // unknown: void* may work ("user knows best"), void will fail on use of + // return value + h = (cpd == "") ? gExecFactories.find("void") + : gExecFactories.find("void ptr"); + } + + if (!result && h != gExecFactories.end()) + // executor factory available, use it to create executor + result = (h->second)(dims); + + return result; // may still be null +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +void cpyrt::DestroyExecutor(Executor* p) { + if (p && p->HasState()) + delete p; // state-less executors are always shared +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::RegisterExecutor(const std::string& name, ef_t fac) { + // register a custom executor + auto f = gExecFactories.find(name); + if (f != gExecFactories.end()) + return false; + + gExecFactories[name] = fac; + return true; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::RegisterExecutorAlias(const std::string& name, + const std::string& target) { + // register a custom executor that is a reference to an existing converter + auto f = gExecFactories.find(name); + if (f != gExecFactories.end()) + return false; + + auto t = gExecFactories.find(target); + if (t == gExecFactories.end()) + return false; + + gExecFactories[name] = t->second; + return true; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +bool cpyrt::UnregisterExecutor(const std::string& name) { + // remove a custom executor + auto f = gExecFactories.find(name); + if (f != gExecFactories.end()) { + gExecFactories.erase(f); + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +CPYRT_EXPORT +void* cpyrt::CallVoidP(interop::TCppMethod_t meth, interop::TCppObject_t obj, + CallContext* ctxt) { + return GILCallR(meth, obj, ctxt); +} + +//---------------------------------------------------------------------------- +namespace { + +using namespace cppjit::cpyrt; + +#define WSTRING1 "std::basic_string" +#define WSTRING2 \ + "std::basic_string,std::allocator>" + +//-- aliasing special case: C complex (is binary compatible with C++ +// std::complex) +#ifndef _WIN32 +#define CCOMPLEX_D "_Complex double" +#define CCOMPLEX_F "_Complex float" +#else +#define CCOMPLEX_D "_C_double_complex" +#define CCOMPLEX_F "_C_float_complex" +#endif + +struct InitExecFactories_t { +public: + InitExecFactories_t() { + // load all executor factories in the global map 'gExecFactories' + cpyrt::ExecFactories_t& gf = gExecFactories; + + // factories for built-ins + gf["bool"] = (ef_t) + [](cdims_t) { + static BoolExecutor e{}; + return &e; + }; + gf["bool&"] = (ef_t) + [](cdims_t) { return new BoolRefExecutor{}; }; + gf["const bool&"] = (ef_t) + [](cdims_t) { + static BoolConstRefExecutor e{}; + return &e; + }; + gf["char"] = (ef_t) + [](cdims_t) { + static CharExecutor e{}; + return &e; + }; + gf["signed char"] = gf["char"]; + gf["unsigned char"] = (ef_t) + [](cdims_t) { + static UCharExecutor e{}; + return &e; + }; + gf["char&"] = (ef_t) + [](cdims_t) { return new CharRefExecutor{}; }; + gf["signed char&"] = gf["char&"]; + gf["unsigned char&"] = + (ef_t) + [](cdims_t) { return new UCharRefExecutor{}; }; + gf["const char&"] = (ef_t) + [](cdims_t) { + static CharConstRefExecutor e{}; + return &e; + }; + gf["const signed char&"] = gf["const char&"]; + gf["const unsigned char&"] = (ef_t) + [](cdims_t) { + static UCharConstRefExecutor e{}; + return &e; + }; + gf["wchar_t"] = (ef_t) + [](cdims_t) { + static WCharExecutor e{}; + return &e; + }; + gf["char16_t"] = (ef_t) + [](cdims_t) { + static Char16Executor e{}; + return &e; + }; + gf["char32_t"] = (ef_t) + [](cdims_t) { + static Char32Executor e{}; + return &e; + }; + gf["int8_t"] = (ef_t) + [](cdims_t) { + static Int8Executor e{}; + return &e; + }; + gf["int8_t&"] = (ef_t) + [](cdims_t) { return new Int8RefExecutor{}; }; + gf["const int8_t&"] = (ef_t) + [](cdims_t) { + static Int8ConstRefExecutor e{}; + return &e; + }; + gf["uint8_t"] = (ef_t) + [](cdims_t) { + static UInt8Executor e{}; + return &e; + }; + gf["uint8_t&"] = (ef_t) + [](cdims_t) { return new UInt8RefExecutor{}; }; + gf["const uint8_t&"] = (ef_t) + [](cdims_t) { + static UInt8ConstRefExecutor e{}; + return &e; + }; + gf["short"] = (ef_t) + [](cdims_t) { + static ShortExecutor e{}; + return &e; + }; + gf["short&"] = (ef_t) + [](cdims_t) { return new ShortRefExecutor{}; }; + gf["int"] = (ef_t) + [](cdims_t) { + static IntExecutor e{}; + return &e; + }; + gf["int&"] = (ef_t) + [](cdims_t) { return new IntRefExecutor{}; }; + gf["unsigned short"] = gf["int"]; + gf["unsigned short&"] = + (ef_t) + [](cdims_t) { return new UShortRefExecutor{}; }; + gf["unsigned long"] = (ef_t) + [](cdims_t) { + static ULongExecutor e{}; + return &e; + }; + gf["unsigned long&"] = + (ef_t) + [](cdims_t) { return new ULongRefExecutor{}; }; + gf["unsigned int"] = gf["unsigned long"]; + gf["unsigned int&"] = + (ef_t) + [](cdims_t) { return new UIntRefExecutor{}; }; + gf["long"] = (ef_t) + [](cdims_t) { + static LongExecutor e{}; + return &e; + }; + gf["long&"] = (ef_t) + [](cdims_t) { return new LongRefExecutor{}; }; + gf["unsigned long"] = (ef_t) + [](cdims_t) { + static ULongExecutor e{}; + return &e; + }; + gf["unsigned long&"] = + (ef_t) + [](cdims_t) { return new ULongRefExecutor{}; }; + gf["long long"] = (ef_t) + [](cdims_t) { + static LongLongExecutor e{}; + return &e; + }; + gf["long long&"] = + (ef_t) + [](cdims_t) { return new LongLongRefExecutor{}; }; + gf["unsigned long long"] = (ef_t) + [](cdims_t) { + static ULongLongExecutor e{}; + return &e; + }; + gf["unsigned long long&"] = + (ef_t) + [](cdims_t) { return new ULongLongRefExecutor{}; }; + + gf["float"] = (ef_t) + [](cdims_t) { + static FloatExecutor e{}; + return &e; + }; + gf["float&"] = (ef_t) + [](cdims_t) { return new FloatRefExecutor{}; }; + gf["double"] = (ef_t) + [](cdims_t) { + static DoubleExecutor e{}; + return &e; + }; + gf["double&"] = (ef_t) + [](cdims_t) { return new DoubleRefExecutor{}; }; + gf["long double"] = (ef_t) + [](cdims_t) { + static LongDoubleExecutor e{}; + return &e; + }; // TODO: lost precision + gf["long double&"] = + (ef_t) + [](cdims_t) { return new LongDoubleRefExecutor{}; }; + gf["std::complex"] = (ef_t) + [](cdims_t) { + static ComplexDExecutor e{}; + return &e; + }; + gf["std::complex&"] = + (ef_t) + [](cdims_t) { return new ComplexDRefExecutor{}; }; + gf["void"] = (ef_t) + [](cdims_t) { + static VoidExecutor e{}; + return &e; + }; + + // pointer/array factories + gf["void ptr"] = + (ef_t) + [](cdims_t d) { return new VoidArrayExecutor{d}; }; + gf["bool ptr"] = + (ef_t) + [](cdims_t d) { return new BoolArrayExecutor{d}; }; + gf["unsigned char ptr"] = + (ef_t) + [](cdims_t d) { return new UCharArrayExecutor{d}; }; + gf["const unsigned char ptr"] = gf["unsigned char ptr"]; + gf["std::byte ptr"] = + (ef_t) + [](cdims_t d) { return new ByteArrayExecutor{d}; }; + gf["const std::byte ptr"] = gf["std::byte ptr"]; + gf["int8_t ptr"] = + (ef_t) + [](cdims_t d) { return new Int8ArrayExecutor{d}; }; + gf["uint8_t ptr"] = + (ef_t) + [](cdims_t d) { return new UInt8ArrayExecutor{d}; }; + gf["short ptr"] = + (ef_t) + [](cdims_t d) { return new ShortArrayExecutor{d}; }; + gf["unsigned short ptr"] = + (ef_t) + [](cdims_t d) { return new UShortArrayExecutor{d}; }; + gf["int ptr"] = (ef_t) + [](cdims_t d) { return new IntArrayExecutor{d}; }; + gf["unsigned int ptr"] = + (ef_t) + [](cdims_t d) { return new UIntArrayExecutor{d}; }; + gf["long ptr"] = + (ef_t) + [](cdims_t d) { return new LongArrayExecutor{d}; }; + gf["unsigned long ptr"] = + (ef_t) + [](cdims_t d) { return new ULongArrayExecutor{d}; }; + gf["long long ptr"] = + (ef_t) + [](cdims_t d) { return new LLongArrayExecutor{d}; }; + gf["unsigned long long ptr"] = + (ef_t) + [](cdims_t d) { return new ULLongArrayExecutor{d}; }; + gf["float ptr"] = + (ef_t) + [](cdims_t d) { return new FloatArrayExecutor{d}; }; + gf["double ptr"] = + (ef_t) + [](cdims_t d) { return new DoubleArrayExecutor{d}; }; + gf["long double ptr"] = + (ef_t) + [](cdims_t d) { return new LDoubleArrayExecutor{d}; }; + gf["std::complex ptr"] = + (ef_t) + [](cdims_t d) { return new ComplexFArrayExecutor{d}; }; + gf["std::complex ptr"] = + (ef_t) + [](cdims_t d) { return new ComplexDArrayExecutor{d}; }; + gf["std::complex ptr"] = + (ef_t) + [](cdims_t d) { return new ComplexIArrayExecutor{d}; }; + gf["std::complex ptr"] = + (ef_t) + [](cdims_t d) { return new ComplexLArrayExecutor{d}; }; + + // aliases + gf["internal_enum_type_t"] = gf["int"]; + gf["internal_enum_type_t&"] = gf["int&"]; + gf["internal_enum_type_t ptr"] = gf["int ptr"]; + gf["std::byte"] = gf["uint8_t"]; + gf["std::byte&"] = gf["uint8_t&"]; + gf["const std::byte&"] = gf["const uint8_t&"]; + gf["const byte&"] = gf["const uint8_t&"]; + gf["std::int8_t"] = gf["int8_t"]; + gf["std::int8_t&"] = gf["int8_t&"]; + gf["const std::int8_t&"] = gf["const int8_t&"]; + gf["std::int8_t ptr"] = gf["int8_t ptr"]; + gf["std::uint8_t"] = gf["uint8_t"]; + gf["std::uint8_t&"] = gf["uint8_t&"]; + gf["const std::uint8_t&"] = gf["const uint8_t&"]; + gf["std::uint8_t ptr"] = gf["uint8_t ptr"]; +#ifdef _WIN32 + gf["__int64"] = gf["long long"]; + gf["__int64&"] = gf["long long&"]; + gf["__int64 ptr"] = gf["long long ptr"]; + gf["unsigned __int64"] = gf["unsigned long long"]; + gf["unsigned __int64&"] = gf["unsigned long long&"]; + gf["unsigned __int64 ptr"] = gf["unsigned long long ptr"]; +#endif + gf[CCOMPLEX_D] = gf["std::complex"]; + gf[CCOMPLEX_D "&"] = gf["std::complex&"]; + gf[CCOMPLEX_F " ptr"] = gf["std::complex ptr"]; + gf[CCOMPLEX_D " ptr"] = gf["std::complex ptr"]; + + // We always need these executors when cppjit is based on an unpatched + // ROOT, because the "long long" types are always converted to Long64_t + // and ULong64_t already at the ROOT Meta level. + // See + // https://github.com/root-project/root/issues/15872#issuecomment-2174092763 + gf["Long64_t"] = gf["long long"]; + gf["Long64_t&"] = gf["long long&"]; + gf["Long64_t ptr"] = gf["long long ptr"]; + gf["ULong64_t"] = gf["unsigned long long"]; + gf["ULong64_t&"] = gf["unsigned long long&"]; + gf["ULong64_t ptr"] = gf["unsigned long long ptr"]; + + // factories for special cases + gf["const char*"] = (ef_t) + [](cdims_t) { + static CStringExecutor e{}; + return &e; + }; + gf["char*"] = gf["const char*"]; + gf["const char*&"] = (ef_t) + [](cdims_t) { + static CStringRefExecutor e{}; + return &e; + }; + gf["char*&"] = gf["const char*&"]; + gf["const signed char*"] = gf["const char*"]; + // gf["signed char*"] = gf["char*"]; + gf["signed char ptr"] = + (ef_t) + [](cdims_t d) { return new SCharArrayExecutor{d}; }; + gf["wchar_t*"] = (ef_t) + [](cdims_t) { + static WCStringExecutor e{}; + return &e; + }; + gf["char16_t*"] = (ef_t) + [](cdims_t) { + static CString16Executor e{}; + return &e; + }; + gf["char32_t*"] = (ef_t) + [](cdims_t) { + static CString32Executor e{}; + return &e; + }; + gf["std::basic_string"] = (ef_t) + [](cdims_t) { + static STLStringExecutor e{}; + return &e; + }; + gf["std::basic_string&"] = + (ef_t) + [](cdims_t) { return new STLStringRefExecutor{}; }; + gf["std::basic_string"] = (ef_t) + [](cdims_t) { + static STLWStringExecutor e{}; + return &e; + }; + gf[WSTRING1] = gf["std::basic_string"]; + gf[WSTRING2] = gf["std::basic_string"]; + gf["__init__"] = (ef_t) + [](cdims_t) { + static ConstructorExecutor e{}; + return &e; + }; + gf["PyObject*"] = (ef_t) + [](cdims_t) { + static PyObjectExecutor e{}; + return &e; + }; + gf["_object*"] = gf["PyObject*"]; + gf["FILE*"] = gf["void ptr"]; + } +} initExecvFactories_; + +} // unnamed namespace diff --git a/src/cpyrt/Executors.h b/src/cpyrt/Executors.h new file mode 100644 index 0000000..be38af6 --- /dev/null +++ b/src/cpyrt/Executors.h @@ -0,0 +1,53 @@ +#ifndef CPYRT_EXECUTORS_H +#define CPYRT_EXECUTORS_H + +#include "Python.h" +#include "cppjit_interop.h" + +// Bindings +#include "Dimensions.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +struct CallContext; + +class CPYRT_CLASS_EXPORT Executor { +public: + virtual ~Executor(); + virtual PyObject* Execute(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*) = 0; + virtual bool HasState() { return false; } +}; + +// special case needed for CPPSetItem +class RefExecutor : public Executor { +public: + RefExecutor() : fAssignable(nullptr) {} + virtual bool SetAssignable(PyObject*); + bool HasState() override { return true; } + +protected: + PyObject* fAssignable; +}; + +// create/destroy executor from fully qualified type (public API) +CPYRT_EXPORT Executor* CreateExecutor(const std::string& fullType, cdims_t = 0); +CPYRT_EXPORT Executor* CreateExecutor(cppjit::interop::TCppType_t type, + cdims_t = 0); +CPYRT_EXPORT void DestroyExecutor(Executor* p); +typedef Executor* (*ef_t)(cdims_t); +CPYRT_EXPORT bool RegisterExecutor(const std::string& name, ef_t fac); +CPYRT_EXPORT bool RegisterExecutorAlias(const std::string& name, + const std::string& target); +CPYRT_EXPORT bool UnregisterExecutor(const std::string& name); + +// helper for the actual call +CPYRT_EXPORT void* CallVoidP(cppjit::interop::TCppMethod_t, + cppjit::interop::TCppObject_t, CallContext*); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_EXECUTORS_H diff --git a/src/cpyrt/LowLevelViews.cxx b/src/cpyrt/LowLevelViews.cxx new file mode 100644 index 0000000..b4b5be1 --- /dev/null +++ b/src/cpyrt/LowLevelViews.cxx @@ -0,0 +1,1184 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "Converters.h" +#include "CustomPyTypes.h" +#include "LowLevelViews.h" +#include "PyStrings.h" + +// Standard +#include +#include +#include +#include + +//= memoryview-like object =================================================== +// This is largely setup just like Python builtin memory view objects, with +// the exceptions that there is no need of a "base" object (it views on C++ +// memory, not a Python object with a buffer interface), it uses the cpyrt +// converters, and typed results and assignments are supported. Code reused +// under PSF License Version 2. + +//- helpers ------------------------------------------------------------------ +static inline void set_strides(Py_buffer& view, size_t itemsize, bool isfix) { + if (isfix) { + Py_ssize_t stride = itemsize; + for (Py_ssize_t idim = view.ndim - 1; 0 <= idim; --idim) { + view.strides[idim] = stride; + stride *= view.shape[idim]; + } + } else { + view.strides[view.ndim - 1] = itemsize; + for (Py_ssize_t idim = 0; idim < view.ndim - 1; ++idim) + view.strides[idim] = view.itemsize; + } +} + +//= cpyrt low level view construction/destruction ========================= +static cpyrt::LowLevelView* ll_new(PyTypeObject* subtype, PyObject*, + PyObject*) { + // Create a new low level ptr type + cpyrt::LowLevelView* pyobj = + (cpyrt::LowLevelView*)subtype->tp_alloc(subtype, 0); + if (!pyobj) + return nullptr; + + memset(&pyobj->fBufInfo, 0, sizeof(Py_buffer)); + (intptr_t&)pyobj->fBufInfo.internal |= cpyrt::LowLevelView::kIsCppArray; + pyobj->fBuf = nullptr; + pyobj->fConverter = nullptr; + pyobj->fElemCnv = nullptr; + + return pyobj; +} + +//---------------------------------------------------------------------------- +static void ll_dealloc(cpyrt::LowLevelView* pyobj) { + // Destruction requires the deletion of the converter (if any) + PyMem_Free(pyobj->fBufInfo.shape); + PyMem_Free(pyobj->fBufInfo.strides); + if ((intptr_t)pyobj->fBufInfo.internal & cpyrt::LowLevelView::kIsOwner) { + if ((intptr_t)pyobj->fBufInfo.internal & cpyrt::LowLevelView::kIsCppArray) + delete[] pyobj->fBuf; + else + free(pyobj->fBuf); + } + + if (pyobj->fElemCnv != pyobj->fConverter && pyobj->fElemCnv && + pyobj->fElemCnv->HasState()) + delete pyobj->fElemCnv; + + if (pyobj->fConverter && pyobj->fConverter->HasState()) + delete pyobj->fConverter; + + Py_TYPE(pyobj)->tp_free((PyObject*)pyobj); +} + +//---------------------------------------------------------------------------- +#define CPYRT_LL_FLAG_GETSET(name, flag, doc) \ + static PyObject* ll_get##name(cpyrt::LowLevelView* pyobj) { \ + return PyBool_FromLong((long)((intptr_t)pyobj->fBufInfo.internal & flag)); \ + } \ + \ + static int ll_set##name(cpyrt::LowLevelView* pyobj, PyObject* value, \ + void*) { \ + long settrue = PyLong_AsLong(value); \ + if (settrue == -1 && PyErr_Occurred()) { \ + PyErr_SetString(PyExc_ValueError, \ + #doc " should be either True or False"); \ + return -1; \ + } \ + \ + if ((bool)settrue) \ + (intptr_t&)pyobj->fBufInfo.internal |= flag; \ + else \ + (intptr_t&)pyobj->fBufInfo.internal &= ~flag; \ + \ + return 0; \ + } + +// clang-format off +CPYRT_LL_FLAG_GETSET(ownership, cpyrt::LowLevelView::kIsOwner, __python_owns__) +CPYRT_LL_FLAG_GETSET(cpparray, cpyrt::LowLevelView::kIsCppArray, __cpp_array__) +// clang-format on + +//--------------------------------------------------------------------------- +static PyObject* ll_typecode(cpyrt::LowLevelView* self, void*) { + return cpyrt_PyText_FromString((char*)self->fBufInfo.format); +} + +//- Copy memoryview buffers ================================================= + +// The functions in this section take a source and a destination buffer +// with the same logical structure: format, itemsize, ndim and shape +// are identical, with ndim > 0. + +// Check for the presence of suboffsets in the first dimension. +#define HAVE_PTR(suboffsets, dim) (suboffsets && suboffsets[dim] >= 0) +// Adjust ptr if suboffsets are present. +#define ADJUST_PTR(ptr, suboffsets, dim) \ + (HAVE_PTR(suboffsets, dim) ? *((char**)ptr) + suboffsets[dim] : ptr) + +// Assumptions: ndim >= 1. The macro tests for a corner case that should +// perhaps be explicitly forbidden in the PEP. +#define HAVE_SUBOFFSETS_IN_LAST_DIM(view) \ + (view->suboffsets && view->suboffsets[dest->ndim - 1] >= 0) + +//--------------------------------------------------------------------------- +static inline int last_dim_is_contiguous(const Py_buffer* dest, + const Py_buffer* src) { + assert(dest->ndim > 0 && src->ndim > 0); + return (!HAVE_SUBOFFSETS_IN_LAST_DIM(dest) && + !HAVE_SUBOFFSETS_IN_LAST_DIM(src) && + dest->strides[dest->ndim - 1] == dest->itemsize && + src->strides[src->ndim - 1] == src->itemsize); +} + +//--------------------------------------------------------------------------- +static inline bool equiv_shape(const Py_buffer* dest, const Py_buffer* src) { + // Two shapes are equivalent if they are either equal or identical up + // to a zero element at the same position. For example, in NumPy arrays + // the shapes [1, 0, 5] and [1, 0, 7] are equivalent. + if (dest->ndim != src->ndim) + return false; + + for (int i = 0; i < dest->ndim; i++) { + if (dest->shape[i] != src->shape[i]) + return 0; + if (dest->shape[i] == 0) + break; + } + + return true; +} + +//--------------------------------------------------------------------------- +static bool equiv_structure(const Py_buffer* dest, const Py_buffer* src) { + // Check that the logical structure of the destination and source buffers + // is identical. + if (strcmp(dest->format, src->format) != 0 || + dest->itemsize != src->itemsize || !equiv_shape(dest, src)) { + PyErr_SetString(PyExc_ValueError, "low level pointer assignment: lvalue " + "and rvalue have different structures"); + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +static void copy_base(const Py_ssize_t* shape, Py_ssize_t itemsize, char* dptr, + const Py_ssize_t* dstrides, const Py_ssize_t* dsuboffsets, + char* sptr, const Py_ssize_t* sstrides, + const Py_ssize_t* ssuboffsets, char* mem) { + // Base case for recursive multi-dimensional copying. Contiguous arrays are + // copied with very little overhead. Assumptions: ndim == 1, mem == nullptr or + // sizeof(mem) == shape[0] * itemsize. + if (!mem) { // contiguous + Py_ssize_t size = shape[0] * itemsize; + if (dptr + size < sptr || sptr + size < dptr) + memcpy(dptr, sptr, size); // no overlapping + else + memmove(dptr, sptr, size); + } else { + char* p; + Py_ssize_t i; + for (i = 0, p = mem; i < shape[0]; + p += itemsize, sptr += sstrides[0], i++) { + char* xsptr = ADJUST_PTR(sptr, ssuboffsets, 0); + memcpy(p, xsptr, itemsize); + } + for (i = 0, p = mem; i < shape[0]; + p += itemsize, dptr += dstrides[0], i++) { + char* xdptr = ADJUST_PTR(dptr, dsuboffsets, 0); + memcpy(xdptr, p, itemsize); + } + } +} + +//--------------------------------------------------------------------------- +static int copy_single(Py_buffer* dest, Py_buffer* src) { + // Faster copying of one-dimensional arrays. + char* mem = nullptr; + + assert(dest->ndim == 1); + + if (!equiv_structure(dest, src)) + return -1; + + if (!last_dim_is_contiguous(dest, src)) { + mem = (char*)PyMem_Malloc(dest->shape[0] * dest->itemsize); + if (!mem) { + PyErr_NoMemory(); + return -1; + } + } + + copy_base(dest->shape, dest->itemsize, (char*)dest->buf, dest->strides, + dest->suboffsets, (char*)src->buf, src->strides, src->suboffsets, + mem); + + if (mem) + PyMem_Free(mem); + + return 0; +} + +//- Indexing and slicing ---------------------------------------------------- +static char* lookup_dimension(Py_buffer& view, char* ptr, int dim, + Py_ssize_t index) { + Py_ssize_t nitems; // items in the given dimension + + assert(view.shape); + assert(view.strides); + + nitems = view.shape[dim]; + if (index < 0) { + if (nitems != cpyrt::UNKNOWN_SIZE) + index += nitems; + else { + PyErr_Format( + PyExc_IndexError, + "negative index not supported on dimension %d with unknown size", + dim + 1); + return nullptr; + } + } + + if (view.strides[dim] == cpyrt::UNKNOWN_SIZE) { + PyErr_Format( + PyExc_IndexError, + "multi index not supported on dimension %d with unknown stride", + dim + 1); + return nullptr; + } + + if (nitems != cpyrt::UNKNOWN_SIZE && (index < 0 || index >= nitems)) { + PyErr_Format(PyExc_IndexError, "index out of bounds on dimension %d", + dim + 1); + return nullptr; + } + + ptr += view.strides[dim] * index; + ptr = ADJUST_PTR(ptr, view.suboffsets, dim); + + return ptr; +} + +// Get the pointer to the item at index. +//--------------------------------------------------------------------------- +static inline void* ptr_from_index(cpyrt::LowLevelView* llview, + Py_ssize_t index) { + Py_buffer& view = llview->fBufInfo; + return lookup_dimension(view, (char*)llview->get_buf(), 0, index); +} + +// Get the pointer to the item at tuple. +//--------------------------------------------------------------------------- +static void* ptr_from_tuple(cpyrt::LowLevelView* llview, PyObject* tup) { + Py_buffer& view = llview->fBufInfo; + + Py_ssize_t nindices = PyTuple_GET_SIZE(tup); + if (nindices > view.ndim) { + PyErr_Format(PyExc_TypeError, + "cannot index %d-dimension view with %zd-element tuple", + view.ndim, nindices); + return nullptr; + } + + char* ptr = (char*)llview->get_buf(); + for (Py_ssize_t dim = 0; dim < nindices; dim++) { + Py_ssize_t index; + index = PyNumber_AsSsize_t(PyTuple_GET_ITEM(tup, dim), PyExc_IndexError); + if (index == -1 && PyErr_Occurred()) + return nullptr; + + ptr = lookup_dimension(view, ptr, (int)dim, index); + if (!ptr) + return nullptr; + + if (!((intptr_t)view.internal & cpyrt::LowLevelView::kIsFixed) && + dim != view.ndim - 1) + ptr = *(char**)ptr; + } + return ptr; +} + +//= mapping methods ========================================================= +static Py_ssize_t ll_length(cpyrt::LowLevelView* self) { + if (!self->get_buf()) + return 0; + return self->fBufInfo.ndim == 0 ? 1 : self->fBufInfo.shape[0]; +} + +//--------------------------------------------------------------------------- +static inline int init_slice(Py_buffer* base, PyObject* _key, int dim) { + Py_ssize_t start, stop, step, slicelength; + + PyObject* key = _key; + + if (PySlice_GetIndicesEx(key, base->shape[dim], &start, &stop, &step, + &slicelength) < 0) + return -1; + + if (!base->suboffsets || dim == 0) { + adjust_buf: + base->buf = (char*)base->buf + base->strides[dim] * start; + } else { + Py_ssize_t n = dim - 1; + while (n >= 0 && base->suboffsets[n] < 0) + n--; + if (n < 0) + goto adjust_buf; // all suboffsets are negative + base->suboffsets[n] = base->suboffsets[n] + base->strides[dim] * start; + } + base->shape[dim] = slicelength; + base->strides[dim] = base->strides[dim] * step; + + return 0; +} + +//--------------------------------------------------------------------------- +static bool is_multislice(PyObject* key) { + if (!PyTuple_Check(key)) + return false; + + Py_ssize_t size = PyTuple_GET_SIZE(key); + if (size == 0) + return false; + + for (Py_ssize_t i = 0; i < size; i++) { + PyObject* x = PyTuple_GET_ITEM(key, i); + if (!PySlice_Check(x)) + return false; + } + return true; +} + +//--------------------------------------------------------------------------- +static Py_ssize_t is_multiindex(PyObject* key) { + if (!PyTuple_Check(key)) + return 0; + + Py_ssize_t size = PyTuple_GET_SIZE(key); + for (Py_ssize_t i = 0; i < size; i++) { + PyObject* x = PyTuple_GET_ITEM(key, i); + if (!PyIndex_Check(x)) + return 0; + } + return 1; +} + +// Return the item at index. In a one-dimensional view, this is an object +// with the type specified by view->format. Otherwise, the item is a sub-view. +// The function is used in ll_subscript() and ll_as_sequence. +//--------------------------------------------------------------------------- +static PyObject* ll_item(cpyrt::LowLevelView* self, Py_ssize_t index) { + Py_buffer& view = self->fBufInfo; + + if (!self->get_buf()) { + PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); + return nullptr; + } + + if (view.ndim == 0) { + PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory"); + return nullptr; + } + + void* ptr = ptr_from_index(self, index); + if (ptr) { + bool isfix = (intptr_t)view.internal & cpyrt::LowLevelView::kIsFixed; + if (self->fBufInfo.ndim == 1 || !isfix) + return self->fConverter->FromMemory(ptr); + return self->fConverter->FromMemory((void*)&ptr); + } + + return nullptr; // error already set by lookup_dimension +} + +// Return the item at position *key* (a tuple of indices). +//--------------------------------------------------------------------------- +static PyObject* ll_item_multi(cpyrt::LowLevelView* self, PyObject* tup) { + Py_buffer& view = self->fBufInfo; + Py_ssize_t nindices = PyTuple_GET_SIZE(tup); + + if (nindices < view.ndim) { + // TODO: implement + PyErr_SetString(PyExc_NotImplementedError, "sub-views are not implemented"); + return nullptr; + } + + void* ptr = ptr_from_tuple(self, tup); + + // if there's an error, it was already set by lookup_dimension + return ptr ? self->fElemCnv->FromMemory(ptr) : nullptr; +} + +// llp[obj] returns an object holding the data for one element if obj +// fully indexes the lowlevelptr or another lowlevelptr object if it +// does not. +// +// 0-d lowlevelptr objects can be referenced using llp[...] or llp[()] +// but not with anything else. +//--------------------------------------------------------------------------- +static PyObject* ll_subscript(cpyrt::LowLevelView* self, PyObject* key) { + Py_buffer& view = self->fBufInfo; + + if (view.ndim == 0) { + if (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0) { + return self->fConverter->FromMemory(self->get_buf()); + } else if (key == Py_Ellipsis) { + Py_INCREF(self); + return (PyObject*)self; + } else { + PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory"); + return nullptr; + } + } + + if (PyIndex_Check(key)) { + Py_ssize_t index = PyNumber_AsSsize_t(key, PyExc_IndexError); + if (index == -1 && PyErr_Occurred()) + return nullptr; + return ll_item(self, index); + } else if (PySlice_Check(key)) { + if (view.ndim == 1) { + Py_ssize_t start, stop, step, slicelen; + if (PySlice_Unpack(key, &start, &stop, &step) < 0) + return nullptr; + + slicelen = PySlice_AdjustIndices(view.shape[0], &start, &stop, step); + if (slicelen <= 0) + slicelen = view.shape[0]; + + char* buf = (char*)self->get_buf(); + char* slice_buf = new char[slicelen * view.itemsize]; + size_t isize = view.itemsize; + for (size_t i = 0, cur = 0; i < (size_t)slicelen; cur += step, ++i) { + for (size_t j = 0; j < isize; ++j) + slice_buf[i * isize + j] = buf[(start + cur) * isize + j]; + } + + cpyrt::LowLevelView* ll = self->fCreator(slice_buf, {1, slicelen}); + if (!ll) + delete[] slice_buf; + else + (intptr_t&)ll->fBufInfo.internal |= cpyrt::LowLevelView::kIsOwner; + + return (PyObject*)ll; + + } else { + // TODO: handle slicing. This should be simpler than the memoryview + // case as there is no Python object holding the buffer. + PyErr_SetString(PyExc_NotImplementedError, + "multi-dimensional slicing is not implemented"); + return nullptr; + } + } else if (is_multiindex(key)) { + return ll_item_multi(self, key); + } else if (is_multislice(key)) { + PyErr_SetString(PyExc_NotImplementedError, + "multi-dimensional slicing is not implemented"); + return nullptr; + } + + PyErr_SetString(PyExc_TypeError, "invalid slice key"); + return nullptr; +} + +//--------------------------------------------------------------------------- +static int ll_ass_sub(cpyrt::LowLevelView* self, PyObject* key, + PyObject* value) { + Py_buffer& view = self->fBufInfo; + Py_buffer src; + + if (view.readonly) { + PyErr_SetString(PyExc_TypeError, "cannot modify read-only memory"); + return -1; + } + + if (value == nullptr) { + PyErr_SetString(PyExc_TypeError, "cannot delete memory"); + return -1; + } + + if (view.ndim == 0) { + if (key == Py_Ellipsis || + (PyTuple_Check(key) && PyTuple_GET_SIZE(key) == 0)) { + return self->fConverter->ToMemory(value, self->get_buf()) ? 0 : -1; + } else { + PyErr_SetString(PyExc_TypeError, "invalid indexing of 0-dim memory"); + return -1; + } + } + + if (PyIndex_Check(key)) { + Py_ssize_t index; + if (1 < view.ndim) { + PyErr_SetString(PyExc_NotImplementedError, + "sub-views are not implemented"); + return -1; + } + index = PyNumber_AsSsize_t(key, PyExc_IndexError); + if (index == -1 && PyErr_Occurred()) + return -1; + void* ptr = ptr_from_index(self, index); + if (ptr == nullptr) + return -1; + return self->fConverter->ToMemory(value, ptr) ? 0 : -1; + } + + // one-dimensional: fast path + if (PySlice_Check(key) && view.ndim == 1) { + Py_buffer dest; // sliced view + Py_ssize_t arrays[3]; + int ret = -1; + + // rvalue must be an exporter + if (PyObject_GetBuffer(value, &src, PyBUF_FULL_RO) < 0) { + if (src.obj) + PyBuffer_Release(&src); + return ret; + } + + dest = view; + dest.shape = &arrays[0]; + dest.shape[0] = view.shape[0]; + dest.strides = &arrays[1]; + dest.strides[0] = view.strides[0]; + if (view.suboffsets) { + dest.suboffsets = &arrays[2]; + dest.suboffsets[0] = view.suboffsets[0]; + } + + if (init_slice(&dest, key, 0) < 0) + return -1; + dest.len = dest.shape[0] * dest.itemsize; + + ret = copy_single(&dest, &src); + PyBuffer_Release(&src); + return ret; + } + + if (is_multiindex(key)) { + // TODO: implement + if (PyTuple_GET_SIZE(key) < view.ndim) { + PyErr_SetString(PyExc_NotImplementedError, + "sub-views are not implemented"); + return -1; + } + void* ptr = ptr_from_tuple(self, key); + if (ptr == nullptr) + return -1; + return self->fElemCnv->ToMemory(value, ptr) ? 0 : -1; + } + + if (PySlice_Check(key) || is_multislice(key)) { + // TODO: implement + PyErr_SetString(PyExc_NotImplementedError, + "LowLevelView slice assignments are currently restricted " + "to ndim = 1"); + return -1; + } + + PyErr_SetString(PyExc_TypeError, "invalid slice key"); + return -1; +} + +//--------------------------------------------------------------------------- +static int ll_getbuf(cpyrt::LowLevelView* self, Py_buffer* view, int flags) { + // Simplified from memoryobject, as we're always dealing with C arrays. + + // start with full copy + *view = self->fBufInfo; + + if (!(flags & PyBUF_FORMAT)) { + /* NULL indicates that the buffer's data type has been cast to 'B'. + view->itemsize is the _previous_ itemsize. If shape is present, + the equality product(shape) * itemsize = len still holds at this + point. The equality calcsize(format) = itemsize does _not_ hold + from here on! */ + view->format = NULL; + } + + if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS) { + PyErr_SetString(PyExc_BufferError, + "underlying buffer is not Fortran contiguous"); + return -1; + } + + if (!(flags & PyBUF_FORMAT)) { + /* PyBUF_SIMPLE or PyBUF_WRITABLE: at this point buf is C-contiguous, + so base->buf = ndbuf->data. */ + if (view->format != NULL) { + /* PyBUF_SIMPLE|PyBUF_FORMAT and PyBUF_WRITABLE|PyBUF_FORMAT do + not make sense. */ + PyErr_Format( + PyExc_BufferError, + "cannot cast to unsigned bytes if the format flag is present"); + return -1; + } + /* product(shape) * itemsize = len and calcsize(format) = itemsize + do _not_ hold from here on! */ + view->ndim = 1; + view->shape = NULL; + } + + view->obj = (PyObject*)self; + Py_INCREF(view->obj); + + return 0; +} + +//= iterator protocol ======================================================= +static PyObject* ll_iter(PyObject* self) { + // The index iterator indexes through getitem, just like python would do by + // default, except that it checks the size externally to raise StopIteration, + // rather than geitem failing. + + using namespace cppjit::cpyrt; + + indexiterobject* ii = PyObject_GC_New(indexiterobject, &IndexIter_Type); + if (!ii) + return nullptr; + + Py_INCREF(self); + ii->ii_container = self; + ii->ii_pos = 0; + ii->ii_len = ll_length((LowLevelView*)self); + + PyObject_GC_Track(ii); + return (PyObject*)ii; +} + +//- mapping methods --------------------------------------------------------- +static PyMappingMethods ll_as_mapping = { + (lenfunc)ll_length, // mp_length + (binaryfunc)ll_subscript, // mp_subscript + (objobjargproc)ll_ass_sub, // mp_ass_subscript +}; + +//- sequence methods -------------------------------------------------------- +static PySequenceMethods ll_as_sequence = { + (lenfunc)ll_length, // sq_length + 0, // sq_concat + 0, // sq_repeat + (ssizeargfunc)ll_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 +}; + +//- buffer methods ---------------------------------------------------------- +static PyBufferProcs ll_as_buffer = { + (getbufferproc)ll_getbuf, // bf_getbuffer + 0, // bf_releasebuffer +}; + +//--------------------------------------------------------------------------- +static PyObject* ll_shape(cpyrt::LowLevelView* self) { + Py_buffer& view = self->fBufInfo; + + PyObject* shape = PyTuple_New(view.ndim); + for (Py_ssize_t idim = 0; idim < view.ndim; ++idim) + PyTuple_SET_ITEM(shape, idim, PyInt_FromSsize_t(view.shape[idim])); + + return shape; +} + +//--------------------------------------------------------------------------- +static PyObject* ll_reshape(cpyrt::LowLevelView* self, PyObject* shape) { + // Allow the user to fix up the actual (type-strided) size of the buffer. + if (!PyTuple_Check(shape)) { + if (shape) { + PyObject* pystr = PyObject_Str(shape); + if (pystr) { + PyErr_Format(PyExc_TypeError, "tuple object expected, received %s", + cpyrt_PyText_AsStringChecked(pystr)); + Py_DECREF(pystr); + return nullptr; + } + } + PyErr_SetString(PyExc_TypeError, "tuple object expected"); + return nullptr; + } + + Py_buffer& view = self->fBufInfo; + + // verify size match + Py_ssize_t oldsz = 0; + for (Py_ssize_t idim = 0; idim < view.ndim; ++idim) { + Py_ssize_t nlen = view.shape[idim]; + if (nlen == cpyrt::UNKNOWN_SIZE || + nlen == INT_MAX / view.itemsize /* fake 'max' */) { + oldsz = -1; // meaning, unable to check size match + break; + } + oldsz += view.shape[idim]; + } + + if (0 < oldsz) { + Py_ssize_t newsz = 0; + for (Py_ssize_t idim = 0; idim < PyTuple_GET_SIZE(shape); ++idim) + newsz += PyInt_AsSsize_t(PyTuple_GET_ITEM(shape, idim)); + if (oldsz != newsz) { + PyObject* tas = PyObject_Str(shape); + PyErr_Format(PyExc_ValueError, + "cannot reshape array of size %ld into shape %s", + (long)oldsz, cpyrt_PyText_AsString(tas)); + Py_DECREF(tas); + return nullptr; + } + } + + // reshape + size_t itemsize = view.strides[view.ndim - 1]; + if (view.ndim != PyTuple_GET_SIZE(shape)) { + PyMem_Free(view.shape); + PyMem_Free(view.strides); + + view.ndim = (int)PyTuple_GET_SIZE(shape); + view.shape = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); + view.strides = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); + } + + for (Py_ssize_t idim = 0; idim < PyTuple_GET_SIZE(shape); ++idim) { + Py_ssize_t nlen = PyInt_AsSsize_t(PyTuple_GET_ITEM(shape, idim)); + if (nlen == -1 && PyErr_Occurred()) + return nullptr; + + if (idim == 0) + view.len = nlen * view.itemsize; + + view.shape[idim] = nlen; + } + + set_strides(view, itemsize, false /* by definition not fixed */); + + Py_RETURN_NONE; +} + +//--------------------------------------------------------------------------- +static PyObject* ll_array(cpyrt::LowLevelView* self, PyObject* args, + PyObject* kwds) { + // Construct a numpy array from the lowlevelview (w/o copy if possible); this + // uses the Python methods to avoid depending on numpy directly + + // Expect as most a dtype from the arguments; + static PyObject* npmod = PyImport_ImportModule("numpy"); // ref-count kept + if (!npmod) + return nullptr; + + bool docopy = false; + if (kwds) { + PyObject* pycp = PyObject_GetItem(kwds, cpyrt::PyStrings::gCopy); + if (!pycp) { + PyErr_SetString(PyExc_TypeError, + "__array__ only supports the \"copy\" keyword"); + return nullptr; + } + + docopy = PyObject_IsTrue(pycp); + Py_DECREF(pycp); + } + + if (!docopy) { // view requested + // expect possible dtype from the arguments, otherwise take it from the type + // code + PyObject* dtype; + if (!args || PyTuple_GET_SIZE(args) != 1) { + PyObject* npdtype = PyObject_GetAttr(npmod, cpyrt::PyStrings::gDType); + PyObject* typecode = ll_typecode(self, nullptr); + dtype = PyObject_CallFunctionObjArgs(npdtype, typecode, nullptr); + Py_DECREF(typecode); + Py_DECREF(npdtype); + } else { + dtype = PyTuple_GET_ITEM(args, 0); + Py_INCREF(dtype); + } + + if (!dtype) + return nullptr; + + PyObject* npfrombuf = + PyObject_GetAttr(npmod, cpyrt::PyStrings::gFromBuffer); + PyObject* view = PyObject_CallFunctionObjArgs(npfrombuf, (PyObject*)self, + dtype, nullptr); + Py_DECREF(dtype); + Py_DECREF(npfrombuf); + + return view; + + } else { // copy requested + PyObject* npcopy = PyObject_GetAttr(npmod, cpyrt::PyStrings::gCopy); + PyObject* newarr = + PyObject_CallFunctionObjArgs(npcopy, (PyObject*)self, nullptr); + Py_DECREF(npcopy); + + return newarr; + } + + // never get here + return nullptr; +} + +//--------------------------------------------------------------------------- +static PyObject* ll_as_string(cpyrt::LowLevelView* self) { + // Interpret memory as a null-terminated char string. + Py_buffer& view = self->fBufInfo; + + if (strcmp(view.format, "b") != 0 || view.ndim != 1) { + PyErr_Format( + PyExc_TypeError, + "as_string only supported for 1-dim char strings (format: %s, dim: %d)", + view.format, (int)view.ndim); + return nullptr; + } + + char* buf = (char*)self->get_buf(); + size_t sz = strnlen(buf, (size_t)view.shape[0]); + return cpyrt_PyText_FromStringAndSize(buf, sz); +} + +//--------------------------------------------------------------------------- +static PyMethodDef ll_methods[] = { + {(char*)"reshape", (PyCFunction)ll_reshape, METH_O, + (char*)"change the shape (not layout) of the low level view"}, + {(char*)"as_string", (PyCFunction)ll_as_string, METH_NOARGS, + (char*)"interpret memory as a null-terminated char string and return " + "Python str"}, + {(char*)"__array__", (PyCFunction)ll_array, METH_VARARGS | METH_KEYWORDS, + (char*)"return a numpy array from the low level view"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//--------------------------------------------------------------------------- +static PyGetSetDef ll_getset[] = { + {(char*)"__python_owns__", (getter)ll_getownership, (setter)ll_setownership, + (char*)"If true, python manages the life time of this buffer", nullptr}, + {(char*)"__cpp_array__", (getter)ll_getcpparray, (setter)ll_setcpparray, + (char*)"If true, this array was allocated with C++\'s new[]", nullptr}, + {(char*)"format", (getter)ll_typecode, nullptr, nullptr, nullptr}, + {(char*)"typecode", (getter)ll_typecode, nullptr, nullptr, nullptr}, + {(char*)"shape", (getter)ll_shape, (setter)ll_reshape, nullptr, nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +namespace cppjit::cpyrt { + +//= cpyrt low level view type ============================================ +PyTypeObject LowLevelView_Type = {PyVarObject_HEAD_INIT(&PyType_Type, 0)( + char*) "cppjit.LowLevelView", // tp_name + sizeof(cpyrt::LowLevelView), // tp_basicsize + 0, // tp_itemsize + (destructor)ll_dealloc, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // itp_as_async / tp_compare + 0, // tp_repr + 0, // tp_as_number + &ll_as_sequence, // tp_as_sequence + &ll_as_mapping, // tp_as_mapping + 0, // tp_hash + 0, // tp_call + 0, // tp_str + 0, // tp_getattro + 0, // tp_setattro + &ll_as_buffer, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, // tp_flags + (char*)"memory view on C++ pointer", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + (getiterfunc)ll_iter, // tp_iter + 0, // tp_iternext + ll_methods, // tp_methods + 0, // tp_members + ll_getset, // 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 + (newfunc)ll_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 + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt + +namespace { + +template struct typecode_traits {}; +template <> struct typecode_traits { + static constexpr const char* format = "?"; + static constexpr const char* name = "bool"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "b"; + static constexpr const char* name = "char"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "b"; + static constexpr const char* name = "SCharAsInt"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "B"; + static constexpr const char* name = "UCharAsInt"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "B"; + static constexpr const char* name = "UCharAsInt"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "b"; + static constexpr const char* name = "char*"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "b"; + static constexpr const char* name = "const char*"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "h"; + static constexpr const char* name = "short"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "H"; + static constexpr const char* name = "unsigned short"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "i"; + static constexpr const char* name = "int"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "I"; + static constexpr const char* name = "unsigned int"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "l"; + static constexpr const char* name = "long"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "L"; + static constexpr const char* name = "unsigned long"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "q"; + static constexpr const char* name = "long long"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "Q"; + static constexpr const char* name = "unsigned long long"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "f"; + static constexpr const char* name = "float"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "d"; + static constexpr const char* name = "double"; +}; +template <> struct typecode_traits { + static constexpr const char* format = "D"; + static constexpr const char* name = "long double"; +}; +template <> struct typecode_traits> { + static constexpr const char* format = "Zf"; + static constexpr const char* name = "std::complex"; +}; +template <> struct typecode_traits> { + static constexpr const char* format = "Zd"; + static constexpr const char* name = "std::complex"; +}; +template <> struct typecode_traits> { + static constexpr const char* format = "Zi"; + static constexpr const char* name = "std::complex"; +}; +template <> struct typecode_traits> { + static constexpr const char* format = "Zl"; + static constexpr const char* name = "std::complex"; +}; + +} // unnamed namespace + +//--------------------------------------------------------------------------- +bool cpyrt::LowLevelView::resize(size_t sz) { + Py_buffer& bi = this->fBufInfo; + if (bi.ndim == 1 && bi.shape) { + bi.len = sz * bi.itemsize; + bi.shape[0] = sz; + return true; + } + + return false; +} + +//--------------------------------------------------------------------------- +template +static inline cpyrt::LowLevelView* +CreateLowLevelViewT(T* address, cpyrt::cdims_t shape, + const char* format = nullptr, const char* name = nullptr, + Py_ssize_t itemsize = -1) { + using namespace cppjit::cpyrt; + Py_ssize_t nx = + (shape.ndim() != UNKNOWN_SIZE) ? shape[0] : INT_MAX / sizeof(T); + if (nx == UNKNOWN_SIZE) + nx = INT_MAX / sizeof(T); + PyObject* args = PyTuple_New(0); + LowLevelView* llp = (LowLevelView*)LowLevelView_Type.tp_new( + &LowLevelView_Type, args, nullptr); + Py_DECREF(args); + + Py_buffer& view = llp->fBufInfo; + view.buf = address; + view.obj = nullptr; + view.readonly = 0; + view.format = (char*)(format ? format : typecode_traits::format); + view.ndim = int(shape.ndim() != UNKNOWN_SIZE ? shape.ndim() : 1); + view.shape = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); + view.shape[0] = nx; // view.len / view.itemsize + view.strides = (Py_ssize_t*)PyMem_Malloc(view.ndim * sizeof(Py_ssize_t)); + view.suboffsets = nullptr; + (intptr_t&)view.internal = cpyrt::LowLevelView::kIsCppArray; // assumed + bool isfix = shape.ndim() != UNKNOWN_SIZE; + if (isfix) { + for (int i = 0; i < shape.ndim(); ++i) + isfix = isfix && (shape[i] != UNKNOWN_SIZE); + if (isfix) + (intptr_t&)view.internal |= cpyrt::LowLevelView::kIsFixed; + } + + llp->fElemCnv = CreateConverter(name ? name : typecode_traits::name); + if (view.ndim == 1) { + // simple 1-dim array of the declared type + view.len = nx * sizeof(T); + view.itemsize = (itemsize > 0 ? (size_t)itemsize : sizeof(T)); + llp->fConverter = llp->fElemCnv; + } else { + // multi-dim array; sub-views are projected by using more LLViews + const size_t elemsize = isfix ? sizeof(T) : sizeof(void*); + view.len = nx * elemsize; + view.itemsize = elemsize; + for (Py_ssize_t idim = 1; idim < view.ndim; ++idim) + view.shape[idim] = shape[idim]; + + // peel off one dimension and create a new LLView converter + std::string tname{name ? name : typecode_traits::name}; + tname.append("[]"); // make sure to ask for another array + // TODO: although this will work, it means that "naive" loops are expensive + llp->fConverter = CreateConverter(tname, shape.sub()); + } + + set_strides(view, sizeof(T), isfix); + + return llp; +} + +//--------------------------------------------------------------------------- +template +static inline cpyrt::LowLevelView* +CreateLowLevelViewT(T** address, cpyrt::cdims_t shape, + const char* format = nullptr, const char* name = nullptr) { + using namespace cppjit::cpyrt; + LowLevelView* llp = + (LowLevelView*)CreateLowLevelViewT((T*)address, shape, format, name); + llp->set_buf((void**)address); + return llp; +} + +//--------------------------------------------------------------------------- +#define CPPJIT_RET_W_CREATOR(type, fname) \ + PyObject* (*c)(type, cdims_t) = &fname; \ + ll->fCreator = (LowLevelView::Creator_t)c; \ + return (PyObject*)ll + +#define CPPJIT_IMPL_VIEW_CREATOR(type) \ + PyObject* cpyrt::CreateLowLevelView(type* address, cdims_t shape) { \ + LowLevelView* ll = CreateLowLevelViewT(address, shape); \ + CPPJIT_RET_W_CREATOR(type*, CreateLowLevelView); \ + } \ + PyObject* cpyrt::CreateLowLevelView(type** address, cdims_t shape) { \ + LowLevelView* ll = CreateLowLevelViewT(address, shape); \ + CPPJIT_RET_W_CREATOR(type**, CreateLowLevelView); \ + } + +CPPJIT_IMPL_VIEW_CREATOR(bool); +CPPJIT_IMPL_VIEW_CREATOR(signed char); +CPPJIT_IMPL_VIEW_CREATOR(unsigned char); +CPPJIT_IMPL_VIEW_CREATOR(std::byte); +CPPJIT_IMPL_VIEW_CREATOR(short); +CPPJIT_IMPL_VIEW_CREATOR(unsigned short); +CPPJIT_IMPL_VIEW_CREATOR(int); +CPPJIT_IMPL_VIEW_CREATOR(unsigned int); +CPPJIT_IMPL_VIEW_CREATOR(long); +CPPJIT_IMPL_VIEW_CREATOR(unsigned long); +CPPJIT_IMPL_VIEW_CREATOR(long long); +CPPJIT_IMPL_VIEW_CREATOR(unsigned long long); +CPPJIT_IMPL_VIEW_CREATOR(float); +CPPJIT_IMPL_VIEW_CREATOR(double); +CPPJIT_IMPL_VIEW_CREATOR(long double); +CPPJIT_IMPL_VIEW_CREATOR(std::complex); +CPPJIT_IMPL_VIEW_CREATOR(std::complex); +CPPJIT_IMPL_VIEW_CREATOR(std::complex); +CPPJIT_IMPL_VIEW_CREATOR(std::complex); + +PyObject* cpyrt::CreateLowLevelView(char* address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape); + CPPJIT_RET_W_CREATOR(char*, CreateLowLevelView); +} + +PyObject* cpyrt::CreateLowLevelView(char** address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape); + CPPJIT_RET_W_CREATOR(char**, CreateLowLevelView); +} + +PyObject* cpyrt::CreateLowLevelViewString(char** address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape, nullptr, + nullptr, sizeof(char)); + CPPJIT_RET_W_CREATOR(char**, CreateLowLevelViewString); +} + +PyObject* cpyrt::CreateLowLevelViewString(const char** address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape, nullptr, + nullptr, sizeof(char)); + CPPJIT_RET_W_CREATOR(const char**, CreateLowLevelViewString); +} + +PyObject* cpyrt::CreateLowLevelView_i8(int8_t* address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape, "b", "int8_t"); + CPPJIT_RET_W_CREATOR(int8_t*, CreateLowLevelView_i8); +} + +PyObject* cpyrt::CreateLowLevelView_i8(int8_t** address, cdims_t shape) { + LowLevelView* ll = CreateLowLevelViewT(address, shape, "b", "int8_t"); + CPPJIT_RET_W_CREATOR(int8_t**, CreateLowLevelView_i8); +} + +PyObject* cpyrt::CreateLowLevelView_i8(uint8_t* address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "B", "uint8_t"); + CPPJIT_RET_W_CREATOR(uint8_t*, CreateLowLevelView_i8); +} + +PyObject* cpyrt::CreateLowLevelView_i8(uint8_t** address, cdims_t shape) { + LowLevelView* ll = + CreateLowLevelViewT(address, shape, "B", "uint8_t"); + CPPJIT_RET_W_CREATOR(uint8_t**, CreateLowLevelView_i8); +} diff --git a/src/cpyrt/LowLevelViews.h b/src/cpyrt/LowLevelViews.h new file mode 100644 index 0000000..429ee7e --- /dev/null +++ b/src/cpyrt/LowLevelViews.h @@ -0,0 +1,93 @@ +#ifndef CPYRT_LOWLEVELVIEWS_H +#define CPYRT_LOWLEVELVIEWS_H + +// Bindings +#include "Dimensions.h" + +// Standard +#include +#include +#include + +namespace cppjit::cpyrt { + +class Converter; + +class LowLevelView { +public: + enum EFlags { + kDefault = 0x0000, + kIsCppArray = 0x0001, // allocated with new[] + kIsFixed = 0x0002, // fixed size array (assumed flat) + kIsOwner = 0x0004 + }; // Python owns + +public: + PyObject_HEAD Py_buffer fBufInfo; + void** fBuf; + Converter* fConverter; + Converter* fElemCnv; + + typedef LowLevelView* (*Creator_t)(void*, cdims_t); + Creator_t fCreator; // for slicing, which requires copying + +public: + void* get_buf() { return fBuf ? *fBuf : fBufInfo.buf; } + void set_buf(void** buf) { + fBuf = buf; + fBufInfo.buf = get_buf(); + } + + bool resize(size_t sz); +}; + +#define CPPJIT_DECL_VIEW_CREATOR(type) \ + PyObject* CreateLowLevelView(type*, cdims_t shape); \ + PyObject* CreateLowLevelView(type**, cdims_t shape) + +CPPJIT_DECL_VIEW_CREATOR(bool); +CPPJIT_DECL_VIEW_CREATOR(char); +CPPJIT_DECL_VIEW_CREATOR(signed char); +CPPJIT_DECL_VIEW_CREATOR(unsigned char); +CPPJIT_DECL_VIEW_CREATOR(std::byte); +PyObject* CreateLowLevelView_i8(int8_t*, cdims_t shape); +PyObject* CreateLowLevelView_i8(int8_t**, cdims_t shape); +PyObject* CreateLowLevelView_i8(uint8_t*, cdims_t shape); +PyObject* CreateLowLevelView_i8(uint8_t**, cdims_t shape); +CPPJIT_DECL_VIEW_CREATOR(short); +CPPJIT_DECL_VIEW_CREATOR(unsigned short); +CPPJIT_DECL_VIEW_CREATOR(int); +CPPJIT_DECL_VIEW_CREATOR(unsigned int); +CPPJIT_DECL_VIEW_CREATOR(long); +CPPJIT_DECL_VIEW_CREATOR(unsigned long); +CPPJIT_DECL_VIEW_CREATOR(long long); +CPPJIT_DECL_VIEW_CREATOR(unsigned long long); +CPPJIT_DECL_VIEW_CREATOR(float); +CPPJIT_DECL_VIEW_CREATOR(double); +CPPJIT_DECL_VIEW_CREATOR(long double); +CPPJIT_DECL_VIEW_CREATOR(std::complex); +CPPJIT_DECL_VIEW_CREATOR(std::complex); +CPPJIT_DECL_VIEW_CREATOR(std::complex); +CPPJIT_DECL_VIEW_CREATOR(std::complex); + +PyObject* CreateLowLevelViewString(char**, cdims_t shape); +PyObject* CreateLowLevelViewString(const char**, cdims_t shape); + +inline PyObject* CreatePointerView(void* ptr, cdims_t shape = 0) { + return CreateLowLevelView((uintptr_t*)ptr, shape); +} + +//- low level view type and type verification -------------------------------- +extern PyTypeObject LowLevelView_Type; + +template inline bool LowLevelView_Check(T* object) { + return object && PyObject_TypeCheck(object, &LowLevelView_Type); +} + +template inline bool LowLevelView_CheckExact(T* object) { + return object && Py_TYPE(object) == &LowLevelView_Type; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_LOWLEVELVIEWS_H diff --git a/src/cpyrt/MemoryRegulator.cxx b/src/cpyrt/MemoryRegulator.cxx new file mode 100644 index 0000000..6ea7671 --- /dev/null +++ b/src/cpyrt/MemoryRegulator.cxx @@ -0,0 +1,256 @@ +// Bindings +#include "cppjit_interop.h" +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPInstance.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" + +// Standard +#include +#include +#include + +//= pseudo-None type for masking out objects on the python side =============== +static PyTypeObject cpyrt_NoneType; + +//----------------------------------------------------------------------------- +static Py_ssize_t AlwaysNullLength(PyObject*) { return 0; } + +//----------------------------------------------------------------------------- +static PyMappingMethods cpyrt_NoneType_mapping = { + AlwaysNullLength, (binaryfunc)0, (objobjargproc)0}; + +// silence warning about some cast operations +#if defined(__GNUC__) && \ + (__GNUC__ >= 5 || \ + (__GNUC__ >= 4 && ((__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ >= 1) || \ + (__GNUC_MINOR__ >= 3)))) && \ + !__INTEL_COMPILER +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +//----------------------------------------------------------------------------- +namespace { + +struct Initcpyrt_NoneType_t { + Initcpyrt_NoneType_t() { + // create a cpyrt NoneType (for references that went dodo) from NoneType + memset(&cpyrt_NoneType, 0, sizeof(cpyrt_NoneType)); + + ((PyObject&)cpyrt_NoneType).ob_type = &PyType_Type; + Py_SET_REFCNT((PyObject*)&cpyrt_NoneType, 1); + ((PyVarObject&)cpyrt_NoneType).ob_size = 0; + + cpyrt_NoneType.tp_name = const_cast("cpyrt_NoneType"); + cpyrt_NoneType.tp_flags = Py_TPFLAGS_HAVE_RICHCOMPARE; + + cpyrt_NoneType.tp_traverse = (traverseproc)0; + cpyrt_NoneType.tp_clear = (inquiry)0; + cpyrt_NoneType.tp_dealloc = (destructor)&Initcpyrt_NoneType_t::DeAlloc; + cpyrt_NoneType.tp_repr = Py_TYPE(Py_None)->tp_repr; + cpyrt_NoneType.tp_richcompare = + (richcmpfunc)&Initcpyrt_NoneType_t::RichCompare; + cpyrt_NoneType.tp_hash = (hashfunc)&Initcpyrt_NoneType_t::PtrHash; + + cpyrt_NoneType.tp_as_mapping = &cpyrt_NoneType_mapping; + + PyType_Ready(&cpyrt_NoneType); + } + + static void DeAlloc(PyObject* pyobj) { Py_TYPE(pyobj)->tp_free(pyobj); } + static int PtrHash(PyObject* pyobj) { return (int)ptrdiff_t(pyobj); } + + static PyObject* RichCompare(PyObject*, PyObject* other, int opid) { + return PyObject_RichCompare(other, Py_None, opid); + } + + static int Compare(PyObject*, PyObject* other) { + // TODO the following isn't correct as it doesn't order, but will do for now + // ... + return !PyObject_RichCompareBool(other, Py_None, Py_EQ); + } +}; + +} // unnamed namespace + +// Memory regulation hooks +cpyrt::MemHook_t cpyrt::MemoryRegulator::registerHook = nullptr; +cpyrt::MemHook_t cpyrt::MemoryRegulator::unregisterHook = nullptr; + +//- ctor/dtor ---------------------------------------------------------------- +cpyrt::MemoryRegulator::MemoryRegulator() { + // setup NoneType for referencing and create weakref cache + static Initcpyrt_NoneType_t initcpyrt_NoneType; +} + +//- public members ----------------------------------------------------------- +bool cpyrt::MemoryRegulator::RecursiveRemove(interop::TCppObject_t cppobj, + interop::TCppScope_t klass) { + // if registered by the framework, called whenever a cppobj gets destroyed + if (!cppobj) + return false; + + PyObject* pyscope = GetScopeProxy(klass); + if (!CPPScope_Check(pyscope)) { + Py_XDECREF(pyscope); + return false; + } + + CppToPyMap_t* cppobjs = ((CPPClass*)pyscope)->fImp.fCppObjects; + if (!cppobjs) { // table may have been deleted on shutdown + Py_DECREF(pyscope); + return false; + } + + // see whether we're tracking this object + CppToPyMap_t::iterator ppo = cppobjs->find(cppobj); + + if (ppo != cppobjs->end()) { + // get the tracked object + CPPInstance* pyobj = (CPPInstance*)ppo->second; + + // erase the object from tracking + pyobj->fFlags &= ~CPPInstance::kIsRegulated; + cppobjs->erase(ppo); + + // nullify the object + if (!cpyrt_NoneType.tp_traverse) { + // take a reference as we're copying its function pointers + Py_INCREF(Py_TYPE(pyobj)); + + // all object that arrive here are expected to be of the same type + // ("instance") + cpyrt_NoneType.tp_traverse = Py_TYPE(pyobj)->tp_traverse; + cpyrt_NoneType.tp_clear = Py_TYPE(pyobj)->tp_clear; + cpyrt_NoneType.tp_free = Py_TYPE(pyobj)->tp_free; + cpyrt_NoneType.tp_flags |= Py_TYPE(pyobj)->tp_flags; + } else if (cpyrt_NoneType.tp_traverse != Py_TYPE(pyobj)->tp_traverse) { + // TODO: SystemError? + std::cerr << "in cpyrt::MemoryRegulater, unexpected object of type: " + << Py_TYPE(pyobj)->tp_name << std::endl; + + // drop object and leave before too much damage is done + Py_DECREF(pyscope); + return false; + } + + // notify any other weak referents by playing dead + Py_ssize_t refcnt = Py_REFCNT((PyObject*)pyobj); + Py_SET_REFCNT((PyObject*)pyobj, 0); + PyObject_ClearWeakRefs((PyObject*)pyobj); + Py_SET_REFCNT((PyObject*)pyobj, refcnt); + + // cleanup object internals + pyobj->CppOwns(); // held object is out of scope now anyway + op_dealloc_nofree(pyobj); // normal object cleanup, while keeping memory + + // reset type object + Py_INCREF((PyObject*)(void*)&cpyrt_NoneType); + Py_DECREF(Py_TYPE(pyobj)); + ((PyObject*)pyobj)->ob_type = &cpyrt_NoneType; + + Py_DECREF(pyscope); + return true; + } + + // unregulated cppobj + Py_DECREF(pyscope); + return false; +} + +//----------------------------------------------------------------------------- +bool cpyrt::MemoryRegulator::RegisterPyObject(CPPInstance* pyobj, + interop::TCppObject_t cppobj) { + // start tracking proxied by + if (!(pyobj && cppobj)) + return false; + + if (registerHook) { + auto res = registerHook(cppobj, pyobj->ObjectIsA(false)); + if (!res.second) + return res.first; + } + + CppToPyMap_t* cppobjs = ((CPPClass*)Py_TYPE(pyobj))->fImp.fCppObjects; + if (!cppobjs) + return false; + + // if an address was already associated with a different object, then stop + // following the old and force insert the new proxy for following + const auto& res = cppobjs->insert(std::make_pair(cppobj, (PyObject*)pyobj)); + if (!res.second) { + ((CPPInstance*)res.first->second)->fFlags &= ~CPPInstance::kIsRegulated; + (*cppobjs)[cppobj] = (PyObject*)pyobj; + } + + pyobj->fFlags |= CPPInstance::kIsRegulated; + return true; +} + +//----------------------------------------------------------------------------- +bool cpyrt::MemoryRegulator::UnregisterPyObject(CPPInstance* pyobj, + PyObject* pyclass) { + // called when the proxy is garbage collected or is about delete the C++ + // instance + if (!(pyobj && pyclass)) + return false; + + interop::TCppObject_t cppobj = + pyobj->IsSmart() ? pyobj->GetObjectRaw() : pyobj->GetObject(); + if (!cppobj) + return false; + + if (unregisterHook) { + auto res = unregisterHook(cppobj, ((CPPClass*)pyclass)->fCppType); + if (!res.second) + return res.first; + } + + CppToPyMap_t* cppobjs = ((CPPClass*)pyclass)->fImp.fCppObjects; + if (!cppobjs) + return false; + + // erase if tracked + if (cppobjs->erase(cppobj)) { + pyobj->fFlags &= ~CPPInstance::kIsRegulated; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::MemoryRegulator::RetrievePyObject(interop::TCppObject_t cppobj, + PyObject* pyclass) { + // lookup to see if a C++ address is already known, return old proxy if + // tracked + if (!(cppobj && pyclass)) + return nullptr; + + CppToPyMap_t* cppobjs = ((CPPClass*)pyclass)->fImp.fCppObjects; + if (!cppobjs) + return nullptr; + + CppToPyMap_t::iterator ppo = cppobjs->find(cppobj); + if (ppo != cppobjs->end()) { + Py_INCREF(ppo->second); + return ppo->second; + } + + return nullptr; +} + +//----------------------------------------------------------------------------- +void cpyrt::MemoryRegulator::SetRegisterHook(MemHook_t h) { + // Set custom register hook; called when a regulated object is to be tracked + registerHook = h; +} + +//----------------------------------------------------------------------------- +void cpyrt::MemoryRegulator::SetUnregisterHook(MemHook_t h) { + // Set custom unregister hook; called when a regulated object is to be + // untracked + unregisterHook = h; +} diff --git a/src/cpyrt/MemoryRegulator.h b/src/cpyrt/MemoryRegulator.h new file mode 100644 index 0000000..419a67b --- /dev/null +++ b/src/cpyrt/MemoryRegulator.h @@ -0,0 +1,48 @@ +#ifndef CPYRT_MEMORYREGULATOR_H +#define CPYRT_MEMORYREGULATOR_H + +#include "Python.h" +#include "cppjit_interop.h" + +#include +#include + +namespace cppjit::cpyrt { + +class CPPInstance; + +typedef std::function(cppjit::interop::TCppObject_t, + cppjit::interop::TCppScope_t)> + MemHook_t; + +class MemoryRegulator { +private: + static MemHook_t registerHook, unregisterHook; + +public: + MemoryRegulator(); + + // callback from C++-side frameworks + static bool RecursiveRemove(cppjit::interop::TCppObject_t cppobj, + cppjit::interop::TCppScope_t klass); + + // called when a new python proxy object is created + static bool RegisterPyObject(CPPInstance* pyobj, + cppjit::interop::TCppObject_t cppobj); + + // called when a the python proxy object is about to be garbage collected or + // when it is about to delete the proxied C++ object, if owned + static bool UnregisterPyObject(CPPInstance* pyobj, PyObject* pyclass); + + // new reference to python object matching cppobj, or 0 on failure + static PyObject* RetrievePyObject(cppjit::interop::TCppObject_t cppobj, + PyObject* pyclass); + + // set hooks for custom memory regulation + static void SetRegisterHook(MemHook_t h); + static void SetUnregisterHook(MemHook_t h); +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_MEMORYREGULATOR_H diff --git a/src/cpyrt/ProxyWrappers.cxx b/src/cpyrt/ProxyWrappers.cxx new file mode 100644 index 0000000..4503502 --- /dev/null +++ b/src/cpyrt/ProxyWrappers.cxx @@ -0,0 +1,1060 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPClassMethod.h" +#include "CPPConstructor.h" +#include "CPPDataMember.h" +#include "CPPExcInstance.h" +#include "CPPFunction.h" +#include "CPPGetSetItem.h" +#include "CPPInstance.h" +#include "CPPMethod.h" +#include "CPPOperator.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "Pythonize.h" +#include "TemplateProxy.h" +#include "TupleOfInstances.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include +#include +#include +#include +#include + +//- data _______________________________________________________________________ +namespace cppjit::cpyrt { +extern PyObject* gThisModule; +extern PyObject* gPyTypeMap; +extern std::unordered_set gPinnedTypes; +} // namespace cppjit::cpyrt + +// to prevent having to walk scopes, track python classes by C++ class +typedef std::unordered_map PyClassMap_t; +static PyClassMap_t gPyClasses; + +//- helpers -------------------------------------------------------------------- + +namespace cppjit::cpyrt { + +typedef struct { + PyObject_HEAD PyObject* dict; +} proxyobject; + +// helper for creating new C++ proxy python types +static PyObject* CreateNewCppProxyClass(interop::TCppScope_t klass, + PyObject* pybases) { + // Create a new python shadow class with the required hierarchy and + // meta-classes. + PyObject* pymetabases = PyTuple_New(PyTuple_GET_SIZE(pybases)); + for (int i = 0; i < PyTuple_GET_SIZE(pybases); ++i) { + PyObject* btype = (PyObject*)Py_TYPE(PyTuple_GetItem(pybases, i)); + Py_INCREF(btype); + PyTuple_SET_ITEM(pymetabases, i, btype); + } + + std::string name = interop::GetFinalName(klass); + + // create meta-class, add a dummy __module__ to pre-empt the default setting + PyObject* args = + Py_BuildValue((char*)"sO{}", (name + "_meta").c_str(), pymetabases); + PyDict_SetItem(PyTuple_GET_ITEM(args, 2), PyStrings::gModule, Py_True); + Py_DECREF(pymetabases); + + PyObject* pymeta = (PyObject*)CPPScopeMeta_New(klass, args); + Py_DECREF(args); + if (!pymeta) + return nullptr; + + // alright, and now we really badly want to get rid of the dummy ... + PyObject* dictproxy = PyObject_GetAttr(pymeta, PyStrings::gDict); + PyDict_DelItem(((proxyobject*)dictproxy)->dict, PyStrings::gModule); + + // create actual class + args = Py_BuildValue((char*)"sO{}", name.c_str(), pybases); + PyObject* pyclass = + ((PyTypeObject*)pymeta)->tp_new((PyTypeObject*)pymeta, args, nullptr); + + Py_DECREF(args); + Py_DECREF(pymeta); + + return pyclass; +} + +static inline void AddPropertyToClass(PyObject* pyclass, + interop::TCppScope_t scope, + interop::TCppScope_t data) { + cpyrt::CPPDataMember* property = cpyrt::CPPDataMember_New(scope, data); + PyObject* pname = cpyrt_PyText_InternFromString( + const_cast(property->GetName().c_str())); + + // allow access at the instance level + PyType_Type.tp_setattro(pyclass, pname, (PyObject*)property); + if (PyErr_Occurred()) { + // CPPDataMember.tp_descr_set raises an error + // when all of the following conditions are met + // 1. data is static const + // 2. scope is a derived class + // 3. data is defined in both parent and base class (using + // parent::attribute;) + PyErr_Clear(); + } + + // allow access at the class level (always add after setting instance level) + if (interop::IsStaticDatamember(data) || interop::IsEnumConstant(data)) + PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pname, + (PyObject*)property); + + // cleanup + Py_DECREF(pname); + Py_DECREF(property); +} + +static inline void AddScopeToParent(PyObject* parent, const std::string& name, + PyObject* newscope) { + PyObject* pyname = cpyrt_PyText_InternFromString((char*)name.c_str()); + if (CPPScope_Check(parent)) + PyType_Type.tp_setattro(parent, pyname, newscope); + else + PyObject_SetAttr(parent, pyname, newscope); + Py_DECREF(pyname); +} + +static inline PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { + // get an attribute without causing getattr lookups + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + if (dct) { + PyObject* attr = PyObject_GetItem(dct, pyname); + Py_DECREF(dct); + return attr; + } + return nullptr; +} + +} // namespace cppjit::cpyrt + +//- public functions --------------------------------------------------------- +namespace cppjit::cpyrt { + +static inline void sync_templates(PyObject* pyclass, + const std::string& mtCppName, + const std::string& mtName) { + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + PyObject* pyname = cpyrt_PyText_FromString(const_cast(mtName.c_str())); + PyObject* attr = PyObject_GetItem(dct, pyname); + if (!attr) + PyErr_Clear(); + Py_DECREF(dct); + if (!TemplateProxy_Check(attr)) { + TemplateProxy* pytmpl = TemplateProxy_New(mtCppName, mtName, pyclass); + if (CPPOverload_Check(attr)) + pytmpl->MergeOverload((CPPOverload*)attr); + PyType_Type.tp_setattro(pyclass, pyname, (PyObject*)pytmpl); + Py_DECREF(pytmpl); + } + Py_XDECREF(attr); + Py_DECREF(pyname); +} + +static int BuildScopeProxyDict(interop::TCppScope_t scope, PyObject* pyclass, + const unsigned int flags) { + // Collect methods and data for the given scope, and add them to the given + // python proxy object. + + // some properties that'll affect building the dictionary + bool isNamespace = interop::IsNamespace(scope); + bool isComplete = interop::IsComplete(scope); + bool hasConstructor = false; + interop::TCppMethod_t potGetItem = nullptr; + + // load all public methods and data members + typedef std::vector Callables_t; + typedef std::unordered_map CallableCache_t; + CallableCache_t cache; + + // bypass custom __getattr__ for efficiency + getattrofunc oldgetattro = Py_TYPE(pyclass)->tp_getattro; + Py_TYPE(pyclass)->tp_getattro = PyType_Type.tp_getattro; + + // functions in namespaces are properly found through lazy lookup, so do not + // create them until needed (the same is not true for data members) + std::vector methods; + if (isComplete) + interop::GetClassMethods(scope, methods); + + for (auto& method : methods) { + + // do not expose non-public methods as the Cling wrappers as those won't + // compile + if (!interop::IsPublicMethod(method)) + continue; + + // process the method based on its name + std::string mtCppName = interop::GetName(interop::TCppScope_t(method.data)); + + // special case trackers + bool setupSetItem = false; + bool isConstructor = interop::IsConstructor(method); + + // Here isTemplate means to ignore templated constructors, that is handled + // in the next loop. + bool isTemplate = interop::IsTemplatedMethod(method) && !isConstructor; + bool isStubbedOperator = false; + + // filter empty names (happens for namespaces, is bug?) + if (mtCppName == "") + continue; + + // filter C++ destructors + if (mtCppName[0] == '~') + continue; + + // translate operators + std::string mtName = Utility::MapOperatorName( + mtCppName, interop::GetMethodNumArgs(method), &isStubbedOperator); + if (mtName.empty()) + continue; + + // operator[]/() returning a reference type will be used for __setitem__ + bool isCall = mtName == "__call__"; + if (isCall || mtName == "__getitem__") { + const std::string& qual_return = + interop::GetMethodReturnTypeAsString(method); + const std::string& cpd = TypeManip::compound(qual_return); + if (!cpd.empty() && cpd[cpd.size() - 1] == '&' && + qual_return.find("const", 0, 5) == std::string::npos) { + if (isCall && !potGetItem) + potGetItem = method; + setupSetItem = true; // will add methods as overloads + } else if (isCall && 1 < interop::GetMethodNumArgs(method)) { + // not a non-const by-ref return, thus better __getitem__ candidate; the + // requirement for multiple arguments is that there is otherwise no + // benefit over the use of normal __getitem__ (this allows + // multi-indexing arguments, which is clean in Python, but not allowed + // in C++) + potGetItem = method; + } + } + + // template members; handled by adding a dispatcher to the class + bool storeOnTemplate = isTemplate; + if (storeOnTemplate) { + sync_templates(pyclass, mtCppName, mtName); + // continue processing to actually add the method so that the proxy can + // find it on the class when called explicitly + } + + // construct the holder + PyCallable* pycall = nullptr; + if (interop::IsStaticMethod(method)) // class method + pycall = new CPPClassMethod(scope, method); + else if (isNamespace) // free function + pycall = new CPPFunction(scope, method); + else if (isConstructor) { // ctor + mtName = "__init__"; + hasConstructor = true; + if (!interop::IsAbstract(scope)) { + if (flags & CPPScope::kIsMultiCross) { + pycall = new CPPMultiConstructor(scope, method); + } else + pycall = new CPPConstructor(scope, method); + } else + pycall = new CPPAbstractClassConstructor(scope, method); + } else if (isStubbedOperator) { + pycall = new CPPOperator(scope, method, mtName); + } else // member function + pycall = new CPPMethod(scope, method); + + if (storeOnTemplate) { + // template proxy was already created in sync_templates call above, so + // add only here, not to the cache of collected methods + PyObject* attr = + PyObject_GetAttrString(pyclass, const_cast(mtName.c_str())); + if (isTemplate) + ((TemplateProxy*)attr)->AdoptTemplate(pycall); + else + ((TemplateProxy*)attr)->AdoptMethod(pycall); + Py_DECREF(attr); + + // for operator[]/() that returns by ref, also add __setitem__ + if (setupSetItem) { + TemplateProxy* pysi = + (TemplateProxy*)GetAttrDirect(pyclass, PyStrings::gSetItem); + if (!TemplateProxy_Check(pysi)) { + CPPOverload* precursor = + (CPPOverload_Check(pysi)) ? (CPPOverload*)pysi : nullptr; + if (pysi && !precursor) + Py_DECREF(pysi); // something unknown, just drop it + pysi = TemplateProxy_New(mtCppName, "__setitem__", pyclass); + if (precursor) + pysi->MergeOverload(precursor); + Py_XDECREF(precursor); + PyObject_SetAttrString(pyclass, const_cast("__setitem__"), + (PyObject*)pysi); + } + if (isTemplate) + pysi->AdoptTemplate(new CPPSetItem(scope, method)); + else + pysi->AdoptMethod(new CPPSetItem(scope, method)); + Py_XDECREF(pysi); + } + + } else { + // lookup method dispatcher and store method + Callables_t& md = + (*(cache.insert(std::make_pair(mtName, Callables_t())).first)).second; + md.push_back(pycall); + + // special case for operator[]/() that returns by ref, use for + // getitem/call and setitem + if (setupSetItem) { + Callables_t& setitem = + (*(cache + .insert(std::make_pair(std::string("__setitem__"), + Callables_t())) + .first)) + .second; + setitem.push_back(new CPPSetItem(scope, method)); + } + } + } + + // add proxies for un-instantiated/non-overloaded templated methods + std::vector templ_methods; + interop::GetTemplatedMethods(scope, templ_methods); + for (auto& method : templ_methods) { + const std::string mtCppName = + interop::GetName(interop::TCppScope_t(method.data)); + // the number of arguments isn't known until instantiation and as far as C++ + // is concerned, all same-named operators are simply overloads; so will + // pre-emptively add both names if with and without arguments differ, + // letting the normal overload mechanism resolve on call + bool isConstructor = interop::IsConstructor(method); + + // first add with no arguments + std::string mtName0 = + isConstructor ? "__init__" : Utility::MapOperatorName(mtCppName, false); + sync_templates(pyclass, mtCppName, mtName0); + + // then add when taking arguments, if this method is different + if (!isConstructor) { + std::string mtName1 = Utility::MapOperatorName(mtCppName, true); + if (mtName0 != mtName1) + sync_templates(pyclass, mtCppName, mtName1); + } + } + + // add a pseudo-default ctor, if none defined + if (!hasConstructor) { + PyCallable* defctor = nullptr; + if (!isComplete) { + ((CPPScope*)pyclass)->fFlags |= CPPScope::kIsInComplete; + defctor = + new CPPIncompleteClassConstructor(scope, interop::TCppMethod_t{}); + } else if (interop::IsAbstract(scope)) { + defctor = new CPPAbstractClassConstructor(scope, interop::TCppMethod_t{}); + } else if (isNamespace) { + defctor = new CPPNamespaceConstructor(scope, interop::TCppMethod_t{}); + } else { + defctor = + new CPPAllPrivateClassConstructor(scope, interop::TCppMethod_t{}); + } + + if (defctor) + cache["__init__"].push_back(defctor); + } + + // map __call__ to __getitem__ if also mapped to __setitem__ + if (potGetItem) { + Callables_t& getitem = + (*(cache + .insert( + std::make_pair(std::string("__getitem__"), Callables_t())) + .first)) + .second; + getitem.push_back(new CPPGetItem(scope, potGetItem)); + } + + // add the methods to the class dictionary + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + for (CallableCache_t::iterator imd = cache.begin(); imd != cache.end(); + ++imd) { + // in order to prevent removing templated editions of this method (which + // were set earlier, above, as a different proxy object), we'll check and + // add this method flagged as a generic one (to be picked up by the + // templated one as appropriate) if a template exists + PyObject* pyname = + cpyrt_PyText_FromString(const_cast(imd->first.c_str())); + PyObject* attr = PyObject_GetItem(dct, pyname); + Py_DECREF(pyname); + if (TemplateProxy_Check(attr)) { + // template exists, supply it with the non-templated method overloads + for (auto cit : imd->second) + ((TemplateProxy*)attr)->AdoptMethod(cit); + } else { + if (!attr) + PyErr_Clear(); + // normal case, add a new method + CPPOverload* method = CPPOverload_New(imd->first, imd->second); + PyObject* pymname = cpyrt_PyText_InternFromString( + const_cast(method->GetName().c_str())); + PyType_Type.tp_setattro(pyclass, pymname, (PyObject*)method); + Py_DECREF(pymname); + Py_DECREF(method); + } + + Py_XDECREF(attr); // could have been found in base class or non-existent + } + Py_DECREF(dct); + + // collect data members (including enums) + std::vector datamembers; + interop::GetDatamembers(scope, datamembers); + for (auto& datamember : datamembers) { + // allow only public members + if (!interop::IsPublicData(datamember)) + continue; + + // enum datamembers (this in conjunction with previously collected enums + // above) + if (interop::IsEnumType(interop::GetDatamemberType(datamember)) && + interop::IsEnumConstant(datamember)) { + // two options: this is a static variable, or it is the enum value, the + // latter already exists, so check for it and move on if set + PyObject* eset = PyObject_GetAttrString( + pyclass, + const_cast(interop::GetFinalName(datamember).c_str())); + if (eset) { + Py_DECREF(eset); + continue; + } + + PyErr_Clear(); + + // it could still be that this is an anonymous enum, which is not in the + // list provided by the class + if (strstr(interop::GetDatamemberTypeAsString(datamember).c_str(), + "(anonymous)") != 0 || + strstr(interop::GetDatamemberTypeAsString(datamember).c_str(), + "(unnamed)") != 0) { + AddPropertyToClass(pyclass, scope, datamember); + continue; + } + } + + // properties (aka public (static) data members) + AddPropertyToClass(pyclass, scope, datamember); + } + + // restore custom __getattr__ + Py_TYPE(pyclass)->tp_getattro = oldgetattro; + + // all ok, done + return 0; +} + +//---------------------------------------------------------------------------- +static void CollectUniqueBases(interop::TCppScope_t klass, + std::deque& uqb) { + // collect bases in acceptable mro order, while removing duplicates (this may + // break the overload resolution in esoteric cases, but otherwise the class + // can not be used at all, as CPython will refuse the mro). + size_t nbases = interop::GetNumBases(klass); + + for (size_t ibase = 0; ibase < nbases; ++ibase) { + interop::TCppScope_t tp = interop::GetBaseScope(klass, ibase); + int decision = 2; + if (!tp) + continue; // means this base with not be available Python-side + for (size_t ibase2 = 0; ibase2 < uqb.size(); ++ibase2) { + if (uqb[ibase2] == tp) { // not unique ... skip + decision = 0; + break; + } + + if (interop::IsSubclass(tp, uqb[ibase2])) { + // mro requirement: sub-type has to follow base + decision = 1; + break; + } + } + + if (decision == 1) { + uqb.push_front(tp); + } else if (decision == 2) { + uqb.push_back(tp); + } + // skipped if decision == 0 (not unique) + } +} + +static PyObject* BuildCppClassBases(interop::TCppScope_t klass) { + // Build a tuple of python proxy classes of all the bases of the given + // 'klass'. + std::deque uqb; + CollectUniqueBases(klass, uqb); + + // allocate a tuple for the base classes, special case for first base + size_t nbases = uqb.size(); + + PyObject* pybases = PyTuple_New(nbases ? nbases : 1); + if (!pybases) + return nullptr; + + // build all the bases + if (nbases == 0) { + Py_INCREF((PyObject*)(void*)&CPPInstance_Type); + PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPInstance_Type); + } else { + for (std::deque::size_type ibase = 0; ibase < nbases; + ++ibase) { + PyObject* pyclass = CreateScopeProxy(uqb[ibase]); + if (!pyclass) { + Py_DECREF(pybases); + return nullptr; + } + + PyTuple_SET_ITEM(pybases, ibase, pyclass); + } + + // special case, if true python types enter the hierarchy, make sure that + // the first base seen is still the CPPInstance_Type + if (!PyObject_IsSubclass(PyTuple_GET_ITEM(pybases, 0), + (PyObject*)&CPPInstance_Type)) { + PyObject* newpybases = PyTuple_New(nbases + 1); + Py_INCREF((PyObject*)(void*)&CPPInstance_Type); + PyTuple_SET_ITEM(newpybases, 0, (PyObject*)(void*)&CPPInstance_Type); + for (int ibase = 0; ibase < (int)nbases; ++ibase) { + PyObject* pyclass = PyTuple_GET_ITEM(pybases, ibase); + Py_INCREF(pyclass); + PyTuple_SET_ITEM(newpybases, ibase + 1, pyclass); + } + Py_DECREF(pybases); + pybases = newpybases; + } + } + + return pybases; +} + +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +PyObject* cpyrt::GetScopeProxy(interop::TCppScope_t scope) { + // Retrieve scope proxy from the known ones. + PyClassMap_t::iterator pci = gPyClasses.find(scope); + if (pci != gPyClasses.end()) { + PyObject* pyclass = cpyrt_GetWeakRef(pci->second); + if (pyclass) + return pyclass; + } + + return nullptr; +} +namespace cppjit::cpyrt { +PyObject* CppType_To_PyObject(interop::TCppType_t type, std::string name, + interop::TCppScope_t parent_scope, + PyObject* parent) { + interop::TCppType_t resolved_type = interop::ResolveType(type); + if (gPyTypeMap) { + const std::string& resolved = interop::GetTypeAsString(resolved_type); + PyObject* tc = + PyDict_GetItemString(gPyTypeMap, resolved.c_str()); // borrowed + if (tc && PyCallable_Check(tc)) { + const std::string& scName = interop::GetScopedFinalName(parent_scope); + PyObject* nt = + PyObject_CallFunction(tc, (char*)"ss", name.c_str(), + scName != "" ? scName.c_str() : ""); + if (nt) { + if (parent) { + AddScopeToParent(parent, name, nt); + Py_DECREF(parent); + } + return nt; + } + PyErr_Clear(); + } + } + return nullptr; +} +} // namespace cppjit::cpyrt + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CreateScopeProxy(PyObject*, PyObject* args) { + // Build a python shadow class for the named C++ class. + std::string cname = cpyrt_PyText_AsString(PyTuple_GetItem(args, 0)); + if (PyErr_Occurred()) + return nullptr; + + return CreateScopeProxy(cname); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CreateScopeProxy(const std::string& name, PyObject* parent, + const unsigned flags) { + // Build a python shadow class for the named C++ class or namespace. + + // determine complete scope name, if a python parent has been given + interop::TCppScope_t parent_scope; + if (parent) { + if (CPPScope_Check(parent)) + parent_scope = ((CPPScope*)parent)->fCppType; + else { + PyObject* parname = PyObject_GetAttr(parent, PyStrings::gName); + if (!parname) { + PyErr_Format(PyExc_SystemError, "given scope has no name for %s", + name.c_str()); + return nullptr; + } + + // should be a string + std::string scName = cpyrt_PyText_AsString(parname); + Py_DECREF(parname); + if (PyErr_Occurred()) + return nullptr; + parent_scope = interop::GetScope(scName); + } + + Py_INCREF(parent); + } + + // retrieve C++ class (this verifies name, and is therefore done first) + if (name == "") { + interop::TCppScope_t klass = interop::GetGlobalScope(); + Py_INCREF(gThisModule); + parent = gThisModule; + return CreateScopeProxy(klass, parent, flags); + } else if (interop::TCppScope_t klass = + interop::GetScope(name, parent_scope)) { + if (interop::IsTypedefed(klass) && + interop::IsPointerType(interop::GetTypeFromScope(klass)) && + interop::IsClass(interop::GetUnderlyingScope(klass))) + return nullptr; // this is handled by the caller; typedef to class pointer + return CreateScopeProxy(klass, parent, flags); + } else if (interop::IsBuiltin(name)) { + interop::TCppType_t type = interop::GetType(name); + PyObject* result = nullptr; + if (type) + result = CppType_To_PyObject(type, name, parent_scope, parent); + if (result) + return result; + } + // all options have been exhausted: it doesn't exist as such + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", + name.c_str()); + Py_XDECREF(parent); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CreateScopeProxy(interop::TCppScope_t scope, PyObject* parent, + const unsigned flags) { + // Convenience function with a lookup first through the known existing + // proxies. + PyObject* pyclass = GetScopeProxy(scope); + if (pyclass) + return pyclass; + + interop::TCppScope_t parent_scope = nullptr; + if (CPPScope_Check(parent)) + parent_scope = ((CPPScope*)parent)->fCppType; + if (!parent_scope) + parent_scope = interop::GetParentScope(scope); + + if (!parent) { + if (parent_scope) + parent = CreateScopeProxy(parent_scope); + else { + Py_INCREF(gThisModule); + parent = gThisModule; + } + } + + std::string name = interop::GetFinalName(scope); + bool is_typedef = false; + std::string typedefed_name = ""; + if (interop::IsTypedefed(scope)) { + is_typedef = true; + typedefed_name = interop::GetFullName(scope); + interop::TCppScope_t underlying_scope = interop::GetUnderlyingScope(scope); + if ((underlying_scope) && (underlying_scope != scope)) { + scope = underlying_scope; + } else { + if (PyObject* result = CppType_To_PyObject( + interop::GetTypeFromScope(scope), name, parent_scope, parent)) + return result; + + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", + interop::GetScopedFinalName(scope).c_str()); + Py_XDECREF(parent); + return nullptr; + } + } + + if (interop::IsTemplate(scope)) { + // a "naked" templated class is requested: return callable proxy for + // instantiations + PyObject* pytcl = PyObject_GetAttr(gThisModule, PyStrings::gTemplate); + PyObject* cppscope = PyLong_FromVoidPtr(scope.data); + PyObject* pytemplate = PyObject_CallFunction( + pytcl, const_cast("sO"), + const_cast(interop::GetScopedFinalName(scope).c_str()), + cppscope); + Py_DECREF(pytcl); + + // cache the result + AddScopeToParent(parent, name, pytemplate); + + // done, next step should be a call into this template + Py_XDECREF(parent); + return pytemplate; + } + + if (interop::IsEnumScope(scope)) + return nullptr; + + // locate class by ID, if possible, to prevent parsing scopes/templates anew + PyObject* pyscope = GetScopeProxy(scope); + if (pyscope) { + if (parent) { + AddScopeToParent(parent, name, pyscope); + Py_DECREF(parent); + } + return pyscope; + } + + // if the scope was earlier found as actual, then we're done already, + // otherwise build a new scope proxy + if (!pyscope) { + // construct the base classes + PyObject* pybases = BuildCppClassBases(scope); + if (pybases != 0) { + // create a fresh Python class, given bases, name, and empty dictionary + pyscope = CreateNewCppProxyClass(scope, pybases); + Py_DECREF(pybases); + } + + // fill the dictionary, if successful + if (pyscope) { + if (BuildScopeProxyDict(scope, pyscope, flags)) { + // something failed in building the dictionary + Py_DECREF(pyscope); + pyscope = nullptr; + } + } + + // store a ref from cppjit scope id to new python class + if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { + gPyClasses[scope] = PyWeakref_NewRef(pyscope, nullptr); + + if (!(((CPPScope*)pyscope)->fFlags & CPPScope::kIsNamespace)) { + // add python-style features to classes only + if (!Pythonize(pyscope, scope)) { + Py_DECREF(pyscope); + pyscope = nullptr; + } + } else { + // add to sys.modules to allow importing from this namespace + PyObject* pyfullname = PyObject_GetAttr(pyscope, PyStrings::gModule); + cpyrt_PyText_AppendAndDel(&pyfullname, cpyrt_PyText_FromString(".")); + cpyrt_PyText_AppendAndDel(&pyfullname, + PyObject_GetAttr(pyscope, PyStrings::gName)); + PyObject* modules = PySys_GetObject(const_cast("modules")); + if (modules && PyDict_Check(modules)) + PyDict_SetItem(modules, pyfullname, pyscope); + Py_DECREF(pyfullname); + } + } + } + + // store on parent if found/created and complete + if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { + // FIXME: This is to mimic original behaviour. Still required? + if (interop::IsTemplateInstantiation(scope)) + name = interop::GetScopedFinalName(scope); + if (is_typedef && !typedefed_name.empty()) + AddScopeToParent(parent, typedefed_name, pyscope); + AddScopeToParent(parent, name, pyscope); + } + Py_DECREF(parent); + + // all done + return pyscope; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, + PyObject* parent) { + // To allow use of C++ exceptions in lieue of Python exceptions, they need to + // derive from BaseException, which can not mix with the normal CPPInstance + // and use of the meta-class. Instead, encapsulate them in a forwarding class + // that derives from Pythons Exception class + + // start with creation of CPPExcInstance type base classes + std::deque uqb; + CollectUniqueBases(((CPPScope*)pyscope)->fCppType, uqb); + size_t nbases = uqb.size(); + + // Support for multiple bases actually can not actually work as-is: the reason + // for deriving from BaseException is to guarantee the layout needed for + // storing traces. If some other base is std::exception (as e.g. + // boost::bad_any_cast) or also derives from std::exception, then there are + // two trace locations. OTOH, if the other class is a non-exception type, then + // the exception class does not need to derive from it because it can never be + // caught as that type forwarding to the proxy will work as expected, through, + // which is good enough). + // + // The code below restricts the hierarchy to a single base class, picking the + // "best" by filtering std::exception and non-exception bases. + + PyObject* pybases = PyTuple_New(1); + if (nbases == 0) { + Py_INCREF((PyObject*)(void*)&CPPExcInstance_Type); + PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPExcInstance_Type); + } else { + PyObject* best_base = nullptr; + + for (std::deque::size_type ibase = 0; ibase < nbases; + ++ibase) { + // retrieve bases through their enclosing scope to guarantee treatment as + // exception classes and proper caching + interop::TCppScope_t parent_scope = interop::GetParentScope(uqb[ibase]); + PyObject* base_parent = CreateScopeProxy(parent_scope); + if (!base_parent) { + Py_DECREF(pybases); + return nullptr; + } + + PyObject* excbase = PyObject_GetAttrString( + base_parent, interop::GetFinalName(uqb[ibase]).c_str()); + Py_DECREF(base_parent); + if (!excbase) { + Py_DECREF(pybases); + return nullptr; + } + + if (PyType_IsSubtype((PyTypeObject*)excbase, &CPPExcInstance_Type)) { + Py_XDECREF(best_base); + best_base = excbase; + + if (interop::GetScopedFinalName(uqb[ibase]) != "std::exception") + break; + } else { + // just skip: there will be at least one exception derived base class + Py_DECREF(excbase); + } + } + + PyTuple_SET_ITEM(pybases, 0, best_base); + } + + PyObject* args = Py_BuildValue((char*)"OO{}", pyname, pybases); + + // meta-class attributes (__cpp_name__, etc.) can not be resolved lazily so + // add them directly instead in case they are needed + PyObject* dct = PyTuple_GET_ITEM(args, 2); + PyDict_SetItem(dct, PyStrings::gUnderlying, pyscope); + PyDict_SetItem(dct, PyStrings::gName, + PyObject_GetAttr(pyscope, PyStrings::gName)); + PyDict_SetItem(dct, PyStrings::gCppName, + PyObject_GetAttr(pyscope, PyStrings::gCppName)); + PyDict_SetItem(dct, PyStrings::gModule, + PyObject_GetAttr(pyscope, PyStrings::gModule)); + + // create the actual exception class + PyObject* exc_pyscope = PyType_Type.tp_new(&PyType_Type, args, nullptr); + Py_DECREF(args); + Py_DECREF(pybases); + + // cache the result for future lookups and return + PyType_Type.tp_setattro(parent, pyname, exc_pyscope); + return exc_pyscope; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::BindCppObjectNoCast(interop::TCppObject_t address, + interop::TCppScope_t klass, + const unsigned flags) { + // only known or knowable objects will be bound (null object is ok) + if (!klass) { + PyErr_SetString(PyExc_TypeError, "attempt to bind C++ object w/o class"); + return nullptr; + } + + // retrieve python class + PyObject* pyclass = CreateScopeProxy(klass); + if (!pyclass) + return nullptr; // error has been set in CreateScopeProxy + + bool noReg = flags & (CPPInstance::kNoMemReg | CPPInstance::kNoWrapConv); + bool isRef = flags & CPPInstance::kIsReference; + void* r_address = + isRef ? (address ? *(void**)address.data : nullptr) : address.data; + + // check whether the object to be bound is a smart pointer that needs + // embedding + PyObject* smart_type = (!(flags & CPPInstance::kNoWrapConv) && + (((CPPClass*)pyclass)->fFlags & CPPScope::kIsSmart)) + ? pyclass + : nullptr; + if (smart_type) { + interop::TCppScope_t underlying = + ((CPPSmartClass*)smart_type)->fUnderlyingType; + + // Downcast the underlying object to its actual (most derived) class, as + // BindCppObject does for raw pointers, but only when the cast is safe: + // * the actual class must really derive from the declared one; + // GetActualClass() can report a base (e.g. when the actual class has no + // dictionary of its own), and such an up-cast must be rejected. + // * the cast must need no pointer adjustment: the dereferencer always + // returns a pointer to the declared class, so a non-zero offset could + // not be applied consistently on later member access (multiple + // inheritance). + if (address && !isRef) { + if (void* deref = + interop::CallR(((CPPSmartClass*)smart_type)->fDereferencer, + address, 0, nullptr)) { + interop::TCppScope_t clActual = + interop::GetActualClass(underlying, deref); + if (clActual && clActual != underlying && + interop::IsSubclass(clActual, underlying) && + interop::GetBaseOffset(clActual, underlying, deref, + -1 /* down-cast */) == 0) + underlying = clActual; + } + } + + pyclass = CreateScopeProxy(underlying); + if (!pyclass) { + // simply restore and expose as the actual smart pointer class + pyclass = smart_type; + smart_type = nullptr; + } + } + + // TODO: make sure that a consistent address is used (may have to be done in + // BindCppObject) + if (address && !(flags & CPPInstance::kIsValue) /* always fresh */ && + !noReg) { + PyObject* oldPyObject = + MemoryRegulator::RetrievePyObject(r_address, pyclass); + + // embedded smart pointers are registered with the class of the underlying + // type, b/c there is no Python proxy for the smart pointer itself in that + // case; check whether the result found matches the smart-ness and if so, + // whether the smart pointer found is the same as the one requested (note: + // ptr-ptr requires old object to be a reference to enable re-use) + if (oldPyObject) { + CPPInstance* o_pyobj = ((CPPInstance*)oldPyObject); + + if ((bool)smart_type == o_pyobj->IsSmart() && + r_address == + (smart_type ? o_pyobj->GetObjectRaw() : o_pyobj->GetObject()) && + (!(flags & CPPInstance::kIsPtrPtr) || + (o_pyobj->fFlags & CPPInstance::kIsReference))) { + Py_DECREF(pyclass); + return oldPyObject; + } else { // reference or naked v.s. smart pointer mismatch + Py_DECREF(oldPyObject); + } + } + } + + // instantiate an object of this class + PyObject* args = PyTuple_New(0); + CPPInstance* pyobj = (CPPInstance*)((PyTypeObject*)pyclass) + ->tp_new((PyTypeObject*)pyclass, args, nullptr); + Py_DECREF(args); + + // bind, register and return if successful + if (pyobj != 0) { // fill proxy value? + unsigned objflags = + flags & (CPPInstance::kIsReference | CPPInstance::kIsPtrPtr | + CPPInstance::kIsValue | CPPInstance::kIsOwner | + CPPInstance::kIsActual); + pyobj->Set(address.data, (CPPInstance::EFlags)objflags); + + if (smart_type) + pyobj->SetSmart(smart_type); + + // do not register null pointers, references (regulated objects can be + // referenced, but referenced objects can not be regulated), or direct usage + // of smart pointers or iterators + if (address && !isRef && !noReg) + MemoryRegulator::RegisterPyObject( + pyobj, smart_type ? pyobj->GetObjectRaw() : pyobj->GetObject()); + } + + // successful completion; wrap exception options to make them raiseable, + // normal return otherwise + if (((CPPClass*)pyclass)->fFlags & CPPScope::kIsException) { + PyObject* exc_obj = + CPPExcInstance_Type.tp_new(&CPPExcInstance_Type, nullptr, nullptr); + ((CPPExcInstance*)exc_obj)->fCppInstance = (PyObject*)pyobj; + Py_DECREF(pyclass); + return exc_obj; + } + + Py_DECREF(pyclass); + return (PyObject*)pyobj; +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::BindCppObject(interop::TCppObject_t address, + interop::TCppScope_t klass, + const unsigned flags) { + // if the object is a null pointer, return a typed one (as needed for + // overloading) + if (!address) + return BindCppObjectNoCast(address, klass, flags); + + // only known or knowable objects will be bound + if (!klass) { + PyErr_SetString(PyExc_TypeError, "attempt to bind C++ object w/o class"); + return nullptr; + } + + bool isRef = flags & CPPInstance::kIsReference; + + // downcast to real class for object returns, unless pinned + // TODO: should the memory regulator for klass be searched first, so that if + // successful, no down-casting is attempted? + // TODO: optimize for final classes + unsigned new_flags = flags; + if (!isRef && (gPinnedTypes.empty() || + gPinnedTypes.find(klass) == gPinnedTypes.end())) { + interop::TCppScope_t clActual = interop::GetActualClass(klass, address); + + if (clActual) { + if (clActual != klass) { + intptr_t offset = + interop::GetBaseOffset(clActual, klass, address, -1 /* down-cast */, + true /* report errors */); + if (offset != -1) { // may fail if clActual not fully defined + address = (void*)((intptr_t)address.data + offset); + klass = clActual; + } + } + new_flags |= CPPInstance::kIsActual; + } + } + + // actual binding (returned object may be zero w/ a python exception set) + return BindCppObjectNoCast(address, klass, new_flags); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::BindCppObjectArray(interop::TCppObject_t address, + interop::TCppScope_t klass, cdims_t dims) { + // TODO: this function exists for symmetry; need to figure out if it's useful + return TupleOfInstances_New(address, klass, dims); +} diff --git a/src/cpyrt/ProxyWrappers.h b/src/cpyrt/ProxyWrappers.h new file mode 100644 index 0000000..c258c03 --- /dev/null +++ b/src/cpyrt/ProxyWrappers.h @@ -0,0 +1,38 @@ +#ifndef CPYRT_PROXYWRAPPERS_H +#define CPYRT_PROXYWRAPPERS_H + +// Bindings +#include "Dimensions.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +// construct a Python shadow class for the named C++ class +PyObject* GetScopeProxy(cppjit::interop::TCppScope_t); +PyObject* CreateScopeProxy(PyObject*, PyObject* args); +PyObject* CreateScopeProxy(const std::string& scope_name, + PyObject* parent = nullptr, + const unsigned flags = 0); + +PyObject* CreateScopeProxy(cppjit::interop::TCppScope_t scope, + PyObject* parent = nullptr, + const unsigned flags = 0); +// C++ exceptions form a special case b/c they have to derive from BaseException +PyObject* CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, + PyObject* parent); + +// bind a C++ object into a Python proxy object (flags are CPPInstance::Default) +PyObject* BindCppObjectNoCast(cppjit::interop::TCppObject_t object, + cppjit::interop::TCppScope_t klass, + const unsigned flags = 0); +PyObject* BindCppObject(cppjit::interop::TCppObject_t object, + cppjit::interop::TCppScope_t klass, + const unsigned flags = 0); +PyObject* BindCppObjectArray(cppjit::interop::TCppObject_t address, + cppjit::interop::TCppScope_t klass, cdims_t dims); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PROXYWRAPPERS_H diff --git a/src/cpyrt/PyCallable.h b/src/cpyrt/PyCallable.h new file mode 100644 index 0000000..cd38d98 --- /dev/null +++ b/src/cpyrt/PyCallable.h @@ -0,0 +1,55 @@ +#ifndef CPYRT_PYCALLABLE_H +#define CPYRT_PYCALLABLE_H + +#include + +// Bindings +#include "CallContext.h" +#include "cpyrt/Reflex.h" + +namespace cppjit::cpyrt { + +class CPPInstance; + +class PyCallable { +public: + virtual ~PyCallable() {} + +public: + virtual PyObject* GetSignature(bool show_formalargs = true) = 0; + virtual PyObject* GetSignatureNames() = 0; + virtual PyObject* GetSignatureTypes() = 0; + virtual PyObject* GetPrototype(bool show_formalargs = true) = 0; + virtual PyObject* GetTypeName() { return GetPrototype(false); } + virtual PyObject* GetDocString() { return GetPrototype(); } + virtual PyObject* Reflex(cppjit::interop::Reflex::RequestId_t request, + cppjit::interop::Reflex::FormatId_t format = + cppjit::interop::Reflex::OPTIMAL) { + PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", + request, format); + return nullptr; + }; + + virtual int GetPriority() = 0; + virtual bool IsGreedy() = 0; + + virtual int GetMaxArgs() = 0; + virtual PyObject* GetCoVarNames() = 0; + virtual PyObject* GetArgDefault(int /* iarg */, bool silent = true) = 0; + virtual bool IsConst() { return false; } + + virtual PyObject* GetScopeProxy() = 0; + virtual cppjit::interop::TCppFuncAddr_t GetFunctionAddress() = 0; + + virtual PyCallable* Clone() = 0; + + virtual int GetArgMatchScore(PyObject* /* args_tuple */) { return INT_MAX; } + +public: + virtual PyObject* Call(CPPInstance*& self, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, CallContext* ctxt = nullptr) = 0; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PYCALLABLE_H diff --git a/src/cpyrt/PyException.cxx b/src/cpyrt/PyException.cxx new file mode 100644 index 0000000..5840faf --- /dev/null +++ b/src/cpyrt/PyException.cxx @@ -0,0 +1,137 @@ +// Standard +#include + +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#define CPYRT_INTERNAL 1 +#include "cpyrt/DispatchPtr.h" +#include "cpyrt/PyException.h" +#undef CPYRT_INTERNAL + +//______________________________________________________________________________ +// C++ exception for throwing python exceptions +// ============================================ +// Purpose: A C++ exception class for throwing python exceptions +// through C++ code. +// Created: Apr, 2004, Scott Snyder, from the version in D0's python_util. +// +// Note: Don't be tempted to declare the virtual functions defined here +// as inline. +// If you do, you may not be able to properly throw these +// exceptions across shared libraries. + +//- constructors/destructor -------------------------------------------------- +cppjit::cpyrt::PyException::PyException() { +#ifdef WITH_THREAD + PythonGILRAII python_gil_raii; +#endif + +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* pyvalue = PyErr_GetRaisedException(); + PyObject* pytype = pyvalue ? (PyObject*)Py_TYPE(pyvalue) : nullptr; + PyObject* traceback = pyvalue ? PyException_GetTraceback(pyvalue) : nullptr; +#else + PyObject *pytype = nullptr, *pyvalue = nullptr, *pytrace = nullptr; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + PyObject* traceback = pytrace; // to keep the original unchanged + Py_XINCREF(traceback); +#endif + + if (pytype && pyvalue) { + const char* tname = PyExceptionClass_Name(pytype); + if (tname) { + char* dot = strrchr((char*)tname, '.'); + if (dot) + tname = dot + 1; + fMsg += tname; + fMsg += ": "; + } + + PyObject* msg = PyObject_Str(pyvalue); + if (msg) { + fMsg += cpyrt_PyText_AsString(msg); + Py_DECREF(msg); + } + } + + std::string locName; + std::string locFile; + int locLine = 0; + + while (traceback && traceback != Py_None) { + PyObject* frame = PyObject_GetAttrString(traceback, "tb_frame"); + PyObject* code = PyObject_GetAttrString(frame, "f_code"); + Py_DECREF(frame); + + PyObject* filename = PyObject_GetAttrString(code, "co_filename"); + Py_DECREF(code); + + PyObject* filenameStr = PyObject_Str(filename); + locFile = cpyrt_PyText_AsString(filenameStr); + Py_DECREF(filenameStr); + Py_DECREF(filename); + + PyObject* name = PyObject_GetAttrString(code, "co_name"); + PyObject* nameStr = PyObject_Str(name); + locName = cpyrt_PyText_AsString(nameStr); + Py_DECREF(nameStr); + Py_DECREF(name); + + PyObject* lineno = PyObject_GetAttrString(traceback, "tb_lineno"); + locLine = PyLong_AsLong(lineno); + Py_DECREF(lineno); + + if (locFile == "") { // these are not that useful, skipping + PyObject* nextTraceback = PyObject_GetAttrString(traceback, "tb_next"); + Py_DECREF(traceback); + traceback = nextTraceback; + continue; + } + + break; + } + + Py_XDECREF(traceback); + +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(pyvalue); +#else + PyErr_Restore(pytype, pyvalue, pytrace); +#endif + + if (fMsg.empty()) + fMsg = "python exception"; + + if (!locFile.empty()) { + + // only keeping the filename, not the full path + locFile = locFile.substr(locFile.find_last_of("/\\") + 1); + + fMsg += " (at " + locFile + ":" + std::to_string(locLine); + + if (locName != "") + fMsg += " in " + locName; + + fMsg += ")"; + } +} + +cppjit::cpyrt::PyException::~PyException() noexcept { + // destructor +} + +//- public members ----------------------------------------------------------- +const char* cppjit::cpyrt::PyException::what() const noexcept { + // Return reason for throwing this exception: a python exception was raised. + return fMsg.c_str(); +} + +void cppjit::cpyrt::PyException::clear() const noexcept { +#ifdef WITH_THREAD + PythonGILRAII python_gil_raii; +#endif + // clear Python error, to allow full error handling C++ side + PyErr_Clear(); +} diff --git a/src/CPyCppyy/include/CPyCppyy/PyException.h b/src/cpyrt/PyException.h similarity index 76% rename from src/CPyCppyy/include/CPyCppyy/PyException.h rename to src/cpyrt/PyException.h index 5c8133d..e18e93a 100644 --- a/src/CPyCppyy/include/CPyCppyy/PyException.h +++ b/src/cpyrt/PyException.h @@ -1,5 +1,5 @@ -#ifndef CPYCPPYY_PYEXCEPTION_H -#define CPYCPPYY_PYEXCEPTION_H +#ifndef CPYRT_PYEXCEPTION_H +#define CPYRT_PYEXCEPTION_H ////////////////////////////////////////////////////////////////////////////// // // @@ -14,7 +14,7 @@ // - The C++ code can call back to python. // // - What to do then if the python callback throws an exception? // // // -// We need to get the control flow back to where CPyCppyy calls C++. // +// We need to get the control flow back to where cpyrt calls C++. // // To do that we throw a TPyException. // // We can then catch this exception when we do the C++ call. // // // @@ -31,26 +31,25 @@ #include // Bindings -#include "CPyCppyy/CommonDefs.h" +#include "cpyrt/CommonDefs.h" +namespace cppjit::cpyrt { -namespace CPyCppyy { - -class CPYCPPYY_CLASS_EXTERN PyException : public std::exception { +class CPYRT_CLASS_EXTERN PyException : public std::exception { public: - PyException(); - ~PyException() noexcept override; + PyException(); + ~PyException() noexcept override; -// give reason for raised exception - const char* what() const noexcept override; + // give reason for raised exception + const char* what() const noexcept override; -// clear Python error, to allow full error handling C++ side - void clear() const noexcept; + // clear Python error, to allow full error handling C++ side + void clear() const noexcept; private: - std::string fMsg; + std::string fMsg; }; -} // namespace CPyCppyy +} // namespace cppjit::cpyrt -#endif // !CPYCPPYY_PYEXCEPTION_H +#endif // !CPYRT_PYEXCEPTION_H diff --git a/src/CPyCppyy/src/PyObjectDir27.inc b/src/cpyrt/PyObjectDir27.inc similarity index 100% rename from src/CPyCppyy/src/PyObjectDir27.inc rename to src/cpyrt/PyObjectDir27.inc diff --git a/src/cpyrt/PyStrings.cxx b/src/cpyrt/PyStrings.cxx new file mode 100644 index 0000000..f255244 --- /dev/null +++ b/src/cpyrt/PyStrings.cxx @@ -0,0 +1,298 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "PyStrings.h" + +//- data _____________________________________________________________________ +PyObject* cpyrt::PyStrings::gAssign = nullptr; +PyObject* cpyrt::PyStrings::gBases = nullptr; +PyObject* cpyrt::PyStrings::gBase = nullptr; +PyObject* cpyrt::PyStrings::gContains = nullptr; +PyObject* cpyrt::PyStrings::gCopy = nullptr; +PyObject* cpyrt::PyStrings::gCppBool = nullptr; +PyObject* cpyrt::PyStrings::gCppName = nullptr; +PyObject* cpyrt::PyStrings::gAnnotations = nullptr; +PyObject* cpyrt::PyStrings::gCastCpp = nullptr; +PyObject* cpyrt::PyStrings::gCType = nullptr; +PyObject* cpyrt::PyStrings::gDeref = nullptr; +PyObject* cpyrt::PyStrings::gPreInc = nullptr; +PyObject* cpyrt::PyStrings::gPostInc = nullptr; +PyObject* cpyrt::PyStrings::gDict = nullptr; +PyObject* cpyrt::PyStrings::gEmptyString = nullptr; +PyObject* cpyrt::PyStrings::gEq = nullptr; +PyObject* cpyrt::PyStrings::gFollow = nullptr; +PyObject* cpyrt::PyStrings::gGetItem = nullptr; +PyObject* cpyrt::PyStrings::gGetNoCheck = nullptr; +PyObject* cpyrt::PyStrings::gSetItem = nullptr; +PyObject* cpyrt::PyStrings::gInit = nullptr; +PyObject* cpyrt::PyStrings::gIter = nullptr; +PyObject* cpyrt::PyStrings::gLen = nullptr; +PyObject* cpyrt::PyStrings::gLifeLine = nullptr; +PyObject* cpyrt::PyStrings::gModule = nullptr; +PyObject* cpyrt::PyStrings::gMRO = nullptr; +PyObject* cpyrt::PyStrings::gName = nullptr; +PyObject* cpyrt::PyStrings::gNe = nullptr; +PyObject* cpyrt::PyStrings::gRepr = nullptr; +PyObject* cpyrt::PyStrings::gCppRepr = nullptr; +PyObject* cpyrt::PyStrings::gStr = nullptr; +PyObject* cpyrt::PyStrings::gCppStr = nullptr; +PyObject* cpyrt::PyStrings::gTypeCode = nullptr; +PyObject* cpyrt::PyStrings::gCTypesType = nullptr; + +PyObject* cpyrt::PyStrings::gUnderlying = nullptr; +PyObject* cpyrt::PyStrings::gRealInit = nullptr; + +PyObject* cpyrt::PyStrings::gAdd = nullptr; +PyObject* cpyrt::PyStrings::gSub = nullptr; +PyObject* cpyrt::PyStrings::gMul = nullptr; +PyObject* cpyrt::PyStrings::gDiv = nullptr; + +PyObject* cpyrt::PyStrings::gLShift = nullptr; +PyObject* cpyrt::PyStrings::gLShiftC = nullptr; + +PyObject* cpyrt::PyStrings::gAt = nullptr; +PyObject* cpyrt::PyStrings::gBegin = nullptr; +PyObject* cpyrt::PyStrings::gEnd = nullptr; +PyObject* cpyrt::PyStrings::gFirst = nullptr; +PyObject* cpyrt::PyStrings::gSecond = nullptr; +PyObject* cpyrt::PyStrings::gSize = nullptr; +PyObject* cpyrt::PyStrings::gTemplate = nullptr; +PyObject* cpyrt::PyStrings::gVectorAt = nullptr; +PyObject* cpyrt::PyStrings::gInsert = nullptr; +PyObject* cpyrt::PyStrings::gValueType = nullptr; +PyObject* cpyrt::PyStrings::gValueTypePtr = nullptr; +PyObject* cpyrt::PyStrings::gValueSize = nullptr; + +PyObject* cpyrt::PyStrings::gCppReal = nullptr; +PyObject* cpyrt::PyStrings::gCppImag = nullptr; + +PyObject* cpyrt::PyStrings::gThisModule = nullptr; + +PyObject* cpyrt::PyStrings::gDispInit = nullptr; +PyObject* cpyrt::PyStrings::gDispGet = nullptr; + +PyObject* cpyrt::PyStrings::gExPythonize = nullptr; +PyObject* cpyrt::PyStrings::gPythonize = nullptr; + +PyObject* cpyrt::PyStrings::gArray = nullptr; +PyObject* cpyrt::PyStrings::gDType = nullptr; +PyObject* cpyrt::PyStrings::gFromBuffer = nullptr; + +//----------------------------------------------------------------------------- +#define CPPJIT_INITIALIZE_STRING(var, str) \ + if (!(PyStrings::var = cpyrt_PyText_InternFromString((char*)#str))) \ + return false + +bool cpyrt::CreatePyStrings() { + // Build cache of commonly used python strings (the cache is python intern, so + // all strings are shared python-wide, not just in cppjit). + CPPJIT_INITIALIZE_STRING(gAssign, __assign__); + CPPJIT_INITIALIZE_STRING(gBases, __bases__); + CPPJIT_INITIALIZE_STRING(gBase, __base__); + CPPJIT_INITIALIZE_STRING(gContains, contains); + CPPJIT_INITIALIZE_STRING(gCopy, copy); + CPPJIT_INITIALIZE_STRING(gCppBool, __cpp_bool__); + CPPJIT_INITIALIZE_STRING(gCppName, __cpp_name__); + CPPJIT_INITIALIZE_STRING(gAnnotations, __annotations__); + CPPJIT_INITIALIZE_STRING(gCastCpp, __cast_cpp__); + CPPJIT_INITIALIZE_STRING(gCType, __ctype__); + CPPJIT_INITIALIZE_STRING(gDeref, __deref__); + CPPJIT_INITIALIZE_STRING(gPreInc, __preinc__); + CPPJIT_INITIALIZE_STRING(gPostInc, __postinc__); + CPPJIT_INITIALIZE_STRING(gDict, __dict__); + if (!(PyStrings::gEmptyString = cpyrt_PyText_FromString((char*)""))) + return false; + CPPJIT_INITIALIZE_STRING(gEq, __eq__); + CPPJIT_INITIALIZE_STRING(gFollow, __follow__); + CPPJIT_INITIALIZE_STRING(gGetItem, __getitem__); + CPPJIT_INITIALIZE_STRING(gGetNoCheck, _getitem__unchecked); + CPPJIT_INITIALIZE_STRING(gSetItem, __setitem__); + CPPJIT_INITIALIZE_STRING(gInit, __init__); + CPPJIT_INITIALIZE_STRING(gIter, __iter__); + CPPJIT_INITIALIZE_STRING(gLen, __len__); + CPPJIT_INITIALIZE_STRING(gLifeLine, __lifeline); + CPPJIT_INITIALIZE_STRING(gModule, __module__); + CPPJIT_INITIALIZE_STRING(gMRO, __mro__); + CPPJIT_INITIALIZE_STRING(gName, __name__); + CPPJIT_INITIALIZE_STRING(gNe, __ne__); + CPPJIT_INITIALIZE_STRING(gRepr, __repr__); + CPPJIT_INITIALIZE_STRING(gCppRepr, __cpp_repr); + CPPJIT_INITIALIZE_STRING(gStr, __str__); + CPPJIT_INITIALIZE_STRING(gCppStr, __cpp_str); + CPPJIT_INITIALIZE_STRING(gTypeCode, typecode); + CPPJIT_INITIALIZE_STRING(gCTypesType, _type_); + + CPPJIT_INITIALIZE_STRING(gUnderlying, __underlying); + CPPJIT_INITIALIZE_STRING(gRealInit, __real_init); + + CPPJIT_INITIALIZE_STRING(gAdd, __add__); + CPPJIT_INITIALIZE_STRING(gSub, __sub__); + CPPJIT_INITIALIZE_STRING(gMul, __mul__); + CPPJIT_INITIALIZE_STRING(gDiv, CPPJIT__div__); + + CPPJIT_INITIALIZE_STRING(gLShift, __lshift__); + CPPJIT_INITIALIZE_STRING(gLShiftC, __lshiftc__); + + CPPJIT_INITIALIZE_STRING(gAt, at); + CPPJIT_INITIALIZE_STRING(gBegin, begin); + CPPJIT_INITIALIZE_STRING(gEnd, end); + CPPJIT_INITIALIZE_STRING(gFirst, first); + CPPJIT_INITIALIZE_STRING(gSecond, second); + CPPJIT_INITIALIZE_STRING(gSize, size); + CPPJIT_INITIALIZE_STRING(gTemplate, Template); + CPPJIT_INITIALIZE_STRING(gVectorAt, _vector__at); + CPPJIT_INITIALIZE_STRING(gInsert, insert); + CPPJIT_INITIALIZE_STRING(gValueType, value_type); + CPPJIT_INITIALIZE_STRING(gValueTypePtr, _value_type); + CPPJIT_INITIALIZE_STRING(gValueSize, value_size); + + CPPJIT_INITIALIZE_STRING(gCppReal, __cpp_real); + CPPJIT_INITIALIZE_STRING(gCppImag, __cpp_imag); + + CPPJIT_INITIALIZE_STRING(gThisModule, cppjit); + + CPPJIT_INITIALIZE_STRING(gDispInit, _init_dispatchptr); + CPPJIT_INITIALIZE_STRING(gDispGet, _get_dispatch); + + CPPJIT_INITIALIZE_STRING(gExPythonize, __cppjit_explicit_pythonize__); + CPPJIT_INITIALIZE_STRING(gPythonize, __cppjit_pythonize__); + + CPPJIT_INITIALIZE_STRING(gArray, __array__); + CPPJIT_INITIALIZE_STRING(gDType, dtype); + CPPJIT_INITIALIZE_STRING(gFromBuffer, frombuffer); + + return true; +} + +//----------------------------------------------------------------------------- +PyObject* cpyrt::DestroyPyStrings() { + // Remove all cached python strings. + Py_DECREF(PyStrings::gBases); + PyStrings::gBases = nullptr; + Py_DECREF(PyStrings::gBase); + PyStrings::gBase = nullptr; + Py_DECREF(PyStrings::gContains); + PyStrings::gContains = nullptr; + Py_DECREF(PyStrings::gCopy); + PyStrings::gCopy = nullptr; + Py_DECREF(PyStrings::gCppBool); + PyStrings::gCppBool = nullptr; + Py_DECREF(PyStrings::gCppName); + PyStrings::gCppName = nullptr; + Py_DECREF(PyStrings::gAnnotations); + PyStrings::gAnnotations = nullptr; + Py_DECREF(PyStrings::gCType); + PyStrings::gCType = nullptr; + Py_DECREF(PyStrings::gDeref); + PyStrings::gDeref = nullptr; + Py_DECREF(PyStrings::gPreInc); + PyStrings::gPreInc = nullptr; + Py_DECREF(PyStrings::gPostInc); + PyStrings::gPostInc = nullptr; + Py_DECREF(PyStrings::gDict); + PyStrings::gDict = nullptr; + Py_DECREF(PyStrings::gEmptyString); + PyStrings::gEmptyString = nullptr; + Py_DECREF(PyStrings::gEq); + PyStrings::gEq = nullptr; + Py_DECREF(PyStrings::gFollow); + PyStrings::gFollow = nullptr; + Py_DECREF(PyStrings::gGetItem); + PyStrings::gGetItem = nullptr; + Py_DECREF(PyStrings::gGetNoCheck); + PyStrings::gGetNoCheck = nullptr; + Py_DECREF(PyStrings::gSetItem); + PyStrings::gSetItem = nullptr; + Py_DECREF(PyStrings::gInit); + PyStrings::gInit = nullptr; + Py_DECREF(PyStrings::gIter); + PyStrings::gIter = nullptr; + Py_DECREF(PyStrings::gLen); + PyStrings::gLen = nullptr; + Py_DECREF(PyStrings::gLifeLine); + PyStrings::gLifeLine = nullptr; + Py_DECREF(PyStrings::gModule); + PyStrings::gModule = nullptr; + Py_DECREF(PyStrings::gMRO); + PyStrings::gMRO = nullptr; + Py_DECREF(PyStrings::gName); + PyStrings::gName = nullptr; + Py_DECREF(PyStrings::gNe); + PyStrings::gNe = nullptr; + Py_DECREF(PyStrings::gTypeCode); + PyStrings::gTypeCode = nullptr; + Py_DECREF(PyStrings::gCTypesType); + PyStrings::gCTypesType = nullptr; + + Py_DECREF(PyStrings::gUnderlying); + PyStrings::gUnderlying = nullptr; + Py_DECREF(PyStrings::gRealInit); + PyStrings::gRealInit = nullptr; + + Py_DECREF(PyStrings::gAdd); + PyStrings::gAdd = nullptr; + Py_DECREF(PyStrings::gSub); + PyStrings::gSub = nullptr; + Py_DECREF(PyStrings::gMul); + PyStrings::gMul = nullptr; + Py_DECREF(PyStrings::gDiv); + PyStrings::gDiv = nullptr; + + Py_DECREF(PyStrings::gLShift); + PyStrings::gLShift = nullptr; + Py_DECREF(PyStrings::gLShiftC); + PyStrings::gLShiftC = nullptr; + + Py_DECREF(PyStrings::gAt); + PyStrings::gAt = nullptr; + Py_DECREF(PyStrings::gBegin); + PyStrings::gBegin = nullptr; + Py_DECREF(PyStrings::gEnd); + PyStrings::gEnd = nullptr; + Py_DECREF(PyStrings::gFirst); + PyStrings::gFirst = nullptr; + Py_DECREF(PyStrings::gSecond); + PyStrings::gSecond = nullptr; + Py_DECREF(PyStrings::gSize); + PyStrings::gSize = nullptr; + Py_DECREF(PyStrings::gTemplate); + PyStrings::gTemplate = nullptr; + Py_DECREF(PyStrings::gVectorAt); + PyStrings::gVectorAt = nullptr; + Py_DECREF(PyStrings::gInsert); + PyStrings::gInsert = nullptr; + Py_DECREF(PyStrings::gValueType); + PyStrings::gValueType = nullptr; + Py_DECREF(PyStrings::gValueTypePtr); + PyStrings::gValueTypePtr = nullptr; + Py_DECREF(PyStrings::gValueSize); + PyStrings::gValueSize = nullptr; + + Py_DECREF(PyStrings::gCppReal); + PyStrings::gCppReal = nullptr; + Py_DECREF(PyStrings::gCppImag); + PyStrings::gCppImag = nullptr; + + Py_DECREF(PyStrings::gThisModule); + PyStrings::gThisModule = nullptr; + + Py_DECREF(PyStrings::gDispInit); + PyStrings::gDispInit = nullptr; + Py_DECREF(PyStrings::gDispGet); + PyStrings::gDispGet = nullptr; + + Py_DECREF(PyStrings::gExPythonize); + PyStrings::gExPythonize = nullptr; + Py_DECREF(PyStrings::gPythonize); + PyStrings::gPythonize = nullptr; + + Py_DECREF(PyStrings::gArray); + PyStrings::gArray = nullptr; + Py_DECREF(PyStrings::gDType); + PyStrings::gDType = nullptr; + Py_DECREF(PyStrings::gFromBuffer); + PyStrings::gFromBuffer = nullptr; + + Py_RETURN_NONE; +} diff --git a/src/cpyrt/PyStrings.h b/src/cpyrt/PyStrings.h new file mode 100644 index 0000000..072dddd --- /dev/null +++ b/src/cpyrt/PyStrings.h @@ -0,0 +1,91 @@ +#ifndef CPYRT_PYSTRINGS_H +#define CPYRT_PYSTRINGS_H + +namespace cppjit::cpyrt { + +// python strings kept for performance reasons + +namespace PyStrings { + +extern PyObject* gAssign; +extern PyObject* gBases; +extern PyObject* gBase; +extern PyObject* gContains; +extern PyObject* gCopy; +extern PyObject* gCppBool; +extern PyObject* gCppName; +extern PyObject* gAnnotations; +extern PyObject* gCastCpp; +extern PyObject* gCType; +extern PyObject* gDeref; +extern PyObject* gPreInc; +extern PyObject* gPostInc; +extern PyObject* gDict; +extern PyObject* gEmptyString; +extern PyObject* gEq; +extern PyObject* gFollow; +extern PyObject* gGetItem; +extern PyObject* gGetNoCheck; +extern PyObject* gSetItem; +extern PyObject* gInit; +extern PyObject* gIter; +extern PyObject* gLen; +extern PyObject* gLifeLine; +extern PyObject* gModule; +extern PyObject* gMRO; +extern PyObject* gName; +extern PyObject* gNe; +extern PyObject* gRepr; +extern PyObject* gCppRepr; +extern PyObject* gStr; +extern PyObject* gCppStr; +extern PyObject* gTypeCode; +extern PyObject* gCTypesType; + +extern PyObject* gUnderlying; +extern PyObject* gRealInit; + +extern PyObject* gAdd; +extern PyObject* gSub; +extern PyObject* gMul; +extern PyObject* gDiv; + +extern PyObject* gLShift; +extern PyObject* gLShiftC; + +extern PyObject* gAt; +extern PyObject* gBegin; +extern PyObject* gEnd; +extern PyObject* gFirst; +extern PyObject* gSecond; +extern PyObject* gSize; +extern PyObject* gTemplate; +extern PyObject* gVectorAt; +extern PyObject* gInsert; +extern PyObject* gValueType; +extern PyObject* gValueTypePtr; +extern PyObject* gValueSize; + +extern PyObject* gCppReal; +extern PyObject* gCppImag; + +extern PyObject* gThisModule; + +extern PyObject* gDispInit; +extern PyObject* gDispGet; + +extern PyObject* gExPythonize; +extern PyObject* gPythonize; + +extern PyObject* gArray; +extern PyObject* gDType; +extern PyObject* gFromBuffer; + +} // namespace PyStrings + +bool CreatePyStrings(); +PyObject* DestroyPyStrings(); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PYSTRINGS_H diff --git a/src/cpyrt/Pythonize.cxx b/src/cpyrt/Pythonize.cxx new file mode 100644 index 0000000..044bfcb --- /dev/null +++ b/src/cpyrt/Pythonize.cxx @@ -0,0 +1,2173 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPFunction.h" +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "Converters.h" +#include "CustomPyTypes.h" +#include "LowLevelViews.h" +#include "ProxyWrappers.h" +#include "PyCallable.h" +#include "PyStrings.h" +#include "Pythonize.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include + +//- data and local helpers --------------------------------------------------- +namespace cppjit::cpyrt { +extern PyObject* gThisModule; +std::unordered_map>& pythonizations(); +} // namespace cppjit::cpyrt + +namespace { + +// for convenience +using namespace cppjit::cpyrt; + +//----------------------------------------------------------------------------- +bool HasAttrDirect(PyObject* pyclass, PyObject* pyname, + bool mustBecpyrt = false) { + // prevents calls to Py_TYPE(pyclass)->tp_getattr, which is unnecessary for + // our purposes here and could tickle problems w/ spurious lookups into ROOT + // meta + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + if (dct) { + PyObject* attr = PyObject_GetItem(dct, pyname); + Py_DECREF(dct); + if (attr) { + bool ret = !mustBecpyrt || CPPOverload_Check(attr); + Py_DECREF(attr); + return ret; + } + } + PyErr_Clear(); + return false; +} + +PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { + // get an attribute without causing getattr lookups + PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); + if (dct) { + PyObject* attr = PyObject_GetItem(dct, pyname); + Py_DECREF(dct); + return attr; + } + return nullptr; +} + +//----------------------------------------------------------------------------- +inline bool IsTemplatedSTLClass(const std::string& name, + const std::string& klass) { + // Scan the name of the class and determine whether it is a template + // instantiation. + auto pos = name.find(klass); + return pos == 5 && name.rfind("std::", 0, 5) == 0 && + name.find("::", name.rfind(">")) == std::string::npos; +} + +// to prevent compiler warnings about const char* -> char* +inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth) { + // Helper; call method with signature: obj->meth(). + Py_INCREF(obj); + PyObject* result = + PyObject_CallMethod(obj, const_cast(meth), const_cast("")); + Py_DECREF(obj); + return result; +} + +//----------------------------------------------------------------------------- +inline PyObject* CallPyObjMethod(PyObject* obj, const char* meth, + PyObject* arg1) { + // Helper; call method with signature: obj->meth(arg1). + Py_INCREF(obj); + PyObject* result = PyObject_CallMethod(obj, const_cast(meth), + const_cast("O"), arg1); + Py_DECREF(obj); + return result; +} + +//----------------------------------------------------------------------------- +PyObject* PyStyleIndex(PyObject* self, PyObject* index) { + // Helper; converts python index into straight C index. + Py_ssize_t idx = PyInt_AsSsize_t(index); + if (idx == (Py_ssize_t)-1 && PyErr_Occurred()) + return nullptr; + + Py_ssize_t size = PySequence_Size(self); + if (idx >= size || (idx < 0 && idx < -size)) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return nullptr; + } + + PyObject* pyindex = nullptr; + if (idx >= 0) { + Py_INCREF(index); + pyindex = index; + } else + pyindex = PyLong_FromSsize_t(size + idx); + + return pyindex; +} + +//----------------------------------------------------------------------------- +inline bool AdjustSlice(const Py_ssize_t nlen, Py_ssize_t& start, + Py_ssize_t& stop, Py_ssize_t& step) { + // Helper; modify slice range to match the container. + if ((step > 0 && stop <= start) || (step < 0 && start <= stop)) + return false; + + if (start < 0) + start = 0; + if (start >= nlen) + start = nlen - 1; + if (step >= nlen) + step = nlen; + + stop = step > 0 ? std::min(nlen, stop) : (stop >= 0 ? stop : -1); + return true; +} + +//----------------------------------------------------------------------------- +inline PyObject* CallSelfIndex(CPPInstance* self, PyObject* idx, + PyObject* pymeth) { + // Helper; call method with signature: meth(pyindex). + Py_INCREF((PyObject*)self); + PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); + if (!pyindex) { + Py_DECREF((PyObject*)self); + return nullptr; + } + + PyObject* result = + PyObject_CallMethodOneArg((PyObject*)self, pymeth, pyindex); + Py_DECREF(pyindex); + Py_DECREF((PyObject*)self); + return result; +} + +//- "smart pointer" behavior --------------------------------------------------- +static PyObject* smart_follow(PyObject* self, PyObject* name, + PyObject* method) { + if (!cpyrt_PyText_Check(name)) + PyErr_SetString(PyExc_TypeError, + "getattr(): attribute name must be string"); + + PyObject* pyptr = PyObject_CallMethodNoArgs(self, method); + if (!pyptr) + return nullptr; + + // prevent a potential infinite loop + if (Py_TYPE(pyptr) == Py_TYPE(self)) { + PyObject* val1 = PyObject_Str((PyObject*)Py_TYPE(self)); + PyObject* val2 = PyObject_Str(name); + PyErr_Format(PyExc_AttributeError, "%s object has no attribute \'%s\'", + cpyrt_PyText_AsString(val1), cpyrt_PyText_AsString(val2)); + Py_DECREF(val2); + Py_DECREF(val1); + + Py_DECREF(pyptr); + return nullptr; + } + + PyObject* result = PyObject_GetAttr(pyptr, name); + Py_DECREF(pyptr); + return result; +} + +PyObject* DeRefGetAttr(PyObject* self, PyObject* name) { + // Follow operator*() if present (available in python as __deref__), so that + // smart pointers behave as expected. + if (name == PyStrings::gTypeCode || name == PyStrings::gCTypesType) { + // TODO: these calls come from TemplateProxy and are unlikely to be needed + // in practice, whereas as-is, they can accidentally dereference the result + // of end() on some STL containers. Obviously, this is a dumb hack that + // should be resolved more fundamentally. + PyErr_SetString(PyExc_AttributeError, cpyrt_PyText_AsString(name)); + return nullptr; + } + + return smart_follow(self, name, PyStrings::gDeref); +} + +//----------------------------------------------------------------------------- +PyObject* FollowGetAttr(PyObject* self, PyObject* name) { + // Follow operator->() if present (available in python as __follow__), so that + // smart pointers behave as expected. + return smart_follow(self, name, PyStrings::gFollow); +} + +//- pointer checking bool converter ------------------------------------------- +PyObject* NullCheckBool(PyObject* self) { + if (!CPPInstance_Check(self)) { + PyErr_SetString(PyExc_TypeError, "C++ object proxy expected"); + return nullptr; + } + + if (!((CPPInstance*)self)->GetObject()) + Py_RETURN_FALSE; + + return PyObject_CallMethodNoArgs(self, PyStrings::gCppBool); +} + +//- vector behavior as primitives ---------------------------------------------- + +// TODO: can probably use the below getters in the InitializerListConverter +struct ItemGetter { + ItemGetter(PyObject* pyobj) : fPyObject(pyobj) { Py_INCREF(fPyObject); } + virtual ~ItemGetter() { Py_DECREF(fPyObject); } + virtual Py_ssize_t size() = 0; + virtual PyObject* get() = 0; + PyObject* fPyObject; +}; + +struct CountedItemGetter : public ItemGetter { + CountedItemGetter(PyObject* pyobj) : ItemGetter(pyobj), fCur(0) {} + Py_ssize_t fCur; +}; + +struct TupleItemGetter : public CountedItemGetter { + using CountedItemGetter::CountedItemGetter; + Py_ssize_t size() override { return PyTuple_GET_SIZE(fPyObject); } + PyObject* get() override { + if (fCur < PyTuple_GET_SIZE(fPyObject)) { + PyObject* item = PyTuple_GET_ITEM(fPyObject, fCur++); + Py_INCREF(item); + return item; + } + PyErr_SetString(PyExc_StopIteration, "end of tuple"); + return nullptr; + } +}; + +struct ListItemGetter : public CountedItemGetter { + using CountedItemGetter::CountedItemGetter; + Py_ssize_t size() override { return PyList_GET_SIZE(fPyObject); } + PyObject* get() override { + if (fCur < PyList_GET_SIZE(fPyObject)) { + PyObject* item = PyList_GET_ITEM(fPyObject, fCur++); + Py_INCREF(item); + return item; + } + PyErr_SetString(PyExc_StopIteration, "end of list"); + return nullptr; + } +}; + +struct SequenceItemGetter : public CountedItemGetter { + using CountedItemGetter::CountedItemGetter; + Py_ssize_t size() override { + Py_ssize_t sz = PySequence_Size(fPyObject); + if (sz < 0) { + PyErr_Clear(); + return PyObject_LengthHint(fPyObject, 8); + } + return sz; + } + PyObject* get() override { return PySequence_GetItem(fPyObject, fCur++); } +}; + +struct IterItemGetter : public ItemGetter { + using ItemGetter::ItemGetter; + Py_ssize_t size() override { return PyObject_LengthHint(fPyObject, 8); } + PyObject* get() override { + return (*(Py_TYPE(fPyObject)->tp_iternext))(fPyObject); + } +}; + +static ItemGetter* GetGetter(PyObject* args) { + // Create an ItemGetter to loop over the iterable argument, if any. + ItemGetter* getter = nullptr; + + if (PyTuple_GET_SIZE(args) == 1) { + PyObject* fi = PyTuple_GET_ITEM(args, 0); + if (cpyrt_PyText_Check(fi) || PyBytes_Check(fi)) + return nullptr; // do not accept string to fill std::vector + + // TODO: this only tests for new-style buffers, which is too strict, but a + // generic check for Py_TYPE(fi)->tp_as_buffer is too loose (note that the + // main use case is numpy, which offers the new interface) + if (PyObject_CheckBuffer(fi)) + return nullptr; + + if (PyTuple_CheckExact(fi)) + getter = new TupleItemGetter(fi); + else if (PyList_CheckExact(fi)) + getter = new ListItemGetter(fi); + else if (PySequence_Check(fi)) + getter = new SequenceItemGetter(fi); + else { + PyObject* iter = PyObject_GetIter(fi); + if (iter) { + getter = new IterItemGetter{iter}; + Py_DECREF(iter); + } else + PyErr_Clear(); + } + } + + return getter; +} + +static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter) { + Py_ssize_t sz = getter->size(); + if (sz < 0) + return false; + + // reserve memory as applicable + if (0 < sz) { + PyObject* res = + PyObject_CallMethod(vecin, (char*)"reserve", (char*)"n", sz); + Py_DECREF(res); + } else // i.e. sz == 0, so empty container: done + return true; + + bool fill_ok = true; + + // two main options: a list of lists (or tuples), or a list of objects; the + // former are emplace_back'ed, the latter push_back'ed + PyObject* fi = PySequence_GetItem(PyTuple_GET_ITEM(args, 0), 0); + if (!fi) + PyErr_Clear(); + if (fi && (PyTuple_CheckExact(fi) || PyList_CheckExact(fi))) { + // use emplace_back to construct the vector entries one by one + PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back"); + PyObject* vtype = + GetAttrDirect((PyObject*)Py_TYPE(vecin), PyStrings::gValueTypePtr); + bool value_is_vector = false; + if (vtype && PyLong_Check(vtype)) { + // if the value_type is a vector, then allow for initialization from + // sequences + if (interop::GetTypeAsString(PyLong_AsVoidPtr(vtype)) + .rfind("std::vector", 0) != std::string::npos) + value_is_vector = true; + } else + PyErr_Clear(); + Py_XDECREF(vtype); + + if (eb_call) { + PyObject* eb_args; + for (int i = 0; /* until break */; ++i) { + PyObject* item = getter->get(); + if (item) { + if (value_is_vector && PySequence_Check(item)) { + eb_args = PyTuple_New(1); + PyTuple_SET_ITEM(eb_args, 0, item); + } else if (PyTuple_CheckExact(item)) { + eb_args = item; + } else if (PyList_CheckExact(item)) { + Py_ssize_t isz = PyList_GET_SIZE(item); + eb_args = PyTuple_New(isz); + for (Py_ssize_t j = 0; j < isz; ++j) { + PyObject* iarg = PyList_GET_ITEM(item, j); + Py_INCREF(iarg); + PyTuple_SET_ITEM(eb_args, j, iarg); + } + Py_DECREF(item); + } else { + Py_DECREF(item); + PyErr_Format(PyExc_TypeError, "argument %d is not a tuple or list", + i); + fill_ok = false; + break; + } + PyObject* ebres = PyObject_CallObject(eb_call, eb_args); + Py_DECREF(eb_args); + if (!ebres) { + fill_ok = false; + break; + } + Py_DECREF(ebres); + } else { + if (PyErr_Occurred()) { + if (!(PyErr_ExceptionMatches(PyExc_IndexError) || + PyErr_ExceptionMatches(PyExc_StopIteration))) + fill_ok = false; + else { + PyErr_Clear(); + } + } + break; + } + } + Py_DECREF(eb_call); + } + } else { + // use push_back to add the vector entries one by one + PyObject* pb_call = PyObject_GetAttrString(vecin, (char*)"push_back"); + if (pb_call) { + for (;;) { + PyObject* item = getter->get(); + if (item) { + PyObject* pbres = + PyObject_CallFunctionObjArgs(pb_call, item, nullptr); + Py_DECREF(item); + if (!pbres) { + fill_ok = false; + break; + } + Py_DECREF(pbres); + } else { + if (PyErr_Occurred()) { + if (!(PyErr_ExceptionMatches(PyExc_IndexError) || + PyErr_ExceptionMatches(PyExc_StopIteration))) + fill_ok = false; + else { + PyErr_Clear(); + } + } + break; + } + } + Py_DECREF(pb_call); + } + } + Py_XDECREF(fi); + + return fill_ok; +} + +PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // Implement fast __iadd__ on std::vector (generic __iadd__ is in Python) + ItemGetter* getter = GetGetter(args); + + if (getter) { + bool fill_ok = FillVector(self, args, getter); + delete getter; + + if (!fill_ok) + return nullptr; + + Py_INCREF(self); + return self; + } + + // if no getter, it could still be b/c we have a buffer (e.g. numpy); looping + // over a buffer here is slow, so use insert() instead + if (PyTuple_GET_SIZE(args) == 1) { + PyObject* fi = PyTuple_GET_ITEM(args, 0); + if (PyObject_CheckBuffer(fi) && + !(cpyrt_PyText_Check(fi) || PyBytes_Check(fi))) { + PyObject* vend = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); + if (vend) { + PyObject* result = PyObject_CallMethodObjArgs(self, PyStrings::gInsert, + vend, fi, nullptr); + Py_DECREF(vend); + return result; + } + } + } + + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_TypeError, "argument is not iterable"); + return nullptr; // error already set +} + +PyObject* VectorInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // Specialized vector constructor to allow construction from containers; + // allowing such construction from initializer_list instead would possible, + // but can be error-prone. This use case is common enough for std::vector to + // implement it directly, except for arrays (which can be passed wholesale) + // and strings (which won't convert properly as they'll be seen as buffers) + + ItemGetter* getter = GetGetter(args); + + if (getter) { + // construct an empty vector, then back-fill it + PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); + if (!result) { + delete getter; + return nullptr; + } + + bool fill_ok = FillVector(self, args, getter); + delete getter; + + if (!fill_ok) { + Py_DECREF(result); + return nullptr; + } + + return result; + } + + // The given argument wasn't iterable: simply forward to regular constructor + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + return result; + } + + return nullptr; +} + +//--------------------------------------------------------------------------- +PyObject* VectorData(PyObject* self, PyObject*) { + PyObject* pydata = CallPyObjMethod(self, "__real_data"); + if (!LowLevelView_Check(pydata) && !CPPInstance_Check(pydata)) + return pydata; + + PyObject* pylen = PyObject_CallMethodNoArgs(self, PyStrings::gSize); + if (!pylen) { + PyErr_Clear(); + return pydata; + } + + long clen = PyInt_AsLong(pylen); + Py_DECREF(pylen); + + if (CPPInstance_Check(pydata)) { + ((CPPInstance*)pydata)->CastToArray(clen); + return pydata; + } + + ((LowLevelView*)pydata)->resize((size_t)clen); + return pydata; +} + +//--------------------------------------------------------------------------- +PyObject* VectorArray(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* pydata = VectorData(self, nullptr); + PyObject* arrcall = PyObject_GetAttr(pydata, PyStrings::gArray); + PyObject* newarr = PyObject_Call(arrcall, args, kwargs); + Py_DECREF(arrcall); + Py_DECREF(pydata); + return newarr; +} + +//----------------------------------------------------------------------------- +static PyObject* vector_iter(PyObject* v) { + vectoriterobject* vi = PyObject_GC_New(vectoriterobject, &VectorIter_Type); + if (!vi) + return nullptr; + + Py_INCREF(v); + vi->ii_container = v; + + // tell the iterator code to set a life line if this container is a temporary + vi->vi_flags = vectoriterobject::kDefault; +#if PY_VERSION_HEX >= 0x030e0000 + if (PyUnstable_Object_IsUniqueReferencedTemporary(v) || + (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) +#else + if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) +#endif + vi->vi_flags = vectoriterobject::kNeedLifeLine; + + Py_INCREF(v); + + PyObject* pyvalue_type = + PyObject_GetAttr((PyObject*)Py_TYPE(v), PyStrings::gValueTypePtr); + if (pyvalue_type) { + PyObject* pyvalue_size = + GetAttrDirect((PyObject*)Py_TYPE(v), PyStrings::gValueSize); + if (pyvalue_size) { + vi->vi_stride = PyLong_AsLong(pyvalue_size); + Py_DECREF(pyvalue_size); + } else { + PyErr_Clear(); + vi->vi_stride = 0; + } + + if (PyLong_Check(pyvalue_type)) { + interop::TCppType_t value_type = PyLong_AsVoidPtr(pyvalue_type); + value_type = interop::ResolveType(value_type); + vi->vi_klass = interop::GetScopeFromType(value_type); + if (!vi->vi_klass) { + // look for a special case of pointer to a class type (which is a + // builtin, but it is more useful to treat it polymorphically by + // allowing auto-downcasts) + const std::string& clean_type = TypeManip::clean_type( + interop::GetTypeAsString(value_type), false, false); + interop::TCppScope_t c = interop::GetScope(clean_type); + if (c && + TypeManip::compound(interop::GetTypeAsString(value_type)) == "*") { + vi->vi_klass = c; + vi->vi_flags = vectoriterobject::kIsPolymorphic; + } + } + if (interop::IsPointerType(value_type)) + vi->vi_flags = vectoriterobject::kIsPolymorphic; + if (vi->vi_klass) { + vi->vi_converter = nullptr; + if (!vi->vi_flags) { + value_type = interop::ResolveType(value_type); + if (interop::GetTypeAsString(value_type).back() != + '*') // meaning, object stored by-value + vi->vi_flags = vectoriterobject::kNeedLifeLine; + } + } else + vi->vi_converter = cppjit::cpyrt::CreateConverter(value_type); + if (!vi->vi_stride) + vi->vi_stride = interop::SizeOfType(value_type); + + } else if (CPPScope_Check(pyvalue_type)) { + vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType; + vi->vi_converter = nullptr; + if (!vi->vi_stride) + vi->vi_stride = interop::SizeOf(vi->vi_klass); + if (!vi->vi_flags) + vi->vi_flags = vectoriterobject::kNeedLifeLine; + } + + PyObject* pydata = CallPyObjMethod(v, "__real_data"); + if (!pydata || Utility::GetBuffer(pydata, '*', 1, vi->vi_data, false) == 0) + vi->vi_data = CPPInstance_Check(pydata) + ? ((CPPInstance*)pydata)->GetObjectRaw() + : nullptr; + Py_XDECREF(pydata); + + } else { + PyErr_Clear(); + vi->vi_data = nullptr; + vi->vi_stride = 0; + vi->vi_converter = nullptr; + vi->vi_klass = nullptr; + vi->vi_flags = 0; + } + + Py_XDECREF(pyvalue_type); + + vi->ii_pos = 0; + vi->ii_len = PySequence_Size(v); + + PyObject_GC_Track(vi); + return (PyObject*)vi; +} + +PyObject* VectorGetItem(CPPInstance* self, PySliceObject* index) { + // Implement python's __getitem__ for std::vector<>s. + if (PySlice_Check(index)) { + if (!self->GetObject()) { + PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); + return nullptr; + } + + PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)self); + PyObject* nseq = PyObject_CallObject(pyclass, nullptr); + + Py_ssize_t start, stop, step; + PySlice_GetIndices((cpyrt_PySliceCast)index, + PyObject_Length((PyObject*)self), &start, &stop, &step); + + const Py_ssize_t nlen = PySequence_Size((PyObject*)self); + if (!AdjustSlice(nlen, start, stop, step)) + return nseq; + + const Py_ssize_t sign = step < 0 ? -1 : 1; + for (Py_ssize_t i = start; i * sign < stop * sign; i += step) { + PyObject* pyidx = PyInt_FromSsize_t(i); + PyObject* item = PyObject_CallMethodOneArg((PyObject*)self, + PyStrings::gGetNoCheck, pyidx); + CallPyObjMethod(nseq, "push_back", item); + Py_DECREF(item); + Py_DECREF(pyidx); + } + + return nseq; + } + + return CallSelfIndex(self, (PyObject*)index, PyStrings::gGetNoCheck); +} + +static interop::TCppScope_t sVectorBoolTypeID; + +PyObject* VectorBoolGetItem(CPPInstance* self, PyObject* idx) { + // std::vector is a special-case in C++, and its return type depends on + // the compiler: treat it special here as well + if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) { + PyErr_Format(PyExc_TypeError, + "require object of type std::vector, but %s given", + interop::GetScopedFinalName(self->ObjectIsA()).c_str()); + return nullptr; + } + + if (!self->GetObject()) { + PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); + return nullptr; + } + + if (PySlice_Check(idx)) { + PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)self); + PyObject* nseq = PyObject_CallObject(pyclass, nullptr); + + Py_ssize_t start, stop, step; + PySlice_GetIndices((cpyrt_PySliceCast)idx, PyObject_Length((PyObject*)self), + &start, &stop, &step); + const Py_ssize_t nlen = PySequence_Size((PyObject*)self); + if (!AdjustSlice(nlen, start, stop, step)) + return nseq; + + const Py_ssize_t sign = step < 0 ? -1 : 1; + for (Py_ssize_t i = start; i * sign < stop * sign; i += step) { + PyObject* pyidx = PyInt_FromSsize_t(i); + PyObject* item = PyObject_CallMethodOneArg((PyObject*)self, + PyStrings::gGetItem, pyidx); + CallPyObjMethod(nseq, "push_back", item); + Py_DECREF(item); + Py_DECREF(pyidx); + } + + return nseq; + } + + PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); + if (!pyindex) + return nullptr; + + int index = (int)PyLong_AsLong(pyindex); + Py_DECREF(pyindex); + + // get hold of the actual std::vector (no cast, as vector is never a + // base) + std::vector* vb = (std::vector*)self->GetObject(); + + // finally, return the value + if (bool((*vb)[index])) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +PyObject* VectorBoolSetItem(CPPInstance* self, PyObject* args) { + // std::vector is a special-case in C++, and its return type depends on + // the compiler: treat it special here as well + if (!CPPInstance_Check(self) || self->ObjectIsA() != sVectorBoolTypeID) { + PyErr_Format(PyExc_TypeError, + "require object of type std::vector, but %s given", + interop::GetScopedFinalName(self->ObjectIsA()).c_str()); + return nullptr; + } + + if (!self->GetObject()) { + PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); + return nullptr; + } + + int bval = 0; + PyObject* idx = nullptr; + if (!PyArg_ParseTuple(args, const_cast("Oi:__setitem__"), &idx, &bval)) + return nullptr; + + PyObject* pyindex = PyStyleIndex((PyObject*)self, idx); + if (!pyindex) + return nullptr; + + int index = (int)PyLong_AsLong(pyindex); + Py_DECREF(pyindex); + + // get hold of the actual std::vector (no cast, as vector is never a + // base) + std::vector* vb = (std::vector*)self->GetObject(); + + // finally, set the value + (*vb)[index] = (bool)bval; + + Py_RETURN_NONE; +} + +//- array behavior as primitives ---------------------------------------------- +PyObject* ArrayInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // std::array is normally only constructed using aggregate initialization, + // which is a concept that does not exist in python, so use this custom + // constructor to to fill the array using setitem + + if (args && PyTuple_GET_SIZE(args) == 1 && + PySequence_Check(PyTuple_GET_ITEM(args, 0))) { + // construct the empty array, then fill it + PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); + if (!result) + return nullptr; + + PyObject* items = PyTuple_GET_ITEM(args, 0); + Py_ssize_t fillsz = PySequence_Size(items); + if (PySequence_Size(self) != fillsz) { + PyErr_Format(PyExc_ValueError, + "received sequence of size %zd where %zd expected", fillsz, + PySequence_Size(self)); + Py_DECREF(result); + return nullptr; + } + + PyObject* si_call = PyObject_GetAttr(self, PyStrings::gSetItem); + for (Py_ssize_t i = 0; i < fillsz; ++i) { + PyObject* item = PySequence_GetItem(items, i); + PyObject* index = PyInt_FromSsize_t(i); + PyObject* sires = + PyObject_CallFunctionObjArgs(si_call, index, item, nullptr); + Py_DECREF(index); + Py_DECREF(item); + if (!sires) { + Py_DECREF(si_call); + Py_DECREF(result); + return nullptr; + } else + Py_DECREF(sires); + } + Py_DECREF(si_call); + + return result; + } else + PyErr_Clear(); + + // The given argument wasn't iterable: simply forward to regular constructor + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + return result; + } + + return nullptr; +} + +//- map behavior as primitives ------------------------------------------------ +static PyObject* MapFromPairs(PyObject* self, PyObject* pairs) { + // construct an empty map, then fill it with the key, value pairs + PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); + if (!result) + return nullptr; + + PyObject* si_call = PyObject_GetAttr(self, PyStrings::gSetItem); + for (Py_ssize_t i = 0; i < PySequence_Size(pairs); ++i) { + PyObject* pair = PySequence_GetItem(pairs, i); + PyObject* sires = nullptr; + if (pair && PySequence_Check(pair) && PySequence_Size(pair) == 2) { + PyObject* key = PySequence_GetItem(pair, 0); + PyObject* value = PySequence_GetItem(pair, 1); + sires = PyObject_CallFunctionObjArgs(si_call, key, value, nullptr); + Py_DECREF(value); + Py_DECREF(key); + } + Py_DECREF(pair); + if (!sires) { + Py_DECREF(si_call); + Py_DECREF(result); + if (!PyErr_Occurred()) + PyErr_SetString( + PyExc_TypeError, + "Failed to fill map (argument not a dict or sequence of pairs)"); + return nullptr; + } else + Py_DECREF(sires); + } + Py_DECREF(si_call); + + return result; +} + +PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // Specialized map constructor to allow construction from mapping containers + // and from tuples of pairs ("initializer_list style"). + + // PyMapping_Check is not very discriminatory, as it basically only checks for + // the existence of __getitem__, hence the most common cases of tuple and + // list are dropped straight-of-the-bat (the PyMapping_Items call will fail on + // them). + if (PyTuple_GET_SIZE(args) == 1 && + PyMapping_Check(PyTuple_GET_ITEM(args, 0)) && + !(PyTuple_Check(PyTuple_GET_ITEM(args, 0)) || + PyList_Check(PyTuple_GET_ITEM(args, 0)))) { + PyObject* assoc = PyTuple_GET_ITEM(args, 0); + // in p3, PyMapping_Items isn't a macro, but a function that short-circuits + // dict + PyObject* items = PyMapping_Items(assoc); + if (items && PySequence_Check(items)) { + PyObject* result = MapFromPairs(self, items); + Py_DECREF(items); + return result; + } + + Py_XDECREF(items); + PyErr_Clear(); + + // okay to fall through as long as 'self' has not been created (is done in + // MapFromPairs) + } + + // tuple of pairs case (some mapping types are sequences) + if (PyTuple_GET_SIZE(args) == 1 && + PySequence_Check(PyTuple_GET_ITEM(args, 0))) + return MapFromPairs(self, PyTuple_GET_ITEM(args, 0)); + + // The given argument wasn't a mapping or tuple of pairs: forward to regular + // constructor + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + return result; + } + + return nullptr; +} + +PyObject* STLContainsWithFind(PyObject* self, PyObject* obj) { + // Implement python's __contains__ for std::map/std::set + PyObject* result = nullptr; + + PyObject* iter = CallPyObjMethod(self, "find", obj); + if (CPPInstance_Check(iter)) { + PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); + if (CPPInstance_Check(end)) { + if (!PyObject_RichCompareBool(iter, end, Py_EQ)) { + Py_INCREF(Py_True); + result = Py_True; + } + } + Py_XDECREF(end); + } + Py_XDECREF(iter); + + if (!result) { + PyErr_Clear(); // e.g. wrong argument type, which should always lead to + // False + Py_INCREF(Py_False); + result = Py_False; + } + + return result; +} + +//- set behavior as primitives ------------------------------------------------ +PyObject* SetInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // Specialized set constructor to allow construction from Python sets. + if (PyTuple_GET_SIZE(args) == 1 && PySet_Check(PyTuple_GET_ITEM(args, 0))) { + PyObject* pyset = PyTuple_GET_ITEM(args, 0); + + // construct an empty set, then fill it + PyObject* result = PyObject_CallMethodNoArgs(self, PyStrings::gRealInit); + if (!result) + return nullptr; + + PyObject* iter = PyObject_GetIter(pyset); + if (iter) { + PyObject* ins_call = PyObject_GetAttrString(self, (char*)"insert"); + + IterItemGetter getter{iter}; + Py_DECREF(iter); + + PyObject* item = getter.get(); + while (item) { + PyObject* insres = + PyObject_CallFunctionObjArgs(ins_call, item, nullptr); + Py_DECREF(item); + if (!insres) { + Py_DECREF(ins_call); + Py_DECREF(result); + return nullptr; + } else + Py_DECREF(insres); + item = getter.get(); + } + Py_DECREF(ins_call); + } + + return result; + } + + // The given argument wasn't iterable: simply forward to regular constructor + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + return result; + } + + return nullptr; +} + +//- STL container iterator support -------------------------------------------- +static const ptrdiff_t PS_END_ADDR = 7; // non-aligned address, so no clash +static const ptrdiff_t PS_FLAG_ADDR = 11; // id. +static const ptrdiff_t PS_COLL_ADDR = 13; // id. + +PyObject* STLIterNext(PyObject* self); // defined below; used by STLSequenceIter + +PyObject* LLSequenceIter(PyObject* self) { + // Implement python's __iter__ for low level views used through STL-type + // begin()/end() + PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gBegin); + + if (LowLevelView_Check(iter)) { + // builtin pointer iteration: can only succeed if a size is available + Py_ssize_t sz = PySequence_Size(self); + if (sz == -1) { + Py_DECREF(iter); + return nullptr; + } + PyObject* lliter = Py_TYPE(iter)->tp_iter(iter); + ((indexiterobject*)lliter)->ii_len = sz; + Py_DECREF(iter); + return lliter; + } + + if (iter) { + Py_DECREF(iter); + PyErr_SetString(PyExc_TypeError, + "unrecognized iterator type for low level views"); + } + + return nullptr; +} + +PyObject* STLSequenceIter(PyObject* self) { + // Implement python's __iter__ for std::iterator<>s + PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gBegin); + if (iter) { + PyObject* end = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); + if (end) { + if (CPPInstance_Check(iter)) { + // Guarantee the returned iterator implements Python's iterator + // protocol. The deferred, name-based __next__ install can miss when an + // iterator's canonical return-type spelling and its scoped-final-name + // diverge (e.g libc++'s std::__1::__wrap_iter on macOS) leaving the + // type without + // __next__, so iter() rejects it. begin()/end() resolving here proves + // this is an STL forward iterator, so install the protocol now if it is + // still absent + PyTypeObject* itype = Py_TYPE(iter); + if (!PyIter_Check(iter)) { // no tp_iternext, or the + // _PyObject_NextNotImplemented sentinel + itype->tp_iternext = (iternextfunc)STLIterNext; + Utility::AddToClass((PyObject*)itype, CPPJIT__next__, + (PyCFunction)STLIterNext, METH_NOARGS); + if (!itype->tp_iter) { + itype->tp_iter = (getiterfunc)PyObject_SelfIter; + Utility::AddToClass((PyObject*)itype, "__iter__", + (PyCFunction)PyObject_SelfIter, METH_NOARGS); + } + PyType_Modified(itype); + } + // use the data member cache to store extra state on the iterator + // object, without it being visible on the Python side + auto& dmc = ((CPPInstance*)iter)->GetDatamemberCache(); + dmc.push_back(std::make_pair(PS_END_ADDR, end)); + + // set a flag, indicating first iteration (reset in __next__) + Py_INCREF(Py_False); + dmc.push_back(std::make_pair(PS_FLAG_ADDR, Py_False)); + + // make sure the iterated over collection remains alive for the duration + Py_INCREF(self); + dmc.push_back(std::make_pair(PS_COLL_ADDR, self)); + } else { + // could store "end" on the object's dictionary anyway, but if end() + // returns a user-customized object, then its __next__ is probably + // custom, too + Py_DECREF(end); + } + } + } + return iter; +} + +//- generic iterator support over a sequence with operator[] and size --------- +//----------------------------------------------------------------------------- +static PyObject* index_iter(PyObject* c) { + indexiterobject* ii = PyObject_GC_New(indexiterobject, &IndexIter_Type); + if (!ii) + return nullptr; + + Py_INCREF(c); + ii->ii_container = c; + ii->ii_pos = 0; + ii->ii_len = PySequence_Size(c); + + PyObject_GC_Track(ii); + return (PyObject*)ii; +} + +//- safe indexing for STL-like vector w/o iterator dictionaries --------------- +/* replaced by indexiterobject iteration, but may still have some future use ... +PyObject* CheckedGetItem(PyObject* self, PyObject* obj) +{ +// Implement a generic python __getitem__ for STL-like classes that are missing +the +// reflection info for their iterators. This is then used for iteration by means +of +// consecutive indices, it such index is of integer type. + Py_ssize_t size = PySequence_Size(self); + Py_ssize_t idx = PyInt_AsSsize_t(obj); + if ((size == (Py_ssize_t)-1 || idx == (Py_ssize_t)-1) && PyErr_Occurred()) { + // argument conversion problem: let method itself resolve anew and report + PyErr_Clear(); + return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj); + } + + bool inbounds = false; + if (idx < 0) idx += size; + if (0 <= idx && 0 <= size && idx < size) + inbounds = true; + + if (inbounds) + return PyObject_CallMethodOneArg(self, PyStrings::gGetNoCheck, obj); + else + PyErr_SetString( PyExc_IndexError, "index out of range" ); + + return nullptr; +}*/ + +//- pair as sequence to allow tuple unpacking -------------------------------- +PyObject* PairUnpack(PyObject* self, PyObject* pyindex) { + // For std::map<> iteration, unpack std::pair<>s into tuples for the loop. + long idx = PyLong_AsLong(pyindex); + if (idx == -1 && PyErr_Occurred()) + return nullptr; + + if (!CPPInstance_Check(self) || !((CPPInstance*)self)->GetObject()) { + PyErr_SetString(PyExc_TypeError, "unsubscriptable object"); + return nullptr; + } + + if ((int)idx == 0) + return PyObject_GetAttr(self, PyStrings::gFirst); + else if ((int)idx == 1) + return PyObject_GetAttr(self, PyStrings::gSecond); + + // still here? Trigger stop iteration + PyErr_SetString(PyExc_IndexError, "out of bounds"); + return nullptr; +} + +//- simplistic len() functions ----------------------------------------------- +PyObject* ReturnTwo(CPPInstance*, PyObject*) { return PyInt_FromLong(2); } + +//- shared/unique_ptr behavior ----------------------------------------------- +PyObject* SmartPtrInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // since the shared/unique pointer will take ownership, we need to relinquish + // it + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject* result = PyObject_Call(realInit, args, nullptr); + Py_DECREF(realInit); + if (result && PyTuple_GET_SIZE(args) == 1 && + CPPInstance_Check(PyTuple_GET_ITEM(args, 0))) { + CPPInstance* cppinst = (CPPInstance*)PyTuple_GET_ITEM(args, 0); + if (!(cppinst->fFlags & CPPInstance::kIsSmartPtr)) + cppinst->CppOwns(); + } + return result; + } + return nullptr; +} + +//- string behavior as primitives -------------------------------------------- +// TODO: this is wrong, b/c it doesn't order +static int PyObject_Compare(PyObject* one, PyObject* other) { + return !PyObject_RichCompareBool(one, other, Py_EQ); +} +static inline PyObject* cpyrt_PyString_FromCppString(std::string_view s, + bool native = true) { + if (native) + return PyBytes_FromStringAndSize(s.data(), s.size()); + return cpyrt_PyText_FromStringAndSize(s.data(), s.size()); +} + +static inline PyObject* cpyrt_PyString_FromCppString(std::wstring_view s, + bool native = true) { + PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size()); + if (pyobj && native) { + PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict"); + Py_DECREF(pyobj); + pyobj = pybytes; + } + return pyobj; +} + +#define CPPJIT_IMPL_STRING_PYTHONIZATION(type, name) \ + static inline PyObject* name##StringGetData(PyObject* self, \ + bool native = true) { \ + if (cpyrt::CPPInstance_Check(self)) { \ + type* obj = ((type*)((CPPInstance*)self)->GetObject()); \ + if (obj) \ + return cpyrt_PyString_FromCppString(*obj, native); \ + } \ + PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \ + return nullptr; \ + } \ + \ + PyObject* name##StringStr(PyObject* self) { \ + PyObject* pyobj = name##StringGetData(self, false); \ + if (!pyobj) { \ + /* do a native conversion to make printing possible (debatable) */ \ + PyErr_Clear(); \ + PyObject* pybytes = name##StringGetData(self, true); \ + if (pybytes) { /* should not fail */ \ + pyobj = PyObject_Str(pybytes); \ + Py_DECREF(pybytes); \ + } \ + } \ + return pyobj; \ + } \ + \ + PyObject* name##StringBytes(PyObject* self) { \ + return name##StringGetData(self, true); \ + } \ + \ + PyObject* name##StringRepr(PyObject* self) { \ + PyObject* data = name##StringGetData(self, true); \ + if (data) { \ + PyObject* repr = PyObject_Repr(data); \ + Py_DECREF(data); \ + return repr; \ + } \ + return nullptr; \ + } \ + \ + PyObject* name##StringIsEqual(PyObject* self, PyObject* obj) { \ + PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ + if (data) { \ + PyObject* result = PyObject_RichCompare(data, obj, Py_EQ); \ + Py_DECREF(data); \ + return result; \ + } \ + return nullptr; \ + } \ + \ + PyObject* name##StringIsNotEqual(PyObject* self, PyObject* obj) { \ + PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ + if (data) { \ + PyObject* result = PyObject_RichCompare(data, obj, Py_NE); \ + Py_DECREF(data); \ + return result; \ + } \ + return nullptr; \ + } + +// Only define STLStringCompare: +#define CPPJIT_IMPL_STRING_PYTHONIZATION_CMP(type, name) \ + CPPJIT_IMPL_STRING_PYTHONIZATION(type, name) \ + PyObject* name##StringCompare(PyObject* self, PyObject* obj) { \ + PyObject* data = name##StringGetData(self, PyBytes_Check(obj)); \ + int result = 0; \ + if (data) { \ + result = PyObject_Compare(data, obj); \ + Py_DECREF(data); \ + } \ + if (PyErr_Occurred()) \ + return nullptr; \ + return PyInt_FromLong(result); \ + } + +CPPJIT_IMPL_STRING_PYTHONIZATION_CMP(std::string, STL) +CPPJIT_IMPL_STRING_PYTHONIZATION_CMP(std::wstring, STLW) +CPPJIT_IMPL_STRING_PYTHONIZATION_CMP(std::string_view, STLView) + +static inline std::string* GetSTLString(CPPInstance* self) { + if (!CPPInstance_Check(self)) { + PyErr_SetString(PyExc_TypeError, "std::string object expected"); + return nullptr; + } + + std::string* obj = (std::string*)self->GetObject(); + if (!obj) + PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); + + return obj; +} + +PyObject* STLStringDecode(CPPInstance* self, PyObject* args, PyObject* kwds) { + std::string* obj = GetSTLString(self); + if (!obj) + return nullptr; + + char* keywords[] = {(char*)"encoding", (char*)"errors", (char*)nullptr}; + const char* encoding = nullptr; + const char* errors = nullptr; + if (!PyArg_ParseTupleAndKeywords(args, kwds, const_cast("s|s"), + keywords, &encoding, &errors)) + return nullptr; + + return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors); +} + +PyObject* STLStringContains(CPPInstance* self, PyObject* pyobj) { + std::string* obj = GetSTLString(self); + if (!obj) + return nullptr; + + const char* needle = cpyrt_PyText_AsString(pyobj); + if (!needle) + return nullptr; + + if (obj->find(needle) != std::string::npos) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +PyObject* STLStringReplace(CPPInstance* self, PyObject* args, + PyObject* /*kwds*/) { + std::string* obj = GetSTLString(self); + if (!obj) + return nullptr; + + // both str and std::string have a method "replace", but the Python version + // only accepts strings and takes no keyword arguments, whereas the C++ + // version has no overload that takes a string + + if (2 <= PyTuple_GET_SIZE(args) && + cpyrt_PyText_Check(PyTuple_GET_ITEM(args, 0))) { + PyObject* pystr = cpyrt_PyText_FromStringAndSize(obj->data(), obj->size()); + PyObject* meth = PyObject_GetAttrString(pystr, (char*)"replace"); + Py_DECREF(pystr); + PyObject* result = PyObject_CallObject(meth, args); + Py_DECREF(meth); + return result; + } + + PyObject* cppreplace = + PyObject_GetAttrString((PyObject*)self, (char*)"__cpp_replace"); + if (cppreplace) { + PyObject* result = PyObject_Call(cppreplace, args, nullptr); + Py_DECREF(cppreplace); + return result; + } + + PyErr_SetString(PyExc_AttributeError, + "\'std::string\' object has no attribute \'replace\'"); + return nullptr; +} + +#define CPYRT_STRING_FINDMETHOD(name, cppname, pyname) \ + PyObject* STLString##name(CPPInstance* self, PyObject* args, \ + PyObject* /*kwds*/) { \ + std::string* obj = GetSTLString(self); \ + if (!obj) \ + return nullptr; \ + \ + PyObject* cppmeth = \ + PyObject_GetAttrString((PyObject*)self, (char*)#cppname); \ + if (cppmeth) { \ + PyObject* result = PyObject_Call(cppmeth, args, nullptr); \ + Py_DECREF(cppmeth); \ + if (result) { \ + if (PyLongOrInt_AsULong64(result) == \ + (PY_ULONG_LONG)std::string::npos) { \ + Py_DECREF(result); \ + return PyInt_FromLong(-1); \ + } \ + return result; \ + } \ + PyErr_Clear(); \ + } \ + \ + PyObject* pystr = \ + cpyrt_PyText_FromStringAndSize(obj->data(), obj->size()); \ + PyObject* pymeth = PyObject_GetAttrString(pystr, (char*)#pyname); \ + Py_DECREF(pystr); \ + PyObject* result = PyObject_CallObject(pymeth, args); \ + Py_DECREF(pymeth); \ + return result; \ + } + +// both str and std::string have method "find" and "rfin"; try the C++ version +// first and fall back on the Python one in case of failure +// clang-format off +CPYRT_STRING_FINDMETHOD( Find, __cpp_find, find) +CPYRT_STRING_FINDMETHOD(RFind, __cpp_rfind, rfind) +// clang-format on + +PyObject* STLStringGetAttr(CPPInstance* self, PyObject* attr_name) { + std::string* obj = GetSTLString(self); + if (!obj) + return nullptr; + + PyObject* pystr = cpyrt_PyText_FromStringAndSize(obj->data(), obj->size()); + PyObject* attr = PyObject_GetAttr(pystr, attr_name); + Py_DECREF(pystr); + return attr; +} + +PyObject* UTF8Repr(PyObject* self) { + // force C++ string types conversion to Python str per Python __repr__ + // requirements + PyObject* res = PyObject_CallMethodNoArgs(self, PyStrings::gCppRepr); + if (!res || cpyrt_PyText_Check(res)) + return res; + PyObject* str_res = PyObject_Str(res); + Py_DECREF(res); + return str_res; +} + +PyObject* UTF8Str(PyObject* self) { + // force C++ string types conversion to Python str per Python __str__ + // requirements + PyObject* res = PyObject_CallMethodNoArgs(self, PyStrings::gCppStr); + if (!res || cpyrt_PyText_Check(res)) + return res; + PyObject* str_res = PyObject_Str(res); + Py_DECREF(res); + return str_res; +} + +Py_hash_t STLStringHash(PyObject* self) { + // std::string objects hash to the same values as Python strings to allow + // matches in dictionaries etc. + PyObject* data = STLStringGetData(self, false); + Py_hash_t h = cpyrt_PyText_Type.tp_hash(data); + Py_DECREF(data); + return h; +} + +//- string_view behavior as primitive ---------------------------------------- +PyObject* StringViewInit(PyObject* self, PyObject* args, PyObject* /* kwds */) { + // if constructed from a Python unicode object, the constructor will convert + // it to a temporary byte string, which is likely to go out of scope too soon; + // so buffer it as needed + PyObject* realInit = PyObject_GetAttr(self, PyStrings::gRealInit); + if (realInit) { + PyObject *strbuf = nullptr, *newArgs = nullptr; + if (PyTuple_GET_SIZE(args) == 1) { + PyObject* arg0 = PyTuple_GET_ITEM(args, 0); + if (PyUnicode_Check(arg0)) { + // convert to the expected bytes array to control the temporary + strbuf = PyUnicode_AsEncodedString(arg0, "UTF-8", "strict"); + newArgs = PyTuple_New(1); + Py_INCREF(strbuf); + PyTuple_SET_ITEM(newArgs, 0, strbuf); + } else if (PyBytes_Check(arg0)) { + // tie the life time of the provided string to the string_view + Py_INCREF(arg0); + strbuf = arg0; + } + } + + PyObject* result = + PyObject_Call(realInit, newArgs ? newArgs : args, nullptr); + + Py_XDECREF(newArgs); + Py_DECREF(realInit); + + // if construction was successful and a string buffer was used, add a + // life line to it from the string_view bound object + if (result && self && strbuf) + PyObject_SetAttr(self, PyStrings::gLifeLine, strbuf); + Py_XDECREF(strbuf); + + return result; + } + return nullptr; +} + +//- STL iterator behavior ---------------------------------------------------- +PyObject* STLIterNext(PyObject* self) { + // Python iterator protocol __next__ for STL forward iterators. + bool mustIncrement = true; + PyObject* last = nullptr; + if (CPPInstance_Check(self)) { + auto& dmc = ((CPPInstance*)self)->GetDatamemberCache(); + for (auto& p : dmc) { + if (p.first == PS_END_ADDR) { + last = p.second; + Py_INCREF(last); + } else if (p.first == PS_FLAG_ADDR) { + mustIncrement = p.second == Py_True; + if (!mustIncrement) { + Py_DECREF(p.second); + Py_INCREF(Py_True); + p.second = Py_True; + } + } + } + } + + PyObject* next = nullptr; + if (last) { + // handle special case of empty container (i.e. self is end) + if (!PyObject_RichCompareBool(last, self, Py_EQ)) { + bool iter_valid = true; + if (mustIncrement) { + // prefer preinc, but allow post-inc; in both cases, it is "self" that + // has the updated state to dereference + PyObject* iter = PyObject_CallMethodNoArgs(self, PyStrings::gPreInc); + if (!iter) { + PyErr_Clear(); + static PyObject* dummy = PyInt_FromLong(1l); + iter = PyObject_CallMethodOneArg(self, PyStrings::gPostInc, dummy); + } + iter_valid = iter && PyObject_RichCompareBool(last, self, Py_NE); + Py_XDECREF(iter); + } + + if (iter_valid) { + next = PyObject_CallMethodNoArgs(self, PyStrings::gDeref); + if (!next) + PyErr_Clear(); + } + } + Py_DECREF(last); + } + + if (!next) + PyErr_SetString(PyExc_StopIteration, ""); + return next; +} + +//- STL complex behavior -------------------------------------------------- +#define COMPLEX_METH_GETSET(name, cppname) \ + static PyObject* name##ComplexGet(PyObject* self, void*) { \ + return PyObject_CallMethodNoArgs(self, cppname); \ + } \ + static int name##ComplexSet(PyObject* self, PyObject* value, void*) { \ + PyObject* result = PyObject_CallMethodOneArg(self, cppname, value); \ + if (result) { \ + Py_DECREF(result); \ + return 0; \ + } \ + return -1; \ + } \ + PyGetSetDef name##Complex{(char*)#name, (getter)name##ComplexGet, \ + (setter)name##ComplexSet, nullptr, nullptr}; + +COMPLEX_METH_GETSET(real, PyStrings::gCppReal) +COMPLEX_METH_GETSET(imag, PyStrings::gCppImag) + +static PyObject* ComplexComplex(PyObject* self) { + PyObject* real = PyObject_CallMethodNoArgs(self, PyStrings::gCppReal); + if (!real) + return nullptr; + double r = PyFloat_AsDouble(real); + Py_DECREF(real); + if (r == -1. && PyErr_Occurred()) + return nullptr; + + PyObject* imag = PyObject_CallMethodNoArgs(self, PyStrings::gCppImag); + if (!imag) + return nullptr; + double i = PyFloat_AsDouble(imag); + Py_DECREF(imag); + if (i == -1. && PyErr_Occurred()) + return nullptr; + + return PyComplex_FromDoubles(r, i); +} + +static PyObject* ComplexRepr(PyObject* self) { + PyObject* real = PyObject_CallMethodNoArgs(self, PyStrings::gCppReal); + if (!real) + return nullptr; + double r = PyFloat_AsDouble(real); + Py_DECREF(real); + if (r == -1. && PyErr_Occurred()) + return nullptr; + + PyObject* imag = PyObject_CallMethodNoArgs(self, PyStrings::gCppImag); + if (!imag) + return nullptr; + double i = PyFloat_AsDouble(imag); + Py_DECREF(imag); + if (i == -1. && PyErr_Occurred()) + return nullptr; + + std::ostringstream s; + s << '(' << r << '+' << i << "j)"; + return cpyrt_PyText_FromString(s.str().c_str()); +} + +static PyObject* ComplexDRealGet(CPPInstance* self, void*) { + return PyFloat_FromDouble(((std::complex*)self->GetObject())->real()); +} + +static int ComplexDRealSet(CPPInstance* self, PyObject* value, void*) { + double d = PyFloat_AsDouble(value); + if (d == -1.0 && PyErr_Occurred()) + return -1; + ((std::complex*)self->GetObject())->real(d); + return 0; +} + +PyGetSetDef ComplexDReal{(char*)"real", (getter)ComplexDRealGet, + (setter)ComplexDRealSet, nullptr, nullptr}; + +static PyObject* ComplexDImagGet(CPPInstance* self, void*) { + return PyFloat_FromDouble(((std::complex*)self->GetObject())->imag()); +} + +static int ComplexDImagSet(CPPInstance* self, PyObject* value, void*) { + double d = PyFloat_AsDouble(value); + if (d == -1.0 && PyErr_Occurred()) + return -1; + ((std::complex*)self->GetObject())->imag(d); + return 0; +} + +PyGetSetDef ComplexDImag{(char*)"imag", (getter)ComplexDImagGet, + (setter)ComplexDImagSet, nullptr, nullptr}; + +static PyObject* ComplexDComplex(CPPInstance* self) { + double r = ((std::complex*)self->GetObject())->real(); + double i = ((std::complex*)self->GetObject())->imag(); + return PyComplex_FromDoubles(r, i); +} + +} // unnamed namespace + +//- public functions --------------------------------------------------------- +namespace cppjit::cpyrt { +std::set gIteratorTypes; +} + +static inline bool run_pythonizors(PyObject* pyclass, PyObject* pyname, + const std::vector& v) { + PyObject* args = PyTuple_New(2); + Py_INCREF(pyclass); + PyTuple_SET_ITEM(args, 0, pyclass); + Py_INCREF(pyname); + PyTuple_SET_ITEM(args, 1, pyname); + + bool pstatus = true; + for (auto pythonizor : v) { + PyObject* result = PyObject_CallObject(pythonizor, args); + if (!result) { + pstatus = false; // TODO: detail the error handling + break; + } + Py_DECREF(result); + } + Py_DECREF(args); + + return pstatus; +} + +bool cpyrt::Pythonize(PyObject* pyclass, interop::TCppScope_t scope) { + const std::string& name = interop::GetScopedFinalName(scope); + + // Add pre-defined pythonizations (for STL and ROOT) to classes based on their + // signature and/or class name. + if (!pyclass) + return false; + + CPPScope* klass = (CPPScope*)pyclass; + + //- method name based pythonization ------------------------------------------ + + // for smart pointer style classes that are otherwise not known as such; would + // prefer operator-> as that returns a pointer (which is simpler since it + // never has to deal with ref-assignment), but operator* plays better with STL + // iters and algorithms + if (HasAttrDirect(pyclass, PyStrings::gDeref) && + !interop::IsSmartPtr(klass->fCppType)) + Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)DeRefGetAttr, + METH_O); + else if (HasAttrDirect(pyclass, PyStrings::gFollow) && + !interop::IsSmartPtr(klass->fCppType)) + Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)FollowGetAttr, + METH_O); + + // for pre-check of nullptr for boolean types + if (HasAttrDirect(pyclass, PyStrings::gCppBool)) { + const char* pybool_name = "__bool__"; + Utility::AddToClass(pyclass, pybool_name, (PyCFunction)NullCheckBool, + METH_NOARGS); + } + + // for STL containers, and user classes modeled after them + // the attribute must be a cpyrt overload, otherwise the check gives false + // positives in the case where the class has a non-function attribute that is + // called "size". + if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBecpyrt=*/true)) { + Utility::AddToClass(pyclass, "__len__", "size"); + } + + if (HasAttrDirect(pyclass, PyStrings::gContains)) { + Utility::AddToClass(pyclass, "__contains__", "contains"); + } + + if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below + !((PyTypeObject*)pyclass)->tp_iter) { + if (HasAttrDirect(pyclass, PyStrings::gBegin) && + HasAttrDirect(pyclass, PyStrings::gEnd)) { + // obtain the name of the return type + const auto& methods = + interop::GetMethodsFromName(klass->fCppType, "begin"); + if (!methods.empty()) { + // check return type; if not explicitly an iterator, add it to the + // "known" return types to add the "next" method on use + interop::TCppMethod_t meth = methods[0]; + const std::string& resname = interop::GetMethodReturnTypeAsString(meth); + bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end(); + if (!isIterator && interop::GetScope(resname)) { + if (resname.find("iterator") == std::string::npos) + gIteratorTypes.insert(resname); + isIterator = true; + } + + if (isIterator) { + // install iterator protocol a la STL + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)STLSequenceIter; + Utility::AddToClass(pyclass, "__iter__", (PyCFunction)STLSequenceIter, + METH_NOARGS); + } else { + // still okay if this is some pointer type of builtin persuasion + // (general class won't work: the return type needs to understand the + // iterator protocol) + std::string resolved = interop::ResolveName(resname); + if (resolved.back() == '*' && + interop::IsBuiltin(resolved.substr(0, resolved.size() - 1))) { + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)LLSequenceIter; + Utility::AddToClass(pyclass, "__iter__", + (PyCFunction)LLSequenceIter, METH_NOARGS); + } + } + } + } + if (!((PyTypeObject*)pyclass)->tp_iter && // no iterator resolved + HasAttrDirect(pyclass, PyStrings::gGetItem) && + PyObject_HasAttr(pyclass, PyStrings::gLen)) { + // Python will iterate over __getitem__ using integers, but C++ operator[] + // will never raise a StopIteration. A checked getitem (raising IndexError + // if beyond size()) works in some cases but would mess up if operator[] + // is meant to implement an associative container. So, this has to be + // implemented as an iterator protocol. + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)index_iter; + Utility::AddToClass(pyclass, "__iter__", (PyCFunction)index_iter, + METH_NOARGS); + } + } + + // operator==/!= are used in op_richcompare of CPPInstance, which subsequently + // allows comparisons to None; if no operator is available, a hook is + // installed for lazy lookups in the global and/or class namespace + if (HasAttrDirect(pyclass, PyStrings::gEq, true) && + interop::GetMethodsFromName(klass->fCppType, "__eq__").empty()) { + PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gEq); + if (!klass->fOperators) + klass->fOperators = new Utility::PyOperators(); + klass->fOperators->fEq = cppol; + // re-insert the forwarding __eq__ from the CPPInstance in case there was a + // Python-side override in the base class + static PyObject* top_eq = nullptr; + if (!top_eq) { + PyObject* top_cls = PyObject_GetAttrString(gThisModule, "CPPInstance"); + top_eq = PyObject_GetAttr(top_cls, PyStrings::gEq); + Py_DECREF(top_eq); // make it borrowed + Py_DECREF(top_cls); + } + PyObject_SetAttr(pyclass, PyStrings::gEq, top_eq); + } + + if (HasAttrDirect(pyclass, PyStrings::gNe, true) && + interop::GetMethodsFromName(klass->fCppType, "__ne__").empty()) { + PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gNe); + if (!klass->fOperators) + klass->fOperators = new Utility::PyOperators(); + klass->fOperators->fNe = cppol; + // re-insert the forwarding __ne__ (same reason as above for __eq__) + static PyObject* top_ne = nullptr; + if (!top_ne) { + PyObject* top_cls = PyObject_GetAttrString(gThisModule, "CPPInstance"); + top_ne = PyObject_GetAttr(top_cls, PyStrings::gNe); + Py_DECREF(top_ne); // make it borrowed + Py_DECREF(top_cls); + } + PyObject_SetAttr(pyclass, PyStrings::gNe, top_ne); + } + + if (HasAttrDirect(pyclass, PyStrings::gRepr, true)) { + // guarantee that the result of __repr__ is a Python string + Utility::AddToClass(pyclass, "__cpp_repr", "__repr__"); + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)UTF8Repr, + METH_NOARGS); + } + + if (HasAttrDirect(pyclass, PyStrings::gStr, true)) { + // guarantee that the result of __str__ is a Python string + Utility::AddToClass(pyclass, "__cpp_str", "__str__"); + Utility::AddToClass(pyclass, "__str__", (PyCFunction)UTF8Str, METH_NOARGS); + } + + if (interop::IsAggregate(((CPPClass*)pyclass)->fCppType) && + name.compare(0, 5, "std::", 5) != 0) { + // create a pseudo-constructor to allow initializer-style object creation + interop::TCppScope_t kls = ((CPPClass*)pyclass)->fCppType; + std::vector datamems; + interop::GetDatamembers(kls, datamems); + if (!datamems.empty()) { + std::string rname = name; + TypeManip::cppscope_to_legalname(rname); + + std::ostringstream initdef; + initdef << "namespace __cppjit_internal {\n" + << "void init_" << rname << "(" << name << "** self"; + bool codegen_ok = true; + std::vector arg_types, arg_names, arg_defaults; + const int ndata = datamems.size(); + arg_types.reserve(ndata); + arg_names.reserve(ndata); + arg_defaults.reserve(ndata); + for (auto data : datamems) { + if (interop::IsStaticDatamember(data) || !interop::IsPublicData(data)) + continue; + + interop::TCppType_t datammember_type = interop::GetDatamemberType(data); + const std::string& res = + interop::IsEnumType(datammember_type) + ? interop::GetScopedFinalName( + interop::GetScopeFromType(datammember_type)) + : interop::GetTypeAsString( + interop::ResolveType(datammember_type)); + const std::string& cpd = TypeManip::compound(res); + std::string res_clean = TypeManip::clean_type(res, false, true); + + if (res_clean == "internal_enum_type_t") + res_clean = res; // restore (properly scoped name) + + if (res.rfind(']') == std::string::npos && + res.rfind(')') == std::string::npos) { + if (!cpd.empty()) + arg_types.push_back(res_clean + cpd); + else + arg_types.push_back("const " + res_clean + "&"); + arg_names.push_back(interop::GetFinalName(data)); + if ((!cpd.empty() && cpd.back() == '*') || + interop::IsBuiltin(res_clean)) + arg_defaults.push_back("0"); + else { + interop::TCppScope_t klsid = interop::GetScope(res_clean); + if (interop::IsDefaultConstructable(klsid)) + arg_defaults.push_back(res_clean + "{}"); + } + } else { + codegen_ok = + false; // TODO: how to support arrays, anonymous enums, etc? + break; + } + } + + if (codegen_ok && !arg_types.empty()) { + bool defaults_ok = arg_defaults.size() == arg_types.size(); + for (std::vector::size_type i = 0; i < arg_types.size(); + ++i) { + initdef << ", " << arg_types[i] << " " << arg_names[i]; + if (defaults_ok) + initdef << " = " << arg_defaults[i]; + } + initdef << ") {\n *self = new " << name << "{"; + for (std::vector::size_type i = 0; i < arg_names.size(); + ++i) { + if (i != 0) + initdef << ", "; + initdef << arg_names[i]; + } + initdef << "};\n} }"; + + if (interop::Compile(initdef.str(), true /* silent */)) { + interop::TCppScope_t cis = interop::GetScope("__cppjit_internal"); + const auto& methods = + interop::GetMethodsFromName(cis, "init_" + rname); + if (methods.size()) { + if (!Utility::AddToClass(pyclass, "__init__", + new CPPFunction(cis, methods[0]))) + PyErr_Clear(); + } + } + } + } + } + + //- class name based pythonization ------------------------------------------- + + if (IsTemplatedSTLClass(name, "vector")) { + + // std::vector is a special case in C++ + if (!sVectorBoolTypeID) + sVectorBoolTypeID = interop::GetScope("std::vector"); + if (klass->fCppType == sVectorBoolTypeID) { + Utility::AddToClass(pyclass, "__getitem__", + (PyCFunction)VectorBoolGetItem, METH_O); + Utility::AddToClass(pyclass, "__setitem__", + (PyCFunction)VectorBoolSetItem); + } else { + // constructor that takes python collections + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)VectorInit, + METH_VARARGS | METH_KEYWORDS); + + // data with size + Utility::AddToClass(pyclass, "__real_data", "data"); + Utility::AddToClass(pyclass, "data", (PyCFunction)VectorData); + + // numpy array conversion + Utility::AddToClass(pyclass, "__array__", (PyCFunction)VectorArray, + METH_VARARGS | METH_KEYWORDS /* unused */); + + // checked getitem + if (HasAttrDirect(pyclass, PyStrings::gLen)) { + Utility::AddToClass(pyclass, "_getitem__unchecked", "__getitem__"); + Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)VectorGetItem, + METH_O); + } + + // vector-optimized iterator protocol + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)vector_iter; + + // optimized __iadd__ + Utility::AddToClass(pyclass, "__iadd__", (PyCFunction)VectorIAdd, + METH_VARARGS | METH_KEYWORDS); + + // helpers for iteration + interop::TCppType_t value_type = + interop::GetTypeFromScope(interop::GetNamed("value_type", scope)); + interop::TCppType_t vtype = interop::ResolveType(value_type); + if (vtype) { // actually resolved? + PyObject* pyvalue_type = PyLong_FromVoidPtr(vtype.data); + PyObject_SetAttr(pyclass, PyStrings::gValueTypePtr, pyvalue_type); + Py_DECREF(pyvalue_type); + pyvalue_type = + PyUnicode_FromString(interop::GetTypeAsString(vtype).c_str()); + PyObject_SetAttr(pyclass, PyStrings::gValueType, pyvalue_type); + Py_DECREF(pyvalue_type); + } + + size_t typesz = interop::SizeOfType(vtype); + if (typesz) { + PyObject* pyvalue_size = PyLong_FromSsize_t(typesz); + PyObject_SetAttr(pyclass, PyStrings::gValueSize, pyvalue_size); + Py_DECREF(pyvalue_size); + } + } + } + + else if (IsTemplatedSTLClass(name, "array")) { + // constructor that takes python associative collections + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)ArrayInit, + METH_VARARGS | METH_KEYWORDS); + } + + else if (IsTemplatedSTLClass(name, "map") || + IsTemplatedSTLClass(name, "unordered_map")) { + // constructor that takes python associative collections + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)MapInit, + METH_VARARGS | METH_KEYWORDS); + // From C++20, std::map/unordered_map have a native contains() that the + // generic contains->__contains__ mapping above will pick up. Strong-types + // reflection does not always expose it, so fall back to a find()-based + // __contains__ when none was reflected (still O(log n)/O(1), never + // iterating). + if (!HasAttrDirect(pyclass, PyStrings::gContains)) + Utility::AddToClass(pyclass, "__contains__", + (PyCFunction)STLContainsWithFind, METH_O); + } + + else if (IsTemplatedSTLClass(name, "set")) { + // constructor that takes python associative collections + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)SetInit, + METH_VARARGS | METH_KEYWORDS); + // From C++20, std::set has a native contains() that the generic + // contains->__contains__ mapping above will pick up. Strong-types + // reflection does not always expose it, so fall back to a find()-based + // __contains__ when none was reflected (still O(log n), never iterating). + if (!HasAttrDirect(pyclass, PyStrings::gContains)) + Utility::AddToClass(pyclass, "__contains__", + (PyCFunction)STLContainsWithFind, METH_O); + } + + else if (IsTemplatedSTLClass(name, "pair")) { + Utility::AddToClass(pyclass, "__getitem__", (PyCFunction)PairUnpack, + METH_O); + Utility::AddToClass(pyclass, "__len__", (PyCFunction)ReturnTwo, + METH_NOARGS); + } + + if (IsTemplatedSTLClass(name, "shared_ptr") || + IsTemplatedSTLClass(name, "unique_ptr")) { + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)SmartPtrInit, + METH_VARARGS | METH_KEYWORDS); + } + + else if (!((PyTypeObject*)pyclass)->tp_iter && + (name.find("iterator") != std::string::npos || + gIteratorTypes.find(name) != gIteratorTypes.end())) { + ((PyTypeObject*)pyclass)->tp_iternext = (iternextfunc)STLIterNext; + Utility::AddToClass(pyclass, CPPJIT__next__, (PyCFunction)STLIterNext, + METH_NOARGS); + ((PyTypeObject*)pyclass)->tp_iter = (getiterfunc)PyObject_SelfIter; + Utility::AddToClass(pyclass, "__iter__", (PyCFunction)PyObject_SelfIter, + METH_NOARGS); + } + + else if (name == "std::basic_string" || + name == "std::__1::basic_string" || // libc++ inline namespace + name == "std::string") { // typedef preserved by GetScopedFinalName + // on libc++ + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLStringRepr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLStringStr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLStringBytes, + METH_NOARGS); + Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLStringCompare, + METH_O); + Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLStringIsEqual, + METH_O); + Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLStringIsNotEqual, + METH_O); + // Python's `in` operator needs __contains__ on the proxy regardless of the + // C++ standard cpyrt is built with; it never dispatches to C++'s + // std::string::contains (C++23+). The old `__cplusplus <= 202302L` guard + // wrongly dropped it when built with -std=c++2c (__cplusplus == 202400L). + Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLStringContains, + METH_O); + Utility::AddToClass(pyclass, "decode", (PyCFunction)STLStringDecode, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__cpp_find", "find"); + Utility::AddToClass(pyclass, "find", (PyCFunction)STLStringFind, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__cpp_rfind", "rfind"); + Utility::AddToClass(pyclass, "rfind", (PyCFunction)STLStringRFind, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__cpp_replace", "replace"); + Utility::AddToClass(pyclass, "replace", (PyCFunction)STLStringReplace, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)STLStringGetAttr, + METH_O); + + // to allow use of std::string in dictionaries and findable with str + ((PyTypeObject*)pyclass)->tp_hash = (hashfunc)STLStringHash; + } + + else if (name == "std::basic_string_view" || + name == + "std::__1::basic_string_view" || // libc++ inline namespace + name == "std::string_view") { // typedef preserved by + // GetScopedFinalName on libc++ + Utility::AddToClass(pyclass, "__real_init", "__init__"); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, + METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLViewStringBytes, + METH_NOARGS); + Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLViewStringCompare, + METH_O); + Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLViewStringIsEqual, + METH_O); + Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLViewStringIsNotEqual, + METH_O); + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLViewStringRepr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, + METH_NOARGS); + } + + else if (name == "std::basic_string,std::" + "allocator >" || + name == "std::__1::basic_string,std::__1::allocator >" || + name == "std::wstring") { + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLWStringRepr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLWStringStr, + METH_NOARGS); + Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLWStringBytes, + METH_NOARGS); + Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLWStringCompare, + METH_O); + Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLWStringIsEqual, + METH_O); + Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLWStringIsNotEqual, + METH_O); + } + + else if (name == "complex" || name == "std::complex") { + Utility::AddToClass(pyclass, "__cpp_real", "real"); + PyObject_SetAttrString( + pyclass, "real", + PyDescr_NewGetSet((PyTypeObject*)pyclass, &ComplexDReal)); + Utility::AddToClass(pyclass, "__cpp_imag", "imag"); + PyObject_SetAttrString( + pyclass, "imag", + PyDescr_NewGetSet((PyTypeObject*)pyclass, &ComplexDImag)); + Utility::AddToClass(pyclass, "__complex__", (PyCFunction)ComplexDComplex, + METH_NOARGS); + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)ComplexRepr, + METH_NOARGS); + } + + else if (IsTemplatedSTLClass(name, "complex")) { + Utility::AddToClass(pyclass, "__cpp_real", "real"); + PyObject_SetAttrString( + pyclass, "real", + PyDescr_NewGetSet((PyTypeObject*)pyclass, &realComplex)); + Utility::AddToClass(pyclass, "__cpp_imag", "imag"); + PyObject_SetAttrString( + pyclass, "imag", + PyDescr_NewGetSet((PyTypeObject*)pyclass, &imagComplex)); + Utility::AddToClass(pyclass, "__complex__", (PyCFunction)ComplexComplex, + METH_NOARGS); + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)ComplexRepr, + METH_NOARGS); + } + + // direct user access; there are two calls here: + // - explicit pythonization: won't fall through to the base classes and is + // preferred if present + // - normal pythonization: only called if explicit isn't present, falls + // through to base classes + bool bUserOk = true; + PyObject* res = nullptr; + PyObject* pyname = cpyrt_PyText_FromString(name.c_str()); + if (HasAttrDirect(pyclass, PyStrings::gExPythonize)) { + res = PyObject_CallMethodObjArgs(pyclass, PyStrings::gExPythonize, pyclass, + pyname, nullptr); + bUserOk = (bool)res; + } else { + PyObject* func = PyObject_GetAttr(pyclass, PyStrings::gPythonize); + if (func) { + res = PyObject_CallFunctionObjArgs(func, pyclass, pyname, nullptr); + Py_DECREF(func); + bUserOk = (bool)res; + } else + PyErr_Clear(); + } + if (!bUserOk) { + Py_DECREF(pyname); + return false; + } else { + Py_XDECREF(res); + // pyname handed to args tuple below + } + + // call registered pythonizors, if any: first run the namespace-specific + // pythonizors, then the global ones (the idea is to allow writing a + // pythonizor that see all classes) + bool pstatus = true; + std::string outer_scope = TypeManip::extract_namespace(name); + auto& pyzMap = pythonizations(); + if (!outer_scope.empty()) { + auto p = pyzMap.find(outer_scope); + if (p != pyzMap.end()) { + PyObject* subname = cpyrt_PyText_FromString( + name.substr(outer_scope.size() + 2, std::string::npos).c_str()); + pstatus = run_pythonizors(pyclass, subname, p->second); + Py_DECREF(subname); + } + } + + if (pstatus) { + auto p = pyzMap.find(""); + if (p != pyzMap.end()) + pstatus = run_pythonizors(pyclass, pyname, p->second); + } + + Py_DECREF(pyname); + + // phew! all done ... + return pstatus; +} diff --git a/src/cpyrt/Pythonize.h b/src/cpyrt/Pythonize.h new file mode 100644 index 0000000..c033452 --- /dev/null +++ b/src/cpyrt/Pythonize.h @@ -0,0 +1,14 @@ +#ifndef CPYRT_PYTHONIZE_H +#define CPYRT_PYTHONIZE_H + +// Standard +#include + +namespace cppjit::cpyrt { + +// make the named C++ class more python-like +bool Pythonize(PyObject* pyclass, cppjit::interop::TCppScope_t scope); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_PYTHONIZE_H diff --git a/src/cpyrt/Reflex.h b/src/cpyrt/Reflex.h new file mode 100644 index 0000000..c232087 --- /dev/null +++ b/src/cpyrt/Reflex.h @@ -0,0 +1,30 @@ +#ifndef CPYRT_REFLEX_H +#define CPYRT_REFLEX_H + +// +// Access to the C++ reflection information +// + +namespace cppjit::interop { + +namespace Reflex { + +typedef int RequestId_t; + +const RequestId_t IS_NAMESPACE = 1; +const RequestId_t IS_AGGREGATE = 2; + +const RequestId_t OFFSET = 16; +const RequestId_t RETURN_TYPE = 17; +const RequestId_t TYPE = 18; + +typedef int FormatId_t; +const FormatId_t OPTIMAL = 1; +const FormatId_t AS_TYPE = 2; +const FormatId_t AS_STRING = 3; + +} // namespace Reflex + +} // namespace cppjit::interop + +#endif // !CPYRT_REFLEX_H diff --git a/src/cpyrt/SignalTryCatch.h b/src/cpyrt/SignalTryCatch.h new file mode 100644 index 0000000..3ab04d2 --- /dev/null +++ b/src/cpyrt/SignalTryCatch.h @@ -0,0 +1,84 @@ +// Partial reproduction of ROOT's TException.h + +/************************************************************************* + * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef CPYRT_SIGNALTRYCATCH_H +#define CPYRT_SIGNALTRYCATCH_H + +#include +#include "cpyrt/CommonDefs.h" + +#ifndef _WIN32 +#define NEED_SIGJMP 1 +#endif + +// By default, the ExceptionContext_t class is expected in the namespace +// CppyyLegacy, If it is expected in no namespace, one can explicitly define +// NO_CPPJIT_LEGACY_NAMESPACE at build time (e.g. if one wants to use ROOT). + +#ifndef NO_CPPJIT_LEGACY_NAMESPACE +namespace CppyyLegacy { +#endif +struct ExceptionContext_t { +#ifdef NEED_SIGJMP + sigjmp_buf fBuf; +#else + jmp_buf fBuf; +#endif +}; +#ifndef NO_CPPJIT_LEGACY_NAMESPACE +} + +using cppjit_interopExceptionContext_t = CppyyLegacy::ExceptionContext_t; +#else +using cppjit_interopExceptionContext_t = ExceptionContext_t; +#endif + +// FIXME: This is a dummy, replace with cling equivalent of gException +static cppjit_interopExceptionContext_t DummyException; +static cppjit_interopExceptionContext_t* gException = &DummyException; + +#ifdef NEED_SIGJMP +#define CLING_EXCEPTION_SETJMP(buf) sigsetjmp(buf, 1) +#else +#define CLING_EXCEPTION_SETJMP(buf) setjmp(buf) +#endif + +#define CLING_EXCEPTION_RETRY \ + { \ + static cppjit_interopExceptionContext_t R__curr, *R__old = gException; \ + int R__code; \ + gException = &R__curr; \ + R__code = CLING_EXCEPTION_SETJMP(gException->fBuf); \ + if (R__code) { \ + }; \ + { + +#define CLING_EXCEPTION_TRY \ + { \ + static cppjit_interopExceptionContext_t R__curr, *R__old = gException; \ + int R__code; \ + gException = &R__curr; \ + if ((R__code = CLING_EXCEPTION_SETJMP(gException->fBuf)) == 0) { + +#define CLING_EXCEPTION_CATCH(n) \ + gException = R__old; \ + } \ + else { \ + int n = R__code; \ + gException = R__old; + +#define CLING_EXCEPTION_ENDTRY \ + } \ + gException = R__old; \ + } + +CPYRT_IMPORT cppjit_interopExceptionContext_t* gException; + +#endif diff --git a/src/cpyrt/TemplateProxy.cxx b/src/cpyrt/TemplateProxy.cxx new file mode 100644 index 0000000..30c0694 --- /dev/null +++ b/src/cpyrt/TemplateProxy.cxx @@ -0,0 +1,959 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPClassMethod.h" +#include "CPPConstructor.h" +#include "CPPFunction.h" +#include "CPPMethod.h" +#include "CPPOverload.h" +#include "PyCallable.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "Utility.h" +#include "cppjit_interop.h" + +// Standard +#include + +namespace cppjit::cpyrt { + +static inline std::string targs2str(TemplateProxy* pytmpl) { + if (!pytmpl || !pytmpl->fTemplateArgs) + return ""; + return cpyrt_PyText_AsString(pytmpl->fTemplateArgs); +} + +//---------------------------------------------------------------------------- +TemplateInfo::TemplateInfo() + : fPyClass(nullptr), fNonTemplated(nullptr), fTemplated(nullptr), + fLowPriority(nullptr), fDoc(nullptr) { + /* empty */ +} + +//---------------------------------------------------------------------------- +TemplateInfo::~TemplateInfo() { + Py_XDECREF(fPyClass); + + Py_XDECREF(fDoc); + Py_DECREF(fNonTemplated); + Py_DECREF(fTemplated); + Py_DECREF(fLowPriority); + + for (const auto& p : fDispatchMap) { + for (const auto& c : p.second) { + Py_DECREF(c.second); + } + } +} + +//---------------------------------------------------------------------------- +void TemplateProxy::MergeOverload(CPPOverload* mp) { + // Store overloads of this templated method. + bool isGreedy = false; + for (auto pc : mp->fMethodInfo->fMethods) { + if (pc->IsGreedy()) { + isGreedy = true; + break; + } + } + + CPPOverload* cppol = isGreedy ? fTI->fLowPriority : fTI->fNonTemplated; + cppol->MergeOverload(mp); +} + +void TemplateProxy::AdoptMethod(PyCallable* pc) { + // Store overload of this templated method. + CPPOverload* cppol = pc->IsGreedy() ? fTI->fLowPriority : fTI->fNonTemplated; + cppol->AdoptMethod(pc); +} + +void TemplateProxy::AdoptTemplate(PyCallable* pc) { + // Store known template methods. + fTI->fTemplated->AdoptMethod(pc); +} + +//---------------------------------------------------------------------------- +PyObject* TemplateProxy::Instantiate(const std::string& fname, + cpyrt_PyArgs_t args, size_t nargsf, + Utility::ArgPreference pref, int* pcnt) { + // Instantiate (and cache) templated methods, return method if any + std::string proto = ""; + + // adjust arguments for self if this is a rebound global function + bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); + if (!isNS && cpyrt_PyArgs_GET_SIZE(args, nargsf) && + (!fSelf || (fSelf == Py_None && + !interop::IsStaticTemplate( + ((CPPScope*)fTI->fPyClass)->fCppType, fname)))) { + args += 1; + nargsf -= 1; + } + + Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf); + if (argc != 0) { + PyObject* tpArgs = PyTuple_New(argc); + for (Py_ssize_t i = 0; i < argc; ++i) { + PyObject* itemi = cpyrt_PyArgs_GET_ITEM(args, i); + + bool bArgSet = false; + + // special case for arrays + if (TemplateProxy_CheckExact(itemi)) { + TemplateProxy* tn = (TemplateProxy*)itemi; + PyObject* f = PyUnicode_FromFormat("%s%s", tn->fTI->fCppName.c_str(), + targs2str(tn).c_str()); + PyTuple_SET_ITEM(tpArgs, i, f); + bArgSet = true; + } + PyObject* pytc = nullptr; + if (!bArgSet && (pytc = PyObject_GetAttr(itemi, PyStrings::gTypeCode))) { + Py_buffer bufinfo; + memset(&bufinfo, 0, sizeof(Py_buffer)); + std::string ptrdef; + if (PyObject_GetBuffer(itemi, &bufinfo, PyBUF_FORMAT) == 0) { + for (int j = 0; j < bufinfo.ndim; ++j) + ptrdef += "*"; + PyBuffer_Release(&bufinfo); + } else { + ptrdef += "*"; + PyErr_Clear(); + } + + PyObject* pyptrname = Utility::CT2CppName(pytc, ptrdef.c_str(), true); + if (pyptrname) { + PyTuple_SET_ITEM(tpArgs, i, pyptrname); + bArgSet = true; + // string added, but not counted towards nStrings + } + Py_DECREF(pytc); + pytc = nullptr; + } else + PyErr_Clear(); + + // if not arg set, try special case for ctypes + if (!bArgSet) + pytc = PyObject_GetAttr(itemi, PyStrings::gCTypesType); + + if (!bArgSet && pytc) { + PyObject* pyactname = Utility::CT2CppName(pytc, "&", false); + if (!pyactname) { + // _type_ of a pointer to c_type is that type, which will have a type + PyObject* newpytc = PyObject_GetAttr(pytc, PyStrings::gCTypesType); + Py_DECREF(pytc); + pytc = newpytc; + if (pytc) { + pyactname = Utility::CT2CppName(pytc, "*", false); + } else + PyErr_Clear(); + } + Py_XDECREF(pytc); + pytc = nullptr; + if (pyactname) { + PyTuple_SET_ITEM(tpArgs, i, pyactname); + bArgSet = true; + // string added, but not counted towards nStrings + } + } else + PyErr_Clear(); + + if (!bArgSet) { + // normal case (may well fail) + PyErr_Clear(); + PyObject* tp = (PyObject*)Py_TYPE(itemi); + Py_INCREF(tp); + PyTuple_SET_ITEM(tpArgs, i, tp); + } + } + + PyObject* pyargs = PyTuple_New(argc); + for (Py_ssize_t i = 0; i < argc; ++i) { + PyObject* item = cpyrt_PyArgs_GET_ITEM(args, i); + Py_INCREF(item); + PyTuple_SET_ITEM(pyargs, i, item); + } + const std::string& name_v1 = + Utility::ConstructTemplateArgs(nullptr, tpArgs, pyargs, pref, 0, pcnt); + + Py_DECREF(pyargs); + Py_DECREF(tpArgs); + + // Propagate the error that occurs if we can't construct the C++ name + // from the provided template argument + if (PyErr_Occurred()) { + return nullptr; + } + + if (name_v1.size()) + proto = name_v1.substr(1, name_v1.size() - 2); + } + + // the following causes instantiation as necessary + interop::TCppScope_t scope = ((CPPClass*)fTI->fPyClass)->fCppType; + interop::TCppMethod_t cppmeth = + interop::GetMethodTemplate(scope, fname, proto); + if (cppmeth) { // overload stops here + // A successful instantiation needs to be cached to pre-empt future + // instantiations. There are two names involved, the original asked (which + // may be partial) and the received. + // + // Caching scheme: if the match is exact, simply add the overload to the + // pre-existing one, or create a new overload for later lookups. If the + // match is not exact, do the same, but also create an alias. Only add exact + // matches to the set of known template instantiations, to prevent piling on + // from different partial instantiations. + // + // TODO: this caches the lookup method before the call, meaning that failing + // overloads can add already existing overloads to the set of methods. + + std::string resname = + interop::GetFullName(interop::TCppScope_t(cppmeth.data)); + + // An initializer_list is preferred for the argument types, but should not + // leak into the argument types. If it did, replace with vector and lookup + // anew. + if (resname.find("initializer_list") != std::string::npos) { + auto pos = proto.find("initializer_list"); + while (pos != std::string::npos) { + proto.replace(pos, 16, "vector"); + pos = proto.find("initializer_list", pos + 6); + } + + interop::TCppMethod_t m2 = + interop::GetMethodTemplate(scope, fname, proto); + if (m2 && m2 != cppmeth) { + // replace if the new method with vector was found; otherwise just + // continue with the previously found method with initializer_list. + cppmeth = m2; + resname = interop::GetFullName(interop::TCppScope_t(cppmeth.data)); + } + } + + bool bExactMatch = fname == resname; + + // lookup on existing name in case this was an overload, not a caching, + // failure + PyObject* dct = PyObject_GetAttr(fTI->fPyClass, PyStrings::gDict); + PyObject* pycachename = cpyrt_PyText_InternFromString(fname.c_str()); + PyObject* pyol = PyObject_GetItem(dct, pycachename); + if (!pyol) + PyErr_Clear(); + bool bIsCppOL = CPPOverload_Check(pyol); + bool bIsCppTP = TemplateProxy_Check(pyol); + + // find the full name if the requested one was partial + PyObject* exact = nullptr; + PyObject* pyresname = cpyrt_PyText_FromString(resname.c_str()); + if (!bExactMatch) { + exact = PyObject_GetItem(dct, pyresname); + if (!exact) + PyErr_Clear(); + } + Py_DECREF(dct); + + bool bIsConstructor = false, bNeedsRebind = true; + + PyCallable* meth = nullptr; + if (interop::IsNamespace(scope)) { + meth = new CPPFunction(scope, cppmeth); + bNeedsRebind = false; + } else if (interop::IsStaticMethod(cppmeth)) { + meth = new CPPClassMethod(scope, cppmeth); + bNeedsRebind = false; + } else if (interop::IsConstructor(cppmeth)) { + bIsConstructor = true; + meth = new CPPConstructor(scope, cppmeth); + } else + meth = new CPPMethod(scope, cppmeth); + + // Case 1/2: method simply did not exist before + if (!pyol) { + // actual overload to use (now owns meth) + pyol = (PyObject*)CPPOverload_New(fname, meth); + if (bIsConstructor) { + // TODO: this is an ugly hack :( + ((CPPOverload*)pyol)->fMethodInfo->fFlags |= + CallContext::kIsCreator | CallContext::kIsConstructor; + } + + // add to class dictionary + PyType_Type.tp_setattro(fTI->fPyClass, pycachename, pyol); + } + + // Case 3/4: pre-existing method that was either not found b/c the full + // templated name was constructed in this call or it failed as overload + else if (bIsCppOL) { + // TODO: see above, since the call hasn't happened yet, this overload may + // already exist and fail again. + ((CPPOverload*)pyol)->AdoptMethod(meth); // takes ownership + } + + // Case 5: must be a template proxy, meaning that current template name is + // not a template overload + else if (bIsCppTP) { + ((TemplateProxy*)pyol)->AdoptTemplate(meth->Clone()); + Py_DECREF(pyol); + pyol = (PyObject*)CPPOverload_New(fname, meth); // takes ownership + } + // Case 6: pre-existing object is not a CPPOverload nor TemplateProxy + // we do not cache it, as this might be a pythonization (monkey-patched + // func/method) + else { + Py_DECREF(pyol); + pyol = (PyObject*)CPPOverload_New(fname, meth); + } + + // Special Case if name was aliased (e.g. typedef in template instantiation) + if (!exact && !bExactMatch) { + PyType_Type.tp_setattro(fTI->fPyClass, pyresname, pyol); + } + + // cleanup + Py_XDECREF(exact); + Py_DECREF(pyresname); + Py_DECREF(pycachename); + + // retrieve fresh (for boundedness) and call + PyObject* pymeth = CPPOverload_Type.tp_descr_get( + pyol, bNeedsRebind ? fSelf : nullptr, (PyObject*)&CPPOverload_Type); + Py_DECREF(pyol); + return pymeth; + } + + PyErr_Format(PyExc_TypeError, "Failed to instantiate \"%s(%s)\"", + fname.c_str(), proto.c_str()); + return nullptr; +} + +//= cpyrt template proxy construction/destruction ========================= +static TemplateProxy* tpp_new(PyTypeObject*, PyObject*, PyObject*) { + // Create a new empty template method proxy. + TemplateProxy* pytmpl = PyObject_GC_New(TemplateProxy, &TemplateProxy_Type); + pytmpl->fSelf = nullptr; + pytmpl->fTemplateArgs = nullptr; + pytmpl->fWeakrefList = nullptr; + new (&pytmpl->fTI) TP_TInfo_t{}; + pytmpl->fTI = std::make_shared(); + + PyObject_GC_Track(pytmpl); + return pytmpl; +} + +//---------------------------------------------------------------------------- +static Py_hash_t tpp_hash(TemplateProxy* self) { return (Py_hash_t)self; } + +//---------------------------------------------------------------------------- +static PyObject* tpp_richcompare(TemplateProxy* self, PyObject* other, int op) { + if (op == Py_EQ || op == Py_NE) { + if (!TemplateProxy_CheckExact(other)) + Py_RETURN_FALSE; + + if (self->fTI == ((TemplateProxy*)other)->fTI) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; + } + + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; +} + +//---------------------------------------------------------------------------- +static int tpp_clear(TemplateProxy* pytmpl) { + // Garbage collector clear of held python member objects. + Py_CLEAR(pytmpl->fSelf); + Py_CLEAR(pytmpl->fTemplateArgs); + + return 0; +} + +//---------------------------------------------------------------------------- +static void tpp_dealloc(TemplateProxy* pytmpl) { + // Destroy the given template method proxy. + if (pytmpl->fWeakrefList) + PyObject_ClearWeakRefs((PyObject*)pytmpl); + PyObject_GC_UnTrack(pytmpl); + tpp_clear(pytmpl); + pytmpl->fTI.~TP_TInfo_t(); + PyObject_GC_Del(pytmpl); +} + +//---------------------------------------------------------------------------- +static int tpp_traverse(TemplateProxy* pytmpl, visitproc visit, void* arg) { + // Garbage collector traverse of held python member objects. + Py_VISIT(pytmpl->fSelf); + Py_VISIT(pytmpl->fTemplateArgs); + + return 0; +} + +//---------------------------------------------------------------------------- +static PyObject* tpp_doc(TemplateProxy* pytmpl, void*) { + if (pytmpl->fTI->fDoc) { + Py_INCREF(pytmpl->fTI->fDoc); + return pytmpl->fTI->fDoc; + } + + // Forward to method proxies to doc all overloads + PyObject* doc = nullptr; + if (pytmpl->fTI->fNonTemplated->HasMethods()) + doc = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fNonTemplated, + "__doc__"); + if (pytmpl->fTI->fTemplated->HasMethods()) { + PyObject* doc2 = + PyObject_GetAttrString((PyObject*)pytmpl->fTI->fTemplated, "__doc__"); + if (doc && doc2) { + cpyrt_PyText_AppendAndDel(&doc, cpyrt_PyText_FromString("\n")); + cpyrt_PyText_AppendAndDel(&doc, doc2); + } else if (!doc && doc2) { + doc = doc2; + } + } + if (pytmpl->fTI->fLowPriority->HasMethods()) { + PyObject* doc2 = + PyObject_GetAttrString((PyObject*)pytmpl->fTI->fLowPriority, "__doc__"); + if (doc && doc2) { + cpyrt_PyText_AppendAndDel(&doc, cpyrt_PyText_FromString("\n")); + cpyrt_PyText_AppendAndDel(&doc, doc2); + } else if (!doc && doc2) { + doc = doc2; + } + } + + if (doc) + return doc; + + return cpyrt_PyText_FromString(TemplateProxy_Type.tp_doc); +} + +static int tpp_doc_set(TemplateProxy* pytmpl, PyObject* val, void*) { + Py_XDECREF(pytmpl->fTI->fDoc); + Py_INCREF(val); + pytmpl->fTI->fDoc = val; + return 0; +} + +//---------------------------------------------------------------------------- + +//= cpyrt template proxy callable behavior ================================ + +#define TPPCALL_RETURN \ + { \ + errors.clear(); \ + return result; \ + } + +static inline void UpdateDispatchMap(TemplateProxy* pytmpl, bool use_targs, + uint64_t sighash, CPPOverload* pymeth) { + // Memoize a method in the dispatch map after successful call; replace old if + // need be (may be with the same CPPOverload, just with more methods). + bool bInserted = false; + auto& v = pytmpl->fTI->fDispatchMap[use_targs ? targs2str(pytmpl) : ""]; + + Py_INCREF(pymeth); + for (auto& p : v) { + if (p.first == sighash) { + Py_DECREF(p.second); + p.second = pymeth; + bInserted = true; + } + } + if (!bInserted) + v.push_back(std::make_pair(sighash, pymeth)); +} + +static inline PyObject* +SelectAndForward(TemplateProxy* pytmpl, CPPOverload* pymeth, + cpyrt_PyArgs_t args, size_t nargsf, PyObject* kwds, + bool implicitOkay, bool use_targs, uint64_t sighash, + std::vector& errors) { + // Forward a call to known overloads, if any. + if (pymeth->HasMethods()) { + PyObject* pycall = CPPOverload_Type.tp_descr_get( + (PyObject*)pymeth, pytmpl->fSelf, (PyObject*)&CPPOverload_Type); + + if (!implicitOkay) + ((CPPOverload*)pycall)->fFlags |= CallContext::kNoImplicit; + + // now call the method with the arguments (loops internally) + PyObject* result = cpyrt_tp_call(pycall, args, nargsf, kwds); + Py_DECREF(pycall); + if (result) { + UpdateDispatchMap(pytmpl, use_targs, sighash, pymeth); + TPPCALL_RETURN; + } + Utility::FetchError(errors); + } + + return nullptr; +} + +static inline PyObject* CallMethodImp(TemplateProxy* pytmpl, PyObject*& pymeth, + cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds, bool impOK, + uint64_t sighash) { + // Actual call of a given overload: takes care of handlign of "self" and + // dereferences the overloaded method after use. + + PyObject* result; + if (!impOK && CPPOverload_Check(pymeth)) + ((CPPOverload*)pymeth)->fFlags |= CallContext::kNoImplicit; + bool isNS = + (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); + if (isNS && pytmpl->fSelf && pytmpl->fSelf != Py_None) { + // this is a global method added a posteriori to the class + PyCallArgs cargs{(CPPInstance*&)pytmpl->fSelf, args, nargsf, kwds}; + AdjustSelf(cargs); + result = cpyrt_tp_call(pymeth, cargs.fArgs, cargs.fNArgsf, cargs.fKwds); + } else { + if (!pytmpl->fSelf && CPPOverload_Check(pymeth)) + ((CPPOverload*)pymeth)->fFlags &= ~CallContext::kFromDescr; + result = cpyrt_tp_call(pymeth, args, nargsf, kwds); + } + + if (result) { + Py_XDECREF(((CPPOverload*)pymeth)->fSelf); + ((CPPOverload*)pymeth)->fSelf = nullptr; // unbind + UpdateDispatchMap(pytmpl, true, sighash, (CPPOverload*)pymeth); + } + + Py_DECREF(pymeth); + pymeth = nullptr; + return result; +} + +static PyObject* tpp_vectorcall(TemplateProxy* pytmpl, PyObject* const* args, + size_t nargsf, PyObject* kwds) { + // Dispatcher to the actual member method, several uses possible; in order: + // + // case 1: explicit template previously selected through subscript + // + // case 2: select known non-template overload + // + // obj.method(a0, a1, ...) + // => obj->method(a0, a1, ...) // non-template + // + // case 3: select known template overload + // + // obj.method(a0, a1, ...) + // => obj->method(a0, a1, ...) // all known templates + // + // case 4: auto-instantiation from types of arguments + // + // obj.method(a0, a1, ...) + // => obj->method(a0, a1, ...) + // + // Note: explicit instantiation needs to use [] syntax: + // + // obj.method[type, type, ...](a0, a1, ...) + // + // case 5: low priority methods, such as ones that take void* arguments + // + + // TODO: should previously instantiated templates be considered first? + + PyObject *pymeth = nullptr, *result = nullptr; + + // short-cut through memoization map + Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf); + uint64_t sighash = HashSignature(args, argc); + + CPPOverload* ol = nullptr; + if (!pytmpl->fTemplateArgs) { + // look for known signatures ... + auto& v = pytmpl->fTI->fDispatchMap[""]; + for (const auto& p : v) { + if (p.first == sighash) { + ol = p.second; + break; + } + } + + if (ol != nullptr) { + if (!pytmpl->fSelf || pytmpl->fSelf == Py_None) { + result = cpyrt_tp_call((PyObject*)ol, args, nargsf, kwds); + } else { + pymeth = CPPOverload_Type.tp_descr_get((PyObject*)ol, pytmpl->fSelf, + (PyObject*)&CPPOverload_Type); + result = cpyrt_tp_call(pymeth, args, nargsf, kwds); + Py_DECREF(pymeth); + pymeth = nullptr; + } + if (result) + return result; + } + } + + // container for collecting errors + std::vector errors; + if (ol) + Utility::FetchError(errors); + + // case 1: explicit template previously selected through subscript + if (pytmpl->fTemplateArgs) { + // instantiate explicitly + PyObject* pyfullname = + cpyrt_PyText_FromString(pytmpl->fTI->fCppName.c_str()); + cpyrt_PyText_Append(&pyfullname, pytmpl->fTemplateArgs); + + // first, lookup by full name, if previously stored + bool isNS = + (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); + if (pytmpl->fSelf && pytmpl->fSelf != Py_None && !isNS) + pymeth = PyObject_GetAttr(pytmpl->fSelf, pyfullname); + else // by-passes custom scope getattr that searches into Cling + pymeth = PyType_Type.tp_getattro(pytmpl->fTI->fPyClass, pyfullname); + + // attempt call if found (this may fail if there are specializations) + if (CPPOverload_Check(pymeth)) { + // since the template args are fully explicit, allow implicit conversion + // of arguments + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, + sighash); + if (result) { + Py_DECREF(pyfullname); + TPPCALL_RETURN; + } + Utility::FetchError(errors); + } else if (pymeth && PyCallable_Check(pymeth)) { + // something different (user provided?) + result = cpyrt_PyObject_Call(pymeth, args, nargsf, kwds); + Py_DECREF(pymeth); + if (result) { + Py_DECREF(pyfullname); + TPPCALL_RETURN; + } + Utility::FetchError(errors); + } else if (!pymeth) + PyErr_Clear(); + + // not cached or failed call; try instantiation + pymeth = pytmpl->Instantiate(cpyrt_PyText_AsString(pyfullname), args, + nargsf, Utility::kNone); + if (pymeth) { + // attempt actual call; same as above, allow implicit conversion of + // arguments + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, + sighash); + if (result) { + Py_DECREF(pyfullname); + TPPCALL_RETURN; + } + } + + // no drop through if failed (if implicit was desired, don't provide + // template args) + Utility::FetchError(errors); + PyObject* topmsg = cpyrt_PyText_FromFormat( + "Could not find \"%s\" (set cppjit.set_debug() for C++ errors):", + cpyrt_PyText_AsString(pyfullname)); + Py_DECREF(pyfullname); + Utility::SetDetailedException(std::move(errors), topmsg /* steals */, + PyExc_TypeError /* default error */); + + return nullptr; + } + + // case 2: select known non-template overload + result = SelectAndForward(pytmpl, pytmpl->fTI->fNonTemplated, args, nargsf, + kwds, true /* implicitOkay */, + false /* use_targs */, sighash, errors); + if (result) + TPPCALL_RETURN; + + // case 3: select known template overload + result = SelectAndForward(pytmpl, pytmpl->fTI->fTemplated, args, nargsf, kwds, + false /* implicitOkay */, true /* use_targs */, + sighash, errors); + if (result) + TPPCALL_RETURN; + + // case 4: auto-instantiation from types of arguments + for (auto pref : {Utility::kReference, Utility::kPointer, Utility::kValue}) { + // TODO: no need to loop if there are no non-instance arguments; also, + // should any failed lookup be removed? + int pcnt = 0; + pymeth = + pytmpl->Instantiate(pytmpl->fTI->fCppName, args, nargsf, pref, &pcnt); + if (pymeth) { + // attempt actual call; even if argument based, allow implicit + // conversions, for example for non-template arguments + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, + sighash); + if (result) + TPPCALL_RETURN; + } + Utility::FetchError(errors); + if (!pcnt) + break; // preference never used; no point trying others + } + + // case 5: low priority methods, such as ones that take void* arguments + result = SelectAndForward(pytmpl, pytmpl->fTI->fLowPriority, args, nargsf, + kwds, false /* implicitOkay */, + false /* use_targs */, sighash, errors); + if (result) + TPPCALL_RETURN; + + // error reporting is fraud, given the numerous steps taken, but more details + // seems better + if (!errors.empty()) { + PyObject* topmsg = + cpyrt_PyText_FromString("Template method resolution failed:"); + Utility::SetDetailedException(std::move(errors), topmsg /* steals */, + PyExc_TypeError /* default error */); + } else { + PyErr_Format(PyExc_TypeError, + "cannot resolve method template call for \'%s\'", + pytmpl->fTI->fCppName.c_str()); + } + + return nullptr; +} + +//---------------------------------------------------------------------------- +static TemplateProxy* tpp_descr_get(TemplateProxy* pytmpl, PyObject* pyobj, + PyObject*) { + // create and use a new template proxy (language requirement) + TemplateProxy* newPyTmpl = + (TemplateProxy*)TemplateProxy_Type.tp_alloc(&TemplateProxy_Type, 0); + + // new method is to be bound to current object (may be nullptr) + if (pyobj) { + Py_INCREF(pyobj); + newPyTmpl->fSelf = pyobj; + } else { + Py_INCREF(Py_None); + newPyTmpl->fSelf = Py_None; + } + + Py_XINCREF(pytmpl->fTemplateArgs); + newPyTmpl->fTemplateArgs = pytmpl->fTemplateArgs; + + // copy name, class, etc. pointers + new (&newPyTmpl->fTI) std::shared_ptr{pytmpl->fTI}; + + newPyTmpl->fVectorCall = pytmpl->fVectorCall; + + return newPyTmpl; +} + +//---------------------------------------------------------------------------- +static PyObject* tpp_subscript(TemplateProxy* pytmpl, PyObject* args) { + // Explicit template member lookup/instantiation; works by re-bounding. This + // method can not cache overloads as instantiations need not be unique for the + // argument types due to template specializations. + TemplateProxy* typeBoundMethod = + tpp_descr_get(pytmpl, pytmpl->fSelf, nullptr); + Py_XDECREF(typeBoundMethod->fTemplateArgs); + typeBoundMethod->fTemplateArgs = cpyrt_PyText_FromString( + Utility::ConstructTemplateArgs(nullptr, args).c_str()); + // Propagate the error that occurs if we can't construct the C++ name + // from the provided template argument + if (PyErr_Occurred()) { + return nullptr; + } + return (PyObject*)typeBoundMethod; +} + +//----------------------------------------------------------------------------- +static PyObject* tpp_getuseffi(CPPOverload*, void*) { + return PyInt_FromLong(0); // dummy (__useffi__ unused) +} + +//----------------------------------------------------------------------------- +static int tpp_setuseffi(CPPOverload*, PyObject*, void*) { + return 0; // dummy (__useffi__ unused) +} + +//---------------------------------------------------------------------------- +static PyMappingMethods tpp_as_mapping = {nullptr, (binaryfunc)tpp_subscript, + nullptr}; + +static PyGetSetDef tpp_getset[] = { + {(char*)"__doc__", (getter)tpp_doc, (setter)tpp_doc_set, nullptr, nullptr}, + {(char*)"__useffi__", (getter)tpp_getuseffi, (setter)tpp_setuseffi, + (char*)"unused", nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +//---------------------------------------------------------------------------- +void TemplateProxy::Set(const std::string& cppname, const std::string& pyname, + PyObject* pyclass) { + // Initialize the proxy for the given 'pyclass.' + fSelf = nullptr; + fTemplateArgs = nullptr; + + fTI->fCppName = cppname; + Py_XINCREF(pyclass); + fTI->fPyClass = pyclass; + + std::vector dummy; + fTI->fNonTemplated = CPPOverload_New(pyname, dummy); + fTI->fTemplated = CPPOverload_New(pyname, dummy); + fTI->fLowPriority = CPPOverload_New(pyname, dummy); + + fVectorCall = (vectorcallfunc)tpp_vectorcall; +} + +//= cpyrt method proxy access to internals ================================ +static PyObject* tpp_overload(TemplateProxy* pytmpl, PyObject* args) { + // Select and call a specific C++ overload, based on its signature. + const char* sigarg = nullptr; + PyObject* sigarg_tuple = nullptr; + int want_const = -1; + + interop::TCppScope_t scope = nullptr; + interop::TCppMethod_t cppmeth = nullptr; + std::string proto; + + if (PyArg_ParseTuple(args, const_cast("s|i:__overload__"), &sigarg, + &want_const)) { + want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; + + // check existing overloads in order + PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg, want_const); + if (ol) + return ol; + PyErr_Clear(); + ol = pytmpl->fTI->fTemplated->FindOverload(sigarg, want_const); + if (ol) + return ol; + PyErr_Clear(); + ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg, want_const); + if (ol) + return ol; + + proto = Utility::ConstructTemplateArgs(nullptr, args); + // Propagate the error that occurs if we can't construct the C++ name + // from the provided template argument + if (PyErr_Occurred()) { + return nullptr; + } + + scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; + cppmeth = interop::GetMethodTemplate(scope, pytmpl->fTI->fCppName, + proto.substr(1, proto.size() - 2)); + } else if (PyArg_ParseTuple(args, const_cast("O|i:__overload__"), + &sigarg_tuple, &want_const)) { + PyErr_Clear(); + want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; + + // check existing overloads in order + PyObject* ol = + pytmpl->fTI->fNonTemplated->FindOverload(sigarg_tuple, want_const); + if (ol) + return ol; + PyErr_Clear(); + ol = pytmpl->fTI->fTemplated->FindOverload(sigarg_tuple, want_const); + if (ol) + return ol; + PyErr_Clear(); + ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg_tuple, want_const); + if (ol) + return ol; + + proto.reserve(128); + proto.push_back('<'); + Py_ssize_t n = PyTuple_Size(sigarg_tuple); + for (int i = 0; i < n; i++) { + PyObject* pItem = PyTuple_GetItem(sigarg_tuple, i); + if (!cpyrt_PyText_Check(pItem)) { + PyErr_Format(PyExc_LookupError, + "argument types should be in string format"); + return (PyObject*)nullptr; + } + proto.append(cpyrt_PyText_AsString(pItem)); + if (i < n - 1) + proto.push_back(','); + } + proto.push_back('>'); + + scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; + cppmeth = interop::GetMethodTemplate(scope, pytmpl->fTI->fCppName, + proto.substr(1, proto.size() - 2)); + } else { + PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__"); + return nullptr; + } + + // else attempt instantiation + if (!cppmeth) { + return nullptr; + } + + PyErr_Clear(); + + // TODO: the next step should be consolidated with Instantiate() + PyCallable* meth = nullptr; + if (interop::IsNamespace(scope)) { + meth = new CPPFunction(scope, cppmeth); + } else if (interop::IsStaticMethod(cppmeth)) { + meth = new CPPClassMethod(scope, cppmeth); + } else if (interop::IsConstructor(cppmeth)) { + meth = new CPPConstructor(scope, cppmeth); + } else + meth = new CPPMethod(scope, cppmeth); + + return (PyObject*)CPPOverload_New(pytmpl->fTI->fCppName + proto, meth); +} + +static PyMethodDef tpp_methods[] = {{(char*)"__overload__", + (PyCFunction)tpp_overload, METH_VARARGS, + (char*)"select overload for dispatch"}, + {(char*)nullptr, nullptr, 0, nullptr}}; + +//= cpyrt template proxy type ============================================= +PyTypeObject TemplateProxy_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.TemplateProxy", // tp_name + sizeof(TemplateProxy), // tp_basicsize + 0, // tp_itemsize + (destructor)tpp_dealloc, // tp_dealloc + offsetof(TemplateProxy, fVectorCall), + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / tp_compare + 0, // tp_repr + 0, // tp_as_number + 0, // tp_as_sequence + &tpp_as_mapping, // tp_as_mapping + (hashfunc)tpp_hash, // tp_hash + (ternaryfunc)PyVectorcall_Call, // tp_call + 0, // tp_str + 0, // tp_getattro + 0, // tp_setattro + 0, // tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VECTORCALL | + Py_TPFLAGS_METHOD_DESCRIPTOR, // tp_flags + (char*)"cppjit template proxy (internal)", // tp_doc + (traverseproc)tpp_traverse, // tp_traverse + (inquiry)tpp_clear, // tp_clear + (richcmpfunc)tpp_richcompare, // tp_richcompare + offsetof(TemplateProxy, fWeakrefList), // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + tpp_methods, // tp_methods + 0, // tp_members + tpp_getset, // tp_getset + 0, // tp_base + 0, // tp_dict + (descrgetfunc)tpp_descr_get, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + 0, // tp_init + 0, // tp_alloc + (newfunc)tpp_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 + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/src/cpyrt/TemplateProxy.h b/src/cpyrt/TemplateProxy.h new file mode 100644 index 0000000..ff9ad99 --- /dev/null +++ b/src/cpyrt/TemplateProxy.h @@ -0,0 +1,101 @@ +#ifndef CPYRT_TEMPLATEPROXY_H +#define CPYRT_TEMPLATEPROXY_H + +// Bindings +#include "CPPScope.h" +#include "Utility.h" + +// Standard +#include +#include +#include +#include +#include + +namespace cppjit::cpyrt { + +class PyCallable; +class CPPOverload; + +/** Template proxy object to return functions and methods + */ + +typedef std::pair TP_DispatchEntry_t; +typedef std::unordered_map> + TP_DispatchMap_t; + +class TemplateInfo { +public: + TemplateInfo(); + TemplateInfo(const TemplateInfo&) = delete; + TemplateInfo& operator=(const TemplateInfo&) = delete; + ~TemplateInfo(); + +public: + std::string fCppName; + PyObject* fPyClass; + CPPOverload* fNonTemplated; // holder for non-template overloads + CPPOverload* fTemplated; // holder for templated overloads + CPPOverload* fLowPriority; // low priority overloads such as void*/void** + + TP_DispatchMap_t fDispatchMap; + PyObject* fDoc; +}; + +typedef std::shared_ptr TP_TInfo_t; + +class TemplateProxy { +private: + friend TemplateProxy* TemplateProxy_New(const std::string& cppname, + const std::string& pyname, + PyObject* pyclass); + void Set(const std::string& cppname, const std::string& pyname, + PyObject* pyclass); + +public: // public, as the python C-API works with C structs + PyObject_HEAD PyObject* fSelf; // must be first (same layout as CPPOverload) + PyObject* fTemplateArgs; + PyObject* fWeakrefList; + vectorcallfunc fVectorCall; + TP_TInfo_t fTI; + +public: + void MergeOverload(CPPOverload* mp); + void AdoptMethod(PyCallable* pc); + void AdoptTemplate(PyCallable* pc); + PyObject* Instantiate(const std::string& fname, cpyrt_PyArgs_t tmplArgs, + size_t nargsf, Utility::ArgPreference, + int* pcnt = nullptr); + +private: // private, as the python C-API will handle creation + TemplateProxy() = delete; +}; + +//- template proxy type and type verification -------------------------------- +extern PyTypeObject TemplateProxy_Type; + +template inline bool TemplateProxy_Check(T* object) { + return object && PyObject_TypeCheck(object, &TemplateProxy_Type); +} + +template inline bool TemplateProxy_CheckExact(T* object) { + return object && Py_TYPE(object) == &TemplateProxy_Type; +} + +//- creation ----------------------------------------------------------------- +inline TemplateProxy* TemplateProxy_New(const std::string& cppname, + const std::string& pyname, + PyObject* pyclass) { + // Create and initialize a new template method proxy for the class. + if (!CPPScope_Check(pyclass)) + return nullptr; + + TemplateProxy* pytmpl = (TemplateProxy*)TemplateProxy_Type.tp_new( + &TemplateProxy_Type, nullptr, nullptr); + pytmpl->Set(cppname, pyname, pyclass); + return pytmpl; +} + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_TEMPLATEPROXY_H diff --git a/src/cpyrt/TupleOfInstances.cxx b/src/cpyrt/TupleOfInstances.cxx new file mode 100644 index 0000000..624c449 --- /dev/null +++ b/src/cpyrt/TupleOfInstances.cxx @@ -0,0 +1,266 @@ +#include "Python.h" +#include "cppjit_interop.h" + +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "ProxyWrappers.h" +#include "TupleOfInstances.h" + +namespace { + +typedef struct { + PyObject_HEAD interop::TCppScope_t ia_klass; + void* ia_array_start; + Py_ssize_t ia_pos; + Py_ssize_t ia_len; + Py_ssize_t ia_stride; +} ia_iterobject; + +static PyObject* ia_iternext(ia_iterobject* ia) { + if (ia->ia_len != (Py_ssize_t)-1 && ia->ia_pos >= ia->ia_len) { + ia->ia_pos = 0; // debatable, but since the iterator is cached, this + return nullptr; // allows for multiple conversions to e.g. a tuple + } else if (ia->ia_stride == 0 && ia->ia_pos != 0) { + PyErr_SetString(PyExc_ReferenceError, "no stride available for indexing"); + return nullptr; + } + PyObject* result = cpyrt::BindCppObjectNoCast( + (char*)ia->ia_array_start + ia->ia_pos * ia->ia_stride, ia->ia_klass); + ia->ia_pos += 1; + return result; +} + +static int ia_traverse(ia_iterobject*, visitproc, void*) { return 0; } + +static PyObject* ia_getsize(ia_iterobject* ia, void*) { + return PyInt_FromSsize_t(ia->ia_len); +} + +static int ia_setsize(ia_iterobject* ia, PyObject* pysize, void*) { + Py_ssize_t size = PyInt_AsSsize_t(pysize); + if (size == (Py_ssize_t)-1 && PyErr_Occurred()) + return -1; + ia->ia_len = size; + return 0; +} + +static PyGetSetDef ia_getset[] = { + {(char*)"size", (getter)ia_getsize, (setter)ia_setsize, + (char*)"set size of array to which this iterator refers", nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}}; + +static Py_ssize_t ia_length(ia_iterobject* ia) { return ia->ia_len; } + +static PyObject* ia_subscript(ia_iterobject* ia, PyObject* pyidx) { + // Subscripting the iterator allows direct access through indexing on arrays + // that do not have a defined length. This way, the return from accessing such + // an array as a data member can both be used in a loop and directly. + Py_ssize_t idx = PyInt_AsSsize_t(pyidx); + if (idx == (Py_ssize_t)-1 && PyErr_Occurred()) + return nullptr; + + if (ia->ia_len != (Py_ssize_t)-1 && (idx < 0 || ia->ia_len <= idx)) { + PyErr_SetString(PyExc_IndexError, "index out of range"); + return nullptr; + } + + return cpyrt::BindCppObjectNoCast( + (char*)ia->ia_array_start + ia->ia_pos * ia->ia_stride, ia->ia_klass); +} + +static PyMappingMethods ia_as_mapping = { + (lenfunc)ia_length, // mp_length + (binaryfunc)ia_subscript, // mp_subscript + (objobjargproc) nullptr, // mp_ass_subscript +}; + +} // unnamed namespace + +namespace cppjit::cpyrt { + +PyTypeObject InstanceArrayIter_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.instancearrayiter", // tp_name + sizeof(ia_iterobject), // tp_basicsize + 0, + (destructor)PyObject_GC_Del, // tp_dealloc + 0, + 0, + 0, + 0, + 0, + 0, + 0, + &ia_as_mapping, // tp_as_mapping + 0, + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags + 0, + (traverseproc)ia_traverse, // tp_traverse + 0, + 0, + 0, + PyObject_SelfIter, // tp_iter + (iternextfunc)ia_iternext, // tp_iternext + 0, + 0, + ia_getset, // tp_getset + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +//= support for C-style arrays of objects ==================================== +PyObject* TupleOfInstances_New(interop::TCppObject_t address, + interop::TCppScope_t klass, cdims_t dims) { + // recursively set up tuples of instances on all dimensions + if (dims.ndim() == UNKNOWN_SIZE || + dims[0] == UNKNOWN_SIZE /* unknown shape or size */) { + // no known length ... return an iterable object and let the user figure it + // out + ia_iterobject* ia = PyObject_GC_New(ia_iterobject, &InstanceArrayIter_Type); + if (!ia) + return nullptr; + + ia->ia_klass = klass; + ia->ia_array_start = address.data; + ia->ia_pos = 0; + ia->ia_len = -1; + ia->ia_stride = interop::SizeOf(klass); + + PyObject_GC_Track(ia); + return (PyObject*)ia; + } else if (1 < dims.ndim()) { + // not the innermost dimension, descend one level + size_t block_size = 1; + for (Py_ssize_t i = 1; i < dims.ndim(); ++i) { + if (dims[i] != 0) + block_size *= (size_t)dims[i]; + } + block_size *= interop::SizeOf(klass); + + Py_ssize_t nelems = dims[0]; + PyObject* tup = PyTuple_New(nelems); + for (Py_ssize_t i = 0; i < nelems; ++i) { + PyTuple_SetItem( + tup, i, + TupleOfInstances_New(((char*)address.data) + i * block_size, klass, + dims.sub())); + } + return tup; + } else { + // innermost dimension: construct tuple + int nelems = (int)dims[0]; + size_t block_size = interop::SizeOf(klass); + if (block_size == 0) { + PyErr_Format(PyExc_TypeError, + "can not determine size of type \"%s\" for array indexing", + interop::GetScopedFinalName(klass).c_str()); + return nullptr; + } + + // TODO: the extra copy is inefficient, but it appears that the only way to + // initialize a subclass of a tuple is through a sequence + PyObject* tup = PyTuple_New(nelems); + for (int i = 0; i < nelems; ++i) { + // TODO: there's an assumption here that there is no padding, which is + // bound to be incorrect in certain cases + PyTuple_SetItem( + tup, i, + BindCppObjectNoCast(((char*)address.data) + i * block_size, klass)); + // Note: objects are bound as pointers, yet since the pointer value stays + // in place, updates propagate just as if they were bound by-reference + } + + PyObject* args = PyTuple_New(1); + Py_INCREF(tup); + PyTuple_SET_ITEM(args, 0, tup); + PyObject* arr = PyTuple_Type.tp_new(&TupleOfInstances_Type, args, nullptr); + + Py_DECREF(args); + // tup ref eaten by SET_ITEM on args + + return arr; + } + + // never get here + return nullptr; +} + +//= cpyrt custom tuple-like array type ==================================== +PyTypeObject TupleOfInstances_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, + 0)(char*) "cppjit.InstancesArray", // tp_name + 0, // tp_basicsize + 0, // tp_itemsize + 0, // tp_dealloc + 0, // tp_vectorcall_offset / tp_print + 0, // tp_getattr + 0, // tp_setattr + 0, // tp_as_async / 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_CHECKTYPES | + Py_TPFLAGS_BASETYPE, // tp_flags + (char*)"array of C++ instances", // 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 + &PyTuple_Type, // 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 + CPYRT_PYTYPE_TAIL}; + +} // namespace cppjit::cpyrt diff --git a/src/cpyrt/TupleOfInstances.h b/src/cpyrt/TupleOfInstances.h new file mode 100644 index 0000000..47f57b0 --- /dev/null +++ b/src/cpyrt/TupleOfInstances.h @@ -0,0 +1,33 @@ +#ifndef CPYRT_TUPLEOFINSTANCES_H +#define CPYRT_TUPLEOFINSTANCES_H + +// Bindings +#include "Dimensions.h" + +namespace cppjit::cpyrt { + +/** Representation of C-style array of instances + @author WLAV + @date 02/10/2014 + @version 1.0 + */ + +//- custom tuple type that can pass through C-style arrays ------------------- +extern PyTypeObject TupleOfInstances_Type; +extern PyTypeObject InstanceArrayIter_Type; + +template inline bool TupleOfInstances_Check(T* object) { + return object && PyObject_TypeCheck(object, &TupleOfInstances_Type); +} + +template inline bool TupleOfInstances_CheckExact(T* object) { + return object && Py_TYPE(object) == &TupleOfInstances_Type; +} + +PyObject* TupleOfInstances_New(cppjit::interop::TCppObject_t address, + cppjit::interop::TCppScope_t klass, + cdims_t dims); + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_TUPLEOFINSTANCES_H diff --git a/src/cpyrt/TypeManip.cxx b/src/cpyrt/TypeManip.cxx new file mode 100644 index 0000000..887899b --- /dev/null +++ b/src/cpyrt/TypeManip.cxx @@ -0,0 +1,279 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "TypeManip.h" + +// Standard +#include +#include +#include + +//- helpers ------------------------------------------------------------------ +static inline bool is_varchar(char c) { + return isalnum((int)c) || c == '_' || c == ')' || + c == '(' /* for (anonymous)/(unnamed) */; +} + +static inline std::string::size_type +find_qualifier_index(const std::string& name) { + // Find the first location that is not part of the class name proper. + std::string::size_type i = name.size() - 1; + bool arr_open = false; + for (; 0 < i; --i) { + std::string::value_type c = name[i]; + if (!arr_open && (is_varchar(c) || c == '>')) { + if (c == 't' && 6 < i && !is_varchar(name[i - 5]) && + name.substr(i - 4, 5) == "const") + i -= 4; // this skips 'const' on a pointer type + else + break; + } else if (c == ']') { + arr_open = true; + } else if (c == '[') { + arr_open = false; + } + } + + return i + 1; +} + +static inline void erase_const(std::string& name) { + // Find and remove all occurrence of 'const'. + if (name.empty()) + return; + + std::string::size_type spos = std::string::npos; + std::string::size_type start = 0; + while ((spos = name.find("const", start)) != std::string::npos) { + // make sure not to erase 'const' as part of the name: if it is + // connected, before or after, to a variable name, then keep it + std::string::size_type after = spos + 5; + if (after < name.size() && is_varchar(name[after])) { + start = after; + continue; + } else if (after == name.size()) { + if (spos > 0 && is_varchar(name[spos - 1])) + break; + } + + std::string::size_type i = 5; + while (name[spos + i] == ' ') + ++i; + name.swap(name.erase(spos, i)); + } +} + +static inline void rstrip(std::string& name) { + // Remove space from the right side of name. + std::string::size_type i = name.size(); + for (; 0 < i; --i) { + if (!isspace(name[i - 1])) + break; + } + + if (i != name.size()) + name = name.substr(0, i); +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::remove_const(const std::string& cppname) { + // Remove 'const' qualifiers from the given C++ name. + std::string::size_type tmplt_start = cppname.find('<'); + std::string::size_type type_stop = cppname.rfind('>'); + if (cppname.find("::", type_stop + 1) != + std::string::npos) // e.g. klass::some_typedef + type_stop = cppname.find(' ', type_stop + 1); + if (tmplt_start != std::string::npos && cppname[tmplt_start + 1] != '<') { + // only replace const qualifying cppname, not in template parameters + std::string pre = cppname.substr(0, tmplt_start); + erase_const(pre); + std::string post = ""; + if (type_stop != std::string::npos && type_stop != cppname.size() - 1) { + post = cppname.substr(type_stop + 1, std::string::npos); + erase_const(post); + } + + return pre + cppname.substr(tmplt_start, type_stop + 1 - tmplt_start) + + post; + } + + std::string clean_name = cppname; + erase_const(clean_name); + return clean_name; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::clean_type(const std::string& cppname, + bool template_strip, + bool const_strip) { + // Strip C++ name from all qualifiers and compounds. + std::string::size_type i = find_qualifier_index(cppname); + std::string name = cppname.substr(0, i); + rstrip(name); + + if (name.back() == ']') { // array type? + // TODO: this fails templates instantiated on arrays (not common) + name = name.substr(0, name.find('[')); + } else if (template_strip && name.back() == '>') { + name = name.substr(0, name.find('<')); + } + + if (const_strip) { + if (template_strip) + erase_const(name); + else + name = remove_const(name); + } + + return name; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::template_base(const std::string& cppname) { + // If this is a template, return the underlying template name w/o arguments + if (cppname.empty() || cppname.back() != '>') + return cppname; + + int tpl_open = 0; + for (std::string::size_type pos = cppname.size() - 1; 0 < pos; --pos) { + std::string::value_type c = cppname[pos]; + + // count '<' and '>' to be able to skip template contents + if (c == '>') + --tpl_open; + else if (c == '<' && cppname[pos + 1] != '<') + ++tpl_open; + + if (tpl_open == 0) + return cppname.substr(0, pos); + } + + return cppname; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::compound(const std::string& name) { + // FIXME: temporary fix for translation unit decl being passed in + // CreateExecutor from InitExecutor_ (run test10_object_identity), remove + // later + if (name.empty()) + return ""; + // Break down the compound of a fully qualified type name. + std::string cleanName = remove_const(name); + auto idx = find_qualifier_index(cleanName); + + std::string cpd = cleanName.substr(idx, std::string::npos); + + // for easy identification of fixed size arrays + if (!cpd.empty() && cpd.back() == ']') { + if (cpd.front() == '[') + return "[]"; // fixed array any; dimensions handled separately + + std::ostringstream scpd; + scpd << cpd.substr(0, cpd.find('[')) << "[]"; + cpd = scpd.str(); + } + + // XXX: remove this hack + if (!cpd.empty() && cpd[0] == ' ') + return cpd.substr(1, cpd.length() - 1); + + return cpd; +} + +//---------------------------------------------------------------------------- +void cpyrt::TypeManip::cppscope_to_pyscope(std::string& cppscope) { + // Change '::' in C++ scope into '.' as in a Python scope. + std::string::size_type pos = 0; + while ((pos = cppscope.find("::", pos)) != std::string::npos) { + cppscope.replace(pos, 2, "."); + pos += 1; + } +} + +//---------------------------------------------------------------------------- +void cpyrt::TypeManip::cppscope_to_legalname(std::string& cppscope) { + // Change characters illegal in a variable name into '_' to form a legal name. + for (char& c : cppscope) { + for (char needle : {':', '>', '<', ' ', ',', '&', '=', '*', '-'}) + if (c == needle) + c = '_'; + } +} + +//---------------------------------------------------------------------------- +std::string cpyrt::TypeManip::extract_namespace(const std::string& name) { + // Find the namespace the named class lives in, take care of templates + if (name.empty()) + return name; + + int tpl_open = 0; + for (std::string::size_type pos = name.size() - 1; 0 < pos; --pos) { + std::string::value_type c = name[pos]; + + // count '<' and '>' to be able to skip template contents + if (c == '>') + --tpl_open; + else if (c == '<' && name[pos + 1] != '<') + ++tpl_open; + + // collect name up to "::" + else if (tpl_open == 0 && c == ':' && name[pos - 1] == ':') { + // found the extend of the scope ... done + return name.substr(0, pos - 1); + } + } + + // no namespace; assume outer scope + return ""; +} + +//---------------------------------------------------------------------------- +std::vector +cpyrt::TypeManip::extract_arg_types(const std::string& sig) { + // break out the argument types from the signature string + std::vector result; + + if (sig.empty() || sig == "()") + return result; + + int tpl_open = 0; + std::string::size_type start = 1; + for (std::string::size_type pos = 1; pos < sig.size() - 1; ++pos) { + std::string::value_type c = sig[pos]; + + // count '<' and '>' to be able to skip template contents + if (c == '>') + --tpl_open; + else if (c == '<' && sig[pos + 1] != '<') + ++tpl_open; + + // collect type name up to ',' or end ')' + else if (tpl_open == 0 && c == ',') { + // found the extend of the scope ... done + result.push_back(sig.substr(start, pos - start)); + start = pos + 1; + } + } + + // add last type + result.push_back(sig.substr(start, sig.rfind(")") - start)); + + return result; +} + +//---------------------------------------------------------------------------- +Py_ssize_t cpyrt::TypeManip::array_size(const std::string& name) { + // Extract the array size from a given type name (assumes 1D arrays) + std::string cleanName = remove_const(name); + if (cleanName[cleanName.size() - 1] == ']') { + std::string::size_type idx = cleanName.rfind('['); + if (idx != std::string::npos) { + const std::string asize = cleanName.substr(idx + 1, cleanName.size() - 2); + return strtoul(asize.c_str(), nullptr, 0); + } + } + + return -1; +} diff --git a/src/cpyrt/TypeManip.h b/src/cpyrt/TypeManip.h new file mode 100644 index 0000000..49c3b4f --- /dev/null +++ b/src/cpyrt/TypeManip.h @@ -0,0 +1,28 @@ +#ifndef CPYRT_TYPEMANIP_H +#define CPYRT_TYPEMANIP_H + +#include +#include + +namespace cppjit::cpyrt { + +namespace TypeManip { + +std::string remove_const(const std::string& cppname); +std::string clean_type(const std::string& cppname, bool template_strip = true, + bool const_strip = true); +std::string template_base(const std::string& cppname); +std::string compound(const std::string& name); + +void cppscope_to_pyscope(std::string& cppscope); +void cppscope_to_legalname(std::string& cppscope); +std::string extract_namespace(const std::string& name); + +std::vector extract_arg_types(const std::string& sig); +Py_ssize_t array_size(const std::string& name); + +} // namespace TypeManip + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_TYPEMANIP_H diff --git a/src/cpyrt/Utility.cxx b/src/cpyrt/Utility.cxx new file mode 100644 index 0000000..9e947e9 --- /dev/null +++ b/src/cpyrt/Utility.cxx @@ -0,0 +1,1585 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPFunction.h" +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CustomPyTypes.h" +#include "ProxyWrappers.h" +#include "PyCallable.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "TypeManip.h" +#include "Utility.h" +#include "cpyrt/DispatchPtr.h" + +// Standard +#include +#include +#include +#include +#include +#include +#include +#include + +//- data _____________________________________________________________________ +#if PY_VERSION_HEX < 0x030b0000 +bool cpyrt::gDictLookupActive = false; +#endif + +typedef std::unordered_map TC2POperatorMapping_t; +static TC2POperatorMapping_t gC2POperatorMapping; +static std::set gOpSkip; +static std::set gOpRemove; + +namespace cppjit::cpyrt { +// special objects +extern PyObject* gNullPtrObject; +extern PyObject* gDefaultObject; +} // namespace cppjit::cpyrt + +namespace { + +using namespace cpyrt::Utility; + +struct InitOperatorMapping_t { +public: + InitOperatorMapping_t() { + // Initialize the global map of operator names C++ -> python. + + gOpSkip.insert("[]"); // __s/getitem__, depends on return type + gOpSkip.insert("+"); // __add__, depends on # of args (see __pos__) + gOpSkip.insert("-"); // __sub__, id. (eq. __neg__) + gOpSkip.insert("*"); // __mul__ or __deref__ + gOpSkip.insert("++"); // __postinc__ or __preinc__ + gOpSkip.insert("--"); // __postdec__ or __predec__ + + gOpRemove.insert("new"); // this and the following not handled at all + gOpRemove.insert("new[]"); + gOpRemove.insert("delete"); + gOpRemove.insert("delete[]"); + + gC2POperatorMapping["[]"] = "__getitem__"; + gC2POperatorMapping["()"] = "__call__"; + gC2POperatorMapping["%"] = "__mod__"; + gC2POperatorMapping["**"] = "__pow__"; + gC2POperatorMapping["<<"] = "__lshift__"; + gC2POperatorMapping[">>"] = "__rshift__"; + gC2POperatorMapping["&"] = "__and__"; + gC2POperatorMapping["&&"] = "__dand__"; + gC2POperatorMapping["|"] = "__or__"; + gC2POperatorMapping["||"] = "__dor__"; + gC2POperatorMapping["^"] = "__xor__"; + gC2POperatorMapping["~"] = "__invert__"; + gC2POperatorMapping[","] = "__comma__"; + gC2POperatorMapping["+="] = "__iadd__"; + gC2POperatorMapping["-="] = "__isub__"; + gC2POperatorMapping["*="] = "__imul__"; + gC2POperatorMapping["/="] = CPPJIT__idiv__; + gC2POperatorMapping["%="] = "__imod__"; + gC2POperatorMapping["**="] = "__ipow__"; + gC2POperatorMapping["<<="] = "__ilshift__"; + gC2POperatorMapping[">>="] = "__irshift__"; + gC2POperatorMapping["&="] = "__iand__"; + gC2POperatorMapping["|="] = "__ior__"; + gC2POperatorMapping["^="] = "__ixor__"; + gC2POperatorMapping["=="] = "__eq__"; + gC2POperatorMapping["!="] = "__ne__"; + gC2POperatorMapping[">"] = "__gt__"; + gC2POperatorMapping["<"] = "__lt__"; + gC2POperatorMapping[">="] = "__ge__"; + gC2POperatorMapping["<="] = "__le__"; + + // the following type mappings are "exact" + gC2POperatorMapping["const char*"] = "__str__"; + gC2POperatorMapping["char*"] = "__str__"; + gC2POperatorMapping["const char *"] = gC2POperatorMapping["const char*"]; + gC2POperatorMapping["char *"] = gC2POperatorMapping["char*"]; + gC2POperatorMapping["int"] = "__int__"; + gC2POperatorMapping["long"] = CPPJIT__long__; + gC2POperatorMapping["double"] = "__float__"; + + // the following type mappings are "okay"; the assumption is that they + // are not mixed up with the ones above or between themselves (and if + // they are, that it is done consistently) + gC2POperatorMapping["short"] = "__int__"; + gC2POperatorMapping["unsigned short"] = "__int__"; + gC2POperatorMapping["unsigned int"] = CPPJIT__long__; + gC2POperatorMapping["unsigned long"] = CPPJIT__long__; + gC2POperatorMapping["long long"] = CPPJIT__long__; + gC2POperatorMapping["unsigned long long"] = CPPJIT__long__; + gC2POperatorMapping["float"] = "__float__"; + + gC2POperatorMapping["->"] = "__follow__"; // not an actual python operator + gC2POperatorMapping["="] = "__assign__"; // id. + + gC2POperatorMapping["bool"] = "__cpp_bool__"; + } +} initOperatorMapping_; + +inline std::string full_scope(const std::string& tpname) { + return tpname[0] == ':' ? tpname : "::" + tpname; +} + +} // unnamed namespace + +//- public functions --------------------------------------------------------- +unsigned long cpyrt::PyLongOrInt_AsULong(PyObject* pyobject) { + // Convert to C++ unsigned long, with bounds checking, allow int -> + // ulong. + if (PyFloat_Check(pyobject)) { + PyErr_SetString(PyExc_TypeError, "can\'t convert float to unsigned long"); + return (unsigned long)-1; + } else if (pyobject == cpyrt::gDefaultObject) { + return (unsigned long)0; + } + + unsigned long ul = PyLong_AsUnsignedLong(pyobject); + if (ul == (unsigned long)-1 && PyErr_Occurred() && PyInt_Check(pyobject)) { + PyErr_Clear(); + long i = PyInt_AS_LONG(pyobject); + if (0 <= i) { + ul = (unsigned long)i; + } else { + PyErr_SetString(PyExc_ValueError, + "can\'t convert negative value to unsigned long"); + return (unsigned long)-1; + } + } + + return ul; +} + +//---------------------------------------------------------------------------- +PY_ULONG_LONG cpyrt::PyLongOrInt_AsULong64(PyObject* pyobject) { + // Convert to C++ unsigned long long, with bounds checking. + if (PyFloat_Check(pyobject)) { + PyErr_SetString(PyExc_TypeError, + "can\'t convert float to unsigned long long"); + return -1; + } else if (pyobject == cpyrt::gDefaultObject) { + return (unsigned long)0; + } + + PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(pyobject); + if (PyErr_Occurred() && PyInt_Check(pyobject)) { + PyErr_Clear(); + long i = PyInt_AS_LONG(pyobject); + if (0 <= i) { + ull = (PY_ULONG_LONG)i; + } else { + PyErr_SetString(PyExc_ValueError, + "can\'t convert negative value to unsigned long long"); + } + } + + return ull; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::AddToClass(PyObject* pyclass, const char* label, + PyCFunction cfunc, int flags) { + // Add the given function to the class under name 'label'. + + // use list for clean-up (.so's are unloaded only at interpreter shutdown) + static std::list s_pymeths; + + s_pymeths.push_back(PyMethodDef()); + PyMethodDef* pdef = &s_pymeths.back(); + pdef->ml_name = const_cast(label); + pdef->ml_meth = cfunc; + pdef->ml_flags = flags; + pdef->ml_doc = nullptr; + + PyObject* func = PyCFunction_New(pdef, nullptr); + PyObject* name = cpyrt_PyText_InternFromString(pdef->ml_name); + PyObject* method = CustomInstanceMethod_New(func, nullptr, pyclass); + PyObject *pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + bool isOk = PyType_Type.tp_setattro(pyclass, name, method) == 0; + PyErr_Restore(pytype, pyvalue, pytrace); + Py_DECREF(method); + Py_DECREF(name); + Py_DECREF(func); + + if (PyErr_Occurred()) + return false; + + if (!isOk) { + PyErr_Format(PyExc_TypeError, "could not add method %s", label); + return false; + } + + return true; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::AddToClass(PyObject* pyclass, const char* label, + const char* func) { + // Add the given function to the class under name 'label'. + PyObject* pyfunc = PyObject_GetAttrString(pyclass, const_cast(func)); + if (!pyfunc) + return false; + + PyObject* pylabel = cpyrt_PyText_InternFromString(const_cast(label)); + bool isOk = PyType_Type.tp_setattro(pyclass, pylabel, pyfunc) == 0; + Py_DECREF(pylabel); + + Py_DECREF(pyfunc); + return isOk; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::AddToClass(PyObject* pyclass, const char* label, + PyCallable* pyfunc) { + // Add the given function to the class under name 'label'. + CPPOverload* method = + (CPPOverload*)PyObject_GetAttrString(pyclass, const_cast(label)); + + if (!method || !CPPOverload_Check(method)) { + // not adding to existing CPPOverload; add callable directly to the class + if (PyErr_Occurred()) + PyErr_Clear(); + Py_XDECREF((PyObject*)method); + method = CPPOverload_New(label, pyfunc); + PyObject* pylabel = cpyrt_PyText_InternFromString(const_cast(label)); + bool isOk = + PyType_Type.tp_setattro(pyclass, pylabel, (PyObject*)method) == 0; + Py_DECREF(pylabel); + Py_DECREF(method); + return isOk; + } + + method->AdoptMethod(pyfunc); + + Py_DECREF(method); + return true; +} + +//---------------------------------------------------------------------------- +static inline cpyrt::PyCallable* BuildOperator(const std::string& lcname, + const std::string& rcname, + const char* op, + interop::TCppScope_t scope, + bool reverse = false) { + // Helper to find a function with matching signature in 'funcs'. + + interop::TCppMethod_t meth = + interop::GetGlobalOperator(scope, lcname, rcname, op); + if (!meth) + return nullptr; + + if (!reverse) + return new cpyrt::CPPFunction(scope, meth); + return new cpyrt::CPPReverseBinary(scope, meth); +} + +//---------------------------------------------------------------------------- +cpyrt::PyCallable* cpyrt::Utility::FindUnaryOperator(PyObject* pyclass, + const char* op) { + // Find a callable matching named operator (op) and klass arguments in the + // global namespace or the klass' namespace. + + if (!CPPScope_Check(pyclass)) + return nullptr; + + CPPClass* klass = (CPPClass*)pyclass; + const std::string& lcname = interop::GetScopedFinalName(klass->fCppType); + interop::TCppScope_t scope = + interop::GetScope(TypeManip::extract_namespace(lcname)); + return FindBinaryOperator(lcname, "", op, scope, false); +} + +//---------------------------------------------------------------------------- +cpyrt::PyCallable* +cpyrt::Utility::FindBinaryOperator(PyObject* left, PyObject* right, + const char* op, interop::TCppScope_t scope) { + // Find a callable matching the named operator (op) and the (left, right) + // arguments in the global or these objects' namespaces. + + bool reverse = false; + if (!CPPInstance_Check(left)) { + if (CPPInstance_Check(right)) + reverse = true; + else + return nullptr; + } + + // retrieve the class names to match the signature of any found global + // functions + const std::string& lcname = ClassName(left); + const std::string& rcname = ClassName(right); + return FindBinaryOperator(lcname, rcname, op, scope, reverse); +} + +//---------------------------------------------------------------------------- +cpyrt::PyCallable* +cpyrt::Utility::FindBinaryOperator(const std::string& lcname, + const std::string& rcname, const char* op, + interop::TCppScope_t scope, bool reverse) { + // Find a global function with a matching signature; search __gnu_cxx, + // std::__1, and __cppjit_internal pro-actively (as there's AFAICS no way to + // unearth 'using' information). + + if (rcname == "" || lcname == "") + return nullptr; + + PyCallable* pyfunc = 0; + + if (!scope) { + // TODO: the following should remain sync with what clingwrapper does in its + // type remapper; there must be a better way? + if (lcname == "str" || lcname == "unicode" || lcname == "complex" || + lcname.find("std::") == 0) + scope = interop::GetScope("std"); + } + if (scope) + pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); + if (!pyfunc) + if ((scope = interop::GetScope(TypeManip::extract_namespace(lcname)))) + pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); + + if (!pyfunc && + scope != interop::GetGlobalScope()) // search in global scope anyway + pyfunc = + BuildOperator(lcname, rcname, op, interop::GetGlobalScope(), reverse); + + if (!pyfunc) { + // For GNU on clang, search the internal __gnu_cxx namespace for binary + // operators (is typically the case for STL iterators operator==/!=. + // TODO: only look in __gnu_cxx for iterators (and more generally: do + // lookups in the + // namespace where the class is defined + static interop::TCppScope_t gnucxx = interop::GetScope("__gnu_cxx"); + if (gnucxx) + pyfunc = BuildOperator(lcname, rcname, op, gnucxx, reverse); + } + + if (!pyfunc) { + // Same for clang (on Mac only?). TODO: find proper pre-processor magic to + // only use those specific namespaces that are actually around; although to + // be sure, this isn't expensive. + static interop::TCppScope_t std__1 = interop::GetFullScope("std::__1"); + + if (std__1 +#ifdef __APPLE__ + && lcname.find("__wrap_iter") == + std::string::npos // wrapper call does not compile +#endif + ) { + pyfunc = BuildOperator(lcname, rcname, op, std__1, reverse); + } + } + + if (!pyfunc) { + // One more, mostly for Mac, but again not sure whether this is not a + // general issue. Some operators are declared as friends only in classes, so + // then they're not found in the global namespace, so this helper let's the + // compiler resolve the operator. + static interop::TCppScope_t s_intern = + interop::GetScope("__cppjit_internal"); + if (s_intern) { + std::stringstream fname, proto; + if (strncmp(op, "==", 2) == 0) { + fname << "is_equal<"; + } else if (strncmp(op, "!=", 2) == 0) { + fname << "is_not_equal<"; + } else { + fname << "not_implemented<"; + } + fname << lcname << ", " << rcname << ">"; + proto << "const " << lcname << "&, const " << rcname; + interop::TCppMethod_t method = + interop::GetMethodTemplate(s_intern, fname.str(), proto.str()); + if (method) + pyfunc = new CPPFunction(s_intern, method); + } + } + + return pyfunc; +} + +//---------------------------------------------------------------------------- +static inline std::string AnnotationAsText(PyObject* pyobj) { + if (!cpyrt_PyText_Check(pyobj)) { + PyObject* pystr = PyObject_GetAttr(pyobj, cpyrt::PyStrings::gName); + if (!pystr) { + PyErr_Clear(); + pystr = PyObject_Str(pyobj); + } + + std::string str = cpyrt_PyText_AsString(pystr); + Py_DECREF(pystr); + return str; + } + return cpyrt_PyText_AsString(pyobj); +} + +static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg, + cpyrt::Utility::ArgPreference pref, + int* pcnt = nullptr) { + // Determine the appropriate C++ type for a given Python type; this is a + // helper because it can recurse if the type is list or tuple and needs + // matching on std::vector. + using namespace cppjit::cpyrt; + using namespace cpyrt::Utility; + + if (tn == (PyObject*)&PyInt_Type) { + if (arg) { + PY_LONG_LONG ll = PyLong_AsLongLong(arg); + if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); + if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + tmpl_name.append("int"); // still out of range, will fail later + } else + tmpl_name.append( + "unsigned long long"); // since already failed long long + } else + tmpl_name.append( + (ll < INT_MIN || INT_MAX < ll) + ? ((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long") + : "int"); + } else + tmpl_name.append("int"); + + return true; + } + + if (tn == (PyObject*)&PyFloat_Type) { + // special case for floats (Python-speak for double) if from argument (only) + tmpl_name.append(arg ? "double" : "float"); + return true; + } + + if (tn == (PyObject*)&PyUnicode_Type) { + tmpl_name.append("std::string"); + return true; + } + + if (tn == (PyObject*)&PyList_Type || tn == (PyObject*)&PyTuple_Type) { + if (arg && PySequence_Size(arg)) { + std::string subtype{"std::initializer_list<"}; + PyObject* item = PySequence_GetItem(arg, 0); + ArgPreference subpref = pref == kValue ? kValue : kPointer; + bool ret = AddTypeName(subtype, (PyObject*)Py_TYPE(item), item, subpref); + if (ret) { + tmpl_name.append(subtype); + tmpl_name.append(">"); + } + Py_DECREF(item); + // Error occurred in inner call to AddTypeName, which means it has + // also set a TypeError. We return and let the error propagate. + if (!ret) { + return false; + } + } + + return true; + } + + if (CPPScope_Check(tn)) { + auto cpp_type = interop::GetScopedFinalName(((CPPClass*)tn)->fCppType); + tmpl_name.append(full_scope(cpp_type)); + if (arg) { + // try to specialize the type match for the given object + CPPInstance* pyobj = (CPPInstance*)arg; + if (CPPInstance_Check(pyobj)) { + if (pyobj->fFlags & CPPInstance::kIsRValue) + tmpl_name.append("&&"); + else { + if (pcnt) + *pcnt += 1; + if ((pyobj->fFlags & CPPInstance::kIsReference) || pref == kPointer) + tmpl_name.push_back('*'); + else if (pref != kValue) + tmpl_name.push_back('&'); + } + } + } + + return true; + } + + if (tn == (PyObject*)&CPPOverload_Type) { + PyObject* tpName = arg ? PyObject_GetAttr(arg, PyStrings::gCppName) + : cpyrt_PyText_FromString("void* (*)(...)"); + tmpl_name.append(cpyrt_PyText_AsString(tpName)); + Py_DECREF(tpName); + + return true; + } + + if (arg && PyCallable_Check(arg)) { + // annotated/typed Python function + PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations); + if (annot) { + if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) { + PyObject* ret = PyDict_GetItemString(annot, "return"); + if (ret) { + // dict is ordered, with the last value being the return type + std::ostringstream tpn; + tpn << (CPPScope_Check(ret) ? ClassName(ret) : AnnotationAsText(ret)) + << " (*)("; + + PyObject* values = PyDict_Values(annot); + for (Py_ssize_t i = 0; i < (PyList_GET_SIZE(values) - 1); ++i) { + if (i) + tpn << ", "; + PyObject* item = PyList_GET_ITEM(values, i); + tpn << (CPPScope_Check(item) ? full_scope(ClassName(item)) + : AnnotationAsText(item)); + } + Py_DECREF(values); + + tpn << ')'; + tmpl_name.append(tpn.str()); + + Py_DECREF(annot); + return true; + + } else + PyErr_Clear(); + } + Py_DECREF(annot); + } else + PyErr_Clear(); + + // ctypes function pointer + PyObject* argtypes = nullptr; + PyObject* ret = nullptr; + if ((argtypes = PyObject_GetAttrString(arg, "argtypes")) && + (ret = PyObject_GetAttrString(arg, "restype"))) { + std::ostringstream tpn; + PyObject* pytc = PyObject_GetAttr(ret, PyStrings::gCTypesType); + tpn << CT2CppNameS(pytc, false) << " (*)("; + Py_DECREF(pytc); + + for (Py_ssize_t i = 0; i < PySequence_Length(argtypes); ++i) { + if (i) + tpn << ", "; + PyObject* item = PySequence_GetItem(argtypes, i); + pytc = PyObject_GetAttr(item, PyStrings::gCTypesType); + tpn << CT2CppNameS(pytc, false); + Py_DECREF(pytc); + Py_DECREF(item); + } + + tpn << ')'; + tmpl_name.append(tpn.str()); + + Py_DECREF(ret); + Py_DECREF(argtypes); + + return true; + + } else { + PyErr_Clear(); + Py_XDECREF(ret); + Py_XDECREF(argtypes); + } + + // callable C++ type (e.g. std::function) + PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName); + if (tpName) { + const char* cname = cpyrt_PyText_AsString(tpName); + tmpl_name.append(CPPScope_Check(arg) ? full_scope(cname) : cname); + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + for (auto nn : {PyStrings::gCppName, PyStrings::gName}) { + PyObject* tpName = PyObject_GetAttr(tn, nn); + if (tpName) { + tmpl_name.append(cpyrt_PyText_AsString(tpName)); + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + if (PyInt_Check(tn) || PyLong_Check(tn) || PyFloat_Check(tn)) { + // last ditch attempt, works for things like int values; since this is a + // source of errors otherwise, it is limited to specific types and not + // generally used (str(obj) can print anything ...) + PyObject* pystr = PyObject_Str(tn); + tmpl_name.append(cpyrt_PyText_AsString(pystr)); + Py_DECREF(pystr); + return true; + } + + // Give up with a TypeError + + // Try to get a readable representation of the argument + PyObject* repr = PyObject_Repr(tn); + const char* repr_cstr = repr ? PyUnicode_AsUTF8(repr) : ""; + + PyErr_Format(PyExc_TypeError, + "could not construct C++ name from template argument %s", + repr_cstr); + + Py_XDECREF(repr); + return false; +} + +std::string cpyrt::Utility::ConstructTemplateArgs(PyObject* pyname, + PyObject* tpArgs, + PyObject* args, + ArgPreference pref, + int argoff, int* pcnt) { + // Helper to construct the "" part of a templated name + // (either for a class or method lookup + bool justOne = !PyTuple_CheckExact(tpArgs); + + // Note: directly appending to string is a lot faster than stringstream + std::string tmpl_name; + tmpl_name.reserve(128); + if (pyname) + tmpl_name.append(cpyrt_PyText_AsString(pyname)); + tmpl_name.push_back('<'); + + if (pcnt) + *pcnt = 0; // count number of times 'pref' is used + + Py_ssize_t nArgs = justOne ? 1 : PyTuple_GET_SIZE(tpArgs); + for (int i = argoff; i < nArgs; ++i) { + // add type as string to name + PyObject* tn = justOne ? tpArgs : PyTuple_GET_ITEM(tpArgs, i); + if (cpyrt_PyText_Check(tn)) { + tmpl_name.append(cpyrt_PyText_AsString(tn)); + // some common numeric types (separated out for performance: checking for + // __cpp_name__ and/or __name__ is rather expensive) + } else { + if (!AddTypeName(tmpl_name, tn, + (args ? PyTuple_GET_ITEM(args, i) : nullptr), pref, + pcnt)) { + PyErr_SetString( + PyExc_SyntaxError, + "could not construct C++ name from provided template argument."); + return ""; + } + } + + // add a comma, as needed (no space as internally, final names don't have + // them) + if (i != nArgs - 1) + tmpl_name.push_back(','); + } + + // close template name + tmpl_name.push_back('>'); + + return tmpl_name; +} + +//---------------------------------------------------------------------------- +static bool AddTypeName(std::vector& types, PyObject* tn, + PyObject* arg, cpyrt::Utility::ArgPreference pref, + int* pcnt = nullptr) { + // Determine the appropriate C++ type for a given Python type; this is a + // helper because it can recurse if the type is list or tuple and needs + // matching on std::vector. + using namespace cppjit::cpyrt; + using namespace cpyrt::Utility; + + if (tn == (PyObject*)&PyInt_Type) { + if (arg) { + PY_LONG_LONG ll = PyLong_AsLongLong(arg); + if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); + if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + types.push_back(interop::GetType("int") + .data); // still out of range, will fail later + } else + types.push_back(interop::GetType("unsigned long long") + .data); // since already failed long long + } else + types.push_back( + interop::GetType( + (ll < INT_MIN || INT_MAX < ll) + ? ((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long") + : "int") + .data); + } else { + types.push_back(interop::GetType("int").data); + } + + return true; + } + + if (tn == (PyObject*)&PyFloat_Type) { + // special case for floats (Python-speak for double) if from argument (only) + types.push_back(interop::GetType(arg ? "double" : "float").data); + return true; + } + + if (tn == (PyObject*)&PyUnicode_Type) { + types.push_back( + interop::GetType("std::string", /* enable_slow_lookup */ true).data); + return true; + } + + if (tn == (PyObject*)&PyList_Type || tn == (PyObject*)&PyTuple_Type) { + if (arg && PySequence_Size(arg)) { + std::string subtype{"std::initializer_list<"}; + PyObject* item = PySequence_GetItem(arg, 0); + ArgPreference subpref = pref == kValue ? kValue : kPointer; + if (AddTypeName(subtype, (PyObject*)Py_TYPE(item), item, subpref)) { + subtype.append(">"); + types.push_back(interop::GetType(subtype).data); + } + Py_DECREF(item); + } + + return true; + } + + if (CPPScope_Check(tn)) { + auto cpp_type = interop::GetTypeFromScope(((CPPClass*)tn)->fCppType); + if (arg) { + // try to specialize the type match for the given object + CPPInstance* pyobj = (CPPInstance*)arg; + if (CPPInstance_Check(pyobj)) { + if (pyobj->fFlags & CPPInstance::kIsRValue) + cpp_type = interop::GetReferencedType(cpp_type, /*rvalue=*/true); + else { + if (pcnt) + *pcnt += 1; + if ((pyobj->fFlags & CPPInstance::kIsReference) || pref == kPointer) + cpp_type = interop::GetPointerType(cpp_type); + else if (pref != kValue) + cpp_type = interop::GetReferencedType(cpp_type, /*rvalue=*/false); + } + } + } + types.push_back(cpp_type.data); + return true; + } + + if (tn == (PyObject*)&CPPOverload_Type) { + PyObject* tpName = arg ? PyObject_GetAttr(arg, PyStrings::gCppName) + : cpyrt_PyText_FromString("void* (*)(...)"); + types.push_back(interop::GetType(cpyrt_PyText_AsString(tpName), + /* enable_slow_lookup */ true) + .data); + Py_DECREF(tpName); + + return true; + } + + if (arg && PyCallable_Check(arg)) { + PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations); + if (annot) { + if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) { + PyObject* ret = PyDict_GetItemString(annot, "return"); + if (ret) { + // dict is ordered, with the last value being the return type + std::ostringstream tpn; + tpn << (CPPScope_Check(ret) ? ClassName(ret) + : cpyrt_PyText_AsString(ret)) + << " (*)("; + + PyObject* values = PyDict_Values(annot); + for (Py_ssize_t i = 0; i < (PyList_GET_SIZE(values) - 1); ++i) { + if (i) + tpn << ", "; + PyObject* item = PyList_GET_ITEM(values, i); + tpn << (CPPScope_Check(item) ? ClassName(item) + : cpyrt_PyText_AsString(item)); + } + Py_DECREF(values); + + tpn << ')'; + // tmpl_name.append(tpn.str()); + // FIXME: find a way to add it to types + throw std::runtime_error( + "This path is not yet implemented (AddTypeName) \n"); + + return true; + + } else + PyErr_Clear(); + } + Py_DECREF(annot); + } else + PyErr_Clear(); + + PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName); + if (tpName) { + types.push_back(interop::GetType(cpyrt_PyText_AsString(tpName), + /* enable_slow_lookup */ true) + .data); + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + for (auto nn : {PyStrings::gCppName, PyStrings::gName}) { + PyObject* tpName = PyObject_GetAttr(tn, nn); + if (tpName) { + interop::TCppType_t type = interop::GetType( + cpyrt_PyText_AsString(tpName), /* enable_slow_lookup */ true); + if (interop::IsEnumType(type)) { + PyObject* value_int = PyNumber_Index(tn); + if (!value_int) { + types.push_back(type.data); + PyErr_Clear(); + } else { + PyObject* pystr = PyObject_Str(tn); + std::string num = cpyrt_PyText_AsString(pystr); + types.push_back({type.data, strdup(num.c_str())}); + Py_DECREF(pystr); + Py_DECREF(value_int); + } + } else { + types.push_back(type.data); + } + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + if (PyInt_Check(tn) || PyLong_Check(tn) || PyFloat_Check(tn)) { + // last ditch attempt, works for things like int values; since this is a + // source of errors otherwise, it is limited to specific types and not + // generally used (str(obj) can print anything ...) + PyObject* pystr = PyObject_Str(tn); + std::string num = cpyrt_PyText_AsString(pystr); + if (num == "True") + num = "1"; + else if (num == "False") + num = "0"; + types.push_back({interop::GetType("int").data, strdup(num.c_str())}); + Py_DECREF(pystr); + return true; + } + + return false; +} + +std::vector +cpyrt::Utility::GetTemplateArgsTypes(PyObject* /*scope*/, PyObject* tpArgs, + PyObject* args, ArgPreference pref, + int argoff, int* pcnt) { + // Helper to construct the "" part of a templated name + // (either for a class or method lookup + bool justOne = !PyTuple_CheckExact(tpArgs); + + // Note: directly appending to string is a lot faster than stringstream + std::vector types; + types.reserve(8); + + if (pcnt) + *pcnt = 0; // count number of times 'pref' is used + + Py_ssize_t nArgs = justOne ? 1 : PyTuple_GET_SIZE(tpArgs); + for (int i = argoff; i < nArgs; ++i) { + // add type as string to name + PyObject* tn = justOne ? tpArgs : PyTuple_GET_ITEM(tpArgs, i); + if (cpyrt_PyText_Check(tn)) { + const char* tn_string = cpyrt_PyText_AsString(tn); + + if (interop::AppendTypesSlow(tn_string, types)) { + PyErr_Format(PyExc_TypeError, "Cannot find Templated Arg: %s", + tn_string); + return {}; + } + + // some commmon numeric types (separated out for performance: checking for + // __cpp_name__ and/or __name__ is rather expensive) + } else { + if (!AddTypeName(types, tn, (args ? PyTuple_GET_ITEM(args, i) : nullptr), + pref, pcnt)) { + PyErr_SetString( + PyExc_TypeError, + "could not construct C++ name from provided template argument."); + return {}; + } + } + } + + return types; +} + +std::string cpyrt::Utility::CT2CppNameS(PyObject* pytc, bool allow_voidp) { + // helper to convert ctypes' `_type_` info to the equivalent C++ name + const char* name = ""; + if (cpyrt_PyText_Check(pytc)) { + char tc = ((char*)cpyrt_PyText_AsString(pytc))[0]; + switch (tc) { + case '?': + name = "bool"; + break; + case 'c': + name = "char"; + break; + case 'b': + name = "char"; + break; + case 'B': + name = "unsigned char"; + break; + case 'h': + name = "short"; + break; + case 'H': + name = "unsigned short"; + break; + case 'i': + name = "int"; + break; + case 'I': + name = "unsigned int"; + break; + case 'l': + name = "long"; + break; + case 'L': + name = "unsigned long"; + break; + case 'q': + name = "long long"; + break; + case 'Q': + name = "unsigned long long"; + break; + case 'f': + name = "float"; + break; + case 'd': + name = "double"; + break; + case 'g': + name = "long double"; + break; + case 'z': + name = "const char*"; + break; + default: + if (allow_voidp) + name = "void*"; + break; + } + } + + return name; +} + +//---------------------------------------------------------------------------- +static inline bool check_scope(const std::string& name) { + return (bool)interop::GetScope(cpyrt::TypeManip::clean_type(name, false)); +} + +void cpyrt::Utility::ConstructCallbackPreamble( + const std::string& retType, const std::vector& argtypes, + std::ostringstream& code) { + // Generate function setup to be used in callbacks (wrappers and overrides). + int nArgs = (int)argtypes.size(); + + // return value and argument type converters + bool isVoid = retType.find("void") == 0; // might contain trailing space + if (!isVoid) + code << " CPYRT_STATIC std::unique_ptr> " + "retconv{cppjit::cpyrt::CreateConverter(\"" + << retType << "\"), cppjit::cpyrt::DestroyConverter};\n"; + std::vector arg_is_ptr; + if (nArgs) { + arg_is_ptr.resize(nArgs); + code << " CPYRT_STATIC " + "std::vector>> argcvs;\n" + << " if (argcvs.empty()) {\n" + << " argcvs.reserve(" << nArgs << ");\n"; + for (int i = 0; i < nArgs; ++i) { + arg_is_ptr[i] = false; + code << " argcvs.emplace_back(cppjit::cpyrt::CreateConverter(\""; + const std::string& at = argtypes[i]; + const std::string& res_at = interop::ResolveName(at); + const std::string& cpd = TypeManip::compound(res_at); + if (!cpd.empty() && check_scope(res_at)) { + // in case of a pointer, the original argument needs to be used to + // ensure the pointer-value remains comparable + // + // in case of a reference, there is no extra indirection on the C++ side + // as would be when converting a data member, so adjust the converter + arg_is_ptr[i] = cpd.back() == '*'; + if (arg_is_ptr[i] || cpd.back() == '&') { + code << res_at.substr(0, res_at.size() - 1); + } else + code << at; + } else + code << at; + code << "\"), cppjit::cpyrt::DestroyConverter);\n"; + } + code << " }\n"; + } + + // declare return value (TODO: this does not work for most non-builtin values) + if (!isVoid) + code << " " << retType << " ret{};\n"; + + // build argument tuple if needed + if (nArgs) { + code << " std::vector pyargs;\n"; + code << " pyargs.reserve(" << nArgs << ");\n" + << " try {\n"; + for (int i = 0; i < nArgs; ++i) { + code << " pyargs.emplace_back(argcvs[" << i + << "]->FromMemory((void*)"; + if (!arg_is_ptr[i]) + code << '&'; + code << "arg" << i << "));\n" + << " if (!pyargs.back()) throw " << i << ";\n"; + } + code << " } catch(int) {\n" + << " for (auto pyarg : pyargs) Py_XDECREF(pyarg);\n" + << " cppjit::cpyrt::PyException pyexc; throw pyexc;\n" + << " }\n"; + } +} + +void cpyrt::Utility::ConstructCallbackReturn(const std::string& retType, + int nArgs, + std::ostringstream& code) { + // Generate code for return value conversion and error handling. + bool isVoid = retType.find("void") == 0; // might contain trailing space + bool isPtr = interop::ResolveName(retType).back() == '*'; + + if (nArgs) + code << " for (auto pyarg : pyargs) Py_DECREF(pyarg);\n"; + code << " bool cOk = (bool)pyresult;\n" + " if (pyresult) {\n"; + if (isPtr) { + // If the return type is a CPPInstance, owned by Python, and the ref-count + // down to 1, the return will hold a dangling pointer, so set it to nullptr + // instead. + code << " if (!cppjit::cpyrt::Instance_IsLively(pyresult))\n" + " ret = nullptr;\n" + " else {\n"; + } + code << (isVoid ? "" + : " cOk = retconv->ToMemory(pyresult, (void*)&ret);\n") + << " Py_DECREF(pyresult);\n }\n"; + if (isPtr) + code << " }\n"; + code << " if (!cOk) {" // assume error set when converter failed +// TODO: On Windows, throwing a C++ exception here makes the code hang; leave +// the error be which allows at least one layer of propagation +#ifdef _WIN32 + " /* do nothing */ }\n" +#else + " cppjit::cpyrt::PyException pyexc; throw pyexc; }\n" +#endif + " return"; + code << (isVoid ? ";\n }\n" : " ret;\n }\n"); +} + +//---------------------------------------------------------------------------- +static std::unordered_map sStdFuncLookup; +static std::unordered_map sStdFuncMakerLookup; +PyObject* cpyrt::Utility::FuncPtr2StdFunction(const std::string& retType, + const std::string& signature, + void* address) { + // Convert a function pointer to an equivalent std::function<> object. + static int maker_count = 0; + + auto pf = sStdFuncLookup.find(address); + if (pf != sStdFuncLookup.end()) { + Py_INCREF(pf->second); + return pf->second; + } + + PyObject* maker = nullptr; + + auto pm = sStdFuncMakerLookup.find(retType + signature); + if (pm == sStdFuncMakerLookup.end()) { + std::ostringstream fname; + fname << "ptr2func" << ++maker_count; + + std::ostringstream code; + code << "namespace __cppjit_internal { std::function<" << retType + << signature << "> " << fname.str() << "(intptr_t faddr) { return (" + << retType << "(*)" << signature << ")faddr;} }"; + + if (!interop::Compile(code.str())) { + PyErr_SetString(PyExc_TypeError, "conversion to std::function failed"); + return nullptr; + } + + PyObject* pyscope = CreateScopeProxy("__cppjit_internal"); + maker = PyObject_GetAttrString(pyscope, fname.str().c_str()); + Py_DECREF(pyscope); + if (!maker) + return nullptr; + + // cache the new maker (TODO: does it make sense to use weakrefs?) + sStdFuncMakerLookup[retType + signature] = maker; + } else + maker = pm->second; + + PyObject* args = PyTuple_New(1); + PyTuple_SET_ITEM(args, 0, PyLong_FromLongLong((intptr_t)address)); + PyObject* func = PyObject_Call(maker, args, NULL); + Py_DECREF(args); + + if (func) { // prevent moving this func object, since then it can not be + // reused + ((CPPInstance*)func)->fFlags |= CPPInstance::kIsLValue; + Py_INCREF(func); // TODO: use weak? The C++ maker doesn't go away either + sStdFuncLookup[address] = func; + } + + return func; +} + +//---------------------------------------------------------------------------- +bool cpyrt::Utility::InitProxy(PyObject* module, PyTypeObject* pytype, + const char* name) { + // Initialize a proxy class for use by python, and add it to the module. + + // finalize proxy type + if (PyType_Ready(pytype) < 0) + return false; + + // add proxy type to the given module + Py_INCREF(pytype); // PyModule_AddObject steals reference + if (PyModule_AddObject(module, (char*)name, (PyObject*)pytype) < 0) { + Py_DECREF(pytype); + return false; + } + + // declare success + return true; +} + +//---------------------------------------------------------------------------- +std::unordered_map const& cpyrt::Utility::TypecodeMap() { + // See https://docs.python.org/3/library/array.html#array.array + static std::unordered_map typecodeMap{ + {"char", 'b'}, {"unsigned char", 'B'}, +#if PY_VERSION_HEX >= 0x030d0000 + {"Py_UCS4", 'w'}, +#endif + {"short", 'h'}, {"unsigned short", 'H'}, + {"int", 'i'}, {"unsigned int", 'I'}, + {"long", 'l'}, {"unsigned long", 'L'}, + {"long long", 'q'}, {"unsigned long long", 'Q'}, + {"float", 'f'}, {"double", 'd'}}; + return typecodeMap; +} + +//---------------------------------------------------------------------------- +Py_ssize_t cpyrt::Utility::GetBuffer(PyObject* pyobject, char tc, int size, + void*& buf, bool check) { + // Retrieve a linear buffer pointer from the given pyobject. + + // special case: don't handle character strings here (yes, they're buffers, + // but not quite) + if (PyBytes_Check(pyobject) || PyUnicode_Check(pyobject)) + return 0; + + // special case: bytes array + if ((!check || tc == '*' || tc == 'B') && PyByteArray_CheckExact(pyobject)) { + buf = PyByteArray_AS_STRING(pyobject); + return PyByteArray_GET_SIZE(pyobject); + } + + // new-style buffer interface + if (PyObject_CheckBuffer(pyobject)) { + if (PySequence_Check(pyobject) && !PySequence_Size(pyobject)) + return 0; // PyObject_GetBuffer() crashes on some platforms for some + // zero-sized seqeunces + if (PyErr_Occurred()) { + // PySequence_Size errored with + // TypeError: object of type 'LP_c_type' has no len() + PyErr_Clear(); + } + + Py_buffer bufinfo; + memset(&bufinfo, 0, sizeof(Py_buffer)); + if (PyObject_GetBuffer(pyobject, &bufinfo, PyBUF_FORMAT) == 0) { + if (tc == '*' || + strchr(bufinfo.format, tc) + // if `long int` and `int` are the same size (on Windows and 32bit + // Linux, for example), `ctypes` isn't too picky about the type + // format, so make sure both integer types pass the type check + || (sizeof(long int) == sizeof(int) && + ((tc == 'I' && strchr(bufinfo.format, 'L')) || + (tc == 'i' && strchr(bufinfo.format, 'l')))) + // complex float is 'Zf' in bufinfo.format, but 'z' in single char + || (tc == 'z' && strstr(bufinfo.format, "Zf")) + // allow 'signed char' ('b') from array to pass through '?' (bool as + // from struct) + || (tc == '?' && strchr(bufinfo.format, 'b'))) { + buf = bufinfo.buf; + + if (check && bufinfo.itemsize != size) { + PyErr_Format( + PyExc_TypeError, + "buffer itemsize (%ld) does not match expected size (%d)", + bufinfo.itemsize, size); + PyBuffer_Release(&bufinfo); + return 0; + } + + Py_ssize_t buflen = 0; + if (buf && bufinfo.ndim == 0) + buflen = bufinfo.len / bufinfo.itemsize; + else if (buf && bufinfo.ndim == 1) + buflen = + bufinfo.shape ? bufinfo.shape[0] : bufinfo.len / bufinfo.itemsize; + PyBuffer_Release(&bufinfo); + if (buflen) + return buflen; + } else { + // have buf, but format mismatch: bail out now, otherwise the old + // code will return based on itemsize match + PyBuffer_Release(&bufinfo); + return 0; + } + } else if (bufinfo.obj) + PyBuffer_Release(&bufinfo); + PyErr_Clear(); + } + + // attempt to retrieve pointer through old-style buffer interface + PyBufferProcs* bufprocs = Py_TYPE(pyobject)->tp_as_buffer; + + PySequenceMethods* seqmeths = Py_TYPE(pyobject)->tp_as_sequence; + if (seqmeths != 0 && bufprocs != 0 && bufprocs->bf_getbuffer != 0) { + + // get the buffer + Py_buffer bufinfo; + (*(bufprocs->bf_getbuffer))(pyobject, &bufinfo, PyBUF_WRITABLE); + buf = (char*)bufinfo.buf; + Py_ssize_t buflen = bufinfo.len; + PyBuffer_Release(&bufinfo); + + if (buf && check == true) { + // determine buffer compatibility (use "buf" as a status flag) + PyObject* pytc = tc != '*' + ? PyObject_GetAttr(pyobject, PyStrings::gTypeCode) + : nullptr; + if (pytc != 0) { // for array objects + char cpytc = cpyrt_PyText_AsString(pytc)[0]; + if (!(cpytc == tc || (tc == '?' && cpytc == 'b'))) + buf = 0; // no match + Py_DECREF(pytc); + } else if (seqmeths->sq_length && + (int)(buflen / (*(seqmeths->sq_length))(pyobject)) == size) { + // this is a gamble ... may or may not be ok, but that's for the user + PyErr_Clear(); + } else if (buflen == size) { + // also a gamble, but at least 1 item will fit into the buffer, so very + // likely ok ... + PyErr_Clear(); + } else { + buf = 0; // not compatible + + // clarify error message + auto error = FetchPyError(); + PyObject* pyvalue2 = cpyrt_PyText_FromFormat( + (char*)"%s and given element size (%ld) do not match needed (%d)", + cpyrt_PyText_AsString(error.fValue.get()), + seqmeths->sq_length + ? (long)(buflen / (*(seqmeths->sq_length))(pyobject)) + : (long)buflen, + size); + error.fValue.reset(pyvalue2); + RestorePyError(error); + } + } + + if (!buf) + return 0; + return buflen / (size ? size : 1); + } + + return 0; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::Utility::MapOperatorName(const std::string& name, + bool bTakesParams, bool* stubbed) { + // Map the given C++ operator name on the python equivalent. + if (8 < name.size() && name.substr(0, 8) == "operator") { + std::string op = name.substr(8, std::string::npos); + + // stripping ... + std::string::size_type start = 0, end = op.size(); + while (start < end && isspace(op[start])) + ++start; + while (start < end && isspace(op[end - 1])) + --end; + op = op.substr(start, end - start); + + // certain operators should be removed completely (e.g. operator delete & + // friends) + if (gOpRemove.find(op) != gOpRemove.end()) + return ""; + + TC2POperatorMapping_t::iterator pop = gC2POperatorMapping.find(op); + // map C++ operator to python equivalent, or made up name if no equivalent + // exists + if (pop != gC2POperatorMapping.end()) { + return pop->second; + + } else if (op == "*") { + // dereference v.s. multiplication of two instances + if (!bTakesParams) + return "__deref__"; + if (stubbed) + *stubbed = true; + return "__mul__"; + + } else if (op == "/") { + // no unary, but is stubbed + return CPPJIT__div__; + + } else if (op == "+") { + // unary positive v.s. addition of two instances + if (!bTakesParams) + return "__pos__"; + if (stubbed) + *stubbed = true; + return "__add__"; + + } else if (op == "-") { + // unary negative v.s. subtraction of two instances + if (!bTakesParams) + return "__neg__"; + if (stubbed) + *stubbed = true; + return "__sub__"; + + } else if (op == "++") { + // prefix v.s. postfix increment + return bTakesParams ? "__postinc__" : "__preinc__"; + + } else if (op == "--") { + // prefix v.s. postfix decrement + return bTakesParams ? "__postdec__" : "__predec__"; + } + } + + // might get here, as not all operator methods are handled (new, delete, etc.) + return name; +} + +//---------------------------------------------------------------------------- +std::string cpyrt::Utility::ClassName(PyObject* pyobj) { + // Retrieve the class name from the given Python instance. + std::string clname = ""; + PyObject* pyclass = (PyObject*)Py_TYPE(pyobj); + PyObject* pyname = PyObject_GetAttr(pyclass, PyStrings::gCppName); + if (!pyname) { + PyErr_Clear(); + pyname = PyObject_GetAttr(pyclass, PyStrings::gName); + } + + if (pyname) { + clname = cpyrt_PyText_AsString(pyname); + Py_DECREF(pyname); + } else + PyErr_Clear(); + return clname; +} + +//---------------------------------------------------------------------------- +static std::set sIteratorTypes; +bool cpyrt::Utility::IsSTLIterator(const std::string& classname) { + // attempt to recognize STL iterators (TODO: probably belongs in the backend), + // using a couple of common container classes with different iterator + // protocols (note that mapping iterators are handled separately in the + // pythonizations) as exemplars (the actual, resolved, names will be + // compiler-specific) that are picked b/c they are baked into the CoreLegacy + // dictionary + if (sIteratorTypes.empty()) { + std::string tt = "::"; + for (auto c : {"std::vector", "std::list", "std::deque"}) { + for (auto i : {"iterator", "const_iterator"}) { + const std::string& itname = interop::ResolveName(c + tt + i); + auto pos = itname.find('<'); + if (pos != std::string::npos) + sIteratorTypes.insert(itname.substr(0, pos)); + } + } + } + + auto pos = classname.find('<'); + if (pos != std::string::npos) + return sIteratorTypes.find(classname.substr(0, pos)) != + sIteratorTypes.end(); + return false; +} + +//---------------------------------------------------------------------------- +cpyrt::Utility::PyOperators::~PyOperators() { + Py_XDECREF(fEq); + Py_XDECREF(fNe); + Py_XDECREF(fLAdd); + Py_XDECREF(fRAdd); + Py_XDECREF(fSub); + Py_XDECREF(fLMul); + Py_XDECREF(fRMul); + Py_XDECREF(fDiv); + Py_XDECREF(fHash); +} + +//---------------------------------------------------------------------------- +PyObject* cpyrt::Utility::PyErr_Occurred_WithGIL() { + // Re-acquire the GIL before calling PyErr_Occurred() in case it has been + // released; note that the p2.2 code assumes that there are no callbacks in + // C++ to python (or at least none returning errors). + PythonGILRAII python_gil_raii; + PyObject* e = PyErr_Occurred(); + + return e; +} + +//---------------------------------------------------------------------------- +cpyrt::Utility::PyError_t cpyrt::Utility::FetchPyError() { + // create a PyError_t RAII object that will capture and store the exception + // data + cpyrt::Utility::PyError_t error{}; +#if PY_VERSION_HEX >= 0x030c0000 + error.fValue.reset(PyErr_GetRaisedException()); +#else + PyObject* pytype = nullptr; + PyObject* pyvalue = nullptr; + PyObject* pytrace = nullptr; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + error.fType.reset(pytype); + error.fValue.reset(pyvalue); + error.fTrace.reset(pytrace); +#endif + return error; +} + +//---------------------------------------------------------------------------- +void cpyrt::Utility::RestorePyError(cpyrt::Utility::PyError_t& error) { +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(error.fValue.release()); +#else + PyErr_Restore(error.fType.release(), error.fValue.release(), + error.fTrace.release()); +#endif +} + +//---------------------------------------------------------------------------- +size_t cpyrt::Utility::FetchError(std::vector& errors, bool is_cpp) { + // Fetch the current python error, if any, and store it for future use. + if (PyErr_Occurred()) { + errors.emplace_back(FetchPyError()); + errors.back().fIsCpp = is_cpp; + } + return errors.size(); +} + +//---------------------------------------------------------------------------- +void cpyrt::Utility::SetDetailedException(std::vector&& errors, + PyObject* topmsg, PyObject* defexc) { + // Use the collected exceptions to build up a detailed error log. + if (errors.empty()) { + // should not happen ... + PyErr_SetString(defexc, cpyrt_PyText_AsString(topmsg)); + Py_DECREF(topmsg); + return; + } + + // if a _single_ exception was thrown from C++, assume it has priority (see + // below) + PyError_t* unique_from_cpp = nullptr; + for (auto& e : errors) { + if (e.fIsCpp) { + if (!unique_from_cpp) + unique_from_cpp = &e; + else { + // two C++ exceptions, resort to default behavior + unique_from_cpp = nullptr; + break; + } + } + } + + if (unique_from_cpp) { + // report only this error; the idea here is that all other errors come from + // the bindings (e.g. argument conversion errors), while the exception from + // C++ means that it originated from an otherwise successful call + + // bind the original C++ object, rather than constructing from topmsg, as it + // is expected to have informative state + RestorePyError(*unique_from_cpp); + } else { + // try to consolidate Python exceptions, otherwise select default + PyObject* exc_type = nullptr; + for (auto& e : errors) { +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* pytype = (PyObject*)Py_TYPE(e.fValue.get()); +#else + PyObject* pytype = e.fType.get(); +#endif + if (!exc_type) + exc_type = pytype; + else if (exc_type != pytype) { + exc_type = defexc; + break; + } + } + + // add the details to the topmsg + PyObject* separator = cpyrt_PyText_FromString("\n "); + for (auto& e : errors) { + PyObject* pyvalue = e.fValue.get(); + cpyrt_PyText_Append(&topmsg, separator); + if (cpyrt_PyText_Check(pyvalue)) { + cpyrt_PyText_Append(&topmsg, pyvalue); + } else if (pyvalue) { + PyObject* excstr = PyObject_Str(pyvalue); + if (!excstr) { + PyErr_Clear(); + excstr = PyObject_Str((PyObject*)Py_TYPE(pyvalue)); + } + cpyrt_PyText_AppendAndDel(&topmsg, excstr); + } else { + cpyrt_PyText_AppendAndDel(&topmsg, + cpyrt_PyText_FromString("unknown exception")); + } + } + + Py_DECREF(separator); + + // set the python exception + PyErr_SetString(exc_type, cpyrt_PyText_AsString(topmsg)); + } + + Py_DECREF(topmsg); +} + +//---------------------------------------------------------------------------- +static bool includesDone = false; +bool cpyrt::Utility::IncludePython() { + // setup Python API for callbacks + if (!includesDone) { + bool okay = interop::Compile( + // basic API (converters etc.) + "#include \"cpyrt/API.h\"\n" + + // utilities from the cpyrt public API + "#include \"cpyrt/DispatchPtr.h\"\n" + "#include \"cpyrt/PyException.h\"\n"); + includesDone = okay; + } + + return includesDone; +} diff --git a/src/cpyrt/Utility.h b/src/cpyrt/Utility.h new file mode 100644 index 0000000..c51b38e --- /dev/null +++ b/src/cpyrt/Utility.h @@ -0,0 +1,144 @@ +#ifndef CPYRT_UTILITY_H +#define CPYRT_UTILITY_H + +// Standard +#include +#include +#include +#include + +#include "Python.h" +#include "cppjit_interop.h" + +namespace cppjit::cpyrt { + +class PyCallable; + +#if PY_VERSION_HEX < 0x030b0000 +extern bool gDictLookupActive; +#endif + +// additional converter functions +unsigned long PyLongOrInt_AsULong(PyObject* pyobject); +PY_ULONG_LONG PyLongOrInt_AsULong64(PyObject* pyobject); + +namespace Utility { + +// convenience functions for adding methods to classes +bool AddToClass(PyObject* pyclass, const char* label, PyCFunction cfunc, + int flags = METH_VARARGS); +bool AddToClass(PyObject* pyclass, const char* label, const char* func); +bool AddToClass(PyObject* pyclass, const char* label, PyCallable* pyfunc); + +// helpers for dynamically constructing operators +PyCallable* FindUnaryOperator(PyObject* pyclass, const char* op); +PyCallable* FindBinaryOperator( + PyObject* left, PyObject* right, const char* op, + cppjit::interop::TCppScope_t scope = cppjit::interop::TCppScope_t{}); +PyCallable* FindBinaryOperator( + const std::string& lcname, const std::string& rcname, const char* op, + cppjit::interop::TCppScope_t scope = cppjit::interop::TCppScope_t{}, + bool reverse = false); + +// helper for template classes and methods +enum ArgPreference { kNone, kPointer, kReference, kValue }; +std::string ConstructTemplateArgs(PyObject* pyname, PyObject* tpArgs, + PyObject* args = nullptr, + ArgPreference = kNone, int argoff = 0, + int* pcnt = nullptr); +std::vector +GetTemplateArgsTypes(PyObject* scope, PyObject* tpArgs, + PyObject* args = nullptr, ArgPreference = kNone, + int argoff = 0, int* pcnt = nullptr); + +std::string CT2CppNameS(PyObject* pytc, bool allow_voidp); +inline PyObject* CT2CppName(PyObject* pytc, const char* cpd, bool allow_voidp) { + const std::string& name = CT2CppNameS(pytc, allow_voidp); + if (!name.empty()) { + if (name == "const char*") + cpd = ""; + return PyUnicode_FromString((std::string{name} + cpd).c_str()); + } + return nullptr; +} + +// helper for generating callbacks +void ConstructCallbackPreamble(const std::string& retType, + const std::vector& argtypes, + std::ostringstream& code); +void ConstructCallbackReturn(const std::string& retType, int nArgs, + std::ostringstream& code); + +// helper for function pointer conversions +PyObject* FuncPtr2StdFunction(const std::string& retType, + const std::string& signature, void* address); + +// initialize proxy type objects +bool InitProxy(PyObject* module, PyTypeObject* pytype, const char* name); + +std::unordered_map const& TypecodeMap(); + +// retrieve the memory buffer from pyobject, return buflength, tc (optional) is +// python array.array type code, size is type size, buf will point to buffer, +// and if check is true, some heuristics will be applied to check buffer +// compatibility with the type +Py_ssize_t GetBuffer(PyObject* pyobject, char tc, int size, void*& buf, + bool check = true); + +// data/operator mappings +std::string MapOperatorName(const std::string& name, bool bTakesParames, + bool* stubbed = nullptr); + +struct PyOperators { + PyOperators() + : fEq(nullptr), fNe(nullptr), fLt(nullptr), fLe(nullptr), fGt(nullptr), + fGe(nullptr), fLAdd(nullptr), fRAdd(nullptr), fSub(nullptr), + fLMul(nullptr), fRMul(nullptr), fDiv(nullptr), fHash(nullptr) {} + ~PyOperators(); + + PyObject* fEq; + PyObject* fNe; + PyObject *fLt, *fLe; + PyObject *fGt, *fGe; + PyObject *fLAdd, *fRAdd; + PyObject* fSub; + PyObject *fLMul, *fRMul; + PyObject* fDiv; + PyObject* fHash; +}; + +// meta information +std::string ClassName(PyObject* pyobj); +bool IsSTLIterator(const std::string& classname); + +// for threading: save call to PyErr_Occurred() +PyObject* PyErr_Occurred_WithGIL(); + +// helpers for collecting/maintaining python exception data +struct PyError_t { + struct PyObjectDeleter { + void operator()(PyObject* obj) { Py_XDECREF(obj); } + }; +#if PY_VERSION_HEX < 0x030c0000 + std::unique_ptr fType; + std::unique_ptr fTrace; +#endif + std::unique_ptr fValue; + bool fIsCpp = false; +}; + +PyError_t FetchPyError(); +void RestorePyError(PyError_t& error); + +size_t FetchError(std::vector&, bool is_cpp = false); +void SetDetailedException(std::vector&& errors /* clears */, + PyObject* topmsg /* steals ref */, PyObject* defexc); + +// setup Python API for callbacks +bool IncludePython(); + +} // namespace Utility + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_UTILITY_H diff --git a/src/cpyrt/cppjit_interop.h b/src/cpyrt/cppjit_interop.h new file mode 100644 index 0000000..9a6bc05 --- /dev/null +++ b/src/cpyrt/cppjit_interop.h @@ -0,0 +1,468 @@ +#ifndef CPYRT_CPPJIT_H +#define CPYRT_CPPJIT_H + +// Standard +#include +#include +#include +#include +#include + +// import/export (after precommondefs.h from PyPy) +#ifdef _MSC_VER +#define CPPJIT_IMPORT extern __declspec(dllimport) +#else +#define CPPJIT_IMPORT extern +#endif + +// some more types; assumes cppjit_interop.h follows Python.h +#ifndef PY_LONG_LONG +#ifdef _WIN32 +typedef __int64 PY_LONG_LONG; +#else +typedef long long PY_LONG_LONG; +#endif +#endif + +#ifndef PY_ULONG_LONG +#ifdef _WIN32 +typedef unsigned __int64 PY_ULONG_LONG; +#else +typedef unsigned long long PY_ULONG_LONG; +#endif +#endif + +#ifndef PY_LONG_DOUBLE +typedef long double PY_LONG_DOUBLE; +#endif + +// FIXME: We should not duplicate these definitions here and in CppInterOp.h +// The current setup relies on finding an identical symbol definition in +// libcppjitbackend.so which is fragile and requires updating both locations +// when changing. Ideally we should have the ability to set/get the template arg +// info provided through some factory methods in CppInterOp API, so the clients +// can rely completely on opaque pointers like we do for the rest of the +// argument types. +struct TemplateArgInfo { + void* m_Type; + const char* m_IntegralValue; + TemplateArgInfo(void* type, const char* integral_value = nullptr) + : m_Type(type), m_IntegralValue(integral_value) {} +}; + +namespace Cpp { +using TemplateArgInfo = ::TemplateArgInfo; + +struct DeclRef { + void* data; + DeclRef() : data(nullptr) {} + DeclRef(void* P) : data(P) {} + DeclRef(decltype(nullptr)) : data(nullptr) {} + explicit operator bool() const { return data != nullptr; } + friend bool operator==(DeclRef a, DeclRef b) { return a.data == b.data; } + friend bool operator!=(DeclRef a, DeclRef b) { return !(a == b); } +}; + +struct TypeRef { + void* data; + TypeRef() : data(nullptr) {} + TypeRef(void* P) : data(P) {} + TypeRef(decltype(nullptr)) : data(nullptr) {} + explicit operator bool() const { return data != nullptr; } + friend bool operator==(TypeRef a, TypeRef b) { return a.data == b.data; } + friend bool operator!=(TypeRef a, TypeRef b) { return !(a == b); } +}; + +struct FuncRef { + void* data; + FuncRef() : data(nullptr) {} + FuncRef(void* P) : data(P) {} + FuncRef(decltype(nullptr)) : data(nullptr) {} + explicit operator bool() const { return data != nullptr; } + friend bool operator==(FuncRef a, FuncRef b) { return a.data == b.data; } + friend bool operator!=(FuncRef a, FuncRef b) { return !(a == b); } +}; + +struct ObjectRef { + void* data; + ObjectRef() : data(nullptr) {} + ObjectRef(void* P) : data(P) {} + ObjectRef(decltype(nullptr)) : data(nullptr) {} + explicit operator bool() const { return data != nullptr; } + friend bool operator==(ObjectRef a, ObjectRef b) { return a.data == b.data; } + friend bool operator!=(ObjectRef a, ObjectRef b) { return !(a == b); } +}; +} // namespace Cpp + +template <> struct std::hash { + std::size_t operator()(const Cpp::DeclRef& obj) const { + return std::hash{}(obj.data); + } +}; +template <> struct std::hash { + std::size_t operator()(const Cpp::TypeRef& obj) const { + return std::hash{}(obj.data); + } +}; +template <> struct std::hash { + std::size_t operator()(const Cpp::FuncRef& obj) const { + return std::hash{}(obj.data); + } +}; +template <> struct std::hash { + std::size_t operator()(const Cpp::ObjectRef& obj) const { + return std::hash{}(obj.data); + } +}; + +namespace cppjit::interop { +typedef Cpp::DeclRef TCppScope_t; +typedef Cpp::TypeRef TCppType_t; +typedef Cpp::ObjectRef TCppObject_t; +typedef Cpp::FuncRef TCppMethod_t; +typedef size_t TCppIndex_t; +typedef void* TCppFuncAddr_t; + +// direct interpreter access ------------------------------------------------- +CPPJIT_IMPORT +bool Compile(const std::string& code, bool silent = false); +CPPJIT_IMPORT +std::string ToString(TCppScope_t klass, TCppObject_t obj); + +// name to opaque C++ scope representation ----------------------------------- +CPPJIT_IMPORT +std::string ResolveName(const std::string& cppitem_name); +CPPJIT_IMPORT +TCppType_t ResolveType(TCppType_t cppitem_name); +CPPJIT_IMPORT +TCppType_t ResolveEnumReferenceType(TCppType_t type); +CPPJIT_IMPORT +TCppType_t ResolveEnumPointerType(TCppType_t type); +CPPJIT_IMPORT +TCppType_t GetRealType(TCppType_t type); +CPPJIT_IMPORT +TCppType_t GetPointerType(TCppType_t type); +CPPJIT_IMPORT +TCppType_t GetReferencedType(TCppType_t type, bool rvalue = false); +CPPJIT_IMPORT +std::string ResolveEnum(TCppScope_t enum_scope); +CPPJIT_IMPORT +bool IsLValueReferenceType(TCppType_t type); +CPPJIT_IMPORT +bool IsRValueReferenceType(TCppType_t type); +CPPJIT_IMPORT +bool IsClassType(TCppType_t type); +CPPJIT_IMPORT +bool IsIntegerType(TCppType_t type, bool* is_signed = nullptr); +CPPJIT_IMPORT +bool IsPointerType(TCppType_t type); +CPPJIT_IMPORT +bool IsFunctionPointerType(TCppType_t type); +CPPJIT_IMPORT +TCppType_t GetType(const std::string& name, bool enable_slow_lookup = false); +CPPJIT_IMPORT +bool AppendTypesSlow(const std::string& name, + std::vector& types, + cppjit::interop::TCppScope_t parent = nullptr); +CPPJIT_IMPORT +TCppType_t GetComplexType(const std::string& element_type); +CPPJIT_IMPORT +TCppScope_t GetScope(const std::string& scope_name, + TCppScope_t parent_scope = TCppScope_t{}); +CPPJIT_IMPORT +TCppScope_t GetUnderlyingScope(TCppScope_t scope); +CPPJIT_IMPORT +TCppScope_t GetFullScope(const std::string& scope_name); +CPPJIT_IMPORT +TCppScope_t GetTypeScope(TCppScope_t klass); +CPPJIT_IMPORT +TCppScope_t GetNamed(const std::string& scope_name, + TCppScope_t parent_scope = TCppScope_t{}); +CPPJIT_IMPORT +TCppScope_t GetParentScope(TCppScope_t scope); +CPPJIT_IMPORT +TCppScope_t GetScopeFromType(TCppType_t type); +CPPJIT_IMPORT +TCppType_t GetTypeFromScope(TCppScope_t klass); +CPPJIT_IMPORT +TCppScope_t GetGlobalScope(); +CPPJIT_IMPORT +TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); +CPPJIT_IMPORT +size_t SizeOf(TCppScope_t klass); +CPPJIT_IMPORT +size_t SizeOfType(TCppType_t type); + +CPPJIT_IMPORT +bool IsBuiltin(const std::string& type_name); + +CPPJIT_IMPORT +bool IsBuiltin(TCppType_t type); + +CPPJIT_IMPORT +bool IsComplete(TCppScope_t type); + +// memory management --------------------------------------------------------- +CPPJIT_IMPORT +TCppObject_t Allocate(TCppScope_t scope); +CPPJIT_IMPORT +void Deallocate(TCppScope_t scope, TCppObject_t instance); +CPPJIT_IMPORT +TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); +CPPJIT_IMPORT +void Destruct(TCppScope_t scope, TCppObject_t instance); + +// method/function dispatching ----------------------------------------------- +CPPJIT_IMPORT +void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +CPPJIT_IMPORT +unsigned char CallB(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); +CPPJIT_IMPORT +char CallC(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +CPPJIT_IMPORT +short CallH(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +CPPJIT_IMPORT +int CallI(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +CPPJIT_IMPORT +long CallL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +CPPJIT_IMPORT +PY_LONG_LONG CallLL(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); +CPPJIT_IMPORT +float CallF(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +CPPJIT_IMPORT +double CallD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +CPPJIT_IMPORT +PY_LONG_DOUBLE CallLD(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); + +CPPJIT_IMPORT +void* CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +CPPJIT_IMPORT +char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, + size_t* length); +CPPJIT_IMPORT +TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, + size_t nargs, void* args); +CPPJIT_IMPORT +void CallDestructor(TCppScope_t type, TCppObject_t self); +CPPJIT_IMPORT +TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args, TCppType_t result_type); + +CPPJIT_IMPORT +TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, + bool check_enabled = true); + +// handling of function argument buffer -------------------------------------- +CPPJIT_IMPORT +void* AllocateFunctionArgs(size_t nargs); +CPPJIT_IMPORT +void DeallocateFunctionArgs(void* args); +CPPJIT_IMPORT +size_t GetFunctionArgSizeof(); +CPPJIT_IMPORT +size_t GetFunctionArgTypeoffset(); + +// scope reflection information ---------------------------------------------- +CPPJIT_IMPORT +bool IsNamespace(TCppScope_t scope); +CPPJIT_IMPORT +bool IsClass(TCppScope_t scope); +CPPJIT_IMPORT +bool IsTemplate(TCppScope_t scope); +CPPJIT_IMPORT +bool IsTemplateInstantiation(TCppScope_t scope); +CPPJIT_IMPORT +bool IsTypedefed(TCppScope_t scope); +CPPJIT_IMPORT +bool IsAbstract(TCppScope_t scope); +CPPJIT_IMPORT +bool IsEnumScope(TCppScope_t scope); +CPPJIT_IMPORT +bool IsEnumConstant(TCppScope_t scope); +CPPJIT_IMPORT +bool IsEnumType(TCppType_t type); +CPPJIT_IMPORT +bool IsAggregate(TCppScope_t type); +CPPJIT_IMPORT +bool IsDefaultConstructable(TCppScope_t scope); +CPPJIT_IMPORT +bool IsVariable(TCppScope_t scope); + +CPPJIT_IMPORT +void GetAllCppNames(TCppScope_t scope, std::set& cppnames); + +// namespace reflection information ------------------------------------------ +CPPJIT_IMPORT +std::vector GetUsingNamespaces(TCppScope_t); + +// class reflection information ---------------------------------------------- +CPPJIT_IMPORT +std::string GetFinalName(TCppScope_t type); +CPPJIT_IMPORT +std::string GetScopedFinalName(TCppScope_t type); +CPPJIT_IMPORT +bool HasVirtualDestructor(TCppScope_t type); +CPPJIT_IMPORT +TCppIndex_t GetNumBases(TCppScope_t klass); +CPPJIT_IMPORT +TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass); +CPPJIT_IMPORT +std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase); +CPPJIT_IMPORT +TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase); +CPPJIT_IMPORT +bool IsSubclass(TCppScope_t derived, TCppScope_t base); +CPPJIT_IMPORT +bool IsSmartPtr(TCppScope_t klass); +CPPJIT_IMPORT +bool GetSmartPtrInfo(const std::string&, TCppScope_t* raw, TCppMethod_t* deref); +// calculate offsets between declared and actual type, up-cast: direction > 0; +// down-cast: direction < 0 +CPPJIT_IMPORT +ptrdiff_t GetBaseOffset(TCppScope_t derived, TCppScope_t base, + TCppObject_t address, int direction, + bool rerror = false); + +// method/function reflection information ------------------------------------ +CPPJIT_IMPORT +void GetClassMethods(TCppScope_t scope, std::vector& methods); +CPPJIT_IMPORT +std::vector GetMethodsFromName(TCppScope_t scope, + const std::string& name); +CPPJIT_IMPORT +std::string GetName(TCppScope_t); +CPPJIT_IMPORT +std::string GetFullName(TCppScope_t); +CPPJIT_IMPORT +TCppType_t GetMethodReturnType(TCppMethod_t); +CPPJIT_IMPORT +std::string GetMethodReturnTypeAsString(TCppMethod_t); +CPPJIT_IMPORT +TCppIndex_t GetMethodNumArgs(TCppMethod_t); +CPPJIT_IMPORT +TCppIndex_t GetMethodReqArgs(TCppMethod_t); +CPPJIT_IMPORT +std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); +CPPJIT_IMPORT +TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); +CPPJIT_IMPORT +TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, + const std::string& req_type); +CPPJIT_IMPORT +std::string GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg); +CPPJIT_IMPORT +std::string GetMethodArgCanonTypeAsString(TCppMethod_t method, + TCppIndex_t iarg); +CPPJIT_IMPORT +std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); +CPPJIT_IMPORT +std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, + TCppIndex_t max_args = (TCppIndex_t)-1); +// GetMethodPrototype is unused. +CPPJIT_IMPORT +std::string GetMethodPrototype(TCppMethod_t, bool show_formal_args); +CPPJIT_IMPORT +std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); +CPPJIT_IMPORT +bool IsConstMethod(TCppMethod_t); +// Templated method/function reflection information +// ------------------------------------ +CPPJIT_IMPORT +void GetTemplatedMethods(TCppScope_t scope, std::vector& methods); +CPPJIT_IMPORT +TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, + bool accept_namespace = false); +CPPJIT_IMPORT +std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); +CPPJIT_IMPORT +bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); +CPPJIT_IMPORT +bool IsTemplatedMethod(TCppMethod_t method); +CPPJIT_IMPORT +bool IsStaticTemplate(TCppScope_t scope, const std::string& name); +CPPJIT_IMPORT +TCppMethod_t GetMethodTemplate(TCppScope_t scope, const std::string& name, + const std::string& proto); +CPPJIT_IMPORT +void GetClassOperators(cppjit::interop::TCppScope_t klass, + const std::string& opname, + std::vector& operators); +CPPJIT_IMPORT +TCppMethod_t GetGlobalOperator(TCppScope_t scope, const std::string& lc, + const std::string& rc, const std::string& op); + +// method properties --------------------------------------------------------- +CPPJIT_IMPORT +bool IsDeletedMethod(TCppMethod_t method); +CPPJIT_IMPORT +bool IsPublicMethod(TCppMethod_t method); +CPPJIT_IMPORT +bool IsProtectedMethod(TCppMethod_t method); +CPPJIT_IMPORT +bool IsPrivateMethod(TCppMethod_t method); +CPPJIT_IMPORT +bool IsConstructor(TCppMethod_t method); +CPPJIT_IMPORT +bool IsDestructor(TCppMethod_t method); +CPPJIT_IMPORT +bool IsStaticMethod(TCppMethod_t method); +CPPJIT_IMPORT +bool IsExplicit(TCppMethod_t method); + +// data member reflection information ---------------------------------------- +CPPJIT_IMPORT +void GetDatamembers(TCppScope_t scope, std::vector& datamembers); +CPPJIT_IMPORT +bool IsLambdaClass(TCppType_t type); +CPPJIT_IMPORT +TCppScope_t WrapLambdaFromVariable(TCppScope_t var); +CPPJIT_IMPORT +TCppMethod_t AdaptFunctionForLambdaReturn(TCppMethod_t fn); +CPPJIT_IMPORT +TCppType_t GetDatamemberType(TCppScope_t data); +CPPJIT_IMPORT +std::string GetDatamemberTypeAsString(TCppScope_t var); +CPPJIT_IMPORT +std::string GetTypeAsString(TCppType_t type); +CPPJIT_IMPORT +intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); +CPPJIT_IMPORT +bool CheckDatamember(TCppScope_t scope, const std::string& name); + +// // data member properties +// ---------------------------------------------------- +CPPJIT_IMPORT +bool IsPublicData(TCppScope_t var); +CPPJIT_IMPORT +bool IsProtectedData(TCppScope_t var); +CPPJIT_IMPORT +bool IsPrivateData(TCppScope_t var); +CPPJIT_IMPORT +bool IsStaticDatamember(TCppScope_t var); +CPPJIT_IMPORT +bool IsConstVar(TCppScope_t var); +CPPJIT_IMPORT +TCppMethod_t ReduceReturnType(TCppMethod_t fn, TCppType_t reduce); +CPPJIT_IMPORT +std::vector GetDimensions(TCppType_t type); + +// enum properties ----------------------------------------------------------- +CPPJIT_IMPORT +std::vector GetEnumConstants(TCppScope_t scope); +CPPJIT_IMPORT +TCppType_t GetEnumConstantType(TCppScope_t scope); +CPPJIT_IMPORT +TCppIndex_t GetEnumDataValue(TCppScope_t scope); + +CPPJIT_IMPORT +TCppScope_t InstantiateTemplate(TCppScope_t tmpl, Cpp::TemplateArgInfo* args, + size_t args_size); + +CPPJIT_IMPORT +void DumpScope(TCppScope_t scope); +} // namespace cppjit::interop + +#endif // !CPYRT_CPPJIT_H diff --git a/src/cpyrt/cpyrt.h b/src/cpyrt/cpyrt.h new file mode 100644 index 0000000..f7ce901 --- /dev/null +++ b/src/cpyrt/cpyrt.h @@ -0,0 +1,226 @@ +#ifndef CPYRT_CPYRT_H +#define CPYRT_CPYRT_H + +#ifdef _WIN32 +// Disable warning C4275: non dll-interface class +#pragma warning(disable : 4275) +// Disable warning C4251: needs to have dll-interface to be used by clients +#pragma warning(disable : 4251) +// Disable warning C4800: 'int' : forcing value to bool +#pragma warning(disable : 4800) +// Avoid that pyconfig.h decides using a #pragma what library python library to +// use +// #define MS_NO_COREDLL 1 +#endif + +// to prevent problems with fpos_t and redefinition warnings +#if defined(linux) + +#include + +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif + +#ifdef _FILE_OFFSET_BITS +#undef _FILE_OFFSET_BITS +#endif + +#ifdef _XOPEN_SOURCE +#undef _XOPEN_SOURCE +#endif + +#endif // linux + +#define PY_SSIZE_T_CLEAN +#include "Python.h" +#include + +namespace cppjit::cpyrt { +typedef Py_ssize_t dim_t; +} // namespace cppjit::cpyrt + +#if PY_VERSION_HEX >= 0x030b0000 +typedef Py_ssize_t (*dict_lookup_func)(PyDictObject*, PyObject*, Py_hash_t, + PyObject**); +#else +typedef Py_ssize_t (*dict_lookup_func)(PyDictObject*, PyObject*, Py_hash_t, + PyObject***, Py_ssize_t*); +#endif + +// for 3.0 support (backwards compatibility, really) +#define cpyrt_PyText_Check PyUnicode_Check +#define cpyrt_PyText_CheckExact PyUnicode_CheckExact +#define cpyrt_PyText_AsString PyUnicode_AsUTF8 +#define cpyrt_PyText_AsStringChecked PyUnicode_AsUTF8 +#define cpyrt_PyText_GetSize PyUnicode_GetSize +#define cpyrt_PyText_GET_SIZE PyUnicode_GET_LENGTH +#define cpyrt_PyUnicode_GET_SIZE PyUnicode_GET_LENGTH +#define cpyrt_PyText_FromFormat PyUnicode_FromFormat +#define cpyrt_PyText_FromString PyUnicode_FromString +#define cpyrt_PyText_InternFromString PyUnicode_InternFromString +#define cpyrt_PyText_Append PyUnicode_Append +#define cpyrt_PyText_AppendAndDel PyUnicode_AppendAndDel +#define cpyrt_PyText_FromStringAndSize PyUnicode_FromStringAndSize + +#define _cpyrt_PyText_AsStringAndSize PyUnicode_AsUTF8AndSize + +static inline const char* cpyrt_PyText_AsStringAndSize(PyObject* pystr, + Py_ssize_t* size) { + const char* cstr = _cpyrt_PyText_AsStringAndSize(pystr, size); + if (!cstr && PyBytes_CheckExact(pystr)) { + PyErr_Clear(); + PyBytes_AsStringAndSize(pystr, (char**)&cstr, size); + } + return cstr; +} + +#define cpyrt_PyText_Type PyUnicode_Type + +#define PyIntObject PyLongObject +#define PyInt_Check PyLong_Check +#define PyInt_AsLong PyLong_AsLong +#define PyInt_AS_LONG PyLong_AsLong +#define PyInt_AsSsize_t PyLong_AsSsize_t +#define PyInt_CheckExact PyLong_CheckExact +#define PyInt_FromLong PyLong_FromLong +#define PyInt_FromSsize_t PyLong_FromSsize_t + +#define PyInt_Type PyLong_Type + +#define cpyrt_PyCapsule_New PyCapsule_New +#define cpyrt_PyCapsule_CheckExact PyCapsule_CheckExact +#define cpyrt_PyCapsule_GetPointer PyCapsule_GetPointer + +#define CPPJIT__long__ "__int__" +#define CPPJIT__idiv__ "__itruediv__" +#define CPPJIT__div__ "__truediv__" +#define CPPJIT__next__ "__next__" + +#define Py_TPFLAGS_HAVE_RICHCOMPARE 0 +#define Py_TPFLAGS_CHECKTYPES 0 + +#define PyClass_Check PyType_Check + +#define PyBuffer_Type PyMemoryView_Type + +#define cpyrt_PySliceCast PyObject* +#define PyUnicode_GetSize PyUnicode_GetLength + +#define cpyrt_PyUnicode_AsWideChar PyUnicode_AsWideChar + +#ifdef R__MACOSX +#if SIZEOF_SIZE_T == SIZEOF_INT +#if defined(MAC_OS_X_VERSION_10_4) +#define PY_SSIZE_T_FORMAT "%ld" +#else +#define PY_SSIZE_T_FORMAT "%d" +#endif +#elif SIZEOF_SIZE_T == SIZEOF_LONG +#define PY_SSIZE_T_FORMAT "%ld" +#endif +#else +#define PY_SSIZE_T_FORMAT "%zd" +#endif + +#ifndef Py_RETURN_NONE +#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None +#endif + +#ifndef Py_RETURN_TRUE +#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True +#endif + +#ifndef Py_RETURN_FALSE +#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False +#endif + +// vector call support +#define cpyrt_PyCFunction_Call PyObject_Call + +// vector call support +typedef PyObject* const* cpyrt_PyArgs_t; +static inline PyObject* cpyrt_PyArgs_GET_ITEM(cpyrt_PyArgs_t args, + Py_ssize_t i) { + return args[i]; +} +static inline PyObject* cpyrt_PyArgs_SET_ITEM(cpyrt_PyArgs_t args, Py_ssize_t i, + PyObject* item) { + return ((PyObject**)args)[i] = item; +} +static inline Py_ssize_t cpyrt_PyArgs_GET_SIZE(cpyrt_PyArgs_t, size_t nargsf) { + return PyVectorcall_NARGS(nargsf); +} +static inline cpyrt_PyArgs_t cpyrt_PyArgs_New(Py_ssize_t N) { + return (cpyrt_PyArgs_t)PyMem_Malloc(N * sizeof(PyObject*)); +} +static inline void cpyrt_PyArgs_DEL(cpyrt_PyArgs_t args) { + PyMem_Free((void*)args); +} +#define cpyrt_PyObject_Call PyObject_Vectorcall +inline PyObject* cpyrt_tp_call(PyObject* cb, cpyrt_PyArgs_t args, size_t nargsf, + PyObject* kwds) { + Py_ssize_t offset = Py_TYPE(cb)->tp_vectorcall_offset; + vectorcallfunc func = *(vectorcallfunc*)(((char*)cb) + offset); + return func(cb, args, nargsf, kwds); +} + +#ifndef Py_TPFLAGS_HAVE_VECTORCALL +#define Py_TPFLAGS_HAVE_VECTORCALL _Py_TPFLAGS_HAVE_VECTORCALL +#endif + +// weakref forced strong reference +#if PY_VERSION_HEX < 0x30d0000 +static inline PyObject* cpyrt_GetWeakRef(PyObject* ref) { + PyObject* pyobject = PyWeakref_GetObject(ref); + if (!pyobject || pyobject == Py_None) + return nullptr; + Py_INCREF(pyobject); + return pyobject; +} +#else +static inline PyObject* cpyrt_GetWeakRef(PyObject* ref) { + PyObject* pyobject = nullptr; + if (PyWeakref_GetRef(ref, &pyobject) != -1) + return pyobject; + return nullptr; +} +#endif + +// C++ version of the cppjit API +#include "cppjit_interop.h" + +// export macros for our own API +#include "cpyrt/CommonDefs.h" + +// --- reusable PyTypeObject initializer tail ------------------------------- +// Members appended to PyTypeObject in newer CPython releases. Every cpyrt +// type leaves all of these zero/null-initialized, so they share one tail. +// To support a future Python version, add one block below and one line to +// CPYRT_PYTYPE_TAIL. + +#if PY_VERSION_HEX >= 0x030c0000 +#define CPYRT_TP_WATCHED , 0 /* tp_watched (>= 3.12) */ +#else +#define CPYRT_TP_WATCHED +#endif + +#if PY_VERSION_HEX >= 0x030d0000 +#define CPYRT_TP_VERSIONS_USED , 0 /* tp_versions_used(>= 3.13) */ +#else +#define CPYRT_TP_VERSIONS_USED +#endif + +#if PY_VERSION_HEX >= 0x030f0000 +#define CPYRT_TP_ITERITEM , nullptr /* _tp_iteritem (>= 3.15) */ +#else +#define CPYRT_TP_ITERITEM +#endif + +#define CPYRT_PYTYPE_TAIL \ + CPYRT_TP_WATCHED \ + CPYRT_TP_VERSIONS_USED \ + CPYRT_TP_ITERITEM +// -------------------------------------------------------------------------- + +#endif // !CPYRT_CPYRT_H diff --git a/src/cpyrt/cpyrtModule.cxx b/src/cpyrt/cpyrtModule.cxx new file mode 100644 index 0000000..b2c0733 --- /dev/null +++ b/src/cpyrt/cpyrtModule.cxx @@ -0,0 +1,1215 @@ +// Bindings +#include "cpyrt.h" + +using namespace cppjit; +#include "CPPDataMember.h" +#include "CPPExcInstance.h" +#include "CPPInstance.h" +#include "CPPOverload.h" +#include "CPPScope.h" +#include "CallContext.h" +#include "Converters.h" +#include "CustomPyTypes.h" +#include "LowLevelViews.h" +#include "MemoryRegulator.h" +#include "ProxyWrappers.h" +#include "PyStrings.h" +#include "TemplateProxy.h" +#include "TupleOfInstances.h" +#include "Utility.h" +#include "cppjit_interop.h" +#include + +#define CPYRT_INTERNAL 1 +#include "cpyrt/DispatchPtr.h" +namespace cppjit::cpyrt { +void* Instance_AsVoidPtr(PyObject* pyobject); +PyObject* Instance_FromVoidPtr(void* addr, const std::string& classname, + bool python_owns = false); +} // namespace cppjit::cpyrt +#undef CPYRT_INTERNAL + +// Standard +#include +#include +#include +#include +#include +#include +#include +#include + +#if PY_VERSION_HEX < 0x030b0000 +namespace cppjit::cpyrt { +extern dict_lookup_func gDictLookupOrg; +dict_lookup_func gDictLookupOrg = nullptr; +} // namespace cppjit::cpyrt +#endif + +std::unordered_map TypeReductionMap; + +// Note: as of py3.11, dictionary objects no longer carry a function pointer for +// the lookup, so it can no longer be shimmed and "from cppjit.interactive +// import *" thus no longer works. +#if PY_VERSION_HEX < 0x030b0000 + +//- from Python's dictobject.c ------------------------------------------------- +typedef struct PyDictKeyEntry { + /* Cached hash code of me_key. */ + Py_hash_t me_hash; + PyObject* me_key; + PyObject* me_value; /* This field is only meaningful for combined tables */ +} PyDictEntry; + +typedef struct _dictkeysobject { + Py_ssize_t dk_refcnt; + Py_ssize_t dk_size; + dict_lookup_func dk_lookup; + Py_ssize_t dk_usable; + Py_ssize_t dk_nentries; + union { + int8_t as_1[8]; + int16_t as_2[4]; + int32_t as_4[2]; +#if SIZEOF_VOID_P > 4 + int64_t as_8[1]; +#endif + } dk_indices; +} PyDictKeysObject; + +#define CPYRT_GET_DICT_LOOKUP(mp) ((dict_lookup_func&)mp->ma_keys->dk_lookup) + +#endif // PY_VERSION_HEX < 0x030b0000 + +//- data ----------------------------------------------------------------------- +static PyObject* nullptr_repr(PyObject*) { + return cpyrt_PyText_FromString("nullptr"); +} + +static void nullptr_dealloc(PyObject*) { + Py_FatalError("deallocating nullptr"); +} + +static int nullptr_nonzero(PyObject*) { return 0; } + +static PyNumberMethods nullptr_as_number = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + (inquiry)nullptr_nonzero, // tp_nonzero (nb_bool in p3) + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // nb_floor_divide + 0, // nb_true_divide + 0, + 0, + 0, // nb_index + 0, // nb_matrix_multiply + 0 // nb_inplace_matrix_multiply +}; + +static PyTypeObject PyNullPtr_t_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) "nullptr_t", // tp_name + sizeof(PyObject), // tp_basicsize + 0, // tp_itemsize + nullptr_dealloc, // tp_dealloc (never called) + 0, + 0, + 0, + 0, + nullptr_repr, // tp_repr + &nullptr_as_number, // tp_as_number + 0, + 0, +#if PY_VERSION_HEX >= 0x030d0000 + (hashfunc)Py_HashPointer, // tp_hash +#else + (hashfunc)_Py_HashPointer, // tp_hash +#endif + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +static PyObject* default_repr(PyObject*) { + return cpyrt_PyText_FromString("type default"); +} + +static void default_dealloc(PyObject*) { + Py_FatalError("deallocating default"); +} + +static PyTypeObject PyDefault_t_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) "default_t", // tp_name + sizeof(PyObject), // tp_basicsize + 0, // tp_itemsize + default_dealloc, // tp_dealloc (never called) + 0, + 0, + 0, + 0, + default_repr, // tp_repr + 0, + 0, + 0, +#if PY_VERSION_HEX >= 0x030d0000 + (hashfunc)Py_HashPointer, // tp_hash +#else + (hashfunc)_Py_HashPointer, // tp_hash +#endif + 0, + 0, + 0, + 0, + 0, + Py_TPFLAGS_DEFAULT, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // tp_del + 0, // tp_version_tag + 0, // tp_finalize + 0 // tp_vectorcall + CPYRT_PYTYPE_TAIL}; + +namespace { + +struct { + PyObject_HEAD +} _cpyrt_NullPtrStruct{PyObject_HEAD_INIT(&PyNullPtr_t_Type)}; + +struct { + PyObject_HEAD +} _cpyrt_DefaultStruct{PyObject_HEAD_INIT(&PyDefault_t_Type)}; + +// TODO: refactor with Converters.cxx +struct cpyrt_tagCDataObject { // non-public (but stable) + PyObject_HEAD char* b_ptr; + int b_needsfree; +}; + +} // unnamed namespace + +namespace cppjit::cpyrt { +PyObject* gThisModule = nullptr; +PyObject* gPyTypeMap = nullptr; +PyObject* gNullPtrObject = nullptr; +PyObject* gDefaultObject = nullptr; +PyObject* gBusException = nullptr; +PyObject* gSegvException = nullptr; +PyObject* gIllException = nullptr; +PyObject* gAbrtException = nullptr; +std::unordered_set gPinnedTypes; +std::ostringstream gCapturedError; +std::streambuf* gOldErrorBuffer = nullptr; + +std::unordered_map>& pythonizations() { + static std::unordered_map> pyzMap; + return pyzMap; +} +} // namespace cppjit::cpyrt + +//- private helpers ------------------------------------------------------------ +namespace { + +using namespace cppjit::cpyrt; + +//---------------------------------------------------------------------------- +#if PY_VERSION_HEX < 0x030b0000 +namespace { + +class GblGetter { +public: + GblGetter() { + PyObject* cppjit = PyImport_AddModule((char*)"cppjit"); + fGbl = PyObject_GetAttrString(cppjit, (char*)"gbl"); + } + ~GblGetter() { Py_DECREF(fGbl); } + + PyObject* operator*() { return fGbl; } + +private: + PyObject* fGbl; +}; + +} // unnamed namespace + +inline Py_ssize_t OrgDictLookup(PyDictObject* mp, PyObject* key, Py_hash_t hash, + PyObject*** value_addr, Py_ssize_t* hashpos) { + return (*gDictLookupOrg)(mp, key, hash, value_addr, hashpos); +} +#define CPYRT_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos) \ + OrgDictLookup(mp, key, hash, value_addr, hashpos) + +Py_ssize_t cpyrtLookDictString(PyDictObject* mp, PyObject* key, Py_hash_t hash, + PyObject*** value_addr, Py_ssize_t* hashpos) { + static GblGetter gbl; + Py_ssize_t ep; + + // first search dictionary itself + ep = CPYRT_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); + if (gDictLookupActive) + return ep; + + if (ep >= 0) + return ep; + + // filter for builtins + if (PyDict_GetItem(PyEval_GetBuiltins(), key) != 0) + return ep; + + // normal lookup failed, attempt to get C++ enum/global/class from top-level + gDictLookupActive = true; + + // attempt to get C++ enum/global/class from top-level + PyObject* val = PyObject_GetAttr(*gbl, key); + + if (val) { + // success ... + + if (CPPDataMember_CheckExact(val)) { + // don't want to add to dictionary (the proper place would be the + // dictionary of the (meta)class), but modifying ep will be noticed no + // matter what; just return the actual value and live with the copy in + // the dictionary (mostly, this is correct) + PyObject* actual_val = Py_TYPE(val)->tp_descr_get(val, nullptr, nullptr); + Py_DECREF(val); + val = actual_val; + } + + // add reference to C++ entity in the given dictionary + CPYRT_GET_DICT_LOOKUP(mp) = gDictLookupOrg; // prevent recursion + if (PyDict_SetItem((PyObject*)mp, key, val) == 0) { + ep = CPYRT_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); + } else { + ep = -1; + } + CPYRT_GET_DICT_LOOKUP(mp) = cpyrtLookDictString; // restore + + // done with val + Py_DECREF(val); + } else + PyErr_Clear(); + + if (mp->ma_keys->dk_usable <= 0) { + // big risk that this lookup will result in a resize, so force it here + // to be able to reset the lookup function; of course, this is nowhere + // near fool-proof, but should cover interactive usage ... + CPYRT_GET_DICT_LOOKUP(mp) = gDictLookupOrg; + const int maxinsert = 5; + PyObject* buf[maxinsert]; + for (int varmax = 1; varmax <= maxinsert; ++varmax) { + for (int ivar = 0; ivar < varmax; ++ivar) { + buf[ivar] = cpyrt_PyText_FromFormat("__CPYRT_FORCE_RESIZE_%d", ivar); + PyDict_SetItem((PyObject*)mp, buf[ivar], Py_None); + } + for (int ivar = 0; ivar < varmax; ++ivar) { + PyDict_DelItem((PyObject*)mp, buf[ivar]); + Py_DECREF(buf[ivar]); + } + if (0 < mp->ma_keys->dk_usable) + break; + } + + // make sure the entry pointer is still valid by re-doing the lookup + ep = CPYRT_ORGDICT_LOOKUP(mp, key, hash, value_addr, hashpos); + + // full reset of all lookup functions + gDictLookupOrg = CPYRT_GET_DICT_LOOKUP(mp); + CPYRT_GET_DICT_LOOKUP(mp) = cpyrtLookDictString; // restore + } + + // stopped calling into the reflection system + gDictLookupActive = false; + return ep; +} + +#endif // PY_VERSION_HEX < 0x030b0000 + +//---------------------------------------------------------------------------- +static PyObject* SetCppLazyLookup(PyObject*, PyObject* args) { +#if PY_VERSION_HEX < 0x030b0000 + // Modify the given dictionary to install the lookup function that also + // tries the global C++ namespace before failing. Called on a module's + // dictionary, this allows for lazy lookups. This works fine for p3.2 and + // earlier, but should not be used beyond interactive code for p3.3 and later + // b/c resizing causes the lookup function to revert to the default + // (lookdict_unicode_nodummy). + PyDictObject* dict = nullptr; + if (!PyArg_ParseTuple(args, const_cast("O!"), &PyDict_Type, &dict)) + return nullptr; + + CPYRT_GET_DICT_LOOKUP(dict) = cpyrtLookDictString; +#else + // As of py3.11, there is no longer a lookup function pointer in the dict + // object to replace. Since this feature is not widely advertised, it's simply + // dropped + if (PyErr_WarnEx(PyExc_RuntimeWarning, + (char*)"lazy lookup is no longer supported", 1) < 0) + return nullptr; + (void)args; // avoid warning about unused parameter +#endif + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* MakeCppTemplateClass(PyObject* /* self */, PyObject* args) { + // Create a binding for a templated class instantiation. + + // args is class name + template arguments; build full instantiation + Py_ssize_t nArgs = PyTuple_GET_SIZE(args); + if (nArgs < 2) { + PyErr_Format(PyExc_TypeError, + "too few arguments for template instantiation"); + return nullptr; + } + PyObject* cppscope = PyTuple_GET_ITEM(args, 0); + void* tmpl = PyLong_AsVoidPtr(cppscope); + + // build "< type, type, ... >" part of class name (modifies pyname) + std::vector types = + Utility::GetTemplateArgsTypes(cppscope, args, nullptr, Utility::kNone, 1); + if (PyErr_Occurred()) + return nullptr; + + interop::TCppScope_t scope = + interop::InstantiateTemplate(tmpl, types.data(), types.size()); + for (Cpp::TemplateArgInfo i : types) { + if (i.m_IntegralValue) + std::free((void*)i.m_IntegralValue); + } + + if (!scope) { + PyErr_Format(PyExc_TypeError, + "Template instantiation failed: '%s' with args: '%s\n'", + interop::GetScopedFinalName(tmpl).c_str(), + cpyrt_PyText_AsString(PyObject_Repr(args))); + return nullptr; + } + + return CreateScopeProxy(scope); +} + +//---------------------------------------------------------------------------- +static char* GCIA_kwlist[] = {(char*)"instance", (char*)"field", (char*)"byref", + NULL}; +static void* GetCPPInstanceAddress(const char* fname, PyObject* args, + PyObject* kwds) { + // Helper to get the address (address-of-address) of various object proxy + // types. + CPPInstance* pyobj = 0; + PyObject* pyname = 0; + int byref = 0; + if (PyArg_ParseTupleAndKeywords(args, kwds, const_cast("O|O!b"), + GCIA_kwlist, &pyobj, &cpyrt_PyText_Type, + &pyname, &byref)) { + + if (CPPInstance_Check(pyobj)) { + if (pyname != 0) { + // locate property proxy for offset info + CPPDataMember* pyprop = nullptr; + + PyObject* pyclass = (PyObject*)Py_TYPE((PyObject*)pyobj); + PyObject* dict = PyObject_GetAttr(pyclass, PyStrings::gDict); + pyprop = (CPPDataMember*)PyObject_GetItem(dict, pyname); + Py_DECREF(dict); + + if (CPPDataMember_Check(pyprop)) { + // this is an address of a value (i.e. &myobj->prop) + void* addr = (void*)pyprop->GetAddress(pyobj); + Py_DECREF(pyprop); + return addr; + } + + Py_XDECREF(pyprop); + + PyErr_Format(PyExc_TypeError, "%s is not a valid data member", + cpyrt_PyText_AsString(pyname)); + return nullptr; + } + + // this is an address of an address (i.e. &myobj, with myobj of type + // MyObj*) note that the return result may be null + if (!byref) + return ((CPPInstance*)pyobj)->GetObject(); + return &((CPPInstance*)pyobj)->GetObjectRaw(); + + } else if (cpyrt_PyText_Check(pyobj)) { + // special cases for access to the cpyrt API + std::string req = cpyrt_PyText_AsString((PyObject*)pyobj); + if (req == "Instance_AsVoidPtr") + return (void*)&Instance_AsVoidPtr; + else if (req == "Instance_FromVoidPtr") + return (void*)&Instance_FromVoidPtr; + } + } + + if (!PyErr_Occurred()) + PyErr_Format(PyExc_ValueError, "invalid argument for %s", fname); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* addressof(PyObject* /* dummy */, PyObject* args, + PyObject* kwds) { + // Return object proxy address as a value (cppjit-style), or the same for an + // array. + void* addr = GetCPPInstanceAddress("addressof", args, kwds); + if (addr) + return PyLong_FromLongLong((intptr_t)addr); + else if (!PyErr_Occurred()) { + return PyLong_FromLong(0); + } else if (PyTuple_CheckExact(args) && PyTuple_GET_SIZE(args) == 1) { + PyErr_Clear(); + PyObject* arg0 = PyTuple_GET_ITEM(args, 0); + + // nullptr special case + if (arg0 == gNullPtrObject || + (PyInt_Check(arg0) && PyInt_AsLong(arg0) == 0)) + return PyLong_FromLong(0); + + // overload if unambiguous + if (CPPOverload_CheckExact(arg0)) { + const auto& methods = ((CPPOverload*)arg0)->fMethodInfo->fMethods; + if (methods.size() != 1) { + PyErr_SetString(PyExc_TypeError, "overload is not unambiguous"); + return nullptr; + } + + interop::TCppFuncAddr_t caddr = methods[0]->GetFunctionAddress(); + return PyLong_FromLongLong((intptr_t)caddr); + } + + // C functions (incl. ourselves) + if (PyCFunction_Check(arg0)) { + void* caddr = (void*)PyCFunction_GetFunction(arg0); + return PyLong_FromLongLong((intptr_t)caddr); + } + + // LowLevelViews + if (LowLevelView_CheckExact(arg0)) { + auto* llv = (LowLevelView*)arg0; + return PyLong_FromLongLong((intptr_t)llv->get_buf()); + } + + // final attempt: any type of buffer + Utility::GetBuffer(arg0, '*', 1, addr, false); + if (addr) + return PyLong_FromLongLong((intptr_t)addr); + } + + // error message if not already set + if (!PyErr_Occurred()) { + if (PyTuple_CheckExact(args) && PyTuple_GET_SIZE(args)) { + PyObject* str = PyObject_Str(PyTuple_GET_ITEM(args, 0)); + if (str && cpyrt_PyText_Check(str)) + PyErr_Format(PyExc_TypeError, "unknown object %s", + cpyrt_PyText_AsString(str)); + else + PyErr_Format(PyExc_TypeError, "unknown object at %p", + (void*)PyTuple_GET_ITEM(args, 0)); + Py_XDECREF(str); + } + } + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* AsCObject(PyObject* /* unused */, PyObject* args, + PyObject* kwds) { + // Return object proxy as an opaque CObject. + void* addr = GetCPPInstanceAddress("as_cobject", args, kwds); + if (addr) + return cpyrt_PyCapsule_New((void*)addr, nullptr, nullptr); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* AsCapsule(PyObject* /* unused */, PyObject* args, + PyObject* kwds) { + // Return object proxy as an opaque PyCapsule. + void* addr = GetCPPInstanceAddress("as_capsule", args, kwds); + if (addr) + return PyCapsule_New(addr, nullptr, nullptr); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* AsCTypes(PyObject* /* unused */, PyObject* args, + PyObject* kwds) { + // Return object proxy as a ctypes c_void_p + void* addr = GetCPPInstanceAddress("as_ctypes", args, kwds); + if (!addr) + return nullptr; + + // TODO: refactor code below with converters code + static PyTypeObject* ct_cvoidp = nullptr; + if (!ct_cvoidp) { + PyObject* ctmod = PyImport_ImportModule("ctypes"); // ref-count kept + if (!ctmod) + return nullptr; + + ct_cvoidp = (PyTypeObject*)PyObject_GetAttrString(ctmod, "c_void_p"); + Py_DECREF(ctmod); + if (!ct_cvoidp) + return nullptr; + Py_DECREF(ct_cvoidp); // module keeps a reference + } + + PyObject* ref = ct_cvoidp->tp_new(ct_cvoidp, nullptr, nullptr); + *(void**)((cpyrt_tagCDataObject*)ref)->b_ptr = addr; + ((cpyrt_tagCDataObject*)ref)->b_needsfree = 0; + return ref; +} + +//---------------------------------------------------------------------------- +static PyObject* AsMemoryView(PyObject* /* unused */, PyObject* pyobject) { + // Return a raw memory view on arrays of PODs. + if (!CPPInstance_Check(pyobject)) { + PyErr_SetString(PyExc_TypeError, "C++ object proxy expected"); + return nullptr; + } + + CPPInstance* pyobj = (CPPInstance*)pyobject; + interop::TCppScope_t klass = ((CPPClass*)Py_TYPE(pyobject))->fCppType; + + Py_ssize_t array_len = pyobj->ArrayLength(); + + if (array_len < 0 || !interop::IsAggregate(klass)) { + PyErr_SetString(PyExc_TypeError, + "object is not a proxy to an array of PODs of known size"); + return nullptr; + } + + Py_buffer view; + + view.obj = pyobject; + view.buf = pyobj->GetObject(); + view.itemsize = interop::SizeOf(klass); + view.len = view.itemsize * array_len; + view.readonly = 0; + view.format = NULL; // i.e. "B" assumed + view.ndim = 1; + view.shape = NULL; + view.strides = NULL; + view.suboffsets = NULL; + view.internal = NULL; + + return PyMemoryView_FromBuffer(&view); +} + +//---------------------------------------------------------------------------- +static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) { + // From a long representing an address or a PyCapsule/CObject, bind to a + // class. + Py_ssize_t argc = PyTuple_GET_SIZE(args); + if (argc != 2) { + PyErr_Format( + PyExc_TypeError, + "bind_object takes 2 positional arguments but (" PY_SSIZE_T_FORMAT + " were given)", + argc); + return nullptr; + } + + // convert 2nd argument first (used for both pointer value and instance cases) + interop::TCppScope_t cast_type = nullptr; + PyObject* arg1 = PyTuple_GET_ITEM(args, 1); + if (!cpyrt_PyText_Check(arg1)) { // not string, then class + if (CPPScope_Check(arg1)) + cast_type = ((CPPClass*)arg1)->fCppType; + else + arg1 = PyObject_GetAttr(arg1, PyStrings::gName); + } else + Py_INCREF(arg1); + + if (!cast_type && arg1) { + cast_type = + (interop::TCppScope_t)interop::GetScope(cpyrt_PyText_AsString(arg1)); + Py_DECREF(arg1); + } + + if (!cast_type) { + PyErr_SetString( + PyExc_TypeError, + "bind_object expects a valid class or class name as an argument"); + return nullptr; + } + + // next, convert the first argument, some pointer value or a pre-existing + // instance + PyObject* arg0 = PyTuple_GET_ITEM(args, 0); + + if (CPPInstance_Check(arg0)) { + // if this instance's class has a relation to the requested one, calculate + // the offset, erase if from any caches, and update the pointer and type + CPPInstance* arg0_pyobj = (CPPInstance*)arg0; + interop::TCppScope_t cur_type = + arg0_pyobj->ObjectIsA(false /* check_smart */); + + bool isPython = CPPScope_Check(arg1) && + (((CPPClass*)arg1)->fFlags & CPPScope::kIsPython); + + if (cur_type == cast_type && !isPython) { + Py_INCREF(arg0); // nothing to do + return arg0; + } + + int direction = 0; + interop::TCppScope_t base = nullptr, derived = nullptr; + if (interop::IsSubclass(cast_type, cur_type)) { + derived = cast_type; + base = cur_type; + direction = -1; // down-cast + } else if (interop::IsSubclass(cur_type, cast_type)) { + base = cast_type; + derived = cur_type; + direction = 1; // up-cast + } else { + PyErr_SetString( + PyExc_TypeError, + "provided instance and provided target type are unrelated"); + return nullptr; + } + + interop::TCppObject_t address = + (interop::TCppObject_t)arg0_pyobj->GetObject(); + ptrdiff_t offset = + interop::GetBaseOffset(derived, base, address, direction); + + // it's debatable whether a new proxy should be created rather than updating + // the old, but changing the old object would be changing the behavior of + // all code that has a reference to it, which may not be the intention if + // the cast is on a C++ data member; this probably is the "least surprise" + // option + + // ownership is taken over as needed, again following the principle of + // "least surprise" as most likely only the cast object will be retained + bool owns = arg0_pyobj->fFlags & CPPInstance::kIsOwner; + + if (!isPython) { + // ordinary C++ class + PyObject* pyobj = BindCppObjectNoCast( + interop::TCppObject_t((void*)((intptr_t)address.data + offset)), + cast_type, owns ? CPPInstance::kIsOwner : 0); + if (owns && pyobj) + arg0_pyobj->CppOwns(); + return pyobj; + + } else { + // rebinding to a Python-side class, create a fresh instance first to be + // able to perform a lookup of the original dispatch object and if found, + // return original + void* cast_address = (void*)((intptr_t)address.data + offset); + PyObject* pyobj = + ((PyTypeObject*)arg1)->tp_new((PyTypeObject*)arg1, nullptr, nullptr); + ((CPPInstance*)pyobj)->GetObjectRaw() = cast_address; + + PyObject* dispproxy = cpyrt::GetScopeProxy(cast_type); + PyObject* res = + PyObject_CallMethodOneArg(dispproxy, PyStrings::gDispGet, pyobj); + /* Note: the resultant object is borrowed */ + if (CPPInstance_Check(res) && + ((CPPInstance*)res)->GetObject() == cast_address) { + ((CPPInstance*)pyobj)->CppOwns(); // make sure C++ object isn't deleted + Py_DECREF(pyobj); // on DECREF (is default, but still) + pyobj = res; + } else { + if (res) + Py_DECREF(res); // most likely Py_None + else + PyErr_Clear(); // should not happen + } + Py_DECREF(dispproxy); + + if (pyobj && owns) { + arg0_pyobj->CppOwns(); + ((CPPInstance*)pyobj)->PythonOwns(); + } + + return pyobj; + } + } + + // not a pre-existing object; get the address and bind + void* addr = nullptr; + if (arg0 != gNullPtrObject) { + addr = cpyrt_PyCapsule_GetPointer(arg0, nullptr); + if (PyErr_Occurred()) { + PyErr_Clear(); + + addr = PyLong_AsVoidPtr(arg0); + if (PyErr_Occurred()) { + PyErr_Clear(); + + // last chance, perhaps it's a buffer/array (return from void*) + Py_ssize_t buflen = + Utility::GetBuffer(PyTuple_GetItem(args, 0), '*', 1, addr, false); + if (!addr || !buflen) { + PyErr_SetString(PyExc_TypeError, + "bind_object requires a CObject/Capsule, long " + "integer, buffer, or instance as first argument"); + return nullptr; + } + } + } + } + + bool do_cast = false; + if (kwds) { + PyObject* cast = PyDict_GetItemString(kwds, "cast"); + do_cast = cast && PyObject_IsTrue(cast); + } + + if (do_cast) + return BindCppObject(addr, cast_type); + + return BindCppObjectNoCast(addr, cast_type); +} + +//---------------------------------------------------------------------------- +static PyObject* Move(PyObject*, PyObject* pyobject) { + // Prepare the given C++ object for moving. + if (!CPPInstance_Check(pyobject)) { + PyErr_SetString(PyExc_TypeError, "C++ object expected"); + return nullptr; + } + + ((CPPInstance*)pyobject)->fFlags |= CPPInstance::kIsRValue; + Py_INCREF(pyobject); + return pyobject; +} + +//---------------------------------------------------------------------------- +static PyObject* AddPythonization(PyObject*, PyObject* args) { + // Remove a previously registered pythonizor from the given scope. + PyObject* pythonizor = nullptr; + const char* scope; + if (!PyArg_ParseTuple(args, const_cast("Os"), &pythonizor, &scope)) + return nullptr; + + if (!PyCallable_Check(pythonizor)) { + PyObject* pystr = PyObject_Str(pythonizor); + PyErr_Format(PyExc_TypeError, "given \'%s\' object is not callable", + cpyrt_PyText_AsString(pystr)); + Py_DECREF(pystr); + return nullptr; + } + + Py_INCREF(pythonizor); + pythonizations()[scope].push_back(pythonizor); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* RemovePythonization(PyObject*, PyObject* args) { + // Remove a previously registered pythonizor from the given scope. + PyObject* pythonizor = nullptr; + const char* scope; + if (!PyArg_ParseTuple(args, const_cast("Os"), &pythonizor, &scope)) + return nullptr; + + auto& pyzMap = pythonizations(); + auto p1 = pyzMap.find(scope); + if (p1 != pyzMap.end()) { + auto p2 = std::find(p1->second.begin(), p1->second.end(), pythonizor); + if (p2 != p1->second.end()) { + p1->second.erase(p2); + Py_RETURN_TRUE; + } + } + + Py_RETURN_FALSE; +} + +//---------------------------------------------------------------------------- +static PyObject* PinType(PyObject*, PyObject* pyclass) { + // Add a pinning so that objects of type `derived' are interpreted as + // objects of type `base'. + if (!CPPScope_Check(pyclass)) { + PyErr_SetString(PyExc_TypeError, "C++ class expected"); + return nullptr; + } + + gPinnedTypes.insert(((CPPClass*)pyclass)->fCppType); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* AddTypeReducer(PyObject*, PyObject* args) { + // Add a type reducer to map type2 to type2 on function returns. + const char *reducable, *reduced; + if (!PyArg_ParseTuple(args, const_cast("ss"), &reducable, &reduced)) + return nullptr; + + interop::TCppType_t reducable_type = + interop::GetTypeFromScope(interop::GetScope(reducable)); + interop::TCppType_t reduced_type = + interop::GetTypeFromScope(interop::GetScope(reduced)); + TypeReductionMap[reducable_type] = reduced_type; + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* SetMemoryPolicy(PyObject*, PyObject* args) { + // Set the global memory policy, which affects object ownership when objects + // are passed as function arguments. + PyObject* policy = nullptr; + if (!PyArg_ParseTuple(args, const_cast("O!"), &PyInt_Type, &policy)) + return nullptr; + + long old = (long)CallContext::sMemoryPolicy; + + long l = PyInt_AS_LONG(policy); + if (CallContext::SetMemoryPolicy((CallContext::ECallFlags)l)) { + return PyInt_FromLong(old); + } + + PyErr_Format(PyExc_ValueError, "Unknown policy %ld", l); + return nullptr; +} + +//---------------------------------------------------------------------------- +static PyObject* SetGlobalSignalPolicy(PyObject*, PyObject* args) { + // Set the global signal policy, which determines whether a jmp address + // should be saved to return to after a C++ segfault. + PyObject* setProtected = 0; + if (!PyArg_ParseTuple(args, const_cast("O"), &setProtected)) + return nullptr; + + if (CallContext::SetGlobalSignalPolicy(PyObject_IsTrue(setProtected))) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} + +//---------------------------------------------------------------------------- +static PyObject* SetOwnership(PyObject*, PyObject* args) { + // Set the ownership (True is python-owns) for the given object. + CPPInstance* pyobj = nullptr; + PyObject* pykeep = nullptr; + if (!PyArg_ParseTuple(args, const_cast("O!O!"), &CPPInstance_Type, + (void*)&pyobj, &PyInt_Type, &pykeep)) + return nullptr; + + (bool)PyLong_AsLong(pykeep) ? pyobj->PythonOwns() : pyobj->CppOwns(); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* AddSmartPtrType(PyObject*, PyObject* args) { + // Add a smart pointer to the list of known smart pointer types. + const char* type_name; + if (!PyArg_ParseTuple(args, const_cast("s"), &type_name)) + return nullptr; + + // interop::AddSmartPtrType(type_name); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* BeginCaptureStderr(PyObject*, PyObject*) { + gOldErrorBuffer = std::cerr.rdbuf(); + std::cerr.rdbuf(gCapturedError.rdbuf()); + + Py_RETURN_NONE; +} + +//---------------------------------------------------------------------------- +static PyObject* EndCaptureStderr(PyObject*, PyObject*) { + // restore old rdbuf and return captured result + std::cerr.rdbuf(gOldErrorBuffer); + gOldErrorBuffer = nullptr; + + std::string capturedError = std::move(gCapturedError).str(); + + gCapturedError.str(""); + gCapturedError.clear(); + + return Py_BuildValue("s", capturedError.c_str()); +} +} // unnamed namespace + +//- data ----------------------------------------------------------------------- +static PyMethodDef gcpyrtMethods[] = { + {(char*)"CreateScopeProxy", (PyCFunction)cpyrt::CreateScopeProxy, + METH_VARARGS, (char*)"cppjit internal function"}, + {(char*)"MakeCppTemplateClass", (PyCFunction)MakeCppTemplateClass, + METH_VARARGS, (char*)"cppjit internal function"}, + {(char*)"_set_cpp_lazy_lookup", (PyCFunction)SetCppLazyLookup, METH_VARARGS, + (char*)"cppjit internal function"}, + {(char*)"_DestroyPyStrings", (PyCFunction)cpyrt::DestroyPyStrings, + METH_NOARGS, (char*)"cppjit internal function"}, + {(char*)"addressof", (PyCFunction)addressof, METH_VARARGS | METH_KEYWORDS, + (char*)"Retrieve address of proxied object or field as a value."}, + {(char*)"as_cobject", (PyCFunction)AsCObject, METH_VARARGS | METH_KEYWORDS, + (char*)"Retrieve address of proxied object or field in a CObject."}, + {(char*)"as_capsule", (PyCFunction)AsCapsule, METH_VARARGS | METH_KEYWORDS, + (char*)"Retrieve address of proxied object or field in a PyCapsule."}, + {(char*)"as_ctypes", (PyCFunction)AsCTypes, METH_VARARGS | METH_KEYWORDS, + (char*)"Retrieve address of proxied object or field in a ctypes " + "c_void_p."}, + {(char*)"as_memoryview", (PyCFunction)AsMemoryView, METH_O, + (char*)"Represent an array of objects as raw memory."}, + {(char*)"bind_object", (PyCFunction)BindObject, + METH_VARARGS | METH_KEYWORDS, + (char*)"Create an object of given type, from given address."}, + {(char*)"move", (PyCFunction)Move, METH_O, + (char*)"Cast the C++ object to become movable."}, + {(char*)"add_pythonization", (PyCFunction)AddPythonization, METH_VARARGS, + (char*)"Add a pythonizor."}, + {(char*)"remove_pythonization", (PyCFunction)RemovePythonization, + METH_VARARGS, (char*)"Remove a pythonizor."}, + {(char*)"_pin_type", (PyCFunction)PinType, METH_O, + (char*)"Install a type pinning."}, + {(char*)"_add_type_reducer", (PyCFunction)AddTypeReducer, METH_VARARGS, + (char*)"Add a type reducer."}, + {(char*)"SetMemoryPolicy", (PyCFunction)SetMemoryPolicy, METH_VARARGS, + (char*)"Determines object ownership model."}, + {(char*)"SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, + METH_VARARGS, + (char*)"Trap signals in safe mode to prevent interpreter abort."}, + {(char*)"SetOwnership", (PyCFunction)SetOwnership, METH_VARARGS, + (char*)"Modify held C++ object ownership."}, + {(char*)"AddSmartPtrType", (PyCFunction)AddSmartPtrType, METH_VARARGS, + (char*)"Add a smart pointer to the list of known smart pointer types."}, + {(char*)"_begin_capture_stderr", (PyCFunction)BeginCaptureStderr, + METH_NOARGS, (char*)"Begin capturing stderr to a in memory buffer."}, + {(char*)"_end_capture_stderr", (PyCFunction)EndCaptureStderr, METH_NOARGS, + (char*)"End capturing stderr and returns the captured buffer."}, + {nullptr, nullptr, 0, nullptr}}; + +struct module_state { + PyObject* error; +}; + +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) + +static int cpycppjitmodule_traverse(PyObject* m, visitproc visit, void* arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int cpycppjitmodule_clear(PyObject* m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, "libcppjit", nullptr, + sizeof(struct module_state), gcpyrtMethods, nullptr, + cpycppjitmodule_traverse, cpycppjitmodule_clear, nullptr}; + +namespace cppjit::cpyrt { + +//---------------------------------------------------------------------------- +extern "C" PyObject* PyInit_libcppjit() { + // Initialization of extension module libcppjit. + + // load commonly used python strings + if (!cpyrt::CreatePyStrings()) + return nullptr; + + // setup interpreter + +#if PY_VERSION_HEX < 0x030b0000 + // prepare for laziness (the insert is needed to capture the most generic + // lookup function, just in case ...) + PyObject* dict = PyDict_New(); + PyObject* notstring = PyInt_FromLong(5); + PyDict_SetItem(dict, notstring, notstring); + Py_DECREF(notstring); + gDictLookupOrg = (dict_lookup_func)((PyDictObject*)dict)->ma_keys->dk_lookup; + Py_DECREF(dict); +#endif // PY_VERSION_HEX < 0x030b0000 + + // setup this module + gThisModule = PyModule_Create(&moduledef); + if (!gThisModule) + return nullptr; + + // keep gThisModule, but do not increase its reference count even as it is + // borrowed, or a self-referencing cycle would be created + + // external types + gPyTypeMap = PyDict_New(); + PyModule_AddObject(gThisModule, "type_map", gPyTypeMap); // steals reference + + // Pythonizations ... + PyModule_AddObject(gThisModule, "UserExceptions", PyDict_New()); + + // inject meta type + if (!Utility::InitProxy(gThisModule, &CPPScope_Type, "CPPScope")) + return nullptr; + + // inject object proxy type + if (!Utility::InitProxy(gThisModule, &CPPInstance_Type, "CPPInstance")) + return nullptr; + + // inject exception object proxy type + if (!Utility::InitProxy(gThisModule, &CPPExcInstance_Type, "CPPExcInstance")) + return nullptr; + + // inject method proxy type + if (!Utility::InitProxy(gThisModule, &CPPOverload_Type, "CPPOverload")) + return nullptr; + + // inject template proxy type + if (!Utility::InitProxy(gThisModule, &TemplateProxy_Type, "TemplateProxy")) + return nullptr; + + // inject property proxy type + if (!Utility::InitProxy(gThisModule, &CPPDataMember_Type, "CPPDataMember")) + return nullptr; + + if (!Utility::InitProxy(gThisModule, &CustomInstanceMethod_Type, + "InstanceMethod")) + return nullptr; + + if (!Utility::InitProxy(gThisModule, &TupleOfInstances_Type, "InstanceArray")) + return nullptr; + + if (!Utility::InitProxy(gThisModule, &LowLevelView_Type, "LowLevelView")) + return nullptr; + + if (!Utility::InitProxy(gThisModule, &PyNullPtr_t_Type, "nullptr_t")) + return nullptr; + + // custom iterators + if (PyType_Ready(&InstanceArrayIter_Type) < 0) + return nullptr; + + if (PyType_Ready(&IndexIter_Type) < 0) + return nullptr; + + if (PyType_Ready(&VectorIter_Type) < 0) + return nullptr; + + // inject identifiable nullptr and default + gNullPtrObject = (PyObject*)&_cpyrt_NullPtrStruct; + Py_INCREF(gNullPtrObject); + PyModule_AddObject(gThisModule, (char*)"nullptr", gNullPtrObject); + + gDefaultObject = (PyObject*)&_cpyrt_DefaultStruct; + Py_INCREF(gDefaultObject); + PyModule_AddObject(gThisModule, (char*)"default", gDefaultObject); + + // C++-specific exceptions + PyObject* cppfatal = + PyErr_NewException((char*)"cppjit.ll.FatalError", nullptr, nullptr); + PyModule_AddObject(gThisModule, (char*)"FatalError", cppfatal); + + gBusException = + PyErr_NewException((char*)"cppjit.ll.BusError", cppfatal, nullptr); + PyModule_AddObject(gThisModule, (char*)"BusError", gBusException); + gSegvException = PyErr_NewException((char*)"cppjit.ll.SegmentationViolation", + cppfatal, nullptr); + PyModule_AddObject(gThisModule, (char*)"SegmentationViolation", + gSegvException); + gIllException = PyErr_NewException((char*)"cppjit.ll.IllegalInstruction", + cppfatal, nullptr); + PyModule_AddObject(gThisModule, (char*)"IllegalInstruction", gIllException); + gAbrtException = + PyErr_NewException((char*)"cppjit.ll.AbortSignal", cppfatal, nullptr); + PyModule_AddObject(gThisModule, (char*)"AbortSignal", gAbrtException); + + // policy labels + PyModule_AddObject(gThisModule, (char*)"kMemoryHeuristics", + PyInt_FromLong((int)CallContext::kUseHeuristics)); + PyModule_AddObject(gThisModule, (char*)"kMemoryStrict", + PyInt_FromLong((int)CallContext::kUseStrict)); + + // gbl namespace is injected in cppjit.py + + // create the memory regulator + static MemoryRegulator s_memory_regulator; + + Py_INCREF(gThisModule); + return gThisModule; +} + +} // namespace cppjit::cpyrt diff --git a/src/interop/callcontext.h b/src/interop/callcontext.h new file mode 100644 index 0000000..d0f04f9 --- /dev/null +++ b/src/interop/callcontext.h @@ -0,0 +1,37 @@ +#ifndef CPYRT_CALLCONTEXT_H +#define CPYRT_CALLCONTEXT_H + +// Standard +#include + +namespace cppjit::cpyrt { + +// general place holder for function parameters +struct Parameter { + union Value { + bool fBool; + int8_t fInt8; + uint8_t fUInt8; + short fShort; + unsigned short fUShort; + int fInt; + unsigned int fUInt; + long fLong; + intptr_t fIntPtr; + unsigned long fULong; + long long fLLong; + unsigned long long fULLong; + int64_t fInt64; + uint64_t fUInt64; + float fFloat; + double fDouble; + long double fLDouble; + void* fVoidp; + } fValue; + void* fRef; + char fTypeCode; +}; + +} // namespace cppjit::cpyrt + +#endif // !CPYRT_CALLCONTEXT_H diff --git a/src/interop/cpp_cppjit.h b/src/interop/cpp_cppjit.h new file mode 100644 index 0000000..c56bd19 --- /dev/null +++ b/src/interop/cpp_cppjit.h @@ -0,0 +1,406 @@ +#ifndef CPYRT_CPPJIT_H +#define CPYRT_CPPJIT_H + +#include +#include + +// Standard +#include +#include +#include +#include +#include +#include + +#include "callcontext.h" +#include "precommondefs.h" + +// some more types; assumes cppjit_interop.h follows Python.h +#ifndef PY_LONG_LONG +#ifdef _WIN32 +typedef __int64 PY_LONG_LONG; +#else +typedef long long PY_LONG_LONG; +#endif +#endif + +#ifndef PY_ULONG_LONG +#ifdef _WIN32 +typedef unsigned __int64 PY_ULONG_LONG; +#else +typedef unsigned long long PY_ULONG_LONG; +#endif +#endif + +#ifndef PY_LONG_DOUBLE +typedef long double PY_LONG_DOUBLE; +#endif + +typedef cppjit::cpyrt::Parameter Parameter; + +// small number that allows use of stack for argument passing +const int SMALL_ARGS_N = 8; + +// convention to pass flag for direct calls (similar to Python's vector calls) +#define DIRECT_CALL ((size_t)1 << (8 * sizeof(size_t) - 1)) +static inline size_t CALL_NARGS(size_t nargs) { return nargs & ~DIRECT_CALL; } + +namespace cppjit::interop { +typedef Cpp::DeclRef TCppScope_t; +typedef Cpp::TypeRef TCppType_t; +typedef Cpp::ObjectRef TCppObject_t; +typedef Cpp::FuncRef TCppMethod_t; +typedef Cpp::InterpRef TInterp_t; +typedef size_t TCppIndex_t; +typedef void* TCppFuncAddr_t; + +// direct interpreter access ------------------------------------------------- +RPY_EXPORTED +bool Compile(const std::string& code, bool silent = false); +RPY_EXPORTED +std::string ToString(TCppScope_t klass, TCppObject_t obj); + +// name to opaque C++ scope representation ----------------------------------- +RPY_EXPORTED +std::string ResolveName(const std::string& cppitem_name); +RPY_EXPORTED +TCppType_t ResolveType(TCppType_t cppitem_name); +RPY_EXPORTED +TCppType_t ResolveEnumReferenceType(TCppType_t type); +RPY_EXPORTED +TCppType_t ResolveEnumPointerType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetRealType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetPointerType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetReferencedType(TCppType_t type, bool rvalue = false); +RPY_EXPORTED +std::string ResolveEnum(TCppScope_t enum_scope); +RPY_EXPORTED +bool IsLValueReferenceType(TCppType_t type); +RPY_EXPORTED +bool IsRValueReferenceType(TCppType_t type); +RPY_EXPORTED +bool IsClassType(TCppType_t type); +RPY_EXPORTED +bool IsIntegerType(TCppType_t type, bool* is_signed = nullptr); +RPY_EXPORTED +bool IsPointerType(TCppType_t type); +RPY_EXPORTED +bool IsFunctionPointerType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetType(const std::string& name, bool enable_slow_lookup = false); +RPY_EXPORTED +bool AppendTypesSlow(const std::string& name, + std::vector& types, + cppjit::interop::TCppScope_t parent = nullptr); +RPY_EXPORTED +TCppType_t GetComplexType(const std::string& element_type); +RPY_EXPORTED +TCppScope_t GetScope(const std::string& scope_name, + TCppScope_t parent_scope = TCppScope_t{}); +RPY_EXPORTED +TCppScope_t GetUnderlyingScope(TCppScope_t scope); +RPY_EXPORTED +TCppScope_t GetFullScope(const std::string& scope_name); +RPY_EXPORTED +TCppScope_t GetTypeScope(TCppScope_t klass); +RPY_EXPORTED +TCppScope_t GetNamed(const std::string& scope_name, + TCppScope_t parent_scope = TCppScope_t{}); +RPY_EXPORTED +TCppScope_t GetParentScope(TCppScope_t scope); +RPY_EXPORTED +TCppScope_t GetScopeFromType(TCppType_t type); +RPY_EXPORTED +TCppType_t GetTypeFromScope(TCppScope_t klass); +RPY_EXPORTED +TCppScope_t GetGlobalScope(); +RPY_EXPORTED +TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); +RPY_EXPORTED +size_t SizeOf(TCppScope_t klass); +RPY_EXPORTED +size_t SizeOfType(TCppType_t type); + +RPY_EXPORTED +bool IsBuiltin(const std::string& type_name); + +RPY_EXPORTED +bool IsBuiltin(TCppType_t type); + +RPY_EXPORTED +bool IsComplete(TCppScope_t type); + +// memory management --------------------------------------------------------- +RPY_EXPORTED +TCppObject_t Allocate(TCppScope_t scope); +RPY_EXPORTED +void Deallocate(TCppScope_t scope, TCppObject_t instance); +RPY_EXPORTED +TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); +RPY_EXPORTED +void Destruct(TCppScope_t scope, TCppObject_t instance); + +// method/function dispatching ----------------------------------------------- +RPY_EXPORTED +void CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +unsigned char CallB(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); +RPY_EXPORTED +char CallC(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +short CallH(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +int CallI(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +long CallL(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +PY_LONG_LONG CallLL(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); +RPY_EXPORTED +float CallF(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +double CallD(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +PY_LONG_DOUBLE CallLD(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args); + +RPY_EXPORTED +void* CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args); +RPY_EXPORTED +char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, + size_t* length); +RPY_EXPORTED +TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, + size_t nargs, void* args); +RPY_EXPORTED +void CallDestructor(TCppScope_t type, TCppObject_t self); +RPY_EXPORTED +TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args, TCppType_t result_type); + +RPY_EXPORTED +TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, + bool check_enabled = true); + +// handling of function argument buffer -------------------------------------- +RPY_EXPORTED +void* AllocateFunctionArgs(size_t nargs); +RPY_EXPORTED +void DeallocateFunctionArgs(void* args); +RPY_EXPORTED +size_t GetFunctionArgSizeof(); +RPY_EXPORTED +size_t GetFunctionArgTypeoffset(); + +// scope reflection information ---------------------------------------------- +RPY_EXPORTED +bool IsNamespace(TCppScope_t scope); +RPY_EXPORTED +bool IsClass(TCppScope_t scope); +RPY_EXPORTED +bool IsTemplate(TCppScope_t scope); +RPY_EXPORTED +bool IsTemplateInstantiation(TCppScope_t scope); +RPY_EXPORTED +bool IsTypedefed(TCppScope_t scope); +RPY_EXPORTED +bool IsAbstract(TCppScope_t scope); +RPY_EXPORTED +bool IsEnumScope(TCppScope_t scope); +RPY_EXPORTED +bool IsEnumConstant(TCppScope_t scope); +RPY_EXPORTED +bool IsEnumType(TCppType_t type); +RPY_EXPORTED +bool IsAggregate(TCppScope_t type); +RPY_EXPORTED +bool IsDefaultConstructable(TCppScope_t scope); +RPY_EXPORTED +bool IsVariable(TCppScope_t scope); + +RPY_EXPORTED +void GetAllCppNames(TCppScope_t scope, std::set& cppnames); + +// namespace reflection information ------------------------------------------ +RPY_EXPORTED +std::vector GetUsingNamespaces(TCppScope_t); + +// class reflection information ---------------------------------------------- +RPY_EXPORTED +std::string GetFinalName(TCppScope_t type); +RPY_EXPORTED +std::string GetScopedFinalName(TCppScope_t type); +RPY_EXPORTED +bool HasVirtualDestructor(TCppScope_t type); +RPY_EXPORTED +TCppIndex_t GetNumBases(TCppScope_t klass); +RPY_EXPORTED +TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass); +RPY_EXPORTED +std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase); +RPY_EXPORTED +TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase); +RPY_EXPORTED +bool IsSubclass(TCppScope_t derived, TCppScope_t base); +RPY_EXPORTED +bool IsSmartPtr(TCppScope_t klass); +RPY_EXPORTED +bool GetSmartPtrInfo(const std::string&, TCppScope_t* raw, TCppMethod_t* deref); +// calculate offsets between declared and actual type, up-cast: direction > 0; +// down-cast: direction < 0 +RPY_EXPORTED +ptrdiff_t GetBaseOffset(TCppScope_t derived, TCppScope_t base, + TCppObject_t address, int direction, + bool rerror = false); + +// method/function reflection information ------------------------------------ +RPY_EXPORTED +void GetClassMethods(TCppScope_t scope, std::vector& methods); +RPY_EXPORTED +std::vector GetMethodsFromName(TCppScope_t scope, + const std::string& name); +RPY_EXPORTED +std::string GetName(TCppScope_t); +RPY_EXPORTED +std::string GetFullName(TCppScope_t); +RPY_EXPORTED +TCppType_t GetMethodReturnType(TCppMethod_t); +RPY_EXPORTED +std::string GetMethodReturnTypeAsString(TCppMethod_t); +RPY_EXPORTED +TCppIndex_t GetMethodNumArgs(TCppMethod_t); +RPY_EXPORTED +TCppIndex_t GetMethodReqArgs(TCppMethod_t); +RPY_EXPORTED +std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); +RPY_EXPORTED +TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); +RPY_EXPORTED +TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, + const std::string& req_type); +RPY_EXPORTED +std::string GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg); +RPY_EXPORTED +std::string GetMethodArgCanonTypeAsString(TCppMethod_t method, + TCppIndex_t iarg); +RPY_EXPORTED +std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); +RPY_EXPORTED +std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, + TCppIndex_t max_args = (TCppIndex_t)-1); +RPY_EXPORTED +bool IsFunctionType(TCppType_t typ); +RPY_EXPORTED +TCppType_t GetFnTypeFromStdFn(TCppType_t fn_type); +RPY_EXPORTED +void GetFnTypeSig(TCppType_t fn_type, std::vector& arg_types); +RPY_EXPORTED +bool IsSameType(TCppType_t typ1, TCppType_t typ2); +RPY_EXPORTED +bool IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2); +RPY_EXPORTED +std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); +RPY_EXPORTED +bool IsConstMethod(TCppMethod_t); +// Templated method/function reflection information +// ------------------------------------ +RPY_EXPORTED +void GetTemplatedMethods(TCppScope_t scope, std::vector& methods); +RPY_EXPORTED +TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, + bool accept_namespace = false); +RPY_EXPORTED +std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); +RPY_EXPORTED +bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); +RPY_EXPORTED +bool IsTemplatedMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsStaticTemplate(TCppScope_t scope, const std::string& name); +RPY_EXPORTED +TCppMethod_t GetMethodTemplate(TCppScope_t scope, const std::string& name, + const std::string& proto); +RPY_EXPORTED +void GetClassOperators(cppjit::interop::TCppScope_t klass, + const std::string& opname, + std::vector& operators); +RPY_EXPORTED +TCppMethod_t GetGlobalOperator(TCppScope_t scope, const std::string& lc, + const std::string& rc, const std::string& op); + +// method properties --------------------------------------------------------- +RPY_EXPORTED +bool IsDeletedMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsPublicMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsProtectedMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsPrivateMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsConstructor(TCppMethod_t method); +RPY_EXPORTED +bool IsDestructor(TCppMethod_t method); +RPY_EXPORTED +bool IsStaticMethod(TCppMethod_t method); +RPY_EXPORTED +bool IsExplicit(TCppMethod_t method); + +// data member reflection information ---------------------------------------- +RPY_EXPORTED +void GetDatamembers(TCppScope_t scope, std::vector& datamembers); +RPY_EXPORTED +bool IsLambdaClass(TCppType_t type); +RPY_EXPORTED +TCppScope_t WrapLambdaFromVariable(TCppScope_t var); +RPY_EXPORTED +TCppMethod_t AdaptFunctionForLambdaReturn(TCppMethod_t fn); +RPY_EXPORTED +TCppType_t GetDatamemberType(TCppScope_t data); +RPY_EXPORTED +std::string GetDatamemberTypeAsString(TCppScope_t var); +RPY_EXPORTED +std::string GetTypeAsString(TCppType_t type); +RPY_EXPORTED +intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); +RPY_EXPORTED +bool CheckDatamember(TCppScope_t scope, const std::string& name); + +// // data member properties +// ---------------------------------------------------- +RPY_EXPORTED +bool IsPublicData(TCppScope_t var); +RPY_EXPORTED +bool IsProtectedData(TCppScope_t var); +RPY_EXPORTED +bool IsPrivateData(TCppScope_t var); +RPY_EXPORTED +bool IsStaticDatamember(TCppScope_t var); +RPY_EXPORTED +bool IsConstVar(TCppScope_t var); +RPY_EXPORTED +TCppMethod_t ReduceReturnType(TCppMethod_t fn, TCppType_t reduce); +RPY_EXPORTED +std::vector GetDimensions(TCppType_t type); + +// enum properties ----------------------------------------------------------- +RPY_EXPORTED +std::vector GetEnumConstants(TCppScope_t scope); +RPY_EXPORTED +TCppType_t GetEnumConstantType(TCppScope_t scope); +RPY_EXPORTED +TCppIndex_t GetEnumDataValue(TCppScope_t scope); + +RPY_EXPORTED +TCppScope_t InstantiateTemplate(TCppScope_t tmpl, Cpp::TemplateArgInfo* args, + size_t args_size); + +RPY_EXPORTED +void DumpScope(TCppScope_t scope); +} // namespace cppjit::interop + +#endif // !CPYRT_CPPJIT_H diff --git a/src/backend/cppinterop_dispatch.cxx b/src/interop/cppinterop_dispatch.cxx similarity index 81% rename from src/backend/cppinterop_dispatch.cxx rename to src/interop/cppinterop_dispatch.cxx index 5d70020..bd4452c 100644 --- a/src/backend/cppinterop_dispatch.cxx +++ b/src/interop/cppinterop_dispatch.cxx @@ -1,8 +1,8 @@ #include #if __has_include("CppInterOp/CppInterOpAPI.inc") using namespace Cpp; -#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ - Ret (*CppInternal::DispatchRaw::DN) RawTypes = nullptr; +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + Ret(*CppInternal::DispatchRaw::DN) RawTypes = nullptr; #include "CppInterOp/CppInterOpAPI.inc" #else #define DISPATCH_API(name, type) CppAPIType::name Cpp::name = nullptr; diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx new file mode 100644 index 0000000..a387b1d --- /dev/null +++ b/src/interop/interop_wrapper.cxx @@ -0,0 +1,1891 @@ +#ifndef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +// silence warnings about getenv, strncpy, etc. +#define _CRT_SECURE_NO_WARNINGS +#endif +#endif + +#include "precommondefs.h" // This defines several system feature macros and should be included before any system header. + +// Bindings +#include "cpp_cppjit.h" + +using namespace cppjit; +#include "callcontext.h" + +#ifndef _WIN32 +#include +#endif + +// Standard +#include // for std::count, std::remove +#include +#include +#include // for getenv +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::recursive_mutex InterOpMutex; + +// builtin types +static std::set g_builtins = {"bool", + "char", + "signed char", + "unsigned char", + "wchar_t", + "short", + "unsigned short", + "int", + "unsigned int", + "long", + "unsigned long", + "long long", + "unsigned long long", + "float", + "double", + "long double", + "void"}; + +// to filter out ROOT names +static std::set gInitialNames; +static std::set gRootSOs; + +// configuration +static bool gEnableFastPath = true; + +// global initialization ----------------------------------------------------- +namespace { + +// const int kMAXSIGNALS = 16; + +// names copied from TUnixSystem +#ifdef WIN32 +const int SIGBUS = 0; // simple placeholders for ones that don't exist +const int SIGSYS = 0; +const int SIGPIPE = 0; +const int SIGQUIT = 0; +const int SIGWINCH = 0; +const int SIGALRM = 0; +const int SIGCHLD = 0; +const int SIGURG = 0; +const int SIGUSR1 = 0; +const int SIGUSR2 = 0; +#endif + +#if 0 +static struct Signalmap_t { + int fCode; + const char *fSigName; +} gSignalMap[kMAXSIGNALS] = { // the order of the signals should be identical + { SIGBUS, "bus error" }, // to the one in TSysEvtHandler.h + { SIGSEGV, "segmentation violation" }, + { SIGSYS, "bad argument to system call" }, + { SIGPIPE, "write on a pipe with no one to read it" }, + { SIGILL, "illegal instruction" }, + { SIGABRT, "abort" }, + { SIGQUIT, "quit" }, + { SIGINT, "interrupt" }, + { SIGWINCH, "window size change" }, + { SIGALRM, "alarm clock" }, + { SIGCHLD, "death of a child" }, + { SIGURG, "urgent data arrived on an I/O channel" }, + { SIGFPE, "floating point exception" }, + { SIGTERM, "termination signal" }, + { SIGUSR1, "user-defined signal 1" }, + { SIGUSR2, "user-defined signal 2" } +}; +#endif + +static inline void push_tokens_from_string(char* s, + std::vector& tokens) { + char* token = strtok(s, " "); + + while (token) { + tokens.push_back(token); + token = strtok(NULL, " "); + } +} + +static inline bool is_integral(std::string& s) { + if (s == "false") { + s = "0"; + return true; + } else if (s == "true") { + s = "1"; + return true; + } + return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) { + return !std::isdigit(c); + }) == s.end(); +} + +class ApplicationStarter { + interop::TInterp_t Interp; + +public: + ApplicationStarter() { + std::lock_guard Lock(InterOpMutex); + if (!Cpp::LoadDispatchAPI( + CPPINTEROP_DIR + "/lib/libclangCppInterOp" CMAKE_SHARED_LIBRARY_SUFFIX)) { + std::cerr << "[cppjit-backend] Failed to load CppInterOp" << std::endl; + return; + } + // Check if somebody already loaded CppInterOp and created an + // interpreter for us. + if (auto existingInterp = Cpp::GetInterpreter()) { + Interp = existingInterp; + } else { +#ifdef __arm64__ +#ifdef __APPLE__ + // If on apple silicon don't use -march=native + std::vector InterpArgs({"-std=c++17"}); +#else + std::vector InterpArgs({"-std=c++17", "-march=native"}); +#endif +#else + std::vector InterpArgs({"-std=c++17", "-march=native"}); +#endif + char* InterpArgString = getenv("CPPINTEROP_EXTRA_INTERPRETER_ARGS"); + + if (InterpArgString) + push_tokens_from_string(InterpArgString, InterpArgs); + +#ifdef __arm64__ +#ifdef __APPLE__ + // If on apple silicon don't use -march=native + Interp = Cpp::CreateInterpreter({"-std=c++17"}, /*GpuArgs=*/{}); +#else + Interp = Cpp::CreateInterpreter({"-std=c++17", "-march=native"}, + /*GpuArgs=*/{}); +#endif +#else + Interp = Cpp::CreateInterpreter({"-std=c++17", "-march=native"}, + /*GpuArgs=*/{}); +#endif + } + + // fill out the builtins + std::set bi{g_builtins}; + for (const auto& name : bi) { + for (const char* a : {"*", "&", "*&", "[]", "*[]"}) + g_builtins.insert(name + a); + } + + // disable fast path if requested + if (getenv("CPPJIT_DISABLE_FASTPATH")) + gEnableFastPath = false; + + // set opt level (default to 2 if not given; Cling itself defaults to 0) + int optLevel = 2; + + if (getenv("CPPJIT_OPT_LEVEL")) + optLevel = atoi(getenv("CPPJIT_OPT_LEVEL")); + + if (optLevel != 0) { + std::ostringstream s; + s << "#pragma cling optimize " << optLevel; + Cpp::Process(s.str().c_str()); + } + + // This would give us something like: + // /home/vvassilev/workspace/builds/scratch/cling-build/builddir/lib/clang/13.0.0 + const char* ResourceDir = Cpp::GetResourceDir(); + std::string ClingSrc = std::string(ResourceDir) + "/../../../../cling-src"; + std::string ClingBuildDir = std::string(ResourceDir) + "/../../../"; + Cpp::AddIncludePath((ClingSrc + "/tools/cling/include").c_str()); + Cpp::AddIncludePath((ClingSrc + "/include").c_str()); + Cpp::AddIncludePath((ClingBuildDir + "/include").c_str()); + Cpp::AddIncludePath((std::string(CPPINTEROP_DIR) + "/include").c_str()); + Cpp::LoadLibrary("libstdc++", /* lookup= */ true); + + // load frequently used headers + const char* code = "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" // for strcpy + "#include \n" + // "#include \n" // defines R__EXTERN + "#include \n" + "#include \n" + "#include \n" + "#include \n" // for the dispatcher code to + // use std::function + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#if __has_include()\n" + "#include \n" + "#endif\n" + "#include \n"; + Cpp::Process(code); + + // create helpers for comparing thingies + Cpp::Declare("namespace __cppjit_internal { template" + " bool is_equal(const C1& c1, const C2& c2) { return " + "(bool)(c1 == c2); } }", + /*silent=*/false); + Cpp::Declare("namespace __cppjit_internal { template" + " bool is_not_equal(const C1& c1, const C2& c2) { return " + "(bool)(c1 != c2); } }", + /*silent=*/false); + + // Define gCling when we run with clang-repl. + // FIXME: We should get rid of all the uses of gCling as this seems to + // break encapsulation. + std::stringstream InterpPtrSS; + InterpPtrSS << "#ifndef __CLING__\n" + << "namespace cling { namespace runtime {\n" + << "void* gCling=(void*)" << Interp.data << ";\n }}\n" + << "#endif \n"; + Cpp::Process(InterpPtrSS.str().c_str()); + + // helper for multiple inheritance + Cpp::Declare("namespace __cppjit_internal { struct Sep; }", + /*silent=*/false); + + // std::string libInterOp = + // I->getDynamicLibraryManager()->lookupLibrary("libcling"); void *interopDL + // = dlopen(libInterOp.c_str(), RTLD_LAZY); if (!interopDL) { + // std::cerr << "libInterop could not be opened!\n"; + // exit(1); + // } + + // start off with a reasonable size placeholder for wrappers + // gWrapperHolder.reserve(1024); + + // create an exception handler to process signals + // gExceptionHandler = new TExceptionHandlerImp{}; + } + + ~ApplicationStarter() { + // Cpp::DeleteInterpreter(Interp); + // for (auto wrap : gWrapperHolder) + // delete wrap; + // delete gExceptionHandler; gExceptionHandler = nullptr; + } +} _applicationStarter; + +} // unnamed namespace + +// local helpers ------------------------------------------------------------- +static inline char* cppstring_to_cstring(const std::string& cppstr) { + char* cstr = (char*)malloc(cppstr.size() + 1); + memcpy(cstr, cppstr.c_str(), cppstr.size() + 1); + return cstr; +} + +// direct interpreter access ------------------------------------------------- +// Returns false on failure and true on success +bool interop::Compile(const std::string& code, bool silent) { + std::lock_guard Lock(InterOpMutex); + // Declare returns an enum which equals 0 on success + return !Cpp::Declare(code.c_str(), silent); +} + +std::string interop::ToString(TCppScope_t klass, TCppObject_t obj) { + std::lock_guard Lock(InterOpMutex); + if (klass && obj && !Cpp::IsNamespace(klass)) + return Cpp::ObjToString(Cpp::GetQualifiedCompleteName(klass).c_str(), + obj.data); + return ""; +} + +// // name to opaque C++ scope representation +// ----------------------------------- +std::string interop::ResolveName(const std::string& name) { + if (!name.empty()) { + if (interop::TCppType_t type = + interop::GetType(name, /*enable_slow_lookup=*/true)) + return interop::GetTypeAsString(interop::ResolveType(type)); + return name; + } + return ""; +} + +interop::TCppType_t interop::ResolveEnumReferenceType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + if (Cpp::GetValueKind(type) != Cpp::ValueKind::LValue) + return type; + + TCppType_t nonReferenceType = Cpp::GetNonReferenceType(type); + if (Cpp::IsEnumType(nonReferenceType)) { + TCppType_t underlying_type = + Cpp::GetIntegerTypeFromEnumType(nonReferenceType); + return Cpp::GetReferencedType(underlying_type, /*rvalue=*/false); + } + return type; +} + +interop::TCppType_t interop::ResolveEnumPointerType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + if (!Cpp::IsPointerType(type)) + return type; + + TCppType_t PointeeType = Cpp::GetPointeeType(type); + if (Cpp::IsEnumType(PointeeType)) { + TCppType_t underlying_type = Cpp::GetIntegerTypeFromEnumType(PointeeType); + return Cpp::GetPointerType(underlying_type); + } + return type; +} + +interop::TCppType_t int_like_type(interop::TCppType_t type) { + interop::TCppType_t check_int_typedefs = type; + if (Cpp::IsPointerType(check_int_typedefs)) + check_int_typedefs = Cpp::GetPointeeType(check_int_typedefs); + if (Cpp::IsReferenceType(check_int_typedefs)) + check_int_typedefs = + Cpp::GetReferencedType(check_int_typedefs, /*rvalue=*/false); + + if (Cpp::GetTypeAsString(check_int_typedefs) == "int8_t" || + Cpp::GetTypeAsString(check_int_typedefs) == "uint8_t") + return check_int_typedefs; + return nullptr; +} + +interop::TCppType_t interop::ResolveType(TCppType_t type) { + if (!type) + return type; + + std::lock_guard Lock(InterOpMutex); + + TCppType_t check_int_typedefs = int_like_type(type); + if (check_int_typedefs) + return type; + + interop::TCppType_t canonType = Cpp::GetCanonicalType(type); + + if (Cpp::IsEnumType(canonType)) { + if (Cpp::GetTypeAsString(type) != "std::byte") + return Cpp::GetIntegerTypeFromEnumType(canonType); + } + if (Cpp::HasTypeQualifier(canonType, Cpp::QualKind::Restrict)) { + return Cpp::RemoveTypeQualifier(canonType, Cpp::QualKind::Restrict); + } + + return canonType; +} + +interop::TCppType_t interop::GetRealType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + TCppType_t check_int_typedefs = int_like_type(type); + if (check_int_typedefs) + return check_int_typedefs; + return Cpp::GetUnderlyingType(type); +} + +interop::TCppType_t interop::GetPointerType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetPointerType(type); +} + +interop::TCppType_t interop::GetReferencedType(TCppType_t type, bool rvalue) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetReferencedType(type, rvalue); +} + +bool interop::IsRValueReferenceType(TCppType_t type) { + return Cpp::GetValueKind(type) == Cpp::ValueKind::RValue; +} + +bool interop::IsLValueReferenceType(TCppType_t type) { + return Cpp::GetValueKind(type) == Cpp::ValueKind::LValue; +} + +bool interop::IsClassType(TCppType_t type) { return Cpp::IsRecordType(type); } + +bool interop::IsIntegerType(TCppType_t type, bool* is_signed /*= nullptr*/) { + if (is_signed) { + Cpp::Signedness sign; + bool res = Cpp::IsIntegerType(type, &sign); + *is_signed = (sign == Cpp::Signedness::kSigned); + return res; + } + return Cpp::IsIntegerType(type, nullptr); +} + +bool interop::IsPointerType(TCppType_t type) { + return Cpp::IsPointerType(type); +} + +bool interop::IsFunctionPointerType(TCppType_t type) { + return Cpp::IsFunctionPointerType(type); +} + +std::string trim(const std::string& line) { + if (line.empty()) + return ""; + const char* WhiteSpace = " \t\v\r\n"; + std::size_t start = line.find_first_not_of(WhiteSpace); + std::size_t end = line.find_last_not_of(WhiteSpace); + return line.substr(start, end - start + 1); +} + +// returns false of angular brackets dont match, else true +bool split_comma_saparated_types(const std::string& name, + std::vector& types) { + std::string trimed_name = trim(name); + size_t start_pos = 0; + size_t end_pos = 0; + int matching_angular_brackets = 0; + while (end_pos < trimed_name.size()) { + switch (trimed_name[end_pos]) { + case ',': { + if (!matching_angular_brackets) { + if (end_pos > start_pos) + types.push_back( + trim(trimed_name.substr(start_pos, end_pos - start_pos))); + start_pos = end_pos + 1; + } + break; + } + case '<': { + matching_angular_brackets++; + break; + } + case '>': { + matching_angular_brackets--; + break; + } + } + end_pos++; + } + if (start_pos < trimed_name.size()) + types.push_back(trim(trimed_name.substr(start_pos, end_pos - start_pos))); + return true; +} + +interop::TCppScope_t GetEnumFromCompleteName(const std::string& name) { + std::string delim = "::"; + size_t start = 0; + size_t end = name.find(delim); + interop::TCppScope_t curr_scope; + while (end != std::string::npos) { + curr_scope = Cpp::GetNamed(name.substr(start, end - start), curr_scope); + start = end + delim.length(); + end = name.find(delim, start); + } + return Cpp::GetNamed(name.substr(start, end), curr_scope); +} +static bool is_identifier(std::string_view s) { + if (s.empty()) + return false; + auto is_valid_start = [](unsigned char c) { + return std::isalpha(c) || c == '_'; + }; + auto is_valid_body = [](unsigned char c) { + return std::isalnum(c) || c == '_'; + }; + return is_valid_start(s[0]) && + std::all_of(s.begin() + 1, s.end(), is_valid_body); +}; + +// returns true if no new type was added. +bool interop::AppendTypesSlow(const std::string& name, + std::vector& types, + interop::TCppScope_t parent) { + + // Add no new type if string is empty + if (name.empty()) + return true; + + // The ast printer gave us garbage. + if (name == "") + return true; + + auto replace_all = [](std::string& str, const std::string& from, + const std::string& to) { + if (from.empty()) + return; + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + }; + + std::string resolved_name = name; + replace_all(resolved_name, "std::initializer_list<", + "std::vector<"); // replace initializer_list with vector + + // If we have a single identifier, we don't need anything complicated. + // Try scoped lookup first (catches type aliases / nested types declared + // inside `parent`), then fall back to TU (catches typedefs declared + // outside the query scope, e.g. `typedef Foo Bar;` at TU consulted + // from a method on Foo). + if (is_identifier(name)) { + TCppType_t type = parent ? Cpp::GetType(name, parent) : nullptr; + if (!type) + type = Cpp::GetType(name); + if (type) { + types.emplace_back(type.data); + return false; + } + return true; + } + + std::lock_guard Lock(InterOpMutex); + + // We might have an entire expression such as int, double. + static unsigned long long struct_count = 0; + std::string code = + "template struct __cppjit_interop_AppendTypesSlow {};\n"; + if (!struct_count) + Cpp::Declare(code.c_str(), /*silent=*/true); // initialize the trampoline + + // The trampoline declares its variable in the global scope, so a name + // written relative to a parent (e.g. "vector" looked up in std) + // won't resolve. Try the name as given, then qualified by the parent. + std::vector candidates = {resolved_name}; + if (parent && parent != Cpp::GetGlobalScope() && + (interop::IsNamespace(parent) || interop::IsClass(parent))) + candidates.push_back(Cpp::GetQualifiedCompleteName(parent) + + "::" + resolved_name); + + for (const std::string& candidate : candidates) { + std::string var = "__cppjit_interop_s" + std::to_string(struct_count++); + // nodebug: with -g the variable's debug info would carry the full DIE + // tree of every template argument (all member declarations included) -- + // a large, uncacheable per-lookup cost on heavyweight types. + if (!Cpp::Declare(("__cppjit_interop_AppendTypesSlow<" + candidate + + "> __attribute__((nodebug)) " + var + ";\n") + .c_str(), + /*silent=*/true)) { + TCppType_t varN = + Cpp::GetVariableType(Cpp::GetNamed(var.c_str(), /*parent=*/nullptr)); + TCppScope_t instance_class = Cpp::GetScopeFromType(varN); + size_t oldSize = types.size(); + Cpp::GetClassTemplateInstantiationArgs(instance_class, types); + return oldSize == types.size(); + } + } + + // We split each individual types based on , and resolve it + // FIXME: see discussion on should we support template instantiation with + // string: + // https://github.com/compiler-research/cppyy-backend/pull/137#discussion_r2079357491 + // We should consider eliminating the `split_comma_saparated_types` and + // `is_integral` string parsing. + std::vector individual_types; + if (!split_comma_saparated_types(resolved_name, individual_types)) + return true; + + for (std::string& i : individual_types) { + // Try going via interop::GetType first. + const char* integral_value = nullptr; + interop::TCppType_t type = nullptr; + + type = GetType(i, /*enable_slow_lookup=*/true); + if (!type && parent && + (interop::IsNamespace(parent) || interop::IsClass(parent))) { + type = + interop::GetTypeFromScope(interop::GetNamed(resolved_name, parent)); + } + + if (!type) { + types.clear(); + return true; + } + + if (is_integral(i)) + integral_value = strdup(i.c_str()); + if (TCppScope_t scope = GetEnumFromCompleteName(i)) + if (Cpp::IsEnumConstant(scope)) + integral_value = + strdup(std::to_string(Cpp::GetEnumConstantValue(scope)).c_str()); + types.emplace_back(type.data, integral_value); + } + return false; +} + +interop::TCppType_t interop::GetType(const std::string& name, + bool enable_slow_lookup /* = false */) { + // The ast printer gave us garbage. + if (name == "") + return nullptr; + std::lock_guard Lock(InterOpMutex); + + if (auto type = Cpp::GetType(name)) + return type; + + // Plain identifiers don't need the heavy __typeof__ trampoline: + // Cpp::GetType above already covers builtin types and named + // scopes. Exception: the three identifier-shaped C++ value- + // literals -- `true`, `false`, `nullptr` -- aren't reachable by + // name (no type called "false") but appear as non-type template + // args in libstdc++ types like _Node_iterator<..., false, false>; + // map them to their underlying type directly so the per-chunk + // fallback in AppendTypesSlow gets a real type without paying + // the trampoline cost. + if (is_identifier(name)) { + if (name == "true" || name == "false") + return Cpp::GetType("bool"); + if (name == "nullptr") + return Cpp::GetType("nullptr_t", Cpp::GetNamed("std")); + return nullptr; + } + + if (!enable_slow_lookup) { + if (name.find("::") != std::string::npos) + throw std::runtime_error( + "Calling interop::GetType with qualified name '" + name + "'\n"); + return nullptr; + } + + // Here we might need to deal with integral types such as 3.14. + + static unsigned long long var_count = 0; + std::string id = "__cppjit_interop_GetType_" + std::to_string(var_count++); + std::string using_clause = "using " + id + " = __typeof__(" + name + ");\n"; + + if (!Cpp::Declare(using_clause.c_str(), /*silent=*/true)) { + TCppScope_t lookup = Cpp::GetNamed(id); + TCppType_t lookup_ty = Cpp::GetTypeFromScope(lookup); + return Cpp::GetCanonicalType(lookup_ty); + } + return nullptr; +} + +interop::TCppType_t interop::GetComplexType(const std::string& name) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetComplexType(Cpp::GetType(name)); +} + +// //---------------------------------------------------------------------------- +// static std::string extract_namespace(const std::string& name) +// { +// // Find the namespace the named class lives in, take care of templates +// // Note: this code also lives in cpyrt (TODO: refactor?) +// if (name.empty()) +// return name; +// +// int tpl_open = 0; +// for (std::string::size_type pos = name.size()-1; 0 < pos; --pos) { +// std::string::value_type c = name[pos]; +// +// // count '<' and '>' to be able to skip template contents +// if (c == '>') +// ++tpl_open; +// else if (c == '<') +// --tpl_open; +// +// // collect name up to "::" +// else if (tpl_open == 0 && c == ':' && name[pos-1] == ':') { +// // found the extend of the scope ... done +// return name.substr(0, pos-1); +// } +// } +// +// // no namespace; assume outer scope +// return ""; +// } +// + +std::string interop::ResolveEnum(TCppScope_t handle) { + std::lock_guard Lock(InterOpMutex); + std::string type = + Cpp::GetTypeAsString(Cpp::GetIntegerTypeFromEnumScope(handle)); + if (type == "signed char") + return "char"; + return type; +} + +interop::TCppScope_t interop::GetUnderlyingScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetUnderlyingScope(scope); +} + +interop::TCppScope_t interop::GetScope(const std::string& name, + TCppScope_t parent_scope) { + std::lock_guard Lock(InterOpMutex); + if (interop::TCppScope_t scope = Cpp::GetScope(name, parent_scope)) + return scope; + if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) + if (interop::TCppScope_t scope = Cpp::GetScopeFromCompleteName(name)) + return scope; + + // FIXME: avoid string parsing here + if (name.find('<') != std::string::npos) { + // Templated type; may need instantiation. Resolve the whole type + // expression (e.g. "std::array") and read back its scope. + // Splitting off the argument list and resolving it directly cannot + // represent non-type arguments such as the `3` in std::array. + std::vector types; + InterOpMutex.unlock(); // unlock to allow AppendTypesSlow + bool added_new_type = + !interop::AppendTypesSlow(name, types, /*parent=*/parent_scope); + std::lock_guard Lock(InterOpMutex); + if (added_new_type && types.size() == 1) { + // A pointer or reference spelling (e.g. "std::chrono::nanoseconds *", + // the return type of std::array::begin()) does not + // name a scope; GetScopeFromType would silently strip the pointer and + // return the pointee's scope, misclassifying the name. + if (Cpp::IsPointerType(types[0].m_Type) || + Cpp::IsReferenceType(types[0].m_Type)) + return nullptr; + TCppScope_t scope = Cpp::GetScopeFromType(types[0].m_Type); + // Naming the type as a template argument above does not instantiate + // it, so the specialization may still be declared-but-undefined. + // Force its definition: callers expect a complete scope, e.g. to + // walk its base classes. + if (scope) + Cpp::IsComplete(scope); + return scope; + } + } + return nullptr; +} + +interop::TCppScope_t interop::GetFullScope(const std::string& name) { + return interop::GetScope(name); +} + +interop::TCppScope_t interop::GetTypeScope(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetScopeFromType(Cpp::GetVariableType(var)); +} + +interop::TCppScope_t interop::GetNamed(const std::string& name, + TCppScope_t parent_scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetNamed(name, parent_scope); +} + +interop::TCppScope_t interop::GetParentScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetParentScope(scope); +} + +interop::TCppScope_t interop::GetScopeFromType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetScopeFromType(type); +} + +interop::TCppType_t interop::GetTypeFromScope(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeFromScope(klass); +} + +interop::TCppScope_t interop::GetGlobalScope() { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetGlobalScope(); +} + +bool interop::IsTemplate(TCppScope_t handle) { return Cpp::IsTemplate(handle); } + +bool interop::IsTemplateInstantiation(TCppScope_t handle) { + return Cpp::IsTemplateSpecialization(handle); +} + +bool interop::IsTypedefed(TCppScope_t handle) { + return Cpp::IsTypedefed(handle); +} + +namespace { +class AutoCastRTTI { +public: + virtual ~AutoCastRTTI() {} +}; +} // namespace + +interop::TCppScope_t interop::GetActualClass(TCppScope_t klass, + TCppObject_t obj) { + std::lock_guard Lock(InterOpMutex); + + if (!Cpp::IsClassPolymorphic(klass)) + return klass; + + const std::type_info* typ = &typeid(*(AutoCastRTTI*)obj.data); + + std::string mangled_name = typ->name(); + std::string demangled_name = Cpp::Demangle(mangled_name); + + if (TCppScope_t scope = interop::GetScope(demangled_name)) + return scope; + + return klass; +} + +size_t interop::SizeOf(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::SizeOf(klass); +} + +size_t interop::SizeOfType(TCppType_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetSizeOfType(klass); +} + +bool interop::IsBuiltin(const std::string& type_name) { + static std::set s_builtins = {"bool", + "char", + "signed char", + "unsigned char", + "wchar_t", + "short", + "unsigned short", + "int", + "unsigned int", + "long", + "unsigned long", + "long long", + "unsigned long long", + "float", + "double", + "long double", + "void"}; + if (s_builtins.find(trim(type_name)) != s_builtins.end()) + return true; + + if (strstr(type_name.c_str(), "std::complex")) + return true; + + return false; +} + +bool interop::IsBuiltin(TCppType_t type) { return Cpp::IsBuiltin(type); } + +bool interop::IsComplete(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::IsComplete(scope); +} + +// // memory management +// --------------------------------------------------------- +interop::TCppObject_t interop::Allocate(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::Allocate(scope, /*count=*/1); +} + +void interop::Deallocate(TCppScope_t scope, TCppObject_t instance) { + std::lock_guard Lock(InterOpMutex); + Cpp::Deallocate(scope, instance, /*count=*/1); +} + +interop::TCppObject_t interop::Construct(TCppScope_t scope, + void* arena /*=nullptr*/) { + std::lock_guard Lock( + InterOpMutex); // TODO: this shouldn't locks the JIT call + return Cpp::Construct(scope, arena, /*count=*/1); +} + +void interop::Destruct(TCppScope_t scope, TCppObject_t instance) { + std::lock_guard Lock( + InterOpMutex); // TODO: this shouldn't locks the JIT call + Cpp::Destruct(instance, scope, true, /*count=*/0); +} + +static inline bool copy_args(Parameter* args, size_t nargs, void** vargs) { + bool runRelease = false; + for (size_t i = 0; i < nargs; ++i) { + switch (args[i].fTypeCode) { + case 'X': /* (void*)type& with free */ + runRelease = true; + case 'V': /* (void*)type& */ + vargs[i] = args[i].fValue.fVoidp; + break; + case 'r': /* const type& */ + vargs[i] = args[i].fRef; + break; + default: /* all other types in union */ + vargs[i] = (void*)&args[i].fValue.fVoidp; + break; + } + } + return runRelease; +} + +static inline void release_args(Parameter* args, size_t nargs) { + for (size_t i = 0; i < nargs; ++i) { + if (args[i].fTypeCode == 'X') + free(args[i].fValue.fVoidp); + } +} + +static inline bool WrapperCall(interop::TCppMethod_t method, size_t nargs, + void* args_, void* self, void* result) { + Parameter* args = (Parameter*)args_; + // bool is_direct = nargs & DIRECT_CALL; + nargs = CALL_NARGS(nargs); + + // if (!is_ready(wrap, is_direct)) + // return false; // happens with compilation error + InterOpMutex.lock(); + if (Cpp::JitCall JC = Cpp::MakeFunctionCallable(method)) { + InterOpMutex.unlock(); + bool runRelease = false; + // const auto& fgen = /* is_direct ? faceptr.fDirect : */ faceptr; + if (nargs <= SMALL_ARGS_N) { + void* smallbuf[SMALL_ARGS_N]; + if (nargs) + runRelease = copy_args(args, nargs, smallbuf); + // CLING_CATCH_UNCAUGHT_ + JC.Invoke(result, {smallbuf, nargs}, self); + // _CLING_CATCH_UNCAUGHT + } else { + std::vector buf(nargs); + runRelease = copy_args(args, nargs, buf.data()); + // CLING_CATCH_UNCAUGHT_ + JC.Invoke(result, {buf.data(), nargs}, self); + // _CLING_CATCH_UNCAUGHT + } + if (runRelease) + release_args(args, nargs); + return true; + } + InterOpMutex.unlock(); + return false; +} + +template +static inline T CallT(interop::TCppMethod_t method, interop::TCppObject_t self, + size_t nargs, void* args) { + T t{}; + if (WrapperCall(method, nargs, args, self.data, &t)) + return t; + throw std::runtime_error("failed to resolve function"); + return (T)-1; +} + +#ifdef PRINT_DEBUG +#define _IMP_CALL_PRINT_STMT(type) printf("IMP CALL with type: %s\n", #type); +#else +#define _IMP_CALL_PRINT_STMT(type) +#endif + +#define CPPJIT_IMP_CALL(typecode, rtype) \ + rtype interop::Call##typecode(TCppMethod_t method, TCppObject_t self, \ + size_t nargs, void* args) { \ + _IMP_CALL_PRINT_STMT(rtype) \ + return CallT(method, self, nargs, args); \ + } + +void interop::CallV(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args) { + if (!WrapperCall(method, nargs, args, self.data, nullptr)) + return /* TODO ... report error */; +} + +// clang-format off +CPPJIT_IMP_CALL(B, unsigned char) +CPPJIT_IMP_CALL(C, char ) +CPPJIT_IMP_CALL(H, short ) +CPPJIT_IMP_CALL(I, int ) +CPPJIT_IMP_CALL(L, long ) +CPPJIT_IMP_CALL(LL, long long ) +CPPJIT_IMP_CALL(F, float ) +CPPJIT_IMP_CALL(D, double ) +CPPJIT_IMP_CALL(LD, long double ) +// clang-format on + +void* interop::CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args) { + void* r = nullptr; + if (WrapperCall(method, nargs, args, self.data, &r)) + return r; + return nullptr; +} + +char* interop::CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, + void* args, size_t* length) { + char* cstr = nullptr; + // TClassRef cr("std::string"); // TODO: Why is this required? + std::string* cppresult = (std::string*)malloc(sizeof(std::string)); + if (WrapperCall(method, nargs, args, self.data, (void*)cppresult)) { + cstr = cppstring_to_cstring(*cppresult); + *length = cppresult->size(); + cppresult->std::string::~basic_string(); + } else + *length = 0; + free((void*)cppresult); + return cstr; +} + +interop::TCppObject_t interop::CallConstructor(TCppMethod_t method, + TCppScope_t /*klass*/, + size_t nargs, void* args) { + void* obj = nullptr; + WrapperCall(method, nargs, args, nullptr, &obj); + return (TCppObject_t)obj; +} + +void interop::CallDestructor(TCppScope_t scope, TCppObject_t self) { + std::lock_guard Lock( + InterOpMutex); // TODO: this shouldn't locks the JIT call + Cpp::Destruct(self, scope, /*withFree=*/false, /*count=*/0); +} + +interop::TCppObject_t interop::CallO(TCppMethod_t method, TCppObject_t self, + size_t nargs, void* args, + TCppType_t result_type) { + void* obj = ::operator new(interop::SizeOfType(result_type)); + if (WrapperCall(method, nargs, args, self.data, obj)) + return (TCppObject_t)obj; + ::operator delete(obj); + return TCppObject_t{}; +} + +interop::TCppFuncAddr_t interop::GetFunctionAddress(TCppMethod_t method, + bool /*check_enabled*/) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionAddress(method); +} + +// handling of function argument buffer -------------------------------------- +void* interop::AllocateFunctionArgs(size_t nargs) { + return new Parameter[nargs]; +} + +void interop::DeallocateFunctionArgs(void* args) { delete[] (Parameter*)args; } + +size_t interop::GetFunctionArgSizeof() { return sizeof(Parameter); } + +size_t interop::GetFunctionArgTypeoffset() { + return offsetof(Parameter, fTypeCode); +} + +// scope reflection information ---------------------------------------------- +bool interop::IsNamespace(TCppScope_t scope) { + if (!scope) + return false; + + // Test if this scope represents a namespace. + std::lock_guard Lock(InterOpMutex); + return Cpp::IsNamespace(scope) || Cpp::GetGlobalScope() == scope; +} + +bool interop::IsClass(TCppScope_t scope) { + // Test if this scope represents a namespace. + return Cpp::IsClass(scope); +} +// +bool interop::IsAbstract(TCppScope_t scope) { + // Test if this type may not be instantiated. + return Cpp::IsAbstract(scope); +} + +bool interop::IsEnumScope(TCppScope_t scope) { return Cpp::IsEnumScope(scope); } + +bool interop::IsEnumConstant(TCppScope_t scope) { + return Cpp::IsEnumConstant(interop::GetUnderlyingScope(scope)); +} + +bool interop::IsEnumType(TCppType_t type) { return Cpp::IsEnumType(type); } + +bool interop::IsAggregate(TCppScope_t type) { + // Test if this type is a "plain old data" type + return Cpp::IsAggregate(type); +} + +bool interop::IsDefaultConstructable(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + // Test if this type has a default constructor or is a "plain old data" type + return Cpp::HasDefaultConstructor(scope); +} + +bool interop::IsVariable(TCppScope_t scope) { return Cpp::IsVariable(scope); } + +// // helpers for stripping scope names +// static +// std::string outer_with_template(const std::string& name) +// { +// // Cut down to the outer-most scope from , taking proper care of +// templates. +// int tpl_open = 0; +// for (std::string::size_type pos = 0; pos < name.size(); ++pos) { +// std::string::value_type c = name[pos]; +// +// // count '<' and '>' to be able to skip template contents +// if (c == '<') +// ++tpl_open; +// else if (c == '>') +// --tpl_open; +// +// // collect name up to "::" +// else if (tpl_open == 0 && \ +// c == ':' && pos+1 < name.size() && name[pos+1] == ':') { +// // found the extend of the scope ... done +// return name.substr(0, pos-1); +// } +// } +// +// // whole name is apparently a single scope +// return name; +// } +// +// static +// std::string outer_no_template(const std::string& name) +// { +// // Cut down to the outer-most scope from , drop templates +// std::string::size_type first_scope = name.find(':'); +// if (first_scope == std::string::npos) +// return name.substr(0, name.find('<')); +// std::string::size_type first_templ = name.find('<'); +// if (first_templ == std::string::npos) +// return name.substr(0, first_scope); +// return name.substr(0, std::min(first_templ, first_scope)); +// } +// +// #define FILL_COLL(type, filter) { \ +// TIter itr{coll}; \ +// type* obj = nullptr; \ +// while ((obj = (type*)itr.Next())) { \ +// const char* nm = obj->GetName(); \ +// if (nm && nm[0] != '_' && !(obj->Property() & (filter))) { \ +// if (gInitialNames.find(nm) == gInitialNames.end()) \ +// cppnames.insert(nm); \ +// }}} +// +// static inline +// void cond_add(interop::TCppScope_t scope, const std::string& ns_scope, +// std::set& cppnames, const char* name, bool nofilter = false) +// { +// if (!name || name[0] == '_' || strstr(name, ".h") != 0 || strncmp(name, +// "operator", 8) == 0) +// return; +// +// if (scope == GLOBAL_HANDLE) { +// std::string to_add = outer_no_template(name); +// if (nofilter || gInitialNames.find(to_add) == gInitialNames.end()) +// cppnames.insert(outer_no_template(name)); +// } else if (scope == STD_HANDLE) { +// if (strncmp(name, "std::", 5) == 0) { +// name += 5; +// #ifdef __APPLE__ +// if (strncmp(name, "__1::", 5) == 0) name += 5; +// #endif +// } +// cppnames.insert(outer_no_template(name)); +// } else { +// if (strncmp(name, ns_scope.c_str(), ns_scope.size()) == 0) +// cppnames.insert(outer_with_template(name + ns_scope.size())); +// } +// } + +void interop::GetAllCppNames(TCppScope_t scope, + std::set& cppnames) { + // Collect all known names of C++ entities under scope. This is useful for + // IDEs employing tab-completion, for example. Note that functions names need + // not be unique as they can be overloaded. + std::lock_guard Lock(InterOpMutex); + Cpp::GetAllCppNames(scope, cppnames); +} + +// class reflection information ---------------------------------------------- +std::vector +interop::GetUsingNamespaces(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetUsingNamespaces(scope); +} + +// class reflection information ---------------------------------------------- +std::string interop::GetFinalName(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetCompleteName(Cpp::GetUnderlyingScope(klass)); +} + +std::string interop::GetScopedFinalName(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetQualifiedCompleteName(klass); +} + +bool interop::HasVirtualDestructor(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + TCppMethod_t func = Cpp::GetDestructor(scope); + return Cpp::IsVirtualMethod(func); +} + +interop::TCppIndex_t interop::GetNumBases(TCppScope_t klass) { + // Get the total number of base classes that this class has. + std::lock_guard Lock(InterOpMutex); + return Cpp::GetNumBases(klass); +} + +//////////////////////////////////////////////////////////////////////////////// +/// \fn interop::TCppIndex_t interop::GetNumBasesLongestBranch(TCppScope_t +/// klass) \brief Retrieve number of base classes in the longest branch of the +/// inheritance tree of the input class. +/// \param[in] klass The class to start the retrieval process from. +/// +/// This is a helper function for interop::GetNumBasesLongestBranch. +/// Given an inheritance tree, the function assigns weight 1 to each class that +/// has at least one base. Starting from the input class, the function is +/// called recursively on all the bases. For each base the return value is one +/// (the weight of the base itself) plus the maximum value retrieved for their +/// bases in turn. For example, given the following inheritance tree: +/// +/// ~~~{.cpp} +/// class A {}; class B: public A {}; +/// class X {}; class Y: public X {}; class Z: public Y {}; +/// class C: public B, Z {}; +/// ~~~ +/// +/// calling this function on an instance of `C` will return 3, the steps +/// required to go from C to X. +interop::TCppIndex_t interop::GetNumBasesLongestBranch(TCppScope_t klass) { + std::vector num; + for (TCppIndex_t ibase = 0; ibase < GetNumBases(klass); ++ibase) + num.push_back( + GetNumBasesLongestBranch(interop::GetBaseScope(klass, ibase))); + if (num.empty()) + return 0; + return *std::max_element(num.begin(), num.end()) + 1; +} + +std::string interop::GetBaseName(TCppScope_t klass, TCppIndex_t ibase) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetName(Cpp::GetBaseClass(klass, ibase)); +} + +interop::TCppScope_t interop::GetBaseScope(TCppScope_t klass, + TCppIndex_t ibase) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetBaseClass(klass, ibase); +} + +bool interop::IsSubclass(TCppScope_t derived, TCppScope_t base) { + std::lock_guard Lock(InterOpMutex); + return Cpp::IsSubclass(derived, base); +} + +static std::set gSmartPtrTypes = { + "std::auto_ptr", "std::shared_ptr", "std::unique_ptr", "std::weak_ptr"}; + +bool interop::IsSmartPtr(TCppScope_t klass) { + const std::string& rn = interop::GetScopedFinalName(klass); + if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) != gSmartPtrTypes.end()) + return true; + return false; +} + +bool interop::GetSmartPtrInfo(const std::string& tname, TCppScope_t* raw, + TCppMethod_t* deref) { + // TODO: We can directly accept scope instead of name + const std::string& rn = ResolveName(tname); + if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) == gSmartPtrTypes.end()) + return false; + + if (!raw && !deref) + return true; + + TCppScope_t scope = interop::GetScope(rn); + if (!scope) + return false; + + std::vector ops; + { + std::lock_guard Lock(InterOpMutex); + Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops, + /*kind=*/Cpp::OperatorArity::kBoth); + } + if (ops.size() != 1) + return false; + + if (deref) + *deref = ops[0]; + if (raw) + *raw = interop::GetScopeFromType(interop::GetMethodReturnType(ops[0])); + return (!deref || *deref) && (!raw || *raw); +} + +// type offsets -------------------------------------------------------------- +ptrdiff_t interop::GetBaseOffset(TCppScope_t derived, TCppScope_t base, + TCppObject_t /*address*/, int direction, + bool rerror) { + std::lock_guard Lock(InterOpMutex); + intptr_t offset = Cpp::GetBaseClassOffset(derived, base); + + if (offset == -1) // Cling error, treat silently + return rerror ? (ptrdiff_t)offset : 0; + + return (ptrdiff_t)(direction < 0 ? -offset : offset); +} + +// method/function reflection information ------------------------------------ +void interop::GetClassMethods(TCppScope_t scope, + std::vector& methods) { + std::lock_guard Lock(InterOpMutex); + Cpp::GetClassMethods(scope, methods); +} + +std::vector +interop::GetMethodsFromName(TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionsUsingName(scope, name); +} + +std::string interop::GetName(TCppScope_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetName(method); +} + +std::string interop::GetFullName(TCppScope_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetCompleteName(method); +} + +interop::TCppType_t interop::GetMethodReturnType(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionReturnType(method); +} + +std::string interop::GetMethodReturnTypeAsString(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString( + Cpp::GetCanonicalType(Cpp::GetFunctionReturnType(method))); +} + +interop::TCppIndex_t interop::GetMethodNumArgs(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionNumArgs(method); +} + +interop::TCppIndex_t interop::GetMethodReqArgs(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionRequiredArgs(method); +} + +std::string interop::GetMethodArgName(TCppMethod_t method, TCppIndex_t iarg) { + if (!method) + return ""; + + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionArgName(method, iarg); +} + +interop::TCppType_t interop::GetMethodArgType(TCppMethod_t method, + TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionArgType(method, iarg); +} + +std::string interop::GetMethodArgTypeAsString(TCppMethod_t method, + TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier( + Cpp::GetFunctionArgType(method, iarg), Cpp::QualKind::Const)); +} + +std::string interop::GetMethodArgCanonTypeAsString(TCppMethod_t method, + TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString( + Cpp::GetCanonicalType(Cpp::GetFunctionArgType(method, iarg))); +} + +std::string interop::GetMethodArgDefault(TCppMethod_t method, + TCppIndex_t iarg) { + if (!method) + return ""; + + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionArgDefault(method, iarg); +} + +interop::TCppIndex_t +interop::CompareMethodArgType(TCppMethod_t /*method*/, TCppIndex_t iarg, + const std::string& req_type) { + // if (method) { + // TFunction* f = m2f(method); + // TMethodArg* arg = (TMethodArg + // *)f->GetListOfMethodArgs()->At((int)iarg); void *argqtp = + // gInterpreter->TypeInfo_QualTypePtr(arg->GetTypeInfo()); + + // TypeInfo_t *reqti = gInterpreter->TypeInfo_Factory(req_type.c_str()); + // void *reqqtp = gInterpreter->TypeInfo_QualTypePtr(reqti); + + // if (ArgSimilarityScore(argqtp, reqqtp) < 10) { + // return ArgSimilarityScore(argqtp, reqqtp); + // } + // else { // Match using underlying types + // if(gInterpreter->IsPointerType(argqtp)) + // argqtp = + // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetPointerType(argqtp)); + + // // Handles reference types and strips qualifiers + // TypeInfo_t *arg_ul = gInterpreter->GetNonReferenceType(argqtp); + // TypeInfo_t *req_ul = gInterpreter->GetNonReferenceType(reqqtp); + // argqtp = + // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(arg_ul))); + // reqqtp = + // gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(req_ul))); + + // return ArgSimilarityScore(argqtp, reqqtp); + // } + // } + return 0; // Method is not valid +} + +std::string interop::GetMethodSignature(TCppMethod_t method, + bool show_formal_args, + TCppIndex_t max_args) { + std::ostringstream sig; + sig << "("; + int nArgs = GetMethodNumArgs(method); + if (max_args != (TCppIndex_t)-1) + nArgs = std::min(nArgs, (int)max_args); + for (int iarg = 0; iarg < nArgs; ++iarg) { + sig << interop::GetMethodArgTypeAsString(method, iarg); + if (show_formal_args) { + std::string argname = interop::GetMethodArgName(method, iarg); + if (!argname.empty()) + sig << " " << argname; + std::string defvalue = interop::GetMethodArgDefault(method, iarg); + if (!defvalue.empty()) + sig << " = " << defvalue; + } + if (iarg != nArgs - 1) + sig << ", "; + } + sig << ")"; + return sig.str(); +} + +interop::TCppType_t interop::GetFnTypeFromStdFn(TCppType_t fn_type) { + fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type) + : fn_type; + fn_type = + Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type; + TCppScope_t scope = Cpp::GetScopeFromType(fn_type); + std::vector args; + Cpp::GetClassTemplateArgs(scope, args); + assert(args.size() == 1); + if (args.size() == 1) + return args[0].m_Type; + return nullptr; +} + +void interop::GetFnTypeSig(TCppType_t fn_type, + std::vector& arg_types) { + fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type) + : fn_type; + fn_type = + Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type; + Cpp::GetFnTypeSignature(fn_type, arg_types); +} + +bool interop::IsSameType(TCppType_t typ1, TCppType_t typ2) { + return Cpp::IsSameType(typ1, typ2); +} + +bool interop::IsFunctionType(TCppType_t typ) { + typ = Cpp::IsReferenceType(typ) ? Cpp::GetNonReferenceType(typ) : typ; + typ = Cpp::IsPointerType(typ) ? Cpp::GetPointeeType(typ) : typ; + return Cpp::IsFunctionProtoType(typ); +} + +bool interop::IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2) { + typ1 = Cpp::IsReferenceType(typ1) ? Cpp::GetNonReferenceType(typ1) : typ1; + typ2 = Cpp::IsReferenceType(typ2) ? Cpp::GetNonReferenceType(typ2) : typ2; + typ1 = Cpp::IsPointerType(typ1) ? Cpp::GetPointeeType(typ1) : typ1; + typ2 = Cpp::IsPointerType(typ2) ? Cpp::GetPointeeType(typ2) : typ2; + return Cpp::IsSameType(typ1, typ2); +} + +std::string interop::GetDoxygenComment(TCppScope_t scope, bool strip_markers) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetDoxygenComment(scope, strip_markers); +} + +bool interop::IsConstMethod(TCppMethod_t method) { + if (!method) + return false; + std::lock_guard Lock(InterOpMutex); + return Cpp::IsConstMethod(method); +} + +void interop::GetTemplatedMethods(TCppScope_t scope, + std::vector& methods) { + std::lock_guard Lock(InterOpMutex); + Cpp::GetFunctionTemplatedDecls(scope, methods); +} + +interop::TCppIndex_t +interop::GetNumTemplatedMethods(TCppScope_t scope, bool /*accept_namespace*/) { + std::lock_guard Lock(InterOpMutex); + std::vector mc; + Cpp::GetFunctionTemplatedDecls(scope, mc); + return mc.size(); +} + +std::string interop::GetTemplatedMethodName(TCppScope_t scope, + TCppIndex_t imeth) { + std::lock_guard Lock(InterOpMutex); + std::vector mc; + Cpp::GetFunctionTemplatedDecls(scope, mc); + + if (imeth < mc.size()) + return Cpp::GetName(TCppScope_t(mc[imeth].data)); + + return ""; +} + +bool interop::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); + return Cpp::ExistsFunctionTemplate(name, scope); +} + +bool interop::IsTemplatedMethod(TCppMethod_t method) { + return Cpp::IsTemplatedFunction(method); +} + +bool interop::IsStaticTemplate(TCppScope_t scope, const std::string& name) { + std::vector candidate_methods; + Cpp::GetClassTemplatedMethods(name, scope, candidate_methods); + bool is_static = true; + for (auto i : candidate_methods) { + if (!Cpp::IsStaticMethod(i)) { + is_static = false; + break; + } + } + return is_static; +} + +interop::TCppMethod_t interop::GetMethodTemplate(TCppScope_t scope, + const std::string& name, + const std::string& proto) { + std::string pureName; + std::string explicit_params; + + if ((name.find("operator<") != 0) && (name.find('<') != std::string::npos)) { + pureName = name.substr(0, name.find('<')); + size_t start = name.find('<'); + size_t end = name.rfind('>'); + explicit_params = name.substr(start + 1, end - start - 1); + } else { + pureName = name; + } + + std::lock_guard Lock(InterOpMutex); + + std::vector unresolved_candidate_methods; + Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods); + if (unresolved_candidate_methods.empty() && name.find("operator") == 0) { + // try operators + interop::GetClassOperators(scope, pureName, unresolved_candidate_methods); + } + + // cpyrt assumes that we attempt instantiation here + std::vector arg_types; + std::vector templ_params; + interop::AppendTypesSlow(proto, arg_types, scope); + interop::AppendTypesSlow(explicit_params, templ_params, scope); + interop::TCppMethod_t cppmeth = nullptr; + cppmeth = Cpp::BestOverloadFunctionMatch(unresolved_candidate_methods, + templ_params, arg_types); + + // If overload resolution failed but explicit template arguments were + // supplied, fall back to direct template-argument substitution: ask Sema + // to instantiate each candidate with the explicit args. Sema's SFINAE + // rejects overloads whose substitution fails (e.g. the initializer_list + // form of std::make_any with non-init-list explicit args), so iterating + // gives back exactly the viable specialisation. The wrapper-side argument + // conversion then handles e.g. taking the address of an instance when the + // substituted parameter is a pointer. + if (!cppmeth && !templ_params.empty()) { + for (const auto& cand : unresolved_candidate_methods) { + if (Cpp::DeclRef spec = Cpp::InstantiateTemplate( + TCppScope_t(cand.data), templ_params.data(), templ_params.size(), + /*instantiate_body=*/false)) { + cppmeth = spec.data; + break; + } + } + } + + return TCppMethod_t(cppmeth.data); + // if it fails, use Sema to propogate info about why it failed (DeductionInfo) +} + +static inline std::string type_remap(const std::string& n1, + const std::string& n2) { + // Operator lookups of (C++ string, Python str) should succeed for the + // combos of string/str, wstring/str, string/unicode and wstring/unicode; + // since C++ does not have a operator+(std::string, std::wstring), we'll + // have to look up the same type and rely on the converters in + // cpyrt/_cppjit. + if (n1 == "str" || n1 == "unicode" || n1 == "std::basic_string") { + if (n2 == "std::basic_string") + return "std::basic_string&"; // match like for like + return "std::basic_string&"; // probably best bet + } else if (n1 == "std::basic_string") { + return "std::basic_string&"; + } else if (n1 == "complex") { + return "std::complex"; + } + return n1; +} + +void interop::GetClassOperators(interop::TCppScope_t klass, + const std::string& opname, + std::vector& operators) { + std::lock_guard Lock(InterOpMutex); + std::string op = opname.substr(8); + Cpp::GetOperator(klass, Cpp::GetOperatorFromSpelling(op), operators, + /*kind=*/Cpp::OperatorArity::kBoth); +} + +interop::TCppMethod_t interop::GetGlobalOperator(TCppScope_t scope, + const std::string& lc, + const std::string& rc, + const std::string& opname) { + std::string rc_type = type_remap(rc, lc); + std::string lc_type = type_remap(lc, rc); + + std::vector overloads; + Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads, + /*kind=*/Cpp::OperatorArity::kBoth); + + // Avoid pushing nullptr into arg_types which would crash + // BestOverloadFunctionMatch when it dereferences each entry's QualType. + auto resolve_arg_type = [](const std::string& name) -> interop::TCppType_t { + if (auto s = interop::GetScope(name)) + if (auto t = interop::GetTypeFromScope(s)) + return interop::GetReferencedType(t); + return interop::GetType(name, /*enable_slow_lookup=*/true); + }; + + std::vector arg_types; + if (auto l = resolve_arg_type(lc_type)) + arg_types.emplace_back(l.data); + else + return nullptr; + + if (!rc_type.empty()) { + if (auto r = resolve_arg_type(rc_type)) + arg_types.emplace_back(r.data); + else + return nullptr; + } + interop::TCppMethod_t cppmeth = + Cpp::BestOverloadFunctionMatch(overloads, {}, arg_types); + if (cppmeth) + return cppmeth; + return nullptr; +} + +// method properties --------------------------------------------------------- +bool interop::IsDeletedMethod(TCppMethod_t method) { + return Cpp::IsFunctionDeleted(method); +} + +bool interop::IsPublicMethod(TCppMethod_t method) { + return Cpp::IsPublicMethod(method); +} + +bool interop::IsProtectedMethod(TCppMethod_t method) { + return Cpp::IsProtectedMethod(method); +} + +bool interop::IsPrivateMethod(TCppMethod_t method) { + return Cpp::IsPrivateMethod(method); +} + +bool interop::IsConstructor(TCppMethod_t method) { + return Cpp::IsConstructor(method); +} + +bool interop::IsDestructor(TCppMethod_t method) { + return Cpp::IsDestructor(method); +} + +bool interop::IsStaticMethod(TCppMethod_t method) { + return Cpp::IsStaticMethod(method); +} + +bool interop::IsExplicit(TCppMethod_t method) { + return Cpp::IsExplicit(method); +} + +// data member reflection information ---------------------------------------- +void interop::GetDatamembers(TCppScope_t scope, + std::vector& datamembers) { + std::lock_guard Lock(InterOpMutex); + Cpp::GetDatamembers(scope, datamembers); + Cpp::GetStaticDatamembers(scope, datamembers); + Cpp::GetEnumConstantDatamembers(scope, datamembers, false); +} + +bool interop::CheckDatamember(TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); + return (bool)Cpp::LookupDatamember(name, scope); +} + +bool interop::IsLambdaClass(TCppType_t type) { + return Cpp::IsLambdaClass(type); +} + +interop::TCppScope_t interop::WrapLambdaFromVariable(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); + std::ostringstream code; + std::string name = interop::GetFinalName(var); + code << "namespace __cppjit_internal_wrap_g {\n" + << " " << "std::function " << name + << " = ::" << Cpp::GetQualifiedName(var) << ";\n" + << "}\n"; + + if (interop::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr)); + if (res) + return res; + } + return var; +} + +interop::TCppMethod_t +interop::AdaptFunctionForLambdaReturn(interop::TCppMethod_t fn) { + std::lock_guard Lock(InterOpMutex); + + std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data)); + std::string signature = interop::GetMethodSignature(fn, true); + + std::ostringstream call; + call << "("; + for (size_t i = 0, n = interop::GetMethodNumArgs(fn); i < n; i++) { + call << interop::GetMethodArgName(fn, i); + if (i != n - 1) + call << ", "; + } + call << ")"; + + std::ostringstream code; + static int i = 0; + std::string name = "lambda_return_convert_" + std::to_string(++i); + code << "namespace __cppjit_internal_wrap_g {\n" + << "auto " << name << signature << "{" << "return std::function(" + << fn_name << call.str() << "); }\n" + << "}\n"; + if (interop::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr)); + if (res) + return TCppMethod_t(res.data); + } + return fn; +} + +interop::TCppType_t interop::GetDatamemberType(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetVariableType(Cpp::GetUnderlyingScope(var)); +} + +std::string interop::GetDatamemberTypeAsString(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString( + Cpp::GetVariableType(Cpp::GetUnderlyingScope(scope))); +} + +std::string interop::GetTypeAsString(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString(type); +} + +intptr_t interop::GetDatamemberOffset(TCppScope_t var, TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetVariableOffset(Cpp::GetUnderlyingScope(var), klass); +} + +// data member properties ---------------------------------------------------- +bool interop::IsPublicData(TCppScope_t datamem) { + return Cpp::IsPublicVariable(datamem); +} + +bool interop::IsProtectedData(TCppScope_t datamem) { + return Cpp::IsProtectedVariable(datamem); +} + +bool interop::IsPrivateData(TCppScope_t datamem) { + return Cpp::IsPrivateVariable(datamem); +} + +bool interop::IsStaticDatamember(TCppScope_t var) { + return Cpp::IsStaticVariable(interop::GetUnderlyingScope(var)); +} + +bool interop::IsConstVar(TCppScope_t var) { return Cpp::IsConstVariable(var); } + +interop::TCppMethod_t interop::ReduceReturnType(TCppMethod_t fn, + TCppType_t reduce) { + std::lock_guard Lock(InterOpMutex); + + std::string fn_name = Cpp::GetQualifiedCompleteName(TCppScope_t(fn.data)); + std::string signature = interop::GetMethodSignature(fn, true); + std::string result_type = interop::GetTypeAsString(reduce); + + std::ostringstream call; + call << "("; + for (size_t i = 0, n = interop::GetMethodNumArgs(fn); i < n; i++) { + call << interop::GetMethodArgName(fn, i); + if (i != n - 1) + call << ", "; + } + call << ")"; + + std::ostringstream code; + static int i = 0; + std::string name = "reduced_function_" + std::to_string(++i); + code << "namespace __cppjit_internal_wrap_g {\n" + << result_type << " " << name << signature << "{" << "return (" + << result_type << ")::" << fn_name << call.str() << "; }\n" + << "}\n"; + if (interop::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppjit_internal_wrap_g", /*parent=*/nullptr)); + if (res) + return TCppMethod_t(res.data); + } + return fn; +} + +std::vector interop::GetDimensions(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetDimensions(type); +} + +// enum properties ----------------------------------------------------------- +std::vector interop::GetEnumConstants(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstants(scope); +} + +interop::TCppType_t interop::GetEnumConstantType(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope)); +} + +interop::TCppIndex_t interop::GetEnumDataValue(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstantValue(scope); +} + +interop::TCppScope_t interop::InstantiateTemplate(TCppScope_t tmpl, + Cpp::TemplateArgInfo* args, + size_t args_size) { + std::lock_guard Lock(InterOpMutex); + return Cpp::InstantiateTemplate(tmpl, args, args_size, + /*instantiate_body=*/false); +} + +void interop::DumpScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); + Cpp::DumpScope(scope); +} diff --git a/src/backend/precommondefs.h b/src/interop/precommondefs.h similarity index 79% rename from src/backend/precommondefs.h rename to src/interop/precommondefs.h index 62710b6..3cb57df 100644 --- a/src/backend/precommondefs.h +++ b/src/interop/precommondefs.h @@ -7,7 +7,6 @@ #ifndef __PYPY_PRECOMMONDEFS_H #define __PYPY_PRECOMMONDEFS_H - /* Define on Darwin to activate all library features */ #define _DARWIN_C_SOURCE 1 /* This must be set to 64 on some systems to enable large file support. */ @@ -20,7 +19,7 @@ #define _NETBSD_SOURCE 1 /* Define to activate features from IEEE Stds 1003.1-2001 */ #ifndef _POSIX_C_SOURCE -# define _POSIX_C_SOURCE 200112L +#define _POSIX_C_SOURCE 200112L #endif /* Define on FreeBSD to activate all library features */ #define __BSD_VISIBLE 1 @@ -28,20 +27,19 @@ /* Windows: winsock/winsock2 mess */ #define WIN32_LEAN_AND_MEAN #ifdef _WIN64 - typedef __int64 Signed; - typedef unsigned __int64 Unsigned; -# define SIGNED_MIN LLONG_MIN +typedef __int64 Signed; +typedef unsigned __int64 Unsigned; +#define SIGNED_MIN LLONG_MIN #else - typedef long Signed; - typedef unsigned long Unsigned; -# define SIGNED_MIN LONG_MIN +typedef long Signed; +typedef unsigned long Unsigned; +#define SIGNED_MIN LONG_MIN #endif #if !defined(RPY_ASSERT) && !defined(RPY_LL_ASSERT) && !defined(NDEBUG) -# define NDEBUG +#define NDEBUG #endif - /* All functions and global variables declared anywhere should use one of the following attributes: @@ -61,17 +59,16 @@ a bug; please report or fix it. */ #ifdef __GNUC__ -# define RPY_EXPORTED extern __attribute__((visibility("default"))) -# define _RPY_HIDDEN __attribute__((visibility("hidden"))) +#define RPY_EXPORTED extern __attribute__((visibility("default"))) +#define _RPY_HIDDEN __attribute__((visibility("hidden"))) #else -# define RPY_EXPORTED extern __declspec(dllexport) -# define _RPY_HIDDEN /* nothing */ +#define RPY_EXPORTED extern __declspec(dllexport) +#define _RPY_HIDDEN /* nothing */ #endif #ifndef RPY_EXTERN -# define RPY_EXTERN extern +#define RPY_EXTERN extern #endif - #endif /* __PYPY_PRECOMMONDEFS_H */ /***** End of precommondefs.h *****/ diff --git a/test/Makefile b/test/Makefile index 686e55e..e07e775 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,23 +1,26 @@ -dicts = advancedcppDict.so \ - advancedcpp2Dict.so \ - conversionsDict.so \ - cpp11featuresDict.so \ - crossinheritanceDict.so \ - datatypesDict.so \ - doc_helperDict.so \ - example01Dict.so \ - fragileDict.so \ - operatorsDict.so \ - overloadsDict.so \ - pythonizablesDict.so \ - std_streamsDict.so \ - stltypesDict.so \ - templatesDict.so +# the C++ sides of the tests live under cpp/ and build into shared +# libraries next to their sources (load_reflection_info resolves the +# header as a sibling of the loaded Dict.so) +dictnames = advancedcpp \ + advancedcpp2 \ + conversions \ + cpp11features \ + crossinheritance \ + datatypes \ + doc_helper \ + example01 \ + fragile \ + operators \ + overloads \ + pythonizables \ + std_streams \ + stltypes \ + templates + +dicts = $(addprefix cpp/,$(addsuffix Dict.so,$(dictnames))) all : $(dicts) -#cppflags=$(shell llvm-config --cxxflags) -O3 -fPIC -I$(shell python -c 'import sysconfig as sc; print(sc.get_config_var("INCLUDEPY"))') -Wno-register -# FIXME: stltypes.cxx does not compile with clang! PYTHON ?= python3 cppflags= -std=c++17 -O3 -fPIC -I$(shell $(PYTHON) -c 'import sysconfig as sc; print(sc.get_config_var("INCLUDEPY"))') -Wno-register @@ -26,11 +29,11 @@ ifeq ($(PLATFORM),Darwin) cppflags+=-dynamiclib -single_module -undefined dynamic_lookup -Wno-delete-non-virtual-dtor endif -%Dict.so: %.cxx +cpp/%Dict.so: cpp/%.cxx $(CXX) $(cppflags) -shared -o $@ $^ -#%_rflx.cpp: %.h %.xml -# echo $< --selection=$*.xml --rootmap=$*Dict.rootmap --rootmap-lib=$*Dict.so +# convenience: `make datatypesDict.so` builds cpp/datatypesDict.so +%Dict.so: cpp/%Dict.so ; .PHONY: test clean @@ -38,5 +41,4 @@ test: pytest test_*.py clean: - -rm -f $(dicts) $(wildcard *.pyc) -# -rm -f $(dicts) $(subst .so,.rootmap,$(dicts)) $(subst Dict.so,_rflx_rdict.pcm,$(dicts)) $(subst Dict.so,_rflx.cpp,$(dicts)) $(wildcard *.pyc) + -rm -f $(dicts) diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/advancedcpp.h b/test/advancedcpp.h deleted file mode 100644 index 73ca0a6..0000000 --- a/test/advancedcpp.h +++ /dev/null @@ -1,533 +0,0 @@ -#include -#include -#include -#include -#include - -//=========================================================================== -#define DECLARE_DEFAULTERS(type, tname) \ -class tname##_defaulter { \ -public: \ - tname##_defaulter(type a = 11, type b = 22, type c = 33); \ - \ -public: \ - type m_a, m_b, m_c; \ -}; \ -type tname##_defaulter_func(int idx = 0, type a = 11, type b = 22, type c = 33); -DECLARE_DEFAULTERS(short, short) // for testing of default arguments -DECLARE_DEFAULTERS(unsigned short, ushort) -DECLARE_DEFAULTERS(int, int) -DECLARE_DEFAULTERS(unsigned, uint) -DECLARE_DEFAULTERS(long, long) -DECLARE_DEFAULTERS(unsigned long, ulong) -DECLARE_DEFAULTERS(long long, llong) -DECLARE_DEFAULTERS(unsigned long long, ullong) -DECLARE_DEFAULTERS(float, float) -DECLARE_DEFAULTERS(double, double) - -std::string string_defaulter_func(int idx, const std::string& name1 = "aap", std::string name2 = "noot"); - - -//=========================================================================== -class base_class { // for simple inheritance testing -public: - base_class() { m_b = 1; m_db = 1.1; } - virtual ~base_class() {} - virtual int get_value() { return m_b; } - double get_base_value() { return m_db; } - - virtual base_class* cycle(base_class* b) { return b; } - virtual base_class* clone() { return new base_class; } - - virtual void* mask(void* p) { return p; } - -public: - int m_b; - double m_db; -}; - -class derived_class : public base_class { -public: - derived_class() { m_d = 2; m_dd = 2.2;} - virtual int get_value() { return m_d; } - double get_derived_value() { return m_dd; } - virtual base_class* clone() { return new derived_class; } - -public: - int m_d; - double m_dd; -}; - - -//=========================================================================== -class a_class { // for esoteric inheritance testing -public: - a_class() { m_a = 1; m_da = 1.1; } - virtual ~a_class() {} - virtual int get_value() = 0; - -public: - int m_a; - double m_da; -}; - -class b_class : public virtual a_class { -public: - b_class() { m_b = 2; m_db = 2.2;} - virtual int get_value() { return m_b; } - -public: - int m_b; - double m_db; -}; - -class c_class_1 : public virtual a_class, public virtual b_class { -public: - c_class_1() { m_c = 3; } - virtual int get_value() { return m_c; } - -public: - int m_c; -}; - -class c_class_2 : public virtual b_class, public virtual a_class { -public: - c_class_2() { m_c = 3; } - virtual int get_value() { return m_c; } - -public: - int m_c; -}; - -typedef c_class_2 c_class; - -class d_class : public virtual c_class, public virtual a_class { -public: - d_class() { m_d = 4; } - virtual int get_value() { return m_d; } - -public: - int m_d; -}; - -a_class* create_c1(); -a_class* create_c2(); - -int get_a(a_class& a); -int get_b(b_class& b); -int get_c(c_class& c); -int get_d(d_class& d); - - -//=========================================================================== -namespace a_ns { // for namespace testing - extern int g_a; - int get_g_a(); - - struct b_class { - b_class() { m_b = -2; } - int m_b; - static int s_b; - - struct c_class { - c_class() { m_c = -3; } - int m_c; - static int s_c; - }; - }; - - namespace d_ns { - extern int g_d; - int get_g_d(); - - struct e_class { - e_class() { m_e = -5; } - int m_e; - static int s_e; - - struct f_class { - f_class() { m_f = -6; } - int m_f; - static int s_f; - }; - }; - - } // namespace d_ns - -} // namespace a_ns - - -//=========================================================================== -template // for template testing -class T1 { -public: - T1(T t = T(1)) : m_t1(t) {} - T get_value() { return m_t1; } - -public: - T m_t1; -}; - -template -class T2 { -public: - T2(T t = T(2)) : m_t2(t) {} - T get_value() { return m_t2; } - -public: - T m_t2; -}; - -template -class T3 { -public: - T3(T t = T(3), U u = U(33)) : m_t3(t), m_u3(u) {} - T get_value_t() { return m_t3; } - U get_value_u() { return m_u3; } - -public: - T m_t3; - U m_u3; -}; - -namespace a_ns { - - template - class T4 { - public: - T4(T t = T(4)) : m_t4(t) {} - T get_value() { return m_t4; } - - public: - T m_t4; - }; - -} // namespace a_ns - -extern template class T1; -extern template class T2 >; -extern template class T3; -extern template class T3, T2 > >; -extern template class a_ns::T4; -extern template class a_ns::T4 > >; - - -//=========================================================================== -// for checking pass-by-reference of builtin types -void set_int_through_ref(int& i, int val); -int pass_int_through_const_ref(const int& i); -void set_long_through_ref(long& l, long val); -long pass_long_through_const_ref(const long& l); -void set_double_through_ref(double& d, double val); -double pass_double_through_const_ref(const double& d); - - -//=========================================================================== -class some_abstract_class { // to test abstract class handling -public: - virtual ~some_abstract_class() {} - virtual void a_virtual_method() = 0; -}; - -class some_concrete_class : public some_abstract_class { -public: - virtual void a_virtual_method() {} -}; - - -//=========================================================================== -class ref_tester { // for assignment by-ref testing -public: - ref_tester() : m_i(-99) {} - ref_tester(int i) : m_i(i) {} - ref_tester(const ref_tester& s) : m_i(s.m_i) {} - ref_tester& operator=(const ref_tester& s) { - if (&s != this) m_i = s.m_i; - return *this; - } - ~ref_tester() {} - -public: - int m_i; -}; - - -//=========================================================================== -class some_convertible { // for math conversions testing -public: - some_convertible() : m_i(-99), m_d(-99.) {} - - operator int() { return m_i; } - operator long() { return m_i; } - operator double() { return m_d; } - -public: - int m_i; - double m_d; -}; - - -class some_comparable { -}; - -bool operator==(const some_comparable& c1, const some_comparable& c2); -bool operator!=(const some_comparable& c1, const some_comparable& c2); - - -//=========================================================================== -extern double my_global_double; // a couple of globals for access testing -extern double my_global_array[500]; -extern double* my_global_ptr; -static const char my_global_string1[] = "aap " " noot " " mies"; -extern const char my_global_string2[]; -extern const char* my_global_string3[3]; - -class some_int_holder { -public: - some_int_holder(int val) : m_val(val) {} - -public: - int m_val; - char gap[7]; -}; -extern some_int_holder my_global_int_holders[5]; - -extern some_abstract_class* g_abstract_ptr; - - -//=========================================================================== -class some_class_with_data { // for life-line and identity testing -public: - class some_data { - public: - some_data() { ++s_num_data; } - some_data(const some_data&) { ++s_num_data; } - ~some_data() { --s_num_data; } - - static int s_num_data; - }; - - some_class_with_data gime_copy() { - return *this; - } - - const some_data& gime_data() { /* TODO: methptrgetter const support */ - return m_data; - } - - int m_padding; - some_data m_data; -}; - -class refers_to_self { // for data member reuse testing -public: - refers_to_self* m_other = nullptr; -}; - - -//=========================================================================== -class pointer_pass { // for testing passing of void*'s -public: - intptr_t gime_address_ptr(void* obj) { - return (intptr_t)obj; - } - - intptr_t gime_address_ptr_ptr(void** obj) { - return (intptr_t)*((long**)obj); - } - - intptr_t gime_address_ptr_ref(const void*& obj) { - return (intptr_t)obj; - } - - static intptr_t set_address_ptr_ptr(void** obj) { - (*(intptr_t**)obj) = (intptr_t*)0x4321; - return 42; - } - - static intptr_t set_address_ptr_ref(const void*& obj) { - obj = (void*)0x1234; - return 21; - } -}; - - -//=========================================================================== -class multi1 { // for testing multiple inheritance -public: - multi1(int val) : m_int(val) {} - virtual ~multi1(); - int get_multi1_int() { return m_int; } - -private: - int m_int; -}; - -class multi2 { -public: - multi2(int val) : m_int(val) {} - virtual ~multi2(); - int get_multi2_int() { return m_int; } - -private: - int m_int; -}; - -class multi : public multi1, public multi2 { -public: - multi(int val1, int val2, int val3) : - multi1(val1), multi2(val2), m_int(val3) {} - virtual ~multi(); - int get_my_own_int() { return m_int; } - -private: - int m_int; -}; - - -//=========================================================================== -class new_overloader { // for testing calls to overloaded new -public: - static int s_instances; - -public: - void* operator new(std::size_t size); - void* operator new(std::size_t, void* p) throw(); - void operator delete(void* p, std::size_t size); -}; - - -//=========================================================================== -template // more template testing -class my_templated_class { -public: - T m_b; -}; - -template -T my_templated_function(T t) { return t; } - -template class my_templated_class >; -template char my_templated_function(char); -template double my_templated_function(double); - - -//=========================================================================== -class overload_one_way { // overload order testing -public: - int gime() const; - std::string gime(); -}; - -class overload_the_other_way { -public: - std::string gime(); - int gime() const; -}; - - -//=========================================================================== -class Thrower { // exception handling testing -public: - void throw_anything(); - void throw_exception(); -}; - - -//=========================================================================== -class UsingBase1 { // using declaration testing -public: - UsingBase1(int n = 13) : m_int(n) {} - virtual ~UsingBase1() {} - virtual char vcheck() { return 'A'; } - int m_int; -}; - -class UsingDerived1 : public UsingBase1 { -public: - using UsingBase1::UsingBase1; - virtual char vcheck() { return 'B'; } - int m_int2 = 42; -}; - -class UsingBase2 { -public: - virtual ~UsingBase2() {} - virtual char vcheck() { return 'A'; } -}; - -class UsingDerived2 : public UsingBase2 { -public: - using UsingBase2::vcheck; - virtual char vcheck(int) { return 'B'; } -}; - - -//=========================================================================== -class TypedefToPrivateClass { // typedef resolution testing -private: - class PC { - public: - PC(int i) : m_val(i) {} - int m_val; - }; - -public: - typedef PC PP; - PP f() { return PC(42); } -}; - - -//=========================================================================== -namespace Cpp2PyPrinting { // operator to __str__ mapping - -class Printable1 { -public: - std::ostream& operator<<(std::ostream& os); -}; - -class Printable2 { /* empty */ }; -class Printable3 { /* empty */ }; - -std::ostream& operator<<(std::ostream& os, const Printable2&); - -} // namespace Cpp2PyPrinting - -class Printable4 { /* empty */ }; - -std::ostream& operator<<(std::ostream& os, const Cpp2PyPrinting::Printable3&); -std::ostream& operator<<(std::ostream& os, const Printable4&); - -template -class PrintableBase1 {}; -class Printable5 : public PrintableBase1 {}; - -template -std::ostream& operator<<(std::ostream& os, const PrintableBase1& y) { return os << "Ok."; } - -class Printable6 { -public: - Printable6& operator<<(int); -}; - -std::ostream& operator<<(std::ostream& os, const Printable6& y); - - -//=========================================================================== -namespace UsedSpace1 { // for using directives testing - int foo1(); - - namespace inner { - int foo2(); - } -} - -namespace UsedSpace2 { - int bar(); -} - -namespace UserDirs { - using namespace UsedSpace1; - using namespace UsedSpace2; - using namespace UsedSpace1::inner; -} diff --git a/test/advancedcpp.xml b/test/advancedcpp.xml deleted file mode 100644 index e414fe9..0000000 --- a/test/advancedcpp.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/advancedcpp2.cxx b/test/advancedcpp2.cxx deleted file mode 100644 index 3eb76b2..0000000 --- a/test/advancedcpp2.cxx +++ /dev/null @@ -1,13 +0,0 @@ -#include "advancedcpp2.h" - - -// for namespace testing -int a_ns::g_g = 77; -int a_ns::g_class::s_g = 88; -int a_ns::g_class::h_class::s_h = 99; -int a_ns::d_ns::g_i = 111; -int a_ns::d_ns::i_class::s_i = 222; -int a_ns::d_ns::i_class::j_class::s_j = 333; - -int a_ns::get_g_g() { return g_g; } -int a_ns::d_ns::get_g_i() { return g_i; } diff --git a/test/advancedcpp2.h b/test/advancedcpp2.h deleted file mode 100644 index 86167cc..0000000 --- a/test/advancedcpp2.h +++ /dev/null @@ -1,36 +0,0 @@ -//=========================================================================== -namespace a_ns { // for namespace testing - extern int g_g; - int get_g_g(); - - struct g_class { - g_class() { m_g = -7; } - int m_g; - static int s_g; - - struct h_class { - h_class() { m_h = -8; } - int m_h; - static int s_h; - }; - }; - - namespace d_ns { - extern int g_i; - int get_g_i(); - - struct i_class { - i_class() { m_i = -9; } - int m_i; - static int s_i; - - struct j_class { - j_class() { m_j = -10; } - int m_j; - static int s_j; - }; - }; - - } // namespace d_ns - -} // namespace a_ns diff --git a/test/advancedcpp2.xml b/test/advancedcpp2.xml deleted file mode 100644 index 43925d1..0000000 --- a/test/advancedcpp2.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/test/assert_interactive.py b/test/assert_interactive.py index 6f592b0..94c30ab 100644 --- a/test/assert_interactive.py +++ b/test/assert_interactive.py @@ -1,17 +1,18 @@ -from cppyy.interactive import * +from cppjit.interactive import * # namespace at the global level assert std -# cppyy functions +# cppjit functions assert cppdef assert include try: - import __pypy__ - # 'cppyy.gbl' bound to 'g' + import __pypy__ # noqa: F401 + + # 'cppjit.gbl' bound to 'g' assert g assert g.std except ImportError: - # full lazy lookup available + # full lazy lookup available assert cling.runtime.gCling diff --git a/test/bindexplib.py b/test/bindexplib.py deleted file mode 100755 index 838dca4..0000000 --- a/test/bindexplib.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import print_function - -import os, sys, subprocess - -target = sys.argv[1] -output = sys.argv[2] - -def isokay(name): - # filter standard symbols - return name[0] != '_' and not name in {'memcpy', 'memmove', 'memset'} - -popen = subprocess.Popen(['dumpbin', '/SYMBOLS', target+'.obj'], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - -stdout, _ = popen.communicate() -stdout = stdout.decode('utf-8').strip() - -outf = open(output+'.def', 'w') -outf.write('LIBRARY %s.dll\nEXPORTS\n' % output) -for line in stdout.split('\r\n'): - parts = line.split() - if len(parts) < 8: - continue - if parts[7][0:4] in ['??_G', '??_E']: # do not export deleting destructors - continue - if parts[4] == 'External': - if isokay(parts[6]): - outf.write('\t%s\tDATA\n' % parts[6]) - elif parts[4] == '()' and parts[5] == 'External': - if isokay(parts[7]): - outf.write('\t%s\n' % parts[7]) - diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index d37cad4..0000000 --- a/test/conftest.py +++ /dev/null @@ -1,11 +0,0 @@ -"""alias cppyy to cppjit.cppyy so upstream tests work unmodified.""" -import sys - -import cppjit -import cppjit.cppyy -import cppjit.cppyy_backend - -sys.modules['cppyy'] = cppjit.cppyy -sys.modules['cppyy_backend'] = cppjit.cppyy_backend - -disabled = None diff --git a/test/conversions.cxx b/test/conversions.cxx deleted file mode 100644 index 2340b42..0000000 --- a/test/conversions.cxx +++ /dev/null @@ -1,51 +0,0 @@ -#include "conversions.h" - -#include - - -//=========================================================================== -double CNS::sumit(const std::vector& v) { - return std::accumulate(v.begin(), v.end(), 0.); -} - -double CNS::sumit(const std::vector& v1, const std::vector& v2) { - return std::accumulate(v1.begin(), v1.end(), std::accumulate(v2.begin(), v2.end(), 0.)); -} - -int CNS::Counter::s_count; - -CNS::Counter::Counter() { - ++s_count; -} - -CNS::Counter::Counter(const Counter&) { - ++s_count; -} - -CNS::Counter& CNS::Counter::operator=(const Counter&) { - return *this; -} - -CNS::Counter::~Counter() { - --s_count; -} - -double CNS::myhowmany(const std::vector& v) { - return v.size(); -} - -double CNS::myhowmany(const std::vector& v1, const std::vector& v2) { - return v1.size() + v2.size(); -} - -int CNS::sumints(const std::vector& v) { - return std::accumulate(v.begin(), v.end(), 0); -} - -int CNS::sumints(const std::vector& v1, const std::vector& v2) { - return std::accumulate(v1.begin(), v1.end(), std::accumulate(v2.begin(), v2.end(), 0)); -} - -double notallowed(std::vector& v) { - return std::accumulate(v.begin(), v.end(), 0.); -} diff --git a/test/conversions.xml b/test/conversions.xml deleted file mode 100644 index e42409b..0000000 --- a/test/conversions.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/test/advancedcpp.cxx b/test/cpp/advancedcpp.cxx similarity index 52% rename from test/advancedcpp.cxx rename to test/cpp/advancedcpp.cxx index 3daa11c..b15eaba 100644 --- a/test/advancedcpp.cxx +++ b/test/cpp/advancedcpp.cxx @@ -2,18 +2,22 @@ #include - // for testing of default arguments -#define IMPLEMENT_DEFAULTERS(type, tname) \ -tname##_defaulter::tname##_defaulter(type a, type b, type c) { \ - m_a = a; m_b = b; m_c = c; \ -} \ -type tname##_defaulter_func(int idx, type a, type b, type c) { \ - if (idx == 0) return a; \ - if (idx == 1) return b; \ - if (idx == 2) return c; \ - return (type)idx; \ -} +#define IMPLEMENT_DEFAULTERS(type, tname) \ + tname##_defaulter::tname##_defaulter(type a, type b, type c) { \ + m_a = a; \ + m_b = b; \ + m_c = c; \ + } \ + type tname##_defaulter_func(int idx, type a, type b, type c) { \ + if (idx == 0) \ + return a; \ + if (idx == 1) \ + return b; \ + if (idx == 2) \ + return c; \ + return (type)idx; \ + } IMPLEMENT_DEFAULTERS(short, short) IMPLEMENT_DEFAULTERS(unsigned short, ushort) IMPLEMENT_DEFAULTERS(int, int) @@ -25,65 +29,60 @@ IMPLEMENT_DEFAULTERS(unsigned long long, ullong) IMPLEMENT_DEFAULTERS(float, float) IMPLEMENT_DEFAULTERS(double, double) -std::string string_defaulter_func(int idx, const std::string& name1, std::string name2) { - if (idx == 0) return name1; - if (idx == 1) return name2; - return "mies"; +std::string string_defaulter_func(int idx, const std::string& name1, + std::string name2) { + if (idx == 0) + return name1; + if (idx == 1) + return name2; + return "mies"; } - // for esoteric inheritance testing a_class* create_c1() { return new c_class_1; } a_class* create_c2() { return new c_class_2; } -int get_a( a_class& a ) { return a.m_a; } -int get_b( b_class& b ) { return b.m_b; } -int get_c( c_class& c ) { return c.m_c; } -int get_d( d_class& d ) { return d.m_d; } - +int get_a(a_class& a) { return a.m_a; } +int get_b(b_class& b) { return b.m_b; } +int get_c(c_class& c) { return c.m_c; } +int get_d(d_class& d) { return d.m_d; } // for namespace testing -int a_ns::g_a = 11; -int a_ns::b_class::s_b = 22; -int a_ns::b_class::c_class::s_c = 33; -int a_ns::d_ns::g_d = 44; -int a_ns::d_ns::e_class::s_e = 55; +int a_ns::g_a = 11; +int a_ns::b_class::s_b = 22; +int a_ns::b_class::c_class::s_c = 33; +int a_ns::d_ns::g_d = 44; +int a_ns::d_ns::e_class::s_e = 55; int a_ns::d_ns::e_class::f_class::s_f = 66; int a_ns::get_g_a() { return g_a; } int a_ns::d_ns::get_g_d() { return g_d; } - // for template testing template class T1; -template class T2 >; +template class T2>; template class T3; -template class T3, T2 > >; +template class T3, T2>>; template class a_ns::T4; -template class a_ns::T4 > >; - +template class a_ns::T4>>; // helpers for checking pass-by-ref -void set_int_through_ref(int& i, int val) { i = val; } -int pass_int_through_const_ref(const int& i) { return i; } -void set_long_through_ref(long& l, long val) { l = val; } -long pass_long_through_const_ref(const long& l) { return l; } -void set_double_through_ref(double& d, double val) { d = val; } +void set_int_through_ref(int& i, int val) { i = val; } +int pass_int_through_const_ref(const int& i) { return i; } +void set_long_through_ref(long& l, long val) { l = val; } +long pass_long_through_const_ref(const long& l) { return l; } +void set_double_through_ref(double& d, double val) { d = val; } double pass_double_through_const_ref(const double& d) { return d; } - // for math conversions testing -bool operator==(const some_comparable& c1, const some_comparable& c2 ) -{ - return &c1 != &c2; // the opposite of a pointer comparison +bool operator==(const some_comparable& c1, const some_comparable& c2) { + return &c1 != &c2; // the opposite of a pointer comparison } -bool operator!=( const some_comparable& c1, const some_comparable& c2 ) -{ - return &c1 == &c2; // the opposite of a pointer comparison +bool operator!=(const some_comparable& c1, const some_comparable& c2) { + return &c1 == &c2; // the opposite of a pointer comparison } - // a couple of globals for access testing double my_global_double = 12.; double my_global_array[500]; @@ -93,41 +92,38 @@ const char my_global_string2[] = "zus jet teun"; const char* my_global_string3[3] = {"aap", "noot", "mies"}; some_int_holder my_global_int_holders[5] = { some_int_holder(13), some_int_holder(42), some_int_holder(88), - some_int_holder(-1), some_int_holder(17) }; + some_int_holder(-1), some_int_holder(17)}; some_abstract_class* g_abstract_ptr = nullptr; - // for life-line and identity testing int some_class_with_data::some_data::s_num_data = 0; - // for testing multiple inheritance multi1::~multi1() {} multi2::~multi2() {} multi::~multi() {} - // for testing calls to overloaded new int new_overloader::s_instances = 0; void* new_overloader::operator new(std::size_t size) { - ++s_instances; - return ::operator new(size); + ++s_instances; + return ::operator new(size); } void* new_overloader::operator new(std::size_t, void* p) throw() { - // no ++s_instances, as no memory is allocated - return p; + // no ++s_instances, as no memory is allocated + return p; } void new_overloader::operator delete(void* p, std::size_t) { - if (p == 0) return; - --s_instances; - ::operator delete(p); + if (p == 0) + return; + --s_instances; + ::operator delete(p); } - // overload order testing int overload_one_way::gime() const { return 1; } std::string overload_one_way::gime() { return "aap"; } @@ -135,52 +131,43 @@ std::string overload_one_way::gime() { return "aap"; } std::string overload_the_other_way::gime() { return "aap"; } int overload_the_other_way::gime() const { return 1; } - // exception handling testing -void Thrower::throw_anything() { - throw 1; -} +void Thrower::throw_anything() { throw 1; } void Thrower::throw_exception() { - throw std::runtime_error("C++ function failed"); + throw std::runtime_error("C++ function failed"); } - // operator to __str__ mapping std::ostream& Cpp2PyPrinting::Printable1::operator<<(std::ostream& os) { - os << "Printable1::operator<<"; - return os; + os << "Printable1::operator<<"; + return os; } std::ostream& Cpp2PyPrinting::operator<<(std::ostream& os, const Printable2&) { - os << "Cpp2PyPrinting::operator<<"; - return os; + os << "Cpp2PyPrinting::operator<<"; + return os; } std::ostream& operator<<(std::ostream& os, const Cpp2PyPrinting::Printable3&) { - os << "::operator<<(3)"; - return os; + os << "::operator<<(3)"; + return os; } std::ostream& operator<<(std::ostream& os, const Printable4&) { - os << "::operator<<(4)"; - return os; + os << "::operator<<(4)"; + return os; } Printable6& Printable6::operator<<(int) { return *this; } -std::ostream& operator<<(std::ostream& os, const Printable6& y) { return os << "Printable6"; } - +std::ostream& operator<<(std::ostream& os, const Printable6& y) { + return os << "Printable6"; +} // for using directives testing -int UsedSpace1::foo1() { - return 13; -} +int UsedSpace1::foo1() { return 13; } -int UsedSpace2::bar() { - return 42; -} +int UsedSpace2::bar() { return 42; } -int UsedSpace1::inner::foo2() { - return 27; -} +int UsedSpace1::inner::foo2() { return 27; } diff --git a/test/cpp/advancedcpp.h b/test/cpp/advancedcpp.h new file mode 100644 index 0000000..fba4afc --- /dev/null +++ b/test/cpp/advancedcpp.h @@ -0,0 +1,522 @@ +#include +#include +#include +#include +#include + +//=========================================================================== +#define DECLARE_DEFAULTERS(type, tname) \ + class tname##_defaulter { \ + public: \ + tname##_defaulter(type a = 11, type b = 22, type c = 33); \ + \ + public: \ + type m_a, m_b, m_c; \ + }; \ + type tname##_defaulter_func(int idx = 0, type a = 11, type b = 22, \ + type c = 33); +DECLARE_DEFAULTERS(short, short) // for testing of default arguments +DECLARE_DEFAULTERS(unsigned short, ushort) +DECLARE_DEFAULTERS(int, int) +DECLARE_DEFAULTERS(unsigned, uint) +DECLARE_DEFAULTERS(long, long) +DECLARE_DEFAULTERS(unsigned long, ulong) +DECLARE_DEFAULTERS(long long, llong) +DECLARE_DEFAULTERS(unsigned long long, ullong) +DECLARE_DEFAULTERS(float, float) +DECLARE_DEFAULTERS(double, double) + +std::string string_defaulter_func(int idx, const std::string& name1 = "aap", + std::string name2 = "noot"); + +//=========================================================================== +class base_class { // for simple inheritance testing +public: + base_class() { + m_b = 1; + m_db = 1.1; + } + virtual ~base_class() {} + virtual int get_value() { return m_b; } + double get_base_value() { return m_db; } + + virtual base_class* cycle(base_class* b) { return b; } + virtual base_class* clone() { return new base_class; } + + virtual void* mask(void* p) { return p; } + +public: + int m_b; + double m_db; +}; + +class derived_class : public base_class { +public: + derived_class() { + m_d = 2; + m_dd = 2.2; + } + virtual int get_value() { return m_d; } + double get_derived_value() { return m_dd; } + virtual base_class* clone() { return new derived_class; } + +public: + int m_d; + double m_dd; +}; + +//=========================================================================== +class a_class { // for esoteric inheritance testing +public: + a_class() { + m_a = 1; + m_da = 1.1; + } + virtual ~a_class() {} + virtual int get_value() = 0; + +public: + int m_a; + double m_da; +}; + +class b_class : public virtual a_class { +public: + b_class() { + m_b = 2; + m_db = 2.2; + } + virtual int get_value() { return m_b; } + +public: + int m_b; + double m_db; +}; + +class c_class_1 : public virtual a_class, public virtual b_class { +public: + c_class_1() { m_c = 3; } + virtual int get_value() { return m_c; } + +public: + int m_c; +}; + +class c_class_2 : public virtual b_class, public virtual a_class { +public: + c_class_2() { m_c = 3; } + virtual int get_value() { return m_c; } + +public: + int m_c; +}; + +typedef c_class_2 c_class; + +class d_class : public virtual c_class, public virtual a_class { +public: + d_class() { m_d = 4; } + virtual int get_value() { return m_d; } + +public: + int m_d; +}; + +a_class* create_c1(); +a_class* create_c2(); + +int get_a(a_class& a); +int get_b(b_class& b); +int get_c(c_class& c); +int get_d(d_class& d); + +//=========================================================================== +namespace a_ns { // for namespace testing +extern int g_a; +int get_g_a(); + +struct b_class { + b_class() { m_b = -2; } + int m_b; + static int s_b; + + struct c_class { + c_class() { m_c = -3; } + int m_c; + static int s_c; + }; +}; + +namespace d_ns { +extern int g_d; +int get_g_d(); + +struct e_class { + e_class() { m_e = -5; } + int m_e; + static int s_e; + + struct f_class { + f_class() { m_f = -6; } + int m_f; + static int s_f; + }; +}; + +} // namespace d_ns + +} // namespace a_ns + +//=========================================================================== +template // for template testing +class T1 { +public: + T1(T t = T(1)) : m_t1(t) {} + T get_value() { return m_t1; } + +public: + T m_t1; +}; + +template class T2 { +public: + T2(T t = T(2)) : m_t2(t) {} + T get_value() { return m_t2; } + +public: + T m_t2; +}; + +template class T3 { +public: + T3(T t = T(3), U u = U(33)) : m_t3(t), m_u3(u) {} + T get_value_t() { return m_t3; } + U get_value_u() { return m_u3; } + +public: + T m_t3; + U m_u3; +}; + +namespace a_ns { + +template class T4 { +public: + T4(T t = T(4)) : m_t4(t) {} + T get_value() { return m_t4; } + +public: + T m_t4; +}; + +} // namespace a_ns + +extern template class T1; +extern template class T2>; +extern template class T3; +extern template class T3, T2>>; +extern template class a_ns::T4; +extern template class a_ns::T4>>; + +//=========================================================================== +// for checking pass-by-reference of builtin types +void set_int_through_ref(int& i, int val); +int pass_int_through_const_ref(const int& i); +void set_long_through_ref(long& l, long val); +long pass_long_through_const_ref(const long& l); +void set_double_through_ref(double& d, double val); +double pass_double_through_const_ref(const double& d); + +//=========================================================================== +class some_abstract_class { // to test abstract class handling +public: + virtual ~some_abstract_class() {} + virtual void a_virtual_method() = 0; +}; + +class some_concrete_class : public some_abstract_class { +public: + virtual void a_virtual_method() {} +}; + +//=========================================================================== +class ref_tester { // for assignment by-ref testing +public: + ref_tester() : m_i(-99) {} + ref_tester(int i) : m_i(i) {} + ref_tester(const ref_tester& s) : m_i(s.m_i) {} + ref_tester& operator=(const ref_tester& s) { + if (&s != this) + m_i = s.m_i; + return *this; + } + ~ref_tester() {} + +public: + int m_i; +}; + +//=========================================================================== +class some_convertible { // for math conversions testing +public: + some_convertible() : m_i(-99), m_d(-99.) {} + + operator int() { return m_i; } + operator long() { return m_i; } + operator double() { return m_d; } + +public: + int m_i; + double m_d; +}; + +class some_comparable {}; + +bool operator==(const some_comparable& c1, const some_comparable& c2); +bool operator!=(const some_comparable& c1, const some_comparable& c2); + +//=========================================================================== +extern double my_global_double; // a couple of globals for access testing +extern double my_global_array[500]; +extern double* my_global_ptr; +static const char my_global_string1[] = "aap " + " noot " + " mies"; +extern const char my_global_string2[]; +extern const char* my_global_string3[3]; + +class some_int_holder { +public: + some_int_holder(int val) : m_val(val) {} + +public: + int m_val; + char gap[7]; +}; +extern some_int_holder my_global_int_holders[5]; + +extern some_abstract_class* g_abstract_ptr; + +//=========================================================================== +class some_class_with_data { // for life-line and identity testing +public: + class some_data { + public: + some_data() { ++s_num_data; } + some_data(const some_data&) { ++s_num_data; } + ~some_data() { --s_num_data; } + + static int s_num_data; + }; + + some_class_with_data gime_copy() { return *this; } + + const some_data& gime_data() { /* TODO: methptrgetter const support */ + return m_data; + } + + int m_padding; + some_data m_data; +}; + +class refers_to_self { // for data member reuse testing +public: + refers_to_self* m_other = nullptr; +}; + +//=========================================================================== +class pointer_pass { // for testing passing of void*'s +public: + intptr_t gime_address_ptr(void* obj) { return (intptr_t)obj; } + + intptr_t gime_address_ptr_ptr(void** obj) { + return (intptr_t) * ((long**)obj); + } + + intptr_t gime_address_ptr_ref(const void*& obj) { return (intptr_t)obj; } + + static intptr_t set_address_ptr_ptr(void** obj) { + (*(intptr_t**)obj) = (intptr_t*)0x4321; + return 42; + } + + static intptr_t set_address_ptr_ref(const void*& obj) { + obj = (void*)0x1234; + return 21; + } +}; + +//=========================================================================== +class multi1 { // for testing multiple inheritance +public: + multi1(int val) : m_int(val) {} + virtual ~multi1(); + int get_multi1_int() { return m_int; } + +private: + int m_int; +}; + +class multi2 { +public: + multi2(int val) : m_int(val) {} + virtual ~multi2(); + int get_multi2_int() { return m_int; } + +private: + int m_int; +}; + +class multi : public multi1, public multi2 { +public: + multi(int val1, int val2, int val3) + : multi1(val1), multi2(val2), m_int(val3) {} + virtual ~multi(); + int get_my_own_int() { return m_int; } + +private: + int m_int; +}; + +//=========================================================================== +class new_overloader { // for testing calls to overloaded new +public: + static int s_instances; + +public: + void* operator new(std::size_t size); + void* operator new(std::size_t, void* p) throw(); + void operator delete(void* p, std::size_t size); +}; + +//=========================================================================== +template // more template testing +class my_templated_class { +public: + T m_b; +}; + +template T my_templated_function(T t) { return t; } + +template class my_templated_class>; +template char my_templated_function(char); +template double my_templated_function(double); + +//=========================================================================== +class overload_one_way { // overload order testing +public: + int gime() const; + std::string gime(); +}; + +class overload_the_other_way { +public: + std::string gime(); + int gime() const; +}; + +//=========================================================================== +class Thrower { // exception handling testing +public: + void throw_anything(); + void throw_exception(); +}; + +//=========================================================================== +class UsingBase1 { // using declaration testing +public: + UsingBase1(int n = 13) : m_int(n) {} + virtual ~UsingBase1() {} + virtual char vcheck() { return 'A'; } + int m_int; +}; + +class UsingDerived1 : public UsingBase1 { +public: + using UsingBase1::UsingBase1; + virtual char vcheck() { return 'B'; } + int m_int2 = 42; +}; + +class UsingBase2 { +public: + virtual ~UsingBase2() {} + virtual char vcheck() { return 'A'; } +}; + +class UsingDerived2 : public UsingBase2 { +public: + using UsingBase2::vcheck; + virtual char vcheck(int) { return 'B'; } +}; + +//=========================================================================== +class TypedefToPrivateClass { // typedef resolution testing +private: + class PC { + public: + PC(int i) : m_val(i) {} + int m_val; + }; + +public: + typedef PC PP; + PP f() { return PC(42); } +}; + +//=========================================================================== +namespace Cpp2PyPrinting { // operator to __str__ mapping + +class Printable1 { +public: + std::ostream& operator<<(std::ostream& os); +}; + +class Printable2 { /* empty */ +}; +class Printable3 { /* empty */ +}; + +std::ostream& operator<<(std::ostream& os, const Printable2&); + +} // namespace Cpp2PyPrinting + +class Printable4 { /* empty */ +}; + +std::ostream& operator<<(std::ostream& os, const Cpp2PyPrinting::Printable3&); +std::ostream& operator<<(std::ostream& os, const Printable4&); + +template class PrintableBase1 {}; +class Printable5 : public PrintableBase1 {}; + +template +std::ostream& operator<<(std::ostream& os, const PrintableBase1& y) { + return os << "Ok."; +} + +class Printable6 { +public: + Printable6& operator<<(int); +}; + +std::ostream& operator<<(std::ostream& os, const Printable6& y); + +//=========================================================================== +namespace UsedSpace1 { // for using directives testing +int foo1(); + +namespace inner { +int foo2(); +} +} // namespace UsedSpace1 + +namespace UsedSpace2 { +int bar(); +} + +namespace UserDirs { +using namespace UsedSpace1; +using namespace UsedSpace2; +using namespace UsedSpace1::inner; +} // namespace UserDirs diff --git a/test/cpp/advancedcpp2.cxx b/test/cpp/advancedcpp2.cxx new file mode 100644 index 0000000..b096505 --- /dev/null +++ b/test/cpp/advancedcpp2.cxx @@ -0,0 +1,12 @@ +#include "advancedcpp2.h" + +// for namespace testing +int a_ns::g_g = 77; +int a_ns::g_class::s_g = 88; +int a_ns::g_class::h_class::s_h = 99; +int a_ns::d_ns::g_i = 111; +int a_ns::d_ns::i_class::s_i = 222; +int a_ns::d_ns::i_class::j_class::s_j = 333; + +int a_ns::get_g_g() { return g_g; } +int a_ns::d_ns::get_g_i() { return g_i; } diff --git a/test/cpp/advancedcpp2.h b/test/cpp/advancedcpp2.h new file mode 100644 index 0000000..eec7259 --- /dev/null +++ b/test/cpp/advancedcpp2.h @@ -0,0 +1,36 @@ +//=========================================================================== +namespace a_ns { // for namespace testing +extern int g_g; +int get_g_g(); + +struct g_class { + g_class() { m_g = -7; } + int m_g; + static int s_g; + + struct h_class { + h_class() { m_h = -8; } + int m_h; + static int s_h; + }; +}; + +namespace d_ns { +extern int g_i; +int get_g_i(); + +struct i_class { + i_class() { m_i = -9; } + int m_i; + static int s_i; + + struct j_class { + j_class() { m_j = -10; } + int m_j; + static int s_j; + }; +}; + +} // namespace d_ns + +} // namespace a_ns diff --git a/test/cpp/conversions.cxx b/test/cpp/conversions.cxx new file mode 100644 index 0000000..c092fa5 --- /dev/null +++ b/test/cpp/conversions.cxx @@ -0,0 +1,44 @@ +#include "conversions.h" + +#include + +//=========================================================================== +double CNS::sumit(const std::vector& v) { + return std::accumulate(v.begin(), v.end(), 0.); +} + +double CNS::sumit(const std::vector& v1, + const std::vector& v2) { + return std::accumulate(v1.begin(), v1.end(), + std::accumulate(v2.begin(), v2.end(), 0.)); +} + +int CNS::Counter::s_count; + +CNS::Counter::Counter() { ++s_count; } + +CNS::Counter::Counter(const Counter&) { ++s_count; } + +CNS::Counter& CNS::Counter::operator=(const Counter&) { return *this; } + +CNS::Counter::~Counter() { --s_count; } + +double CNS::myhowmany(const std::vector& v) { return v.size(); } + +double CNS::myhowmany(const std::vector& v1, + const std::vector& v2) { + return v1.size() + v2.size(); +} + +int CNS::sumints(const std::vector& v) { + return std::accumulate(v.begin(), v.end(), 0); +} + +int CNS::sumints(const std::vector& v1, const std::vector& v2) { + return std::accumulate(v1.begin(), v1.end(), + std::accumulate(v2.begin(), v2.end(), 0)); +} + +double notallowed(std::vector& v) { + return std::accumulate(v.begin(), v.end(), 0.); +} diff --git a/test/conversions.h b/test/cpp/conversions.h similarity index 81% rename from test/conversions.h rename to test/cpp/conversions.h index 9f1401b..c0f8f79 100644 --- a/test/conversions.h +++ b/test/cpp/conversions.h @@ -1,6 +1,5 @@ #include - namespace CNS { //=========================================================================== @@ -9,12 +8,12 @@ double sumit(const std::vector&, const std::vector&); class Counter { public: - Counter(); - Counter(const Counter&); - Counter& operator=(const Counter&); - ~Counter(); + Counter(); + Counter(const Counter&); + Counter& operator=(const Counter&); + ~Counter(); - static int s_count; + static int s_count; }; double myhowmany(const std::vector&); diff --git a/test/cpp/cpp11features.cxx b/test/cpp/cpp11features.cxx new file mode 100644 index 0000000..72a2153 --- /dev/null +++ b/test/cpp/cpp11features.cxx @@ -0,0 +1,61 @@ +#include "cpp11features.h" + +// for std::shared/unique_ptr<> testing +int TestSmartPtr::s_counter = 0; + +std::shared_ptr create_shared_ptr_instance() { + return std::shared_ptr(new TestSmartPtr); +} + +std::unique_ptr create_unique_ptr_instance() { + return std::unique_ptr(new TestSmartPtr); +} + +int TestSmartPtr::get_value() { return 17; } + +int DerivedTestSmartPtr::get_value() { return m_int + 76; } + +int pass_shared_ptr(std::shared_ptr p) { return p->get_value(); } + +int move_shared_ptr(std::shared_ptr&& p) { + return p->get_value(); +} + +int move_unique_ptr(std::unique_ptr&& p) { + return p->get_value(); +} + +int move_unique_ptr_derived(std::unique_ptr&& p) { + return p->get_value(); +} + +TestSmartPtr create_TestSmartPtr_by_value() { return TestSmartPtr{}; } + +std::shared_ptr create_shared_ptr_to_derived() { + return std::shared_ptr(new PubDerivedTestSmartPtr); +} + +std::unique_ptr create_unique_ptr_to_derived() { + return std::unique_ptr(new PubDerivedTestSmartPtr); +} + +std::unique_ptr create_unique_ptr_to_offset_derived() { + return std::unique_ptr(new MultiDerivedTestSmartPtr); +} + +// for move ctors etc. +int TestMoving1::s_move_counter = 0; +int TestMoving1::s_instance_counter = 0; +int TestMoving2::s_move_counter = 0; +int TestMoving2::s_instance_counter = 0; + +void implicit_converion_move(TestMoving2&&) { /* empty */ } + +// for std::function testing +std::function FNCreateTestStructFunc() { + return [](const FNTestStruct& t) { return t.t; }; +} +std::function +FunctionNS::FNCreateTestStructFunc() { + return [](const FNTestStruct& t) { return t.t; }; +} diff --git a/test/cpp/cpp11features.h b/test/cpp/cpp11features.h new file mode 100644 index 0000000..5102651 --- /dev/null +++ b/test/cpp/cpp11features.h @@ -0,0 +1,182 @@ +#ifndef CPP11FEATURES_H +#define CPP11FEATURES_H + +#include +#include +#include +#include + +//=========================================================================== +class TestSmartPtr { // for std::shared/unique_ptr<> testing +public: + static int s_counter; + +public: + TestSmartPtr() { ++s_counter; } + TestSmartPtr(const TestSmartPtr&) { ++s_counter; } + virtual ~TestSmartPtr() { --s_counter; } + +public: + virtual int get_value(); +}; + +std::shared_ptr create_shared_ptr_instance(); +std::unique_ptr create_unique_ptr_instance(); + +class DerivedTestSmartPtr : TestSmartPtr { +public: + DerivedTestSmartPtr(int i) : m_int(i) {} + virtual int get_value(); + +public: + int m_int; +}; + +int pass_shared_ptr(std::shared_ptr p); +int move_shared_ptr(std::shared_ptr&& p); +int move_unique_ptr(std::unique_ptr&& p); +int move_unique_ptr_derived(std::unique_ptr&& p); + +TestSmartPtr create_TestSmartPtr_by_value(); + +// for auto-downcast of objects returned through a smart pointer +class PubDerivedTestSmartPtr : public TestSmartPtr { +public: + int only_in_derived() { return 27; } +}; + +// second base so that the cross-cast to the most derived type needs a +// non-zero pointer adjustment, which the smart pointer's dereferencer can +// not apply consistently (so no down-cast should happen in that case) +class TestSmartPtrIface { +public: + virtual ~TestSmartPtrIface() {} + long m_pad = 0; + int only_in_iface() { return 37; } +}; + +class MultiDerivedTestSmartPtr : public PubDerivedTestSmartPtr, + public TestSmartPtrIface {}; + +std::shared_ptr create_shared_ptr_to_derived(); +std::unique_ptr create_unique_ptr_to_derived(); +std::unique_ptr create_unique_ptr_to_offset_derived(); + +// sinks expecting a smart pointer to the *derived* type; a base-class smart +// pointer (even when its object was auto-down-cast) must not be accepted here +int pass_unique_ptr_to_derived(std::unique_ptr p) { + return p->only_in_derived(); +} + +int pass_shared_ptr_to_derived(std::shared_ptr p) { + return p->only_in_derived(); +} + +// Overloaded function to check if automatic downcasting is consistently +// applied for regular proxy objects and smart pointer proxies. +std::string pass_ptr_overloaded(TestSmartPtr*) { return "TestSmartPtr"; } +std::string pass_ptr_overloaded(PubDerivedTestSmartPtr*) { + return "PubDerivedTestSmartPtr"; +} +std::string pass_ref_overloaded(TestSmartPtr&) { return "TestSmartPtr"; } +std::string pass_ref_overloaded(PubDerivedTestSmartPtr&) { + return "PubDerivedTestSmartPtr"; +} +std::string pass_val_overloaded(TestSmartPtr) { return "TestSmartPtr"; } +std::string pass_val_overloaded(PubDerivedTestSmartPtr) { + return "PubDerivedTestSmartPtr"; +} + +//=========================================================================== +class TestMoving1 { // for move ctors etc. +public: + static int s_move_counter; + static int s_instance_counter; + +public: + TestMoving1() { ++s_instance_counter; } + TestMoving1(TestMoving1&&) { + ++s_move_counter; + ++s_instance_counter; + } + TestMoving1(const TestMoving1&) { ++s_instance_counter; } + TestMoving1& operator=(TestMoving1&&) { + ++s_move_counter; + return *this; + } + TestMoving1& operator=(TestMoving1&) { return *this; } + ~TestMoving1() { --s_instance_counter; } +}; + +class TestMoving2 { // note opposite method order from TestMoving1 +public: + static int s_move_counter; + static int s_instance_counter; + +public: + TestMoving2() { ++s_instance_counter; } + TestMoving2(const TestMoving1&) { ++s_instance_counter; } + TestMoving2(const TestMoving2&) { ++s_instance_counter; } + TestMoving2(TestMoving2&& other) { + ++s_move_counter; + ++s_instance_counter; + } + TestMoving2& operator=(TestMoving2&) { return *this; } + TestMoving2& operator=(TestMoving2&&) { + ++s_move_counter; + return *this; + } + ~TestMoving2() { --s_instance_counter; } +}; + +void implicit_converion_move(TestMoving2&&); + +//=========================================================================== +struct TestData { // for initializer list construction + TestData(int i = 0) : m_int(i) {} + int m_int; +}; + +struct TestData2 { + TestData2(int i = 0) : m_int(i) {} + virtual ~TestData2() {} + int m_int; +}; + +template class WithInitList { +public: + WithInitList(std::initializer_list ll) : m_data(ll) {} + const T& operator[](int i) { return m_data[i]; } + + int size() { return m_data.size(); } + +private: + std::vector m_data; +}; + +//=========================================================================== +struct FNTestStruct { // for std::function<> testing + FNTestStruct(int i) : t(i) {} + int t; +}; +std::function FNCreateTestStructFunc(); + +namespace FunctionNS { +struct FNTestStruct { + FNTestStruct(int i) : t(i) {} + int t; +}; +std::function FNCreateTestStructFunc(); +} // namespace FunctionNS + +//=========================================================================== +struct StructWithHash {}; // for std::hash<> testing +struct StructWithoutHash {}; + +namespace std { +template <> struct hash { + size_t operator()(const StructWithHash&) const { return 17; } +}; +} // namespace std + +#endif // CPP11FEATURES_H diff --git a/test/cpp/crossinheritance.cxx b/test/cpp/crossinheritance.cxx new file mode 100644 index 0000000..88f8e9e --- /dev/null +++ b/test/cpp/crossinheritance.cxx @@ -0,0 +1,100 @@ +#include "crossinheritance.h" + +// for overridden method checking +CrossInheritance::Base1::~Base1() {} + +int CrossInheritance::Base1::call_get_value(Base1* b) { return b->get_value(); } + +int CrossInheritance::Base1::call_sum_value(Base1* b, int i) { + return b->sum_value(i); +} + +int CrossInheritance::Base1::call_sum_all(Base1* b, int i) { + return b->sum_all(i); +} + +int CrossInheritance::Base1::call_sum_all(Base1* b, int i, int j) { + return b->sum_all(i, j); +} + +int CrossInheritance::Base1::sum_pass_value(Base1* b) { + int a = 0; + a += b->pass_value1(1); + int i = 2; + a += b->pass_value2(i); + a += b->pass_value3(3); + a += b->pass_value4(*b); + a += b->pass_value5(*b); + return a; +} + +int CrossInheritance::IBase2::call_get_value(IBase2* b) { + return b->get_value(); +} + +CrossInheritance::IBase3::IBase3(int i) { m_int = i; } + +int CrossInheritance::CBase2::get_value() { return 42; } + +int CrossInheritance::IBase4::call_get_value(IBase4* b) { + return b->get_value(); +} + +int CrossInheritance::CBase4::get_value() const { return 27; } + +int CrossInheritance::TDerived1::get_value() { return 27; } + +int CrossInheritance::CountableBase::s_count = 0; + +CrossInheritance::CountableBase::CountableBase() { ++s_count; } + +CrossInheritance::CountableBase::CountableBase(const CountableBase&) { + ++s_count; +} + +CrossInheritance::CountableBase& +CrossInheritance::CountableBase::operator=(const CountableBase&) { + return *this; +} + +CrossInheritance::CountableBase::~CountableBase() { --s_count; } + +int CrossInheritance::CountableBase::call() { return -1; } + +CrossInheritance::Component::Component() { ++s_count; } + +CrossInheritance::Component::~Component() { --s_count; } + +int CrossInheritance::Component::get_count() { return s_count; } + +int CrossInheritance::Component::s_count = 0; + +namespace { + +class ComponentWithValue : public CrossInheritance::Component { +public: + ComponentWithValue(int value) : m_value(value) {} + int getValue() { return m_value; } + +protected: + int m_value; +}; + +} // unnamed namespace + +CrossInheritance::Component* CrossInheritance::build_component(int value) { + return new ComponentWithValue(value); +} + +CrossInheritance::Component* CrossInheritance::cycle_component(Component* c) { + return c; +} + +// for protected member testing +AccessProtected::MyBase::MyBase() : my_data(101) { /* empty */ } + +AccessProtected::MyBase::~MyBase() { /* empty */ } + +int AccessProtected::MyBase::get_data_v() { return my_data; } + +int AccessProtected::MyBase::get_data() { return my_data; } diff --git a/test/cpp/crossinheritance.h b/test/cpp/crossinheritance.h new file mode 100644 index 0000000..e7ed4f6 --- /dev/null +++ b/test/cpp/crossinheritance.h @@ -0,0 +1,130 @@ +#ifndef CPPJIT_TEST_CROSSINHERITANCE_H +#define CPPJIT_TEST_CROSSINHERITANCE_H + +#include + +//=========================================================================== +namespace CrossInheritance { + +class Base1 { // for overridden method checking +public: + Base1() : m_int(42) {} + Base1(int i) : m_int(i) {} + virtual ~Base1(); + + virtual int get_value() { return m_int; } + static int call_get_value(Base1* b); + + virtual int sum_value(int i) { return m_int + i; } + static int call_sum_value(Base1* b, int); + + virtual int sum_all(int i) { return m_int + i; } + virtual int sum_all(int i, int j) { return m_int + i + j; } + static int call_sum_all(Base1* b, int); + static int call_sum_all(Base1* b, int, int); + + virtual int pass_value1(int a) { return a; } + virtual int pass_value2(int& a) { return a; } + virtual int pass_value3(const int& a) { return a; } + virtual int pass_value4(const Base1& b) { return b.m_int; } + virtual int pass_value5(Base1& b) { return b.m_int; } + static int sum_pass_value(Base1* b); + +public: + int m_int; +}; + +class IBase2 { +public: + IBase2() {} + virtual ~IBase2() {} + virtual int get_value() = 0; + static int call_get_value(IBase2* b); +}; + +class IBase3 : IBase2 { +public: + IBase3(int); + int m_int; +}; + +class CBase2 : public IBase2 { +public: + int get_value(); +}; + +class IBase4 { +public: + IBase4() {} + virtual ~IBase4() {} + virtual int get_value() const = 0; // <- const, as opposed to IBase2 + static int call_get_value(IBase4* b); +}; + +class CBase4 : public IBase4 { +public: + int get_value() const; +}; + +template class TBase1 { +public: + virtual ~TBase1() {} + virtual int get_value() { return 42; } +}; + +class TDerived1 : public TBase1 { +public: + int get_value(); +}; + +using TBase1_I = TBase1; + +class CountableBase { +public: + CountableBase(); + CountableBase(const CountableBase&); + CountableBase& operator=(const CountableBase&); + virtual ~CountableBase(); + + virtual int call(); + + static int s_count; +}; + +class Component { +public: + Component(); + Component(const Component&) = delete; + Component& operator=(const Component&) = delete; + virtual ~Component(); + + static int get_count(); + +private: + static int s_count; +}; + +Component* build_component(int value); +Component* cycle_component(Component* c); + +} // namespace CrossInheritance + +//=========================================================================== +namespace AccessProtected { // for protected member testing + +class MyBase { +public: + MyBase(); + virtual ~MyBase(); + +protected: + virtual int get_data_v(); + int get_data(); + +protected: + int my_data; +}; + +} // namespace AccessProtected + +#endif // !CPPJIT_TEST_CROSSINHERITANCE_H diff --git a/test/cpp/datatypes.cxx b/test/cpp/datatypes.cxx new file mode 100644 index 0000000..babc876 --- /dev/null +++ b/test/cpp/datatypes.cxx @@ -0,0 +1,1042 @@ +#include "datatypes.h" + +//=========================================================================== +std::vector vecFruits{kCitrus, kApple}; + +//=========================================================================== +CppjitTestData::CppjitTestData() : m_const_int(17), m_owns_arrays(false) { + m_bool = false; + m_char = 'a'; + m_schar = 'b'; + m_uchar = 'c'; + m_wchar = L'D'; + m_char16 = u'\u00df'; + m_char32 = U'\u00df'; + m_byte = (std::byte)'d'; + m_int8 = -9; + m_uint8 = 9; + m_short = -11; + m_ushort = 11u; + m_int = -22; + m_uint = 22u; + m_long = -33l; + m_ulong = 33ul; + m_llong = -44ll; + m_ullong = 44ull; + m_long64 = -55ll; + m_ulong64 = 55ull; + m_float = -66.f; + m_double = -77.; + m_ldouble = -88.l; + m_complex = {99., 101.}; + m_icomplex = {121, 141}; + m_ccomplex = {151., 161.}; + m_enum = kNothing; + m_voidp = (void*)0; + + m_bool_array2 = new bool[N]; + m_schar_array2 = new signed char[N]; + m_uchar_array2 = new unsigned char[N]; + m_byte_array2 = new std::byte[N]; + m_int8_array2 = new int8_t[N]; + m_uint8_array2 = new uint8_t[N]; + m_short_array2 = new short[N]; + m_ushort_array2 = new unsigned short[N]; + m_int_array2 = new int[N]; + m_uint_array2 = new unsigned int[N]; + m_long_array2 = new long[N]; + m_ulong_array2 = new unsigned long[N]; + + m_float_array2 = new float[N]; + m_double_array2 = new double[N]; + m_complex_array2 = new complex_t[N]; + m_ccomplex_array2 = new ccomplex_t[N]; + + for (int i = 0; i < N; ++i) { + m_bool_array[i] = bool(i % 2); + m_bool_array2[i] = bool((i + 1) % 2); + m_schar_array[i] = 1 * i; + m_schar_array2[i] = 2 * i; + m_uchar_array[i] = 1u * i; + m_uchar_array2[i] = 2u * i; + m_byte_array[i] = (std::byte)(3u * i); + m_byte_array2[i] = (std::byte)(4u * i); + m_int8_array[i] = -1 * i; + m_int8_array2[i] = -2 * i; + m_uint8_array[i] = 3u * i; + m_uint8_array2[i] = 4u * i; + m_short_array[i] = -5 * i; + m_short_array2[i] = -6 * i; + m_ushort_array[i] = 7u * i; + m_ushort_array2[i] = 8u * i; + m_int_array[i] = -9 * i; + m_int_array2[i] = -10 * i; + m_uint_array[i] = 11u * i; + m_uint_array2[i] = 23u * i; + m_long_array[i] = -13l * i; + m_long_array2[i] = -14l * i; + m_ulong_array[i] = 15ul * i; + m_ulong_array2[i] = 18ul * i; + + m_float_array[i] = -13.f * i; + m_float_array2[i] = -14.f * i; + m_double_array[i] = -15. * i; + m_double_array2[i] = -16. * i; + m_complex_array2[i] = {17. * i, 18. * i}; + m_ccomplex_array2[i] = {19. * i, 20. * i}; + } + + m_owns_arrays = true; + + m_pod.m_int = 888; + m_pod.m_double = 3.14; + + m_ppod = &m_pod; +}; + +CppjitTestData::~CppjitTestData() { destroy_arrays(); } + +void CppjitTestData::destroy_arrays() { + if (m_owns_arrays == true) { + delete[] m_bool_array2; + delete[] m_schar_array2; + delete[] m_uchar_array2; + delete[] m_byte_array2; + delete[] m_int8_array2; + delete[] m_uint8_array2; + delete[] m_short_array2; + delete[] m_ushort_array2; + delete[] m_int_array2; + delete[] m_uint_array2; + delete[] m_long_array2; + delete[] m_ulong_array2; + + delete[] m_float_array2; + delete[] m_double_array2; + delete[] m_complex_array2; + delete[] m_ccomplex_array2; + + m_owns_arrays = false; + } +} + +//- getters ----------------------------------------------------------------- +bool CppjitTestData::get_bool() { return m_bool; } +char CppjitTestData::get_char() { return m_char; } +signed char CppjitTestData::get_schar() { return m_schar; } +unsigned char CppjitTestData::get_uchar() { return m_uchar; } +wchar_t CppjitTestData::get_wchar() { return m_wchar; } +char16_t CppjitTestData::get_char16() { return m_char16; } +char32_t CppjitTestData::get_char32() { return m_char32; } +std::byte CppjitTestData::get_byte() { return m_byte; } +int8_t CppjitTestData::get_int8() { return m_int8; } +uint8_t CppjitTestData::get_uint8() { return m_uint8; } +short CppjitTestData::get_short() { return m_short; } +unsigned short CppjitTestData::get_ushort() { return m_ushort; } +int CppjitTestData::get_int() { return m_int; } +unsigned int CppjitTestData::get_uint() { return m_uint; } +long CppjitTestData::get_long() { return m_long; } +unsigned long CppjitTestData::get_ulong() { return m_ulong; } +long long CppjitTestData::get_llong() { return m_llong; } +unsigned long long CppjitTestData::get_ullong() { return m_ullong; } +Long64_t CppjitTestData::get_long64() { return m_long64; } +ULong64_t CppjitTestData::get_ulong64() { return m_ulong64; } +float CppjitTestData::get_float() { return m_float; } +double CppjitTestData::get_double() { return m_double; } +long double CppjitTestData::get_ldouble() { return m_ldouble; } +long double CppjitTestData::get_ldouble_def(long double ld) { return ld; } +complex_t CppjitTestData::get_complex() { return m_complex; } +icomplex_t CppjitTestData::get_icomplex() { return m_icomplex; } +ccomplex_t CppjitTestData::get_ccomplex() { return m_ccomplex; } +CppjitTestData::EWhat CppjitTestData::get_enum() { return m_enum; } +void* CppjitTestData::get_voidp() { return m_voidp; } + +bool* CppjitTestData::get_bool_array() { return m_bool_array; } +bool* CppjitTestData::get_bool_array2() { return m_bool_array2; } +signed char* CppjitTestData::get_schar_array() { return m_schar_array; } +signed char* CppjitTestData::get_schar_array2() { return m_schar_array2; } +unsigned char* CppjitTestData::get_uchar_array() { return m_uchar_array; } +unsigned char* CppjitTestData::get_uchar_array2() { return m_uchar_array2; } +std::byte* CppjitTestData::get_byte_array() { return m_byte_array; } +std::byte* CppjitTestData::get_byte_array2() { return m_byte_array2; } +int8_t* CppjitTestData::get_int8_array() { return m_int8_array; } +int8_t* CppjitTestData::get_int8_array2() { return m_int8_array2; } +uint8_t* CppjitTestData::get_uint8_array() { return m_uint8_array; } +uint8_t* CppjitTestData::get_uint8_array2() { return m_uint8_array2; } +short* CppjitTestData::get_short_array() { return m_short_array; } +short* CppjitTestData::get_short_array2() { return m_short_array2; } +unsigned short* CppjitTestData::get_ushort_array() { return m_ushort_array; } +unsigned short* CppjitTestData::get_ushort_array2() { return m_ushort_array2; } +int* CppjitTestData::get_int_array() { return m_int_array; } +int* CppjitTestData::get_int_array2() { return m_int_array2; } +unsigned int* CppjitTestData::get_uint_array() { return m_uint_array; } +unsigned int* CppjitTestData::get_uint_array2() { return m_uint_array2; } +long* CppjitTestData::get_long_array() { return m_long_array; } +long* CppjitTestData::get_long_array2() { return m_long_array2; } +unsigned long* CppjitTestData::get_ulong_array() { return m_ulong_array; } +unsigned long* CppjitTestData::get_ulong_array2() { return m_ulong_array2; } + +float* CppjitTestData::get_float_array() { return m_float_array; } +float* CppjitTestData::get_float_array2() { return m_float_array2; } +double* CppjitTestData::get_double_array() { return m_double_array; } +double* CppjitTestData::get_double_array2() { return m_double_array2; } +complex_t* CppjitTestData::get_complex_array() { return m_complex_array; } +complex_t* CppjitTestData::get_complex_array2() { return m_complex_array2; } +ccomplex_t* CppjitTestData::get_ccomplex_array() { return m_ccomplex_array; } +ccomplex_t* CppjitTestData::get_ccomplex_array2() { return m_ccomplex_array2; } + +CppjitTestPod CppjitTestData::get_pod_val() { return m_pod; } +CppjitTestPod* CppjitTestData::get_pod_val_ptr() { return &m_pod; } +CppjitTestPod& CppjitTestData::get_pod_val_ref() { return m_pod; } +CppjitTestPod*& CppjitTestData::get_pod_ptrref() { return m_ppod; } + +CppjitTestPod* CppjitTestData::get_pod_ptr() { return m_ppod; } + +//- getters const-ref ------------------------------------------------------- +const bool& CppjitTestData::get_bool_cr() { return m_bool; } +const char& CppjitTestData::get_char_cr() { return m_char; } +const signed char& CppjitTestData::get_schar_cr() { return m_schar; } +const unsigned char& CppjitTestData::get_uchar_cr() { return m_uchar; } +const wchar_t& CppjitTestData::get_wchar_cr() { return m_wchar; } +const char16_t& CppjitTestData::get_char16_cr() { return m_char16; } +const char32_t& CppjitTestData::get_char32_cr() { return m_char32; } +const std::byte& CppjitTestData::get_byte_cr() { return m_byte; } +const int8_t& CppjitTestData::get_int8_cr() { return m_int8; } +const uint8_t& CppjitTestData::get_uint8_cr() { return m_uint8; } +const short& CppjitTestData::get_short_cr() { return m_short; } +const unsigned short& CppjitTestData::get_ushort_cr() { return m_ushort; } +const int& CppjitTestData::get_int_cr() { return m_int; } +const unsigned int& CppjitTestData::get_uint_cr() { return m_uint; } +const long& CppjitTestData::get_long_cr() { return m_long; } +const unsigned long& CppjitTestData::get_ulong_cr() { return m_ulong; } +const long long& CppjitTestData::get_llong_cr() { return m_llong; } +const unsigned long long& CppjitTestData::get_ullong_cr() { return m_ullong; } +const Long64_t& CppjitTestData::get_long64_cr() { return m_long64; } +const ULong64_t& CppjitTestData::get_ulong64_cr() { return m_ulong64; } +const float& CppjitTestData::get_float_cr() { return m_float; } +const double& CppjitTestData::get_double_cr() { return m_double; } +const long double& CppjitTestData::get_ldouble_cr() { return m_ldouble; } +const complex_t& CppjitTestData::get_complex_cr() { return m_complex; } +const icomplex_t& CppjitTestData::get_icomplex_cr() { return m_icomplex; } +const ccomplex_t& CppjitTestData::get_ccomplex_cr() { return m_ccomplex; } +const CppjitTestData::EWhat& CppjitTestData::get_enum_cr() { return m_enum; } + +//- getters ref ------------------------------------------------------------- +bool& CppjitTestData::get_bool_r() { return m_bool; } +char& CppjitTestData::get_char_r() { return m_char; } +signed char& CppjitTestData::get_schar_r() { return m_schar; } +unsigned char& CppjitTestData::get_uchar_r() { return m_uchar; } +wchar_t& CppjitTestData::get_wchar_r() { return m_wchar; } +char16_t& CppjitTestData::get_char16_r() { return m_char16; } +char32_t& CppjitTestData::get_char32_r() { return m_char32; } +std::byte& CppjitTestData::get_byte_r() { return m_byte; } +int8_t& CppjitTestData::get_int8_r() { return m_int8; } +uint8_t& CppjitTestData::get_uint8_r() { return m_uint8; } +short& CppjitTestData::get_short_r() { return m_short; } +unsigned short& CppjitTestData::get_ushort_r() { return m_ushort; } +int& CppjitTestData::get_int_r() { return m_int; } +unsigned int& CppjitTestData::get_uint_r() { return m_uint; } +long& CppjitTestData::get_long_r() { return m_long; } +unsigned long& CppjitTestData::get_ulong_r() { return m_ulong; } +long long& CppjitTestData::get_llong_r() { return m_llong; } +unsigned long long& CppjitTestData::get_ullong_r() { return m_ullong; } +Long64_t& CppjitTestData::get_long64_r() { return m_long64; } +ULong64_t& CppjitTestData::get_ulong64_r() { return m_ulong64; } +float& CppjitTestData::get_float_r() { return m_float; } +double& CppjitTestData::get_double_r() { return m_double; } +long double& CppjitTestData::get_ldouble_r() { return m_ldouble; } +complex_t& CppjitTestData::get_complex_r() { return m_complex; } +icomplex_t& CppjitTestData::get_icomplex_r() { return m_icomplex; } +ccomplex_t& CppjitTestData::get_ccomplex_r() { return m_ccomplex; } +CppjitTestData::EWhat& CppjitTestData::get_enum_r() { return m_enum; } + +//- setters ----------------------------------------------------------------- +void CppjitTestData::set_bool(bool b) { m_bool = b; } +void CppjitTestData::set_char(char c) { m_char = c; } +void CppjitTestData::set_schar(signed char sc) { m_schar = sc; } +void CppjitTestData::set_uchar(unsigned char uc) { m_uchar = uc; } +void CppjitTestData::set_wchar(wchar_t wc) { m_wchar = wc; } +void CppjitTestData::set_char16(char16_t c16) { m_char16 = c16; } +void CppjitTestData::set_char32(char32_t c32) { m_char32 = c32; } +void CppjitTestData::set_byte(std::byte b) { m_byte = b; } +void CppjitTestData::set_int8(int8_t s8) { m_int8 = s8; } +void CppjitTestData::set_uint8(uint8_t u8) { m_uint8 = u8; } +void CppjitTestData::set_short(short s) { m_short = s; } +void CppjitTestData::set_ushort(unsigned short us) { m_ushort = us; } +void CppjitTestData::set_int(int i) { m_int = i; } +void CppjitTestData::set_uint(unsigned int ui) { m_uint = ui; } +void CppjitTestData::set_long(long l) { m_long = l; } +void CppjitTestData::set_ulong(unsigned long ul) { m_ulong = ul; } +void CppjitTestData::set_llong(long long ll) { m_llong = ll; } +void CppjitTestData::set_ullong(unsigned long long ull) { m_ullong = ull; } +void CppjitTestData::set_long64(Long64_t l64) { m_long64 = l64; } +void CppjitTestData::set_ulong64(ULong64_t ul64) { m_ulong64 = ul64; } +void CppjitTestData::set_float(float f) { m_float = f; } +void CppjitTestData::set_double(double d) { m_double = d; } +void CppjitTestData::set_ldouble(long double ld) { m_ldouble = ld; } +void CppjitTestData::set_complex(complex_t cd) { m_complex = cd; } +void CppjitTestData::set_icomplex(icomplex_t ci) { m_icomplex = ci; } +void CppjitTestData::set_ccomplex(ccomplex_t cd) { m_ccomplex = cd; } +void CppjitTestData::set_enum(EWhat w) { m_enum = w; } +void CppjitTestData::set_voidp(void* p) { m_voidp = p; } + +void CppjitTestData::set_pod_val(CppjitTestPod p) { m_pod = p; } +void CppjitTestData::set_pod_ptr_in(CppjitTestPod* pp) { m_pod = *pp; } +void CppjitTestData::set_pod_ptr_out(CppjitTestPod* pp) { *pp = m_pod; } +void CppjitTestData::set_pod_ref(const CppjitTestPod& rp) { m_pod = rp; } +void CppjitTestData::set_pod_ptrptr_in(CppjitTestPod** ppp) { m_pod = **ppp; } +void CppjitTestData::set_pod_void_ptrptr_in(void** pp) { + m_pod = **((CppjitTestPod**)pp); +} +void CppjitTestData::set_pod_ptrptr_out(CppjitTestPod** ppp) { + delete *ppp; + *ppp = new CppjitTestPod(m_pod); +} +void CppjitTestData::set_pod_void_ptrptr_out(void** pp) { + delete *((CppjitTestPod**)pp); + *((CppjitTestPod**)pp) = new CppjitTestPod(m_pod); +} + +void CppjitTestData::set_pod_ptr(CppjitTestPod* pp) { m_ppod = pp; } + +//- setters const-ref ------------------------------------------------------- +void CppjitTestData::set_bool_cr(const bool& b) { m_bool = b; } +void CppjitTestData::set_char_cr(const char& c) { m_char = c; } +void CppjitTestData::set_schar_cr(const signed char& sc) { m_schar = sc; } +void CppjitTestData::set_uchar_cr(const unsigned char& uc) { m_uchar = uc; } +void CppjitTestData::set_wchar_cr(const wchar_t& wc) { m_wchar = wc; } +void CppjitTestData::set_char16_cr(const char16_t& c16) { m_char16 = c16; } +void CppjitTestData::set_char32_cr(const char32_t& c32) { m_char32 = c32; } +void CppjitTestData::set_byte_cr(const std::byte& b) { m_byte = b; } +void CppjitTestData::set_int8_cr(const int8_t& s8) { m_int8 = s8; } +void CppjitTestData::set_uint8_cr(const uint8_t& u8) { m_uint8 = u8; } +void CppjitTestData::set_short_cr(const short& s) { m_short = s; } +void CppjitTestData::set_ushort_cr(const unsigned short& us) { m_ushort = us; } +void CppjitTestData::set_int_cr(const int& i) { m_int = i; } +void CppjitTestData::set_uint_cr(const unsigned int& ui) { m_uint = ui; } +void CppjitTestData::set_long_cr(const long& l) { m_long = l; } +void CppjitTestData::set_ulong_cr(const unsigned long& ul) { m_ulong = ul; } +void CppjitTestData::set_llong_cr(const long long& ll) { m_llong = ll; } +void CppjitTestData::set_ullong_cr(const unsigned long long& ull) { + m_ullong = ull; +} +void CppjitTestData::set_long64_cr(const Long64_t& l64) { m_long64 = l64; } +void CppjitTestData::set_ulong64_cr(const ULong64_t& ul64) { m_ulong64 = ul64; } +void CppjitTestData::set_float_cr(const float& f) { m_float = f; } +void CppjitTestData::set_double_cr(const double& d) { m_double = d; } +void CppjitTestData::set_ldouble_cr(const long double& ld) { m_ldouble = ld; } +void CppjitTestData::set_complex_cr(const complex_t& cd) { m_complex = cd; } +void CppjitTestData::set_icomplex_cr(const icomplex_t& ci) { m_icomplex = ci; } +void CppjitTestData::set_ccomplex_cr(const ccomplex_t& cd) { m_ccomplex = cd; } +void CppjitTestData::set_enum_cr(const EWhat& w) { m_enum = w; } + +//- setters ref ------------------------------------------------------------- +void CppjitTestData::set_bool_r(bool& b) { b = true; } +void CppjitTestData::set_char_r(char& c) { c = 'a'; } +void CppjitTestData::set_wchar_r(wchar_t& wc) { wc = 'b'; } +void CppjitTestData::set_char16_r(char16_t& c16) { c16 = u'\u6c24'; } +void CppjitTestData::set_char32_r(char32_t& c32) { c32 = U'\U0001f34e'; } +void CppjitTestData::set_schar_r(signed char& sc) { sc = 'c'; } +void CppjitTestData::set_uchar_r(unsigned char& uc) { uc = 'd'; } +void CppjitTestData::set_byte_r(std::byte& b) { b = (std::byte)'e'; } +void CppjitTestData::set_short_r(short& s) { s = -1; } +void CppjitTestData::set_ushort_r(unsigned short& us) { us = 2; } +void CppjitTestData::set_int_r(int& i) { i = -3; } +void CppjitTestData::set_uint_r(unsigned int& ui) { ui = 4; } +void CppjitTestData::set_long_r(long& l) { l = -5; } +void CppjitTestData::set_ulong_r(unsigned long& ul) { ul = 6; } +void CppjitTestData::set_llong_r(long long& ll) { ll = -7; } +void CppjitTestData::set_ullong_r(unsigned long long& ull) { ull = 8; } +void CppjitTestData::set_float_r(float& f) { f = 5.f; } +void CppjitTestData::set_double_r(double& d) { d = -5.; } +void CppjitTestData::set_ldouble_r(long double& ld) { ld = 10.l; } + +//- setters ptr ------------------------------------------------------------- +void CppjitTestData::set_bool_p(bool* b) { *b = true; } +void CppjitTestData::set_char_p(char* c) { *c = 'a'; } +void CppjitTestData::set_wchar_p(wchar_t* wc) { *wc = 'b'; } +void CppjitTestData::set_char16_p(char16_t* c16) { *c16 = u'\u6c24'; } +void CppjitTestData::set_char32_p(char32_t* c32) { *c32 = U'\U0001f34e'; } +void CppjitTestData::set_schar_p(signed char* sc) { *sc = 'c'; } +void CppjitTestData::set_uchar_p(unsigned char* uc) { *uc = 'd'; } +void CppjitTestData::set_byte_p(std::byte* b) { *b = (std::byte)'e'; } +void CppjitTestData::set_int8_p(int8_t* i8) { *i8 = -27; } +void CppjitTestData::set_uint8_p(uint8_t* ui8) { *ui8 = 28; } +void CppjitTestData::set_short_p(short* s) { *s = -1; } +void CppjitTestData::set_ushort_p(unsigned short* us) { *us = 2; } +void CppjitTestData::set_int_p(int* i) { *i = -3; } +void CppjitTestData::set_uint_p(unsigned int* ui) { *ui = 4; } +void CppjitTestData::set_long_p(long* l) { *l = -5; } +void CppjitTestData::set_ulong_p(unsigned long* ul) { *ul = 6; } +void CppjitTestData::set_llong_p(long long* ll) { *ll = -7; } +void CppjitTestData::set_ullong_p(unsigned long long* ull) { *ull = 8; } +void CppjitTestData::set_float_p(float* f) { *f = 5.f; } +void CppjitTestData::set_double_p(double* d) { *d = -5.; } +void CppjitTestData::set_ldouble_p(long double* ld) { *ld = 10.l; } + +//- setters ptrptr ---------------------------------------------------------- +void CppjitTestData::set_bool_ppa(bool** b) { + (*b) = new bool[3]; + (*b)[0] = true; + (*b)[1] = false; + (*b)[2] = true; +} +void CppjitTestData::set_char_ppa(char** c) { + (*c) = new char[3]; + (*c)[0] = 'a'; + (*c)[1] = 'b'; + (*c)[2] = 'c'; +} +void CppjitTestData::set_wchar_ppa(wchar_t** wc) { + (*wc) = new wchar_t[3]; + (*wc)[0] = 'd'; + (*wc)[1] = 'e'; + (*wc)[2] = 'f'; +} +void CppjitTestData::set_char16_ppa(char16_t** c16) { + (*c16) = new char16_t[3]; + (*c16)[0] = u'\u6c24'; + (*c16)[1] = u'\u6c25'; + (*c16)[2] = u'\u6c26'; +} +void CppjitTestData::set_char32_ppa(char32_t** c32) { + (*c32) = new char32_t[3]; + (*c32)[0] = U'\U0001f34d'; + (*c32)[1] = U'\U0001f34e'; + (*c32)[2] = U'\U0001f34f'; +} +void CppjitTestData::set_schar_ppa(signed char** sc) { + (*sc) = new signed char[3]; + (*sc)[0] = 'g'; + (*sc)[1] = 'h'; + (*sc)[2] = 'j'; +} +void CppjitTestData::set_uchar_ppa(unsigned char** uc) { + (*uc) = new unsigned char[3]; + (*uc)[0] = 'k'; + (*uc)[1] = 'l'; + (*uc)[2] = 'm'; +} +void CppjitTestData::set_byte_ppa(std::byte** b) { + (*b) = new std::byte[3]; + (*b)[0] = (std::byte)'n'; + (*b)[1] = (std::byte)'o'; + (*b)[2] = (std::byte)'p'; +} +void CppjitTestData::set_int8_ppa(int8_t** i8) { + (*i8) = new int8_t[3]; + (*i8)[0] = -27; + (*i8)[1] = -28; + (*i8)[2] = -29; +} +void CppjitTestData::set_uint8_ppa(uint8_t** ui8) { + (*ui8) = new uint8_t[3]; + (*ui8)[0] = 28; + (*ui8)[1] = 29; + (*ui8)[2] = 30; +} +void CppjitTestData::set_short_ppa(short** s) { + (*s) = new short[3]; + (*s)[0] = -1; + (*s)[1] = -2; + (*s)[2] = -3; +} +void CppjitTestData::set_ushort_ppa(unsigned short** us) { + (*us) = new unsigned short[3]; + (*us)[0] = 4; + (*us)[1] = 5; + (*us)[2] = 6; +} +void CppjitTestData::set_int_ppa(int** i) { + (*i) = new int[3]; + (*i)[0] = -7; + (*i)[1] = -8; + (*i)[2] = -9; +} +void CppjitTestData::set_uint_ppa(unsigned int** ui) { + (*ui) = new unsigned int[3]; + (*ui)[0] = 10; + (*ui)[1] = 11; + (*ui)[2] = 12; +} +void CppjitTestData::set_long_ppa(long** l) { + (*l) = new long[3]; + (*l)[0] = -13; + (*l)[1] = -14; + (*l)[2] = -15; +} +void CppjitTestData::set_ulong_ppa(unsigned long** ul) { + (*ul) = new unsigned long[3]; + (*ul)[0] = 16; + (*ul)[1] = 17; + (*ul)[2] = 18; +} +void CppjitTestData::set_llong_ppa(long long** ll) { + (*ll) = new long long[3]; + (*ll)[0] = -19; + (*ll)[1] = -20; + (*ll)[2] = -21; +} +void CppjitTestData::set_ullong_ppa(unsigned long long** ull) { + (*ull) = new unsigned long long[3]; + (*ull)[0] = 22; + (*ull)[1] = 23; + (*ull)[2] = 24; +} +void CppjitTestData::set_float_ppa(float** f) { + (*f) = new float[3]; + (*f)[0] = 5.f; + (*f)[1] = 10.f; + (*f)[2] = 20.f; +} +void CppjitTestData::set_double_ppa(double** d) { + (*d) = new double[3]; + (*d)[0] = -5; + (*d)[1] = -10.; + (*d)[2] = -20.; +} +void CppjitTestData::set_ldouble_ppa(long double** ld) { + (*ld) = new long double[3]; + (*ld)[0] = 5.l; + (*ld)[1] = 10.f; + (*ld)[2] = 20.l; +} + +intptr_t CppjitTestData::set_char_ppm(char** c) { + *c = (char*)malloc(4 * sizeof(char)); + return (intptr_t)*c; +} +intptr_t CppjitTestData::set_cchar_ppm(const char** cc) { + *cc = (const char*)malloc(4 * sizeof(char)); + return (intptr_t)*cc; +} +intptr_t CppjitTestData::set_wchar_ppm(wchar_t** w) { + *w = (wchar_t*)malloc(4 * sizeof(wchar_t)); + return (intptr_t)*w; +} +intptr_t CppjitTestData::set_char16_ppm(char16_t** c16) { + *c16 = (char16_t*)malloc(4 * sizeof(char16_t)); + return (intptr_t)*c16; +} +intptr_t CppjitTestData::set_char32_ppm(char32_t** c32) { + *c32 = (char32_t*)malloc(4 * sizeof(char32_t)); + return (intptr_t)*c32; +} +intptr_t CppjitTestData::set_cwchar_ppm(const wchar_t** cw) { + *cw = (const wchar_t*)malloc(4 * sizeof(wchar_t)); + return (intptr_t)*cw; +} +intptr_t CppjitTestData::set_cchar16_ppm(const char16_t** c16) { + *c16 = (const char16_t*)malloc(4 * sizeof(char16_t)); + return (intptr_t)*c16; +} +intptr_t CppjitTestData::set_cchar32_ppm(const char32_t** c32) { + *c32 = (const char32_t*)malloc(4 * sizeof(char32_t)); + return (intptr_t)*c32; +} +intptr_t CppjitTestData::set_void_ppm(void** v) { + *v = malloc(4 * sizeof(void*)); + return (intptr_t)*v; +} + +intptr_t CppjitTestData::freeit(void* ptr) { + intptr_t out = (intptr_t)ptr; + free(ptr); + return out; +} + +//- setters r-value --------------------------------------------------------- +void CppjitTestData::set_bool_rv(bool&& b) { m_bool = b; } +void CppjitTestData::set_char_rv(char&& c) { m_char = c; } +void CppjitTestData::set_schar_rv(signed char&& sc) { m_schar = sc; } +void CppjitTestData::set_uchar_rv(unsigned char&& uc) { m_uchar = uc; } +void CppjitTestData::set_wchar_rv(wchar_t&& wc) { m_wchar = wc; } +void CppjitTestData::set_char16_rv(char16_t&& c16) { m_char16 = c16; } +void CppjitTestData::set_char32_rv(char32_t&& c32) { m_char32 = c32; } +void CppjitTestData::set_byte_rv(std::byte&& b) { m_byte = b; } +void CppjitTestData::set_int8_rv(int8_t&& s8) { m_int8 = s8; } +void CppjitTestData::set_uint8_rv(uint8_t&& u8) { m_uint8 = u8; } +void CppjitTestData::set_short_rv(short&& s) { m_short = s; } +void CppjitTestData::set_ushort_rv(unsigned short&& us) { m_ushort = us; } +void CppjitTestData::set_int_rv(int&& i) { m_int = i; } +void CppjitTestData::set_uint_rv(unsigned int&& ui) { m_uint = ui; } +void CppjitTestData::set_long_rv(long&& l) { m_long = l; } +void CppjitTestData::set_ulong_rv(unsigned long&& ul) { m_ulong = ul; } +void CppjitTestData::set_llong_rv(long long&& ll) { m_llong = ll; } +void CppjitTestData::set_ullong_rv(unsigned long long&& ull) { m_ullong = ull; } +void CppjitTestData::set_long64_rv(Long64_t&& l64) { m_long64 = l64; } +void CppjitTestData::set_ulong64_rv(ULong64_t&& ul64) { m_ulong64 = ul64; } +void CppjitTestData::set_float_rv(float&& f) { m_float = f; } +void CppjitTestData::set_double_rv(double&& d) { m_double = d; } +void CppjitTestData::set_ldouble_rv(long double&& ld) { m_ldouble = ld; } +void CppjitTestData::set_complex_rv(complex_t&& cd) { m_complex = cd; } +void CppjitTestData::set_icomplex_rv(icomplex_t&& ci) { m_icomplex = ci; } +void CppjitTestData::set_ccomplex_rv(ccomplex_t&& cd) { m_ccomplex = cd; } +void CppjitTestData::set_enum_rv(EWhat&& w) { m_enum = w; } + +//- passers ----------------------------------------------------------------- +unsigned char* CppjitTestData::pass_array(unsigned char* a) { return a; } +short* CppjitTestData::pass_array(short* a) { return a; } +unsigned short* CppjitTestData::pass_array(unsigned short* a) { return a; } +int* CppjitTestData::pass_array(int* a) { return a; } +unsigned int* CppjitTestData::pass_array(unsigned int* a) { return a; } +long* CppjitTestData::pass_array(long* a) { return a; } +unsigned long* CppjitTestData::pass_array(unsigned long* a) { return a; } +float* CppjitTestData::pass_array(float* a) { return a; } +double* CppjitTestData::pass_array(double* a) { return a; } +complex_t* CppjitTestData::pass_array(complex_t* a) { return a; } +ccomplex_t* CppjitTestData::pass_array(ccomplex_t* a) { return a; } + +//- static data members ----------------------------------------------------- +bool CppjitTestData::s_bool = false; +char CppjitTestData::s_char = 'c'; +signed char CppjitTestData::s_schar = 's'; +unsigned char CppjitTestData::s_uchar = 'u'; +wchar_t CppjitTestData::s_wchar = L'U'; +char16_t CppjitTestData::s_char16 = u'\u6c29'; +char32_t CppjitTestData::s_char32 = U'\U0001f34b'; +std::byte CppjitTestData::s_byte = (std::byte)'b'; +int8_t CppjitTestData::s_int8 = -87; +uint8_t CppjitTestData::s_uint8 = 87; +short CppjitTestData::s_short = -101; +unsigned short CppjitTestData::s_ushort = 255u; +int CppjitTestData::s_int = -202; +unsigned int CppjitTestData::s_uint = 202u; +long CppjitTestData::s_long = -303l; +unsigned long CppjitTestData::s_ulong = 303ul; +long long CppjitTestData::s_llong = -404ll; +unsigned long long CppjitTestData::s_ullong = 404ull; +Long64_t CppjitTestData::s_long64 = -505ll; +ULong64_t CppjitTestData::s_ulong64 = 505ull; +float CppjitTestData::s_float = -606.f; +double CppjitTestData::s_double = -707.; +long double CppjitTestData::s_ldouble = -808.l; +complex_t CppjitTestData::s_complex = {909., -909.}; +icomplex_t CppjitTestData::s_icomplex = {979, -979}; +ccomplex_t CppjitTestData::s_ccomplex = {919., -919.}; +CppjitTestData::EWhat CppjitTestData::s_enum = CppjitTestData::kNothing; +void* CppjitTestData::s_voidp = (void*)0; +std::string CppjitTestData::s_strv = "Hello"; +std::string* CppjitTestData::s_strp = nullptr; + +//- strings ----------------------------------------------------------------- +const char* CppjitTestData::get_valid_string(const char* in) { return in; } +const char* CppjitTestData::get_invalid_string() { return (const char*)0; } +const wchar_t* CppjitTestData::get_valid_wstring(const wchar_t* in) { + return in; +} +const wchar_t* CppjitTestData::get_invalid_wstring() { + return (const wchar_t*)0; +} +const char16_t* CppjitTestData::get_valid_string16(const char16_t* in) { + return in; +} +const char16_t* CppjitTestData::get_invalid_string16() { + return (const char16_t*)0; +} +const char32_t* CppjitTestData::get_valid_string32(const char32_t* in) { + return in; +} +const char32_t* CppjitTestData::get_invalid_string32() { + return (const char32_t*)0; +} + +//= global functions ======================================================== +intptr_t get_pod_address(CppjitTestData& c) { return (intptr_t)&c.m_pod; } + +intptr_t get_int_address(CppjitTestData& c) { return (intptr_t)&c.m_pod.m_int; } + +intptr_t get_double_address(CppjitTestData& c) { + return (intptr_t)&c.m_pod.m_double; +} + +//= global variables/pointers =============================================== +bool g_bool = false; +char g_char = 'w'; +signed char g_schar = 'v'; +unsigned char g_uchar = 'u'; +wchar_t g_wchar = L'U'; +char16_t g_char16 = u'\u6c21'; +char32_t g_char32 = U'\u6c21'; +std::byte g_byte = (std::byte)'x'; +int8_t g_int8 = -66; +uint8_t g_uint8 = 66; +short g_short = -88; +unsigned short g_ushort = 88u; +int g_int = -188; +unsigned int g_uint = 188u; +long g_long = -288; +unsigned long g_ulong = 288ul; +long long g_llong = -388ll; +unsigned long long g_ullong = 388ull; +Long64_t g_long64 = -488ll; +ULong64_t g_ulong64 = 488ull; +float g_float = -588.f; +double g_double = -688.; +long double g_ldouble = -788.l; +complex_t g_complex = {808., -808.}; +icomplex_t g_icomplex = {909, -909}; +ccomplex_t g_ccomplex = {858., -858.}; +EFruit g_enum = kBanana; +void* g_voidp = nullptr; + +//= global accessors ======================================================== +void set_global_int(int i) { g_int = i; } + +int get_global_int() { return g_int; } + +CppjitTestPod* g_pod = (CppjitTestPod*)0; + +bool is_global_pod(CppjitTestPod* t) { return t == g_pod; } + +void set_global_pod(CppjitTestPod* t) { g_pod = t; } + +CppjitTestPod* get_global_pod() { return g_pod; } + +CppjitTestPod* get_null_pod() { return (CppjitTestPod*)0; } + +std::string g_some_global_string = "C++"; +std::string get_some_global_string() { return g_some_global_string; } +std::string g_some_global_string2 = "C++"; +std::string get_some_global_string2() { return g_some_global_string2; } + +const char16_t* g_some_global_string16 = u"z\u00df\u6c34"; +const char32_t* g_some_global_string32 = U"z\u00df\u6c34\U0001f34c"; + +std::string SomeStaticDataNS::s_some_static_string = "C++"; +std::string SomeStaticDataNS::get_some_static_string() { + return s_some_static_string; +} +std::string SomeStaticDataNS::s_some_static_string2 = "C++"; +std::string SomeStaticDataNS::get_some_static_string2() { + return s_some_static_string2; +} + +StorableData gData{5.}; + +//= special case of "byte" arrays =========================================== +int64_t sum_uc_data(unsigned char* data, int size) { + int64_t total = 0; + for (int i = 0; i < size; ++i) + total += int64_t(data[i]); + return total; +} + +int64_t sum_byte_data(std::byte* data, int size) { + return sum_uc_data((unsigned char*)data, size); +} + +//= function pointer passing ================================================ +int sum_of_int1(int i1, int i2) { return i1 + i2; } + +int sum_of_int2(int i1, int i2) { return 2 * i1 + i2; } + +int (*sum_of_int_ptr)(int, int) = sum_of_int1; + +int call_sum_of_int(int i1, int i2) { + if (sum_of_int_ptr) + return (*sum_of_int_ptr)(i1, i2); + return -1; +} + +double sum_of_double(double d1, double d2) { return d1 + d2; } + +double call_double_double(double (*f)(double, double), double d1, double d2) { + if (!f) + return -1.; + return f(d1, d2); +} + +//= callable passing ======================================================== +int call_int_int(int (*f)(int, int), int i1, int i2) { return f(i1, i2); } + +void call_void(void (*f)(int), int i) { f(i); } + +int call_refi(void (*f)(int&)) { + int i = -1; + f(i); + return i; +} + +int call_refl(void (*f)(long&)) { + long l = -1L; + f(l); + return l; +} + +int call_refd(void (*f)(double&)) { + double d = -1.; + f(d); + return d; +} + +StoreCallable::StoreCallable(double (*f)(double, double)) : fF(f) { + /* empty */ +} + +void StoreCallable::set_callable(double (*f)(double, double)) { fF = f; } + +double StoreCallable::operator()(double d1, double d2) { return fF(d1, d2); } + +//= callable through std::function ========================================== +double call_double_double_sf(const std::function& f, + double d1, double d2) { + return f(d1, d2); +} + +int call_int_int_sf(const std::function& f, int i1, int i2) { + return f(i1, i2); +} + +void call_void_sf(const std::function& f, int i) { f(i); } + +int call_refi_sf(const std::function& f) { + int i = -1; + f(i); + return i; +} + +int call_refl_sf(const std::function& f) { + long l = -1L; + f(l); + return l; +} + +int call_refd_sf(const std::function& f) { + double d = -1.; + f(d); + return d; +} + +StoreCallable_sf::StoreCallable_sf( + const std::function& f) + : fF(f) { + /* empty */ +} + +void StoreCallable_sf::set_callable( + const std::function& f) { + fF = f; +} + +double StoreCallable_sf::operator()(double d1, double d2) { return fF(d1, d2); } + +//= array of C strings passing ============================================== +std::vector +ArrayOfCStrings::takes_array_of_cstrings(const char* args[], int len) { + std::vector v; + v.reserve(len); + for (int i = 0; i < len; ++i) + v.emplace_back(args[i]); + + return v; +} + +//= aggregate testing ====================================================== +int AggregateTest::Aggregate1::sInt = 17; +int AggregateTest::Aggregate2::sInt = 27; + +//= multi-dim arrays ======================================================= +namespace MultiDimArrays { + +template static inline T** allocate_2d(size_t N, size_t M) { + T** arr = (T**)malloc(sizeof(void*) * N); + for (size_t i = 0; i < N; ++i) + arr[i] = (T*)malloc(sizeof(T) * M); + return arr; +} + +static inline void free_2d(void** arr, size_t N) { + if (arr) { + for (size_t i = 0; i < N; ++i) + free(arr[i]); + free(arr); + } +} + +template +static inline T*** allocate_3d(size_t N, size_t M, size_t K) { + T*** arr = (T***)malloc(sizeof(void*) * N); + for (size_t i = 0; i < N; ++i) { + arr[i] = (T**)malloc(sizeof(void*) * M); + for (size_t j = 0; j < M; ++j) + arr[i][j] = (T*)malloc(sizeof(T) * K); + } + return arr; +} + +static inline void free_3d(void*** arr, size_t N, size_t M) { + if (arr) { + for (size_t i = 0; i < N; ++i) { + for (size_t j = 0; j < M; ++j) + free(arr[i][j]); + free(arr[i]); + } + free(arr); + } +} + +} // namespace MultiDimArrays + +MultiDimArrays::DataHolder::DataHolder() { + m_short2a = allocate_2d(5, 7); + m_unsigned_short2a = allocate_2d(5, 7); + m_int2a = allocate_2d(5, 7); + m_unsigned_int2a = allocate_2d(5, 7); + m_long2a = allocate_2d(5, 7); + m_unsigned_long2a = allocate_2d(5, 7); + m_long_long2a = allocate_2d(5, 7); + m_unsigned_long_long2a = allocate_2d(5, 7); + m_float2a = allocate_2d(5, 7); + m_double2a = allocate_2d(5, 7); + + for (size_t i = 0; i < 5; ++i) { + for (size_t j = 0; j < 7; ++j) { + size_t val = 5 * i + j; + m_short2a[i][j] = (short)val; + m_unsigned_short2a[i][j] = (unsigned short)val; + m_int2a[i][j] = (int)val; + m_unsigned_int2a[i][j] = (unsigned int)val; + m_long2a[i][j] = (long)val; + m_unsigned_long2a[i][j] = (unsigned long)val; + m_long_long2a[i][j] = (long long)val; + m_unsigned_long_long2a[i][j] = (unsigned long long)val; + m_float2a[i][j] = (float)val; + m_double2a[i][j] = (double)val; + } + } + + m_short2b = nullptr; + m_unsigned_short2b = nullptr; + m_int2b = nullptr; + m_unsigned_int2b = nullptr; + m_long2b = nullptr; + m_unsigned_long2b = nullptr; + m_long_long2b = nullptr; + m_unsigned_long_long2b = nullptr; + m_float2b = nullptr; + m_double2b = nullptr; + + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 5; ++j) { + size_t val = 3 * i + j; + m_short2c[i][j] = (short)val; + m_unsigned_short2c[i][j] = (unsigned short)val; + m_int2c[i][j] = (int)val; + m_unsigned_int2c[i][j] = (unsigned int)val; + m_long2c[i][j] = (long)val; + m_unsigned_long2c[i][j] = (unsigned long)val; + m_long_long2c[i][j] = (long long)val; + m_unsigned_long_long2c[i][j] = (unsigned long long)val; + m_float2c[i][j] = (float)val; + m_double2c[i][j] = (double)val; + + for (size_t k = 0; k < 7; ++k) { + val = 3 * i + 2 * j + k; + m_short3c[i][j][k] = (short)val; + m_unsigned_short3c[i][j][k] = (unsigned short)val; + m_int3c[i][j][k] = (int)val; + m_unsigned_int3c[i][j][k] = (unsigned int)val; + m_long3c[i][j][k] = (long)val; + m_unsigned_long3c[i][j][k] = (unsigned long)val; + m_long_long3c[i][j][k] = (long long)val; + m_unsigned_long_long3c[i][j][k] = (unsigned long long)val; + m_float3c[i][j][k] = (float)val; + m_double3c[i][j][k] = (double)val; + } + } + } + + m_short3a = allocate_3d(5, 7, 11); + m_unsigned_short3a = allocate_3d(5, 7, 11); + m_int3a = allocate_3d(5, 7, 11); + m_unsigned_int3a = allocate_3d(5, 7, 11); + m_long3a = allocate_3d(5, 7, 11); + m_unsigned_long3a = allocate_3d(5, 7, 11); + m_long_long3a = allocate_3d(5, 7, 11); + m_unsigned_long_long3a = allocate_3d(5, 7, 11); + m_float3a = allocate_3d(5, 7, 11); + m_double3a = allocate_3d(5, 7, 11); + + for (size_t i = 0; i < 5; ++i) { + for (size_t j = 0; j < 7; ++j) { + for (size_t k = 0; k < 11; ++k) { + size_t val = 7 * i + 3 * j + k; + m_short3a[i][j][k] = (short)val; + m_unsigned_short3a[i][j][k] = (unsigned short)val; + m_int3a[i][j][k] = (int)val; + m_unsigned_int3a[i][j][k] = (unsigned int)val; + m_long3a[i][j][k] = (long)val; + m_unsigned_long3a[i][j][k] = (unsigned long)val; + m_long_long3a[i][j][k] = (long long)val; + m_unsigned_long_long3a[i][j][k] = (unsigned long long)val; + m_float3a[i][j][k] = (float)val; + m_double3a[i][j][k] = (double)val; + } + } + } +} + +MultiDimArrays::DataHolder::~DataHolder() { + free_2d((void**)m_short2a, 5); + free_2d((void**)m_unsigned_short2a, 5); + free_2d((void**)m_int2a, 5); + free_2d((void**)m_unsigned_int2a, 5); + free_2d((void**)m_long2a, 5); + free_2d((void**)m_unsigned_long2a, 5); + free_2d((void**)m_long_long2a, 5); + free_2d((void**)m_unsigned_long_long2a, 5); + free_2d((void**)m_float2a, 5); + free_2d((void**)m_double2a, 5); + + free_2d((void**)m_short2b, 5); + free_2d((void**)m_unsigned_short2b, 5); + free_2d((void**)m_int2b, 5); + free_2d((void**)m_unsigned_int2b, 5); + free_2d((void**)m_long2b, 5); + free_2d((void**)m_unsigned_long2b, 5); + free_2d((void**)m_long_long2b, 5); + free_2d((void**)m_unsigned_long_long2b, 5); + free_2d((void**)m_float2b, 5); + free_2d((void**)m_double2b, 5); + + free_3d((void***)m_short3a, 5, 7); + free_3d((void***)m_unsigned_short3a, 5, 7); + free_3d((void***)m_int3a, 5, 7); + free_3d((void***)m_unsigned_int3a, 5, 7); + free_3d((void***)m_long3a, 5, 7); + free_3d((void***)m_unsigned_long3a, 5, 7); + free_3d((void***)m_long_long3a, 5, 7); + free_3d((void***)m_unsigned_long_long3a, 5, 7); + free_3d((void***)m_float3a, 5, 7); + free_3d((void***)m_double3a, 5, 7); +} + +#define MULTIDIM_ARRAYS_NEW2D(type, name) \ + type** MultiDimArrays::DataHolder::new_##name##2d(int N, int M) { \ + type** arr = allocate_2d(N, M); \ + for (size_t i = 0; i < N; ++i) { \ + for (size_t j = 0; j < M; ++j) { \ + size_t val = 7 * i + j; \ + arr[i][j] = (type)val; \ + } \ + } \ + return arr; \ + } + +MULTIDIM_ARRAYS_NEW2D(short, short) +MULTIDIM_ARRAYS_NEW2D(unsigned short, ushort) +MULTIDIM_ARRAYS_NEW2D(int, int) +MULTIDIM_ARRAYS_NEW2D(unsigned int, uint) +MULTIDIM_ARRAYS_NEW2D(long, long) +MULTIDIM_ARRAYS_NEW2D(unsigned long, ulong) +MULTIDIM_ARRAYS_NEW2D(long long, llong) +MULTIDIM_ARRAYS_NEW2D(unsigned long long, ullong) +MULTIDIM_ARRAYS_NEW2D(float, float) +MULTIDIM_ARRAYS_NEW2D(double, double) + +//=========================================================================== +namespace Int8_Uint8_Arrays { +int8_t test[6] = {-0x12, -0x34, -0x56, -0x78}; +uint8_t utest[6] = {0x12, 0x34, 0x56, 0x78}; +} // namespace Int8_Uint8_Arrays diff --git a/test/cpp/datatypes.h b/test/cpp/datatypes.h new file mode 100644 index 0000000..2a3a3a5 --- /dev/null +++ b/test/cpp/datatypes.h @@ -0,0 +1,835 @@ +#ifndef CPPJIT_TEST_DATATYPES_H +#define CPPJIT_TEST_DATATYPES_H + +#ifdef _WIN32 +typedef __int64 Long64_t; +typedef unsigned __int64 ULong64_t; +#else +typedef long long Long64_t; +typedef unsigned long long ULong64_t; +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +const int N = 5; + +//=========================================================================== +struct CppjitTestPod { + int m_int; + double m_double; +}; + +//=========================================================================== +enum EFruit { kApple = 78, kBanana = 29, kCitrus = 34 }; +extern std::vector vecFruits; + +//=========================================================================== +enum class NamedClassEnum { E1 = 42 }; + +namespace EnumSpace { +enum E { E1 = 1, E2 }; +class EnumClass { +public: + enum { E1 = -1 }; + enum EE { E2 = -1 }; +}; + +typedef enum { AA = 1, BB, CC, DD } letter_code; + +enum class NamedClassEnum { E1 = -42 }; +} // namespace EnumSpace + +//=========================================================================== +class FourVector { +public: + FourVector(double x, double y, double z, double t) + : m_cc_called(false), m_x(x), m_y(y), m_z(z), m_t(t) {} + FourVector(const FourVector& s) + : m_cc_called(true), m_x(s.m_x), m_y(s.m_y), m_z(s.m_z), m_t(s.m_t) {} + + double operator[](int i) { + if (i == 0) + return m_x; + if (i == 1) + return m_y; + if (i == 2) + return m_z; + if (i == 3) + return m_t; + return -1; + } + + bool operator==(const FourVector& o) { + return (m_x == o.m_x && m_y == o.m_y && m_z == o.m_z && m_t == o.m_t); + } + +public: + bool m_cc_called; + +private: + double m_x, m_y, m_z, m_t; +}; + +//=========================================================================== +typedef std::complex complex_t; // maps to Py_complex +typedef std::complex icomplex_t; // no equivalent +#ifndef _WIN32 +typedef _Complex double ccomplex_t; // C-style complex, maps to Py_complex +#else +typedef _C_double_complex ccomplex_t; // id. +#endif + +class CppjitTestData { +public: + CppjitTestData(); + ~CppjitTestData(); + + // special cases + enum EWhat { kNothing = 6, kSomething = 111, kLots = 42 }; + + // helper + void destroy_arrays(); + + // getters + bool get_bool(); + char get_char(); + signed char get_schar(); + unsigned char get_uchar(); + wchar_t get_wchar(); + char16_t get_char16(); + char32_t get_char32(); + std::byte get_byte(); + int8_t get_int8(); + uint8_t get_uint8(); + short get_short(); + unsigned short get_ushort(); + int get_int(); + unsigned int get_uint(); + long get_long(); + unsigned long get_ulong(); + long long get_llong(); + unsigned long long get_ullong(); + Long64_t get_long64(); + ULong64_t get_ulong64(); + float get_float(); + double get_double(); + long double get_ldouble(); + long double get_ldouble_def(long double ld = 1); + complex_t get_complex(); + icomplex_t get_icomplex(); + ccomplex_t get_ccomplex(); + EWhat get_enum(); + void* get_voidp(); + + bool* get_bool_array(); + bool* get_bool_array2(); + signed char* get_schar_array(); + signed char* get_schar_array2(); + unsigned char* get_uchar_array(); + unsigned char* get_uchar_array2(); + std::byte* get_byte_array(); + std::byte* get_byte_array2(); + int8_t* get_int8_array(); + int8_t* get_int8_array2(); + uint8_t* get_uint8_array(); + uint8_t* get_uint8_array2(); + short* get_short_array(); + short* get_short_array2(); + unsigned short* get_ushort_array(); + unsigned short* get_ushort_array2(); + int* get_int_array(); + int* get_int_array2(); + unsigned int* get_uint_array(); + unsigned int* get_uint_array2(); + long* get_long_array(); + long* get_long_array2(); + unsigned long* get_ulong_array(); + unsigned long* get_ulong_array2(); + + float* get_float_array(); + float* get_float_array2(); + double* get_double_array(); + double* get_double_array2(); + complex_t* get_complex_array(); + complex_t* get_complex_array2(); + ccomplex_t* get_ccomplex_array(); + ccomplex_t* get_ccomplex_array2(); + + CppjitTestPod get_pod_val(); // for m_pod + CppjitTestPod* get_pod_val_ptr(); + CppjitTestPod& get_pod_val_ref(); + CppjitTestPod*& get_pod_ptrref(); + + CppjitTestPod* get_pod_ptr(); // for m_ppod + + // getters const-ref + const bool& get_bool_cr(); + const char& get_char_cr(); + const signed char& get_schar_cr(); + const unsigned char& get_uchar_cr(); + const wchar_t& get_wchar_cr(); + const char16_t& get_char16_cr(); + const char32_t& get_char32_cr(); + const std::byte& get_byte_cr(); + const int8_t& get_int8_cr(); + const uint8_t& get_uint8_cr(); + const short& get_short_cr(); + const unsigned short& get_ushort_cr(); + const int& get_int_cr(); + const unsigned int& get_uint_cr(); + const long& get_long_cr(); + const unsigned long& get_ulong_cr(); + const long long& get_llong_cr(); + const unsigned long long& get_ullong_cr(); + const Long64_t& get_long64_cr(); + const ULong64_t& get_ulong64_cr(); + const float& get_float_cr(); + const double& get_double_cr(); + const long double& get_ldouble_cr(); + const complex_t& get_complex_cr(); + const icomplex_t& get_icomplex_cr(); + const ccomplex_t& get_ccomplex_cr(); + const EWhat& get_enum_cr(); + + // getters ref + bool& get_bool_r(); + char& get_char_r(); + signed char& get_schar_r(); + unsigned char& get_uchar_r(); + wchar_t& get_wchar_r(); + char16_t& get_char16_r(); + char32_t& get_char32_r(); + std::byte& get_byte_r(); + int8_t& get_int8_r(); + uint8_t& get_uint8_r(); + short& get_short_r(); + unsigned short& get_ushort_r(); + int& get_int_r(); + unsigned int& get_uint_r(); + long& get_long_r(); + unsigned long& get_ulong_r(); + long long& get_llong_r(); + unsigned long long& get_ullong_r(); + Long64_t& get_long64_r(); + ULong64_t& get_ulong64_r(); + float& get_float_r(); + double& get_double_r(); + long double& get_ldouble_r(); + complex_t& get_complex_r(); + icomplex_t& get_icomplex_r(); + ccomplex_t& get_ccomplex_r(); + EWhat& get_enum_r(); + + // setters + void set_bool(bool); + void set_char(char); + void set_schar(signed char); + void set_uchar(unsigned char); + void set_wchar(wchar_t); + void set_char16(char16_t); + void set_char32(char32_t); + void set_byte(std::byte); + void set_int8(int8_t); + void set_uint8(uint8_t); + void set_short(short); + void set_ushort(unsigned short); + void set_int(int); + void set_uint(unsigned int); + void set_long(long); + void set_ulong(unsigned long); + void set_llong(long long); + void set_ullong(unsigned long long); + void set_long64(Long64_t); + void set_ulong64(ULong64_t); + void set_float(float); + void set_double(double); + void set_ldouble(long double); + void set_complex(complex_t); + void set_icomplex(icomplex_t); + void set_ccomplex(ccomplex_t); + void set_enum(EWhat); + void set_voidp(void*); + + void set_pod_val(CppjitTestPod); // for m_pod + void set_pod_ptr_in(CppjitTestPod*); + void set_pod_ptr_out(CppjitTestPod*); + void set_pod_ref(const CppjitTestPod&); + void set_pod_ptrptr_in(CppjitTestPod**); + void set_pod_void_ptrptr_in(void**); + void set_pod_ptrptr_out(CppjitTestPod**); + void set_pod_void_ptrptr_out(void**); + + void set_pod_ptr(CppjitTestPod*); // for m_ppod + + // setters const-ref + void set_bool_cr(const bool&); + void set_char_cr(const char&); + void set_schar_cr(const signed char&); + void set_uchar_cr(const unsigned char&); + void set_wchar_cr(const wchar_t&); + void set_char16_cr(const char16_t&); + void set_char32_cr(const char32_t&); + void set_byte_cr(const std::byte&); + void set_int8_cr(const int8_t&); + void set_uint8_cr(const uint8_t&); + void set_short_cr(const short&); + void set_ushort_cr(const unsigned short&); + void set_int_cr(const int&); + void set_uint_cr(const unsigned int&); + void set_long_cr(const long&); + void set_ulong_cr(const unsigned long&); + void set_llong_cr(const long long&); + void set_ullong_cr(const unsigned long long&); + void set_long64_cr(const Long64_t&); + void set_ulong64_cr(const ULong64_t&); + void set_float_cr(const float&); + void set_double_cr(const double&); + void set_ldouble_cr(const long double&); + void set_complex_cr(const complex_t&); + void set_icomplex_cr(const icomplex_t&); + void set_ccomplex_cr(const ccomplex_t&); + void set_enum_cr(const EWhat&); + + // setters ref + void set_bool_r(bool&); + void set_char_r(char&); + void set_wchar_r(wchar_t&); + void set_char16_r(char16_t&); + void set_char32_r(char32_t&); + void set_schar_r(signed char&); + void set_uchar_r(unsigned char&); + void set_byte_r(std::byte&); + void set_short_r(short&); + void set_ushort_r(unsigned short&); + void set_int_r(int&); + void set_uint_r(unsigned int&); + void set_long_r(long&); + void set_ulong_r(unsigned long&); + void set_llong_r(long long&); + void set_ullong_r(unsigned long long&); + void set_float_r(float&); + void set_double_r(double&); + void set_ldouble_r(long double&); + + // setters ptr + void set_bool_p(bool*); + void set_char_p(char*); + void set_wchar_p(wchar_t*); + void set_char16_p(char16_t*); + void set_char32_p(char32_t*); + void set_schar_p(signed char*); + void set_uchar_p(unsigned char*); + void set_byte_p(std::byte*); + void set_int8_p(int8_t*); + void set_uint8_p(uint8_t*); + void set_short_p(short*); + void set_ushort_p(unsigned short*); + void set_int_p(int*); + void set_uint_p(unsigned int*); + void set_long_p(long*); + void set_ulong_p(unsigned long*); + void set_llong_p(long long*); + void set_ullong_p(unsigned long long*); + void set_float_p(float*); + void set_double_p(double*); + void set_ldouble_p(long double*); + + // setters ptrptr + void set_bool_ppa(bool**); + void set_char_ppa(char**); + void set_wchar_ppa(wchar_t**); + void set_char16_ppa(char16_t**); + void set_char32_ppa(char32_t**); + void set_schar_ppa(signed char**); + void set_uchar_ppa(unsigned char**); + void set_byte_ppa(std::byte**); + void set_int8_ppa(int8_t**); + void set_uint8_ppa(uint8_t**); + void set_short_ppa(short**); + void set_ushort_ppa(unsigned short**); + void set_int_ppa(int**); + void set_uint_ppa(unsigned int**); + void set_long_ppa(long**); + void set_ulong_ppa(unsigned long**); + void set_llong_ppa(long long**); + void set_ullong_ppa(unsigned long long**); + void set_float_ppa(float**); + void set_double_ppa(double**); + void set_ldouble_ppa(long double**); + + intptr_t set_char_ppm(char**); + intptr_t set_cchar_ppm(const char**); + intptr_t set_wchar_ppm(wchar_t**); + intptr_t set_char16_ppm(char16_t**); + intptr_t set_char32_ppm(char32_t**); + intptr_t set_cwchar_ppm(const wchar_t**); + intptr_t set_cchar16_ppm(const char16_t**); + intptr_t set_cchar32_ppm(const char32_t**); + intptr_t set_void_ppm(void**); + + intptr_t freeit(void*); + + // setters r-value + void set_bool_rv(bool&&); + void set_char_rv(char&&); + void set_schar_rv(signed char&&); + void set_uchar_rv(unsigned char&&); + void set_wchar_rv(wchar_t&&); + void set_char16_rv(char16_t&&); + void set_char32_rv(char32_t&&); + void set_byte_rv(std::byte&&); + void set_int8_rv(int8_t&&); + void set_uint8_rv(uint8_t&&); + void set_short_rv(short&&); + void set_ushort_rv(unsigned short&&); + void set_int_rv(int&&); + void set_uint_rv(unsigned int&&); + void set_long_rv(long&&); + void set_ulong_rv(unsigned long&&); + void set_llong_rv(long long&&); + void set_ullong_rv(unsigned long long&&); + void set_long64_rv(Long64_t&&); + void set_ulong64_rv(ULong64_t&&); + void set_float_rv(float&&); + void set_double_rv(double&&); + void set_ldouble_rv(long double&&); + void set_complex_rv(complex_t&&); + void set_icomplex_rv(icomplex_t&&); + void set_ccomplex_rv(ccomplex_t&&); + void set_enum_rv(EWhat&&); + + // passers + unsigned char* pass_array(unsigned char*); + short* pass_array(short*); + unsigned short* pass_array(unsigned short*); + int* pass_array(int*); + unsigned int* pass_array(unsigned int*); + long* pass_array(long*); + unsigned long* pass_array(unsigned long*); + float* pass_array(float*); + double* pass_array(double*); + complex_t* pass_array(complex_t*); + ccomplex_t* pass_array(ccomplex_t*); + + unsigned char* pass_void_array_B(void* a) { + return pass_array((unsigned char*)a); + } + short* pass_void_array_h(void* a) { return pass_array((short*)a); } + unsigned short* pass_void_array_H(void* a) { + return pass_array((unsigned short*)a); + } + int* pass_void_array_i(void* a) { return pass_array((int*)a); } + unsigned int* pass_void_array_I(void* a) { + return pass_array((unsigned int*)a); + } + long* pass_void_array_l(void* a) { return pass_array((long*)a); } + unsigned long* pass_void_array_L(void* a) { + return pass_array((unsigned long*)a); + } + float* pass_void_array_f(void* a) { return pass_array((float*)a); } + double* pass_void_array_d(void* a) { return pass_array((double*)a); } + complex_t* pass_void_array_Z(void* a) { return pass_array((complex_t*)a); } + ccomplex_t* pass_void_array_cZ(void* a) { return pass_array((ccomplex_t*)a); } + + // strings + const char* get_valid_string(const char* in); + const char* get_invalid_string(); + const wchar_t* get_valid_wstring(const wchar_t* in); + const wchar_t* get_invalid_wstring(); + const char16_t* get_valid_string16(const char16_t* in); + const char16_t* get_invalid_string16(); + const char32_t* get_valid_string32(const char32_t* in); + const char32_t* get_invalid_string32(); + +public: + // basic types + bool m_bool; + char m_char; + signed char m_schar; + unsigned char m_uchar; + wchar_t m_wchar; + char16_t m_char16; + char32_t m_char32; + std::byte m_byte; + int8_t m_int8; + uint8_t m_uint8; + short m_short; + unsigned short m_ushort; + int m_int; + const int m_const_int; // special case: const testing + unsigned int m_uint; + long m_long; + unsigned long m_ulong; + long long m_llong; + unsigned long long m_ullong; + Long64_t m_long64; + ULong64_t m_ulong64; + float m_float; + double m_double; + long double m_ldouble; + complex_t m_complex; + icomplex_t m_icomplex; + ccomplex_t m_ccomplex; + EWhat m_enum; + void* m_voidp; + + // array types + bool m_bool_array[N]; + bool* m_bool_array2; + signed char m_schar_array[N]; + signed char* m_schar_array2; + unsigned char m_uchar_array[N]; + unsigned char* m_uchar_array2; + std::byte m_byte_array[N]; + std::byte* m_byte_array2; + int8_t m_int8_array[N]; + int8_t* m_int8_array2; + uint8_t m_uint8_array[N]; + uint8_t* m_uint8_array2; + short m_short_array[N]; + short* m_short_array2; + unsigned short m_ushort_array[N]; + unsigned short* m_ushort_array2; + int m_int_array[N]; + int* m_int_array2; + unsigned int m_uint_array[N]; + unsigned int* m_uint_array2; + long m_long_array[N]; + long* m_long_array2; + unsigned long m_ulong_array[N]; + unsigned long* m_ulong_array2; + + float m_float_array[N]; + float* m_float_array2; + double m_double_array[N]; + double* m_double_array2; + complex_t m_complex_array[N]; + complex_t* m_complex_array2; + icomplex_t m_icomplex_array[N]; + icomplex_t* m_icomplex_array2; + ccomplex_t m_ccomplex_array[N]; + ccomplex_t* m_ccomplex_array2; + + // object types + CppjitTestPod m_pod; + CppjitTestPod* m_ppod; + +public: + static bool s_bool; + static char s_char; + static signed char s_schar; + static unsigned char s_uchar; + static wchar_t s_wchar; + static char16_t s_char16; + static char32_t s_char32; + static std::byte s_byte; + static int8_t s_int8; + static uint8_t s_uint8; + static short s_short; + static unsigned short s_ushort; + static int s_int; + static unsigned int s_uint; + static long s_long; + static unsigned long s_ulong; + static long long s_llong; + static unsigned long long s_ullong; + static Long64_t s_long64; + static ULong64_t s_ulong64; + static float s_float; + static double s_double; + static long double s_ldouble; + static complex_t s_complex; + static icomplex_t s_icomplex; + static ccomplex_t s_ccomplex; + static EWhat s_enum; + static void* s_voidp; + static std::string s_strv; + static std::string* s_strp; + +private: + bool m_owns_arrays; +}; + +//= global functions ======================================================== +intptr_t get_pod_address(CppjitTestData& c); +intptr_t get_int_address(CppjitTestData& c); +intptr_t get_double_address(CppjitTestData& c); + +//= global variables/pointers =============================================== +extern bool g_bool; +extern char g_char; +extern signed char g_schar; +extern unsigned char g_uchar; +extern wchar_t g_wchar; +extern char16_t g_char16; +extern char32_t g_char32; +extern std::byte g_byte; +extern int8_t g_int8; +extern uint8_t g_uint8; +extern short g_short; +extern unsigned short g_ushort; +extern int g_int; +extern unsigned int g_uint; +extern long g_long; +extern unsigned long g_ulong; +extern long long g_llong; +extern unsigned long long g_ullong; +extern Long64_t g_long64; +extern ULong64_t g_ulong64; +extern float g_float; +extern double g_double; +extern long double g_ldouble; +extern complex_t g_complex; +extern icomplex_t g_icomplex; +extern ccomplex_t g_ccomplex; +extern EFruit g_enum; +extern void* g_voidp; + +static const bool g_c_bool = true; +static const char g_c_char = 'z'; +static const signed char g_c_schar = 'y'; +static const unsigned char g_c_uchar = 'x'; +static const wchar_t g_c_wchar = L'U'; +static const char16_t g_c_char16 = u'\u6c34'; +static const char32_t g_c_char32 = U'\U0001f34c'; +static const std::byte g_c_byte = (std::byte)'u'; +static const int8_t g_c_int8 = -12; +static const uint8_t g_c_uint8 = 12; +static const short g_c_short = -99; +static const unsigned short g_c_ushort = 99u; +static const int g_c_int = -199; +static const unsigned int g_c_uint = 199u; +static const long g_c_long = -299; +static const unsigned long g_c_ulong = 299ul; +static const long long g_c_llong = -399ll; +static const unsigned long long g_c_ullong = 399ull; +static const Long64_t g_c_long64 = -499ll; +static const ULong64_t g_c_ulong64 = 499ull; +static const float g_c_float = -599.f; +static const double g_c_double = -699.; +static const long double g_c_ldouble = -799.l; +static const complex_t g_c_complex = {1., 2.}; +static const icomplex_t g_c_icomplex = {3, 4}; +static const ccomplex_t g_c_ccomplex = {5., 6.}; +static const EFruit g_c_enum = kApple; +static const void* g_c_voidp = nullptr; + +//= global accessors ======================================================== +void set_global_int(int i); +int get_global_int(); + +extern CppjitTestPod* g_pod; +bool is_global_pod(CppjitTestPod* t); +void set_global_pod(CppjitTestPod* t); +CppjitTestPod* get_global_pod(); +CppjitTestPod* get_null_pod(); + +extern std::string g_some_global_string; +std::string get_some_global_string(); +extern std::string g_some_global_string2; +std::string get_some_global_string2(); + +extern const char16_t* g_some_global_string16; +extern const char32_t* g_some_global_string32; + +namespace SomeStaticDataNS { +extern std::string s_some_static_string; +std::string get_some_static_string(); +extern std::string s_some_static_string2; +std::string get_some_static_string2(); +} // namespace SomeStaticDataNS + +struct StorableData { + StorableData(double d) : fData(d) {} + double fData; +}; + +extern StorableData gData; + +//= special case of "byte" arrays =========================================== +int64_t sum_uc_data(unsigned char* data, int size); +int64_t sum_byte_data(std::byte* data, int size); + +//= function pointer passing ================================================ +int sum_of_int1(int i1, int i2); +int sum_of_int2(int i1, int i2); +extern int (*sum_of_int_ptr)(int, int); +int call_sum_of_int(int i1, int i2); + +double sum_of_double(double d1, double d2); +double call_double_double(double (*d)(double, double), double d1, double d2); + +struct sum_of_int_struct { + int (*sum_of_int_ptr)(int, int); +}; + +//= callable passing ======================================================== +int call_int_int(int (*)(int, int), int, int); +void call_void(void (*f)(int), int i); +int call_refi(void (*fcn)(int&)); +int call_refl(void (*fcn)(long&)); +int call_refd(void (*fcn)(double&)); + +class StoreCallable { + double (*fF)(double, double); + +public: + StoreCallable(double (*)(double, double)); + void set_callable(double (*)(double, double)); + double operator()(double, double); +}; + +//= callable through std::function ========================================== +double call_double_double_sf(const std::function&, + double d1, double d2); + +int call_int_int_sf(const std::function&, int, int); +void call_void_sf(const std::function&, int i); +int call_refi_sf(const std::function&); +int call_refl_sf(const std::function&); +int call_refd_sf(const std::function&); + +class StoreCallable_sf { + std::function fF; + +public: + StoreCallable_sf(const std::function&); + void set_callable(const std::function&); + double operator()(double, double); +}; + +//= array of struct variants ================================================ +namespace ArrayOfStruct { + +struct Foo { + int fVal; +}; + +struct Bar1 { + Bar1() : fArr(new Foo[2]) { + fArr[0].fVal = 42; + fArr[1].fVal = 13; + } + Bar1(const Bar1&) = delete; + Bar1& operator=(const Bar1&) = delete; + ~Bar1() { delete[] fArr; } + Foo* fArr; +}; + +struct Bar2 { + Bar2(int num_foo) : fArr(std::unique_ptr{new Foo[num_foo]}) { + for (int i = 0; i < num_foo; ++i) + fArr[i].fVal = 2 * i; + } + std::unique_ptr fArr; +}; + +} // namespace ArrayOfStruct + +//= array of C strings passing ============================================== +namespace ArrayOfCStrings { +std::vector takes_array_of_cstrings(const char* args[], int len); +} + +//= aggregate testing ====================================================== +namespace AggregateTest { + +struct Aggregate1 { + static int sInt; +}; + +struct Aggregate2 { + static int sInt; + int fInt = 42; +}; + +} // namespace AggregateTest + +//= multi-dim arrays ======================================================= +namespace MultiDimArrays { + +struct DataHolder { + DataHolder(); + ~DataHolder(); + + short** m_short2a; + unsigned short** m_unsigned_short2a; + int** m_int2a; + unsigned int** m_unsigned_int2a; + long** m_long2a; + unsigned long** m_unsigned_long2a; + long long** m_long_long2a; + unsigned long long** m_unsigned_long_long2a; + float** m_float2a; + double** m_double2a; + + short** m_short2b; + short** new_short2d(int N, int M); + unsigned short** m_unsigned_short2b; + unsigned short** new_ushort2d(int N, int M); + int** m_int2b; + int** new_int2d(int N, int M); + unsigned int** m_unsigned_int2b; + unsigned int** new_uint2d(int N, int M); + long** m_long2b; + long** new_long2d(int N, int M); + unsigned long** m_unsigned_long2b; + unsigned long** new_ulong2d(int N, int M); + long long** m_long_long2b; + long long** new_llong2d(int N, int M); + unsigned long long** m_unsigned_long_long2b; + unsigned long long** new_ullong2d(int N, int M); + float** m_float2b; + float** new_float2d(int N, int M); + double** m_double2b; + double** new_double2d(int N, int M); + + short m_short2c[3][5]; + unsigned short m_unsigned_short2c[3][5]; + int m_int2c[3][5]; + unsigned int m_unsigned_int2c[3][5]; + long m_long2c[3][5]; + unsigned long m_unsigned_long2c[3][5]; + long long m_long_long2c[3][5]; + unsigned long long m_unsigned_long_long2c[3][5]; + float m_float2c[3][5]; + double m_double2c[3][5]; + + short*** m_short3a; + unsigned short*** m_unsigned_short3a; + int*** m_int3a; + unsigned int*** m_unsigned_int3a; + long*** m_long3a; + unsigned long*** m_unsigned_long3a; + long long*** m_long_long3a; + unsigned long long*** m_unsigned_long_long3a; + float*** m_float3a; + double*** m_double3a; + + short m_short3c[3][5][7]; + unsigned short m_unsigned_short3c[3][5][7]; + int m_int3c[3][5][7]; + unsigned int m_unsigned_int3c[3][5][7]; + long m_long3c[3][5][7]; + unsigned long m_unsigned_long3c[3][5][7]; + long long m_long_long3c[3][5][7]; + unsigned long long m_unsigned_long_long3c[3][5][7]; + float m_float3c[3][5][7]; + double m_double3c[3][5][7]; +}; + +} // namespace MultiDimArrays + +//= int8_t/uint8_t arrays =================================================== +namespace Int8_Uint8_Arrays { +extern int8_t test[6]; +extern uint8_t utest[6]; +} // namespace Int8_Uint8_Arrays + +#endif // !CPPJIT_TEST_DATATYPES_H diff --git a/test/cpp/doc_helper.cxx b/test/cpp/doc_helper.cxx new file mode 100644 index 0000000..783baf9 --- /dev/null +++ b/test/cpp/doc_helper.cxx @@ -0,0 +1,7 @@ +#include "doc_helper.h" + +void DocHelper::throw_an_error(int i) { + if (i) + throw SomeError{"this is an error"}; + throw SomeOtherError{"this is another error"}; +} diff --git a/test/cpp/doc_helper.h b/test/cpp/doc_helper.h new file mode 100644 index 0000000..75b62e3 --- /dev/null +++ b/test/cpp/doc_helper.h @@ -0,0 +1,30 @@ +#ifndef CPPJIT_TEST_DOCHELPER_H +#define CPPJIT_TEST_DOCHELPER_H + +#include +#include + +class SomeError : public std::exception { +public: + explicit SomeError(const std::string& msg) : fMsg(msg) {} + SomeError(const SomeError& s) : fMsg(s.fMsg) {} + + const char* what() const throw() override { return fMsg.c_str(); } + +private: + std::string fMsg; +}; + +class SomeOtherError : public SomeError { +public: + explicit SomeOtherError(const std::string& msg) : SomeError(msg) {} + SomeOtherError(const SomeOtherError& s) : SomeError(s) {} +}; + +namespace DocHelper { + +void throw_an_error(int i); + +} // namespace DocHelper + +#endif // !CPPJIT_TEST_DOCHELPER_H diff --git a/test/cpp/example01.cxx b/test/cpp/example01.cxx new file mode 100644 index 0000000..4791f7f --- /dev/null +++ b/test/cpp/example01.cxx @@ -0,0 +1,164 @@ +#include +#include +#include +#include + +#include "example01.h" + +//=========================================================================== +payload::payload(double d) : m_data(d) { count++; } +payload::payload(const payload& p) : m_data(p.m_data) { count++; } +payload& payload::operator=(const payload& p) { + if (this != &p) { + m_data = p.m_data; + } + return *this; +} +payload::~payload() { count--; } + +double payload::getData() { return m_data; } +void payload::setData(double d) { m_data = d; } + +// class-level data +int payload::count = 0; + +//=========================================================================== +example01::example01() : m_somedata(-99) { count++; } +example01::example01(int a) : m_somedata(a) { count++; } +example01::example01(const example01& e) : m_somedata(e.m_somedata) { count++; } +example01& example01::operator=(const example01& e) { + if (this != &e) { + m_somedata = e.m_somedata; + } + return *this; +} +example01::~example01() { count--; } + +// class-level methods +int example01::staticAddOneToInt(int a) { return a + 1; } +int example01::staticAddOneToInt(int a, int b) { return a + b + 1; } +double example01::staticAddToDouble(double a) { return a + 0.01; } +int example01::staticAtoi(const char* str) { return ::atoi(str); } +char* example01::staticStrcpy(const char* strin) { + char* strout = (char*)malloc(::strlen(strin) + 1); + ::strcpy(strout, strin); + return strout; +} +void example01::staticSetPayload(payload* p, double d) { p->setData(d); } + +payload* example01::staticCyclePayload(payload* p, double d) { + staticSetPayload(p, d); + return p; +} + +payload example01::staticCopyCyclePayload(payload* p, double d) { + staticSetPayload(p, d); + return *p; +} + +int example01::getCount() { return count; } + +void example01::setCount(int value) { count = value; } + +// instance methods +int example01::addDataToInt(int a) { return m_somedata + a; } + +int example01::addDataToIntConstRef(const int& a) { return m_somedata + a; } + +int example01::overloadedAddDataToInt(int a, int b) { + return m_somedata + a + b; +} + +int example01::overloadedAddDataToInt(int a) { return m_somedata + a; } + +int example01::overloadedAddDataToInt(int a, int b, int c) { + return m_somedata + a + b + c; +} + +double example01::addDataToDouble(double a) { return m_somedata + a; } + +int example01::addDataToAtoi(const char* str) { + return ::atoi(str) + m_somedata; +} + +char* example01::addToStringValue(const char* str) { + int out = ::atoi(str) + m_somedata; + std::ostringstream ss; + ss << out << std::ends; + std::string result = ss.str(); + char* cresult = (char*)malloc(result.size() + 1); + ::strcpy(cresult, result.c_str()); + return cresult; +} + +void example01::setPayload(payload* p) { p->setData(m_somedata); } + +payload* example01::cyclePayload(payload* p) { + setPayload(p); + return p; +} + +payload example01::copyCyclePayload(payload* p) { + setPayload(p); + return *p; +} + +// class-level data +int example01::count = 0; + +// global +int globalAddOneToInt(int a) { return a + 1; } + +int ns_example01::globalAddOneToInt(int a) { return ::globalAddOneToInt(a); } + +int installableAddOneToInt(example01& e, int a) { + return e.staticAddOneToInt(a); +} + +int ns_example01::gMyGlobalInt = 99; + +// argument passing +#define typeValueImp(itype, tname) \ + itype ArgPasser::tname##Value(itype arg0, int argn, itype arg1, \ + itype arg2) { \ + switch (argn) { \ + case 0: \ + return arg0; \ + case 1: \ + return arg1; \ + case 2: \ + return arg2; \ + default: \ + break; \ + } \ + \ + return (itype) - 1; \ + } + +typeValueImp(short, short) typeValueImp(unsigned short, ushort) + typeValueImp(int, int) typeValueImp(unsigned int, uint) + typeValueImp(long, long) typeValueImp(unsigned long, ulong) + + typeValueImp(float, float) typeValueImp(double, double) + + std::string ArgPasser::stringValue(std::string arg0, int argn, + std::string arg1) { + switch (argn) { + case 0: + return arg0; + case 1: + return arg1; + default: + break; + } + + return "argn invalid"; +} + +std::string ArgPasser::stringRef(const std::string& arg0, int argn, + const std::string& arg1) { + return stringValue(arg0, argn, arg1); +} + +// special case naming +z_& z_::gime_z_(z_& z) { return z; } diff --git a/test/cpp/example01.h b/test/cpp/example01.h new file mode 100644 index 0000000..e0a9537 --- /dev/null +++ b/test/cpp/example01.h @@ -0,0 +1,115 @@ +#ifndef EXAMPLE_01_H +#define EXAMPLE_01_H + +#include + +class payload { +public: + payload(double d = 0.); + payload(const payload& p); + payload& operator=(const payload& e); + ~payload(); + + double getData(); + void setData(double d); + +public: // class-level data + static int count; + +private: + double m_data; +}; + +class example01 { +public: + example01(); + example01(int a); + example01(const example01& e); + example01& operator=(const example01& e); + virtual ~example01(); + +public: // class-level methods + static int staticAddOneToInt(int a); + static int staticAddOneToInt(int a, int b); + static double staticAddToDouble(double a); + static int staticAtoi(const char* str); + static char* staticStrcpy(const char* strin); + static void staticSetPayload(payload* p, double d); + static payload* staticCyclePayload(payload* p, double d); + static payload staticCopyCyclePayload(payload* p, double d); + static int getCount(); + static void setCount(int); + +public: // instance methods + int addDataToInt(int a); + int addDataToIntConstRef(const int& a); + int overloadedAddDataToInt(int a, int b); + int overloadedAddDataToInt(int a); + int overloadedAddDataToInt(int a, int b, int c); + double addDataToDouble(double a); + int addDataToAtoi(const char* str); + char* addToStringValue(const char* str); + + void setPayload(payload* p); + payload* cyclePayload(payload* p); + payload copyCyclePayload(payload* p); + +public: // class-level data + static int count; + +public: // instance data + int m_somedata; +}; + +// global functions and data +int globalAddOneToInt(int a); +namespace ns_example01 { +int globalAddOneToInt(int a); +extern int gMyGlobalInt; +} // namespace ns_example01 + +int installableAddOneToInt(example01&, int a); + +#define itypeValue(itype, tname) \ + itype tname##Value(itype arg0, int argn = 0, itype arg1 = 1, itype arg2 = 2) + +#define ftypeValue(ftype) \ + ftype ftype##Value(ftype arg0, int argn = 0, ftype arg1 = 1., ftype arg2 = 2.) + +// argument passing +class ArgPasser { // use a class for now as methptrgetter not +public: // implemented for global functions + itypeValue(short, short); + itypeValue(unsigned short, ushort); + itypeValue(int, int); + itypeValue(unsigned int, uint); + itypeValue(long, long); + itypeValue(unsigned long, ulong); + + ftypeValue(float); + ftypeValue(double); + + std::string stringValue(std::string arg0, int argn = 0, + std::string arg1 = "default"); + + std::string stringRef(const std::string& arg0, int argn = 0, + const std::string& arg1 = "default"); +}; + +// typedefs +typedef example01 example01_t; + +// special case naming +class z_ { +public: + z_& gime_z_(z_& z); + int myint; +}; + +// for pythonization checking +class example01a : public example01 { +public: + example01a(int a) : example01(a) {} +}; + +#endif // EXAMPLE_01_H diff --git a/test/cpp/fragile.cxx b/test/cpp/fragile.cxx new file mode 100644 index 0000000..f87bc1a --- /dev/null +++ b/test/cpp/fragile.cxx @@ -0,0 +1,61 @@ +#include "fragile.h" +#include + +fragile::H::HH* fragile::H::HH::copy() { return (HH*)0; } + +fragile::I fragile::gI; + +void fragile::fglobal(int, double, char) { + /* empty; only used for doc-string testing */ +} + +namespace fragile { + +class Kderived : public K { +public: + virtual ~Kderived(); +}; + +} // namespace fragile + +fragile::Kderived::~Kderived() {} + +fragile::K::~K() {} + +fragile::K* fragile::K::GimeK(bool derived) { + if (!derived) + return this; + else { + static Kderived kd; + return &kd; + } +}; + +fragile::K* fragile::K::GimeL() { + static L l; + return &l; +} + +fragile::L::~L() {} + +int fragile::create_handle(OpaqueHandle_t* handle) { + *handle = (OpaqueHandle_t)0x01; + return 0x01; +} + +int fragile::destroy_handle(OpaqueHandle_t handle, intptr_t addr) { + if ((intptr_t)handle == addr) + return 1; + return 0; +} + +// for signal -> exception testing +void fragile::segfault() { + int* i = 0; + *i = 42; +} + +void fragile::sigabort() { assert(0); } + +// for duplicate testing +int fragile::add42(int i) { return i + 42; } diff --git a/test/cpp/fragile.h b/test/cpp/fragile.h new file mode 100644 index 0000000..d244ca0 --- /dev/null +++ b/test/cpp/fragile.h @@ -0,0 +1,143 @@ +#ifndef FRAGILE_H +#define FRAGILE_H + +#include + +namespace fragile { + +class no_such_class; + +class A { +public: + virtual int check() { return (int)'A'; } + virtual A* gime_null() { return (A*)0; } +}; + +class B { +public: + virtual int check() { return (int)'B'; } + no_such_class* gime_no_such() { return 0; } +}; + +class C { +public: + virtual int check() { return (int)'C'; } + void use_no_such(no_such_class*) {} +}; + +class D { +public: + virtual int check() { return (int)'D'; } + virtual int check(int, int) { return (int)'D'; } + void overload() {} + void overload(no_such_class*) {} + void overload(char, int i = 0) {} // Reflex requires a named arg + void overload(int, no_such_class* p = 0) {} +}; + +static const uintptr_t dummy_location = 0xdead; + +class E { +public: + E() : m_pp_no_such((no_such_class**)&dummy_location), m_pp_a(0) {} + + virtual int check() { return (int)'E'; } + void overload(no_such_class**) {} + + no_such_class** m_pp_no_such; + A** m_pp_a; +}; + +class F { +public: + F() : m_int(0) {} + virtual int check() { return (int)'F'; } + int m_int; +}; + +class G { +public: + enum { unnamed1 = 24, unnamed2 = 96 }; + + class GG {}; +}; + +class H { +public: + class HH { + public: + HH* copy(); + }; + HH* m_h; +}; + +class I { +public: + operator bool() { return 0; } +}; + +extern I gI; + +class J { +public: + int method1(int, double) { return 0; } +}; + +void fglobal(int, double, char); + +namespace nested1 { +class A {}; +namespace nested2 { +class A {}; +namespace nested3 { +class A {}; +} // namespace nested3 +} // namespace nested2 +} // namespace nested1 + +class K { +public: + virtual ~K(); + K* GimeK(bool derived); + K* GimeL(); +}; + +class L : public K { +public: + virtual ~L(); + no_such_class* m_no_such; +}; + +class M { +public: + virtual ~M() {} + enum E1 { kOnce = 42 }; + enum E2 { kTwice = 12 }; +}; + +class N : public M { +public: + enum E2 { kTwice = 12 }; +}; + +class O { +public: + virtual int abstract() = 0; +}; + +class OpaqueType; +typedef OpaqueType* OpaqueHandle_t; + +int create_handle(OpaqueHandle_t* handle); +int destroy_handle(OpaqueHandle_t handle, intptr_t addr); + +// for signal -> exception testing +void segfault(); +void sigabort(); + +// for duplicate testing +int add42(int i); + +} // namespace fragile + +#endif // FRAGILE_H \ No newline at end of file diff --git a/test/cpp/operators.cxx b/test/cpp/operators.cxx new file mode 100644 index 0000000..96d1e2b --- /dev/null +++ b/test/cpp/operators.cxx @@ -0,0 +1,140 @@ +#include "operators.h" + +// for testing the case of virtual operator== +v_opeq_base::v_opeq_base(int val) : m_val(val) {} +v_opeq_base::~v_opeq_base() {} + +bool v_opeq_base::operator==(const v_opeq_base& other) { + return m_val == other.m_val; +} + +v_opeq_derived::v_opeq_derived(int val) : v_opeq_base(val) {} +v_opeq_derived::~v_opeq_derived() {} + +bool v_opeq_derived::operator==(const v_opeq_derived& other) { + return m_val != other.m_val; +} + +// for indexing tests +int& YAMatrix1::operator()(int, int) { return m_val; } + +const int& YAMatrix1::operator()(int, int) const { return m_val; } + +//- +int& YAMatrix2::operator[](int) { return m_val; } + +const int& YAMatrix2::operator[](int) const { return m_val; } + +//- +int& YAMatrix3::operator()(int, int) { return m_val; } + +const int& YAMatrix3::operator()(int, int) const { return m_val; } + +int& YAMatrix3::operator[](int) { return m_val; } + +const int& YAMatrix3::operator[](int) const { return m_val; } + +//- +int& YAMatrix4::operator[](int) { return m_val; } + +const int& YAMatrix4::operator[](int) const { return m_val; } + +int& YAMatrix4::operator()(int, int) { return m_val; } + +const int& YAMatrix4::operator()(int, int) const { return m_val; } + +//- +int& YAMatrix5::operator[](int) { return m_val; } + +int YAMatrix5::operator[](int) const { return m_val; } + +int& YAMatrix5::operator()(int, int) { return m_val; } + +int YAMatrix5::operator()(int, int) const { return m_val; } + +//- +int& YAMatrix6::operator[](int) { return m_val; } + +int YAMatrix6::operator[](int) const { return m_val; } + +int& YAMatrix6::operator()(int, int) { return m_val; } + +//- +int& YAMatrix7::operator[](int) { return m_val; } + +int& YAMatrix7::operator()(int, int) { return m_val; } + +//- for __radd__/__rmul__, non-associative +AssocADD operator+(int n, AssocADD& a) { return AssocADD(n + a.fx); } + +AssocADD operator+(AssocADD& a, int n) { return AssocADD(a.fx + n); } + +NonAssocRADD operator+(int n, NonAssocRADD& a) { + return NonAssocRADD(n + a.fx); +} + +AssocMUL operator*(int n, AssocMUL& m) { return AssocMUL(n * m.fx); } + +AssocMUL operator*(AssocMUL& m, int n) { return AssocMUL(m.fx * n); } + +NonAssocRMUL operator*(int n, NonAssocRMUL& m) { + return NonAssocRMUL(n * m.fx); +} + +//- for multi-lookup +double MultiLookup::operator*(const Vector2& v1, const Vector2& v2) { + return v1.x * v2.x + v1.y * v2.y; +} + +MultiLookup::Vector2 MultiLookup::operator*(const Vector2& v, double a) { + return Vector2{v.x * a, v.y * a}; +} + +double MultiLookup::operator/(const Vector2& v1, const Vector2& v2) { + return v1.x / v2.x + v1.y / v2.y; +} + +MultiLookup::Vector2 MultiLookup::operator/(const Vector2& v, double a) { + return Vector2{v.x / a, v.y / a}; +} + +double MultiLookup::operator+(const Vector2& v1, const Vector2& v2) { + return v1.x + v2.x + v1.y + v2.y; +} + +MultiLookup::Vector2 MultiLookup::operator+(const Vector2& v, double a) { + return Vector2{v.x + a, v.y + a}; +} + +double MultiLookup::operator-(const Vector2& v1, const Vector2& v2) { + return v1.x - v2.x + v1.y - v2.y; +} + +MultiLookup::Vector2 MultiLookup::operator-(const Vector2& v, double a) { + return Vector2{v.x - a, v.y - a}; +} + +//- for unary functions +SomeGlobalNumber operator-(const SomeGlobalNumber& n) { + return SomeGlobalNumber{-n.i}; +} + +SomeGlobalNumber operator+(const SomeGlobalNumber& n) { + return SomeGlobalNumber{+n.i}; +} + +SomeGlobalNumber operator~(const SomeGlobalNumber& n) { + return SomeGlobalNumber{~n.i}; +} + +Unary::SomeNumber Unary::operator-(const SomeNumber& n) { + return SomeNumber{-n.i}; +} + +Unary::SomeNumber Unary::operator+(const SomeNumber& n) { + return SomeNumber{+n.i}; +} + +Unary::SomeNumber Unary::operator~(const SomeNumber& n) { + return SomeNumber{~n.i}; +} diff --git a/test/cpp/operators.h b/test/cpp/operators.h new file mode 100644 index 0000000..e284779 --- /dev/null +++ b/test/cpp/operators.h @@ -0,0 +1,350 @@ +#ifndef CPPJIT_TEST_OPERATORS_H +#define CPPJIT_TEST_OPERATORS_H + +class number { +public: + number() { m_int = 0; } + number(int i) { m_int = i; } + + number operator+(const number& n) const { return number(m_int + n.m_int); } + number operator+(int n) const { return number(m_int + n); } + number operator-(const number& n) const { return number(m_int - n.m_int); } + number operator-(int n) const { return number(m_int - n); } + number operator*(const number& n) const { return number(m_int * n.m_int); } + number operator*(int n) const { return number(m_int * n); } + number operator/(const number& n) const { return number(m_int / n.m_int); } + number operator/(int n) const { return number(m_int / n); } + number operator%(const number& n) const { return number(m_int % n.m_int); } + number operator%(int n) const { return number(m_int % n); } + + number& operator+=(const number& n) { + m_int += n.m_int; + return *this; + } + number& operator-=(const number& n) { + m_int -= n.m_int; + return *this; + } + number& operator*=(const number& n) { + m_int *= n.m_int; + return *this; + } + number& operator/=(const number& n) { + m_int /= n.m_int; + return *this; + } + number& operator%=(const number& n) { + m_int %= n.m_int; + return *this; + } + + number operator-() { return number(-m_int); } + + bool operator<(const number& n) const { return m_int < n.m_int; } + bool operator>(const number& n) const { return m_int > n.m_int; } + bool operator<=(const number& n) const { return m_int <= n.m_int; } + bool operator>=(const number& n) const { return m_int >= n.m_int; } + bool operator!=(const number& n) const { return m_int != n.m_int; } + bool operator==(const number& n) const { return m_int == n.m_int; } + + operator bool() { return m_int != 0; } + + number operator&(const number& n) const { return number(m_int & n.m_int); } + number operator|(const number& n) const { return number(m_int | n.m_int); } + number operator^(const number& n) const { return number(m_int ^ n.m_int); } + + number& operator&=(const number& n) { + m_int &= n.m_int; + return *this; + } + number& operator|=(const number& n) { + m_int |= n.m_int; + return *this; + } + number& operator^=(const number& n) { + m_int ^= n.m_int; + return *this; + } + + number operator<<(int i) const { return number(m_int << i); } + number operator>>(int i) const { return number(m_int >> i); } + +private: + int m_int; +}; + +//---------------------------------------------------------------------------- +struct operator_char_star { // for testing user-defined implicit casts + operator_char_star() : m_str((char*)"operator_char_star") {} + operator char*() { return m_str; } + char* m_str; +}; + +struct operator_const_char_star { + operator_const_char_star() : m_str("operator_const_char_star") {} + operator const char*() { return m_str; } + const char* m_str; +}; + +struct operator_int { + operator int() { return m_int; } + int m_int; +}; + +struct operator_long { + operator long() { return m_long; } + long m_long; +}; + +struct operator_double { + operator double() { return m_double; } + double m_double; +}; + +struct operator_short { + operator short() { return m_short; } + unsigned short m_short; +}; + +struct operator_unsigned_int { + operator unsigned int() { return m_uint; } + unsigned int m_uint; +}; + +struct operator_unsigned_long { + operator unsigned long() { return m_ulong; } + unsigned long m_ulong; +}; + +struct operator_float { + operator float() { return m_float; } + float m_float; +}; + +//---------------------------------------------------------------------------- +class v_opeq_base { +public: + v_opeq_base(int val); + virtual ~v_opeq_base(); + + virtual bool operator==(const v_opeq_base& other); + +protected: + int m_val; +}; + +class v_opeq_derived : public v_opeq_base { +public: + v_opeq_derived(int val); + virtual ~v_opeq_derived(); + + virtual bool operator==(const v_opeq_derived& other); +}; + +//---------------------------------------------------------------------------- +class YAMatrix1 { // YetAnotherMatrix class for indexing tests +public: + YAMatrix1() : m_val(42) {} + + int& operator()(int i, int j); + const int& operator()(int i, int j) const; + + int m_val; +}; + +class YAMatrix2 { +public: + YAMatrix2() : m_val(42) {} + + int& operator[](int i); + const int& operator[](int i) const; + + int m_val; +}; + +class YAMatrix3 { +public: + YAMatrix3() : m_val(42) {} + + int& operator()(int i, int j); + const int& operator()(int i, int j) const; + + int& operator[](int i); + const int& operator[](int i) const; + + int m_val; +}; + +class YAMatrix4 { +public: + YAMatrix4() : m_val(42) {} + + // opposite order of method declarations from YAMatrix3 + int& operator[](int i); + const int& operator[](int i) const; + + int& operator()(int i, int j); + const int& operator()(int i, int j) const; + + int m_val; +}; + +class YAMatrix5 { +public: + YAMatrix5() : m_val(42) {} + + int& operator[](int i); + int operator[](int i) const; + + int& operator()(int i, int j); + int operator()(int i, int j) const; + + int m_val; +}; + +class YAMatrix6 { +public: + YAMatrix6() : m_val(42) {} + + int& operator[](int i); + int operator[](int i) const; + + int& operator()(int i, int j); + + int m_val; +}; + +class YAMatrix7 { +public: + YAMatrix7() : m_val(42) {} + + int& operator[](int i); + int& operator()(int i, int j); + + int m_val; +}; + +//---------------------------------------------------------------------------- +// templated operator +class TOIClass { +public: + template bool operator<(T t) { return true; } +}; + +//---------------------------------------------------------------------------- +// __radd__/__rmul__, (non-)associative +class AssocADD { +public: + AssocADD(double d) : fx(d) {} + bool operator==(double d) { return d == fx; } + + friend AssocADD operator+(int n, AssocADD& a); + friend AssocADD operator+(AssocADD& a, int n); + +private: + double fx; +}; + +AssocADD operator+(int n, AssocADD& a); +AssocADD operator+(AssocADD& a, int n); + +class NonAssocRADD { +public: + NonAssocRADD(double d) : fx(d) {} + bool operator==(double d) { return d == fx; } + + friend NonAssocRADD operator+(int n, NonAssocRADD& a); + +private: + double fx; +}; + +NonAssocRADD operator+(int n, NonAssocRADD& a); + +class AssocMUL { +public: + AssocMUL(double d) : fx(d) {} + bool operator==(double d) { return d == fx; } + + friend AssocMUL operator*(int n, AssocMUL& m); + friend AssocMUL operator*(AssocMUL& m, int n); + +private: + double fx; +}; + +AssocMUL operator*(int n, AssocMUL& m); +AssocMUL operator*(AssocMUL& m, int n); + +class NonAssocRMUL { +public: + NonAssocRMUL(double d) : fx(d) {} + bool operator==(double d) { return d == fx; } + + friend NonAssocRMUL operator*(int n, NonAssocRMUL& m); + +private: + double fx; +}; + +NonAssocRMUL operator*(int n, NonAssocRMUL& m); + +//---------------------------------------------------------------------------- +// multi-lookup +namespace MultiLookup { + +struct Vector2 { + Vector2(double d1, double d2) : x(d1), y(d2) {} + double x, y; +}; + +double operator*(const Vector2&, const Vector2&); +Vector2 operator*(const Vector2&, double); + +double operator/(const Vector2&, const Vector2&); +Vector2 operator/(const Vector2&, double); + +double operator+(const Vector2&, const Vector2&); +Vector2 operator+(const Vector2&, double); + +double operator-(const Vector2&, const Vector2&); +Vector2 operator-(const Vector2&, double); + +} // namespace MultiLookup + +//---------------------------------------------------------------------------- +// unary functions +struct SomeGlobalNumber { + SomeGlobalNumber(int n) : i(n) {} + int i; +}; + +SomeGlobalNumber operator-(const SomeGlobalNumber&); +SomeGlobalNumber operator+(const SomeGlobalNumber&); +SomeGlobalNumber operator~(const SomeGlobalNumber&); + +namespace Unary { + +struct SomeNumber { + SomeNumber(int n) : i(n) {} + int i; +}; + +SomeNumber operator-(const SomeNumber&); +SomeNumber operator+(const SomeNumber&); +SomeNumber operator~(const SomeNumber&); + +} // namespace Unary + +//---------------------------------------------------------------------------- +// comma operator (mapped to __comma__) +struct CommaOperator { + CommaOperator(int i) : fInt(i) {} + CommaOperator& operator,(int i) { + fInt += i; + return *this; + } + int fInt; +}; + +#endif // !CPPJIT_TEST_OPERATORS_H diff --git a/test/overloads.cxx b/test/cpp/overloads.cxx similarity index 51% rename from test/overloads.cxx rename to test/cpp/overloads.cxx index eeab7dd..dd89b3f 100644 --- a/test/overloads.cxx +++ b/test/cpp/overloads.cxx @@ -1,30 +1,41 @@ #include "overloads.h" - -a_overload::a_overload() { i1 = 42; i2 = -1; } - -ns_a_overload::a_overload::a_overload() { i1 = 88; i2 = -34; } +a_overload::a_overload() { + i1 = 42; + i2 = -1; +} + +ns_a_overload::a_overload::a_overload() { + i1 = 88; + i2 = -34; +} int ns_a_overload::b_overload::f(const std::vector* v) { return (*v)[0]; } -ns_b_overload::a_overload::a_overload() { i1 = -33; i2 = 89; } +ns_b_overload::a_overload::a_overload() { + i1 = -33; + i2 = 89; +} -b_overload::b_overload() { i1 = -2; i2 = 13; } +b_overload::b_overload() { + i1 = -2; + i2 = 13; +} c_overload::c_overload() {} -int c_overload::get_int(a_overload* a) { return a->i1; } +int c_overload::get_int(a_overload* a) { return a->i1; } int c_overload::get_int(ns_a_overload::a_overload* a) { return a->i1; } int c_overload::get_int(ns_b_overload::a_overload* a) { return a->i1; } -int c_overload::get_int(short* p) { return *p; } -int c_overload::get_int(b_overload* b) { return b->i2; } -int c_overload::get_int(int* p) { return *p; } +int c_overload::get_int(short* p) { return *p; } +int c_overload::get_int(b_overload* b) { return b->i2; } +int c_overload::get_int(int* p) { return *p; } d_overload::d_overload() {} -int d_overload::get_int(int* p) { return *p; } -int d_overload::get_int(b_overload* b) { return b->i2; } -int d_overload::get_int(short* p) { return *p; } +int d_overload::get_int(int* p) { return *p; } +int d_overload::get_int(b_overload* b) { return b->i2; } +int d_overload::get_int(short* p) { return *p; } int d_overload::get_int(ns_b_overload::a_overload* a) { return a->i1; } int d_overload::get_int(ns_a_overload::a_overload* a) { return a->i1; } -int d_overload::get_int(a_overload* a) { return a->i1; } +int d_overload::get_int(a_overload* a) { return a->i1; } class bb_ol {}; bb_ol* get_bb_ol() { return new bb_ol{}; } @@ -33,18 +44,20 @@ dd_ol* get_dd_ol() { return new dd_ol{}; } more_overloads::more_overloads() {} std::string more_overloads::call(const aa_ol&) { return "aa_ol"; } -std::string more_overloads::call(const bb_ol&, void* n) { n = 0; return "bb_ol"; } +std::string more_overloads::call(const bb_ol&, void* n) { + n = 0; + return "bb_ol"; +} std::string more_overloads::call(const cc_ol&) { return "cc_ol"; } std::string more_overloads::call(const dd_ol&) { return "dd_ol"; } std::string more_overloads::call_unknown(const dd_ol&) { return "dd_ol"; } -std::string more_overloads::call(double) { return "double"; } -std::string more_overloads::call(int) { return "int"; } -std::string more_overloads::call1(int) { return "int"; } +std::string more_overloads::call(double) { return "double"; } +std::string more_overloads::call(int) { return "int"; } +std::string more_overloads::call1(int) { return "int"; } std::string more_overloads::call1(double) { return "double"; } - more_overloads2::more_overloads2() {} std::string more_overloads2::call(const bb_ol&) { return "bb_olref"; } std::string more_overloads2::call(const bb_ol*) { return "bb_olptr"; } @@ -52,16 +65,13 @@ std::string more_overloads2::call(const bb_ol*) { return "bb_olptr"; } std::string more_overloads2::call(const dd_ol*, int) { return "dd_olptr"; } std::string more_overloads2::call(const dd_ol&, int) { return "dd_olref"; } - -double calc_mean(long n, const float* a) { return calc_mean(n, a); } -double calc_mean(long n, const double* a) { return calc_mean(n, a); } -double calc_mean(long n, const int* a) { return calc_mean(n, a); } -double calc_mean(long n, const short* a) { return calc_mean(n, a); } -double calc_mean(long n, const long* a) { return calc_mean(n, a); } - +double calc_mean(long n, const float* a) { return calc_mean(n, a); } +double calc_mean(long n, const double* a) { return calc_mean(n, a); } +double calc_mean(long n, const int* a) { return calc_mean(n, a); } +double calc_mean(long n, const short* a) { return calc_mean(n, a); } +double calc_mean(long n, const long* a) { return calc_mean(n, a); } std::string more_overloads3::slice(size_t) const { return "const"; } std::string more_overloads3::slice(size_t) { return "non-const"; } std::string more_overloads3::slice(size_t, size_t) const { return "const"; } std::string more_overloads3::slice(size_t, size_t) { return "non-const"; } - diff --git a/test/cpp/overloads.h b/test/cpp/overloads.h new file mode 100644 index 0000000..4d330fa --- /dev/null +++ b/test/cpp/overloads.h @@ -0,0 +1,116 @@ +#include +#include + +class a_overload { +public: + a_overload(); + int i1, i2; +}; + +namespace ns_a_overload { +class a_overload { +public: + a_overload(); + int i1, i2; +}; + +class b_overload { +public: + int f(const std::vector* v); +}; +} // namespace ns_a_overload + +namespace ns_b_overload { +class a_overload { +public: + a_overload(); + int i1, i2; +}; +} // namespace ns_b_overload + +class b_overload { +public: + b_overload(); + int i1, i2; +}; + +class c_overload { +public: + c_overload(); + int get_int(a_overload* a); + int get_int(ns_a_overload::a_overload* a); + int get_int(ns_b_overload::a_overload* a); + int get_int(short* p); + int get_int(b_overload* b); + int get_int(int* p); +}; + +class d_overload { +public: + d_overload(); + // int get_int(void* p) { return *(int*)p; } + int get_int(int* p); + int get_int(b_overload* b); + int get_int(short* p); + int get_int(ns_b_overload::a_overload* a); + int get_int(ns_a_overload::a_overload* a); + int get_int(a_overload* a); +}; + +class aa_ol {}; +class bb_ol; +bb_ol* get_bb_ol(); +class cc_ol {}; +class dd_ol; +dd_ol* get_dd_ol(); + +class more_overloads { +public: + more_overloads(); + std::string call(const aa_ol&); + std::string call(const bb_ol&, void* n = 0); + std::string call(const cc_ol&); + std::string call(const dd_ol&); + + std::string call_unknown(const dd_ol&); + + std::string call(double); + std::string call(int); + std::string call1(int); + std::string call1(double); +}; + +class more_overloads2 { +public: + more_overloads2(); + std::string call(const bb_ol&); + std::string call(const bb_ol*); + + std::string call(const dd_ol*, int); + std::string call(const dd_ol&, int); +}; + +template double calc_mean(long n, const T* a) { + double sum = 0., sumw = 0.; + const T* end = a + n; + while (a != end) { + sum += *a++; + sumw += 1; + } + + return sum / sumw; +} + +double calc_mean(long n, const float* a); +double calc_mean(long n, const double* a); +double calc_mean(long n, const int* a); +double calc_mean(long n, const short* a); +double calc_mean(long n, const long* a); + +class more_overloads3 { +public: + std::string slice(size_t) const; + std::string slice(size_t); + std::string slice(size_t, size_t); + std::string slice(size_t, size_t) const; +}; diff --git a/test/pythonizables.cxx b/test/cpp/pythonizables.cxx similarity index 52% rename from test/pythonizables.cxx rename to test/cpp/pythonizables.cxx index 8729067..c6c7ead 100644 --- a/test/pythonizables.cxx +++ b/test/cpp/pythonizables.cxx @@ -2,21 +2,21 @@ #include "Python.h" - //=========================================================================== -pyzables::NakedBuffers::NakedBuffers(int size, double valx, double valy) : m_size(size) { - m_Xbuf = new double[size]; - m_Ybuf = new double[size]; - - for (int i=0; i(new Countable); } +void pyzables::renew_mine() { + mine = std::shared_ptr(new Countable); +} pyzables::SharedCountable_t pyzables::gime_mine() { return mine; } pyzables::SharedCountable_t* pyzables::gime_mine_ptr() { return &mine; } pyzables::SharedCountable_t& pyzables::gime_mine_ref() { return mine; } -unsigned int pyzables::pass_mine_sp(std::shared_ptr ptr) { return ptr->m_check; } -unsigned int pyzables::pass_mine_sp_ref(std::shared_ptr& ptr) { return ptr->m_check; } -unsigned int pyzables::pass_mine_sp_ptr(std::shared_ptr* ptr) { return (*ptr)->m_check; } +unsigned int pyzables::pass_mine_sp(std::shared_ptr ptr) { + return ptr->m_check; +} +unsigned int pyzables::pass_mine_sp_ref(std::shared_ptr& ptr) { + return ptr->m_check; +} +unsigned int pyzables::pass_mine_sp_ptr(std::shared_ptr* ptr) { + return (*ptr)->m_check; +} unsigned int pyzables::pass_mine_rp(Countable c) { return c.m_check; } -unsigned int pyzables::pass_mine_rp_ref(const Countable& c) { return c.m_check; } -unsigned int pyzables::pass_mine_rp_ptr(const Countable* c) { return c->m_check; } - -pyzables::Countable* pyzables::gime_naked_countable() { return new Countable{}; } +unsigned int pyzables::pass_mine_rp_ref(const Countable& c) { + return c.m_check; +} +unsigned int pyzables::pass_mine_rp_ptr(const Countable* c) { + return c->m_check; +} +pyzables::Countable* pyzables::gime_naked_countable() { + return new Countable{}; +} //=========================================================================== pyzables::WithCallback1::WithCallback1(int i) : m_int(i) {} @@ -62,20 +71,22 @@ pyzables::WithCallback1::WithCallback1(int i) : m_int(i) {} int pyzables::WithCallback1::get_int() { return m_int; } void pyzables::WithCallback1::set_int(int i) { m_int = i; } -static inline void replace_method_name(PyObject* klass, const char* n1, const char* n2) { - PyObject* meth = PyObject_GetAttrString(klass, n1); - PyObject_SetAttrString(klass, n2, meth); - Py_DECREF(meth); - PyObject_DelAttrString(klass, n1); +static inline void replace_method_name(PyObject* klass, const char* n1, + const char* n2) { + PyObject* meth = PyObject_GetAttrString(klass, n1); + PyObject_SetAttrString(klass, n2, meth); + Py_DECREF(meth); + PyObject_DelAttrString(klass, n1); } -void pyzables::WithCallback1::WithCallback1::__cppyy_explicit_pythonize__(PyObject* klass, const std::string& name) { -// change methods to camel case - replace_method_name(klass, "get_int", "GetInt"); - replace_method_name(klass, "set_int", "SetInt"); +void pyzables::WithCallback1::WithCallback1::__cppjit_explicit_pythonize__( + PyObject* klass, const std::string& name) { + // change methods to camel case + replace_method_name(klass, "get_int", "GetInt"); + replace_method_name(klass, "set_int", "SetInt"); -// store the provided class name - klass_name = name; + // store the provided class name + klass_name = name; } std::string pyzables::WithCallback1::klass_name{"not set"}; @@ -85,16 +96,17 @@ pyzables::WithCallback2::WithCallback2(int i) : m_int(i) {} int pyzables::WithCallback2::get_int() { return m_int; } void pyzables::WithCallback2::set_int(int i) { m_int = i; } -void pyzables::WithCallback2::WithCallback2::__cppyy_pythonize__(PyObject* klass, const std::string& name) { -// change methods to camel case - replace_method_name(klass, "get_int", "GetInt"); - replace_method_name(klass, "set_int", "SetInt"); +void pyzables::WithCallback2::WithCallback2::__cppjit_pythonize__( + PyObject* klass, const std::string& name) { + // change methods to camel case + replace_method_name(klass, "get_int", "GetInt"); + replace_method_name(klass, "set_int", "SetInt"); -// store the provided class name - klass_name = name; + // store the provided class name + klass_name = name; } std::string pyzables::WithCallback2::klass_name{"not set"}; -int pyzables::WithCallback3::get_int() { return 2*m_int; } -void pyzables::WithCallback3::set_int(int i) { m_int = 2*i; } +int pyzables::WithCallback3::get_int() { return 2 * m_int; } +void pyzables::WithCallback3::set_int(int i) { m_int = 2 * i; } diff --git a/test/cpp/pythonizables.h b/test/cpp/pythonizables.h new file mode 100644 index 0000000..3ec1c6f --- /dev/null +++ b/test/cpp/pythonizables.h @@ -0,0 +1,164 @@ +#include +#include +#include + +// Python +struct _object; +typedef _object PyObject; + +namespace pyzables { + +//=========================================================================== +class SomeDummy1 {}; +class SomeDummy2 {}; + +//=========================================================================== +class NakedBuffers { +public: + NakedBuffers(int size, double valx, double valy); + NakedBuffers(const NakedBuffers&) = delete; + NakedBuffers& operator=(const NakedBuffers&) = delete; + ~NakedBuffers(); + +public: + int GetN(); + double* GetX(); + double* GetY(); + +private: + double* m_Xbuf; + double* m_Ybuf; + int m_size; +}; + +template class NakedBuffers2 { +public: + NakedBuffers2(int size, double valx, double valy) + : m_Xbuf(size), m_Ybuf(size) { + for (int i = 0; i < size; ++i) { + m_Xbuf[i] = valx * i; + m_Ybuf[i] = valy * i; + } + } + +public: + int GetN() { return m_Xbuf.size(); } + double* GetX() { return m_Xbuf.data(); } + double* GetY() { return m_Ybuf.data(); } + +private: + C m_Xbuf; + C m_Ybuf; + int m_size; +}; + +class Vector : public std::vector { +public: + Vector(int size) : std::vector(size) {} +}; + +//=========================================================================== +class MyBase { +public: + virtual ~MyBase(); +}; +class MyDerived : public MyBase { +public: + virtual ~MyDerived(); +}; + +MyBase* GimeDerived(); + +//=========================================================================== +class Countable { +public: + Countable() { ++sInstances; } + Countable(const Countable&) { ++sInstances; } + Countable& operator=(const Countable&) { return *this; } + ~Countable() { --sInstances; } + +public: + virtual const char* say_hi() { return "Hi!"; } + +public: + unsigned int m_check = 0xcdcdcdcd; + +public: + static int sInstances; +}; + +typedef std::shared_ptr SharedCountable_t; +extern SharedCountable_t mine; + +void renew_mine(); + +SharedCountable_t gime_mine(); +SharedCountable_t* gime_mine_ptr(); +SharedCountable_t& gime_mine_ref(); + +unsigned int pass_mine_sp(SharedCountable_t p); +unsigned int pass_mine_sp_ref(SharedCountable_t& p); +unsigned int pass_mine_sp_ptr(SharedCountable_t* p); + +unsigned int pass_mine_rp(Countable); +unsigned int pass_mine_rp_ref(const Countable&); +unsigned int pass_mine_rp_ptr(const Countable*); + +Countable* gime_naked_countable(); + +//=========================================================================== +class unknown_iterator; +class IndexableBase { +public: + unknown_iterator* begin() { return nullptr; } + unknown_iterator* end() { return (unknown_iterator*)1; } + int operator[](int) { return 42; } + int size() { return 1; } +}; + +class IndexableDerived : public IndexableBase {}; + +//=========================================================================== +class WithCallback1 { +public: + WithCallback1(int i); + +public: + int get_int(); + void set_int(int i); + +private: + int m_int; + +public: + static void __cppjit_explicit_pythonize__(PyObject* klass, + const std::string&); + static std::string klass_name; +}; + +class WithCallback2 { +public: + WithCallback2(int i); + +public: + int get_int(); + void set_int(int i); + +protected: + int m_int; + +public: + static void __cppjit_pythonize__(PyObject* klass, const std::string&); + static std::string klass_name; +}; + +class WithCallback3 : public WithCallback2 { +public: + using WithCallback2::WithCallback2; + +public: + int get_int(); + void set_int(int i); +}; + +} // namespace pyzables diff --git a/test/cpp/std_streams.cxx b/test/cpp/std_streams.cxx new file mode 100644 index 0000000..dcbb599 --- /dev/null +++ b/test/cpp/std_streams.cxx @@ -0,0 +1,5 @@ +#include "std_streams.h" + +#ifndef _WIN32 +template class std::basic_ios>; +#endif diff --git a/test/std_streams.h b/test/cpp/std_streams.h similarity index 63% rename from test/std_streams.h rename to test/cpp/std_streams.h index e676223..db5cac8 100644 --- a/test/std_streams.h +++ b/test/cpp/std_streams.h @@ -1,4 +1,4 @@ -#ifndef STD_STREAMS_H +#ifndef STD_STREAMS_H #define STD_STREAMS_H 1 #ifndef __CLING__ @@ -8,7 +8,7 @@ #ifndef __CLING__ #ifndef _WIN32 -extern template class std::basic_ios >; +extern template class std::basic_ios>; #endif #endif diff --git a/test/cpp/stltypes.cxx b/test/cpp/stltypes.cxx new file mode 100644 index 0000000..6dbd7d2 --- /dev/null +++ b/test/cpp/stltypes.cxx @@ -0,0 +1,83 @@ +#include "stltypes.h" + +#include + +//- adverse effect of implicit conversion on vector +int vectest_ol1(const std::vector&) { return 1; } +int vectest_ol1(std::string) { return 2; } +int vectest_ol2(std::string) { return 2; } +int vectest_ol2(const std::vector&) { return 1; } + +//- helpers for testing array +int ArrayTest::get_pp_px(Point** p, int idx) { return p[idx]->px; } + +int ArrayTest::get_pp_py(Point** p, int idx) { return p[idx]->py; } + +int ArrayTest::get_pa_px(Point* p[], int idx) { return p[idx]->px; } + +int ArrayTest::get_pa_py(Point* p[], int idx) { return p[idx]->py; } + +// helpers for string testing +std::string str_array_1[3] = {"a", "b", "c"}; +std::string str_array_2[] = {"d", "e", "f", "g"}; +std::string str_array_3[3][2] = {{"a", "b"}, {"c", "d"}, {"e", "f"}}; +std::string str_array_4[4][2][2] = { + {{"a", "b"}, {"c", "d"}}, + {{"e", "f"}, {"g", "h"}}, + {{"i", "j"}, {"k", "l"}}, + {{"m", "n"}, {"o", "p"}}, +}; + +// helpers for mixing unicode and std::string +size_t UnicodeAndSTL::get_size(std::string s) { return s.size(); } +size_t UnicodeAndSTL::get_size_cr(const std::string& s) { return s.size(); } +size_t UnicodeAndSTL::get_size_cc(const char* s) { return strlen(s); } +size_t UnicodeAndSTL::get_size_w(std::wstring s) { return s.size(); } +size_t UnicodeAndSTL::get_size_wcr(const std::wstring& s) { return s.size(); } +std::string UnicodeAndSTL::get_string(std::string s) { return s; } +std::string UnicodeAndSTL::get_string_cr(const std::string& s) { return s; } +std::string UnicodeAndSTL::get_string_cc(const char* s) { return s; } +std::wstring UnicodeAndSTL::get_string_w(std::wstring s) { return s; } +std::wstring UnicodeAndSTL::get_string_wcr(const std::wstring& s) { return s; } + +// helpers for string_view testing +std::string_view::size_type StringViewTest::count(const std::string_view arg) { + return arg.size(); +} + +std::string_view::size_type +StringViewTest::count_cr(const std::string_view& arg) { + return arg.size(); +} + +// helper for exception base class testing +int MyError::s_count = 0; +int MyError::get_count() { return s_count; } + +MyError::MyError(const std::string& msg) : fMsg(msg) { s_count += 1; } + +MyError::MyError(const MyError& other) : fMsg(other.fMsg) { s_count += 1; } + +MyError::~MyError() { s_count -= 1; } + +const char* MyError::what() const throw() { return fMsg.c_str(); } + +int GetMyErrorCount() { return MyError::s_count; } + +YourError::YourError(const std::string& msg) : MyError(msg) {} +YourError::YourError(const YourError& s) : MyError(s) {} + +ErrorNamespace::MyError::MyError(const std::string& msg) : fMsg(msg) {} +const char* ErrorNamespace::MyError::what() const throw() { + return fMsg.c_str(); +} + +void ErrorNamespace::throw_error(int i) { + if (i == 0) + throw ::MyError("first error"); + else if (i == 1) + throw ::YourError("second error"); + else if (i == 2) + throw MyError("third error"); + throw YourError("fourth error"); +} diff --git a/test/cpp/stltypes.h b/test/cpp/stltypes.h new file mode 100644 index 0000000..c354879 --- /dev/null +++ b/test/cpp/stltypes.h @@ -0,0 +1,261 @@ +#ifndef CPPJIT_TEST_STLTYPES_H +#define CPPJIT_TEST_STLTYPES_H + +#include "datatypes.h" // for gbl.N +#include +#include +#include +#include +#include +#include +#include +#include + +//- basic example class +class just_a_class { +public: + int m_i; +}; + +// enum for vector of enums setitem tests +enum VecTestEnum { EVal1 = 1, EVal2 = 3 }; + +namespace VecTestEnumNS { +enum VecTestEnum { EVal1 = 5, EVal2 = 42 }; +} + +//- adverse effect of implicit conversion on vector +int vectest_ol1(const std::vector&); +int vectest_ol1(std::string); +int vectest_ol2(std::string); +int vectest_ol2(const std::vector&); + +//- class with lots of std::[w]string handling +template class stringy_class { +public: + stringy_class(const typename S::value_type* s) : m_string(s) {} + + S get_string1() { return m_string; } + void get_string2(S& s) { s = m_string; } + + void set_string1(const S& s) { m_string = s; } + void set_string2(S s) { m_string = s; } + + S m_string; +}; + +typedef stringy_class stringy_class_t; +typedef stringy_class wstringy_class_t; + +//- class that has an STL-like interface +class no_dict_available; + +template class stl_like_class { +public: + no_dict_available* begin() { return 0; } + no_dict_available* end() { return (no_dict_available*)1; } + int size() { return 4; } + int operator[](int i) { return i; } + std::string operator[](double) { return "double"; } + std::string operator[](const std::string&) { return "string"; } +}; + +namespace { +stl_like_class stlc_1; +} + +//- similar, but now the iterators don't work b/c they don't compile +template class stl_like_class2 { +protected: + value_type fData[sz]; + +public: + static const size_t size() { return sz; } + value_type& operator[](ptrdiff_t i) { return fData[i]; } +}; + +template +class stl_like_class3 : public stl_like_class2 { + using stl_like_class2::fData; + +public: + size_t size() { return sz; } + value_type& begin() { return fData; } + value_type& end() { return fData + sz; } +}; + +class stl_like_class4 { +public: + struct iterator { + iterator(int p) : pos(p) {} + int operator++() { + ++pos; + return pos; + } + bool operator!=(const iterator& other) { return pos != other.pos; } + int operator*() { return pos; } + int pos; + }; + iterator begin() { return iterator{0}; } + iterator end() { return iterator{10}; } +}; + +class stl_like_class5 { +public: + struct iterator { + iterator(int p) : pos(p) {} + int operator++(int) { + int val = pos; + ++pos; + return val; + } + bool operator!=(const iterator& other) { return pos != other.pos; } + int operator*() { return pos; } + int pos; + }; + iterator begin() { return iterator{0}; } + iterator end() { return iterator{10}; } +}; + +class stl_like_class6 { +public: + struct some_name { + some_name(int p) : pos(p) {} + int operator++() { + ++pos; + return pos; + } + bool operator!=(const some_name& other) { return pos != other.pos; } + int operator*() { return pos; } + int pos; + }; + some_name begin() { return some_name{0}; } + some_name end() { return some_name{10}; } +}; + +struct some_name7 { + some_name7(int p) : pos(p) {} + int operator++() { + ++pos; + return pos; + } + bool operator!=(const some_name7& other) { return pos != other.pos; } + int operator*() { return pos; } + int pos; +}; + +class stl_like_class7 { +public: + some_name7 begin() { return some_name7{0}; } + some_name7 end() { return some_name7{10}; } +}; + +// similar, but now with a base/derives split +class stl_like_class_base { +protected: + std::vector fVec; + +public: + void push_back(double d) { fVec.push_back(d); } + std::vector::size_type size() { return fVec.size(); } +}; + +class stl_like_class8 : public stl_like_class_base { +public: + double& operator[](int idx) { return fVec[idx]; } +}; + +class stl_like_class9 : public stl_like_class_base { +public: + double* begin() { return fVec.data(); } + double* end() { return fVec.data() + fVec.size(); } +}; + +//- helpers for testing array +namespace ArrayTest { + +struct Point { + Point() : px(0), py(0) {} + Point(int x, int y) : px(x), py(y) {} + int px, py; +}; + +int get_pp_px(Point** p, int idx); +int get_pp_py(Point** p, int idx); +int get_pa_px(Point* p[], int idx); +int get_pa_py(Point* p[], int idx); + +} // namespace ArrayTest + +// helpers for string testing +extern std::string str_array_1[3]; +extern std::string str_array_2[]; +extern std::string str_array_3[3][2]; +extern std::string str_array_4[4][2][2]; + +// helpers for mixing unicode and std::string +namespace UnicodeAndSTL { +size_t get_size(std::string s); +size_t get_size_cr(const std::string& s); +size_t get_size_cc(const char* s); +size_t get_size_w(std::wstring s); +size_t get_size_wcr(const std::wstring& s); +std::string get_string(std::string s); +std::string get_string_cr(const std::string& s); +std::string get_string_cc(const char* s); +std::wstring get_string_w(std::wstring s); +std::wstring get_string_wcr(const std::wstring& s); +} // namespace UnicodeAndSTL + +// helpers for string_view testing +namespace StringViewTest { +std::string_view::size_type count(const std::string_view arg); +std::string_view::size_type count_cr(const std::string_view& arg); +} // namespace StringViewTest + +// helper for exception base class testing +class MyError : public std::exception { +public: + static int s_count; + static int get_count(); + +public: + explicit MyError(const std::string& msg); + MyError(const MyError&); + virtual ~MyError(); + MyError& operator=(const MyError&) = default; + const char* what() const throw() override; + +private: + std::string fMsg; +}; + +int GetMyErrorCount(); + +class YourError : public MyError { +public: + explicit YourError(const std::string& msg); + YourError(const YourError& s); +}; + +namespace ErrorNamespace { + +class MyError : public std::exception { +public: + explicit MyError(const std::string& msg); + const char* what() const throw() override; + +private: + std::string fMsg; +}; + +class YourError : public MyError { +public: + using MyError::MyError; +}; + +void throw_error(int i); + +} // namespace ErrorNamespace + +#endif // !CPPJIT_TEST_STLTYPES_H diff --git a/test/templates.cxx b/test/cpp/templates.cxx similarity index 57% rename from test/templates.cxx rename to test/cpp/templates.cxx index 90c3ec4..b748e09 100644 --- a/test/templates.cxx +++ b/test/cpp/templates.cxx @@ -1,16 +1,16 @@ #include "templates.h" - // template methods long MyTemplatedMethodClass::get_size() { return -1; } -long MyTemplatedMethodClass::get_char_size() { return (long)sizeof(char); } -long MyTemplatedMethodClass::get_int_size() { return (long)sizeof(int); } -long MyTemplatedMethodClass::get_long_size() { return (long)42; /* "lying" */ } -long MyTemplatedMethodClass::get_float_size() { return (long)sizeof(float); } +long MyTemplatedMethodClass::get_char_size() { return (long)sizeof(char); } +long MyTemplatedMethodClass::get_int_size() { return (long)sizeof(int); } +long MyTemplatedMethodClass::get_long_size() { return (long)42; /* "lying" */ } +long MyTemplatedMethodClass::get_float_size() { return (long)sizeof(float); } long MyTemplatedMethodClass::get_double_size() { return (long)sizeof(double); } -long MyTemplatedMethodClass::get_self_size() { return (long)sizeof(MyTemplatedMethodClass); } - +long MyTemplatedMethodClass::get_self_size() { + return (long)sizeof(MyTemplatedMethodClass); +} // variadic templates #ifdef WIN32 @@ -18,7 +18,6 @@ __declspec(dllexport) #endif std::string some_variadic::gTypeName = ""; - // template with empty body namespace T_WithEmptyBody { @@ -27,15 +26,11 @@ __declspec(dllexport) #endif std::string side_effect = "not set"; -template -void some_empty() { - side_effect = "side effect"; -} +template void some_empty() { side_effect = "side effect"; } template void some_empty(); -} // namespace T_WithRValue - +} // namespace T_WithEmptyBody // The following is hidden from the Cling interpreter, but available to the // linker; it allows for testing whether a function return is picked up from @@ -43,10 +38,9 @@ template void some_empty(); namespace FailedTypeDeducer { -template -class A { +template class A { public: - T result() { return T{42}; } + T result() { return T{42}; } }; template class A; diff --git a/test/cpp/templates.h b/test/cpp/templates.h new file mode 100644 index 0000000..ddf8568 --- /dev/null +++ b/test/cpp/templates.h @@ -0,0 +1,478 @@ +#ifndef CPPJIT_TEST_TEMPLATES_H +#define CPPJIT_TEST_TEMPLATES_H + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +inline std::string demangle_it(const char* name, const char* errmsg) { + int status; + std::string res = abi::__cxa_demangle(name, 0, 0, &status); + if (status != 0) + throw std::runtime_error(errmsg); + return res; +} +#else +inline std::string demangle_it(const char* name, const char*) { + return name; // typeinfo's name() is already demangled +} +#endif + +//=========================================================================== +class MyTemplatedMethodClass { // template methods +public: + template long get_size(A&); + template long get_size(const A&); + + long get_size(); + template long get_size(); + + long get_char_size(); + long get_int_size(); + long get_long_size(); + long get_float_size(); + long get_double_size(); + + long get_self_size(); + +private: + double m_data[3]; +}; + +template long MyTemplatedMethodClass::get_size(A&) { + return sizeof(A); +} + +template long MyTemplatedMethodClass::get_size(const A&) { + return sizeof(A) + 1; +} + +template inline long MyTemplatedMethodClass::get_size() { + return sizeof(B); +} + +// +typedef MyTemplatedMethodClass MyTMCTypedef_t; + +// explicit instantiation +template long MyTemplatedMethodClass::get_size(); +template long MyTemplatedMethodClass::get_size(); + +// "lying" specialization +template <> inline long MyTemplatedMethodClass::get_size() { return 42; } + +//=========================================================================== +// global templated functions +template long global_get_size() { return sizeof(T); } + +template int global_some_foo(T) { return 42; } + +template int global_some_bar() { return T; } + +template int global_some_bar_var(T) { return 13; } + +template struct SomeResult { + F m_retval; +}; + +template +SomeResult global_get_some_result(const I& carrier) { + SomeResult r{}; + r.m_retval = O(carrier[0]); + return r; +} + +//=========================================================================== +// variadic functions +inline bool isSomeInt(int) { return true; } +inline bool isSomeInt(double) { return false; } +template inline bool isSomeInt(Args...) { return false; } + +namespace AttrTesting { + +struct Obj1 { + int var1; +}; +struct Obj2 { + int var2; +}; + +template constexpr auto has_var1(T t) -> decltype(t.var1, true) { + return true; +} + +template constexpr bool has_var1(Args...) { return false; } + +template constexpr bool call_has_var1(T&& t) { + return AttrTesting::has_var1(std::forward(t)); +} + +template struct select_template_arg {}; + +template struct select_template_arg<0, T0, T...> { + typedef T0 type; + typedef type argument; +}; + +template +struct select_template_arg { + typedef typename select_template_arg::type argument; +}; + +} // namespace AttrTesting + +namespace SomeNS { + +template int some_foo(T) { return 42; } + +template int some_bar() { return T; } + +struct SomeStruct { + template static int some_bar() { return T; } +}; + +inline std::string tuplify(std::ostringstream& out) { + out << "NULL)"; + return out.str(); +} + +template +std::string tuplify(std::ostringstream& out, T value, Args... args) { + out << value << ", "; + return tuplify(out, args...); +} + +} // namespace SomeNS + +//=========================================================================== +// using of static data +// TODO: this should live here instead of in test_templates.test08 +/* +template struct BaseClassWithStatic { + static T const ref_value; +}; + +template +T const BaseClassWithStatic::ref_value = 42; + +template +struct DerivedClassUsingStatic : public BaseClassWithStatic { + using BaseClassWithStatic::ref_value; + + explicit DerivedClassUsingStatic(T x) : BaseClassWithStatic() { + m_value = x > ref_value ? ref_value : x; + } + + T m_value; +}; +*/ + +//=========================================================================== +// templated callable +class TemplatedCallable { +public: + template O operator()(const I& in) const { + return O(in); + } +}; + +//=========================================================================== +// templated typedefs +namespace TemplatedTypedefs { + +template +struct BaseWithEnumAndTypedefs { + enum { vsize = _vsize }; + typedef TYPE_IN in_type; + typedef TYPE_OUT out_type; +}; + +template +struct DerivedWithUsing + : public BaseWithEnumAndTypedefs { + typedef BaseWithEnumAndTypedefs base_type; + using base_type::vsize; + using typename base_type::in_type; + typedef typename base_type::in_type in_type_tt; + using typename base_type::out_type; +}; + +struct SomeDummy {}; + +} // namespace TemplatedTypedefs + +//=========================================================================== +// hiding templated methods +namespace TemplateHiding { + +struct Base { + template int callme(T t = T(1)) { return 2 * t; } +}; + +struct Derived : public Base { + int callme(int t = 2) { return t; } +}; + +} // namespace TemplateHiding + +//=========================================================================== +// 'using' of templates +template using DA_vector = std::vector; + +namespace using_problem { + +template struct vector { + vector() : m_val(SZ) {} + T m_val; +}; + +template struct matryoshka { + typedef T type; +}; + +template +struct matryoshka { + typedef vector::type, SZ> type; +}; + +template +using make_vector = typename matryoshka::type; +typedef make_vector iiv_t; +}; // namespace using_problem + +namespace using_problem { + +template class Base { +public: + template R get1(T t) { return t + R{5}; } + T get2() { return T{5}; } + template R get3(T t) { return t + R{5}; } + T get3() { return T{5}; } +}; + +template class Derived : public Base { +public: + typedef Base _Mybase; + using _Mybase::get1; + using _Mybase::get2; + using _Mybase::get3; +}; + +template class Foo { + T x; +}; + +namespace Bar { +using ::using_problem::Foo; +} + +} // namespace using_problem + +//=========================================================================== +// template with r-value +namespace T_WithRValue { + +template bool is_valid(T&& new_value) { return new_value != T{}; } + +} // namespace T_WithRValue + +//=========================================================================== +// variadic templates +namespace some_variadic { + +#ifdef _WIN32 +#ifdef __CLING__ +extern __declspec(dllimport) std::string gTypeName; +#else +extern __declspec(dllexport) std::string gTypeName; +#endif +#else +extern std::string gTypeName; +#endif + +template class A { +public: + A() { gTypeName = demangle_it(typeid(A).name(), "A::A"); } + A(const A&) = default; + A(A&&) = default; + A& operator=(const A&) = default; + A& operator=(A&&) = default; + + template void a(FArgs&&... args) { + gTypeName = demangle_it(typeid(&A::a).name(), "A::a-2"); + } + + template T a_T(FArgs&&... args) { + gTypeName = + demangle_it(typeid(&A::a_T).name(), "A::a_T-2"); + return T{}; + } + + template static void sa(FArgs&&... args) { + gTypeName = demangle_it(typeid(A).name(), "A::sa-1"); + gTypeName += "::"; + gTypeName += + demangle_it(typeid(A::sa).name(), "A::sa-2"); + } + + template static T sa_T(FArgs&&... args) { + gTypeName = demangle_it(typeid(A).name(), "A::sa_T-1"); + gTypeName += "::"; + gTypeName += + demangle_it(typeid(A::sa_T).name(), "A::sa_T-2"); + return T{}; + } +}; + +class B { +public: + B() { gTypeName = demangle_it(typeid(B).name(), "B::B"); } + B(const B&) = default; + B(B&&) = default; + B& operator=(const B&) = default; + B& operator=(B&&) = default; + + template void b(FArgs&&... args) { + gTypeName = demangle_it(typeid(&B::b).name(), "B::b-2"); + } + + template T b_T(FArgs&&... args) { + gTypeName = demangle_it(typeid(&B::b_T).name(), "B::b_T-2"); + return T{}; + } + + template static void sb(FArgs&&... args) { + gTypeName = demangle_it(typeid(B).name(), "B::sb-1"); + gTypeName += "::"; + gTypeName += demangle_it(typeid(B::sb).name(), "B::sb-2"); + } + + template static T sb_T(FArgs&&... args) { + gTypeName = demangle_it(typeid(B).name(), "B::sb_T-1"); + gTypeName += "::"; + gTypeName += demangle_it(typeid(B::sb_T).name(), "B::sb_T-2"); + return T{}; + } +}; + +template void fn(Args&&... args) { + gTypeName = demangle_it(typeid(fn).name(), "fn"); +} + +template T fn_T(Args&&... args) { + gTypeName = demangle_it(typeid(fn).name(), "fn_T"); + return T{}; +} + +} // namespace some_variadic + +//=========================================================================== +// template with empty body +namespace T_WithEmptyBody { + +#ifdef _WIN32 +#ifdef __CLING__ +extern __declspec(dllimport) std::string side_effect; +#else +extern __declspec(dllexport) std::string side_effect; +#endif +#else +extern std::string side_effect; +#endif + +template void some_empty(); + +} // namespace T_WithEmptyBody + +//=========================================================================== +// template with catch-all (void*, void**)overloads +namespace T_WithGreedyOverloads { + +class SomeClass { + double fD; +}; + +class WithGreedy1 { +public: + template int get_size(T*) { return (int)sizeof(T); } + int get_size(void*, bool force = false) { return -1; } +}; + +class WithGreedy2 { +public: + template int get_size(T*) { return (int)sizeof(T); } + int get_size(void**, bool force = false) { return -1; } +}; + +class DoesNotExist; + +class WithGreedy3 { +public: + template int get_size(T*) { return (int)sizeof(T); } + int get_size(DoesNotExist*, bool force = false) { return -1; } +}; + +} // namespace T_WithGreedyOverloads + +//=========================================================================== +// template with overloaded non-templated and templated setitem +namespace TemplateWithSetItem { + +template class MyVec { +private: + std::vector fData; + +public: + using size_type = typename std::vector::size_type; + + MyVec(size_type count) : fData(count) {} + + T& operator[](size_type index) { return fData[index]; } + + // The definition of this templated operator causes the issue + template MyVec operator[](const MyVec& conds) const { + return MyVec(2); + } +}; + +} // namespace TemplateWithSetItem + +//=========================================================================== +// type reduction examples on gmpxx-like template expressions +namespace TypeReduction { + +template struct BinaryExpr; + +template struct Expr { + Expr() {} + Expr(const BinaryExpr&) {} +}; + +template struct BinaryExpr { + BinaryExpr(const Expr&, const Expr&) {} +}; + +template +BinaryExpr operator+(const Expr& e1, const Expr& e2) { + return BinaryExpr(e1, e2); +} + +} // namespace TypeReduction + +//=========================================================================== +// type deduction examples +namespace FailedTypeDeducer { + +template class B { +public: + auto result() { return 5.; } +}; + +extern template class B; + +} // namespace FailedTypeDeducer + +#endif // !CPPJIT_TEST_TEMPLATES_H diff --git a/test/cpp11features.cxx b/test/cpp11features.cxx deleted file mode 100644 index edbe069..0000000 --- a/test/cpp11features.cxx +++ /dev/null @@ -1,69 +0,0 @@ -#include "cpp11features.h" - - -// for std::shared/unique_ptr<> testing -int TestSmartPtr::s_counter = 0; - -std::shared_ptr create_shared_ptr_instance() { - return std::shared_ptr(new TestSmartPtr); -} - -std::unique_ptr create_unique_ptr_instance() { - return std::unique_ptr(new TestSmartPtr); -} - -int TestSmartPtr::get_value() { - return 17; -} - -int DerivedTestSmartPtr::get_value() { - return m_int + 76; -} - -int pass_shared_ptr(std::shared_ptr p) { - return p->get_value(); -} - -int move_shared_ptr(std::shared_ptr&& p) { - return p->get_value(); -} - -int move_unique_ptr(std::unique_ptr&& p) { - return p->get_value(); -} - -int move_unique_ptr_derived(std::unique_ptr&& p) { - return p->get_value(); -} - -TestSmartPtr create_TestSmartPtr_by_value() { - return TestSmartPtr{}; -} - -std::shared_ptr create_shared_ptr_to_derived() { - return std::shared_ptr(new PubDerivedTestSmartPtr); -} - -std::unique_ptr create_unique_ptr_to_derived() { - return std::unique_ptr(new PubDerivedTestSmartPtr); -} - -std::unique_ptr create_unique_ptr_to_offset_derived() { - return std::unique_ptr(new MultiDerivedTestSmartPtr); -} - - -// for move ctors etc. -int TestMoving1::s_move_counter = 0; -int TestMoving1::s_instance_counter = 0; -int TestMoving2::s_move_counter = 0; -int TestMoving2::s_instance_counter = 0; - -void implicit_converion_move(TestMoving2&&) { - /* empty */ -} - - -// for std::function testing -std::function FNCreateTestStructFunc() { return [](const FNTestStruct& t) { return t.t; }; } -std::function FunctionNS::FNCreateTestStructFunc() { return [](const FNTestStruct& t) { return t.t; }; } diff --git a/test/cpp11features.h b/test/cpp11features.h deleted file mode 100644 index 17d7a78..0000000 --- a/test/cpp11features.h +++ /dev/null @@ -1,168 +0,0 @@ -#ifndef CPP11FEATURES_H -#define CPP11FEATURES_H - -#include -#include -#include -#include - - -//=========================================================================== -class TestSmartPtr { // for std::shared/unique_ptr<> testing -public: - static int s_counter; - -public: - TestSmartPtr() { ++s_counter; } - TestSmartPtr(const TestSmartPtr&) { ++s_counter; } - virtual ~TestSmartPtr() { --s_counter; } - -public: - virtual int get_value(); -}; - -std::shared_ptr create_shared_ptr_instance(); -std::unique_ptr create_unique_ptr_instance(); - -class DerivedTestSmartPtr : TestSmartPtr { -public: - DerivedTestSmartPtr(int i) : m_int(i) {} - virtual int get_value(); - -public: - int m_int; -}; - -int pass_shared_ptr(std::shared_ptr p); -int move_shared_ptr(std::shared_ptr&& p); -int move_unique_ptr(std::unique_ptr&& p); -int move_unique_ptr_derived(std::unique_ptr&& p); - -TestSmartPtr create_TestSmartPtr_by_value(); - -// for auto-downcast of objects returned through a smart pointer -class PubDerivedTestSmartPtr : public TestSmartPtr { -public: - int only_in_derived() { return 27; } -}; - -// second base so that the cross-cast to the most derived type needs a -// non-zero pointer adjustment, which the smart pointer's dereferencer can -// not apply consistently (so no down-cast should happen in that case) -class TestSmartPtrIface { -public: - virtual ~TestSmartPtrIface() {} - long m_pad = 0; - int only_in_iface() { return 37; } -}; - -class MultiDerivedTestSmartPtr : public PubDerivedTestSmartPtr, public TestSmartPtrIface { -}; - -std::shared_ptr create_shared_ptr_to_derived(); -std::unique_ptr create_unique_ptr_to_derived(); -std::unique_ptr create_unique_ptr_to_offset_derived(); - -// sinks expecting a smart pointer to the *derived* type; a base-class smart -// pointer (even when its object was auto-down-cast) must not be accepted here -int pass_unique_ptr_to_derived(std::unique_ptr p) { - return p->only_in_derived(); -} - -int pass_shared_ptr_to_derived(std::shared_ptr p) { - return p->only_in_derived(); -} - -// Overloaded function to check if automatic downcasting is consistently -// applied for regular proxy objects and smart pointer proxies. -std::string pass_ptr_overloaded(TestSmartPtr *) { return "TestSmartPtr"; } -std::string pass_ptr_overloaded(PubDerivedTestSmartPtr *) { return "PubDerivedTestSmartPtr"; } -std::string pass_ref_overloaded(TestSmartPtr &) { return "TestSmartPtr"; } -std::string pass_ref_overloaded(PubDerivedTestSmartPtr &) { return "PubDerivedTestSmartPtr"; } -std::string pass_val_overloaded(TestSmartPtr) { return "TestSmartPtr"; } -std::string pass_val_overloaded(PubDerivedTestSmartPtr) { return "PubDerivedTestSmartPtr"; } - - -//=========================================================================== -class TestMoving1 { // for move ctors etc. -public: - static int s_move_counter; - static int s_instance_counter; - -public: - TestMoving1() { ++s_instance_counter; } - TestMoving1(TestMoving1&&) { ++s_move_counter; ++s_instance_counter; } - TestMoving1(const TestMoving1&) { ++s_instance_counter; } - TestMoving1& operator=(TestMoving1&&) { ++s_move_counter; return *this; } - TestMoving1& operator=(TestMoving1&) { return *this; } - ~TestMoving1() { --s_instance_counter; } -}; - -class TestMoving2 { // note opposite method order from TestMoving1 -public: - static int s_move_counter; - static int s_instance_counter; - -public: - TestMoving2() { ++s_instance_counter; } - TestMoving2(const TestMoving1&) { ++s_instance_counter; } - TestMoving2(const TestMoving2&) { ++s_instance_counter; } - TestMoving2(TestMoving2&& other) { ++s_move_counter; ++s_instance_counter; } - TestMoving2& operator=(TestMoving2&) { return *this; } - TestMoving2& operator=(TestMoving2&&) { ++s_move_counter; return *this; } - ~TestMoving2() { --s_instance_counter; } -}; - -void implicit_converion_move(TestMoving2&&); - - -//=========================================================================== -struct TestData { // for initializer list construction - TestData(int i=0) : m_int(i) {} - int m_int; -}; - -struct TestData2 { - TestData2(int i=0) : m_int(i) {} - virtual ~TestData2() {} - int m_int; -}; - -template -class WithInitList { -public: - WithInitList(std::initializer_list ll) : m_data(ll) {} - const T& operator[](int i) { return m_data[i]; } - - int size() { return m_data.size(); } - -private: - std::vector m_data; -}; - - -//=========================================================================== -struct FNTestStruct { // for std::function<> testing - FNTestStruct(int i) : t(i) {} - int t; -}; -std::function FNCreateTestStructFunc(); - -namespace FunctionNS { - struct FNTestStruct { FNTestStruct(int i) : t(i) {} int t; }; - std::function FNCreateTestStructFunc(); -} - - -//=========================================================================== -struct StructWithHash {}; // for std::hash<> testing -struct StructWithoutHash {}; - -namespace std { - template<> - struct hash { - size_t operator()(const StructWithHash&) const { return 17; } - }; -} // namespace std - -#endif // CPP11FEATURES_H diff --git a/test/cpp11features.xml b/test/cpp11features.xml deleted file mode 100644 index e6cf218..0000000 --- a/test/cpp11features.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/crossinheritance.cxx b/test/crossinheritance.cxx deleted file mode 100644 index 8dacb16..0000000 --- a/test/crossinheritance.cxx +++ /dev/null @@ -1,130 +0,0 @@ -#include "crossinheritance.h" - - -// for overridden method checking -CrossInheritance::Base1::~Base1() {} - -int CrossInheritance::Base1::call_get_value(Base1* b) { - return b->get_value(); -} - -int CrossInheritance::Base1::call_sum_value(Base1* b, int i) { - return b->sum_value(i); -} - -int CrossInheritance::Base1::call_sum_all(Base1* b, int i) { - return b->sum_all(i); -} - -int CrossInheritance::Base1::call_sum_all(Base1* b, int i, int j) { - return b->sum_all(i, j); -} - -int CrossInheritance::Base1::sum_pass_value(Base1* b) { - int a = 0; - a += b->pass_value1(1); - int i = 2; - a += b->pass_value2(i); - a += b->pass_value3(3); - a += b->pass_value4(*b); - a += b->pass_value5(*b); - return a; -} - -int CrossInheritance::IBase2::call_get_value(IBase2* b) { - return b->get_value(); -} - -CrossInheritance::IBase3::IBase3(int i) { - m_int = i; -} - -int CrossInheritance::CBase2::get_value() { - return 42; -} - -int CrossInheritance::IBase4::call_get_value(IBase4* b) { - return b->get_value(); -} - -int CrossInheritance::CBase4::get_value() const { - return 27; -} - -int CrossInheritance::TDerived1::get_value() { - return 27; -} - -int CrossInheritance::CountableBase::s_count = 0; - -CrossInheritance::CountableBase::CountableBase() { - ++s_count; -} - -CrossInheritance::CountableBase::CountableBase(const CountableBase&) { - ++s_count; -} - -CrossInheritance::CountableBase& CrossInheritance::CountableBase::operator=(const CountableBase&) { - return *this; -} - -CrossInheritance::CountableBase::~CountableBase() { - --s_count; -} - -int CrossInheritance::CountableBase::call() { - return -1; -} - -CrossInheritance::Component::Component() { - ++s_count; -} - -CrossInheritance::Component::~Component() { - --s_count; -} - -int CrossInheritance::Component::get_count() { - return s_count; -} - -int CrossInheritance::Component::s_count = 0; - -namespace { - -class ComponentWithValue : public CrossInheritance::Component { -public: - ComponentWithValue(int value) : m_value(value) {} - int getValue() { return m_value; } - -protected: - int m_value; -}; - -} // unnamed namespace - -CrossInheritance::Component* CrossInheritance::build_component(int value) { - return new ComponentWithValue(value); -} - -CrossInheritance::Component* CrossInheritance::cycle_component(Component* c) { - return c; -} - -// for protected member testing -AccessProtected::MyBase::MyBase() : my_data(101) { - /* empty */ -} - -AccessProtected::MyBase::~MyBase() { - /* empty */ -} - -int AccessProtected::MyBase::get_data_v() { - return my_data; -} - -int AccessProtected::MyBase::get_data() { - return my_data; -} diff --git a/test/crossinheritance.h b/test/crossinheritance.h deleted file mode 100644 index a8c3200..0000000 --- a/test/crossinheritance.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef CPPYY_TEST_CROSSINHERITANCE_H -#define CPPYY_TEST_CROSSINHERITANCE_H - -#include - - -//=========================================================================== -namespace CrossInheritance { - -class Base1 { // for overridden method checking -public: - Base1() : m_int(42) {} - Base1(int i) : m_int(i) {} - virtual ~Base1(); - - virtual int get_value() { return m_int; } - static int call_get_value(Base1* b); - - virtual int sum_value(int i) { return m_int + i; } - static int call_sum_value(Base1* b, int); - - virtual int sum_all(int i) { return m_int + i; } - virtual int sum_all(int i, int j) { return m_int + i + j; } - static int call_sum_all(Base1* b, int); - static int call_sum_all(Base1* b, int, int); - - virtual int pass_value1(int a) { return a; } - virtual int pass_value2(int& a) { return a; } - virtual int pass_value3(const int& a) { return a; } - virtual int pass_value4(const Base1& b) { return b.m_int; } - virtual int pass_value5(Base1& b) { return b.m_int; } - static int sum_pass_value(Base1* b); - -public: - int m_int; -}; - -class IBase2 { -public: - IBase2() {} - virtual ~IBase2() {} - virtual int get_value() = 0; - static int call_get_value(IBase2* b); -}; - -class IBase3 : IBase2 { -public: - IBase3(int); - int m_int; -}; - -class CBase2 : public IBase2 { -public: - int get_value(); -}; - -class IBase4 { -public: - IBase4() {} - virtual ~IBase4() {} - virtual int get_value() const = 0; // <- const, as opposed to IBase2 - static int call_get_value(IBase4* b); -}; - -class CBase4 : public IBase4 { -public: - int get_value() const; -}; - -template -class TBase1 { -public: - virtual ~TBase1() {} - virtual int get_value() { - return 42; - } -}; - -class TDerived1 : public TBase1 { -public: - int get_value(); -}; - -using TBase1_I = TBase1; - -class CountableBase { -public: - CountableBase(); - CountableBase(const CountableBase&); - CountableBase& operator=(const CountableBase&); - virtual ~CountableBase(); - - virtual int call(); - - static int s_count; -}; - -class Component { -public: - Component(); - Component(const Component&) = delete; - Component& operator=(const Component&) = delete; - virtual ~Component(); - - static int get_count(); - -private: - static int s_count; -}; - -Component* build_component(int value); -Component* cycle_component(Component* c); - -} // namespace CrossInheritance - - -//=========================================================================== -namespace AccessProtected { // for protected member testing - -class MyBase { -public: - MyBase(); - virtual ~MyBase(); - -protected: - virtual int get_data_v(); - int get_data(); - -protected: - int my_data; -}; - -} // AccessProtected - -#endif // !CPPYY_TEST_CROSSINHERITANCE_H diff --git a/test/crossinheritance.xml b/test/crossinheritance.xml deleted file mode 100644 index 12db5fc..0000000 --- a/test/crossinheritance.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/test/datatypes.cxx b/test/datatypes.cxx deleted file mode 100644 index 7bb4016..0000000 --- a/test/datatypes.cxx +++ /dev/null @@ -1,1013 +0,0 @@ -#include "datatypes.h" - - -//=========================================================================== -std::vector vecFruits{kCitrus, kApple}; - - -//=========================================================================== -CppyyTestData::CppyyTestData() : m_const_int(17), m_owns_arrays(false) -{ - m_bool = false; - m_char = 'a'; - m_schar = 'b'; - m_uchar = 'c'; - m_wchar = L'D'; - m_char16 = u'\u00df'; - m_char32 = U'\u00df'; - m_byte = (std::byte)'d'; - m_int8 = -9; - m_uint8 = 9; - m_short = -11; - m_ushort = 11u; - m_int = -22; - m_uint = 22u; - m_long = -33l; - m_ulong = 33ul; - m_llong = -44ll; - m_ullong = 44ull; - m_long64 = -55ll; - m_ulong64 = 55ull; - m_float = -66.f; - m_double = -77.; - m_ldouble = -88.l; - m_complex = {99., 101.}; - m_icomplex = {121, 141}; - m_ccomplex = {151., 161.}; - m_enum = kNothing; - m_voidp = (void*)0; - - m_bool_array2 = new bool[N]; - m_schar_array2 = new signed char[N]; - m_uchar_array2 = new unsigned char[N]; - m_byte_array2 = new std::byte[N]; - m_int8_array2 = new int8_t[N]; - m_uint8_array2 = new uint8_t[N]; - m_short_array2 = new short[N]; - m_ushort_array2 = new unsigned short[N]; - m_int_array2 = new int[N]; - m_uint_array2 = new unsigned int[N]; - m_long_array2 = new long[N]; - m_ulong_array2 = new unsigned long[N]; - - m_float_array2 = new float[N]; - m_double_array2 = new double[N]; - m_complex_array2 = new complex_t[N]; - m_ccomplex_array2 = new ccomplex_t[N]; - - for (int i = 0; i < N; ++i) { - m_bool_array[i] = bool(i%2); - m_bool_array2[i] = bool((i+1)%2); - m_schar_array[i] = 1*i; - m_schar_array2[i] = 2*i; - m_uchar_array[i] = 1u*i; - m_uchar_array2[i] = 2u*i; - m_byte_array[i] = (std::byte)(3u*i); - m_byte_array2[i] = (std::byte)(4u*i); - m_int8_array[i] = - 1*i; - m_int8_array2[i] = - 2*i; - m_uint8_array[i] = 3u*i; - m_uint8_array2[i] = 4u*i; - m_short_array[i] = - 5*i; - m_short_array2[i] = - 6*i; - m_ushort_array[i] = 7u*i; - m_ushort_array2[i] = 8u*i; - m_int_array[i] = - 9*i; - m_int_array2[i] = -10*i; - m_uint_array[i] = 11u*i; - m_uint_array2[i] = 23u*i; - m_long_array[i] = -13l*i; - m_long_array2[i] = -14l*i; - m_ulong_array[i] = 15ul*i; - m_ulong_array2[i] = 18ul*i; - - m_float_array[i] = -13.f*i; - m_float_array2[i] = -14.f*i; - m_double_array[i] = -15.*i; - m_double_array2[i] = -16.*i; - m_complex_array2[i] = {17.*i, 18.*i}; - m_ccomplex_array2[i] = {19.*i, 20.*i}; - } - - m_owns_arrays = true; - - m_pod.m_int = 888; - m_pod.m_double = 3.14; - - m_ppod = &m_pod; -}; - -CppyyTestData::~CppyyTestData() -{ - destroy_arrays(); -} - -void CppyyTestData::destroy_arrays() { - if (m_owns_arrays == true) { - delete[] m_bool_array2; - delete[] m_schar_array2; - delete[] m_uchar_array2; - delete[] m_byte_array2; - delete[] m_int8_array2; - delete[] m_uint8_array2; - delete[] m_short_array2; - delete[] m_ushort_array2; - delete[] m_int_array2; - delete[] m_uint_array2; - delete[] m_long_array2; - delete[] m_ulong_array2; - - delete[] m_float_array2; - delete[] m_double_array2; - delete[] m_complex_array2; - delete[] m_ccomplex_array2; - - m_owns_arrays = false; - } -} - -//- getters ----------------------------------------------------------------- -bool CppyyTestData::get_bool() { return m_bool; } -char CppyyTestData::get_char() { return m_char; } -signed char CppyyTestData::get_schar() { return m_schar; } -unsigned char CppyyTestData::get_uchar() { return m_uchar; } -wchar_t CppyyTestData::get_wchar() { return m_wchar; } -char16_t CppyyTestData::get_char16() { return m_char16; } -char32_t CppyyTestData::get_char32() { return m_char32; } -std::byte CppyyTestData::get_byte() { return m_byte; } -int8_t CppyyTestData::get_int8() { return m_int8; } -uint8_t CppyyTestData::get_uint8() { return m_uint8; } -short CppyyTestData::get_short() { return m_short; } -unsigned short CppyyTestData::get_ushort() { return m_ushort; } -int CppyyTestData::get_int() { return m_int; } -unsigned int CppyyTestData::get_uint() { return m_uint; } -long CppyyTestData::get_long() { return m_long; } -unsigned long CppyyTestData::get_ulong() { return m_ulong; } -long long CppyyTestData::get_llong() { return m_llong; } -unsigned long long CppyyTestData::get_ullong() { return m_ullong; } -Long64_t CppyyTestData::get_long64() { return m_long64; } -ULong64_t CppyyTestData::get_ulong64() { return m_ulong64; } -float CppyyTestData::get_float() { return m_float; } -double CppyyTestData::get_double() { return m_double; } -long double CppyyTestData::get_ldouble() { return m_ldouble; } -long double CppyyTestData::get_ldouble_def(long double ld) { return ld; } -complex_t CppyyTestData::get_complex() { return m_complex; } -icomplex_t CppyyTestData::get_icomplex() { return m_icomplex; } -ccomplex_t CppyyTestData::get_ccomplex() { return m_ccomplex; } -CppyyTestData::EWhat CppyyTestData::get_enum() { return m_enum; } -void* CppyyTestData::get_voidp() { return m_voidp; } - -bool* CppyyTestData::get_bool_array() { return m_bool_array; } -bool* CppyyTestData::get_bool_array2() { return m_bool_array2; } -signed char* CppyyTestData::get_schar_array() { return m_schar_array; } -signed char* CppyyTestData::get_schar_array2() { return m_schar_array2; } -unsigned char* CppyyTestData::get_uchar_array() { return m_uchar_array; } -unsigned char* CppyyTestData::get_uchar_array2() { return m_uchar_array2; } -std::byte* CppyyTestData::get_byte_array() { return m_byte_array; } -std::byte* CppyyTestData::get_byte_array2() { return m_byte_array2; } -int8_t* CppyyTestData::get_int8_array() { return m_int8_array; } -int8_t* CppyyTestData::get_int8_array2() { return m_int8_array2; } -uint8_t* CppyyTestData::get_uint8_array() { return m_uint8_array; } -uint8_t* CppyyTestData::get_uint8_array2() { return m_uint8_array2; } -short* CppyyTestData::get_short_array() { return m_short_array; } -short* CppyyTestData::get_short_array2() { return m_short_array2; } -unsigned short* CppyyTestData::get_ushort_array() { return m_ushort_array; } -unsigned short* CppyyTestData::get_ushort_array2() { return m_ushort_array2; } -int* CppyyTestData::get_int_array() { return m_int_array; } -int* CppyyTestData::get_int_array2() { return m_int_array2; } -unsigned int* CppyyTestData::get_uint_array() { return m_uint_array; } -unsigned int* CppyyTestData::get_uint_array2() { return m_uint_array2; } -long* CppyyTestData::get_long_array() { return m_long_array; } -long* CppyyTestData::get_long_array2() { return m_long_array2; } -unsigned long* CppyyTestData::get_ulong_array() { return m_ulong_array; } -unsigned long* CppyyTestData::get_ulong_array2() { return m_ulong_array2; } - -float* CppyyTestData::get_float_array() { return m_float_array; } -float* CppyyTestData::get_float_array2() { return m_float_array2; } -double* CppyyTestData::get_double_array() { return m_double_array; } -double* CppyyTestData::get_double_array2() { return m_double_array2; } -complex_t* CppyyTestData::get_complex_array() { return m_complex_array; } -complex_t* CppyyTestData::get_complex_array2() { return m_complex_array2; } -ccomplex_t* CppyyTestData::get_ccomplex_array() { return m_ccomplex_array; } -ccomplex_t* CppyyTestData::get_ccomplex_array2() { return m_ccomplex_array2; } - -CppyyTestPod CppyyTestData::get_pod_val() { return m_pod; } -CppyyTestPod* CppyyTestData::get_pod_val_ptr() { return &m_pod; } -CppyyTestPod& CppyyTestData::get_pod_val_ref() { return m_pod; } -CppyyTestPod*& CppyyTestData::get_pod_ptrref() { return m_ppod; } - -CppyyTestPod* CppyyTestData::get_pod_ptr() { return m_ppod; } - -//- getters const-ref ------------------------------------------------------- -const bool& CppyyTestData::get_bool_cr() { return m_bool; } -const char& CppyyTestData::get_char_cr() { return m_char; } -const signed char& CppyyTestData::get_schar_cr() { return m_schar; } -const unsigned char& CppyyTestData::get_uchar_cr() { return m_uchar; } -const wchar_t& CppyyTestData::get_wchar_cr() { return m_wchar; } -const char16_t& CppyyTestData::get_char16_cr() { return m_char16; } -const char32_t& CppyyTestData::get_char32_cr() { return m_char32; } -const std::byte& CppyyTestData::get_byte_cr() { return m_byte; } -const int8_t& CppyyTestData::get_int8_cr() { return m_int8; } -const uint8_t& CppyyTestData::get_uint8_cr() { return m_uint8; } -const short& CppyyTestData::get_short_cr() { return m_short; } -const unsigned short& CppyyTestData::get_ushort_cr() { return m_ushort; } -const int& CppyyTestData::get_int_cr() { return m_int; } -const unsigned int& CppyyTestData::get_uint_cr() { return m_uint; } -const long& CppyyTestData::get_long_cr() { return m_long; } -const unsigned long& CppyyTestData::get_ulong_cr() { return m_ulong; } -const long long& CppyyTestData::get_llong_cr() { return m_llong; } -const unsigned long long& CppyyTestData::get_ullong_cr() { return m_ullong; } -const Long64_t& CppyyTestData::get_long64_cr() { return m_long64; } -const ULong64_t& CppyyTestData::get_ulong64_cr() { return m_ulong64; } -const float& CppyyTestData::get_float_cr() { return m_float; } -const double& CppyyTestData::get_double_cr() { return m_double; } -const long double& CppyyTestData::get_ldouble_cr() { return m_ldouble; } -const complex_t& CppyyTestData::get_complex_cr() { return m_complex; } -const icomplex_t& CppyyTestData::get_icomplex_cr() { return m_icomplex; } -const ccomplex_t& CppyyTestData::get_ccomplex_cr() { return m_ccomplex; } -const CppyyTestData::EWhat& CppyyTestData::get_enum_cr() { return m_enum; } - -//- getters ref ------------------------------------------------------------- -bool& CppyyTestData::get_bool_r() { return m_bool; } -char& CppyyTestData::get_char_r() { return m_char; } -signed char& CppyyTestData::get_schar_r() { return m_schar; } -unsigned char& CppyyTestData::get_uchar_r() { return m_uchar; } -wchar_t& CppyyTestData::get_wchar_r() { return m_wchar; } -char16_t& CppyyTestData::get_char16_r() { return m_char16; } -char32_t& CppyyTestData::get_char32_r() { return m_char32; } -std::byte& CppyyTestData::get_byte_r() { return m_byte; } -int8_t& CppyyTestData::get_int8_r() { return m_int8; } -uint8_t& CppyyTestData::get_uint8_r() { return m_uint8; } -short& CppyyTestData::get_short_r() { return m_short; } -unsigned short& CppyyTestData::get_ushort_r() { return m_ushort; } -int& CppyyTestData::get_int_r() { return m_int; } -unsigned int& CppyyTestData::get_uint_r() { return m_uint; } -long& CppyyTestData::get_long_r() { return m_long; } -unsigned long& CppyyTestData::get_ulong_r() { return m_ulong; } -long long& CppyyTestData::get_llong_r() { return m_llong; } -unsigned long long& CppyyTestData::get_ullong_r() { return m_ullong; } -Long64_t& CppyyTestData::get_long64_r() { return m_long64; } -ULong64_t& CppyyTestData::get_ulong64_r() { return m_ulong64; } -float& CppyyTestData::get_float_r() { return m_float; } -double& CppyyTestData::get_double_r() { return m_double; } -long double& CppyyTestData::get_ldouble_r() { return m_ldouble; } -complex_t& CppyyTestData::get_complex_r() { return m_complex; } -icomplex_t& CppyyTestData::get_icomplex_r() { return m_icomplex; } -ccomplex_t& CppyyTestData::get_ccomplex_r() { return m_ccomplex; } -CppyyTestData::EWhat& CppyyTestData::get_enum_r() { return m_enum; } - -//- setters ----------------------------------------------------------------- -void CppyyTestData::set_bool(bool b) { m_bool = b; } -void CppyyTestData::set_char(char c) { m_char = c; } -void CppyyTestData::set_schar(signed char sc) { m_schar = sc; } -void CppyyTestData::set_uchar(unsigned char uc) { m_uchar = uc; } -void CppyyTestData::set_wchar(wchar_t wc) { m_wchar = wc; } -void CppyyTestData::set_char16(char16_t c16) { m_char16 = c16; } -void CppyyTestData::set_char32(char32_t c32) { m_char32 = c32; } -void CppyyTestData::set_byte(std::byte b) { m_byte = b; } -void CppyyTestData::set_int8(int8_t s8) { m_int8 = s8; } -void CppyyTestData::set_uint8(uint8_t u8) { m_uint8 = u8; } -void CppyyTestData::set_short(short s) { m_short = s; } -void CppyyTestData::set_ushort(unsigned short us) { m_ushort = us; } -void CppyyTestData::set_int(int i) { m_int = i; } -void CppyyTestData::set_uint(unsigned int ui) { m_uint = ui; } -void CppyyTestData::set_long(long l) { m_long = l; } -void CppyyTestData::set_ulong(unsigned long ul) { m_ulong = ul; } -void CppyyTestData::set_llong(long long ll) { m_llong = ll; } -void CppyyTestData::set_ullong(unsigned long long ull) { m_ullong = ull; } -void CppyyTestData::set_long64(Long64_t l64) { m_long64 = l64; } -void CppyyTestData::set_ulong64(ULong64_t ul64) { m_ulong64 = ul64; } -void CppyyTestData::set_float(float f) { m_float = f; } -void CppyyTestData::set_double(double d) { m_double = d; } -void CppyyTestData::set_ldouble(long double ld) { m_ldouble = ld; } -void CppyyTestData::set_complex(complex_t cd) { m_complex = cd; } -void CppyyTestData::set_icomplex(icomplex_t ci) { m_icomplex = ci; } -void CppyyTestData::set_ccomplex(ccomplex_t cd) { m_ccomplex = cd; } -void CppyyTestData::set_enum(EWhat w) { m_enum = w; } -void CppyyTestData::set_voidp(void* p) { m_voidp = p; } - -void CppyyTestData::set_pod_val(CppyyTestPod p) { m_pod = p; } -void CppyyTestData::set_pod_ptr_in(CppyyTestPod* pp) { m_pod = *pp; } -void CppyyTestData::set_pod_ptr_out(CppyyTestPod* pp) { *pp = m_pod; } -void CppyyTestData::set_pod_ref(const CppyyTestPod& rp) { m_pod = rp; } -void CppyyTestData::set_pod_ptrptr_in(CppyyTestPod** ppp) { m_pod = **ppp; } -void CppyyTestData::set_pod_void_ptrptr_in(void** pp) { m_pod = **((CppyyTestPod**)pp); } -void CppyyTestData::set_pod_ptrptr_out(CppyyTestPod** ppp) { delete *ppp; *ppp = new CppyyTestPod(m_pod); } -void CppyyTestData::set_pod_void_ptrptr_out(void** pp) { delete *((CppyyTestPod**)pp); - *((CppyyTestPod**)pp) = new CppyyTestPod(m_pod); } - -void CppyyTestData::set_pod_ptr(CppyyTestPod* pp) { m_ppod = pp; } - -//- setters const-ref ------------------------------------------------------- -void CppyyTestData::set_bool_cr(const bool& b) { m_bool = b; } -void CppyyTestData::set_char_cr(const char& c) { m_char = c; } -void CppyyTestData::set_schar_cr(const signed char& sc) { m_schar = sc; } -void CppyyTestData::set_uchar_cr(const unsigned char& uc) { m_uchar = uc; } -void CppyyTestData::set_wchar_cr(const wchar_t& wc) { m_wchar = wc; } -void CppyyTestData::set_char16_cr(const char16_t& c16) { m_char16 = c16; } -void CppyyTestData::set_char32_cr(const char32_t& c32) { m_char32 = c32; } -void CppyyTestData::set_byte_cr(const std::byte& b) { m_byte = b; } -void CppyyTestData::set_int8_cr(const int8_t& s8) { m_int8 = s8; } -void CppyyTestData::set_uint8_cr(const uint8_t& u8) { m_uint8 = u8; } -void CppyyTestData::set_short_cr(const short& s) { m_short = s; } -void CppyyTestData::set_ushort_cr(const unsigned short& us) { m_ushort = us; } -void CppyyTestData::set_int_cr(const int& i) { m_int = i; } -void CppyyTestData::set_uint_cr(const unsigned int& ui) { m_uint = ui; } -void CppyyTestData::set_long_cr(const long& l) { m_long = l; } -void CppyyTestData::set_ulong_cr(const unsigned long& ul) { m_ulong = ul; } -void CppyyTestData::set_llong_cr(const long long& ll) { m_llong = ll; } -void CppyyTestData::set_ullong_cr(const unsigned long long& ull) { m_ullong = ull; } -void CppyyTestData::set_long64_cr(const Long64_t& l64) { m_long64 = l64; } -void CppyyTestData::set_ulong64_cr(const ULong64_t& ul64) { m_ulong64 = ul64; } -void CppyyTestData::set_float_cr(const float& f) { m_float = f; } -void CppyyTestData::set_double_cr(const double& d) { m_double = d; } -void CppyyTestData::set_ldouble_cr(const long double& ld) { m_ldouble = ld; } -void CppyyTestData::set_complex_cr(const complex_t& cd) { m_complex = cd; } -void CppyyTestData::set_icomplex_cr(const icomplex_t& ci) { m_icomplex = ci; } -void CppyyTestData::set_ccomplex_cr(const ccomplex_t& cd) { m_ccomplex = cd; } -void CppyyTestData::set_enum_cr(const EWhat& w) { m_enum = w; } - -//- setters ref ------------------------------------------------------------- -void CppyyTestData::set_bool_r(bool& b) { b = true; } -void CppyyTestData::set_char_r(char& c) { c = 'a'; } -void CppyyTestData::set_wchar_r(wchar_t& wc) { wc = 'b'; } -void CppyyTestData::set_char16_r(char16_t& c16) { c16 = u'\u6c24'; } -void CppyyTestData::set_char32_r(char32_t& c32) { c32 = U'\U0001f34e'; } -void CppyyTestData::set_schar_r(signed char& sc) { sc = 'c'; } -void CppyyTestData::set_uchar_r(unsigned char& uc) { uc = 'd'; } -void CppyyTestData::set_byte_r(std::byte& b) { b = (std::byte)'e'; } -void CppyyTestData::set_short_r(short& s) { s = -1; } -void CppyyTestData::set_ushort_r(unsigned short& us) { us = 2; } -void CppyyTestData::set_int_r(int& i) { i = -3; } -void CppyyTestData::set_uint_r(unsigned int& ui) { ui = 4; } -void CppyyTestData::set_long_r(long& l) { l = -5; } -void CppyyTestData::set_ulong_r(unsigned long& ul) { ul = 6; } -void CppyyTestData::set_llong_r(long long& ll) { ll = -7; } -void CppyyTestData::set_ullong_r(unsigned long long& ull) { ull = 8; } -void CppyyTestData::set_float_r(float& f) { f = 5.f; } -void CppyyTestData::set_double_r(double& d) { d = -5.; } -void CppyyTestData::set_ldouble_r(long double& ld) { ld = 10.l; } - -//- setters ptr ------------------------------------------------------------- -void CppyyTestData::set_bool_p(bool* b) { *b = true; } -void CppyyTestData::set_char_p(char* c) { *c = 'a'; } -void CppyyTestData::set_wchar_p(wchar_t* wc) { *wc = 'b'; } -void CppyyTestData::set_char16_p(char16_t* c16) { *c16 = u'\u6c24'; } -void CppyyTestData::set_char32_p(char32_t* c32) { *c32 = U'\U0001f34e'; } -void CppyyTestData::set_schar_p(signed char* sc) { *sc = 'c'; } -void CppyyTestData::set_uchar_p(unsigned char* uc) { *uc = 'd'; } -void CppyyTestData::set_byte_p(std::byte* b) { *b = (std::byte)'e'; } -void CppyyTestData::set_int8_p(int8_t* i8) { *i8 = -27; } -void CppyyTestData::set_uint8_p(uint8_t* ui8) { *ui8 = 28; } -void CppyyTestData::set_short_p(short* s) { *s = -1; } -void CppyyTestData::set_ushort_p(unsigned short* us) { *us = 2; } -void CppyyTestData::set_int_p(int* i) { *i = -3; } -void CppyyTestData::set_uint_p(unsigned int* ui) { *ui = 4; } -void CppyyTestData::set_long_p(long* l) { *l = -5; } -void CppyyTestData::set_ulong_p(unsigned long* ul) { *ul = 6; } -void CppyyTestData::set_llong_p(long long* ll) { *ll = -7; } -void CppyyTestData::set_ullong_p(unsigned long long* ull) { *ull = 8; } -void CppyyTestData::set_float_p(float* f) { *f = 5.f; } -void CppyyTestData::set_double_p(double* d) { *d = -5.; } -void CppyyTestData::set_ldouble_p(long double* ld) { *ld = 10.l; } - -//- setters ptrptr ---------------------------------------------------------- -void CppyyTestData::set_bool_ppa(bool** b) { - (*b) = new bool[3]; - (*b)[0] = true; (*b)[1] = false; (*b)[2] = true; -} -void CppyyTestData::set_char_ppa(char** c) { - (*c) = new char[3]; - (*c)[0] = 'a'; (*c)[1] = 'b'; (*c)[2] = 'c'; -} -void CppyyTestData::set_wchar_ppa(wchar_t** wc) { - (*wc) = new wchar_t[3]; - (*wc)[0] = 'd'; (*wc)[1] = 'e'; (*wc)[2] = 'f'; -} -void CppyyTestData::set_char16_ppa(char16_t** c16) { - (*c16) = new char16_t[3]; - (*c16)[0] = u'\u6c24'; (*c16)[1] = u'\u6c25'; (*c16)[2] = u'\u6c26'; -} -void CppyyTestData::set_char32_ppa(char32_t** c32) { - (*c32) = new char32_t[3]; - (*c32)[0] = U'\U0001f34d'; (*c32)[1] = U'\U0001f34e'; (*c32)[2] = U'\U0001f34f'; -} -void CppyyTestData::set_schar_ppa(signed char** sc) { - (*sc) = new signed char[3]; - (*sc)[0] = 'g'; (*sc)[1] = 'h'; (*sc)[2] = 'j'; -} -void CppyyTestData::set_uchar_ppa(unsigned char** uc) { - (*uc) = new unsigned char[3]; - (*uc)[0] = 'k'; (*uc)[1] = 'l'; (*uc)[2] = 'm'; -} -void CppyyTestData::set_byte_ppa(std::byte** b) { - (*b) = new std::byte[3]; - (*b)[0] = (std::byte)'n'; (*b)[1] = (std::byte)'o'; (*b)[2] = (std::byte)'p'; -} -void CppyyTestData::set_int8_ppa(int8_t** i8) { - (*i8) = new int8_t[3]; - (*i8)[0] = -27; (*i8)[1] = -28; (*i8)[2] = -29; -} -void CppyyTestData::set_uint8_ppa(uint8_t** ui8) { - (*ui8) = new uint8_t[3]; - (*ui8)[0] = 28; (*ui8)[1] = 29; (*ui8)[2] = 30; -} -void CppyyTestData::set_short_ppa(short** s) { - (*s) = new short[3]; - (*s)[0] = -1; (*s)[1] = -2; (*s)[2] = -3; -} -void CppyyTestData::set_ushort_ppa(unsigned short** us) { - (*us) = new unsigned short[3]; - (*us)[0] = 4; (*us)[1] = 5; (*us)[2] = 6; -} -void CppyyTestData::set_int_ppa(int** i) { - (*i) = new int[3]; - (*i)[0] = -7; (*i)[1] = -8; (*i)[2] = -9; -} -void CppyyTestData::set_uint_ppa(unsigned int** ui) { - (*ui) = new unsigned int[3]; - (*ui)[0] = 10; (*ui)[1] = 11; (*ui)[2] = 12; -} -void CppyyTestData::set_long_ppa(long** l) { - (*l) = new long[3]; - (*l)[0] = -13; (*l)[1] = -14; (*l)[2] = -15; -} -void CppyyTestData::set_ulong_ppa(unsigned long** ul) { - (*ul) = new unsigned long[3]; - (*ul)[0] = 16; (*ul)[1] = 17; (*ul)[2] = 18; -} -void CppyyTestData::set_llong_ppa(long long** ll) { - (*ll) = new long long[3]; - (*ll)[0] = -19; (*ll)[1] = -20; (*ll)[2] = -21; -} -void CppyyTestData::set_ullong_ppa(unsigned long long** ull) { - (*ull) = new unsigned long long[3]; - (*ull)[0] = 22; (*ull)[1] = 23; (*ull)[2] = 24; -} -void CppyyTestData::set_float_ppa(float** f) { - (*f) = new float[3]; - (*f)[0] = 5.f; (*f)[1] = 10.f; (*f)[2] = 20.f; -} -void CppyyTestData::set_double_ppa(double** d) { - (*d) = new double[3]; - (*d)[0] = -5; (*d)[1] = -10.; (*d)[2] = -20.; -} -void CppyyTestData::set_ldouble_ppa(long double** ld) { - (*ld) = new long double[3]; - (*ld)[0] = 5.l; (*ld)[1] = 10.f; (*ld)[2] = 20.l; -} - -intptr_t CppyyTestData::set_char_ppm(char** c) { - *c = (char*)malloc(4*sizeof(char)); - return (intptr_t)*c; -} -intptr_t CppyyTestData::set_cchar_ppm(const char** cc) { - *cc = (const char*)malloc(4*sizeof(char)); - return (intptr_t)*cc; -} -intptr_t CppyyTestData::set_wchar_ppm(wchar_t** w) { - *w = (wchar_t*)malloc(4*sizeof(wchar_t)); - return (intptr_t)*w; -} -intptr_t CppyyTestData::set_char16_ppm(char16_t** c16) { - *c16 = (char16_t*)malloc(4*sizeof(char16_t)); - return (intptr_t)*c16; -} -intptr_t CppyyTestData::set_char32_ppm(char32_t** c32) { - *c32 = (char32_t*)malloc(4*sizeof(char32_t)); - return (intptr_t)*c32; -} -intptr_t CppyyTestData::set_cwchar_ppm(const wchar_t** cw) { - *cw = (const wchar_t*)malloc(4*sizeof(wchar_t)); - return (intptr_t)*cw; -} -intptr_t CppyyTestData::set_cchar16_ppm(const char16_t** c16) { - *c16 = (const char16_t*)malloc(4*sizeof(char16_t)); - return (intptr_t)*c16; -} -intptr_t CppyyTestData::set_cchar32_ppm(const char32_t** c32) { - *c32 = (const char32_t*)malloc(4*sizeof(char32_t)); - return (intptr_t)*c32; -} -intptr_t CppyyTestData::set_void_ppm(void** v) { - *v = malloc(4*sizeof(void*)); - return (intptr_t)*v; -} - -intptr_t CppyyTestData::freeit(void* ptr) { - intptr_t out = (intptr_t)ptr; - free(ptr); - return out; -} - -//- setters r-value --------------------------------------------------------- -void CppyyTestData::set_bool_rv(bool&& b) { m_bool = b; } -void CppyyTestData::set_char_rv(char&& c) { m_char = c; } -void CppyyTestData::set_schar_rv(signed char&& sc) { m_schar = sc; } -void CppyyTestData::set_uchar_rv(unsigned char&& uc) { m_uchar = uc; } -void CppyyTestData::set_wchar_rv(wchar_t&& wc) { m_wchar = wc; } -void CppyyTestData::set_char16_rv(char16_t&& c16) { m_char16 = c16; } -void CppyyTestData::set_char32_rv(char32_t&& c32) { m_char32 = c32; } -void CppyyTestData::set_byte_rv(std::byte&& b) { m_byte = b; } -void CppyyTestData::set_int8_rv(int8_t&& s8) { m_int8 = s8; } -void CppyyTestData::set_uint8_rv(uint8_t&& u8) { m_uint8 = u8; } -void CppyyTestData::set_short_rv(short&& s) { m_short = s; } -void CppyyTestData::set_ushort_rv(unsigned short&& us) { m_ushort = us; } -void CppyyTestData::set_int_rv(int&& i) { m_int = i; } -void CppyyTestData::set_uint_rv(unsigned int&& ui) { m_uint = ui; } -void CppyyTestData::set_long_rv(long&& l) { m_long = l; } -void CppyyTestData::set_ulong_rv(unsigned long&& ul) { m_ulong = ul; } -void CppyyTestData::set_llong_rv(long long&& ll) { m_llong = ll; } -void CppyyTestData::set_ullong_rv(unsigned long long&& ull) { m_ullong = ull; } -void CppyyTestData::set_long64_rv(Long64_t&& l64) { m_long64 = l64; } -void CppyyTestData::set_ulong64_rv(ULong64_t&& ul64) { m_ulong64 = ul64; } -void CppyyTestData::set_float_rv(float&& f) { m_float = f; } -void CppyyTestData::set_double_rv(double&& d) { m_double = d; } -void CppyyTestData::set_ldouble_rv(long double&& ld) { m_ldouble = ld; } -void CppyyTestData::set_complex_rv(complex_t&& cd) { m_complex = cd; } -void CppyyTestData::set_icomplex_rv(icomplex_t&& ci) { m_icomplex = ci; } -void CppyyTestData::set_ccomplex_rv(ccomplex_t&& cd) { m_ccomplex = cd; } -void CppyyTestData::set_enum_rv(EWhat&& w) { m_enum = w; } - -//- passers ----------------------------------------------------------------- -unsigned char* CppyyTestData::pass_array(unsigned char* a) { return a; } -short* CppyyTestData::pass_array(short* a) { return a; } -unsigned short* CppyyTestData::pass_array(unsigned short* a) { return a; } -int* CppyyTestData::pass_array(int* a) { return a; } -unsigned int* CppyyTestData::pass_array(unsigned int* a) { return a; } -long* CppyyTestData::pass_array(long* a) { return a; } -unsigned long* CppyyTestData::pass_array(unsigned long* a) { return a; } -float* CppyyTestData::pass_array(float* a) { return a; } -double* CppyyTestData::pass_array(double* a) { return a; } -complex_t* CppyyTestData::pass_array(complex_t* a) { return a; } -ccomplex_t* CppyyTestData::pass_array(ccomplex_t* a) { return a; } - -//- static data members ----------------------------------------------------- -bool CppyyTestData::s_bool = false; -char CppyyTestData::s_char = 'c'; -signed char CppyyTestData::s_schar = 's'; -unsigned char CppyyTestData::s_uchar = 'u'; -wchar_t CppyyTestData::s_wchar = L'U'; -char16_t CppyyTestData::s_char16 = u'\u6c29'; -char32_t CppyyTestData::s_char32 = U'\U0001f34b'; -std::byte CppyyTestData::s_byte = (std::byte)'b'; -int8_t CppyyTestData::s_int8 = - 87; -uint8_t CppyyTestData::s_uint8 = 87; -short CppyyTestData::s_short = -101; -unsigned short CppyyTestData::s_ushort = 255u; -int CppyyTestData::s_int = -202; -unsigned int CppyyTestData::s_uint = 202u; -long CppyyTestData::s_long = -303l; -unsigned long CppyyTestData::s_ulong = 303ul; -long long CppyyTestData::s_llong = -404ll; -unsigned long long CppyyTestData::s_ullong = 404ull; -Long64_t CppyyTestData::s_long64 = -505ll; -ULong64_t CppyyTestData::s_ulong64 = 505ull; -float CppyyTestData::s_float = -606.f; -double CppyyTestData::s_double = -707.; -long double CppyyTestData::s_ldouble = -808.l; -complex_t CppyyTestData::s_complex = {909., -909.}; -icomplex_t CppyyTestData::s_icomplex = {979, -979}; -ccomplex_t CppyyTestData::s_ccomplex = {919., -919.}; -CppyyTestData::EWhat CppyyTestData::s_enum = CppyyTestData::kNothing; -void* CppyyTestData::s_voidp = (void*)0; -std::string CppyyTestData::s_strv = "Hello"; -std::string* CppyyTestData::s_strp = nullptr; - -//- strings ----------------------------------------------------------------- -const char* CppyyTestData::get_valid_string(const char* in) { return in; } -const char* CppyyTestData::get_invalid_string() { return (const char*)0; } -const wchar_t* CppyyTestData::get_valid_wstring(const wchar_t* in) { return in; } -const wchar_t* CppyyTestData::get_invalid_wstring() { return (const wchar_t*)0; } -const char16_t* CppyyTestData::get_valid_string16(const char16_t* in) { return in; } -const char16_t* CppyyTestData::get_invalid_string16() { return (const char16_t*)0; } -const char32_t* CppyyTestData::get_valid_string32(const char32_t* in) { return in; } -const char32_t* CppyyTestData::get_invalid_string32() { return (const char32_t*)0; } - - -//= global functions ======================================================== -intptr_t get_pod_address(CppyyTestData& c) -{ - return (intptr_t)&c.m_pod; -} - -intptr_t get_int_address(CppyyTestData& c) -{ - return (intptr_t)&c.m_pod.m_int; -} - -intptr_t get_double_address(CppyyTestData& c) -{ - return (intptr_t)&c.m_pod.m_double; -} - - -//= global variables/pointers =============================================== -bool g_bool = false; -char g_char = 'w'; -signed char g_schar = 'v'; -unsigned char g_uchar = 'u'; -wchar_t g_wchar = L'U'; -char16_t g_char16 = u'\u6c21'; -char32_t g_char32 = U'\u6c21'; -std::byte g_byte = (std::byte)'x'; -int8_t g_int8 = -66; -uint8_t g_uint8 = 66; -short g_short = -88; -unsigned short g_ushort = 88u; -int g_int = -188; -unsigned int g_uint = 188u; -long g_long = -288; -unsigned long g_ulong = 288ul; -long long g_llong = -388ll; -unsigned long long g_ullong = 388ull; -Long64_t g_long64 = -488ll; -ULong64_t g_ulong64 = 488ull; -float g_float = -588.f; -double g_double = -688.; -long double g_ldouble = -788.l; -complex_t g_complex = {808., -808.}; -icomplex_t g_icomplex = {909, -909}; -ccomplex_t g_ccomplex = {858., -858.}; -EFruit g_enum = kBanana; -void* g_voidp = nullptr; - - -//= global accessors ======================================================== -void set_global_int(int i) { - g_int = i; -} - -int get_global_int() { - return g_int; -} - -CppyyTestPod* g_pod = (CppyyTestPod*)0; - -bool is_global_pod(CppyyTestPod* t) { - return t == g_pod; -} - -void set_global_pod(CppyyTestPod* t) { - g_pod = t; -} - -CppyyTestPod* get_global_pod() { - return g_pod; -} - -CppyyTestPod* get_null_pod() { - return (CppyyTestPod*)0; -} - -std::string g_some_global_string = "C++"; -std::string get_some_global_string() { return g_some_global_string; } -std::string g_some_global_string2 = "C++"; -std::string get_some_global_string2() { return g_some_global_string2; } - -const char16_t* g_some_global_string16 = u"z\u00df\u6c34"; -const char32_t* g_some_global_string32 = U"z\u00df\u6c34\U0001f34c"; - -std::string SomeStaticDataNS::s_some_static_string = "C++"; -std::string SomeStaticDataNS::get_some_static_string() { return s_some_static_string; } -std::string SomeStaticDataNS::s_some_static_string2 = "C++"; -std::string SomeStaticDataNS::get_some_static_string2() { return s_some_static_string2; } - -StorableData gData{5.}; - - -//= special case of "byte" arrays =========================================== -int64_t sum_uc_data(unsigned char* data, int size) -{ - int64_t total = 0; - for (int i = 0; i < size; ++i) total += int64_t(data[i]); - return total; -} - -int64_t sum_byte_data(std::byte* data, int size) -{ - return sum_uc_data((unsigned char*)data, size); -} - -//= function pointer passing ================================================ -int sum_of_int1(int i1, int i2) { - return i1+i2; -} - -int sum_of_int2(int i1, int i2) { - return 2*i1+i2; -} - -int (*sum_of_int_ptr)(int, int) = sum_of_int1; - -int call_sum_of_int(int i1, int i2) { - if (sum_of_int_ptr) - return (*sum_of_int_ptr)(i1, i2); - return -1; -} - -double sum_of_double(double d1, double d2) { - return d1+d2; -} - -double call_double_double(double (*f)(double, double), double d1, double d2) { - if (!f) return -1.; - return f(d1, d2); -} - - -//= callable passing ======================================================== -int call_int_int(int (*f)(int, int), int i1, int i2) { - return f(i1, i2); -} - -void call_void(void (*f)(int), int i) { - f(i); -} - -int call_refi(void (*f)(int&)) { - int i = -1; f(i); return i; -} - -int call_refl(void (*f)(long&)) { - long l = -1L; f(l); return l; -} - -int call_refd(void (*f)(double&)) { - double d = -1.; f(d); return d; -} - - -StoreCallable::StoreCallable(double (*f)(double, double)) : fF(f) { - /* empty */ -} - -void StoreCallable::set_callable(double (*f)(double, double)) { - fF = f; -} - -double StoreCallable::operator()(double d1, double d2) { - return fF(d1, d2); -} - -//= callable through std::function ========================================== -double call_double_double_sf(const std::function& f, double d1, double d2) { - return f(d1, d2); -} - -int call_int_int_sf(const std::function& f, int i1, int i2) { - return f(i1, i2); -} - -void call_void_sf(const std::function& f, int i) { - f(i); -} - -int call_refi_sf(const std::function& f) { - int i = -1; f(i); return i; -} - -int call_refl_sf(const std::function& f) { - long l = -1L; f(l); return l; -} - -int call_refd_sf(const std::function& f) { - double d = -1.; f(d); return d; -} - - -StoreCallable_sf::StoreCallable_sf(const std::function& f) : fF(f) { - /* empty */ -} - -void StoreCallable_sf::set_callable(const std::function& f) { - fF = f; -} - -double StoreCallable_sf::operator()(double d1, double d2) { - return fF(d1, d2); -} - - -//= array of C strings passing ============================================== -std::vector ArrayOfCStrings::takes_array_of_cstrings(const char* args[], int len) -{ - std::vector v; - v.reserve(len); - for (int i = 0; i < len; ++i) - v.emplace_back(args[i]); - - return v; -} - - -//= aggregate testing ====================================================== -int AggregateTest::Aggregate1::sInt = 17; -int AggregateTest::Aggregate2::sInt = 27; - - -//= multi-dim arrays ======================================================= -namespace MultiDimArrays { - -template -static inline T** allocate_2d(size_t N, size_t M) { - T** arr = (T**)malloc(sizeof(void*)*N); - for (size_t i = 0; i < N; ++i) - arr[i] = (T*)malloc(sizeof(T)*M); - return arr; -} - -static inline void free_2d(void** arr, size_t N) { - if (arr) { - for (size_t i = 0; i < N; ++i) - free(arr[i]); - free(arr); - } -} - -template -static inline T*** allocate_3d(size_t N, size_t M, size_t K) { - T*** arr = (T***)malloc(sizeof(void*)*N); - for (size_t i = 0; i < N; ++i) { - arr[i] = (T**)malloc(sizeof(void*)*M); - for (size_t j = 0; j < M; ++j) - arr[i][j] = (T*)malloc(sizeof(T)*K); - } - return arr; -} - -static inline void free_3d(void*** arr, size_t N, size_t M) { - if (arr) { - for (size_t i = 0; i < N; ++i) { - for (size_t j = 0; j < M; ++j) - free(arr[i][j]); - free(arr[i]); - } - free(arr); - } -} - -} // namespace MultiDimArrays - -MultiDimArrays::DataHolder::DataHolder() { - m_short2a = allocate_2d(5, 7); - m_unsigned_short2a = allocate_2d(5, 7); - m_int2a = allocate_2d(5, 7); - m_unsigned_int2a = allocate_2d(5, 7); - m_long2a = allocate_2d(5, 7); - m_unsigned_long2a = allocate_2d(5, 7); - m_long_long2a = allocate_2d(5, 7); - m_unsigned_long_long2a = allocate_2d(5, 7); - m_float2a = allocate_2d(5, 7); - m_double2a = allocate_2d(5, 7); - - for (size_t i = 0; i < 5; ++i) { - for (size_t j = 0; j < 7; ++j) { - size_t val = 5*i+j; - m_short2a[i][j] = (short)val; - m_unsigned_short2a[i][j] = (unsigned short)val; - m_int2a[i][j] = (int)val; - m_unsigned_int2a[i][j] = (unsigned int)val; - m_long2a[i][j] = (long)val; - m_unsigned_long2a[i][j] = (unsigned long)val; - m_long_long2a[i][j] = (long long)val; - m_unsigned_long_long2a[i][j] = (unsigned long long)val; - m_float2a[i][j] = (float)val; - m_double2a[i][j] = (double)val; - } - } - - m_short2b = nullptr; - m_unsigned_short2b = nullptr; - m_int2b = nullptr; - m_unsigned_int2b = nullptr; - m_long2b = nullptr; - m_unsigned_long2b = nullptr; - m_long_long2b = nullptr; - m_unsigned_long_long2b = nullptr; - m_float2b = nullptr; - m_double2b = nullptr; - - for (size_t i = 0; i < 3; ++i) { - for (size_t j = 0; j < 5; ++j) { - size_t val = 3*i+j; - m_short2c[i][j] = (short)val; - m_unsigned_short2c[i][j] = (unsigned short)val; - m_int2c[i][j] = (int)val; - m_unsigned_int2c[i][j] = (unsigned int)val; - m_long2c[i][j] = (long)val; - m_unsigned_long2c[i][j] = (unsigned long)val; - m_long_long2c[i][j] = (long long)val; - m_unsigned_long_long2c[i][j] = (unsigned long long)val; - m_float2c[i][j] = (float)val; - m_double2c[i][j] = (double)val; - - for (size_t k = 0; k < 7; ++k) { - val = 3*i+2*j+k; - m_short3c[i][j][k] = (short)val; - m_unsigned_short3c[i][j][k] = (unsigned short)val; - m_int3c[i][j][k] = (int)val; - m_unsigned_int3c[i][j][k] = (unsigned int)val; - m_long3c[i][j][k] = (long)val; - m_unsigned_long3c[i][j][k] = (unsigned long)val; - m_long_long3c[i][j][k] = (long long)val; - m_unsigned_long_long3c[i][j][k] = (unsigned long long)val; - m_float3c[i][j][k] = (float)val; - m_double3c[i][j][k] = (double)val; - } - } - } - - m_short3a = allocate_3d(5, 7, 11); - m_unsigned_short3a = allocate_3d(5, 7, 11); - m_int3a = allocate_3d(5, 7, 11); - m_unsigned_int3a = allocate_3d(5, 7, 11); - m_long3a = allocate_3d(5, 7, 11); - m_unsigned_long3a = allocate_3d(5, 7, 11); - m_long_long3a = allocate_3d(5, 7, 11); - m_unsigned_long_long3a = allocate_3d(5, 7, 11); - m_float3a = allocate_3d(5, 7, 11); - m_double3a = allocate_3d(5, 7, 11); - - for (size_t i = 0; i < 5; ++i) { - for (size_t j = 0; j < 7; ++j) { - for (size_t k = 0; k < 11; ++k) { - size_t val = 7*i+3*j+k; - m_short3a[i][j][k] = (short)val; - m_unsigned_short3a[i][j][k] = (unsigned short)val; - m_int3a[i][j][k] = (int)val; - m_unsigned_int3a[i][j][k] = (unsigned int)val; - m_long3a[i][j][k] = (long)val; - m_unsigned_long3a[i][j][k] = (unsigned long)val; - m_long_long3a[i][j][k] = (long long)val; - m_unsigned_long_long3a[i][j][k] = (unsigned long long)val; - m_float3a[i][j][k] = (float)val; - m_double3a[i][j][k] = (double)val; - } - } - } -} - -MultiDimArrays::DataHolder::~DataHolder() { - free_2d((void**)m_short2a, 5); - free_2d((void**)m_unsigned_short2a, 5); - free_2d((void**)m_int2a, 5); - free_2d((void**)m_unsigned_int2a, 5); - free_2d((void**)m_long2a, 5); - free_2d((void**)m_unsigned_long2a, 5); - free_2d((void**)m_long_long2a, 5); - free_2d((void**)m_unsigned_long_long2a, 5); - free_2d((void**)m_float2a, 5); - free_2d((void**)m_double2a, 5); - - free_2d((void**)m_short2b, 5); - free_2d((void**)m_unsigned_short2b, 5); - free_2d((void**)m_int2b, 5); - free_2d((void**)m_unsigned_int2b, 5); - free_2d((void**)m_long2b, 5); - free_2d((void**)m_unsigned_long2b, 5); - free_2d((void**)m_long_long2b, 5); - free_2d((void**)m_unsigned_long_long2b, 5); - free_2d((void**)m_float2b, 5); - free_2d((void**)m_double2b, 5); - - free_3d((void***)m_short3a, 5, 7); - free_3d((void***)m_unsigned_short3a, 5, 7); - free_3d((void***)m_int3a, 5, 7); - free_3d((void***)m_unsigned_int3a, 5, 7); - free_3d((void***)m_long3a, 5, 7); - free_3d((void***)m_unsigned_long3a, 5, 7); - free_3d((void***)m_long_long3a, 5, 7); - free_3d((void***)m_unsigned_long_long3a, 5, 7); - free_3d((void***)m_float3a, 5, 7); - free_3d((void***)m_double3a, 5, 7); -} - -#define MULTIDIM_ARRAYS_NEW2D(type, name) \ -type** MultiDimArrays::DataHolder::new_##name##2d(int N, int M) { \ - type** arr = allocate_2d(N, M); \ - for (size_t i = 0; i < N; ++i) { \ - for (size_t j = 0; j < M; ++j) { \ - size_t val = 7*i+j; \ - arr [i][j] = (type)val; \ - } \ - } \ - return arr; \ -} - -MULTIDIM_ARRAYS_NEW2D(short, short) -MULTIDIM_ARRAYS_NEW2D(unsigned short, ushort) -MULTIDIM_ARRAYS_NEW2D(int, int) -MULTIDIM_ARRAYS_NEW2D(unsigned int, uint) -MULTIDIM_ARRAYS_NEW2D(long, long) -MULTIDIM_ARRAYS_NEW2D(unsigned long, ulong) -MULTIDIM_ARRAYS_NEW2D(long long, llong) -MULTIDIM_ARRAYS_NEW2D(unsigned long long, ullong) -MULTIDIM_ARRAYS_NEW2D(float, float) -MULTIDIM_ARRAYS_NEW2D(double, double) - -//=========================================================================== -namespace Int8_Uint8_Arrays { - int8_t test[6] = {-0x12, -0x34, -0x56, -0x78}; - uint8_t utest[6] = { 0x12, 0x34, 0x56, 0x78}; -} diff --git a/test/datatypes.h b/test/datatypes.h deleted file mode 100644 index 104edc2..0000000 --- a/test/datatypes.h +++ /dev/null @@ -1,832 +0,0 @@ -#ifndef CPPYY_TEST_DATATYPES_H -#define CPPYY_TEST_DATATYPES_H - -#ifdef _WIN32 -typedef __int64 Long64_t; -typedef unsigned __int64 ULong64_t; -#else -typedef long long Long64_t; -typedef unsigned long long ULong64_t; -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -const int N = 5; - - -//=========================================================================== -struct CppyyTestPod { - int m_int; - double m_double; -}; - - -//=========================================================================== -enum EFruit {kApple=78, kBanana=29, kCitrus=34}; -extern std::vector vecFruits; - - -//=========================================================================== -enum class NamedClassEnum { E1 = 42 }; - -namespace EnumSpace { - enum E {E1 = 1, E2}; - class EnumClass { - public: - enum {E1 = -1}; - enum EE {E2 = -1}; - }; - - typedef enum { AA = 1, BB, CC, DD } letter_code; - - enum class NamedClassEnum { E1 = -42 }; -} - - -//=========================================================================== -class FourVector { -public: - FourVector(double x, double y, double z, double t) : - m_cc_called(false), m_x(x), m_y(y), m_z(z), m_t(t) {} - FourVector(const FourVector& s) : - m_cc_called(true), m_x(s.m_x), m_y(s.m_y), m_z(s.m_z), m_t(s.m_t) {} - - double operator[](int i) { - if (i == 0) return m_x; - if (i == 1) return m_y; - if (i == 2) return m_z; - if (i == 3) return m_t; - return -1; - } - - bool operator==(const FourVector& o) { - return (m_x == o.m_x && m_y == o.m_y && - m_z == o.m_z && m_t == o.m_t); - } - -public: - bool m_cc_called; - -private: - double m_x, m_y, m_z, m_t; -}; - - -//=========================================================================== -typedef std::complex complex_t; // maps to Py_complex -typedef std::complex icomplex_t; // no equivalent -#ifndef _WIN32 -typedef _Complex double ccomplex_t; // C-style complex, maps to Py_complex -#else -typedef _C_double_complex ccomplex_t; // id. -#endif - -class CppyyTestData { -public: - CppyyTestData(); - ~CppyyTestData(); - -// special cases - enum EWhat { kNothing=6, kSomething=111, kLots=42 }; - -// helper - void destroy_arrays(); - -// getters - bool get_bool(); - char get_char(); - signed char get_schar(); - unsigned char get_uchar(); - wchar_t get_wchar(); - char16_t get_char16(); - char32_t get_char32(); - std::byte get_byte(); - int8_t get_int8(); - uint8_t get_uint8(); - short get_short(); - unsigned short get_ushort(); - int get_int(); - unsigned int get_uint(); - long get_long(); - unsigned long get_ulong(); - long long get_llong(); - unsigned long long get_ullong(); - Long64_t get_long64(); - ULong64_t get_ulong64(); - float get_float(); - double get_double(); - long double get_ldouble(); - long double get_ldouble_def(long double ld = 1); - complex_t get_complex(); - icomplex_t get_icomplex(); - ccomplex_t get_ccomplex(); - EWhat get_enum(); - void* get_voidp(); - - bool* get_bool_array(); - bool* get_bool_array2(); - signed char* get_schar_array(); - signed char* get_schar_array2(); - unsigned char* get_uchar_array(); - unsigned char* get_uchar_array2(); - std::byte* get_byte_array(); - std::byte* get_byte_array2(); - int8_t* get_int8_array(); - int8_t* get_int8_array2(); - uint8_t* get_uint8_array(); - uint8_t* get_uint8_array2(); - short* get_short_array(); - short* get_short_array2(); - unsigned short* get_ushort_array(); - unsigned short* get_ushort_array2(); - int* get_int_array(); - int* get_int_array2(); - unsigned int* get_uint_array(); - unsigned int* get_uint_array2(); - long* get_long_array(); - long* get_long_array2(); - unsigned long* get_ulong_array(); - unsigned long* get_ulong_array2(); - - float* get_float_array(); - float* get_float_array2(); - double* get_double_array(); - double* get_double_array2(); - complex_t* get_complex_array(); - complex_t* get_complex_array2(); - ccomplex_t* get_ccomplex_array(); - ccomplex_t* get_ccomplex_array2(); - - CppyyTestPod get_pod_val(); // for m_pod - CppyyTestPod* get_pod_val_ptr(); - CppyyTestPod& get_pod_val_ref(); - CppyyTestPod*& get_pod_ptrref(); - - CppyyTestPod* get_pod_ptr(); // for m_ppod - -// getters const-ref - const bool& get_bool_cr(); - const char& get_char_cr(); - const signed char& get_schar_cr(); - const unsigned char& get_uchar_cr(); - const wchar_t& get_wchar_cr(); - const char16_t& get_char16_cr(); - const char32_t& get_char32_cr(); - const std::byte& get_byte_cr(); - const int8_t& get_int8_cr(); - const uint8_t& get_uint8_cr(); - const short& get_short_cr(); - const unsigned short& get_ushort_cr(); - const int& get_int_cr(); - const unsigned int& get_uint_cr(); - const long& get_long_cr(); - const unsigned long& get_ulong_cr(); - const long long& get_llong_cr(); - const unsigned long long& get_ullong_cr(); - const Long64_t& get_long64_cr(); - const ULong64_t& get_ulong64_cr(); - const float& get_float_cr(); - const double& get_double_cr(); - const long double& get_ldouble_cr(); - const complex_t& get_complex_cr(); - const icomplex_t& get_icomplex_cr(); - const ccomplex_t& get_ccomplex_cr(); - const EWhat& get_enum_cr(); - -// getters ref - bool& get_bool_r(); - char& get_char_r(); - signed char& get_schar_r(); - unsigned char& get_uchar_r(); - wchar_t& get_wchar_r(); - char16_t& get_char16_r(); - char32_t& get_char32_r(); - std::byte& get_byte_r(); - int8_t& get_int8_r(); - uint8_t& get_uint8_r(); - short& get_short_r(); - unsigned short& get_ushort_r(); - int& get_int_r(); - unsigned int& get_uint_r(); - long& get_long_r(); - unsigned long& get_ulong_r(); - long long& get_llong_r(); - unsigned long long& get_ullong_r(); - Long64_t& get_long64_r(); - ULong64_t& get_ulong64_r(); - float& get_float_r(); - double& get_double_r(); - long double& get_ldouble_r(); - complex_t& get_complex_r(); - icomplex_t& get_icomplex_r(); - ccomplex_t& get_ccomplex_r(); - EWhat& get_enum_r(); - -// setters - void set_bool(bool); - void set_char(char); - void set_schar(signed char); - void set_uchar(unsigned char); - void set_wchar(wchar_t); - void set_char16(char16_t); - void set_char32(char32_t); - void set_byte(std::byte); - void set_int8(int8_t); - void set_uint8(uint8_t); - void set_short(short); - void set_ushort(unsigned short); - void set_int(int); - void set_uint(unsigned int); - void set_long(long); - void set_ulong(unsigned long); - void set_llong(long long); - void set_ullong(unsigned long long); - void set_long64(Long64_t); - void set_ulong64(ULong64_t); - void set_float(float); - void set_double(double); - void set_ldouble(long double); - void set_complex(complex_t); - void set_icomplex(icomplex_t); - void set_ccomplex(ccomplex_t); - void set_enum(EWhat); - void set_voidp(void*); - - void set_pod_val(CppyyTestPod); // for m_pod - void set_pod_ptr_in(CppyyTestPod*); - void set_pod_ptr_out(CppyyTestPod*); - void set_pod_ref(const CppyyTestPod&); - void set_pod_ptrptr_in(CppyyTestPod**); - void set_pod_void_ptrptr_in(void**); - void set_pod_ptrptr_out(CppyyTestPod**); - void set_pod_void_ptrptr_out(void**); - - void set_pod_ptr(CppyyTestPod*); // for m_ppod - -// setters const-ref - void set_bool_cr(const bool&); - void set_char_cr(const char&); - void set_schar_cr(const signed char&); - void set_uchar_cr(const unsigned char&); - void set_wchar_cr(const wchar_t&); - void set_char16_cr(const char16_t&); - void set_char32_cr(const char32_t&); - void set_byte_cr(const std::byte&); - void set_int8_cr(const int8_t&); - void set_uint8_cr(const uint8_t&); - void set_short_cr(const short&); - void set_ushort_cr(const unsigned short&); - void set_int_cr(const int&); - void set_uint_cr(const unsigned int&); - void set_long_cr(const long&); - void set_ulong_cr(const unsigned long&); - void set_llong_cr(const long long&); - void set_ullong_cr(const unsigned long long&); - void set_long64_cr(const Long64_t&); - void set_ulong64_cr(const ULong64_t&); - void set_float_cr(const float&); - void set_double_cr(const double&); - void set_ldouble_cr(const long double&); - void set_complex_cr(const complex_t&); - void set_icomplex_cr(const icomplex_t&); - void set_ccomplex_cr(const ccomplex_t&); - void set_enum_cr(const EWhat&); - -// setters ref - void set_bool_r(bool&); - void set_char_r(char&); - void set_wchar_r(wchar_t&); - void set_char16_r(char16_t&); - void set_char32_r(char32_t&); - void set_schar_r(signed char&); - void set_uchar_r(unsigned char&); - void set_byte_r(std::byte&); - void set_short_r(short&); - void set_ushort_r(unsigned short&); - void set_int_r(int&); - void set_uint_r(unsigned int&); - void set_long_r(long&); - void set_ulong_r(unsigned long&); - void set_llong_r(long long&); - void set_ullong_r(unsigned long long&); - void set_float_r(float&); - void set_double_r(double&); - void set_ldouble_r(long double&); - -// setters ptr - void set_bool_p(bool*); - void set_char_p(char*); - void set_wchar_p(wchar_t*); - void set_char16_p(char16_t*); - void set_char32_p(char32_t*); - void set_schar_p(signed char*); - void set_uchar_p(unsigned char*); - void set_byte_p(std::byte*); - void set_int8_p(int8_t*); - void set_uint8_p(uint8_t*); - void set_short_p(short*); - void set_ushort_p(unsigned short*); - void set_int_p(int*); - void set_uint_p(unsigned int*); - void set_long_p(long*); - void set_ulong_p(unsigned long*); - void set_llong_p(long long*); - void set_ullong_p(unsigned long long*); - void set_float_p(float*); - void set_double_p(double*); - void set_ldouble_p(long double*); - -// setters ptrptr - void set_bool_ppa(bool**); - void set_char_ppa(char**); - void set_wchar_ppa(wchar_t**); - void set_char16_ppa(char16_t**); - void set_char32_ppa(char32_t**); - void set_schar_ppa(signed char**); - void set_uchar_ppa(unsigned char**); - void set_byte_ppa(std::byte**); - void set_int8_ppa(int8_t**); - void set_uint8_ppa(uint8_t**); - void set_short_ppa(short**); - void set_ushort_ppa(unsigned short**); - void set_int_ppa(int**); - void set_uint_ppa(unsigned int**); - void set_long_ppa(long**); - void set_ulong_ppa(unsigned long**); - void set_llong_ppa(long long**); - void set_ullong_ppa(unsigned long long**); - void set_float_ppa(float**); - void set_double_ppa(double**); - void set_ldouble_ppa(long double**); - - intptr_t set_char_ppm(char**); - intptr_t set_cchar_ppm(const char**); - intptr_t set_wchar_ppm(wchar_t**); - intptr_t set_char16_ppm(char16_t**); - intptr_t set_char32_ppm(char32_t**); - intptr_t set_cwchar_ppm(const wchar_t**); - intptr_t set_cchar16_ppm(const char16_t**); - intptr_t set_cchar32_ppm(const char32_t**); - intptr_t set_void_ppm(void**); - - intptr_t freeit(void*); - -// setters r-value - void set_bool_rv(bool&&); - void set_char_rv(char&&); - void set_schar_rv(signed char&&); - void set_uchar_rv(unsigned char&&); - void set_wchar_rv(wchar_t&&); - void set_char16_rv(char16_t&&); - void set_char32_rv(char32_t&&); - void set_byte_rv(std::byte&&); - void set_int8_rv(int8_t&&); - void set_uint8_rv(uint8_t&&); - void set_short_rv(short&&); - void set_ushort_rv(unsigned short&&); - void set_int_rv(int&&); - void set_uint_rv(unsigned int&&); - void set_long_rv(long&&); - void set_ulong_rv(unsigned long&&); - void set_llong_rv(long long&&); - void set_ullong_rv(unsigned long long&&); - void set_long64_rv(Long64_t&&); - void set_ulong64_rv(ULong64_t&&); - void set_float_rv(float&&); - void set_double_rv(double&&); - void set_ldouble_rv(long double&&); - void set_complex_rv(complex_t&&); - void set_icomplex_rv(icomplex_t&&); - void set_ccomplex_rv(ccomplex_t&&); - void set_enum_rv(EWhat&&); - -// passers - unsigned char* pass_array(unsigned char*); - short* pass_array(short*); - unsigned short* pass_array(unsigned short*); - int* pass_array(int*); - unsigned int* pass_array(unsigned int*); - long* pass_array(long*); - unsigned long* pass_array(unsigned long*); - float* pass_array(float*); - double* pass_array(double*); - complex_t* pass_array(complex_t*); - ccomplex_t* pass_array(ccomplex_t*); - - unsigned char* pass_void_array_B(void* a) { return pass_array((unsigned char*)a); } - short* pass_void_array_h(void* a) { return pass_array((short*)a); } - unsigned short* pass_void_array_H(void* a) { return pass_array((unsigned short*)a); } - int* pass_void_array_i(void* a) { return pass_array((int*)a); } - unsigned int* pass_void_array_I(void* a) { return pass_array((unsigned int*)a); } - long* pass_void_array_l(void* a) { return pass_array((long*)a); } - unsigned long* pass_void_array_L(void* a) { return pass_array((unsigned long*)a); } - float* pass_void_array_f(void* a) { return pass_array((float*)a); } - double* pass_void_array_d(void* a) { return pass_array((double*)a); } - complex_t* pass_void_array_Z(void* a) { return pass_array((complex_t*)a); } - ccomplex_t* pass_void_array_cZ(void* a) { return pass_array((ccomplex_t*)a); } - -// strings - const char* get_valid_string(const char* in); - const char* get_invalid_string(); - const wchar_t* get_valid_wstring(const wchar_t* in); - const wchar_t* get_invalid_wstring(); - const char16_t* get_valid_string16(const char16_t* in); - const char16_t* get_invalid_string16(); - const char32_t* get_valid_string32(const char32_t* in); - const char32_t* get_invalid_string32(); - -public: -// basic types - bool m_bool; - char m_char; - signed char m_schar; - unsigned char m_uchar; - wchar_t m_wchar; - char16_t m_char16; - char32_t m_char32; - std::byte m_byte; - int8_t m_int8; - uint8_t m_uint8; - short m_short; - unsigned short m_ushort; - int m_int; - const int m_const_int; // special case: const testing - unsigned int m_uint; - long m_long; - unsigned long m_ulong; - long long m_llong; - unsigned long long m_ullong; - Long64_t m_long64; - ULong64_t m_ulong64; - float m_float; - double m_double; - long double m_ldouble; - complex_t m_complex; - icomplex_t m_icomplex; - ccomplex_t m_ccomplex; - EWhat m_enum; - void* m_voidp; - -// array types - bool m_bool_array[N]; - bool* m_bool_array2; - signed char m_schar_array[N]; - signed char* m_schar_array2; - unsigned char m_uchar_array[N]; - unsigned char* m_uchar_array2; - std::byte m_byte_array[N]; - std::byte* m_byte_array2; - int8_t m_int8_array[N]; - int8_t* m_int8_array2; - uint8_t m_uint8_array[N]; - uint8_t* m_uint8_array2; - short m_short_array[N]; - short* m_short_array2; - unsigned short m_ushort_array[N]; - unsigned short* m_ushort_array2; - int m_int_array[N]; - int* m_int_array2; - unsigned int m_uint_array[N]; - unsigned int* m_uint_array2; - long m_long_array[N]; - long* m_long_array2; - unsigned long m_ulong_array[N]; - unsigned long* m_ulong_array2; - - float m_float_array[N]; - float* m_float_array2; - double m_double_array[N]; - double* m_double_array2; - complex_t m_complex_array[N]; - complex_t* m_complex_array2; - icomplex_t m_icomplex_array[N]; - icomplex_t* m_icomplex_array2; - ccomplex_t m_ccomplex_array[N]; - ccomplex_t* m_ccomplex_array2; - -// object types - CppyyTestPod m_pod; - CppyyTestPod* m_ppod; - -public: - static bool s_bool; - static char s_char; - static signed char s_schar; - static unsigned char s_uchar; - static wchar_t s_wchar; - static char16_t s_char16; - static char32_t s_char32; - static std::byte s_byte; - static int8_t s_int8; - static uint8_t s_uint8; - static short s_short; - static unsigned short s_ushort; - static int s_int; - static unsigned int s_uint; - static long s_long; - static unsigned long s_ulong; - static long long s_llong; - static unsigned long long s_ullong; - static Long64_t s_long64; - static ULong64_t s_ulong64; - static float s_float; - static double s_double; - static long double s_ldouble; - static complex_t s_complex; - static icomplex_t s_icomplex; - static ccomplex_t s_ccomplex; - static EWhat s_enum; - static void* s_voidp; - static std::string s_strv; - static std::string* s_strp; - -private: - bool m_owns_arrays; -}; - - -//= global functions ======================================================== -intptr_t get_pod_address(CppyyTestData& c); -intptr_t get_int_address(CppyyTestData& c); -intptr_t get_double_address(CppyyTestData& c); - - -//= global variables/pointers =============================================== -extern bool g_bool; -extern char g_char; -extern signed char g_schar; -extern unsigned char g_uchar; -extern wchar_t g_wchar; -extern char16_t g_char16; -extern char32_t g_char32; -extern std::byte g_byte; -extern int8_t g_int8; -extern uint8_t g_uint8; -extern short g_short; -extern unsigned short g_ushort; -extern int g_int; -extern unsigned int g_uint; -extern long g_long; -extern unsigned long g_ulong; -extern long long g_llong; -extern unsigned long long g_ullong; -extern Long64_t g_long64; -extern ULong64_t g_ulong64; -extern float g_float; -extern double g_double; -extern long double g_ldouble; -extern complex_t g_complex; -extern icomplex_t g_icomplex; -extern ccomplex_t g_ccomplex; -extern EFruit g_enum; -extern void* g_voidp; - -static const bool g_c_bool = true; -static const char g_c_char = 'z'; -static const signed char g_c_schar = 'y'; -static const unsigned char g_c_uchar = 'x'; -static const wchar_t g_c_wchar = L'U'; -static const char16_t g_c_char16 = u'\u6c34'; -static const char32_t g_c_char32 = U'\U0001f34c'; -static const std::byte g_c_byte = (std::byte)'u'; -static const int8_t g_c_int8 = -12; -static const uint8_t g_c_uint8 = 12; -static const short g_c_short = -99; -static const unsigned short g_c_ushort = 99u; -static const int g_c_int = -199; -static const unsigned int g_c_uint = 199u; -static const long g_c_long = -299; -static const unsigned long g_c_ulong = 299ul; -static const long long g_c_llong = -399ll; -static const unsigned long long g_c_ullong = 399ull; -static const Long64_t g_c_long64 = -499ll; -static const ULong64_t g_c_ulong64 = 499ull; -static const float g_c_float = -599.f; -static const double g_c_double = -699.; -static const long double g_c_ldouble = -799.l; -static const complex_t g_c_complex = {1., 2.}; -static const icomplex_t g_c_icomplex = {3, 4}; -static const ccomplex_t g_c_ccomplex = {5., 6.}; -static const EFruit g_c_enum = kApple; -static const void* g_c_voidp = nullptr; - - -//= global accessors ======================================================== -void set_global_int(int i); -int get_global_int(); - -extern CppyyTestPod* g_pod; -bool is_global_pod(CppyyTestPod* t); -void set_global_pod(CppyyTestPod* t); -CppyyTestPod* get_global_pod(); -CppyyTestPod* get_null_pod(); - -extern std::string g_some_global_string; -std::string get_some_global_string(); -extern std::string g_some_global_string2; -std::string get_some_global_string2(); - -extern const char16_t* g_some_global_string16; -extern const char32_t* g_some_global_string32; - -namespace SomeStaticDataNS { - extern std::string s_some_static_string; - std::string get_some_static_string(); - extern std::string s_some_static_string2; - std::string get_some_static_string2(); -} - -struct StorableData { - StorableData(double d) : fData(d) {} - double fData; -}; - -extern StorableData gData; - - -//= special case of "byte" arrays =========================================== -int64_t sum_uc_data(unsigned char* data, int size); -int64_t sum_byte_data(std::byte* data, int size); - -//= function pointer passing ================================================ -int sum_of_int1(int i1, int i2); -int sum_of_int2(int i1, int i2); -extern int (*sum_of_int_ptr)(int, int); -int call_sum_of_int(int i1, int i2); - -double sum_of_double(double d1, double d2); -double call_double_double(double (*d)(double, double), double d1, double d2); - -struct sum_of_int_struct { - int (*sum_of_int_ptr)(int, int); -}; - -//= callable passing ======================================================== -int call_int_int(int (*)(int, int), int, int); -void call_void(void (*f)(int), int i); -int call_refi(void (*fcn)(int&)); -int call_refl(void (*fcn)(long&)); -int call_refd(void (*fcn)(double&)); - -class StoreCallable { - double (*fF)(double, double); -public: - StoreCallable(double (*)(double, double)); - void set_callable(double (*)(double, double)); - double operator()(double, double); -}; - - -//= callable through std::function ========================================== -double call_double_double_sf(const std::function&, double d1, double d2); - -int call_int_int_sf(const std::function&, int, int); -void call_void_sf(const std::function&, int i); -int call_refi_sf(const std::function&); -int call_refl_sf(const std::function&); -int call_refd_sf(const std::function&); - -class StoreCallable_sf { - std::function fF; -public: - StoreCallable_sf(const std::function&); - void set_callable(const std::function&); - double operator()(double, double); -}; - - -//= array of struct variants ================================================ -namespace ArrayOfStruct { - -struct Foo { - int fVal; -}; - -struct Bar1 { - Bar1() : fArr(new Foo[2]) { fArr[0].fVal = 42; fArr[1].fVal = 13; } - Bar1(const Bar1&) = delete; - Bar1& operator=(const Bar1&) = delete; - ~Bar1() { delete[] fArr; } - Foo* fArr; -}; - -struct Bar2 { - Bar2(int num_foo) : fArr(std::unique_ptr{new Foo[num_foo]}) { - for (int i = 0; i < num_foo; ++i) fArr[i].fVal = 2*i; - } - std::unique_ptr fArr; -}; - -} // namespace ArrayOfStruct - - -//= array of C strings passing ============================================== -namespace ArrayOfCStrings { - std::vector takes_array_of_cstrings(const char* args[], int len); -} - - -//= aggregate testing ====================================================== -namespace AggregateTest { - -struct Aggregate1 { - static int sInt; -}; - -struct Aggregate2 { - static int sInt; - int fInt = 42; -}; - -} // namespace AggregateTest - - -//= multi-dim arrays ======================================================= -namespace MultiDimArrays { - -struct DataHolder { - DataHolder(); - ~DataHolder(); - - short** m_short2a; - unsigned short** m_unsigned_short2a; - int** m_int2a; - unsigned int** m_unsigned_int2a; - long** m_long2a; - unsigned long** m_unsigned_long2a; - long long** m_long_long2a; - unsigned long long** m_unsigned_long_long2a; - float** m_float2a; - double** m_double2a; - - short** m_short2b; - short** new_short2d(int N, int M); - unsigned short** m_unsigned_short2b; - unsigned short** new_ushort2d(int N, int M); - int** m_int2b; - int** new_int2d(int N, int M); - unsigned int** m_unsigned_int2b; - unsigned int** new_uint2d(int N, int M); - long** m_long2b; - long** new_long2d(int N, int M); - unsigned long** m_unsigned_long2b; - unsigned long** new_ulong2d(int N, int M); - long long** m_long_long2b; - long long** new_llong2d(int N, int M); - unsigned long long** m_unsigned_long_long2b; - unsigned long long** new_ullong2d(int N, int M); - float** m_float2b; - float** new_float2d(int N, int M); - double** m_double2b; - double** new_double2d(int N, int M); - - short m_short2c[3][5]; - unsigned short m_unsigned_short2c[3][5]; - int m_int2c[3][5]; - unsigned int m_unsigned_int2c[3][5]; - long m_long2c[3][5]; - unsigned long m_unsigned_long2c[3][5]; - long long m_long_long2c[3][5]; - unsigned long long m_unsigned_long_long2c[3][5]; - float m_float2c[3][5]; - double m_double2c[3][5]; - - short*** m_short3a; - unsigned short*** m_unsigned_short3a; - int*** m_int3a; - unsigned int*** m_unsigned_int3a; - long*** m_long3a; - unsigned long*** m_unsigned_long3a; - long long*** m_long_long3a; - unsigned long long*** m_unsigned_long_long3a; - float*** m_float3a; - double*** m_double3a; - - short m_short3c[3][5][7]; - unsigned short m_unsigned_short3c[3][5][7]; - int m_int3c[3][5][7]; - unsigned int m_unsigned_int3c[3][5][7]; - long m_long3c[3][5][7]; - unsigned long m_unsigned_long3c[3][5][7]; - long long m_long_long3c[3][5][7]; - unsigned long long m_unsigned_long_long3c[3][5][7]; - float m_float3c[3][5][7]; - double m_double3c[3][5][7]; -}; - -} // namespace MultiDimArrays - - -//= int8_t/uint8_t arrays =================================================== -namespace Int8_Uint8_Arrays { - extern int8_t test[6]; - extern uint8_t utest[6]; -} - -#endif // !CPPYY_TEST_DATATYPES_H diff --git a/test/datatypes.xml b/test/datatypes.xml deleted file mode 100644 index 9281b65..0000000 --- a/test/datatypes.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/doc_args_funcs.py b/test/doc_args_funcs.py deleted file mode 100644 index 481c3a8..0000000 --- a/test/doc_args_funcs.py +++ /dev/null @@ -1,5 +0,0 @@ -def ann_f1(arg: 'int') -> 'double': - return 3.1415*arg - -def ann_f2(arg1: 'int', arg2: 'int') -> 'int': - return 3*arg1*arg2 diff --git a/test/doc_helper.cxx b/test/doc_helper.cxx deleted file mode 100644 index 87bb2e3..0000000 --- a/test/doc_helper.cxx +++ /dev/null @@ -1,6 +0,0 @@ -#include "doc_helper.h" - -void DocHelper::throw_an_error(int i) { - if (i) throw SomeError{"this is an error"}; - throw SomeOtherError{"this is another error"}; -} diff --git a/test/doc_helper.h b/test/doc_helper.h deleted file mode 100644 index 9c6775e..0000000 --- a/test/doc_helper.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CPPYY_TEST_DOCHELPER_H -#define CPPYY_TEST_DOCHELPER_H - -#include -#include - - -class SomeError : public std::exception { -public: - explicit SomeError(const std::string& msg) : fMsg(msg) {} - SomeError(const SomeError& s) : fMsg(s.fMsg) {} - - const char* what() const throw() override { return fMsg.c_str(); } - -private: - std::string fMsg; -}; - -class SomeOtherError : public SomeError { -public: - explicit SomeOtherError(const std::string& msg) : SomeError(msg) {} - SomeOtherError(const SomeOtherError& s) : SomeError(s) {} -}; - -namespace DocHelper { - -void throw_an_error(int i); - -} // namespace DocHelper - -#endif // !CPPYY_TEST_DOCHELPER_H diff --git a/test/doc_helper.xml b/test/doc_helper.xml deleted file mode 100644 index 79bc4f4..0000000 --- a/test/doc_helper.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/test/example01.cxx b/test/example01.cxx deleted file mode 100644 index c70f23f..0000000 --- a/test/example01.cxx +++ /dev/null @@ -1,214 +0,0 @@ -#include -#include -#include -#include - -#include "example01.h" - -//=========================================================================== -payload::payload(double d) : m_data(d) { - count++; -} -payload::payload(const payload& p) : m_data(p.m_data) { - count++; -} -payload& payload::operator=(const payload& p) { - if (this != &p) { - m_data = p.m_data; - } - return *this; -} -payload::~payload() { - count--; -} - -double payload::getData() { return m_data; } -void payload::setData(double d) { m_data = d; } - -// class-level data -int payload::count = 0; - - -//=========================================================================== -example01::example01() : m_somedata(-99) { - count++; -} -example01::example01(int a) : m_somedata(a) { - count++; -} -example01::example01(const example01& e) : m_somedata(e.m_somedata) { - count++; -} -example01& example01::operator=(const example01& e) { - if (this != &e) { - m_somedata = e.m_somedata; - } - return *this; -} -example01::~example01() { - count--; -} - -// class-level methods -int example01::staticAddOneToInt(int a) { - return a + 1; -} -int example01::staticAddOneToInt(int a, int b) { - return a + b + 1; -} -double example01::staticAddToDouble(double a) { - return a + 0.01; -} -int example01::staticAtoi(const char* str) { - return ::atoi(str); -} -char* example01::staticStrcpy(const char* strin) { - char* strout = (char*)malloc(::strlen(strin)+1); - ::strcpy(strout, strin); - return strout; -} -void example01::staticSetPayload(payload* p, double d) { - p->setData(d); -} - -payload* example01::staticCyclePayload(payload* p, double d) { - staticSetPayload(p, d); - return p; -} - -payload example01::staticCopyCyclePayload(payload* p, double d) { - staticSetPayload(p, d); - return *p; -} - -int example01::getCount() { - return count; -} - -void example01::setCount(int value) { - count = value; -} - -// instance methods -int example01::addDataToInt(int a) { - return m_somedata + a; -} - -int example01::addDataToIntConstRef(const int& a) { - return m_somedata + a; -} - -int example01::overloadedAddDataToInt(int a, int b) { - return m_somedata + a + b; -} - -int example01::overloadedAddDataToInt(int a) { - return m_somedata + a; -} - -int example01::overloadedAddDataToInt(int a, int b, int c) { - return m_somedata + a + b + c; -} - -double example01::addDataToDouble(double a) { - return m_somedata + a; -} - -int example01::addDataToAtoi(const char* str) { - return ::atoi(str) + m_somedata; -} - -char* example01::addToStringValue(const char* str) { - int out = ::atoi(str) + m_somedata; - std::ostringstream ss; - ss << out << std::ends; - std::string result = ss.str(); - char* cresult = (char*)malloc(result.size()+1); - ::strcpy(cresult, result.c_str()); - return cresult; -} - -void example01::setPayload(payload* p) { - p->setData(m_somedata); -} - -payload* example01::cyclePayload(payload* p) { - setPayload(p); - return p; -} - -payload example01::copyCyclePayload(payload* p) { - setPayload(p); - return *p; -} - -// class-level data -int example01::count = 0; - - -// global -int globalAddOneToInt(int a) { - return a + 1; -} - -int ns_example01::globalAddOneToInt(int a) { - return ::globalAddOneToInt(a); -} - -int installableAddOneToInt(example01& e, int a) { - return e.staticAddOneToInt(a); -} - -int ns_example01::gMyGlobalInt = 99; - - -// argument passing -#define typeValueImp(itype, tname) \ -itype ArgPasser::tname##Value(itype arg0, int argn, itype arg1, itype arg2) \ -{ \ - switch (argn) { \ - case 0: \ - return arg0; \ - case 1: \ - return arg1; \ - case 2: \ - return arg2; \ - default: \ - break; \ - } \ - \ - return (itype)-1; \ -} - -typeValueImp(short, short) -typeValueImp(unsigned short, ushort) -typeValueImp(int, int) -typeValueImp(unsigned int, uint) -typeValueImp(long, long) -typeValueImp(unsigned long, ulong) - -typeValueImp(float, float) -typeValueImp(double, double) - -std::string ArgPasser::stringValue(std::string arg0, int argn, std::string arg1) -{ - switch (argn) { - case 0: - return arg0; - case 1: - return arg1; - default: - break; - } - - return "argn invalid"; -} - -std::string ArgPasser::stringRef(const std::string& arg0, int argn, const std::string& arg1) -{ - return stringValue(arg0, argn, arg1); -} - - -// special case naming -z_& z_::gime_z_(z_& z) { return z; } diff --git a/test/example01.h b/test/example01.h deleted file mode 100644 index e3e353c..0000000 --- a/test/example01.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef EXAMPLE_01_H -#define EXAMPLE_01_H - -#include - -class payload { -public: - payload(double d = 0.); - payload(const payload& p); - payload& operator=(const payload& e); - ~payload(); - - double getData(); - void setData(double d); - -public: // class-level data - static int count; - -private: - double m_data; -}; - - -class example01 { -public: - example01(); - example01(int a); - example01(const example01& e); - example01& operator=(const example01& e); - virtual ~example01(); - -public: // class-level methods - static int staticAddOneToInt(int a); - static int staticAddOneToInt(int a, int b); - static double staticAddToDouble(double a); - static int staticAtoi(const char* str); - static char* staticStrcpy(const char* strin); - static void staticSetPayload(payload* p, double d); - static payload* staticCyclePayload(payload* p, double d); - static payload staticCopyCyclePayload(payload* p, double d); - static int getCount(); - static void setCount(int); - -public: // instance methods - int addDataToInt(int a); - int addDataToIntConstRef(const int& a); - int overloadedAddDataToInt(int a, int b); - int overloadedAddDataToInt(int a); - int overloadedAddDataToInt(int a, int b, int c); - double addDataToDouble(double a); - int addDataToAtoi(const char* str); - char* addToStringValue(const char* str); - - void setPayload(payload* p); - payload* cyclePayload(payload* p); - payload copyCyclePayload(payload* p); - -public: // class-level data - static int count; - -public: // instance data - int m_somedata; -}; - - -// global functions and data -int globalAddOneToInt(int a); -namespace ns_example01 { - int globalAddOneToInt(int a); - extern int gMyGlobalInt; -} - -int installableAddOneToInt(example01&, int a); - -#define itypeValue(itype, tname) \ - itype tname##Value(itype arg0, int argn=0, itype arg1=1, itype arg2=2) - -#define ftypeValue(ftype) \ - ftype ftype##Value(ftype arg0, int argn=0, ftype arg1=1., ftype arg2=2.) - - -// argument passing -class ArgPasser { // use a class for now as methptrgetter not -public: // implemented for global functions - itypeValue(short, short); - itypeValue(unsigned short, ushort); - itypeValue(int, int); - itypeValue(unsigned int, uint); - itypeValue(long, long); - itypeValue(unsigned long, ulong); - - ftypeValue(float); - ftypeValue(double); - - std::string stringValue( - std::string arg0, int argn=0, std::string arg1 = "default"); - - std::string stringRef( - const std::string& arg0, int argn=0, const std::string& arg1="default"); -}; - - -// typedefs -typedef example01 example01_t; - - -// special case naming -class z_ { -public: - z_& gime_z_(z_& z); - int myint; -}; - -// for pythonization checking -class example01a : public example01 { -public: - example01a(int a) : example01(a) {} -}; - -#endif // EXAMPLE_01_H diff --git a/test/example01.xml b/test/example01.xml deleted file mode 100644 index bc1dfcf..0000000 --- a/test/example01.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/test/fragile.cxx b/test/fragile.cxx deleted file mode 100644 index 8ca3cf0..0000000 --- a/test/fragile.cxx +++ /dev/null @@ -1,69 +0,0 @@ -#include "fragile.h" -#include - - -fragile::H::HH* fragile::H::HH::copy() { - return (HH*)0; -} - -fragile::I fragile::gI; - -void fragile::fglobal(int, double, char) { - /* empty; only used for doc-string testing */ -} - -namespace fragile { - - class Kderived : public K { - public: - virtual ~Kderived(); - }; - -} // namespace fragile - -fragile::Kderived::~Kderived() {} - -fragile::K::~K() {} - -fragile::K* fragile::K::GimeK(bool derived) { - if (!derived) return this; - else { - static Kderived kd; - return &kd; - } -}; - -fragile::K* fragile::K::GimeL() { - static L l; - return &l; -} - -fragile::L::~L() {} - - -int fragile::create_handle(OpaqueHandle_t* handle) { - *handle = (OpaqueHandle_t)0x01; - return 0x01; -} - -int fragile::destroy_handle(OpaqueHandle_t handle, intptr_t addr) { - if ((intptr_t)handle == addr) - return 1; - return 0; -} - - -// for signal -> exception testing -void fragile::segfault() { - int* i = 0; *i = 42; -} - -void fragile::sigabort() { - assert(0); -} - - -// for duplicate testing -int fragile::add42(int i) { - return i + 42; -} diff --git a/test/fragile.h b/test/fragile.h deleted file mode 100644 index b7eb7a2..0000000 --- a/test/fragile.h +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef FRAGILE_H -#define FRAGILE_H - -#include - - -namespace fragile { - -class no_such_class; - -class A { -public: - virtual int check() { return (int)'A'; } - virtual A* gime_null() { return (A*)0; } -}; - -class B { -public: - virtual int check() { return (int)'B'; } - no_such_class* gime_no_such() { return 0; } -}; - -class C { -public: - virtual int check() { return (int)'C'; } - void use_no_such(no_such_class*) {} -}; - -class D { -public: - virtual int check() { return (int)'D'; } - virtual int check(int, int) { return (int)'D'; } - void overload() {} - void overload(no_such_class*) {} - void overload(char, int i = 0) {} // Reflex requires a named arg - void overload(int, no_such_class* p = 0) {} -}; - - -static const uintptr_t dummy_location = 0xdead; - -class E { -public: - E() : m_pp_no_such((no_such_class**)&dummy_location), m_pp_a(0) {} - - virtual int check() { return (int)'E'; } - void overload(no_such_class**) {} - - no_such_class** m_pp_no_such; - A** m_pp_a; -}; - -class F { -public: - F() : m_int(0) {} - virtual int check() { return (int)'F'; } - int m_int; -}; - -class G { -public: - enum { unnamed1=24, unnamed2=96 }; - - class GG {}; -}; - -class H { -public: - class HH { - public: - HH* copy(); - }; - HH* m_h; -}; - -class I { -public: - operator bool() { return 0; } -}; - -extern I gI; - -class J { -public: - int method1(int, double) { return 0; } -}; - -void fglobal(int, double, char); - -namespace nested1 { - class A {}; - namespace nested2 { - class A {}; - namespace nested3 { - class A {}; - } // namespace nested3 - } // namespace nested2 -} // namespace nested1 - -class K { -public: - virtual ~K(); - K* GimeK(bool derived); - K* GimeL(); -}; - -class L : public K { -public: - virtual ~L(); - no_such_class* m_no_such; -}; - -class M { -public: - virtual ~M() {} - enum E1 { kOnce=42 }; - enum E2 { kTwice=12 }; -}; - -class N : public M { -public: - enum E2 { kTwice=12 }; -}; - -class O { -public: - virtual int abstract() = 0; -}; - -class OpaqueType; -typedef OpaqueType* OpaqueHandle_t; - -int create_handle(OpaqueHandle_t* handle); -int destroy_handle(OpaqueHandle_t handle, intptr_t addr); - - -// for signal -> exception testing -void segfault(); -void sigabort(); - - -// for duplicate testing -int add42(int i); - -} // namespace fragile - -#endif // FRAGILE_H \ No newline at end of file diff --git a/test/fragile.xml b/test/fragile.xml deleted file mode 100644 index 652b56a..0000000 --- a/test/fragile.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/make_dict_win32.py b/test/make_dict_win32.py deleted file mode 100755 index 6837f6b..0000000 --- a/test/make_dict_win32.py +++ /dev/null @@ -1,108 +0,0 @@ -import glob, os, sys, subprocess - -USES_PYTHON_CAPI = set(('pythonizables',)) - -fn = sys.argv[1] - -if fn == 'all': - all_headers = glob.glob('*.h') - for header in all_headers: - res = os.system(" ".join(['python', sys.argv[0], header[:-2]]+sys.argv[2:])) - if res != 0: - sys.exit(res) - sys.exit(0) -else: - if fn[-4:] == '.cxx': fn = fn[:-4] - elif fn[-2:] == '.h': fn = fn[:-2] - if not os.path.exists(fn+'.h'): - print("file %s.h does not exist" % (fn,)) - sys.exit(1) - -uses_python_capi = False -if fn in USES_PYTHON_CAPI: - uses_python_capi = True - -if os.path.exists(fn+'Dict.dll'): - dct_time = os.stat(fn+'Dict.dll').st_mtime - if not '-f' in sys.argv: - mustbuild = False - for ext in ['.h', '.cxx', '.xml']: - if os.stat(fn+ext).st_mtime > dct_time: - mustbuild = True - break - if not mustbuild: - sys.exit(0) - - # cleanup - for fg in set(glob.glob(fn+"_rflx*") + glob.glob(fn+"Dict*") + \ - glob.glob("*.obj") + glob.glob(fn+"Linkdef.h")): - os.remove(fg) - -def _get_config_exec(): - return [sys.executable, '-m', 'cppyy_backend._cling_config'] - -def get_config(what): - config_exec_args = _get_config_exec() - config_exec_args.append('--'+what) - cli_arg = subprocess.check_output(config_exec_args) - return cli_arg.decode("utf-8").strip() - -def get_python_include_dir(): - incdir = subprocess.check_output([sys.executable, '-c', "import sysconfig; print(sysconfig.get_path('include'))"]) - return incdir.decode("utf-8").strip() - -def get_python_lib_dir(): - libdir = subprocess.check_output([sys.executable, '-c', "import sysconfig; print(sysconfig.get_path('stdlib'))"]) - return os.path.join(os.path.dirname(libdir.decode("utf-8").strip()), 'libs') - -# genreflex option -#DICTIONARY_CMD = "genreflex {fn}.h --selection={fn}.xml --rootmap={fn}Dict.rootmap --rootmap-lib={fn}Dict.dll".format(fn=fn) - -with open(fn+'Linkdef.h', 'w') as linkdef: - linkdef.write("#ifdef __CLING__\n\n") - linkdef.write("#pragma link C++ defined_in %s.h;\n" % fn) - linkdef.write("\n#endif") - -DICTIONARY_CMD = "python -m cppyy_backend._rootcling -f {fn}_rflx.cxx -rmf {fn}Dict.rootmap -rml {fn}Dict.dll {fn}.h {fn}Linkdef.h".format(fn=fn) -if os.system(DICTIONARY_CMD): - sys.exit(1) - -import platform -if '64' in platform.architecture()[0]: - PLATFORMFLAG = '-D_AMD64_' - MACHINETYPE = 'X64' -else: - PLATFORMFLAG = '-D_X86_' - MACHINETYPE = 'IX86' - -cppflags = get_config('cppflags') -if uses_python_capi: - cppflags += ' -I"' + get_python_include_dir() + '"' -BUILDOBJ_CMD_PART = "cl -O2 -nologo -TP -c -nologo " + cppflags + " -FIsehmap.h -MD -GR -D_WINDOWS -DWIN32 " + PLATFORMFLAG + " -EHsc- -W3 -wd4141 -wd4291 -wd4244 -wd4049 -D_XKEYCHECK_H -D_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS {fn}.cxx -Fo{fn}.obj" -BUILDOBJ_CMD = BUILDOBJ_CMD_PART.format(fn=fn) -if os.system(BUILDOBJ_CMD): - sys.exit(1) -BUILDOBJ_CMD = BUILDOBJ_CMD_PART.format(fn=fn+'_rflx') -if os.system(BUILDOBJ_CMD): - sys.exit(1) - -import cppyy_backend -CREATEDEF_CMD = "python bindexplib.py {fn} {fn}Dict".format(fn=fn) -if os.system(CREATEDEF_CMD): - sys.exit(1) - -ldflags = '' -if uses_python_capi: - ldflags = ' /LIBPATH:"' + get_python_lib_dir() + '" ' -CREATELIB_CMD = ("lib -nologo -MACHINE:" + MACHINETYPE + " -out:{fn}Dict.lib {fn}.obj {fn}_rflx.obj -def:{fn}Dict.def " + ldflags).format(fn=fn) -if os.system(CREATELIB_CMD): - sys.exit(1) - -ldflags += get_config('ldflags') -LINKDLL_CMD = ("link -nologo {fn}.obj {fn}_rflx.obj -DLL -out:{fn}Dict.dll {fn}Dict.exp " + ldflags).format(fn=fn) -if os.system(LINKDLL_CMD): - sys.exit(1) - -# cleanup -for fg in set(glob.glob(fn+"_rflx.cxx*") + glob.glob("*.obj") + glob.glob(fn+"Linkdef.h")): - os.remove(fg) diff --git a/test/operators.cxx b/test/operators.cxx deleted file mode 100644 index 4bd4c3e..0000000 --- a/test/operators.cxx +++ /dev/null @@ -1,194 +0,0 @@ -#include "operators.h" - -// for testing the case of virtual operator== -v_opeq_base::v_opeq_base(int val) : m_val(val) {} -v_opeq_base::~v_opeq_base() {} - -bool v_opeq_base::operator==(const v_opeq_base& other) { - return m_val == other.m_val; -} - -v_opeq_derived::v_opeq_derived(int val) : v_opeq_base(val) {} -v_opeq_derived::~v_opeq_derived() {} - -bool v_opeq_derived::operator==(const v_opeq_derived& other) { - return m_val != other.m_val; -} - - -// for indexing tests -int& YAMatrix1::operator() (int, int) { - return m_val; -} - -const int& YAMatrix1::operator() (int, int) const { - return m_val; -} - -//- -int& YAMatrix2::operator[] (int) { - return m_val; -} - -const int& YAMatrix2::operator[] (int) const { - return m_val; -} - -//- -int& YAMatrix3::operator() (int, int) { - return m_val; -} - -const int& YAMatrix3::operator() (int, int) const { - return m_val; -} - -int& YAMatrix3::operator[] (int) { - return m_val; -} - -const int& YAMatrix3::operator[] (int) const { - return m_val; -} - -//- -int& YAMatrix4::operator[] (int) { - return m_val; -} - -const int& YAMatrix4::operator[] (int) const { - return m_val; -} - -int& YAMatrix4::operator() (int, int) { - return m_val; -} - -const int& YAMatrix4::operator() (int, int) const { - return m_val; -} - -//- -int& YAMatrix5::operator[] (int) { - return m_val; -} - -int YAMatrix5::operator[] (int) const { - return m_val; -} - -int& YAMatrix5::operator() (int, int) { - return m_val; -} - -int YAMatrix5::operator() (int, int) const { - return m_val; -} - -//- -int& YAMatrix6::operator[] (int) { - return m_val; -} - -int YAMatrix6::operator[] (int) const { - return m_val; -} - -int& YAMatrix6::operator() (int, int) { - return m_val; -} - -//- -int& YAMatrix7::operator[] (int) { - return m_val; -} - -int& YAMatrix7::operator() (int, int) { - return m_val; -} - - -//- for __radd__/__rmul__, non-associative -AssocADD operator+(int n, AssocADD& a) { - return AssocADD(n+a.fx); -} - -AssocADD operator+(AssocADD& a, int n) { - return AssocADD(a.fx+n); -} - -NonAssocRADD operator+(int n, NonAssocRADD& a) { - return NonAssocRADD(n+a.fx); -} - -AssocMUL operator*(int n, AssocMUL& m) { - return AssocMUL(n*m.fx); -} - -AssocMUL operator*(AssocMUL& m, int n) { - return AssocMUL(m.fx*n); -} - -NonAssocRMUL operator*(int n, NonAssocRMUL& m) { - return NonAssocRMUL(n*m.fx); -} - - -//- for multi-lookup -double MultiLookup::operator*(const Vector2& v1, const Vector2& v2) { - return v1.x*v2.x + v1.y*v2.y; -} - -MultiLookup::Vector2 MultiLookup::operator*(const Vector2& v, double a) { - return Vector2{v.x*a, v.y*a}; -} - -double MultiLookup::operator/(const Vector2& v1, const Vector2& v2) { - return v1.x/v2.x + v1.y/v2.y; -} - -MultiLookup::Vector2 MultiLookup::operator/(const Vector2& v, double a) { - return Vector2{v.x/a, v.y/a}; -} - -double MultiLookup::operator+(const Vector2& v1, const Vector2& v2) { - return v1.x+v2.x + v1.y+v2.y; -} - -MultiLookup::Vector2 MultiLookup::operator+(const Vector2& v, double a) { - return Vector2{v.x+a, v.y+a}; -} - -double MultiLookup::operator-(const Vector2& v1, const Vector2& v2) { - return v1.x-v2.x + v1.y-v2.y; -} - -MultiLookup::Vector2 MultiLookup::operator-(const Vector2& v, double a) { - return Vector2{v.x-a, v.y-a}; -} - - -//- for unary functions -SomeGlobalNumber operator-(const SomeGlobalNumber& n) { - return SomeGlobalNumber{-n.i}; -} - -SomeGlobalNumber operator+(const SomeGlobalNumber& n) { - return SomeGlobalNumber{+n.i}; -} - -SomeGlobalNumber operator~(const SomeGlobalNumber& n) { - return SomeGlobalNumber{~n.i}; -} - -Unary::SomeNumber Unary::operator-(const SomeNumber& n) { - return SomeNumber{-n.i}; -} - -Unary::SomeNumber Unary::operator+(const SomeNumber& n) { - return SomeNumber{+n.i}; -} - -Unary::SomeNumber Unary::operator~(const SomeNumber& n) { - return SomeNumber{~n.i}; -} diff --git a/test/operators.h b/test/operators.h deleted file mode 100644 index 98d1123..0000000 --- a/test/operators.h +++ /dev/null @@ -1,332 +0,0 @@ -#ifndef CPPYY_TEST_OPERATORS_H -#define CPPYY_TEST_OPERATORS_H - -class number { -public: - number() { m_int = 0; } - number(int i) { m_int = i; } - - number operator+(const number& n) const { return number(m_int + n.m_int); } - number operator+(int n) const { return number(m_int + n); } - number operator-(const number& n) const { return number(m_int - n.m_int); } - number operator-(int n) const { return number(m_int - n); } - number operator*(const number& n) const { return number(m_int * n.m_int); } - number operator*(int n) const { return number(m_int * n); } - number operator/(const number& n) const { return number(m_int / n.m_int); } - number operator/(int n) const { return number(m_int / n); } - number operator%(const number& n) const { return number(m_int % n.m_int); } - number operator%(int n) const { return number(m_int % n); } - - number& operator+=(const number& n) { m_int += n.m_int; return *this; } - number& operator-=(const number& n) { m_int -= n.m_int; return *this; } - number& operator*=(const number& n) { m_int *= n.m_int; return *this; } - number& operator/=(const number& n) { m_int /= n.m_int; return *this; } - number& operator%=(const number& n) { m_int %= n.m_int; return *this; } - - number operator-() { return number( -m_int ); } - - bool operator<(const number& n) const { return m_int < n.m_int; } - bool operator>(const number& n) const { return m_int > n.m_int; } - bool operator<=(const number& n) const { return m_int <= n.m_int; } - bool operator>=(const number& n) const { return m_int >= n.m_int; } - bool operator!=(const number& n) const { return m_int != n.m_int; } - bool operator==(const number& n) const { return m_int == n.m_int; } - - operator bool() { return m_int != 0; } - - number operator&(const number& n) const { return number(m_int & n.m_int); } - number operator|(const number& n) const { return number(m_int | n.m_int); } - number operator^(const number& n) const { return number(m_int ^ n.m_int); } - - number& operator&=(const number& n) { m_int &= n.m_int; return *this; } - number& operator|=(const number& n) { m_int |= n.m_int; return *this; } - number& operator^=(const number& n) { m_int ^= n.m_int; return *this; } - - number operator<<(int i) const { return number(m_int << i); } - number operator>>(int i) const { return number(m_int >> i); } - -private: - int m_int; -}; - - -//---------------------------------------------------------------------------- -struct operator_char_star { // for testing user-defined implicit casts - operator_char_star() : m_str((char*)"operator_char_star") {} - operator char*() { return m_str; } - char* m_str; -}; - -struct operator_const_char_star { - operator_const_char_star() : m_str("operator_const_char_star" ) {} - operator const char*() { return m_str; } - const char* m_str; -}; - -struct operator_int { - operator int() { return m_int; } - int m_int; -}; - -struct operator_long { - operator long() { return m_long; } - long m_long; -}; - -struct operator_double { - operator double() { return m_double; } - double m_double; -}; - -struct operator_short { - operator short() { return m_short; } - unsigned short m_short; -}; - -struct operator_unsigned_int { - operator unsigned int() { return m_uint; } - unsigned int m_uint; -}; - -struct operator_unsigned_long { - operator unsigned long() { return m_ulong; } - unsigned long m_ulong; -}; - -struct operator_float { - operator float() { return m_float; } - float m_float; -}; - - -//---------------------------------------------------------------------------- -class v_opeq_base { -public: - v_opeq_base(int val); - virtual ~v_opeq_base(); - - virtual bool operator==(const v_opeq_base& other); - -protected: - int m_val; -}; - -class v_opeq_derived : public v_opeq_base { -public: - v_opeq_derived(int val); - virtual ~v_opeq_derived(); - - virtual bool operator==(const v_opeq_derived& other); -}; - - -//---------------------------------------------------------------------------- -class YAMatrix1 { // YetAnotherMatrix class for indexing tests -public: - YAMatrix1() : m_val(42) {} - - int& operator() (int i, int j); - const int& operator() (int i, int j) const; - - int m_val; -}; - -class YAMatrix2 { -public: - YAMatrix2() : m_val(42) {} - - int& operator[] (int i); - const int& operator[] (int i) const; - - int m_val; -}; - -class YAMatrix3 { -public: - YAMatrix3() : m_val(42) {} - - int& operator() (int i, int j); - const int& operator() (int i, int j) const; - - int& operator[] (int i); - const int& operator[] (int i) const; - - int m_val; -}; - -class YAMatrix4 { -public: - YAMatrix4() : m_val(42) {} - -// opposite order of method declarations from YAMatrix3 - int& operator[] (int i); - const int& operator[] (int i) const; - - int& operator() (int i, int j); - const int& operator() (int i, int j) const; - - int m_val; -}; - -class YAMatrix5 { -public: - YAMatrix5() : m_val(42) {} - - int& operator[] (int i); - int operator[] (int i) const; - - int& operator() (int i, int j); - int operator() (int i, int j) const; - - int m_val; -}; - -class YAMatrix6 { -public: - YAMatrix6() : m_val(42) {} - - int& operator[] (int i); - int operator[] (int i) const; - - int& operator() (int i, int j); - - int m_val; -}; - -class YAMatrix7 { -public: - YAMatrix7() : m_val(42) {} - - int& operator[] (int i); - int& operator() (int i, int j); - - int m_val; -}; - - -//---------------------------------------------------------------------------- -// templated operator -class TOIClass { -public: - template - bool operator<(T t) { return true; } -}; - - -//---------------------------------------------------------------------------- -// __radd__/__rmul__, (non-)associative -class AssocADD { -public: - AssocADD(double d) : fx(d) {} - bool operator==(double d) { return d == fx; } - - friend AssocADD operator+(int n, AssocADD& a); - friend AssocADD operator+(AssocADD& a, int n); - -private: - double fx; -}; - -AssocADD operator+(int n, AssocADD& a); -AssocADD operator+(AssocADD& a, int n); - -class NonAssocRADD { -public: - NonAssocRADD(double d) : fx(d) {} - bool operator==(double d) { return d == fx; } - - friend NonAssocRADD operator+(int n, NonAssocRADD& a); - -private: - double fx; -}; - -NonAssocRADD operator+(int n, NonAssocRADD& a); - -class AssocMUL { -public: - AssocMUL(double d) : fx(d) {} - bool operator==(double d) { return d == fx; } - - friend AssocMUL operator*(int n, AssocMUL& m); - friend AssocMUL operator*(AssocMUL& m, int n); - -private: - double fx; -}; - -AssocMUL operator*(int n, AssocMUL& m); -AssocMUL operator*(AssocMUL& m, int n); - -class NonAssocRMUL { -public: - NonAssocRMUL(double d) : fx(d) {} - bool operator==(double d) { return d == fx; } - - friend NonAssocRMUL operator*(int n, NonAssocRMUL& m); - -private: - double fx; -}; - -NonAssocRMUL operator*(int n, NonAssocRMUL& m); - - -//---------------------------------------------------------------------------- -// multi-lookup -namespace MultiLookup { - -struct Vector2 { - Vector2(double d1, double d2) : x(d1), y(d2) {} - double x, y; -}; - -double operator*(const Vector2&, const Vector2&); -Vector2 operator*(const Vector2&, double); - -double operator/(const Vector2&, const Vector2&); -Vector2 operator/(const Vector2&, double); - -double operator+(const Vector2&, const Vector2&); -Vector2 operator+(const Vector2&, double); - -double operator-(const Vector2&, const Vector2&); -Vector2 operator-(const Vector2&, double); - -} // namespace MultiLookup - - -//---------------------------------------------------------------------------- -// unary functions -struct SomeGlobalNumber { - SomeGlobalNumber(int n) : i(n) {} - int i; -}; - -SomeGlobalNumber operator-(const SomeGlobalNumber&); -SomeGlobalNumber operator+(const SomeGlobalNumber&); -SomeGlobalNumber operator~(const SomeGlobalNumber&); - -namespace Unary { - -struct SomeNumber { - SomeNumber(int n) : i(n) {} - int i; -}; - -SomeNumber operator-(const SomeNumber&); -SomeNumber operator+(const SomeNumber&); -SomeNumber operator~(const SomeNumber&); - -} - - -//---------------------------------------------------------------------------- -// comma operator (mapped to __comma__) -struct CommaOperator { - CommaOperator(int i) : fInt(i) {} - CommaOperator& operator,(int i) { fInt += i; return *this; } - int fInt; -}; - -#endif // !CPPYY_TEST_OPERATORS_H diff --git a/test/operators.xml b/test/operators.xml deleted file mode 100644 index 4bd7ab5..0000000 --- a/test/operators.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/overloads.h b/test/overloads.h deleted file mode 100644 index 96b5da1..0000000 --- a/test/overloads.h +++ /dev/null @@ -1,119 +0,0 @@ -#include -#include - -class a_overload { -public: - a_overload(); - int i1, i2; -}; - -namespace ns_a_overload { - class a_overload { - public: - a_overload(); - int i1, i2; - }; - - class b_overload { - public: - int f(const std::vector* v); - }; -} - -namespace ns_b_overload { - class a_overload { - public: - a_overload(); - int i1, i2; - }; -} - -class b_overload { -public: - b_overload(); - int i1, i2; -}; - -class c_overload { -public: - c_overload(); - int get_int(a_overload* a); - int get_int(ns_a_overload::a_overload* a); - int get_int(ns_b_overload::a_overload* a); - int get_int(short* p); - int get_int(b_overload* b); - int get_int(int* p); -}; - -class d_overload { -public: - d_overload(); -// int get_int(void* p) { return *(int*)p; } - int get_int(int* p); - int get_int(b_overload* b); - int get_int(short* p); - int get_int(ns_b_overload::a_overload* a); - int get_int(ns_a_overload::a_overload* a); - int get_int(a_overload* a); -}; - - -class aa_ol {}; -class bb_ol; -bb_ol* get_bb_ol(); -class cc_ol {}; -class dd_ol; -dd_ol* get_dd_ol(); - - -class more_overloads { -public: - more_overloads(); - std::string call(const aa_ol&); - std::string call(const bb_ol&, void* n=0); - std::string call(const cc_ol&); - std::string call(const dd_ol&); - - std::string call_unknown(const dd_ol&); - - std::string call(double); - std::string call(int); - std::string call1(int); - std::string call1(double); -}; - -class more_overloads2 { -public: - more_overloads2(); - std::string call(const bb_ol&); - std::string call(const bb_ol*); - - std::string call(const dd_ol*, int); - std::string call(const dd_ol&, int); -}; - -template -double calc_mean(long n, const T* a) { - double sum = 0., sumw = 0.; - const T* end = a+n; - while (a != end) { - sum += *a++; - sumw += 1; - } - - return sum/sumw; -} - -double calc_mean(long n, const float* a); -double calc_mean(long n, const double* a); -double calc_mean(long n, const int* a); -double calc_mean(long n, const short* a); -double calc_mean(long n, const long* a); - -class more_overloads3 { -public: - std::string slice(size_t) const; - std::string slice(size_t); - std::string slice(size_t, size_t); - std::string slice(size_t, size_t) const; -}; diff --git a/test/overloads.xml b/test/overloads.xml deleted file mode 100644 index 418344b..0000000 --- a/test/overloads.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/pythonizables.h b/test/pythonizables.h deleted file mode 100644 index 1dc5610..0000000 --- a/test/pythonizables.h +++ /dev/null @@ -1,169 +0,0 @@ -#include -#include -#include - -// Python -struct _object; -typedef _object PyObject; - - -namespace pyzables { - -//=========================================================================== -class SomeDummy1 {}; -class SomeDummy2 {}; - - -//=========================================================================== -class NakedBuffers { -public: - NakedBuffers(int size, double valx, double valy); - NakedBuffers(const NakedBuffers&) = delete; - NakedBuffers& operator=(const NakedBuffers&) = delete; - ~NakedBuffers(); - -public: - int GetN(); - double* GetX(); - double* GetY(); - -private: - double* m_Xbuf; - double* m_Ybuf; - int m_size; -}; - -template -class NakedBuffers2 { -public: - NakedBuffers2(int size, double valx, double valy) : m_Xbuf(size), m_Ybuf(size) { - for (int i=0; i { -public: - Vector(int size) : std::vector(size) {} -}; - - -//=========================================================================== -class MyBase { -public: - virtual ~MyBase(); -}; -class MyDerived : public MyBase { -public: - virtual ~MyDerived(); -}; - -MyBase* GimeDerived(); - - -//=========================================================================== -class Countable { -public: - Countable() { ++sInstances; } - Countable(const Countable&) { ++sInstances; } - Countable& operator=(const Countable&) { return *this; } - ~Countable() { --sInstances; } - -public: - virtual const char* say_hi() { return "Hi!"; } - -public: - unsigned int m_check = 0xcdcdcdcd; - -public: - static int sInstances; -}; - -typedef std::shared_ptr SharedCountable_t; -extern SharedCountable_t mine; - -void renew_mine(); - -SharedCountable_t gime_mine(); -SharedCountable_t* gime_mine_ptr(); -SharedCountable_t& gime_mine_ref(); - -unsigned int pass_mine_sp(SharedCountable_t p); -unsigned int pass_mine_sp_ref(SharedCountable_t& p); -unsigned int pass_mine_sp_ptr(SharedCountable_t* p); - -unsigned int pass_mine_rp(Countable); -unsigned int pass_mine_rp_ref(const Countable&); -unsigned int pass_mine_rp_ptr(const Countable*); - -Countable* gime_naked_countable(); - - -//=========================================================================== -class unknown_iterator; -class IndexableBase { -public: - unknown_iterator* begin() { return nullptr; } - unknown_iterator* end() { return (unknown_iterator*)1; } - int operator[](int) { return 42; } - int size() { return 1; } -}; - -class IndexableDerived : public IndexableBase {}; - - -//=========================================================================== -class WithCallback1 { -public: - WithCallback1(int i); - -public: - int get_int(); - void set_int(int i); - -private: - int m_int; - -public: - static void __cppyy_explicit_pythonize__(PyObject* klass, const std::string&); - static std::string klass_name; -}; - -class WithCallback2 { -public: - WithCallback2(int i); - -public: - int get_int(); - void set_int(int i); - -protected: - int m_int; - -public: - static void __cppyy_pythonize__(PyObject* klass, const std::string&); - static std::string klass_name; -}; - -class WithCallback3 : public WithCallback2 { -public: - using WithCallback2::WithCallback2; - -public: - int get_int(); - void set_int(int i); -}; - -} // namespace pyzables diff --git a/test/pythonizables.xml b/test/pythonizables.xml deleted file mode 100644 index 154869d..0000000 --- a/test/pythonizables.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/test/std_streams.cxx b/test/std_streams.cxx deleted file mode 100644 index 30a7940..0000000 --- a/test/std_streams.cxx +++ /dev/null @@ -1,5 +0,0 @@ -#include "std_streams.h" - -#ifndef _WIN32 -template class std::basic_ios >; -#endif diff --git a/test/std_streams.xml b/test/std_streams.xml deleted file mode 100644 index b14aa88..0000000 --- a/test/std_streams.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/test/stltypes.cxx b/test/stltypes.cxx deleted file mode 100644 index be178ff..0000000 --- a/test/stltypes.cxx +++ /dev/null @@ -1,97 +0,0 @@ -#include "stltypes.h" - -#include - -//- adverse effect of implicit conversion on vector -int vectest_ol1(const std::vector&) { return 1; } -int vectest_ol1(std::string) { return 2; } -int vectest_ol2(std::string) { return 2; } -int vectest_ol2(const std::vector&) { return 1; } - - -//- helpers for testing array -int ArrayTest::get_pp_px(Point** p, int idx) { - return p[idx]->px; -} - -int ArrayTest::get_pp_py(Point** p, int idx) { - return p[idx]->py; -} - -int ArrayTest::get_pa_px(Point* p[], int idx) { - return p[idx]->px; -} - -int ArrayTest::get_pa_py(Point* p[], int idx) { - return p[idx]->py; -} - - -// helpers for string testing -std::string str_array_1[3] = {"a", "b", "c"}; -std::string str_array_2[] = {"d", "e", "f", "g"}; -std::string str_array_3[3][2] = {{"a", "b"}, {"c", "d"}, {"e", "f"}}; -std::string str_array_4[4][2][2] = { - {{"a", "b"}, {"c", "d"}}, - {{"e", "f"}, {"g", "h"}}, - {{"i", "j"}, {"k", "l"}}, - {{"m", "n"}, {"o", "p"}}, -}; - - -// helpers for mixing unicode and std::string -size_t UnicodeAndSTL::get_size(std::string s) { return s.size(); } -size_t UnicodeAndSTL::get_size_cr(const std::string& s ) { return s.size(); } -size_t UnicodeAndSTL::get_size_cc(const char* s) { return strlen(s); } -size_t UnicodeAndSTL::get_size_w(std::wstring s) { return s.size(); } -size_t UnicodeAndSTL::get_size_wcr(const std::wstring& s) { return s.size(); } -std::string UnicodeAndSTL::get_string(std::string s) { return s; } -std::string UnicodeAndSTL::get_string_cr(const std::string& s) { return s; } -std::string UnicodeAndSTL::get_string_cc(const char* s) { return s; } -std::wstring UnicodeAndSTL::get_string_w(std::wstring s) { return s; } -std::wstring UnicodeAndSTL::get_string_wcr(const std::wstring& s) { return s; } - - -// helpers for string_view testing -std::string_view::size_type StringViewTest::count(const std::string_view arg) { - return arg.size(); -} - -std::string_view::size_type StringViewTest::count_cr(const std::string_view& arg) { - return arg.size(); -} - -// helper for exception base class testing -int MyError::s_count = 0; -int MyError::get_count() { return s_count; } - -MyError::MyError(const std::string& msg) : fMsg(msg) { - s_count += 1; -} - -MyError::MyError(const MyError& other) : fMsg(other.fMsg) { - s_count += 1; -} - -MyError::~MyError() { - s_count -= 1; -} - -const char* MyError::what() const throw() { return fMsg.c_str(); } - -int GetMyErrorCount() { - return MyError::s_count; -} - -YourError::YourError(const std::string& msg) : MyError(msg) {} -YourError::YourError(const YourError& s) : MyError(s) {} - -ErrorNamespace::MyError::MyError(const std::string& msg) : fMsg(msg) {} -const char* ErrorNamespace::MyError::what() const throw() { return fMsg.c_str(); } - -void ErrorNamespace::throw_error(int i) { - if (i == 0) throw ::MyError("first error"); - else if (i == 1) throw ::YourError("second error"); - else if (i == 2) throw MyError("third error"); - throw YourError("fourth error"); -} diff --git a/test/stltypes.h b/test/stltypes.h deleted file mode 100644 index ac55ea8..0000000 --- a/test/stltypes.h +++ /dev/null @@ -1,261 +0,0 @@ -#ifndef CPPYY_TEST_STLTYPES_H -#define CPPYY_TEST_STLTYPES_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include "datatypes.h" // for gbl.N - -//- basic example class -class just_a_class { -public: - int m_i; -}; - -// enum for vector of enums setitem tests -enum VecTestEnum { - EVal1 = 1, EVal2 = 3 -}; - -namespace VecTestEnumNS { - enum VecTestEnum { EVal1 = 5, EVal2 = 42 }; -} - - -//- adverse effect of implicit conversion on vector -int vectest_ol1(const std::vector&); -int vectest_ol1(std::string); -int vectest_ol2(std::string); -int vectest_ol2(const std::vector&); - - -//- class with lots of std::[w]string handling -template -class stringy_class { -public: - stringy_class(const typename S::value_type* s) : m_string(s) {} - - S get_string1() { return m_string; } - void get_string2(S& s) { s = m_string; } - - void set_string1(const S& s) { m_string = s; } - void set_string2(S s) { m_string = s; } - - S m_string; -}; - -typedef stringy_class stringy_class_t; -typedef stringy_class wstringy_class_t; - - -//- class that has an STL-like interface -class no_dict_available; - -template -class stl_like_class { -public: - no_dict_available* begin() { return 0; } - no_dict_available* end() { return (no_dict_available*)1; } - int size() { return 4; } - int operator[](int i) { return i; } - std::string operator[](double) { return "double"; } - std::string operator[](const std::string&) { return "string"; } -}; - -namespace { - stl_like_class stlc_1; -} - - -//- similar, but now the iterators don't work b/c they don't compile -template -class stl_like_class2 { -protected: - value_type fData[sz]; - -public: - static const size_t size() { return sz; } - value_type& operator[](ptrdiff_t i) { return fData[i]; } -}; - -template -class stl_like_class3 : public stl_like_class2 { - using stl_like_class2::fData; -public: - size_t size() { return sz; } - value_type& begin() { return fData; } - value_type& end() { return fData + sz; } -}; - -class stl_like_class4 { -public: - struct iterator { - iterator(int p) : pos(p) {} - int operator++() { ++pos; return pos; } - bool operator!=(const iterator& other) { return pos != other.pos; } - int operator*() { return pos; } - int pos; - }; - iterator begin() { return iterator{0}; } - iterator end() { return iterator{10}; } -}; - -class stl_like_class5 { -public: - struct iterator { - iterator(int p) : pos(p) {} - int operator++(int) { int val = pos; ++pos; return val; } - bool operator!=(const iterator& other) { return pos != other.pos; } - int operator*() { return pos; } - int pos; - }; - iterator begin() { return iterator{0}; } - iterator end() { return iterator{10}; } -}; - -class stl_like_class6 { -public: - struct some_name { - some_name(int p) : pos(p) {} - int operator++() { ++pos; return pos; } - bool operator!=(const some_name& other) { return pos != other.pos; } - int operator*() { return pos; } - int pos; - }; - some_name begin() { return some_name{0}; } - some_name end() { return some_name{10}; } -}; - -struct some_name7 { - some_name7(int p) : pos(p) {} - int operator++() { ++pos; return pos; } - bool operator!=(const some_name7& other) { return pos != other.pos; } - int operator*() { return pos; } - int pos; -}; - -class stl_like_class7 { -public: - some_name7 begin() { return some_name7{0}; } - some_name7 end() { return some_name7{10}; } -}; - - -// similar, but now with a base/derives split -class stl_like_class_base { -protected: - std::vector fVec; - -public: - void push_back(double d) { fVec.push_back(d); } - std::vector::size_type size() { return fVec.size(); } -}; - -class stl_like_class8 : public stl_like_class_base { -public: - double& operator[](int idx) { return fVec[idx]; } -}; - -class stl_like_class9 : public stl_like_class_base { -public: - double* begin() { return fVec.data(); } - double* end() { return fVec.data() + fVec.size(); } -}; - - -//- helpers for testing array -namespace ArrayTest { - -struct Point { - Point() : px(0), py(0) {} - Point(int x, int y) : px(x), py(y) {} - int px, py; -}; - -int get_pp_px(Point** p, int idx); -int get_pp_py(Point** p, int idx); -int get_pa_px(Point* p[], int idx); -int get_pa_py(Point* p[], int idx); - -} // namespace ArrayTest - - -// helpers for string testing -extern std::string str_array_1[3]; -extern std::string str_array_2[]; -extern std::string str_array_3[3][2]; -extern std::string str_array_4[4][2][2]; - - -// helpers for mixing unicode and std::string -namespace UnicodeAndSTL { - size_t get_size(std::string s); - size_t get_size_cr(const std::string& s); - size_t get_size_cc(const char* s); - size_t get_size_w(std::wstring s); - size_t get_size_wcr(const std::wstring& s); - std::string get_string(std::string s); - std::string get_string_cr(const std::string& s); - std::string get_string_cc(const char* s); - std::wstring get_string_w(std::wstring s); - std::wstring get_string_wcr(const std::wstring& s); -} - - -// helpers for string_view testing -namespace StringViewTest { - std::string_view::size_type count(const std::string_view arg); - std::string_view::size_type count_cr(const std::string_view& arg); -} - -// helper for exception base class testing -class MyError : public std::exception { -public: - static int s_count; - static int get_count(); - -public: - explicit MyError(const std::string& msg); - MyError(const MyError&); - virtual ~MyError(); - MyError& operator=(const MyError&) = default; - const char* what() const throw() override; - -private: - std::string fMsg; -}; - -int GetMyErrorCount(); - -class YourError : public MyError { -public: - explicit YourError(const std::string& msg); - YourError(const YourError& s); -}; - -namespace ErrorNamespace { - -class MyError : public std::exception { -public: - explicit MyError(const std::string& msg); - const char* what() const throw() override; - -private: - std::string fMsg; -}; - -class YourError : public MyError { -public: - using MyError::MyError; -}; - -void throw_error(int i); - -} // ErrorNamespace - -#endif // !CPPYY_TEST_STLTYPES_H diff --git a/test/stltypes.xml b/test/stltypes.xml deleted file mode 100644 index 4a5c2c0..0000000 --- a/test/stltypes.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/support.py b/test/support.py index aa6c380..34b9f9a 100644 --- a/test/support.py +++ b/test/support.py @@ -1,48 +1,56 @@ from __future__ import print_function -import os, py, sys, subprocess + +import os +import subprocess +import sys + +import py currpath = py.path.local(__file__).dirpath() def setup_make(targetname): - if os.getenv("CPPYY_TEST_SKIP_MAKE", False): + if os.getenv("CPPJIT_TEST_SKIP_MAKE", False): return - if sys.platform == 'win32': - popen = subprocess.Popen([sys.executable, "make_dict_win32.py", targetname], cwd=str(currpath), - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - else: - popen = subprocess.Popen(["make", targetname+"Dict.so"], cwd=str(currpath), - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + popen = subprocess.Popen( + ["make", targetname + "Dict.so"], + cwd=str(currpath), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) stdout, _ = popen.communicate() if popen.returncode: raise OSError("'make' failed:\n%s" % (stdout,)) + if sys.hexversion >= 0x3000000: pylong = int pyunicode = str maxvalue = sys.maxsize else: - pylong = long - pyunicode = unicode + pylong = long # noqa: F821 + pyunicode = unicode # noqa: F821 maxvalue = sys.maxint IS_WINDOWS = 0 -if 'win32' in sys.platform: +if "win32" in sys.platform: import platform - if '64' in platform.architecture()[0]: + + if "64" in platform.architecture()[0]: IS_WINDOWS = 64 - maxvalue = 2**31-1 + maxvalue = 2**31 - 1 else: IS_WINDOWS = 32 IS_MAC_ARM = 0 IS_MAC_X86 = 0 -if 'darwin' in sys.platform: +if "darwin" in sys.platform: import platform - if 'arm64' in platform.machine(): + + if "arm64" in platform.machine(): IS_MAC_ARM = 64 - os.environ["CPPYY_UNCAUGHT_QUIET"] = "1" + os.environ["CPPJIT_UNCAUGHT_QUIET"] = "1" else: IS_MAC_X86 = 1 IS_MAC = IS_MAC_ARM or IS_MAC_X86 @@ -50,31 +58,40 @@ def setup_make(targetname): IS_LINUX = 0 IS_LINUX_ARM = 0 IS_LINUX_X86 = 0 -if 'linux' in sys.platform: +if "linux" in sys.platform: IS_LINUX = 1 import platform - if 'aarch64' in platform.machine(): + + if "aarch64" in platform.machine(): IS_LINUX_ARM = 64 - os.environ["CPPYY_UNCAUGHT_QUIET"] = "1" + os.environ["CPPJIT_UNCAUGHT_QUIET"] = "1" else: IS_LINUX_X86 = 1 try: - import __pypy__ + import __pypy__ # noqa: F401 + ispypy = True except ImportError: ispypy = False -import cppyy -IS_CLANG_REPL = (cppyy.evaluate("""#ifndef __CLING__ +import cppjit # noqa: E402 + +IS_CLANG_REPL = ( + cppjit.evaluate("""#ifndef __CLING__ true #else false - #endif\n""") == 1) -IS_CLANG_DEBUG = (cppyy.evaluate("""#ifdef NDEBUG + #endif\n""") + == 1 +) +IS_CLANG_DEBUG = ( + cppjit.evaluate("""#ifdef NDEBUG false #else true - #endif\n""") == 1) + #endif\n""") + == 1 +) IS_CLING = not IS_CLANG_REPL IS_VALGRIND = True if os.getenv("IS_VALGRIND") else False diff --git a/test/templ_args_funcs.py b/test/templ_args_funcs.py deleted file mode 100644 index b8fc617..0000000 --- a/test/templ_args_funcs.py +++ /dev/null @@ -1,9 +0,0 @@ -import cppyy - -def ann_adapt(node: 'FPTA::Node&') -> cppyy.gbl.FPTA.EventId: - return cppyy.gbl.FPTA.EventId(node.fData) - -def ann_ref_mod(node: 'FPTA::Node&') -> cppyy.gbl.FPTA.EventId: - ev_id = cppyy.gbl.FPTA.EventId(node.fData) - node.fData = 81 - return ev_id diff --git a/test/templates.h b/test/templates.h deleted file mode 100644 index addded9..0000000 --- a/test/templates.h +++ /dev/null @@ -1,547 +0,0 @@ -#ifndef CPPYY_TEST_TEMPLATES_H -#define CPPYY_TEST_TEMPLATES_H - -#include -#include -#include -#include - -#ifndef _WIN32 -#include -inline std::string demangle_it(const char* name, const char* errmsg) { - int status; - std::string res = abi::__cxa_demangle(name, 0, 0, &status); - if (status != 0) throw std::runtime_error(errmsg); - return res; -} -#else -inline std::string demangle_it(const char* name, const char*) { - return name; // typeinfo's name() is already demangled -} -#endif - - -//=========================================================================== -class MyTemplatedMethodClass { // template methods -public: - template long get_size(A&); - template long get_size(const A&); - - long get_size(); - template long get_size(); - - long get_char_size(); - long get_int_size(); - long get_long_size(); - long get_float_size(); - long get_double_size(); - - long get_self_size(); - -private: - double m_data[3]; -}; - -template -long MyTemplatedMethodClass::get_size(A&) { - return sizeof(A); -} - -template -long MyTemplatedMethodClass::get_size(const A&) { - return sizeof(A)+1; -} - -template -inline long MyTemplatedMethodClass::get_size() { - return sizeof(B); -} - -// -typedef MyTemplatedMethodClass MyTMCTypedef_t; - -// explicit instantiation -template long MyTemplatedMethodClass::get_size(); -template long MyTemplatedMethodClass::get_size(); - -// "lying" specialization -template<> -inline long MyTemplatedMethodClass::get_size() { - return 42; -} - - -//=========================================================================== -// global templated functions -template -long global_get_size() { - return sizeof(T); -} - -template -int global_some_foo(T) { - return 42; -} - -template -int global_some_bar() { - return T; -} - -template -int global_some_bar_var(T) { - return 13; -} - -template -struct SomeResult { - F m_retval; -}; - -template -SomeResult global_get_some_result(const I& carrier) { - SomeResult r{}; - r.m_retval = O(carrier[0]); - return r; -} - - -//=========================================================================== -// variadic functions -inline bool isSomeInt(int) { return true; } -inline bool isSomeInt(double) { return false; } -template -inline bool isSomeInt(Args...) { return false; } - -namespace AttrTesting { - -struct Obj1 { int var1; }; -struct Obj2 { int var2; }; - -template -constexpr auto has_var1(T t) -> decltype(t.var1, true) { return true; } - -template -constexpr bool has_var1(Args...) { return false; } - -template -constexpr bool call_has_var1(T&& t) { return AttrTesting::has_var1(std::forward(t)); } - -template -struct select_template_arg {}; - -template -struct select_template_arg<0, T0, T...> { - typedef T0 type; - typedef type argument; -}; - -template -struct select_template_arg { - typedef typename select_template_arg::type argument; -}; - -} // AttrTesting - - -namespace SomeNS { - -template -int some_foo(T) { - return 42; -} - -template -int some_bar() { - return T; -} - -struct SomeStruct { - template - static int some_bar() { - return T; - } -}; - -inline std::string tuplify(std::ostringstream& out) { - out << "NULL)"; - return out.str(); -} - -template -std::string tuplify(std::ostringstream& out, T value, Args... args) -{ - out << value << ", "; - return tuplify(out, args...); -} - -} // namespace SomeNS - - -//=========================================================================== -// using of static data -// TODO: this should live here instead of in test_templates.test08 -/* -template struct BaseClassWithStatic { - static T const ref_value; -}; - -template -T const BaseClassWithStatic::ref_value = 42; - -template -struct DerivedClassUsingStatic : public BaseClassWithStatic { - using BaseClassWithStatic::ref_value; - - explicit DerivedClassUsingStatic(T x) : BaseClassWithStatic() { - m_value = x > ref_value ? ref_value : x; - } - - T m_value; -}; -*/ - - -//=========================================================================== -// templated callable -class TemplatedCallable { -public: - template - O operator() (const I& in) const { return O(in); } -}; - - -//=========================================================================== -// templated typedefs -namespace TemplatedTypedefs { - -template -struct BaseWithEnumAndTypedefs { - enum { vsize = _vsize }; - typedef TYPE_IN in_type; - typedef TYPE_OUT out_type; -}; - -template -struct DerivedWithUsing : public BaseWithEnumAndTypedefs -{ - typedef BaseWithEnumAndTypedefs base_type; - using base_type::vsize; - using typename base_type::in_type; - typedef typename base_type::in_type in_type_tt; - using typename base_type::out_type; -}; - -struct SomeDummy {}; - -} // namespace TemplatedTypedefs - - -//=========================================================================== -// hiding templated methods -namespace TemplateHiding { - -struct Base { - template - int callme(T t = T(1)) { return 2*t; } -}; - -struct Derived : public Base { - int callme(int t = 2) { return t; } -}; - -} // namespace TemplateHiding - - -//=========================================================================== -// 'using' of templates -template using DA_vector = std::vector; - -namespace using_problem { - -template -struct vector { - vector() : m_val(SZ) {} - T m_val; -}; - -template -struct matryoshka { - typedef T type; -}; - -template -struct matryoshka { - typedef vector::type, SZ> type; -}; - -template -using make_vector = typename matryoshka::type; - typedef make_vector iiv_t; -}; - -namespace using_problem { - -template -class Base { -public: - template - R get1(T t) { return t + R{5}; } - T get2() { return T{5}; } - template - R get3(T t) { return t + R{5}; } - T get3() { return T{5}; } -}; - -template -class Derived : public Base { -public: - typedef Base _Mybase; - using _Mybase::get1; - using _Mybase::get2; - using _Mybase::get3; -}; - -template class Foo { T x; }; - -namespace Bar { - using ::using_problem::Foo; -} - -} // namespace using_problem - - -//=========================================================================== -// template with r-value -namespace T_WithRValue { - -template -bool is_valid(T&& new_value) { - return new_value != T{}; -} - -} // namespace T_WithRValue - - -//=========================================================================== -// variadic templates -namespace some_variadic { - -#ifdef _WIN32 -#ifdef __CLING__ -extern __declspec(dllimport) std::string gTypeName; -#else -extern __declspec(dllexport) std::string gTypeName; -#endif -#else -extern std::string gTypeName; -#endif - -template -class A { -public: - A() { - gTypeName = demangle_it(typeid(A).name(), "A::A"); - } - A(const A&) = default; - A(A&&) = default; - A& operator=(const A&) = default; - A& operator=(A&&) = default; - - template - void a(FArgs&&... args) { - gTypeName = demangle_it(typeid(&A::a).name(), "A::a-2"); - } - - template - T a_T(FArgs&&... args) { - gTypeName = demangle_it(typeid(&A::a_T).name(), "A::a_T-2"); - return T{}; - } - - template - static void sa(FArgs&&... args) { - gTypeName = demangle_it(typeid(A).name(), "A::sa-1"); - gTypeName += "::"; - gTypeName += demangle_it(typeid(A::sa).name(), "A::sa-2"); - } - - template - static T sa_T(FArgs&&... args) { - gTypeName = demangle_it(typeid(A).name(), "A::sa_T-1"); - gTypeName += "::"; - gTypeName += demangle_it(typeid(A::sa_T).name(), "A::sa_T-2"); - return T{}; - } -}; - -class B { -public: - B() { - gTypeName = demangle_it(typeid(B).name(), "B::B"); - } - B(const B&) = default; - B(B&&) = default; - B& operator=(const B&) = default; - B& operator=(B&&) = default; - - template - void b(FArgs&&... args) { - gTypeName = demangle_it(typeid(&B::b).name(), "B::b-2"); - } - - template - T b_T(FArgs&&... args) { - gTypeName = demangle_it(typeid(&B::b_T).name(), "B::b_T-2"); - return T{}; - } - - template - static void sb(FArgs&&... args) { - gTypeName = demangle_it(typeid(B).name(), "B::sb-1"); - gTypeName += "::"; - gTypeName += demangle_it(typeid(B::sb).name(), "B::sb-2"); - } - - template - static T sb_T(FArgs&&... args) { - gTypeName = demangle_it(typeid(B).name(), "B::sb_T-1"); - gTypeName += "::"; - gTypeName += demangle_it(typeid(B::sb_T).name(), "B::sb_T-2"); - return T{}; - } -}; - -template -void fn(Args&&... args) { - gTypeName = demangle_it(typeid(fn).name(), "fn"); -} - -template -T fn_T(Args&&... args) { - gTypeName = demangle_it(typeid(fn).name(), "fn_T"); - return T{}; -} - -} // namespace some_variadic - - -//=========================================================================== -// template with empty body -namespace T_WithEmptyBody { - -#ifdef _WIN32 -#ifdef __CLING__ -extern __declspec(dllimport) std::string side_effect; -#else -extern __declspec(dllexport) std::string side_effect; -#endif -#else -extern std::string side_effect; -#endif - -template -void some_empty(); - -} // namespace T_WithEmptyBody - - -//=========================================================================== -// template with catch-all (void*, void**)overloads -namespace T_WithGreedyOverloads { - -class SomeClass { - double fD; -}; - -class WithGreedy1 { -public: - template - int get_size(T*) { return (int)sizeof(T); } - int get_size(void*, bool force=false) { return -1; } -}; - -class WithGreedy2 { -public: - template - int get_size(T*) { return (int)sizeof(T); } - int get_size(void**, bool force=false) { return -1; } -}; - -class DoesNotExist; - -class WithGreedy3 { -public: - template - int get_size(T*) { return (int)sizeof(T); } - int get_size(DoesNotExist*, bool force=false) { return -1; } -}; - -} // namespace T_WithGreedyOverloads - - -//=========================================================================== -// template with overloaded non-templated and templated setitem -namespace TemplateWithSetItem { - -template -class MyVec { -private: - std::vector fData; - -public: - using size_type = typename std::vector::size_type; - - MyVec(size_type count) : fData(count) {} - - T & operator[](size_type index) { return fData[index]; } - - // The definition of this templated operator causes the issue - template - MyVec operator[](const MyVec &conds) const { return MyVec(2); } -}; - -} // namespace TemplateWithSetItem - - -//=========================================================================== -// type reduction examples on gmpxx-like template expressions -namespace TypeReduction { - -template -struct BinaryExpr; - -template -struct Expr { - Expr() {} - Expr(const BinaryExpr&) {} -}; - -template -struct BinaryExpr { - BinaryExpr(const Expr&, const Expr&) {} -}; - -template -BinaryExpr operator+(const Expr& e1, const Expr& e2) { - return BinaryExpr(e1, e2); -} - -} // namespace TypeReduction - - -//=========================================================================== -// type deduction examples -namespace FailedTypeDeducer { - -template -class B { -public: - auto result() { return 5.; } -}; - -extern template class B; - -} - -#endif // !CPPYY_TEST_TEMPLATES_H diff --git a/test/templates.xml b/test/templates.xml deleted file mode 100644 index 7cb33f6..0000000 --- a/test/templates.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/test_aclassloader.py b/test/test_aclassloader.py index 16c2117..371a0d4 100644 --- a/test/test_aclassloader.py +++ b/test/test_aclassloader.py @@ -1,25 +1,27 @@ -import py, os, sys -from pytest import raises, mark -from .support import setup_make +import py +from support import setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("example01Dict")) +test_dct = str(currpath.join("cpp/example01Dict")) + def setup_module(mod): setup_make("example01") -class TestACLASSLOADER: +class TestACLASSLOADER: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.example01 = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.example01 = cppjit.load_reflection_info(cls.test_dct) def test01_class_autoloading(self): """Test whether a class can be found""" - import cppyy - example01_class = cppyy.gbl.example01 + import cppjit + + example01_class = cppjit.gbl.example01 assert example01_class - cl2 = cppyy.gbl.example01 + cl2 = cppjit.gbl.example01 assert cl2 assert example01_class is cl2 diff --git a/test/test_advancedcpp.py b/test/test_advancedcpp.py index 6e9dbfe..2d0f469 100644 --- a/test/test_advancedcpp.py +++ b/test/test_advancedcpp.py @@ -1,9 +1,18 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, pylong, IS_WINDOWS, IS_MAC, IS_LINUX, ispypy, IS_CLANG_REPL, IS_MAC_ARM, IS_MAC_X86, IS_VALGRIND, IS_CLING +import py +from pytest import mark, raises, skip +from support import ( + IS_MAC, + IS_MAC_ARM, + IS_VALGRIND, + IS_WINDOWS, + ispypy, + pylong, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("advancedcppDict")) +test_dct = str(currpath.join("cpp/advancedcppDict")) + def setup_module(mod): setup_make("advancedcpp") @@ -13,16 +22,18 @@ def setup_module(mod): class TestADVANCEDCPP: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.advanced = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.advanced = cppjit.load_reflection_info(cls.test_dct) @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test01_default_arguments(self): """Test usage of default arguments""" - import cppyy + import cppjit + def test_defaulter(n, t): - defaulter = getattr(cppyy.gbl, '%s_defaulter' % n) + defaulter = getattr(cppjit.gbl, "%s_defaulter" % n) d = defaulter() assert d.m_a == t(11) @@ -31,52 +42,53 @@ def test_defaulter(n, t): d.__destruct__() d = defaulter(0) - assert d.m_a == t(0) + assert d.m_a == t(0) assert d.m_b == t(22) assert d.m_c == t(33) d.__destruct__() d = defaulter(1, 2) - assert d.m_a == t(1) - assert d.m_b == t(2) + assert d.m_a == t(1) + assert d.m_b == t(2) assert d.m_c == t(33) d.__destruct__() d = defaulter(3, 4, 5) - assert d.m_a == t(3) - assert d.m_b == t(4) - assert d.m_c == t(5) + assert d.m_a == t(3) + assert d.m_b == t(4) + assert d.m_c == t(5) d.__destruct__() - defaulter_func = getattr(cppyy.gbl, '%s_defaulter_func' %n) + defaulter_func = getattr(cppjit.gbl, "%s_defaulter_func" % n) answers = [11, 22, 33, 3] for idx in range(4): assert defaulter_func(idx) == answers[idx] - test_defaulter('short', int) - test_defaulter('ushort', int) - test_defaulter('int', int) - test_defaulter('uint', int) - test_defaulter('long', pylong) - test_defaulter('ulong', pylong) - test_defaulter('llong', pylong) - test_defaulter('ullong', pylong) - test_defaulter('float', float) - test_defaulter('double', float) - - assert cppyy.gbl.string_defaulter_func(0) == "aap" - assert cppyy.gbl.string_defaulter_func(0, "zus") == "zus" - assert cppyy.gbl.string_defaulter_func(1) == "noot" - assert cppyy.gbl.string_defaulter_func(1, "zus") == "noot" - assert cppyy.gbl.string_defaulter_func(1, "zus", "jet") == "jet" - assert cppyy.gbl.string_defaulter_func(2) == "mies" + test_defaulter("short", int) + test_defaulter("ushort", int) + test_defaulter("int", int) + test_defaulter("uint", int) + test_defaulter("long", pylong) + test_defaulter("ulong", pylong) + test_defaulter("llong", pylong) + test_defaulter("ullong", pylong) + test_defaulter("float", float) + test_defaulter("double", float) + + assert cppjit.gbl.string_defaulter_func(0) == "aap" + assert cppjit.gbl.string_defaulter_func(0, "zus") == "zus" + assert cppjit.gbl.string_defaulter_func(1) == "noot" + assert cppjit.gbl.string_defaulter_func(1, "zus") == "noot" + assert cppjit.gbl.string_defaulter_func(1, "zus", "jet") == "jet" + assert cppjit.gbl.string_defaulter_func(2) == "mies" def test02_simple_inheritance(self): """Test binding of a basic inheritance structure""" - import cppyy - base_class = cppyy.gbl.base_class - derived_class = cppyy.gbl.derived_class + import cppjit + + base_class = cppjit.gbl.base_class + derived_class = cppjit.gbl.derived_class assert issubclass(derived_class, base_class) assert not issubclass(base_class, derived_class) @@ -85,15 +97,15 @@ def test02_simple_inheritance(self): assert isinstance(b, base_class) assert not isinstance(b, derived_class) - assert b.m_b == 1 - assert b.get_value() == 1 - assert b.m_db == 1.1 + assert b.m_b == 1 + assert b.get_value() == 1 + assert b.m_db == 1.1 assert b.get_base_value() == 1.1 b.m_b, b.m_db = 11, 11.11 - assert b.m_b == 11 - assert b.get_value() == 11 - assert b.m_db == 11.11 + assert b.m_b == 11 + assert b.get_value() == 11 + assert b.m_db == 11.11 assert b.get_base_value() == 11.11 b.__destruct__() @@ -102,158 +114,163 @@ def test02_simple_inheritance(self): assert isinstance(d, derived_class) assert isinstance(d, base_class) - assert d.m_d == 2 - assert d.get_value() == 2 - assert d.m_dd == 2.2 + assert d.m_d == 2 + assert d.get_value() == 2 + assert d.m_dd == 2.2 assert d.get_derived_value() == 2.2 - assert d.m_b == 1 - assert d.m_db == 1.1 - assert d.get_base_value() == 1.1 + assert d.m_b == 1 + assert d.m_db == 1.1 + assert d.get_base_value() == 1.1 d.m_b, d.m_db = 11, 11.11 d.m_d, d.m_dd = 22, 22.22 - assert d.m_d == 22 - assert d.get_value() == 22 - assert d.m_dd == 22.22 + assert d.m_d == 22 + assert d.get_value() == 22 + assert d.m_dd == 22.22 assert d.get_derived_value() == 22.22 - assert d.m_b == 11 - assert d.m_db == 11.11 - assert d.get_base_value() == 11.11 + assert d.m_b == 11 + assert d.m_db == 11.11 + assert d.get_base_value() == 11.11 d.__destruct__() def test03_namespaces(self): """Test access to namespaces and inner classes""" - import cppyy - gbl = cppyy.gbl + import cppjit - assert gbl.a_ns is gbl.a_ns + gbl = cppjit.gbl + + assert gbl.a_ns is gbl.a_ns assert gbl.a_ns.d_ns is gbl.a_ns.d_ns - assert gbl.a_ns.b_class is gbl.a_ns.b_class - assert gbl.a_ns.b_class.c_class is gbl.a_ns.b_class.c_class - assert gbl.a_ns.d_ns.e_class is gbl.a_ns.d_ns.e_class + assert gbl.a_ns.b_class is gbl.a_ns.b_class + assert gbl.a_ns.b_class.c_class is gbl.a_ns.b_class.c_class + assert gbl.a_ns.d_ns.e_class is gbl.a_ns.d_ns.e_class assert gbl.a_ns.d_ns.e_class.f_class is gbl.a_ns.d_ns.e_class.f_class - assert gbl.a_ns.g_a == 11 - assert gbl.a_ns.get_g_a() == 11 - assert gbl.a_ns.b_class.s_b == 22 - assert gbl.a_ns.b_class().m_b == -2 - assert gbl.a_ns.b_class.c_class.s_c == 33 - assert gbl.a_ns.b_class.c_class().m_c == -3 - assert gbl.a_ns.d_ns.g_d == 44 - assert gbl.a_ns.d_ns.get_g_d() == 44 - assert gbl.a_ns.d_ns.e_class.s_e == 55 - assert gbl.a_ns.d_ns.e_class().m_e == -5 - assert gbl.a_ns.d_ns.e_class.f_class.s_f == 66 - assert gbl.a_ns.d_ns.e_class.f_class().m_f == -6 + assert gbl.a_ns.g_a == 11 + assert gbl.a_ns.get_g_a() == 11 + assert gbl.a_ns.b_class.s_b == 22 + assert gbl.a_ns.b_class().m_b == -2 + assert gbl.a_ns.b_class.c_class.s_c == 33 + assert gbl.a_ns.b_class.c_class().m_c == -3 + assert gbl.a_ns.d_ns.g_d == 44 + assert gbl.a_ns.d_ns.get_g_d() == 44 + assert gbl.a_ns.d_ns.e_class.s_e == 55 + assert gbl.a_ns.d_ns.e_class().m_e == -5 + assert gbl.a_ns.d_ns.e_class.f_class.s_f == 66 + assert gbl.a_ns.d_ns.e_class.f_class().m_f == -6 raises(TypeError, gbl.a_ns) def test03a_namespace_lookup_on_update(self): """Test whether namespaces can be shared across dictionaries.""" - import cppyy - gbl = cppyy.gbl + import cppjit + + gbl = cppjit.gbl - lib2 = cppyy.load_reflection_info("advancedcpp2Dict") + lib2 = cppjit.load_reflection_info(str(currpath.join("cpp/advancedcpp2Dict"))) - assert gbl.a_ns is gbl.a_ns + assert gbl.a_ns is gbl.a_ns assert gbl.a_ns.d_ns is gbl.a_ns.d_ns - assert gbl.a_ns.g_class is gbl.a_ns.g_class - assert gbl.a_ns.g_class.h_class is gbl.a_ns.g_class.h_class - assert gbl.a_ns.d_ns.i_class is gbl.a_ns.d_ns.i_class + assert gbl.a_ns.g_class is gbl.a_ns.g_class + assert gbl.a_ns.g_class.h_class is gbl.a_ns.g_class.h_class + assert gbl.a_ns.d_ns.i_class is gbl.a_ns.d_ns.i_class assert gbl.a_ns.d_ns.i_class.j_class is gbl.a_ns.d_ns.i_class.j_class - assert gbl.a_ns.g_g == 77 - assert gbl.a_ns.get_g_g() == 77 - assert gbl.a_ns.g_class.s_g == 88 - assert gbl.a_ns.g_class().m_g == -7 - assert gbl.a_ns.g_class.h_class.s_h == 99 - assert gbl.a_ns.g_class.h_class().m_h == -8 - assert gbl.a_ns.d_ns.g_i == 111 - assert gbl.a_ns.d_ns.get_g_i() == 111 - assert gbl.a_ns.d_ns.i_class.s_i == 222 - assert gbl.a_ns.d_ns.i_class().m_i == -9 - assert gbl.a_ns.d_ns.i_class.j_class.s_j == 333 - assert gbl.a_ns.d_ns.i_class.j_class().m_j == -10 + assert gbl.a_ns.g_g == 77 + assert gbl.a_ns.get_g_g() == 77 + assert gbl.a_ns.g_class.s_g == 88 + assert gbl.a_ns.g_class().m_g == -7 + assert gbl.a_ns.g_class.h_class.s_h == 99 + assert gbl.a_ns.g_class.h_class().m_h == -8 + assert gbl.a_ns.d_ns.g_i == 111 + assert gbl.a_ns.d_ns.get_g_i() == 111 + assert gbl.a_ns.d_ns.i_class.s_i == 222 + assert gbl.a_ns.d_ns.i_class().m_i == -9 + assert gbl.a_ns.d_ns.i_class.j_class.s_j == 333 + assert gbl.a_ns.d_ns.i_class.j_class().m_j == -10 def test04_template_types(self): """Test bindings of templated types""" - import cppyy - gbl = cppyy.gbl + import cppjit + + gbl = cppjit.gbl assert gbl.T1 is gbl.T1 assert gbl.T2 is gbl.T2 assert gbl.T3 is gbl.T3 - assert not gbl.T1 is gbl.T2 - assert not gbl.T2 is gbl.T3 - - assert gbl.T1('int') is gbl.T1('int') - assert gbl.T1(int) is gbl.T1('int') - assert gbl.T2('T1') is gbl.T2('T1') - assert gbl.T2(gbl.T1('int')) is gbl.T2('T1') - assert gbl.T2(gbl.T1(int)) is gbl.T2('T1') - assert gbl.T3('int,double') is gbl.T3('int,double') - assert gbl.T3('int', 'double') is gbl.T3('int,double') - assert gbl.T3(int, 'double') is gbl.T3('int,double') - assert gbl.T3('T1,T2 >') is gbl.T3('T1,T2 >') - assert gbl.T3('T1', gbl.T2(gbl.T1(int))) is gbl.T3('T1,T2 >') - - assert gbl.a_ns.T4(int) is gbl.a_ns.T4('int') - assert gbl.a_ns.T4('a_ns::T4 >')\ - is gbl.a_ns.T4(gbl.a_ns.T4(gbl.T3(int, 'double'))) - - #----- mix in some of the alternative syntax - assert gbl.T1['int'] is gbl.T1('int') - assert gbl.T1[int] is gbl.T1('int') - assert gbl.T2['T1'] is gbl.T2('T1') - assert gbl.T2[gbl.T1('int')] is gbl.T2('T1') - assert gbl.T2[gbl.T1(int)] is gbl.T2('T1') - assert gbl.T3['int,double'] is gbl.T3('int,double') - assert gbl.T3['int', 'double'] is gbl.T3('int,double') - assert gbl.T3[int, 'double'] is gbl.T3('int,double') - assert gbl.T3['T1,T2 >'] is gbl.T3('T1,T2 >') - assert gbl.T3['T1', gbl.T2[gbl.T1[int]]] is gbl.T3('T1,T2 >') - - assert gbl.a_ns.T4[int] is gbl.a_ns.T4('int') - assert gbl.a_ns.T4['a_ns::T4 >']\ - is gbl.a_ns.T4(gbl.a_ns.T4(gbl.T3(int, 'double'))) - - #----- + assert gbl.T1 is not gbl.T2 + assert gbl.T2 is not gbl.T3 + + assert gbl.T1("int") is gbl.T1("int") + assert gbl.T1(int) is gbl.T1("int") + assert gbl.T2("T1") is gbl.T2("T1") + assert gbl.T2(gbl.T1("int")) is gbl.T2("T1") + assert gbl.T2(gbl.T1(int)) is gbl.T2("T1") + assert gbl.T3("int,double") is gbl.T3("int,double") + assert gbl.T3("int", "double") is gbl.T3("int,double") + assert gbl.T3(int, "double") is gbl.T3("int,double") + assert gbl.T3("T1,T2 >") is gbl.T3("T1,T2 >") + assert gbl.T3("T1", gbl.T2(gbl.T1(int))) is gbl.T3("T1,T2 >") + + assert gbl.a_ns.T4(int) is gbl.a_ns.T4("int") + assert gbl.a_ns.T4("a_ns::T4 >") is gbl.a_ns.T4( + gbl.a_ns.T4(gbl.T3(int, "double")) + ) + + # ----- mix in some of the alternative syntax + assert gbl.T1["int"] is gbl.T1("int") + assert gbl.T1[int] is gbl.T1("int") + assert gbl.T2["T1"] is gbl.T2("T1") + assert gbl.T2[gbl.T1("int")] is gbl.T2("T1") + assert gbl.T2[gbl.T1(int)] is gbl.T2("T1") + assert gbl.T3["int,double"] is gbl.T3("int,double") + assert gbl.T3["int", "double"] is gbl.T3("int,double") + assert gbl.T3[int, "double"] is gbl.T3("int,double") + assert gbl.T3["T1,T2 >"] is gbl.T3("T1,T2 >") + assert gbl.T3["T1", gbl.T2[gbl.T1[int]]] is gbl.T3("T1,T2 >") + + assert gbl.a_ns.T4[int] is gbl.a_ns.T4("int") + assert gbl.a_ns.T4["a_ns::T4 >"] is gbl.a_ns.T4( + gbl.a_ns.T4(gbl.T3(int, "double")) + ) + + # ----- t1 = gbl.T1(int)() - assert t1.m_t1 == 1 + assert t1.m_t1 == 1 assert t1.get_value() == 1 t1.__destruct__() - #----- + # ----- t1 = gbl.T1(int)(11) - assert t1.m_t1 == 11 + assert t1.m_t1 == 11 assert t1.get_value() == 11 t1.m_t1 = 111 assert t1.get_value() == 111 - assert t1.m_t1 == 111 + assert t1.m_t1 == 111 t1.__destruct__() - #----- + # ----- t2 = gbl.T2(gbl.T1(int))(gbl.T1(int)(32)) t2.m_t2.m_t1 = 32 assert t2.m_t2.get_value() == 32 - assert t2.m_t2.m_t1 == 32 + assert t2.m_t2.m_t1 == 32 t2.__destruct__() - def test05_abstract_classes(self): """Test non-instatiatability of abstract classes""" - import cppyy - gbl = cppyy.gbl + import cppjit + + gbl = cppjit.gbl raises(TypeError, gbl.a_class) raises(TypeError, gbl.some_abstract_class) @@ -267,12 +284,13 @@ def test05_abstract_classes(self): def test06_datamembers(self): """Test data member access when using virtual inheritence""" - import cppyy - a_class = cppyy.gbl.a_class - b_class = cppyy.gbl.b_class - c_class_1 = cppyy.gbl.c_class_1 - c_class_2 = cppyy.gbl.c_class_2 - d_class = cppyy.gbl.d_class + import cppjit + + a_class = cppjit.gbl.a_class + b_class = cppjit.gbl.b_class + c_class_1 = cppjit.gbl.c_class_1 + c_class_2 = cppjit.gbl.c_class_2 + d_class = cppjit.gbl.d_class assert issubclass(b_class, a_class) assert issubclass(c_class_1, a_class) @@ -283,98 +301,99 @@ def test06_datamembers(self): assert issubclass(d_class, b_class) assert issubclass(d_class, c_class_2) - #----- + # ----- b = b_class() - assert b.m_a == 1 - assert b.m_da == 1.1 - assert b.m_b == 2 - assert b.m_db == 2.2 + assert b.m_a == 1 + assert b.m_da == 1.1 + assert b.m_b == 2 + assert b.m_db == 2.2 b.m_a = 11 - assert b.m_a == 11 - assert b.m_b == 2 + assert b.m_a == 11 + assert b.m_b == 2 b.m_da = 11.11 - assert b.m_da == 11.11 - assert b.m_db == 2.2 + assert b.m_da == 11.11 + assert b.m_db == 2.2 b.m_b = 22 - assert b.m_a == 11 - assert b.m_da == 11.11 - assert b.m_b == 22 - assert b.get_value() == 22 + assert b.m_a == 11 + assert b.m_da == 11.11 + assert b.m_b == 22 + assert b.get_value() == 22 b.m_db = 22.22 - assert b.m_db == 22.22 + assert b.m_db == 22.22 b.__destruct__() - #----- + # ----- c1 = c_class_1() - assert c1.m_a == 1 - assert c1.m_b == 2 - assert c1.m_c == 3 + assert c1.m_a == 1 + assert c1.m_b == 2 + assert c1.m_c == 3 c1.m_a = 11 - assert c1.m_a == 11 + assert c1.m_a == 11 c1.m_b = 22 - assert c1.m_a == 11 - assert c1.m_b == 22 + assert c1.m_a == 11 + assert c1.m_b == 22 c1.m_c = 33 - assert c1.m_a == 11 - assert c1.m_b == 22 - assert c1.m_c == 33 + assert c1.m_a == 11 + assert c1.m_b == 22 + assert c1.m_c == 33 assert c1.get_value() == 33 c1.__destruct__() - #----- + # ----- d = d_class() - assert d.m_a == 1 - assert d.m_b == 2 - assert d.m_c == 3 - assert d.m_d == 4 + assert d.m_a == 1 + assert d.m_b == 2 + assert d.m_c == 3 + assert d.m_d == 4 d.m_a = 11 - assert d.m_a == 11 + assert d.m_a == 11 d.m_b = 22 - assert d.m_a == 11 - assert d.m_b == 22 + assert d.m_a == 11 + assert d.m_b == 22 d.m_c = 33 - assert d.m_a == 11 - assert d.m_b == 22 - assert d.m_c == 33 + assert d.m_a == 11 + assert d.m_b == 22 + assert d.m_c == 33 d.m_d = 44 - assert d.m_a == 11 - assert d.m_b == 22 - assert d.m_c == 33 - assert d.m_d == 44 - assert d.get_value() == 44 + assert d.m_a == 11 + assert d.m_b == 22 + assert d.m_c == 33 + assert d.m_d == 44 + assert d.get_value() == 44 d.__destruct__() def test07_pass_by_reference(self): """Test reference passing when using virtual inheritance""" - import cppyy - gbl = cppyy.gbl + import cppjit + + gbl = cppjit.gbl b_class = gbl.b_class c_class = gbl.c_class_2 d_class = gbl.d_class - #----- + # ----- b = b_class() b.m_a, b.m_b = 11, 22 assert gbl.get_a(b) == 11 assert gbl.get_b(b) == 22 b.__destruct__() - #----- + # ----- c = c_class() c.m_a, c.m_b, c.m_c = 11, 22, 33 assert gbl.get_a(c) == 11 @@ -382,7 +401,7 @@ def test07_pass_by_reference(self): assert gbl.get_c(c) == 33 c.__destruct__() - #----- + # ----- d = d_class() d.m_a, d.m_b, d.m_c, d.m_d = 11, 22, 33, 44 assert gbl.get_a(d) == 11 @@ -394,102 +413,108 @@ def test07_pass_by_reference(self): def test08_void_pointer_passing(self): """Test passing of variants of void pointer arguments""" - import cppyy - pointer_pass = cppyy.gbl.pointer_pass - some_concrete_class = cppyy.gbl.some_concrete_class + import cppjit + + pointer_pass = cppjit.gbl.pointer_pass + some_concrete_class = cppjit.gbl.some_concrete_class pp = pointer_pass() o = some_concrete_class() - assert cppyy.addressof(o) == pp.gime_address_ptr(o) - assert cppyy.addressof(o) == pp.gime_address_ptr_ptr(o) - assert cppyy.addressof(o) == pp.gime_address_ptr_ref(o) + assert cppjit.addressof(o) == pp.gime_address_ptr(o) + assert cppjit.addressof(o) == pp.gime_address_ptr_ptr(o) + assert cppjit.addressof(o) == pp.gime_address_ptr_ref(o) if IS_WINDOWS != 64: - # there is no 8-byte integer type array on Windows 64b + # there is no 8-byte integer type array on Windows 64b import array - addressofo = array.array('l', [cppyy.addressof(o)]) + + addressofo = array.array("l", [cppjit.addressof(o)]) assert addressofo[0] == pp.gime_address_ptr_ptr(addressofo) assert 0 == pp.gime_address_ptr(0) raises(TypeError, pp.gime_address_ptr, None) - ptr = cppyy.bind_object(0, some_concrete_class) - assert cppyy.addressof(ptr) == 0 + ptr = cppjit.bind_object(0, some_concrete_class) + assert cppjit.addressof(ptr) == 0 pp.set_address_ptr_ref(ptr) - assert cppyy.addressof(ptr) == 0x1234 + assert cppjit.addressof(ptr) == 0x1234 - # alternate path through static method handling, which does NOT - # provide 'pp' as argument (and thus need no removing) + # alternate path through static method handling, which does NOT + # provide 'pp' as argument (and thus need no removing) sf = pp.set_address_ptr_ref sf(ptr) - assert cppyy.addressof(ptr) == 0x1234 + assert cppjit.addressof(ptr) == 0x1234 pp.set_address_ptr_ptr(ptr) - assert cppyy.addressof(ptr) == 0x4321 + assert cppjit.addressof(ptr) == 0x4321 - assert cppyy.addressof(cppyy.nullptr) == 0 - raises(TypeError, cppyy.addressof, None) - assert cppyy.addressof(0) == 0 + assert cppjit.addressof(cppjit.nullptr) == 0 + raises(TypeError, cppjit.addressof, None) + assert cppjit.addressof(0) == 0 def test09_opaque_pointer_passing(self): """Test passing around of opaque pointers""" - import cppyy - some_concrete_class = cppyy.gbl.some_concrete_class + import cppjit + + some_concrete_class = cppjit.gbl.some_concrete_class o = some_concrete_class() # TODO: figure out the PyPy equivalent of CObject (may have to do this # through the C-API from C++) - #cobj = cppyy.as_cobject(o) - addr = cppyy.addressof(o) - - #assert o == cppyy.bind_object(cobj, some_concrete_class) - #assert o == cppyy.bind_object(cobj, type(o)) - #assert o == cppyy.bind_object(cobj, o.__class__) - #assert o == cppyy.bind_object(cobj, "some_concrete_class") - assert cppyy.addressof(o) == cppyy.addressof(cppyy.bind_object(addr, some_concrete_class)) - assert o is cppyy.bind_object(addr, some_concrete_class) - assert o is cppyy.bind_object(addr, type(o)) - assert o is cppyy.bind_object(addr, o.__class__) - assert o is cppyy.bind_object(addr, "some_concrete_class") - raises(TypeError, cppyy.bind_object, addr, "does_not_exist") - raises(TypeError, cppyy.bind_object, addr, 1) + # cobj = cppjit.as_cobject(o) + addr = cppjit.addressof(o) + + # assert o == cppjit.bind_object(cobj, some_concrete_class) + # assert o == cppjit.bind_object(cobj, type(o)) + # assert o == cppjit.bind_object(cobj, o.__class__) + # assert o == cppjit.bind_object(cobj, "some_concrete_class") + assert cppjit.addressof(o) == cppjit.addressof( + cppjit.bind_object(addr, some_concrete_class) + ) + assert o is cppjit.bind_object(addr, some_concrete_class) + assert o is cppjit.bind_object(addr, type(o)) + assert o is cppjit.bind_object(addr, o.__class__) + assert o is cppjit.bind_object(addr, "some_concrete_class") + raises(TypeError, cppjit.bind_object, addr, "does_not_exist") + raises(TypeError, cppjit.bind_object, addr, 1) def test10_object_identity(self): """Test object identity""" - import cppyy - some_concrete_class = cppyy.gbl.some_concrete_class - some_class_with_data = cppyy.gbl.some_class_with_data + import cppjit + + some_concrete_class = cppjit.gbl.some_concrete_class + some_class_with_data = cppjit.gbl.some_class_with_data o = some_concrete_class() - addr = cppyy.addressof(o) + addr = cppjit.addressof(o) - o2 = cppyy.bind_object(addr, some_concrete_class) + o2 = cppjit.bind_object(addr, some_concrete_class) assert o is o2 - o3 = cppyy.bind_object(addr, some_class_with_data) - assert not o is o3 + o3 = cppjit.bind_object(addr, some_class_with_data) + assert o is not o3 d1 = some_class_with_data() d2 = d1.gime_copy() - assert not d1 is d2 + assert d1 is not d2 dd1a = d1.gime_data() dd1b = d1.gime_data() assert dd1a is dd1b dd2 = d2.gime_data() - assert not dd1a is dd2 - assert not dd1b is dd2 + assert dd1a is not dd2 + assert dd1b is not dd2 d2.__destruct__() d1.__destruct__() - RTS = cppyy.gbl.refers_to_self + RTS = cppjit.gbl.refers_to_self r1 = RTS() r2 = RTS() @@ -509,16 +534,17 @@ def test10_object_identity(self): def test11_multi_methods(self): """Test calling of methods from multiple inheritance""" - import cppyy - multi = cppyy.gbl.multi + import cppjit - assert cppyy.gbl.multi1 is multi.__bases__[0] - assert cppyy.gbl.multi2 is multi.__bases__[1] + multi = cppjit.gbl.multi + + assert cppjit.gbl.multi1 is multi.__bases__[0] + assert cppjit.gbl.multi2 is multi.__bases__[1] dict_keys = list(multi.__dict__.keys()) - assert dict_keys.count('get_my_own_int') == 1 - assert dict_keys.count('get_multi1_int') == 0 - assert dict_keys.count('get_multi2_int') == 0 + assert dict_keys.count("get_my_own_int") == 1 + assert dict_keys.count("get_multi1_int") == 0 + assert dict_keys.count("get_multi2_int") == 0 m = multi(1, 2, 3) assert m.get_multi1_int() == 1 @@ -528,10 +554,11 @@ def test11_multi_methods(self): def test12_actual_type(self): """Test that a pointer to base return does an auto-downcast""" - import cppyy - base_class = cppyy.gbl.base_class + import cppjit + + base_class = cppjit.gbl.base_class base_class.clone.__creates__ = True - derived_class = cppyy.gbl.derived_class + derived_class = cppjit.gbl.derived_class derived_class.clone.__creates__ = True b = base_class() @@ -551,7 +578,7 @@ def test12_actual_type(self): assert isinstance(b.cycle(d), derived_class) assert isinstance(d.cycle(d), derived_class) - base_class.clone.__creates__ = True + base_class.clone.__creates__ = True assert isinstance(b.clone(), base_class) derived_class.clone.__creates__ = True assert isinstance(d.clone(), derived_class) @@ -561,54 +588,56 @@ def test12_actual_type(self): assert not isinstance(voidp, base_class) assert not isinstance(voidp, derived_class) - d1 = cppyy.bind_object(voidp, base_class, cast=True) + d1 = cppjit.bind_object(voidp, base_class, cast=True) assert isinstance(d1, derived_class) assert d1 is d - b1 = cppyy.bind_object(voidp, base_class) + b1 = cppjit.bind_object(voidp, base_class) assert isinstance(b1, base_class) - assert cppyy.addressof(b1) == cppyy.addressof(d) - assert not (b1 is d) + assert cppjit.addressof(b1) == cppjit.addressof(d) + assert b1 is not d def test13_actual_type_virtual_multi(self): """Test auto-downcast in adverse inheritance situation""" - import cppyy + import cppjit - cppyy.gbl.create_c1.__creates__ = True - c1 = cppyy.gbl.create_c1() - assert type(c1) == cppyy.gbl.c_class_1 + cppjit.gbl.create_c1.__creates__ = True + c1 = cppjit.gbl.create_c1() + assert type(c1) == cppjit.gbl.c_class_1 assert c1.m_c == 3 c1.__destruct__() - cppyy.gbl.create_c2.__creates__ = True - c2 = cppyy.gbl.create_c2() - assert type(c2) == cppyy.gbl.c_class_2 + cppjit.gbl.create_c2.__creates__ = True + c2 = cppjit.gbl.create_c2() + assert type(c2) == cppjit.gbl.c_class_2 assert c2.m_c == 3 c2.__destruct__() def test14_new_overloader(self): """Verify that class-level overloaded new/delete are called""" - import cppyy - cppyy.include("new") - assert cppyy.gbl.new_overloader.s_instances == 0 - nl = cppyy.gbl.new_overloader() - assert cppyy.gbl.new_overloader.s_instances == 1 + import cppjit + + cppjit.include("new") + assert cppjit.gbl.new_overloader.s_instances == 0 + nl = cppjit.gbl.new_overloader() + assert cppjit.gbl.new_overloader.s_instances == 1 nl.__destruct__() import gc + gc.collect() - assert cppyy.gbl.new_overloader.s_instances == 0 + assert cppjit.gbl.new_overloader.s_instances == 0 def test15_template_instantiation_with_vector_of_float(self): """Test template instantiation with a std::vector""" - import cppyy + import cppjit # the following will simply fail if there is a naming problem (e.g. # std::, allocator, etc., etc.); note the parsing required ... - b = cppyy.gbl.my_templated_class(cppyy.gbl.std.vector(float))() + b = cppjit.gbl.my_templated_class(cppjit.gbl.std.vector(float))() for i in range(5): b.m_b.push_back(i) @@ -618,22 +647,22 @@ def test15_template_instantiation_with_vector_of_float(self): def test16_template_global_functions(self): """Test template global function lookup and calls""" - import cppyy + import cppjit - f = cppyy.gbl.my_templated_function + f = cppjit.gbl.my_templated_function - assert f('c') == 'c' - assert type(f('c')) == type('c') - assert f(3.) == 3. - assert type(f(4.)) == type(4.) + assert f("c") == "c" + assert type(f("c")) == type("c") + assert f(3.0) == 3.0 + assert type(f(4.0)) == type(4.0) def test17_assign_to_return_byref(self): """Test assignment to an instance returned by reference""" if ispypy: - skip('segfaults in pypy') + skip("segfaults in pypy") - from cppyy import gbl + from cppjit import gbl a = gbl.std.vector(gbl.ref_tester)() a.push_back(gbl.ref_tester(42)) @@ -648,23 +677,23 @@ def test17_assign_to_return_byref(self): def test18_math_converters(self): """Test operator int/long/double incl. typedef""" - from cppyy import gbl + from cppjit import gbl a = gbl.some_convertible() a.m_i = 1234 - a.m_d = 4321. + a.m_d = 4321.0 - assert int(a) == 1234 - assert int(a) == a.m_i + assert int(a) == 1234 + assert int(a) == a.m_i assert pylong(a) == a.m_i - assert float(a) == 4321. - assert float(a) == a.m_d + assert float(a) == 4321.0 + assert float(a) == a.m_d def test19_comparator(self): """Check that the global operator!=/== is picked up""" - from cppyy import gbl + from cppjit import gbl a, b = gbl.some_comparable(), gbl.some_comparable() @@ -683,53 +712,58 @@ def test19_comparator(self): def test20_overload_order_with_proper_return(self): """Test return type against proper overload w/ const and covariance""" - import cppyy + import cppjit - assert cppyy.gbl.overload_one_way().gime() == 1 - assert cppyy.gbl.overload_the_other_way().gime() == "aap" + assert cppjit.gbl.overload_one_way().gime() == 1 + assert cppjit.gbl.overload_the_other_way().gime() == "aap" @mark.xfail(run=not IS_VALGRIND) def test21_access_to_global_variables(self): """Access global_variables_and_pointers""" - import cppyy + import cppjit - assert cppyy.gbl.my_global_double == 12. - assert len(cppyy.gbl.my_global_array) == 500 - assert cppyy.gbl.my_global_string1 == "aap noot mies" - assert cppyy.gbl.my_global_string2 == "zus jet teun" - assert list(cppyy.gbl.my_global_string3) == ["aap", "noot", "mies"] - assert cppyy.gbl.my_global_ptr[0] == 1234. + assert cppjit.gbl.my_global_double == 12.0 + assert len(cppjit.gbl.my_global_array) == 500 + assert cppjit.gbl.my_global_string1 == "aap noot mies" + assert cppjit.gbl.my_global_string2 == "zus jet teun" + assert list(cppjit.gbl.my_global_string3) == ["aap", "noot", "mies"] + assert cppjit.gbl.my_global_ptr[0] == 1234.0 - v = cppyy.gbl.my_global_int_holders + v = cppjit.gbl.my_global_int_holders assert len(v) == 5 expected_vals = [13, 42, 88, -1, 17] for i in range(len(v)): assert v[i].m_val == expected_vals[i] - o = cppyy.gbl.some_concrete_class() - assert type(o) != type(cppyy.gbl.g_abstract_ptr) - cppyy.gbl.g_abstract_ptr = o - assert type(o) != type(cppyy.gbl.g_abstract_ptr) - assert cppyy.gbl.g_abstract_ptr == o - cppyy.gbl.g_abstract_ptr = cppyy.nullptr - - cppyy.cppexec("std::vector* gtestv1 = nullptr;") - pyv = cppyy.gbl.std.vector[int]() - cppyy.gbl.gtestv1 = pyv - cppyy.cppexec("auto* gtestv2 = gtestv1;") + o = cppjit.gbl.some_concrete_class() + assert type(o) != type(cppjit.gbl.g_abstract_ptr) + cppjit.gbl.g_abstract_ptr = o + assert type(o) != type(cppjit.gbl.g_abstract_ptr) + assert cppjit.gbl.g_abstract_ptr == o + cppjit.gbl.g_abstract_ptr = cppjit.nullptr + + cppjit.cppexec("std::vector* gtestv1 = nullptr;") + pyv = cppjit.gbl.std.vector[int]() + cppjit.gbl.gtestv1 = pyv + cppjit.cppexec("auto* gtestv2 = gtestv1;") pyv.push_back(42) - assert len(cppyy.gbl.gtestv1) == 1 - assert len(cppyy.gbl.gtestv2) == 1 - - @mark.xfail(run=False, condition=IS_MAC_ARM, reason="Crashes with exception not being caught on Apple Silicon") + assert len(cppjit.gbl.gtestv1) == 1 + assert len(cppjit.gbl.gtestv2) == 1 + + @mark.xfail( + run=False, + condition=IS_MAC_ARM, + reason="Crashes with exception not being caught on Apple Silicon", + ) def test22_exceptions(self): """Catching of C++ exceptions""" - import cppyy - Thrower = cppyy.gbl.Thrower + import cppjit - Thrower.throw_anything.__useffi__ = False + Thrower = cppjit.gbl.Thrower + + Thrower.throw_anything.__useffi__ = False Thrower.throw_exception.__useffi__ = False t = Thrower() @@ -749,73 +783,75 @@ def test22_exceptions(self): def test23_using(self): """Accessibility of using declarations""" - import cppyy + import cppjit - assert cppyy.gbl.UsingBase1().vcheck() == 'A' + assert cppjit.gbl.UsingBase1().vcheck() == "A" - D1 = cppyy.gbl.UsingDerived1 - assert not 'UsingBase1' in D1.__init__.__doc__ + D1 = cppjit.gbl.UsingDerived1 + assert "UsingBase1" not in D1.__init__.__doc__ d1a = D1() - assert d1a.m_int == 13 - assert d1a.m_int2 == 42 - assert d1a.vcheck() == 'B' + assert d1a.m_int == 13 + assert d1a.m_int2 == 42 + assert d1a.vcheck() == "B" d1b = D1(10) - assert d1b.m_int == 10 - assert d1b.m_int2 == 42 - assert d1b.vcheck() == 'B' + assert d1b.m_int == 10 + assert d1b.m_int2 == 42 + assert d1b.vcheck() == "B" d1c = D1(d1b) - assert d1c.m_int == 10 - assert d1c.m_int2 == 42 - assert d1c.vcheck() == 'B' + assert d1c.m_int == 10 + assert d1c.m_int2 == 42 + assert d1c.vcheck() == "B" - D2 = cppyy.gbl.UsingDerived2 - assert 'vcheck(int)' in D2.vcheck.__doc__ - assert 'vcheck()' in D2.vcheck.__doc__ + D2 = cppjit.gbl.UsingDerived2 + assert "vcheck(int)" in D2.vcheck.__doc__ + assert "vcheck()" in D2.vcheck.__doc__ d2 = D2() - assert d2.vcheck() == 'A' - assert d2.vcheck(1) == 'B' + assert d2.vcheck() == "A" + assert d2.vcheck(1) == "B" def test24_typedef_to_private_class(self): """Typedefs to private classes should not resolve""" - import cppyy + import cppjit - assert cppyy.gbl.TypedefToPrivateClass().f().m_val == 42 + assert cppjit.gbl.TypedefToPrivateClass().f().m_val == 42 @mark.xfail(run=False, reason="Crashes") def test25_ostream_printing(self): """Mapping of __str__ through operator<<(ostream&)""" - import cppyy + import cppjit - ns = cppyy.gbl.Cpp2PyPrinting + ns = cppjit.gbl.Cpp2PyPrinting assert str(ns.Printable1()) == "Printable1::operator<<" - for tst in [(ns.Printable2, "Cpp2PyPrinting::operator<<"), - (ns.Printable3, "::operator<<(3)"), - (cppyy.gbl.Printable4, "::operator<<(4)"), - (cppyy.gbl.Printable6, "Printable6")]: + for tst in [ + (ns.Printable2, "Cpp2PyPrinting::operator<<"), + (ns.Printable3, "::operator<<(3)"), + (cppjit.gbl.Printable4, "::operator<<(4)"), + (cppjit.gbl.Printable6, "Printable6"), + ]: assert str(tst[0]()) == tst[1] - if '__lshiftc__' in tst[0].__dict__: - # only cached for global functions and in principle should - # not be needed anymore ... + if "__lshiftc__" in tst[0].__dict__: + # only cached for global functions and in principle should + # not be needed anymore ... assert tst[0].__lshiftc__ del tst[0].__lshiftc__ assert str(tst[0]()) == tst[1] assert tst[0].__lshiftc__ - s = cppyy.gbl.std.ostringstream() + s = cppjit.gbl.std.ostringstream() tst[0].__lshiftc__(s, tst[0]()) assert s.str() == tst[1] - # print through base class (used to fail with compilation error) - assert str(cppyy.gbl.Printable5()) == "Ok." + # print through base class (used to fail with compilation error) + assert str(cppjit.gbl.Printable5()) == "Ok." - # print through friend - cppyy.cppdef("""\ + # print through friend + cppjit.cppdef("""\ namespace PrintingNS { class X { friend std::ostream& operator<<(std::ostream& os, const X&) { return os << "X"; } @@ -827,27 +863,27 @@ class Y { std::ostream& operator<<(std::ostream& os, const Y&) { return os << "Y"; } } """) - x = cppyy.gbl.PrintingNS.X() - assert str(x) == 'X' + x = cppjit.gbl.PrintingNS.X() + assert str(x) == "X" - y = cppyy.gbl.PrintingNS.Y() - assert str(y) == 'Y' + y = cppjit.gbl.PrintingNS.Y() + assert str(y) == "Y" def test26_using_directive(self): """Test using directive in namespaces""" - import cppyy + import cppjit - assert cppyy.gbl.UserDirs.foo1() == cppyy.gbl.UsedSpace1.foo1() - assert cppyy.gbl.UserDirs.bar() == cppyy.gbl.UsedSpace2.bar() - assert cppyy.gbl.UserDirs.foo2() == cppyy.gbl.UsedSpace1.inner.foo2() + assert cppjit.gbl.UserDirs.foo1() == cppjit.gbl.UsedSpace1.foo1() + assert cppjit.gbl.UserDirs.bar() == cppjit.gbl.UsedSpace2.bar() + assert cppjit.gbl.UserDirs.foo2() == cppjit.gbl.UsedSpace1.inner.foo2() def test27_shadowed_typedef(self): """Test that typedefs are not shadowed""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace ShadowedTypedef { struct A { typedef std::shared_ptr Ptr; @@ -863,14 +899,14 @@ def test27_shadowed_typedef(self): /* empty */ }; }""") - ns = cppyy.gbl.ShadowedTypedef + ns = cppjit.gbl.ShadowedTypedef - ns.A.Ptr # pull A::Ptr first - ns.A.Val # id. A::Val - ns.B.Ptr # used to be A.Ptr through python-side dict lookup - ns.B.Val # id. B::Val - ns.C.Ptr # is A.Ptr - ns.C.Val # is A.Val + ns.A.Ptr # pull A::Ptr first + ns.A.Val # id. A::Val + ns.B.Ptr # used to be A.Ptr through python-side dict lookup + ns.B.Val # id. B::Val + ns.C.Ptr # is A.Ptr + ns.C.Val # is A.Val assert ns.A.Ptr == ns.A.Ptr assert ns.B.Ptr == ns.B.Ptr @@ -880,16 +916,16 @@ def test27_shadowed_typedef(self): # TODO: currently only classes are checked; typedefs of builtin types are # mapped through the type mapper and as such can be anything - #assert ns.A.Val != ns.B.Val - #assert type(ns.A.Val(1)) == int - #assert type(ns.B.Val(1)) == float + # assert ns.A.Val != ns.B.Val + # assert type(ns.A.Val(1)) == int + # assert type(ns.B.Val(1)) == float def test28_extern_C_in_namespace(self): """Access to extern "C" declared functions in namespaces""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace extern_c_in_ns { extern "C" int some_func_xc(void) { return 21; } int some_func() { return some_func_xc(); } @@ -898,9 +934,9 @@ def test28_extern_C_in_namespace(self): extern "C" int some_other_func_xc(void) { return 42; } } }""") - ns = cppyy.gbl.extern_c_in_ns + ns = cppjit.gbl.extern_c_in_ns - assert ns.some_func() == 21 + assert ns.some_func() == 21 assert ns.some_func_xc() == 21 assert ns.deeper.some_other_func_xc() == 42 @@ -908,10 +944,11 @@ def test28_extern_C_in_namespace(self): def test29_castcpp(self): """Allow casting a Python class to a C++ one""" - import cppyy import math - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace castcpp { struct MyPoint { double x, y; @@ -937,7 +974,7 @@ def test29_castcpp(self): return std::sqrt(p->x*p->x + p->y*p->y); } }""") - ns = cppyy.gbl.castcpp + ns = cppjit.gbl.castcpp class MyPyPoint1: def __init__(self, x, y): @@ -955,7 +992,7 @@ def __cast_cpp__(self): p1 = MyPyPoint1(5, 10) p2 = MyPyPoint2(p1.x, p1.y) p3 = MyPyPoint3(p1.x, p1.y) - pynorm = math.sqrt(p2.x**2+p2.y**2) + pynorm = math.sqrt(p2.x**2 + p2.y**2) for norm in [ns.norm_cr, ns.norm_r, ns.norm_v, ns.norm_p]: with raises(TypeError): @@ -965,4 +1002,3 @@ def __cast_cpp__(self): for norm in [ns.norm_cr, ns.norm_m, ns.norm_v]: assert round(norm(p3) - pynorm, 8) == 0 - diff --git a/test/test_api.py b/test/test_api.py index 586803a..c52fc4c 100644 --- a/test/test_api.py +++ b/test/test_api.py @@ -1,24 +1,25 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import ispypy, IS_MAC, IS_LINUX_ARM +from pytest import mark, skip +from support import IS_LINUX_ARM, IS_MAC, ispypy class TestAPI: def setup_class(cls): if ispypy: - skip('C++ API only available on CPython') + skip("C++ API only available on CPython") - import cppyy - cppyy.include('CPyCppyy/API.h') + import cppjit + + cppjit.include("cpyrt/API.h") def test01_type_checking(self): """Python class type checks""" - import cppyy - cpp = cppyy.gbl - API = cpp.CPyCppyy + import cppjit + + cpp = cppjit.gbl + API = cpp.cppjit.cpyrt - cppyy.cppdef(""" + cppjit.cppdef(""" class APICheck { public: void some_method() {} @@ -39,20 +40,22 @@ class APICheck { def test02_interpreter_access(self): """Access to the python interpreter""" - import cppyy - API = cppyy.gbl.CPyCppyy + import cppjit - assert API.Exec('import sys') + API = cppjit.gbl.cppjit.cpyrt + + assert API.Exec("import sys") @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test03_instance_conversion(self): """Proxy object conversions""" - import cppyy - cpp = cppyy.gbl - API = cpp.CPyCppyy + import cppjit + + cpp = cppjit.gbl + API = cpp.cppjit.cpyrt - cppyy.cppdef(""" + cppjit.cppdef(""" class APICheck2 { public: virtual ~APICheck2() {} @@ -61,17 +64,17 @@ class APICheck2 { m = cpp.APICheck2() voidp = API.Instance_AsVoidPtr(m) - m2 = API.Instance_FromVoidPtr(voidp, 'APICheck2') + m2 = API.Instance_FromVoidPtr(voidp, "APICheck2") assert m is m2 @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test04_custom_converter(self): """Custom type converter""" - import cppyy + import cppjit - cppyy.cppdef(""" - #include "CPyCppyy/API.h" + cppjit.cppdef(""" + #include "cpyrt/API.h" class APICheck3 { int fFlags; @@ -87,10 +90,10 @@ class APICheck3 { bool wasToMemoryCalled() { return fFlags & 0x04; } }; - class APICheck3Converter : public CPyCppyy::Converter { + class APICheck3Converter : public cppjit::cpyrt::Converter { public: - virtual bool SetArg(PyObject* pyobject, CPyCppyy::Parameter& para, CPyCppyy::CallContext* = nullptr) { - APICheck3* a3 = (APICheck3*)CPyCppyy::Instance_AsVoidPtr(pyobject); + virtual bool SetArg(PyObject* pyobject, cppjit::cpyrt::Parameter& para, cppjit::cpyrt::CallContext* = nullptr) { + APICheck3* a3 = (APICheck3*)cppjit::cpyrt::Instance_AsVoidPtr(pyobject); a3->setSetArgCalled(); para.fValue.fVoidp = a3; para.fTypeCode = 'V'; @@ -100,57 +103,57 @@ class APICheck3Converter : public CPyCppyy::Converter { virtual PyObject* FromMemory(void* address) { APICheck3* a3 = (APICheck3*)address; a3->setFromMemoryCalled(); - return CPyCppyy::Instance_FromVoidPtr(a3, "APICheck3"); + return cppjit::cpyrt::Instance_FromVoidPtr(a3, "APICheck3"); } virtual bool ToMemory(PyObject* value, void* address) { APICheck3* a3 = (APICheck3*)address; a3->setToMemoryCalled(); - *a3 = *(APICheck3*)CPyCppyy::Instance_AsVoidPtr(value); + *a3 = *(APICheck3*)cppjit::cpyrt::Instance_AsVoidPtr(value); return true; } }; - typedef CPyCppyy::ConverterFactory_t cf_t; + typedef cppjit::cpyrt::ConverterFactory_t cf_t; void register_a3() { - CPyCppyy::RegisterConverter("APICheck3", (cf_t)+[](CPyCppyy::cdims_t) { static APICheck3Converter c{}; return &c; }); - CPyCppyy::RegisterConverter("APICheck3&", (cf_t)+[](CPyCppyy::cdims_t) { static APICheck3Converter c{}; return &c; }); + cppjit::cpyrt::RegisterConverter("APICheck3", (cf_t)+[](cppjit::cpyrt::cdims_t) { static APICheck3Converter c{}; return &c; }); + cppjit::cpyrt::RegisterConverter("APICheck3&", (cf_t)+[](cppjit::cpyrt::cdims_t) { static APICheck3Converter c{}; return &c; }); } void unregister_a3() { - CPyCppyy::UnregisterConverter("APICheck3"); - CPyCppyy::UnregisterConverter("APICheck3&"); + cppjit::cpyrt::UnregisterConverter("APICheck3"); + cppjit::cpyrt::UnregisterConverter("APICheck3&"); } APICheck3 gA3a, gA3b; void CallWithAPICheck3(APICheck3&) {} """) - cppyy.gbl.register_a3() + cppjit.gbl.register_a3() - gA3a = cppyy.gbl.gA3a + gA3a = cppjit.gbl.gA3a assert gA3a - assert type(gA3a) == cppyy.gbl.APICheck3 + assert type(gA3a) == cppjit.gbl.APICheck3 assert gA3a.wasFromMemoryCalled() assert not gA3a.wasSetArgCalled() - cppyy.gbl.CallWithAPICheck3(gA3a) + cppjit.gbl.CallWithAPICheck3(gA3a) assert gA3a.wasSetArgCalled() - cppyy.gbl.unregister_a3() + cppjit.gbl.unregister_a3() - gA3b = cppyy.gbl.gA3b + gA3b = cppjit.gbl.gA3b assert gA3b - assert type(gA3b) == cppyy.gbl.APICheck3 + assert type(gA3b) == cppjit.gbl.APICheck3 assert not gA3b.wasFromMemoryCalled() @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test05_custom_executor(self): """Custom type executor""" - import cppyy + import cppjit - cppyy.cppdef(""" - #include "CPyCppyy/API.h" + cppjit.cppdef(""" + #include "cpyrt/API.h" class APICheck4 { int fFlags; @@ -162,49 +165,49 @@ class APICheck4 { bool wasExecutorCalled() { return fFlags & 0x01; } }; - class APICheck4Executor : public CPyCppyy::Executor { + class APICheck4Executor : public cppjit::cpyrt::Executor { public: - virtual PyObject* Execute(Cppyy::TCppMethod_t meth, Cppyy::TCppObject_t obj, CPyCppyy::CallContext* ctxt) { - APICheck4* a4 = (APICheck4*)CPyCppyy::CallVoidP(meth, obj, ctxt); + virtual PyObject* Execute(cppjit::interop::TCppMethod_t meth, cppjit::interop::TCppObject_t obj, cppjit::cpyrt::CallContext* ctxt) { + APICheck4* a4 = (APICheck4*)cppjit::cpyrt::CallVoidP(meth, obj, ctxt); a4->setExecutorCalled(); - return CPyCppyy::Instance_FromVoidPtr(a4, "APICheck4", true); + return cppjit::cpyrt::Instance_FromVoidPtr(a4, "APICheck4", true); } }; - typedef CPyCppyy::ExecutorFactory_t ef_t; + typedef cppjit::cpyrt::ExecutorFactory_t ef_t; void register_a4() { - CPyCppyy::RegisterExecutor("APICheck4*", (ef_t)+[](CPyCppyy::cdims_t) { static APICheck4Executor c{}; return &c; }); + cppjit::cpyrt::RegisterExecutor("APICheck4*", (ef_t)+[](cppjit::cpyrt::cdims_t) { static APICheck4Executor c{}; return &c; }); } void unregister_a4() { - CPyCppyy::UnregisterExecutor("APICheck4*"); + cppjit::cpyrt::UnregisterExecutor("APICheck4*"); } APICheck4* CreateAPICheck4() { return new APICheck4{}; } APICheck4* CreateAPICheck4b() { return new APICheck4{}; } """) - cppyy.gbl.register_a4() + cppjit.gbl.register_a4() - a4 = cppyy.gbl.CreateAPICheck4() + a4 = cppjit.gbl.CreateAPICheck4() assert a4 - assert type(a4) == cppyy.gbl.APICheck4 - assert a4.wasExecutorCalled(); + assert type(a4) == cppjit.gbl.APICheck4 + assert a4.wasExecutorCalled() del a4 - cppyy.gbl.unregister_a4() + cppjit.gbl.unregister_a4() - a4 = cppyy.gbl.CreateAPICheck4b() + a4 = cppjit.gbl.CreateAPICheck4b() assert a4 - assert type(a4) == cppyy.gbl.APICheck4 - assert not a4.wasExecutorCalled(); + assert type(a4) == cppjit.gbl.APICheck4 + assert not a4.wasExecutorCalled() def test06_custom_executor(self): """Custom type executor""" - import cppyy + import cppjit - cppyy.cppdef(""" - #include "CPyCppyy/API.h" + cppjit.cppdef(""" + #include "cpyrt/API.h" namespace ArrayLike { class MyClass{}; @@ -216,13 +219,13 @@ class MyArray { int operator[](int) { return 42; } }; }""") - ns = cppyy.gbl.ArrayLike; - Sequence_Check = cppyy.gbl.CPyCppyy.Sequence_Check + ns = cppjit.gbl.ArrayLike + Sequence_Check = cppjit.gbl.cppjit.cpyrt.Sequence_Check assert not Sequence_Check(ns.my) - assert Sequence_Check(ns.myA) + assert Sequence_Check(ns.myA) assert not Sequence_Check(ns.MyClass()) - assert Sequence_Check(ns.MyArray()) - assert Sequence_Check(tuple()) - assert Sequence_Check(cppyy.gbl.std.vector[ns.MyClass]()) - assert not Sequence_Check(cppyy.gbl.std.list[ns.MyClass]()) + assert Sequence_Check(ns.MyArray()) + assert Sequence_Check(tuple()) + assert Sequence_Check(cppjit.gbl.std.vector[ns.MyClass]()) + assert not Sequence_Check(cppjit.gbl.std.list[ns.MyClass]()) diff --git a/test/test_basic_api.py b/test/test_basic_api.py index 6020c98..9e9fdbe 100644 --- a/test/test_basic_api.py +++ b/test/test_basic_api.py @@ -1,12 +1,14 @@ -import py import shutil import tempfile -from pytest import raises, mark -from support import setup_make, IS_MAC + +import py +from pytest import mark, raises +from support import IS_MAC, setup_make # reuse the example01 currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("example01Dict")) +test_dct = str(currpath.join("cpp/example01Dict")) + def setup_module(mod): setup_make("example01") @@ -15,11 +17,11 @@ def setup_module(mod): class TestBASICAPI: @mark.xfail(IS_MAC, reason="evaluate is broken on macos") def test01_evaluate(self): - import cppyy + import cppjit # we use evaluate as a way to set these flags in support, for example # for IS_CLANG_REPL, etc. let's make sure basic uses work - out = cppyy.evaluate("""#define TEST01_EVALUATE + out = cppjit.evaluate("""#define TEST01_EVALUATE #ifdef TEST01_EVALUATE true #else @@ -27,43 +29,48 @@ def test01_evaluate(self): #endif""") assert out - cppyy.evaluate("__cplusplus") >= 201100 + cppjit.evaluate("__cplusplus") >= 201100 x = 42 - assert cppyy.evaluate(str(x)) == x + assert cppjit.evaluate(str(x)) == x - @mark.xfail(IS_MAC, reason="unidentified IsDebugOutputEnabled issue on macos, also failing in test_fragile") + @mark.xfail( + IS_MAC, + reason="unidentified IsDebugOutputEnabled issue on macos, also failing in test_fragile", + ) def test02_cppdef(self): - import cppyy - assert cppyy.cppdef("namespace test02_NS { int x = 42; }") - assert cppyy.gbl.test02_NS.x == 42 + import cppjit + + assert cppjit.cppdef("namespace test02_NS { int x = 42; }") + assert cppjit.gbl.test02_NS.x == 42 def test03_add_library_path(self): - import cppyy + import cppjit + with raises(OSError, match="No such directory"): - cppyy.add_library_path("not/a/real/path") + cppjit.add_library_path("not/a/real/path") with tempfile.TemporaryDirectory() as tpath: - cppyy.add_library_path(tpath) + cppjit.add_library_path(tpath) # now we should actually see if load library can follow this... # first, try to load without moving to directory... with raises(RuntimeError, match="Could not load library"): - cppyy.load_library("test.so") + cppjit.load_library("test.so") # then copy to our rpath, and make sure it can be loaded now shutil.copyfile(test_dct + ".so", tpath + "/test.so") - cppyy.load_library("test.so") + cppjit.load_library("test.so") def test04_add_include_path(self): - import cppyy + import cppjit with tempfile.TemporaryDirectory() as tpath: - cppyy.add_include_path(tpath) + cppjit.add_include_path(tpath) header = "test_add_include_path.h" with open(tpath + "/" + header, "w") as out: print("int test04f() { return 42; }", file=out) - assert cppyy.include(header) - assert cppyy.gbl.test04f() == 42 + assert cppjit.include(header) + assert cppjit.gbl.test04f() == 42 diff --git a/test/test_boost.py b/test/test_boost.py index 43fc4c1..3680641 100644 --- a/test/test_boost.py +++ b/test/test_boost.py @@ -1,30 +1,33 @@ -import py, os, sys +import os + from pytest import mark, raises, skip -from support import setup_make, IS_CLANG_REPL, IS_MAC_X86, IS_MAC_ARM +from support import IS_MAC_ARM, IS_MAC_X86 noboost = False -if not (os.path.exists(os.path.join(os.path.sep, 'usr', 'include', 'boost')) or \ - os.path.exists(os.path.join(os.path.sep, 'usr', 'local', 'include', 'boost'))): +if not ( + os.path.exists(os.path.join(os.path.sep, "usr", "include", "boost")) + or os.path.exists(os.path.join(os.path.sep, "usr", "local", "include", "boost")) +): noboost = True @mark.skipif(noboost == True, reason="boost not found") class TestBOOSTANY: def setup_class(cls): - import cppyy + import cppjit - cppyy.include('boost/any.hpp') + cppjit.include("boost/any.hpp") @mark.skipif((IS_MAC_ARM or IS_MAC_X86), reason="Fails to include boost on OS X") def test01_any_class(self): """Availability of boost::any""" - import cppyy + import cppjit - assert cppyy.gbl.boost.any + assert cppjit.gbl.boost.any - from cppyy.gbl import std - from cppyy.gbl.boost import any + from cppjit.gbl import std + from cppjit.gbl.boost import any assert std.list[any] @@ -32,18 +35,18 @@ def test01_any_class(self): def test02_any_usage(self): """boost::any assignment and casting""" - import cppyy + import cppjit - assert cppyy.gbl.boost + assert cppjit.gbl.boost - from cppyy.gbl import std, boost + from cppjit.gbl import boost, std val = boost.any() # test both by-ref and by rvalue v = std.vector[int]() val.__assign__(v) val.__assign__(std.move(std.vector[int](range(100)))) - assert val.type() == cppyy.typeid(std.vector[int]) + assert val.type() == cppjit.typeid(std.vector[int]) extract = boost.any_cast[std.vector[int]](val) assert type(extract) is std.vector[int] @@ -51,17 +54,17 @@ def test02_any_usage(self): extract += range(100) assert len(extract) == 200 - val.__assign__(std.move(extract)) # move forced - #assert len(extract) == 0 # not guaranteed by the standard + val.__assign__(std.move(extract)) # move forced + # assert len(extract) == 0 # not guaranteed by the standard # TODO: we hit boost::any_cast(boost::any* operand) instead # of the reference version which raises boost.any_cast.__useffi__ = False try: - # raises(Exception, boost.any_cast[int], val) + # raises(Exception, boost.any_cast[int], val) assert not boost.any_cast[int](val) except Exception: - # getting here is good, too ... + # getting here is good, too ... pass extract = boost.any_cast[std.vector[int]](val) @@ -71,53 +74,53 @@ def test02_any_usage(self): @mark.skipif(((noboost == True) or IS_MAC_ARM or IS_MAC_X86), reason="boost not found") class TestBOOSTOPERATORS: def setup_class(cls): - import cppyy + import cppjit - cppyy.include('boost/operators.hpp') + cppjit.include("boost/operators.hpp") def test01_ordered(self): """ordered_field_operators as base used to crash""" - import cppyy + import cppjit try: - cppyy.include("gmpxx.h") + cppjit.include("gmpxx.h") except ImportError: skip("gmpxx not installed") - cppyy.cppdef(""" + cppjit.cppdef(""" namespace boost_test { class Derived : boost::ordered_field_operators, boost::ordered_field_operators {}; } """) - assert cppyy.gbl.boost_test.Derived + assert cppjit.gbl.boost_test.Derived @mark.skipif(noboost == True, reason="boost not found") class TestBOOSTVARIANT: def setup_class(cls): - import cppyy + import cppjit - cppyy.include("boost/variant/variant.hpp") - cppyy.include("boost/variant/get.hpp") + cppjit.include("boost/variant/variant.hpp") + cppjit.include("boost/variant/get.hpp") @mark.xfail(run=False) def test01_variant_usage(self): """boost::variant usage""" - # as posted on stackoverflow as example - import cppyy + # as posted on stackoverflow as example + import cppjit - cpp = cppyy.gbl - std = cpp.std + cpp = cppjit.gbl + std = cpp.std boost = cpp.boost - cppyy.cppdef("""namespace BV { + cppjit.cppdef("""namespace BV { class A { }; class B { }; class C { }; } """) - VariantType = boost.variant['BV::A, BV::B, BV::C'] + VariantType = boost.variant["BV::A, BV::B, BV::C"] VariantTypeList = std.vector[VariantType] v = VariantTypeList() @@ -129,31 +132,31 @@ class C { }; } """) v.push_back(VariantType(cpp.BV.C())) assert v.back().which() == 2 - assert type(boost.get['BV::A'](v[0])) == cpp.BV.A + assert type(boost.get["BV::A"](v[0])) == cpp.BV.A # Trying to raise this exception seg faults, by trying to execute an unfit instantiation. # This comes from `Instantiate` obtaining a single handle and providing a result # The same issue happens with trying `BestOverloadFunctionMatch` first since the candidate set is single - raises(Exception, boost.get['BV::B'], v[0]) - assert type(boost.get['BV::B'](v[1])) == cpp.BV.B - assert type(boost.get['BV::C'](v[2])) == cpp.BV.C + raises(Exception, boost.get["BV::B"], v[0]) + assert type(boost.get["BV::B"](v[1])) == cpp.BV.B + assert type(boost.get["BV::C"](v[2])) == cpp.BV.C @mark.skipif(((noboost == True) or IS_MAC_ARM or IS_MAC_X86), reason="boost not found") class TestBOOSTERASURE: def setup_class(cls): - import cppyy + import cppjit - cppyy.include("boost/type_erasure/any.hpp") - cppyy.include("boost/type_erasure/member.hpp") - cppyy.include("boost/mpl/vector.hpp") + cppjit.include("boost/type_erasure/any.hpp") + cppjit.include("boost/type_erasure/member.hpp") + cppjit.include("boost/mpl/vector.hpp") def test01_erasure_usage(self): """boost::type_erasure usage""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" BOOST_TYPE_ERASURE_MEMBER((has_member_f), f, 0) using LengthsInterface = boost::mpl::vector< @@ -171,4 +174,4 @@ def test01_erasure_usage(self): } """) - assert cppyy.gbl.lengths() is not None + assert cppjit.gbl.lengths() is not None diff --git a/test/test_concurrent.py b/test/test_concurrent.py index 2d5fc7c..7c176d0 100644 --- a/test/test_concurrent.py +++ b/test/test_concurrent.py @@ -1,34 +1,33 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import IS_MAC_ARM, IS_MAC_X86, IS_LINUX_ARM +from pytest import mark, skip +from support import IS_LINUX_ARM, IS_MAC_ARM, IS_MAC_X86 class TestCONCURRENT: - def setup_class(cls): - import cppyy + import cppjit cls.data = [3.1415, 2.7183, 1.4142, 1.3807, -9.2848] - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace Workers { double calc(double d) { return d*42.; } }""") - cppyy.gbl.Workers.calc.__release_gil__ = True + cppjit.gbl.Workers.calc.__release_gil__ = True @mark.xfail(run=False, reason="Crashes") def test01_simple_threads(self): """Run basic Python threads""" - import cppyy import threading - cppyy.gbl.Workers.calc.__release_gil__ = True + import cppjit + + cppjit.gbl.Workers.calc.__release_gil__ = True threads = [] for d in self.data: - threads.append(threading.Thread(target=cppyy.gbl.Workers.calc, args=(d,))) + threads.append(threading.Thread(target=cppjit.gbl.Workers.calc, args=(d,))) for t in threads: t.start() @@ -40,51 +39,57 @@ def test01_simple_threads(self): def test02_futures(self): """Run with Python futures""" - import cppyy + import cppjit try: import concurrent.futures except ImportError: skip("module concurrent is not installed") - total = 0. - with concurrent.futures.ThreadPoolExecutor(max_workers=len(self.data)) as executor: - futures = [executor.submit(cppyy.gbl.Workers.calc, d) for d in self.data] + total = 0.0 + with concurrent.futures.ThreadPoolExecutor( + max_workers=len(self.data) + ) as executor: + futures = [executor.submit(cppjit.gbl.Workers.calc, d) for d in self.data] for f in concurrent.futures.as_completed(futures): total += f.result() - assert round(total+26.4642, 8) == 0.0 + assert round(total + 26.4642, 8) == 0.0 def test03_timeout(self): """Time-out with threads""" return - import cppyy - import threading, time + import threading + import time - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace test12_timeout { bool _islive = false; volatile bool* islive = &_islive; bool _stopit = false; volatile bool* stopit = &_stopit; }""") - cppyy.gbl.cling.runtime.gCling.process.__release_gil__ = True + cppjit.gbl.cling.runtime.gCling.process.__release_gil__ = True cmd = r"""\ *test12_timeout::islive = true; while (!*test12_timeout::stopit); """ - t = threading.Thread(target=cppyy.gbl.cling.runtime.gCling.process, args=(cmd,)) + t = threading.Thread( + target=cppjit.gbl.cling.runtime.gCling.process, args=(cmd,) + ) t.start() - # have to give ProcessLine() time to actually start doing work - while not cppyy.gbl.test12_timeout.islive: - time.sleep(0.1) # in seconds + # have to give ProcessLine() time to actually start doing work + while not cppjit.gbl.test12_timeout.islive: + time.sleep(0.1) # in seconds - # join the thread with a timeout after 0.1s - t.join(0.1) # id. + # join the thread with a timeout after 0.1s + t.join(0.1) # id. - if t.is_alive(): # was timed-out - cppyy.gbl.test12_timeout.stopit[0] = True + if t.is_alive(): # was timed-out + cppjit.gbl.test12_timeout.stopit[0] = True @mark.xfail(condition=IS_MAC_X86, reason="Fails on OS X x86") def test04_cpp_threading_with_exceptions(self): @@ -93,11 +98,11 @@ def test04_cpp_threading_with_exceptions(self): if IS_MAC_ARM: skip("JIT exceptions can not be caught in JITed code on Mac ARM") - import cppyy + import cppjit - cppyy.include("CPyCppyy/PyException.h") + cppjit.include("cpyrt/PyException.h") - cppyy.cppdef(""" + cppjit.cppdef(""" #include namespace thread_test { @@ -116,7 +121,7 @@ def test04_cpp_threading_with_exceptions(self): while (counter++ < 10) try { cons->process(counter); - } catch (CPyCppyy::PyException& e) { + } catch (cppjit::cpyrt::PyException& e) { err_msg = e.what(); return; } @@ -133,13 +138,14 @@ def test04_cpp_threading_with_exceptions(self): std::string err_msg; }; }""") - ns = cppyy.gbl.thread_test + ns = cppjit.gbl.thread_test consumer = ns.consumer worker = ns.worker worker.wait.__release_gil__ = True class C(consumer): count = 0 + def process(self, c): self.count += 1 @@ -154,6 +160,7 @@ def process(self, c): class C(consumer): count = 0 + def process(self, c): raise RuntimeError("all wrong") @@ -164,15 +171,15 @@ def process(self, c): w.wait() assert "RuntimeError" in w.err_msg - assert "all wrong" in w.err_msg + assert "all wrong" in w.err_msg @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test05_float2d_callback(self): """Passing of 2-dim float arguments""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ #include namespace FloatDim2 { @@ -215,10 +222,10 @@ def test05_float2d_callback(self): t.join(); } }""") - cppyy.gbl.FloatDim2.callback.__release_gil__ = True + cppjit.gbl.FloatDim2.callback.__release_gil__ = True - class Processor(cppyy.gbl.FloatDim2.Processor): - buffer = cppyy.gbl.FloatDim2.Buffer() + class Processor(cppjit.gbl.FloatDim2.Processor): + buffer = cppjit.gbl.FloatDim2.Buffer() def process(self, data, channels, samples): self.buffer.setData(data) @@ -226,20 +233,21 @@ def process(self, data, channels, samples): try: for c in range(channels): for s in range(samples): - self.buffer.setSample(c, s, 0.0) # < used to crash here + self.buffer.setSample(c, s, 0.0) # < used to crash here except Exception as e: print(e) p = Processor() - cppyy.gbl.FloatDim2.callback(p) + cppjit.gbl.FloatDim2.callback(p) def test06_overload_reuse_in_threads(self): """Threads reuse overload objects; check for clashes""" - import cppyy import threading - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace CPPOverloadReuse { class Simulation1 { public: @@ -247,8 +255,8 @@ class Simulation1 { }; }""") def test(): - o = {"a": "b"} # causes a temporary to be created - simulation = cppyy.gbl.CPPOverloadReuse.Simulation1() + o = {"a": "b"} # causes a temporary to be created + simulation = cppjit.gbl.CPPOverloadReuse.Simulation1() simulation.set_something(o, ".") threads = [threading.Thread(target=test) for i in range(0, 100)] @@ -264,13 +272,14 @@ def test07_overload_reuse_in_threads_wo_gil(self): """Threads reuse overload objects; check for clashes if no GIL""" if IS_MAC_ARM: - # the culprit here is occasional std::system_error if a thread can not be joined + # the culprit here is occasional std::system_error if a thread can not be joined skip("JIT exceptions can not be caught in JITed code on Mac ARM") - import cppyy import threading - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace CPPOverloadReuse { class Simulation2 { int fCount; @@ -279,16 +288,16 @@ class Simulation2 { virtual int do_something() { return fCount; } }; }""") - cppyy.gbl.CPPOverloadReuse.Simulation2.do_something.__release_gil__ = True + cppjit.gbl.CPPOverloadReuse.Simulation2.do_something.__release_gil__ = True class State(object): lock = threading.Lock() c1, c2, c3 = 0, 0, 0 def test(i, state=State): - #global c1, c2, c3, lock + # global c1, c2, c3, lock - simulation = cppyy.gbl.CPPOverloadReuse.Simulation2(i) + simulation = cppjit.gbl.CPPOverloadReuse.Simulation2(i) res = simulation.do_something() with state.lock: @@ -309,10 +318,10 @@ def test(i, state=State): def test08_python_callabcks_with_release_gil(self): """Test release GIL, when the C++ func calls back into Python""" - import cppyy - from cppyy import gbl + import cppjit + from cppjit import gbl - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace NS1 { int callback(int(*fn)(int, int), int x, int y) { return fn(x, y); @@ -320,11 +329,9 @@ def test08_python_callabcks_with_release_gil(self): }; """) - def add(x: int, y: int) -> int: return x + y - callback = gbl.NS1.callback callback.__release_gil__ = True assert callback(add, 1, 2) == 3 diff --git a/test/test_conversions.py b/test/test_conversions.py index 98af38b..0e980f0 100644 --- a/test/test_conversions.py +++ b/test/test_conversions.py @@ -1,9 +1,10 @@ -import py, os, sys -from pytest import raises, mark -from support import setup_make, IS_LINUX, IS_CLANG_REPL, IS_CLING, IS_MAC +import py +from pytest import mark, raises +from support import IS_CLANG_REPL, IS_CLING, IS_MAC, setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("conversionsDict")) +test_dct = str(currpath.join("cpp/conversionsDict")) + def setup_module(mod): setup_make("conversions") @@ -12,44 +13,50 @@ def setup_module(mod): class TestCONVERSIONS: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.conversion = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.conversion = cppjit.load_reflection_info(cls.test_dct) def test01_implicit_vector_conversions(self): """Test implicit conversions of std::vector""" - import cppyy - CNS = cppyy.gbl.CNS + import cppjit + + CNS = cppjit.gbl.CNS N = 10 total = float(sum(range(N))) - v = cppyy.gbl.std.vector['double'](range(N)) + v = cppjit.gbl.std.vector["double"](range(N)) assert CNS.sumit(v) == total assert sum(v) == total assert CNS.sumit(range(N)) == total M = 5 total = float(sum(range(N)) + sum(range(M, N))) - v1 = cppyy.gbl.std.vector['double'](range(N)) - v2 = cppyy.gbl.std.vector['double'](range(M, N)) + v1 = cppjit.gbl.std.vector["double"](range(N)) + v2 = cppjit.gbl.std.vector["double"](range(M, N)) assert CNS.sumit(v1, v2) == total - assert sum(v1)+sum(v2) == total - assert CNS.sumit(v1, range(M, N)) == total - assert CNS.sumit(range(N), v2) == total + assert sum(v1) + sum(v2) == total + assert CNS.sumit(v1, range(M, N)) == total + assert CNS.sumit(range(N), v2) == total assert CNS.sumit(range(N), range(M, N)) == total def test02_memory_handling_of_temporaries(self): """Verify that memory of temporaries is properly cleaned up""" - import cppyy, gc - CNS, CC = cppyy.gbl.CNS, cppyy.gbl.CNS.Counter + import gc + + import cppjit + + CNS, CC = cppjit.gbl.CNS, cppjit.gbl.CNS.Counter assert CC.s_count == 0 c = CC() assert c.__python_owns__ assert CC.s_count == 1 - del c; gc.collect() + del c + gc.collect() assert CC.s_count == 0 assert CNS.myhowmany((CC(), CC(), CC())) == 3 @@ -63,19 +70,22 @@ def test02_memory_handling_of_temporaries(self): def test03_error_handling(self): """Verify error handling""" - import cppyy, gc - CNS, CC = cppyy.gbl.CNS, cppyy.gbl.CNS.Counter + import gc + + import cppjit + + CNS, CC = cppjit.gbl.CNS, cppjit.gbl.CNS.Counter N = 13 total = sum(range(N)) assert CNS.sumints(range(N)) == total assert CNS.sumit([float(x) for x in range(N)]) == float(total) raises(TypeError, CNS.sumints, [float(x) for x in range(N)]) - raises(TypeError, CNS.sumints, list(range(N))+[0.]) + raises(TypeError, CNS.sumints, list(range(N)) + [0.0]) assert CC.s_count == 0 - raises(TypeError, CNS.sumints, list(range(N))+[CC()]) + raises(TypeError, CNS.sumints, list(range(N)) + [CC()]) gc.collect() assert CC.s_count == 0 @@ -87,25 +97,27 @@ def test03_error_handling(self): gc.collect() assert CC.s_count == 0 - @mark.xfail(run=IS_CLANG_REPL, condition = IS_MAC or IS_CLING, reason = "Crashes on Cling") + @mark.xfail( + run=IS_CLANG_REPL, condition=IS_MAC or IS_CLING, reason="Crashes on Cling" + ) def test04_implicit_conversion_from_tuple(self): """Allow implicit conversions from tuples as arguments {}-like""" # Note: fails on windows b/c the assignment operator for strings is # template, which ("operator=(std::string)") doesn't instantiate - import cppyy + import cppjit - m = cppyy.gbl.std.map[str, str]() - m.insert(('a', 'b')) # implicit conversion to std::pair + m = cppjit.gbl.std.map[str, str]() + m.insert(("a", "b")) # implicit conversion to std::pair - assert m['a'] == 'b' + assert m["a"] == "b" def test05_bool_conversions(self): """Test operator bool() and null pointer behavior""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace BoolConversions { struct Test1 {}; struct Test2 { @@ -118,11 +130,11 @@ def test05_bool_conversions(self): Test2* CreateNullTest2() { return nullptr; } }""") - ns = cppyy.gbl.BoolConversions + ns = cppjit.gbl.BoolConversions for t in [ns.CreateNullTest1(), ns.CreateNullTest2()]: assert not t - assert ns.Test1() - assert ns.Test2(True) - assert not ns.Test2(False) \ No newline at end of file + assert ns.Test1() + assert ns.Test2(True) + assert not ns.Test2(False) diff --git a/test/test_cpp11features.py b/test/test_cpp11features.py index 51abe67..d5fc75f 100644 --- a/test/test_cpp11features.py +++ b/test/test_cpp11features.py @@ -1,28 +1,43 @@ -import py, os, sys -from pytest import raises, mark -from support import setup_make, ispypy, IS_CLANG_REPL, IS_CLING, IS_LINUX_ARM, IS_LINUX, IS_MAC, IS_VALGRIND - +import sys + +import py +from pytest import mark, raises +from support import ( + IS_LINUX, + IS_LINUX_ARM, + IS_MAC, + IS_VALGRIND, + ispypy, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("cpp11featuresDict")) +test_dct = str(currpath.join("cpp/cpp11featuresDict")) + def setup_module(mod): setup_make("cpp11features") + class TestCPP11FEATURES: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.cpp11features = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.cpp11features = cppjit.load_reflection_info(cls.test_dct) @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test01_smart_ptr(self): """Usage and access of std::shared/unique_ptr<>""" - from cppyy.gbl import TestSmartPtr - from cppyy.gbl import create_shared_ptr_instance, create_unique_ptr_instance import gc + from cppjit.gbl import ( + TestSmartPtr, + create_shared_ptr_instance, + create_unique_ptr_instance, + ) + for cf in [create_shared_ptr_instance, create_unique_ptr_instance]: assert TestSmartPtr.s_counter == 0 @@ -44,13 +59,18 @@ def test01_smart_ptr(self): gc.collect() assert TestSmartPtr.s_counter == 0 - @mark.xfail(run=False, condition=IS_LINUX_ARM and IS_VALGRIND, reason="Valgrind issues on ARM") + @mark.xfail( + run=False, + condition=IS_LINUX_ARM and IS_VALGRIND, + reason="Valgrind issues on ARM", + ) def test02_smart_ptr_construction(self): """Shared/Unique pointer ctor is templated, requiring special care""" - from cppyy.gbl import std, TestSmartPtr import gc + from cppjit.gbl import TestSmartPtr, std + class C(TestSmartPtr): pass @@ -72,13 +92,14 @@ class C(TestSmartPtr): gc.collect() assert TestSmartPtr.s_counter == 0 - @mark.xfail(run=False, condition=IS_LINUX and IS_VALGRIND, reason = "Valgrind issue") + @mark.xfail(run=False, condition=IS_LINUX and IS_VALGRIND, reason="Valgrind issue") def test03_smart_ptr_memory_handling(self): """Test shared/unique pointer memory ownership""" - from cppyy.gbl import std, TestSmartPtr import gc + from cppjit.gbl import TestSmartPtr, std + class C(TestSmartPtr): pass @@ -107,10 +128,17 @@ class C(TestSmartPtr): def test04_shared_ptr_passing(self): """Ability to pass shared_ptr through shared_ptr""" - from cppyy.gbl import std, TestSmartPtr, DerivedTestSmartPtr - from cppyy.gbl import pass_shared_ptr, move_shared_ptr, create_TestSmartPtr_by_value import gc + from cppjit.gbl import ( + DerivedTestSmartPtr, + TestSmartPtr, + create_TestSmartPtr_by_value, + move_shared_ptr, + pass_shared_ptr, + std, + ) + for ff, mv in [(pass_shared_ptr, lambda x: x), (move_shared_ptr, std.move)]: assert TestSmartPtr.s_counter == 0 @@ -122,7 +150,7 @@ def test04_shared_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # ability to take over by-value python-owned objects + # ability to take over by-value python-owned objects tsp = create_TestSmartPtr_by_value() assert TestSmartPtr.s_counter == 1 assert tsp.__python_owns__ @@ -135,7 +163,7 @@ def test04_shared_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # alternative make_shared with type taken from pointer + # alternative make_shared with type taken from pointer tsp = create_TestSmartPtr_by_value() shared_stp = std.make_shared(tsp) assert TestSmartPtr.s_counter == 1 @@ -146,14 +174,20 @@ def test04_shared_ptr_passing(self): def test05_unique_ptr_passing(self): """Ability to pass unique_ptr through unique_ptr""" - from cppyy.gbl import std, TestSmartPtr, DerivedTestSmartPtr - from cppyy.gbl import move_unique_ptr, move_unique_ptr_derived - from cppyy.gbl import create_TestSmartPtr_by_value import gc + from cppjit.gbl import ( + DerivedTestSmartPtr, + TestSmartPtr, + create_TestSmartPtr_by_value, + move_unique_ptr, # noqa: F401 + move_unique_ptr_derived, + std, + ) + assert TestSmartPtr.s_counter == 0 - # move matching unique_ptr + # move matching unique_ptr dd = std.make_unique[DerivedTestSmartPtr](DerivedTestSmartPtr(24)) assert TestSmartPtr.s_counter == 1 assert move_unique_ptr_derived(std.move(dd)) == 100 @@ -163,7 +197,7 @@ def test05_unique_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # move with conversion + # move with conversion dd = std.make_unique[DerivedTestSmartPtr](DerivedTestSmartPtr(24)) assert TestSmartPtr.s_counter == 1 # TODO: why does the following fail, but succeed for shared_ptr?? @@ -174,7 +208,7 @@ def test05_unique_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # ability to take over by-value python-owned objects + # ability to take over by-value python-owned objects tsp = create_TestSmartPtr_by_value() assert TestSmartPtr.s_counter == 1 assert tsp.__python_owns__ @@ -187,7 +221,7 @@ def test05_unique_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - # alternative make_unique with type taken from pointer + # alternative make_unique with type taken from pointer tsp = create_TestSmartPtr_by_value() unique_stp = std.make_unique(tsp) assert TestSmartPtr.s_counter == 1 @@ -199,28 +233,32 @@ def test05_unique_ptr_passing(self): def test06_nullptr(self): """Allow the programmer to pass NULL in certain cases""" - import cppyy + import cppjit - # test existence - nullptr = cppyy.nullptr - # assert not hasattr(cppyy.gbl, 'nullptr') + # test existence + nullptr = cppjit.nullptr + # assert not hasattr(cppjit.gbl, 'nullptr') - assert cppyy.bind_object(cppyy.nullptr, 'std::vector') == cppyy.nullptr - assert not cppyy.bind_object(cppyy.nullptr, 'std::vector') != cppyy.nullptr + assert cppjit.bind_object(cppjit.nullptr, "std::vector") == cppjit.nullptr + assert ( + not cppjit.bind_object(cppjit.nullptr, "std::vector") != cppjit.nullptr + ) - # further usage is tested in datatypes.py:test15_nullptr_passing + # further usage is tested in datatypes.py:test15_nullptr_passing def test07_move(self): """Move construction, assignment, and methods""" - import cppyy, gc + import gc + + import cppjit def moveit(T): assert T.s_instance_counter == 0 - from cppyy.gbl import std + from cppjit.gbl import std - # move constructor + # move constructor i1 = T() assert T.s_move_counter == 0 @@ -228,23 +266,23 @@ def moveit(T): assert T.s_move_counter == 0 if ispypy or 0x3000000 <= sys.hexversion: - i3 = T(std.move(T())) # can't check ref-count + i3 = T(std.move(T())) # can't check ref-count else: - i3 = T(T()) # should call move, not memoized cctor + i3 = T(T()) # should call move, not memoized cctor assert T.s_move_counter == 1 - i3 = T(std.move(T())) # both move and ref-count + i3 = T(std.move(T())) # both move and ref-count assert T.s_move_counter == 2 i4 = T(std.move(i1)) assert T.s_move_counter == 3 - # move assignment + # move assignment i4.__assign__(i2) assert T.s_move_counter == 3 if ispypy or 0x3000000 <= sys.hexversion: - i4.__assign__(std.move(T())) # can't check ref-count + i4.__assign__(std.move(T())) # can't check ref-count else: i4.__assign__(T()) assert T.s_move_counter == 4 @@ -256,33 +294,33 @@ def moveit(T): gc.collect() assert T.s_instance_counter == 0 - # order of moving and normal functions are reversed in 1, 2, for - # overload resolution testing - moveit(cppyy.gbl.TestMoving1) - moveit(cppyy.gbl.TestMoving2) + # order of moving and normal functions are reversed in 1, 2, for + # overload resolution testing + moveit(cppjit.gbl.TestMoving1) + moveit(cppjit.gbl.TestMoving2) - # implicit conversion and move - assert cppyy.gbl.TestMoving1.s_instance_counter == 0 - assert cppyy.gbl.TestMoving2.s_instance_counter == 0 - cppyy.gbl.implicit_converion_move(cppyy.gbl.TestMoving1()) - cppyy.gbl.implicit_converion_move(cppyy.gbl.TestMoving2()) + # implicit conversion and move + assert cppjit.gbl.TestMoving1.s_instance_counter == 0 + assert cppjit.gbl.TestMoving2.s_instance_counter == 0 + cppjit.gbl.implicit_converion_move(cppjit.gbl.TestMoving1()) + cppjit.gbl.implicit_converion_move(cppjit.gbl.TestMoving2()) gc.collect() - assert cppyy.gbl.TestMoving1.s_instance_counter == 0 - assert cppyy.gbl.TestMoving2.s_instance_counter == 0 + assert cppjit.gbl.TestMoving1.s_instance_counter == 0 + assert cppjit.gbl.TestMoving2.s_instance_counter == 0 @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test08_initializer_list(self): """Initializer list construction""" - from cppyy.gbl import std, TestData, TestData2, WithInitList + from cppjit.gbl import TestData, TestData2, WithInitList, std v = std.vector[int]((1, 2, 3, 4)) assert list(v) == [1, 2, 3, 4] - v = std.vector['double']((1, 2, 3, 4)) - assert list(v) == [1., 2., 3., 4.] + v = std.vector["double"]((1, 2, 3, 4)) + assert list(v) == [1.0, 2.0, 3.0, 4.0] - raises(TypeError, std.vector[int], [1., 2., 3., 4.]) + raises(TypeError, std.vector[int], [1.0, 2.0, 3.0, 4.0]) for cls in [std.vector, WithInitList]: for cls_arg in [TestData, TestData2]: @@ -295,69 +333,72 @@ def test08_initializer_list(self): for i in range(len(l)): assert v[i].m_int == l[i].m_int - import cppyy + import cppjit - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace InitializerListTest { std::vector foo(const std::initializer_list& vals) { return std::vector{vals}; } }""") - ns = cppyy.gbl.InitializerListTest + ns = cppjit.gbl.InitializerListTest - for l in (['x'], ['x', 'y', 'z']): - assert ns.foo(l) == std.vector['std::string'](l) + for l in (["x"], ["x", "y", "z"]): + assert ns.foo(l) == std.vector["std::string"](l) def test09_lambda_calls(self): """Call (global) lambdas""" - import cppyy + import cppjit - cppyy.cppdef("auto gMyLambda = [](int a) { return 40 + a; };") + cppjit.cppdef("auto gMyLambda = [](int a) { return 40 + a; };") - assert cppyy.gbl.gMyLambda - assert cppyy.gbl.gMyLambda(2) == 42 - assert cppyy.gbl.gMyLambda(40) == 80 + assert cppjit.gbl.gMyLambda + assert cppjit.gbl.gMyLambda(2) == 42 + assert cppjit.gbl.gMyLambda(40) == 80 - cppyy.cppdef("auto gime_a_lambda1() { return []() { return 42; }; }") - l1 = cppyy.gbl.gime_a_lambda1() + cppjit.cppdef("auto gime_a_lambda1() { return []() { return 42; }; }") + l1 = cppjit.gbl.gime_a_lambda1() assert l1 assert l1() == 42 - cppyy.cppdef("auto gime_a_lambda2() { int a = 4; return [a](int b) { return 42+a+b; }; }") - l2 = cppyy.gbl.gime_a_lambda2() + cppjit.cppdef( + "auto gime_a_lambda2() { int a = 4; return [a](int b) { return 42+a+b; }; }" + ) + l2 = cppjit.gbl.gime_a_lambda2() assert l2 assert l2(2) == 48 - cppyy.cppdef("auto gime_a_lambda3(int a ) { return [a](int b) { return 42+a+b; }; }") - l3 = cppyy.gbl.gime_a_lambda3(4) + cppjit.cppdef( + "auto gime_a_lambda3(int a ) { return [a](int b) { return 42+a+b; }; }" + ) + l3 = cppjit.gbl.gime_a_lambda3(4) assert l3 assert l3(2) == 48 def test10_optional(self): """Use of optional and nullopt""" - import cppyy + import cppjit - assert cppyy.gbl.std.optional - assert cppyy.gbl.std.nullopt + assert cppjit.gbl.std.optional + assert cppjit.gbl.std.nullopt - cppyy.cppdef(""" + cppjit.cppdef(""" enum Enum { A = -1 }; bool callopt(std::optional) { return true; } """) - a = cppyy.gbl.std.optional[cppyy.gbl.Enum]() - assert cppyy.gbl.callopt(a) + a = cppjit.gbl.std.optional[cppjit.gbl.Enum]() + assert cppjit.gbl.callopt(a) - c = cppyy.gbl.std.nullopt - assert cppyy.gbl.callopt(c) + c = cppjit.gbl.std.nullopt + assert cppjit.gbl.callopt(c) def test11_chrono(self): """Use of chrono and overloaded operator+""" - import cppyy - from cppyy.gbl import std + from cppjit.gbl import std t = std.chrono.system_clock.now() - std.chrono.seconds(1) # following used to fail with compilation error @@ -366,8 +407,8 @@ def test11_chrono(self): def test12_stdfunction(self): """Use of std::function with arguments in a namespace""" - import cppyy - from cppyy.gbl import FunctionNS, FNTestStruct, FNCreateTestStructFunc + import cppjit + from cppjit.gbl import FNCreateTestStructFunc, FNTestStruct, FunctionNS t = FNTestStruct(42) f = FNCreateTestStructFunc() @@ -377,13 +418,13 @@ def test12_stdfunction(self): f = FunctionNS.FNCreateTestStructFunc() assert f(t) == 13 - # and for good measure, inline - cppyy.cppdef("""namespace FunctionNS2 { + # and for good measure, inline + cppjit.cppdef("""namespace FunctionNS2 { struct FNTestStruct { FNTestStruct(int i) : t(i) {} int t; }; std::function FNCreateTestStructFunc() { return [](const FNTestStruct& t) { return t.t; }; } }""") - from cppyy.gbl import FunctionNS2 + from cppjit.gbl import FunctionNS2 # noqa: F401 t = FunctionNS.FNTestStruct(27) f = FunctionNS.FNCreateTestStructFunc() @@ -392,27 +433,34 @@ def test12_stdfunction(self): def test13_stdhash(self): """Use of std::hash""" - import cppyy - from cppyy.gbl import StructWithHash, StructWithoutHash + from cppjit.gbl import StructWithHash, StructWithoutHash - for i in range(3): # to test effect of caching + for i in range(3): # to test effect of caching swo = StructWithoutHash() assert hash(swo) == object.__hash__(swo) assert hash(swo) == object.__hash__(swo) sw = StructWithHash() - assert hash(sw) == 17 - assert hash(sw) == 17 + assert hash(sw) == 17 + assert hash(sw) == 17 @mark.xfail def test14_shared_ptr_passing(self): """Ability to pass normal pointers through shared_ptr by value""" - from cppyy.gbl import std, TestSmartPtr, DerivedTestSmartPtr - from cppyy.gbl import pass_shared_ptr import gc - for cls, val in [(lambda: TestSmartPtr(), 17), (lambda: DerivedTestSmartPtr(24), 100)]: + from cppjit.gbl import ( # noqa: F401 + DerivedTestSmartPtr, + TestSmartPtr, + pass_shared_ptr, + std, + ) + + for cls, val in [ + (lambda: TestSmartPtr(), 17), + (lambda: DerivedTestSmartPtr(24), 100), + ]: assert TestSmartPtr.s_counter == 0 obj = cls() @@ -424,7 +472,7 @@ def test14_shared_ptr_passing(self): assert obj.__python_owns__ assert TestSmartPtr.s_counter == 1 - assert not not obj # pass was by shared copy + assert not not obj # pass was by shared copy del obj gc.collect() @@ -434,28 +482,29 @@ def test14_shared_ptr_passing(self): def test15_unique_ptr_template_deduction(self): """Argument type deduction with std::unique_ptr""" - import cppyy + import cppjit - cppyy.cppdef("""namespace UniqueTempl { + cppjit.cppdef("""namespace UniqueTempl { template std::unique_ptr returnptr(std::unique_ptr&& a) { return std::move(a); } }""") - uptr_in = cppyy.gbl.std.make_unique[int]() - uptr_out = cppyy.gbl.UniqueTempl.returnptr["int"](cppyy.gbl.std.move(uptr_in)) + uptr_in = cppjit.gbl.std.make_unique[int]() + uptr_out = cppjit.gbl.UniqueTempl.returnptr["int"](cppjit.gbl.std.move(uptr_in)) assert not not uptr_out - uptr_in = cppyy.gbl.std.make_unique['int']() + uptr_in = cppjit.gbl.std.make_unique["int"]() with raises(ValueError): # not an RValue - cppyy.gbl.UniqueTempl.returnptr[int](uptr_in) + cppjit.gbl.UniqueTempl.returnptr[int](uptr_in) - @mark.xfail(IS_MAC, reason = "Fails on Mac platforms") + @mark.xfail(IS_MAC, reason="Fails on Mac platforms") def test16_unique_ptr_moves(self): """std::unique_ptr requires moves""" - import cppyy - cppyy.cppdef("""namespace unique_ptr_moves { + import cppjit + + cppjit.cppdef("""namespace unique_ptr_moves { template std::unique_ptr returnptr_value(std::unique_ptr a) { return std::move(a); @@ -465,12 +514,15 @@ def test16_unique_ptr_moves(self): return std::move(a); } }""") - up = cppyy.gbl.std.make_unique[int](42) + up = cppjit.gbl.std.make_unique[int](42) - ns = cppyy.gbl.unique_ptr_moves - up = ns.returnptr_value(up) ; assert up and up.get()[0] == 42 - up = ns.returnptr_value(cppyy.gbl.std.move(up)); assert up and up.get()[0] == 42 - up = ns.returnptr_move(cppyy.gbl.std.move(up)) ; assert up and up.get()[0] == 42 + ns = cppjit.gbl.unique_ptr_moves + up = ns.returnptr_value(up) + assert up and up.get()[0] == 42 + up = ns.returnptr_value(cppjit.gbl.std.move(up)) + assert up and up.get()[0] == 42 + up = ns.returnptr_move(cppjit.gbl.std.move(up)) + assert up and up.get()[0] == 42 with raises(TypeError): ns.returnptr_move(up) @@ -478,9 +530,9 @@ def test16_unique_ptr_moves(self): def test17_unique_ptr_data(self): """std::unique_ptr as data means implicitly no copy ctor""" - import cppyy + import cppjit - cppyy.cppdef("""namespace unique_ptr_data{ + cppjit.cppdef("""namespace unique_ptr_data{ class Example { private: std::unique_ptr x; @@ -490,19 +542,19 @@ class Example { double y = 66.; }; }""") - class Inherit(cppyy.gbl.unique_ptr_data.Example): + class Inherit(cppjit.gbl.unique_ptr_data.Example): pass a = Inherit() - # Test whether this attribute was inherited - assert a.y == 66. + # Test whether this attribute was inherited + assert a.y == 66.0 def test18_unique_ptr_identity(self): """std::unique_ptr identity preservation""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace UniqueIdentity { struct A { A(int _a) : a(_a) {} @@ -524,7 +576,7 @@ def test18_unique_ptr_identity(self): std::unique_ptr fPtr; }; }""") - ns = cppyy.gbl.UniqueIdentity + ns = cppjit.gbl.UniqueIdentity x = ns.create() assert x.a == 37 @@ -537,13 +589,17 @@ def test18_unique_ptr_identity(self): p2 = c.pget() assert p1 is p2 - @mark.xfail(run=False, condition=IS_LINUX_ARM and IS_VALGRIND, reason="Valgrind issues on ARM") + @mark.xfail( + run=False, + condition=IS_LINUX_ARM and IS_VALGRIND, + reason="Valgrind issues on ARM", + ) def test19_smartptr_from_callback(self): """Return a smart pointer from a callback""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace SmartPtrCallback { struct Dummy { virtual ~Dummy() = default; @@ -559,20 +615,20 @@ def test19_smartptr_from_callback(self): return func(); }}""") - std = cppyy.gbl.std - ns = cppyy.gbl.SmartPtrCallback + std = cppjit.gbl.std + ns = cppjit.gbl.SmartPtrCallback def pyfunc() -> std.shared_ptr[ns.Dummy]: - return ns.dummy_create() + return ns.dummy_create() assert ns.call_creator(pyfunc) def test21_smart_ptr_downcast(self): """Object returned through a smart pointer is auto-downcast""" - import cppyy + import cppjit - gbl = cppyy.gbl + gbl = cppjit.gbl # unique_ptr holding a Derived comes back as Derived, with the # derived-only method callable, just like a raw pointer return @@ -580,7 +636,7 @@ def test21_smart_ptr_downcast(self): obj = cf() assert type(obj) == gbl.PubDerivedTestSmartPtr assert obj.only_in_derived() == 27 - assert obj.__smartptr__() # smart-pointer semantics preserved + assert obj.__smartptr__() # smart-pointer semantics preserved # an object that really is of the declared type stays that type obj = gbl.create_unique_ptr_instance() @@ -597,8 +653,16 @@ def test21_smart_ptr_downcast(self): # still embeds a smart pointer to the *base* type, which does not convert # to a smart pointer to the derived type (no implicit down-conversion of # smart pointers in C++), so passing it to such a sink must be rejected - raises(TypeError, gbl.pass_unique_ptr_to_derived, gbl.create_unique_ptr_to_derived()) - raises(TypeError, gbl.pass_shared_ptr_to_derived, gbl.create_shared_ptr_to_derived()) + raises( + TypeError, + gbl.pass_unique_ptr_to_derived, + gbl.create_unique_ptr_to_derived(), + ) + raises( + TypeError, + gbl.pass_shared_ptr_to_derived, + gbl.create_shared_ptr_to_derived(), + ) # passing it where the matching base smart pointer is expected still works assert gbl.pass_shared_ptr(gbl.create_shared_ptr_to_derived()) == 17 @@ -607,9 +671,27 @@ def test21_smart_ptr_downcast(self): # derived class should resolve to the downcasted type overload, # no matter if the Python proxy is a regular proxy or wraps a smart pointer # (should hold for pointer, reference, and value types) - assert gbl.pass_ptr_overloaded(gbl.PubDerivedTestSmartPtr()) == "PubDerivedTestSmartPtr" - assert gbl.pass_ptr_overloaded(gbl.create_unique_ptr_to_derived()) == "PubDerivedTestSmartPtr" - assert gbl.pass_ref_overloaded(gbl.PubDerivedTestSmartPtr()) == "PubDerivedTestSmartPtr" - assert gbl.pass_ref_overloaded(gbl.create_unique_ptr_to_derived()) == "PubDerivedTestSmartPtr" - assert gbl.pass_val_overloaded(gbl.PubDerivedTestSmartPtr()) == "PubDerivedTestSmartPtr" - assert gbl.pass_val_overloaded(gbl.create_unique_ptr_to_derived()) == "PubDerivedTestSmartPtr" + assert ( + gbl.pass_ptr_overloaded(gbl.PubDerivedTestSmartPtr()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_ptr_overloaded(gbl.create_unique_ptr_to_derived()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_ref_overloaded(gbl.PubDerivedTestSmartPtr()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_ref_overloaded(gbl.create_unique_ptr_to_derived()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_val_overloaded(gbl.PubDerivedTestSmartPtr()) + == "PubDerivedTestSmartPtr" + ) + assert ( + gbl.pass_val_overloaded(gbl.create_unique_ptr_to_derived()) + == "PubDerivedTestSmartPtr" + ) diff --git a/test/test_cpp23features.py b/test/test_cpp23features.py index 9e93dc5..0d2925a 100644 --- a/test/test_cpp23features.py +++ b/test/test_cpp23features.py @@ -1,44 +1,48 @@ -import os, sys, subprocess -from pytest import mark +import os +import subprocess +import sys +from pytest import mark # C++23 isn't the stack's default and the interpreter is a process-wide -# singleton pinned at the first `import cppyy`. So TestCPP23Driver re-runs the +# singleton pinned at the first `import cppjit`. So TestCPP23Driver re-runs the # whole class once in a child process that selects C++23 before import # (EXTRA_CLING_ARGS for cling, CPPINTEROP_EXTRA_INTERPRETER_ARGS for clang-repl); # all tests then share that one interpreter. To iterate locally, run the tests # directly in "child mode": # -# CPPYY_TEST_CPP23_CHILD=1 EXTRA_CLING_ARGS=-std=c++23 \ +# CPPJIT_TEST_CPP23_CHILD=1 EXTRA_CLING_ARGS=-std=c++23 \ # CPPINTEROP_EXTRA_INTERPRETER_ARGS=-std=c++23 \ # pytest -v test_cpp23features.py -_CPP23_CHILD = "CPPYY_TEST_CPP23_CHILD" +_CPP23_CHILD = "CPPJIT_TEST_CPP23_CHILD" _IN_CHILD = bool(os.environ.get(_CPP23_CHILD)) -@mark.skipif(not _IN_CHILD, - reason="C++23 tests run in the child interpreter launched by " - "TestCPP23Driver") +@mark.skipif( + not _IN_CHILD, + reason="C++23 tests run in the child interpreter launched by TestCPP23Driver", +) class TestCPP23FEATURES: - """C++23 features driven via cppyy.cppdef through the JIT (clang-repl/cling). + """C++23 features driven via cppjit.cppdef through the JIT (clang-repl/cling). The class owns one C++23 interpreter (booted in setup_class); tests share - it via ``self.cppyy``. + it via ``self.cppjit``. """ @classmethod def setup_class(cls): # In the child process C++23 is already selected, so this one-time boot # is the C++23 interpreter every test shares. - import cppyy - cls.cppyy = cppyy + import cppjit + + cls.cppjit = cppjit def test01_deducing_this_basic(self): """Explicit object parameter on a member function (P0847R7)""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct Widget { int value = 42; @@ -47,15 +51,15 @@ def test01_deducing_this_basic(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.Widget() + w = cppjit.gbl.Cpp23DeducingThis.Widget() assert w.value == 42 assert w.get() == 42 def test02_deducing_this_const(self): """const explicit object parameter (this const Widget& self)""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct CWidget { int value = 7; @@ -64,14 +68,14 @@ def test02_deducing_this_const(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.CWidget() + w = cppjit.gbl.Cpp23DeducingThis.CWidget() assert w.read() == 7 def test03_deducing_this_by_value(self): """by-value explicit object parameter (this Widget self)""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct VWidget { int value = 5; @@ -80,15 +84,15 @@ def test03_deducing_this_by_value(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.VWidget() + w = cppjit.gbl.Cpp23DeducingThis.VWidget() w.value = 11 assert w.snapshot() == 11 def test04_deducing_this_with_extra_args(self): """explicit object parameter alongside regular arguments""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct AWidget { int base = 100; @@ -97,14 +101,14 @@ def test04_deducing_this_with_extra_args(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.AWidget() + w = cppjit.gbl.Cpp23DeducingThis.AWidget() assert w.add(20, 3) == 123 def test05_deducing_this_rvalue_ref(self): """rvalue-ref-qualified explicit object parameter (this Widget&&)""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct RWidget { int value = 13; @@ -113,14 +117,14 @@ def test05_deducing_this_rvalue_ref(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.RWidget() + w = cppjit.gbl.Cpp23DeducingThis.RWidget() assert w.consume() == 13 def test05b_traditional_rvalue_ref_qualifier(self): """traditional rvalue-ref-qualified member (int f() &&), no deducing this""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct QWidget { int value = 17; @@ -129,14 +133,14 @@ def test05b_traditional_rvalue_ref_qualifier(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.QWidget() + w = cppjit.gbl.Cpp23DeducingThis.QWidget() assert w.consume() == 17 def test06_deducing_this_chaining(self): """builder-style chaining: explicit object parameter returns self""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct BWidget { int value = 0; @@ -145,16 +149,16 @@ def test06_deducing_this_chaining(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.BWidget() + w = cppjit.gbl.Cpp23DeducingThis.BWidget() r = w.set(5).set(8) assert r.value == 8 - assert w.value == 8 # same object returned by reference + assert w.value == 8 # same object returned by reference def test07_deducing_this_default_arg(self): """explicit object parameter with a defaulted regular argument""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct DWidget { int base = 3; @@ -163,15 +167,15 @@ def test07_deducing_this_default_arg(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.DWidget() - assert w.scale() == 12 # default factor=4 + w = cppjit.gbl.Cpp23DeducingThis.DWidget() + assert w.scale() == 12 # default factor=4 assert w.scale(10) == 30 def test08_deducing_this_call_operator(self): """call operator with an explicit object parameter""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct Adder { int base = 100; @@ -180,14 +184,14 @@ def test08_deducing_this_call_operator(self): } """) - a = cppyy.gbl.Cpp23DeducingThis.Adder() + a = cppjit.gbl.Cpp23DeducingThis.Adder() assert a(23) == 123 def test09_deducing_this_mixed_overload(self): """overload set mixing an explicit-object and a normal member function""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct MWidget { int value = 50; @@ -197,15 +201,15 @@ def test09_deducing_this_mixed_overload(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.MWidget() - assert w.get() == 50 # explicit-object overload - assert w.get(7) == 57 # normal overload + w = cppjit.gbl.Cpp23DeducingThis.MWidget() + assert w.get() == 50 # explicit-object overload + assert w.get(7) == 57 # normal overload def test10_deducing_this_templated(self): """templated explicit object parameter (the CRTP-replacement idiom)""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct TWidget { int value = 9; @@ -215,14 +219,14 @@ def test10_deducing_this_templated(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.TWidget() + w = cppjit.gbl.Cpp23DeducingThis.TWidget() assert w.via() == 9 def test11_deducing_this_templated_with_args(self): """templated explicit object parameter alongside a regular argument""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct TAWidget { int base = 40; @@ -232,14 +236,14 @@ def test11_deducing_this_templated_with_args(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.TAWidget() + w = cppjit.gbl.Cpp23DeducingThis.TAWidget() assert w.plus(2) == 42 def test12_deducing_this_templated_returns_self_type(self): """deduced Self drives the return: returns the deduced object's field""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct TRWidget { int value = 99; @@ -249,14 +253,14 @@ def test12_deducing_this_templated_returns_self_type(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.TRWidget() + w = cppjit.gbl.Cpp23DeducingThis.TRWidget() assert w.identity() == 99 def test13_deducing_this_abbreviated_auto(self): """abbreviated `this auto&&` form (the canonical CRTP-replacement idiom)""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct AAWidget { int value = 23; @@ -265,14 +269,14 @@ def test13_deducing_this_abbreviated_auto(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.AAWidget() + w = cppjit.gbl.Cpp23DeducingThis.AAWidget() assert w.get() == 23 def test14_deducing_this_by_value_copy_semantics(self): """by-value `this W self` operates on an independent copy""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct CopyWidget { int value = 1; @@ -281,15 +285,15 @@ def test14_deducing_this_by_value_copy_semantics(self): } """) - w = cppyy.gbl.Cpp23DeducingThis.CopyWidget() - assert w.bump() == 101 # the copy is mutated - assert w.value == 1 # ... the original is untouched + w = cppjit.gbl.Cpp23DeducingThis.CopyWidget() + assert w.bump() == 101 # the copy is mutated + assert w.value == 1 # ... the original is untouched def test15_deducing_this_inheritance(self): """base-class explicit object method invoked on a derived object""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct Base { int value = 8; @@ -299,7 +303,7 @@ def test15_deducing_this_inheritance(self): } """) - d = cppyy.gbl.Cpp23DeducingThis.Derived() + d = cppjit.gbl.Cpp23DeducingThis.Derived() assert d.get() == 8 # ------------------------------------------------------------------ # @@ -309,9 +313,9 @@ def test15_deducing_this_inheritance(self): def test16_blog_deduplication(self): """Blog use-case 1: code de-duplication of cv/ref accessors.""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" #include namespace Cpp23DeducingThis { struct Optional { @@ -328,10 +332,10 @@ def test16_blog_deduplication(self): } """) - o = cppyy.gbl.Cpp23DeducingThis.Optional() - assert o.read() == 5 # forwarding read - o.value()[0] = 17 # write through the forwarded reference - assert o.read() == 17 # ... mutation is visible on the object + o = cppjit.gbl.Cpp23DeducingThis.Optional() + assert o.read() == 5 # forwarding read + o.value()[0] = 17 # write through the forwarded reference + assert o.read() == 17 # ... mutation is visible on the object def test17_blog_crtp_postfix_increment(self): """Blog use-case 2: CRTP postfix increment without templating the base. @@ -339,9 +343,9 @@ def test17_blog_crtp_postfix_increment(self): The using-declaration re-exposes the inherited postfix (standard name hiding); driven from C++ as Python has no postfix-increment syntax. """ - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct add_postfix_increment { template @@ -357,13 +361,13 @@ def test17_blog_crtp_postfix_increment(self): } """) - assert cppyy.gbl.Cpp23DeducingThis.drive_postfix() == 1 # old.v=0, c.v=1 + assert cppjit.gbl.Cpp23DeducingThis.drive_postfix() == 1 # old.v=0, c.v=1 def test18_blog_recursive_lambda(self): """Blog use-case 4: recursive lambda via the explicit object parameter.""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { int fib(int n) { auto f = [](this auto const& self, int n) -> int { @@ -374,7 +378,7 @@ def test18_blog_recursive_lambda(self): } """) - assert cppyy.gbl.Cpp23DeducingThis.fib(10) == 55 + assert cppjit.gbl.Cpp23DeducingThis.fib(10) == 55 def test19_blog_lambda_forwarding(self): """Blog use-case 3: closure with an explicit object parameter. @@ -382,9 +386,9 @@ def test19_blog_lambda_forwarding(self): The blog uses std::forward_like (not in the host libstdc++); this exercises the explicit-object closure mechanism itself. """ - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" #include namespace Cpp23DeducingThis { struct Scheduler { int submitted = 0; int submit(int m) { submitted = m; return m; } }; @@ -399,13 +403,13 @@ def test19_blog_lambda_forwarding(self): } """) - assert cppyy.gbl.Cpp23DeducingThis.run_callback() == 42 + assert cppjit.gbl.Cpp23DeducingThis.run_callback() == 42 def test20_blog_pass_by_value(self): """Blog use-case 5: pass the object by value for better codegen on small types.""" - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { struct just_a_little_guy { int how_smol = 21; @@ -414,17 +418,17 @@ def test20_blog_pass_by_value(self): } """) - assert cppyy.gbl.Cpp23DeducingThis.just_a_little_guy().uwu() == 42 + assert cppjit.gbl.Cpp23DeducingThis.just_a_little_guy().uwu() == 42 def test21_blog_sfinae_friendly_transform(self): """Blog use-case 6: SFINAE-friendly optional::transform. - Driven from C++: cppyy can't resolve a two-template-parameter + Driven from C++: cppjit can't resolve a two-template-parameter explicit-object method against a Python callable. """ - cppyy = self.cppyy + cppjit = self.cppjit - assert cppyy.cppdef(""" + assert cppjit.cppdef(""" namespace Cpp23DeducingThis { template struct Optional6 { @@ -437,7 +441,7 @@ def test21_blog_sfinae_friendly_transform(self): } """) - assert cppyy.gbl.Cpp23DeducingThis.drive_transform() == 42 + assert cppjit.gbl.Cpp23DeducingThis.drive_transform() == 42 @mark.skipif(_IN_CHILD, reason="launcher runs only in the parent process") @@ -447,13 +451,24 @@ class TestCPP23Driver: def test_run_under_cpp23(self): env = dict(os.environ) env[_CPP23_CHILD] = "1" - env["EXTRA_CLING_ARGS"] = "-std=c++23" # cling + env["EXTRA_CLING_ARGS"] = "-std=c++23" # cling env["CPPINTEROP_EXTRA_INTERPRETER_ARGS"] = "-std=c++23" # clang-repl proc = subprocess.run( - [sys.executable, "-m", "pytest", "-q", "-p", "no:cacheprovider", - os.path.basename(__file__)], + [ + sys.executable, + "-m", + "pytest", + "-q", + "-p", + "no:cacheprovider", + os.path.basename(__file__), + ], cwd=os.path.dirname(os.path.abspath(__file__)), - env=env, capture_output=True, text=True) + env=env, + capture_output=True, + text=True, + ) assert proc.returncode == 0, ( "C++23 child run failed (rc=%d)\n--- stdout ---\n%s\n--- stderr ---\n%s" - % (proc.returncode, proc.stdout, proc.stderr)) + % (proc.returncode, proc.stdout, proc.stderr) + ) diff --git a/test/test_crossinheritance.py b/test/test_crossinheritance.py index 748ce1a..ab0ae9e 100644 --- a/test/test_crossinheritance.py +++ b/test/test_crossinheritance.py @@ -1,26 +1,42 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, pylong, IS_MAC_ARM, IS_MAC, IS_CLANG_REPL, IS_CLANG_DEBUG, IS_LINUX_ARM, IS_LINUX, IS_CLING, IS_VALGRIND - +import os + +import py +from pytest import mark, raises, skip +from support import ( + IS_CLANG_DEBUG, + IS_CLING, + IS_LINUX_ARM, + IS_MAC, + IS_MAC_ARM, + IS_VALGRIND, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("crossinheritanceDict")) +test_dct = str(currpath.join("cpp/crossinheritanceDict")) + def setup_module(mod): setup_make("crossinheritance") + class TestCROSSINHERITANCE: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.example01 = cppyy.load_reflection_info(cls.test_dct) + import cppjit - @mark.xfail(run=not (IS_CLANG_DEBUG or IS_CLING), reason="Crashes with ClangRepl with 'toString not implemented' and on Cling") + cls.example01 = cppjit.load_reflection_info(cls.test_dct) + + @mark.xfail( + run=not (IS_CLANG_DEBUG or IS_CLING), + reason="Crashes with ClangRepl with 'toString not implemented' and on Cling", + ) def test01_override_function(self): """Test ability to override a simple function""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1().get_value() == 42 @@ -30,18 +46,19 @@ def get_value(self): assert Derived().get_value() == 13 - assert 'Derived' in str(Derived()) - assert 'Derived' in repr(Derived()) + assert "Derived" in str(Derived()) + assert "Derived" in repr(Derived()) - assert Base1.call_get_value(Base1()) == 42 + assert Base1.call_get_value(Base1()) == 42 assert Base1.call_get_value(Derived()) == 13 @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test02_constructor(self): """Test constructor usage for derived classes""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1(27).get_value() == 27 @@ -51,12 +68,12 @@ def __init__(self, pyval): self.m_pyint = pyval def get_value(self): - return self.m_pyint+self.m_int + return self.m_pyint + self.m_int d = Derived1(2) - assert d.m_int == 42 - assert d.m_pyint == 2 - assert d.get_value() == 44 + assert d.m_int == 42 + assert d.m_pyint == 2 + assert d.get_value() == 44 assert Base1.call_get_value(d) == 44 class Derived2(Base1): @@ -65,20 +82,21 @@ def __init__(self, pyval, cppval): self.m_pyint = pyval def get_value(self): - return self.m_pyint+self.m_int + return self.m_pyint + self.m_int d = Derived2(2, 27) - assert d.m_int == 27 - assert d.m_pyint == 2 - assert d.get_value() == 29 + assert d.m_int == 27 + assert d.m_pyint == 2 + assert d.get_value() == 29 assert Base1.call_get_value(d) == 29 @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test03_override_function_abstract_base(self): """Test ability to override a simple function with an abstract base""" - import cppyy - CX = cppyy.gbl.CrossInheritance + import cppjit + + CX = cppjit.gbl.CrossInheritance class C1PyBase2(CX.IBase2): def __init__(self): @@ -106,10 +124,10 @@ def get_value(self): return 13 try: - c2 = C2PyBase2() # direct call to init can not work + c2 = C2PyBase2() # direct call to init can not work assert not "should have raised TypeError" except TypeError as e: - assert "super" in str(e) # clarifying message + assert "super" in str(e) # clarifying message assert "abstract" in str(e) c1, c3, c4 = C1PyBase2(), C3PyBase2(), C4PyBase2() @@ -130,12 +148,17 @@ def get_value(self): assert c4.m_int == 88 assert CX.IBase2.call_get_value(c4) == 77 - @mark.xfail(run=False, condition=IS_MAC_ARM, reason="Crashes with exception not being caught on Apple Silicon") + @mark.xfail( + run=False, + condition=IS_MAC_ARM, + reason="Crashes with exception not being caught on Apple Silicon", + ) def test04_arguments(self): """Test ability to override functions that take arguments""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1(27).sum_value(-7) == 20 @@ -144,36 +167,41 @@ def sum_value(self, val): return val + 13 d1 = Derived1() - assert d1.m_int == 42 - assert d1.sum_value(-7) == 6 + assert d1.m_int == 42 + assert d1.sum_value(-7) == 6 assert Base1.call_sum_value(d1, -7) == 6 b1 = Base1() - assert Base1.sum_pass_value(b1) == 6+2*b1.m_int + assert Base1.sum_pass_value(b1) == 6 + 2 * b1.m_int class Derived2(Base1): def pass_value1(self, a): - return a*2 + return a * 2 + def pass_value2(self, a): - return a.value*2 + return a.value * 2 + def pass_value3(self, a): - return a.value*2 + return a.value * 2 + def pass_value4(self, b): - return b.m_int*2 + return b.m_int * 2 + def pass_value5(self, b): - return b.m_int*2 + return b.m_int * 2 d2 = Derived2() - assert Base1.sum_pass_value(d2) == 12+4*d2.m_int + assert Base1.sum_pass_value(d2) == 12 + 4 * d2.m_int @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test05_override_overloads(self): """Test ability to override overloaded functions""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 - assert Base1(27).sum_all(-7) == 20 + assert Base1(27).sum_all(-7) == 20 assert Base1(27).sum_all(-3, -4) == 20 class Derived(Base1): @@ -181,18 +209,19 @@ def sum_all(self, *args): return sum(args) + 13 d = Derived() - assert d.m_int == 42 - assert d.sum_all(-7) == 6 + assert d.m_int == 42 + assert d.sum_all(-7) == 6 assert Base1.call_sum_all(d, -7) == 6 - assert d.sum_all(-7, -5) == 1 + assert d.sum_all(-7, -5) == 1 assert Base1.call_sum_all(d, -7, -5) == 1 @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test06_const_methods(self): """Declared const methods should keep that qualifier""" - import cppyy - CX = cppyy.gbl.CrossInheritance + import cppjit + + CX = cppjit.gbl.CrossInheritance class C1PyBase4(CX.IBase4): def __init__(self): @@ -210,13 +239,13 @@ def __init__(self): assert CX.IBase4.call_get_value(c1) == 17 assert CX.IBase4.call_get_value(c2) == 27 - @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Fails with ModuleNotFound error") + @mark.xfail( + run=False, condition=IS_LINUX_ARM, reason="Fails with ModuleNotFound error" + ) def test07_templated_base(self): """Derive from a base class that is instantiated from a template""" - import cppyy - - from cppyy.gbl.CrossInheritance import TBase1, TDerived1, TBase1_I + from cppjit.gbl.CrossInheritance import TBase1, TBase1_I, TDerived1 class TPyDerived1(TBase1_I): def __init__(self): @@ -239,12 +268,14 @@ def get_value(self): def test08_error_handling(self): """Python errors should propagate through wrapper""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1(27).sum_value(-7) == 20 errmsg = "I do not like the given value" + class Derived(Base1): def sum_value(self, val): raise ValueError(errmsg) @@ -261,36 +292,42 @@ def sum_value(self, val): except ValueError as e: assert errmsg in str(e) - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CrossInheritance { std::string call_base1(Base1* b) { try { b->sum_value(-7); - } catch (CPyCppyy::PyException& e) { + } catch (cppjit::cpyrt::PyException& e) { e.clear(); return e.what(); } return ""; } }""") - res = cppyy.gbl.CrossInheritance.call_base1(d) + res = cppjit.gbl.CrossInheritance.call_base1(d) - assert 'ValueError' in res + assert "ValueError" in res assert os.path.basename(__file__) in res - @mark.xfail(run=not IS_MAC_ARM, condition=IS_MAC_ARM, reason="Crashes with exception not being caught on Apple Silicon") + @mark.xfail( + run=not IS_MAC_ARM, + condition=IS_MAC_ARM, + reason="Crashes with exception not being caught on Apple Silicon", + ) def test09_interface_checking(self): """Conversion errors should be Python exceptions""" - import cppyy - Base1 = cppyy.gbl.CrossInheritance.Base1 + import cppjit + + Base1 = cppjit.gbl.CrossInheritance.Base1 assert Base1(27).sum_value(-7) == 20 errmsg = "I do not like the given value" + class Derived(Base1): def get_value(self): - self.m_int*2 # missing return + self.m_int * 2 # missing return d = Derived(4) @@ -299,9 +336,11 @@ def get_value(self): def test10_python_in_templates(self): """Usage of Python derived objects in std::vector""" - import cppyy, gc + import gc - CB = cppyy.gbl.CrossInheritance.CountableBase + import cppjit + + CB = cppjit.gbl.CrossInheritance.CountableBase class PyCountable(CB): def call(self): @@ -312,8 +351,8 @@ def call(self): start_count = CB.s_count - v = cppyy.gbl.std.vector[PyCountable]() - v.emplace_back(PyCountable()) # uses copy ctor + v = cppjit.gbl.std.vector[PyCountable]() + v.emplace_back(PyCountable()) # uses copy ctor assert len(v) == 1 gc.collect() assert CB.s_count == 1 + start_count @@ -324,7 +363,7 @@ def call(self): assert p.call() == 84 v.emplace_back(p) assert len(v) == 2 - assert v[1].call() == 84 # copy ctor copies python state + assert v[1].call() == 84 # copy ctor copies python state p.extra = 13 assert p.call() == 55 assert v[1].call() == 84 @@ -332,7 +371,7 @@ def call(self): gc.collect() assert CB.s_count == 2 + start_count - v.push_back(PyCountable()) # uses copy ctor + v.push_back(PyCountable()) # uses copy ctor assert len(v) == 3 gc.collect() assert CB.s_count == 3 + start_count @@ -345,9 +384,9 @@ def call(self): def test11_python_in_make_shared(self): """Usage of Python derived objects with std::make_shared""" - import cppyy + import cppjit - cppyy.cppdef("""namespace MakeSharedTest { + cppjit.cppdef("""namespace MakeSharedTest { class Abstract { public: virtual ~Abstract() = 0; @@ -360,13 +399,14 @@ class Abstract { return ptr->some_imp(); } }""") - from cppyy.gbl import std, MakeSharedTest - from cppyy.gbl.MakeSharedTest import Abstract, call_shared + from cppjit.gbl import MakeSharedTest, std # noqa: F401 + from cppjit.gbl.MakeSharedTest import Abstract, call_shared class PyDerived(Abstract): def __init__(self, val): super(PyDerived, self).__init__() self.val = val + def some_imp(self): return self.val @@ -383,10 +423,12 @@ def some_imp(self): def test12a_counter_test(self): """Test countable base counting""" - import cppyy, gc + import gc + + import cppjit - std = cppyy.gbl.std - CB = cppyy.gbl.CrossInheritance.CountableBase + std = cppjit.gbl.std + CB = cppjit.gbl.CrossInheritance.CountableBase class PyCountable(CB): def call(self): @@ -397,7 +439,7 @@ def call(self): start_count = CB.s_count - # test counter + # test counter pyc = PyCountable() assert CB.s_count == 1 + start_count @@ -405,14 +447,16 @@ def call(self): gc.collect() assert CB.s_count == 0 + start_count - @mark.xfail(run=False, condition=IS_VALGRIND, reason = "Valgrind issue") + @mark.xfail(run=False, condition=IS_VALGRIND, reason="Valgrind issue") def test12_python_shared_ptr_memory(self): """Usage of Python derived objects with std::shared_ptr""" - import cppyy, gc + import gc - std = cppyy.gbl.std - CB = cppyy.gbl.CrossInheritance.CountableBase + import cppjit + + std = cppjit.gbl.std + CB = cppjit.gbl.CrossInheritance.CountableBase class PyCountable(CB): def call(self): @@ -436,9 +480,11 @@ def call(self): def test13_virtual_dtors_and_del(self): """Usage of virtual destructors and Python-side del.""" - import cppyy, warnings + import warnings + + import cppjit - cppyy.cppdef("""namespace VirtualDtor { + cppjit.cppdef("""namespace VirtualDtor { class MyClass1 {}; // no virtual dtor ... class MyClass2 { @@ -454,11 +500,11 @@ class MyClass4 { virtual ~MyClass4() {} }; }""") - VD = cppyy.gbl.VirtualDtor + VD = cppjit.gbl.VirtualDtor - # rethought this: just issue a warning if there is no virtual destructor - # as the C++ side now carries the type of the dispatcher, not the type of - # the direct base class + # rethought this: just issue a warning if there is no virtual destructor + # as the C++ side now carries the type of the dispatcher, not the type of + # the direct base class with warnings.catch_warnings(record=True) as w: # ensure warnings are not turned into errors, even if we run python -W error # The reason why we don't turn warnings into errors instead and just @@ -467,21 +513,22 @@ class MyClass4 { warnings.simplefilter("default") class MyPyDerived1(VD.MyClass1): - pass # TODO: verify warning is given + pass # TODO: verify warning is given + assert len(w) == 1 assert issubclass(w[-1].category, RuntimeWarning) assert "has no virtual destructor" in str(w[-1].message) d = MyPyDerived1() - del d # used to crash + del d # used to crash class MyPyDerived2(VD.MyClass2): pass d = MyPyDerived2() - del d # used to crash + del d # used to crash - # check a few more derivations that should not fail + # check a few more derivations that should not fail class MyPyDerived3(VD.MyClass3): pass @@ -491,17 +538,17 @@ class MyPyDerived4(VD.MyClass4[int]): def test14_protected_access(self): """Derived classes should have access to protected members""" - import cppyy + import cppjit - ns = cppyy.gbl.AccessProtected + ns = cppjit.gbl.AccessProtected - assert not 'my_data' in ns.MyBase.__dict__ - assert not hasattr(ns.MyBase(), 'my_data') + assert "my_data" not in ns.MyBase.__dict__ + assert not hasattr(ns.MyBase(), "my_data") class MyPyDerived(ns.MyBase): pass - assert 'my_data' in MyPyDerived.__dict__ + assert "my_data" in MyPyDerived.__dict__ assert MyPyDerived().my_data == 101 class MyPyDerived(ns.MyBase): @@ -512,19 +559,19 @@ def __init__(self): self.my_data = 42 m = MyPyDerived() - assert m.py_data == 13 - assert m.my_data == 42 - assert m.get_data() == 42 + assert m.py_data == 13 + assert m.my_data == 42 + assert m.get_data() == 42 assert m.get_data_v() == 42 - + @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test15_object_returns(self): """Return of C++ objects from overridden functions""" - import cppyy + import cppjit - # Part 1: return of a new C++ object - cppyy.cppdef("""namespace object_returns { + # Part 1: return of a new C++ object + cppjit.cppdef("""namespace object_returns { class Base { public: virtual Base* foo() { return new Base(); } @@ -540,7 +587,7 @@ class CppDerived : public Base { Base* call_foo(Base& obj) { return obj.foo(); } }""") - ns = cppyy.gbl.object_returns + ns = cppjit.gbl.object_returns class PyDerived1(ns.Base): def foo(self): @@ -558,7 +605,7 @@ def foo(self): obj = PyDerived2() assert not not ns.call_foo(obj) - # Part 2: return of a new Python derived object + # Part 2: return of a new Python derived object class PyDerived3(ns.Base): def foo(self): return PyDerived3() @@ -589,9 +636,9 @@ def whoami(self): def test16_cctor_access_controlled(self): """Python derived class of C++ class with access controlled cctor""" - import cppyy + import cppjit - cppyy.cppdef("""namespace cctor_access_controlled { + cppjit.cppdef("""namespace cctor_access_controlled { class CommonBase { public: virtual ~CommonBase() {} @@ -617,9 +664,10 @@ class Base2 : public CommonBase { std::string callit(CommonBase& obj) { return obj.whoami(); } }""") - ns = cppyy.gbl.cctor_access_controlled + ns = cppjit.gbl.cctor_access_controlled for base in (ns.Base1, ns.Base2): + class PyDerived(base): def whoami(self): return "PyDerived" @@ -631,9 +679,9 @@ def whoami(self): def test17_deep_hierarchy(self): """Test a deep Python hierarchy with pure virtual functions""" - import cppyy + import cppjit - cppyy.cppdef("""namespace deep_hierarchy { + cppjit.cppdef("""namespace deep_hierarchy { class Base { public: virtual ~Base() {} @@ -642,7 +690,7 @@ class Base { std::string callit(Base& obj) { return obj.whoami(); } }""") - ns = cppyy.gbl.deep_hierarchy + ns = cppjit.gbl.deep_hierarchy class PyDerived1(ns.Base): def whoami(self): @@ -655,7 +703,7 @@ class PyDerived2(PyDerived1): pass obj = PyDerived2() - assert obj.whoami() == "PyDerived1" + assert obj.whoami() == "PyDerived1" assert ns.callit(obj) == "PyDerived1" class PyDerived3(PyDerived1): @@ -663,7 +711,7 @@ def whoami(self): return "PyDerived3" obj = PyDerived3() - assert obj.whoami() == "PyDerived3" + assert obj.whoami() == "PyDerived3" assert ns.callit(obj) == "PyDerived3" class PyDerived4(PyDerived2): @@ -671,17 +719,16 @@ def whoami(self): return "PyDerived4" obj = PyDerived4() - assert obj.whoami() == "PyDerived4" + assert obj.whoami() == "PyDerived4" assert ns.callit(obj) == "PyDerived4" @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test18_abstract_hierarchy(self): """Hierarchy with abstract classes""" + import cppjit - import cppyy - - cppyy.cppdef("""namespace abstract_classes { + cppjit.cppdef("""namespace abstract_classes { class Base { public: virtual ~Base() {} @@ -692,7 +739,7 @@ class Base { std::string whois(Base& obj) { return obj.whoami(); } std::string saywot(Base& obj) { return obj.message(); } }""") - ns = cppyy.gbl.abstract_classes + ns = cppjit.gbl.abstract_classes class PyDerived1(ns.Base): def __init__(self): @@ -711,18 +758,18 @@ def message(self): return self._message obj = PyDerived2() - assert obj.whoami() == "PyDerived1" + assert obj.whoami() == "PyDerived1" assert ns.whois(obj) == "PyDerived1" - assert obj.message() == "Hello, World!" + assert obj.message() == "Hello, World!" assert ns.saywot(obj) == "Hello, World!" def test19_cpp_side_multiple_inheritance(self): """Hierarchy with multiple inheritance on the C++ side""" - import cppyy + import cppjit - cppyy.cppdef(""" namespace cpp_side_multiple_inheritance { + cppjit.cppdef(""" namespace cpp_side_multiple_inheritance { struct Result { Result() : result(1337) {} Result(int r) : result(r) {} @@ -746,7 +793,7 @@ class Base : public Base1, public Base2 { Result abstract2() override { return Result(999); } }; } """) - ns = cppyy.gbl.cpp_side_multiple_inheritance + ns = cppjit.gbl.cpp_side_multiple_inheritance class Derived(ns.Base): def abstract1(self): @@ -756,9 +803,9 @@ def abstract1(self): def test20_basic_multiple_inheritance(self): """Basic multiple inheritance""" - import cppyy + import cppjit - cppyy.cppdef("""namespace basic_multiple_inheritance { + cppjit.cppdef("""namespace basic_multiple_inheritance { class MyClass1 { public: MyClass1() : m_1(13) {} @@ -792,9 +839,9 @@ class MyClass3 { }; int callz(MyClass3& m) { return m.z(); } }""") - ns = cppyy.gbl.basic_multiple_inheritance + ns = cppjit.gbl.basic_multiple_inheritance - class MyPyDerived(cppyy.multi(ns.MyClass1, ns.MyClass2)): + class MyPyDerived(cppjit.multi(ns.MyClass1, ns.MyClass2)): def x(self): return 16 @@ -810,7 +857,7 @@ def y(self): assert a.m_1 == 13 assert a.m_2 == 42 - class MyPyDerived2(cppyy.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): + class MyPyDerived2(cppjit.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): def x(self): return 16 @@ -831,13 +878,17 @@ def z(self): assert a.m_2 == 42 assert a.m_3 == 67 - @mark.xfail(run=False, condition=IS_LINUX_ARM and IS_VALGRIND, reason="Crashes with Valgrind on Linux ARM") + @mark.xfail( + run=False, + condition=IS_LINUX_ARM and IS_VALGRIND, + reason="Crashes with Valgrind on Linux ARM", + ) def test21_multiple_inheritance_with_constructors(self): """Multiple inheritance with constructors""" - import cppyy + import cppjit - cppyy.cppdef("""namespace multiple_inheritance_with_constructors { + cppjit.cppdef("""namespace multiple_inheritance_with_constructors { class MyClass1 { public: MyClass1() : m_1(13) {} @@ -874,9 +925,9 @@ class MyClass3 { }; int callz(MyClass3& m) { return m.z(); } }""") - ns = cppyy.gbl.multiple_inheritance_with_constructors + ns = cppjit.gbl.multiple_inheritance_with_constructors - class MyPyDerived(cppyy.multi(ns.MyClass1, ns.MyClass2)): + class MyPyDerived(cppjit.multi(ns.MyClass1, ns.MyClass2)): def __init__(self, val1, val2): super(MyPyDerived, self).__init__((val1,), (val2,)) @@ -895,7 +946,7 @@ def y(self): assert a.m_1 == 27 assert a.m_2 == 88 - class MyPyDerived2(cppyy.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): + class MyPyDerived2(cppjit.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): def __init__(self, val1, val2, val3): super(MyPyDerived2, self).__init__((val1,), (val2,), (val3,)) @@ -915,17 +966,21 @@ def z(self): assert a.y() == ns.cally(a) assert a.z() == ns.callz(a) - assert a.m_1 == 27 - assert a.m_2 == 88 + assert a.m_1 == 27 + assert a.m_2 == 88 assert a.m_3 == -11 - @mark.xfail(run=False, condition=IS_LINUX_ARM and IS_VALGRIND, reason="Crashes with Valgrind on Linux ARM") + @mark.xfail( + run=False, + condition=IS_LINUX_ARM and IS_VALGRIND, + reason="Crashes with Valgrind on Linux ARM", + ) def test22_multiple_inheritance_with_defaults(self): """Multiple inheritance with defaults""" - import cppyy + import cppjit - cppyy.cppdef("""namespace multiple_inheritance_with_defaults { + cppjit.cppdef("""namespace multiple_inheritance_with_defaults { class MyClass1 { public: MyClass1(int i=13) : m_1(i) {} @@ -959,9 +1014,9 @@ class MyClass3 { }; int callz(MyClass3& m) { return m.z(); } }""") - ns = cppyy.gbl.multiple_inheritance_with_defaults + ns = cppjit.gbl.multiple_inheritance_with_defaults - class MyPyDerived(cppyy.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): + class MyPyDerived(cppjit.multi(ns.MyClass1, ns.MyClass2, ns.MyClass3)): def __init__(self, val1=None, val2=None, val3=None, nArgs=3): a1 = val1 is not None and (val1,) or () a2 = val2 is not None and (val2,) or () @@ -1014,9 +1069,9 @@ def verify(a, n1, n2, n3): def test23_const_byvalue_return(self): """Const by-value return in overridden method""" - import cppyy + import cppjit - cppyy.cppdef("""namespace const_byvalue_return { + cppjit.cppdef("""namespace const_byvalue_return { struct Const { Const() = default; explicit Const(const std::string& s) { m_value = s; } @@ -1030,23 +1085,23 @@ def test23_const_byvalue_return(self): const Const callit(Abstract* a) { return a->return_const(); } }""") - ns = cppyy.gbl.const_byvalue_return + ns = cppjit.gbl.const_byvalue_return class ReturnConstByValue(ns.Abstract): - def return_const(self): - return ns.Const("abcdef") + def return_const(self): + return ns.Const("abcdef") a = ReturnConstByValue() assert a.return_const().m_value == "abcdef" - assert ns.callit(a).m_value == "abcdef" + assert ns.callit(a).m_value == "abcdef" - @mark.xfail(condition = IS_MAC, reason = "Fails on OS X") + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test24_non_copyable(self): """Inheriting from a non-copyable base class""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace non_copyable { struct Copyable { Copyable() = default; @@ -1090,30 +1145,30 @@ class NoCopyNoMove { NoCopyNoMove* fActual; }; }""") - ns = cppyy.gbl.non_copyable + ns = cppjit.gbl.non_copyable Copyable = ns.Copyable - Movable = ns.Movable + Movable = ns.Movable NoCopyNoMove = ns.NoCopyNoMove class DerivedCopyable(Copyable): pass - # used to fail with compilation error + # used to fail with compilation error class DerivedMovable(Movable): pass - # used to fail with compilation error - class DerivedMulti(cppyy.multi(Movable, Copyable)): + # used to fail with compilation error + class DerivedMulti(cppjit.multi(Movable, Copyable)): pass - # used to fail with compilation error + # used to fail with compilation error class DerivedNoCopyNoMove(NoCopyNoMove): def __init__(self): super(DerivedNoCopyNoMove, self).__init__(self) - # TODO: chicken-and-egg situation here, 'this' from 'self' is - # nullptr until the constructor has been called, so it can't - # be passed as an argument to the same constructor + # TODO: chicken-and-egg situation here, 'this' from 'self' is + # nullptr until the constructor has been called, so it can't + # be passed as an argument to the same constructor self.fActual = self def callme_imp(self): @@ -1124,9 +1179,9 @@ def callme_imp(self): def test25_default_ctor_and_multiple_inheritance(self): """Regression test: default ctor did not get added""" - import cppyy + import cppjit - cppyy.cppdef("""namespace default_ctor_and_multiple { + cppjit.cppdef("""namespace default_ctor_and_multiple { struct Copyable { Copyable() = default; virtual ~Copyable() {} @@ -1149,12 +1204,12 @@ def test25_default_ctor_and_multiple_inheritance(self): virtual ~SomeClass() {} }; }""") - ns = cppyy.gbl.default_ctor_and_multiple - Copyable = ns.Copyable - Movable = ns.Movable + ns = cppjit.gbl.default_ctor_and_multiple + Copyable = ns.Copyable + Movable = ns.Movable SomeClass = ns.SomeClass - class DerivedMulti(cppyy.multi(Movable, Copyable, SomeClass)): + class DerivedMulti(cppjit.multi(Movable, Copyable, SomeClass)): def __init__(self): super(DerivedMulti, self).__init__() @@ -1164,9 +1219,9 @@ def __init__(self): def test26_no_default_ctor(self): """Make sure no default ctor is created if not viable""" - import cppyy + import cppjit - cppyy.cppdef("""namespace no_default_ctor { + cppjit.cppdef("""namespace no_default_ctor { struct NoDefCtor1 { NoDefCtor1(int) {} virtual ~NoDefCtor1() {} @@ -1189,9 +1244,10 @@ class Simple { }; }""") - ns = cppyy.gbl.no_default_ctor + ns = cppjit.gbl.no_default_ctor for kls in (ns.NoDefCtor1, ns.NoDefCtor2, ns.NoDefCtor3): + class PyDerived(kls): def __init__(self): super(PyDerived, self).__init__() @@ -1199,14 +1255,14 @@ def __init__(self): with raises(TypeError): PyDerived() - class PyDerived(cppyy.multi(kls, ns.Simple)): + class PyDerived(cppjit.multi(kls, ns.Simple)): def __init__(self): super(PyDerived, self).__init__() with raises(TypeError): PyDerived() - class PyDerived(cppyy.multi(ns.Simple, kls)): + class PyDerived(cppjit.multi(ns.Simple, kls)): def __init__(self): super(PyDerived, self).__init__() @@ -1216,9 +1272,9 @@ def __init__(self): def test27_interfaces(self): """Inherit from base with non-standard offset""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace NonStandardOffset { struct Calc1 { virtual int calc1() = 0; @@ -1243,26 +1299,26 @@ def test27_interfaces(self): int callback2(Calc2* c2) { return c2->calc2(); } }""") - ns = cppyy.gbl.NonStandardOffset + ns = cppjit.gbl.NonStandardOffset class MyPyDerived(ns.Derived): pass obj = MyPyDerived() - assert obj.calc1() == 1 + assert obj.calc1() == 1 assert ns.callback1(obj) == 1 - assert obj.calc2() == 2 + assert obj.calc2() == 2 assert ns.callback2(obj) == 2 @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test28_cross_deep(self): """Deep inheritance hierarchy""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CrossDeep { class A { @@ -1273,14 +1329,14 @@ class A { virtual int fun2() const { return fun1(); } }; }""") - A = cppyy.gbl.CrossDeep.A + A = cppjit.gbl.CrossDeep.A class B(A): - def __init__ (self, name = 'b'): + def __init__(self, name="b"): super(B, self).__init__(name) def fun1(self): - return 1 + return 1 class C(B): def fun1(self): @@ -1289,17 +1345,21 @@ def fun1(self): class D(B): pass - for inst, val1 in [(A('a'), 0), (B('b'), 1), (C('c'), -1), (D('d'), 1)]: + for inst, val1 in [(A("a"), 0), (B("b"), 1), (C("c"), -1), (D("d"), 1)]: assert inst.fun1() == val1 assert inst.fun2() == inst.fun1() - @mark.xfail(run=False, condition=IS_LINUX_ARM and IS_VALGRIND, reason="Crashes with Valgrind on Linux ARM") + @mark.xfail( + run=False, + condition=IS_LINUX_ARM and IS_VALGRIND, + reason="Crashes with Valgrind on Linux ARM", + ) def test29_cross_deep_multi(self): """Deep multi-inheritance hierarchy""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CrossMultiDeep { class A { @@ -1318,9 +1378,9 @@ class B { int calc_b(B* b) { return b->calc_b(); } }""") - ns = cppyy.gbl.CrossMultiDeep + ns = cppjit.gbl.CrossMultiDeep - class C(cppyy.multi(ns.A, ns.B)): + class C(cppjit.multi(ns.A, ns.B)): def calc_a(self): return 18 @@ -1338,7 +1398,7 @@ def calc_b(self): d = D() assert ns.calc_b(d) == 44 - class E(cppyy.multi(ns.A, D)): + class E(cppjit.multi(ns.A, D)): def calc_a(self): return 19 @@ -1353,7 +1413,7 @@ def calc_a(self): f = F() assert ns.calc_a(f) == 20 - class G(cppyy.multi(F, ns.B)): + class G(cppjit.multi(F, ns.B)): def calc_b(self): return 45 @@ -1365,27 +1425,26 @@ class H(object): def calc_a(self): return 66 - class I(cppyy.multi(ns.A, H)): + class I(cppjit.multi(ns.A, H)): def calc_a(self): return 77 i = I() assert ns.calc_a(i) == 77 - class J(cppyy.multi(H, ns.A)): + class J(cppjit.multi(H, ns.A)): def calc_a(self): return 88 j = J() assert ns.calc_a(j) == 88 - def test30_access_and_overload(self): """Inheritance with access and overload complications""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace AccessAndOverload { class Base { public: @@ -1404,18 +1463,20 @@ class Base { int call3(int i, int j) { return i+j; } }; }""") - ns = cppyy.gbl.AccessAndOverload + ns = cppjit.gbl.AccessAndOverload - # used to produce uncompilable code + # used to produce uncompilable code class PyDerived(ns.Base): pass def test31_object_rebind(self): """Usage of bind_object to cast with Python derived objects""" - import cppyy, gc + import gc - ns = cppyy.gbl.CrossInheritance + import cppjit + + ns = cppjit.gbl.CrossInheritance ns.build_component.__creates__ = True assert ns.Component.get_count() == 0 @@ -1428,9 +1489,9 @@ def test31_object_rebind(self): assert ns.Component.get_count() == 1 - # introduce the actual component type; would have been a header, - # but this simply has to match what is in crossinheritance.cxx - cppyy.cppdef("""namespace CrossInheritance { + # introduce the actual component type; would have been a header, + # but this simply has to match what is in crossinheritance.cxx + cppjit.cppdef("""namespace CrossInheritance { class ComponentWithValue : public Component { public: ComponentWithValue(int value) : m_value(value) {} @@ -1440,9 +1501,9 @@ class ComponentWithValue : public Component { int m_value; }; }""") - # rebind cmp1 to its actual C++ class - act_cmp1 = cppyy.bind_object(cmp1, ns.ComponentWithValue) - assert not cmp1.__python_owns__ # b/c transferred + # rebind cmp1 to its actual C++ class + act_cmp1 = cppjit.bind_object(cmp1, ns.ComponentWithValue) + assert not cmp1.__python_owns__ # b/c transferred assert act_cmp1.__python_owns__ assert act_cmp1.getValue() == 42 @@ -1450,13 +1511,14 @@ class ComponentWithValue : public Component { gc.collect() assert ns.Component.get_count() == 0 - # introduce a Python derived class + # introduce a Python derived class ns.ComponentWithValue.__init__.__creates__ = False + class PyComponentWithValue(ns.ComponentWithValue): def getValue(self): return self.m_value + 12 - # wipe the python-side connection + # wipe the python-side connection pycmp2a = PyComponentWithValue(27) assert not pycmp2a.__python_owns__ pycmp2a.__python_owns__ = True @@ -1470,24 +1532,27 @@ def getValue(self): gc.collect() assert ns.Component.get_count() == 0 - cmp2 = cppyy.bind_object(cppyy.addressof(PyComponentWithValue(13)), ns.Component) + cmp2 = cppjit.bind_object( + cppjit.addressof(PyComponentWithValue(13)), ns.Component + ) assert ns.Component.get_count() == 1 - cmp2 = ns.cycle_component(cmp2) # causes auto down-cast + cmp2 = ns.cycle_component(cmp2) # causes auto down-cast assert ns.Component.get_count() == 1 - #assert type(cmp2) != PyComponentWithValue + # assert type(cmp2) != PyComponentWithValue - # rebind cmp2 to the python type - act_cmp2 = cppyy.bind_object(cmp2, PyComponentWithValue) + # rebind cmp2 to the python type + act_cmp2 = cppjit.bind_object(cmp2, PyComponentWithValue) act_cmp2.__python_owns__ = True - assert act_cmp2.getValue() == 13+12 + assert act_cmp2.getValue() == 13 + 12 del cmp2, act_cmp2 gc.collect() assert ns.Component.get_count() == 0 - # introduce a Python derived class with initialization + # introduce a Python derived class with initialization ns.ComponentWithValue.__init__.__creates__ = True + class PyComponentWithInit(ns.ComponentWithValue): def __init__(self, cppvalue): super(PyComponentWithInit, self).__init__(cppvalue) @@ -1496,52 +1561,58 @@ def __init__(self, cppvalue): def getValue(self): return self.m_value + self.m_pyvalue - cmp3 = cppyy.bind_object(PyComponentWithInit(77), PyComponentWithInit) + cmp3 = cppjit.bind_object(PyComponentWithInit(77), PyComponentWithInit) assert type(cmp3) == PyComponentWithInit assert ns.Component.get_count() == 1 - assert cmp3.getValue() == 77+11 + assert cmp3.getValue() == 77 + 11 del cmp3 gc.collect() assert ns.Component.get_count() == 0 pyc = PyComponentWithInit(88) - cmp4 = cppyy.bind_object(cppyy.addressof(pyc), ns.Component) + cmp4 = cppjit.bind_object(cppjit.addressof(pyc), ns.Component) assert type(cmp4) == ns.Component assert ns.Component.get_count() == 1 - # rebind cmp4 to the python type - act_cmp4 = cppyy.bind_object(cmp4, PyComponentWithInit) - assert act_cmp4.getValue() == 88+11 + # rebind cmp4 to the python type + act_cmp4 = cppjit.bind_object(cmp4, PyComponentWithInit) + assert act_cmp4.getValue() == 88 + 11 del cmp4, act_cmp4, pyc gc.collect() assert ns.Component.get_count() == 0 ns.ComponentWithValue.__init__.__creates__ = False - cmp5 = cppyy.bind_object(cppyy.addressof(PyComponentWithInit(22)), ns.Component) + cmp5 = cppjit.bind_object( + cppjit.addressof(PyComponentWithInit(22)), ns.Component + ) cmp5.__python_owns__ = True assert type(cmp5) == ns.Component assert ns.Component.get_count() == 1 - # rebind cmp5 to the python type - act_cmp5 = cppyy.bind_object(cmp5, PyComponentWithInit) + # rebind cmp5 to the python type + act_cmp5 = cppjit.bind_object(cmp5, PyComponentWithInit) assert not cmp5.__python_owns__ assert act_cmp5.__python_owns__ - assert act_cmp5.getValue() == 22+11 + assert act_cmp5.getValue() == 22 + 11 del cmp5, act_cmp5 gc.collect() assert ns.Component.get_count() == 0 - @mark.xfail(run=False, condition=IS_LINUX_ARM and IS_VALGRIND, reason="Crashes with Valgrind on Linux ARM") + @mark.xfail( + run=False, + condition=IS_LINUX_ARM and IS_VALGRIND, + reason="Crashes with Valgrind on Linux ARM", + ) def test32_by_value_arguments(self): """Override base function taking by-value arguments""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CrossCallWithValue { struct Data { int value; @@ -1557,7 +1628,7 @@ def test32_by_value_arguments(self): virtual int extra_func(Data d) = 0; }; }""") - ns = cppyy.gbl.CrossCallWithValue + ns = cppjit.gbl.CrossCallWithValue class PyDerived(ns.CppBase): def extra_func(self, d): @@ -1571,9 +1642,9 @@ def extra_func(self, d): def test33_direct_base_methods(self): """Call base class methods directly""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DirectCalls { struct A { virtual ~A() {} @@ -1588,14 +1659,14 @@ def test33_direct_base_methods(self): } }; }""") - ns = cppyy.gbl.DirectCalls + ns = cppjit.gbl.DirectCalls a = ns.A() - assert a.func() == 1 + assert a.func() == 1 assert ns.A.func(a) == 1 b = ns.B() - assert b.func() == 2 + assert b.func() == 2 assert ns.B.func(b) == 2 assert ns.A.func(b) == 1 @@ -1614,9 +1685,9 @@ def func(self): def test34_no_ctors_in_base(self): """Base classes with no constructors""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace BlockedCtors { class Z { public: @@ -1634,7 +1705,7 @@ class Y: Z { Y(const Y&&) = delete; }; }""") - ns = cppyy.gbl.BlockedCtors + ns = cppjit.gbl.BlockedCtors with raises(TypeError): ns.X() @@ -1657,9 +1728,9 @@ def __init__(self): def test35_deletion(self): """C++ side deletion should propagate to the Python side""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DeletionTest1 { class Base { public: @@ -1670,10 +1741,11 @@ class Base { void delete_it(Base *p) { delete p; } }""") - ns = cppyy.gbl.DeletionTest1 + ns = cppjit.gbl.DeletionTest1 class Derived(ns.Base): was_deleted = False + def __del__(self): Derived.was_deleted = True @@ -1691,9 +1763,9 @@ def __del__(self): def test36_custom_destruct(self): """C++ side deletion calls __destruct__""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DeletionTest2 { class Base { public: @@ -1704,17 +1776,17 @@ class Base { void delete_it(Base *p) { delete p; } }""") - ns = cppyy.gbl.DeletionTest2 + ns = cppjit.gbl.DeletionTest2 class Derived(ns.Base): was_cpp_deleted = False - was_py_deleted = False + was_py_deleted = False def __destruct__(self): Derived.was_cpp_deleted = True def __del__(self): - Derived.was_py_deleted = True + Derived.was_py_deleted = True o1 = Derived() o1.do_work() @@ -1724,17 +1796,17 @@ def __del__(self): o1.do_work() assert Derived.was_cpp_deleted == True - assert Derived.was_py_deleted == False + assert Derived.was_py_deleted == False del o1 - assert Derived.was_py_deleted == True + assert Derived.was_py_deleted == True @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test37_deep_tree(self): """Find overridable methods deep in the tree""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DeepTree { class Base { @@ -1777,7 +1849,7 @@ class CppSub: public Sub { return res; } }""") - ns = cppyy.gbl.DeepTree + ns = cppjit.gbl.DeepTree cppsub = ns.CppSub() assert cppsub.f1() == "C++: CppSub::f1()" @@ -1805,9 +1877,9 @@ def f3(self): def test38_protected_data(self): """Multiple cross inheritance with protected data""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace multiple_inheritance_with_protected_data { class MyBaseClass { public: @@ -1826,7 +1898,7 @@ class MyBaseClass { int get_z() { return z; } }; }""") - ns = cppyy.gbl.multiple_inheritance_with_protected_data + ns = cppjit.gbl.multiple_inheritance_with_protected_data class MyDerivedClass(ns.MyBaseClass): def __init__(self, x, y, z): @@ -1842,9 +1914,9 @@ def __init__(self, x, y, z): def test39_returning_multi_keyword_types(self): """Supporting dispatcher for functions that return multi-keyword types like `unsigned int`""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" class MyUIntBaseClass { public: virtual ~MyUIntBaseClass() = default; @@ -1855,6 +1927,6 @@ class MyUIntBaseClass { # Compiling the dispatcher for the overridden member function used to # fail because of the `unsigned int` type, whose name has two keywords. - class MyUIntDerivedClass( cppyy.gbl.MyUIntBaseClass ): - def give_unsigned_int( self ): - return 1 \ No newline at end of file + class MyUIntDerivedClass(cppjit.gbl.MyUIntBaseClass): + def give_unsigned_int(self): + return 1 diff --git a/test/test_datatypes.py b/test/test_datatypes.py index cc668eb..eed3b3a 100644 --- a/test/test_datatypes.py +++ b/test/test_datatypes.py @@ -1,9 +1,12 @@ -import py, os, sys -from pytest import raises, skip, xfail, mark -from support import setup_make, pylong, pyunicode, IS_CLING, IS_CLANG_REPL, IS_MAC +import sys + +import py +from pytest import mark, raises, skip +from support import IS_CLANG_REPL, IS_MAC, pylong, pyunicode, setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("datatypesDict")) +test_dct = str(currpath.join("cpp/datatypesDict")) + def setup_module(mod): setup_make("datatypes") @@ -12,99 +15,129 @@ def setup_module(mod): class TestDATATYPES: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.datatypes = cppyy.load_reflection_info(cls.test_dct) - cls.N = 5 #cppyy.gbl.N + import cppjit + + cls.datatypes = cppjit.load_reflection_info(cls.test_dct) + cls.N = 5 # cppjit.gbl.N def test01_instance_data_read_access(self): """Read access to instance public data and verify values""" - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + c = CppjitTestData() + assert isinstance(c, CppjitTestData) # reading boolean type assert c.m_bool == False - assert not c.get_bool(); assert not c.get_bool_cr(); assert not c.get_bool_r() + assert not c.get_bool() + assert not c.get_bool_cr() + assert not c.get_bool_r() # reading char types - assert c.m_char == 'a' - assert c.m_schar == 'b' - assert c.m_uchar == 'c' + assert c.m_char == "a" + assert c.m_schar == "b" + assert c.m_uchar == "c" assert type(c.m_wchar) == pyunicode - assert c.m_wchar == u'D' + assert c.m_wchar == "D" assert type(c.m_char16) == pyunicode - assert c.m_char16 == u'\u00df' + assert c.m_char16 == "\u00df" assert type(c.m_char32) == pyunicode - assert c.m_char32 == u'\u00df' + assert c.m_char32 == "\u00df" # reading integer types - assert c.m_int8 == - 9; assert c.get_int8_cr() == - 9; assert c.get_int8_r() == - 9 - assert c.m_uint8 == 9; assert c.get_uint8_cr() == 9; assert c.get_uint8_r() == 9 - assert c.m_byte == ord('d'); assert c.get_byte_cr() == ord('d'); assert c.get_byte_r() == ord('d') - assert c.m_short == -11; assert c.get_short_cr() == -11; assert c.get_short_r() == -11 - assert c.m_ushort == 11; assert c.get_ushort_cr() == 11; assert c.get_ushort_r() == 11 - assert c.m_int == -22; assert c.get_int_cr() == -22; assert c.get_int_r() == -22 - assert c.m_uint == 22; assert c.get_uint_cr() == 22; assert c.get_uint_r() == 22 - assert c.m_long == -33; assert c.get_long_cr() == -33; assert c.get_long_r() == -33 - assert c.m_ulong == 33; assert c.get_ulong_cr() == 33; assert c.get_ulong_r() == 33 - assert c.m_llong == -44; assert c.get_llong_cr() == -44; assert c.get_llong_r() == -44 - assert c.m_ullong == 44; assert c.get_ullong_cr() == 44; assert c.get_ullong_r() == 44 - assert c.m_long64 == -55; assert c.get_long64_cr() == -55; assert c.get_long64_r() == -55 - assert c.m_ulong64 == 55; assert c.get_ulong64_cr() == 55; assert c.get_ulong64_r() == 55 + assert c.m_int8 == -9 + assert c.get_int8_cr() == -9 + assert c.get_int8_r() == -9 + assert c.m_uint8 == 9 + assert c.get_uint8_cr() == 9 + assert c.get_uint8_r() == 9 + assert c.m_byte == ord("d") + assert c.get_byte_cr() == ord("d") + assert c.get_byte_r() == ord("d") + assert c.m_short == -11 + assert c.get_short_cr() == -11 + assert c.get_short_r() == -11 + assert c.m_ushort == 11 + assert c.get_ushort_cr() == 11 + assert c.get_ushort_r() == 11 + assert c.m_int == -22 + assert c.get_int_cr() == -22 + assert c.get_int_r() == -22 + assert c.m_uint == 22 + assert c.get_uint_cr() == 22 + assert c.get_uint_r() == 22 + assert c.m_long == -33 + assert c.get_long_cr() == -33 + assert c.get_long_r() == -33 + assert c.m_ulong == 33 + assert c.get_ulong_cr() == 33 + assert c.get_ulong_r() == 33 + assert c.m_llong == -44 + assert c.get_llong_cr() == -44 + assert c.get_llong_r() == -44 + assert c.m_ullong == 44 + assert c.get_ullong_cr() == 44 + assert c.get_ullong_r() == 44 + assert c.m_long64 == -55 + assert c.get_long64_cr() == -55 + assert c.get_long64_r() == -55 + assert c.m_ulong64 == 55 + assert c.get_ulong64_cr() == 55 + assert c.get_ulong64_r() == 55 # reading floating point types - assert round(c.m_float + 66., 5) == 0 - assert round(c.get_float_cr() + 66., 5) == 0 - assert round(c.get_float_r() + 66., 5) == 0 - assert round(c.m_double + 77., 11) == 0 - assert round(c.get_double_cr() + 77., 11) == 0 - assert round(c.get_double_r() + 77., 11) == 0 - assert round(c.m_ldouble + 88., 24) == 0 - assert round(c.get_ldouble_cr() + 88., 24) == 0 - assert round(c.get_ldouble_r() + 88., 24) == 0 - assert round(c.get_ldouble_def() -1., 24) == 0 - assert round(c.get_ldouble_def(2) -2., 24) == 0 + assert round(c.m_float + 66.0, 5) == 0 + assert round(c.get_float_cr() + 66.0, 5) == 0 + assert round(c.get_float_r() + 66.0, 5) == 0 + assert round(c.m_double + 77.0, 11) == 0 + assert round(c.get_double_cr() + 77.0, 11) == 0 + assert round(c.get_double_r() + 77.0, 11) == 0 + assert round(c.m_ldouble + 88.0, 24) == 0 + assert round(c.get_ldouble_cr() + 88.0, 24) == 0 + assert round(c.get_ldouble_r() + 88.0, 24) == 0 + assert round(c.get_ldouble_def() - 1.0, 24) == 0 + assert round(c.get_ldouble_def(2) - 2.0, 24) == 0 # complex type assert type(c.get_complex()) == complex - assert round(c.get_complex().real - 99., 11) == 0 - assert round(c.get_complex().imag - 101., 11) == 0 - assert repr(c.get_complex()) == '(99+101j)' - assert round(c.get_complex_cr().real - 99., 11) == 0 - assert round(c.get_complex_cr().imag - 101., 11) == 0 - assert round(c.get_complex_r().real - 99., 11) == 0 - assert round(c.get_complex_r().imag - 101., 11) == 0 - assert complex(cppyy.gbl.std.complex['double'](1, 2)) == complex(1, 2) - assert repr(cppyy.gbl.std.complex['double'](1, 2)) == '(1+2j)' + assert round(c.get_complex().real - 99.0, 11) == 0 + assert round(c.get_complex().imag - 101.0, 11) == 0 + assert repr(c.get_complex()) == "(99+101j)" + assert round(c.get_complex_cr().real - 99.0, 11) == 0 + assert round(c.get_complex_cr().imag - 101.0, 11) == 0 + assert round(c.get_complex_r().real - 99.0, 11) == 0 + assert round(c.get_complex_r().imag - 101.0, 11) == 0 + assert complex(cppjit.gbl.std.complex["double"](1, 2)) == complex(1, 2) + assert repr(cppjit.gbl.std.complex["double"](1, 2)) == "(1+2j)" # complex retains C++ type in all cases (but includes pythonization to # resemble Python's complex more closely - assert type(c.get_icomplex()) == cppyy.gbl.std.complex[int] - assert round(c.get_icomplex().real - 121., 11) == 0 - assert round(c.get_icomplex().imag - 141., 11) == 0 - assert repr(c.get_icomplex()) == '(121+141j)' - assert round(c.get_icomplex_cr().real - 121., 11) == 0 - assert round(c.get_icomplex_cr().imag - 141., 11) == 0 - assert type(c.get_icomplex_r()) == cppyy.gbl.std.complex[int] - assert round(c.get_icomplex_r().real - 121., 11) == 0 - assert round(c.get_icomplex_r().imag - 141., 11) == 0 - assert complex(cppyy.gbl.std.complex['int'](1, 2)) == complex(1, 2) + assert type(c.get_icomplex()) == cppjit.gbl.std.complex[int] + assert round(c.get_icomplex().real - 121.0, 11) == 0 + assert round(c.get_icomplex().imag - 141.0, 11) == 0 + assert repr(c.get_icomplex()) == "(121+141j)" + assert round(c.get_icomplex_cr().real - 121.0, 11) == 0 + assert round(c.get_icomplex_cr().imag - 141.0, 11) == 0 + assert type(c.get_icomplex_r()) == cppjit.gbl.std.complex[int] + assert round(c.get_icomplex_r().real - 121.0, 11) == 0 + assert round(c.get_icomplex_r().imag - 141.0, 11) == 0 + assert complex(cppjit.gbl.std.complex["int"](1, 2)) == complex(1, 2) # _Complex double type assert type(c.get_ccomplex()) == complex - assert round(c.get_ccomplex().real - 151., 11) == 0 - assert round(c.get_ccomplex().imag - 161., 11) == 0 - assert repr(c.get_ccomplex()) == '(151+161j)' - assert round(c.get_ccomplex_cr().real - 151., 11) == 0 - assert round(c.get_ccomplex_cr().imag - 161., 11) == 0 - assert round(c.get_ccomplex_r().real - 151., 11) == 0 - assert round(c.get_ccomplex_r().imag - 161., 11) == 0 + assert round(c.get_ccomplex().real - 151.0, 11) == 0 + assert round(c.get_ccomplex().imag - 161.0, 11) == 0 + assert repr(c.get_ccomplex()) == "(151+161j)" + assert round(c.get_ccomplex_cr().real - 151.0, 11) == 0 + assert round(c.get_ccomplex_cr().imag - 161.0, 11) == 0 + assert round(c.get_ccomplex_r().real - 151.0, 11) == 0 + assert round(c.get_ccomplex_r().imag - 161.0, 11) == 0 # complex overloads - cppyy.cppdef(""" + cppjit.cppdef(""" namespace ComplexOverload { template struct CO { @@ -116,461 +149,545 @@ def test01_instance_data_read_access(self): }; }""") - COd = cppyy.gbl.ComplexOverload.CO['double'] - COf = cppyy.gbl.ComplexOverload.CO['float'] - scf = cppyy.gbl.std.complex['float'] + COd = cppjit.gbl.ComplexOverload.CO["double"] + COf = cppjit.gbl.ComplexOverload.CO["float"] + scf = cppjit.gbl.std.complex["float"] - assert COd(2).m_size == 2 - assert COd(2).m_cplx == 7.+42j - assert COd(3.14).m_size == 42 - assert COd(3.14).m_cplx == 3.14+0j - assert COd(9.+7j).m_size == 42 - assert COd(9.+7j).m_cplx == 9.+7j + assert COd(2).m_size == 2 + assert COd(2).m_cplx == 7.0 + 42j + assert COd(3.14).m_size == 42 + assert COd(3.14).m_cplx == 3.14 + 0j + assert COd(9.0 + 7j).m_size == 42 + assert COd(9.0 + 7j).m_cplx == 9.0 + 7j - assert COf(2).m_size == 2 - assert COf(2).m_cplx == scf(7, 42) - assert COf(3.14).m_size == 42 - assert COf(3.14).m_cplx == scf(3.14, 0) - assert COf(9.+7j).m_size == 42 - assert COf(9.+7j).m_cplx == scf(9., 7.) + assert COf(2).m_size == 2 + assert COf(2).m_cplx == scf(7, 42) + assert COf(3.14).m_size == 42 + assert COf(3.14).m_cplx == scf(3.14, 0) + assert COf(9.0 + 7j).m_size == 42 + assert COf(9.0 + 7j).m_cplx == scf(9.0, 7.0) # reading of enum types - assert c.m_enum == CppyyTestData.kNothing + assert c.m_enum == CppjitTestData.kNothing assert c.m_enum == c.kNothing # reading of boolean array for i in range(self.N): - assert c.m_bool_array[i] == bool(i%2) - assert c.get_bool_array()[i] == bool(i%2) - assert c.m_bool_array2[i] == bool((i+1)%2) - assert c.get_bool_array2()[i] == bool((i+1)%2) + assert c.m_bool_array[i] == bool(i % 2) + assert c.get_bool_array()[i] == bool(i % 2) + assert c.m_bool_array2[i] == bool((i + 1) % 2) + assert c.get_bool_array2()[i] == bool((i + 1) % 2) # reading of integer array types - names = ['schar', 'uchar', 'short', 'ushort', 'int', 'uint', 'long', 'ulong', 'byte'] - alpha = [( 1, 2), (1, 2), (-5, -6), (7, 8), (-9, -10), (11, 23), (-13, -14), (15, 18), (3,4)] + names = [ + "schar", + "uchar", + "short", + "ushort", + "int", + "uint", + "long", + "ulong", + "byte", + ] + alpha = [ + (1, 2), + (1, 2), + (-5, -6), + (7, 8), + (-9, -10), + (11, 23), + (-13, -14), + (15, 18), + (3, 4), + ] for type_idx, name in enumerate(names): for j in range(self.N): - assert getattr(c, f'm_{name}_array')[j] == alpha[type_idx][0] * j - assert getattr(c, f'get_{name}_array')()[j] == alpha[type_idx][0] * j + assert getattr(c, f"m_{name}_array")[j] == alpha[type_idx][0] * j + assert getattr(c, f"get_{name}_array")()[j] == alpha[type_idx][0] * j # the signed char* array m_schar_array2 is implicitly converted to basic_string # hence we only compare the value returned using the getter get_schar_array2 - if name != 'schar': - assert getattr(c, f'm_{name}_array2')[j] == alpha[type_idx][1] * j - assert getattr(c, f'get_{name}_array2')()[j] == alpha[type_idx][1] * j + if name != "schar": + assert getattr(c, f"m_{name}_array2")[j] == alpha[type_idx][1] * j + assert getattr(c, f"get_{name}_array2")()[j] == alpha[type_idx][1] * j # reading of floating point array types for k in range(self.N): - assert round(c.m_float_array[k] + 13.*k, 5) == 0 - assert round(c.m_float_array2[k] + 14.*k, 5) == 0 - assert round(c.m_double_array[k] + 15.*k, 8) == 0 - assert round(c.m_double_array2[k] + 16.*k, 8) == 0 + assert round(c.m_float_array[k] + 13.0 * k, 5) == 0 + assert round(c.m_float_array2[k] + 14.0 * k, 5) == 0 + assert round(c.m_double_array[k] + 15.0 * k, 8) == 0 + assert round(c.m_double_array2[k] + 16.0 * k, 8) == 0 # out-of-bounds checks - raises(IndexError, c.m_schar_array.__getitem__, self.N) - raises(IndexError, c.m_uchar_array.__getitem__, self.N) - raises(IndexError, c.m_byte_array.__getitem__, self.N) - raises(IndexError, c.m_short_array.__getitem__, self.N) + raises(IndexError, c.m_schar_array.__getitem__, self.N) + raises(IndexError, c.m_uchar_array.__getitem__, self.N) + raises(IndexError, c.m_byte_array.__getitem__, self.N) + raises(IndexError, c.m_short_array.__getitem__, self.N) raises(IndexError, c.m_ushort_array.__getitem__, self.N) - raises(IndexError, c.m_int_array.__getitem__, self.N) - raises(IndexError, c.m_uint_array.__getitem__, self.N) - raises(IndexError, c.m_long_array.__getitem__, self.N) - raises(IndexError, c.m_ulong_array.__getitem__, self.N) - raises(IndexError, c.m_float_array.__getitem__, self.N) + raises(IndexError, c.m_int_array.__getitem__, self.N) + raises(IndexError, c.m_uint_array.__getitem__, self.N) + raises(IndexError, c.m_long_array.__getitem__, self.N) + raises(IndexError, c.m_ulong_array.__getitem__, self.N) + raises(IndexError, c.m_float_array.__getitem__, self.N) raises(IndexError, c.m_double_array.__getitem__, self.N) # can not access an instance member on the class - raises(AttributeError, getattr, CppyyTestData, 'm_bool') - raises(AttributeError, getattr, CppyyTestData, 'm_int') + raises(AttributeError, getattr, CppjitTestData, "m_bool") + raises(AttributeError, getattr, CppjitTestData, "m_int") - assert not hasattr(CppyyTestData, 'm_bool') - assert not hasattr(CppyyTestData, 'm_int') + assert not hasattr(CppjitTestData, "m_bool") + assert not hasattr(CppjitTestData, "m_int") c.__destruct__() def test02_instance_data_write_access(self): """Test write access to instance public data and verify values""" - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) # boolean types through functions - c.set_bool(True); assert c.get_bool() == True - c.set_bool(0); assert c.get_bool() == False + c.set_bool(True) + assert c.get_bool() == True + c.set_bool(0) + assert c.get_bool() == False # boolean types through data members - c.m_bool = True; assert c.get_bool() == True - c.set_bool(True); assert c.m_bool == True - c.m_bool = 0; assert c.get_bool() == False - c.set_bool(0); assert c.m_bool == False + c.m_bool = True + assert c.get_bool() == True + c.set_bool(True) + assert c.m_bool == True + c.m_bool = 0 + assert c.get_bool() == False + c.set_bool(0) + assert c.m_bool == False raises(ValueError, c.set_bool, 10) # char types through functions - c.set_char('c'); assert c.get_char() == 'c' - c.set_uchar('e'); assert c.get_uchar() == 'e' - c.set_wchar(u'F'); assert c.get_wchar() == u'F' + c.set_char("c") + assert c.get_char() == "c" + c.set_uchar("e") + assert c.get_uchar() == "e" + c.set_wchar("F") + assert c.get_wchar() == "F" assert type(c.get_wchar()) == pyunicode - c.set_char16(u'\u00f2'); assert c.get_char16() == u'\u00f2' - c.set_char32(u'\U0001f31c'); assert c.get_char32() == u'\U0001f31c' + c.set_char16("\u00f2") + assert c.get_char16() == "\u00f2" + c.set_char32("\U0001f31c") + assert c.get_char32() == "\U0001f31c" # char types through data members - c.m_char = 'b'; assert c.get_char() == 'b' - c.m_char = 40; assert c.get_char() == chr(40) - c.set_char('c'); assert c.m_char == 'c' - c.set_char(41); assert c.m_char == chr(41) - c.m_uchar = 'd'; assert c.get_uchar() == 'd' - c.m_uchar = 42; assert c.get_uchar() == chr(42) - c.set_uchar('e'); assert c.m_uchar == 'e' - c.set_uchar(43); assert c.m_uchar == chr(43) - c.m_wchar = u'G'; assert c.get_wchar() == u'G' - c.set_wchar(u'H'); assert c.m_wchar == u'H' - c.m_char16 = u'\u00f3'; assert c.get_char16() == u'\u00f3' - c.set_char16(u'\u00f4'); assert c.m_char16 == u'\u00f4' - c.m_char32 = u'\U0001f31d'; assert c.get_char32() == u'\U0001f31d' - c.set_char32(u'\U0001f31e'); assert c.m_char32 == u'\U0001f31e' - - raises(ValueError, c.set_char, "string") - raises(ValueError, c.set_char, 500) - raises(ValueError, c.set_uchar, "string") - raises(ValueError, c.set_uchar, -1) - raises(ValueError, c.set_wchar, "string") + c.m_char = "b" + assert c.get_char() == "b" + c.m_char = 40 + assert c.get_char() == chr(40) + c.set_char("c") + assert c.m_char == "c" + c.set_char(41) + assert c.m_char == chr(41) + c.m_uchar = "d" + assert c.get_uchar() == "d" + c.m_uchar = 42 + assert c.get_uchar() == chr(42) + c.set_uchar("e") + assert c.m_uchar == "e" + c.set_uchar(43) + assert c.m_uchar == chr(43) + c.m_wchar = "G" + assert c.get_wchar() == "G" + c.set_wchar("H") + assert c.m_wchar == "H" + c.m_char16 = "\u00f3" + assert c.get_char16() == "\u00f3" + c.set_char16("\u00f4") + assert c.m_char16 == "\u00f4" + c.m_char32 = "\U0001f31d" + assert c.get_char32() == "\U0001f31d" + c.set_char32("\U0001f31e") + assert c.m_char32 == "\U0001f31e" + + raises(ValueError, c.set_char, "string") + raises(ValueError, c.set_char, 500) + raises(ValueError, c.set_uchar, "string") + raises(ValueError, c.set_uchar, -1) + raises(ValueError, c.set_wchar, "string") raises(ValueError, c.set_char16, "string") raises(ValueError, c.set_char32, "string") # integer types - names = ['int8', 'uint8', 'short', 'ushort', 'int', 'uint', 'long', 'ulong', 'llong', 'ullong', 'byte'] + names = [ + "int8", + "uint8", + "short", + "ushort", + "int", + "uint", + "long", + "ulong", + "llong", + "ullong", + "byte", + ] for i in range(len(names)): - setattr(c, 'm_'+names[i], i) - assert eval('c.get_%s()' % names[i]) == i + setattr(c, "m_" + names[i], i) + assert eval("c.get_%s()" % names[i]) == i for i in range(len(names)): - getattr(c, 'set_'+names[i])(2*i) - assert eval('c.m_%s' % names[i]) == 2*i + getattr(c, "set_" + names[i])(2 * i) + assert eval("c.m_%s" % names[i]) == 2 * i for i in range(len(names)): - getattr(c, 'set_'+names[i]+'_cr')(3*i) - assert eval('c.m_%s' % names[i]) == 3*i + getattr(c, "set_" + names[i] + "_cr")(3 * i) + assert eval("c.m_%s" % names[i]) == 3 * i for i in range(len(names)): - getattr(c, 'set_'+names[i]+'_rv')(4*i) - assert eval('c.m_%s' % names[i]) == 4*i + getattr(c, "set_" + names[i] + "_rv")(4 * i) + assert eval("c.m_%s" % names[i]) == 4 * i for i in range(len(names)): - setattr(c, 'm_'+names[i], cppyy.default) - assert eval('c.get_%s()' % names[i]) == 0 + setattr(c, "m_" + names[i], cppjit.default) + assert eval("c.get_%s()" % names[i]) == 0 for i in range(len(names)): - getattr(c, 'set_'+names[i])(cppyy.default) - assert eval('c.m_%s' % names[i]) == 0 + getattr(c, "set_" + names[i])(cppjit.default) + assert eval("c.m_%s" % names[i]) == 0 for i in range(len(names)): - getattr(c, 'set_'+names[i]+'_cr')(cppyy.default) - assert eval('c.m_%s' % names[i]) == 0 + getattr(c, "set_" + names[i] + "_cr")(cppjit.default) + assert eval("c.m_%s" % names[i]) == 0 # float types through functions - c.set_float(0.123); assert round(c.get_float() - 0.123, 5) == 0 - c.set_double(0.456); assert round(c.get_double() - 0.456, 8) == 0 - c.set_ldouble(0.789); assert round(c.get_ldouble() - 0.789, 8) == 0 + c.set_float(0.123) + assert round(c.get_float() - 0.123, 5) == 0 + c.set_double(0.456) + assert round(c.get_double() - 0.456, 8) == 0 + c.set_ldouble(0.789) + assert round(c.get_ldouble() - 0.789, 8) == 0 # float types through data members - c.m_float = 0.123; assert round(c.get_float() - 0.123, 5) == 0 - c.set_float(0.234); assert round(c.m_float - 0.234, 5) == 0 - c.set_float_cr(0.456); assert round(c.m_float - 0.456, 5) == 0 - c.m_double = 0.678; assert round(c.get_double() - 0.678, 8) == 0 - c.set_double(0.890); assert round(c.m_double - 0.890, 8) == 0 - c.set_double_cr(0.012); assert round(c.m_double - 0.012, 8) == 0 - c.m_ldouble = 0.876; assert round(c.get_ldouble() - 0.876, 8) == 0 - c.set_ldouble(0.098); assert round(c.m_ldouble - 0.098, 8) == 0 - c.set_ldouble_cr(0.210); assert round(c.m_ldouble - 0.210, 8) == 0 - - names = ['float', 'double', 'ldouble'] + c.m_float = 0.123 + assert round(c.get_float() - 0.123, 5) == 0 + c.set_float(0.234) + assert round(c.m_float - 0.234, 5) == 0 + c.set_float_cr(0.456) + assert round(c.m_float - 0.456, 5) == 0 + c.m_double = 0.678 + assert round(c.get_double() - 0.678, 8) == 0 + c.set_double(0.890) + assert round(c.m_double - 0.890, 8) == 0 + c.set_double_cr(0.012) + assert round(c.m_double - 0.012, 8) == 0 + c.m_ldouble = 0.876 + assert round(c.get_ldouble() - 0.876, 8) == 0 + c.set_ldouble(0.098) + assert round(c.m_ldouble - 0.098, 8) == 0 + c.set_ldouble_cr(0.210) + assert round(c.m_ldouble - 0.210, 8) == 0 + + names = ["float", "double", "ldouble"] for i in range(len(names)): - setattr(c, 'm_'+names[i], cppyy.default) - assert eval('c.get_%s()' % names[i]) == 0. + setattr(c, "m_" + names[i], cppjit.default) + assert eval("c.get_%s()" % names[i]) == 0.0 for i in range(len(names)): - getattr(c, 'set_'+names[i])(cppyy.default) - assert eval('c.m_%s' % names[i]) == 0. + getattr(c, "set_" + names[i])(cppjit.default) + assert eval("c.m_%s" % names[i]) == 0.0 for i in range(len(names)): - getattr(c, 'set_'+names[i]+'_cr')(cppyy.default) - assert eval('c.m_%s' % names[i]) == 0. + getattr(c, "set_" + names[i] + "_cr")(cppjit.default) + assert eval("c.m_%s" % names[i]) == 0.0 # (non-)writing of enum types - raises(TypeError, setattr, CppyyTestData, 'kNothing', 42) + raises(TypeError, setattr, CppjitTestData, "kNothing", 42) # arrays; there will be pointer copies, so destroy the current ones c.destroy_arrays() # integer arrays - names = ['uchar', 'short', 'ushort', 'int', 'uint', 'long', 'ulong', 'byte'] + names = ["uchar", "short", "ushort", "int", "uint", "long", "ulong", "byte"] import array + a = range(self.N) - atypes = ['B', 'h', 'H', 'i', 'I', 'l', 'L', 'B'] + atypes = ["B", "h", "H", "i", "I", "l", "L", "B"] for j in range(len(names)): b = array.array(atypes[j], a) - setattr(c, 'm_'+names[j]+'_array', b) # buffer copies + setattr(c, "m_" + names[j] + "_array", b) # buffer copies for i in range(self.N): - assert eval('c.m_%s_array[i]' % names[j]) == b[i] + assert eval("c.m_%s_array[i]" % names[j]) == b[i] - setattr(c, 'm_'+names[j]+'_array2', b) # pointer copies + setattr(c, "m_" + names[j] + "_array2", b) # pointer copies assert 3 < self.N b[3] = 28 for i in range(self.N): - assert eval('c.m_%s_array2[i]' % names[j]) == b[i] + assert eval("c.m_%s_array2[i]" % names[j]) == b[i] # can not write to constant data assert c.m_const_int == 17 - raises(TypeError, setattr, c, 'm_const_int', 71) + raises(TypeError, setattr, c, "m_const_int", 71) c.__destruct__() def test03_array_passing(self): """Test passing of array arguments""" - import cppyy, array, sys - CppyyTestData = cppyy.gbl.CppyyTestData + import array - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) a = range(self.N) # test arrays in mixed order, to give overload resolution a workout - for t in ['d', 'i', 'f', 'H', 'I', 'h', 'L', 'l']: + for t in ["d", "i", "f", "H", "I", "h", "L", "l"]: b = array.array(t, a) # typed passing ca = c.pass_array(b) - if t != 'l': assert type(ca[0]) == type(b[0]) - else: assert type(ca[0]) == pylong # 'l' returns PyInt for small values in p2 + if t != "l": + assert type(ca[0]) == type(b[0]) + else: + assert type(ca[0]) == pylong # 'l' returns PyInt for small values in p2 assert len(b) == self.N for i in range(self.N): assert ca[i] == b[i] # void* passing - ca = eval('c.pass_void_array_%s(b)' % t) - if t != 'l': assert type(ca[0]) == type(b[0]) - else: assert type(ca[0]) == pylong # 'l' returns PyInt for small values in p2 + ca = eval("c.pass_void_array_%s(b)" % t) + if t != "l": + assert type(ca[0]) == type(b[0]) + else: + assert type(ca[0]) == pylong # 'l' returns PyInt for small values in p2 assert len(b) == self.N for i in range(self.N): assert ca[i] == b[i] # NULL/nullptr passing (will use short*) assert not c.pass_array(0) - raises(Exception, c.pass_array(0).__getitem__, 0) # raises SegfaultException + raises(Exception, c.pass_array(0).__getitem__, 0) # raises SegfaultException assert raises(TypeError, c.pass_array, None) - assert not c.pass_array(cppyy.nullptr) - raises(Exception, c.pass_array(cppyy.nullptr).__getitem__, 0) # id. id. + assert not c.pass_array(cppjit.nullptr) + raises(Exception, c.pass_array(cppjit.nullptr).__getitem__, 0) # id. id. c.__destruct__() def test04_class_read_access(self): """Test read access to class public data and verify values""" - import cppyy, sys - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) # char types - assert CppyyTestData.s_char == 'c' - assert c.s_char == 'c' - assert CppyyTestData.s_uchar == 'u' - assert c.s_uchar == 'u' - assert CppyyTestData.s_wchar == u'U' - assert c.s_wchar == u'U' - assert CppyyTestData.s_char16 == u'\u6c29' - assert c.s_char16 == u'\u6c29' - assert CppyyTestData.s_char32 == u'\U0001f34b' - assert c.s_char32 == u'\U0001f34b' - - assert type(c.s_wchar) == pyunicode - assert type(CppyyTestData.s_wchar) == pyunicode - assert type(c.s_char16) == pyunicode - assert type(CppyyTestData.s_char16) == pyunicode - assert type(c.s_char32) == pyunicode - assert type(CppyyTestData.s_char32) == pyunicode + assert CppjitTestData.s_char == "c" + assert c.s_char == "c" + assert CppjitTestData.s_uchar == "u" + assert c.s_uchar == "u" + assert CppjitTestData.s_wchar == "U" + assert c.s_wchar == "U" + assert CppjitTestData.s_char16 == "\u6c29" + assert c.s_char16 == "\u6c29" + assert CppjitTestData.s_char32 == "\U0001f34b" + assert c.s_char32 == "\U0001f34b" + + assert type(c.s_wchar) == pyunicode + assert type(CppjitTestData.s_wchar) == pyunicode + assert type(c.s_char16) == pyunicode + assert type(CppjitTestData.s_char16) == pyunicode + assert type(c.s_char32) == pyunicode + assert type(CppjitTestData.s_char32) == pyunicode # integer types - assert CppyyTestData.s_byte == ord('b') - assert c.s_byte == ord('b') - assert CppyyTestData.s_int8 == - 87 - assert c.s_int8 == - 87 - assert CppyyTestData.s_uint8 == 87 - assert c.s_uint8 == 87 - assert CppyyTestData.s_short == -101 - assert c.s_short == -101 - assert c.s_ushort == 255 - assert CppyyTestData.s_ushort == 255 - assert CppyyTestData.s_int == -202 - assert c.s_int == -202 - assert c.s_uint == 202 - assert CppyyTestData.s_uint == 202 - assert CppyyTestData.s_long == -pylong(303) - assert c.s_long == -pylong(303) - assert c.s_ulong == pylong(303) - assert CppyyTestData.s_ulong == pylong(303) - assert CppyyTestData.s_llong == -pylong(404) - assert c.s_llong == -pylong(404) - assert c.s_ullong == pylong(404) - assert CppyyTestData.s_ullong == pylong(404) + assert CppjitTestData.s_byte == ord("b") + assert c.s_byte == ord("b") + assert CppjitTestData.s_int8 == -87 + assert c.s_int8 == -87 + assert CppjitTestData.s_uint8 == 87 + assert c.s_uint8 == 87 + assert CppjitTestData.s_short == -101 + assert c.s_short == -101 + assert c.s_ushort == 255 + assert CppjitTestData.s_ushort == 255 + assert CppjitTestData.s_int == -202 + assert c.s_int == -202 + assert c.s_uint == 202 + assert CppjitTestData.s_uint == 202 + assert CppjitTestData.s_long == -pylong(303) + assert c.s_long == -pylong(303) + assert c.s_ulong == pylong(303) + assert CppjitTestData.s_ulong == pylong(303) + assert CppjitTestData.s_llong == -pylong(404) + assert c.s_llong == -pylong(404) + assert c.s_ullong == pylong(404) + assert CppjitTestData.s_ullong == pylong(404) # floating point types - assert round(CppyyTestData.s_float + 606., 5) == 0 - assert round(c.s_float + 606., 5) == 0 - assert round(CppyyTestData.s_double + 707., 8) == 0 - assert round(c.s_double + 707., 8) == 0 - assert round(CppyyTestData.s_ldouble + 808., 8) == 0 - assert round(c.s_ldouble + 808., 8) == 0 + assert round(CppjitTestData.s_float + 606.0, 5) == 0 + assert round(c.s_float + 606.0, 5) == 0 + assert round(CppjitTestData.s_double + 707.0, 8) == 0 + assert round(c.s_double + 707.0, 8) == 0 + assert round(CppjitTestData.s_ldouble + 808.0, 8) == 0 + assert round(c.s_ldouble + 808.0, 8) == 0 c.__destruct__() def test05_class_data_write_access(self): """Test write access to class public data and verify values""" - import cppyy, sys - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + c = CppjitTestData() + assert isinstance(c, CppjitTestData) # char types - CppyyTestData.s_char = 'a' - assert c.s_char == 'a' - c.s_char = 'b' - assert CppyyTestData.s_char == 'b' - CppyyTestData.s_uchar = 'c' - assert c.s_uchar == 'c' - c.s_uchar = 'd' - assert CppyyTestData.s_uchar == 'd' - raises(ValueError, setattr, CppyyTestData, 's_uchar', -1) - raises(ValueError, setattr, c, 's_uchar', -1) - CppyyTestData.s_wchar = u'K' - assert c.s_wchar == u'K' - c.s_wchar = u'L' - assert CppyyTestData.s_wchar == u'L' - CppyyTestData.s_char16 = u'\u00df' - assert c.s_char16 == u'\u00df' - c.s_char16 = u'\u00ef' - assert CppyyTestData.s_char16 == u'\u00ef' - CppyyTestData.s_char32 = u'\u00df' - assert c.s_char32 == u'\u00df' - c.s_char32 = u'\u00ef' - assert CppyyTestData.s_char32 == u'\u00ef' + CppjitTestData.s_char = "a" + assert c.s_char == "a" + c.s_char = "b" + assert CppjitTestData.s_char == "b" + CppjitTestData.s_uchar = "c" + assert c.s_uchar == "c" + c.s_uchar = "d" + assert CppjitTestData.s_uchar == "d" + raises(ValueError, setattr, CppjitTestData, "s_uchar", -1) + raises(ValueError, setattr, c, "s_uchar", -1) + CppjitTestData.s_wchar = "K" + assert c.s_wchar == "K" + c.s_wchar = "L" + assert CppjitTestData.s_wchar == "L" + CppjitTestData.s_char16 = "\u00df" + assert c.s_char16 == "\u00df" + c.s_char16 = "\u00ef" + assert CppjitTestData.s_char16 == "\u00ef" + CppjitTestData.s_char32 = "\u00df" + assert c.s_char32 == "\u00df" + c.s_char32 = "\u00ef" + assert CppjitTestData.s_char32 == "\u00ef" # integer types - c.s_byte = 66 - assert CppyyTestData.s_byte == 66 - CppyyTestData.s_byte = 66 - assert c.s_byte == 66 - c.s_short = -102 - assert CppyyTestData.s_short == -102 - CppyyTestData.s_short = -203 - assert c.s_short == -203 - c.s_ushort = 127 - assert CppyyTestData.s_ushort == 127 - CppyyTestData.s_ushort = 227 - assert c.s_ushort == 227 - CppyyTestData.s_int = -234 - assert c.s_int == -234 - c.s_int = -321 - assert CppyyTestData.s_int == -321 - CppyyTestData.s_uint = 1234 - assert c.s_uint == 1234 - c.s_uint = 4321 - assert CppyyTestData.s_uint == 4321 - raises(ValueError, setattr, c, 's_uint', -1) - raises(ValueError, setattr, CppyyTestData, 's_uint', -1) - CppyyTestData.s_long = -pylong(87) - assert c.s_long == -pylong(87) - c.s_long = pylong(876) - assert CppyyTestData.s_long == pylong(876) - CppyyTestData.s_ulong = pylong(876) - assert c.s_ulong == pylong(876) - c.s_ulong = pylong(678) - assert CppyyTestData.s_ulong == pylong(678) - raises(ValueError, setattr, CppyyTestData, 's_ulong', -1) - raises(ValueError, setattr, c, 's_ulong', -1) + c.s_byte = 66 + assert CppjitTestData.s_byte == 66 + CppjitTestData.s_byte = 66 + assert c.s_byte == 66 + c.s_short = -102 + assert CppjitTestData.s_short == -102 + CppjitTestData.s_short = -203 + assert c.s_short == -203 + c.s_ushort = 127 + assert CppjitTestData.s_ushort == 127 + CppjitTestData.s_ushort = 227 + assert c.s_ushort == 227 + CppjitTestData.s_int = -234 + assert c.s_int == -234 + c.s_int = -321 + assert CppjitTestData.s_int == -321 + CppjitTestData.s_uint = 1234 + assert c.s_uint == 1234 + c.s_uint = 4321 + assert CppjitTestData.s_uint == 4321 + raises(ValueError, setattr, c, "s_uint", -1) + raises(ValueError, setattr, CppjitTestData, "s_uint", -1) + CppjitTestData.s_long = -pylong(87) + assert c.s_long == -pylong(87) + c.s_long = pylong(876) + assert CppjitTestData.s_long == pylong(876) + CppjitTestData.s_ulong = pylong(876) + assert c.s_ulong == pylong(876) + c.s_ulong = pylong(678) + assert CppjitTestData.s_ulong == pylong(678) + raises(ValueError, setattr, CppjitTestData, "s_ulong", -1) + raises(ValueError, setattr, c, "s_ulong", -1) # floating point types - CppyyTestData.s_float = -3.1415 - assert round(c.s_float, 5) == -3.1415 - c.s_float = 3.1415 - assert round(CppyyTestData.s_float, 5) == 3.1415 + CppjitTestData.s_float = -3.1415 + assert round(c.s_float, 5) == -3.1415 + c.s_float = 3.1415 + assert round(CppjitTestData.s_float, 5) == 3.1415 import math - c.s_double = -math.pi - assert CppyyTestData.s_double == -math.pi - CppyyTestData.s_double = math.pi - assert c.s_double == math.pi - c.s_ldouble = -math.pi - assert CppyyTestData.s_ldouble == -math.pi - CppyyTestData.s_ldouble = math.pi - assert c.s_ldouble == math.pi + + c.s_double = -math.pi + assert CppjitTestData.s_double == -math.pi + CppjitTestData.s_double = math.pi + assert c.s_double == math.pi + c.s_ldouble = -math.pi + assert CppjitTestData.s_ldouble == -math.pi + CppjitTestData.s_ldouble = math.pi + assert c.s_ldouble == math.pi c.__destruct__() def test06_range_access(self): """Test the ranges of integer types""" - import cppyy, sys - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) # TODO: should these be TypeErrors, or should char/bool raise # ValueErrors? In any case, consistency is needed ... - raises(ValueError, setattr, c, 'm_uint', -1) - raises(ValueError, setattr, c, 'm_ulong', -1) + raises(ValueError, setattr, c, "m_uint", -1) + raises(ValueError, setattr, c, "m_ulong", -1) c.__destruct__() def test07_type_conversions(self): """Test conversions between builtin types""" - import cppyy, sys - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + c = CppjitTestData() + assert isinstance(c, CppjitTestData) c.m_double = -1 assert round(c.m_double + 1.0, 8) == 0 - raises(TypeError, setattr, c.m_double, 'c') - raises(TypeError, setattr, c.m_int, -1.) - raises(TypeError, setattr, c.m_int, 1.) - raises(TypeError, setattr, c.m_long, 3.14) - raises(TypeError, setattr, c.m_ulong, 3.14) - raises(TypeError, setattr, c.m_llong, 3.14) - raises(TypeError, setattr, c.m_ullong, 3.14) - - raises(TypeError, c.set_int, 3.14) - raises(TypeError, c.set_long, 3.14) - raises(TypeError, c.set_ulong, 3.14) - raises(TypeError, c.set_llong, 3.14) - raises(TypeError, c.set_ullong, 3.14) - - raises(TypeError, c.set_int_cr, 3.14) - raises(TypeError, c.set_long_cr, 3.14) - raises(TypeError, c.set_ulong_cr, 3.14) - raises(TypeError, c.set_llong_cr, 3.14) - raises(TypeError, c.set_ullong_cr, 3.14) + raises(TypeError, setattr, c.m_double, "c") + raises(TypeError, setattr, c.m_int, -1.0) + raises(TypeError, setattr, c.m_int, 1.0) + raises(TypeError, setattr, c.m_long, 3.14) + raises(TypeError, setattr, c.m_ulong, 3.14) + raises(TypeError, setattr, c.m_llong, 3.14) + raises(TypeError, setattr, c.m_ullong, 3.14) + + raises(TypeError, c.set_int, 3.14) + raises(TypeError, c.set_long, 3.14) + raises(TypeError, c.set_ulong, 3.14) + raises(TypeError, c.set_llong, 3.14) + raises(TypeError, c.set_ullong, 3.14) + + raises(TypeError, c.set_int_cr, 3.14) + raises(TypeError, c.set_long_cr, 3.14) + raises(TypeError, c.set_ulong_cr, 3.14) + raises(TypeError, c.set_llong_cr, 3.14) + raises(TypeError, c.set_ullong_cr, 3.14) c.__destruct__() - @mark.xfail(condition= IS_MAC, reason="Fails on OS X") + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test08_global_builtin_type(self): """Test access to a global builtin type""" - import cppyy - gbl = cppyy.gbl + import cppjit + + gbl = cppjit.gbl assert gbl.g_int == gbl.get_global_int() @@ -582,7 +699,7 @@ def test08_global_builtin_type(self): assert gbl.get_global_int() == 22 assert gbl.g_int == 22 - # if setting before checking, the change must be reflected on the C++ side + # if setting before checking, the change must be reflected on the C++ side gbl.g_some_global_string = "Python" assert gbl.get_some_global_string() == "Python" assert gbl.g_some_global_string2 == "C++" @@ -590,8 +707,8 @@ def test08_global_builtin_type(self): gbl.g_some_global_string2 = "Python" assert gbl.get_some_global_string2() == "Python" - assert gbl.g_some_global_string16 == u'z\u00df\u6c34' - assert gbl.g_some_global_string32 == u'z\u00df\u6c34\U0001f34c' + assert gbl.g_some_global_string16 == "z\u00df\u6c34" + assert gbl.g_some_global_string32 == "z\u00df\u6c34\U0001f34c" NS = gbl.SomeStaticDataNS NS.s_some_static_string = "Python" @@ -601,7 +718,7 @@ def test08_global_builtin_type(self): NS.s_some_static_string2 = "Python" assert NS.get_some_static_string2() == "Python" - # verify that non-C++ data can still be set + # verify that non-C++ data can still be set gbl.g_python_only = "Python" assert gbl.g_python_only == "Python" @@ -611,21 +728,23 @@ def test08_global_builtin_type(self): def test08a_global_object(self): """Test access to global objects by value""" - import cppyy - gbl = cppyy.gbl + import cppjit - assert gbl.gData.fData == 5. + gbl = cppjit.gbl + + assert gbl.gData.fData == 5.0 def test09_global_ptr(self): """Test access of global objects through a pointer""" - import cppyy - gbl = cppyy.gbl + import cppjit + + gbl = cppjit.gbl with raises(ReferenceError): gbl.g_pod.m_int - c = gbl.CppyyTestPod() + c = gbl.CppjitTestPod() c.m_int = 42 c.m_double = 3.14 @@ -639,7 +758,7 @@ def test09_global_ptr(self): assert c is d assert id(c) == id(d) - e = gbl.CppyyTestPod() + e = gbl.CppjitTestPod() e.m_int = 43 e.m_double = 2.14 @@ -651,70 +770,71 @@ def test09_global_ptr(self): def test10_enum(self): """Test access to enums""" - import cppyy - gbl = cppyy.gbl + import cppjit - CppyyTestData = cppyy.gbl.CppyyTestData + gbl = cppjit.gbl - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + assert isinstance(c, CppjitTestData) # test that the enum is accessible as a type - assert CppyyTestData.EWhat + assert CppjitTestData.EWhat - assert CppyyTestData.kNothing == 6 - assert CppyyTestData.kSomething == 111 - assert CppyyTestData.kLots == 42 + assert CppjitTestData.kNothing == 6 + assert CppjitTestData.kSomething == 111 + assert CppjitTestData.kLots == 42 - assert CppyyTestData.EWhat(CppyyTestData.kNothing) == CppyyTestData.kNothing - assert CppyyTestData.EWhat(6) == CppyyTestData.kNothing + assert CppjitTestData.EWhat(CppjitTestData.kNothing) == CppjitTestData.kNothing + assert CppjitTestData.EWhat(6) == CppjitTestData.kNothing # TODO: only allow instantiations with correct values (C++11) - assert c.get_enum() == CppyyTestData.kNothing - assert c.m_enum == CppyyTestData.kNothing + assert c.get_enum() == CppjitTestData.kNothing + assert c.m_enum == CppjitTestData.kNothing - c.m_enum = CppyyTestData.kSomething - assert c.get_enum() == CppyyTestData.kSomething - assert c.m_enum == CppyyTestData.kSomething + c.m_enum = CppjitTestData.kSomething + assert c.get_enum() == CppjitTestData.kSomething + assert c.m_enum == CppjitTestData.kSomething - c.set_enum(CppyyTestData.kLots) - assert c.get_enum() == CppyyTestData.kLots - assert c.m_enum == CppyyTestData.kLots + c.set_enum(CppjitTestData.kLots) + assert c.get_enum() == CppjitTestData.kLots + assert c.m_enum == CppjitTestData.kLots - assert c.s_enum == CppyyTestData.s_enum - assert c.s_enum == CppyyTestData.kNothing - assert CppyyTestData.s_enum == CppyyTestData.kNothing + assert c.s_enum == CppjitTestData.s_enum + assert c.s_enum == CppjitTestData.kNothing + assert CppjitTestData.s_enum == CppjitTestData.kNothing - c.s_enum = CppyyTestData.kSomething - assert c.s_enum == CppyyTestData.s_enum - assert c.s_enum == CppyyTestData.kSomething - assert CppyyTestData.s_enum == CppyyTestData.kSomething + c.s_enum = CppjitTestData.kSomething + assert c.s_enum == CppjitTestData.s_enum + assert c.s_enum == CppjitTestData.kSomething + assert CppjitTestData.s_enum == CppjitTestData.kSomething # global enums - assert gbl.EFruit # test type accessible - assert gbl.kApple == 78 + assert gbl.EFruit # test type accessible + assert gbl.kApple == 78 assert gbl.kBanana == 29 assert gbl.kCitrus == 34 - assert gbl.EFruit.__name__ == 'EFruit' - assert gbl.EFruit.__cpp_name__ == 'EFruit' + assert gbl.EFruit.__name__ == "EFruit" + assert gbl.EFruit.__cpp_name__ == "EFruit" - assert gbl.EFruit.kApple == 78 + assert gbl.EFruit.kApple == 78 assert gbl.EFruit.kBanana == 29 assert gbl.EFruit.kCitrus == 34 assert gbl.NamedClassEnum.E1 == 42 - assert gbl.NamedClassEnum.__name__ == 'NamedClassEnum' - assert gbl.NamedClassEnum.__cpp_name__ == 'NamedClassEnum' + assert gbl.NamedClassEnum.__name__ == "NamedClassEnum" + assert gbl.NamedClassEnum.__cpp_name__ == "NamedClassEnum" assert gbl.EnumSpace.E - assert gbl.EnumSpace.EnumClass.E1 == -1 # anonymous - assert gbl.EnumSpace.EnumClass.E2 == -1 # named type + assert gbl.EnumSpace.EnumClass.E1 == -1 # anonymous + assert gbl.EnumSpace.EnumClass.E2 == -1 # named type assert gbl.EnumSpace.NamedClassEnum.E1 == -42 - assert gbl.EnumSpace.NamedClassEnum.__name__ == 'NamedClassEnum' - assert gbl.EnumSpace.NamedClassEnum.__cpp_name__ == 'EnumSpace::NamedClassEnum' + assert gbl.EnumSpace.NamedClassEnum.__name__ == "NamedClassEnum" + assert gbl.EnumSpace.NamedClassEnum.__cpp_name__ == "EnumSpace::NamedClassEnum" - raises(TypeError, setattr, gbl.EFruit, 'kBanana', 42) + raises(TypeError, setattr, gbl.EFruit, "kBanana", 42) assert gbl.g_enum == gbl.EFruit.kBanana gbl.g_enum = gbl.EFruit.kCitrus @@ -728,9 +848,9 @@ def test10_enum(self): def test11_typed_enums(self): """Determine correct types of enums""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace TrueEnumTypes { class Test { enum nums { ZERO = 0, ONE = 1 }; @@ -739,41 +859,41 @@ class Test { enum vraioufaux : bool { faux = false, vrai = true }; }; }""") - sc = cppyy.gbl.TrueEnumTypes.Test + sc = cppjit.gbl.TrueEnumTypes.Test assert sc.nums.ZERO == 0 - assert sc.nums.ONE == 1 + assert sc.nums.ONE == 1 assert type(sc.nums.ZERO) == sc.nums assert isinstance(sc.nums.ZERO, int) - assert 'int' in repr(sc.nums.ZERO) - assert str(sc.nums.ZERO) == '0' + assert "int" in repr(sc.nums.ZERO) + assert str(sc.nums.ZERO) == "0" - assert sc.dir.left == 'l' - assert sc.dir.right == 'r' + assert sc.dir.left == "l" + assert sc.dir.right == "r" assert type(sc.dir.left) == sc.dir assert isinstance(sc.dir.left, str) - assert 'char' in repr(sc.dir.left) + assert "char" in repr(sc.dir.left) assert str(sc.dir.left) == "'l'" - assert sc.rank.FIRST == 1 + assert sc.rank.FIRST == 1 assert sc.rank.SECOND == 2 assert type(sc.rank.FIRST) == sc.rank assert isinstance(sc.rank.FIRST, pylong) - assert 'long' in repr(sc.rank.FIRST) - assert str(sc.rank.FIRST) == '1' or str(sc.rank.FIRST) == '1L' + assert "long" in repr(sc.rank.FIRST) + assert str(sc.rank.FIRST) == "1" or str(sc.rank.FIRST) == "1L" assert sc.vraioufaux.faux == False assert sc.vraioufaux.vrai == True assert type(sc.vraioufaux.faux) == sc.vraioufaux - assert isinstance(sc.vraioufaux.faux, int) # no bool as base class - assert 'bool' in repr(sc.vraioufaux.faux) + assert isinstance(sc.vraioufaux.faux, int) # no bool as base class + assert "bool" in repr(sc.vraioufaux.faux) def test12_enum_scopes(self): """Enum accessibility and scopes""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ enum { g_one = 1, g_two = 2 }; enum GEnum1 { g_three = 3, g_four = 4 }; enum class GEnum2 { g_five = 5, g_six = 6 }; @@ -791,66 +911,70 @@ class EnumClass { enum class CEnum2 { c_five = 5, c_six = 6 }; }; """) - gn = cppyy.gbl - assert not hasattr(gn, 'n_one') - assert not hasattr(gn, 'c_one') - assert gn.g_two == 2 + gn = cppjit.gbl + assert not hasattr(gn, "n_one") + assert not hasattr(gn, "c_one") + assert gn.g_two == 2 assert gn.g_four == 4 assert gn.GEnum1.g_three == 3 assert gn.GEnum1.g_three == gn.g_three assert type(gn.GEnum1.g_three) == type(gn.g_three) - assert not hasattr(gn, 'g_five') + assert not hasattr(gn, "g_five") assert gn.GEnum2.g_six == 6 - ns = cppyy.gbl.EnumScopes - assert not hasattr(ns, 'g_one') - assert not hasattr(ns, 'c_one') - assert ns.n_two == 2 + ns = cppjit.gbl.EnumScopes + assert not hasattr(ns, "g_one") + assert not hasattr(ns, "c_one") + assert ns.n_two == 2 assert ns.n_four == 4 assert ns.NEnum1.n_three == 3 assert ns.NEnum1.n_three == ns.n_three assert type(ns.NEnum1.n_three) == type(ns.n_three) - assert not hasattr(ns, 'n_five') + assert not hasattr(ns, "n_five") assert ns.NEnum2.n_six == 6 - cl = cppyy.gbl.EnumClass - assert not hasattr(cl, 'g_one') - assert not hasattr(cl, 'n_one') - assert cl.c_two == 2 + cl = cppjit.gbl.EnumClass + assert not hasattr(cl, "g_one") + assert not hasattr(cl, "n_one") + assert cl.c_two == 2 assert cl.c_four == 4 assert cl.CEnum1.c_three == 3 assert cl.CEnum1.c_three == cl.c_three assert type(cl.CEnum1.c_three) == type(cl.c_three) - assert not hasattr(cl, 'c_five') + assert not hasattr(cl, "c_five") assert cl.CEnum2.c_six == 6 def test13_string_passing(self): """Test passing/returning of a const char*""" - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData - c = CppyyTestData() - assert c.get_valid_string('aap') == 'aap' - assert c.get_invalid_string() == '' + c = CppjitTestData() + assert c.get_valid_string("aap") == "aap" + assert c.get_invalid_string() == "" - assert c.get_valid_wstring(u'aap') == u'aap' - assert c.get_invalid_wstring() == u'' + assert c.get_valid_wstring("aap") == "aap" + assert c.get_invalid_wstring() == "" - assert c.get_valid_string16(u'z\u00df\u6c34') == u'z\u00df\u6c34' - assert c.get_invalid_string16() == u'' + assert c.get_valid_string16("z\u00df\u6c34") == "z\u00df\u6c34" + assert c.get_invalid_string16() == "" - assert c.get_valid_string32(u'z\u00df\u6c34\U0001f34c') == u'z\u00df\u6c34\U0001f34c' - assert c.get_invalid_string32() == u'' + assert ( + c.get_valid_string32("z\u00df\u6c34\U0001f34c") == "z\u00df\u6c34\U0001f34c" + ) + assert c.get_invalid_string32() == "" def test14_copy_constructor(self): """Test copy constructor""" - import cppyy - FourVector = cppyy.gbl.FourVector + import cppjit - t1 = FourVector(1., 2., 3., -4.) - t2 = FourVector(0., 0., 0., 0.) + FourVector = cppjit.gbl.FourVector + + t1 = FourVector(1.0, 2.0, 3.0, -4.0) + t2 = FourVector(0.0, 0.0, 0.0, 0.0) t3 = FourVector(t1) assert t1 == t3 @@ -862,9 +986,9 @@ def test14_copy_constructor(self): def test15_object_returns(self): """Test access to and return of PODs""" - import cppyy + import cppjit - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() assert c.m_pod.m_int == 888 assert c.m_pod.m_double == 3.14 @@ -889,55 +1013,55 @@ def test15_object_returns(self): def test16_object_arguments(self): """Test setting and returning of a POD through arguments""" - import cppyy + import cppjit - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() assert c.m_pod.m_int == 888 assert c.m_pod.m_double == 3.14 - p = cppyy.gbl.CppyyTestPod() + p = cppjit.gbl.CppjitTestPod() p.m_int = 123 assert p.m_int == 123 - p.m_double = 321. - assert p.m_double == 321. + p.m_double = 321.0 + assert p.m_double == 321.0 c.set_pod_val(p) assert c.m_pod.m_int == 123 - assert c.m_pod.m_double == 321. + assert c.m_pod.m_double == 321.0 - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() c.set_pod_ptr_in(p) assert c.m_pod.m_int == 123 - assert c.m_pod.m_double == 321. + assert c.m_pod.m_double == 321.0 - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() c.set_pod_ptr_out(p) assert p.m_int == 888 assert p.m_double == 3.14 p.m_int = 555 - p.m_double = 666. + p.m_double = 666.0 - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() c.set_pod_ref(p) assert c.m_pod.m_int == 555 - assert c.m_pod.m_double == 666. + assert c.m_pod.m_double == 666.0 - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() c.set_pod_ptrptr_in(p) assert c.m_pod.m_int == 555 - assert c.m_pod.m_double == 666. + assert c.m_pod.m_double == 666.0 assert p.m_int == 555 - assert p.m_double == 666. + assert p.m_double == 666.0 - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() c.set_pod_void_ptrptr_in(p) assert c.m_pod.m_int == 555 - assert c.m_pod.m_double == 666. + assert c.m_pod.m_double == 666.0 assert p.m_int == 555 - assert p.m_double == 666. + assert p.m_double == 666.0 - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() c.set_pod_ptrptr_out(p) assert c.m_pod.m_int == 888 assert c.m_pod.m_double == 3.14 @@ -945,9 +1069,9 @@ def test16_object_arguments(self): assert p.m_double == 3.14 p.m_int = 777 - p.m_double = 888. + p.m_double = 888.0 - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() c.set_pod_void_ptrptr_out(p) assert c.m_pod.m_int == 888 assert c.m_pod.m_double == 3.14 @@ -957,10 +1081,10 @@ def test16_object_arguments(self): def test17_nullptr_passing(self): """Integer 0 ('NULL') and nullptr allowed to pass through instance*""" - import cppyy + import cppjit - for o in (0, cppyy.nullptr): - c = cppyy.gbl.CppyyTestData() + for o in (0, cppjit.nullptr): + c = cppjit.gbl.CppjitTestData() assert c.m_pod.m_int == 888 assert c.m_pod.m_double == 3.14 assert not not c.m_ppod @@ -972,37 +1096,39 @@ def test17_nullptr_passing(self): def test18_respect_privacy(self): """Test that privacy settings are respected""" - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData - c = CppyyTestData() - assert isinstance(c, CppyyTestData) + c = CppjitTestData() + assert isinstance(c, CppjitTestData) - raises(AttributeError, getattr, c, 'm_owns_arrays') + raises(AttributeError, getattr, c, "m_owns_arrays") c.__destruct__() def test19_object_and_pointer_comparisons(self): """Verify object and pointer comparisons""" - import cppyy - gbl = cppyy.gbl + import cppjit - c1 = cppyy.bind_object(0, gbl.CppyyTestData) + gbl = cppjit.gbl + + c1 = cppjit.bind_object(0, gbl.CppjitTestData) assert not c1 - c2 = cppyy.bind_object(0, gbl.CppyyTestData) + c2 = cppjit.bind_object(0, gbl.CppjitTestData) assert c1 == c2 assert c2 == c1 # FourVector overrides operator== - l1 = cppyy.bind_object(0, gbl.FourVector) + l1 = cppjit.bind_object(0, gbl.FourVector) assert not l1 assert c1 != l1 assert l1 != c1 - l2 = cppyy.bind_object(0, gbl.FourVector) + l2 = cppjit.bind_object(0, gbl.FourVector) assert l1 == l2 assert l2 == l1 @@ -1013,15 +1139,15 @@ def test19_object_and_pointer_comparisons(self): assert l4 == l3 assert l3 - assert l3 != l5 # like this to ensure __ne__ is called - assert l5 != l3 # id. + assert l3 != l5 # like this to ensure __ne__ is called + assert l5 != l3 # id. def test20_object_comparisons_with_cpp__eq__(self): """Comparisons with C++ providing __eq__/__ne__""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace MoreComparisons { struct Comparable1 { Comparable1(int i) : fInt(i) {} @@ -1045,22 +1171,22 @@ def test20_object_comparisons_with_cpp__eq__(self): } }; }""") - ns = cppyy.gbl.MoreComparisons + ns = cppjit.gbl.MoreComparisons c1_1 = ns.Comparable1(42) c1_2 = ns.Comparable1(42) c1_3 = ns.Comparable1(43) - assert ns.Comparable1.__dict__['__eq__'](c1_1, c1_2) - assert not ns.Comparable1.__dict__['__eq__'](c1_1, c1_3) - assert not ns.Comparable1.__dict__['__ne__'](c1_1, c1_2) - assert ns.Comparable1.__dict__['__ne__'](c1_1, c1_3) + assert ns.Comparable1.__dict__["__eq__"](c1_1, c1_2) + assert not ns.Comparable1.__dict__["__eq__"](c1_1, c1_3) + assert not ns.Comparable1.__dict__["__ne__"](c1_1, c1_2) + assert ns.Comparable1.__dict__["__ne__"](c1_1, c1_3) - # the following works as a side-effect of a workaround for vector calls and - # it is probably preferable to have it working, so leave the discrepancy for - # now: python's aggressive end-of-life schedule will catch up soon enough + # the following works as a side-effect of a workaround for vector calls and + # it is probably preferable to have it working, so leave the discrepancy for + # now: python's aggressive end-of-life schedule will catch up soon enough if 0x3080000 <= sys.hexversion: - assert c1_1 == c1_2 + assert c1_1 == c1_2 assert not c1_1 != c1_2 else: with raises(TypeError): @@ -1072,19 +1198,19 @@ def test20_object_comparisons_with_cpp__eq__(self): c2_2 = ns.Comparable2(27) c2_3 = ns.Comparable2(28) - assert c2_1 == c2_1 - assert c2_1 == c2_2 + assert c2_1 == c2_1 + assert c2_1 == c2_2 assert not c2_1 == c2_3 assert not c2_1 != c2_1 assert not c2_1 != c2_2 - assert c2_1 != c2_3 + assert c2_1 != c2_3 def test21_object_validity(self): """Test object validity checking""" - from cppyy import gbl + from cppjit import gbl - d = gbl.CppyyTestPod() + d = gbl.CppjitTestPod() assert d assert not not d @@ -1096,9 +1222,9 @@ def test21_object_validity(self): def test22_buffer_shapes(self): """Correctness of declared buffer shapes""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ShapeTester { enum Enum{One, Two, Three}; @@ -1109,34 +1235,53 @@ def test22_buffer_shapes(self): T aaa[5][4][3]; }; }""") - ns = cppyy.gbl.ShapeTester - - for dtype in ["int", "double", "long", "bool", "char", "void*", "ShapeTester::Enum"]: + ns = cppjit.gbl.ShapeTester + + for dtype in [ + "int", + "double", + "long", + "bool", + "char", + "void*", + "ShapeTester::Enum", + ]: foo = ns.Foo[dtype]() - if dtype != 'char': - assert foo.a.shape == (5,) + if dtype != "char": + assert foo.a.shape == (5,) else: - # TODO: verify the following is for historic reasons and should be modified - # once bug #344 (bitbucket) is fixed + # TODO: verify the following is for historic reasons and should be modified + # once bug #344 (bitbucket) is fixed assert len(foo.a) == 5 - assert foo.aa.shape == (5, 4) + assert foo.aa.shape == (5, 4) assert foo.aaa.shape == (5, 4, 3) def test23_buffer_reshaping(self): """Test usage of buffer sizing""" - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData - - c = CppyyTestData() - for func in ['get_bool_array', 'get_bool_array2', - 'get_uchar_array', 'get_uchar_array2', - 'get_ushort_array', 'get_ushort_array2', - 'get_int_array', 'get_int_array2', - 'get_uint_array', 'get_uint_array2', - 'get_long_array', 'get_long_array2', - 'get_ulong_array', 'get_ulong_array2', - 'get_byte_array', 'get_byte_array2']: + import cppjit + + CppjitTestData = cppjit.gbl.CppjitTestData + + c = CppjitTestData() + for func in [ + "get_bool_array", + "get_bool_array2", + "get_uchar_array", + "get_uchar_array2", + "get_ushort_array", + "get_ushort_array2", + "get_int_array", + "get_int_array2", + "get_uint_array", + "get_uint_array2", + "get_long_array", + "get_long_array2", + "get_ulong_array", + "get_ulong_array2", + "get_byte_array", + "get_byte_array2", + ]: arr = getattr(c, func)() arr.reshape((self.N,)) assert len(arr) == self.N @@ -1144,7 +1289,7 @@ def test23_buffer_reshaping(self): raises(ValueError, arr.reshape, (1, 2)) assert len(arr) == self.N - raises(TypeError, arr.reshape, 2*self.N) + raises(TypeError, arr.reshape, 2 * self.N) assert len(arr) == self.N l = list(arr) @@ -1154,52 +1299,55 @@ def test23_buffer_reshaping(self): def test24_voidp(self): """Test usage of void* data""" - import cppyy - CppyyTestData = cppyy.gbl.CppyyTestData + import cppjit - c = CppyyTestData() + CppjitTestData = cppjit.gbl.CppjitTestData - assert not cppyy.nullptr + c = CppjitTestData() - assert c.s_voidp is cppyy.nullptr - assert CppyyTestData.s_voidp is cppyy.nullptr + assert not cppjit.nullptr - assert c.m_voidp is cppyy.nullptr - assert c.get_voidp() is cppyy.nullptr + assert c.s_voidp is cppjit.nullptr + assert CppjitTestData.s_voidp is cppjit.nullptr - c2 = CppyyTestData() - assert c2.m_voidp is cppyy.nullptr + assert c.m_voidp is cppjit.nullptr + assert c.get_voidp() is cppjit.nullptr + + c2 = CppjitTestData() + assert c2.m_voidp is cppjit.nullptr c.set_voidp(c2.m_voidp) - assert c.m_voidp is cppyy.nullptr + assert c.m_voidp is cppjit.nullptr c.set_voidp(c2.get_voidp()) - assert c.m_voidp is cppyy.nullptr - c.set_voidp(cppyy.nullptr) - assert c.m_voidp is cppyy.nullptr + assert c.m_voidp is cppjit.nullptr + c.set_voidp(cppjit.nullptr) + assert c.m_voidp is cppjit.nullptr c.set_voidp(c2) + def address_equality_test(a, b): - assert cppyy.addressof(a) == cppyy.addressof(b) - b2 = cppyy.bind_object(a, CppyyTestData) - assert b is b2 # memory regulator recycles - b3 = cppyy.bind_object(cppyy.addressof(a), CppyyTestData) - assert b is b3 # likewise + assert cppjit.addressof(a) == cppjit.addressof(b) + b2 = cppjit.bind_object(a, CppjitTestData) + assert b is b2 # memory regulator recycles + b3 = cppjit.bind_object(cppjit.addressof(a), CppjitTestData) + assert b is b3 # likewise address_equality_test(c.m_voidp, c2) address_equality_test(c.get_voidp(), c2) def null_test(null): c.m_voidp = null - assert c.m_voidp is cppyy.nullptr - map(null_test, [0, cppyy.nullptr]) + assert c.m_voidp is cppjit.nullptr + + map(null_test, [0, cppjit.nullptr]) c.m_voidp = c2 - address_equality_test(c.m_voidp, c2) + address_equality_test(c.m_voidp, c2) address_equality_test(c.get_voidp(), c2) c.s_voidp = c2 address_equality_test(c.s_voidp, c2) - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace VoidP { void* vvv[3][5][7]; struct Init { @@ -1213,34 +1361,37 @@ def null_test(null): } } _init; }""") - ns = cppyy.gbl.VoidP + ns = cppjit.gbl.VoidP for i in range(3): for j in range(5): for k in range(7): - assert int(ns.vvv[i,j,k]) == i+j+k + assert int(ns.vvv[i, j, k]) == i + j + k def test25_byte_arrays(self): """Usage of unsigned char* as byte array and std::byte*""" - import array, cppyy, ctypes + import array + import ctypes + + import cppjit - buf = b'123456789' + buf = b"123456789" total = 0 for c in buf: try: - total += ord(c) # p2 + total += ord(c) # p2 except TypeError: - total += c # p3 + total += c # p3 def run(self, f, buf, total): # The following create a unique type for fixed-size C arrays: ctypes.c_char_Array_9 # and neither inherits from a non-sized type nor implements the buffer interface. # As such, it can't be handled. TODO? - #pbuf = ctypes.create_string_buffer(len(buf), buf) - #assert f(pbuf, len(buf)) == total + # pbuf = ctypes.create_string_buffer(len(buf), buf) + # assert f(pbuf, len(buf)) == total - pbuf = array.array('B', buf) + pbuf = array.array("B", buf) assert f(pbuf, len(buf)) == total pbuf = (ctypes.c_ubyte * len(buf)).from_buffer_copy(buf) @@ -1249,225 +1400,241 @@ def run(self, f, buf, total): pbuf = ctypes.cast(buf, ctypes.POINTER(ctypes.c_ubyte * len(buf)))[0] assert f(pbuf, len(buf)) == total - run(self, cppyy.gbl.sum_uc_data, buf, total) - run(self, cppyy.gbl.sum_byte_data, buf, total) + run(self, cppjit.gbl.sum_uc_data, buf, total) + run(self, cppjit.gbl.sum_byte_data, buf, total) @mark.xfail(run=False, condition=IS_MAC, reason="Crashes on OSX") def test26_function_pointers(self): """Function pointer passing""" - import cppyy + import cppjit - fi1 = cppyy.gbl.sum_of_int1 - fi2 = cppyy.gbl.sum_of_int2 - fd = cppyy.gbl.sum_of_double - fdd = cppyy.gbl.call_double_double + fi1 = cppjit.gbl.sum_of_int1 + fi2 = cppjit.gbl.sum_of_int2 + fd = cppjit.gbl.sum_of_double + fdd = cppjit.gbl.call_double_double assert 5 == fi1(2, 3) - assert 5. == fd(5., 0.) + assert 5.0 == fd(5.0, 0.0) raises(TypeError, fdd, fi1, 2, 3) - assert 5. == fdd(fd, 5., 0.) - assert -1. == fdd(cppyy.nullptr, 5., 0.) + assert 5.0 == fdd(fd, 5.0, 0.0) + assert -1.0 == fdd(cppjit.nullptr, 5.0, 0.0) - fip = cppyy.gbl.sum_of_int_ptr + fip = cppjit.gbl.sum_of_int_ptr assert 5 == fip(2, 3) - cppyy.gbl.sum_of_int_ptr = cppyy.gbl.sum_of_int2 - assert 7 == cppyy.gbl.sum_of_int_ptr(2, 3) + cppjit.gbl.sum_of_int_ptr = cppjit.gbl.sum_of_int2 + assert 7 == cppjit.gbl.sum_of_int_ptr(2, 3) - cppyy.gbl.sum_of_int_ptr = cppyy.nullptr - assert not cppyy.gbl.sum_of_int_ptr - with raises(cppyy.gbl.std.bad_function_call): - cppyy.gbl.sum_of_int_ptr(2, 3) + cppjit.gbl.sum_of_int_ptr = cppjit.nullptr + assert not cppjit.gbl.sum_of_int_ptr + with raises(cppjit.gbl.std.bad_function_call): + cppjit.gbl.sum_of_int_ptr(2, 3) with raises(AttributeError): - cppyy.gbl.sim_of_int_ptr # incorrect spelling + cppjit.gbl.sim_of_int_ptr # incorrect spelling - cppyy.gbl.sum_of_int_ptr = cppyy.gbl.sum_of_int1 - assert fip is cppyy.gbl.sum_of_int_ptr # b/c cached + cppjit.gbl.sum_of_int_ptr = cppjit.gbl.sum_of_int1 + assert fip is cppjit.gbl.sum_of_int_ptr # b/c cached - o = cppyy.gbl.sum_of_int_struct() - o.sum_of_int_ptr = cppyy.gbl.sum_of_int1 + o = cppjit.gbl.sum_of_int_struct() + o.sum_of_int_ptr = cppjit.gbl.sum_of_int1 assert 5 == o.sum_of_int_ptr(2, 3) - o.sum_of_int_ptr = cppyy.gbl.sum_of_int2 + o.sum_of_int_ptr = cppjit.gbl.sum_of_int2 assert 7 == o.sum_of_int_ptr(2, 3) def sum_in_python(i1, i2): - return i1-i2 - cppyy.gbl.sum_of_int_ptr = sum_in_python - assert 1 == cppyy.gbl.call_sum_of_int(3, 2) + return i1 - i2 + + cppjit.gbl.sum_of_int_ptr = sum_in_python + assert 1 == cppjit.gbl.call_sum_of_int(3, 2) def sum_in_python(i1, i2, i3): - return i1+i2+i3 - cppyy.gbl.sum_of_int_ptr = sum_in_python + return i1 + i2 + i3 + + cppjit.gbl.sum_of_int_ptr = sum_in_python with raises(TypeError): - cppyy.gbl.call_sum_of_int(3, 2) + cppjit.gbl.call_sum_of_int(3, 2) - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace FuncPtrReturn { typedef std::string (*func_t)(void); std::string hello() { return "Hello, World!"; } func_t foo() { return hello; } }""") - ns = cppyy.gbl.FuncPtrReturn + ns = cppjit.gbl.FuncPtrReturn assert ns.foo()() == "Hello, World!" @mark.xfail(run=False, condition=IS_MAC, reason="Crashes") def test27_callable_passing(self): """Passing callables through function pointers""" - import cppyy, gc + import gc + + import cppjit - fdd = cppyy.gbl.call_double_double - fii = cppyy.gbl.call_int_int - fv = cppyy.gbl.call_void - fri = cppyy.gbl.call_refi - frl = cppyy.gbl.call_refl - frd = cppyy.gbl.call_refd + fdd = cppjit.gbl.call_double_double + fii = cppjit.gbl.call_int_int + fv = cppjit.gbl.call_void + fri = cppjit.gbl.call_refi + frl = cppjit.gbl.call_refl + frd = cppjit.gbl.call_refd - assert 'call_double_double' in str(fdd) - assert 'call_refd' in str(frd) + assert "call_double_double" in str(fdd) + assert "call_refd" in str(frd) def pyf(arg0, arg1): - return arg0+arg1 + return arg0 + arg1 assert type(fdd(pyf, 2, 3)) == float - assert fdd(pyf, 2, 3) == 5. + assert fdd(pyf, 2, 3) == 5.0 assert type(fii(pyf, 2, 3)) == int assert fii(pyf, 2, 3) == 5 def pyf(arg0, arg1): - return arg0*arg1 + return arg0 * arg1 - assert fdd(pyf, 2, 3) == 6. + assert fdd(pyf, 2, 3) == 6.0 assert fii(pyf, 2, 3) == 6 # call of void function global retval retval = None + def voidf(i): global retval retval = i assert retval is None - assert fv(voidf, 5) == None + assert fv(voidf, 5) == None # noqa: E711 assert retval == 5 # call of function with reference argument def reff(ref): ref.value = 5 + assert fri(reff) == 5 assert frl(reff) == pylong(5) - assert frd(reff) == 5. + assert frd(reff) == 5.0 # callable that does not accept weak-ref import math - assert fdd(math.atan2, 0, 3.) == 0. + + assert fdd(math.atan2, 0, 3.0) == 0.0 # error testing raises(TypeError, fii, None, 2, 3) def pyf(arg0, arg1): - return arg0/arg1 + return arg0 / arg1 raises(ZeroDivisionError, fii, pyf, 1, 0) def pyd(arg0, arg1): - return arg0*arg1 - c = cppyy.gbl.StoreCallable(pyd) - assert c(3, 3) == 9. + return arg0 * arg1 + + c = cppjit.gbl.StoreCallable(pyd) + assert c(3, 3) == 9.0 - c.set_callable(lambda x, y: x*y) - assert c(3, 3) == 9. # life line protected + c.set_callable(lambda x, y: x * y) + assert c(3, 3) == 9.0 # life line protected - c.__dict__.clear() # destroys life lines + c.__dict__.clear() # destroys life lines gc.collect() - raises(TypeError, c, 3, 3) # lambda gone out of scope + raises(TypeError, c, 3, 3) # lambda gone out of scope @mark.xfail(run=False, condition=IS_MAC, reason="Crashes on MacOS") def test28_callable_through_function_passing(self): """Passing callables through std::function""" - import cppyy, gc + import gc - fdd = cppyy.gbl.call_double_double_sf - fii = cppyy.gbl.call_int_int_sf - fv = cppyy.gbl.call_void_sf - fri = cppyy.gbl.call_refi_sf - frl = cppyy.gbl.call_refl_sf - frd = cppyy.gbl.call_refd_sf + import cppjit - assert 'call_double_double_sf' in str(fdd) - assert 'call_refd_sf' in str(frd) + fdd = cppjit.gbl.call_double_double_sf + fii = cppjit.gbl.call_int_int_sf + fv = cppjit.gbl.call_void_sf + fri = cppjit.gbl.call_refi_sf + frl = cppjit.gbl.call_refl_sf + frd = cppjit.gbl.call_refd_sf + + assert "call_double_double_sf" in str(fdd) + assert "call_refd_sf" in str(frd) def pyf(arg0, arg1): - return arg0+arg1 + return arg0 + arg1 assert type(fdd(pyf, 2, 3)) == float - assert fdd(pyf, 2, 3) == 5. + assert fdd(pyf, 2, 3) == 5.0 assert type(fii(pyf, 2, 3)) == int assert fii(pyf, 2, 3) == 5 def pyf(arg0, arg1): - return arg0*arg1 + return arg0 * arg1 - assert fdd(pyf, 2, 3) == 6. + assert fdd(pyf, 2, 3) == 6.0 assert fii(pyf, 2, 3) == 6 # call of void function global retval retval = None + def voidf(i): global retval retval = i assert retval is None - assert fv(voidf, 5) == None + assert fv(voidf, 5) == None # noqa: E711 assert retval == 5 # call of function with reference argument def reff(ref): ref.value = 5 + assert fri(reff) == 5 assert frl(reff) == pylong(5) - assert frd(reff) == 5. + assert frd(reff) == 5.0 # callable that does not accept weak-ref import math - assert fdd(math.atan2, 0, 3.) == 0. + + assert fdd(math.atan2, 0, 3.0) == 0.0 # error testing raises(TypeError, fii, None, 2, 3) def pyf(arg0, arg1): - return arg0/arg1 + return arg0 / arg1 raises(ZeroDivisionError, fii, pyf, 1, 0) def pyd(arg0, arg1): - return arg0*arg1 - c = cppyy.gbl.StoreCallable(pyd) - assert c(3, 3) == 9. + return arg0 * arg1 + + c = cppjit.gbl.StoreCallable(pyd) + assert c(3, 3) == 9.0 - c.set_callable(lambda x, y: x*y) - assert c(3, 3) == 9. # life line protected + c.set_callable(lambda x, y: x * y) + assert c(3, 3) == 9.0 # life line protected - c.__dict__.clear() # destroys life lines + c.__dict__.clear() # destroys life lines gc.collect() - raises(TypeError, c, 3, 3) # lambda gone out of scope + raises(TypeError, c, 3, 3) # lambda gone out of scope @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test29_std_function_life_lines(self): """Life lines to std::function data members""" - import cppyy, gc + import gc - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace BoundMethod2StdFunction { class Base { public: @@ -1479,7 +1646,7 @@ class Base { } }; } """) - ns = cppyy.gbl.BoundMethod2StdFunction + ns = cppjit.gbl.BoundMethod2StdFunction class Derived(ns.Base): def __init__(self): @@ -1502,9 +1669,11 @@ def xyz(self): def test30_multi_dim_arrays_of_builtins(test): """Multi-dim arrays of builtins""" - import cppyy, ctypes + import ctypes + + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" template struct MultiDimTest { T* layers[nlayers]; @@ -1520,25 +1689,31 @@ def test30_multi_dim_arrays_of_builtins(test): }; """) - from cppyy.gbl import MultiDimTest + from cppjit.gbl import MultiDimTest - nlayers = 3; width = 8; height = 4 - for (cpptype, ctype) in (('unsigned char', ctypes.c_ubyte), - ('int', ctypes.c_int), - ('double', ctypes.c_double)): + nlayers = 3 + width = 8 + height = 4 + for cpptype, ctype in ( + ("unsigned char", ctypes.c_ubyte), + ("int", ctypes.c_int), + ("double", ctypes.c_double), + ): m = MultiDimTest[cpptype, nlayers](width, height) for i in range(nlayers): buf = m.layers[i] p = (ctype * len(buf)).from_buffer(buf) - assert [p[j] for j in range(width*height)] == [2*j for j in range(width*height)] + assert [p[j] for j in range(width * height)] == [ + 2 * j for j in range(width * height) + ] def test31_anonymous_union(self): """Anonymous unions place there fields in the parent scope""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace AnonUnion { struct Event1 { Event1() : num(1) { shrd.a = 5.; } @@ -1591,47 +1766,47 @@ def test31_anonymous_union(self): }; }""") # named union - e = cppyy.gbl.AnonUnion.Event1() + e = cppjit.gbl.AnonUnion.Event1() assert e.num == 1 - raises(AttributeError, getattr, e, 'a') - raises(AttributeError, getattr, e, 'b') - assert e.shrd.a == 5. + raises(AttributeError, getattr, e, "a") + raises(AttributeError, getattr, e, "b") + assert e.shrd.a == 5.0 # anonymous union, with field name - e = cppyy.gbl.AnonUnion.Event2() + e = cppjit.gbl.AnonUnion.Event2() assert e.num == 1 - raises(AttributeError, getattr, e, 'a') - raises(AttributeError, getattr, e, 'b') - assert e.shrd.a == 5. + raises(AttributeError, getattr, e, "a") + raises(AttributeError, getattr, e, "b") + assert e.shrd.a == 5.0 # anonymous union, no field name - e = cppyy.gbl.AnonUnion.Event3(42) + e = cppjit.gbl.AnonUnion.Event3(42) assert e.b == 42 - e = cppyy.gbl.AnonUnion.Event3(5.) - assert e.a == 5. + e = cppjit.gbl.AnonUnion.Event3(5.0) + assert e.a == 5.0 # anonymous union, no field name, with offset - e = cppyy.gbl.AnonUnion.Event4(42) + e = cppjit.gbl.AnonUnion.Event4(42) assert e.num == 1 assert e.b == 42 - e = cppyy.gbl.AnonUnion.Event4(5.) + e = cppjit.gbl.AnonUnion.Event4(5.0) assert e.num == 2 - assert e.a == 5. + assert e.a == 5.0 # anonumous struct in anonymous union - p = cppyy.gbl.AnonUnion.PointXYZI() + p = cppjit.gbl.AnonUnion.PointXYZI() assert type(p.x) == float assert type(p.data_c[0]) == float - assert p.intensity == 5. + assert p.intensity == 5.0 def test32_anonymous_struct(self): """Anonymous struct creates an unnamed type""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace AnonStruct { class Foo1 { public: @@ -1662,24 +1837,26 @@ class Foo2 { } """) - ns = cppyy.gbl.AnonStruct + ns = cppjit.gbl.AnonStruct foo = ns.Foo1() assert foo.bar.x == 5 - assert not hasattr(foo.bar, 'bar') + assert not hasattr(foo.bar, "bar") foo = ns.Foo2() assert foo.bar.x == 5 assert foo.baz.x == 7 - assert 'foo' in dir(ns.libuntitled1_ExportedSymbols().kotlin.root.com.justamouse.kmmdemo) + assert "foo" in dir( + ns.libuntitled1_ExportedSymbols().kotlin.root.com.justamouse.kmmdemo + ) def test33_pointer_to_array(self): """Usability of pointer to array""" - import cppyy + import cppjit - AoS = cppyy.gbl.ArrayOfStruct + AoS = cppjit.gbl.ArrayOfStruct bar = AoS.Bar1() assert bar.fArr[0].fVal == 42 @@ -1687,9 +1864,9 @@ def test33_pointer_to_array(self): bar = AoS.Bar2(4) for i in range(4): - assert bar.fArr[i].fVal == 2*i + assert bar.fArr[i].fVal == 2 * i - cppyy.cppdef(""" + cppjit.cppdef(""" namespace ArrayOfStruct { union Bar3 { // not supported in dictionary Foo fArr[]; // clang only @@ -1698,7 +1875,7 @@ def test33_pointer_to_array(self): }""") bar = AoS.Bar3() - assert cppyy.sizeof(AoS.Bar3) >= cppyy.sizeof(AoS.Foo) + assert cppjit.sizeof(AoS.Bar3) >= cppjit.sizeof(AoS.Foo) arr = bar.fArr arr.size = 1 for f in arr: @@ -1709,44 +1886,46 @@ def test33_pointer_to_array(self): def test34_object_pointers(self): """Read/write access to objects through pointers""" - import cppyy + import cppjit - c = cppyy.gbl.CppyyTestData() + c = cppjit.gbl.CppjitTestData() - assert cppyy.gbl.CppyyTestData.s_strv == "Hello" - assert c.s_strv == "Hello" - assert not cppyy.gbl.CppyyTestData.s_strp + assert cppjit.gbl.CppjitTestData.s_strv == "Hello" + assert c.s_strv == "Hello" + assert not cppjit.gbl.CppjitTestData.s_strp assert not c.s_strp - c.s_strv = "World" - assert cppyy.gbl.CppyyTestData.s_strv == "World" + c.s_strv = "World" + assert cppjit.gbl.CppjitTestData.s_strv == "World" - # assign on nullptr is a pointer copy - sn = cppyy.gbl.std.string("aap") - cppyy.gbl.CppyyTestData.s_strp = sn - assert c.s_strp == "aap" + # assign on nullptr is a pointer copy + sn = cppjit.gbl.std.string("aap") + cppjit.gbl.CppjitTestData.s_strp = sn + assert c.s_strp == "aap" - # assign onto the existing object - cppyy.gbl.CppyyTestData.s_strp.__assign__(cppyy.gbl.std.string("noot")) - assert c.s_strp == "noot" - assert sn == "noot" # set through pointer + # assign onto the existing object + cppjit.gbl.CppjitTestData.s_strp.__assign__(cppjit.gbl.std.string("noot")) + assert c.s_strp == "noot" + assert sn == "noot" # set through pointer @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test35_restrict(self): """Strip __restrict keyword from use""" - import cppyy + import cppjit - cppyy.cppdef("std::string restrict_call(const char*__restrict s) { return s; }") + cppjit.cppdef( + "std::string restrict_call(const char*__restrict s) { return s; }" + ) - assert cppyy.gbl.restrict_call("aap") == "aap" + assert cppjit.gbl.restrict_call("aap") == "aap" def test36_legacy_matrix(self): """Handling of legacy matrix""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace Pointer2D { int** g_matrix; @@ -1776,16 +1955,14 @@ def test36_legacy_matrix(self): return true; } }""") - ns = cppyy.gbl.Pointer2D; - + ns = cppjit.gbl.Pointer2D N, M = 2, 3 m = ns.create_matrix(N, M) - g = ns.g_matrix; - + g = ns.g_matrix for i in range(N): for j in range(M): - assert m[i][j] == 13+i*N+j - assert g[i][j] == 13+i*N+j + assert m[i][j] == 13 + i * N + j + assert g[i][j] == 13 + i * N + j assert ns.destroy_matrix(m, N, M) @@ -1795,9 +1972,9 @@ def test36_legacy_matrix(self): def test37_legacy_matrix_of_structs(self): """Handling of legacy matrix of structs""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace StructPointer2D { typedef struct { int x,y,z; // 'z' so that sizeof(xy) != sizeof(void*) @@ -1834,21 +2011,19 @@ def test37_legacy_matrix_of_structs(self): return true; } }""") - ns = cppyy.gbl.StructPointer2D; - + ns = cppjit.gbl.StructPointer2D N, M = 2, 3 m = ns.create_matrix(N, M) - g = ns.g_matrix; - + g = ns.g_matrix assert (m.x, m.y) == (13, 7) assert (g.x, g.y) == (13, 7) for i in range(N): - assert (m[i].x, m[i].y) == (i+13, 7) - assert (g[i].x, g[i].y) == (i+13, 7) + assert (m[i].x, m[i].y) == (i + 13, 7) + assert (g[i].x, g[i].y) == (i + 13, 7) for j in range(M): - assert (m[i][j].x, m[i][j].y) == (i+13, j+7) - assert (g[i][j].x, g[i][j].y) == (i+13, j+7) + assert (m[i][j].x, m[i][j].y) == (i + 13, j + 7) + assert (g[i][j].x, g[i][j].y) == (i + 13, j + 7) assert ns.destroy_matrix(m, N, M) @@ -1858,9 +2033,9 @@ def test37_legacy_matrix_of_structs(self): def test38_plain_old_data(self): """Initializer construction of PODs""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ struct SomePOD_A { }; struct SomePOD_B { int fInt; }; struct SomePOD_C { int fInt; double fDouble; }; @@ -1877,76 +2052,76 @@ def test38_plain_old_data(self): struct SomePOD_F { std::array* fPtrArr; }; }""") - for ns in [cppyy.gbl, cppyy.gbl.LotsOfPODS]: - # no data member POD + for ns in [cppjit.gbl, cppjit.gbl.LotsOfPODS]: + # no data member POD assert ns.SomePOD_A() - # single data member POD + # single data member POD b0 = ns.SomePOD_B() assert b0.__python_owns__ assert b0.fInt == 0 b1 = ns.SomePOD_B(42) assert b1.__python_owns__ assert b1.fInt == 42 - b2 = ns.SomePOD_B(fInt = 17) + b2 = ns.SomePOD_B(fInt=17) assert b2.__python_owns__ assert b2.fInt == 17 - # dual data member POD + # dual data member POD c0 = ns.SomePOD_C() assert c0.__python_owns__ - assert c0.fInt == 0 - assert c0.fDouble == 0. + assert c0.fInt == 0 + assert c0.fDouble == 0.0 c1a = ns.SomePOD_C(42) assert c1a.__python_owns__ - assert c1a.fInt == 42 - assert c1a.fDouble == 0. - c1b = ns.SomePOD_C(fInt = 17) + assert c1a.fInt == 42 + assert c1a.fDouble == 0.0 + c1b = ns.SomePOD_C(fInt=17) assert c1b.__python_owns__ - assert c1b.fInt == 17 - assert c1b.fDouble == 0. - c1c = ns.SomePOD_C(fDouble = 5.) + assert c1b.fInt == 17 + assert c1b.fDouble == 0.0 + c1c = ns.SomePOD_C(fDouble=5.0) assert c1c.__python_owns__ - assert c1c.fInt == 0 - assert c1c.fDouble == 5. - c2a = ns.SomePOD_C(88, 10.) + assert c1c.fInt == 0 + assert c1c.fDouble == 5.0 + c2a = ns.SomePOD_C(88, 10.0) assert c2a.__python_owns__ - assert c2a.fInt == 88 - assert c2a.fDouble == 10. - c2b = ns.SomePOD_C(fDouble=5., fInt=77) + assert c2a.fInt == 88 + assert c2a.fDouble == 10.0 + c2b = ns.SomePOD_C(fDouble=5.0, fInt=77) assert c2b.__python_owns__ - assert c2b.fInt == 77 - assert c2b.fDouble ==5. + assert c2b.fInt == 77 + assert c2b.fDouble == 5.0 - # object type data member POD + # object type data member POD d0 = ns.SomePOD_D() assert d0.__python_owns__ assert len(d0.fArr) == 3 - assert d0.fArr[0] == 0. - d1 = ns.SomePOD_D((1., 2., 3.)) + assert d0.fArr[0] == 0.0 + d1 = ns.SomePOD_D((1.0, 2.0, 3.0)) assert d1.__python_owns__ assert len(d1.fArr) == 3 - assert list(d1.fArr) == [1., 2., 3] + assert list(d1.fArr) == [1.0, 2.0, 3] - # ptr type data member POD + # ptr type data member POD e0 = ns.SomePOD_E() assert e0.__python_owns__ - # ptr to object type data member pOD + # ptr to object type data member pOD f0 = ns.SomePOD_F() assert f0.__python_owns__ - arr = cppyy.gbl.std.array['double', 3]((1., 2., 3.)) + arr = cppjit.gbl.std.array["double", 3]((1.0, 2.0, 3.0)) f1 = ns.SomePOD_F(arr) assert f1.__python_owns__ assert len(f1.fPtrArr) == 3 - assert list(f1.fPtrArr) == [1., 2., 3] + assert list(f1.fPtrArr) == [1.0, 2.0, 3] def test39_aggregates(self): """Initializer construction of aggregates""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace AggregateTest { class Atom { public: @@ -1959,7 +2134,7 @@ class Atom { std::array coords; }; }""") - ns = cppyy.gbl.AggregateTest + ns = cppjit.gbl.AggregateTest Z = ns.Atom.AtomicNumber(5) assert Z.a_n == 5 @@ -1974,7 +2149,7 @@ class Atom { a = ns.Aggregate2(fInt=13) assert a.fInt == 13 - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace AggregateTest { typedef enum _TYPE { DATA=0, SHAPE } TYPE; @@ -1985,20 +2160,20 @@ class Atom { TYPE buf_type; } Buf; }""") - ns = cppyy.gbl.AggregateTest + ns = cppjit.gbl.AggregateTest b = ns.Buf(val=10, name="aap", buf_type=ns.SHAPE) - assert b.val == 10 - assert b.name == "aap" + assert b.val == 10 + assert b.name == "aap" assert b.buf_type == ns.SHAPE def test40_more_aggregates(self): """More aggregate testings (used to fail/report errors)""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace AggregateTest { enum E { A=1 }; @@ -2011,12 +2186,12 @@ def test40_more_aggregates(self): int x; }; }""") - ns = cppyy.gbl.AggregateTest + ns = cppjit.gbl.AggregateTest r1 = ns.make_R1() assert r1.e == ns.E.A - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace AggregateTest { struct R2 { std::optional s = {}; @@ -2032,14 +2207,14 @@ def test40_more_aggregates(self): def test41_complex_numpy_arrays(self): """Usage of complex numpy arrays""" - import cppyy + import cppjit try: import numpy as np except ImportError: - skip('numpy is not installed') + skip("numpy is not installed") - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ComplexArrays { typedef std::complex fcomp; fcomp fcompdot(const fcomp* arr1, const fcomp* arr2, const int N) { @@ -2058,18 +2233,16 @@ def test41_complex_numpy_arrays(self): } }""") def pycompdot(a, b, N): - c = 0.+0.j + c = 0.0 + 0.0j for i in range(N): - c += a[i]*b[i] + c += a[i] * b[i] return c - ns = cppyy.gbl.ComplexArrays + ns = cppjit.gbl.ComplexArrays - for ctype, func in [(np.complex64, ns.fcompdot), - (np.complex128, ns.dcompdot)]: - - Acl = np.array([1.+2.j, 3.+4.j], dtype=ctype) - Bcl = np.array([5.+6.j, 7.+8.j], dtype=ctype) + for ctype, func in [(np.complex64, ns.fcompdot), (np.complex128, ns.dcompdot)]: + Acl = np.array([1.0 + 2.0j, 3.0 + 4.0j], dtype=ctype) + Bcl = np.array([5.0 + 6.0j, 7.0 + 8.0j], dtype=ctype) pyCcl = pycompdot(Acl, Bcl, 2) @@ -2079,75 +2252,78 @@ def pycompdot(a, b, N): def test42_mixed_complex_arithmetic(self): """Mixin of Python and C++ std::complex in arithmetic""" - import cppyy + import cppjit - c = cppyy.gbl.std.complex['double'](1, 2) - p = 1+2j + c = cppjit.gbl.std.complex["double"](1, 2) + p = 1 + 2j - assert c*c == p*p - assert c*c*c == p*p*p - assert c*(c*c) == p*(p*p) - assert (c*c)*c == (p*p)*p + assert c * c == p * p + assert c * c * c == p * p * p + assert c * (c * c) == p * (p * p) + assert (c * c) * c == (p * p) * p def test43_ccharp_memory_handling(self): - """cppyy side handled memory of C strings""" + """cppjit side handled memory of C strings""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace StructWithCChar { typedef struct { const char* name; int val; } BufInfo; }""") - ns = cppyy.gbl.StructWithCChar + ns = cppjit.gbl.StructWithCChar - a_str, b_str = 'abc', 'def' + a_str, b_str = "abc", "def" a = ns.BufInfo(a_str, 4) b = ns.BufInfo(b_str, 5) - assert a.name == 'abc' - assert a.val == 4 - assert b.name == 'def' - assert b.val == 5 + assert a.name == "abc" + assert a.val == 4 + assert b.name == "def" + assert b.val == 5 - a = ns.BufInfo('abc', 4) - b = ns.BufInfo('def', 5) + a = ns.BufInfo("abc", 4) + b = ns.BufInfo("def", 5) - assert a.name == 'abc' - assert a.val == 4 - assert b.name == 'def' - assert b.val == 5 + assert a.name == "abc" + assert a.val == 4 + assert b.name == "def" + assert b.val == 5 - a.name = 'ghi' - b.name = 'jkl' + a.name = "ghi" + b.name = "jkl" - assert a.name == 'ghi' - assert b.name == 'jkl' + assert a.name == "ghi" + assert b.name == "jkl" - a_str, b_str = 'mno', 'pqr' + a_str, b_str = "mno", "pqr" a = ns.BufInfo(val=4) b = ns.BufInfo(val=5) a.name = a_str b.name = b_str - assert a.name == 'mno' - assert a.val == 4 - assert b.name == 'pqr' - assert b.val == 5 + assert a.name == "mno" + assert a.val == 4 + assert b.name == "pqr" + assert b.val == 5 def test44_buffer_memory_handling(self): - """cppyy side handled memory of LL buffers""" + """cppjit side handled memory of LL buffers""" + + import gc + + import cppjit - import cppyy, gc try: import numpy as np except ImportError: - skip('numpy is not installed') + skip("numpy is not installed") - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace StructWithBuf { typedef struct { double* data1; @@ -2155,38 +2331,41 @@ def test44_buffer_memory_handling(self): int size; } BufInfo; }""") - ns = cppyy.gbl.StructWithBuf + ns = cppjit.gbl.StructWithBuf N = 10 buf1 = ns.BufInfo( - np.array(range(N), dtype=np.float64), 2.*np.array(range(N), dtype=np.float64), N) + np.array(range(N), dtype=np.float64), + 2.0 * np.array(range(N), dtype=np.float64), + N, + ) gc.collect() for i in range(buf1.size): - assert buf1.data1[i] == 1.*i - assert buf1.data2[i] == 2.*i + assert buf1.data1[i] == 1.0 * i + assert buf1.data2[i] == 2.0 * i buf2 = ns.BufInfo() - buf2.data1 = 4.*np.array(range(N), dtype=np.float64) - buf2.data2 = 5.*np.array(range(N), dtype=np.float64) + buf2.data1 = 4.0 * np.array(range(N), dtype=np.float64) + buf2.data2 = 5.0 * np.array(range(N), dtype=np.float64) buf2.size = N gc.collect() assert len(buf2.data1) == N for i in range(buf2.size): - assert buf2.data1[i] == 4.*i - assert buf2.data2[i] == 5.*i + assert buf2.data1[i] == 4.0 * i + assert buf2.data2[i] == 5.0 * i for i in range(buf1.size): - assert buf1.data1[i] == 1.*i - assert buf1.data2[i] == 2.*i + assert buf1.data1[i] == 1.0 * i + assert buf1.data2[i] == 2.0 * i def test45_const_ref_data(self): """Proper indirection for addressing const-ref data""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ConstRefData { struct A { std::string name; @@ -2200,7 +2379,7 @@ def test45_const_ref_data(self): const A& body2; }; }""") - ns = cppyy.gbl.ConstRefData + ns = cppjit.gbl.ConstRefData b = ns.B() assert b.body1.name == b.body2.name @@ -2208,9 +2387,9 @@ def test45_const_ref_data(self): def test46_small_int_enums(self): """Proper typing of small int enums""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace SmallEnums { enum class Enum { E0 = 0, E1 = 1, EN1 = -1, EN2 = -2 }; @@ -2225,45 +2404,46 @@ def test46_small_int_enums(self): enum class UCharEnum : unsigned char { E0 = '0', E1 = '1' }; }""") - ns = cppyy.gbl.SmallEnums + ns = cppjit.gbl.SmallEnums - for eclsname in ('Enum', 'Int8Enum', 'Int16Enum'): + for eclsname in ("Enum", "Int8Enum", "Int16Enum"): ecls = getattr(ns, eclsname) - for ename, val in (('E0', 0), ('E1', 1), ('EN1', -1), ('EN2', -2)): + for ename, val in (("E0", 0), ("E1", 1), ("EN1", -1), ("EN2", -2)): assert getattr(ecls, ename) == val - for eclsname in ('UInt8Enum', 'UInt16Enum'): + for eclsname in ("UInt8Enum", "UInt16Enum"): ecls = getattr(ns, eclsname) - for ename, val in (('E0', 0), ('E1', 1)): + for ename, val in (("E0", 0), ("E1", 1)): assert getattr(ecls, ename) == val - assert ns.UInt8Enum.EMAX == 255 + assert ns.UInt8Enum.EMAX == 255 assert ns.UInt16Enum.EMAX == 65535 - for eclsname in ('CharEnum', 'SCharEnum'): + for eclsname in ("CharEnum", "SCharEnum"): ecls = getattr(ns, eclsname) - for ename, val in (('E0', '0'), ('E1', '1')): + for ename, val in (("E0", "0"), ("E1", "1")): assert getattr(ecls, ename) == val # TODO: this is b/c unsigned char is considered a "byte" type by default; # it's meaning should probably be configurable? - assert ns.UCharEnum.E0 == ord('0') - assert ns.UCharEnum.E1 == ord('1') + assert ns.UCharEnum.E0 == ord("0") + assert ns.UCharEnum.E1 == ord("1") - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace SmallEnums { Int8Enum func_int8() { return Int8Enum::EN1; } UInt8Enum func_uint8() { return UInt8Enum::EMAX; } }""") - assert ns.func_int8() == -1 + assert ns.func_int8() == -1 assert ns.func_uint8() == 255 def test47_hidden_name_enum(self): """Usage of hidden name enum""" - import cppyy - cppyy.cppdef(r""" + import cppjit + + cppjit.cppdef(r""" namespace EnumFunctionSameName { enum foo { FOO = 42 }; void foo() {} @@ -2271,44 +2451,44 @@ def test47_hidden_name_enum(self): struct Bar { Bar(enum foo f) : fFoo(f) {} enum foo fFoo; }; }""") - ns = cppyy.gbl.EnumFunctionSameName + ns = cppjit.gbl.EnumFunctionSameName assert ns.bar(ns.FOO) == 42 assert ns.Bar(ns.FOO) assert ns.Bar(ns.FOO).fFoo == 42 # TODO: - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace EnumFunctionSameName { template struct BarT {}; }""") - #ns.BarT[ns.FOO]() + # ns.BarT[ns.FOO]() # TODO: - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace EnumFunctionSameName { enum class Foo : int8_t { FOO }; void Foo() {} void bar(enum Foo) {} }""") - #ns.bar(ns.Foo.FOO) + # ns.bar(ns.Foo.FOO) def test48_bool_typemap(self): """Test mapping of bool type typedefs""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" struct BoolTypeMapTest { typedef bool BoolType; }; """) - bt = cppyy.gbl.BoolTypeMapTest.BoolType + bt = cppjit.gbl.BoolTypeMapTest.BoolType - assert bt.__name__ == 'BoolType' - assert bt.__cpp_name__ == 'BoolTypeMapTest::BoolType' + assert bt.__name__ == "BoolType" + assert bt.__cpp_name__ == "BoolTypeMapTest::BoolType" assert bt(1) assert bt(1) == True assert bt(1) != False @@ -2318,45 +2498,58 @@ def test48_bool_typemap(self): assert bt() == False assert bt() != True assert bt() is False - assert str(bt(1)) == 'True' - assert str(bt(0)) == 'False' + assert str(bt(1)) == "True" + assert str(bt(0)) == "False" - @mark.xfail(condition=IS_MAC and IS_CLANG_REPL, reason="Fails on OS X with Clang-Repl") + @mark.xfail( + condition=IS_MAC and IS_CLANG_REPL, reason="Fails on OS X with Clang-Repl" + ) def test49_addressof_method(self): """Use of addressof for (const) methods""" - import cppyy + import cppjit - assert cppyy.addressof(cppyy.gbl.std.vector[int].at.__overload__(':any:', False)) - assert cppyy.addressof(cppyy.gbl.std.vector[int].at.__overload__(':any:', True)) + assert cppjit.addressof( + cppjit.gbl.std.vector[int].at.__overload__(":any:", False) + ) + assert cppjit.addressof( + cppjit.gbl.std.vector[int].at.__overload__(":any:", True) + ) def test50_int8_uint8_global_arrays(self): """Access to int8_t/uint8_t arrays that are global variables""" - import cppyy + import cppjit - ns = cppyy.gbl.Int8_Uint8_Arrays + ns = cppjit.gbl.Int8_Uint8_Arrays - assert [ns.test[i] for i in range(6)] == [-0x12, -0x34, -0x56, -0x78, 0x0, 0x0] - assert [ns.utest[i] for i in range(6)] == [ 0x12, 0x34, 0x56, 0x78, 0x0, 0x0] + assert [ns.test[i] for i in range(6)] == [-0x12, -0x34, -0x56, -0x78, 0x0, 0x0] + assert [ns.utest[i] for i in range(6)] == [0x12, 0x34, 0x56, 0x78, 0x0, 0x0] def test51_enum_integrity(self): - import cppyy import enum - cppyy.cppdef("enum class Eint : int { ON = 1, OFF = 0 };") - Eint = cppyy.gbl.Eint - cls_Eint0 = enum.Enum("Eint0", [(n, v) for n, v in Eint.__dict__.items() if isinstance(v, Eint)]) + import cppjit + + cppjit.cppdef("enum class Eint : int { ON = 1, OFF = 0 };") + Eint = cppjit.gbl.Eint + cls_Eint0 = enum.Enum( + "Eint0", [(n, v) for n, v in Eint.__dict__.items() if isinstance(v, Eint)] + ) assert len(cls_Eint0.__dict__["_member_names_"]) == 2 - cppyy.cppdef("enum class Ebool : bool { ON = true, OFF = false };") - Ebool = cppyy.gbl.Ebool - cls_Ebool0 = enum.Enum("Ebool0", [(n, v) for n, v in Ebool.__dict__.items() if isinstance(v, Ebool)]) + cppjit.cppdef("enum class Ebool : bool { ON = true, OFF = false };") + Ebool = cppjit.gbl.Ebool + cls_Ebool0 = enum.Enum( + "Ebool0", + [(n, v) for n, v in Ebool.__dict__.items() if isinstance(v, Ebool)], + ) assert len(cls_Ebool0.__dict__["_member_names_"]) == 2 def test52_8bit_goodness(self): - import cppyy - cppyy.cppdef(""" + import cppjit + + cppjit.cppdef(""" namespace ns52 { int8_t ti8; uint8_t tui8; @@ -2368,7 +2561,7 @@ def test52_8bit_goodness(self): auto teui8p1 = Teui8::P1; }""") - from cppyy.gbl import ns52 + from cppjit.gbl import ns52 assert ns52.ti8 == 0 ns52.ti8 = 123 @@ -2388,14 +2581,16 @@ def test52_8bit_goodness(self): assert pteui8p2 == ns52.Teui8.P2 def test53_basic_nanoseconds_goodness(self): - import cppyy - cppyy.cppdef(""" + import cppjit + + cppjit.cppdef(""" namespace ns53 { auto f0(int64_t x) { return std::chrono::nanoseconds{x}.count(); } auto f1(std::chrono::nanoseconds x) { return x.count(); } } """) - from cppyy.gbl import std, ns53 + from cppjit.gbl import ns53, std + assert ns53.f0(1000) == 1000 assert ns53.f1(std.chrono.nanoseconds(1000)) == 1000 @@ -2403,10 +2598,14 @@ def test53_basic_nanoseconds_goodness(self): with raises(TypeError): ns53.f1(1000) - @mark.xfail(condition= IS_MAC, reason="std::optional::value_or rvalue conversion fails on OS X clang-repl (InstanceMoveConverter)") + @mark.xfail( + condition=IS_MAC, + reason="std::optional::value_or rvalue conversion fails on OS X clang-repl (InstanceMoveConverter)", + ) def test54_optional_use(self): - import cppyy - cppyy.cppdef(""" + import cppjit + + cppjit.cppdef(""" #include namespace ns54 { std::optional toi; @@ -2425,7 +2624,7 @@ def test54_optional_use(self): std::optional teu8; } """) - from cppyy.gbl import ns54 + from cppjit.gbl import ns54 ns54.toi = 5 assert ns54.toi == 5 @@ -2451,13 +2650,13 @@ def test54_optional_use(self): assert ns54.teu8 == ns54.Teu8.TWO def test55_qt_cache_alias_collision(self): - """`unsigned char` and `uint8_t` share a canonical type but cppyy + """`unsigned char` and `uint8_t` share a canonical type but cppjit wires them to different converters (str vs int). Locks in the QT-keyed cache's alias-collision skip in Converters.cxx.""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" #include namespace ns55 { void take_uchar(unsigned char) {} @@ -2466,18 +2665,18 @@ def test55_qt_cache_alias_collision(self): void take_int8(int8_t) {} } """) - ns = cppyy.gbl.ns55 + ns = cppjit.gbl.ns55 # `unsigned char`: UCharConverter -- accepts 1-char str. - ns.take_uchar('e') - ns.take_uchar(101) # int also accepted as a char value + ns.take_uchar("e") + ns.take_uchar(101) # int also accepted as a char value raises(TypeError, ns.take_uchar, 1.5) # `uint8_t`: UInt8Converter -- accepts int. ns.take_uint8(101) - raises(TypeError, ns.take_uint8, 'e') + raises(TypeError, ns.take_uint8, "e") # Symmetric for the signed pair. - ns.take_schar('e') + ns.take_schar("e") ns.take_int8(101) - raises(TypeError, ns.take_int8, 'e') + raises(TypeError, ns.take_int8, "e") diff --git a/test/test_doc_features.py b/test/test_doc_features.py index b77d03a..9822605 100644 --- a/test/test_doc_features.py +++ b/test/test_doc_features.py @@ -1,9 +1,22 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, ispypy, IS_WINDOWS, IS_CLANG_REPL, IS_CLING, IS_CLANG_DEBUG, IS_MAC, IS_MAC_X86, IS_MAC_ARM, IS_LINUX_ARM, IS_VALGRIND +import sys + +import py +from pytest import mark, raises, skip +from support import ( + IS_CLANG_REPL, + IS_CLING, + IS_LINUX_ARM, + IS_MAC, + IS_MAC_ARM, + IS_VALGRIND, + IS_WINDOWS, + ispypy, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("doc_helperDict")) +test_dct = str(currpath.join("cpp/doc_helperDict")) + def setup_module(mod): setup_make("doc_helper") @@ -13,14 +26,14 @@ def setup_module(mod): class TestDOCFEATURES: def setup_class(cls): cls.test_dct = test_dct - import cppyy + import cppjit # touch __version__ as a test - assert hasattr(cppyy, '__version__') + assert hasattr(cppjit, "__version__") - cls.doc_helper = cppyy.load_reflection_info(cls.test_dct) + cls.doc_helper = cppjit.load_reflection_info(cls.test_dct) - cppyy.cppdef(""" + cppjit.cppdef(""" #include #include #include @@ -163,8 +176,7 @@ class NestedClass { """) def test_abstract_class(self): - import cppyy - from cppyy.gbl import Abstract, Concrete + from cppjit.gbl import Abstract, Concrete raises(TypeError, Abstract) assert issubclass(Concrete, Abstract) @@ -172,55 +184,53 @@ def test_abstract_class(self): assert isinstance(c, Abstract) def test_array(self): - import cppyy - from cppyy.gbl import Concrete from array import array + from cppjit.gbl import Concrete + c = Concrete() - c.array_method(array('d', [1., 2., 3., 4.]), 4) + c.array_method(array("d", [1.0, 2.0, 3.0, 4.0]), 4) raises(IndexError, c.m_data.__getitem__, 4) def test_builtin_data(self): - import cppyy + import cppjit - assert cppyy.gbl.gUint == 0 - raises(ValueError, setattr, cppyy.gbl, 'gUint', -1) + assert cppjit.gbl.gUint == 0 + raises(ValueError, setattr, cppjit.gbl, "gUint", -1) def test_casting(self): - import cppyy - from cppyy.gbl import Abstract, Concrete + import cppjit + from cppjit.gbl import Abstract, Concrete c = Concrete() - assert 'Abstract' in Concrete.show_autocast.__doc__ + assert "Abstract" in Concrete.show_autocast.__doc__ d = c.show_autocast() - assert type(d) == cppyy.gbl.Concrete + assert type(d) == cppjit.gbl.Concrete + + from cppjit import addressof, bind_object - from cppyy import addressof, bind_object e = bind_object(addressof(d), Abstract) - assert type(e) == cppyy.gbl.Abstract + assert type(e) == cppjit.gbl.Abstract def test_classes_and_structs(self): - import cppyy - from cppyy.gbl import Concrete, Namespace + from cppjit.gbl import Concrete, Namespace assert Concrete != Namespace.Concrete n = Namespace.Concrete.NestedClass() - assert 'Namespace.Concrete.NestedClass' in str(type(n)) - assert 'NestedClass' == type(n).__name__ - assert 'cppyy.gbl.Namespace.Concrete' == type(n).__module__ - assert 'Namespace::Concrete::NestedClass' == type(n).__cpp_name__ + assert "Namespace.Concrete.NestedClass" in str(type(n)) + assert "NestedClass" == type(n).__name__ + assert "cppjit.gbl.Namespace.Concrete" == type(n).__module__ + assert "Namespace::Concrete::NestedClass" == type(n).__cpp_name__ def test_data_members(self): - import cppyy - from cppyy.gbl import Concrete + from cppjit.gbl import Concrete c = Concrete() assert c.m_int == 42 - raises(TypeError, setattr, c, 'm_const_int', 71) + raises(TypeError, setattr, c, "m_const_int", 71) def test_default_arguments(self): - import cppyy - from cppyy.gbl import Concrete + from cppjit.gbl import Concrete c = Concrete() assert c.m_int == 42 @@ -231,168 +241,163 @@ def test_default_arguments(self): assert c.m_int == 27 def test_keyword_arguments(self): - import cppyy - from cppyy.gbl import Concrete + from cppjit.gbl import Concrete c = Concrete(n=17) assert c.m_int == 17 caught = False try: - c = Concrete(m=18) # unknown keyword + c = Concrete(m=18) # unknown keyword except TypeError as e: - assert 'unexpected keyword argument' in str(e) + assert "unexpected keyword argument" in str(e) caught = True assert caught == True - kwds = {'n' : 18} + kwds = {"n": 18} c = Concrete(**kwds) assert c.m_int == 18 def test_doc_strings(self): - import cppyy - from cppyy.gbl import Concrete - assert 'void Concrete::array_method(int * ad, int size)'.replace(" ", "") in Concrete.array_method.__doc__.replace(" ", "") - assert 'void Concrete::array_method(double * ad, int size)'.replace(" ", "") in Concrete.array_method.__doc__.replace(" ", "") + from cppjit.gbl import Concrete + + assert "void Concrete::array_method(int * ad, int size)".replace( + " ", "" + ) in Concrete.array_method.__doc__.replace(" ", "") + assert "void Concrete::array_method(double * ad, int size)".replace( + " ", "" + ) in Concrete.array_method.__doc__.replace(" ", "") def test_enums(self): - import cppyy pass @mark.xfail(run=False, condition=IS_MAC, reason="Seg Fault") def test_functions(self): - import cppyy - from cppyy.gbl import global_function, call_int_int_function, Namespace - assert not(global_function == Namespace.global_function) + from cppjit.gbl import Namespace, call_int_int_function, global_function + + assert not (global_function == Namespace.global_function) - assert round(global_function(1.)-2.718281828459045, 8) == 0. + assert round(global_function(1.0) - 2.718281828459045, 8) == 0.0 assert global_function(1) == 42 - assert Namespace.global_function(1) == 42*2 - assert round(Namespace.global_function(1.)-2.718281828459045*2, 8) == 0. + assert Namespace.global_function(1) == 42 * 2 + assert round(Namespace.global_function(1.0) - 2.718281828459045 * 2, 8) == 0.0 - assert round(global_function.__overload__('double')(1)-2.718281828459045, 8) == 0. + assert ( + round(global_function.__overload__("double")(1) - 2.718281828459045, 8) + == 0.0 + ) def add(a, b): - return a+b + return a + b + assert call_int_int_function(add, 3, 4) == 7 - assert call_int_int_function(lambda x, y: x*y, 3, 7) == 21 + assert call_int_int_function(lambda x, y: x * y, 3, 7) == 21 def test_inheritance(self): - import cppyy pass def test_memory(self): - import cppyy - from cppyy.gbl import Concrete + from cppjit.gbl import Concrete c = Concrete() assert c.__python_owns__ == True def test_methods(self): - import cppyy pass def test_namespaces(self): - import cppyy pass def test_null(self): - import cppyy + import cppjit - assert hasattr(cppyy, 'nullptr') - assert not cppyy.nullptr + assert hasattr(cppjit, "nullptr") + assert not cppjit.nullptr def test_operator_conversions(self): - import cppyy - from cppyy.gbl import Concrete + from cppjit.gbl import Concrete - assert str(Concrete()) == 'Hello operator const char*!' + assert str(Concrete()) == "Hello operator const char*!" def test_operator_overloads(self): - import cppyy pass def test_pointers(self): - import cppyy pass def test_pyobject(self): - import cppyy pass def test_ref(self): - import cppyy - from cppyy.gbl import Concrete from ctypes import c_uint + from cppjit.gbl import Concrete + c = Concrete() u = c_uint(0) c.uint_ref_assign(u, 42) assert u.value == 42 def test_static_data_members(self): - import cppyy - from cppyy.gbl import Concrete + from cppjit.gbl import Concrete assert Concrete.s_int == 321 Concrete().s_int = 123 assert Concrete.s_int == 123 def test_static_methods(self): - import cppyy pass def test_strings(self): - import cppyy pass def test_templated_classes(self): - import cppyy + import cppjit - assert cppyy.gbl.std.vector - assert isinstance(cppyy.gbl.std.vector(int), type) - assert type(cppyy.gbl.std.vector(int)()) == cppyy.gbl.std.vector(int) + assert cppjit.gbl.std.vector + assert isinstance(cppjit.gbl.std.vector(int), type) + assert type(cppjit.gbl.std.vector(int)()) == cppjit.gbl.std.vector(int) def test_templated_functions(self): - import cppyy pass def test_templated_methods(self): - import cppyy pass def test_typedefs(self): - import cppyy - from cppyy.gbl import Concrete, Concrete_t + from cppjit.gbl import Concrete, Concrete_t assert Concrete is Concrete_t def test_unary_operators(sef): - import cppyy pass - @mark.xfail(condition=IS_MAC, run=not IS_MAC_ARM, reason="Crashes with exception not being caught on Apple Silicon") + @mark.xfail( + condition=IS_MAC, + run=not IS_MAC_ARM, + reason="Crashes with exception not being caught on Apple Silicon", + ) def test_x_inheritance(self): - import cppyy - from cppyy.gbl import Abstract, Concrete, call_abstract_method + import cppjit + from cppjit.gbl import Abstract, Concrete, call_abstract_method class PyConcrete1(Abstract): def abstract_method(self): - return cppyy.gbl.std.string("Hello, Python World! (1)") + return cppjit.gbl.std.string("Hello, Python World! (1)") def concrete_method(self): pass @@ -433,13 +438,15 @@ def abstract_method(self): pc = PyConcrete4() assert call_abstract_method(pc) == "Hello, Python World! (4)" - @mark.xfail(condition=((IS_MAC) and IS_CLANG_REPL), reason="Fails on OSX with Clang-REPL") + @mark.xfail( + condition=((IS_MAC) and IS_CLANG_REPL), reason="Fails on OSX with Clang-REPL" + ) def test_multi_x_inheritance(self): """Multiple cross-inheritance""" - import cppyy + import cppjit - class PyConcrete(cppyy.multi(cppyy.gbl.Abstract1, cppyy.gbl.Abstract2)): + class PyConcrete(cppjit.multi(cppjit.gbl.Abstract1, cppjit.gbl.Abstract2)): def abstract_method1(self): return "first message" @@ -448,36 +455,42 @@ def abstract_method2(self): pc = PyConcrete() - assert cppyy.gbl.call_abstract_method1(pc) == "first message" - assert cppyy.gbl.call_abstract_method2(pc) == "second message" + assert cppjit.gbl.call_abstract_method1(pc) == "first message" + assert cppjit.gbl.call_abstract_method2(pc) == "second message" - @mark.xfail(run=False, condition=IS_MAC_ARM, reason="Crashes with exception not being caught on Apple Silicon") + @mark.xfail( + run=False, + condition=IS_MAC_ARM, + reason="Crashes with exception not being caught on Apple Silicon", + ) def test_exceptions(self): """Exception throwing and catching""" if ispypy: - skip('throwing exceptions terminates the process') + skip("throwing exceptions terminates the process") - import cppyy + import cppjit caught = False try: - cppyy.gbl.DocHelper.throw_an_error(1) - except cppyy.gbl.SomeError as e: - assert 'this is an error' in str(e) - assert e.what() == 'this is an error' + cppjit.gbl.DocHelper.throw_an_error(1) + except cppjit.gbl.SomeError as e: + assert "this is an error" in str(e) + assert e.what() == "this is an error" caught = True assert caught == True caught = False - for exc_type in (cppyy.gbl.SomeOtherError, - cppyy.gbl.SomeError, - cppyy.gbl.std.exception, - Exception, - BaseException): + for exc_type in ( + cppjit.gbl.SomeOtherError, + cppjit.gbl.SomeError, + cppjit.gbl.std.exception, + Exception, + BaseException, + ): caught = False try: - cppyy.gbl.DocHelper.throw_an_error(0) + cppjit.gbl.DocHelper.throw_an_error(0) except exc_type as e: caught = True assert caught == True @@ -487,16 +500,16 @@ def test_exceptions(self): @mark.skipif(IS_MAC and IS_CLING, reason="setup class fails with OS X cling") class TestTUTORIALFEATURES: def setup_class(cls): - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" class Integer1 { public: Integer1(int i) : m_data(i) {} int m_data; };""") - cppyy.cppdef(""" + cppjit.cppdef(""" namespace Math { class Integer2 : public Integer1 { public: @@ -505,7 +518,7 @@ class Integer2 : public Integer1 { }; }""") - cppyy.cppdef(""" + cppjit.cppdef(""" namespace Zoo { enum EAnimal { eLion, eMouse }; @@ -545,20 +558,20 @@ class Mouse : public Animal { } """) - # pythonize the animal release function to take ownership on return - cppyy.gbl.Zoo.release_animal.__creates__ = True + # pythonize the animal release function to take ownership on return + cppjit.gbl.Zoo.release_animal.__creates__ = True def test01_class_existence(self): """Existence and importability of created class""" - import cppyy + import cppjit - assert cppyy.gbl.Integer1 - assert issubclass(cppyy.gbl.Integer1, object) + assert cppjit.gbl.Integer1 + assert issubclass(cppjit.gbl.Integer1, object) - from cppyy.gbl import Integer1 + from cppjit.gbl import Integer1 - assert cppyy.gbl.Integer1 is Integer1 + assert cppjit.gbl.Integer1 is Integer1 i = Integer1(42) assert isinstance(i, Integer1) @@ -566,19 +579,23 @@ def test01_class_existence(self): def test02_python_introspection(self): """Introspection of newly created class/instances""" - from cppyy.gbl import Integer1 + from cppjit.gbl import Integer1 i = Integer1(42) - assert hasattr(i, 'm_data') + assert hasattr(i, "m_data") assert not isinstance(i, int) assert isinstance(i, Integer1) - @mark.xfail(run=(not IS_MAC and IS_CLANG_REPL), condition=IS_MAC and IS_CLING, reason= "Crashes on OS X Cling") + @mark.xfail( + run=(not IS_MAC and IS_CLANG_REPL), + condition=IS_MAC and IS_CLING, + reason="Crashes on OS X Cling", + ) def test03_STL_containers(self): """Instantiate STL containers with new class""" - from cppyy.gbl import Integer1 - from cppyy.gbl.std import vector + from cppjit.gbl import Integer1 + from cppjit.gbl.std import vector v = vector[Integer1]() @@ -587,26 +604,28 @@ def test03_STL_containers(self): def test04_pretty_repr(self): """Create a pretty repr for the new class""" - from cppyy.gbl import Integer1 + from cppjit.gbl import Integer1 Integer1.__repr__ = lambda self: repr(self.m_data) - assert str([Integer1(j) for j in range(10)]) == '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' + assert str([Integer1(j) for j in range(10)]) == "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" def test05_pythonizer(self): """Implement and test a pythonizor""" - import cppyy + import cppjit def pythonizor(klass, name): - if name == 'Integer2': + if name == "Integer2": klass.__repr__ = lambda self: repr(self.m_data) - cppyy.py.add_pythonization(pythonizor, 'Math') + cppjit.py.add_pythonization(pythonizor, "Math") - Integer2 = cppyy.gbl.Math.Integer2 # first time a new namespace is used, it can not be imported from + Integer2 = ( + cppjit.gbl.Math.Integer2 + ) # first time a new namespace is used, it can not be imported from v = [Integer2(j) for j in range(10)] - assert str(v) == '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' + assert str(v) == "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" i2 = Integer2(13) assert int(i2) == 13 @@ -615,17 +634,17 @@ def pythonizor(klass, name): def test06_add_operator(self): """Add operator+""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace Math { Integer2 operator+(const Integer2& left, const Integer1& right) { return left.m_data + right.m_data; } }""") - from cppyy.gbl import Integer1 - from cppyy.gbl.Math import Integer2 + from cppjit.gbl import Integer1 + from cppjit.gbl.Math import Integer2 i = Integer1(42) i2 = Integer2(13) @@ -636,7 +655,7 @@ def test06_add_operator(self): def test07_run_zoo(self): """Bunch of zoo animals running around""" - from cppyy.gbl import Zoo + from cppjit.gbl import Zoo assert raises(TypeError, Zoo.Animal) @@ -650,11 +669,11 @@ def test07_run_zoo(self): assert lion.__python_owns__ assert mouse.__python_owns__ - assert mouse.make_sound() == 'peep!' - assert lion.make_sound() == 'growl!' + assert mouse.make_sound() == "peep!" + assert lion.make_sound() == "growl!" Zoo.Lion.s_lion_sound = "mooh!" - assert lion.make_sound() == 'mooh!' + assert lion.make_sound() == "mooh!" assert Zoo.identify_animal(mouse) == "the animal is a mouse" assert Zoo.identify_animal(lion) == "the animal is a lion" @@ -663,9 +682,9 @@ def test07_run_zoo(self): def test08_shared_ptr(self): """Shared pointer transparency""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace Zoo { std::shared_ptr free_lion{new Lion{}}; @@ -675,12 +694,17 @@ def test08_shared_ptr(self): } """) - from cppyy.gbl import Zoo + from cppjit.gbl import Zoo - assert type(Zoo.free_lion).__name__ == 'Lion' + assert type(Zoo.free_lion).__name__ == "Lion" smart_lion = Zoo.free_lion.__smartptr__() - assert type(smart_lion).__name__ in ['shared_ptr', 'std::shared_ptr', 'shared_ptr', 'std::shared_ptr'] + assert type(smart_lion).__name__ in [ + "shared_ptr", + "std::shared_ptr", + "shared_ptr", + "std::shared_ptr", + ] assert Zoo.identify_animal(Zoo.free_lion) == "the animal is a lion" assert Zoo.identify_animal_smart(Zoo.free_lion) == "the animal is a lion" @@ -688,61 +712,67 @@ def test08_shared_ptr(self): def test09_templated_function(self): """Templated free function""" - import cppyy + import cppjit - mul = cppyy.gbl.multiply + mul = cppjit.gbl.multiply - assert 'multiply' in cppyy.gbl.__dict__ + assert "multiply" in cppjit.gbl.__dict__ - assert mul(1, 2) == 2 - assert 'multiply' in map(lambda x: x.replace(" ", ""), cppyy.gbl.__dict__) - assert mul(1., 5) == 5. - assert 'multiply' in map(lambda x: x.replace(" ", ""), cppyy.gbl.__dict__) + assert mul(1, 2) == 2 + assert "multiply" in map( + lambda x: x.replace(" ", ""), cppjit.gbl.__dict__ + ) + assert mul(1.0, 5) == 5.0 + assert "multiply" in map( + lambda x: x.replace(" ", ""), cppjit.gbl.__dict__ + ) - assert mul[int] (1, 1) == 1 - assert 'multiply' in map(lambda x: x.replace(" ", ""), cppyy.gbl.__dict__) + assert mul[int](1, 1) == 1 + assert "multiply" in map(lambda x: x.replace(" ", ""), cppjit.gbl.__dict__) assert mul[int, int](1, 1) == 1 - assert 'multiply' in map(lambda x: x.replace(" ", ""), cppyy.gbl.__dict__) + assert "multiply" in map( + lambda x: x.replace(" ", ""), cppjit.gbl.__dict__ + ) - # make sure cached values are actually looked up - old = getattr(cppyy.gbl, 'multiply') - setattr(cppyy.gbl, 'multiply', staticmethod(lambda x, y: 2*x*y)) + # make sure cached values are actually looked up + old = getattr(cppjit.gbl, "multiply") + setattr(cppjit.gbl, "multiply", staticmethod(lambda x, y: 2 * x * y)) assert mul[int, int](2, 2) == 8 - setattr(cppyy.gbl, 'multiply', old) + setattr(cppjit.gbl, "multiply", old) assert mul[int, int](2, 2) == 4 assert raises(TypeError, mul[int, int, int, int], 1, 1) - assert raises(TypeError, mul[int, int], 1, 1.) + assert raises(TypeError, mul[int, int], 1, 1.0) assert type(mul[int, int, float](1, 1)) == float # TODO: the following error message is rather confusing :( - assert raises(TypeError, mul[int, int], 1, 'a') + assert raises(TypeError, mul[int, int], 1, "a") - assert mul['double, double, double'](1., 5) == 5. + assert mul["double, double, double"](1.0, 5) == 5.0 @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test10_stl_algorithm(self): """STL algorithm on std::string""" - import cppyy + import cppjit - cppstr = cppyy.gbl.std.string - n = cppstr('this is a C++ string') - assert n == 'this is a C++ string' - n.erase(cppyy.gbl.std.remove(n.begin(), n.end(), cppstr.value_type(' '))) - assert n == 'thisisaC++stringing' + cppstr = cppjit.gbl.std.string + n = cppstr("this is a C++ string") + assert n == "this is a C++ string" + n.erase(cppjit.gbl.std.remove(n.begin(), n.end(), cppstr.value_type(" "))) + assert n == "thisisaC++stringing" @mark.skipif(IS_MAC and IS_CLING, reason="setup class fails with OS X cling") class TestADVERTISED: def setup_class(cls): - import cppyy + pass def test01_reduction_of_overloads(self): """Reduce available overloads to 1""" - import cppyy + import cppjit - cppyy.cppdef("""namespace Advert01 { + cppjit.cppdef("""namespace Advert01 { class A { public: A(int) {} @@ -750,23 +780,25 @@ class A { }; }""") def pythonize_A(klass, name): - if name == 'A': + if name == "A": klass.__init__ = klass.__init__.__overload__("int") - cppyy.py.add_pythonization(pythonize_A, 'Advert01') + cppjit.py.add_pythonization(pythonize_A, "Advert01") - from cppyy.gbl import Advert01 + from cppjit.gbl import Advert01 assert Advert01.A(1) - raises(TypeError, Advert01.A, 1.) + raises(TypeError, Advert01.A, 1.0) def test02_use_c_void_p(self): """Use of opaque handles and ctypes.c_void_p""" - import cppyy, ctypes + import ctypes - ### void pointer as opaque handle - cppyy.cppdef("""namespace Advert02 { + import cppjit + + ### void pointer as opaque handle + cppjit.cppdef("""namespace Advert02 { typedef void* PicamHandle; void Picam_OpenFirstCamera(PicamHandle* cam) { *cam = new int(42); @@ -780,16 +812,16 @@ def test02_use_c_void_p(self): } }""") - from cppyy.gbl import Advert02 + from cppjit.gbl import Advert02 assert Advert02.PicamHandle - # first approach - cam = Advert02.PicamHandle(cppyy.nullptr) + # first approach + cam = Advert02.PicamHandle(cppjit.nullptr) Advert02.Picam_OpenFirstCamera(cam) assert Advert02.Picam_CloseCamera(cam) - # second approch + # second approch cam = ctypes.c_void_p() Advert02.Picam_OpenFirstCamera(cam) assert Advert02.Picam_CloseCamera(cam) @@ -797,9 +829,11 @@ def test02_use_c_void_p(self): def test03_use_of_ctypes_and_enum(self): """Use of (opaque) enum through ctypes.c_void_p""" - import cppyy, ctypes + import ctypes + + import cppjit - cppyy.cppdef("""namespace Advert03 { + cppjit.cppdef("""namespace Advert03 { enum SomeEnum1 { AA = -1, BB = 42 }; void build_enum_array1(SomeEnum1** ptr, int* sz) { *ptr = (SomeEnum1*)malloc(sizeof(SomeEnum1)*4); @@ -814,44 +848,57 @@ def test03_use_of_ctypes_and_enum(self): (*ptr)[0] = CC; (*ptr)[1] = DD; (*ptr)[2] = CC; (*ptr)[3] = DD; } }""") - # enum through void pointer (b/c underlying type unknown) - vp = ctypes.c_void_p(0); cnt = ctypes.c_int(0) - cppyy.gbl.Advert03.build_enum_array2(vp, ctypes.pointer(cnt)) + # enum through void pointer (b/c underlying type unknown) + vp = ctypes.c_void_p(0) + cnt = ctypes.c_int(0) + cppjit.gbl.Advert03.build_enum_array2(vp, ctypes.pointer(cnt)) assert cnt.value == 4 - vp = ctypes.c_void_p(0); cnt = ctypes.c_int(0) - cppyy.gbl.Advert03.build_enum_array1(vp, ctypes.pointer(cnt)) + vp = ctypes.c_void_p(0) + cnt = ctypes.c_int(0) + cppjit.gbl.Advert03.build_enum_array1(vp, ctypes.pointer(cnt)) assert cnt.value == 4 - # helper to convert the enum array pointer & size to something packaged - cppyy.cppdef("""namespace Advert03 { + # helper to convert the enum array pointer & size to something packaged + cppjit.cppdef("""namespace Advert03 { std::vector ptr2vec(intptr_t ptr, int sz) { std::vector result{(SomeEnum1*)ptr, (SomeEnum1*)ptr+sz}; free((void*)ptr); return result; } }""") - assert list(cppyy.gbl.Advert03.ptr2vec(vp.value, cnt.value)) == [-1, 42, -1, 42] - - # 2nd approach through low level cast - vp = ctypes.pointer(cppyy.gbl.Advert03.SomeEnum2.__ctype__(0)); cnt = ctypes.c_int(0) - cppyy.gbl.Advert03.build_enum_array2(vp, ctypes.pointer(cnt)) + assert list(cppjit.gbl.Advert03.ptr2vec(vp.value, cnt.value)) == [ + -1, + 42, + -1, + 42, + ] + + # 2nd approach through low level cast + vp = ctypes.pointer(cppjit.gbl.Advert03.SomeEnum2.__ctype__(0)) + cnt = ctypes.c_int(0) + cppjit.gbl.Advert03.build_enum_array2(vp, ctypes.pointer(cnt)) assert cnt.value == 4 - import cppyy.ll - arr = cppyy.ll.cast['Advert03::SomeEnum2*'](vp) + import cppjit.ll + + arr = cppjit.ll.cast["Advert03::SomeEnum2*"](vp) arr.reshape((cnt.value,)) assert list(arr) == [1, 42, 1, 42] - cppyy.gbl.free(vp) + cppjit.gbl.free(vp) - @mark.xfail(run=(not IS_MAC and IS_CLANG_REPL), condition=IS_MAC and IS_CLING, reason= "Crashes on OS X Cling") + @mark.xfail( + run=(not IS_MAC and IS_CLANG_REPL), + condition=IS_MAC and IS_CLING, + reason="Crashes on OS X Cling", + ) def test04_ptr_ptr_python_owns(self): """Example of ptr-ptr use where python owns""" - import cppyy + import cppjit - cppyy.cppdef("""namespace Advert04 { + cppjit.cppdef("""namespace Advert04 { struct SomeStruct { SomeStruct(int i) : i(i) {} int i; @@ -863,31 +910,34 @@ def test04_ptr_ptr_python_owns(self): return total; } }""") - cppyy.gbl.Advert04 - from cppyy.gbl.Advert04 import SomeStruct, count_them + cppjit.gbl.Advert04 + from cppjit.gbl.Advert04 import SomeStruct, count_them - # initialization on python side - v = cppyy.gbl.std.vector['Advert04::SomeStruct*']() + # initialization on python side + v = cppjit.gbl.std.vector["Advert04::SomeStruct*"]() v._l = [SomeStruct(i) for i in range(10)] - for s in v._l: v.push_back(s) + for s in v._l: + v.push_back(s) assert count_them(v.data(), v.size()) == sum(range(10)) - # initialization on C++ side - cppyy.cppdef("""namespace Advert04 { + # initialization on C++ side + cppjit.cppdef("""namespace Advert04 { void ptr2ptr_init(SomeStruct** ref) { *ref = new SomeStruct(42); } }""") - s = cppyy.bind_object(cppyy.nullptr, SomeStruct) - cppyy.gbl.Advert04.ptr2ptr_init(s) + s = cppjit.bind_object(cppjit.nullptr, SomeStruct) + cppjit.gbl.Advert04.ptr2ptr_init(s) assert s.i == 42 def test05_ptr_ptr_with_array(self): """Example of ptr-ptr with array""" - import cppyy, ctypes + import ctypes + + import cppjit - cppyy.cppdef("""namespace Advert05 { + cppjit.cppdef("""namespace Advert05 { struct SomeStruct { int i; }; void create_them(SomeStruct** them, int* sz) { @@ -896,24 +946,29 @@ def test05_ptr_ptr_with_array(self): for (int i = 0; i < *sz; ++i) (*them)[i].i = i*i; } }""") - cppyy.gbl.Advert05 - from cppyy.gbl.Advert05 import SomeStruct, create_them + cppjit.gbl.Advert05 + from cppjit.gbl.Advert05 import SomeStruct, create_them - ptr = cppyy.bind_object(cppyy.nullptr, SomeStruct) + ptr = cppjit.bind_object(cppjit.nullptr, SomeStruct) sz = ctypes.c_int(0) create_them(ptr, sz) - arr = cppyy.bind_object(cppyy.addressof(ptr), cppyy.gbl.std.array[SomeStruct, sz.value]) + arr = cppjit.bind_object( + cppjit.addressof(ptr), cppjit.gbl.std.array[SomeStruct, sz.value] + ) total = 0 - for s in arr: total += s.i + for s in arr: + total += s.i assert total == 14 def test06_c_char_p(self): """Example of ctypes.c_char_p usage""" - import cppyy, ctypes + import ctypes - cppyy.cppdef("""namespace Advert06 { + import cppjit + + cppjit.cppdef("""namespace Advert06 { intptr_t createit(const char** out) { *out = (char*)malloc(4); return (intptr_t)*out; @@ -924,8 +979,8 @@ def test06_c_char_p(self): return out; } }""") - cppyy.gbl.Advert06 - from cppyy.gbl.Advert06 import createit, destroyit + cppjit.gbl.Advert06 + from cppjit.gbl.Advert06 import createit, destroyit ptr = ctypes.c_char_p() val = createit(ptr) @@ -934,13 +989,14 @@ def test06_c_char_p(self): def test07_array_of_arrays(self): """Example of array of array usage""" - import cppyy - import cppyy.ll + import cppjit + import cppjit.ll NREADOUTS = 4 NPIXELS = 16 - cppyy.cppdef(""" + cppjit.cppdef( + """ #define NREADOUTS %d #define NPIXELS %d namespace Advert07 { @@ -958,21 +1014,23 @@ def test07_array_of_arrays(self): delete [] (uint16_t**)fField; } void* fField; - }; }""" % (NREADOUTS, NPIXELS)) + }; }""" + % (NREADOUTS, NPIXELS) + ) - s = cppyy.gbl.Advert07.S() + s = cppjit.gbl.Advert07.S() for i in range(NREADOUTS): - image_array = cppyy.ll.cast['uint16_t*'](s.fField[i]) - for j in range (NPIXELS): - assert image_array[j] == i*NPIXELS+j + image_array = cppjit.ll.cast["uint16_t*"](s.fField[i]) + for j in range(NPIXELS): + assert image_array[j] == i * NPIXELS + j def test08_voidptr_array(self): """Example of access to array of void ptrs""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace VoidPtrArray { typedef struct _name { _name() { p[0] = (void*)0x1; p[1] = (void*)0x2; p[2] = (void*)0x3; } @@ -980,19 +1038,23 @@ def test08_voidptr_array(self): } name; }""") - n = cppyy.gbl.VoidPtrArray.name() + n = cppjit.gbl.VoidPtrArray.name() assert n.p[0] == 0x1 assert n.p[1] == 0x2 assert n.p[2] == 0x3 assert len(n.p) == 3 - @mark.xfail(condition=(IS_CLANG_REPL and IS_MAC), run=False, reason="Crashes with ClangRepl with 'toString not implemented'") + @mark.xfail( + condition=(IS_CLANG_REPL and IS_MAC), + run=False, + reason="Crashes with ClangRepl with 'toString not implemented'", + ) def test09_custom_str(self): """Example of customized str""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace TopologicCore { class Shell { @@ -1002,24 +1064,23 @@ class Shell { } }; }""") - def pythonize_topologic_printing(klass, name): - if 'GetTypeAsString' in klass.__dict__: + if "GetTypeAsString" in klass.__dict__: klass.__str__ = lambda self: str(self.GetTypeAsString()) - cppyy.py.add_pythonization(pythonize_topologic_printing, 'TopologicCore') + cppjit.py.add_pythonization(pythonize_topologic_printing, "TopologicCore") - s = cppyy.gbl.TopologicCore.Shell() + s = cppjit.gbl.TopologicCore.Shell() assert str(s) == "hi there!" - @mark.xfail(condition = IS_MAC, reason = "Fails on OS X") + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test10_llvm_blog(self): """Test code posted in the LLVM blog posting""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace LLVMBlog { template class Producer { public: @@ -1045,7 +1106,7 @@ class Consumer { } }; } """) - ns = cppyy.gbl.LLVMBlog + ns = cppjit.gbl.LLVMBlog def factory(base_v, *derived_v): class _F(ns.Producer[type(base_v)]): @@ -1063,18 +1124,18 @@ def produce_imp(self): consumer = ns.Consumer() - assert consumer.consume(factory("hello ", 42)) == 'received: "hello 42"' - assert consumer.consume(factory(3., 0.14, 0.0015)) == 'received: "3.1415"' + assert consumer.consume(factory("hello ", 42)) == 'received: "hello 42"' + assert consumer.consume(factory(3.0, 0.14, 0.0015)) == 'received: "3.1415"' # The series of tests below mostly exists already in other places, but these -# were used as examples for the CaaS' cppyy presentation and are preserved here. +# were used as examples for the CaaS' cppjit presentation and are preserved here. @mark.skipif(IS_MAC and IS_CLING, reason="setup class fails with OS X cling") class TestTALKEXAMPLES: def setup_class(cls): - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace talk_examples { struct MyClass { MyClass(int i) : fData(i) {} @@ -1085,16 +1146,16 @@ def setup_class(cls): int fData; };}""") - cppyy.gbl.talk_examples + cppjit.gbl.talk_examples # @mark.skip def test_template_instantiation(self): """Run-time template instantiation example""" - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC - v = cppyy.gbl.std.vector[CC.MyClass]() + v = cppjit.gbl.std.vector[CC.MyClass]() for i in range(10): v.emplace_back(i) @@ -1106,10 +1167,10 @@ def test_template_instantiation(self): def test_cross_inheritance(self): """Cross-inheritance example""" - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace talk_examples { int callb(MyClass* m, int i) { return m->add(i); @@ -1117,7 +1178,7 @@ def test_cross_inheritance(self): class PyMyClass(CC.MyClass): def add(self, i): - return self.fData + 2*i + return self.fData + 2 * i m = PyMyClass(1) assert CC.callb(m, 2) == 5 @@ -1126,8 +1187,8 @@ def add(self, i): def test_cross_and_templates(self): """Template instantiation with cross-inheritance example""" - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC class PyMyClass(CC.MyClass): def __init__(self, data, extra): @@ -1135,21 +1196,21 @@ def __init__(self, data, extra): self.extra = extra def add(self, i): - return self.fData + self.extra + 2*i + return self.fData + self.extra + 2 * i - v = cppyy.gbl.std.vector[PyMyClass]() + v = cppjit.gbl.std.vector[PyMyClass]() v.push_back(PyMyClass(4, 42)) - assert v.back().add(17) == 4+42+2*17 + assert v.back().add(17) == 4 + 42 + 2 * 17 @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test_fallbacks(self): """Template instantation switches based on value sizes""" - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace talk_examples { template T passT(T t) { @@ -1157,18 +1218,18 @@ def test_fallbacks(self): }}""") assert CC.passT(1) == 1 - assert 'int' in CC.passT.__doc__ - assert CC.passT(2**64-1) == 2**64-1 - assert 'unsigned long long' in CC.passT.__doc__ + assert "int" in CC.passT.__doc__ + assert CC.passT(2**64 - 1) == 2**64 - 1 + assert "unsigned long long" in CC.passT.__doc__ @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crashes pytest on Linux ARM") def test_callbacks(self): """Function callback example""" - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace talk_examples { typedef int (*P)(int); int callPtr(P f, int i) { @@ -1181,52 +1242,49 @@ def test_callbacks(self): }}""") def f(val): - return 2*val + return 2 * val assert CC.callPtr(f, 2) == 4 assert CC.callFun(f, 3) == 6 - assert CC.callPtr(lambda i: 5*i, 4) == 20 - assert CC.callFun(lambda i: 6*i, 4) == 24 - - @mark.xfail(run=False, condition=IS_VALGRIND and IS_LINUX_ARM, reason="Crashes on Valgrind-ARM") + assert CC.callPtr(lambda i: 5 * i, 4) == 20 + assert CC.callFun(lambda i: 6 * i, 4) == 24 + + @mark.xfail( + run=False, + condition=IS_VALGRIND and IS_LINUX_ARM, + reason="Crashes on Valgrind-ARM", + ) def test_templated_callback(self): """Templated callback example""" - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace talk_examples { template R callT(R (*f)(U...), A&&... args) { return f(args...); }}""") - if sys.hexversion < 0x3050000: - def ann_f1(arg): - return 3.1415*arg - ann_f1.__annotations__ = {'arg': 'int', 'return': 'double'} - def ann_f2(arg1, arg2): - return 3*arg1*arg2 - ann_f2.__annotations__ = {'arg1': 'int', 'arg2' : 'int', 'return': 'int'} - else: - oldp = sys.path[:] - sys.path.append('.') - from doc_args_funcs import ann_f1, ann_f2 - sys.path = oldp + def ann_f1(arg: "int") -> "double": # noqa: F821 + return 3.1415 * arg + + def ann_f2(arg1: "int", arg2: "int") -> "int": + return 3 * arg1 * arg2 - assert round(CC.callT(ann_f1, 2)-2*3.1415, 5) == 0. + assert round(CC.callT(ann_f1, 2) - 2 * 3.1415, 5) == 0.0 - assert CC.callT(ann_f2, 6, 7) == 3*6*7 - assert round(CC.callT(ann_f1, 2)-2*3.1415, 5) == 0. + assert CC.callT(ann_f2, 6, 7) == 3 * 6 * 7 + assert round(CC.callT(ann_f1, 2) - 2 * 3.1415, 5) == 0.0 def test_autocast_and_identiy(self): """Auto-cast and identiy preservation example""" - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace talk_examples { struct Base { virtual ~Base() {} @@ -1246,12 +1304,12 @@ def test_exceptions(self): """Exceptions example""" if ispypy or IS_WINDOWS: - skip('throwing exceptions from the JIT terminates the process') + skip("throwing exceptions from the JIT terminates the process") - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace talk_examples { class MyException : public std::exception { public: @@ -1270,10 +1328,10 @@ class MyException : public std::exception { def test_unicode(self): """Unicode non-UTF-8 example""" - import cppyy - import cppyy.gbl.talk_examples as CC + import cppjit + import cppjit.gbl.talk_examples as CC - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace talk_examples { template std::string to_str(const T& chars) { @@ -1291,11 +1349,11 @@ def test_unicode(self): }}""") with raises(Exception) as exc_info: - CC.gbk_chinese().decode('utf-8') + CC.gbk_chinese().decode("utf-8") assert isinstance(exc_info.value, (UnicodeDecodeError, LookupError)) - assert CC.gbk_chinese() == u'\u4e2d\u6587'.encode('gbk') + assert CC.gbk_chinese() == "\u4e2d\u6587".encode("gbk") if 0x3000000 <= sys.hexversion: - assert CC.utf8_chinese() == u'\u4e2d\u6587' + assert CC.utf8_chinese() == "\u4e2d\u6587" else: - assert CC.utf8_chinese() == b'\xe4\xb8\xad\xe6\x96\x87' + assert CC.utf8_chinese() == b"\xe4\xb8\xad\xe6\x96\x87" diff --git a/test/test_eigen.py b/test/test_eigen.py index 8f51e86..ab33c34 100644 --- a/test/test_eigen.py +++ b/test/test_eigen.py @@ -1,13 +1,15 @@ -import py, os, sys -from pytest import mark, raises -from support import setup_make, IS_CLANG_REPL, IS_CLING, IS_MAC_X86 +import os -inc_paths = [os.path.join(os.path.sep, 'usr', 'include'), - os.path.join(os.path.sep, 'usr', 'local', 'include')] +from pytest import mark + +inc_paths = [ + os.path.join(os.path.sep, "usr", "include"), + os.path.join(os.path.sep, "usr", "local", "include"), +] eigen_path = None for p in inc_paths: - p = os.path.join(p, 'eigen3') + p = os.path.join(p, "eigen3") if os.path.exists(p): eigen_path = p @@ -15,33 +17,35 @@ @mark.skipif(eigen_path is None, reason="Eigen not found") class TestEIGEN: def setup_class(cls): - import cppyy, warnings + import warnings + + import cppjit - cppyy.add_include_path(eigen_path) + cppjit.add_include_path(eigen_path) with warnings.catch_warnings(): - warnings.simplefilter('ignore') - cppyy.include('Eigen/Dense') + warnings.simplefilter("ignore") + cppjit.include("Eigen/Dense") def test01_simple_matrix_and_vector(self): """Basic creation of an Eigen::Matrix and Eigen::Vector""" - import cppyy + import cppjit - a = cppyy.gbl.Eigen.Matrix['double', 2, 2]() + a = cppjit.gbl.Eigen.Matrix["double", 2, 2]() assert a.rows() == 2 assert a.cols() == 2 - b = cppyy.gbl.Eigen.MatrixXd(2, 2) - b[0,0] = 3 - assert b(0,0) == 3. - b[1,0] = 2.5 - assert b(1,0) == 2.5 - b[0,1] = -1 - assert b(0,1) == -1. - b[1,1] = b(1,0) + b(0,1) - assert b(1,1) == b[1,0] + b[0,1] - - v = cppyy.gbl.Eigen.VectorXd(2) + b = cppjit.gbl.Eigen.MatrixXd(2, 2) + b[0, 0] = 3 + assert b(0, 0) == 3.0 + b[1, 0] = 2.5 + assert b(1, 0) == 2.5 + b[0, 1] = -1 + assert b(0, 1) == -1.0 + b[1, 1] = b(1, 0) + b(0, 1) + assert b(1, 1) == b[1, 0] + b[0, 1] + + v = cppjit.gbl.Eigen.VectorXd(2) v[0] = 4 assert v[0] == 4 and v(0) == 4 v[1] = v(0) - 1 @@ -50,45 +54,45 @@ def test01_simple_matrix_and_vector(self): def test02_comma_insertion(self): """Comma insertion overload""" - import cppyy + import cppjit - m = cppyy.gbl.Eigen.MatrixXd(2, 5) + m = cppjit.gbl.Eigen.MatrixXd(2, 5) assert m.rows() == 2 assert m.cols() == 5 # TODO: this calls a conversion to int ... - #m.resize(cppyy.gbl.Eigen.NoChange_t(), 3) - #assert m.rows() == 2 - #assert m.cols() == 3 + # m.resize(cppjit.gbl.Eigen.NoChange_t(), 3) + # assert m.rows() == 2 + # assert m.cols() == 3 m.resize(4, 3) assert m.rows() == 4 assert m.cols() == 3 # equivalent of 'm << 12, 11, ..., 1' in C++ - c = (m << 12) + c = m << 12 for i in range(11, 0, -1): c = c.__comma__(i) - assert m[0, 0] == 12. - assert m[0, 1] == 11. - assert m[0, 2] == 10. - assert m[1, 0] == 9. - assert m[1, 1] == 8. - assert m[1, 2] == 7. - assert m[2, 0] == 6. - assert m[2, 1] == 5. - assert m[2, 2] == 4. - assert m[3, 0] == 3. - assert m[3, 1] == 2. - assert m[3, 2] == 1. - - matA = cppyy.gbl.Eigen.MatrixXf(2, 2) + assert m[0, 0] == 12.0 + assert m[0, 1] == 11.0 + assert m[0, 2] == 10.0 + assert m[1, 0] == 9.0 + assert m[1, 1] == 8.0 + assert m[1, 2] == 7.0 + assert m[2, 0] == 6.0 + assert m[2, 1] == 5.0 + assert m[2, 2] == 4.0 + assert m[3, 0] == 3.0 + assert m[3, 1] == 2.0 + assert m[3, 2] == 1.0 + + matA = cppjit.gbl.Eigen.MatrixXf(2, 2) (matA << 1).__comma__(2).__comma__(3).__comma__(4) - matB = cppyy.gbl.Eigen.MatrixXf(4, 4) + matB = cppjit.gbl.Eigen.MatrixXf(4, 4) # TODO: the insertion operator is a template that expect only the base class - #(matB << matA).__comma__(matA/10).__comma__(matA/10).__comma__(matA) + # (matB << matA).__comma__(matA/10).__comma__(matA/10).__comma__(matA) - v = cppyy.gbl.Eigen.VectorXd(2) + v = cppjit.gbl.Eigen.VectorXd(2) v.resize(5) assert v.size() == 5 assert v.rows() == 5 @@ -96,18 +100,19 @@ def test02_comma_insertion(self): # the following is equivalent to: # (v << 1).__comma__(2).__comma__(3).__comma__(4).__comma__(5) from functools import reduce + reduce(lambda x, y: x.__comma__(y), range(2, 6), v << 1) for i in range(5): - assert v(i) == i+1 + assert v(i) == i + 1 def test03_matrices_and_vectors(self): """Matrices and vectors""" - import cppyy + import cppjit - # 'dynamic' matrices/vectors - MatrixXd = cppyy.gbl.Eigen.MatrixXd - VectorXd = cppyy.gbl.Eigen.VectorXd + # 'dynamic' matrices/vectors + MatrixXd = cppjit.gbl.Eigen.MatrixXd + VectorXd = cppjit.gbl.Eigen.VectorXd m = MatrixXd.Random(3, 3) assert m.rows() == 3 @@ -115,29 +120,28 @@ def test03_matrices_and_vectors(self): m = (m + MatrixXd.Constant(3, 3, 1.2)) * 50 v = VectorXd(3) - (v << 1).__comma__(2).__comma__(3); + (v << 1).__comma__(2).__comma__(3) + assert (m * v).size() == v.size() - assert (m*v).size() == v.size() - - # 'static' matrices/vectors - Matrix3d = cppyy.gbl.Eigen.Matrix3d - Vector3d = cppyy.gbl.Eigen.Vector3d + # 'static' matrices/vectors + Matrix3d = cppjit.gbl.Eigen.Matrix3d + Vector3d = cppjit.gbl.Eigen.Vector3d m = Matrix3d.Random() m = (m + Matrix3d.Constant(1.2)) * 50 v = Vector3d(1, 2, 3) - assert (m*v).size() == v.size() + assert (m * v).size() == v.size() def test04_resizing_through_assignment(self): """Resize on assignment""" - import cppyy + import cppjit - a = cppyy.gbl.Eigen.MatrixXf(2, 2) + a = cppjit.gbl.Eigen.MatrixXf(2, 2) assert a.size() == 4 - b = cppyy.gbl.Eigen.MatrixXf(3, 3) + b = cppjit.gbl.Eigen.MatrixXf(3, 3) assert b.size() == 9 a.__assign__(b) @@ -147,20 +151,21 @@ def test04_resizing_through_assignment(self): @mark.skipif(eigen_path is None, reason="Eigen not found") class TestEIGEN_REGRESSIOn: def setup_class(cls): - import cppyy, warnings + import warnings + + import cppjit - cppyy.add_include_path(eigen_path) + cppjit.add_include_path(eigen_path) with warnings.catch_warnings(): - warnings.simplefilter('ignore') - cppyy.include('Eigen/Dense') + warnings.simplefilter("ignore") + cppjit.include("Eigen/Dense") def test01_use_of_Map(self): """Use of Map (used to crash)""" - import cppyy - from cppyy.gbl import Eigen + from cppjit.gbl import Eigen assert Eigen.VectorXd assert Eigen.Map - assert Eigen.Map[Eigen.VectorXd] # used to crash + assert Eigen.Map[Eigen.VectorXd] # used to crash diff --git a/test/test_fragile.py b/test/test_fragile.py index 5c39c51..181a38b 100644 --- a/test/test_fragile.py +++ b/test/test_fragile.py @@ -1,10 +1,21 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, ispypy, IS_LINUX, IS_WINDOWS, IS_MAC_ARM, IS_CLANG_REPL, IS_MAC - +import os +import sys + +import py +from pytest import mark, raises, skip +from support import ( + IS_CLANG_REPL, + IS_LINUX, + IS_MAC, + IS_MAC_ARM, + IS_WINDOWS, + ispypy, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("fragileDict")) +test_dct = str(currpath.join("cpp/fragileDict")) + def setup_module(mod): setup_make("fragile") @@ -13,120 +24,122 @@ def setup_module(mod): class TestFRAGILE: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.fragile = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.fragile = cppjit.load_reflection_info(cls.test_dct) def test01_load_failure(self): """Test failure to load dictionary""" - import cppyy - raises(RuntimeError, cppyy.load_reflection_info, "does_not_exist") + import cppjit + + raises(RuntimeError, cppjit.load_reflection_info, "does_not_exist") try: - cppyy.load_reflection_info("does_not_exist") + cppjit.load_reflection_info("does_not_exist") except RuntimeError as e: assert "does_not_exist" in str(e) def test02_missing_classes(self): """Test (non-)access to missing classes""" - import cppyy + import cppjit - raises(AttributeError, getattr, cppyy.gbl, "no_such_class") + raises(AttributeError, getattr, cppjit.gbl, "no_such_class") - assert cppyy.gbl.fragile is cppyy.gbl.fragile - assert cppyy.gbl.fragile == cppyy.gbl.fragile - fragile = cppyy.gbl.fragile + assert cppjit.gbl.fragile is cppjit.gbl.fragile + assert cppjit.gbl.fragile == cppjit.gbl.fragile + fragile = cppjit.gbl.fragile - # FIXME: Should be fixed with root dictionary + # FIXME: Should be fixed with root dictionary # look at https://github.com/wlav/cppyy/discussions/306 # raises(AttributeError, getattr, fragile, "no_such_class") assert fragile.C is fragile.C assert fragile.C == fragile.C - assert fragile.C().check() == ord('C') + assert fragile.C().check() == ord("C") assert fragile.B is fragile.B assert fragile.B == fragile.B - assert fragile.B().check() == ord('B') + assert fragile.B().check() == ord("B") assert not fragile.B().gime_no_such() assert fragile.C is fragile.C assert fragile.C == fragile.C - assert fragile.C().check() == ord('C') + assert fragile.C().check() == ord("C") raises(TypeError, fragile.C().use_no_such, None) def test03_arguments(self): """Test reporting when providing wrong arguments""" - import cppyy + import cppjit - assert cppyy.gbl.fragile == cppyy.gbl.fragile - fragile = cppyy.gbl.fragile + assert cppjit.gbl.fragile == cppjit.gbl.fragile + fragile = cppjit.gbl.fragile assert fragile.D == fragile.D - assert fragile.D().check() == ord('D') + assert fragile.D().check() == ord("D") d = fragile.D() raises(TypeError, d.overload, None) raises(TypeError, d.overload, None, None, None) - d.overload('a') + d.overload("a") d.overload(1) def test04_unsupported_arguments(self): """Test arguments that are yet unsupported""" - import cppyy + import cppjit - assert cppyy.gbl.fragile == cppyy.gbl.fragile - fragile = cppyy.gbl.fragile + assert cppjit.gbl.fragile == cppjit.gbl.fragile + fragile = cppjit.gbl.fragile assert fragile.E == fragile.E - assert fragile.E().check() == ord('E') + assert fragile.E().check() == ord("E") e = fragile.E() raises(TypeError, e.overload, None) # allowing access to e.m_pp_no_such is debatable, but it allows a typed address # to be passed back into C++, which may be useful ... - assert cppyy.addressof(e.m_pp_no_such[0]) == 0xdead + assert cppjit.addressof(e.m_pp_no_such[0]) == 0xDEAD def test05_wrong_arg_addressof(self): """Test addressof() error reporting""" - import cppyy + import cppjit - assert cppyy.gbl.fragile == cppyy.gbl.fragile - fragile = cppyy.gbl.fragile + assert cppjit.gbl.fragile == cppjit.gbl.fragile + fragile = cppjit.gbl.fragile assert fragile.F == fragile.F - assert fragile.F().check() == ord('F') + assert fragile.F().check() == ord("F") f = fragile.F() o = object() - cppyy.addressof(f) - raises(TypeError, cppyy.addressof, o) - raises(TypeError, cppyy.addressof, 1) + cppjit.addressof(f) + raises(TypeError, cppjit.addressof, o) + raises(TypeError, cppjit.addressof, 1) # regression (m_int is 0 by default, but its address is not) - assert cppyy.addressof(f, 'm_int') + assert cppjit.addressof(f, "m_int") # see also test08_void_pointer_passing in test_advancedcpp.py def test06_wrong_this(self): """Test that using an incorrect self argument raises""" - import cppyy + import cppjit - assert cppyy.gbl.fragile == cppyy.gbl.fragile - fragile = cppyy.gbl.fragile + assert cppjit.gbl.fragile == cppjit.gbl.fragile + fragile = cppjit.gbl.fragile a = fragile.A() - assert fragile.A.check(a) == ord('A') + assert fragile.A.check(a) == ord("A") b = fragile.B() - assert fragile.B.check(b) == ord('B') + assert fragile.B.check(b) == ord("B") raises(TypeError, fragile.A.check, b) raises(TypeError, fragile.B.check, a) @@ -138,47 +151,47 @@ def test06_wrong_this(self): def test07_unnamed_enum(self): """Test that an unnamed enum does not cause infinite recursion""" - import cppyy + import cppjit - assert cppyy.gbl.fragile is cppyy.gbl.fragile - fragile = cppyy.gbl.fragile - assert cppyy.gbl.fragile is fragile + assert cppjit.gbl.fragile is cppjit.gbl.fragile + fragile = cppjit.gbl.fragile + assert cppjit.gbl.fragile is fragile g = fragile.G() def test08_unhandled_scoped_datamember(self): """Test that an unhandled scoped data member does not cause infinite recursion""" - import cppyy + import cppjit - assert cppyy.gbl.fragile is cppyy.gbl.fragile - fragile = cppyy.gbl.fragile - assert cppyy.gbl.fragile is fragile + assert cppjit.gbl.fragile is cppjit.gbl.fragile + fragile = cppjit.gbl.fragile + assert cppjit.gbl.fragile is fragile h = fragile.H() def test09_operator_bool(self): """Access to global vars with an operator bool() returning False""" - import cppyy + import cppjit - i = cppyy.gbl.fragile.I() + i = cppjit.gbl.fragile.I() assert not i - g = cppyy.gbl.fragile.gI + g = cppjit.gbl.fragile.gI assert not g def test10_documentation(self): """Check contents of documentation""" - import cppyy + import cppjit - assert cppyy.gbl.fragile == cppyy.gbl.fragile - fragile = cppyy.gbl.fragile + assert cppjit.gbl.fragile == cppjit.gbl.fragile + fragile = cppjit.gbl.fragile d = fragile.D() try: - d.check(None) # raises TypeError + d.check(None) # raises TypeError assert 0 except TypeError as e: assert "fragile::D::check()" in str(e) @@ -186,7 +199,7 @@ def test10_documentation(self): assert "TypeError: takes at least 2 arguments (1 given)" in str(e) try: - d.overload(None) # raises TypeError + d.overload(None) # raises TypeError assert 0 except TypeError as e: # TODO: pypy-c does not indicate which argument failed to convert, CPython does @@ -195,11 +208,18 @@ def test10_documentation(self): assert "fragile::D::overload()" in err_msg assert "TypeError: takes at most 0 arguments (1 given)" in str(e) assert "fragile::D::overload(fragile::no_such_class*)" in err_msg - #assert "no converter available for 'fragile::no_such_class*'" in str(e) - assert "void fragile::D::overload(char, int i = 0)".replace(" ", "") in err_msg - #assert "char or small int type expected" in str(e) - assert "void fragile::D::overload(int, fragile::no_such_class * p = 0)".replace(" ", "") in err_msg - #assert "int/long conversion expects an integer object" in str(e) + # assert "no converter available for 'fragile::no_such_class*'" in str(e) + assert ( + "void fragile::D::overload(char, int i = 0)".replace(" ", "") in err_msg + ) + # assert "char or small int type expected" in str(e) + assert ( + "void fragile::D::overload(int, fragile::no_such_class * p = 0)".replace( + " ", "" + ) + in err_msg + ) + # assert "int/long conversion expects an integer object" in str(e) j = fragile.J() assert fragile.J.method1.__doc__ == j.method1.__doc__ @@ -209,7 +229,7 @@ def test10_documentation(self): assert f.__doc__ == "void fragile::fglobal(int, double, char)" try: - o = fragile.O() # raises TypeError + o = fragile.O() # raises TypeError assert 0 except TypeError as e: assert "cannot instantiate abstract class 'fragile::O'" in str(e) @@ -218,43 +238,43 @@ def test10_documentation(self): def test11_dir(self): """Test __dir__ method""" - import cppyy + import cppjit - members = dir(cppyy.gbl.fragile) - assert 'A' in members - assert 'B' in members - assert 'C' in members - assert 'D' in members # classes + members = dir(cppjit.gbl.fragile) + assert "A" in members + assert "B" in members + assert "C" in members + assert "D" in members # classes - assert 'nested1' in members # namespace + assert "nested1" in members # namespace # TODO: think this through ... probably want this, but interferes with # the (new) policy of lazy lookups - #assert 'fglobal' in members # function - assert 'gI'in members # variable + # assert 'fglobal' in members # function + assert "gI" in members # variable - # GetAllCppNames() behaves differently from python dir() but providing the full - # set, which is then filtered in dir(); check both - cppyy.cppdef("""\ + # GetAllCppNames() behaves differently from python dir() but providing the full + # set, which is then filtered in dir(); check both + cppjit.cppdef("""\ #ifdef _MSC_VER - #define CPPYY_IMPORT extern __declspec(dllimport) + #define CPPJIT_IMPORT extern __declspec(dllimport) #else - #define CPPYY_IMPORT extern + #define CPPJIT_IMPORT extern #endif namespace Cpp { struct DeclRef; } // namespace Cpp - namespace Cppyy { + namespace cppjit::interop { typedef Cpp::DeclRef TCppScope_t; - CPPYY_IMPORT TCppScope_t GetScope(const std::string& scope_name, TCppScope_t parent_scope = TCppScope_t()); - CPPYY_IMPORT void GetAllCppNames(TCppScope_t scope, std::set& cppnames); + CPPJIT_IMPORT TCppScope_t GetScope(const std::string& scope_name, TCppScope_t parent_scope = TCppScope_t()); + CPPJIT_IMPORT void GetAllCppNames(TCppScope_t scope, std::set& cppnames); }""") - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace GG { struct S { int _a; @@ -262,106 +282,110 @@ def test11_dir(self): S(int a, int c): _a{a}, _c{c} { } S(): _a{0}, _c{0} { } bool operator<(int i) { return i < (_a+_c); } - }; }"""); - - - assert 'S' in dir(cppyy.gbl.GG) + }; }""") + assert "S" in dir(cppjit.gbl.GG) - handle = cppyy.gbl.Cppyy.GetScope("GG::S") + handle = cppjit.gbl.cppjit.interop.GetScope("GG::S") assert handle - cppnames = cppyy.gbl.std.set[str]() - cppyy.gbl.Cppyy.GetAllCppNames(handle, cppnames) + cppnames = cppjit.gbl.std.set[str]() + cppjit.gbl.cppjit.interop.GetAllCppNames(handle, cppnames) - assert 'S' in cppnames - assert '_a' in cppnames - assert '_c' in cppnames + assert "S" in cppnames + assert "_a" in cppnames + assert "_c" in cppnames - assert 'operator<' in cppnames + assert "operator<" in cppnames - dirS = dir(cppyy.gbl.GG.S) + dirS = dir(cppjit.gbl.GG.S) - assert 'S' not in dirS # is __init__ - assert '_a' in dirS - assert '_c' in dirS + assert "S" not in dirS # is __init__ + assert "_a" in dirS + assert "_c" in dirS - assert 'operator<' not in dirS + assert "operator<" not in dirS def test12_imports(self): """Test ability to import from namespace (or fail with ImportError)""" - import cppyy + import cppjit # TODO: namespaces aren't loaded (and thus not added to sys.modules) # with just the from ... import statement; actual use is needed - from cppyy.gbl import fragile + from cppjit.gbl import fragile # noqa: F401 def fail_import(): - from cppyy.gbl import does_not_exist + from cppjit.gbl import does_not_exist # noqa: F401 + raises(ImportError, fail_import) - from cppyy.gbl.fragile import A, B, C, D - assert cppyy.gbl.fragile.A is A - assert cppyy.gbl.fragile.B is B - assert cppyy.gbl.fragile.C is C - assert cppyy.gbl.fragile.D is D + from cppjit.gbl.fragile import A, B, C, D + + assert cppjit.gbl.fragile.A is A + assert cppjit.gbl.fragile.B is B + assert cppjit.gbl.fragile.C is C + assert cppjit.gbl.fragile.D is D # according to warnings, can't test "import *" ... - from cppyy.gbl.fragile import nested1 - assert cppyy.gbl.fragile.nested1 is nested1 - assert nested1.__name__ == 'nested1' - assert nested1.__module__ == 'cppyy.gbl.fragile' - assert nested1.__cpp_name__ == 'fragile::nested1' - - from cppyy.gbl.fragile.nested1 import A, nested2 - assert cppyy.gbl.fragile.nested1.A is A - assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1' - assert A.__cpp_name__ == 'fragile::nested1::A' - assert cppyy.gbl.fragile.nested1.nested2 is nested2 - assert nested2.__name__ == 'nested2' - assert nested2.__module__ == 'cppyy.gbl.fragile.nested1' - assert nested2.__cpp_name__ == 'fragile::nested1::nested2' - - from cppyy.gbl.fragile.nested1.nested2 import A, nested3 - assert cppyy.gbl.fragile.nested1.nested2.A is A - assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1.nested2' - assert A.__cpp_name__ == 'fragile::nested1::nested2::A' - assert cppyy.gbl.fragile.nested1.nested2.nested3 is nested3 - assert nested3.__name__ == 'nested3' - assert nested3.__module__ == 'cppyy.gbl.fragile.nested1.nested2' - assert nested3.__cpp_name__ == 'fragile::nested1::nested2::nested3' - - from cppyy.gbl.fragile.nested1.nested2.nested3 import A - assert cppyy.gbl.fragile.nested1.nested2.nested3.A is nested3.A - assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1.nested2.nested3' - assert A.__cpp_name__ == 'fragile::nested1::nested2::nested3::A' + from cppjit.gbl.fragile import nested1 + + assert cppjit.gbl.fragile.nested1 is nested1 + assert nested1.__name__ == "nested1" + assert nested1.__module__ == "cppjit.gbl.fragile" + assert nested1.__cpp_name__ == "fragile::nested1" + + from cppjit.gbl.fragile.nested1 import A, nested2 + + assert cppjit.gbl.fragile.nested1.A is A + assert A.__name__ == "A" + assert A.__module__ == "cppjit.gbl.fragile.nested1" + assert A.__cpp_name__ == "fragile::nested1::A" + assert cppjit.gbl.fragile.nested1.nested2 is nested2 + assert nested2.__name__ == "nested2" + assert nested2.__module__ == "cppjit.gbl.fragile.nested1" + assert nested2.__cpp_name__ == "fragile::nested1::nested2" + + from cppjit.gbl.fragile.nested1.nested2 import A, nested3 + + assert cppjit.gbl.fragile.nested1.nested2.A is A + assert A.__name__ == "A" + assert A.__module__ == "cppjit.gbl.fragile.nested1.nested2" + assert A.__cpp_name__ == "fragile::nested1::nested2::A" + assert cppjit.gbl.fragile.nested1.nested2.nested3 is nested3 + assert nested3.__name__ == "nested3" + assert nested3.__module__ == "cppjit.gbl.fragile.nested1.nested2" + assert nested3.__cpp_name__ == "fragile::nested1::nested2::nested3" + + from cppjit.gbl.fragile.nested1.nested2.nested3 import A + + assert cppjit.gbl.fragile.nested1.nested2.nested3.A is nested3.A + assert A.__name__ == "A" + assert A.__module__ == "cppjit.gbl.fragile.nested1.nested2.nested3" + assert A.__cpp_name__ == "fragile::nested1::nested2::nested3::A" # test writability of __module__ nested3.__module__ = "peanut butter" assert nested3.__module__ == "peanut butter" # classes in namespace should inherit - assert A.__module__ == 'peanut butter.nested3' - assert 'peanut butter' in repr(A) - assert 'class' in repr(A) - assert 'peanut butter' in repr(nested3) - assert 'namespace' in repr(nested3) + assert A.__module__ == "peanut butter.nested3" + assert "peanut butter" in repr(A) + assert "class" in repr(A) + assert "peanut butter" in repr(nested3) + assert "namespace" in repr(nested3) # as should objects a = A() - assert 'peanut butter' in repr(a) - assert 'object' in repr(a) + assert "peanut butter" in repr(a) + assert "object" in repr(a) def test13_missing_casts(self): """Test proper handling when a hierarchy is not fully available""" - import cppyy + import cppjit - k = cppyy.gbl.fragile.K() + k = cppjit.gbl.fragile.K() assert k is k.GimeK(False) assert k is not k.GimeK(True) @@ -376,10 +400,10 @@ def test13_missing_casts(self): def test14_double_enum_trouble(self): """Test a redefinition of enum in a derived class""" - import cppyy + import cppjit - M = cppyy.gbl.fragile.M - N = cppyy.gbl.fragile.N + M = cppjit.gbl.fragile.M + N = cppjit.gbl.fragile.N assert M.kOnce == N.kOnce assert M.kTwice == N.kTwice @@ -387,9 +411,9 @@ def test14_double_enum_trouble(self): def test15_const_in_name(self): """Make sure 'const' is not erased when part of a name""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" struct Some0Class {} myvar0; struct constSome1Class {} myvar1; struct Some2Classconst {} myvar2; @@ -397,93 +421,92 @@ def test15_const_in_name(self): struct SomeconstClass4 {} myvar4; """) - assert cppyy.gbl.myvar0 - assert cppyy.gbl.myvar1 - assert cppyy.gbl.myvar2 - assert cppyy.gbl.myvar3 - assert cppyy.gbl.myvar4 + assert cppjit.gbl.myvar0 + assert cppjit.gbl.myvar1 + assert cppjit.gbl.myvar2 + assert cppjit.gbl.myvar3 + assert cppjit.gbl.myvar4 def test16_opaque_handle(self): """Support use of opaque handles""" - import cppyy + import cppjit - assert cppyy.gbl.fragile.OpaqueType - assert cppyy.gbl.fragile.OpaqueHandle_t + assert cppjit.gbl.fragile.OpaqueType + assert cppjit.gbl.fragile.OpaqueHandle_t - handle = cppyy.gbl.fragile.OpaqueHandle_t(0x42) + handle = cppjit.gbl.fragile.OpaqueHandle_t(0x42) assert handle - assert cppyy.addressof(handle) == 0x42 + assert cppjit.addressof(handle) == 0x42 - raises(TypeError, cppyy.gbl.fragile.OpaqueType) - assert not 'OpaqueType' in cppyy.gbl.fragile.__dict__ + raises(TypeError, cppjit.gbl.fragile.OpaqueType) + assert "OpaqueType" not in cppjit.gbl.fragile.__dict__ - handle = cppyy.gbl.fragile.OpaqueHandle_t() + handle = cppjit.gbl.fragile.OpaqueHandle_t() assert not handle - addr = cppyy.gbl.fragile.create_handle(handle); + addr = cppjit.gbl.fragile.create_handle(handle) assert addr assert not not handle - assert cppyy.gbl.fragile.destroy_handle(handle, addr); - + assert cppjit.gbl.fragile.destroy_handle(handle, addr) # now define OpaqueType - cppyy.cppdef("namespace fragile { class OpaqueType { public: int m_int; }; }") + cppjit.cppdef("namespace fragile { class OpaqueType { public: int m_int; }; }") # get fresh (should not have been cached while incomplete) - o = cppyy.gbl.fragile.OpaqueType() - assert hasattr(o, 'm_int') + o = cppjit.gbl.fragile.OpaqueType() + assert hasattr(o, "m_int") - assert 'OpaqueType' in cppyy.gbl.fragile.__dict__ + assert "OpaqueType" in cppjit.gbl.fragile.__dict__ def test17_interactive(self): - """Test the usage of 'from cppyy.interactive import *'""" + """Test the usage of 'from cppjit.interactive import *'""" - import sys - - if 0x030b0000 <= sys.hexversion: - skip('"from cppyy.interactive import *" is no longer supported') + if 0x030B0000 <= sys.hexversion: + skip('"from cppjit.interactive import *" is no longer supported') oldsp = sys.path[:] - sys.path.append('.') + sys.path.append(".") try: - import assert_interactive + import assert_interactive # noqa: F401 finally: sys.path = oldsp def test18_overload(self): """Test usage of __overload__""" - import cppyy + import cppjit - cppyy.cppdef("""struct Variable { + cppjit.cppdef("""struct Variable { Variable(double lb, double ub, double value, bool binary, bool integer, const std::string& name) {} Variable(int) {} };""") - for sig in ['double, double, double, bool, bool, const std::string&', - 'double,double,double,bool,bool,const std::string&', - 'double lb, double ub, double value, bool binary, bool integer, const std::string& name']: - assert cppyy.gbl.Variable.__init__.__overload__(sig) + for sig in [ + "double, double, double, bool, bool, const std::string&", + "double,double,double,bool,bool,const std::string&", + "double lb, double ub, double value, bool binary, bool integer, const std::string& name", + ]: + assert cppjit.gbl.Variable.__init__.__overload__(sig) def test19_gbl_contents(self): - """Assure cppyy.gbl is mostly devoid of ROOT thingies""" + """Assure cppjit.gbl is mostly devoid of ROOT thingies""" - import cppyy + import cppjit - dd = dir(cppyy.gbl) + dd = dir(cppjit.gbl) - assert not 'TCanvasImp' in dd - assert not 'ESysConstants' in dd - assert not 'kDoRed' in dd + assert "TCanvasImp" not in dd + assert "ESysConstants" not in dd + assert "kDoRed" not in dd @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test20_capture_output(self): """Capture cerr into a string""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace capture { void say_hello() { std::cerr << "Hello, World\n"; @@ -493,14 +516,14 @@ def test20_capture_output(self): o.rdbuf(b); } }""") - capture = cppyy.gbl.std.ostringstream() - oldbuf = cppyy.gbl.std.cerr.rdbuf() + capture = cppjit.gbl.std.ostringstream() + oldbuf = cppjit.gbl.std.cerr.rdbuf() try: - cppyy.gbl.capture.rdbuf_wa(cppyy.gbl.std.cerr, capture.rdbuf()) - cppyy.gbl.capture.say_hello() + cppjit.gbl.capture.rdbuf_wa(cppjit.gbl.std.cerr, capture.rdbuf()) + cppjit.gbl.capture.say_hello() finally: - cppyy.gbl.std.cerr.rdbuf(oldbuf) + cppjit.gbl.std.cerr.rdbuf(oldbuf) assert capture.str() == "Hello, World\n" @@ -508,29 +531,32 @@ def test20_capture_output(self): def test21_failing_cppcode(self): """Check error behavior of failing C++ code""" - import cppyy, string, re + import re + + import cppjit + + allspace = re.compile(r"\s+") - allspace = re.compile(r'\s+') def get_errmsg(exc, allspace=allspace): err = str(exc.value) - return re.sub(allspace, '', err) + return re.sub(allspace, "", err) with raises(ImportError) as include_exc: - cppyy.include("doesnotexist.h") + cppjit.include("doesnotexist.h") err = get_errmsg(include_exc) - assert "Failedtoloadheaderfile\"doesnotexist.h\"" in err + assert 'Failedtoloadheaderfile"doesnotexist.h"' in err assert "fatalerror:" in err - assert "\'doesnotexist.h\'filenotfound" in err + assert "'doesnotexist.h'filenotfound" in err with raises(ImportError) as c_include_exc: - cppyy.c_include("doesnotexist.h") + cppjit.c_include("doesnotexist.h") err = get_errmsg(c_include_exc) - assert "Failedtoloadheaderfile\"doesnotexist.h\"" in err + assert 'Failedtoloadheaderfile"doesnotexist.h"' in err assert "fatalerror:" in err - assert "\'doesnotexist.h\'filenotfound" in err + assert "'doesnotexist.h'filenotfound" in err with raises(SyntaxError) as cppdef_exc: - cppyy.cppdef("1aap = 42;") + cppjit.cppdef("1aap = 42;") err = get_errmsg(cppdef_exc) assert "FailedtoparsethegivenC++code" in err assert "error:" in err @@ -544,62 +570,63 @@ def get_errmsg(exc, allspace=allspace): def test22_cppexec(self): """Interactive access to the Cling global scope""" - import cppyy + import cppjit - cppyy.cppexec("int interactive_b = 4") - assert cppyy.gbl.interactive_b == 4 + cppjit.cppexec("int interactive_b = 4") + assert cppjit.gbl.interactive_b == 4 with raises(SyntaxError): - cppyy.cppexec("doesnotexist"); + cppjit.cppexec("doesnotexist") def test23_set_debug(self): """Setting of debugging facilities""" - import cppyy + import cppjit - cppyy.set_debug() - assert cppyy.gbl.Cpp.IsDebugOutputEnabled() == True - cppyy.set_debug(False) - assert cppyy.gbl.Cpp.IsDebugOutputEnabled() == False - cppyy.set_debug(True) - assert cppyy.gbl.Cpp.IsDebugOutputEnabled() == True - cppyy.set_debug(False) - assert cppyy.gbl.Cpp.IsDebugOutputEnabled() == False + cppjit.set_debug() + assert cppjit.gbl.Cpp.IsDebugOutputEnabled() == True + cppjit.set_debug(False) + assert cppjit.gbl.Cpp.IsDebugOutputEnabled() == False + cppjit.set_debug(True) + assert cppjit.gbl.Cpp.IsDebugOutputEnabled() == True + cppjit.set_debug(False) + assert cppjit.gbl.Cpp.IsDebugOutputEnabled() == False - @mark.xfail(condition = IS_LINUX, reason="Fails on Ubuntu") + @mark.xfail(condition=IS_LINUX, reason="Fails on Ubuntu") def test24_asan(self): """Check availability of ASAN with gcc""" - import cppyy - import sys + import cppjit - if not 'linux' in sys.platform: + if "linux" not in sys.platform: return - cppyy.include('sanitizer/asan_interface.h') + cppjit.include("sanitizer/asan_interface.h") @mark.xfail def test25_cppdef_error_reporting(self): - """Check error reporting of cppyy.cppdef""" + """Check error reporting of cppjit.cppdef""" - import cppyy, warnings + import warnings - assert cppyy.gbl.fragile.add42(1) == 43 # brings in symbol from library + import cppjit + + assert cppjit.gbl.fragile.add42(1) == 43 # brings in symbol from library with raises(SyntaxError): - # redefine symbol, leading to duplicate - cppyy.cppdef("""\ + # redefine symbol, leading to duplicate + cppjit.cppdef("""\ namespace fragile { int add42(int i) { return i + 42; } }""") - # isolate the warning configuration + # isolate the warning configuration with warnings.catch_warnings(): warnings.simplefilter("error") # turn warnings into errors - # missing return statement + # missing return statement with raises(SyntaxWarning) as exc: - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace fragile { void add42d(double d) { #warning return plastic for recycling! @@ -608,10 +635,10 @@ def test25_cppdef_error_reporting(self): assert "return" in str(exc.value) - # mix of error and warning + # mix of error and warning with raises(SyntaxError): - # redefine symbol, leading to duplicate - cppyy.cppdef("""\ + # redefine symbol, leading to duplicate + cppjit.cppdef("""\ namespace fragile { float add42f(float d) { d + 42.f; } int add42(int i) { return i + 42; } @@ -621,62 +648,65 @@ def test25_cppdef_error_reporting(self): def test26_macro(self): """Test access to C++ pre-processor macro's""" - import cppyy + import cppjit - cppyy.cppdef('#define HELLO "Hello, World!"') - assert cppyy.macro("HELLO") == "Hello, World!" + cppjit.cppdef('#define HELLO "Hello, World!"') + assert cppjit.macro("HELLO") == "Hello, World!" with raises(ValueError): - cppyy.macro("SOME_INT") + cppjit.macro("SOME_INT") - cppyy.cppdef('#define SOME_INT 42') - assert cppyy.macro("SOME_INT") == 42 + cppjit.cppdef("#define SOME_INT 42") + assert cppjit.macro("SOME_INT") == 42 def test27_pickle_enums(self): """Pickling of enum types""" - import cppyy import pickle - cppyy.cppdef(""" + import cppjit + + cppjit.cppdef(""" enum MyPickleEnum { PickleFoo, PickleBar }; namespace MyPickleNamespace { enum MyPickleEnum { PickleFoo, PickleBar }; }""") - e1 = cppyy.gbl.MyPickleEnum - assert e1.__module__ == 'cppyy.gbl' + e1 = cppjit.gbl.MyPickleEnum + assert e1.__module__ == "cppjit.gbl" assert pickle.dumps(e1.PickleFoo) - e2 = cppyy.gbl.MyPickleNamespace.MyPickleEnum - assert e2.__module__ == 'cppyy.gbl.MyPickleNamespace' + e2 = cppjit.gbl.MyPickleNamespace.MyPickleEnum + assert e2.__module__ == "cppjit.gbl.MyPickleNamespace" assert pickle.dumps(e2.PickleBar) def test28_memoryview_of_empty(self): """memoryview of an empty array""" - import cppyy, array + import array + + import cppjit - cppyy.cppdef("void f(unsigned char const *buf) {}") + cppjit.cppdef("void f(unsigned char const *buf) {}") try: - cppyy.gbl.f(memoryview(array.array('B', []))) + cppjit.gbl.f(memoryview(array.array("B", []))) except TypeError: - pass # used to crash in PyObject_CheckBuffer on Linux + pass # used to crash in PyObject_CheckBuffer on Linux def test29_vector_datamember(self): """Offset calculation of vector datamember""" - import cppyy + import cppjit - cppyy.cppdef("struct VectorDatamember { std::vector v; };") - cppyy.gbl.VectorDatamember # used to crash on Mac arm64 + cppjit.cppdef("struct VectorDatamember { std::vector v; };") + cppjit.gbl.VectorDatamember # used to crash on Mac arm64 def test30_two_nested_ambiguity(self): """Nested class ambiguity in older Clangs""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ #include namespace Test { @@ -695,17 +725,17 @@ def test30_two_nested_ambiguity(self): }; }; }""") - from cppyy.gbl import Test + from cppjit.gbl import Test p = Test.Family1.Parent() - p.children # used to crash + p.children # used to crash def test31_template_with_class_enum(self): """Template instantiated with class enum""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ enum class ClassEnumA { A, }; template @@ -728,122 +758,125 @@ def test31_template_with_class_enum(self): template class EnumTemplate; }""") - for ns, val in [(cppyy.gbl, 42), - (cppyy.gbl.ClassEnumNS, 37)]: + for ns, val in [(cppjit.gbl, 42), (cppjit.gbl.ClassEnumNS, 37)]: assert ns.EnumTemplate[ns.ClassEnumA.A]().foo() == val class TestSIGNALS: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.fragile = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.fragile = cppjit.load_reflection_info(cls.test_dct) @mark.xfail(run=False, reason="Crashes") def test01_abortive_signals(self): """Conversion from abortive signals to Python exceptions""" if ispypy: - skip('signals not yet implemented') + skip("signals not yet implemented") if IS_MAC_ARM: skip("JIT exceptions from signals not supported on Mac ARM") - import cppyy - import cppyy.ll + import cppjit + import cppjit.ll - f = cppyy.gbl.fragile + f = cppjit.gbl.fragile - assert issubclass(cppyy.ll.BusError, cppyy.ll.FatalError) - assert issubclass(cppyy.ll.SegmentationViolation, cppyy.ll.FatalError) - assert issubclass(cppyy.ll.IllegalInstruction, cppyy.ll.FatalError) - assert issubclass(cppyy.ll.AbortSignal, cppyy.ll.FatalError) + assert issubclass(cppjit.ll.BusError, cppjit.ll.FatalError) + assert issubclass(cppjit.ll.SegmentationViolation, cppjit.ll.FatalError) + assert issubclass(cppjit.ll.IllegalInstruction, cppjit.ll.FatalError) + assert issubclass(cppjit.ll.AbortSignal, cppjit.ll.FatalError) - import os - os.putenv('CPPYY_CRASH_QUIET', '1') + os.putenv("CPPJIT_CRASH_QUIET", "1") - with raises((cppyy.ll.SegmentationViolation, cppyy.ll.IllegalInstruction)): - with cppyy.ll.signals_as_exception(): + with raises((cppjit.ll.SegmentationViolation, cppjit.ll.IllegalInstruction)): + with cppjit.ll.signals_as_exception(): f.segfault() - with raises(cppyy.ll.AbortSignal): - with cppyy.ll.signals_as_exception(): + with raises(cppjit.ll.AbortSignal): + with cppjit.ll.signals_as_exception(): f.sigabort() - # can only recover once from each error on Windows, which is functionally - # enough, but precludes further testing here + # can only recover once from each error on Windows, which is functionally + # enough, but precludes further testing here if not IS_WINDOWS: - cppyy.ll.set_signals_as_exception(True) - with raises((cppyy.ll.SegmentationViolation, cppyy.ll.IllegalInstruction)): + cppjit.ll.set_signals_as_exception(True) + with raises( + (cppjit.ll.SegmentationViolation, cppjit.ll.IllegalInstruction) + ): f.segfault() - with raises(cppyy.ll.AbortSignal): + with raises(cppjit.ll.AbortSignal): f.sigabort() - cppyy.ll.set_signals_as_exception(False) + cppjit.ll.set_signals_as_exception(False) f.segfault.__sig2exc__ = True - with raises((cppyy.ll.SegmentationViolation, cppyy.ll.IllegalInstruction)): + with raises( + (cppjit.ll.SegmentationViolation, cppjit.ll.IllegalInstruction) + ): f.segfault() f.sigabort.__sig2exc__ = True - with raises(cppyy.ll.AbortSignal): + with raises(cppjit.ll.AbortSignal): f.sigabort() class TestSTDNOTINGLOBAL: def setup_class(cls): - import cppyy + pass def test01_stl_in_std(self): """STL classes should live in std:: only""" - import cppyy + import cppjit - names = ['array', 'function', 'list', 'set', 'vector', 'byte'] + names = ["array", "function", "list", "set", "vector", "byte"] for name in names: - getattr(cppyy.gbl.std, name) + getattr(cppjit.gbl.std, name) with raises(AttributeError): - getattr(cppyy.gbl, name) + getattr(cppjit.gbl, name) - # inject a vector in the global namespace - cppyy.cppdef("class vector{};") - v = cppyy.gbl.vector() - assert cppyy.gbl.vector is not cppyy.gbl.std.vector + # inject a vector in the global namespace + cppjit.cppdef("class vector{};") + v = cppjit.gbl.vector() + assert cppjit.gbl.vector is not cppjit.gbl.std.vector def test02_ctypes_in_both(self): """Standard int types live in both global and std::""" - import cppyy + import cppjit - for name in ['int8_t', 'uint8_t']: - getattr(cppyy.gbl.std, name) - getattr(cppyy.gbl, name) + for name in ["int8_t", "uint8_t"]: + getattr(cppjit.gbl.std, name) + getattr(cppjit.gbl, name) # TODO: get the types to match exactly as well - assert cppyy.gbl.std.int8_t(-42) == cppyy.gbl.int8_t(-42) - assert cppyy.gbl.std.uint8_t(42) == cppyy.gbl.uint8_t(42) + assert cppjit.gbl.std.int8_t(-42) == cppjit.gbl.int8_t(-42) + assert cppjit.gbl.std.uint8_t(42) == cppjit.gbl.uint8_t(42) def test03_clashing_using_in_global(self): """Redefines of std:: typedefs should be possible in global""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" using uint = unsigned int; using ushort = unsigned short; using uchar = unsigned char; using byte = unsigned char; - """ ) + """) - for name in ['int', 'uint', 'ushort', 'uchar', 'byte']: - getattr(cppyy.gbl, name) + for name in ["int", "uint", "ushort", "uchar", "byte"]: + getattr(cppjit.gbl, name) def test04_no_legacy(self): """Test some functions that previously crashed""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace CppyyLegacy { enum ELogLevel { kLogEmerg = 0, @@ -857,7 +890,7 @@ def test04_no_legacy(self): }; }""") - cppyy.cppdef(""" + cppjit.cppdef(""" enum ELogLevel { kLogEmerg = 0, kLogAlert = 1, @@ -869,16 +902,16 @@ def test04_no_legacy(self): kLogDebug = 7 };""") - assert cppyy.gbl.ELogLevel != cppyy.gbl.CppyyLegacy.ELogLevel + assert cppjit.gbl.ELogLevel != cppjit.gbl.CppyyLegacy.ELogLevel def test05_span_compatibility(self): """Test compatibility of span under C++2a compilers that support it""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ #if __cplusplus >= 202002 #include std::span my_test_span1; #endif - """) \ No newline at end of file + """) diff --git a/test/test_leakcheck.py b/test/test_leakcheck.py index 7c7ef6a..39128be 100644 --- a/test/test_leakcheck.py +++ b/test/test_leakcheck.py @@ -1,17 +1,19 @@ -import py, os, sys +import os +import sys + from pytest import mark, skip -from support import setup_make, pylong, pyunicode, IS_CLANG_REPL nopsutil = False try: - import psutil + import psutil # noqa: F401 except ImportError: nopsutil = True + @mark.skipif(nopsutil == True, reason="module psutil not installed") class TestLEAKCHECK: def setup_class(cls): - import cppyy, psutil + import psutil cls.process = psutil.Process(os.getpid()) @@ -32,28 +34,28 @@ def check_func(self, scope, func, *args, **kwds): import gc - # if tmpl_args is provided as a keyword, then this is a templated - # function that is to be found on each call python-side - tmpl_args = kwds.pop('tmpl_args', None) + # if tmpl_args is provided as a keyword, then this is a templated + # function that is to be found on each call python-side + tmpl_args = kwds.pop("tmpl_args", None) - # warmup function + # warmup function gc.collect() if tmpl_args is None: getattr(scope, func)(*args, **kwds) else: getattr(scope, func)[tmpl_args](*args, **kwds) - # number of iterations + # number of iterations N = 100000 - # The use of arena's, free-lists, etc. means that checking rss remains - # unreliable, unless looking for consistent jumps, so the leak check will - # be run M times and only considered failed if it "leaks" every time. In - # actual practice, the number of fails is 0, 1, or M. Note that the total - # number of gc objects tracked is always required to remain the same. + # The use of arena's, free-lists, etc. means that checking rss remains + # unreliable, unless looking for consistent jumps, so the leak check will + # be run M times and only considered failed if it "leaks" every time. In + # actual practice, the number of fails is 0, 1, or M. Note that the total + # number of gc objects tracked is always required to remain the same. M = 3 - # leak check + # leak check fail = 0 for i in range(M): gc.collect() @@ -75,9 +77,9 @@ def check_func(self, scope, func, *args, **kwds): def test01_free_functions(self): """Leak test of free functions""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace LeakCheck { void free_f() {} void free_f_ol(int) {} @@ -87,22 +89,22 @@ def test01_free_functions(self): std::string free_f_ret2() { return "aap"; } }""") - ns = cppyy.gbl.LeakCheck + ns = cppjit.gbl.LeakCheck - self.check_func(ns, 'free_f') - self.check_func(ns, 'free_f_ol', 42) - self.check_func(ns, 'free_f_ol', '42') - self.check_func(ns, 'free_f_ol', 42.) # template - self.check_func(ns, 'free_f_ol', 42., tmpl_args='float') - self.check_func(ns, 'free_f_ret1') - self.check_func(ns, 'free_f_ret1') + self.check_func(ns, "free_f") + self.check_func(ns, "free_f_ol", 42) + self.check_func(ns, "free_f_ol", "42") + self.check_func(ns, "free_f_ol", 42.0) # template + self.check_func(ns, "free_f_ol", 42.0, tmpl_args="float") + self.check_func(ns, "free_f_ret1") + self.check_func(ns, "free_f_ret1") def test02_test_static_methods(self): """Leak test of static methods""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace LeakCheck { class MyClass02 { public: @@ -113,22 +115,22 @@ class MyClass02 { static std::string static_method_ret() { return "aap"; } }; }""") - ns = cppyy.gbl.LeakCheck + ns = cppjit.gbl.LeakCheck for m in [ns.MyClass02, ns.MyClass02()]: - self.check_func(m, 'static_method') - self.check_func(m, 'static_method_ol', 42) - self.check_func(m, 'static_method_ol', '42') - self.check_func(m, 'static_method_ol', 42.) # template - self.check_func(m, 'static_method_ol', 42., tmpl_args='float') - self.check_func(m, 'static_method_ret') + self.check_func(m, "static_method") + self.check_func(m, "static_method_ol", 42) + self.check_func(m, "static_method_ol", "42") + self.check_func(m, "static_method_ol", 42.0) # template + self.check_func(m, "static_method_ol", 42.0, tmpl_args="float") + self.check_func(m, "static_method_ret") def test03_test_methods(self): """Leak test of methods""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace LeakCheck { class MyClass03 { public: @@ -139,22 +141,22 @@ class MyClass03 { template void method_ol(T) {} }; }""") - ns = cppyy.gbl.LeakCheck + ns = cppjit.gbl.LeakCheck m = ns.MyClass03() - self.check_func(m, 'method') - self.check_func(m, 'method_ol', 42) - self.check_func(m, 'method_ol', '42') - self.check_func(m, 'method_ol', 42.) # template - self.check_func(m, 'method_ol', 42., tmpl_args='float') - self.check_func(m, 'method_ret') + self.check_func(m, "method") + self.check_func(m, "method_ol", 42) + self.check_func(m, "method_ol", "42") + self.check_func(m, "method_ol", 42.0) # template + self.check_func(m, "method_ol", 42.0, tmpl_args="float") + self.check_func(m, "method_ret") def test04_default_arguments(self): """Leak test for functions with default arguments""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace LeakCheck { double free_default(int a=11, float b=22.f, double c=33.) { return a*b*c; @@ -170,32 +172,32 @@ class MyClass04 { } }; }""") - ns = cppyy.gbl.LeakCheck + ns = cppjit.gbl.LeakCheck - self.check_func(ns, 'free_default') - self.check_func(ns, 'free_default', a=-99) - self.check_func(ns, 'free_default', b=-99) - self.check_func(ns, 'free_default', c=-99) + self.check_func(ns, "free_default") + self.check_func(ns, "free_default", a=-99) + self.check_func(ns, "free_default", b=-99) + self.check_func(ns, "free_default", c=-99) # TODO: no keyword arguments for static methods yet - #for m in [ns.MyClass04, ns.MyClass04()]: + # for m in [ns.MyClass04, ns.MyClass04()]: # self.check_func(m, 'static_default') # self.check_func(m, 'static_default', a=-99) # self.check_func(m, 'static_default', b=-99) # self.check_func(m, 'static_default', c=-99) m = ns.MyClass04() - self.check_func(m, 'method_default') - self.check_func(m, 'method_default', a=-99) - self.check_func(m, 'method_default', b=-99) - self.check_func(m, 'method_default', c=-99) + self.check_func(m, "method_default") + self.check_func(m, "method_default", a=-99) + self.check_func(m, "method_default", b=-99) + self.check_func(m, "method_default", c=-99) def test05_aggregates(self): """Leak test of aggregate creation""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace LeakCheck { typedef enum _TYPE { DATA=0, SHAPE } TYPE; @@ -210,16 +212,16 @@ def test05_aggregates(self): TYPE buf_type; }; }""") - ns = cppyy.gbl.LeakCheck + ns = cppjit.gbl.LeakCheck - self.check_func(ns, 'SomePOD') - self.check_func(ns, 'SomePOD', fInt=42) - self.check_func(ns, 'SomePOD', fDouble=42.) - self.check_func(ns, 'SomePOD', fInt=42, fDouble=42.) - self.check_func(ns, 'SomePOD', fDouble=42., fInt=42) + self.check_func(ns, "SomePOD") + self.check_func(ns, "SomePOD", fInt=42) + self.check_func(ns, "SomePOD", fDouble=42.0) + self.check_func(ns, "SomePOD", fInt=42, fDouble=42.0) + self.check_func(ns, "SomePOD", fDouble=42.0, fInt=42) - self.check_func(ns, 'SomeBuf') - self.check_func(ns, 'SomeBuf', val=10, name="aap", buf_type=ns.SHAPE) + self.check_func(ns, "SomeBuf") + self.check_func(ns, "SomeBuf", val=10, name="aap", buf_type=ns.SHAPE) def test06_dir(self): """Global function uploads used to cause more function generation""" @@ -227,16 +229,16 @@ def test06_dir(self): if sys.hexversion < 0x03000000: skip("too slow on py2 and won't be fixed as py2 has reached eol") - import cppyy + import cppjit - self.check_func(cppyy.gbl, '__dir__', cppyy.gbl) + self.check_func(cppjit.gbl, "__dir__", cppjit.gbl) def test07_string_handling(self): """Leak check of returning an std::string by value""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace LeakCheck { class Leaker { public: @@ -247,26 +249,26 @@ class Leaker { } }; }""") - ns = cppyy.gbl.LeakCheck + ns = cppjit.gbl.LeakCheck obj = ns.Leaker() - self.check_func(obj, 'leak_string', 2048) + self.check_func(obj, "leak_string", 2048) def test08_list_creation(self): """Leak check of creating a python list from an std::list""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace LeakCheck { std::list list_by_value() { return std::list(3); } } """) - ns = cppyy.gbl.LeakCheck + ns = cppjit.gbl.LeakCheck def wrapped_list_by_value(): return list(ns.list_by_value()) ns.leak_list = wrapped_list_by_value - self.check_func(ns, 'leak_list') \ No newline at end of file + self.check_func(ns, "leak_list") diff --git a/test/test_lowlevel.py b/test/test_lowlevel.py index 202b4b4..0208a5f 100644 --- a/test/test_lowlevel.py +++ b/test/test_lowlevel.py @@ -1,9 +1,19 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, pylong, pyunicode, IS_WINDOWS, ispypy, IS_CLING, IS_MAC, IS_VALGRIND +import sys + +import py +from pytest import mark, raises, skip +from support import ( + IS_CLING, + IS_MAC, + IS_VALGRIND, + IS_WINDOWS, + ispypy, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("datatypesDict")) +test_dct = str(currpath.join("cpp/datatypesDict")) + def setup_module(mod): setup_make("datatypes") @@ -11,16 +21,16 @@ def setup_module(mod): class TestLOWLEVEL: def setup_class(cls): - import cppyy + import cppjit cls.test_dct = test_dct - cls.datatypes = cppyy.load_reflection_info(cls.test_dct) - cls.N = cppyy.gbl.N + cls.datatypes = cppjit.load_reflection_info(cls.test_dct) + cls.N = cppjit.gbl.N def test00_import_all(self): - """Validity of `from cppyy.ll import *`""" + """Validity of `from cppjit.ll import *`""" - from cppyy import ll + from cppjit import ll for attr in ll.__all__: assert hasattr(ll, attr) @@ -28,114 +38,143 @@ def test00_import_all(self): def test01_llv_type(self): """Existence of LowLevelView type""" - import cppyy.types + import cppjit.types - assert cppyy.types.LowLevelView + assert cppjit.types.LowLevelView def test02_builtin_cpp_casts(self): """C++ casting of builtin types""" - from cppyy import ll + from cppjit import ll for cast in (ll.cast, ll.static_cast): assert type(cast[float](1)) == float - assert cast[float](1) == 1. + assert cast[float](1) == 1.0 assert type(cast[int](1.1)) == int assert cast[int](1.1) == 1 - assert len(ll.reinterpret_cast['int*'](0)) == 0 - raises(ReferenceError, ll.reinterpret_cast['int*'](0).__getitem__, 0) + assert len(ll.reinterpret_cast["int*"](0)) == 0 + raises(ReferenceError, ll.reinterpret_cast["int*"](0).__getitem__, 0) def test03_memory(self): """Memory allocation and free-ing""" - import cppyy - from cppyy import ll + import cppjit - # regular C malloc/free - mem = cppyy.gbl.malloc(16) - cppyy.gbl.free(mem) + # regular C malloc/free + mem = cppjit.gbl.malloc(16) + cppjit.gbl.free(mem) - # typed styles - mem = cppyy.ll.malloc[int](self.N) + # typed styles + mem = cppjit.ll.malloc[int](self.N) assert len(mem) == self.N assert not mem.__cpp_array__ for i in range(self.N): - mem[i] = i+1 + mem[i] = i + 1 assert type(mem[i]) == int - assert mem[i] == i+1 - cppyy.ll.free(mem) + assert mem[i] == i + 1 + cppjit.ll.free(mem) - # C++ arrays - mem = cppyy.ll.array_new[int](self.N) + # C++ arrays + mem = cppjit.ll.array_new[int](self.N) assert mem.__cpp_array__ assert len(mem) == self.N for i in range(self.N): - mem[i] = i+1 + mem[i] = i + 1 assert type(mem[i]) == int - assert mem[i] == i+1 - cppyy.ll.array_delete(mem) + assert mem[i] == i + 1 + cppjit.ll.array_delete(mem) - mem = cppyy.ll.array_new[int](self.N, managed=True) - assert mem.__python_owns__ + mem = cppjit.ll.array_new[int](self.N, managed=True) + assert mem.__python_owns__ mem.__python_owns__ = False assert not mem.__python_owns__ mem.__python_owns__ = True - assert mem.__python_owns__ + assert mem.__python_owns__ def test04_python_casts(self): """Casts to common Python pointer encapsulations""" - import cppyy, cppyy.ll + import cppjit + import cppjit.ll - cppyy.cppdef("""namespace pycasts { + cppjit.cppdef("""namespace pycasts { struct SomeObject{}; uintptr_t get_address(SomeObject* ptr) { return (intptr_t)ptr; } uintptr_t get_deref(void* ptr) { return (uintptr_t)(*(void**)ptr); } }""") - from cppyy.gbl import pycasts + from cppjit.gbl import pycasts s = pycasts.SomeObject() actual = pycasts.get_address(s) - assert cppyy.ll.addressof(s) == actual - assert cppyy.ll.as_ctypes(s).value == actual + assert cppjit.ll.addressof(s) == actual + assert cppjit.ll.as_ctypes(s).value == actual - ptrptr = cppyy.ll.as_ctypes(s, byref=True) + ptrptr = cppjit.ll.as_ctypes(s, byref=True) assert pycasts.get_deref(ptrptr) == actual def test05_array_as_ref(self): """Use arrays for pass-by-ref""" - import cppyy, sys from array import array - ctd = cppyy.gbl.CppyyTestData() - - # boolean type - b = array('b', [0]); ctd.set_bool_r(b); assert b[0] == True - - # char types (as data) - c = array('B', [0]); ctd.set_uchar_r(c); assert c[0] == ord('d') - - # integer types - i = array('h', [0]); ctd.set_short_r(i); assert i[0] == -1 - i = array('H', [0]); ctd.set_ushort_r(i); assert i[0] == 2 - i = array('i', [0]); ctd.set_int_r(i); assert i[0] == -3 - i = array('I', [0]); ctd.set_uint_r(i); assert i[0] == 4 - i = array('l', [0]); ctd.set_long_r(i); assert i[0] == -5 - i = array('L', [0]); ctd.set_ulong_r(i); assert i[0] == 6 + import cppjit + + ctd = cppjit.gbl.CppjitTestData() + + # boolean type + b = array("b", [0]) + ctd.set_bool_r(b) + assert b[0] == True + + # char types (as data) + c = array("B", [0]) + ctd.set_uchar_r(c) + assert c[0] == ord("d") + + # integer types + i = array("h", [0]) + ctd.set_short_r(i) + assert i[0] == -1 + i = array("H", [0]) + ctd.set_ushort_r(i) + assert i[0] == 2 + i = array("i", [0]) + ctd.set_int_r(i) + assert i[0] == -3 + i = array("I", [0]) + ctd.set_uint_r(i) + assert i[0] == 4 + i = array("l", [0]) + ctd.set_long_r(i) + assert i[0] == -5 + i = array("L", [0]) + ctd.set_ulong_r(i) + assert i[0] == 6 if sys.hexversion >= 0x3000000: - i = array('q', [0]); ctd.set_llong_r(i); assert i[0] == -7 - i = array('Q', [0]); ctd.set_ullong_r(i); assert i[0] == 8 - - # floating point types - f = array('f', [0]); ctd.set_float_r(f); assert f[0] == 5. - f = array('d', [0]); ctd.set_double_r(f); assert f[0] == -5. - - @mark.xfail(run=False, condition=IS_VALGRIND or IS_CLING, reason="Valgrind detects memory leak with invalid delete[] operator, crashes on Cling") + i = array("q", [0]) + ctd.set_llong_r(i) + assert i[0] == -7 + i = array("Q", [0]) + ctd.set_ullong_r(i) + assert i[0] == 8 + + # floating point types + f = array("f", [0]) + ctd.set_float_r(f) + assert f[0] == 5.0 + f = array("d", [0]) + ctd.set_double_r(f) + assert f[0] == -5.0 + + @mark.xfail( + run=False, + condition=IS_VALGRIND or IS_CLING, + reason="Valgrind detects memory leak with invalid delete[] operator, crashes on Cling", + ) def test06_ctypes_as_ref_and_ptr(self): """Use ctypes for pass-by-ref/ptr""" @@ -167,129 +206,237 @@ def test06_ctypes_as_ref_and_ptr(self): # c_double double float # c_longdouble long double float - import cppyy, ctypes - import cppyy.ll - - ctd = cppyy.gbl.CppyyTestData() - - ### pass by reference/pointer and set value back - - for e in ['_r', '_p']: - # boolean type - b = ctypes.c_bool(False); getattr(ctd, 'set_bool'+e)(b); assert b.value == True - - # char types - if e == '_r': - c = ctypes.c_char(b'\0'); getattr(ctd, 'set_char'+e)(c); assert c.value == b'a' - c = ctypes.c_wchar(u'\0'); getattr(ctd, 'set_wchar'+e)(c); assert c.value == u'b' - c = ctypes.c_byte(0); getattr(ctd, 'set_schar'+e)(c); assert c.value == ord('c') - c = ctypes.c_ubyte(0); getattr(ctd, 'set_uchar'+e)(c); assert c.value == ord('d') - - # integer types - i = ctypes.c_short(0); getattr(ctd, 'set_short'+e)(i); assert i.value == -1 - i = ctypes.c_ushort(0); getattr(ctd, 'set_ushort'+e)(i); assert i.value == 2 - i = ctypes.c_int(0); getattr(ctd, 'set_int'+e)(i); assert i.value == -3 - i = ctypes.c_uint(0); getattr(ctd, 'set_uint'+e)(i); assert i.value == 4 - i = ctypes.c_long(0); getattr(ctd, 'set_long'+e)(i); assert i.value == -5 - i = ctypes.c_ulong(0); getattr(ctd, 'set_ulong'+e)(i); assert i.value == 6 - i = ctypes.c_longlong(0); getattr(ctd, 'set_llong'+e)(i); assert i.value == -7 - i = ctypes.c_ulonglong(0); getattr(ctd, 'set_ullong'+e)(i); assert i.value == 8 - - # floating point types - f = ctypes.c_float(0); getattr(ctd, 'set_float'+e)(f); assert f.value == 5. - f = ctypes.c_double(0); getattr(ctd, 'set_double'+e)(f); assert f.value == -5. - f = ctypes.c_longdouble(0); getattr(ctd, 'set_ldouble'+e)(f); assert f.value == 10. - - ### pass by pointer and set value back, now using byref (not recommended) + import ctypes + + import cppjit + import cppjit.ll + + ctd = cppjit.gbl.CppjitTestData() + + ### pass by reference/pointer and set value back + + for e in ["_r", "_p"]: + # boolean type + b = ctypes.c_bool(False) + getattr(ctd, "set_bool" + e)(b) + assert b.value == True + + # char types + if e == "_r": + c = ctypes.c_char(b"\0") + getattr(ctd, "set_char" + e)(c) + assert c.value == b"a" + c = ctypes.c_wchar("\0") + getattr(ctd, "set_wchar" + e)(c) + assert c.value == "b" + c = ctypes.c_byte(0) + getattr(ctd, "set_schar" + e)(c) + assert c.value == ord("c") + c = ctypes.c_ubyte(0) + getattr(ctd, "set_uchar" + e)(c) + assert c.value == ord("d") + + # integer types + i = ctypes.c_short(0) + getattr(ctd, "set_short" + e)(i) + assert i.value == -1 + i = ctypes.c_ushort(0) + getattr(ctd, "set_ushort" + e)(i) + assert i.value == 2 + i = ctypes.c_int(0) + getattr(ctd, "set_int" + e)(i) + assert i.value == -3 + i = ctypes.c_uint(0) + getattr(ctd, "set_uint" + e)(i) + assert i.value == 4 + i = ctypes.c_long(0) + getattr(ctd, "set_long" + e)(i) + assert i.value == -5 + i = ctypes.c_ulong(0) + getattr(ctd, "set_ulong" + e)(i) + assert i.value == 6 + i = ctypes.c_longlong(0) + getattr(ctd, "set_llong" + e)(i) + assert i.value == -7 + i = ctypes.c_ulonglong(0) + getattr(ctd, "set_ullong" + e)(i) + assert i.value == 8 + + # floating point types + f = ctypes.c_float(0) + getattr(ctd, "set_float" + e)(f) + assert f.value == 5.0 + f = ctypes.c_double(0) + getattr(ctd, "set_double" + e)(f) + assert f.value == -5.0 + f = ctypes.c_longdouble(0) + getattr(ctd, "set_ldouble" + e)(f) + assert f.value == 10.0 + + ### pass by pointer and set value back, now using byref (not recommended) cb = ctypes.byref - # boolean type - b = ctypes.c_bool(False); ctd.set_bool_p(cb(b)); assert b.value == True - - # char types - c = ctypes.c_ubyte(0); ctd.set_uchar_p(cb(c)); assert c.value == ord('d') - - # integer types - i = ctypes.c_int8(0); ctd.set_int8_p(cb(i)); assert i.value == -27 - i = ctypes.c_uint8(0); ctd.set_uint8_p(cb(i)); assert i.value == 28 - i = ctypes.c_short(0); ctd.set_short_p(cb(i)); assert i.value == -1 - i = ctypes.c_ushort(0); ctd.set_ushort_p(cb(i)); assert i.value == 2 - i = ctypes.c_int(0); ctd.set_int_p(cb(i)); assert i.value == -3 - i = ctypes.c_uint(0); ctd.set_uint_p(cb(i)); assert i.value == 4 - i = ctypes.c_long(0); ctd.set_long_p(cb(i)); assert i.value == -5 - i = ctypes.c_ulong(0); ctd.set_ulong_p(cb(i)); assert i.value == 6 - i = ctypes.c_longlong(0); ctd.set_llong_p(cb(i)); assert i.value == -7 - i = ctypes.c_ulonglong(0); ctd.set_ullong_p(cb(i)); assert i.value == 8 - - # floating point types - f = ctypes.c_float(0); ctd.set_float_p(cb(f)); assert f.value == 5. - f = ctypes.c_double(0); ctd.set_double_p(cb(f)); assert f.value == -5. - - ### pass by ptr/ptr with allocation (ptr/ptr is ambiguous in it's task, so many + # boolean type + b = ctypes.c_bool(False) + ctd.set_bool_p(cb(b)) + assert b.value == True + + # char types + c = ctypes.c_ubyte(0) + ctd.set_uchar_p(cb(c)) + assert c.value == ord("d") + + # integer types + i = ctypes.c_int8(0) + ctd.set_int8_p(cb(i)) + assert i.value == -27 + i = ctypes.c_uint8(0) + ctd.set_uint8_p(cb(i)) + assert i.value == 28 + i = ctypes.c_short(0) + ctd.set_short_p(cb(i)) + assert i.value == -1 + i = ctypes.c_ushort(0) + ctd.set_ushort_p(cb(i)) + assert i.value == 2 + i = ctypes.c_int(0) + ctd.set_int_p(cb(i)) + assert i.value == -3 + i = ctypes.c_uint(0) + ctd.set_uint_p(cb(i)) + assert i.value == 4 + i = ctypes.c_long(0) + ctd.set_long_p(cb(i)) + assert i.value == -5 + i = ctypes.c_ulong(0) + ctd.set_ulong_p(cb(i)) + assert i.value == 6 + i = ctypes.c_longlong(0) + ctd.set_llong_p(cb(i)) + assert i.value == -7 + i = ctypes.c_ulonglong(0) + ctd.set_ullong_p(cb(i)) + assert i.value == 8 + + # floating point types + f = ctypes.c_float(0) + ctd.set_float_p(cb(f)) + assert f.value == 5.0 + f = ctypes.c_double(0) + ctd.set_double_p(cb(f)) + assert f.value == -5.0 + + ### pass by ptr/ptr with allocation (ptr/ptr is ambiguous in it's task, so many # types are allowed to pass; this tests allocation into the pointer) from ctypes import POINTER - import cppyy.ll - - # boolean type - b = POINTER(ctypes.c_bool)(); ctd.set_bool_ppa(b); - assert b[0] == True; assert b[1] == False; assert b[2] == True - cppyy.ll.array_delete(b) - - # char types - c = POINTER(ctypes.c_ubyte)(); ctd.set_uchar_ppa(c) - assert c[0] == ord('k'); assert c[1] == ord('l'); assert c[2] == ord('m') - cppyy.ll.array_delete(c) - - # integer types - i = POINTER(ctypes.c_int8)(); ctd.set_int8_ppa(i) - assert i[0] == -27; assert i[1] == -28; assert i[2] == -29 - cppyy.ll.array_delete['void'](i) # template resolves as signed char* - i = POINTER(ctypes.c_uint8)(); ctd.set_uint8_ppa(i) - assert i[0] == 28; assert i[1] == 29; assert i[2] == 30 - cppyy.ll.array_delete['void'](i) # template resolves as unsigned char* - i = POINTER(ctypes.c_short)(); ctd.set_short_ppa(i) - assert i[0] == -1; assert i[1] == -2; assert i[2] == -3 - cppyy.ll.array_delete(i) - i = POINTER(ctypes.c_ushort)(); ctd.set_ushort_ppa(i) - assert i[0] == 4; assert i[1] == 5; assert i[2] == 6 - cppyy.ll.array_delete(i) - i = POINTER(ctypes.c_int)(); ctd.set_int_ppa(i) - assert i[0] == -7; assert i[1] == -8; assert i[2] == -9 - cppyy.ll.array_delete(i) - i = POINTER(ctypes.c_uint)(); ctd.set_uint_ppa(i) - assert i[0] == 10; assert i[1] == 11; assert i[2] == 12 - cppyy.ll.array_delete(i) - i = POINTER(ctypes.c_long)(); ctd.set_long_ppa(i) - assert i[0] == -13; assert i[1] == -14; assert i[2] == -15 - cppyy.ll.array_delete(i) - i = POINTER(ctypes.c_ulong)(); ctd.set_ulong_ppa(i) - assert i[0] == 16; assert i[1] == 17; assert i[2] == 18 - cppyy.ll.array_delete(i) - i = POINTER(ctypes.c_longlong)(); ctd.set_llong_ppa(i) - assert i[0] == -19; assert i[1] == -20; assert i[2] == -21 - cppyy.ll.array_delete(i) - i = POINTER(ctypes.c_ulonglong)(); ctd.set_ullong_ppa(i) - assert i[0] == 22; assert i[1] == 23; assert i[2] == 24 - cppyy.ll.array_delete(i) - - # floating point types - f = POINTER(ctypes.c_float)(); ctd.set_float_ppa(f) - assert f[0] == 5; assert f[1] == 10; assert f[2] == 20 - cppyy.ll.array_delete(f) - f = POINTER(ctypes.c_double)(); ctd.set_double_ppa(f) - assert f[0] == -5; assert f[1] == -10; assert f[2] == -20 - cppyy.ll.array_delete(f) - f = POINTER(ctypes.c_longdouble)(); ctd.set_ldouble_ppa(f) - assert f[0] == 5; assert f[1] == 10; assert f[2] == 20 - cppyy.ll.array_delete(f) + + import cppjit.ll + + # boolean type + b = POINTER(ctypes.c_bool)() + ctd.set_bool_ppa(b) + assert b[0] == True + assert b[1] == False + assert b[2] == True + cppjit.ll.array_delete(b) + + # char types + c = POINTER(ctypes.c_ubyte)() + ctd.set_uchar_ppa(c) + assert c[0] == ord("k") + assert c[1] == ord("l") + assert c[2] == ord("m") + cppjit.ll.array_delete(c) + + # integer types + i = POINTER(ctypes.c_int8)() + ctd.set_int8_ppa(i) + assert i[0] == -27 + assert i[1] == -28 + assert i[2] == -29 + cppjit.ll.array_delete["void"](i) # template resolves as signed char* + i = POINTER(ctypes.c_uint8)() + ctd.set_uint8_ppa(i) + assert i[0] == 28 + assert i[1] == 29 + assert i[2] == 30 + cppjit.ll.array_delete["void"](i) # template resolves as unsigned char* + i = POINTER(ctypes.c_short)() + ctd.set_short_ppa(i) + assert i[0] == -1 + assert i[1] == -2 + assert i[2] == -3 + cppjit.ll.array_delete(i) + i = POINTER(ctypes.c_ushort)() + ctd.set_ushort_ppa(i) + assert i[0] == 4 + assert i[1] == 5 + assert i[2] == 6 + cppjit.ll.array_delete(i) + i = POINTER(ctypes.c_int)() + ctd.set_int_ppa(i) + assert i[0] == -7 + assert i[1] == -8 + assert i[2] == -9 + cppjit.ll.array_delete(i) + i = POINTER(ctypes.c_uint)() + ctd.set_uint_ppa(i) + assert i[0] == 10 + assert i[1] == 11 + assert i[2] == 12 + cppjit.ll.array_delete(i) + i = POINTER(ctypes.c_long)() + ctd.set_long_ppa(i) + assert i[0] == -13 + assert i[1] == -14 + assert i[2] == -15 + cppjit.ll.array_delete(i) + i = POINTER(ctypes.c_ulong)() + ctd.set_ulong_ppa(i) + assert i[0] == 16 + assert i[1] == 17 + assert i[2] == 18 + cppjit.ll.array_delete(i) + i = POINTER(ctypes.c_longlong)() + ctd.set_llong_ppa(i) + assert i[0] == -19 + assert i[1] == -20 + assert i[2] == -21 + cppjit.ll.array_delete(i) + i = POINTER(ctypes.c_ulonglong)() + ctd.set_ullong_ppa(i) + assert i[0] == 22 + assert i[1] == 23 + assert i[2] == 24 + cppjit.ll.array_delete(i) + + # floating point types + f = POINTER(ctypes.c_float)() + ctd.set_float_ppa(f) + assert f[0] == 5 + assert f[1] == 10 + assert f[2] == 20 + cppjit.ll.array_delete(f) + f = POINTER(ctypes.c_double)() + ctd.set_double_ppa(f) + assert f[0] == -5 + assert f[1] == -10 + assert f[2] == -20 + cppjit.ll.array_delete(f) + f = POINTER(ctypes.c_longdouble)() + ctd.set_ldouble_ppa(f) + assert f[0] == 5 + assert f[1] == 10 + assert f[2] == 20 + cppjit.ll.array_delete(f) def test07_ctypes_pointer_types(self): """Use ctypes for pass-by-ptr/ptr-ptr""" if ispypy: - skip('memory corruption') + skip("memory corruption") # See: # https://docs.python.org/2/library/ctypes.html#fundamental-data-types @@ -300,18 +447,20 @@ def test07_ctypes_pointer_types(self): # c_wchar_p wchar_t* (NULL terminated) unicode or None # c_void_p void* int/long or None - import cppyy, ctypes + import ctypes + + import cppjit - ctd = cppyy.gbl.CppyyTestData() + ctd = cppjit.gbl.CppjitTestData() ptr = ctypes.c_char_p() - for meth in ['char', 'cchar']: - val = getattr(ctd, 'set_'+meth+'_ppm')(ptr) + for meth in ["char", "cchar"]: + val = getattr(ctd, "set_" + meth + "_ppm")(ptr) assert ctd.freeit(ptr) == val ptr = ctypes.c_wchar_p() - for meth in ['wchar', 'cwchar']: - val = getattr(ctd, 'set_'+meth+'_ppm')(ptr) + for meth in ["wchar", "cwchar"]: + val = getattr(ctd, "set_" + meth + "_ppm")(ptr) assert ctd.freeit(ptr) == val ptr = ctypes.c_void_p() @@ -321,73 +470,83 @@ def test07_ctypes_pointer_types(self): def test08_ctypes_type_correctness(self): """If types don't match with ctypes, expect exceptions""" - import cppyy, ctypes + import ctypes - ctd = cppyy.gbl.CppyyTestData() + import cppjit - meth_types = ['bool', 'double'] + ctd = cppjit.gbl.CppjitTestData() + + meth_types = ["bool", "double"] if not IS_WINDOWS: - meth_types.append('long') + meth_types.append("long") - i = ctypes.c_int(0); - for ext in ['_r', '_p']: + i = ctypes.c_int(0) + for ext in ["_r", "_p"]: for meth in meth_types: - with raises(TypeError): getattr(ctd, 'set_'+meth+ext)(i) + with raises(TypeError): + getattr(ctd, "set_" + meth + ext)(i) def test09_numpy_bool_array(self): """Test passing of numpy bool array""" - import cppyy + import cppjit + try: import numpy as np except ImportError: - skip('numpy is not installed') + skip("numpy is not installed") - cppyy.cppdef('int convert_bool(bool* x) {return *x;}') + cppjit.cppdef("int convert_bool(bool* x) {return *x;}") x = np.array([True], dtype=bool) - assert cppyy.gbl.convert_bool(x) + assert cppjit.gbl.convert_bool(x) @mark.xfail(run=False, condition=IS_MAC, reason="Crashes on OSX") def test10_array_of_const_char_star(self): """Test passting of const char*[]""" - import cppyy, ctypes + import ctypes + + import cppjit def py2c(pyargs): cargsn = (ctypes.c_char_p * len(pyargs))(*pyargs) return ctypes.POINTER(ctypes.c_char_p)(cargsn) - pyargs = [b'hello', b'world'] + pyargs = [b"hello", b"world"] cargs = py2c(pyargs) - v = cppyy.gbl.ArrayOfCStrings.takes_array_of_cstrings(cargs, len(pyargs)) + v = cppjit.gbl.ArrayOfCStrings.takes_array_of_cstrings(cargs, len(pyargs)) assert len(v) == len(pyargs) assert list(v) == [x.decode() for x in pyargs] for t in (tuple, list): - for pyargs in (t(['aap', 'noot', 'mies']), t([b'zus', 'jet', 'tim'])): - v = cppyy.gbl.ArrayOfCStrings.takes_array_of_cstrings(pyargs, len(pyargs)) + for pyargs in (t(["aap", "noot", "mies"]), t([b"zus", "jet", "tim"])): + v = cppjit.gbl.ArrayOfCStrings.takes_array_of_cstrings( + pyargs, len(pyargs) + ) assert len(v) == len(pyargs) assert t(v) == t([type(x) == str and x or x.decode() for x in pyargs]) - # debatable, but the following works: - pyargs = ['aap', 1, 'mies'] + # debatable, but the following works: + pyargs = ["aap", 1, "mies"] with raises(TypeError): - cppyy.gbl.ArrayOfCStrings.takes_array_of_cstrings(pyargs, len(pyargs)) + cppjit.gbl.ArrayOfCStrings.takes_array_of_cstrings(pyargs, len(pyargs)) - pyargs = ['aap', None, 'mies'] + pyargs = ["aap", None, "mies"] with raises(TypeError): - cppyy.gbl.ArrayOfCStrings.takes_array_of_cstrings(pyargs, len(pyargs)) + cppjit.gbl.ArrayOfCStrings.takes_array_of_cstrings(pyargs, len(pyargs)) def test11_array_of_const_char_ref(self): """Test passting of const char**&""" - import cppyy, ctypes - import cppyy.ll + import ctypes - # IN parameter case - cppyy.cppdef("""\ + import cppjit + import cppjit.ll + + # IN parameter case + cppjit.cppdef("""\ namespace ConstCharStarStarRef { int initialize(int& argc, char**& argv) { argv[0][0] = 'H'; @@ -395,21 +554,21 @@ def test11_array_of_const_char_ref(self): return argc; } }""") - initialize = cppyy.gbl.ConstCharStarStarRef.initialize + initialize = cppjit.gbl.ConstCharStarStarRef.initialize def py2c(pyargs): cargsn = (ctypes.c_char_p * len(pyargs))(*pyargs) return ctypes.POINTER(ctypes.c_char_p)(cargsn) - pyargs = [b'hello', b'world'] + pyargs = [b"hello", b"world"] cargs = py2c(pyargs) assert initialize(ctypes.c_int(len(pyargs)), py2c(pyargs)) == len(pyargs) - assert cargs[0] == b'Hello' - assert cargs[1] == b'World' + assert cargs[0] == b"Hello" + assert cargs[1] == b"World" - # OUT parameter case - cppyy.cppdef("""\ + # OUT parameter case + cppjit.cppdef("""\ namespace ConstCharStarStarRef { void fill(int& argc, char**& argv) { argc = 2; @@ -418,7 +577,7 @@ def py2c(pyargs): argv[1] = new char[6]; strcpy(argv[1], "World"); } }""") - fill = cppyy.gbl.ConstCharStarStarRef.fill + fill = cppjit.gbl.ConstCharStarStarRef.fill argc = ctypes.c_int(0) ptr = ctypes.c_void_p() @@ -432,20 +591,24 @@ def py2c(pyargs): voidpp = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_void_p)) for i in range(argc.value): - cppyy.ll.array_delete(ctypes.cast(voidpp[i], ctypes.POINTER(ctypes.c_ubyte))) - cppyy.ll.array_delete['char*'](ctypes.cast(ptr, ctypes.POINTER(ctypes.c_char_p))) + cppjit.ll.array_delete( + ctypes.cast(voidpp[i], ctypes.POINTER(ctypes.c_ubyte)) + ) + cppjit.ll.array_delete["char*"]( + ctypes.cast(ptr, ctypes.POINTER(ctypes.c_char_p)) + ) def test12_null_array(self): """Null low level view as empty list""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace NullArray { double* gime_null() { return nullptr; } }""") - ns = cppyy.gbl.NullArray + ns = cppjit.gbl.NullArray assert not ns.gime_null() assert list(ns.gime_null()) == [] @@ -453,23 +616,23 @@ def test12_null_array(self): def test13_array_interface(self): """Test usage of __array__ from numpy""" - import cppyy + import cppjit try: import numpy as np except ImportError: - skip('numpy is not installed') + skip("numpy is not installed") - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ArrayConversions { int ivals[] = {1, 2, 3}; }""") - ns = cppyy.gbl.ArrayConversions + ns = cppjit.gbl.ArrayConversions a = ns.ivals - b = np.array(a, copy=True) # default behavior + b = np.array(a, copy=True) # default behavior assert len(a) == len(b) a[0] = 4 assert a[0] == 4 @@ -488,40 +651,44 @@ def test13_array_interface(self): def test14_templated_arrays(self): """Use of arrays in template types""" - import cppyy + import cppjit - assert cppyy.gbl.std.vector[int].value_type == 'int' - assert cppyy.gbl.std.vector[cppyy.gbl.std.vector[int]].value_type == 'std::vector' - assert cppyy.gbl.std.vector['int[1]'].value_type == 'int[1]' + assert cppjit.gbl.std.vector[int].value_type == "int" + assert ( + cppjit.gbl.std.vector[cppjit.gbl.std.vector[int]].value_type + == "std::vector" + ) + assert cppjit.gbl.std.vector["int[1]"].value_type == "int[1]" def test15_templated_arrays_gmpxx(self): """Use of gmpxx array types in templates""" - import cppyy + import cppjit try: - cppyy.include("gmpxx.h") - cppyy.load_library('gmpxx') + cppjit.include("gmpxx.h") + cppjit.load_library("gmpxx") except ImportError: skip("gmpxx not installed") - assert cppyy.gbl.std.vector[cppyy.gbl.mpz_class].value_type + assert cppjit.gbl.std.vector[cppjit.gbl.mpz_class].value_type - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace test15_templated_arrays_gmpxx::vector { template using value_type = typename T::value_type; }""") - - g = cppyy.gbl - assert g.test15_templated_arrays_gmpxx.vector.value_type[g.std.vector[g.mpz_class]] + g = cppjit.gbl + assert g.test15_templated_arrays_gmpxx.vector.value_type[ + g.std.vector[g.mpz_class] + ] def test16_addressof_nullptr(self): - import cppyy - from cppyy import gbl + import cppjit + from cppjit import gbl - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace LLV { int x = 10; int *ptr_x = &x; @@ -529,19 +696,21 @@ def test16_addressof_nullptr(self): } """) - assert type(gbl.LLV.ptr_x) == cppyy._backend.LowLevelView - assert type(gbl.LLV.ptr_null) == cppyy._backend.LowLevelView - assert cppyy.addressof(gbl.LLV.ptr_x) - assert cppyy.addressof(gbl.LLV.ptr_null) == 0 + assert type(gbl.LLV.ptr_x) == cppjit._backend.LowLevelView + assert type(gbl.LLV.ptr_null) == cppjit._backend.LowLevelView + assert cppjit.addressof(gbl.LLV.ptr_x) + assert cppjit.addressof(gbl.LLV.ptr_null) == 0 def test17_array_delete_multidim(self): """Free a multidimensional (jagged) heap array with ll.array_delete""" - import cppyy, ctypes - import cppyy.ll + import ctypes - # C++ allocates a jagged 2D array (array of pointers to rows) - cppyy.cppdef("""\ + import cppjit + import cppjit.ll + + # C++ allocates a jagged 2D array (array of pointers to rows) + cppjit.cppdef("""\ namespace ArrayDeleteMD { void make2d(int& n, int& m, double**& a) { n = 2; m = 3; @@ -552,9 +721,10 @@ def test17_array_delete_multidim(self): } } }""") - n = ctypes.c_int(0); m = ctypes.c_int(0) + n = ctypes.c_int(0) + m = ctypes.c_int(0) ptr = ctypes.c_void_p() - cppyy.gbl.ArrayDeleteMD.make2d(n, m, ptr) + cppjit.gbl.ArrayDeleteMD.make2d(n, m, ptr) assert n.value == 2 assert m.value == 3 @@ -562,21 +732,25 @@ def test17_array_delete_multidim(self): for i in range(n.value): row = ctypes.cast(rows[i], ctypes.POINTER(ctypes.c_double)) for j in range(m.value): - assert row[j] == 10.*i + j + assert row[j] == 10.0 * i + j - # delete[] each row, then the (outer) array of row pointers + # delete[] each row, then the (outer) array of row pointers for i in range(n.value): - cppyy.ll.array_delete(ctypes.cast(rows[i], ctypes.POINTER(ctypes.c_double))) - cppyy.ll.array_delete['void'](ptr) + cppjit.ll.array_delete( + ctypes.cast(rows[i], ctypes.POINTER(ctypes.c_double)) + ) + cppjit.ll.array_delete["void"](ptr) def test18_array_delete_fixed(self): """Free a fixed-size contiguous multidimensional heap array""" - import cppyy, ctypes - import cppyy.ll + import ctypes + + import cppjit + import cppjit.ll - # C++ allocates a contiguous 2D array (single new int[3][4]) - cppyy.cppdef("""\ + # C++ allocates a contiguous 2D array (single new int[3][4]) + cppjit.cppdef("""\ namespace ArrayDeleteFixed { intptr_t make(int& r, int& c) { r = 3; c = 4; @@ -586,114 +760,146 @@ def test18_array_delete_fixed(self): return (intptr_t)a; } }""") - r = ctypes.c_int(0); c = ctypes.c_int(0) - addr = cppyy.gbl.ArrayDeleteFixed.make(r, c) + r = ctypes.c_int(0) + c = ctypes.c_int(0) + addr = cppjit.gbl.ArrayDeleteFixed.make(r, c) blk = ctypes.cast(addr, ctypes.POINTER(ctypes.c_int)) for k in range(r.value * c.value): assert blk[k] == k - # a single contiguous allocation is released with a single delete[] - cppyy.ll.array_delete(ctypes.cast(ctypes.c_void_p(addr), ctypes.POINTER(ctypes.c_int))) + # a single contiguous allocation is released with a single delete[] + cppjit.ll.array_delete( + ctypes.cast(ctypes.c_void_p(addr), ctypes.POINTER(ctypes.c_int)) + ) + class TestMULTIDIMARRAYS: def setup_class(cls): - import cppyy + import cppjit cls.test_dct = test_dct - cls.datatypes = cppyy.load_reflection_info(cls.test_dct) + cls.datatypes = cppjit.load_reflection_info(cls.test_dct) cls.numeric_builtin_types = [ - 'short', 'unsigned short', 'int', 'unsigned int', 'long', 'unsigned long', - 'long long', 'unsigned long long', 'float', 'double' + "short", + "unsigned short", + "int", + "unsigned int", + "long", + "unsigned long", + "long long", + "unsigned long long", + "float", + "double", ] cls.nbt_short_names = [ - 'short', 'ushort', 'int', 'uint', 'long', 'ulong', 'llong', 'ullong', 'float', 'double' + "short", + "ushort", + "int", + "uint", + "long", + "ulong", + "llong", + "ullong", + "float", + "double", ] try: import numpy as np + if IS_WINDOWS: np_long, np_ulong = np.int32, np.uint32 else: np_long, np_ulong = np.int64, np.uint64 cls.numpy_builtin_types = [ - np.short, np.ushort, np.int32, np.uint32, np_long, np_ulong, - np.longlong, np.ulonglong, np.float32, np.double + np.short, + np.ushort, + np.int32, + np.uint32, + np_long, + np_ulong, + np.longlong, + np.ulonglong, + np.float32, + np.double, ] except ImportError: pass def _data_m(self, lbl): - return [('m_'+tp.replace(' ', '_')+lbl, tp) for tp in self.numeric_builtin_types] + return [ + ("m_" + tp.replace(" ", "_") + lbl, tp) for tp in self.numeric_builtin_types + ] def test01_2D_arrays(self): """Access and use of 2D data members""" - import cppyy + import cppjit - ns = cppyy.gbl.MultiDimArrays + ns = cppjit.gbl.MultiDimArrays h = ns.DataHolder() - data2a = self._data_m('2a') + data2a = self._data_m("2a") for m, tp in data2a: getattr(h, m).reshape((5, 7)) arr = getattr(h, m) assert arr.shape == (5, 7) - elem_tp = getattr(cppyy.gbl, tp) + elem_tp = getattr(cppjit.gbl, tp) for i in range(5): for j in range(7): - val = elem_tp(5*i+j) + val = elem_tp(5 * i + j) assert arr[i][j] == val assert arr[i, j] == val for i in range(5): for j in range(7): - arr[i][j] = elem_tp(4+5*i+j) + arr[i][j] = elem_tp(4 + 5 * i + j) for i in range(5): for j in range(7): - val = elem_tp(4+5*i+j) + val = elem_tp(4 + 5 * i + j) assert arr[i][j] == val assert arr[i, j] == val - data2c = self._data_m('2c') + data2c = self._data_m("2c") for m, tp in data2c: arr = getattr(h, m) assert arr.shape == (3, 5) - elem_tp = getattr(cppyy.gbl, tp) + elem_tp = getattr(cppjit.gbl, tp) for i in range(3): for j in range(5): - val = elem_tp(3*i+j) + val = elem_tp(3 * i + j) assert arr[i][j] == val assert arr[i, j] == val def test02_assign_2D_arrays(self): """Direct assignment of 2D arrays""" - import cppyy + import cppjit try: import numpy as np except ImportError: - skip('numpy is not installed') + skip("numpy is not installed") - ns = cppyy.gbl.MultiDimArrays + ns = cppjit.gbl.MultiDimArrays h = ns.DataHolder() - # copy assignment - data2c = self._data_m('2c') + # copy assignment + data2c = self._data_m("2c") for itp, (m, tp) in enumerate(data2c): setattr(h, m, np.ones((3, 5), dtype=self.numpy_builtin_types[itp])) arr = getattr(h, m) assert arr.shape == (3, 5) - val = getattr(cppyy.gbl, tp)(1) + val = getattr(cppjit.gbl, tp)(1) for i in range(3): for j in range(5): assert arr[i][j] == val assert arr[i, j] == val - # size checking for copy assignment + # size checking for copy assignment for itp, (m, tp) in enumerate(data2c): with raises(ValueError): setattr(h, m, np.ones((5, 5), dtype=self.numpy_builtin_types[itp])) @@ -701,17 +907,17 @@ def test02_assign_2D_arrays(self): with raises(ValueError): setattr(h, m, np.ones((3, 7), dtype=self.numpy_builtin_types[itp])) - # pointer assignment + # pointer assignment N, M = 11, 7 - data2b = self._data_m('2b') + data2b = self._data_m("2b") for itp, (m, tp) in enumerate(data2b): - setattr(h, m, getattr(h, 'new_'+self.nbt_short_names[itp]+'2d')(N, M)) + setattr(h, m, getattr(h, "new_" + self.nbt_short_names[itp] + "2d")(N, M)) arr = getattr(h, m) - elem_tp = getattr(cppyy.gbl, tp) + elem_tp = getattr(cppjit.gbl, tp) for i in range(N): for j in range(M): - val = elem_tp(7*i+j) + val = elem_tp(7 * i + j) assert arr[i][j] == val assert arr[i, j] == val @@ -722,56 +928,56 @@ def test02_assign_2D_arrays(self): def test03_3D_arrays(self): """Access and use of 3D data members""" - import cppyy + import cppjit - ns = cppyy.gbl.MultiDimArrays + ns = cppjit.gbl.MultiDimArrays h = ns.DataHolder() - data3a = self._data_m('3a') + data3a = self._data_m("3a") for m, tp in data3a: getattr(h, m).reshape((5, 7, 11)) arr = getattr(h, m) assert arr.shape == (5, 7, 11) - elem_tp = getattr(cppyy.gbl, tp) + elem_tp = getattr(cppjit.gbl, tp) for i in range(5): for j in range(7): for k in range(11): - val = elem_tp(7*i+3*j+k) + val = elem_tp(7 * i + 3 * j + k) assert arr[i][j][k] == val assert arr[i, j, k] == val for i in range(5): for j in range(7): for k in range(11): - arr[i][j][k] = elem_tp(4+7*i+3*j+k) + arr[i][j][k] = elem_tp(4 + 7 * i + 3 * j + k) for i in range(5): for j in range(7): for k in range(11): - val = elem_tp(4+7*i+3*j+k) + val = elem_tp(4 + 7 * i + 3 * j + k) assert arr[i][j][k] == val assert arr[i, j, k] == val - data3c = self._data_m('3c') + data3c = self._data_m("3c") for m, tp in data3c: arr = getattr(h, m) assert arr.shape == (3, 5, 7) - elem_tp = getattr(cppyy.gbl, tp) + elem_tp = getattr(cppjit.gbl, tp) for i in range(3): for j in range(5): for k in range(7): - val = elem_tp(3*i+2*j+k) + val = elem_tp(3 * i + 2 * j + k) assert arr[i][j][k] == val assert arr[i, j, k] == val def test04_malloc(self): """Use of malloc to create multi-dim arrays""" - import cppyy - import cppyy.ll + import cppjit + import cppjit.ll - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace MallocChecker { template struct Foo { @@ -788,46 +994,50 @@ def test04_malloc(self): return Foo(other); } }""") - ns = cppyy.gbl.MallocChecker + ns = cppjit.gbl.MallocChecker - for dtype in ["int", "int*", "int**",]: - bar = cppyy.ll.malloc[dtype](4) + for dtype in [ + "int", + "int*", + "int**", + ]: + bar = cppjit.ll.malloc[dtype](4) assert len(bar) == 4 - # variable assignment + # variable assignment foo = ns.Foo[dtype]() foo.bar = bar assert foo.eq(bar) - # pointer passed to the constructor + # pointer passed to the constructor foo2 = ns.Foo[dtype](bar) assert foo2.eq(bar) - # pointer passed to a function + # pointer passed to a function foo3 = ns.create[dtype](bar) assert foo3.eq(bar) - cppyy.ll.free(bar) + cppjit.ll.free(bar) def test05_char_multidim(self): """Multi-dimensional char arrays""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace StringArray { char str_array[3][8] = {"s1\0", "s23\0", "s456\0"}; }""") - ns = cppyy.gbl.StringArray + ns = cppjit.gbl.StringArray for i, v in enumerate(("s1", "s23", "s456")): assert len(ns.str_array[i]) == 8 - assert list(ns.str_array[i])[:len(v)] == list(v) - + assert list(ns.str_array[i])[: len(v)] == list(v) + def test06_fixed_multidim_array_itemsize(self): """conversion of fixed-length array low level views into NumPy arrays""" - import cppyy + import cppjit try: import numpy as np @@ -845,12 +1055,12 @@ def test06_fixed_multidim_array_itemsize(self): for cpp_type, np_dtype, (rows, cols) in cases: tag = cpp_type.replace(" ", "_") - cppyy.cppdef(f""" + cppjit.cppdef(f""" struct cpp_arr_{tag} {{ {cpp_type} a[{rows}][{cols}]; }}; """) - s = getattr(cppyy.gbl, f"cpp_arr_{tag}")() + s = getattr(cppjit.gbl, f"cpp_arr_{tag}")() itemsize = np.dtype(np_dtype).itemsize mv = memoryview(s.a) @@ -862,12 +1072,11 @@ def test06_fixed_multidim_array_itemsize(self): arr = np.array(s.a, dtype=np_dtype) assert arr.shape == (rows, cols) - def test07_3D_custom_struct(self): - import cppyy - from cppyy import gbl + import cppjit + from cppjit import gbl - cppyy.cppdef(r""" + cppjit.cppdef(r""" constexpr int S = 4; struct Klass { @@ -885,7 +1094,7 @@ def test07_3D_custom_struct(self): """) assert gbl.klasses - # assert type(gbl.klasses) == cppyy._backend.LowLevelView # FIXME: https://github.com/compiler-research/CPyCppyy/issues/141 + # assert type(gbl.klasses) == cppjit._backend.LowLevelView # FIXME: https://github.com/compiler-research/cpyrt/issues/141 for i in range(gbl.S): for j in range(gbl.S + 3): diff --git a/test/test_main.py b/test/test_main.py index 8de140b..9e434de 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -1,5 +1,6 @@ if __name__ == "__main__": import sys + import pytest sys.exit(pytest.main()) diff --git a/test/test_numba.py b/test/test_numba.py index 3cf79e0..646dd2e 100644 --- a/test/test_numba.py +++ b/test/test_numba.py @@ -1,10 +1,13 @@ -import py, os, sys -import math, time +import math +import os +import time + from pytest import mark, raises -from .support import setup_make, IS_LINUX_ARM, IS_VALGRIND +from support import IS_LINUX_ARM, IS_VALGRIND try: import numba + has_numba = True except ImportError: has_numba = False @@ -12,29 +15,29 @@ if IS_LINUX_ARM and IS_VALGRIND: has_numba = False + class TestREFLEX: def setup_class(cls): - import cppyy - import cppyy.reflex + pass def test01_instance_box_unbox(self): """Access to box/unbox methods""" - import cppyy + import cppjit - assert cppyy.addressof('Instance_AsVoidPtr') - assert cppyy.addressof('Instance_FromVoidPtr') + assert cppjit.addressof("Instance_AsVoidPtr") + assert cppjit.addressof("Instance_FromVoidPtr") with raises(TypeError): - cppyy.addressof('doesnotexist') + cppjit.addressof("doesnotexist") def test02_method_reflection(self): """Method reflection tooling""" - import cppyy - import cppyy.reflex as r + import cppjit + import cppjit.reflex as r - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ReflexTest { int free1() { return 42; } double free2() { return 42.; } @@ -42,23 +45,32 @@ def test02_method_reflection(self): class MyData_m1 {}; }""") - ns = cppyy.gbl.ReflexTest + ns = cppjit.gbl.ReflexTest - assert ns.free1.__cpp_reflex__(r.RETURN_TYPE) == 'int' - assert ns.free2.__cpp_reflex__(r.RETURN_TYPE) == 'double' + assert ns.free1.__cpp_reflex__(r.RETURN_TYPE) == "int" + assert ns.free2.__cpp_reflex__(r.RETURN_TYPE) == "double" - assert ns.MyData_m1.__init__.__cpp_reflex__(r.RETURN_TYPE) == ns.MyData_m1 - assert ns.MyData_m1.__init__.__cpp_reflex__(r.RETURN_TYPE, r.OPTIMAL) == ns.MyData_m1 - assert ns.MyData_m1.__init__.__cpp_reflex__(r.RETURN_TYPE, r.AS_TYPE) == ns.MyData_m1 - assert ns.MyData_m1.__init__.__cpp_reflex__(r.RETURN_TYPE, r.AS_STRING) == 'ReflexTest::MyData_m1' + assert ns.MyData_m1.__init__.__cpp_reflex__(r.RETURN_TYPE) == ns.MyData_m1 + assert ( + ns.MyData_m1.__init__.__cpp_reflex__(r.RETURN_TYPE, r.OPTIMAL) + == ns.MyData_m1 + ) + assert ( + ns.MyData_m1.__init__.__cpp_reflex__(r.RETURN_TYPE, r.AS_TYPE) + == ns.MyData_m1 + ) + assert ( + ns.MyData_m1.__init__.__cpp_reflex__(r.RETURN_TYPE, r.AS_STRING) + == "ReflexTest::MyData_m1" + ) def test03_datamember_reflection(self): """Data member reflection tooling""" - import cppyy - import cppyy.reflex as r + import cppjit + import cppjit.reflex as r - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ReflexTest { class MyData_d1 { public: @@ -66,21 +78,24 @@ class MyData_d1 { double m_double; }; }""") - ns = cppyy.gbl.ReflexTest + ns = cppjit.gbl.ReflexTest - assert ns.MyData_d1.__dict__['m_int'].__cpp_reflex__(r.TYPE) == 'int' - assert ns.MyData_d1.__dict__['m_double'].__cpp_reflex__(r.TYPE) == 'double' + assert ns.MyData_d1.__dict__["m_int"].__cpp_reflex__(r.TYPE) == "int" + assert ns.MyData_d1.__dict__["m_double"].__cpp_reflex__(r.TYPE) == "double" - d = ns.MyData_d1(); daddr = cppyy.addressof(d) - assert ns.MyData_d1.__dict__['m_int'].__cpp_reflex__(r.OFFSET) == 0 - assert ns.MyData_d1.__dict__['m_double'].__cpp_reflex__(r.OFFSET) == cppyy.addressof(d, 'm_double') - daddr + d = ns.MyData_d1() + daddr = cppjit.addressof(d) + assert ns.MyData_d1.__dict__["m_int"].__cpp_reflex__(r.OFFSET) == 0 + assert ( + ns.MyData_d1.__dict__["m_double"].__cpp_reflex__(r.OFFSET) + == cppjit.addressof(d, "m_double") - daddr + ) @mark.skipif(has_numba == False, reason="numba not found") class TestNUMBA: def setup_class(cls): - import cppyy - import cppyy.numba_ext + pass def compare(self, go_slow, go_fast, N, *args): t0 = time.time() @@ -98,7 +113,7 @@ def compare(self, go_slow, go_fast, N, *args): def test01_compiled_free_func(self): """Numba-JITing of a compiled free function""" - import cppyy + import cppjit import numpy as np def go_slow(a): @@ -111,7 +126,7 @@ def go_slow(a): def go_fast(a): trace = 0.0 for i in range(a.shape[0]): - trace += cppyy.gbl.tanh(a[i, i]) + trace += cppjit.gbl.tanh(a[i, i]) return a + trace x = np.arange(100, dtype=np.float64).reshape(10, 10) @@ -122,17 +137,17 @@ def go_fast(a): def test02_JITed_template_free_func(self): """Numba-JITing of Cling-JITed templated free function""" - import cppyy + import cppjit import numpy as np - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ template T add42(T t) { return T(t+42); }""") def add42(t): - return type(t)(t+42) + return type(t)(t + 42) def go_slow(a): trace = 0.0 @@ -144,7 +159,7 @@ def go_slow(a): def go_fast(a): trace = 0.0 for i in range(a.shape[0]): - trace += cppyy.gbl.add42(a[i, i]) + cppyy.gbl.add42(int(a[i, i])) + trace += cppjit.gbl.add42(a[i, i]) + cppjit.gbl.add42(int(a[i, i])) return a + trace x = np.arange(100, dtype=np.float64).reshape(10, 10) @@ -155,10 +170,10 @@ def go_fast(a): def test03_proxy_argument_for_field(self): """Numba-JITing of a free function taking a proxy argument for field access""" - import cppyy + import cppjit import numpy as np - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ struct MyNumbaData03 { MyNumbaData03(int64_t i1, int64_t i2) : fField1(i1), fField2(i2) {} int64_t fField1; @@ -178,20 +193,20 @@ def go_fast(a, d): trace += d.fField1 + d.fField2 return a + trace - # note: need a sizable array to outperform given the unboxing overhead + # note: need a sizable array to outperform given the unboxing overhead x = np.arange(10000, dtype=np.float64).reshape(100, 100) - d = cppyy.gbl.MyNumbaData03(42, 27) + d = cppjit.gbl.MyNumbaData03(42, 27) - assert((go_fast(x, d) == go_slow(x, d)).all()) + assert (go_fast(x, d) == go_slow(x, d)).all() assert self.compare(go_slow, go_fast, 10000, x, d) def test04_proxy_argument_for_method(self): """Numba-JITing of a free function taking a proxy argument for method access""" - import cppyy + import cppjit import numpy as np - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ struct MyNumbaData04 { MyNumbaData04(int64_t i) : fField(i) {} int64_t get_field() { return fField; } @@ -211,30 +226,31 @@ def go_fast(a, d): trace += d.get_field() return a + trace - # note: need a sizable array to outperform given the unboxing overhead + # note: need a sizable array to outperform given the unboxing overhead x = np.arange(10000, dtype=np.float64).reshape(100, 100) - d = cppyy.gbl.MyNumbaData04(42) + d = cppjit.gbl.MyNumbaData04(42) - assert((go_fast(x, d) == go_slow(x, d)).all()) + assert (go_fast(x, d) == go_slow(x, d)).all() assert self.compare(go_slow, go_fast, 10000, x, d) - + def test05_multiple_arguments_function(self): """Numba-JITing of functions with multiple arguments""" - import cppyy + import cppjit import numpy as np - cppyy.cppdef(""" + cppjit.cppdef(""" double add_double(double a, double b, double c) { double d = a + b + c; return d; } """) + @numba.njit() def loop_add(x): sum = 0 for row in x: - sum += cppyy.gbl.add_double(row[0], row[1], row[2]) + sum += cppjit.gbl.add_double(row[0], row[1], row[2]) return sum x = np.arange(3000, dtype=np.float64).reshape(1000, 3) @@ -247,9 +263,10 @@ def loop_add(x): def test06_multiple_arguments_template_freefunction(self): """Numba-JITing of a free template function that recieves more than one template arg""" - import cppyy + import cppjit import numpy as np - cppyy.cppdef(""" + + cppjit.cppdef(""" namespace NumbaSupportExample { template T1 add(T1 a, T1 b) { return a + b; } @@ -259,7 +276,7 @@ def test06_multiple_arguments_template_freefunction(self): def tma(x): sum = 0 for row in x: - sum += cppyy.gbl.NumbaSupportExample.add(row[0], row[1]) + sum += cppjit.gbl.NumbaSupportExample.add(row[0], row[1]) return sum x = np.arange(2000, dtype=np.float64).reshape(1000, 2) @@ -272,7 +289,7 @@ def tma(x): def test07_datatype_mapping(self): """Numba-JITing of various data types""" - import cppyy + import cppjit @numba.jit(nopython=True) def access_field(d): @@ -284,31 +301,41 @@ def access_field(d): %s buf, fField; }; }""" - cppyy.cppdef("namespace NumbaDTT { }") - ns = cppyy.gbl.NumbaDTT + cppjit.cppdef("namespace NumbaDTT { }") + ns = cppjit.gbl.NumbaDTT types = ( # 'int8_t', 'uint8_t', # TODO b/c check using return type fails - 'short', 'unsigned short', 'int', 'unsigned int', - 'int32_t', 'uint32_t', 'int64_t', 'uint64_t', - 'long', 'unsigned long', 'long long', 'unsigned long long', - 'float', 'double', + "short", + "unsigned short", + "int", + "unsigned int", + "int32_t", + "uint32_t", + "int64_t", + "uint64_t", + "long", + "unsigned long", + "long long", + "unsigned long long", + "float", + "double", ) - nl = cppyy.gbl.std.numeric_limits + nl = cppjit.gbl.std.numeric_limits for i, ntype in enumerate(types): - cppyy.cppdef(code % (i, i, ntype, ntype)) - for m in ('min', 'max'): + cppjit.cppdef(code % (i, i, ntype, ntype)) + for m in ("min", "max"): val = getattr(nl[ntype], m)() - assert access_field(getattr(ns, 'M%d'%i)(val)) == val + assert access_field(getattr(ns, "M%d" % i)(val)) == val def test08_object_returns(self): """Numba-JITing of a function that returns an object""" - import cppyy + import cppjit import numpy as np - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ struct MyNumbaData06 { MyNumbaData06(int64_t i1) : fField(i1) {} int64_t fField; @@ -320,27 +347,28 @@ def test08_object_returns(self): def go_slow(a): trace = 0.0 for i in range(a.shape[0]): - trace += cppyy.gbl.get_numba_data_06().fField + trace += cppjit.gbl.get_numba_data_06().fField return a + trace @numba.jit(nopython=True) def go_fast(a): trace = 0.0 for i in range(a.shape[0]): - trace += cppyy.gbl.get_numba_data_06().fField + trace += cppjit.gbl.get_numba_data_06().fField return a + trace x = np.arange(100, dtype=np.float64).reshape(10, 10) - assert((go_fast(x) == go_slow(x)).all()) + assert (go_fast(x) == go_slow(x)).all() assert self.compare(go_slow, go_fast, 100000, x) - + def test09_non_typed_templates(self): """Numba-JITing of a free template function that recieves multiple template args with non types""" - import cppyy + import cppjit import numpy as np - cppyy.cppdef(""" + + cppjit.cppdef(""" namespace NumbaSupportExample { template double add(double a, T1 b, T2 c) { return a + b + c; } @@ -350,7 +378,7 @@ def test09_non_typed_templates(self): def tma(x): sum = 0 for row in x: - sum += cppyy.gbl.NumbaSupportExample.add(row[0], row[1], row[2]) + sum += cppjit.gbl.NumbaSupportExample.add(row[0], row[1], row[2]) return sum x = np.arange(3000, dtype=np.float64).reshape(1000, 3) @@ -361,11 +389,11 @@ def tma(x): assert sum == tma(x) def test10_returning_a_reference(self): - import cppyy - import numpy as np + import cppjit import numba + import numpy as np - cppyy.cppdef(""" + cppjit.cppdef(""" int64_t& ref_add(int64_t x, int64_t y) { int64_t c = x + y; static int64_t result = 0; @@ -382,14 +410,13 @@ def slow_add(X): i = i + 1 return k - @numba.njit() def fast_add(X): res = [] for row in X: a = row[0] b = row[1] - k = cppyy.gbl.ref_add(a, b) + k = cppjit.gbl.ref_add(a, b) res.append(k[0]) return res @@ -398,11 +425,12 @@ def fast_add(X): def test11_ptr_ref_support(self): """Numba-JITing of a increment method belonging to a class, and also swaps the pointers and reflects the change on the python ctypes variables""" - import cppyy import ctypes import random - cppyy.cppdef(""" + import cppjit + + cppjit.cppdef(""" namespace RefTest { class Box{ public: @@ -428,9 +456,9 @@ class Box{ } """) - ns = cppyy.gbl.RefTest - assert ns.Box.__dict__['a'].__cpp_reflex__(cppyy.reflex.TYPE) == 'long' - assert ns.Box.__dict__['b'].__cpp_reflex__(cppyy.reflex.TYPE) == 'long *' + ns = cppjit.gbl.RefTest + assert ns.Box.__dict__["a"].__cpp_reflex__(cppjit.reflex.TYPE) == "long" + assert ns.Box.__dict__["b"].__cpp_reflex__(cppjit.reflex.TYPE) == "long *" @numba.njit() def inc_b(d, k): @@ -462,15 +490,17 @@ def inc_c(d, k): assert b.value == z + k assert c.value == y + k - @mark.xfail(run=False, condition=IS_LINUX_ARM, reason="Crash in llvmlite on Linux ARM") + @mark.xfail( + run=False, condition=IS_LINUX_ARM, reason="Crash in llvmlite on Linux ARM" + ) def test12_std_vector_pass_by_ref(self): - """Numba-JITing of a method that performs scalar addition to a std::vector initialised through pointers """ - import cppyy - import ctypes + """Numba-JITing of a method that performs scalar addition to a std::vector initialised through pointers""" + + import cppjit import numba import numpy as np - cppyy.cppdef(""" + cppjit.cppdef(""" template std::vector make_vector(const std::vector& v, std::vector l) { std::vector u(l); @@ -504,7 +534,8 @@ class BoxVector{ }; } """) - ns = cppyy.gbl.RefTest + ns = cppjit.gbl.RefTest + @numba.njit() def add_vec_fast(d): for i in range(10000): @@ -527,7 +558,10 @@ def square_vec_slow(x): x = np.square(x) return x - assert ns.BoxVector.__dict__['a'].__cpp_reflex__(cppyy.reflex.TYPE) == 'std::vector *' + assert ( + ns.BoxVector.__dict__["a"].__cpp_reflex__(cppjit.reflex.TYPE) + == "std::vector *" + ) add_vec_fast(ns.BoxVector()) square_vec_fast(ns.BoxVector()) @@ -536,8 +570,8 @@ def square_vec_slow(x): a = np.random.randint(1, 100, size=10000, dtype=np.int64) b = np.random.randint(1, 4, size=10, dtype=np.int64) - x = cppyy.gbl.std.vector['long'](a.flatten()) - y = cppyy.gbl.std.vector['long'](b.flatten()) + x = cppjit.gbl.std.vector["long"](a.flatten()) + y = cppjit.gbl.std.vector["long"](b.flatten()) t0 = time.time() add_vec_fast(ns.BoxVector(x)) @@ -560,13 +594,13 @@ def square_vec_slow(x): def test13_std_vector_dot_product(self): """Numba-JITing of a dot_product method of a class that stores pointers to std::vectors on the python side""" - import cppyy, cppyy.ll - import ctypes - import cppyy.numba_ext + import cppjit + import cppjit.ll + import cppjit.numba_ext import numba import numpy as np - cppyy.cppdef(""" + cppjit.cppdef(""" namespace RefTest { class DotVector{ private: @@ -606,13 +640,14 @@ def dot_product_fast(d): for i in range(10000): res += d.self_dot_product() return res + def np_dot_product(x, y): res = 0 for i in range(10000): res += np.dot(x, y) return res - ns = cppyy.gbl.RefTest + ns = cppjit.gbl.RefTest a = np.arange(20000, dtype=np.int64) b = np.arange(20000, dtype=np.int64) @@ -623,8 +658,8 @@ def np_dot_product(x, y): # for i in a: # vec_list.append([vector['long'](i[0]), vector['long'](i[1])]) - x = cppyy.gbl.std.vector['long'](a.flatten()) - y = cppyy.gbl.std.vector['long'](b.flatten()) + x = cppjit.gbl.std.vector["long"](a.flatten()) + y = cppjit.gbl.std.vector["long"](b.flatten()) d = ns.DotVector(x, y) dot_product_fast(d) res = 0 @@ -638,42 +673,44 @@ def np_dot_product(x, y): res = np_dot_product(x, y) time_np = time.time() - t0 - assert (njit_res == res) + assert njit_res == res # assert (time_njit < time_np) @mark.skip(reason="Fails at ImplCLassType Boxing call in lowering") def test14_eigen_numba(self): - """Numba-JITing of a function that uses a cppyy declared Eigen Vector""" + """Numba-JITing of a function that uses a cppjit declared Eigen Vector""" - import numpy as np - import time - import cppyy, numba, warnings - import cppyy.numba_ext - import os + import warnings - inc_paths = [os.path.join(os.path.sep, 'usr', 'include'), - os.path.join(os.path.sep, 'usr', 'local', 'include')] + import cppjit + import cppjit.numba_ext + import numba + + inc_paths = [ + os.path.join(os.path.sep, "usr", "include"), + os.path.join(os.path.sep, "usr", "local", "include"), + ] eigen_path = None for p in inc_paths: - p = os.path.join(p, 'eigen3') + p = os.path.join(p, "eigen3") if os.path.exists(p): eigen_path = p - cppyy.add_include_path(eigen_path) + cppjit.add_include_path(eigen_path) with warnings.catch_warnings(): - warnings.simplefilter('ignore') - cppyy.include('Eigen/Dense') + warnings.simplefilter("ignore") + cppjit.include("Eigen/Dense") # Define the templated function that takes Eigen objects - cppyy.cppdef(''' + cppjit.cppdef(""" template T multiply_scalar(T value, int64_t scalar) { return value * scalar; } - ''') + """) - cppyy.cppdef(''' + cppjit.cppdef(""" #include #include namespace EigenFake { @@ -700,39 +737,38 @@ class Matrix { } }; } - ''') + """) @numba.jit(nopython=True) def mul_njit(m, x): - matrix = cppyy.gbl.multiply_scalar(m, x) + matrix = cppjit.gbl.multiply_scalar(m, x) return matrix - mat = cppyy.gbl.EigenFake.Matrix(int, 2, 2) + mat = cppjit.gbl.EigenFake.Matrix(int, 2, 2) mat = {1.0, 2.0, 3.0, 4.0} - vector = cppyy.gbl.Eigen.VectorXd(2) + vector = cppjit.gbl.Eigen.VectorXd(2) vector[0] = 4.0 vector[1] = 2.0 vector[2] = 3.0 - matrix2 = cppyy.gbl.multiply_scalar(vector, 5) + matrix2 = cppjit.gbl.multiply_scalar(vector, 5) result = mul_njit(vector, 5) - assert(result == matrix2) + assert result == matrix2 @mark.skipif(has_numba == False, reason="numba not found") class TestNUMBA_DOC: def setup_class(cls): - import cppyy - import cppyy.numba_ext + pass def test01_templated_freefunction(self): """Numba support documentation example: free templated function""" - import cppyy + import cppjit import numba import numpy as np - cppyy.cppdef(""" + cppjit.cppdef(""" namespace NumbaSupportExample { template T square(T t) { return t*t; } @@ -742,7 +778,7 @@ def test01_templated_freefunction(self): def tsa(a): total = type(a[0])(0) for i in range(len(a)): - total += cppyy.gbl.NumbaSupportExample.square(a[i]) + total += cppjit.gbl.NumbaSupportExample.square(a[i]) return total a = np.array(range(10), dtype=np.float32) @@ -756,11 +792,11 @@ def tsa(a): def test02_class_features(self): """Numba support documentation example: class features""" - import cppyy + import cppjit import numba import numpy as np - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace NumbaSupportExample { class MyData { public: @@ -785,7 +821,7 @@ def tsdf(a, d): total += a[i] + d.fField1 + d.fField2 return total - d = cppyy.gbl.NumbaSupportExample.MyData(5, 6) + d = cppjit.gbl.NumbaSupportExample.MyData(5, 6) a = np.array(range(10), dtype=np.int32) assert tsdf(a, d) == 155 @@ -794,7 +830,7 @@ def tsdf(a, d): def tsdm(a, d): total = type(a[0])(0) for i in range(len(a)): - total += a[i] + d.get_field1() + d.get_field2() + total += a[i] + d.get_field1() + d.get_field2() return total assert tsdm(a, d) == 155 diff --git a/test/test_operators.py b/test/test_operators.py index 75c6aca..e1124b0 100644 --- a/test/test_operators.py +++ b/test/test_operators.py @@ -1,9 +1,15 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, pylong, maxvalue, IS_WINDOWS, IS_MAC, IS_CLANG_REPL, IS_CLING +import py +from pytest import raises, skip +from support import ( + IS_WINDOWS, + maxvalue, + pylong, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("operatorsDict")) +test_dct = str(currpath.join("cpp/operatorsDict")) + def setup_module(mod): setup_make("operators") @@ -12,62 +18,64 @@ def setup_module(mod): class TestOPERATORS: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.operators = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.operators = cppjit.load_reflection_info(cls.test_dct) def teardown_method(self, meth): import gc + gc.collect() def test01_math_operators(self): """Test overloading of math operators""" - import cppyy + import cppjit - number = cppyy.gbl.number + number = cppjit.gbl.number assert (number(20) + number(10)) == number(30) - assert (number(20) + 10 ) == number(30) + assert (number(20) + 10) == number(30) assert (number(20) - number(10)) == number(10) - assert (number(20) - 10 ) == number(10) + assert (number(20) - 10) == number(10) assert (number(20) / number(10)) == number(2) - assert (number(20) / 10 ) == number(2) + assert (number(20) / 10) == number(2) assert (number(20) * number(10)) == number(200) - assert (number(20) * 10 ) == number(200) - assert (number(20) % 10 ) == number(0) + assert (number(20) * 10) == number(200) + assert (number(20) % 10) == number(0) assert (number(20) % number(10)) == number(0) - assert (number(5) & number(14)) == number(4) - assert (number(5) | number(14)) == number(15) - assert (number(5) ^ number(14)) == number(11) - assert (number(5) << 2) == number(20) + assert (number(5) & number(14)) == number(4) + assert (number(5) | number(14)) == number(15) + assert (number(5) ^ number(14)) == number(11) + assert (number(5) << 2) == number(20) assert (number(20) >> 2) == number(5) def test02_unary_math_operators(self): """Test overloading of unary math operators""" - import cppyy + import cppjit - number = cppyy.gbl.number + number = cppjit.gbl.number - n = number(20) + n = number(20) n += number(10) n -= number(10) n *= number(10) n /= number(2) assert n == number(100) - nn = -n; + nn = -n assert nn == number(-100) def test03_comparison_operators(self): """Test overloading of comparison operators""" - import cppyy + import cppjit - number = cppyy.gbl.number + number = cppjit.gbl.number - assert (number(20) > number(10)) == True - assert (number(20) < number(10)) == False + assert (number(20) > number(10)) == True + assert (number(20) < number(10)) == False assert (number(20) >= number(20)) == True assert (number(20) <= number(10)) == False assert (number(20) != number(10)) == True @@ -76,9 +84,9 @@ def test03_comparison_operators(self): def test04_boolean_operator(self): """Test implementation of operator bool""" - import cppyy + import cppjit - number = cppyy.gbl.number + number = cppjit.gbl.number n = number(20) assert n @@ -89,63 +97,69 @@ def test04_boolean_operator(self): def test05_exact_types(self): """Test converter operators of exact types""" - import cppyy + import cppjit - gbl = cppyy.gbl + gbl = cppjit.gbl o = gbl.operator_char_star() - assert o.m_str == 'operator_char_star' - assert str(o) == 'operator_char_star' + assert o.m_str == "operator_char_star" + assert str(o) == "operator_char_star" o = gbl.operator_const_char_star() - assert o.m_str == 'operator_const_char_star' - assert str(o) == 'operator_const_char_star' + assert o.m_str == "operator_const_char_star" + assert str(o) == "operator_const_char_star" - o = gbl.operator_int(); o.m_int = -13 + o = gbl.operator_int() + o.m_int = -13 assert o.m_int == -13 - assert int(o) == -13 + assert int(o) == -13 - o = gbl.operator_long(); o.m_long = 42 - assert o.m_long == 42 + o = gbl.operator_long() + o.m_long = 42 + assert o.m_long == 42 assert pylong(o) == 42 - o = gbl.operator_double(); o.m_double = 3.1415 + o = gbl.operator_double() + o.m_double = 3.1415 assert o.m_double == 3.1415 - assert float(o) == 3.1415 + assert float(o) == 3.1415 def test06_approximate_types(self): """Test converter operators of approximate types""" - import cppyy, sys + import cppjit - gbl = cppyy.gbl + gbl = cppjit.gbl - o = gbl.operator_short(); o.m_short = 256 + o = gbl.operator_short() + o.m_short = 256 assert o.m_short == 256 - assert int(o) == 256 + assert int(o) == 256 - o = gbl.operator_unsigned_int(); o.m_uint = 2147483647 + 32 - assert o.m_uint == 2147483647 + 32 + o = gbl.operator_unsigned_int() + o.m_uint = 2147483647 + 32 + assert o.m_uint == 2147483647 + 32 assert pylong(o) == 2147483647 + 32 - o = gbl.operator_unsigned_long(); + o = gbl.operator_unsigned_long() o.m_ulong = maxvalue + 128 assert o.m_ulong == maxvalue + 128 assert pylong(o) == maxvalue + 128 - o = gbl.operator_float(); o.m_float = 3.14 - assert round(o.m_float - 3.14, 5) == 0. - assert round(float(o) - 3.14, 5) == 0. + o = gbl.operator_float() + o.m_float = 3.14 + assert round(o.m_float - 3.14, 5) == 0.0 + assert round(float(o) - 3.14, 5) == 0.0 def test07_virtual_operator_eq(self): """Test use of virtual bool operator==""" - import cppyy + import cppjit - b1 = cppyy.gbl.v_opeq_base(1) - b1a = cppyy.gbl.v_opeq_base(1) - b2 = cppyy.gbl.v_opeq_base(2) - b2a = cppyy.gbl.v_opeq_base(2) + b1 = cppjit.gbl.v_opeq_base(1) + b1a = cppjit.gbl.v_opeq_base(1) + b2 = cppjit.gbl.v_opeq_base(2) + b2a = cppjit.gbl.v_opeq_base(2) assert b1 == b1 assert b1 == b1a @@ -154,10 +168,10 @@ def test07_virtual_operator_eq(self): assert b2 == b2 assert b2 == b2a - d1 = cppyy.gbl.v_opeq_derived(1) - d1a = cppyy.gbl.v_opeq_derived(1) - d2 = cppyy.gbl.v_opeq_derived(2) - d2a = cppyy.gbl.v_opeq_derived(2) + d1 = cppjit.gbl.v_opeq_derived(1) + d1a = cppjit.gbl.v_opeq_derived(1) + d2 = cppjit.gbl.v_opeq_derived(2) + d2a = cppjit.gbl.v_opeq_derived(2) # derived operator== returns opposite assert not d1 == d1 @@ -179,147 +193,151 @@ def test07_virtual_operator_eq(self): def test08_call_to_getsetitem_mapping(self): """Map () to []""" - import cppyy + import cppjit - m = cppyy.gbl.YAMatrix1() + m = cppjit.gbl.YAMatrix1() assert m.m_val == 42 - assert m[1,2] == 42 - assert m(1,2) == 42 - m[1,2] = 27 + assert m[1, 2] == 42 + assert m(1, 2) == 42 + m[1, 2] = 27 assert m.m_val == 27 - assert m[1,2] == 27 - assert m(1,2) == 27 + assert m[1, 2] == 27 + assert m(1, 2) == 27 - m = cppyy.gbl.YAMatrix2() + m = cppjit.gbl.YAMatrix2() assert m.m_val == 42 - assert m[1] == 42 + assert m[1] == 42 m[1] = 27 assert m.m_val == 27 - assert m[1] == 27 - - for cls in [cppyy.gbl.YAMatrix3, cppyy.gbl.YAMatrix4, - cppyy.gbl.YAMatrix5, cppyy.gbl.YAMatrix6, - cppyy.gbl.YAMatrix7]: + assert m[1] == 27 + + for cls in [ + cppjit.gbl.YAMatrix3, + cppjit.gbl.YAMatrix4, + cppjit.gbl.YAMatrix5, + cppjit.gbl.YAMatrix6, + cppjit.gbl.YAMatrix7, + ]: m = cls() assert m.m_val == 42 - assert m[1,2] == 42 - assert m[1] == 42 - assert m(1,2) == 42 + assert m[1, 2] == 42 + assert m[1] == 42 + assert m(1, 2) == 42 - m[1,2] = 27 + m[1, 2] = 27 assert m.m_val == 27 - assert m[1,2] == 27 - assert m[1] == 27 - assert m(1,2) == 27 + assert m[1, 2] == 27 + assert m[1] == 27 + assert m(1, 2) == 27 - m[1] = 83 + m[1] = 83 assert m.m_val == 83 - assert m[1,2] == 83 - assert m[1] == 83 - assert m(1,2) == 83 + assert m[1, 2] == 83 + assert m[1] == 83 + assert m(1, 2) == 83 m.m_val = 74 assert m.m_val == 74 - assert m[1,2] == 74 - assert m[1] == 74 - assert m(1,2) == 74 + assert m[1, 2] == 74 + assert m[1] == 74 + assert m(1, 2) == 74 def test09_templated_operator(self): """Templated operator<()""" - from cppyy.gbl import TOIClass + from cppjit.gbl import TOIClass - assert (TOIClass() < 1) + assert TOIClass() < 1 def test10_r_non_associative(self): """Use of radd/rmul with non-associative types""" - import cppyy + import cppjit # Note: calls are repeated to test caching, if any - a = cppyy.gbl.AssocADD(5.) - assert 5+a == 10. - assert a+5 == 10. - assert 5+a == 10. - assert a+5 == 10. + a = cppjit.gbl.AssocADD(5.0) + assert 5 + a == 10.0 + assert a + 5 == 10.0 + assert 5 + a == 10.0 + assert a + 5 == 10.0 - a = cppyy.gbl.NonAssocRADD(5.) - assert 5+a == 10. - assert 5+a == 10. + a = cppjit.gbl.NonAssocRADD(5.0) + assert 5 + a == 10.0 + assert 5 + a == 10.0 with raises(NotImplementedError): - v = a+5 + v = a + 5 - a = cppyy.gbl.AssocMUL(5.) - assert 2*a == 10. - assert a*2 == 10. - assert 2*a == 10. - assert a*2 == 10. + a = cppjit.gbl.AssocMUL(5.0) + assert 2 * a == 10.0 + assert a * 2 == 10.0 + assert 2 * a == 10.0 + assert a * 2 == 10.0 - m = cppyy.gbl.NonAssocRMUL(5.) - assert 2*m == 10. - assert 2*m == 10. + m = cppjit.gbl.NonAssocRMUL(5.0) + assert 2 * m == 10.0 + assert 2 * m == 10.0 with raises(NotImplementedError): - v = m*2 + v = m * 2 def test11_overloaded_operators(self): """Overloaded operator*/+-""" - import cppyy + import cppjit - v = cppyy.gbl.MultiLookup.Vector2(1, 2) - w = cppyy.gbl.MultiLookup.Vector2(3, 4) + v = cppjit.gbl.MultiLookup.Vector2(1, 2) + w = cppjit.gbl.MultiLookup.Vector2(3, 4) - u = v*2 - assert u.x == 2. - assert u.y == 4. + u = v * 2 + assert u.x == 2.0 + assert u.y == 4.0 - assert v*w == 1*3 + 2*4 + assert v * w == 1 * 3 + 2 * 4 - u = v/2 + u = v / 2 assert u.x == 0.5 assert u.y == 1.0 - assert round(v/w - (1./3. + 2./4.), 8) == 0. + assert round(v / w - (1.0 / 3.0 + 2.0 / 4.0), 8) == 0.0 - u = v+2 - assert u.x == 3. - assert u.y == 4. + u = v + 2 + assert u.x == 3.0 + assert u.y == 4.0 - assert v+w == 1+3 + 2+4 + assert v + w == 1 + 3 + 2 + 4 - u = v-2 - assert u.x == -1. - assert u.y == 0. + u = v - 2 + assert u.x == -1.0 + assert u.y == 0.0 - assert v-w == 1-3 + 2-4 + assert v - w == 1 - 3 + 2 - 4 def test12_unary_operators(self): """Unary operator-+~""" - import cppyy + import cppjit - for cls in [cppyy.gbl.SomeGlobalNumber, cppyy.gbl.Unary.SomeNumber]: + for cls in [cppjit.gbl.SomeGlobalNumber, cppjit.gbl.Unary.SomeNumber]: n = cls(42) assert (-n).i == -42 - assert (+n).i == 42 - #assert (~n).i == ~42 + assert (+n).i == 42 + # assert (~n).i == ~42 def test13_comma_operator(self): """Comma operator""" - import cppyy + import cppjit - c = cppyy.gbl.CommaOperator(1) + c = cppjit.gbl.CommaOperator(1) assert c.__comma__(2).__comma__(3).fInt == 6 def test14_single_argument_call(self): """Non-reference, single-argument, call not mapped to getitem""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace IndexingOperators { struct Foo { float operator[] (float x) { return x; } @@ -329,7 +347,7 @@ def test14_single_argument_call(self): float operator() (float x) { return 5.f; } }; }""") - ns = cppyy.gbl.IndexingOperators + ns = cppjit.gbl.IndexingOperators f = ns.Foo() assert f[42] == 42 @@ -342,19 +360,19 @@ def test15_class_and_global_mix(self): if IS_WINDOWS: skip("missing symbol __std_max_element_4") - from cppyy.gbl import std + from cppjit.gbl import std - x = std.vector[int]([1,2,3]) + x = std.vector[int]([1, 2, 3]) assert (x.end() - 1).__deref__() == 3 - assert std.max_element(x.begin(), x.end())-x.begin() == 2 + assert std.max_element(x.begin(), x.end()) - x.begin() == 2 assert (x.end() - 3).__deref__() == 1 def test16_global_ordered_operators(self): """Globally defined ordered oeprators""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace FriendOperator { struct ALt { ALt(int d) : data(d) {} int data; }; @@ -371,26 +389,26 @@ def test16_global_ordered_operators(self): }""") - ns = cppyy.gbl.FriendOperator + ns = cppjit.gbl.FriendOperator - assert ns.ALt(4) < ns.ALt(5) - assert not ns.ALt(5) < ns.ALt(4) + assert ns.ALt(4) < ns.ALt(5) + assert not ns.ALt(5) < ns.ALt(4) - assert ns.ALe(4) <= ns.ALe(5) + assert ns.ALe(4) <= ns.ALe(5) assert not ns.ALe(5) <= ns.ALe(4) - assert ns.AGt(5) > ns.AGt(4) - assert not ns.AGt(4) > ns.AGt(5) + assert ns.AGt(5) > ns.AGt(4) + assert not ns.AGt(4) > ns.AGt(5) - assert ns.AGe(5) >= ns.AGe(4) + assert ns.AGe(5) >= ns.AGe(4) assert not ns.AGe(4) >= ns.AGe(5) def test17_arrow_operator_recursion(self): """operator->() returning same type should not recurse""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace Recursion { class MCPCollection { public: @@ -398,8 +416,8 @@ class MCPCollection { MCPCollection* operator->() { return this; } }; }""") - ns = cppyy.gbl.Recursion + ns = cppjit.gbl.Recursion coll = ns.MCPCollection() with raises(AttributeError): - coll.non_existing_method \ No newline at end of file + coll.non_existing_method diff --git a/test/test_overloads.py b/test/test_overloads.py index e35ebdc..24b8d0a 100644 --- a/test/test_overloads.py +++ b/test/test_overloads.py @@ -1,9 +1,10 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, ispypy, IS_WINDOWS, IS_MAC, IS_MAC_ARM +import py +from pytest import mark, raises, skip +from support import IS_MAC, IS_MAC_ARM, IS_WINDOWS, ispypy, setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("overloadsDict")) +test_dct = str(currpath.join("cpp/overloadsDict")) + def setup_module(mod): setup_make("overloads") @@ -12,59 +13,62 @@ def setup_module(mod): class TestOVERLOADS: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.overloads = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.overloads = cppjit.load_reflection_info(cls.test_dct) def test01_class_based_overloads(self): """Test functions overloaded on different C++ clases""" - import cppyy - a_overload = cppyy.gbl.a_overload - b_overload = cppyy.gbl.b_overload - c_overload = cppyy.gbl.c_overload - d_overload = cppyy.gbl.d_overload + import cppjit + + a_overload = cppjit.gbl.a_overload + b_overload = cppjit.gbl.b_overload + c_overload = cppjit.gbl.c_overload + d_overload = cppjit.gbl.d_overload - ns_a_overload = cppyy.gbl.ns_a_overload - ns_b_overload = cppyy.gbl.ns_b_overload + ns_a_overload = cppjit.gbl.ns_a_overload + ns_b_overload = cppjit.gbl.ns_b_overload assert c_overload().get_int(a_overload()) == 42 assert c_overload().get_int(b_overload()) == 13 assert d_overload().get_int(a_overload()) == 42 assert d_overload().get_int(b_overload()) == 13 - assert c_overload().get_int(ns_a_overload.a_overload()) == 88 + assert c_overload().get_int(ns_a_overload.a_overload()) == 88 assert c_overload().get_int(ns_b_overload.a_overload()) == -33 - assert d_overload().get_int(ns_a_overload.a_overload()) == 88 + assert d_overload().get_int(ns_a_overload.a_overload()) == 88 assert d_overload().get_int(ns_b_overload.a_overload()) == -33 def test02_class_based_overloads_explicit_resolution(self): """Test explicitly resolved function overloads""" - import cppyy - a_overload = cppyy.gbl.a_overload - b_overload = cppyy.gbl.b_overload - c_overload = cppyy.gbl.c_overload - d_overload = cppyy.gbl.d_overload + import cppjit - ns_a_overload = cppyy.gbl.ns_a_overload + a_overload = cppjit.gbl.a_overload + b_overload = cppjit.gbl.b_overload + c_overload = cppjit.gbl.c_overload + d_overload = cppjit.gbl.d_overload + + ns_a_overload = cppjit.gbl.ns_a_overload c = c_overload() - raises(TypeError, c.__dispatch__, 'get_int', 12) - raises(LookupError, c.__dispatch__, 'get_int', 'does_not_exist') - assert c.__dispatch__('get_int', 'a_overload*')(a_overload()) == 42 - assert c_overload.get_int.__overload__('a_overload*')(c, a_overload()) == 42 - assert c.__dispatch__('get_int', 'b_overload*')(b_overload()) == 13 - assert c_overload.get_int.__overload__('b_overload*')(c, b_overload()) == 13 - - assert c_overload().__dispatch__('get_int', 'a_overload*')(a_overload()) == 42 + raises(TypeError, c.__dispatch__, "get_int", 12) + raises(LookupError, c.__dispatch__, "get_int", "does_not_exist") + assert c.__dispatch__("get_int", "a_overload*")(a_overload()) == 42 + assert c_overload.get_int.__overload__("a_overload*")(c, a_overload()) == 42 + assert c.__dispatch__("get_int", "b_overload*")(b_overload()) == 13 + assert c_overload.get_int.__overload__("b_overload*")(c, b_overload()) == 13 + + assert c_overload().__dispatch__("get_int", "a_overload*")(a_overload()) == 42 # TODO: #assert c_overload.__dispatch__('get_int', 'b_overload*')(c, b_overload()) == 13 d = d_overload() - assert d.__dispatch__('get_int', 'a_overload*')(a_overload()) == 42 - assert d_overload.get_int.__overload__('a_overload*')(d, a_overload()) == 42 - assert d.__dispatch__('get_int', 'b_overload*')(b_overload()) == 13 - assert d_overload.get_int.__overload__('b_overload*')(d, b_overload()) == 13 + assert d.__dispatch__("get_int", "a_overload*")(a_overload()) == 42 + assert d_overload.get_int.__overload__("a_overload*")(d, a_overload()) == 42 + assert d.__dispatch__("get_int", "b_overload*")(b_overload()) == 13 + assert d_overload.get_int.__overload__("b_overload*")(d, b_overload()) == 13 nb = ns_a_overload.b_overload() raises(TypeError, nb.f, c_overload()) @@ -73,50 +77,53 @@ def test02_class_based_overloads_explicit_resolution(self): def test03_fragile_class_based_overloads(self): """Test functions overloaded on void* and non-existing classes""" - import cppyy - more_overloads = cppyy.gbl.more_overloads - aa_ol = cppyy.gbl.aa_ol - bb_ol = cppyy.gbl.bb_ol - cc_ol = cppyy.gbl.cc_ol - dd_ol = cppyy.gbl.dd_ol + import cppjit + + more_overloads = cppjit.gbl.more_overloads + aa_ol = cppjit.gbl.aa_ol + bb_ol = cppjit.gbl.bb_ol + cc_ol = cppjit.gbl.cc_ol + dd_ol = cppjit.gbl.dd_ol assert more_overloads().call(aa_ol()) == "aa_ol" - bb = cppyy.gbl.get_bb_ol() - assert more_overloads().call(bb ) == "bb_ol" + bb = cppjit.gbl.get_bb_ol() + assert more_overloads().call(bb) == "bb_ol" assert more_overloads().call(cc_ol()) == "cc_ol" - dd = cppyy.bind_object(cppyy.nullptr, dd_ol) + dd = cppjit.bind_object(cppjit.nullptr, dd_ol) with raises(TypeError): more_overloads().call(dd) - dd = cppyy.gbl.get_dd_ol() - assert more_overloads().call(dd ) == "dd_ol" + dd = cppjit.gbl.get_dd_ol() + assert more_overloads().call(dd) == "dd_ol" @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test04_fully_fragile_overloads(self): """Test that unknown* is preferred over unknown&""" - import cppyy - more_overloads2 = cppyy.gbl.more_overloads2 + import cppjit + + more_overloads2 = cppjit.gbl.more_overloads2 - bb = cppyy.bind_object(cppyy.nullptr, cppyy.gbl.bb_ol) - assert more_overloads2().call(bb) == "bb_olptr" + bb = cppjit.bind_object(cppjit.nullptr, cppjit.gbl.bb_ol) + assert more_overloads2().call(bb) == "bb_olptr" - dd = cppyy.bind_object(cppyy.nullptr, cppyy.gbl.dd_ol) + dd = cppjit.bind_object(cppjit.nullptr, cppjit.gbl.dd_ol) assert more_overloads2().call(dd, 1) == "dd_olptr" def test05_array_overloads(self): """Test functions overloaded on different arrays""" - import cppyy - c_overload = cppyy.gbl.c_overload - d_overload = cppyy.gbl.d_overload + import cppjit + + c_overload = cppjit.gbl.c_overload + d_overload = cppjit.gbl.d_overload from array import array - ai = array('i', [525252]) + ai = array("i", [525252]) assert c_overload().get_int(ai) == 525252 assert d_overload().get_int(ai) == 525252 - ah = array('h', [25]) + ah = array("h", [25]) assert c_overload().get_int(ah) == 25 assert d_overload().get_int(ah) == 25 @@ -124,24 +131,28 @@ def test05_array_overloads(self): def test06_double_int_overloads(self): """Test overloads on int/doubles""" - import cppyy - more_overloads = cppyy.gbl.more_overloads + import cppjit + + more_overloads = cppjit.gbl.more_overloads - assert more_overloads().call(1) == "int" - assert more_overloads().call(1.) == "double" - assert more_overloads().call1(1) == "int" - assert more_overloads().call1(1.) == "double" + assert more_overloads().call(1) == "int" + assert more_overloads().call(1.0) == "double" + assert more_overloads().call1(1) == "int" + assert more_overloads().call1(1.0) == "double" def test07_mean_overloads(self): """Adapted test for array overloading""" - import cppyy, array - cmean = cppyy.gbl.calc_mean + import array + + import cppjit + + cmean = cppjit.gbl.calc_mean numbers = [8, 2, 4, 2, 4, 2, 4, 4, 1, 5, 6, 3, 7] mean, median = 4.0, 4.0 - for l in ['f', 'd', 'i', 'h', 'l']: + for l in ["f", "d", "i", "h", "l"]: a = array.array(l, numbers) assert round(cmean(len(a), a) - mean, 8) == 0 @@ -149,56 +160,55 @@ def test07_mean_overloads(self): def test08_const_non_const_overloads(self): """Check selectability of const/non-const overloads""" - import cppyy + import cppjit - m = cppyy.gbl.more_overloads3() + m = cppjit.gbl.more_overloads3() - assert m.slice.__overload__(':any:', True)(0) == 'const' - assert m.slice.__overload__(':any:', False)(0) == 'non-const' + assert m.slice.__overload__(":any:", True)(0) == "const" + assert m.slice.__overload__(":any:", False)(0) == "non-const" - allmeths = cppyy.gbl.more_overloads3.slice.__overload__(':any:') - cppyy.gbl.more_overloads3.slice = allmeths.__overload__(':any:', False) - cppyy.gbl.more_overloads3.slice_const = allmeths.__overload__(':any:', True) + allmeths = cppjit.gbl.more_overloads3.slice.__overload__(":any:") + cppjit.gbl.more_overloads3.slice = allmeths.__overload__(":any:", False) + cppjit.gbl.more_overloads3.slice_const = allmeths.__overload__(":any:", True) del allmeths - assert m.slice(0) == 'non-const' - assert m,slice(0, 0) == 'non-const' - assert m.slice_const(0) == 'const' - assert m,slice_const(0, 0) == 'const' + assert m.slice(0) == "non-const" + assert m, slice(0, 0) == "non-const" + assert m.slice_const(0) == "const" + assert m, slice_const(0, 0) == "const" # noqa: F821 def test09_bool_int_overloads(self): """Check bool/int overloaded calls""" - import cppyy + import cppjit - cpp = cppyy.gbl + cpp = cppjit.gbl - cppyy.cppdef("namespace BoolInt1 { int fff(int i) { return i; } }"); - cppyy.cppdef("namespace BoolInt1 { bool fff(bool i) { return i; } }") + cppjit.cppdef("namespace BoolInt1 { int fff(int i) { return i; } }") + cppjit.cppdef("namespace BoolInt1 { bool fff(bool i) { return i; } }") assert type(cpp.BoolInt1.fff(0)) == int assert type(cpp.BoolInt1.fff(1)) == int assert type(cpp.BoolInt1.fff(2)) == int - assert type(cpp.BoolInt1.fff(True)) == bool + assert type(cpp.BoolInt1.fff(True)) == bool assert type(cpp.BoolInt1.fff(False)) == bool - cppyy.cppdef("namespace BoolInt2 { int fff(int i) { return i; } }"); - cppyy.cppdef("namespace BoolInt2 { bool fff(bool i) { return i; } }") + cppjit.cppdef("namespace BoolInt2 { int fff(int i) { return i; } }") + cppjit.cppdef("namespace BoolInt2 { bool fff(bool i) { return i; } }") - assert type(cpp.BoolInt2.fff(True)) == bool + assert type(cpp.BoolInt2.fff(True)) == bool assert type(cpp.BoolInt2.fff(False)) == bool assert type(cpp.BoolInt2.fff(0)) == int assert type(cpp.BoolInt2.fff(1)) == int assert type(cpp.BoolInt2.fff(2)) == int - cppyy.cppdef("namespace BoolInt3 { int fff(int i) { return i; } }"); - - assert type(cpp.BoolInt3.fff(True)) == int + cppjit.cppdef("namespace BoolInt3 { int fff(int i) { return i; } }") + assert type(cpp.BoolInt3.fff(True)) == int assert type(cpp.BoolInt3.fff(False)) == int - cppyy.cppdef("namespace BoolInt4 { bool fff(bool i) { return i; } }") + cppjit.cppdef("namespace BoolInt4 { bool fff(bool i) { return i; } }") assert type(cpp.BoolInt4.fff(0)) == bool assert type(cpp.BoolInt4.fff(1)) == bool @@ -210,11 +220,11 @@ def test10_overload_and_exceptions(self): """Prioritize reporting C++ exceptions from callee""" if ispypy or IS_WINDOWS: - skip('throwing exceptions from the JIT terminates the process') + skip("throwing exceptions from the JIT terminates the process") - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ExceptionTypeTest { class ConfigFileNotFoundError : public std::exception { @@ -249,7 +259,7 @@ class MyClass3 { MyClass3(const MyClass3& other) {} }; }""") - ns = cppyy.gbl.ExceptionTypeTest + ns = cppjit.gbl.ExceptionTypeTest with raises(ns.ConfigFileNotFoundError): ns.MyClass1("some_file") @@ -264,9 +274,9 @@ class MyClass3 { def test11_deep_inheritance(self): """Prioritize expected most derived class""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace DeepInheritance { class A {}; class B: public A {}; @@ -281,7 +291,7 @@ class E: public D {}; std::string myfunc2(const D&) { return "D"; } }""") - ns = cppyy.gbl.DeepInheritance + ns = cppjit.gbl.DeepInheritance assert ns.myfunc1(ns.B()) == "B" assert ns.myfunc1(ns.C()) == "C" @@ -292,9 +302,9 @@ class E: public D {}; def test12_static_call_from_derived_instance(self): """Test calling a static member function via a derived instance.""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" class Base { public: static int StaticMethod() { @@ -306,23 +316,23 @@ class Derived : public Base { }; """) - d = cppyy.gbl.Derived() + d = cppjit.gbl.Derived() # Call static method through base class directly - result_direct = cppyy.gbl.Base.StaticMethod() + result_direct = cppjit.gbl.Base.StaticMethod() # Call static method through instance result_instance = d.StaticMethod() assert result_instance == result_direct - + def test13_disallow_functor_to_function_pointer(self): """Make sure we're no allowing to convert C++ functors to function pointers, extending the C++ language in an unnatural way that can lead to wrong overload resolutions.""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" class Test14Functor { public: double operator () (double* args, double*) { @@ -352,19 +362,21 @@ class Test14Functor { } """) - functor = cppyy.gbl.Test14Functor() - assert cppyy.gbl.test14_foo(functor) == 1 # should resolve to foo(T fcn) + functor = cppjit.gbl.Test14Functor() + assert cppjit.gbl.test14_foo(functor) == 1 # should resolve to foo(T fcn) # not allowed, because there is only an overload taking a function pointer - raises(TypeError, cppyy.gbl.test14_bar, functor) + raises(TypeError, cppjit.gbl.test14_bar, functor) # The "baz" function has a std::function overload, which should be selected - assert cppyy.gbl.test14_baz(functor) == 2 # should resolve to baz(std::function) + assert ( + cppjit.gbl.test14_baz(functor) == 2 + ) # should resolve to baz(std::function) def test14_explicit_constructor_in_implicit_conversion(self): """Check that explicit constructors are not used in implicit conversion.""" - import cppyy + import cppjit - cppyy.cppdef("""struct Test12Class { + cppjit.cppdef("""struct Test12Class { explicit Test12Class(int arg) {} }; int test12_foo(Test12Class const&) { return 0; } @@ -375,21 +387,20 @@ def test14_explicit_constructor_in_implicit_conversion(self): int call_test12_bar() { return test12_bar(1); } """) - # Check that the cppyy overload resolution figures out the right + # Check that the cppjit overload resolution figures out the right # overload when calling the functions with an integer. In the past, # this used to go wrong for the "bar" function with the default bool - # argument: cppyy went for the overload that takes the test class, even + # argument: cppjit went for the overload that takes the test class, even # though implicit construction of the test class is forbidden. - assert cppyy.gbl.test12_foo(1) == cppyy.gbl.call_test12_foo() - assert cppyy.gbl.test12_bar(1) == cppyy.gbl.call_test12_bar() + assert cppjit.gbl.test12_foo(1) == cppjit.gbl.call_test12_foo() + assert cppjit.gbl.test12_bar(1) == cppjit.gbl.call_test12_bar() def test15_disallow_mutable_pointer_references(self): - """Verify that mutable pointer references (T*&) are not allowed as arguments. - """ + """Verify that mutable pointer references (T*&) are not allowed as arguments.""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" struct MyClass { int val = 0; }; @@ -397,6 +408,6 @@ def test15_disallow_mutable_pointer_references(self): void changePtr(MyClass *& ptr) {} """) - ptr = cppyy.gbl.MyClass() + ptr = cppjit.gbl.MyClass() - raises(TypeError, cppyy.gbl.changePtr, ptr) + raises(TypeError, cppjit.gbl.changePtr, ptr) diff --git a/test/test_pythonify.py b/test/test_pythonify.py index 0995d9d..4bdf282 100644 --- a/test/test_pythonify.py +++ b/test/test_pythonify.py @@ -1,9 +1,15 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, pylong, ispypy, IS_CLANG_REPL, IS_CLING, IS_MAC_ARM, IS_MAC_X86, IS_MAC +import py +from pytest import mark, raises, skip +from support import ( + IS_MAC, + ispypy, + pylong, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("example01Dict")) +test_dct = str(currpath.join("cpp/example01Dict")) + def setup_module(mod): setup_make("example01") @@ -12,32 +18,36 @@ def setup_module(mod): class TestPYTHONIFY: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.example01 = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.example01 = cppjit.load_reflection_info(cls.test_dct) def test01_load_dictionary_cache(self): """Test whether loading a dictionary twice results in the same object""" - import cppyy - lib2 = cppyy.load_reflection_info(self.test_dct) + import cppjit + + lib2 = cppjit.load_reflection_info(self.test_dct) assert self.example01 is lib2 def test02_finding_classes(self): """Test the lookup of a class, and its caching""" - import cppyy - example01_class = cppyy.gbl.example01 - cl2 = cppyy.gbl.example01 + import cppjit + + example01_class = cppjit.gbl.example01 + cl2 = cppjit.gbl.example01 assert example01_class is cl2 with raises(AttributeError): - cppyy.gbl.nonexistingclass + cppjit.gbl.nonexistingclass def test03_calling_static_functions(self): """Test calling of static methods""" - import cppyy, sys, math - example01_class = cppyy.gbl.example01 + import cppjit + + example01_class = cppjit.gbl.example01 res = example01_class.staticAddOneToInt(1) assert res == 2 @@ -47,32 +57,33 @@ def test03_calling_static_functions(self): assert res == 4 res = example01_class.staticAddOneToInt(-1) assert res == 0 - maxint32 = int(2 ** 31 - 1) - res = example01_class.staticAddOneToInt(maxint32-1) + maxint32 = int(2**31 - 1) + res = example01_class.staticAddOneToInt(maxint32 - 1) assert res == maxint32 res = example01_class.staticAddOneToInt(maxint32) - assert res == -maxint32-1 + assert res == -maxint32 - 1 raises(TypeError, example01_class.staticAddOneToInt, 1, []) - raises(TypeError, example01_class.staticAddOneToInt, 1.) - raises(TypeError, example01_class.staticAddOneToInt, maxint32+1) + raises(TypeError, example01_class.staticAddOneToInt, 1.0) + raises(TypeError, example01_class.staticAddOneToInt, maxint32 + 1) res = example01_class.staticAddToDouble(0.09) assert res == 0.09 + 0.01 res = example01_class.staticAtoi("1") assert res == 1 - res = example01_class.staticStrcpy("aap") # TODO: this leaks + res = example01_class.staticStrcpy("aap") # TODO: this leaks assert res == "aap" - res = example01_class.staticStrcpy(u"aap") # TODO: id. + res = example01_class.staticStrcpy("aap") # TODO: id. assert res == "aap" - raises(TypeError, example01_class.staticStrcpy, 1.) # TODO: id. + raises(TypeError, example01_class.staticStrcpy, 1.0) # TODO: id. def test04_constructing_and_calling(self): """Test object and method calls""" - import cppyy - example01_class = cppyy.gbl.example01 + import cppjit + + example01_class = cppjit.gbl.example01 assert example01_class.getCount() == 0 instance = example01_class(7) assert example01_class.getCount() == 1 @@ -94,12 +105,12 @@ def test04_constructing_and_calling(self): instance = example01_class(13) res = instance.addDataToDouble(16) - assert round(res-29, 8) == 0. + assert round(res - 29, 8) == 0.0 instance.__destruct__() instance = example01_class(-13) res = instance.addDataToDouble(16) - assert round(res-3, 8) == 0. - instance.__destruct__() + assert round(res - 3, 8) == 0.0 + instance.__destruct__() instance = example01_class(42) assert example01_class.getCount() == 1 @@ -107,9 +118,9 @@ def test04_constructing_and_calling(self): res = instance.addDataToAtoi("13") assert res == 55 - res = instance.addToStringValue("12") # TODO: this leaks + res = instance.addToStringValue("12") # TODO: this leaks assert res == "54" - res = instance.addToStringValue("-12") # TODO: this leaks + res = instance.addToStringValue("-12") # TODO: this leaks assert res == "30" res = instance.staticAddOneToInt(pylong(1)) @@ -121,23 +132,24 @@ def test04_constructing_and_calling(self): def test05_passing_object_by_pointer(self): """Pass object by pointer""" - import cppyy - example01_class = cppyy.gbl.example01 - payload_class = cppyy.gbl.payload + import cppjit + + example01_class = cppjit.gbl.example01 + payload_class = cppjit.gbl.payload e = example01_class(14) pl = payload_class(3.14) - assert round(pl.getData()-3.14, 8) == 0 + assert round(pl.getData() - 3.14, 8) == 0 - example01_class.staticSetPayload(pl, 41.) - assert pl.getData() == 41. - example01_class.staticSetPayload(pl, 43.) - assert pl.getData() == 43. - e.staticSetPayload(pl, 45.) - assert pl.getData() == 45. + example01_class.staticSetPayload(pl, 41.0) + assert pl.getData() == 41.0 + example01_class.staticSetPayload(pl, 43.0) + assert pl.getData() == 43.0 + e.staticSetPayload(pl, 45.0) + assert pl.getData() == 45.0 e.setPayload(pl) - assert round(pl.getData()-14., 8) == 0 + assert round(pl.getData() - 14.0, 8) == 0 pl.__destruct__() e.__destruct__() @@ -146,20 +158,21 @@ def test05_passing_object_by_pointer(self): def test06_returning_object_by_pointer(self): """Return an object py pointer""" - import cppyy - example01_class = cppyy.gbl.example01 - payload_class = cppyy.gbl.payload + import cppjit + + example01_class = cppjit.gbl.example01 + payload_class = cppjit.gbl.payload pl = payload_class(3.14) - assert round(pl.getData()-3.14, 8) == 0 + assert round(pl.getData() - 3.14, 8) == 0 - pl2 = example01_class.staticCyclePayload(pl, 38.) - assert pl2.getData() == 38. + pl2 = example01_class.staticCyclePayload(pl, 38.0) + assert pl2.getData() == 38.0 e = example01_class(14) pl2 = e.cyclePayload(pl) - assert round(pl2.getData()-14., 8) == 0 + assert round(pl2.getData() - 14.0, 8) == 0 pl.__destruct__() e.__destruct__() @@ -168,21 +181,22 @@ def test06_returning_object_by_pointer(self): def test07_returning_object_by_value(self): """Return an object by value""" - import cppyy - example01_class = cppyy.gbl.example01 - payload_class = cppyy.gbl.payload + import cppjit + + example01_class = cppjit.gbl.example01 + payload_class = cppjit.gbl.payload pl = payload_class(3.14) - assert round(pl.getData()-3.14, 8) == 0 + assert round(pl.getData() - 3.14, 8) == 0 - pl2 = example01_class.staticCopyCyclePayload(pl, 38.) - assert pl2.getData() == 38. + pl2 = example01_class.staticCopyCyclePayload(pl, 38.0) + assert pl2.getData() == 38.0 pl2.__destruct__() e = example01_class(14) pl2 = e.copyCyclePayload(pl) - assert round(pl2.getData()-14., 8) == 0 + assert round(pl2.getData() - 14.0, 8) == 0 pl2.__destruct__() pl.__destruct__() @@ -192,28 +206,31 @@ def test07_returning_object_by_value(self): def test08_global_functions(self): """Call a global function""" - import cppyy + import cppjit - assert cppyy.gbl.globalAddOneToInt(3) == 4 # creation lookup - assert cppyy.gbl.globalAddOneToInt(3) == 4 # cached lookup + assert cppjit.gbl.globalAddOneToInt(3) == 4 # creation lookup + assert cppjit.gbl.globalAddOneToInt(3) == 4 # cached lookup - assert cppyy.gbl.ns_example01.globalAddOneToInt(4) == 5 - assert cppyy.gbl.ns_example01.globalAddOneToInt(4) == 5 + assert cppjit.gbl.ns_example01.globalAddOneToInt(4) == 5 + assert cppjit.gbl.ns_example01.globalAddOneToInt(4) == 5 def test09_memory(self): """Test proper C++ destruction by the garbage collector""" - import cppyy, gc - example01_class = cppyy.gbl.example01 - payload_class = cppyy.gbl.payload + import gc + + import cppjit + + example01_class = cppjit.gbl.example01 + payload_class = cppjit.gbl.payload pl = payload_class(3.14) assert payload_class.count == 1 - assert round(pl.getData()-3.14, 8) == 0 + assert round(pl.getData() - 3.14, 8) == 0 - pl2 = example01_class.staticCopyCyclePayload(pl, 38.) + pl2 = example01_class.staticCopyCyclePayload(pl, 38.0) assert payload_class.count == 2 - assert pl2.getData() == 38. + assert pl2.getData() == 38.0 pl2 = None gc.collect() assert payload_class.count == 1 @@ -222,7 +239,7 @@ def test09_memory(self): pl2 = e.copyCyclePayload(pl) assert payload_class.count == 2 - assert round(pl2.getData()-14., 8) == 0 + assert round(pl2.getData() - 14.0, 8) == 0 pl2 = None gc.collect() assert payload_class.count == 1 @@ -234,8 +251,8 @@ def test09_memory(self): assert example01_class.getCount() == 0 pl = payload_class(3.14) - pl_a = example01_class.staticCyclePayload(pl, 66.) - pl_a.getData() == 66. + pl_a = example01_class.staticCyclePayload(pl, 66.0) + pl_a.getData() == 66.0 assert payload_class.count == 1 pl_a = None pl = None @@ -248,103 +265,108 @@ def test09_memory(self): def test10_default_arguments(self): """Test propagation of default function arguments""" - import cppyy - a = cppyy.gbl.ArgPasser() + import cppjit + + a = cppjit.gbl.ArgPasser() # NOTE: when called through the stub, default args are fine f = a.stringRef - s = cppyy.gbl.std.string + s = cppjit.gbl.std.string assert f(s("aap"), 0, s("noot")) == "aap" assert f(s("noot"), 1) == "default" assert f(s("mies")) == "mies" - for itype in ['short', 'ushort', 'int', 'uint', 'long', 'ulong']: - g = getattr(a, '%sValue' % itype) + for itype in ["short", "ushort", "int", "uint", "long", "ulong"]: + g = getattr(a, "%sValue" % itype) raises(TypeError, g, 1, 2, 3, 4, 6) assert g(11, 0, 12, 13) == 11 assert g(11, 1, 12, 13) == 12 - assert g(11, 1, 12) == 12 - assert g(11, 2, 12) == 2 - assert g(11, 1) == 1 - assert g(11, 2) == 2 - assert g(11) == 11 - - for ftype in ['float', 'double']: - g = getattr(a, '%sValue' % ftype) - raises(TypeError, g, 1., 2, 3., 4., 6.) - assert g(11., 0, 12., 13.) == 11. - assert g(11., 1, 12., 13.) == 12. - assert g(11., 1, 12.) == 12. - assert g(11., 2, 12.) == 2. - assert g(11., 1) == 1. - assert g(11., 2) == 2. - assert g(11.) == 11. + assert g(11, 1, 12) == 12 + assert g(11, 2, 12) == 2 + assert g(11, 1) == 1 + assert g(11, 2) == 2 + assert g(11) == 11 + + for ftype in ["float", "double"]: + g = getattr(a, "%sValue" % ftype) + raises(TypeError, g, 1.0, 2, 3.0, 4.0, 6.0) + assert g(11.0, 0, 12.0, 13.0) == 11.0 + assert g(11.0, 1, 12.0, 13.0) == 12.0 + assert g(11.0, 1, 12.0) == 12.0 + assert g(11.0, 2, 12.0) == 2.0 + assert g(11.0, 1) == 1.0 + assert g(11.0, 2) == 2.0 + assert g(11.0) == 11.0 def test11_overload_on_arguments(self): """Test functions overloaded on arguments""" - import cppyy - e = cppyy.gbl.example01(1) + import cppjit - assert e.addDataToInt(2) == 3 - assert e.overloadedAddDataToInt(3) == 4 - assert e.overloadedAddDataToInt(4, 5) == 10 + e = cppjit.gbl.example01(1) + + assert e.addDataToInt(2) == 3 + assert e.overloadedAddDataToInt(3) == 4 + assert e.overloadedAddDataToInt(4, 5) == 10 assert e.overloadedAddDataToInt(6, 7, 8) == 22 def test12_typedefs(self): """Test access and use of typedefs""" - import cppyy + import cppjit - assert cppyy.gbl.example01 == cppyy.gbl.example01_t + assert cppjit.gbl.example01 == cppjit.gbl.example01_t def test13_underscore_in_class_name(self): """Test recognition of '_' as part of a valid class name""" - import cppyy + import cppjit - assert cppyy.gbl.z_ == cppyy.gbl.z_ + assert cppjit.gbl.z_ == cppjit.gbl.z_ - z = cppyy.gbl.z_() + z = cppjit.gbl.z_() - assert hasattr(z, 'myint') + assert hasattr(z, "myint") assert z.gime_z_(z) def test14_bound_unbound_calls(self): """Test (un)bound method calls""" if ispypy: - skip('segfaults in pypy') + skip("segfaults in pypy") - import cppyy + import cppjit - raises(TypeError, cppyy.gbl.example01.addDataToInt, 1) + raises(TypeError, cppjit.gbl.example01.addDataToInt, 1) - meth = cppyy.gbl.example01.addDataToInt + meth = cppjit.gbl.example01.addDataToInt raises(TypeError, meth) raises(TypeError, meth, 1) - e = cppyy.gbl.example01(2) + e = cppjit.gbl.example01(2) assert 5 == meth(e, 3) def test15_installable_function(self): - """Test installing and calling global C++ function as python method""" + """Test installing and calling global C++ function as python method""" - import cppyy + import cppjit - cppyy.gbl.example01.fresh = cppyy.gbl.installableAddOneToInt + cppjit.gbl.example01.fresh = cppjit.gbl.installableAddOneToInt - e = cppyy.gbl.example01(0) - assert 2 == e.fresh(1) - assert 3 == e.fresh(2) + e = cppjit.gbl.example01(0) + assert 2 == e.fresh(1) + assert 3 == e.fresh(2) def test16_subclassing(self): """A sub-class on the python side should have that class as type""" - import cppyy, gc + import gc + + import cppjit + gc.collect() - example01 = cppyy.gbl.example01 + example01 = cppjit.gbl.example01 assert example01.getCount() == 0 @@ -371,10 +393,10 @@ def __init__(self, what): example01.__init__(self) self.what = what - o = MyClass2('hi') + o = MyClass2("hi") assert type(o) == MyClass2 assert example01.getCount() == 1 - assert o.what == 'hi' + assert o.what == "hi" o.__destruct__() assert example01.getCount() == 0 @@ -382,9 +404,9 @@ def __init__(self, what): def test17_chaining(self): """Respective return values of temporaries should not go away""" - import cppyy + import cppjit - cppyy.cppdef("""namespace Lifeline { + cppjit.cppdef("""namespace Lifeline { struct A1 { A1(int x) : x(x) {} int x; }; struct A2 { A2(int x) { v.emplace_back(x); } std::vector v; std::vector& get() { return v; } }; struct A3 { A3(int x) { v.emplace_back(x); } std::vector v; std::vector& get() { return v; } }; @@ -394,14 +416,14 @@ def test17_chaining(self): A5 gime(int i) { return A5(i); } }""") - assert cppyy.gbl.Lifeline.gime(42).get()[0].get()[0].get()[0].get()[0].x == 42 + assert cppjit.gbl.Lifeline.gime(42).get()[0].get()[0].get()[0].get()[0].x == 42 def test18_keywords(self): """Use of keyword arguments""" - import cppyy + import cppjit - cppyy.cppdef("""namespace KeyWords { + cppjit.cppdef("""namespace KeyWords { struct A { A(std::initializer_list vals) : fVals(vals) {} std::vector fVals; @@ -429,14 +451,14 @@ def test18_keywords(self): return callme(o.fChoice, a, b, c); } }""") - # constructor and implicit conversion with keywords - A = cppyy.gbl.KeyWords.A - B = cppyy.gbl.KeyWords.B + # constructor and implicit conversion with keywords + A = cppjit.gbl.KeyWords.A + B = cppjit.gbl.KeyWords.B def verify_b(b, val, ti, to): - assert b.fVal == val - assert b.fIn.fVals.size() == len(ti) - assert tuple(b.fIn.fVals) == ti + assert b.fVal == val + assert b.fIn.fVals.size() == len(ti) + assert tuple(b.fIn.fVals) == ti assert b.fOut.fVals.size() == len(to) assert tuple(b.fOut.fVals) == to @@ -456,14 +478,14 @@ def verify_b(b, val, ti, to): b = B(17, val=23, out_A=(78,)) with raises(TypeError): - b = B(17, out_A=(78,)) + b = B(17, out_A=(78,)) - # global function with keywords - callme = cppyy.gbl.KeyWords.callme + # global function with keywords + callme = cppjit.gbl.KeyWords.callme for i in range(3): - assert callme(i, a=1, b=2, c=3) == i+1 - assert callme(i, b=2, c=3, a=1) == i+1 - assert callme(i, c=3, a=1, b=2) == i+1 + assert callme(i, a=1, b=2, c=3) == i + 1 + assert callme(i, b=2, c=3, a=1) == i + 1 + assert callme(i, c=3, a=1, b=2) == i + 1 with raises(TypeError): callme(0, a=1, b=2, d=3) @@ -474,15 +496,15 @@ def verify_b(b, val, ti, to): with raises(TypeError): callme(0, a=1, b=2) - # global function as method with keywords - c = cppyy.gbl.KeyWords.C() - cppyy.gbl.KeyWords.C.callme = cppyy.gbl.KeyWords.callme_c + # global function as method with keywords + c = cppjit.gbl.KeyWords.C() + cppjit.gbl.KeyWords.C.callme = cppjit.gbl.KeyWords.callme_c for i in range(3): c.fChoice = i - assert c.callme(a=1, b=2, c=3) == i+1 - assert c.callme(b=2, c=3, a=1) == i+1 - assert c.callme(c=3, a=1, b=2) == i+1 + assert c.callme(a=1, b=2, c=3) == i + 1 + assert c.callme(b=2, c=3, a=1) == i + 1 + assert c.callme(c=3, a=1, b=2) == i + 1 c.fChoice = 0 with raises(TypeError): @@ -498,9 +520,9 @@ def verify_b(b, val, ti, to): def test19_keywords_and_defaults(self): """Use of keyword arguments mixed with defaults""" - import cppyy + import cppjit - cppyy.cppdef("""namespace KeyWordsAndDefaults { + cppjit.cppdef("""namespace KeyWordsAndDefaults { int foo(int a=10, int b=20, int c=5, int d=4) { return a-b/c*d; } @@ -520,52 +542,55 @@ class MyClass {}; } }""") def pyfoo(a=10, b=20, c=5, d=4): - return a-b//c*d; + return a - b // c * d - ns = cppyy.gbl.KeyWordsAndDefaults + ns = cppjit.gbl.KeyWordsAndDefaults - assert ns.foo() == pyfoo() - assert ns.foo(a=100) == pyfoo(a=100) - assert ns.foo(b=100) == pyfoo(b=100) - assert ns.foo(a=100, b=200) == pyfoo(a=100, b=200) + assert ns.foo() == pyfoo() + assert ns.foo(a=100) == pyfoo(a=100) + assert ns.foo(b=100) == pyfoo(b=100) + assert ns.foo(a=100, b=200) == pyfoo(a=100, b=200) assert ns.foo(a=100, b=200, d=0) == pyfoo(a=100, b=200, d=0) - assert ns.foo(b=100, a=200) == pyfoo(b=100, a=200) + assert ns.foo(b=100, a=200) == pyfoo(b=100, a=200) with raises(TypeError): ns.foo(1, 2, 3, 4, b=5) assert ns.bar() == "ab" - assert ns.bar(b = " greeting") == "a greeting" + assert ns.bar(b=" greeting") == "a greeting" - ns.foobar(m2 = ns.MyClass()) + ns.foobar(m2=ns.MyClass()) assert not ns.barfoo() assert not ns.barfoo(opt2=True) assert not ns.barfoo(opt2=False) - assert ns.barfoo(opt1=True, opt2=True) + assert ns.barfoo(opt1=True, opt2=True) assert not ns.barfoo(opt1=True, opt2=False) class TestPYTHONIFY_UI: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.example01 = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.example01 = cppjit.load_reflection_info(cls.test_dct) def test01_pythonizations(self): """Test addition of user-defined pythonizations""" - import cppyy + import cppjit def example01a_pythonize(pyclass, pyname): - if pyname == 'example01a': + if pyname == "example01a": + def getitem(self, idx): return self.addDataToInt(idx) + pyclass.__getitem__ = getitem - cppyy.py.add_pythonization(example01a_pythonize) + cppjit.py.add_pythonization(example01a_pythonize) - e = cppyy.gbl.example01a(1) + e = cppjit.gbl.example01a(1) assert e[0] == 1 assert e[1] == 2 @@ -574,21 +599,21 @@ def getitem(self, idx): def test02_fragile_pythonizations(self): """Test pythonizations error reporting""" - import cppyy + import cppjit example01_pythonize = 1 - raises(TypeError, cppyy.py.add_pythonization, example01_pythonize) + raises(TypeError, cppjit.py.add_pythonization, example01_pythonize) def test03_write_access_to_globals(self): """Test overwritability of globals""" - import cppyy + import cppjit - oldval = cppyy.gbl.ns_example01.gMyGlobalInt + oldval = cppjit.gbl.ns_example01.gMyGlobalInt assert oldval == 99 - proxy = cppyy.gbl.ns_example01.__class__.__dict__['gMyGlobalInt'] - cppyy.gbl.ns_example01.gMyGlobalInt = 3 + proxy = cppjit.gbl.ns_example01.__class__.__dict__["gMyGlobalInt"] + cppjit.gbl.ns_example01.gMyGlobalInt = 3 assert proxy.__get__(proxy, None) == 3 - cppyy.gbl.ns_example01.gMyGlobalInt = oldval + cppjit.gbl.ns_example01.gMyGlobalInt = oldval diff --git a/test/test_pythonization.py b/test/test_pythonization.py index ee72024..61b0cee 100644 --- a/test/test_pythonization.py +++ b/test/test_pythonization.py @@ -1,9 +1,18 @@ -import py, os, sys -from pytest import raises, mark -from support import setup_make, pylong, IS_MAC_X86, IS_MAC_ARM, IS_MAC, IS_CLANG_REPL, IS_LINUX_ARM, IS_VALGRIND +import sys + +import py +from pytest import mark, raises +from support import ( + IS_CLANG_REPL, + IS_LINUX_ARM, + IS_MAC, + IS_VALGRIND, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("pythonizablesDict")) +test_dct = str(currpath.join("cpp/pythonizablesDict")) + def setup_module(mod): setup_make("pythonizables") @@ -12,16 +21,17 @@ def setup_module(mod): class TestClassPYTHONIZATION: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.pyzables = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.pyzables = cppjit.load_reflection_info(cls.test_dct) @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test00_api(self): """Test basic semantics of the pythonization API""" - import cppyy + import cppjit - raises(TypeError, cppyy.py.add_pythonization, 1) + raises(TypeError, cppjit.py.add_pythonization, 1) def pythonizor1(klass, name): pass @@ -31,31 +41,33 @@ def pythonizor2(klass, name): pythonizor3 = pythonizor1 - cppyy.py.add_pythonization(pythonizor1) - assert cppyy.py.remove_pythonization(pythonizor2) == False - assert cppyy.py.remove_pythonization(pythonizor3) == True + cppjit.py.add_pythonization(pythonizor1) + assert cppjit.py.remove_pythonization(pythonizor2) == False + assert cppjit.py.remove_pythonization(pythonizor3) == True def pythonizor(klass, name): - if name == 'pyzables::SomeDummy1': + if name == "pyzables::SomeDummy1": klass.test = 1 - cppyy.py.add_pythonization(pythonizor) - assert cppyy.gbl.pyzables.SomeDummy1.test == 1 + cppjit.py.add_pythonization(pythonizor) + assert cppjit.gbl.pyzables.SomeDummy1.test == 1 def pythonizor(klass, name): - if name == 'SomeDummy2': + if name == "SomeDummy2": klass.test = 2 - cppyy.py.add_pythonization(pythonizor, 'pyzables') - # global pythonizors are still run even if namespaced ones available + cppjit.py.add_pythonization(pythonizor, "pyzables") + + # global pythonizors are still run even if namespaced ones available def pythonizor(klass, name): - if name == 'pyzables::SomeDummy2': + if name == "pyzables::SomeDummy2": klass.test = 3 - cppyy.py.add_pythonization(pythonizor) - assert cppyy.gbl.pyzables.SomeDummy2.test == 3 + cppjit.py.add_pythonization(pythonizor) + + assert cppjit.gbl.pyzables.SomeDummy2.test == 3 - cppyy.cppdef(""" + cppjit.cppdef(""" namespace pyzables { class TObjString { public: @@ -67,153 +79,167 @@ class TObjString { """) def root_pythonizor(klass, name): - if name == 'pyzables::TObjString': + if name == "pyzables::TObjString": klass.__len__ = klass.Sizeof - cppyy.py.add_pythonization(root_pythonizor) + cppjit.py.add_pythonization(root_pythonizor) - assert len(cppyy.gbl.pyzables.TObjString("aap")) == 4 # include '\0' + assert len(cppjit.gbl.pyzables.TObjString("aap")) == 4 # include '\0' def test01_size_mapping(self): """Use composites to map GetSize() onto buffer returns""" - import cppyy + import cppjit def set_size(self, buf): buf.reshape((self.GetN(),)) return buf - cppyy.py.add_pythonization( - cppyy.py.compose_method('NakedBuffers$', 'Get[XY]$', set_size), 'pyzables') + cppjit.py.add_pythonization( + cppjit.py.compose_method("NakedBuffers$", "Get[XY]$", set_size), "pyzables" + ) bsize, xval, yval = 3, 2, 5 - m = cppyy.gbl.pyzables.NakedBuffers(bsize, xval, yval) + m = cppjit.gbl.pyzables.NakedBuffers(bsize, xval, yval) x = m.GetX() assert len(x) == bsize - assert list(x) == list(map(lambda x: x*xval, range(bsize))) + assert list(x) == list(map(lambda x: x * xval, range(bsize))) y = m.GetY() assert len(y) == bsize - assert list(y) == list(map(lambda x: x*yval, range(bsize))) + assert list(y) == list(map(lambda x: x * yval, range(bsize))) def test02_size_mapping_of_templated_method(self): """Use composites to map GetSize() onto buffer returns""" - import cppyy + import cppjit def set_size(self, buf): buf.reshape((self.GetN(),)) return buf - cppyy.py.add_pythonization( - cppyy.py.compose_method('NakedBuffers2.*Vector.*', 'Get[XY]$', set_size), 'pyzables') + cppjit.py.add_pythonization( + cppjit.py.compose_method("NakedBuffers2.*Vector.*", "Get[XY]$", set_size), + "pyzables", + ) bsize, xval, yval = 3, 2, 5 - m = cppyy.gbl.pyzables.NakedBuffers2[cppyy.gbl.pyzables.Vector](bsize, xval, yval) + m = cppjit.gbl.pyzables.NakedBuffers2[cppjit.gbl.pyzables.Vector]( + bsize, xval, yval + ) x = m.GetX() assert len(x) == bsize - assert list(x) == list(map(lambda x: x*xval, range(bsize))) + assert list(x) == list(map(lambda x: x * xval, range(bsize))) y = m.GetY() assert len(y) == bsize - assert list(y) == list(map(lambda x: x*yval, range(bsize))) + assert list(y) == list(map(lambda x: x * yval, range(bsize))) def test03_type_pinning(self): """Verify pinnability of returns""" - import cppyy + import cppjit - cppyy.gbl.pyzables.GimeDerived.__creates__ = True + cppjit.gbl.pyzables.GimeDerived.__creates__ = True - result = cppyy.gbl.pyzables.GimeDerived() - assert type(result) == cppyy.gbl.pyzables.MyDerived - - cppyy.py.pin_type(cppyy.gbl.pyzables.MyBase) - assert type(result) == cppyy.gbl.pyzables.MyDerived + result = cppjit.gbl.pyzables.GimeDerived() + assert type(result) == cppjit.gbl.pyzables.MyDerived + cppjit.py.pin_type(cppjit.gbl.pyzables.MyBase) + assert type(result) == cppjit.gbl.pyzables.MyDerived def test04_transparency(self): """Transparent use of smart pointers""" - import cppyy + import cppjit - Countable = cppyy.gbl.pyzables.Countable - mine = cppyy.gbl.pyzables.mine + Countable = cppjit.gbl.pyzables.Countable + mine = cppjit.gbl.pyzables.mine assert type(mine) == Countable - assert mine.m_check == 0xcdcdcdcd - assert type(mine.__smartptr__()) == cppyy.gbl.std.shared_ptr(Countable) - assert mine.__smartptr__().get().m_check == 0xcdcdcdcd + assert mine.m_check == 0xCDCDCDCD + assert type(mine.__smartptr__()) == cppjit.gbl.std.shared_ptr(Countable) + assert mine.__smartptr__().get().m_check == 0xCDCDCDCD assert mine.say_hi() == "Hi!" - @mark.xfail(run=False, condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLANG_REPL, reason="Crashes on Valgind Clang-Repl-ARM") + @mark.xfail( + run=False, + condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLANG_REPL, + reason="Crashes on Valgind Clang-Repl-ARM", + ) def test05_converters(self): """Smart pointer argument passing""" - import cppyy + import cppjit - pz = cppyy.gbl.pyzables + pz = cppjit.gbl.pyzables mine = pz.mine - assert 0xcdcdcdcd == pz.pass_mine_rp_ptr(mine) - assert 0xcdcdcdcd == pz.pass_mine_rp_ref(mine) - assert 0xcdcdcdcd == pz.pass_mine_rp(mine) + assert 0xCDCDCDCD == pz.pass_mine_rp_ptr(mine) + assert 0xCDCDCDCD == pz.pass_mine_rp_ref(mine) + assert 0xCDCDCDCD == pz.pass_mine_rp(mine) - assert 0xcdcdcdcd == pz.pass_mine_sp_ptr(mine) - assert 0xcdcdcdcd == pz.pass_mine_sp_ref(mine) + assert 0xCDCDCDCD == pz.pass_mine_sp_ptr(mine) + assert 0xCDCDCDCD == pz.pass_mine_sp_ref(mine) - assert 0xcdcdcdcd == pz.pass_mine_sp_ptr(mine.__smartptr__()) - assert 0xcdcdcdcd == pz.pass_mine_sp_ref(mine.__smartptr__()) + assert 0xCDCDCDCD == pz.pass_mine_sp_ptr(mine.__smartptr__()) + assert 0xCDCDCDCD == pz.pass_mine_sp_ref(mine.__smartptr__()) - assert 0xcdcdcdcd == pz.pass_mine_sp(mine) - assert 0xcdcdcdcd == pz.pass_mine_sp(mine.__smartptr__()) + assert 0xCDCDCDCD == pz.pass_mine_sp(mine) + assert 0xCDCDCDCD == pz.pass_mine_sp(mine.__smartptr__()) # TODO: - # cppyy.gbl.mine = mine + # cppjit.gbl.mine = mine pz.renew_mine() - @mark.xfail(run=False, condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLANG_REPL, reason="Fails with Valgrind with Clang-Repl ARM") + @mark.xfail( + run=False, + condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLANG_REPL, + reason="Fails with Valgrind with Clang-Repl ARM", + ) def test06_executors(self): """Smart pointer return types""" - import cppyy + import cppjit - pz = cppyy.gbl.pyzables + pz = cppjit.gbl.pyzables Countable = pz.Countable mine = pz.gime_mine_ptr() assert type(mine) == Countable - assert mine.m_check == 0xcdcdcdcd - assert type(mine.__smartptr__()) == cppyy.gbl.std.shared_ptr(Countable) - assert mine.__smartptr__().get().m_check == 0xcdcdcdcd + assert mine.m_check == 0xCDCDCDCD + assert type(mine.__smartptr__()) == cppjit.gbl.std.shared_ptr(Countable) + assert mine.__smartptr__().get().m_check == 0xCDCDCDCD assert mine.say_hi() == "Hi!" mine = pz.gime_mine_ref() assert type(mine) == Countable - assert mine.m_check == 0xcdcdcdcd - assert type(mine.__smartptr__()) == cppyy.gbl.std.shared_ptr(Countable) - assert mine.__smartptr__().get().m_check == 0xcdcdcdcd + assert mine.m_check == 0xCDCDCDCD + assert type(mine.__smartptr__()) == cppjit.gbl.std.shared_ptr(Countable) + assert mine.__smartptr__().get().m_check == 0xCDCDCDCD assert mine.say_hi() == "Hi!" mine = pz.gime_mine() assert type(mine) == Countable - assert mine.m_check == 0xcdcdcdcd - assert type(mine.__smartptr__()) == cppyy.gbl.std.shared_ptr(Countable) - assert mine.__smartptr__().get().m_check == 0xcdcdcdcd + assert mine.m_check == 0xCDCDCDCD + assert type(mine.__smartptr__()) == cppjit.gbl.std.shared_ptr(Countable) + assert mine.__smartptr__().get().m_check == 0xCDCDCDCD assert mine.say_hi() == "Hi!" def test07_creates_flag(self): """Effect of creates flag on return type""" - import cppyy, gc + import gc + + import cppjit - pz = cppyy.gbl.pyzables + pz = cppjit.gbl.pyzables Countable = pz.Countable gc.collect() - oldcount = Countable.sInstances # there's eg. one global variable + oldcount = Countable.sInstances # there's eg. one global variable pz.gime_naked_countable.__creates__ = True for i in range(10): @@ -228,66 +254,71 @@ def test07_creates_flag(self): def test08_base_class_pythonization(self): """Derived class should not re-pythonize base class pythonization""" - import cppyy + import cppjit - d = cppyy.gbl.pyzables.IndexableDerived() + d = cppjit.gbl.pyzables.IndexableDerived() - assert d[0] == 42 + assert d[0] == 42 assert d[-1] == 42 # skip the IndexErorr test: pythonization for __getitem__[index] < size() # can not be applied strict enough (instead of an index, this could be an # associative container, with 'index' a key, not a counter - #raises(IndexError, d.__getitem__, 1) + # raises(IndexError, d.__getitem__, 1) @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test09_cpp_side_pythonization(self): """Use of C++ side pythonizations""" - import cppyy + import cppjit - # explicit pythonization - for kls in [cppyy.gbl.pyzables.WithCallback1, cppyy.gbl.pyzables.WithCallback2]: + # explicit pythonization + for kls in [ + cppjit.gbl.pyzables.WithCallback1, + cppjit.gbl.pyzables.WithCallback2, + ]: w = kls(42) - assert hasattr(w, 'GetInt') - assert not hasattr(w, 'get_int') + assert hasattr(w, "GetInt") + assert not hasattr(w, "get_int") assert w.GetInt() == 42 - assert hasattr(w, 'SetInt') - assert not hasattr(w, 'set_int') + assert hasattr(w, "SetInt") + assert not hasattr(w, "set_int") w.SetInt(17) assert w.GetInt() == 17 assert kls.klass_name == kls.__cpp_name__ - # up-the-hierarchy pythonization - w = cppyy.gbl.pyzables.WithCallback3(42) - assert hasattr(w, 'GetInt') - assert not hasattr(w, 'get_int') - assert w.GetInt() == 2*42 + # up-the-hierarchy pythonization + w = cppjit.gbl.pyzables.WithCallback3(42) + assert hasattr(w, "GetInt") + assert not hasattr(w, "get_int") + assert w.GetInt() == 2 * 42 - assert hasattr(w, 'SetInt') - assert not hasattr(w, 'set_int') + assert hasattr(w, "SetInt") + assert not hasattr(w, "set_int") w.SetInt(17) - assert w.GetInt() == 4*17 + assert w.GetInt() == 4 * 17 + + assert cppjit.gbl.pyzables.WithCallback2.klass_name == "pyzables::WithCallback3" - assert cppyy.gbl.pyzables.WithCallback2.klass_name == 'pyzables::WithCallback3' - @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test10_shared_ptr_reset(self): """Checks that smart pointer types are Pythonized with the special __smartptr__ member that can also be used to reset the underlying smart pointer.""" - import cppyy + import cppjit - optr = cppyy.gbl.std.make_shared["std::string"]("hello smart pointer") - o2 = cppyy.gbl.std.string() - cppyy._backend.SetOwnership(o2, False) # This object will be owned by the smart pointer + optr = cppjit.gbl.std.make_shared["std::string"]("hello smart pointer") + o2 = cppjit.gbl.std.string() + cppjit._backend.SetOwnership( + o2, False + ) # This object will be owned by the smart pointer optr.__smartptr__().reset(o2) assert optr == o2 ## actual test run -if __name__ == '__main__': - result = run_pytest(__file__) +if __name__ == "__main__": + result = run_pytest(__file__) # noqa: F821 sys.exit(result) diff --git a/test/test_regression.py b/test/test_regression.py index 942807c..638fc50 100644 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -1,29 +1,42 @@ -import py, os, sys -from pytest import raises, skip, mark -from support import setup_make, IS_WINDOWS, ispypy, IS_CLANG_REPL, IS_CLING, IS_CLANG_DEBUG, IS_MAC_X86, IS_MAC_ARM, IS_MAC +import os +import sys + +from pytest import mark, raises, skip +from support import ( + IS_CLANG_REPL, + IS_CLING, + IS_MAC, + IS_MAC_ARM, + IS_WINDOWS, + ispypy, +) class TestREGRESSION: helpout = [] def setup_class(cls): - import cppyy - if sys.hexversion < 0x30d0000: + if sys.hexversion < 0x30D0000: + def stringpager(text, cls=cls): cls.helpout.append(text) else: - def stringpager(text, title='', cls=cls): + + def stringpager(text, title="", cls=cls): cls.helpout.append(text) import pydoc + pydoc.pager = stringpager @mark.xfail def test01_kdcraw(self): """Doc strings for KDcrawIface (used to crash).""" - import cppyy, pydoc + import pydoc + + import cppjit # TODO: run a find for these paths qtpath = "/usr/include/qt5" @@ -33,31 +46,33 @@ def test01_kdcraw(self): # need to resolve qt_version_tag for the incremental compiler; since # it's not otherwise used, just make something up - cppyy.cppdef("int qt_version_tag = 42;") - cppyy.add_include_path(qtpath) - cppyy.include(kdcraw_h) + cppjit.cppdef("int qt_version_tag = 42;") + cppjit.add_include_path(qtpath) + cppjit.include(kdcraw_h) # bring in some symbols to resolve the class - cppyy.load_library("libQt5Core.so") - cppyy.load_library("libKF5KDcraw.so") + cppjit.load_library("libQt5Core.so") + cppjit.load_library("libKF5KDcraw.so") - from cppyy.gbl import KDcrawIface + from cppjit.gbl import KDcrawIface self.__class__.helpout = [] pydoc.doc(KDcrawIface.KDcraw) - helptext = ''.join(self.__class__.helpout) - assert 'KDcraw' in helptext - assert 'CPPInstance' in helptext + helptext = "".join(self.__class__.helpout) + assert "KDcraw" in helptext + assert "CPPInstance" in helptext def test02_dir(self): """For the same reasons as test01_kdcraw, this used to crash.""" if ispypy: - skip('hangs (??) in pypy') + skip("hangs (??) in pypy") + + import pydoc - import cppyy, pydoc + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace docs { struct MyDocs { void fn() {} @@ -66,83 +81,88 @@ def test02_dir(self): } """) - assert not '__abstractmethods__' in dir(cppyy.gbl.docs.ptrDocs) - assert '__class__' in dir(cppyy.gbl.docs.ptrDocs) + assert "__abstractmethods__" not in dir(cppjit.gbl.docs.ptrDocs) + assert "__class__" in dir(cppjit.gbl.docs.ptrDocs) self.__class__.helpout = [] - pydoc.doc(cppyy.gbl.docs.ptrDocs) - helptext = ''.join(self.__class__.helpout) - assert 'MyDocs' in helptext - assert 'CPPInstance' in helptext - assert 'fn' in helptext + pydoc.doc(cppjit.gbl.docs.ptrDocs) + helptext = "".join(self.__class__.helpout) + assert "MyDocs" in helptext + assert "CPPInstance" in helptext + assert "fn" in helptext - cppyy.cppdef("namespace cppyy_regression_test { void iii() {}; }") + cppjit.cppdef("namespace cppjit_regression_test { void iii() {}; }") - assert not 'iii' in cppyy.gbl.cppyy_regression_test.__dict__ - assert not '__abstractmethods__' in dir(cppyy.gbl.cppyy_regression_test) - assert '__class__' in dir(cppyy.gbl.cppyy_regression_test) - assert 'iii' in dir(cppyy.gbl.cppyy_regression_test) + assert "iii" not in cppjit.gbl.cppjit_regression_test.__dict__ + assert "__abstractmethods__" not in dir(cppjit.gbl.cppjit_regression_test) + assert "__class__" in dir(cppjit.gbl.cppjit_regression_test) + assert "iii" in dir(cppjit.gbl.cppjit_regression_test) - assert not 'iii' in cppyy.gbl.cppyy_regression_test.__dict__ - assert cppyy.gbl.cppyy_regression_test.iii - assert 'iii' in cppyy.gbl.cppyy_regression_test.__dict__ + assert "iii" not in cppjit.gbl.cppjit_regression_test.__dict__ + assert cppjit.gbl.cppjit_regression_test.iii + assert "iii" in cppjit.gbl.cppjit_regression_test.__dict__ self.__class__.helpout = [] - pydoc.doc(cppyy.gbl.cppyy_regression_test) - helptext = ''.join(self.__class__.helpout) - # TODO: it's deeply silly that namespaces inherit from CPPInstance (in CPyCppyy) - assert ('CPPInstance' in helptext or 'CPPNamespace' in helptext) + pydoc.doc(cppjit.gbl.cppjit_regression_test) + helptext = "".join(self.__class__.helpout) + # TODO: it's deeply silly that namespaces inherit from CPPInstance (in cpyrt) + assert "CPPInstance" in helptext or "CPPNamespace" in helptext def test03_pyfunc_doc(self): """Help on a generated pyfunc used to crash.""" - import cppyy, pydoc, sys + import pydoc + import sys import sysconfig as sc - cppyy.add_include_path(sc.get_config_var("INCLUDEPY")) + import cppjit + + cppjit.add_include_path(sc.get_config_var("INCLUDEPY")) if sys.hexversion < 0x3000000: - cppyy.cppdef("#undef _POSIX_C_SOURCE") - cppyy.cppdef("#undef _XOPEN_SOURCE") + cppjit.cppdef("#undef _POSIX_C_SOURCE") + cppjit.cppdef("#undef _XOPEN_SOURCE") else: - cppyy.cppdef("#undef slots") # potentially pulled in by Qt/xapian.h + cppjit.cppdef("#undef slots") # potentially pulled in by Qt/xapian.h - cppyy.cppdef("""#include "Python.h" + cppjit.cppdef("""#include "Python.h" long py2long(PyObject* obj) { return PyLong_AsLong(obj); }""") - pydoc.doc(cppyy.gbl.py2long) + pydoc.doc(cppjit.gbl.py2long) - assert 1 == cppyy.gbl.py2long(1) + assert 1 == cppjit.gbl.py2long(1) def test04_avx(self): """Test usability of AVX by default.""" - import cppyy, subprocess + import subprocess + + import cppjit has_avx = False try: - f = open('/proc/cpuinfo', 'r') + f = open("/proc/cpuinfo", "r") for line in f.readlines(): - if 'avx' in line: + if "avx" in line: has_avx = True break f.close() except Exception: try: - cli_arg = subprocess.check_output(['sysctl', 'machdep.cpu.features']) - has_avx = 'avx' in cli_arg.decode("utf-8").strip().lower() + cli_arg = subprocess.check_output(["sysctl", "machdep.cpu.features"]) + has_avx = "avx" in cli_arg.decode("utf-8").strip().lower() except Exception: pass if has_avx: - assert cppyy.cppdef('int check_avx() { return (int) __AVX__; }') - assert cppyy.gbl.check_avx() # attribute error if compilation failed + assert cppjit.cppdef("int check_avx() { return (int) __AVX__; }") + assert cppjit.gbl.check_avx() # attribute error if compilation failed def test05_default_template_arguments(self): """Calling a templated method on a templated class with all defaults used to crash.""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ template class AllDefault { public: @@ -154,16 +174,16 @@ class AllDefault { T m_t; };""") - a = cppyy.gbl.AllDefault[int](24) - a.m_t = 21; + a = cppjit.gbl.AllDefault[int](24) + a.m_t = 21 assert a.do_stuff() == 24 def test06_default_float_or_unsigned_argument(self): """Calling with default argument for float or unsigned, which not parse""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace Defaulters { float take_float(float a=5.f, int b=2) { return a*b; } double take_double(double a=5.f, int b=2) { return a*b; } @@ -171,24 +191,27 @@ def test06_default_float_or_unsigned_argument(self): unsigned long take_ulong(unsigned long a=5ul, int b=2) { return a*b; } }""") - ns = cppyy.gbl.Defaulters + ns = cppjit.gbl.Defaulters - # the following default argument used to fail to parse - assert ns.take_float() == 10. - assert ns.take_float(b=2) == 10. - assert ns.take_double() == 10. - assert ns.take_double(b=2) == 10. - assert ns.take_long() == 10 - assert ns.take_long(b=2) == 10 - assert ns.take_ulong() == 10 - assert ns.take_ulong(b=2) == 10 + # the following default argument used to fail to parse + assert ns.take_float() == 10.0 + assert ns.take_float(b=2) == 10.0 + assert ns.take_double() == 10.0 + assert ns.take_double(b=2) == 10.0 + assert ns.take_long() == 10 + assert ns.take_long(b=2) == 10 + assert ns.take_ulong() == 10 + assert ns.take_ulong(b=2) == 10 def test07_class_refcounting(self): """The memory regulator would leave an additional refcount on classes""" - import cppyy, gc, sys + import gc + import sys + + import cppjit - x = cppyy.gbl.std.vector['float'] + x = cppjit.gbl.std.vector["float"] old_refcnt = sys.getrefcount(x) y = x() @@ -201,26 +224,26 @@ def test07_class_refcounting(self): def test08_typedef_identity(self): """Nested typedefs should retain identity""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace PyABC { struct S1 {}; struct S2 { typedef std::vector S1_coll; }; }""") - from cppyy.gbl import PyABC + from cppjit.gbl import PyABC assert PyABC.S2.S1_coll - assert 'S1_coll' in dir(PyABC.S2) - assert not 'vector' in dir(PyABC.S2) - assert PyABC.S2.S1_coll is cppyy.gbl.std.vector('const PyABC::S1*') + assert "S1_coll" in dir(PyABC.S2) + assert "vector" not in dir(PyABC.S2) + assert PyABC.S2.S1_coll is cppjit.gbl.std.vector("const PyABC::S1*") def test09_gil_not_released(self): """GIL was released by accident for by-value returns""" - import cppyy + import cppjit something = 5.0 @@ -236,50 +259,50 @@ def test09_gil_not_released(self): } """.replace("ADDRESS", str(id(something))) - cppyy.cppdef(code) - cppyy.gbl.some_foo_calling_python() + cppjit.cppdef(code) + cppjit.gbl.some_foo_calling_python() - @mark.xfail(run = False, condition = IS_CLING, reason="Crashes on Cling") + @mark.xfail(run=False, condition=IS_CLING, reason="Crashes on Cling") def test10_enum_in_global_space(self): """Enum declared in search.h did not appear in global space""" if IS_WINDOWS: - return # no such enum in MSVC's search.h + return # no such enum in MSVC's search.h - import cppyy + import cppjit - cppyy.include('search.h') + cppjit.include("search.h") - assert cppyy.gbl.ACTION - assert hasattr(cppyy.gbl, 'ENTER') - assert hasattr(cppyy.gbl, 'FIND') + assert cppjit.gbl.ACTION + assert hasattr(cppjit.gbl, "ENTER") + assert hasattr(cppjit.gbl, "FIND") def test11_cobject_addressing(self): """AsCObject (now as_cobject) had a deref too many""" - import cppyy - import cppyy.ll + import cppjit + import cppjit.ll - cppyy.cppdef('struct CObjA { CObjA() : m_int(42) {} int m_int; };') - a = cppyy.gbl.CObjA() - co = cppyy.ll.as_cobject(a) + cppjit.cppdef("struct CObjA { CObjA() : m_int(42) {} int m_int; };") + a = cppjit.gbl.CObjA() + co = cppjit.ll.as_cobject(a) - assert a is cppyy.bind_object(co, 'CObjA') + assert a is cppjit.bind_object(co, "CObjA") assert a.m_int == 42 - assert cppyy.bind_object(co, 'CObjA').m_int == 42 + assert cppjit.bind_object(co, "CObjA").m_int == 42 def test12_exception_while_exception(self): """Exception from SetDetailedException during exception handling used to crash""" - import cppyy + import cppjit - cppyy.cppdef("namespace AnExceptionNamespace { }") + cppjit.cppdef("namespace AnExceptionNamespace { }") try: - cppyy.gbl.blabla + cppjit.gbl.blabla except AttributeError: try: - cppyy.gbl.AnExceptionNamespace.blabla + cppjit.gbl.AnExceptionNamespace.blabla except AttributeError: pass @@ -287,14 +310,14 @@ def test12_exception_while_exception(self): def test13_char_star_over_char(self): """Map str to const char* over char""" - # This is debatable, but although a single character string passes through char, - # it is more consistent to prefer const char* or std::string in all cases. The - # original bug report is here: - # https://bitbucket.org/wlav/cppyy/issues/127/string-argument-resolves-incorrectly + # This is debatable, but although a single character string passes through char, + # it is more consistent to prefer const char* or std::string in all cases. The + # original bug report is here: + # https://bitbucket.org/wlav/cppyy/issues/127/string-argument-resolves-incorrectly - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace csoc1 { std::string call(char) { return "char"; } } @@ -310,21 +333,21 @@ def test13_char_star_over_char(self): } """) - assert cppyy.gbl.csoc1.call('0') == 'char' - raises(ValueError, cppyy.gbl.csoc1.call, '00') + assert cppjit.gbl.csoc1.call("0") == "char" + raises(ValueError, cppjit.gbl.csoc1.call, "00") - assert cppyy.gbl.csoc2.call('0') == 'const char*' - assert cppyy.gbl.csoc2.call('00') == 'const char*' + assert cppjit.gbl.csoc2.call("0") == "const char*" + assert cppjit.gbl.csoc2.call("00") == "const char*" - assert cppyy.gbl.csoc3.call('0') == 'string' - assert cppyy.gbl.csoc3.call('00') == 'string' + assert cppjit.gbl.csoc3.call("0") == "string" + assert cppjit.gbl.csoc3.call("00") == "string" def test14_struct_direct_definition(self): """Struct defined directly in a scope miseed scope in renormalized name""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace struct_direct_definition { struct Bar { struct Baz { @@ -347,15 +370,15 @@ class Bar { }; }""") - from cppyy.gbl import struct_direct_definition as sds + from cppjit.gbl import struct_direct_definition as sds b = sds.Bar() assert len(b.baz) == 2 assert len(b.baz[0].data) == 1 - assert b.baz[0].data[0] == 3.14 + assert b.baz[0].data[0] == 3.14 assert len(b.baz[1].data) == 1 - assert b.baz[1].data[0] == 2.73 + assert b.baz[1].data[0] == 2.73 f = sds.Foo() assert f.bar.x == 5 @@ -364,9 +387,9 @@ class Bar { def test15_vector_vs_initializer_list(self): """Prefer vector in template and initializer_list in formal arguments""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace vec_vs_init { template std::string nameit1(const T& t) { @@ -382,24 +405,24 @@ def test15_vector_vs_initializer_list(self): } }""") - nameit1 = cppyy.gbl.vec_vs_init.nameit1 - assert 'vector' in nameit1(list(range(10))) - assert 'vector' in nameit1(cppyy.gbl.std.vector[int]()) + nameit1 = cppjit.gbl.vec_vs_init.nameit1 + assert "vector" in nameit1(list(range(10))) + assert "vector" in nameit1(cppjit.gbl.std.vector[int]()) - nameit2 = cppyy.gbl.vec_vs_init.nameit2 - assert 'vector' in nameit2(list(range(10))) - assert 'vector' in nameit2(cppyy.gbl.std.vector[int]()) + nameit2 = cppjit.gbl.vec_vs_init.nameit2 + assert "vector" in nameit2(list(range(10))) + assert "vector" in nameit2(cppjit.gbl.std.vector[int]()) - sizeit = cppyy.gbl.vec_vs_init.sizeit + sizeit = cppjit.gbl.vec_vs_init.sizeit assert sizeit(list(range(10))) == 10 def test16_iterable_enum(self): """Use template to iterate over an enum""" - # from: https://stackoverflow.com/questions/52459530/pybind11-emulate-python-enum-behaviour + # from: https://stackoverflow.com/questions/52459530/pybind11-emulate-python-enum-behaviour - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" template struct my_iter_enum { struct iterator { @@ -440,7 +463,7 @@ def test16_iterable_enum(self): Unknown };""") - Color = cppyy.gbl.my_iter_enum['MyColorEnum'] + Color = cppjit.gbl.my_iter_enum["MyColorEnum"] assert Color.iterator c_iterable = Color() @@ -454,9 +477,9 @@ def test16_iterable_enum(self): def test17_operator_eq_pickup(self): """Base class python-side operator== interered with derived one""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace SelectOpEq { class Base {}; @@ -470,7 +493,7 @@ class Derived2 : public Base { bool operator!=(Derived2&) { return true; } }; }""") - soe = cppyy.gbl.SelectOpEq + soe = cppjit.gbl.SelectOpEq soe.Base.__eq__ = lambda first, second: False soe.Base.__ne__ = lambda first, second: False @@ -478,51 +501,49 @@ class Derived2 : public Base { a = soe.Derived1() b = soe.Derived1() - assert a == b # derived class' C++ operator== called + assert a == b # derived class' C++ operator== called a = soe.Derived2() b = soe.Derived2() - assert a != b # derived class' C++ operator!= called + assert a != b # derived class' C++ operator!= called @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test18_operator_plus_overloads(self): """operator+(string, string) should return a string""" - import cppyy + import cppjit - a = cppyy.gbl.std.string("a") - b = cppyy.gbl.std.string("b") + a = cppjit.gbl.std.string("a") + b = cppjit.gbl.std.string("b") - assert a == 'a' - assert b == 'b' + assert a == "a" + assert b == "b" - assert type(a+b) == cppyy.gbl.std.string - assert a+b == 'ab' + assert type(a + b) == cppjit.gbl.std.string + assert a + b == "ab" @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test19_std_string_hash(self): """Hashing of std::string""" - import cppyy - - import cppyy + import cppjit - s = cppyy.gbl.std.string("text") + s = cppjit.gbl.std.string("text") d = {} - # hashes of std::string larger than 2**31 would fail; run a couple of - # strings to check although it may still succeed by accident (and never - # was an issue on p3 anyway) - for s in ['abc', 'text', '321', 'stuff', 'very long string']: + # hashes of std::string larger than 2**31 would fail; run a couple of + # strings to check although it may still succeed by accident (and never + # was an issue on p3 anyway) + for s in ["abc", "text", "321", "stuff", "very long string"]: d[s] = 1 def test20_signed_char_ref(self): """Signed char executor was self-referencing""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" class SignedCharRefGetter { public: void setter(signed char sc) { m_c = sc; } @@ -530,31 +551,31 @@ class SignedCharRefGetter { signed char m_c; };""") - obj = cppyy.gbl.SignedCharRefGetter() - obj.setter('c') + obj = cppjit.gbl.SignedCharRefGetter() + obj.setter("c") - assert obj.getter() == 'c' + assert obj.getter() == "c" @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test21_temporaries_and_vector(self): """Extend a life line to references into a vector if needed""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" std::vector get_some_temporary_vector() { return { "x", "y", "z" }; } """) - l = [e for e in cppyy.gbl.get_some_temporary_vector()] - assert l == ['x', 'y', 'z'] + l = [e for e in cppjit.gbl.get_some_temporary_vector()] + assert l == ["x", "y", "z"] @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test22_initializer_list_and_temporary(self): """Conversion rules when selecting intializer_list v.s. temporary""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace regression_test21 { std::string what_called = ""; class Foo { @@ -586,23 +607,25 @@ class Bar { } }; }""") - r21 = cppyy.gbl.regression_test21 + r21 = cppjit.gbl.regression_test21 assert len(r21.what_called) == 0 r21.Bar(1) - assert r21.what_called == 'Bar(int)' + assert r21.what_called == "Bar(int)" - r21.what_called = '' - r21.Bar([1,2]) # used to call Bar(Foo x) through implicit conversion - assert r21.what_called == 'Bar(il)' + r21.what_called = "" + r21.Bar([1, 2]) # used to call Bar(Foo x) through implicit conversion + assert r21.what_called == "Bar(il)" def test23_copy_constructor(self): """Copy construct an object into an empty (NULL) proxy""" - import cppyy, gc + import gc - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace regression_test22 { struct Countable { static int s_count; @@ -618,7 +641,7 @@ def test23_copy_constructor(self): void destroyit() { delete c; } }""") - r22 = cppyy.gbl.regression_test22 + r22 = cppjit.gbl.regression_test22 assert r22.Countable.s_count == 0 c = r22.Countable() @@ -642,7 +665,7 @@ def test23_copy_constructor(self): gc.collect() assert r22.Countable.s_count == 0 - c = cppyy.bind_object(cppyy.nullptr, r22.Countable) + c = cppjit.bind_object(cppjit.nullptr, r22.Countable) assert r22.Countable.s_count == 0 c.__init__(r22.Countable()) gc.collect() @@ -666,15 +689,15 @@ def test23_copy_constructor(self): assert r22.Countable.s_count == 1 r22.destroyit() - r22.c = cppyy.nullptr + r22.c = cppjit.nullptr assert r22.Countable.s_count == 0 def test24_C_style_enum(self): """Support C-style enum variable declarations""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace CStyleEnum { enum MyEnum { kOne, kTwo }; MyEnum my_enum = kOne; @@ -683,13 +706,13 @@ def test24_C_style_enum(self): enum YourEnum your_enum = kThree; }""") - CSE = cppyy.gbl.CStyleEnum + CSE = cppjit.gbl.CStyleEnum assert CSE.my_enum == CSE.MyEnum.kOne CSE.my_enum = CSE.MyEnum.kTwo assert CSE.my_enum == CSE.MyEnum.kTwo - # the following would fail b/c the type was not properly resolved + # the following would fail b/c the type was not properly resolved assert CSE.your_enum == CSE.YourEnum.kThree CSE.your_enum = CSE.YourEnum.kFour assert CSE.your_enum == CSE.YourEnum.kFour @@ -697,9 +720,9 @@ def test24_C_style_enum(self): def test25_const_iterator(self): """const_iterator failed to resolve the proper return type""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace RooStuff { struct RooArg {}; @@ -712,20 +735,20 @@ def test25_const_iterator(self): const_iterator end() const { return _list.end(); } }; }""") - l = cppyy.gbl.RooStuff.RooCollection() + l = cppjit.gbl.RooStuff.RooCollection() i = 0 for e in l: - assert type(e) == cppyy.gbl.RooStuff.RooArg + assert type(e) == cppjit.gbl.RooStuff.RooArg i += 1 assert i def test26_const_charptr_data(self): """const char* is not const; const char* const is""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace ConstCharStar { struct ImGuiIO1 { ImGuiIO1() : BackendPlatformName(nullptr) {} @@ -736,7 +759,7 @@ def test26_const_charptr_data(self): const char* const BackendPlatformName; }; }""") - io = cppyy.gbl.ConstCharStar.ImGuiIO1() + io = cppjit.gbl.ConstCharStar.ImGuiIO1() io.BackendPlatformName = "aap" assert io.BackendPlatformName == "aap" @@ -744,16 +767,18 @@ def test26_const_charptr_data(self): io.BackendPlatformName = "aap\0noot" io.BackendPlatformName = "aap\0noot" - io = cppyy.gbl.ConstCharStar.ImGuiIO2() + io = cppjit.gbl.ConstCharStar.ImGuiIO2() with raises(TypeError): io.BackendPlatformName = "aap" def test27_exception_by_value(self): """Proper memory management of exception return by value""" - import cppyy, gc + import gc + + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ExceptionByValue { class Countable : std::exception { public: @@ -773,7 +798,7 @@ class Countable : std::exception { int count() { return Countable::s_count; } }""") - ns = cppyy.gbl.ExceptionByValue + ns = cppjit.gbl.ExceptionByValue assert ns.count() == 0 c = ns.create_one() @@ -786,25 +811,29 @@ class Countable : std::exception { def test28_exception_as_shared_ptr(self): """shared_ptr of an exception object null-checking""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace exception_as_shared_ptr { std::shared_ptr get_shared_null() { return std::shared_ptr(); } }""") - null = cppyy.gbl.exception_as_shared_ptr.get_shared_null() + null = cppjit.gbl.exception_as_shared_ptr.get_shared_null() assert not null - @mark.xfail(run=False, condition=(IS_CLING and IS_MAC) or IS_MAC_ARM, reason="Dispatcher fix #53 introduces canonical types with std:: namespace that introduces OS X exceptions similar to test_stltypes") + @mark.xfail( + run=False, + condition=(IS_CLING and IS_MAC) or IS_MAC_ARM, + reason="Dispatcher fix #53 introduces canonical types with std:: namespace that introduces OS X exceptions similar to test_stltypes", + ) def test29_callback_pointer_values(self): """Make sure pointer comparisons in callbacks work as expected""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace addressof_regression { class ChangeBroadcaster; @@ -845,9 +874,9 @@ class Implementation { DerivedClass derived; }; }""") - ns = cppyy.gbl.addressof_regression + ns = cppjit.gbl.addressof_regression - class Glue(cppyy.multi(ns.Implementation, ns.ChangeListener)): + class Glue(cppjit.multi(ns.Implementation, ns.ChangeListener)): def __init__(self): super(ns.Implementation, self).__init__() self.derived.addChangeListener(self) @@ -859,7 +888,7 @@ def triggerChange(self): def changeCallback(self, b): assert type(b) == type(self.derived) assert b == self.derived - cast = cppyy.gbl.std.addressof[type(b)] + cast = cppjit.gbl.std.addressof[type(b)] assert cast(b) == cast(self.derived) self.success = True @@ -872,9 +901,9 @@ def changeCallback(self, b): def test30_uint64_t(self): """Failure due to typo""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ #include namespace UInt64_Typo { uint64_t Test(uint64_t v) { return v; } @@ -882,22 +911,22 @@ def test30_uint64_t(self): template Struct TTest(T t) { return Struct{t}; } }""") - from cppyy.gbl import UInt64_Typo as ns + from cppjit.gbl import UInt64_Typo as ns - std = cppyy.gbl.std - uint64_t = cppyy.gbl.uint64_t - umax64 = std.numeric_limits[uint64_t].max() - int64_t = cppyy.gbl.int64_t - max64 = std.numeric_limits[int64_t].max() - min64 = std.numeric_limits[int64_t].min() + std = cppjit.gbl.std + uint64_t = cppjit.gbl.uint64_t + umax64 = std.numeric_limits[uint64_t].max() + int64_t = cppjit.gbl.int64_t + max64 = std.numeric_limits[int64_t].max() + min64 = std.numeric_limits[int64_t].min() assert max64 < umax64 assert min64 < max64 assert umax64 == ns.Test(umax64) assert ns.TTest(umax64).fT == umax64 - assert ns.TTest(max64).fT == max64 - assert ns.TTest(min64).fT == min64 + assert ns.TTest(max64).fT == max64 + assert ns.TTest(min64).fT == min64 assert ns.TTest(1.01).fT == 1.01 assert ns.TTest(True).fT == True assert type(ns.TTest(True).fT) == bool @@ -905,9 +934,9 @@ def test30_uint64_t(self): def test31_enum_in_dir(self): """Failed to pick up enum data""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace enum_in_dir { long prod (long a, long b) { return a * b; } long prod (long a, long b, long c) { return a * b * c; } @@ -919,17 +948,17 @@ def test31_enum_in_dir(self): enum smth my_enum = smth::ONE; }""") - all_names = set(dir(cppyy.gbl.enum_in_dir)) + all_names = set(dir(cppjit.gbl.enum_in_dir)) - required = {'prod', 'a', 'b', 'smth', 'my_enum'} + required = {"prod", "a", "b", "smth", "my_enum"} assert all_names.intersection(required) == required def test32_typedef_class_enum(self): """Use of class enum with typedef'd type""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace typedef_typed_enum { enum class Foo1 : uint16_t { BAZ = 1, BAR = 2 }; enum class Foo2 : unsigned short { BAZ = 3, BAR = 4 }; @@ -941,7 +970,7 @@ def test32_typedef_class_enum(self): uint8_t y; }; } """) - ns = cppyy.gbl.typedef_typed_enum + ns = cppjit.gbl.typedef_typed_enum Info = ns.Info @@ -964,9 +993,9 @@ def test32_typedef_class_enum(self): def test33_explicit_template_in_namespace(self): """Lookup of explicit template in namespace""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace libchemist { namespace type { template class tensor {}; @@ -1004,34 +1033,41 @@ class ReferenceWavefunction {}; } } // namespace type, property_types """) - assert cppyy.gbl.property_types.type.canonical_mos['double'] - assert cppyy.gbl.std.get[0](cppyy.gbl.libchemist.produce()) == 10. - assert cppyy.gbl.std.get[0](cppyy.gbl.property_types.produce()) == 5. + assert cppjit.gbl.property_types.type.canonical_mos["double"] + assert cppjit.gbl.std.get[0](cppjit.gbl.libchemist.produce()) == 10.0 + assert cppjit.gbl.std.get[0](cppjit.gbl.property_types.produce()) == 5.0 - pt_type = cppyy.gbl.property_types.ReferenceWavefunction['double'] - assert cppyy.gbl.std.get[0](cppyy.gbl.property_types.run_as[pt_type]()) == 20. + pt_type = cppjit.gbl.property_types.ReferenceWavefunction["double"] + assert ( + cppjit.gbl.std.get[0](cppjit.gbl.property_types.run_as[pt_type]()) == 20.0 + ) - @mark.xfail(run=False, reason="Crashes on ClangRepl with 'toString not implemented', and on Cling") + @mark.xfail( + run=False, + reason="Crashes on ClangRepl with 'toString not implemented', and on Cling", + ) def test34_print_empty_collection(self): """Print empty collection through Cling""" - import cppyy + import cppjit - # printing an empty collection used to have a missing symbol on 64b Windows - v = cppyy.gbl.std.vector[int]() + # printing an empty collection used to have a missing symbol on 64b Windows + v = cppjit.gbl.std.vector[int]() str(v) - @mark.xfail(run=IS_CLANG_REPL, condition=IS_MAC or IS_CLING, reason="Crashes on Cling") + @mark.xfail( + run=IS_CLANG_REPL, condition=IS_MAC or IS_CLING, reason="Crashes on Cling" + ) def test35_filesytem(self): """Static path object used to crash on destruction""" if IS_WINDOWS: # TODO: this is b/c of the mangling: it's looking for '_std', but name is '__' - skip('fails due to missing _std_fs_convert_narrow_to_wide symbol') + skip("fails due to missing _std_fs_convert_narrow_to_wide symbol") - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ #include std::string stack_std_path() { std::filesystem::path p = "/usr"; @@ -1040,21 +1076,23 @@ def test35_filesytem(self): return os.str(); }""") - assert cppyy.gbl.stack_std_path() == '"/usr"' + assert cppjit.gbl.stack_std_path() == '"/usr"' def test36_ctypes_sizeof(self): - """cppyy.sizeof forwards to ctypes.sizeof where necessary""" + """cppjit.sizeof forwards to ctypes.sizeof where necessary""" - import cppyy, ctypes + import ctypes - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace test36_ctypes_sizeof { void func(uint32_t* param) { *param = 42; } }""") - ns = cppyy.gbl.test36_ctypes_sizeof + ns = cppjit.gbl.test36_ctypes_sizeof holder = ctypes.c_uint32(17) param = ctypes.pointer(holder) @@ -1066,42 +1104,45 @@ def test36_ctypes_sizeof(self): ns.func(holder) assert holder.value == 42 - assert cppyy.sizeof(param) == ctypes.sizeof(param) + assert cppjit.sizeof(param) == ctypes.sizeof(param) def test37_array_of_pointers_argument(self): """Passing an array of pointers used to crash""" - import cppyy - import cppyy.ll + import cppjit + import cppjit.ll - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ArrayOfPointers { void* test(int *arr[8], bool is_int=true) { return is_int ? (void*)arr : nullptr; } void* test(uint8_t *arr[8], bool is_int=false) { return is_int ? nullptr : (void*)arr; } }""") - ns = cppyy.gbl.ArrayOfPointers + ns = cppjit.gbl.ArrayOfPointers N = 9 - for t, b in (('int*', True), ('uint8_t*', False)): - arr = cppyy.ll.array_new[t](N, managed=True) + for t, b in (("int*", True), ("uint8_t*", False)): + arr = cppjit.ll.array_new[t](N, managed=True) assert arr.shape[0] == N assert len(arr) == N res = ns.test(arr, b) - assert cppyy.addressof(res) == cppyy.addressof(arr) + assert cppjit.addressof(res) == cppjit.addressof(arr) - @mark.xfail(run = False, condition = (IS_MAC and IS_CLING), reason = "Crashes on OS X Cling") + @mark.xfail( + run=False, condition=(IS_MAC and IS_CLING), reason="Crashes on OS X Cling" + ) def test38_char16_arrays(self): """Access to fixed-size char16 arrays as data members""" - import cppyy - import cppyy.ll import warnings - cppyy.cppdef(r"""\ + import cppjit + import cppjit.ll + + cppjit.cppdef(r"""\ namespace Char16Fixed { struct AxisInformation { char16_t name[6]; @@ -1115,51 +1156,51 @@ def test38_char16_arrays(self): N = 10 - ns = cppyy.gbl.Char16Fixed + ns = cppjit.gbl.Char16Fixed ai = ns.AxisInformation() - for s in [u'hello', u'hellow']: + for s in ["hello", "hellow"]: ai.name = s len(ai.name) == 6 - assert ai.name[:len(s)] == s + assert ai.name[: len(s)] == s - # isolate the warning configuration + # isolate the warning configuration with warnings.catch_warnings(): warnings.simplefilter("error") # turn warnings into errors with raises(RuntimeWarning) as exc: - ai.name = u'hellowd' - assert 'too long' in str(exc.value) + ai.name = "hellowd" + assert "too long" in str(exc.value) # vector of objects - va = cppyy.gbl.std.vector[ns.AxisInformation](N) + va = cppjit.gbl.std.vector[ns.AxisInformation](N) ns.fillem(va.data(), N) for ai in va: assert len(ai.name) == 6 - assert ai.name[:5] == u'hello' + assert ai.name[:5] == "hello" # array of objects - aa = cppyy.gbl.std.array[ns.AxisInformation, N]() + aa = cppjit.gbl.std.array[ns.AxisInformation, N]() ns.fillem(aa.data(), N) for ai in aa: assert len(ai.name) == 6 - assert ai.name[:5] == u'hello' + assert ai.name[:5] == "hello" # low-level array of objects - aa = cppyy.ll.array_new[ns.AxisInformation](N) + aa = cppjit.ll.array_new[ns.AxisInformation](N) ns.fillem(aa, N) for ai in aa: assert len(ai.name) == 6 - assert ai.name[:5] == u'hello' - cppyy.ll.array_delete(aa) + assert ai.name[:5] == "hello" + cppjit.ll.array_delete(aa) @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test39_vector_of_pointers_conversion(self): """vector's const T*& used to be T**, now T*""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace VectorOfPointers { struct Base1 { std::string name; explicit Base1(const std::string& name) : name(name) { }}; struct Derived1 : Base1 { explicit Derived1(const std::string& name) : Base1(name) { } }; @@ -1169,8 +1210,8 @@ def test39_vector_of_pointers_conversion(self): std::vector GetVector() { return { &d1, &d2 }; } }; }""") - cppyy.gbl.VectorOfPointers - from cppyy.gbl.VectorOfPointers import Base1, Derived1, Owner + cppjit.gbl.VectorOfPointers + from cppjit.gbl.VectorOfPointers import Base1, Derived1, Owner # noqa: F401 o = Owner() @@ -1194,7 +1235,7 @@ def test39_vector_of_pointers_conversion(self): assert l[0].name == "d1" assert l[1].name == "d2" - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace VectorOfPointers { struct Base2 { }; struct Derived2 : Base2 { }; @@ -1207,18 +1248,25 @@ def test39_vector_of_pointers_conversion(self): std::vector vec3 { &d3 }; }""") - from cppyy.gbl import std - from cppyy.gbl.VectorOfPointers import Base2, Derived2, Base3, Derived3, vec2, vec3 - - assert len(vec2) == 1 + from cppjit.gbl import std # noqa: F401 + from cppjit.gbl.VectorOfPointers import ( + Base2, + Base3, # noqa: F401 + Derived2, + Derived3, + vec2, + vec3, + ) + + assert len(vec2) == 1 assert type(vec2[0]) == Base2 - assert len(list(vec2)) == 1 + assert len(list(vec2)) == 1 assert type(list(vec2)[0]) == Base2 assert len([d for d in vec2 if isinstance(d, Derived2)]) == 0 - assert len(vec3) == 1 + assert len(vec3) == 1 assert type(vec3[0]) == Derived3 - assert len(list(vec3)) == 1 + assert len(list(vec3)) == 1 assert type(list(vec2)[0]) == Base2 assert len([d for d in vec3 if isinstance(d, Derived3)]) == 1 @@ -1226,9 +1274,9 @@ def test39_vector_of_pointers_conversion(self): def test40_explicit_initializer_list(self): """Construct and pass an explicit initializer list""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace ExplicitInitializer { enum class TestEnum { Foo, Bar }; @@ -1239,17 +1287,20 @@ class TestClass { TestClass(TestDictClass x) {} }; }""") - ns = cppyy.gbl.ExplicitInitializer + ns = cppjit.gbl.ExplicitInitializer - TestPair = cppyy.gbl.std.pair[ns.TestEnum, int] - arg = ns.TestDictClass([TestPair(ns.TestEnum.Bar, 4), TestPair(ns.TestEnum.Foo, 12)]) + TestPair = cppjit.gbl.std.pair[ns.TestEnum, int] + arg = ns.TestDictClass( + [TestPair(ns.TestEnum.Bar, 4), TestPair(ns.TestEnum.Foo, 12)] + ) assert ns.TestClass(arg) def test41_typedefed_enums(self): """Typedef-ed enums do not have enum tag in declarations""" - import cppyy - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace TypedefedEnum { typedef enum { MONDAY = 0, @@ -1260,16 +1311,16 @@ def test41_typedefed_enums(self): int func(const Day day) { return (int)day; } }""") - ns = cppyy.gbl.TypedefedEnum + ns = cppjit.gbl.TypedefedEnum assert ns.func(ns.WEDNESDAY) == 2 def test42_char_arrays_consistence(self): """Consistent usage of char[] arrays""" - import cppyy + import cppjit - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace CharArrays { struct Foo { char val[10], *ptr; @@ -1288,46 +1339,46 @@ def test42_char_arrays_consistence(self): foo->pointers[1] = foo->values[1]; } }""") - ns = cppyy.gbl.CharArrays + ns = cppjit.gbl.CharArrays foo = ns.Foo() ns.set_pointers(foo) - howdy = 'howdy!' - hello = 'hello' - world = 'world!' + howdy = "howdy!" + hello = "hello" + world = "world!" - assert ''.join(foo.val)[:len(howdy)] == howdy + assert "".join(foo.val)[: len(howdy)] == howdy assert foo.ptr == howdy assert foo.values[0].as_string() == hello assert foo.values[1].as_string() == world - assert foo.pointers[0] == 'hello' - assert foo.pointers[1] == 'world!' + assert foo.pointers[0] == "hello" + assert foo.pointers[1] == "world!" @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test43_static_with_default(self): """Call a static method with default args on an instance""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace StaticWithDefault { struct MyClass { void static smethod(const std::string& s1, const std::string& s2="") {} }; }""") - ns = cppyy.gbl.StaticWithDefault + ns = cppjit.gbl.StaticWithDefault obj = ns.MyClass() obj.smethod("one", "two") - obj.smethod("one") # used to fail with vectorcall + obj.smethod("one") # used to fail with vectorcall def test44_heuristic_mem_policy(self): """Ownership of arguments with heuristic memory policy""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace MemTester { void CallRef( std::string&) {} void CallConstRef( const std::string&) {} @@ -1339,7 +1390,9 @@ def test44_heuristic_mem_policy(self): try: # The scope with the heuristic memory policy is in a try-except-finally block # to ensure the memory policy is always reset. - old_memory_policy = cppyy._backend.SetMemoryPolicy(cppyy._backend.kMemoryHeuristics) + old_memory_policy = cppjit._backend.SetMemoryPolicy( + cppjit._backend.kMemoryHeuristics + ) # Validate the intended behavior for different argument types: # const ref : caller keeps ownership @@ -1348,50 +1401,54 @@ def test44_heuristic_mem_policy(self): # ptr : caller passed ownership to callee # The actual type doesn't matter - args = [cppyy.gbl.std.string() for i in range(4)] + args = [cppjit.gbl.std.string() for i in range(4)] - cppyy.gbl.MemTester.CallConstRef(args[0]) + cppjit.gbl.MemTester.CallConstRef(args[0]) assert args[0].__python_owns__ - cppyy.gbl.MemTester.CallConstPtr(args[1]) + cppjit.gbl.MemTester.CallConstPtr(args[1]) assert args[1].__python_owns__ - cppyy.gbl.MemTester.CallRef(args[2]) + cppjit.gbl.MemTester.CallRef(args[2]) assert args[2].__python_owns__ - cppyy.gbl.MemTester.CallPtr(args[3]) + cppjit.gbl.MemTester.CallPtr(args[3]) assert not args[3].__python_owns__ # Let's give back the ownership to Python here so there is no leak - cppyy._backend.SetOwnership(args[3], True) + cppjit._backend.SetOwnership(args[3], True) except: - raise # rethrow the exception + raise # rethrow the exception finally: - cppyy._backend.SetMemoryPolicy(old_memory_policy) + cppjit._backend.SetMemoryPolicy(old_memory_policy) @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test45_typedef_resolution(self): """Typedefs starting with 'c'""" - import cppyy + import cppjit - cppyy.cppdef("""\ - namespace Cppyy { + cppjit.cppdef("""\ + namespace cppjit::interop { std::string ResolveName(const std::string& name); } typedef const int my_custom_type_t; typedef const int cmy_custom_type_t; """) - assert cppyy.gbl.Cppyy.ResolveName("my_custom_type_t") == "const int" - assert cppyy.gbl.Cppyy.ResolveName("cmy_custom_type_t") == "const int" + assert cppjit.gbl.cppjit.interop.ResolveName("my_custom_type_t") == "const int" + assert cppjit.gbl.cppjit.interop.ResolveName("cmy_custom_type_t") == "const int" - @mark.xfail(run=False, condition=IS_MAC_ARM, reason="Crashes with exception not being caught on Apple Silicon") + @mark.xfail( + run=False, + condition=IS_MAC_ARM, + reason="Crashes with exception not being caught on Apple Silicon", + ) def test46_exception_narrowing(self): """Exception narrowing to C++ exception of all overloads""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace OverloadThrows { class Foo { public: @@ -1399,18 +1456,18 @@ class Foo { void bar() const { throw std::logic_error("This is fine"); } }; }""") - ns = cppyy.gbl.OverloadThrows + ns = cppjit.gbl.OverloadThrows foo = ns.Foo() - with raises(cppyy.gbl.std.logic_error): + with raises(cppjit.gbl.std.logic_error): foo.bar() - + def test47_initializer_list_fail(self, capfd): """Conversion to intializer_list requires default constructor""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace regression_test47 { std::size_t size = 0; struct IntWrapper { IntWrapper(int i) : fInt(i) {} int fInt; }; @@ -1418,7 +1475,7 @@ def test47_initializer_list_fail(self, capfd): void f(std::vector l) { size = l.size(); } }""") - r47 = cppyy.gbl.regression_test47 + r47 = cppjit.gbl.regression_test47 assert r47.size == 0 capfd.readouterr() # discard any output produced so far @@ -1432,10 +1489,10 @@ def test47_initializer_list_fail(self, capfd): def test48_templated_using_with_const(self): """Test parsing of template arguments mixed with using clause and constants""" - import cppyy - from cppyy import gbl + import cppjit + from cppjit import gbl - cppyy.cppdef(""" + cppjit.cppdef(""" template struct NN { bool F = true; @@ -1458,9 +1515,9 @@ def test50_using_decl_base_this_offset(self): pointer, corrupting memory and crashing on destruction. """ - import cppyy + import cppjit - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace UsingDeclThisOffset { // Fat first base so that SecondBase lands at a non-zero offset in Derived. @@ -1494,7 +1551,7 @@ def test50_using_decl_base_this_offset(self): } """) - ns = cppyy.gbl.UsingDeclThisOffset + ns = cppjit.gbl.UsingDeclThisOffset # the bug only manifests when SecondBase is at a non-zero offset assert ns.secondbase_offset() != 0 @@ -1533,9 +1590,9 @@ def test51_nontype_enum_template_arg(self): wrapper ("failed to resolve function"). """ - import cppyy + import cppjit - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace NonTypeEnumTmpl { enum class EOp { Add = 0, Sub = 1, Mul = 2 }; @@ -1551,7 +1608,7 @@ def test51_nontype_enum_template_arg(self): // Factory returning a *raw* base pointer whose dynamic type carries // a non-type enum template argument. Auto-downcasting it back to - // Python makes cppyy resolve the actual type by name, i.e. the + // Python makes cppjit resolve the actual type by name, i.e. the // cast-form "Impl". Base* get(int which) { static Impl a; @@ -1568,13 +1625,13 @@ def test51_nontype_enum_template_arg(self): } """) - ns = cppyy.gbl.NonTypeEnumTmpl + ns = cppjit.gbl.NonTypeEnumTmpl # resolving the derived template type during the auto-downcast must not # crash and must yield the actual (derived) class... op = ns.get(0) assert op.code() == 0 - assert 'Impl= len(self.seqn): raise StopIteration + if self.i >= len(self.seqn): + raise StopIteration v = self.seqn[self.i] self.i += 1 return v - next = __next__ # p2.7 + + next = __next__ # p2.7 + class IterGen: """Sequence using iterator protocol defined with a generator""" + def __init__(self, seqn): self.seqn = seqn self.i = 0 + def __iter__(self): for val in self.seqn: yield val + class IterNextOnly: """Missing __getitem__ and __iter__""" + def __init__(self, seqn): self.seqn = seqn self.i = 0 + def __next__(self): - if self.i >= len(self.seqn): raise StopIteration + if self.i >= len(self.seqn): + raise StopIteration v = self.seqn[self.i] self.i += 1 return v - next = __next__ # p2.7 + + next = __next__ # p2.7 + class IterNoNext: """Iterator missing __next__()""" + def __init__(self, seqn): self.seqn = seqn self.i = 0 + def __iter__(self): return self + class IterGenExc: """Test propagation of exceptions""" + def __init__(self, seqn): self.seqn = seqn self.i = 0 + def __iter__(self): return self + def __next__(self): 3 // 0 - next = __next__ # p2.7 + + next = __next__ # p2.7 + class IterFuncStop: """Test immediate stop""" + def __init__(self, seqn): pass + def __iter__(self): return self + def __next__(self): raise StopIteration - next = __next__ # p2.7 -from itertools import chain + next = __next__ # p2.7 + + +from itertools import chain # noqa: E402 + + def itermulti(seqn): """Test multiple tiers of iterators""" - return chain(map(lambda x:x, iterfunc(IterGen(Sequence(seqn))))) + return chain(map(lambda x: x, iterfunc(IterGen(Sequence(seqn))))) + class LyingTuple(tuple): def __iter__(self): yield 1 + class LyingList(list): def __iter__(self): yield 1 + def constructors_cpython_test(type2test): l0 = [] l1 = [0] @@ -116,39 +169,44 @@ def constructors_cpython_test(type2test): uu2 = type2test(u2) v = type2test(tuple(u)) + class OtherSeq: def __init__(self, initseq): self.__data = initseq + def __len__(self): return len(self.__data) + def __getitem__(self, i): return self.__data[i] + s = OtherSeq(u0) v0 = type2test(s) assert len(v0) == len(s) # the following does not work for type-checked containers - #s = "this is also a sequence" - #vv = type2test(s) - #assert len(vv) == len(s) + # s = "this is also a sequence" + # vv = type2test(s) + # assert len(vv) == len(s) - # Create from various iteratables + # Create from various iteratables # as above, can not put strings in type-checked containers - #for s in ("123", "", range(1000), ('do', 1.2), range(2000,2200,5)): - for s in (range(1000), range(2000,2200,5)): - for g in (Sequence, IterFunc, IterGen, - itermulti, iterfunc): + # for s in ("123", "", range(1000), ('do', 1.2), range(2000,2200,5)): + for s in (range(1000), range(2000, 2200, 5)): + for g in (Sequence, IterFunc, IterGen, itermulti, iterfunc): assert type2test(g(s)) == type2test(s) - assert type2test(IterFuncStop(s)) == type2test() + assert type2test(IterFuncStop(s)) == type2test() # as above, no strings - #assert type2test(c for c in "123") == type2test("123") + # assert type2test(c for c in "123") == type2test("123") raises(TypeError, type2test, IterNextOnly(s)) raises(TypeError, type2test, IterNoNext(s)) raises(ZeroDivisionError, type2test, IterGenExc(s)) - # Issue #23757 (in CPython) - #assert type2test(LyingTuple((2,))) == type2test((1,)) - #assert type2test(LyingList([2])) == type2test([1]) + +# Issue #23757 (in CPython) +# assert type2test(LyingTuple((2,))) == type2test((1,)) +# assert type2test(LyingList([2])) == type2test([1]) + def getslice_cpython_test(type2test): """Detailed slicing tests from CPython""" @@ -156,72 +214,73 @@ def getslice_cpython_test(type2test): l = [0, 1, 2, 3, 4] u = type2test(l) - assert u[0:0] == type2test() - assert u[1:2] == type2test([1]) - assert u[-2:-1] == type2test([3]) + assert u[0:0] == type2test() + assert u[1:2] == type2test([1]) + assert u[-2:-1] == type2test([3]) assert u[-1000:1000] == u assert u[1000:-1000] == type2test([]) - assert u[:] == u - assert u[1:None] == type2test([1, 2, 3, 4]) - assert u[None:3] == type2test([0, 1, 2]) - - # Extended slices - assert u[::] == u - assert u[::2] == type2test([0, 2, 4]) - assert u[1::2] == type2test([1, 3]) - assert u[::-1] == type2test([4, 3, 2, 1, 0]) - assert u[::-2] == type2test([4, 2, 0]) - assert u[3::-2] == type2test([3, 1]) - assert u[3:3:-2] == type2test([]) - assert u[3:2:-2] == type2test([3]) - assert u[3:1:-2] == type2test([3]) - assert u[3:0:-2] == type2test([3, 1]) - assert u[::-100] == type2test([4]) - assert u[100:-100:] == type2test([]) - assert u[-100:100:] == u + assert u[:] == u + assert u[1:None] == type2test([1, 2, 3, 4]) + assert u[None:3] == type2test([0, 1, 2]) + + # Extended slices + assert u[::] == u + assert u[::2] == type2test([0, 2, 4]) + assert u[1::2] == type2test([1, 3]) + assert u[::-1] == type2test([4, 3, 2, 1, 0]) + assert u[::-2] == type2test([4, 2, 0]) + assert u[3::-2] == type2test([3, 1]) + assert u[3:3:-2] == type2test([]) + assert u[3:2:-2] == type2test([3]) + assert u[3:1:-2] == type2test([3]) + assert u[3:0:-2] == type2test([3, 1]) + assert u[::-100] == type2test([4]) + assert u[100:-100:] == type2test([]) + assert u[-100:100:] == u assert u[100:-100:-1] == u[::-1] assert u[-100:100:-1] == type2test([]) - assert u[-pylong(100):pylong(100):pylong(2)] == type2test([0, 2, 4]) + assert u[-pylong(100) : pylong(100) : pylong(2)] == type2test([0, 2, 4]) - # Test extreme cases with long ints - a = type2test([0,1,2,3,4]) + # Test extreme cases with long ints + a = type2test([0, 1, 2, 3, 4]) # the following two fail b/c PySlice_GetIndices succeeds w/o error, while # returning an overflown value (list object uses different internal APIs) - #assert a[ -pow(2,128): 3 ] == type2test([0,1,2]) - #assert a[ 3: pow(2,145) ] == type2test([3,4]) - assert a[3::maxvalue] == type2test([3]) + # assert a[ -pow(2,128): 3 ] == type2test([0,1,2]) + # assert a[ 3: pow(2,145) ] == type2test([3,4]) + assert a[3::maxvalue] == type2test([3]) class TestSTLVECTOR: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) - cls.N = cppyy.gbl.N + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) + cls.N = cppjit.gbl.N def test01_builtin_type_vector_types(self): """Test access to std::vector/std::vector""" - import cppyy + import cppjit - assert cppyy.gbl.std is cppyy.gbl.std - assert cppyy.gbl.std.vector is cppyy.gbl.std.vector + assert cppjit.gbl.std is cppjit.gbl.std + assert cppjit.gbl.std.vector is cppjit.gbl.std.vector - assert callable(cppyy.gbl.std.vector) + assert callable(cppjit.gbl.std.vector) type_info = ( - ("int", int), - ("float", "float"), - ("double", "double"), + ("int", int), + ("float", "float"), + ("double", "double"), ) for c_type, p_type in type_info: - tv1 = getattr(cppyy.gbl.std, 'vector<%s>' % c_type) - tv2 = cppyy.gbl.std.vector(p_type) + tv1 = getattr(cppjit.gbl.std, "vector<%s>" % c_type) + tv2 = cppjit.gbl.std.vector(p_type) assert tv1 is tv2 - assert tv1.iterator is cppyy.gbl.std.vector(p_type).iterator + assert tv1.iterator is cppjit.gbl.std.vector(p_type).iterator - #----- + # ----- v = tv1() assert not v v += range(self.N) @@ -233,7 +292,7 @@ def test01_builtin_type_vector_types(self): assert v.begin() != v.end() assert v.end() != v.begin() - #----- + # ----- for i in range(self.N): v[i] = i assert v[i] == i @@ -243,11 +302,11 @@ def test01_builtin_type_vector_types(self): assert len(v) == self.N assert len(v.data()) == self.N - #----- + # ----- v = tv1() for i in range(self.N): v.push_back(i) - assert v.size() == i+1 + assert v.size() == i + 1 assert v.at(i) == i assert v[i] == i @@ -258,29 +317,29 @@ def test01_builtin_type_vector_types(self): def test02_user_type_vector_type(self): """Test access to an std::vector""" - import cppyy + import cppjit - assert cppyy.gbl.std is cppyy.gbl.std - assert cppyy.gbl.std.vector is cppyy.gbl.std.vector + assert cppjit.gbl.std is cppjit.gbl.std + assert cppjit.gbl.std.vector is cppjit.gbl.std.vector - assert callable(cppyy.gbl.std.vector) + assert callable(cppjit.gbl.std.vector) - tv1 = getattr(cppyy.gbl.std, 'vector') - tv2 = cppyy.gbl.std.vector('just_a_class') - tv3 = cppyy.gbl.std.vector(cppyy.gbl.just_a_class) + tv1 = getattr(cppjit.gbl.std, "vector") + tv2 = cppjit.gbl.std.vector("just_a_class") + tv3 = cppjit.gbl.std.vector(cppjit.gbl.just_a_class) assert tv1 is tv2 assert tv2 is tv3 v = tv3() - assert hasattr(v, 'size') - assert hasattr(v, 'push_back') - assert hasattr(v, '__getitem__') - assert hasattr(v, 'begin') - assert hasattr(v, 'end') + assert hasattr(v, "size") + assert hasattr(v, "push_back") + assert hasattr(v, "__getitem__") + assert hasattr(v, "begin") + assert hasattr(v, "end") for i in range(self.N): - v.push_back(cppyy.gbl.just_a_class()) + v.push_back(cppjit.gbl.just_a_class()) v[i].m_i = i assert v[i].m_i == i @@ -290,27 +349,27 @@ def test02_user_type_vector_type(self): def test03_empty_vector_type(self): """Test behavior of empty std::vector<>""" - import cppyy + import cppjit - v = cppyy.gbl.std.vector(int)() + v = cppjit.gbl.std.vector(int)() assert not v for arg in v: pass v.__destruct__() - for x in cppyy.gbl.std.vector["std::string*"](): + for x in cppjit.gbl.std.vector["std::string*"](): pass def test04_vector_iteration(self): """Test iteration over an std::vector""" - import cppyy + import cppjit - v = cppyy.gbl.std.vector(int)() + v = cppjit.gbl.std.vector(int)() for i in range(self.N): v.push_back(i) - assert v.size() == i+1 + assert v.size() == i + 1 assert v.at(i) == i assert v[i] == i @@ -329,9 +388,9 @@ def test04_vector_iteration(self): def test05_push_back_iterables_with_iadd(self): """Test usage of += of iterable on push_back-able container""" - import cppyy + import cppjit - v = cppyy.gbl.std.vector(int)() + v = cppjit.gbl.std.vector(int)() v += [1, 2, 3] assert len(v) == 3 @@ -345,10 +404,10 @@ def test05_push_back_iterables_with_iadd(self): assert v[4] == 5 assert v[5] == 6 - raises(TypeError, v.__iadd__, (7, '8')) # string shouldn't pass - assert len(v) == 7 # TODO: decide whether this should roll-back + raises(TypeError, v.__iadd__, (7, "8")) # string shouldn't pass + assert len(v) == 7 # TODO: decide whether this should roll-back - v2 = cppyy.gbl.std.vector(int)() + v2 = cppjit.gbl.std.vector(int)() v2 += [8, 9] assert len(v2) == 2 assert v2[0] == 8 @@ -367,9 +426,9 @@ def test05_push_back_iterables_with_iadd(self): def test06_vector_indexing(self): """Test python-style indexing to an std::vector""" - import cppyy + import cppjit - v = cppyy.gbl.std.vector(int)() + v = cppjit.gbl.std.vector(int)() for i in range(self.N): v.push_back(i) @@ -377,79 +436,82 @@ def test06_vector_indexing(self): with raises(IndexError): v[self.N] with raises(IndexError): - v[self.N+1] + v[self.N + 1] - assert v[-1] == self.N-1 - assert v[-2] == self.N-2 + assert v[-1] == self.N - 1 + assert v[-2] == self.N - 2 assert len(v[0:0]) == 0 assert v[1:2][0] == v[1] v2 = v[2:-1] - assert len(v2) == self.N-3 # 2 off from start, 1 from end + assert len(v2) == self.N - 3 # 2 off from start, 1 from end assert v2[0] == v[2] assert v2[-1] == v[-2] - assert v2[self.N-4] == v[-2] + assert v2[self.N - 4] == v[-2] - @mark.xfail(run=False, condition=(IS_MAC and IS_CLING), reason="Crashes on OSX Cling") + @mark.xfail( + run=False, condition=(IS_MAC and IS_CLING), reason="Crashes on OSX Cling" + ) def test07_vector_bool(self): """Usability of std::vector which can be a specialization""" - import cppyy + import cppjit - vb = cppyy.gbl.std.vector(bool)(8) - assert [x for x in vb] == [False]*8 + vb = cppjit.gbl.std.vector(bool)(8) + assert [x for x in vb] == [False] * 8 vb[0] = True assert vb[0] vb[-1] = True assert vb[7] - assert [x for x in vb] == [True]+[False]*6+[True] + assert [x for x in vb] == [True] + [False] * 6 + [True] assert len(vb[4:8]) == 4 - assert list(vb[4:8]) == [False]*3+[True] + assert list(vb[4:8]) == [False] * 3 + [True] @mark.xfail(run=False, condition=IS_MAC and IS_CLING, reason="Crashes on OSX-Cling") def test08_vector_enum(self): """Usability of std::vector<> of some enums""" - import cppyy + import cppjit - assert cppyy.gbl.VecTestEnum - for tp in ['VecTestEnum', cppyy.gbl.VecTestEnum]: - ve = cppyy.gbl.std.vector[tp]() - ve.push_back(cppyy.gbl.EVal1); + assert cppjit.gbl.VecTestEnum + for tp in ["VecTestEnum", cppjit.gbl.VecTestEnum]: + ve = cppjit.gbl.std.vector[tp]() + ve.push_back(cppjit.gbl.EVal1) assert ve[0] == 1 - ve[0] = cppyy.gbl.EVal2 + ve[0] = cppjit.gbl.EVal2 assert ve[0] == 3 - assert cppyy.gbl.VecTestEnumNS.VecTestEnum - for tp in ['VecTestEnumNS::VecTestEnum', cppyy.gbl.VecTestEnumNS.VecTestEnum]: - ve = cppyy.gbl.std.vector['VecTestEnumNS::VecTestEnum']() - ve.push_back(cppyy.gbl.VecTestEnumNS.EVal1); + assert cppjit.gbl.VecTestEnumNS.VecTestEnum + for tp in ["VecTestEnumNS::VecTestEnum", cppjit.gbl.VecTestEnumNS.VecTestEnum]: + ve = cppjit.gbl.std.vector["VecTestEnumNS::VecTestEnum"]() + ve.push_back(cppjit.gbl.VecTestEnumNS.EVal1) assert ve[0] == 5 - ve[0] = cppyy.gbl.VecTestEnumNS.EVal2 + ve[0] = cppjit.gbl.VecTestEnumNS.EVal2 assert ve[0] == 42 - @mark.xfail(run=not (IS_MAC_ARM or IS_MAC_X86), condition=IS_MAC, reason="Fails on OS X") + @mark.xfail( + run=not (IS_MAC_ARM or IS_MAC_X86), condition=IS_MAC, reason="Fails on OS X" + ) def test09_vector_of_string(self): """Adverse effect of implicit conversion on vector""" - import cppyy + import cppjit - assert cppyy.gbl.vectest_ol1("") == 2 - assert cppyy.gbl.vectest_ol1("a") == 2 - assert cppyy.gbl.vectest_ol2("") == 2 - assert cppyy.gbl.vectest_ol2("a") == 2 + assert cppjit.gbl.vectest_ol1("") == 2 + assert cppjit.gbl.vectest_ol1("a") == 2 + assert cppjit.gbl.vectest_ol2("") == 2 + assert cppjit.gbl.vectest_ol2("a") == 2 - raises(TypeError, cppyy.gbl.std.vector["std::string"], "abc") + raises(TypeError, cppjit.gbl.std.vector["std::string"], "abc") def test10_vector_std_distance(self): """Use of std::distance with vector""" - import cppyy - from cppyy.gbl import std + from cppjit.gbl import std v = std.vector[int]([1, 2, 3]) assert v.size() == 3 @@ -459,10 +521,10 @@ def test10_vector_std_distance(self): def test11_vector_of_pair(self): """Use of std::vector""" - import cppyy + import cppjit - # after the original bug report - cppyy.cppdef(""" + # after the original bug report + cppjit.cppdef(""" class PairVector { public: std::vector> vector_pair(const std::vector>& a) { @@ -471,40 +533,41 @@ class PairVector { }; """) - from cppyy.gbl import PairVector + from cppjit.gbl import PairVector + a = PairVector() - ll = [[1., 2.], [2., 3.], [3., 4.], [4., 5.]] + ll = [[1.0, 2.0], [2.0, 3.0], [3.0, 4.0], [4.0, 5.0]] v = a.vector_pair(ll) assert len(v) == 4 i = 0 for p in v: - p.first == ll[i][0] + p.first == ll[i][0] p.second == ll[i][1] i += 1 assert i == 4 - # TODO: nicer error handling for the following (current: template compilation failure trying - # to assign a pair with to ) + # TODO: nicer error handling for the following (current: template compilation failure trying + # to assign a pair with to ) # ll2 = ll[:] # ll2[2] = ll[2][:] # ll2[2][1] = 'a' # v = a.vector_pair(ll2) ll3 = ll[:] - ll3[0] = 'a' + ll3[0] = "a" raises(TypeError, a.vector_pair, ll3) ll4 = ll[:] - ll4[1] = 'a' + ll4[1] = "a" raises(TypeError, a.vector_pair, ll4) def test12_vector_lifeline(self): """Check lifeline setting on vectors of objects""" - import cppyy + import cppjit - cppyy.cppdef("""namespace Lifeline { + cppjit.cppdef("""namespace Lifeline { static int count = 0; template struct C { @@ -517,27 +580,32 @@ def test12_vector_lifeline(self): auto bar() { return std::vector{1024, "hello"}; } }""") - assert cppyy.gbl.Lifeline.count == 0 - assert not cppyy.gbl.Lifeline.foo()._getitem__unchecked.__set_lifeline__ - assert cppyy.gbl.Lifeline.foo()[0].x == 1337 - raises(IndexError, cppyy.gbl.Lifeline.foo().__getitem__, 1) - assert cppyy.gbl.Lifeline.foo()._getitem__unchecked.__set_lifeline__ + assert cppjit.gbl.Lifeline.count == 0 + assert not cppjit.gbl.Lifeline.foo()._getitem__unchecked.__set_lifeline__ + assert cppjit.gbl.Lifeline.foo()[0].x == 1337 + raises(IndexError, cppjit.gbl.Lifeline.foo().__getitem__, 1) + assert cppjit.gbl.Lifeline.foo()._getitem__unchecked.__set_lifeline__ import gc + gc.collect() - assert cppyy.gbl.Lifeline.count == 0 + assert cppjit.gbl.Lifeline.count == 0 - l = list(cppyy.gbl.Lifeline.bar()) + l = list(cppjit.gbl.Lifeline.bar()) for val in l: - assert hasattr(val, '__lifeline') + assert hasattr(val, "__lifeline") - @mark.xfail(run=False, condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLANG_REPL, reason="Fails with Valgrind with Clang-Repl ARM") + @mark.xfail( + run=False, + condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLANG_REPL, + reason="Fails with Valgrind with Clang-Repl ARM", + ) def test13_vector_smartptr_iteration(self): """Iteration over smart pointers""" - import cppyy + import cppjit - cppyy.cppdef("""namespace VectorOfShared { + cppjit.cppdef("""namespace VectorOfShared { struct X { int y; X() : y(0) {} @@ -551,9 +619,9 @@ def test13_vector_smartptr_iteration(self): } }; }""") - test = cppyy.gbl.VectorOfShared.X() + test = cppjit.gbl.VectorOfShared.X() result = test.gimeVec() - assert 'shared' in type(result).__cpp_name__ + assert "shared" in type(result).__cpp_name__ assert len(result) == 10 for i in range(len(result)): @@ -565,11 +633,15 @@ def test13_vector_smartptr_iteration(self): i += 1 assert i == len(result) - @mark.xfail(run=not(IS_MAC and IS_CLING), condition=(IS_MAC and IS_CLING), reason="Fails on OSX-Cling") + @mark.xfail( + run=not (IS_MAC and IS_CLING), + condition=(IS_MAC and IS_CLING), + reason="Fails on OSX-Cling", + ) def test14_vector_of_vector_of_(self): """Nested vectors""" - from cppyy.gbl.std import vector + from cppjit.gbl.std import vector vv = vector[vector[int]](((1, 2), [3, 4])) @@ -584,57 +656,57 @@ def test14_vector_of_vector_of_(self): def test15_vector_slicing(self): """Advanced test of vector slicing""" - from cppyy.gbl.std import vector + from cppjit.gbl.std import vector l = list(range(10)) v = vector[int](range(10)) - assert list(v[2:2]) == l[2:2] + assert list(v[2:2]) == l[2:2] assert list(v[2:2:-1]) == l[2:2:-1] - assert list(v[2:5]) == l[2:5] - assert list(v[5:2]) == l[5:2] + assert list(v[2:5]) == l[2:5] + assert list(v[5:2]) == l[5:2] assert list(v[2:5:-1]) == l[2:5:-1] assert list(v[5:2:-1]) == l[5:2:-1] - assert list(v[2:5: 2]) == l[2:5: 2] - assert list(v[5:2: 2]) == l[5:2: 2] + assert list(v[2:5:2]) == l[2:5:2] + assert list(v[5:2:2]) == l[5:2:2] assert list(v[2:5:-2]) == l[2:5:-2] assert list(v[5:2:-2]) == l[5:2:-2] - assert list(v[2:5: 7]) == l[2:5: 7] - assert list(v[5:2: 7]) == l[5:2: 7] + assert list(v[2:5:7]) == l[2:5:7] + assert list(v[5:2:7]) == l[5:2:7] assert list(v[2:5:-7]) == l[2:5:-7] assert list(v[5:2:-7]) == l[5:2:-7] - # additional test from CPython's test suite + # additional test from CPython's test suite getslice_cpython_test(vector[int]) def test16_vector_construction(self): """Vector construction following CPython's sequence""" - import cppyy + import cppjit - constructors_cpython_test(cppyy.gbl.std.vector[int]) + constructors_cpython_test(cppjit.gbl.std.vector[int]) def test17_vector_cpp17_style(self): """C++17 style initialization of std::vector""" - import cppyy + import cppjit l = [1.0, 2.0, 3.0] - v = cppyy.gbl.std.vector(l) + v = cppjit.gbl.std.vector(l) assert list(l) == l @mark.xfail(condition=IS_MAC and IS_CLING, run=False, reason="Crashes on OSX-Cling") def test18_array_interface(self): """Test usage of __array__ from numpy""" - import cppyy + import cppjit try: import numpy as np except ImportError: - skip('numpy is not installed') + skip("numpy is not installed") - a = cppyy.gbl.std.vector[int]((1, 2, 3)) + a = cppjit.gbl.std.vector[int]((1, 2, 3)) b = np.array(a) assert len(a) == len(b) @@ -652,13 +724,13 @@ def test18_array_interface(self): a[0] = 5 assert b[0] == 5 - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ImplicitVector { int func(std::vector v) { return std::accumulate(v.begin(), v.end(), 0); } }""") - ns = cppyy.gbl.ImplicitVector + ns = cppjit.gbl.ImplicitVector v = np.array(range(10), dtype=np.intc) assert ns.func(v) == sum(v) @@ -670,15 +742,17 @@ def test18_array_interface(self): v = np.array(v, dtype=np.intc) assert ns.func(v) == sum(v) - @mark.xfail(condition=IS_MAC and IS_CLING, run=False, reason="Crashes with OSX-Cling") + @mark.xfail( + condition=IS_MAC and IS_CLING, run=False, reason="Crashes with OSX-Cling" + ) def test19_vector_point3d(self): """Iteration over a vector of by-value objects""" - import cppyy + import cppjit N = 10 - cppyy.cppdef("""namespace vector_point3d { + cppjit.cppdef("""namespace vector_point3d { class Point3D { double x, y, z; @@ -687,16 +761,16 @@ class Point3D { double square() { return x*x+y*y+z*z; } }; }""") - Point3D = cppyy.gbl.vector_point3d.Point3D - v = cppyy.gbl.std.vector[Point3D]() + Point3D = cppjit.gbl.vector_point3d.Point3D + v = cppjit.gbl.std.vector[Point3D]() for i in range(N): - v.emplace_back(i, i*2, i*3) + v.emplace_back(i, i * 2, i * 3) - pysum = 0. + pysum = 0.0 for x in range(N): - pysum += 14*x**2 + pysum += 14 * x**2 - cppsum = 0. + cppsum = 0.0 for p in v: cppsum += p.square() @@ -706,14 +780,14 @@ class Point3D { def test20_vector_cstring(self): """Usage of a vector of const char*""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace VectorConstCharStar { std::vector test = {"hello"}; }""") - ns = cppyy.gbl.VectorConstCharStar + ns = cppjit.gbl.VectorConstCharStar assert len(ns.test) == 1 assert ns.test[0] == "hello" @@ -727,10 +801,10 @@ def test20_vector_cstring(self): def test21_vector_of_structs_data(self): """Vector of structs data() should return array-like""" - import cppyy - import cppyy.ll + import cppjit + import cppjit.ll - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ArrayLike { struct __attribute__((__packed__)) Vector3f { float x, y, z; @@ -738,33 +812,33 @@ def test21_vector_of_structs_data(self): N = 5 - v = cppyy.gbl.std.vector['ArrayLike::Vector3f'](N) + v = cppjit.gbl.std.vector["ArrayLike::Vector3f"](N) for i in range(N): d = v[i] - d.x, d.y, d.z = i, i*N, i*N**2 + d.x, d.y, d.z = i, i * N, i * N**2 data = v.data() for i in range(N): d = data[i] assert d.x == float(i) - assert d.y == float(i*N) - assert d.z == float(i*N**2) + assert d.y == float(i * N) + assert d.z == float(i * N**2) - # the following should not raise - mv = cppyy.ll.as_memoryview(data) + # the following should not raise + mv = cppjit.ll.as_memoryview(data) - # length of the view is in bytes + # length of the view is in bytes assert len(mv) == len(v) - assert mv.itemsize == cppyy.sizeof(cppyy.gbl.ArrayLike.Vector3f) - assert mv.nbytes == cppyy.sizeof(cppyy.gbl.ArrayLike.Vector3f) * len(v) + assert mv.itemsize == cppjit.sizeof(cppjit.gbl.ArrayLike.Vector3f) + assert mv.nbytes == cppjit.sizeof(cppjit.gbl.ArrayLike.Vector3f) * len(v) def test22_polymorphic(self): """Vector of polymorphic types should auto-cast""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace Polymorphic { class vertex { public: @@ -780,7 +854,7 @@ class vCont { const std::vector& vertices() { return verts; } }; }""") - ns = cppyy.gbl.Polymorphic + ns = cppjit.gbl.Polymorphic cont = ns.vCont() verts = cont.vertices() @@ -789,15 +863,15 @@ class vCont { def test23_copy_conversion(self): """Vector given an array of different type should copy convert""" - import cppyy + import cppjit try: import numpy as np except ImportError: - skip('numpy is not installed') + skip("numpy is not installed") - x = np.array([5., 25., 125.]) - v = cppyy.gbl.std.vector('float')(x) + x = np.array([5.0, 25.0, 125.0]) + v = cppjit.gbl.std.vector("float")(x) for f, d in zip(x, v): assert f == d @@ -806,26 +880,26 @@ def test23_copy_conversion(self): def test24_byte_vectors(self): """Vectors of "byte" types should return low level views""" - import cppyy - import cppyy.types + import cppjit + import cppjit.types - vector = cppyy.gbl.std.vector + vector = cppjit.gbl.std.vector - for ctype in ('unsigned char', 'signed char', 'int8_t', 'uint8_t'): + for ctype in ("unsigned char", "signed char", "int8_t", "uint8_t"): vc = vector[ctype](range(10)) data = vc.data() - assert type(data) == cppyy.types.LowLevelView + assert type(data) == cppjit.types.LowLevelView assert len(data) == 10 for i, d in enumerate(data): assert d == i - for ctype in ('signed char', 'int8_t'): + for ctype in ("signed char", "int8_t"): vc = vector[ctype](range(-5, 5, 1)) data = vc.data() - assert type(data) == cppyy.types.LowLevelView + assert type(data) == cppjit.types.LowLevelView assert len(data) == 10 for i, d in zip(range(-5, 5, 1), data): @@ -835,18 +909,20 @@ def test24_byte_vectors(self): class TestSTLSTRING: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test01_string_argument_passing(self): """Test mapping of python strings and std::[w]string""" - import cppyy - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std for stp, pystp in [(std.string, str), (std.wstring, pyunicode)]: - stringy_class = cppyy.gbl.stringy_class[stp] + stringy_class = cppjit.gbl.stringy_class[stp] c, s = stringy_class(pystp("")), stp(pystp("test1")) @@ -876,11 +952,12 @@ def test01_string_argument_passing(self): def test02_string_data_access(self): """Test access to std::string object data members""" - import cppyy - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std for stp, pystp in [(std.string, str), (std.wstring, pyunicode)]: - stringy_class = cppyy.gbl.stringy_class[stp] + stringy_class = cppjit.gbl.stringy_class[stp] c, s = stringy_class(pystp("dummy")), stp(pystp("test string")) @@ -898,9 +975,10 @@ def test02_string_data_access(self): def test03_string_with_null_character(self): """Test that strings with NULL do not get truncated""" - import cppyy - std = cppyy.gbl.std - stringy_class = cppyy.gbl.stringy_class["std::string"] + import cppjit + + std = cppjit.gbl.std + stringy_class = cppjit.gbl.stringy_class["std::string"] t0 = "aap\0noot" assert t0 == "aap\0noot" @@ -911,162 +989,180 @@ def test03_string_with_null_character(self): assert t0 == c.get_string1() assert s == c.get_string1() - assert std.string('ab\0c') == 'ab\0c' - assert repr(std.string('ab\0c')) == repr(b'ab\0c') - assert str(std.string('ab\0c')) == str('ab\0c') + assert std.string("ab\0c") == "ab\0c" + assert repr(std.string("ab\0c")) == repr(b"ab\0c") + assert str(std.string("ab\0c")) == str("ab\0c") @mark.xfail(condition=IS_MAC, run=False, reason="Fails on OS X") def test04_array_of_strings(self): """Access to global arrays of strings""" - import cppyy + import cppjit - assert tuple(cppyy.gbl.str_array_1) == ('a', 'b', 'c') - str_array_2 = cppyy.gbl.str_array_2 + assert tuple(cppjit.gbl.str_array_1) == ("a", "b", "c") + str_array_2 = cppjit.gbl.str_array_2 # fix up the size str_array_2.size = 4 - assert tuple(str_array_2) == ('d', 'e', 'f', 'g') - assert tuple(str_array_2) == ('d', 'e', 'f', 'g') + assert tuple(str_array_2) == ("d", "e", "f", "g") + assert tuple(str_array_2) == ("d", "e", "f", "g") # multi-dimensional - vals = ['a', 'b', 'c', 'd', 'e', 'f'] - str_array_3 = cppyy.gbl.str_array_3 + vals = ["a", "b", "c", "d", "e", "f"] + str_array_3 = cppjit.gbl.str_array_3 for i in range(3): for j in range(2): - assert str_array_3[i][j] == vals[i*2+j] - - vals = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'] - str_array_4 = cppyy.gbl.str_array_4 + assert str_array_3[i][j] == vals[i * 2 + j] + + vals = [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + ] + str_array_4 = cppjit.gbl.str_array_4 for i in range(4): for j in range(2): for k in range(2): - assert str_array_4[i][j][k] == vals[i*4+j*2+k] + assert str_array_4[i][j][k] == vals[i * 4 + j * 2 + k] @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test05_stlstring_and_unicode(self): """Mixing unicode and std::string""" - import cppyy + import cppjit - uas = cppyy.gbl.UnicodeAndSTL + uas = cppjit.gbl.UnicodeAndSTL - actlen = len(u'ℕ'.encode(encoding='UTF-8')) - assert uas.get_size('ℕ') == actlen - assert uas.get_size_cr('ℕ') == actlen - assert uas.get_size_cc('ℕ') == actlen + actlen = len("ℕ".encode(encoding="UTF-8")) + assert uas.get_size("ℕ") == actlen + assert uas.get_size_cr("ℕ") == actlen + assert uas.get_size_cc("ℕ") == actlen - assert uas.get_size_w('ℕ') == 1 - assert uas.get_size_wcr('ℕ') == 1 + assert uas.get_size_w("ℕ") == 1 + assert uas.get_size_wcr("ℕ") == 1 - assert str(uas.get_string('ℕ')) == 'ℕ' - assert str(uas.get_string_cr('ℕ')) == 'ℕ' - assert str(uas.get_string_cc('ℕ')) == 'ℕ' + assert str(uas.get_string("ℕ")) == "ℕ" + assert str(uas.get_string_cr("ℕ")) == "ℕ" + assert str(uas.get_string_cc("ℕ")) == "ℕ" if sys.hexversion >= 0x3000000: - assert uas.get_string_w('ℕ') == 'ℕ' - assert uas.get_string_wcr('ℕ') == 'ℕ' + assert uas.get_string_w("ℕ") == "ℕ" + assert uas.get_string_wcr("ℕ") == "ℕ" else: - assert uas.get_string_w('ℕ').encode(encoding='UTF-8') == 'ℕ' - assert uas.get_string_wcr('ℕ').encode(encoding='UTF-8') == 'ℕ' + assert uas.get_string_w("ℕ").encode(encoding="UTF-8") == "ℕ" + assert uas.get_string_wcr("ℕ").encode(encoding="UTF-8") == "ℕ" - bval = u'ℕ'.encode(encoding='UTF-8') + bval = "ℕ".encode(encoding="UTF-8") actlen = len(bval) - assert uas.get_size(bval) == actlen + assert uas.get_size(bval) == actlen assert uas.get_size_cr(bval) == actlen assert uas.get_size_cc(bval) == actlen - assert str(uas.get_string(bval)) == 'ℕ' - assert str(uas.get_string_cr(bval)) == 'ℕ' - assert str(uas.get_string_cc(bval)) == 'ℕ' + assert str(uas.get_string(bval)) == "ℕ" + assert str(uas.get_string_cr(bval)) == "ℕ" + assert str(uas.get_string_cc(bval)) == "ℕ" - @mark.xfail(run = not IS_CLING, condition=IS_MAC or IS_CLING, reason="Fails on OS X and Cling") + @mark.xfail( + run=not IS_CLING, condition=IS_MAC or IS_CLING, reason="Fails on OS X and Cling" + ) def test06_stlstring_bytes_and_text(self): """Mixing of bytes and str""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace PyBytesTest { std::string string_field = ""; }""") - ns = cppyy.gbl.PyBytesTest - assert type(ns.string_field) == cppyy.gbl.std.string + ns = cppjit.gbl.PyBytesTest + assert type(ns.string_field) == cppjit.gbl.std.string - ns.string_field = b'\xe9' - assert repr(ns.string_field) == repr(b'\xe9') - assert str(ns.string_field) == str(b'\xe9') # b/c fails to decode + ns.string_field = b"\xe9" + assert repr(ns.string_field) == repr(b"\xe9") + assert str(ns.string_field) == str(b"\xe9") # b/c fails to decode @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test07_stlstring_in_dictionaries(self): """Mixing str and std::string as dictionary keys""" - import cppyy + import cppjit - x = cppyy.gbl.std.string("x") - d = { x : 0 } + x = cppjit.gbl.std.string("x") + d = {x: 0} assert d[x] == 0 - assert d['x'] == 0 + assert d["x"] == 0 @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test08_string_operators(self): """Mixing of C++ and Python types in global operators""" - import cppyy + import cppjit - # note order in these checks: the first is str then unicode, the other is - # the reverse; this exercises both paths (once resolved, the operator+ can - # handle both str and unicde - s1 = cppyy.gbl.std.string("Hello") + # note order in these checks: the first is str then unicode, the other is + # the reverse; this exercises both paths (once resolved, the operator+ can + # handle both str and unicde + s1 = cppjit.gbl.std.string("Hello") s2 = ", World!" - assert s1+s2 == "Hello, World!" - assert s2+s1 == ", World!Hello" + assert s1 + s2 == "Hello, World!" + assert s2 + s1 == ", World!Hello" - s2 = u", World!" - assert s1+s2 == "Hello, World!" - assert s2+s1 == ", World!Hello" + s2 = ", World!" + assert s1 + s2 == "Hello, World!" + assert s2 + s1 == ", World!Hello" - s1 = cppyy.gbl.std.wstring("Hello") + s1 = cppjit.gbl.std.wstring("Hello") - assert s1+s2 == "Hello, World!" - assert s2+s1 == ", World!Hello" + assert s1 + s2 == "Hello, World!" + assert s2 + s1 == ", World!Hello" s2 = ", World!" - assert s1+s2 == "Hello, World!" - assert s2+s1 == ", World!Hello" + assert s1 + s2 == "Hello, World!" + assert s2 + s1 == ", World!Hello" @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test09_string_as_str_bytes(self): """Python-style methods of str/bytes on std::string""" - import cppyy + import cppjit - S = cppyy.gbl.std.string + S = cppjit.gbl.std.string - # check that object.method(*args) returns result + # check that object.method(*args) returns result def EQ(result, init, methodname, *args): assert getattr(S(init), methodname)(*args) == result - # npos plays a dual role: both C++ and Python type checking + # npos plays a dual role: both C++ and Python type checking assert S.npos == -1 - assert S.npos != 0 + assert S.npos != 0 assert S.npos == S.size_type(-1) - # -- method decode - s = S(u'\xe9') - assert s.decode('utf-8') == u'\xe9' - assert s.decode('utf-8', "strict") == u'\xe9' - assert s.decode(encoding='utf-8') == u'\xe9' + # -- method decode + s = S("\xe9") + assert s.decode("utf-8") == "\xe9" + assert s.decode("utf-8", "strict") == "\xe9" + assert s.decode(encoding="utf-8") == "\xe9" - # -- method split (only Python) - assert S("a b c").split() == ['a', 'b', 'c'] + # -- method split (only Python) + assert S("a b c").split() == ["a", "b", "c"] - # -- method replace (from Python's string tests) + # -- method replace (from Python's string tests) - # Operations on the empty string + # Operations on the empty string EQ("", "", "replace", "", "") EQ("A", "", "replace", "", "A") EQ("", "", "replace", "A", "") @@ -1074,7 +1170,7 @@ def EQ(result, init, methodname, *args): EQ("", "", "replace", "", "", 100) EQ("", "", "replace", "", "", sys.maxsize) - # interleave (from=="", 'to' gets inserted everywhere) + # interleave (from=="", 'to' gets inserted everywhere) EQ("A", "A", "replace", "", "") EQ("*A*", "A", "replace", "", "*") EQ("*1A*1", "A", "replace", "", "*1") @@ -1088,26 +1184,26 @@ def EQ(result, init, methodname, *args): EQ("*-AA", "AA", "replace", "", "*-", 1) EQ("AA", "AA", "replace", "", "*-", 0) - # -- methods find and rfind - s = S('aap') + # -- methods find and rfind + s = S("aap") - # Python style - assert s.find('a') == 0 - assert s.find('a') != s.npos - assert s.rfind('a') == 1 - assert s.rfind('a') != s.npos - assert s.find('c') < 0 - assert s.find('c') == s.npos - assert s.rfind('c') < 0 - assert s.rfind('c') == s.npos + # Python style + assert s.find("a") == 0 + assert s.find("a") != s.npos + assert s.rfind("a") == 1 + assert s.rfind("a") != s.npos + assert s.find("c") < 0 + assert s.find("c") == s.npos + assert s.rfind("c") < 0 + assert s.rfind("c") == s.npos - @mark.xfail(condition=IS_MAC, run = False, reason="Crashes on OS X") + @mark.xfail(condition=IS_MAC, run=False, reason="Crashes on OS X") def test10_string_in_repr_and_str_bytes(self): """Special cases for __str__/__repr__""" - import cppyy + import cppjit - cppyy.cppdef(r"""\ + cppjit.cppdef(r"""\ namespace ReprAndStr { struct Test1 { @@ -1126,44 +1222,45 @@ def test10_string_in_repr_and_str_bytes(self): }; }""") - ns = cppyy.gbl.ReprAndStr + ns = cppjit.gbl.ReprAndStr - assert str (ns.Test1()) == "Test1" + assert str(ns.Test1()) == "Test1" assert repr(ns.Test1()) == "Test1" - assert str (ns.Test2()) == "Test2" + assert str(ns.Test2()) == "Test2" assert repr(ns.Test2()) == "Test2" - assert str (ns.Test3()) == "Test3" + assert str(ns.Test3()) == "Test3" assert repr(ns.Test3()) == "Test3" class TestSTLLIST: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) cls.N = 13 def test01_builtin_list_type(self): """Test access to a list""" - import cppyy - from cppyy.gbl import std + import cppjit + from cppjit.gbl import std type_info = ( - ("int", int), - ("float", "float"), - ("double", "double"), + ("int", int), + ("float", "float"), + ("double", "double"), ) for c_type, p_type in type_info: - tl1 = getattr(std, 'list<%s>' % c_type) - tl2 = cppyy.gbl.std.list(p_type) + tl1 = getattr(std, "list<%s>" % c_type) + tl2 = cppjit.gbl.std.list(p_type) assert tl1 is tl2 - assert tl1.iterator is cppyy.gbl.std.list(p_type).iterator + assert tl1.iterator is cppjit.gbl.std.list(p_type).iterator - #----- + # ----- a = tl1() assert not a for i in range(self.N): @@ -1174,7 +1271,7 @@ def test01_builtin_list_type(self): assert 11 < self.N assert 11 in a - #----- + # ----- ll = list(a) for i in range(self.N): assert ll[i] == i @@ -1185,8 +1282,7 @@ def test01_builtin_list_type(self): def test02_empty_list_type(self): """Test behavior of empty list""" - import cppyy - from cppyy.gbl import std + from cppjit.gbl import std a = std.list(int)() assert not a @@ -1196,33 +1292,33 @@ def test02_empty_list_type(self): def test03_replacement_of_eq(self): """A global templated function should work as a method""" - import cppyy + import cppjit - cppyy.cppdef("""template + cppjit.cppdef("""template bool cont_eq(const typename C::iterator& a, const typename C::iterator& b) { return a != b; }""") - a = cppyy.gbl.std.list[int]() + a = cppjit.gbl.std.list[int]() icls = a.begin().__class__ oldeq = icls.__eq__ - icls.__eq__ = cppyy.gbl.cont_eq[cppyy.gbl.std.list[int]] + icls.__eq__ = cppjit.gbl.cont_eq[cppjit.gbl.std.list[int]] assert not (a.begin() == a.end()) - a = cppyy.gbl.std.list[float]() + a = cppjit.gbl.std.list[float]() assert a.begin() == a.end() - assert not cppyy.gbl.cont_eq[cppyy.gbl.std.list[float]](a.begin(), a.begin()) + assert not cppjit.gbl.cont_eq[cppjit.gbl.std.list[float]](a.begin(), a.begin()) a.push_back(1) - assert cppyy.gbl.cont_eq[cppyy.gbl.std.list[float]](a.begin(), a.end()) + assert cppjit.gbl.cont_eq[cppjit.gbl.std.list[float]](a.begin(), a.end()) icls.__eq__ = oldeq def test04_iter_of_iter(self): """Iteration using iter()""" - import cppyy + import cppjit - l = cppyy.gbl.std.list['int']((1, 2, 3)) + l = cppjit.gbl.std.list["int"]((1, 2, 3)) assert [x for x in l] == [1, 2, 3] i = 1 @@ -1234,40 +1330,42 @@ def test04_iter_of_iter(self): def test05_list_cpp17_style(self): """C++17 style initialization of std::list""" - import cppyy + import cppjit l = [1.0, 2.0, 3.0] - v = cppyy.gbl.std.list(l) + v = cppjit.gbl.std.list(l) assert list(l) == l @mark.xfail(condition=IS_MAC, reason="Fails with OSX") def test06_convert_list_of_strings(self): """Convert list of strings from C++ to Python types""" - import cppyy + import cppjit contents = ["aap", "noot", "mies"] - l = cppyy.gbl.std.list[str]() + l = cppjit.gbl.std.list[str]() l += contents - # the following used to fail on Windows (TODO: currently worked around in - # cppyy-backend/clingwrapper; need to see whether Clang9 solves the issue) + # the following used to fail on Windows (TODO: currently worked around in + # cppjit-backend/clingwrapper; need to see whether Clang9 solves the issue) assert [str(x) for x in l] == contents class TestSTLMAP: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) cls.N = 13 def test01_builtin_map_type(self): """Test access to a map""" - import cppyy - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std for mtype in (std.map, std.unordered_map): a = mtype(int, int)() @@ -1282,31 +1380,32 @@ def test01_builtin_map_type(self): assert key == value itercount += 1 assert itercount == len(a) - if mtype == std.map: # ordered - assert key == self.N-1 - assert value == self.N-1 + if mtype == std.map: # ordered + assert key == self.N - 1 + assert value == self.N - 1 # add a variation, just in case m = mtype(int, int)() for i in range(self.N): - m[i] = i*i - assert m[i] == i*i + m[i] = i * i + assert m[i] == i * i itercount = 0 for key, value in m: - assert key*key == value + assert key * key == value itercount += 1 assert itercount == len(m) - if mtype == std.map: # ordered - assert key == self.N-1 - assert value == (self.N-1)*(self.N-1) + if mtype == std.map: # ordered + assert key == self.N - 1 + assert value == (self.N - 1) * (self.N - 1) @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test02_keyed_maptype(self): """Test access to a map""" - import cppyy - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std for mtype in (std.map, std.unordered_map): a = mtype(std.string, int)() @@ -1326,8 +1425,9 @@ def test02_keyed_maptype(self): def test03_empty_maptype(self): """Test behavior of empty map""" - import cppyy - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std for mtype in (std.map, std.unordered_map): m = mtype(int, int)() @@ -1339,78 +1439,85 @@ def test03_empty_maptype(self): def test04_unsignedvalue_typemap_types(self): """Test assignability of maps with unsigned value types""" - import cppyy, math, sys - std = cppyy.gbl.std + import math + + import cppjit + + std = cppjit.gbl.std for mtype in (std.map, std.unordered_map): - mui = mtype(str, 'unsigned int')() - mui['one'] = 1 - assert mui['one'] == 1 - raises(ValueError, mui.__setitem__, 'minus one', -1) + mui = mtype(str, "unsigned int")() + mui["one"] = 1 + assert mui["one"] == 1 + raises(ValueError, mui.__setitem__, "minus one", -1) # UInt_t is always 32b, maxvalue is sys.maxint/maxsize and follows system int - maxint32 = int(math.pow(2,31)-1) - mui['maxint'] = maxint32 + 3 - assert mui['maxint'] == maxint32 + 3 + maxint32 = int(math.pow(2, 31) - 1) + mui["maxint"] = maxint32 + 3 + assert mui["maxint"] == maxint32 + 3 - mul = mtype(str, 'unsigned long')() - mul['two'] = 2 - assert mul['two'] == 2 - mul['maxint'] = maxvalue + 3 - assert mul['maxint'] == maxvalue + 3 + mul = mtype(str, "unsigned long")() + mul["two"] = 2 + assert mul["two"] == 2 + mul["maxint"] = maxvalue + 3 + assert mul["maxint"] == maxvalue + 3 - raises(ValueError, mul.__setitem__, 'minus two', -2) + raises(ValueError, mul.__setitem__, "minus two", -2) @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test05_STL_like_class_indexing_overloads(self): """Test overloading of operator[] in STL like class""" - import cppyy - stl_like_class = cppyy.gbl.stl_like_class + import cppjit + + stl_like_class = cppjit.gbl.stl_like_class a = stl_like_class(int)() - assert a["some string" ] == 'string' - assert a[3.1415] == 'double' + assert a["some string"] == "string" + assert a[3.1415] == "double" @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test06_initialize_from_dict(self): """Test std::map initializion from Python dict""" - import cppyy - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std for mtype in (std.map, std.unordered_map): - m = mtype[str, int]({'1' : 1, '2' : 2}) + m = mtype[str, int]({"1": 1, "2": 2}) - assert m['1'] == 1 - assert m['2'] == 2 + assert m["1"] == 1 + assert m["2"] == 2 with raises(TypeError): - m = mtype[int, str]({'1' : 1, '2' : 2}) + m = mtype[int, str]({"1": 1, "2": 2}) @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test07_map_cpp17_style(self): """C++17 style initialization of std::map""" if ispypy: - skip('emulated class crash') + skip("emulated class crash") + + import cppjit - import cppyy - std = cppyy.gbl.std + std = cppjit.gbl.std for mtype in (std.map, std.unordered_map): - m = mtype({'1': 2, '2':1}) - assert m['1'] == 2 - assert m['2'] == 1 + m = mtype({"1": 2, "2": 1}) + assert m["1"] == 2 + assert m["2"] == 1 @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test08_map_derived_objects(self): """Enter derived objects through an initializer list""" - import cppyy - std = cppyy.gbl.std + import cppjit - cppyy.cppdef("""\ + std = cppjit.gbl.std + + cppjit.cppdef("""\ namespace MapInitializer { class Base { public: @@ -1420,42 +1527,50 @@ class Base { class Derived : public Base { }; } """) - ns = cppyy.gbl.MapInitializer + ns = cppjit.gbl.MapInitializer for mtype in (std.map, std.unordered_map): - # dictionary style initializer; allow derived through assignment (this may slice - # but that is the choice of the program; in this case it's fine as both are the - # same size - m = mtype['std::string', ns.Base]({"aap": ns.Base(), "noot": ns.Base()}) + # dictionary style initializer; allow derived through assignment (this may slice + # but that is the choice of the program; in this case it's fine as both are the + # same size + m = mtype["std::string", ns.Base]({"aap": ns.Base(), "noot": ns.Base()}) assert len(m) == 2 - m = mtype['std::string', ns.Base]({"aap": ns.Derived(), "noot": ns.Derived()}) + m = mtype["std::string", ns.Base]( + {"aap": ns.Derived(), "noot": ns.Derived()} + ) assert len(m) == 2 - # similar but now initialize through the initializer_list of pairs style - m = mtype['std::string', ns.Base]((("aap", ns.Base()),)) + # similar but now initialize through the initializer_list of pairs style + m = mtype["std::string", ns.Base]((("aap", ns.Base()),)) assert len(m) == 1 - m = mtype['std::string', ns.Base]([("aap", ns.Base()),]) # list instead of tuple + m = mtype["std::string", ns.Base]( + [ + ("aap", ns.Base()), + ] + ) # list instead of tuple assert len(m) == 1 - m = mtype['std::string', ns.Base]((("aap", ns.Base()), ("noot", ns.Base()))) + m = mtype["std::string", ns.Base]((("aap", ns.Base()), ("noot", ns.Base()))) assert len(m) == 2 - m = mtype['std::string', ns.Base]((("aap", ns.Derived()), ("noot", ns.Derived()))) + m = mtype["std::string", ns.Base]( + (("aap", ns.Derived()), ("noot", ns.Derived())) + ) assert len(m) == 2 class TestSTLITERATOR: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) def test01_builtin_vector_iterators(self): """Test iterator comparison with operator== reflected""" - import cppyy - from cppyy.gbl import std + from cppjit.gbl import std v = std.vector(int)() v.resize(1) @@ -1481,19 +1596,20 @@ def test01_builtin_vector_iterators(self): def test02_STL_like_class_iterators(self): """Test the iterator protocol mapping for an STL like class""" - import cppyy + import cppjit - a = cppyy.gbl.stl_like_class(int)() + a = cppjit.gbl.stl_like_class(int)() assert len(a) == 4 for i, j in enumerate(a): assert i == j - assert i == len(a)-1 + assert i == len(a) - 1 - for cls in [cppyy.gbl.stl_like_class2, cppyy.gbl.stl_like_class3]: + for cls in [cppjit.gbl.stl_like_class2, cppjit.gbl.stl_like_class3]: b = cls[float, 2]() - b[0] = 27; b[1] = 42 - limit = len(b)+1 + b[0] = 27 + b[1] = 42 + limit = len(b) + 1 for x in b: limit -= 1 assert limit and "iterated too far!" @@ -1502,13 +1618,13 @@ def test02_STL_like_class_iterators(self): del x, b for num in [4, 5, 6, 7]: - cls = getattr(cppyy.gbl, 'stl_like_class%d' % num) + cls = getattr(cppjit.gbl, "stl_like_class%d" % num) count = 0 for i in cls(): count += 1 assert count == 10 - for cls in [cppyy.gbl.stl_like_class8, cppyy.gbl.stl_like_class9]: + for cls in [cppjit.gbl.stl_like_class8, cppjit.gbl.stl_like_class9]: b = cls() for i in [1, 2, 3]: b.push_back(i) @@ -1519,9 +1635,9 @@ def test02_STL_like_class_iterators(self): def test03_stllike_preinc(self): """STL-like class with preinc by-ref returns""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace PreIncrement { struct Token { int value; @@ -1544,7 +1660,7 @@ def test03_stllike_preinc(self): Iterator end() { return Iterator{10}; } }; }""") - ns = cppyy.gbl.PreIncrement + ns = cppjit.gbl.PreIncrement stream = ns.Stream() assert [x.value for x in stream] == list(range(10)) @@ -1558,9 +1674,9 @@ def test03_stllike_preinc(self): def test04_stllike_confusing_name(self): """Having "iterator" in the container name used to fail""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ConstainerOfIterators { template class iterator_range { @@ -1599,24 +1715,25 @@ class A { std::array fArray; }; }""") - ns = cppyy.gbl.ConstainerOfIterators + ns = cppjit.gbl.ConstainerOfIterators a = ns.A() m = a.members() assert [x for x in m] == [1, 2, 3, 4] + class TestSTLARRAY: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) def test01_array_of_basic_types(self): """Usage of std::array of basic types""" - import cppyy - from cppyy.gbl import std + from cppjit.gbl import std a = std.array[int, 4]() assert len(a) == 4 @@ -1627,9 +1744,8 @@ def test01_array_of_basic_types(self): def test02_array_of_pods(self): """Usage of std::array of PODs""" - import cppyy - from cppyy import gbl - from cppyy.gbl import std + from cppjit import gbl + from cppjit.gbl import std a = std.array[gbl.ArrayTest.Point, 4]() assert len(a) == 4 @@ -1655,93 +1771,99 @@ def test02_array_of_pods(self): def test03_array_of_pointer_to_pods(self): """Usage of std::array of pointer to PODs""" - import cppyy - from cppyy import gbl - from cppyy.gbl import std + from cppjit import gbl + from cppjit.gbl import std ll = [gbl.ArrayTest.Point() for i in range(4)] for i in range(len(ll)): - ll[i].px = 13*i - ll[i].py = 42*i + ll[i].px = 13 * i + ll[i].py = 42 * i - a = std.array['ArrayTest::Point*', 4]() + a = std.array["ArrayTest::Point*", 4]() assert len(a) == 4 if ispypy: raise RuntimeError("test fails with crash") for i in range(len(a)): a[i] = ll[i] - assert a[i].px == 13*i - assert a[i].py == 42*i + assert a[i].px == 13 * i + assert a[i].py == 42 * i raises(TypeError, a.__setitem__, 1, 42) for i in range(len(a)): - assert gbl.ArrayTest.get_pp_px(a.data(), i) == 13*i - assert gbl.ArrayTest.get_pp_py(a.data(), i) == 42*i + assert gbl.ArrayTest.get_pp_px(a.data(), i) == 13 * i + assert gbl.ArrayTest.get_pp_py(a.data(), i) == 42 * i - assert gbl.ArrayTest.get_pa_px(a.data(), i) == 13*i - assert gbl.ArrayTest.get_pa_py(a.data(), i) == 42*i + assert gbl.ArrayTest.get_pa_px(a.data(), i) == 13 * i + assert gbl.ArrayTest.get_pa_py(a.data(), i) == 42 * i def test04_array_from_aggregate(self): """Initialize an array from an aggregate contructor""" - import cppyy + import cppjit l = [1.0, 1.0, 1.0] - t = cppyy.gbl.std.array["double",3](l) + t = cppjit.gbl.std.array["double", 3](l) assert list(t) == l with raises(ValueError): - cppyy.gbl.std.array["double",3]([1.0, 1.0, 1.0, 1.0]) + cppjit.gbl.std.array["double", 3]([1.0, 1.0, 1.0, 1.0]) with raises(TypeError): - cppyy.gbl.std.array["double",3](['a', 1.0, 1.0]) + cppjit.gbl.std.array["double", 3](["a", 1.0, 1.0]) def test05_array_of_chrono_types_should_be_iterable(self): - import cppyy - cppyy.cppdef(""" + import cppjit + + cppjit.cppdef(""" #include using namespace std::chrono_literals; std::vector vtimes = {10ns, 500ns, 1us}; std::array atimes = {10ns, 500ns, 1us}; """) - assert sum([v.count() for v in cppyy.gbl.vtimes]) == 1510 - assert sum([v.count() for v in cppyy.gbl.atimes]) == 1510 + assert sum([v.count() for v in cppjit.gbl.vtimes]) == 1510 + assert sum([v.count() for v in cppjit.gbl.atimes]) == 1510 class TestSTLSTRING_VIEW: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test01_string_through_string_view(self): """Usage of std::string_view as formal argument""" - import cppyy + import cppjit - countit = cppyy.gbl.StringViewTest.count - countit_cr = cppyy.gbl.StringViewTest.count_cr + countit = cppjit.gbl.StringViewTest.count + countit_cr = cppjit.gbl.StringViewTest.count_cr - assert countit("aap") == 3 + assert countit("aap") == 3 assert countit_cr("aap") == 3 - s = cppyy.gbl.std.string("noot") - assert countit(s) == 4 + s = cppjit.gbl.std.string("noot") + assert countit(s) == 4 assert countit_cr(s) == 4 - v = cppyy.gbl.std.string_view(s.data(), s.size()) - assert v[0] == 'n' - assert countit(v) == 4 + v = cppjit.gbl.std.string_view(s.data(), s.size()) + assert v[0] == "n" + assert countit(v) == 4 assert countit_cr(v) == 4 - @mark.xfail(run=not (IS_CLANG_DEBUG or IS_CLING), reason="Crashes on ClangRepl with 'toString not implemented', and on Cling") + @mark.xfail( + run=not (IS_CLANG_DEBUG or IS_CLING), + reason="Crashes on ClangRepl with 'toString not implemented', and on Cling", + ) def test02_string_view_from_unicode(self): """Life-time management of converted unicode strings""" - import cppyy, gc + import gc + + import cppjit # view on (converted) unicode - text = cppyy.gbl.std.string_view('''\ + text = cppjit.gbl.std.string_view("""\ The standard Lorem Ipsum passage, used since the 1500s "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod @@ -1750,14 +1872,14 @@ def test02_string_view_from_unicode(self): consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."\ - ''') + """) - gc.collect() # likely erases Python-side temporary + gc.collect() # likely erases Python-side temporary assert "Lorem ipsum dolor sit amet" in str(text) # view on bytes - text = cppyy.gbl.std.string_view(b'''\ + text = cppjit.gbl.std.string_view(b"""\ The standard Lorem Ipsum passage, used since the 1500s "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod @@ -1766,76 +1888,88 @@ def test02_string_view_from_unicode(self): consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."\ - ''') + """) - gc.collect() # id. + gc.collect() # id. assert "Lorem ipsum dolor sit amet" in str(text) - @mark.xfail(run = not IS_MAC, condition=IS_MAC or IS_CLING, reason="Crashes on OSX, fails with cling") - def test03_string_view_pythonize(self): + @mark.xfail( + run=not IS_MAC, + condition=IS_MAC or IS_CLING, + reason="Crashes on OSX, fails with cling", + ) + def test03_string_view_pythonize(self): """Pythonization of std::string_view""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" std::string_view s = "Hello, World!"; """) - from cppyy.gbl import s + from cppjit.gbl import s - assert(s == "Hello, World!") - assert(str(s) == "Hello, World!") - - cppyy.cppdef( - """ + assert s == "Hello, World!" + assert str(s) == "Hello, World!" + + cppjit.cppdef( + """ bool is_equal_cpp = s == "Hello, World!"; - """) + """ + ) + + from cppjit.gbl import is_equal_cpp + + assert is_equal_cpp - from cppyy.gbl import is_equal_cpp - assert(is_equal_cpp) class TestSTLDEQUE: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) - cls.N = cppyy.gbl.N + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) + cls.N = cppjit.gbl.N def test01_deque_byvalue_regression(self): """Return by value of a deque used to crash""" - import cppyy - assert cppyy.cppdef("""std::deque emptyf() { + import cppjit + + assert cppjit.cppdef("""std::deque emptyf() { std::deque d; d.push_back(0); return d ; }""") - x = cppyy.gbl.emptyf() + x = cppjit.gbl.emptyf() assert x del x - @mark.xfail(run=False, condition=IS_MAC and IS_CLING, reason="Crashes on OS X Cling") + @mark.xfail( + run=False, condition=IS_MAC and IS_CLING, reason="Crashes on OS X Cling" + ) def test02_deque_cpp17_style(self): """C++17 style initialization of std::deque""" - import cppyy + import cppjit l = [1.0, 2.0, 3.0] - v = cppyy.gbl.std.deque(l) + v = cppjit.gbl.std.deque(l) assert list(l) == l class TestSTLSET: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) - cls.N = cppyy.gbl.N + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) + cls.N = cppjit.gbl.N def test01_set_iteration(self): """Iterate over a set""" - import cppyy + import cppjit - s = cppyy.gbl.std.set[int]() + s = cppjit.gbl.std.set[int]() r = range(self.N) for i in r: s.insert(i) @@ -1850,107 +1984,111 @@ def test01_set_iteration(self): def test02_set_iterators(self): """Access to set iterators and their comparisons""" - import cppyy + import cppjit - cppyy.include("iterator") - s = cppyy.gbl.std.set[int]() + cppjit.include("iterator") + s = cppjit.gbl.std.set[int]() - assert s.begin() == s.end() + assert s.begin() == s.end() assert s.rbegin() == s.rend() val = 42 s.insert(val) assert len(s) == 1 - assert s.begin().__deref__() == val + assert s.begin().__deref__() == val assert s.rbegin().__deref__() == val - assert s.begin() != s.end() - assert s.begin().__preinc__() == s.end() + assert s.begin() != s.end() + assert s.begin().__preinc__() == s.end() assert s.rbegin() != s.rend() assert s.rbegin().__preinc__() == s.rend() def test03_initialize_from_set(self): """Test std::set initializion from Python set""" - import cppyy + import cppjit N = 10 - s = cppyy.gbl.std.set[int](set(range(N))) + s = cppjit.gbl.std.set[int](set(range(N))) assert list(range(N)) == list(s) - s = cppyy.gbl.std.set[int](range(10)) + s = cppjit.gbl.std.set[int](range(10)) assert list(range(N)) == list(s) with raises(TypeError): - s = cppyy.gbl.std.set[int](set([1, "2"])) + s = cppjit.gbl.std.set[int](set([1, "2"])) with raises(TypeError): - s = cppyy.gbl.std.set[int](set(["aap", "noot", "mies"])) + s = cppjit.gbl.std.set[int](set(["aap", "noot", "mies"])) - @mark.xfail(run=False, condition=IS_MAC and IS_CLING, reason="Crashes with OSX-Cling") + @mark.xfail( + run=False, condition=IS_MAC and IS_CLING, reason="Crashes with OSX-Cling" + ) def test04_set_cpp17_style(self): """C++17 style initialization of std::set""" - import cppyy + import cppjit l = [1.0, 2.0, 3.0] - v = cppyy.gbl.std.set(l) + v = cppjit.gbl.std.set(l) assert list(l) == l def test05_contains(self): """Contains check should not iterate and compare""" - import cppyy + import cppjit - assert '__contains__' in cppyy.gbl.std.set[int].__dict__ + assert "__contains__" in cppjit.gbl.std.set[int].__dict__ - S = cppyy.gbl.std.set[int](range(2**20)) + S = cppjit.gbl.std.set[int](range(2**20)) assert 1337 in S - assert not (2**30 in S) + assert 2**30 not in S - # not a true test, but this'll take a noticable amount of time (>1min) if - # there is a regression somehow + # not a true test, but this'll take a noticable amount of time (>1min) if + # there is a regression somehow for i in range(100): - assert not (2**30 in S) + assert 2**30 not in S class TestSTLTUPLE: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) - cls.N = cppyy.gbl.N + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) + cls.N = cppjit.gbl.N @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test01_tuple_creation_and_access(self): """Create tuples and access their elements""" - import cppyy - std = cppyy.gbl.std + import cppjit - t1 = std.make_tuple(1, 'a') + std = cppjit.gbl.std + + t1 = std.make_tuple(1, "a") assert t1 assert std.get[0](t1) == 1 - assert std.get[1](t1) == 'a' + assert std.get[1](t1) == "a" - t2 = std.make_tuple(1, 'a') + t2 = std.make_tuple(1, "a") assert t1 == t2 - t3 = std.make_tuple[int, 'char'](1, 'a') + t3 = std.make_tuple[int, "char"](1, "a") assert t3 assert std.get[0](t3) == 1 - assert std.get[1](t3) == 'a' + assert std.get[1](t3) == "a" # assert t1 != t3 # fails to link (?!) - t4 = std.make_tuple(7., 1, 'b') + t4 = std.make_tuple(7.0, 1, "b") assert t4 - assert std.get[0](t4) == 7. + assert std.get[0](t4) == 7.0 assert std.get[1](t4) == 1 - assert std.get[2](t4) == 'b' + assert std.get[2](t4) == "b" v = std.vector[int](range(self.N)) t5 = std.make_tuple(v, False) @@ -1963,34 +2101,39 @@ def test01_tuple_creation_and_access(self): def test02_tuple_size(self): """Usage of tuple_size helper class""" - import cppyy - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std - t = std.make_tuple("aap", 42, 5.) + t = std.make_tuple("aap", 42, 5.0) assert std.tuple_size(type(t)).value == 3 @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test03_tuple_iter(self): """Pack/unpack tuples""" - import cppyy, ctypes - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std - t = std.make_tuple(1, '2', 5.) + t = std.make_tuple(1, "2", 5.0) assert len(t) == 3 a, b, c = t assert a == 1 - assert b == '2' - assert c == 5. + assert b == "2" + assert c == 5.0 def test04_tuple_lifeline(self): """Tuple memory management""" - import cppyy, gc - std = cppyy.gbl.std + import gc + + import cppjit + + std = cppjit.gbl.std - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace TupleLifeLine { struct Simple { Simple() : fInt(42) {} @@ -2000,7 +2143,7 @@ def test04_tuple_lifeline(self): int fInt; }; }""") - Simple = cppyy.gbl.TupleLifeLine.Simple + Simple = cppjit.gbl.TupleLifeLine.Simple s1, s2 = std.make_tuple(Simple(), Simple()) @@ -2013,15 +2156,17 @@ def test04_tuple_lifeline(self): class TestSTLPAIR: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) - cls.N = cppyy.gbl.N + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) + cls.N = cppjit.gbl.N def test01_pair_pack_unpack(self): """Pack/unpack pairs""" - import cppyy - std = cppyy.gbl.std + import cppjit + + std = cppjit.gbl.std p = std.make_pair(1, 2) a, b = p @@ -2033,183 +2178,191 @@ def test01_pair_pack_unpack(self): class TestSTLEXCEPTION: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.stltypes = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.stltypes = cppjit.load_reflection_info(cls.test_dct) def test01_basics(self): """Test behavior of std::exception derived classes""" - import cppyy + import cppjit - assert issubclass(cppyy.gbl.std.exception, BaseException) - assert cppyy.gbl.std.exception is cppyy.gbl.std.exception + assert issubclass(cppjit.gbl.std.exception, BaseException) + assert cppjit.gbl.std.exception is cppjit.gbl.std.exception - MyError = cppyy.gbl.MyError - assert MyError is cppyy.gbl.MyError + MyError = cppjit.gbl.MyError + assert MyError is cppjit.gbl.MyError assert issubclass(MyError, BaseException) - assert issubclass(MyError, cppyy.gbl.std.exception) - assert MyError.__name__ == 'MyError' - assert MyError.__cpp_name__ == 'MyError' - assert MyError.__module__ == 'cppyy.gbl' + assert issubclass(MyError, cppjit.gbl.std.exception) + assert MyError.__name__ == "MyError" + assert MyError.__cpp_name__ == "MyError" + assert MyError.__module__ == "cppjit.gbl" - YourError = cppyy.gbl.YourError - assert YourError is cppyy.gbl.YourError + YourError = cppjit.gbl.YourError + assert YourError is cppjit.gbl.YourError assert issubclass(YourError, MyError) - assert YourError.__name__ == 'YourError' - assert YourError.__cpp_name__ == 'YourError' - assert YourError.__module__ == 'cppyy.gbl' + assert YourError.__name__ == "YourError" + assert YourError.__cpp_name__ == "YourError" + assert YourError.__module__ == "cppjit.gbl" - MyError = cppyy.gbl.ErrorNamespace.MyError - assert MyError is not cppyy.gbl.MyError - assert MyError is cppyy.gbl.ErrorNamespace.MyError + MyError = cppjit.gbl.ErrorNamespace.MyError + assert MyError is not cppjit.gbl.MyError + assert MyError is cppjit.gbl.ErrorNamespace.MyError assert issubclass(MyError, BaseException) - assert issubclass(MyError, cppyy.gbl.std.exception) - assert MyError.__name__ == 'MyError' - assert MyError.__cpp_name__ == 'ErrorNamespace::MyError' - assert MyError.__module__ == 'cppyy.gbl.ErrorNamespace' - - YourError = cppyy.gbl.ErrorNamespace.YourError - assert YourError is not cppyy.gbl.YourError - assert YourError is cppyy.gbl.ErrorNamespace.YourError + assert issubclass(MyError, cppjit.gbl.std.exception) + assert MyError.__name__ == "MyError" + assert MyError.__cpp_name__ == "ErrorNamespace::MyError" + assert MyError.__module__ == "cppjit.gbl.ErrorNamespace" + + YourError = cppjit.gbl.ErrorNamespace.YourError + assert YourError is not cppjit.gbl.YourError + assert YourError is cppjit.gbl.ErrorNamespace.YourError assert issubclass(YourError, MyError) - assert YourError.__name__ == 'YourError' - assert YourError.__cpp_name__ == 'ErrorNamespace::YourError' - assert YourError.__module__ == 'cppyy.gbl.ErrorNamespace' + assert YourError.__name__ == "YourError" + assert YourError.__cpp_name__ == "ErrorNamespace::YourError" + assert YourError.__module__ == "cppjit.gbl.ErrorNamespace" @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test02_raising(self): """Raise a C++ std::exception derived class as a Python excption""" - import cppyy + import cppjit - assert issubclass(cppyy.gbl.MyError, BaseException) + assert issubclass(cppjit.gbl.MyError, BaseException) def raiseit(cls): - raise cls('Oops') + raise cls("Oops") with raises(Exception): - raiseit(cppyy.gbl.MyError) + raiseit(cppjit.gbl.MyError) - with raises(cppyy.gbl.MyError): - raiseit(cppyy.gbl.MyError) + with raises(cppjit.gbl.MyError): + raiseit(cppjit.gbl.MyError) try: - raiseit(cppyy.gbl.MyError) + raiseit(cppjit.gbl.MyError) except Exception as e: - assert e.what() == 'Oops' + assert e.what() == "Oops" try: - raiseit(cppyy.gbl.MyError) - except cppyy.gbl.MyError as e: - assert e.what() == 'Oops' + raiseit(cppjit.gbl.MyError) + except cppjit.gbl.MyError as e: + assert e.what() == "Oops" try: - raiseit(cppyy.gbl.YourError) - except cppyy.gbl.MyError as e: - assert e.what() == 'Oops' + raiseit(cppjit.gbl.YourError) + except cppjit.gbl.MyError as e: + assert e.what() == "Oops" try: - raiseit(cppyy.gbl.YourError) - except cppyy.gbl.YourError as e: - assert e.what() == 'Oops' + raiseit(cppjit.gbl.YourError) + except cppjit.gbl.YourError as e: + assert e.what() == "Oops" @mark.xfail(condition=(IS_MAC_ARM or IS_MAC_X86), reason="Fails with OS X") def test03_memory(self): """Memory handling of C++ c// helper for exception base class testing""" - import cppyy, gc + import gc + + import cppjit - MyError = cppyy.gbl.MyError - YourError = cppyy.gbl.YourError + MyError = cppjit.gbl.MyError + YourError = cppjit.gbl.YourError gc.collect() - assert cppyy.gbl.GetMyErrorCount() == 0 + assert cppjit.gbl.GetMyErrorCount() == 0 - m = MyError('Oops') - assert cppyy.gbl.GetMyErrorCount() == 1 + m = MyError("Oops") + assert cppjit.gbl.GetMyErrorCount() == 1 del m gc.collect() - assert cppyy.gbl.GetMyErrorCount() == 0 + assert cppjit.gbl.GetMyErrorCount() == 0 def raiseit(cls): - raise cls('Oops') + raise cls("Oops") def run_raiseit(t1, t2): try: raiseit(t1) except t2 as e: - assert e.what() == 'Oops' + assert e.what() == "Oops" return assert not "should not reach this point" - for t1, t2 in [(MyError, Exception), - (MyError, MyError), - (YourError, MyError), - (YourError, YourError)]: + for t1, t2 in [ + (MyError, Exception), + (MyError, MyError), + (YourError, MyError), + (YourError, YourError), + ]: with raises(t2): raiseit(t1) gc.collect() - assert cppyy.gbl.GetMyErrorCount() == 0 + assert cppjit.gbl.GetMyErrorCount() == 0 run_raiseit(t1, t2) gc.collect() - assert cppyy.gbl.GetMyErrorCount() == 0 + assert cppjit.gbl.GetMyErrorCount() == 0 gc.collect() - assert cppyy.gbl.GetMyErrorCount() == 0 + assert cppjit.gbl.GetMyErrorCount() == 0 @mark.xfail(run=False, condition=IS_MAC_ARM, reason="Seg Faults on OSX-ARM") def test04_from_cpp(self): """Catch C++ exceptiosn from C++""" if ispypy: - skip('currently terminates') + skip("currently terminates") + + import gc - import cppyy, gc + import cppjit gc.collect() - assert cppyy.gbl.GetMyErrorCount() == 0 + assert cppjit.gbl.GetMyErrorCount() == 0 - with raises(cppyy.gbl.MyError): - cppyy.gbl.ErrorNamespace.throw_error(0) + with raises(cppjit.gbl.MyError): + cppjit.gbl.ErrorNamespace.throw_error(0) - with raises(cppyy.gbl.MyError): - cppyy.gbl.ErrorNamespace.throw_error(1) + with raises(cppjit.gbl.MyError): + cppjit.gbl.ErrorNamespace.throw_error(1) - with raises(cppyy.gbl.YourError): - cppyy.gbl.ErrorNamespace.throw_error(1) + with raises(cppjit.gbl.YourError): + cppjit.gbl.ErrorNamespace.throw_error(1) - with raises(cppyy.gbl.ErrorNamespace.MyError): - cppyy.gbl.ErrorNamespace.throw_error(2) + with raises(cppjit.gbl.ErrorNamespace.MyError): + cppjit.gbl.ErrorNamespace.throw_error(2) - with raises(cppyy.gbl.ErrorNamespace.MyError): - cppyy.gbl.ErrorNamespace.throw_error(3) + with raises(cppjit.gbl.ErrorNamespace.MyError): + cppjit.gbl.ErrorNamespace.throw_error(3) - with raises(cppyy.gbl.ErrorNamespace.YourError): - cppyy.gbl.ErrorNamespace.throw_error(3) + with raises(cppjit.gbl.ErrorNamespace.YourError): + cppjit.gbl.ErrorNamespace.throw_error(3) gc.collect() - assert cppyy.gbl.GetMyErrorCount() == 0 + assert cppjit.gbl.GetMyErrorCount() == 0 + def has_cpp_20(): - import cppyy + import cppjit - return cppyy.evaluate("__cplusplus") >= 202002 + return cppjit.evaluate("__cplusplus") >= 202002 @mark.skipif(not has_cpp_20(), reason="std::span requires C++20") class TestSTLSPAN: - import cppyy + import cppjit def test01_span_iterators(self): """ Test that std::span::begin() and std::span::end() can be used. """ - import cppyy + import cppjit l1 = [1, 2, 3] - v = cppyy.gbl.std.vector(int)(l1) - s = cppyy.gbl.std.span(int)(v) + v = cppjit.gbl.std.vector(int)(l1) + s = cppjit.gbl.std.span(int)(v) s.begin() s.end() # Check that the iteration also works, which uses begin() and end() @@ -2228,12 +2381,13 @@ def test02_span_argument_conversions(self): 5) std::vector implicit conversion 6) const std::span behavior """ - import cppyy - import numpy as np import array + + import cppjit + import numpy as np import pytest - cppyy.cppdef(""" + cppjit.cppdef(""" #include #include @@ -2254,48 +2408,47 @@ def test02_span_argument_conversions(self): } """) - data = [1., 2., 3.] + data = [1.0, 2.0, 3.0] expected = sum(data) # 1) Python proxy span - v = cppyy.gbl.std.vector["double"](data) - s = cppyy.gbl.std.span["double"](v) - assert cppyy.gbl.sum_span["double"](s) == expected - assert cppyy.gbl.sum_span_const["double"](s) == expected + v = cppjit.gbl.std.vector["double"](data) + s = cppjit.gbl.std.span["double"](v) + assert cppjit.gbl.sum_span["double"](s) == expected + assert cppjit.gbl.sum_span_const["double"](s) == expected # 2) NumPy array np_arr = np.array(data, dtype=np.float64) - assert cppyy.gbl.sum_span["double"](np_arr) == expected - assert cppyy.gbl.sum_span_const["double"](np_arr) == expected + assert cppjit.gbl.sum_span["double"](np_arr) == expected + assert cppjit.gbl.sum_span_const["double"](np_arr) == expected # 3) array.array - arr = array.array('d', data) - assert cppyy.gbl.sum_span["double"](arr) == expected - assert cppyy.gbl.sum_span_const["double"](arr) == expected + arr = array.array("d", data) + assert cppjit.gbl.sum_span["double"](arr) == expected + assert cppjit.gbl.sum_span_const["double"](arr) == expected # 4) Type mismatch → should raise TypeError np_double = np.array([1.0, 2.0, 3.0], dtype=np.float32) with pytest.raises(TypeError): - cppyy.gbl.sum_span["double"](np_double) + cppjit.gbl.sum_span["double"](np_double) # 5) std::vector implicit conversion - v2 = cppyy.gbl.std.vector["double"](data) - assert cppyy.gbl.sum_span["double"](v2) == expected - assert cppyy.gbl.sum_span_const["double"](v2) == expected + v2 = cppjit.gbl.std.vector["double"](data) + assert cppjit.gbl.sum_span["double"](v2) == expected + assert cppjit.gbl.sum_span_const["double"](v2) == expected # 6) const span behaves the same (already checked above, but explicit case) - assert cppyy.gbl.sum_span_const["double"](np_arr) == expected + assert cppjit.gbl.sum_span_const["double"](np_arr) == expected class TestSTLANY: - def test01_make_any(self): """ Test that std::make_any can be used for class types. """ - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" #include namespace STLANY { @@ -2305,6 +2458,6 @@ class MyClass{}; } // namespace STLANY """) - my_inst = cppyy.gbl.STLANY.MyClass() + my_inst = cppjit.gbl.STLANY.MyClass() - cppyy.gbl.std.make_any["STLANY::MyClass*", "STLANY::MyClass*"](my_inst) + cppjit.gbl.std.make_any["STLANY::MyClass*", "STLANY::MyClass*"](my_inst) diff --git a/test/test_streams.py b/test/test_streams.py index 42528df..6fc6052 100644 --- a/test/test_streams.py +++ b/test/test_streams.py @@ -1,9 +1,10 @@ -import py, os, sys -from pytest import raises, mark -from support import setup_make, IS_MAC, IS_CLANG_REPL +import py +from pytest import mark +from support import IS_MAC, setup_make currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("std_streamsDict")) +test_dct = str(currpath.join("cpp/std_streamsDict")) + def setup_module(mod): setup_make("std_streams") @@ -12,54 +13,56 @@ def setup_module(mod): class TestSTDStreams: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.streams = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.streams = cppjit.load_reflection_info(cls.test_dct) def test01_std_ostream(self): """Test availability of std::ostream""" - import cppyy + import cppjit - assert cppyy.gbl.std is cppyy.gbl.std - assert cppyy.gbl.std.ostream is cppyy.gbl.std.ostream + assert cppjit.gbl.std is cppjit.gbl.std + assert cppjit.gbl.std.ostream is cppjit.gbl.std.ostream - assert callable(cppyy.gbl.std.ostream) + assert callable(cppjit.gbl.std.ostream) def test02_std_cout(self): """Test access to std::cout""" - import cppyy + import cppjit - assert not (cppyy.gbl.std.cout is None) + assert cppjit.gbl.std.cout is not None @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test03_consistent_naming_if_char_traits(self): """Naming consistency if char_traits""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace stringstream_base { void pass_through_base(std::ostream& o) { o << "TEST STRING"; } }""") - s = cppyy.gbl.std.ostringstream(); - # base class used to fail to match - cppyy.gbl.stringstream_base.pass_through_base(s) + s = cppjit.gbl.std.ostringstream() + # base class used to fail to match + cppjit.gbl.stringstream_base.pass_through_base(s) assert s.str() == "TEST STRING" def test04_naming_of_ostringstream(self): """Naming consistency of ostringstream""" - import cppyy + import cppjit # Check if the object created is equal in all three cases - cl0 = cppyy.gbl.std.ostringstream - cl1 = cppyy.gbl.std.basic_ostringstream['char'] - cl2 = cppyy.gbl.std.basic_ostringstream['char', cppyy.gbl.std.char_traits['char'] , cppyy.gbl.std.allocator['char']] + cl0 = cppjit.gbl.std.ostringstream + cl1 = cppjit.gbl.std.basic_ostringstream["char"] + cl2 = cppjit.gbl.std.basic_ostringstream[ + "char", cppjit.gbl.std.char_traits["char"], cppjit.gbl.std.allocator["char"] + ] assert cl0 == cl1 assert cl1 == cl2 assert cl2 == cl0 - diff --git a/test/test_templates.py b/test/test_templates.py index fb88608..7f2ad13 100644 --- a/test/test_templates.py +++ b/test/test_templates.py @@ -1,9 +1,18 @@ -import py, os -from pytest import raises, mark -from support import setup_make, pylong, IS_CLANG_REPL, IS_CLING, IS_CLANG_DEBUG, IS_MAC_X86, IS_MAC_ARM, IS_MAC, IS_LINUX_ARM, IS_VALGRIND +import py +from pytest import mark, raises +from support import ( + IS_CLANG_REPL, + IS_CLING, + IS_LINUX_ARM, + IS_MAC, + IS_VALGRIND, + pylong, + setup_make, +) currpath = py.path.local(__file__).dirpath() -test_dct = str(currpath.join("templatesDict")) +test_dct = str(currpath.join("cpp/templatesDict")) + def setup_module(mod): setup_make("templates") @@ -12,159 +21,166 @@ def setup_module(mod): class TestTEMPLATES: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.templates = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.templates = cppjit.load_reflection_info(cls.test_dct) def test00_template_back_reference(self): """Template reflection""" - import cppyy + import cppjit - v1 = cppyy.gbl.std.vector[int] + v1 = cppjit.gbl.std.vector[int] assert v1.__cpp_template__[int] is v1 def test01_template_member_functions(self): """Template member functions lookup and calls""" - import cppyy import ctypes import sys - m = cppyy.gbl.MyTemplatedMethodClass() + import cppjit - # implicit (called before other tests to check caching) - assert m.get_size(1) == m.get_int_size()+1 - assert 'get_size' in dir(cppyy.gbl.MyTemplatedMethodClass) + m = cppjit.gbl.MyTemplatedMethodClass() - # pre-instantiated - assert m.get_size['char']() == m.get_char_size() - assert m.get_size[int]() == m.get_int_size() + # implicit (called before other tests to check caching) + assert m.get_size(1) == m.get_int_size() + 1 + assert "get_size" in dir(cppjit.gbl.MyTemplatedMethodClass) - # specialized + # pre-instantiated + assert m.get_size["char"]() == m.get_char_size() + assert m.get_size[int]() == m.get_int_size() + + # specialized if sys.hexversion >= 0x3000000: - targ = 'long' + targ = "long" else: - targ = long - assert m.get_size[targ]() == m.get_long_size() - - assert m.get_size(ctypes.c_double(3.14)) == m.get_size['double']() - assert m.get_size(ctypes.c_double(3.14).value) == m.get_size['double']()+1 - - # auto-instantiation - assert m.get_size[float]() == m.get_float_size() - assert m.get_size['double']() == m.get_double_size() - assert m.get_size['MyTemplatedMethodClass']() == m.get_self_size() - assert 'get_size' in dir(cppyy.gbl.MyTemplatedMethodClass) + targ = long # noqa: F821 + assert m.get_size[targ]() == m.get_long_size() + + assert m.get_size(ctypes.c_double(3.14)) == m.get_size["double"]() + assert m.get_size(ctypes.c_double(3.14).value) == m.get_size["double"]() + 1 + + # auto-instantiation + assert m.get_size[float]() == m.get_float_size() + assert m.get_size["double"]() == m.get_double_size() + assert m.get_size["MyTemplatedMethodClass"]() == m.get_self_size() + assert "get_size" in dir( + cppjit.gbl.MyTemplatedMethodClass + ) - # auto through typedef - assert m.get_size['MyTMCTypedef_t']() == m.get_self_size() - assert 'get_size' in dir(cppyy.gbl.MyTemplatedMethodClass) - assert m.get_size['MyTemplatedMethodClass']() == m.get_self_size() + # auto through typedef + assert m.get_size["MyTMCTypedef_t"]() == m.get_self_size() + assert "get_size" in dir(cppjit.gbl.MyTemplatedMethodClass) + assert m.get_size["MyTemplatedMethodClass"]() == m.get_self_size() def test02_non_type_template_args(self): """Use of non-types as template arguments""" - import cppyy + import cppjit - cppyy.cppdef("template int nt_templ_args() { return i; };") + cppjit.cppdef("template int nt_templ_args() { return i; };") - assert cppyy.gbl.nt_templ_args[1]() == 1 - assert cppyy.gbl.nt_templ_args[256]() == 256 + assert cppjit.gbl.nt_templ_args[1]() == 1 + assert cppjit.gbl.nt_templ_args[256]() == 256 def test03_templated_function(self): """Templated global and static functions lookup and calls""" - import cppyy + import cppjit # TODO: the following only works if something else has already # loaded the headers associated with this template - ggs = cppyy.gbl.global_get_size - assert ggs['char']() == 1 + ggs = cppjit.gbl.global_get_size + assert ggs["char"]() == 1 - gsf = cppyy.gbl.global_some_foo + gsf = cppjit.gbl.global_some_foo assert gsf[int](3) == 42 - assert gsf(3) == 42 - assert gsf(3.) == 42 + assert gsf(3) == 42 + assert gsf(3.0) == 42 - gsbv = cppyy.gbl.global_some_bar_var - assert gsbv(3) == 13 - assert gsbv['double'](3.) == 13 + gsbv = cppjit.gbl.global_some_bar_var + assert gsbv(3) == 13 + assert gsbv["double"](3.0) == 13 - gsb = cppyy.gbl.global_some_bar + gsb = cppjit.gbl.global_some_bar assert gsb[1] assert gsb[1]() == 1 - nsgsb = cppyy.gbl.SomeNS.some_bar + nsgsb = cppjit.gbl.SomeNS.some_bar assert nsgsb[3] assert nsgsb[3]() == 3 - nscsb = cppyy.gbl.SomeNS.SomeStruct.some_bar + nscsb = cppjit.gbl.SomeNS.SomeStruct.some_bar assert nscsb[8] assert nscsb[8]() == 8 # test forced creation of subsequent overloads - from cppyy.gbl.std import vector + from cppjit.gbl.std import vector + # float in, float out - ggsr = cppyy.gbl.global_get_some_result['std::vector'] - assert type(ggsr(vector['float']([0.5])).m_retval) == float - assert ggsr(vector['float']([0.5])).m_retval == 0.5 + ggsr = cppjit.gbl.global_get_some_result["std::vector"] + assert type(ggsr(vector["float"]([0.5])).m_retval) == float + assert ggsr(vector["float"]([0.5])).m_retval == 0.5 # int in, float out - ggsr = cppyy.gbl.global_get_some_result['std::vector'] - assert type(ggsr(vector['int']([5])).m_retval) == float - assert ggsr(vector['int']([5])).m_retval == 5. + ggsr = cppjit.gbl.global_get_some_result["std::vector"] + assert type(ggsr(vector["int"]([5])).m_retval) == float + assert ggsr(vector["int"]([5])).m_retval == 5.0 # float in, int out - ggsr = cppyy.gbl.global_get_some_result['std::vector, int'] - assert type(ggsr(vector['float']([0.3])).m_retval) == int - assert ggsr(vector['float']([0.3])).m_retval == 0 + ggsr = cppjit.gbl.global_get_some_result["std::vector, int"] + assert type(ggsr(vector["float"]([0.3])).m_retval) == int + assert ggsr(vector["float"]([0.3])).m_retval == 0 # int in, int out - ggsr = cppyy.gbl.global_get_some_result['std::vector, int'] - assert type(ggsr(vector['int']([5])).m_retval) == int - assert ggsr(vector['int']([5])).m_retval == 5 + ggsr = cppjit.gbl.global_get_some_result["std::vector, int"] + assert type(ggsr(vector["int"]([5])).m_retval) == int + assert ggsr(vector["int"]([5])).m_retval == 5 - @mark.xfail(condition = IS_MAC, reason = "Fails on OS X") + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test04_variadic_function(self): """Call a variadic function""" - import cppyy - std = cppyy.gbl.std + import cppjit - s = std.ostringstream('(', std.ios_base.ate) + std = cppjit.gbl.std + + s = std.ostringstream("(", std.ios_base.ate) # Fails; wrong overload on PyPy, none on CPython - #s << "(" - cppyy.gbl.SomeNS.tuplify(s, 1, 4., "aap") + # s << "(" + cppjit.gbl.SomeNS.tuplify(s, 1, 4.0, "aap") assert s.str() == "(1, 4, aap, NULL)" - cppyy.cppdef(""" + cppjit.cppdef(""" template int test04_variadic_func() { return sizeof...(myTypes); } """) - assert cppyy.gbl.test04_variadic_func['int', 'double', 'void*']() == 3 + assert cppjit.gbl.test04_variadic_func["int", "double", "void*"]() == 3 def test05_variadic_overload(self): """Call an overloaded variadic function""" - import cppyy + import cppjit - assert cppyy.gbl.isSomeInt(3.) == False - assert cppyy.gbl.isSomeInt(1) == True - assert cppyy.gbl.isSomeInt() == False - assert cppyy.gbl.isSomeInt(1, 2, 3) == False + assert cppjit.gbl.isSomeInt(3.0) == False + assert cppjit.gbl.isSomeInt(1) == True + assert cppjit.gbl.isSomeInt() == False + assert cppjit.gbl.isSomeInt(1, 2, 3) == False @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test06_variadic_sfinae(self): """Attribute testing through SFINAE""" - import cppyy - cppyy.gbl.AttrTesting # load - from cppyy.gbl.AttrTesting import Obj1, Obj2, has_var1, call_has_var1 - from cppyy.gbl.std import move + import cppjit + + cppjit.gbl.AttrTesting # load + from cppjit.gbl.AttrTesting import Obj1, Obj2, call_has_var1, has_var1 + from cppjit.gbl.std import move - assert has_var1(Obj1()) == hasattr(Obj1(), 'var1') - assert has_var1(Obj2()) == hasattr(Obj2(), 'var1') - assert has_var1(3) == hasattr(3, 'var1') - assert has_var1("aap") == hasattr("aap", 'var1') + assert has_var1(Obj1()) == hasattr(Obj1(), "var1") + assert has_var1(Obj2()) == hasattr(Obj2(), "var1") + assert has_var1(3) == hasattr(3, "var1") + assert has_var1("aap") == hasattr("aap", "var1") assert call_has_var1(move(Obj1())) == True assert call_has_var1(move(Obj2())) == False @@ -172,14 +188,15 @@ def test06_variadic_sfinae(self): def test07_type_deduction(self): """Traits/type deduction""" - import cppyy - cppyy.gbl.AttrTesting # load - from cppyy.gbl.AttrTesting import select_template_arg, Obj1, Obj2 + import cppjit + + cppjit.gbl.AttrTesting # load + from cppjit.gbl.AttrTesting import Obj1, Obj2, select_template_arg assert select_template_arg[0, Obj1, Obj2].argument == Obj1 assert select_template_arg[1, Obj1, Obj2].argument == Obj2 # TODO: the following crashes deep inside cling/clang ... - #raises(TypeError, getattr, select_template_arg[2, Obj1, Obj2], 'argument') + # raises(TypeError, getattr, select_template_arg[2, Obj1, Obj2], 'argument') # This is a bit subtle: to be able to use typedefs in templates, builtin # types are present as subclasses that carry __cpp_name__, hence the result @@ -190,9 +207,9 @@ def test07_type_deduction(self): def test08_using_of_static_data(self): """Derived class using static data of base""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" template struct BaseClassWithStatic { static T const ref_value; }; @@ -211,44 +228,44 @@ def test08_using_of_static_data(self): T m_value; };""") - assert cppyy.gbl.BaseClassWithStatic["size_t"].ref_value == 42 + assert cppjit.gbl.BaseClassWithStatic["size_t"].ref_value == 42 - b1 = cppyy.gbl.DerivedClassUsingStatic["size_t"]( 0) - b2 = cppyy.gbl.DerivedClassUsingStatic["size_t"](100) + b1 = cppjit.gbl.DerivedClassUsingStatic["size_t"](0) + b2 = cppjit.gbl.DerivedClassUsingStatic["size_t"](100) - # assert b1.ref_value == 42 - assert b1.m_value == 0 + # assert b1.ref_value == 42 + assert b1.m_value == 0 - # assert b2.ref_value == 42 - assert b2.m_value == 42 + # assert b2.ref_value == 42 + assert b2.m_value == 42 def test09_templated_callable(self): """Test that templated operator() translates to __call__""" - import cppyy + import cppjit - tc = cppyy.gbl.TemplatedCallable() + tc = cppjit.gbl.TemplatedCallable() - assert tc(5) == 5. + assert tc(5) == 5.0 def test10_templated_hidding_methods(self): """Test that base class methods are not considered when hidden""" - import cppyy + import cppjit - B = cppyy.gbl.TemplateHiding.Base - D = cppyy.gbl.TemplateHiding.Derived + B = cppjit.gbl.TemplateHiding.Base + D = cppjit.gbl.TemplateHiding.Derived assert B().callme(1) == 2 - assert D().callme() == 2 + assert D().callme() == 2 assert D().callme(2) == 2 def test11_templated_ctor(self): """Test templated constructors""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ template class RTTest_SomeClassWithTCtor { public: @@ -268,74 +285,76 @@ class RTTest_SomeClassWithTCtor { }; } """) - from cppyy import gbl + from cppjit import gbl - assert round(gbl.RTTest_SomeClassWithTCtor[int](1, 3.1).m_double - 4.1, 8) == 0. + assert ( + round(gbl.RTTest_SomeClassWithTCtor[int](1, 3.1).m_double - 4.1, 8) == 0.0 + ) RTTest2 = gbl.RTTest_SomeNamespace.RTTest_SomeClassWithTCtor - assert round(RTTest2[int](1, 3.1).m_double - 4.1, 8) == 0. - assert round(RTTest2[int]().m_double + 1., 8) == 0. + assert round(RTTest2[int](1, 3.1).m_double - 4.1, 8) == 0.0 + assert round(RTTest2[int]().m_double + 1.0, 8) == 0.0 @mark.xfail(run=False, condition=IS_CLING, reason="Crashes on Cling") def test12_template_aliases(self): """Access to templates made available with 'using'""" - import cppyy + import cppjit - nsup = cppyy.gbl.using_problem + nsup = cppjit.gbl.using_problem - # through dictionary - davec = cppyy.gbl.DA_vector["float"]() + # through dictionary + davec = cppjit.gbl.DA_vector["float"]() davec += range(10) assert davec[5] == 5 - # through interpreter - cppyy.cppdef("template using IA_vector = std::vector;") - iavec = cppyy.gbl.IA_vector["float"]() + # through interpreter + cppjit.cppdef("template using IA_vector = std::vector;") + iavec = cppjit.gbl.IA_vector["float"]() iavec += range(10) assert iavec[5] == 5 - # with variadic template + # with variadic template assert nsup.matryoshka[int, 3].type assert nsup.matryoshka[int, 3, 4].type - assert nsup.make_vector[int , 3] - assert nsup.make_vector[int , 3]().m_val == 3 - assert nsup.make_vector[int , 4]().m_val == 4 + assert nsup.make_vector[int, 3] + assert nsup.make_vector[int, 3]().m_val == 3 + assert nsup.make_vector[int, 4]().m_val == 4 - # with inner types using - assert cppyy.evaluate("using_problem::Bar::Foo") + # with inner types using + assert cppjit.evaluate("using_problem::Bar::Foo") assert nsup.Foo - assert nsup.Bar.Foo # used to fail + assert nsup.Bar.Foo # used to fail def test13_using_templated_method(self): """Access to base class templated methods through 'using'""" - import cppyy + import cppjit - b = cppyy.gbl.using_problem.Base[int]() + b = cppjit.gbl.using_problem.Base[int]() assert type(b.get3()) == int assert b.get3() == 5 - assert type(b.get3['double'](5)) == float - assert b.get3['double'](5) == 10. + assert type(b.get3["double"](5)) == float + assert b.get3["double"](5) == 10.0 - d = cppyy.gbl.using_problem.Derived[int]() - #assert type(d.get1['double'](5)) == float - #assert d.get1['double'](5) == 10. + d = cppjit.gbl.using_problem.Derived[int]() + # assert type(d.get1['double'](5)) == float + # assert d.get1['double'](5) == 10. assert type(d.get2()) == int assert d.get2() == 5 - assert type(d.get3['double'](5)) == float - assert d.get3['double'](5) == 10. + assert type(d.get3["double"](5)) == float + assert d.get3["double"](5) == 10.0 assert type(d.get3()) == int assert d.get3() == 5 def test14_templated_return_type(self): """Use of a templated return type""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ struct RTTest_SomeStruct1 {}; template struct RTTest_TemplatedList {}; template auto rttest_make_tlist(T ... args) { @@ -351,8 +370,12 @@ def test14_templated_return_type(self): return RTTest_SomeNamespace::RTTest_TemplatedList2{}; } """) - from cppyy.gbl import rttest_make_tlist, rttest_make_tlist2, \ - RTTest_SomeNamespace, RTTest_SomeStruct1 + from cppjit.gbl import ( + RTTest_SomeNamespace, + RTTest_SomeStruct1, + rttest_make_tlist, + rttest_make_tlist2, + ) assert rttest_make_tlist(RTTest_SomeStruct1()) assert rttest_make_tlist(RTTest_SomeNamespace.RTTest_SomeStruct2()) @@ -362,117 +385,136 @@ def test14_templated_return_type(self): def test15_rvalue_templates(self): """Use of a template with r-values; should accept builtin types""" - import cppyy + import cppjit - is_valid = cppyy.gbl.T_WithRValue.is_valid + is_valid = cppjit.gbl.T_WithRValue.is_valid - # bit of regression testing + # bit of regression testing assert is_valid(3) - assert is_valid['int'](3) # used to crash + assert is_valid["int"](3) # used to crash - # actual method calls + # actual method calls assert is_valid[int](1) assert not is_valid(0) - assert is_valid(1.) - assert not is_valid(0.) + assert is_valid(1.0) + assert not is_valid(0.0) - @mark.xfail(condition = IS_MAC, reason = "Fails on OS X") + @mark.xfail(condition=IS_MAC, reason="Fails on OS X") def test16_variadic(self): """Range of variadic templates""" - import cppyy + import cppjit - ns = cppyy.gbl.some_variadic + ns = cppjit.gbl.some_variadic def get_tn(ns): - # helper to make all platforms look the same + # helper to make all platforms look the same tn = ns.gTypeName - tn = tn.replace(' ', '') - tn = tn.replace('class', '') - tn = tn.replace('__cdecl', '') - tn = tn.replace('__thiscall', '') - tn = tn.replace('__ptr64', '') + tn = tn.replace(" ", "") + tn = tn.replace("class", "") + tn = tn.replace("__cdecl", "") + tn = tn.replace("__thiscall", "") + tn = tn.replace("__ptr64", "") return tn - # templated class - a = ns.A['int', 'double']() + # templated class + a = ns.A["int", "double"]() assert get_tn(ns) == "some_variadic::A" - # static functions - a.sa(1, 1., 'a') - assert get_tn(ns).find("some_variadic::A::void(int&&,double&&,std::") == 0 - ns.A['char&', 'double*'].sa(1, 1., 'a') - assert get_tn(ns).find("some_variadic::A::void(int&&,double&&,std::") == 0 - ns.A['char&', 'double*'].sa_T['int'](1, 1., 'a') - assert get_tn(ns).find("some_variadic::A::int(int&&,double&&,std::") == 0 - - # member functions - a.a(1, 1., 'a') - assert get_tn(ns).find("void(some_variadic::A::*)(int&&,double&&,std::") == 0 - a.a_T['int'](1, 1., 'a') - assert get_tn(ns).find("int(some_variadic::A::*)(int&&,double&&,std::") == 0 - - # non-templated class + # static functions + a.sa(1, 1.0, "a") + assert ( + get_tn(ns).find("some_variadic::A::void(int&&,double&&,std::") + == 0 + ) + ns.A["char&", "double*"].sa(1, 1.0, "a") + assert ( + get_tn(ns).find( + "some_variadic::A::void(int&&,double&&,std::" + ) + == 0 + ) + ns.A["char&", "double*"].sa_T["int"](1, 1.0, "a") + assert ( + get_tn(ns).find("some_variadic::A::int(int&&,double&&,std::") + == 0 + ) + + # member functions + a.a(1, 1.0, "a") + assert ( + get_tn(ns).find( + "void(some_variadic::A::*)(int&&,double&&,std::" + ) + == 0 + ) + a.a_T["int"](1, 1.0, "a") + assert ( + get_tn(ns).find("int(some_variadic::A::*)(int&&,double&&,std::") + == 0 + ) + + # non-templated class b = ns.B() assert get_tn(ns) == "some_variadic::B" - # static functions - b.sb(1, 1., 'a') + # static functions + b.sb(1, 1.0, "a") assert get_tn(ns).find("some_variadic::B::void(int&&,double&&,std::") == 0 - ns.B.sb(1, 1., 'a') + ns.B.sb(1, 1.0, "a") assert get_tn(ns).find("some_variadic::B::void(int&&,double&&,std::") == 0 - ns.B.sb_T['int'](1, 1., 'a') + ns.B.sb_T["int"](1, 1.0, "a") assert get_tn(ns).find("some_variadic::B::int(int&&,double&&,std::") == 0 - # member functions - b.b(1, 1., 'a') + # member functions + b.b(1, 1.0, "a") assert get_tn(ns).find("void(some_variadic::B::*)(int&&,double&&,std::") == 0 - b.b_T['int'](1, 1., 'a') + b.b_T["int"](1, 1.0, "a") assert get_tn(ns).find("int(some_variadic::B::*)(int&&,double&&,std::") == 0 @mark.xfail(condition=IS_MAC, reason="Fails on OSX") def test17_empty_body(self): """Use of templated function with empty body""" - import cppyy + import cppjit - f_T = cppyy.gbl.T_WithEmptyBody.some_empty + f_T = cppjit.gbl.T_WithEmptyBody.some_empty - assert cppyy.gbl.T_WithEmptyBody.side_effect == "not set" + assert cppjit.gbl.T_WithEmptyBody.side_effect == "not set" assert f_T[int]() is None - assert cppyy.gbl.T_WithEmptyBody.side_effect == "side effect" + assert cppjit.gbl.T_WithEmptyBody.side_effect == "side effect" @mark.xfail(condition=IS_MAC and IS_CLING, reason="Fails on OSX Cling") def test18_greedy_overloads(self): """void*/void** should not pre-empt template instantiations""" - import cppyy + import cppjit - ns = cppyy.gbl.T_WithGreedyOverloads + ns = cppjit.gbl.T_WithGreedyOverloads - # check that void* does not mask template instantiations + # check that void* does not mask template instantiations g1 = ns.WithGreedy1() assert g1.get_size(ns.SomeClass(), True) == -1 - assert g1.get_size(ns.SomeClass()) == cppyy.sizeof(ns.SomeClass) + assert g1.get_size(ns.SomeClass()) == cppjit.sizeof(ns.SomeClass) - # check that void* does not mask template instantiations + # check that void* does not mask template instantiations g2 = ns.WithGreedy2() - assert g2.get_size(ns.SomeClass()) == cppyy.sizeof(ns.SomeClass) + assert g2.get_size(ns.SomeClass()) == cppjit.sizeof(ns.SomeClass) assert g2.get_size(ns.SomeClass(), True) == -1 - # check that unknown classes do not mask template instantiations + # check that unknown classes do not mask template instantiations g3 = ns.WithGreedy3() - assert g3.get_size(ns.SomeClass()) == cppyy.sizeof(ns.SomeClass) - assert g3.get_size(cppyy.nullptr, True) == -1 + assert g3.get_size(ns.SomeClass()) == cppjit.sizeof(ns.SomeClass) + assert g3.get_size(cppjit.nullptr, True) == -1 - @mark.xfail(condition = IS_CLING, reason = "Fails on Cling") + @mark.xfail(condition=IS_CLING, reason="Fails on Cling") def test19_templated_operator_add(self): """Templated operator+ is ambiguous: either __pos__ or __add__""" - import cppyy - import cppyy.gbl as gbl + import cppjit + import cppjit.gbl as gbl - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace OperatorAddTest { template class CustomVec { @@ -488,19 +530,19 @@ class CustomVec { } }; }""") - c = gbl.OperatorAddTest.CustomVec['double'](5.3) - d = gbl.OperatorAddTest.CustomVec['int'](1) + c = gbl.OperatorAddTest.CustomVec["double"](5.3) + d = gbl.OperatorAddTest.CustomVec["int"](1) q = c + d - assert round(q.X() - 6.3, 8) == 0. + assert round(q.X() - 6.3, 8) == 0.0 def test20_templated_ctor_with_defaults(self): """Templated constructor with defaults used to be ignored""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace TemplatedCtor { class C { public: @@ -509,14 +551,14 @@ class C { C(const std::string&) {} }; } """) - assert cppyy.gbl.TemplatedCtor.C(0) + assert cppjit.gbl.TemplatedCtor.C(0) def test21_type_deduction_with_conversion(self): """Template instantiation with [] -> std::vector conversion""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace l2v { struct Base {}; struct Derived : Base {}; @@ -533,83 +575,91 @@ def test21_type_deduction_with_conversion(self): int test3(const std::vector& v) { return (int)v.size(); } }""") - from cppyy.gbl import l2v + from cppjit.gbl import l2v d1 = l2v.Derived() - assert l2v.test1([d1]) == 1 + assert l2v.test1([d1]) == 1 assert l2v.test1([d1, d1]) == 2 - assert l2v.test2[int]([d1]) == 1 + assert l2v.test2[int]([d1]) == 1 assert l2v.test2[int]([d1, d1]) == 2 - assert l2v.test2a[int]([d1]) == 1 + assert l2v.test2a[int]([d1]) == 1 assert l2v.test2a[int]([d1, d1]) == 2 - assert l2v.test3[int]([d1]) == 1 + assert l2v.test3[int]([d1]) == 1 assert l2v.test3[int]([d1, d1]) == 2 def test22_type_deduction_of_proper_integer_size(self): """Template type from integer arg should be big enough""" - import cppyy + import cppjit - cppyy.cppdef("template T PassSomeInt(T t) { return t; }") + cppjit.cppdef("template T PassSomeInt(T t) { return t; }") - from cppyy.gbl import PassSomeInt + from cppjit.gbl import PassSomeInt - for val in [1, 100000000000, -2**32, 2**32-1, 2**64-1 -2**63]: + for val in [1, 100000000000, -(2**32), 2**32 - 1, 2**64 - 1 - 2**63]: assert val == PassSomeInt(val) - for val in [2**64, -2**63-1]: + for val in [2**64, -(2**63) - 1]: raises(OverflowError, PassSomeInt, val) def test23_overloaded_setitem(self): """Template with overloaded non-templated and templated setitem""" - import cppyy + import cppjit - MyVec = cppyy.gbl.TemplateWithSetItem.MyVec + MyVec = cppjit.gbl.TemplateWithSetItem.MyVec v = MyVec["float"](2) - v[0] = 1 # used to throw TypeError + v[0] = 1 # used to throw TypeError - @mark.xfail(run=False, condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLING, reason="Crashes on Valgind Cling-ARM") + @mark.xfail( + run=False, + condition=IS_VALGRIND and IS_LINUX_ARM and IS_CLING, + reason="Crashes on Valgind Cling-ARM", + ) def test24_stdfunction_templated_arguments(self): """Use of std::function with templated arguments""" - import cppyy + import cppjit def callback(x): return sum(x) - cppyy.cppdef("""double callback_vector( + cppjit.cppdef("""double callback_vector( const std::function)>& callback, std::vector x) { return callback(x); }""") - assert cppyy.gbl.std.function['double(std::vector)'] + assert cppjit.gbl.std.function["double(std::vector)"] - assert cppyy.gbl.callback_vector(callback, [1, 2, 3]) == 6 + assert cppjit.gbl.callback_vector(callback, [1, 2, 3]) == 6 - cppyy.cppdef("""double wrap_callback_vector( + cppjit.cppdef("""double wrap_callback_vector( double (*callback)(std::vector), std::vector x) { return callback_vector(callback, x); }""") - assert cppyy.gbl.wrap_callback_vector(callback, [4, 5, 6]) == 15 + assert cppjit.gbl.wrap_callback_vector(callback, [4, 5, 6]) == 15 - assert cppyy.gbl.std.function['double(std::vector)'] + assert cppjit.gbl.std.function["double(std::vector)"] - @mark.xfail(run=False, condition=IS_VALGRIND and IS_LINUX_ARM, reason="Crashes on Valgrind-ARM") + @mark.xfail( + run=False, + condition=IS_VALGRIND and IS_LINUX_ARM, + reason="Crashes on Valgrind-ARM", + ) def test25_stdfunction_ref_and_ptr_args(self): """Use of std::function with reference or pointer args""" - # used to fail b/c type trimming threw away end ')' together with '*' or '&' + # used to fail b/c type trimming threw away end ')' together with '*' or '&' - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace LambdaAndTemplates { template struct S {}; @@ -630,13 +680,13 @@ def test25_stdfunction_ref_and_ptr_args(self): return callback({}); } }""") - ns = cppyy.gbl.LambdaAndTemplates + ns = cppjit.gbl.LambdaAndTemplates assert ns.f_noref[int](lambda arg: True) assert ns.f_notemplate(lambda arg: True) - # similar/same problem as above - cppyy.cppdef("""\ + # similar/same problem as above + cppjit.cppdef("""\ namespace LambdaAndTemplates { template bool f_nofun(bool (*callback)(const S&)) { @@ -645,10 +695,10 @@ def test25_stdfunction_ref_and_ptr_args(self): assert ns.f_nofun[int](lambda arg: True) - # following used to fail argument conversion + # following used to fail argument conversion assert ns.f[int](lambda arg: True) - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace FuncPtrArrays { typedef struct { double* a0, *a1, *a2, *a3; @@ -662,18 +712,18 @@ def test25_stdfunction_ref_and_ptr_args(self): return; } }""") - ns = cppyy.gbl.FuncPtrArrays + ns = cppjit.gbl.FuncPtrArrays foo = ns.Foo() foo.fnc = ns.bar - foo.fnc # <- this access used to fail + foo.fnc # <- this access used to fail def test26_partial_templates(self): """Deduction of types with partial templates""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ template B partial_template_foo1(B b) { return b; } @@ -688,21 +738,21 @@ def test26_partial_templates(self): B foo2(B b) { return b; } } """) - ns = cppyy.gbl.partial_template + ns = cppjit.gbl.partial_template - assert cppyy.gbl.partial_template_foo1['double', 'int'](17) == 17 - assert cppyy.gbl.partial_template_foo1['double'](17) == 17 + assert cppjit.gbl.partial_template_foo1["double", "int"](17) == 17 + assert cppjit.gbl.partial_template_foo1["double"](17) == 17 - assert cppyy.gbl.partial_template_foo1['double'](17) == 17 - assert cppyy.gbl.partial_template_foo1['double', 'int'](17) == 17 + assert cppjit.gbl.partial_template_foo1["double"](17) == 17 + assert cppjit.gbl.partial_template_foo1["double", "int"](17) == 17 - assert ns.foo1['double', 'int'](17) == 17 - assert ns.foo1['double'](17) == 17 + assert ns.foo1["double", "int"](17) == 17 + assert ns.foo1["double"](17) == 17 - assert ns.foo2['double'](17) == 17 - assert ns.foo2['double', 'int'](17) == 17 + assert ns.foo2["double"](17) == 17 + assert ns.foo2["double", "int"](17) == 17 - cppyy.cppdef("""\ + cppjit.cppdef("""\ template B partial_template_bar1(B b) { return b; } @@ -717,24 +767,24 @@ def test26_partial_templates(self): B bar2(B b) { return b; } }""") - assert cppyy.gbl.partial_template_bar1['double','int'](17) == 17 - assert cppyy.gbl.partial_template_bar1['double'](17) == 17 + assert cppjit.gbl.partial_template_bar1["double", "int"](17) == 17 + assert cppjit.gbl.partial_template_bar1["double"](17) == 17 - assert cppyy.gbl.partial_template_bar2['double'](17) == 17 - assert cppyy.gbl.partial_template_bar2['double','int'](17) == 17 + assert cppjit.gbl.partial_template_bar2["double"](17) == 17 + assert cppjit.gbl.partial_template_bar2["double", "int"](17) == 17 - assert ns.bar1['double','int'](17) == 17 - assert ns.bar1['double'](17) == 17 + assert ns.bar1["double", "int"](17) == 17 + assert ns.bar1["double"](17) == 17 - assert ns.bar2['double'](17) == 17 - assert ns.bar2['double','int'](17) == 17 + assert ns.bar2["double"](17) == 17 + assert ns.bar2["double", "int"](17) == 17 def test27_variadic_constructor(self): """Use of variadic template function as contructor""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace VadiadicConstructor { class Atom { public: @@ -758,18 +808,18 @@ class Atom { mass_type m_m = 0.0; }; }""") - ns = cppyy.gbl.VadiadicConstructor + ns = cppjit.gbl.VadiadicConstructor a = ns.Atom(1567.0) assert a.m_m == 1567.0 - @mark.xfail(condition = IS_MAC, reason = "Fails on OS X ") + @mark.xfail(condition=IS_MAC, reason="Fails on OS X ") def test28_enum_in_constructor(self): """Use of enums in template function as constructor""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace EnumConstructor { struct ST { enum TI { I32 }; @@ -782,20 +832,23 @@ def test28_enum_in_constructor(self): FS(const std::string&, const ST::TI, R, const T&e) {} }; }""") - ns = cppyy.gbl.EnumConstructor + ns = cppjit.gbl.EnumConstructor - assert ns.FS('i', ns.ST.I32, ns.FS.EQ, 10) - assert ns.FS('i', ns.ST.TI.I32, ns.FS.R.EQ, 10) + assert ns.FS("i", ns.ST.I32, ns.FS.EQ, 10) + assert ns.FS("i", ns.ST.TI.I32, ns.FS.R.EQ, 10) - - @mark.xfail(run=False, condition=IS_VALGRIND and IS_LINUX_ARM, reason="Crashes on Valgrind-ARM") + @mark.xfail( + run=False, + condition=IS_VALGRIND and IS_LINUX_ARM, + reason="Crashes on Valgrind-ARM", + ) def test29_function_ptr_as_template_arg(self): """Function pointers as template arguments""" - import cppyy, sys + import cppjit # different templates used to prevent memoization caches resolving calls - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace FPTA { // Function Pointer as Template Argument struct EventId { int fId; }; struct Time { double fSeconds; }; @@ -833,55 +886,54 @@ class Simulator { return EventId{n.fData}; } }""") - ns = cppyy.gbl.FPTA + ns = cppjit.gbl.FPTA def adapt(node): return ns.EventId(node.fData) + adapt.__cpp_name__ = "FPTA::EventId (*)(FPTA::Node&)" - if sys.hexversion < 0x3050000: - def ann_adapt(node): - return ns.EventId(node.fData) - ann_adapt.__annotations__ = {'node': 'FPTA::Node&', 'return': ns.EventId} - def ann_ref_mod(node): - ev_id = ns.EventId(node.fData) - node.fData = 81 - return ev_id - ann_adapt.__annotations__ = {'node': 'FPTA::Node&', 'return': ns.EventId} - else: - oldp = sys.path[:] - sys.path.append('.') - from templ_args_funcs import ann_adapt, ann_ref_mod - sys.path = oldp + def ann_adapt(node: "FPTA::Node&") -> ns.EventId: # noqa: F722 + return ns.EventId(node.fData) + + def ann_ref_mod(node: "FPTA::Node&") -> ns.EventId: # noqa: F722 + ev_id = ns.EventId(node.fData) + node.fData = 81 + return ev_id s = ns.Simulator() # based on reflected __cpp_name__ - assert s.Schedule1(ns.Time(1.0), ns.cpp_adapt, ns.Node(42)).fId == 42 - assert s.Schedule2['FPTA::Node&'](ns.Time(1.0), ns.cpp_adapt, ns.Node(37)).fId == 37 + assert s.Schedule1(ns.Time(1.0), ns.cpp_adapt, ns.Node(42)).fId == 42 + assert ( + s.Schedule2["FPTA::Node&"](ns.Time(1.0), ns.cpp_adapt, ns.Node(37)).fId + == 37 + ) # based on explicit __cpp_name__ - assert s.Schedule3(ns.Time(1.0), adapt, ns.Node(57)).fId == 57 - assert s.Schedule4['FPTA::Node&'](ns.Time(1.0), adapt, ns.Node(77)).fId == 77 + assert s.Schedule3(ns.Time(1.0), adapt, ns.Node(57)).fId == 57 + assert s.Schedule4["FPTA::Node&"](ns.Time(1.0), adapt, ns.Node(77)).fId == 77 # based on __annotations__ (p3.5 and later) - assert s.Schedule5(ns.Time(1.0), ann_adapt, ns.Node(25)).fId == 25 - assert s.Schedule6['FPTA::Node&'](ns.Time(1.0), ann_adapt, ns.Node(88)).fId == 88 + assert s.Schedule5(ns.Time(1.0), ann_adapt, ns.Node(25)).fId == 25 + assert ( + s.Schedule6["FPTA::Node&"](ns.Time(1.0), ann_adapt, ns.Node(88)).fId == 88 + ) # verify that the node is correctly modified tn = ns.Node(25) - assert s.Schedule5(ns.Time(1.0), ann_ref_mod, tn).fId == 25 + assert s.Schedule5(ns.Time(1.0), ann_ref_mod, tn).fId == 25 assert tn.fData == 81 tn = ns.Node(88) - assert s.Schedule6['FPTA::Node&'](ns.Time(1.0), ann_ref_mod, tn).fId == 88 + assert s.Schedule6["FPTA::Node&"](ns.Time(1.0), ann_ref_mod, tn).fId == 88 assert tn.fData == 81 def test30_mix_and_match(self): """Mix of (non-)templated across inheritance""" - import cppyy + import cppjit - cppyy.cppdef("""namespace MixNMatch { + cppjit.cppdef("""namespace MixNMatch { class NonTemplated { public: double& operator[](int idx) { return fPayLoad; } @@ -896,17 +948,17 @@ class Templated: public NonTemplated { template double& operator[](int idx) { return fPayLoad; } }; }""") - ns = cppyy.gbl.MixNMatch + ns = cppjit.gbl.MixNMatch - ns.Templated() # used to crash + ns.Templated() # used to crash @mark.xfail(run=False, condition=IS_CLING, reason="Crashed with Cling") def test31_ltlt_in_template_name(self): """Verify lookup of template names with << in the name""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace TestSomeLut { template struct Lut { @@ -938,40 +990,40 @@ def test31_ltlt_in_template_name(self): std::array gLutData8; """) - ns = cppyy.gbl.TestSomeLut + ns = cppjit.gbl.TestSomeLut X, Y = 14, 15 lut = ns.Lut[int, X, Y]() assert lut - assert lut.size() == (1< class F; @@ -998,17 +1050,29 @@ class V; using vvv = F,V,V)>; }""") - ns = cppyy.gbl.parenthesis - - for t in ['i','v', - 'ii', 'iv', 'vi', 'vv', - 'iii', 'ivi', 'vii', 'vvi', - 'iiv', 'ivv', 'viv', 'vvv']: + ns = cppjit.gbl.parenthesis + + for t in [ + "i", + "v", + "ii", + "iv", + "vi", + "vv", + "iii", + "ivi", + "vii", + "vvi", + "iiv", + "ivv", + "viv", + "vvv", + ]: assert getattr(ns, t) - # second, more elaborate set + # second, more elaborate set - cppyy.cppdef("""\ + cppjit.cppdef("""\ #include #include @@ -1093,27 +1157,55 @@ class TNaVU; n = 0 results = {} - types = ['fi', 'fv', - 'fii', 'fiv', 'fvi', 'fvv', - 'fiii', 'fivi', 'fvii', 'fvvi', - 'fiiv', 'fivv', 'fviv', 'fvvv'] - - for v in ['TNaV', 'TNaN::TNaV', 'TNaVA', 'TNaN::TNaVA', 'TNaVU', 'TNaN::TNaVU', 'std::vector']: - for f in ['TNaF', 'TNaFn', 'TNaN::TNaF', 'TNaN::TNaFn', 'std::function']: - for i in ['TNaI', 'TNaN::TNaI', 'TNaN2::TNaI', 'int']: + types = [ + "fi", + "fv", + "fii", + "fiv", + "fvi", + "fvv", + "fiii", + "fivi", + "fvii", + "fvvi", + "fiiv", + "fivv", + "fviv", + "fvvv", + ] + + for v in [ + "TNaV", + "TNaN::TNaV", + "TNaVA", + "TNaN::TNaVA", + "TNaVU", + "TNaN::TNaVU", + "std::vector", + ]: + for f in [ + "TNaF", + "TNaFn", + "TNaN::TNaF", + "TNaN::TNaFn", + "std::function", + ]: + for i in ["TNaI", "TNaN::TNaI", "TNaN2::TNaI", "int"]: n += 1 - cppyy.cppdef(cpp.format(v=v, f=f, i=i, n=n)) + cppjit.cppdef(cpp.format(v=v, f=f, i=i, n=n)) for t in types: - run_n = getattr(cppyy.gbl, 'TNaRun_%d' % n) + run_n = getattr(cppjit.gbl, "TNaRun_%d" % n) getattr(run_n, t) - @mark.xfail(run = False, condition=IS_MAC and IS_CLING, reason="Crashes on OS X + Cling") + @mark.xfail( + run=False, condition=IS_MAC and IS_CLING, reason="Crashes on OS X + Cling" + ) def test33_using_template_argument(self): """`using` type as template argument""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" namespace UsingPtr { struct Test {}; using testptr = Test*; @@ -1122,29 +1214,32 @@ def test33_using_template_argument(self): bool testfun(T x) { return !(bool)x; } }""") - ns = cppyy.gbl.UsingPtr + ns = cppjit.gbl.UsingPtr - assert ns.testfun["testptr"](cppyy.bind_object(cppyy.nullptr, ns.Test)) + assert ns.testfun["testptr"](cppjit.bind_object(cppjit.nullptr, ns.Test)) # TODO: raises TypeError; the problem is that the type is resolved # from UsingPtr::Test*const& to UsingPtr::Test*& (ie. `const` is lost) - assert ns.testfun["UsingPtr::testptr"](cppyy.nullptr) + assert ns.testfun["UsingPtr::testptr"](cppjit.nullptr) - assert ns.testptr.__name__ == "Test" + assert ns.testptr.__name__ == "Test" assert ns.testptr.__cpp_name__ == "UsingPtr::Test*" - assert cppyy.gbl.std.vector[ns.Test] + assert cppjit.gbl.std.vector[ns.Test] assert ns.testptr - assert cppyy.gbl.std.vector[ns.testptr] + assert cppjit.gbl.std.vector[ns.testptr] - @mark.xfail(condition=IS_MAC, run=IS_CLANG_REPL, reason="fails on OSX & crashes with cling") + @mark.xfail( + condition=IS_MAC, run=IS_CLANG_REPL, reason="fails on OSX & crashes with cling" + ) def test34_cstring_template_argument(self): """`const char*` use over std::string""" - import cppyy import ctypes - cppyy.cppdef(r"""\ + import cppjit + + cppjit.cppdef(r"""\ namespace CStringTemplateArg { template std::string stringify(Args&&... args) { @@ -1153,18 +1248,18 @@ def test34_cstring_template_argument(self): return o.str(); } }""") - ns = cppyy.gbl.CStringTemplateArg + ns = cppjit.gbl.CStringTemplateArg - assert type(ns.stringify("Alice")) == cppyy.gbl.std.string - assert ns.stringify("Alice", "Bob") == "Alice Bob " - assert ns.stringify(1, 2, 3) == "1 2 3 " - assert ns.stringify["const char*"]("Aap") == "Aap " + assert type(ns.stringify("Alice")) == cppjit.gbl.std.string + assert ns.stringify("Alice", "Bob") == "Alice Bob " + assert ns.stringify(1, 2, 3) == "1 2 3 " + assert ns.stringify["const char*"]("Aap") == "Aap " assert ns.stringify(ctypes.c_char_p(bytes("Noot", "ascii"))) == "Noot " def test35_templated_callbacks(self): - import cppyy - - cppyy.cppdef( + import cppjit + + cppjit.cppdef( r""" std::string foo() { return "foo!";} @@ -1183,17 +1278,25 @@ def test35_templated_callbacks(self): } """ ) - - assert cppyy.gbl.dataframe_define_mock(cppyy.gbl.foo) == "foo!" - assert cppyy.gbl.dataframe_define_mock(cppyy.gbl.bar, 42, 11.11) == "bar(42, 11.110000)" - assert cppyy.gbl.dataframe_define_mock(cppyy.gbl.baz["int", "double"], 33, 101.101, "hello") == "baz(33, 101.101000, \"hello\")" + + assert cppjit.gbl.dataframe_define_mock(cppjit.gbl.foo) == "foo!" + assert ( + cppjit.gbl.dataframe_define_mock(cppjit.gbl.bar, 42, 11.11) + == "bar(42, 11.110000)" + ) + assert ( + cppjit.gbl.dataframe_define_mock( + cppjit.gbl.baz["int", "double"], 33, 101.101, "hello" + ) + == 'baz(33, 101.101000, "hello")' + ) @mark.xfail(condition=IS_MAC, reason="Conversion fails in OSX") def test36_templated_callbacks(self): - import cppyy - from cppyy import gbl + import cppjit + from cppjit import gbl - cppyy.cppdef( + cppjit.cppdef( r""" struct ERDataFrame { size_t rows = 0; @@ -1224,19 +1327,15 @@ def test36_templated_callbacks(self): """ ) - class CallBackError(Exception): pass - def callback(x: int) -> float: return x * 2.0 - def raise_error() -> float: raise CallBackError("called raise_error") - o = gbl.ERDataFrame() assert o.rows == 0 o = o.Define("col1", gbl.get_one) @@ -1257,17 +1356,17 @@ def raise_error() -> float: # with raises(CallBackError): # o.Define("errA", raise_error) # FIXME: raises TypeError for failure in overload selection - + # with raises(gbl.std.runtime_error): # o.Define("errB", gbl.throw_error) # FIXME: raises TypeError for failure in overload selection - + assert o.rows == 5 def test37_enum_template_argument_function(self): - import cppyy - from cppyy import gbl + import cppjit + from cppjit import gbl - cppyy.cppdef( + cppjit.cppdef( r""" enum What { NO, YES }; @@ -1288,13 +1387,13 @@ def test37_enum_template_argument_function(self): assert gbl.get[gbl.What.NO]() == 0 assert gbl.get[gbl.What.YES]() == 1 - + def test38_constructor_implicit_conversion(self): """Implicit conversion to call a templated constructor""" - import cppyy + import cppjit - cppyy.cppdef("""\ + cppjit.cppdef("""\ namespace ConstructorImplicitConversion { struct IntWrapper { IntWrapper(int i) : m_i(i) {} @@ -1307,17 +1406,17 @@ def test38_constructor_implicit_conversion(self): int m_a = 0; }; }""") - ns = cppyy.gbl.ConstructorImplicitConversion + ns = cppjit.gbl.ConstructorImplicitConversion a = ns.S(1, 2) assert a.m_a == 1 def test39_monkey_patching_template_proxy(self): """Monkey patching Template Proxy""" - import cppyy - from cppyy import gbl + import cppjit + from cppjit import gbl - cppyy.cppdef(r""" + cppjit.cppdef(r""" struct MyMonkey { template bool m(std::vector v) { return true; } @@ -1337,14 +1436,16 @@ def test39_monkey_patching_template_proxy(self): def test40_instantiation_failure_error_message(self): """Rejected instantiation names the template, not garbage""" - import cppyy + import cppjit - cppyy.cppdef("namespace errpath { template struct Buf { int tag; }; }") + cppjit.cppdef( + "namespace errpath { template struct Buf { int tag; }; }" + ) # Check that the failed instantiation error message contains the # correct template name. with raises(TypeError) as exc: - cppyy.gbl.errpath.Buf["int"] + cppjit.gbl.errpath.Buf["int"] msg = str(exc.value) assert "errpath::Buf" in msg assert "" not in msg @@ -1354,98 +1455,110 @@ def test40_instantiation_failure_error_message(self): class TestTEMPLATED_TYPEDEFS: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.templates = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.templates = cppjit.load_reflection_info(cls.test_dct) @mark.xfail def test01_using(self): """Test presence and validity of using typedefs""" - import cppyy + import cppjit - tct = cppyy.gbl.TemplatedTypedefs.DerivedWithUsing - dum = cppyy.gbl.TemplatedTypedefs.SomeDummy + tct = cppjit.gbl.TemplatedTypedefs.DerivedWithUsing + dum = cppjit.gbl.TemplatedTypedefs.SomeDummy assert tct[int, dum, 4].vsize == 4 assert tct[int, dum, 8].vsize == 8 in_type = tct[int, dum, 4].in_type - assert 'in_type' in dir(tct[int, dum, 4]) + assert "in_type" in dir(tct[int, dum, 4]) - assert in_type.__name__ == 'in_type' - assert in_type.__cpp_name__ == 'TemplatedTypedefs::DerivedWithUsing::in_type' + assert in_type.__name__ == "in_type" + assert ( + in_type.__cpp_name__ + == "TemplatedTypedefs::DerivedWithUsing::in_type" + ) in_type_tt = tct[int, dum, 4].in_type_tt - assert 'in_type_tt' in dir(tct[int, dum, 4]) + assert "in_type_tt" in dir(tct[int, dum, 4]) - assert in_type_tt.__name__ == 'in_type_tt' - assert in_type_tt.__cpp_name__ == 'TemplatedTypedefs::DerivedWithUsing::in_type_tt' + assert in_type_tt.__name__ == "in_type_tt" + assert ( + in_type_tt.__cpp_name__ + == "TemplatedTypedefs::DerivedWithUsing::in_type_tt" + ) def test02_mapped_type_as_internal(self): """Test that mapped types can be used as builtin""" - import cppyy + import cppjit - tct = cppyy.gbl.TemplatedTypedefs.DerivedWithUsing - dum = cppyy.gbl.TemplatedTypedefs.SomeDummy + tct = cppjit.gbl.TemplatedTypedefs.DerivedWithUsing + dum = cppjit.gbl.TemplatedTypedefs.SomeDummy - for argname in ['short', 'unsigned short', 'int']: + for argname in ["short", "unsigned short", "int"]: in_type = tct[argname, dum, 4].in_type assert issubclass(in_type, int) assert in_type(13) == 13 - assert 2*in_type(42) - 84 == 0 + assert 2 * in_type(42) - 84 == 0 - for argname in ['unsigned int', 'long', 'unsigned long']:# TODO: 'long long', 'unsigned long long' + for argname in [ + "unsigned int", + "long", + "unsigned long", + ]: # TODO: 'long long', 'unsigned long long' in_type = tct[argname, dum, 4].in_type assert issubclass(in_type, pylong) assert in_type(13) == 13 - assert 2*in_type(42) - 84 == 0 + assert 2 * in_type(42) - 84 == 0 - for argname in ['float', 'double', 'long double']: + for argname in ["float", "double", "long double"]: in_type = tct[argname, dum, 4].in_type assert issubclass(in_type, float) - assert in_type(13) == 13. - assert 2*in_type(42) - 84. == 0. + assert in_type(13) == 13.0 + assert 2 * in_type(42) - 84.0 == 0.0 - raises(TypeError, tct.__getitem__, 'gibberish', dum, 4) + raises(TypeError, tct.__getitem__, "gibberish", dum, 4) def test03_mapped_type_as_template_arg(self): """Test that mapped types can be used as template arguments""" - import cppyy + import cppjit - tct = cppyy.gbl.TemplatedTypedefs.DerivedWithUsing - dum = cppyy.gbl.TemplatedTypedefs.SomeDummy + tct = cppjit.gbl.TemplatedTypedefs.DerivedWithUsing + dum = cppjit.gbl.TemplatedTypedefs.SomeDummy - in_type = tct['unsigned int', dum, 4].in_type - assert tct['unsigned int', dum, 4] is tct[in_type, dum, 4] + in_type = tct["unsigned int", dum, 4].in_type + assert tct["unsigned int", dum, 4] is tct[in_type, dum, 4] - in_type = tct['long double', dum, 4].in_type - assert tct['long double', dum, 4] is tct[in_type, dum, 4] - assert tct['double', dum, 4] is not tct[in_type, dum, 4] + in_type = tct["long double", dum, 4].in_type + assert tct["long double", dum, 4] is tct[in_type, dum, 4] + assert tct["double", dum, 4] is not tct[in_type, dum, 4] def test04_type_deduction(self): """Usage of type reducer""" - import cppyy + import cppjit - cppyy.cppdef(""" + cppjit.cppdef(""" template struct DeductTest_Wrap { static auto whatis(T t) { return t; } }; """) - w = cppyy.gbl.DeductTest_Wrap[int]() + w = cppjit.gbl.DeductTest_Wrap[int]() three = w.whatis(3) assert three == 3 def test05_type_deduction_and_extern(self): """Usage of type reducer with extern template""" - import cppyy import sys - cppyy.cppdef("""\ + import cppjit + + cppjit.cppdef("""\ namespace FailedTypeDeducer { template class A { @@ -1456,20 +1569,20 @@ class A { extern template class A; }""") - if sys.platform != 'darwin': # feature disabled - assert cppyy.gbl.FailedTypeDeducer.A[int]().result() == 42 - assert cppyy.gbl.FailedTypeDeducer.A['double']().result() == 5. + if sys.platform != "darwin": # feature disabled + assert cppjit.gbl.FailedTypeDeducer.A[int]().result() == 42 + assert cppjit.gbl.FailedTypeDeducer.A["double"]().result() == 5.0 - # FailedTypeDeducer::B is defined in the templates.h header - assert cppyy.gbl.FailedTypeDeducer.B['double']().result() == 5. - assert cppyy.gbl.FailedTypeDeducer.B[int]().result() == 5 + # FailedTypeDeducer::B is defined in the templates.h header + assert cppjit.gbl.FailedTypeDeducer.B["double"]().result() == 5.0 + assert cppjit.gbl.FailedTypeDeducer.B[int]().result() == 5 def test06_type_deduction_and_scoping(self): """Possible shadowing of types used in template construction""" - import cppyy + import cppjit - cppyy.cppdef(r""" + cppjit.cppdef(r""" namespace ShadowX { class ShadowC {}; } @@ -1484,33 +1597,37 @@ class ShadowD {}; } }""") - ns = cppyy.gbl.ShadowY.ShadowZ - C = cppyy.gbl.ShadowX.ShadowC + ns = cppjit.gbl.ShadowY.ShadowZ + C = cppjit.gbl.ShadowX.ShadowC - # TODO: This should error out - # raises(TypeError, ns.f.__getitem__(C.__cpp_name__)) - # lookup of shadowed class no longer fails, but gives us the same template proxy to f() + # TODO: This should error out + # raises(TypeError, ns.f.__getitem__(C.__cpp_name__)) + # lookup of shadowed class no longer fails, but gives us the same template proxy to f() assert ns.f.__getitem__(C.__cpp_name__) == ns.f - # direct instantiation now succeeds + # direct instantiation now succeeds ns.f[C]() - ns.f['::'+C.__cpp_name__]() + ns.f["::" + C.__cpp_name__]() + @mark.skipif((IS_MAC and IS_CLING), reason="setup class fails with OS X cling") class TestTEMPLATE_TYPE_REDUCTION: def setup_class(cls): cls.test_dct = test_dct - import cppyy - cls.templates = cppyy.load_reflection_info(cls.test_dct) + import cppjit + + cls.templates = cppjit.load_reflection_info(cls.test_dct) def test01_reduce_binary(self): """Squash template expressions for binary operations (like in gmpxx)""" - import cppyy + import cppjit - e1 = cppyy.gbl.TypeReduction.Expr[int]() - e2 = cppyy.gbl.TypeReduction.Expr[int]() + e1 = cppjit.gbl.TypeReduction.Expr[int]() + e2 = cppjit.gbl.TypeReduction.Expr[int]() - cppyy.py.add_type_reducer('TypeReduction::BinaryExpr', 'TypeReduction::Expr') + cppjit.py.add_type_reducer( + "TypeReduction::BinaryExpr", "TypeReduction::Expr" + ) - assert type(e1+e2) == cppyy.gbl.TypeReduction.Expr[int] + assert type(e1 + e2) == cppjit.gbl.TypeReduction.Expr[int]