diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 7093effc8fe0a..4ab15ee03420e 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -36,6 +36,7 @@
/tmva/ @lmoneta
/tree/ @pcanal
/tutorials/ @couet
+/tree/dataframe @dpiparo @bluehood
# Projects that span over several code modules:
/bindings/r/ @omazapa
@@ -49,11 +50,8 @@
/core/multiproc/ @gganis
/graf2d/qt/ @bellenot
/graf2d/cocoa/ @TimurP
-/graf2d/ios/ @TimurP
/graf3d/eve/ @osschar
/net/http/ @linev
-/tree/treeplayer/**/TDataFrame.* @dpiparo @bluehood
-/tree/treeplayer/**/TDF* @dpiparo @bluehood
/cmake/ @amadio
*.cmake @amadio
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4c760d0fdb979..a3a0f59925297 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -426,8 +426,7 @@ if(NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_INSTALL_PREFIX)
PATTERN "rootd.xinetd" EXCLUDE
PATTERN "proofd.xinetd" EXCLUDE
PATTERN "root.mimes" EXCLUDE
- PATTERN "cmake" EXCLUDE
- PATTERN "http" EXCLUDE )
+ PATTERN "cmake" EXCLUDE )
install(DIRECTORY fonts/ DESTINATION ${CMAKE_INSTALL_FONTDIR})
install(DIRECTORY icons/ DESTINATION ${CMAKE_INSTALL_ICONDIR})
install(DIRECTORY macros/ DESTINATION ${CMAKE_INSTALL_MACRODIR})
diff --git a/README.md b/README.md
index b0ba178d2c778..c97426c7e81b6 100644
--- a/README.md
+++ b/README.md
@@ -34,8 +34,10 @@ acquisition, simulation and data analysis systems.
|--------|------------|---------|
| master | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-incremental-master/) | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-nightly-master/) |
| master-noimt | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-incremental-master-noimt/) | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-nightly-master-noimt/) |
-| v5-34-00-patches | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-incremental-v5-34-00-patches/) | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-nightly-v5-34-00-patches/) |
+| v6-14-00-patches | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-incremental-v6-14-00-patches/) | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-nightly-v6-14-00-patches/) |
+| v6-12-00-patches | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-incremental-v6-12-00-patches/) | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-nightly-v6-12-00-patches/) |
| v6-10-00-patches | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-incremental-v6-10-00-patches/) | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-nightly-v6-10-00-patches/) |
+| v5-34-00-patches | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-incremental-v5-34-00-patches/) | [](https://epsft-jenkins.cern.ch/view/ROOT/job/root-nightly-v5-34-00-patches/) |
## Cite
We are [](https://zenodo.org/badge/latestdoi/10994345)
diff --git a/README/CREDITS b/README/CREDITS
index 7ba31a8c382f0..71ac2d8b48689 100644
--- a/README/CREDITS
+++ b/README/CREDITS
@@ -531,7 +531,7 @@ D: Integrating Minuit2 in Roofit and adding support for MPI
N: Wim Lavrijsen
E: WLavrijsen@lbl.gov
-D: PyRoot package
+D: PyROOT package, Cppyy package
N: Kerry Lee
E: kerry.t.lee@nasa.gov
@@ -621,7 +621,7 @@ D: pioneer on many fronts, installation, support
N: Pere Mato
E: Pere.Mato@cern.ch
-D: PyRoot package
+D: PyROOT package
D: cmake build system
N: Richard Maunder
@@ -917,7 +917,7 @@ D: Paint3DAlgorithms used by the LEGO and SURF options
N: Enric Tejedor
E: enric.tejedor@cern.ch
-D: IO, Parallelisation, Jupyter integration, TDataFrame
+D: IO, Parallelisation, Jupyter integration, TDataFrame, PyROOT
N: Jan Therhaag
E: therhaag@users.sourceforge.net
diff --git a/README/ReleaseNotes/empty.md b/README/ReleaseNotes/empty.md
index a204982959ee5..3e0a56b9cfe09 100644
--- a/README/ReleaseNotes/empty.md
+++ b/README/ReleaseNotes/empty.md
@@ -8,7 +8,7 @@ ROOT version 6.??/00 is scheduled for release in ???.
For more information, see:
-[http://root.cern.ch](http://root.cern.ch)
+[http://root.cern](http://root.cern)
The following people have contributed to this new version:
diff --git a/README/ReleaseNotes/v614/index.md b/README/ReleaseNotes/v614/index.md
index 41230dc8a44b5..6341511c5f79b 100644
--- a/README/ReleaseNotes/v614/index.md
+++ b/README/ReleaseNotes/v614/index.md
@@ -13,28 +13,44 @@ For more information, see:
The following people have contributed to this new version:
+ Kim Albertsson, CERN/EP-ADP-OS,\
Guilherme Amadio, CERN/SFT,\
Bertrand Bellenot, CERN/SFT,\
Brian Bockelman, UNL,\
Rene Brun, CERN/SFT,\
Philippe Canal, FNAL,\
- David Clark, ANL (SULI),\
Olivier Couet, CERN/SFT,\
Gerri Ganis, CERN/SFT,\
Andrei Gheata, CERN/SFT,\
Enrico Guiraud, CERN/SFT,\
+ Raphael Isemann, Chalmers Univ. of Tech.,\
+ Vladimir Ilievski, GSOC 2017,\
Sergey Linev, GSI,\
- Timur Pocheptsov, CERN/SFT,\
Pere Mato, CERN/SFT,\
Lorenzo Moneta, CERN/SFT,\
Axel Naumann, CERN/SFT,\
Danilo Piparo, CERN/SFT,\
Fons Rademakers, CERN/SFT,\
Enric Tejedor Saavedra, CERN/SFT,\
- Peter van Gemmeren, ANL,\
- Vassil Vassilev, Princeton/CMS,\
Oksana Shadura, UNL,\
- Wouter Verkerke, NIKHEF/Atlas, RooFit
+ Saurav Shekhar, GSOC 2017,\
+ Xavier Valls Pla, UJI, CERN/SFT,\
+ Vassil Vassilev, Princeton/CMS,\
+ Wouter Verkerke, NIKHEF/Atlas, RooFit,\
+ Stefan Wunsch, CERN/SFT, \
+ Zhe Zhang, UNL
+
+## Important Notice
+
+The default compression algorithm used when writing ROOT files has been updated to use LZ4 in particular to improve read (decompression) performance. You can change this default for each file through (for example) the `TFile constructor` or `TFile::SetCompressionAlgorithm`.
+
+It should be noted that ROOT files written with LZ4 compression can not be read with older release of ROOT. Support for LZ4 was however back-ported to the patch branches of previous releases and the following tags (and later release in the same patch series) can read ROOT files written with LZ4 compression:
+
+* v5.34/38
+* v6.08/06 [not yet released]
+* v6.10/08
+* v6.12/02
+
## Removed interfaces
@@ -45,16 +61,30 @@ The following people have contributed to this new version:
- In `TClingCallFunc`, support r-value reference parameters. This paves the way for the corresponding support in PyROOT (implemented now in the latest Cppyy).
- Included the new TSequentialExecutor in ROOT, sharing the interfaces of TExecutor.This should improve code economy when providing a fallback for TThreadExecutor/TProcessExecutor.
+### Thread safety
+ - Resolved several race conditions, dead-locks, performance and order of initialization/destruction issues still lingering because of or despite the new read-write lock mechanism.
+
+## Interpreter
+
+ - Enabled use of multi-threaded code from the interpreter.
+ - Previouslyl multi-threaded code could be run from the interpreter as long as the call starting the threada was the same code that initialized the ROOT global lock, any other uses, including attempting to run the same code a second time in the same session would lead to a dead lock (if any other thread attempted to take on the ROOT lock).
+ - The interpreter now suspend the ROOT lock (which is taken to protect the interpreter global state) during user code execution.
+
## I/O Libraries
- LZ4 (with compression level 4) is now the default compression algorithm for new ROOT files (LZ4 is lossless data compression algorithm that is focused on compression and decompression speed, while in ROOT case providing benefit in faster decompression at the price of a bit worse compression ratio comparing to ZLIB)
+ - If two or more files have an identical streamer info record, this is only treated once therewith avoiding to take the global lock.
+ - Allow writing temporary objects (with same address) in the same TBuffer(s). A new flag to TBuffer*::WriteObject allows to skip the mechanism that prevent the 2nd streaming of an object. This allows the (re)use of temporary objects to store different data in the same buffer.
+ - Reuse branch proxies internally used by TTreeReader{Value,Array} therewith increasing performance when having multiple readers pointing to the same branch.
- Implement reading of objects data from JSON
- Provide TBufferJSON::ToJSON() and TBufferJSON::FromJSON() methods
- Provide TBufferXML::ToXML() and TBufferXML::FromXML() methods
- Converts NaN and Infinity values into null in JSON, there are no other direct equivalent
## TTree Libraries
+ - Enable the TTreeCache by default of `TTree::Draw`, `TTreeReader` and `RDataFrame`
+ - Significant enhancement in the `TTreeCache` filling algorithm to increase robustness in case of oddly clustered `TTree` and under provisioned cache size. See the [merge request](https://github.com/root-project/root/pull/1960) for more details.
- Proxies are now properly re-used when multiple TTreeReader{Value,Array}s are associated to a single branch. Deserialisation is therefore performed once. This is an advantage for complex TDataFrame graphs.
- - Add TBranch::BackFill to allowing the addition of new branches to an existing tree and keep the new basket clustered in the same way as the rest of the TTree. Use with the following pattern,
+ - Add TBranch::BackFill to allow the addition of new branches to an existing tree and keep the new basket clustered in the same way as the rest of the TTree. Use with the following pattern,
make sure to to call BackFill for the same entry for all the branches consecutively:
```
for(auto e = 0; e < tree->GetEntries(); ++e) { // loop over entries.
@@ -66,8 +96,16 @@ The following people have contributed to this new version:
```
Since we loop over all the branches for each new entry all the baskets for a cluster are consecutive in the file.
-### TDataFrame
- - Histograms and profiles returned by TDataFrame (e.g. by a Histo1D action) are now not associated to a ROOT directory (their fDirectory is a nullptr).
+### RDataFrame (formerly TDataFrame)
+#### Behaviour, interface and naming changes
+ - `TDataFrame` and `TDataSource` together with their federation of classes have been renamed according to the coding conventions for new interfaces and extracted from the `Experimental` namespace: they can now be found in the ROOT namespace and they are called `ROOT::RDataFrame` and `ROOT::RDataSource`.
+ - `ROOT::Experimental::TDF::TResultProxy` has been renamed to `ROOT::RDF::RResultPtr`.
+ - `Report` now behaves identically to all other actions: it executes lazily and returns a `RResultPtr` (see the `New features` section for more details).
+ - `Snapshot` now returns a `RResultPtr` like all other actions: specifically, this is a pointer to a new `RDataFrame` which will run on the snapshotted dataset.
+ - `RDataFrame` has been removed from tree/treeplayer and put in its own package, tree/dataframe. The library where this code can be found is `libROOTDataFrame`. This new library is included in the list provided by `root-config --libs`.
+ - The `TArrayBranch` class has been removed and replaced by the more powerful `RVec` (see the `New features` section for more details).
+ - All `RDataFrame` tutorials are now prefixed with `df` rather than `tdf`.
+ - Histograms and profiles returned by RDataFrame (e.g. by a Histo1D action) are now not associated to a ROOT directory (their fDirectory is a nullptr).
The following still works as expected:
```
auto h = tdf.Histo1D("x");
@@ -110,12 +148,30 @@ Since we loop over all the branches for each new entry all the baskets for a clu
## Histogram Libraries
- - Per object statsoverflow flag has been added. This change is required to prevent non reproducible behaviours in a multithreaded environments. For example, if several threads change the `TH1::fgStatOverflows` flag and fill histograms, the behaviour will be undefined.
+ - Per object statsoverflow flag has been added. This change is required to prevent non reproducible behaviours in a multithreaded environments. For example, if several threads change the
+ `TH1::fgStatOverflows` flag and fill histograms, the behaviour will be undefined.
+ - A fix has been added in resetting the statistics of histograms with label. The bug was causing the histogram entries to be set as zero and this was making failing the merging of those
+ histogram (see ROOT-9336).
## Math Libraries
+
## RooFit Libraries
+- A fix has been added in the component selection, which is used for plotting simultaneous models. See [PR #2033](https://github.com/root-project/root/pull/2033).
+
+## TMVA Library
+
+#### New Deep Learning Module
+
+ - TMVA contains a new set of Deep Learning classes ( `MethodDL` ), with support, in addition to dense layer, also convolutional and recurrent layer.
+
+#### Other New TMVA Features
+
+- Support for Parallelization of BDT using Multi-Threads
+- Several improvements in Cross Validation including support for Multi-Process cross-validation running.
+
+
## 2D Graphics Libraries
- `TMultiGraph::GetHistogram` now works even if the multigraph is not drawn. Make sure
it never returns a null pointer.
@@ -167,6 +223,7 @@ Since we loop over all the branches for each new entry all the baskets for a clu
- Make EnableImplicitMT no-op if IMT is already on
- Decompress `TTreeCache` in parallel if IMT is on (upgrade of the `TTreeCacheUnzip` class).
- In `TTreeProcessorMT` delete friend chains after the main chain to avoid double deletes.
+ - If IMT is enabled, the multithreaded execution of the fit respects the number of threads IMT has been initialized with.
## Language Bindings
@@ -233,48 +290,28 @@ Upgrade JSROOT to v5.4.1. Following new features implemented:
## Build System and Configuration
-CMake exported targets now have the `INTERFACE_INCLUDE_DIRECTORIES` property set
-([ROOT-8062](https://sft.its.cern.ch/jira/browse/ROOT-8062)).
-
-The `-fPIC` compile flag is no longer propagated to dependent projects via
-`CMAKE_CXX_FLAGS` ([ROOT-9212](https://sft.its.cern.ch/jira/browse/ROOT-9212)).
-
-Several builtins have updated versions:
-
-- OpenSSL was updated from 1.0.2d to 1.0.2.o (latest lts release,
- [ROOT-9359](https://sft.its.cern.ch/jira/browse/ROOT-9359))
-- Davix was updated from 0.6.4 to 0.6.7 (support for OpenSSL 1.1,
- [ROOT-9353](https://sft.its.cern.ch/jira/browse/ROOT-9353))
-- Vdt has been updated from 0.3.9 to 0.4.1 (includes new atan function)
-- XRootd has been updated from 4.6.1 to 4.8.2 (for GCC 8.x support)
-- Builtin TBB can now be used on Windows
-- xxHash and LZ4 have been separated so that a system version of LZ4
- can be used even if it does not include xxHash headers
- ([ROOT-9099](https://sft.its.cern.ch/jira/browse/ROOT-9099))
-
-In addition, several updates have been made to fix minor build system issues,
-such as not checking for external packages if their builtin is turned off, or
-checking for packages even when the respective option is disabled
-([ROOT-8806](https://sft.its.cern.ch/jira/browse/ROOT-8806),
-[ROOT-9190](https://sft.its.cern.ch/jira/browse/ROOT-9190),
-[ROOT-9315](https://sft.its.cern.ch/jira/browse/ROOT-9315),
-[ROOT-9385](https://sft.its.cern.ch/jira/browse/ROOT-9385)).
-
-The `python3` option to CMake has been removed
-([ROOT-9033](https://sft.its.cern.ch/jira/browse/ROOT-9033),
-[ROOT-9143](https://sft.its.cern.ch/jira/browse/ROOT-9143)). Python support is
-enabled by default. To configure ROOT to use specific Python versions, there is
-a new option called `python_version`. This is how to configure ROOT and Python
-for the common use cases:
-
-* Use the default Python interpreter:
- - `-Dpython=ON` (default)
-* Search only for Python 2.x or only 3.x:
- - `-Dpython_version=2` or `-Dpython_version=3`
-* Use a specific version of Python from `$PATH`:
- - `-Dpython_version=2.7` or `-Dpython_version=3.5`
-* Use a specific Python interpreter, whatever the version:
- - `-DPYTHON_EXECUTABLE=/usr/local/bin/python`
+ - ROOT can now be built against an externally built llvm and clang (llvm can be used unpatched, clang still require ROOT specific patches). The options are builtin_llvm and builtin_clang both defaulting to ON.
+ - Update RConfigure.h with R__HAS__VDT if the package is found/builtin
+ - CMake exported targets now have the `INTERFACE_INCLUDE_DIRECTORIES` property set ([ROOT-8062](https://sft.its.cern.ch/jira/browse/ROOT-8062)).
+ - The `-fPIC` compile flag is no longer propagated to dependent projects via `CMAKE_CXX_FLAGS` ([ROOT-9212](https://sft.its.cern.ch/jira/browse/ROOT-9212)).
+ - Several builtins have updated versions:
+ - OpenSSL was updated from 1.0.2d to 1.0.2.o (latest lts release, [ROOT-9359](https://sft.its.cern.ch/jira/browse/ROOT-9359))
+ - Davix was updated from 0.6.4 to 0.6.7 (support for OpenSSL 1.1, [ROOT-9353](https://sft.its.cern.ch/jira/browse/ROOT-9353))
+ - Vdt has been updated from 0.3.9 to 0.4.1 (includes new atan function)
+ - XRootd has been updated from 4.6.1 to 4.8.2 (for GCC 8.x support)
+ - Builtin TBB can now be used on Windows
+ - xxHash and LZ4 have been separated so that a system version of LZ4 can be used even if it does not include xxHash headers ([ROOT-9099](https://sft.its.cern.ch/jira/browse/ROOT-9099))
+ - In addition, several updates have been made to fix minor build system issues, such as not checking for external packages if their builtin is turned off, or checking for packages even when the respective option is disabled ([ROOT-8806](https://sft.its.cern.ch/jira/browse/ROOT-8806), [ROOT-9190](https://sft.its.cern.ch/jira/browse/ROOT-9190), [ROOT-9315](https://sft.its.cern.ch/jira/browse/ROOT-9315), [ROOT-9385](https://sft.its.cern.ch/jira/browse/ROOT-9385)).
+ - The `python3` option to CMake has been removed ([ROOT-9033](https://sft.its.cern.ch/jira/browse/ROOT-9033), [ROOT-9143](https://sft.its.cern.ch/jira/browse/ROOT-9143)). Python support is enabled by default. To configure ROOT to use specific Python versions, there is a new option called `python_version`. This is how to configure ROOT and Python for the common use cases:
+
+ * Use the default Python interpreter:
+ - `-Dpython=ON` (default)
+ * Search only for Python 2.x or only 3.x:
+ - `-Dpython_version=2` or `-Dpython_version=3`
+ * Use a specific version of Python from `$PATH`:
+ - `-Dpython_version=2.7` or `-Dpython_version=3.5`
+ * Use a specific Python interpreter, whatever the version:
+ - `-DPYTHON_EXECUTABLE=/usr/local/bin/python`
Note: The use of `PYTHON_EXECUTABLE` requires the full path to the interpreter.
diff --git a/README/ReleaseNotes/v616/index.md b/README/ReleaseNotes/v616/index.md
index 19fa7df446156..30229bc94d002 100644
--- a/README/ReleaseNotes/v616/index.md
+++ b/README/ReleaseNotes/v616/index.md
@@ -1,14 +1,14 @@
-% ROOT Version ?.?? Release Notes
-% 20??-??-??
+% ROOT Version 6.16 Release Notes
+% 2018-06-25
## Introduction
-ROOT version 6.??/00 is scheduled for release in ???.
+ROOT version 6.16/00 is scheduled for release end of 2018.
For more information, see:
-[http://root.cern.ch](http://root.cern.ch)
+[http://root.cern](http://root.cern)
The following people have contributed to this new version:
@@ -29,15 +29,48 @@ The following people have contributed to this new version:
Vassil Vassilev, Princeton/CMS,\
Wouter Verkerke, NIKHEF/Atlas, \
Jan Musinsky, SAS Kosice
+ Enrico Guiraud, CERN, \
+ Massimo Tumolo, Politecnico di Torino
+
+## Deprecation and Removal
+
+### Ruby bindings
+
+The ruby binding has been unmaintained for several years; it does not build with current ruby versions.
+Given that this effectively meant that Ruby was dysfunctional and given that nobody (but package maintainers) has complained, we decided to remove it.
+
+### Removal of previously deprecated or disabled packages
+
+The packages `afs`, `chirp`, `glite`, `sapdb`, `srp` and `ios` have been removed from ROOT.
+They were deprecated before, or never ported from configure, make to CMake.
+
## Core Libraries
+### Fish support for thisroot script
+
+`. bin/thisroot.fish` sets up the needed ROOT environment variables for one of the ROOT team's favorite shells, the [fish shell](https://fishshell.com/).
+
+### Change of setting the compression algorithm in `rootrc`
+
+The previous setting called `ROOT.ZipMode` is now unused and ignored.
+Instead, use `Root.CompressionAlgorithm` which sets the compression algorithm according to the values of [ECompression](https://root.cern/doc/master/Compression_8h.html#a0a7df9754a3b7be2b437f357254a771c):
+
+* 0: use the default value of `R__ZipMode` (currently selecting LZ4)
+* 1: use zlib (the default until 6.12)
+* 2: use lzma
+* 3: legacy, please don't use
+* 4: LZ4 (the current default)
+
## I/O Libraries
## TTree Libraries
-
+### RDataFrame
+ - Optimise the creation of the set of branches names of an input dataset,
+ doing the work once and caching it in the RInterface.
+ - Add StdDev action
## Histogram Libraries
@@ -62,9 +95,14 @@ The following people have contributed to this new version:
the "gs" command (https://ghostscript.com).
Example:
+
~~~ {.cpp}
canvas->Print("example.pdf","EmbedFonts");
~~~
+ - In TAttAxis::SaveAttributes` take into account the new default value for `TitleOffset`.
+ - When the histograms' title's font was set in pixel the position of the
+ `TPaveText` containing the title was not correct. This problem was reported
+ [here](https://root-forum.cern.ch/t/titles-disappear-for-font-precision-3/).
## 3D Graphics Libraries
diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt
index 1c74e6e4e9f2c..3caed6ded3a6c 100644
--- a/bindings/CMakeLists.txt
+++ b/bindings/CMakeLists.txt
@@ -1,5 +1,9 @@
if(python)
- add_subdirectory(pyroot)
+ if(NOT pyroot_experimental)
+ add_subdirectory(pyroot)
+ else()
+ add_subdirectory(pyroot_experimental)
+ endif()
endif()
if(ruby)
add_subdirectory(ruby)
diff --git a/bindings/pyroot/src/Pythonize.cxx b/bindings/pyroot/src/Pythonize.cxx
index 8eb4e46da4275..0c4817217f13a 100644
--- a/bindings/pyroot/src/Pythonize.cxx
+++ b/bindings/pyroot/src/Pythonize.cxx
@@ -48,6 +48,7 @@
#include
#include
#include
+#include
#include
#include // only needed for Cling TMinuit workaround
@@ -2262,6 +2263,23 @@ namespace {
return BindCppObject( addr, (Cppyy::TCppType_t)Cppyy::GetScope( "TObject" ), kFALSE );
}
+ //- Pretty printing with cling::PrintValue
+ PyObject *ClingPrintValue(ObjectProxy *self)
+ {
+ PyObject *cppname = PyObject_GetAttrString((PyObject *)self, "__cppname__");
+ if (!PyROOT_PyUnicode_Check(cppname))
+ return 0;
+ std::string className = PyROOT_PyUnicode_AsString(cppname);
+ Py_XDECREF(cppname);
+
+ std::string pprint;
+ std::stringstream calcPrintValue;
+ calcPrintValue << "*((std::string*)" << &pprint << ") = cling::printValue((" << className << "*)"
+ << self->GetObject() << ");";
+ gInterpreter->Calc(calcPrintValue.str().c_str());
+ return PyROOT_PyUnicode_FromString(pprint.c_str());
+ }
+
//- Adding array interface to classes ---------------
void AddArrayInterface(PyObject *pyclass, PyCFunction func)
{
@@ -2331,9 +2349,12 @@ Bool_t PyROOT::Pythonize( PyObject* pyclass, const std::string& name )
if ( pyclass == 0 )
return kFALSE;
-//- method name based pythonization --------------------------------------------
+ // add pretty printing
+ Utility::AddToClass(pyclass, "__str__", (PyCFunction)ClingPrintValue);
+
+ //- method name based pythonization --------------------------------------------
-// for smart pointer style classes (note fall-through)
+ // for smart pointer style classes (note fall-through)
if ( HasAttrDirect( pyclass, PyStrings::gDeref ) ) {
Utility::AddToClass( pyclass, "__getattr__", (PyCFunction) DeRefGetAttr, METH_O );
} else if ( HasAttrDirect( pyclass, PyStrings::gFollow ) ) {
diff --git a/bindings/pyroot/test/CMakeLists.txt b/bindings/pyroot/test/CMakeLists.txt
index 2da5c0240e398..4530e911d5dc9 100644
--- a/bindings/pyroot/test/CMakeLists.txt
+++ b/bindings/pyroot/test/CMakeLists.txt
@@ -3,5 +3,6 @@ ROOT_ADD_PYUNITTEST(pyroot_list_initialization list_initialization.py)
if(NUMPY_FOUND)
ROOT_ADD_PYUNITTEST(pyroot_array_interface array_interface.py)
ROOT_ADD_PYUNITTEST(pyroot_ttree_asmatrix ttree_asmatrix.py)
+ ROOT_ADD_PYUNITTEST(pyroot_pretty_printing pretty_printing.py)
endif()
diff --git a/bindings/pyroot/test/pretty_printing.py b/bindings/pyroot/test/pretty_printing.py
new file mode 100644
index 0000000000000..63addb52e19d1
--- /dev/null
+++ b/bindings/pyroot/test/pretty_printing.py
@@ -0,0 +1,55 @@
+import unittest
+import ROOT
+
+
+class PrettyPrinting(unittest.TestCase):
+ # Helpers
+ def _print(self, obj):
+ print("print({}) -> {}".format(obj.__cppname__, obj))
+
+ # Tests
+ def test_RVec(self):
+ x = ROOT.VecOps.RVec("float")(4)
+ for i in range(x.size()):
+ x[i] = i
+ self._print(x)
+ self.assertIn("{ 0", x.__str__())
+
+ def test_STLVector(self):
+ x = ROOT.std.vector("float")(4)
+ for i in range(x.size()):
+ x[i] = i
+ self._print(x)
+ self.assertIn("{ 0", x.__str__())
+
+ def test_STLMap(self):
+ x = ROOT.std.map("string", "int")()
+ for i, s in enumerate(["foo", "bar"]):
+ x[s] = i
+ self._print(x)
+ self.assertIn("foo", x.__str__())
+ self.assertIn("bar", x.__str__())
+
+ def test_STLPair(self):
+ x = ROOT.std.pair("string", "int")("foo", 42)
+ self._print(x)
+ self.assertIn("foo", x.__str__())
+
+ def test_TNamed(self):
+ x = ROOT.TNamed("name", "title")
+ self._print(x)
+ self.assertEqual("Name: name Title: title", x.__str__())
+
+ def test_TObject(self):
+ x = ROOT.TObject()
+ self._print(x)
+ self.assertEqual("Name: TObject Title: Basic ROOT object", x.__str__())
+
+ def test_TH1F(self):
+ x = ROOT.TH1F("name", "title", 10, 0, 1)
+ self._print(x)
+ self.assertEqual("Name: name Title: title NbinsX: 10", x.__str__())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/bindings/pyroot_experimental/CMakeLists.txt b/bindings/pyroot_experimental/CMakeLists.txt
new file mode 100644
index 0000000000000..0f9a014ddae6d
--- /dev/null
+++ b/bindings/pyroot_experimental/CMakeLists.txt
@@ -0,0 +1,2 @@
+add_subdirectory(cppyy)
+add_subdirectory(PyROOT)
diff --git a/bindings/pyroot_experimental/PyROOT/CMakeLists.txt b/bindings/pyroot_experimental/PyROOT/CMakeLists.txt
new file mode 100644
index 0000000000000..e7152f521373d
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/CMakeLists.txt
@@ -0,0 +1,23 @@
+set(py_sources
+ ROOT/__init__.py
+ ROOT/pythonization/__init__.py
+ ROOT/pythonization/_ttree.py
+)
+
+set(sources
+ src/PyROOTModule.cxx
+ src/PyROOTStrings.cxx
+ src/PyROOTWrapper.cxx
+ src/TTreePyz.cxx
+)
+
+file(COPY python/ROOT DESTINATION ${localruntimedir})
+install(DIRECTORY python/ROOT DESTINATION ${runtimedir})
+
+set(d $ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${runtimedir})
+foreach(py_source ${py_sources})
+ install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} -m py_compile ${d}/${py_source})")
+ install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} -O -m py_compile ${d}/${py_source})")
+endforeach()
+
+ROOT_LINKER_LIBRARY(ROOTPython ${sources} LIBRARIES Core Tree cppyy)
diff --git a/bindings/pyroot_experimental/PyROOT/python/ROOT/__init__.py b/bindings/pyroot_experimental/PyROOT/python/ROOT/__init__.py
new file mode 100644
index 0000000000000..31a05455a783f
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/python/ROOT/__init__.py
@@ -0,0 +1,27 @@
+
+import cppyy
+import ROOT.pythonization as pyz
+
+import pkgutil
+import importlib
+
+def pythonization(fn):
+ """
+ Pythonizor decorator to be used in pythonization modules.
+
+ Parameters
+ ----------
+ fn : function
+ Function that implements some pythonization.
+ The function must accept two parameters: the class
+ to be pythonized and the name of that class.
+ """
+ cppyy.py.add_pythonization(fn)
+
+# Trigger the addition of the pythonizations
+for _, module_name, _ in pkgutil.walk_packages(pyz.__path__):
+ module = importlib.import_module(pyz.__name__ + '.' + module_name)
+
+# Redirect ROOT to cppyy.gbl
+import sys
+sys.modules['ROOT'] = cppyy.gbl
diff --git a/bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/__init__.py b/bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/__init__.py
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/_ttree.py b/bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/_ttree.py
new file mode 100644
index 0000000000000..5853e4edf1b0e
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/_ttree.py
@@ -0,0 +1,33 @@
+
+from libROOTPython import PythonizeTTree
+
+from ROOT import pythonization
+
+# TTree iterator
+def _TTree__iter__(self):
+ i = 0
+ bytes_read = self.GetEntry(i)
+ while 0 < bytes_read:
+ yield self
+ i += 1
+ bytes_read = self.GetEntry(i)
+
+ if bytes_read == -1:
+ raise RuntimeError("TTree I/O error")
+
+# Pythonizor function
+@pythonization
+def pythonize_ttree(klass, name):
+ # Parameters:
+ # klass: class to be pythonized
+ # name: string containing the name of the class
+
+ if name == 'TTree':
+ # Pythonic iterator
+ klass.__iter__ = _TTree__iter__
+
+ # C++ pythonizations
+ # - tree.branch syntax
+ PythonizeTTree(klass)
+
+ return True
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx b/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx
new file mode 100644
index 0000000000000..334b192ee044a
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx
@@ -0,0 +1,104 @@
+
+// Bindings
+#include "PyROOTPythonize.h"
+#include "PyROOTStrings.h"
+#include "PyROOTWrapper.h"
+
+// Cppyy
+#include "CPyCppyy.h"
+#include "CallContext.h"
+#include "ProxyWrappers.h"
+#include "Utility.h"
+
+// ROOT
+#include "TROOT.h"
+#include "TSystem.h"
+
+// Standard
+#include
+#include
+#include
+#include
+
+using namespace CPyCppyy;
+
+namespace PyROOT {
+PyObject *gRootModule = 0;
+}
+
+// Methods offered by the interface
+static PyMethodDef gPyROOTMethods[] = {{(char *)"PythonizeTTree", (PyCFunction)PyROOT::PythonizeTTree, METH_VARARGS,
+ (char *)"Pythonizations for class TTree"},
+ {NULL, NULL, 0, NULL}};
+
+#if PY_VERSION_HEX >= 0x03000000
+struct module_state {
+ PyObject *error;
+};
+
+#define GETSTATE(m) ((struct module_state *)PyModule_GetState(m))
+
+static int rootmodule_traverse(PyObject *m, visitproc visit, void *arg)
+{
+ Py_VISIT(GETSTATE(m)->error);
+ return 0;
+}
+
+static int rootmodule_clear(PyObject *m)
+{
+ Py_CLEAR(GETSTATE(m)->error);
+ return 0;
+}
+
+static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "libROOTPython", NULL,
+ sizeof(struct module_state), gPyROOTMethods, NULL,
+ rootmodule_traverse, rootmodule_clear, NULL};
+
+
+/// Initialization of extension module libROOTPython
+
+#define PYROOT_INIT_ERROR return NULL
+extern "C" PyObject *PyInit_libROOTPython()
+#else // PY_VERSION_HEX >= 0x03000000
+#define PYROOT_INIT_ERROR return
+extern "C" void initlibROOTPython()
+#endif
+{
+ using namespace PyROOT;
+
+ // load commonly used python strings
+ if (!PyROOT::CreatePyStrings())
+ PYROOT_INIT_ERROR;
+
+// setup PyROOT
+#if PY_VERSION_HEX >= 0x03000000
+ gRootModule = PyModule_Create(&moduledef);
+#else
+ gRootModule = Py_InitModule(const_cast("libROOTPython"), gPyROOTMethods);
+#endif
+ if (!gRootModule)
+ PYROOT_INIT_ERROR;
+
+ // keep gRootModule, but do not increase its reference count even as it is borrowed,
+ // or a self-referencing cycle would be created
+
+ // policy labels
+ PyModule_AddObject(gRootModule, (char *)"kMemoryHeuristics", PyInt_FromLong((int)CallContext::kUseHeuristics));
+ PyModule_AddObject(gRootModule, (char *)"kMemoryStrict", PyInt_FromLong((int)CallContext::kUseStrict));
+ PyModule_AddObject(gRootModule, (char *)"kSignalFast", PyInt_FromLong((int)CallContext::kFast));
+ PyModule_AddObject(gRootModule, (char *)"kSignalSafe", PyInt_FromLong((int)CallContext::kSafe));
+
+ // setup PyROOT
+ PyROOT::Init();
+
+ // signal policy: don't abort interpreter in interactive mode
+ CallContext::SetSignalPolicy(gROOT->IsBatch() ? CallContext::kFast : CallContext::kSafe);
+
+ // inject ROOT namespace for convenience
+ PyModule_AddObject(gRootModule, (char *)"ROOT", CreateScopeProxy("ROOT"));
+
+#if PY_VERSION_HEX >= 0x03000000
+ Py_INCREF(gRootModule);
+ return gRootModule;
+#endif
+}
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTPythonize.h b/bindings/pyroot_experimental/PyROOT/src/PyROOTPythonize.h
new file mode 100644
index 0000000000000..e5e562671b97b
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTPythonize.h
@@ -0,0 +1,13 @@
+
+#ifndef PYROOT_PYTHONIZE_H
+#define PYROOT_PYTHONIZE_H
+
+#include "Python.h"
+
+namespace PyROOT {
+
+PyObject *PythonizeTTree(PyObject *self, PyObject *args);
+
+} // namespace PyROOT
+
+#endif // !PYROOT_PYTHONIZE_H
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTStrings.cxx b/bindings/pyroot_experimental/PyROOT/src/PyROOTStrings.cxx
new file mode 100644
index 0000000000000..774e8137cb6c5
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTStrings.cxx
@@ -0,0 +1,51 @@
+
+// Bindings
+#include "CPyCppyy.h"
+#include "PyROOTStrings.h"
+
+// Define cached python strings
+PyObject *PyROOT::PyStrings::gBranch = nullptr;
+PyObject *PyROOT::PyStrings::gFitFCN = nullptr;
+PyObject *PyROOT::PyStrings::gROOTns = nullptr;
+PyObject *PyROOT::PyStrings::gSetBranchAddress = nullptr;
+PyObject *PyROOT::PyStrings::gSetFCN = nullptr;
+PyObject *PyROOT::PyStrings::gTClassDynCast = nullptr;
+
+#define PYROOT_INITIALIZE_STRING(var, str) \
+ if (!(PyStrings::var = CPyCppyy_PyUnicode_InternFromString((char *)#str))) \
+ return false
+
+bool PyROOT::CreatePyStrings()
+{
+ // Build cache of commonly used python strings (the cache is python intern, so
+ // all strings are shared python-wide, not just in PyROOT).
+ PYROOT_INITIALIZE_STRING(gBranch, Branch);
+ PYROOT_INITIALIZE_STRING(gFitFCN, FitFCN);
+ PYROOT_INITIALIZE_STRING(gROOTns, ROOT);
+ PYROOT_INITIALIZE_STRING(gSetBranchAddress, SetBranchAddress);
+ PYROOT_INITIALIZE_STRING(gSetFCN, SetFCN);
+ PYROOT_INITIALIZE_STRING(gTClassDynCast, _TClass__DynamicCast);
+
+ return true;
+}
+
+/// Remove all cached python strings.
+
+PyObject *PyROOT::DestroyPyStrings()
+{
+ Py_DECREF(PyStrings::gBranch);
+ PyStrings::gBranch = nullptr;
+ Py_DECREF(PyStrings::gFitFCN);
+ PyStrings::gFitFCN = nullptr;
+ Py_DECREF(PyStrings::gROOTns);
+ PyStrings::gROOTns = nullptr;
+ Py_DECREF(PyStrings::gSetBranchAddress);
+ PyStrings::gSetBranchAddress = nullptr;
+ Py_DECREF(PyStrings::gSetFCN);
+ PyStrings::gSetFCN = nullptr;
+ Py_DECREF(PyStrings::gTClassDynCast);
+ PyStrings::gTClassDynCast = nullptr;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTStrings.h b/bindings/pyroot_experimental/PyROOT/src/PyROOTStrings.h
new file mode 100644
index 0000000000000..f6eb3b36b88ae
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTStrings.h
@@ -0,0 +1,28 @@
+
+#ifndef PYROOT_PYSTRINGS_H
+#define PYROOT_PYSTRINGS_H
+
+#include "Python.h"
+#include "DllImport.h"
+
+namespace PyROOT {
+
+// python strings kept for performance reasons
+
+namespace PyStrings {
+
+R__EXTERN PyObject *gBranch;
+R__EXTERN PyObject *gFitFCN;
+R__EXTERN PyObject *gROOTns;
+R__EXTERN PyObject *gSetBranchAddress;
+R__EXTERN PyObject *gSetFCN;
+R__EXTERN PyObject *gTClassDynCast;
+
+} // namespace PyStrings
+
+bool CreatePyStrings();
+PyObject *DestroyPyStrings();
+
+} // namespace PyROOT
+
+#endif // !PYROOT_PYSTRINGS_H
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTWrapper.cxx b/bindings/pyroot_experimental/PyROOT/src/PyROOTWrapper.cxx
new file mode 100644
index 0000000000000..90abc911ece6d
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTWrapper.cxx
@@ -0,0 +1,42 @@
+
+// Bindings
+#include "PyROOTWrapper.h"
+
+// Cppyy
+#include "CPyCppyy.h"
+#include "ProxyWrappers.h"
+
+// ROOT
+#include "TROOT.h"
+#include "TSystem.h"
+#include "TClass.h"
+#include "TInterpreter.h"
+#include "DllImport.h"
+
+namespace PyROOT {
+R__EXTERN PyObject *gRootModule;
+}
+
+using namespace PyROOT;
+
+namespace {
+
+static void AddToGlobalScope(const char *label, const char * /* hdr */, TObject *obj, Cppyy::TCppType_t klass)
+{
+ // Bind the given object with the given class in the global scope with the
+ // given label for its reference.
+ PyModule_AddObject(gRootModule, const_cast(label), CPyCppyy::BindCppObjectNoCast(obj, klass));
+}
+
+} // unnamed namespace
+
+void PyROOT::Init()
+{
+ // Initialize and acquire the GIL to allow for threading in ROOT
+ PyEval_InitThreads();
+
+ // Bind ROOT globals that will be needed in ROOT.py
+ AddToGlobalScope("gROOT", "TROOT.h", gROOT, Cppyy::GetScope(gROOT->IsA()->GetName()));
+ AddToGlobalScope("gSystem", "TSystem.h", gSystem, Cppyy::GetScope(gSystem->IsA()->GetName()));
+ AddToGlobalScope("gInterpreter", "TInterpreter.h", gInterpreter, Cppyy::GetScope(gInterpreter->IsA()->GetName()));
+}
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTWrapper.h b/bindings/pyroot_experimental/PyROOT/src/PyROOTWrapper.h
new file mode 100644
index 0000000000000..fe104b8044b1b
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTWrapper.h
@@ -0,0 +1,12 @@
+
+#ifndef PYROOT_ROOTWRAPPER_H
+#define PYROOT_ROOTWRAPPER_H
+
+namespace PyROOT {
+
+// initialize ROOT
+void Init();
+
+} // namespace PyROOT
+
+#endif // !PYROOT_ROOTWRAPPER_H
diff --git a/bindings/pyroot_experimental/PyROOT/src/TTreePyz.cxx b/bindings/pyroot_experimental/PyROOT/src/TTreePyz.cxx
new file mode 100644
index 0000000000000..19303a9674071
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/TTreePyz.cxx
@@ -0,0 +1,175 @@
+
+// Bindings
+#include "CPyCppyy.h"
+#include "PyROOTPythonize.h"
+#include "CPPInstance.h"
+#include "ProxyWrappers.h"
+#include "Converters.h"
+#include "Utility.h"
+
+// ROOT
+#include "TClass.h"
+#include "TTree.h"
+#include "TBranch.h"
+#include "TBranchElement.h"
+#include "TBranchObject.h"
+#include "TLeaf.h"
+#include "TLeafElement.h"
+#include "TLeafObject.h"
+#include "TStreamerElement.h"
+#include "TStreamerInfo.h"
+
+using namespace CPyCppyy;
+
+static TClass *GetClass(const CPPInstance *pyobj)
+{
+ return TClass::GetClass(Cppyy::GetFinalName(pyobj->ObjectIsA()).c_str());
+}
+
+static TBranch *SearchForBranch(TTree *tree, const char *name)
+{
+ TBranch *branch = tree->GetBranch(name);
+ if (!branch) {
+ // for benefit of naming of sub-branches, the actual name may have a trailing '.'
+ branch = tree->GetBranch((std::string(name) + '.').c_str());
+ }
+ return branch;
+}
+
+static TLeaf *SearchForLeaf(TTree *tree, const char *name, TBranch *branch)
+{
+ TLeaf *leaf = tree->GetLeaf(name);
+ if (branch && !leaf) {
+ leaf = branch->GetLeaf(name);
+ if (!leaf) {
+ TObjArray *leaves = branch->GetListOfLeaves();
+ if (leaves->GetSize() && (leaves->First() == leaves->Last())) {
+ // i.e., if unambiguously only this one
+ leaf = (TLeaf *)leaves->At(0);
+ }
+ }
+ }
+ return leaf;
+}
+
+static PyObject *BindBranchToProxy(TTree *tree, const char *name, TBranch *branch)
+{
+ // for partial return of a split object
+ if (branch->InheritsFrom(TBranchElement::Class())) {
+ TBranchElement *be = (TBranchElement *)branch;
+ if (be->GetCurrentClass() && (be->GetCurrentClass() != be->GetTargetClass()) && (0 <= be->GetID())) {
+ Long_t offset = ((TStreamerElement *)be->GetInfo()->GetElements()->At(be->GetID()))->GetOffset();
+ return BindCppObjectNoCast(be->GetObject() + offset, Cppyy::GetScope(be->GetCurrentClass()->GetName()));
+ }
+ }
+
+ // for return of a full object
+ if (branch->IsA() == TBranchElement::Class() || branch->IsA() == TBranchObject::Class()) {
+ TClass *klass = TClass::GetClass(branch->GetClassName());
+ if (klass && branch->GetAddress())
+ return BindCppObjectNoCast(*(void **)branch->GetAddress(), Cppyy::GetScope(branch->GetClassName()));
+
+ // try leaf, otherwise indicate failure by returning a typed null-object
+ TObjArray *leaves = branch->GetListOfLeaves();
+ if (klass && !tree->GetLeaf(name) && !(leaves->GetSize() && (leaves->First() == leaves->Last())))
+ return BindCppObjectNoCast(nullptr, Cppyy::GetScope(branch->GetClassName()));
+ }
+
+ return nullptr;
+}
+
+static PyObject *WrapLeaf(TLeaf *leaf)
+{
+ if (1 < leaf->GetLenStatic() || leaf->GetLeafCount()) {
+ // array types
+ std::string typeName = leaf->GetTypeName();
+ Converter *pcnv = CreateConverter(typeName + '*', leaf->GetNdata());
+
+ void *address = 0;
+ if (leaf->GetBranch())
+ address = (void *)leaf->GetBranch()->GetAddress();
+ if (!address)
+ address = (void *)leaf->GetValuePointer();
+
+ PyObject *value = pcnv->FromMemory(&address);
+ delete pcnv;
+
+ return value;
+ } else if (leaf->GetValuePointer()) {
+ // value types
+ Converter *pcnv = CreateConverter(leaf->GetTypeName());
+ PyObject *value = 0;
+ if (leaf->IsA() == TLeafElement::Class() || leaf->IsA() == TLeafObject::Class())
+ value = pcnv->FromMemory((void *)*(void **)leaf->GetValuePointer());
+ else
+ value = pcnv->FromMemory((void *)leaf->GetValuePointer());
+ delete pcnv;
+
+ return value;
+ }
+
+ return nullptr;
+}
+
+// Allow access to branches/leaves as if they were data members
+PyObject *GetAttr(const CPPInstance *self, PyObject *pyname)
+{
+ const char *name_possibly_alias = CPyCppyy_PyUnicode_AsString(pyname);
+ if (!name_possibly_alias)
+ return 0;
+
+ // get hold of actual tree
+ TTree *tree = (TTree *)GetClass(self)->DynamicCast(TTree::Class(), self->GetObject());
+
+ if (!tree) {
+ PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");
+ return 0;
+ }
+
+ // deal with possible aliasing
+ const char *name = tree->GetAlias(name_possibly_alias);
+ if (!name)
+ name = name_possibly_alias;
+
+ // search for branch first (typical for objects)
+ TBranch *branch = SearchForBranch(tree, name);
+
+ if (branch) {
+ // found a branched object, wrap its address for the object it represents
+ auto proxy = BindBranchToProxy(tree, name, branch);
+ if (proxy != nullptr)
+ return proxy;
+ }
+
+ // if not, try leaf
+ TLeaf *leaf = SearchForLeaf(tree, name, branch);
+
+ if (leaf) {
+ // found a leaf, extract value and wrap with a Python object according to its type
+ auto wrapper = WrapLeaf(leaf);
+ if (wrapper != nullptr)
+ return wrapper;
+ }
+
+ // confused
+ PyErr_Format(PyExc_AttributeError, "\'%s\' object has no attribute \'%s\'", tree->IsA()->GetName(), name);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+/// \brief Add pythonizations to the TTree class.
+/// \param[in] self Always null, since this is a module function.
+/// \param[in] args Pointer to a Python tuple object containing the arguments
+/// received from Python.
+///
+/// Inject new behaviour into the TTree class so that it can be used from
+/// Python in a simpler or more pythonic way.
+///
+/// This function adds the following pythonizations:
+/// - Allow access to branches/leaves as if they were data members (e.g. mytree.branch)
+PyObject *PyROOT::PythonizeTTree(PyObject */* self */, PyObject *args)
+{
+ PyObject *pyclass = PyTuple_GetItem(args, 0);
+ Utility::AddToClass(pyclass, "__getattr__", (PyCFunction)GetAttr, METH_O);
+ Py_RETURN_NONE;
+}
diff --git a/bindings/pyroot_experimental/cppyy/CMakeLists.txt b/bindings/pyroot_experimental/cppyy/CMakeLists.txt
new file mode 100644
index 0000000000000..420916b8726f6
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CMakeLists.txt
@@ -0,0 +1,7 @@
+if(NOT (cxx14 OR cxx17))
+ message(FATAL_ERROR "Cppyy requires C++14 standard or later")
+endif()
+
+add_subdirectory(cppyy-backend)
+add_subdirectory(CPyCppyy)
+add_subdirectory(cppyy)
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/.gitignore b/bindings/pyroot_experimental/cppyy/CPyCppyy/.gitignore
new file mode 100644
index 0000000000000..0de22a4662245
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/.gitignore
@@ -0,0 +1,14 @@
+# python compiled files
+*.pyc
+*.pyo
+
+# dictionary products
+*.so
+*.pcm
+*.rootmap
+
+# build products
+dist
+*.egg-info
+.cache
+build
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/CMakeLists.txt b/bindings/pyroot_experimental/cppyy/CPyCppyy/CMakeLists.txt
new file mode 100644
index 0000000000000..ebaaf1e1ff784
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/CMakeLists.txt
@@ -0,0 +1,48 @@
+set(headers
+ include/TPyArg.h
+ include/TPyException.h
+ include/TPyReturn.h
+ include/TPython.h
+)
+
+set(sources
+ src/CallContext.cxx
+ src/Converters.cxx
+ src/CPPClassMethod.cxx
+ src/CPPConstructor.cxx
+ src/CPPDataMember.cxx
+ src/CPPFunction.cxx
+ src/CPPInstance.cxx
+ src/CPPMethod.cxx
+ src/CPPOverload.cxx
+ src/CPPScope.cxx
+ src/CPPSetItem.cxx
+ src/CPyCppyyModule.cxx
+ src/CustomPyTypes.cxx
+ src/Executors.cxx
+ src/LowLevelViews.cxx
+ src/MemoryRegulator.cxx
+ src/ProxyWrappers.cxx
+ src/PyStrings.cxx
+ src/Pythonize.cxx
+ src/TemplateProxy.cxx
+ src/TPyArg.cxx
+ src/TPyClassGenerator.cxx
+ src/TPyException.cxx
+ src/TPyReturn.cxx
+ src/TPython.cxx
+ src/TupleOfInstances.cxx
+ src/TypeManip.cxx
+ src/Utility.cxx
+)
+
+add_library(cppyy SHARED ${headers} ${sources})
+target_compile_options(cppyy PRIVATE
+ -Wno-shadow -Wno-strict-aliasing -Wno-unused-but-set-parameter)
+target_include_directories(cppyy PUBLIC ${PYTHON_INCLUDE_DIRS}
+ $
+ $)
+target_link_libraries(cppyy cppyy_backend ${PYTHON_LIBRARIES})
+
+set_property(GLOBAL APPEND PROPERTY ROOT_EXPORTED_TARGETS cppyy)
+install(TARGETS cppyy EXPORT ${CMAKE_PROJECT_NAME}Exports DESTINATION ${runtimedir})
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/LICENSE.txt b/bindings/pyroot_experimental/cppyy/CPyCppyy/LICENSE.txt
new file mode 100644
index 0000000000000..bea39a7007530
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/LICENSE.txt
@@ -0,0 +1,51 @@
+Copyright (c) 2003, The Regents of the University of California,
+through Lawrence Berkeley National Laboratory (subject to receipt of
+any required approvals from the U.S. Dept. of Energy). All rights
+reserved. Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the following
+conditions are met:
+
+(1) Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+(2) Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+(3) Neither the name of the University of California, Lawrence Berkeley
+National Laboratory, U.S. Dept. of Energy nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+You are under no obligation whatsoever to provide any bug fixes,
+patches, or upgrades to the features, functionality or performance of
+the source code ("Enhancements") to anyone; however, if you choose to
+make your Enhancements available either publicly, or directly to
+Lawrence Berkeley National Laboratory, without imposing a separate
+written license agreement for such Enhancements, then you hereby grant
+the following license: a non-exclusive, royalty-free perpetual license
+to install, use, modify, prepare derivative works, incorporate into
+other computer software, distribute, and sublicense such Enhancements
+or derivative works thereof, in binary and source code form.
+
+
+
+Additional copyright holders
+----------------------------
+
+Except when otherwise stated (look for LICENSE files or information in
+source files), this package contains files copyrighted by one or more of
+the following people and organizations:
+
+ CERN
+ Toby StClere-Smithe
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/MANIFEST.in b/bindings/pyroot_experimental/cppyy/CPyCppyy/MANIFEST.in
new file mode 100644
index 0000000000000..546a5c7e41542
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/MANIFEST.in
@@ -0,0 +1,10 @@
+# Include the license file
+include LICENSE.txt
+
+# Include all headers
+include include/*.h
+include src/*.h
+
+# Include source files
+include src/*.cxx
+include src/*.inc
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/README.rst b/bindings/pyroot_experimental/cppyy/CPyCppyy/README.rst
new file mode 100644
index 0000000000000..ad1f2da970a77
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/README.rst
@@ -0,0 +1,21 @@
+.. -*- mode: rst -*-
+
+CPyCppyy: Python-C++ bindings interface based on Cling/LLVM
+===========================================================
+
+CPyCppyy is the CPython equivalent of _cppyy in PyPy.
+It provides dynamic Python-C++ bindings by leveraging the Cling C++
+interpreter and LLVM.
+Details and performance are described in
+`this paper `_.
+
+CPyCppyy is a CPython extension module built on top of the same backend API
+as PyPy/_cppyy.
+It thus requires the installation of the
+`cppyy backend `_
+for use, which will pull in Cling.
+CPython/cppyy and PyPy/cppyy are designed to be compatible, although there
+are differences due to the former being reference counted and the latter
+being garbage collected.
+
+Full documentation: `cppyy.readthedocs.io `_.
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyArg.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyArg.h
new file mode 100644
index 0000000000000..95902e6ec9a92
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyArg.h
@@ -0,0 +1,49 @@
+#ifndef CPYCPPYY_TPYARG
+#define CPYCPPYY_TPYARG
+
+//////////////////////////////////////////////////////////////////////////////
+// //
+// TPyArg //
+// //
+// Morphing argument type from evaluating python expressions. //
+// //
+//////////////////////////////////////////////////////////////////////////////
+
+// Python
+struct _object;
+typedef _object PyObject;
+
+// Standard
+#include
+
+
+class TPyArg {
+public:
+// converting constructors
+ TPyArg(PyObject*);
+ TPyArg(int);
+ TPyArg(long);
+ TPyArg(double);
+ TPyArg(const char*);
+
+ TPyArg(const TPyArg&);
+ TPyArg& operator=(const TPyArg&);
+ virtual ~TPyArg();
+
+// "extractor"
+ operator PyObject*() const;
+
+// constructor and generic dispatch
+ static void CallConstructor(
+ PyObject*& pyself, PyObject* pyclass, const std::vector& args);
+ static void CallConstructor(PyObject*& pyself, PyObject* pyclass); // default ctor
+ static PyObject* CallMethod(PyObject* pymeth, const std::vector& args);
+ static void CallDestructor(
+ PyObject*& pyself, PyObject* pymeth, const std::vector& args);
+ static void CallDestructor(PyObject*& pyself);
+
+private:
+ mutable PyObject* fPyObject; //! converted C++ value as python object
+};
+
+#endif // !CPYCPPYY_TPYARG
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyException.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyException.h
new file mode 100644
index 0000000000000..4abaea3fcb63c
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyException.h
@@ -0,0 +1,49 @@
+#ifndef CPYCPPYY_TPyException
+#define CPYCPPYY_TPyException
+
+//////////////////////////////////////////////////////////////////////////////
+// //
+// TPyException //
+// //
+// 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. //
+// //
+// The situation is: //
+// - We're calling C++ code from python. //
+// - 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++. //
+// To do that we throw a TPyException. //
+// We can then catch this exception when we do the C++ call. //
+// //
+// Note that we don't need to save any state in the exception -- it's //
+// already in the python error info variables. //
+// (??? Actually, if the program is multithreaded, this is dangerous //
+// if the code has released and reacquired the lock along the call chain. //
+// Punt on this for now, though.) //
+// //
+//////////////////////////////////////////////////////////////////////////////
+
+// Standard
+#include
+
+
+namespace CPyCppyy {
+
+class TPyException : public std::exception {
+public:
+// default constructor
+ TPyException();
+
+// destructor
+ virtual ~TPyException() noexcept;
+
+// give reason for raised exception
+ virtual const char* what() const noexcept;
+};
+
+} // namespace CPyCppyy
+
+#endif // !CPYCPPYY_TPyException
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyReturn.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyReturn.h
new file mode 100644
index 0000000000000..5626d0144271b
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPyReturn.h
@@ -0,0 +1,59 @@
+#ifndef CPYCPPYY_TPYRETURN
+#define CPYCPPYY_TPYRETURN
+
+//////////////////////////////////////////////////////////////////////////////
+// //
+// TPyReturn //
+// //
+// Morphing return type from evaluating python expressions. //
+// //
+//////////////////////////////////////////////////////////////////////////////
+
+
+// Python
+struct _object;
+typedef _object PyObject;
+
+
+class TPyReturn {
+public:
+ TPyReturn();
+ TPyReturn(PyObject* pyobject);
+ TPyReturn(const TPyReturn&);
+ TPyReturn& operator=(const TPyReturn&);
+ virtual ~TPyReturn();
+
+// conversions to standard types, may fail if unconvertible
+ operator char*() const;
+ operator const char*() const;
+ operator char() const;
+
+ operator long() const;
+ operator int() const { return (int)operator long(); }
+ operator short() const { return (short)operator long(); }
+
+ operator unsigned long() const;
+ operator unsigned int() const {
+ return (unsigned int)operator unsigned long();
+ }
+ operator unsigned short() const {
+ return (unsigned short)operator unsigned long();
+ }
+
+ operator double() const;
+ operator float() const { return (float)operator double(); }
+
+// used for both TObject and PyObject conversions
+ operator void*() const;
+
+ template
+ operator T*() const { return (T*)(void*)*this; }
+
+// used strictly for PyObject conversions
+ operator PyObject*() const;
+
+private:
+ PyObject* fPyObject; //! actual python object
+};
+
+#endif // !CPYCPPYY_TPYRETURN
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPython.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPython.h
new file mode 100644
index 0000000000000..de257fab7b60e
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/include/TPython.h
@@ -0,0 +1,59 @@
+#ifndef CPYCPPYY_TPYTHON
+#define CPYCPPYY_TPYTHON
+
+//////////////////////////////////////////////////////////////////////////////
+// //
+// TPython //
+// //
+// Access to the python interpreter and API onto CPyCppyy. //
+// //
+//////////////////////////////////////////////////////////////////////////////
+
+
+// Bindings
+#include "TPyReturn.h"
+
+
+class TPython {
+
+private:
+ static bool Initialize();
+
+public:
+// import a python module, making its classes available
+ static bool Import(const char* name);
+
+// load a python script as if it were a macro
+ static void LoadMacro(const char* name);
+
+// execute a python stand-alone script, with argv CLI arguments
+ static void ExecScript(const char* name, int argc = 0, const char** argv = 0);
+
+// execute a python statement (e.g. "import sys")
+ static bool Exec(const char* cmd);
+
+// evaluate a python expression (e.g. "1+1")
+ static const TPyReturn Eval(const char* expr);
+
+// enter an interactive python session (exit with ^D)
+ static void Prompt();
+
+// type verifiers for CPPInstance
+ static bool CPPInstance_Check(PyObject* pyobject);
+ static bool CPPInstance_CheckExact(PyObject* pyobject);
+
+// type verifiers for CPPOverload
+ static bool CPPOverload_Check(PyObject* pyobject);
+ static bool CPPOverload_CheckExact(PyObject* pyobject);
+
+// object proxy to void* conversion
+ static void* CPPInstance_AsVoidPtr(PyObject* pyobject);
+
+// void* to object proxy conversion, returns a new reference
+ static PyObject* CPPInstance_FromVoidPtr(
+ void* addr, const char* classname, bool python_owns = false);
+
+ virtual ~TPython() { }
+};
+
+#endif // !CPYCPPYY_TPYTHON
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/setup.cfg b/bindings/pyroot_experimental/cppyy/CPyCppyy/setup.cfg
new file mode 100644
index 0000000000000..8debd01371e4f
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/setup.cfg
@@ -0,0 +1,5 @@
+[bdist_wheel]
+universal=0
+
+[metadata]
+license_file = LICENSE.txt
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/setup.py b/bindings/pyroot_experimental/cppyy/CPyCppyy/setup.py
new file mode 100755
index 0000000000000..2c5f94919590a
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/setup.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+import os, glob, subprocess
+from setuptools import setup, find_packages, Extension
+from distutils.command.build_ext import build_ext as _build_ext
+from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
+from codecs import open
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
+ long_description = f.read()
+
+try:
+ root_install = os.environ["ROOTSYS"]
+except KeyError:
+ root_install = None
+
+def get_cflags():
+ config_exec = 'cling-config'
+ if root_install:
+ config_exec = 'root-config'
+ cli_arg = subprocess.check_output([config_exec, '--auxcflags'])
+ return cli_arg.decode("utf-8").strip()
+
+class my_build_extension(_build_ext):
+ def build_extension(self, ext):
+ ext.extra_compile_args = ['-O2']+get_cflags().split()
+ return _build_ext.build_extension(self, ext)
+
+class my_bdist_wheel(_bdist_wheel):
+ def run(self, *args):
+ # wheels do not respect dependencies; make this a no-op so that it fails (mostly) silently
+ pass
+
+setup(
+ name='CPyCppyy',
+ version='1.0.1',
+ description='Cling-based Python-C++ bindings for CPython',
+ long_description=long_description,
+
+ url='http://cppyy.readthedocs.io/',
+
+ # Author details
+ author='Wim Lavrijsen',
+ author_email='WLavrijsen@lbl.gov',
+
+ license='LBNL BSD',
+
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+
+ 'Intended Audience :: Developers',
+
+ 'Topic :: Software Development',
+ 'Topic :: Software Development :: Interpreters',
+
+ 'License :: OSI Approved :: BSD License',
+
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: C',
+ 'Programming Language :: C++',
+
+ 'Natural Language :: English'
+ ],
+
+ install_requires=['cppyy-backend>=0.5'],
+
+ keywords='C++ bindings data science',
+
+ cmdclass = {
+ 'build_ext': my_build_extension,
+ 'bdist_wheel': my_bdist_wheel
+ },
+
+ ext_modules=[Extension('libcppyy',
+ sources=glob.glob('src/*.cxx'),
+ include_dirs=['include'])],
+)
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPClassMethod.cxx b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPClassMethod.cxx
new file mode 100644
index 0000000000000..d850cee8eac41
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPClassMethod.cxx
@@ -0,0 +1,26 @@
+// Bindings
+#include "CPyCppyy.h"
+#include "CPPClassMethod.h"
+
+
+//- public members --------------------------------------------------------------
+PyObject* CPyCppyy::CPPClassMethod::Call(
+ CPPInstance*&, PyObject* args, PyObject* kwds, CallContext* ctxt)
+{
+// preliminary check in case keywords are accidently used (they are ignored otherwise)
+ if (kwds && PyDict_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
+ if (!this->ConvertAndSetArgs(args, ctxt))
+ return nullptr;
+
+// execute function
+ return this->Execute(nullptr, 0, ctxt);
+}
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPClassMethod.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPClassMethod.h
new file mode 100644
index 0000000000000..4cbfdc055fae9
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPClassMethod.h
@@ -0,0 +1,21 @@
+#ifndef CPYCPPYY_CPPCLASSMETHOD_H
+#define CPYCPPYY_CPPCLASSMETHOD_H
+
+// Bindings
+#include "CPPMethod.h"
+
+
+namespace CPyCppyy {
+
+class CPPClassMethod : public CPPMethod {
+public:
+ using CPPMethod::CPPMethod;
+
+ virtual PyCallable* Clone() { return new CPPClassMethod(*this); }
+ virtual PyObject* Call(
+ CPPInstance*&, PyObject* args, PyObject* kwds, CallContext* ctxt = nullptr);
+};
+
+} // namespace CPyCppyy
+
+#endif // !CPYCPPYY_CPPCLASSMETHOD_H
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPConstructor.cxx b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPConstructor.cxx
new file mode 100644
index 0000000000000..e1d43e0602f14
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPConstructor.cxx
@@ -0,0 +1,90 @@
+// Bindings
+#include "CPyCppyy.h"
+#include "CPPConstructor.h"
+#include "CPPInstance.h"
+#include "Executors.h"
+#include "MemoryRegulator.h"
+
+// Standard
+#include
+
+
+//- 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_PyUnicode_FromFormat("%s::%s%s",
+ clName.c_str(), clName.c_str(), this->GetMethod() ? this->GetSignatureString().c_str() : "()");
+}
+
+//----------------------------------------------------------------------------
+PyObject* CPyCppyy::CPPConstructor::Call(
+ CPPInstance*& self, PyObject* args, PyObject* kwds, CallContext* ctxt)
+{
+// preliminary check in case keywords are accidently used (they are ignored otherwise)
+ if (kwds && PyDict_Size(kwds)) {
+ PyErr_SetString(PyExc_TypeError, "keyword arguments are not yet supported");
+ return nullptr;
+ }
+
+// do not allow instantiation of abstract classes
+ if (Cppyy::IsAbstract(this->GetScope())) {
+ PyErr_Format(PyExc_TypeError, "cannot instantiate abstract class \'%s\'",
+ Cppyy::GetScopedFinalName(this->GetScope()).c_str());
+ return nullptr;
+ }
+
+// setup as necessary
+ if (!this->Initialize(ctxt))
+ return nullptr; // important: 0, not Py_None
+
+// fetch self, verify, and put the arguments in usable order
+ if (!(args = this->PreProcessArgs(self, args, kwds)))
+ return nullptr;
+
+// translate the arguments
+ if (!this->ConvertAndSetArgs(args, ctxt)) {
+ Py_DECREF(args);
+ return nullptr;
+ }
+
+// perform the call, 0 makes the other side allocate the memory
+ Long_t address = (Long_t)this->Execute(nullptr, 0, ctxt);
+
+// done with filtered args
+ Py_DECREF(args);
+
+// 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);
+
+ // TODO: consistent up or down cast ...
+ MemoryRegulator::RegisterPyObject(self, (Cppyy::TCppObject_t)address);
+
+ // 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, '0' might trigger the overload handler to choose a
+// different constructor, which if all fails will throw an exception
+ return nullptr;
+}
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPConstructor.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPConstructor.h
new file mode 100644
index 0000000000000..036daad9d8081
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPConstructor.h
@@ -0,0 +1,28 @@
+#ifndef CPYCPPYY_CPPCONSTRUCTOR_H
+#define CPYCPPYY_CPPCONSTRUCTOR_H
+
+// Bindings
+#include "CPPMethod.h"
+
+
+namespace CPyCppyy {
+
+class CPPConstructor : public CPPMethod {
+public:
+ using CPPMethod::CPPMethod;
+
+public:
+ virtual PyObject* GetDocString();
+ virtual PyCallable* Clone() { return new CPPConstructor(*this); }
+
+public:
+ virtual PyObject* Call(
+ CPPInstance*& self, PyObject* args, PyObject* kwds, CallContext* ctxt = nullptr);
+
+protected:
+ virtual bool InitExecutor_(Executor*&, CallContext* ctxt = nullptr);
+};
+
+} // namespace CPyCppyy
+
+#endif // !CPYCPPYY_CPPCONSTRUCTOR_H
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPDataMember.cxx b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPDataMember.cxx
new file mode 100644
index 0000000000000..c5fb1b3436e7e
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPDataMember.cxx
@@ -0,0 +1,253 @@
+// Bindings
+#include "CPyCppyy.h"
+#include "PyStrings.h"
+#include "CPPDataMember.h"
+#include "CPPInstance.h"
+#include "Utility.h"
+
+// Standard
+#include
+
+
+namespace CPyCppyy {
+
+enum ETypeDetails {
+ kNone = 0,
+ kIsStaticData = 1,
+ kIsEnumData = 2,
+ kIsConstData = 4,
+ kIsArrayType = 8
+};
+
+//= CPyCppyy data member as Python property behavior =========================
+static PyObject* pp_get(CPPDataMember* pyprop, CPPInstance* pyobj, PyObject*)
+{
+// normal getter access
+ void* address = pyprop->GetAddress(pyobj);
+ if (!address || (ptrdiff_t)address == -1 /* Cling error */)
+ return nullptr;
+
+// for fixed size arrays
+ void* ptr = address;
+ if (pyprop->fProperty & kIsArrayType)
+ ptr = &address;
+
+// non-initialized or public data accesses through class (e.g. by help())
+ if (!ptr || (ptrdiff_t)ptr == -1 /* Cling error */) {
+ Py_INCREF(pyprop);
+ return (PyObject*)pyprop;
+ }
+
+ if (pyprop->fConverter != 0) {
+ PyObject* result = pyprop->fConverter->FromMemory(ptr);
+ if (!result)
+ return result;
+
+ // 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")
+ if (pyobj && 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\"", pyprop->GetName().c_str());
+ return nullptr;
+}
+
+//-----------------------------------------------------------------------------
+static int pp_set(CPPDataMember* pyprop, CPPInstance* pyobj, PyObject* value)
+{
+/// Set the value of the C++ datum held.
+ const int errret = -1;
+
+// filter const objects to prevent changing their values
+ if (pyprop->fProperty & kIsConstData) {
+ PyErr_SetString(PyExc_TypeError, "assignment to const data not allowed");
+ return errret;
+ }
+
+ ptrdiff_t address = (ptrdiff_t)pyprop->GetAddress(pyobj);
+ if (!address || address == -1 /* Cling error */)
+ return errret;
+
+// for fixed size arrays
+ void* ptr = (void*)address;
+ if (pyprop->fProperty & kIsArrayType)
+ ptr = &address;
+
+// actual conversion; return on success
+ if (pyprop->fConverter && pyprop->fConverter->ToMemory(value, ptr))
+ 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* pp_new(PyTypeObject* pytype, PyObject*, PyObject*)
+{
+// Create and initialize a new property descriptor.
+ CPPDataMember* pyprop = (CPPDataMember*)pytype->tp_alloc(pytype, 0);
+
+ pyprop->fOffset = 0;
+ pyprop->fProperty = 0;
+ pyprop->fConverter = nullptr;
+ pyprop->fEnclosingScope = 0;
+ new (&pyprop->fName) std::string();
+
+ return pyprop;
+}
+
+//----------------------------------------------------------------------------
+static void pp_dealloc(CPPDataMember* pyprop)
+{
+// Deallocate memory held by this descriptor.
+ using namespace std;
+ delete pyprop->fConverter;
+ pyprop->fName.~string();
+
+ Py_TYPE(pyprop)->tp_free((PyObject*)pyprop);
+}
+
+
+//= 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)pp_dealloc, // tp_dealloc
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ 0, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ 0, // tp_hash
+ 0, // tp_call
+ 0, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // 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
+ 0, // tp_methods
+ 0, // tp_members
+ 0, // tp_getset
+ 0, // tp_base
+ 0, // tp_dict
+ (descrgetfunc)pp_get, // tp_descr_get
+ (descrsetfunc)pp_set, // tp_descr_set
+ 0, // tp_dictoffset
+ 0, // tp_init
+ 0, // tp_alloc
+ (newfunc)pp_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
+#if PY_VERSION_HEX >= 0x02030000
+ , 0 // tp_del
+#endif
+#if PY_VERSION_HEX >= 0x02060000
+ , 0 // tp_version_tag
+#endif
+#if PY_VERSION_HEX >= 0x03040000
+ , 0 // tp_finalize
+#endif
+};
+
+} // namespace CPyCppyy
+
+
+//- public members -----------------------------------------------------------
+void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata)
+{
+ fEnclosingScope = scope;
+ fName = Cppyy::GetDatamemberName(scope, idata);
+ fOffset = Cppyy::GetDatamemberOffset(scope, idata); // TODO: make lazy
+ fProperty = Cppyy::IsStaticData(scope, idata) ? kIsStaticData : 0;
+
+ int size = Cppyy::GetDimensionSize(scope, idata, 0);
+ if (0 < size)
+ fProperty |= kIsArrayType;
+
+ if (size == INT_MAX) // meaning: incomplete array type
+ size = -1;
+
+ std::string fullType = Cppyy::GetDatamemberType(scope, idata);
+ if (Cppyy::IsEnumData(scope, idata)) {
+ fullType = Cppyy::ResolveEnum(fullType); // enum might be any type of int
+ fProperty |= kIsEnumData;
+ }
+
+ if (Cppyy::IsConstData(scope, idata))
+ fProperty |= kIsConstData;
+
+ fConverter = CreateConverter(fullType, size);
+}
+
+//-----------------------------------------------------------------------------
+void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, const std::string& name, void* address)
+{
+ fEnclosingScope = scope;
+ fName = name;
+ fOffset = (ptrdiff_t)address;
+ fProperty = (kIsStaticData | kIsConstData | kIsEnumData /* true, but may chance */);
+ fConverter = CreateConverter("UInt_t", -1); // TODO: use general enum_t type
+}
+
+//-----------------------------------------------------------------------------
+void* CPyCppyy::CPPDataMember::GetAddress(CPPInstance* pyobj)
+{
+// class attributes, global properties
+ if (fProperty & 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;
+ if (pyobj->ObjectIsA() != fEnclosingScope)
+ offset = Cppyy::GetBaseOffset(pyobj->ObjectIsA(), fEnclosingScope, obj, 1 /* up-cast */);
+
+ return (void*)((ptrdiff_t)obj + offset + fOffset);
+}
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPDataMember.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPDataMember.h
new file mode 100644
index 0000000000000..d848d142ee741
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPDataMember.h
@@ -0,0 +1,74 @@
+#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::TCppIndex_t idata);
+ void Set(Cppyy::TCppScope_t scope, const std::string& name, void* address);
+
+ std::string GetName() { return fName; }
+ void* GetAddress(CPPInstance* pyobj /* owner */);
+
+public: // public, as the python C-API works with C structs
+ PyObject_HEAD
+ ptrdiff_t fOffset;
+ Long_t fProperty;
+ Converter* fConverter;
+ Cppyy::TCppScope_t fEnclosingScope;
+ std::string fName;
+
+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::TCppIndex_t idata)
+{
+// 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, idata);
+ 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/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPFunction.cxx b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPFunction.cxx
new file mode 100644
index 0000000000000..8ccf2687fef7a
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPFunction.cxx
@@ -0,0 +1,59 @@
+// Bindings
+#include "CPyCppyy.h"
+#include "CPPFunction.h"
+#include "CPPInstance.h"
+
+
+//- public members --------------------------------------------------------------
+PyObject* CPyCppyy::CPPFunction::PreProcessArgs(
+ CPPInstance*& self, PyObject* args, PyObject*)
+{
+// no self means called as a free function; all ok
+ if (!self) {
+ Py_INCREF(args);
+ return args;
+ }
+
+// otherwise, add self as part of the function arguments (means bound member)
+ Py_ssize_t sz = PyTuple_GET_SIZE(args);
+ PyObject* newArgs = PyTuple_New(sz+1);
+ for (int i = 0; i < sz; ++i) {
+ PyObject* item = PyTuple_GET_ITEM(args, i);
+ Py_INCREF(item);
+ PyTuple_SET_ITEM(newArgs, i+1, item);
+ }
+
+ Py_INCREF(self);
+ PyTuple_SET_ITEM(newArgs, 0, (PyObject*)self);
+
+ return newArgs;
+}
+
+//---------------------------------------------------------------------------
+PyObject* CPyCppyy::CPPFunction::Call(
+ CPPInstance*& self, PyObject* args, PyObject* kwds, CallContext* ctxt)
+{
+// preliminary check in case keywords are accidently used (they are ignored otherwise)
+ if (kwds && PyDict_Size(kwds)) {
+ PyErr_SetString(PyExc_TypeError, "keyword arguments are not yet supported");
+ return nullptr;
+ }
+
+// setup as necessary
+ if (!this->Initialize(ctxt))
+ return nullptr;
+
+// reorder self into args, if necessary
+ if (!(args = this->PreProcessArgs(self, args, kwds)))
+ return nullptr;
+
+// translate the arguments
+ bool bConvertOk = this->ConvertAndSetArgs(args, ctxt);
+ Py_DECREF(args);
+
+ if (bConvertOk == false)
+ return nullptr;
+
+// execute function
+ return this->Execute(nullptr, 0, ctxt);
+}
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPFunction.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPFunction.h
new file mode 100644
index 0000000000000..93b9c84180117
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPFunction.h
@@ -0,0 +1,23 @@
+#ifndef CPYCPPYY_CPPFUNCTION_H
+#define CPYCPPYY_CPPFUNCTION_H
+
+// Bindings
+#include "CPPMethod.h"
+
+
+namespace CPyCppyy {
+
+class CPPFunction : public CPPMethod {
+public:
+ using CPPMethod::CPPMethod;
+
+ virtual PyCallable* Clone() { return new CPPFunction(*this); }
+
+ virtual PyObject* PreProcessArgs(CPPInstance*& self, PyObject* args, PyObject* kwds);
+ virtual PyObject* Call(
+ CPPInstance*&, PyObject* args, PyObject* kwds, CallContext* ctx = nullptr);
+};
+
+} // namespace CPyCppyy
+
+#endif // !CPYCPPYY_CPPFUNCTION_H
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPInstance.cxx b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPInstance.cxx
new file mode 100644
index 0000000000000..b61fa6163d601
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPInstance.cxx
@@ -0,0 +1,390 @@
+// Bindings
+#include "CPyCppyy.h"
+#include "CPPInstance.h"
+#include "MemoryRegulator.h"
+#include "ProxyWrappers.h"
+#include "PyStrings.h"
+#include "TypeManip.h"
+#include "Utility.h"
+
+// Standard
+#include
+
+
+//______________________________________________________________________________
+// 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.
+
+
+//----------------------------------------------------------------------------
+void CPyCppyy::op_dealloc_nofree(CPPInstance* pyobj) {
+// Destroy the held C++ object, if owned; does not deallocate the proxy.
+ bool isSmartPtr = pyobj->fFlags & CPPInstance::kIsSmartPtr;
+ Cppyy::TCppType_t klass = isSmartPtr ? pyobj->fSmartPtrType : pyobj->ObjectIsA();
+
+ if (!(pyobj->fFlags & CPPInstance::kIsReference))
+ MemoryRegulator::UnregisterPyObject(pyobj, klass);
+
+ if (pyobj->fFlags & CPPInstance::kIsValue) {
+ void* addr = isSmartPtr ? pyobj->fObject : pyobj->GetObject();
+ Cppyy::CallDestructor(klass, addr);
+ Cppyy::Deallocate(klass, addr);
+ } else if (pyobj->fObject && (pyobj->fFlags & CPPInstance::kIsOwner)) {
+ void* addr = isSmartPtr ? pyobj->fObject : pyobj->GetObject();
+ Cppyy::Destruct(klass, addr);
+ }
+ pyobj->fObject = nullptr;
+}
+
+
+namespace CPyCppyy {
+
+//= CPyCppyy object proxy null-ness checking =================================
+static PyObject* op_nonzero(CPPInstance* self)
+{
+// Null of the proxy is determined by null-ness of the held C++ object.
+ PyObject* result = self->GetObject() ? Py_True : Py_False;
+ Py_INCREF(result);
+ 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_PyUnicode_Type, &mname, &CPyCppyy_PyUnicode_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_smart_ptr(CPPInstance* self)
+{
+ if (!(self->fFlags & CPPInstance::kIsSmartPtr)) {
+ // TODO: more likely should raise
+ Py_RETURN_NONE;
+ }
+
+ return (PyObject*)CPyCppyy::BindCppObject(self->fObject, self->fSmartPtrType);
+}
+
+
+//----------------------------------------------------------------------------
+static PyMethodDef op_methods[] = {
+ {(char*)"__nonzero__", (PyCFunction)op_nonzero, METH_NOARGS, nullptr},
+ {(char*)"__bool__", (PyCFunction)op_nonzero, METH_NOARGS, nullptr}, // for p3
+ {(char*)"__destruct__", (PyCFunction)op_destruct, METH_NOARGS, nullptr},
+ {(char*)"__dispatch__", (PyCFunction)op_dispatch, METH_VARARGS,
+ (char*)"dispatch to selected overload"},
+ {(char*)"__smartptr__", (PyCFunction)op_get_smart_ptr, METH_NOARGS,
+ (char*)"get associated smart pointer, if any"},
+ {(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 = 0;
+ pyobj->fSmartPtrType = (Cppyy::TCppType_t)0;
+ pyobj->fDereferencer = (Cppyy::TCppMethod_t)0;
+
+ return pyobj;
+}
+
+//----------------------------------------------------------------------------
+static void op_dealloc(CPPInstance* pyobj)
+{
+// Remove (Python-side) memory held by the object proxy.
+ op_dealloc_nofree(pyobj);
+ Py_TYPE(pyobj)->tp_free((PyObject*)pyobj);
+}
+
+//----------------------------------------------------------------------------
+static PyObject* op_richcompare(CPPInstance* self, CPPInstance* other, int op)
+{
+// Rich set of comparison objects; only equals and not-equals are defined.
+ if (op != Py_EQ && op != Py_NE) {
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+ }
+
+ bool bIsEq = false;
+
+// special case for None to compare True to a null-pointer
+ if ((PyObject*)other == Py_None && !self->fObject)
+ bIsEq = true;
+
+// type + held pointer value defines identity (will cover if other is not
+// actually an CPPInstance, as ob_type will be unequal)
+ else if (Py_TYPE(self) == Py_TYPE(other) && self->GetObject() == other->GetObject())
+ bIsEq = true;
+
+ if ((op == Py_EQ && bIsEq) || (op == Py_NE && !bIsEq)) {
+ Py_RETURN_TRUE;
+ }
+
+ Py_RETURN_FALSE;
+}
+
+//----------------------------------------------------------------------------
+static PyObject* op_repr(CPPInstance* pyobj)
+{
+// 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(pyobj);
+ PyObject* modname = PyObject_GetAttr(pyclass, PyStrings::gModule);
+ Py_DECREF(pyclass);
+
+ Cppyy::TCppType_t klass = pyobj->ObjectIsA();
+ std::string clName = klass ? Cppyy::GetFinalName(klass) : "";
+ if (pyobj->fFlags & CPPInstance::kIsReference)
+ clName.append("*");
+
+ PyObject* repr = nullptr;
+ if (pyobj->fFlags & CPPInstance::kIsSmartPtr) {
+ Cppyy::TCppType_t smartPtrType = pyobj->fSmartPtrType;
+ std::string smartPtrName = smartPtrType ?
+ Cppyy::GetFinalName(smartPtrType) : "unknown smart pointer";
+ repr = CPyCppyy_PyUnicode_FromFormat(
+ const_cast("<%s.%s object at %p held by %s at %p>"),
+ CPyCppyy_PyUnicode_AsString(modname), clName.c_str(),
+ pyobj->GetObject(), smartPtrName.c_str(), pyobj->fObject);
+ } else {
+ repr = CPyCppyy_PyUnicode_FromFormat(const_cast("<%s.%s object at %p>"),
+ CPyCppyy_PyUnicode_AsString(modname), clName.c_str(), pyobj->GetObject());
+ }
+
+ Py_DECREF(modname);
+ return repr;
+}
+
+
+//-----------------------------------------------------------------------------
+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(name, op, pystring) \
+static PyObject* op_##name##_stub(PyObject* left, PyObject* right) \
+{ \
+ if (!CPPInstance_Check(left)) { \
+ if (CPPInstance_Check(right)) { \
+ std::swap(left, right); \
+ } else { \
+ Py_INCREF(Py_NotImplemented); \
+ return Py_NotImplemented; \
+ } \
+ } \
+/* place holder to lazily install __name__ if a global overload is available */\
+ if (!Utility::AddBinaryOperator( \
+ left, right, #op, "__"#name"__", "__r"#name"__")) { \
+ Py_INCREF(Py_NotImplemented); \
+ return Py_NotImplemented; \
+ } \
+ \
+/* redo the call, which will now go to the newly installed method */ \
+ return PyObject_CallMethodObjArgs(left, pystring, right, nullptr); \
+}
+
+CPYCPPYY_STUB(add, +, PyStrings::gAdd)
+CPYCPPYY_STUB(sub, -, PyStrings::gSub)
+CPYCPPYY_STUB(mul, *, PyStrings::gMul)
+CPYCPPYY_STUB(div, /, PyStrings::gDiv)
+
+//-----------------------------------------------------------------------------
+static PyNumberMethods op_as_number = {
+ (binaryfunc)op_add_stub, // nb_add
+ (binaryfunc)op_sub_stub, // nb_subtract
+ (binaryfunc)op_mul_stub, // nb_multiply
+#if PY_VERSION_HEX < 0x03000000
+ (binaryfunc)op_div_stub, // nb_divide
+#endif
+ 0, // nb_remainder
+ 0, // nb_divmod
+ 0, // nb_power
+ 0, // nb_negative
+ 0, // nb_positive
+ 0, // nb_absolute
+ 0, // tp_nonzero (nb_bool in p3)
+ 0, // nb_invert
+ 0, // nb_lshift
+ 0, // nb_rshift
+ 0, // nb_and
+ 0, // nb_xor
+ 0, // nb_or
+#if PY_VERSION_HEX < 0x03000000
+ 0, // nb_coerce
+#endif
+ 0, // nb_int
+ 0, // nb_long (nb_reserved in p3)
+ 0, // nb_float
+#if PY_VERSION_HEX < 0x03000000
+ 0, // nb_oct
+ 0, // nb_hex
+#endif
+ 0, // nb_inplace_add
+ 0, // nb_inplace_subtract
+ 0, // nb_inplace_multiply
+#if PY_VERSION_HEX < 0x03000000
+ 0, // nb_inplace_divide
+#endif
+ 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
+#if PY_VERSION_HEX >= 0x02020000
+ , 0 // nb_floor_divide
+#if PY_VERSION_HEX < 0x03000000
+ , 0 // nb_true_divide
+#else
+ , (binaryfunc)op_div_stub // nb_true_divide
+#endif
+ , 0 // nb_inplace_floor_divide
+ , 0 // nb_inplace_true_divide
+#endif
+#if PY_VERSION_HEX >= 0x02050000
+ , 0 // nb_index
+#endif
+#if PY_VERSION_HEX >= 0x03050000
+ , 0 // nb_matrix_multiply
+ , 0 // nb_inplace_matrix_multiply
+#endif
+};
+
+
+//= 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_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ (reprfunc)op_repr, // tp_repr
+ &op_as_number, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ PyBaseObject_Type.tp_hash, // tp_hash
+ 0, // tp_call
+ 0, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT |
+ Py_TPFLAGS_BASETYPE |
+ Py_TPFLAGS_HAVE_GC |
+ Py_TPFLAGS_CHECKTYPES, // tp_flags
+ (char*)"cppyy object proxy (internal)", // tp_doc
+ 0, // tp_traverse
+ 0, // 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
+#if PY_VERSION_HEX >= 0x02030000
+ , 0 // tp_del
+#endif
+#if PY_VERSION_HEX >= 0x02060000
+ , 0 // tp_version_tag
+#endif
+#if PY_VERSION_HEX >= 0x03040000
+ , 0 // tp_finalize
+#endif
+};
+
+} // namespace CPyCppyy
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPInstance.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPInstance.h
new file mode 100644
index 0000000000000..811fde4250899
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPInstance.h
@@ -0,0 +1,113 @@
+#ifndef CPYCPPYY_CPPINSTANCE_H
+#define CPYCPPYY_CPPINSTANCE_H
+
+//////////////////////////////////////////////////////////////////////////////
+// //
+// CpyCppyy::CPPInstance //
+// //
+// Python-side proxy, encapsulaties a C++ object. //
+// //
+//////////////////////////////////////////////////////////////////////////////
+
+
+// Bindings
+#include "CPPScope.h"
+#include "Cppyy.h"
+#include "CallContext.h" // for Parameter
+
+
+// TODO: have an CPPInstance derived or alternative type for smart pointers
+
+namespace CPyCppyy {
+
+class CPPInstance {
+public:
+ enum EFlags {
+ kNone = 0x0,
+ kIsOwner = 0x0001,
+ kIsReference = 0x0002,
+ kIsRValue = 0x0004,
+ kIsValue = 0x0008,
+ kIsSmartPtr = 0x0010,
+ kIsPtrPtr = 0x0020 };
+
+public:
+ void Set(void* address, EFlags flags = kNone)
+ {
+ // Initialize the proxy with the pointer value 'address.'
+ fObject = address;
+ fFlags = flags;
+ fSmartPtrType = (Cppyy::TCppType_t)0;
+ fDereferencer = (Cppyy::TCppMethod_t)0;
+ }
+
+ void SetSmartPtr(Cppyy::TCppType_t ptrtype, Cppyy::TCppMethod_t deref)
+ {
+ fFlags |= kIsSmartPtr;
+ fSmartPtrType = ptrtype;
+ fDereferencer = deref;
+ }
+
+ void* GetObject() const
+ {
+ // Retrieve a pointer to the held C++ object.
+
+ // We get the raw pointer from the smart pointer each time, in case
+ // it has changed or has been freed.
+ if (fFlags & kIsSmartPtr) {
+ std::vector args;
+ return Cppyy::CallR(fDereferencer, fObject, &args);
+ }
+
+ if (fObject && (fFlags & kIsReference))
+ return *(reinterpret_cast(const_cast(fObject)));
+ else
+ return const_cast(fObject); // may be null
+ }
+
+ Cppyy::TCppType_t ObjectIsA() const
+ {
+ // Retrieve a pointer to the C++ type; may return nullptr.
+ return ((CPPClass*)Py_TYPE(this))->fCppType;
+ }
+
+ void PythonOwns() { fFlags |= kIsOwner; }
+ void CppOwns() { fFlags &= ~kIsOwner; }
+
+public: // public, as the python C-API works with C structs
+ PyObject_HEAD
+ void* fObject;
+ int fFlags;
+
+// TODO: should be its own version of CPPInstance so as not to clutter the
+// normal instances
+ Cppyy::TCppType_t fSmartPtrType;
+ Cppyy::TCppMethod_t fDereferencer;
+
+private:
+ CPPInstance() = delete;
+};
+
+
+//- object proxy type and type verification ----------------------------------
+extern PyTypeObject CPPInstance_Type;
+
+template
+inline bool CPPInstance_Check(T* object)
+{
+ return object && PyObject_TypeCheck(object, &CPPInstance_Type);
+}
+
+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/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPMethod.cxx
new file mode 100644
index 0000000000000..351de34bb0c8c
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPMethod.cxx
@@ -0,0 +1,594 @@
+// Bindings
+#include "CPyCppyy.h"
+#include "CPPMethod.h"
+#include "CPPInstance.h"
+#include "Converters.h"
+#include "Executors.h"
+#include "ProxyWrappers.h"
+#include "PyStrings.h"
+#include "TPyException.h"
+#include "Utility.h"
+
+// Standard
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+//- data and local helpers ---------------------------------------------------
+namespace CPyCppyy {
+ extern PyObject* gThisModule;
+}
+
+
+//- private helpers ----------------------------------------------------------
+inline void CPyCppyy::CPPMethod::Copy_(const CPPMethod& /* other */)
+{
+// fScope and fMethod handled separately
+
+// do not copy caches
+ fExecutor = nullptr;
+ fArgsRequired = -1;
+
+// being uninitialized will trigger setting up caches as appropriate
+ fIsInitialized = false;
+}
+
+//----------------------------------------------------------------------------
+inline void CPyCppyy::CPPMethod::Destroy_() const
+{
+// destroy executor and argument converters
+ delete fExecutor;
+
+ for (int i = 0; i < (int)fConverters.size(); ++i)
+ delete fConverters[i];
+}
+
+//----------------------------------------------------------------------------
+inline PyObject* CPyCppyy::CPPMethod::CallFast(
+ void* self, ptrdiff_t offset, CallContext* ctxt)
+{
+// Helper code to prevent some duplication; this is called from CallSafe() as well
+// as directly from CPPMethod::Execute in fast mode.
+ PyObject* result = nullptr;
+
+ try { // C++ try block
+ result = fExecutor->Execute(fMethod, (Cppyy::TCppObject_t)((Long_t)self+offset), ctxt);
+ } catch (TPyException&) {
+ result = nullptr; // error already set
+ } catch (std::exception& e) {
+ /* TODO: figure out what this is about ... ?
+ if (gInterpreter->DiagnoseIfInterpreterException(e)) {
+ return result;
+ }
+
+ // TODO: write w/ot use of TClass
+
+ // map user exceptions .. this needs to move to Cppyy.cxx
+ TClass* cl = TClass::GetClass(typeid(e));
+
+ PyObject* pyUserExcepts = PyObject_GetAttrString(gThisModule, "UserExceptions");
+ std::string exception_type;
+ if (cl) exception_type = cl->GetName();
+ else {
+ int errorCode;
+ std::unique_ptr demangled(TClassEdit::DemangleTypeIdName(typeid(e),errorCode));
+ if (errorCode) exception_type = typeid(e).name();
+ else exception_type = demangled.get();
+ }
+ PyObject* pyexc = PyDict_GetItemString(pyUserExcepts, exception_type.c_str());
+ if (!pyexc) {
+ PyErr_Clear();
+ pyexc = PyDict_GetItemString(pyUserExcepts, ("std::"+exception_type).c_str());
+ }
+ Py_DECREF(pyUserExcepts);
+
+ if (pyexc) {
+ PyErr_Format(pyexc, "%s", e.what());
+ } else {
+ PyErr_Format(PyExc_Exception, "%s (C++ exception of type %s)", e.what(), exception_type.c_str());
+ }
+ */
+
+ PyErr_Format(PyExc_Exception, "%s (C++ exception)", e.what());
+ result = nullptr;
+ } catch (...) {
+ PyErr_SetString(PyExc_Exception, "unhandled, unknown C++ exception");
+ result = nullptr;
+ }
+ return result;
+}
+
+//----------------------------------------------------------------------------
+inline PyObject* CPyCppyy::CPPMethod::CallSafe(
+ void* self, ptrdiff_t offset, CallContext* ctxt)
+{
+// Helper code to prevent some code duplication; this code embeds a "try/catch"
+// block that saves the stack for restoration in case of an otherwise fatal signal.
+ PyObject* result = 0;
+
+// TRY { // ROOT "try block"
+ result = CallFast(self, offset, ctxt);
+ // } CATCH(excode) {
+ // PyErr_SetString(PyExc_SystemError, "problem in C++; program state has been reset");
+ // result = 0;
+ // Throw(excode);
+ // } 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 (size_t iarg = 0; iarg < nArgs; ++iarg) {
+ const std::string& fullType = Cppyy::GetMethodArgType(fMethod, iarg);
+ // CLING WORKAROUND -- std::string can not use kExactMatch as that will
+ // fail, but if no exact match is used, the const-ref
+ // std::string arguments will mask the const char* ones,
+ // even though the extra default arguments differ
+ if (Cppyy::GetFinalName(fScope) == "string" && \
+ Cppyy::GetMethodName(fMethod) == "string" &&
+ // Note with the improve naming normalization we should see only
+ // the spelling "const string&" (and will be "const std::string&")
+ (fullType == "const std::string&" || fullType == "const std::string &"
+ || fullType == "const string&" || fullType == "const string &")) {
+ fConverters[iarg] = new StrictCppObjectConverter(
+ Cppyy::GetScope("string"), false); // TODO: this is sooo wrong
+ // -- CLING WORKAROUND
+ } else
+ fConverters[iarg] = CreateConverter(fullType);
+
+ if (!fConverters[iarg]) {
+ PyErr_Format(PyExc_TypeError, "argument type %s not handled", fullType.c_str());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+//----------------------------------------------------------------------------
+bool CPyCppyy::CPPMethod::InitExecutor_(Executor*& executor, CallContext* ctxt)
+{
+// install executor conform to the return type
+ executor = CreateExecutor(
+ (bool)fMethod == true ? Cppyy::ResolveName(Cppyy::GetMethodResultType(fMethod))\
+ : Cppyy::GetScopedFinalName(fScope),
+ ctxt ? ManagesSmartPtr(ctxt) : false);
+
+ if (!executor)
+ return false;
+
+ return true;
+}
+
+//----------------------------------------------------------------------------
+std::string CPyCppyy::CPPMethod::GetSignatureString(bool fa)
+{
+// built a signature representation (used for doc strings)
+ std::stringstream sig; sig << "(";
+ int count = 0;
+ const size_t nArgs = Cppyy::GetMethodNumArgs(fMethod);
+ for (size_t iarg = 0; iarg < nArgs; ++iarg) {
+ if (count) sig << (fa ? ", " : ",");
+
+ sig << Cppyy::GetMethodArgType(fMethod, iarg);
+
+ if (fa) {
+ const std::string& parname = Cppyy::GetMethodArgName(fMethod, iarg);
+ if (!parname.empty())
+ sig << " " << parname;
+
+ const std::string& defvalue = Cppyy::GetMethodArgDefault(fMethod, iarg);
+ if (!defvalue.empty())
+ sig << " = " << defvalue;
+ }
+ count++;
+ }
+ sig << ")";
+ return sig.str();
+}
+
+//----------------------------------------------------------------------------
+void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg)
+{
+// helper to report errors in a consistent format (derefs msg)
+ PyObject *etype, *evalue, *etrace;
+ PyErr_Fetch(&etype, &evalue, &etrace);
+
+ std::string details = "";
+ if (evalue) {
+ PyObject* descr = PyObject_Str(evalue);
+ if (descr) {
+ details = CPyCppyy_PyUnicode_AsString(descr);
+ Py_DECREF(descr);
+ }
+ }
+
+ Py_XDECREF(evalue); Py_XDECREF(etrace);
+
+ PyObject* doc = GetDocString();
+ PyObject* errtype = etype;
+ if (!errtype) {
+ Py_INCREF(PyExc_TypeError);
+ errtype = PyExc_TypeError;
+ }
+ PyObject* pyname = PyObject_GetAttr(errtype, PyStrings::gName);
+ const char* cname = pyname ? CPyCppyy_PyUnicode_AsString(pyname) : "Exception";
+
+ if (details.empty()) {
+ PyErr_Format(errtype, "%s =>\n %s: %s", CPyCppyy_PyUnicode_AsString(doc),
+ cname, msg ? CPyCppyy_PyUnicode_AsString(msg) : "");
+ } else if (msg) {
+ PyErr_Format(errtype, "%s =>\n %s: %s (%s)",
+ CPyCppyy_PyUnicode_AsString(doc), cname, CPyCppyy_PyUnicode_AsString(msg),
+ details.c_str());
+ } else {
+ PyErr_Format(errtype, "%s =>\n %s: %s",
+ CPyCppyy_PyUnicode_AsString(doc), cname, details.c_str());
+ }
+
+ Py_XDECREF(pyname);
+ Py_XDECREF(etype);
+ Py_DECREF(doc);
+ Py_XDECREF(msg);
+}
+
+//- constructors and destructor ----------------------------------------------
+CPyCppyy::CPPMethod::CPPMethod(
+ Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method) :
+ fMethod(method), fScope(scope), fExecutor(nullptr), fArgsRequired(-1),
+ fIsInitialized(false)
+{
+ // empty
+}
+
+//----------------------------------------------------------------------------
+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 -----------------------------------------------------------
+PyObject* CPyCppyy::CPPMethod::GetPrototype(bool fa)
+{
+// construct python string from the method's prototype
+ return CPyCppyy_PyUnicode_FromFormat("%s%s %s::%s%s",
+ (Cppyy::IsStaticMethod(fMethod) ? "static " : ""),
+ Cppyy::GetMethodResultType(fMethod).c_str(),
+ Cppyy::GetScopedFinalName(fScope).c_str(), Cppyy::GetMethodName(fMethod).c_str(),
+ GetSignatureString(fa).c_str());
+}
+
+//----------------------------------------------------------------------------
+int CPyCppyy::CPPMethod::GetPriority()
+{
+// Method priorities exist (in lieu of true overloading) there to prevent
+// void* or * from usurping otherwise valid calls. TODO: extend this
+// to favour classes that are not bases.
+ int priority = 0;
+
+ const size_t nArgs = Cppyy::GetMethodNumArgs(fMethod);
+ for (size_t iarg = 0; iarg < nArgs; ++iarg) {
+ const std::string aname = Cppyy::GetMethodArgType(fMethod, iarg);
+
+ // the following numbers are made up and may cause problems in specific
+ // situations: use ..disp() for choice of exact dispatch
+ if (Cppyy::IsBuiltin(aname)) {
+ // happens for builtin types (and namespaces, but those can never be an
+ // argument), NOT for unknown classes as that concept no longer exists
+ if (strstr(aname.c_str(), "void*"))
+ // TODO: figure out in general all void* converters
+ priority -= 10000; // void*/void** shouldn't be too greedy
+ else if (strstr(aname.c_str(), "float"))
+ priority -= 1000; // double preferred (no float in python)
+ else if (strstr(aname.c_str(), "long double"))
+ priority -= 100; // id, but better than float
+ else if (strstr(aname.c_str(), "double"))
+ priority -= 10; // char, int, long can't convert float,
+ // but vv. works, so prefer the int types
+ else if (strstr(aname.c_str(), "bool"))
+ priority += 1; // bool over int (does accept 1 and 0)
+
+ } else if (aname.rfind("&&", aname.size()-2) != std::string::npos) {
+ priority += 100;
+ } else if (!aname.empty() && !Cppyy::IsComplete(aname)) {
+ // class is known, but no dictionary available, 2 more cases: * and &
+ if (aname[ aname.size() - 1 ] == '&')
+ priority -= 1000000;
+ else
+ priority -= 100000; // prefer pointer passing over reference
+ }
+ }
+
+// add a small penalty to prefer non-const methods over const ones for
+// getitem/setitem
+ if (Cppyy::IsConstMethod(fMethod) && Cppyy::GetMethodName(fMethod) == "operator[]")
+ priority -= 1;
+
+ return priority;
+}
+
+//----------------------------------------------------------------------------
+int CPyCppyy::CPPMethod::GetMaxArgs()
+{
+ return 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_PyUnicode_FromString("self"));
+ for (int iarg = 0; iarg < co_argcount; ++iarg) {
+ std::string argrep = Cppyy::GetMethodArgType(fMethod, iarg);
+ const std::string& parname = Cppyy::GetMethodArgName(fMethod, iarg);
+ if (!parname.empty()) {
+ argrep += " ";
+ argrep += parname;
+ }
+
+ PyObject* pyspec = CPyCppyy_PyUnicode_FromString(argrep.c_str());
+ PyTuple_SET_ITEM(co_varnames, iarg+1, pyspec);
+ }
+
+ return co_varnames;
+}
+
+PyObject* CPyCppyy::CPPMethod::GetArgDefault(int iarg)
+{
+// get the default value (if any) of argument iarg of this method
+ if (iarg >= (int)GetMaxArgs())
+ return nullptr;
+
+ const std::string& defvalue = Cppyy::GetMethodArgDefault(fMethod, iarg);
+ if (!defvalue.empty()) {
+
+ // attempt to evaluate the string representation (will work for all builtin types)
+ PyObject* pyval = (PyObject*)PyRun_String(
+ (char*)defvalue.c_str(), Py_eval_input, gThisModule, gThisModule);
+ if (!pyval && PyErr_Occurred()) {
+ PyErr_Clear();
+ return CPyCppyy_PyUnicode_FromString(defvalue.c_str());
+ }
+
+ return pyval;
+ }
+
+ return nullptr;
+}
+
+//----------------------------------------------------------------------------
+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);
+}
+
+
+//----------------------------------------------------------------------------
+bool CPyCppyy::CPPMethod::Initialize(CallContext* ctxt)
+{
+// done if cache is already setup
+ if (fIsInitialized == true)
+ return true;
+
+ if (!InitConverters_())
+ return false;
+
+ if (!InitExecutor_(fExecutor, ctxt))
+ return false;
+
+// minimum number of arguments when calling
+ fArgsRequired = (bool)fMethod == true ? Cppyy::GetMethodReqArgs(fMethod) : 0;
+
+// init done
+ fIsInitialized = true;
+
+ return true;
+}
+
+//----------------------------------------------------------------------------
+PyObject* CPyCppyy::CPPMethod::PreProcessArgs(
+ CPPInstance*& self, PyObject* args, PyObject*)
+{
+// verify existence of self, return if ok
+ if (self) {
+ Py_INCREF(args);
+ return args;
+ }
+
+// otherwise, check for a suitable 'self' in args and update accordingly
+ if (PyTuple_GET_SIZE(args) != 0) {
+ CPPInstance* pyobj = (CPPInstance*)PyTuple_GET_ITEM(args, 0);
+
+ // demand CPyCppyy object, and an argument that may match down the road
+ if (CPPInstance_Check(pyobj) &&
+ (fScope == Cppyy::gGlobalScope || // free global
+ (pyobj->ObjectIsA() == 0) || // null pointer or ctor call
+ (Cppyy::IsSubtype(pyobj->ObjectIsA(), fScope)))) { // matching types
+
+ // reset self
+ self = pyobj;
+ Py_INCREF(self); // corresponding Py_DECREF is in CPPOverload
+
+ // offset args by 1 (new ref)
+ return PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));
+ }
+ }
+
+// no self, set error and lament
+ SetPyError_(CPyCppyy_PyUnicode_FromFormat(
+ "unbound method %s::%s must be called with a %s instance as first argument",
+ Cppyy::GetFinalName(fScope).c_str(), Cppyy::GetMethodName(fMethod).c_str(),
+ Cppyy::GetFinalName(fScope).c_str()));
+ return nullptr;
+}
+
+//----------------------------------------------------------------------------
+bool CPyCppyy::CPPMethod::ConvertAndSetArgs(PyObject* args, CallContext* ctxt)
+{
+ int argc = PyTuple_GET_SIZE(args);
+ int argMax = fConverters.size();
+
+// argc must be between min and max number of arguments
+ if (argc < fArgsRequired) {
+ SetPyError_(CPyCppyy_PyUnicode_FromFormat(
+ "takes at least %d arguments (%d given)", fArgsRequired, argc));
+ return false;
+ } else if (argMax < argc) {
+ SetPyError_(CPyCppyy_PyUnicode_FromFormat(
+ "takes at most %d arguments (%d given)", argMax, argc));
+ return false;
+ }
+
+// convert the arguments to the method call array
+ ctxt->fArgs.resize(argc);
+ for (int i = 0; i < argc; ++i) {
+ if (!fConverters[i]->SetArg(
+ PyTuple_GET_ITEM(args, i), ctxt->fArgs[i], ctxt)) {
+ SetPyError_(CPyCppyy_PyUnicode_FromFormat("could not convert argument %d", i+1));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+//----------------------------------------------------------------------------
+PyObject* CPyCppyy::CPPMethod::Execute(void* self, ptrdiff_t offset, CallContext* ctxt)
+{
+// call the interface method
+ PyObject* result = 0;
+
+ if (CallContext::sSignalPolicy == CallContext::kFast) {
+ // bypasses try block (i.e. segfaults will abort)
+ result = CallFast(self, offset, ctxt);
+ } else {
+ // at the cost of ~10% performance, don't abort the interpreter on any signal
+ result = CallSafe(self, offset, ctxt);
+ }
+
+ if (result && Utility::PyErr_Occurred_WithGIL()) {
+ // can happen in the case of a CINT error: trigger exception processing
+ Py_DECREF(result);
+ result = 0;
+ } else if (!result && PyErr_Occurred())
+ SetPyError_(0);
+
+ return result;
+}
+
+//----------------------------------------------------------------------------
+PyObject* CPyCppyy::CPPMethod::Call(
+ CPPInstance*& self, PyObject* args, PyObject* kwds, CallContext* ctxt)
+{
+// preliminary check in case keywords are accidently used (they are ignored otherwise)
+ if (kwds && PyDict_Size(kwds)) {
+ PyErr_SetString(PyExc_TypeError, "keyword arguments are not yet supported");
+ return nullptr;
+ }
+
+// setup as necessary
+ if (!Initialize(ctxt))
+ return nullptr;
+
+// fetch self, verify, and put the arguments in usable order
+ if (!(args = PreProcessArgs(self, args, kwds)))
+ return nullptr;
+
+// translate the arguments
+ if (!ConvertAndSetArgs(args, ctxt)) {
+ Py_DECREF(args);
+ 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");
+ Py_DECREF(args);
+ return nullptr;
+ }
+
+// get its class
+ Cppyy::TCppType_t derived = self->ObjectIsA();
+
+// calculate offset (the method expects 'this' to be an object of fScope)
+ ptrdiff_t offset = 0;
+ if (derived && derived != fScope)
+ offset = Cppyy::GetBaseOffset(derived, fScope, object, 1 /* up-cast */);
+
+// actual call; recycle self instead of returning new object for same address objects
+ CPPInstance* pyobj = (CPPInstance*)Execute(object, offset, ctxt);
+ Py_DECREF(args);
+
+ 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_PyUnicode_FromString(GetSignatureString(fa).c_str());
+}
+
+//----------------------------------------------------------------------------
+std::string CPyCppyy::CPPMethod::GetReturnTypeName()
+{
+ return Cppyy::GetMethodResultType(fMethod);
+}
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPMethod.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPMethod.h
new file mode 100644
index 0000000000000..90f800cb2494c
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPMethod.h
@@ -0,0 +1,84 @@
+#ifndef CPYCPPYY_CPPMETHOD_H
+#define CPYCPPYY_CPPMETHOD_H
+
+// Bindings
+#include "PyCallable.h"
+
+// Standard
+#include
+#include
+
+
+namespace CPyCppyy {
+
+class Executor;
+class Converter;
+
+class CPPMethod : public PyCallable {
+public:
+ CPPMethod(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method);
+ CPPMethod(const CPPMethod&);
+ CPPMethod& operator=(const CPPMethod&);
+ virtual ~CPPMethod();
+
+public:
+ virtual PyObject* GetSignature(bool show_formalargs = true);
+ virtual PyObject* GetPrototype(bool show_formalargs = true);
+ virtual int GetPriority();
+
+ virtual int GetMaxArgs();
+ virtual PyObject* GetCoVarNames();
+ virtual PyObject* GetArgDefault(int iarg);
+ virtual PyObject* GetScopeProxy();
+ virtual Cppyy::TCppFuncAddr_t GetFunctionAddress();
+
+ virtual PyCallable* Clone() { return new CPPMethod(*this); }
+
+public:
+ virtual PyObject* Call(
+ CPPInstance*& self, PyObject* args, PyObject* kwds, CallContext* ctxt = nullptr);
+
+ virtual bool Initialize(CallContext* ctxt = nullptr);
+ virtual PyObject* PreProcessArgs(CPPInstance*& self, PyObject* args, PyObject* kwds);
+ virtual bool ConvertAndSetArgs(PyObject* args, CallContext* ctxt = nullptr);
+ virtual PyObject* Execute(void* self, ptrdiff_t offset, CallContext* ctxt = nullptr);
+
+protected:
+ 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_() const;
+
+ PyObject* CallFast(void*, ptrdiff_t, CallContext*);
+ PyObject* CallSafe(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;
+
+// cached values
+ int fArgsRequired;
+
+// admin
+ bool fIsInitialized;
+};
+
+} // namespace CPyCppyy
+
+#endif // !CPYCPPYY_CPPMETHOD_H
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPOverload.cxx b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPOverload.cxx
new file mode 100644
index 0000000000000..6ec1aeba0ad60
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPOverload.cxx
@@ -0,0 +1,887 @@
+// Bindings
+#include "CPyCppyy.h"
+#include "structmember.h" // from Python
+#if PY_VERSION_HEX >= 0x02050000
+#include "code.h" // from Python
+#else
+#include "compile.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 "TPyException.h"
+#include "PyStrings.h"
+#include "Utility.h"
+
+// Standard
+#include
+#include
+
+
+namespace CPyCppyy {
+
+namespace {
+
+// TODO: only used here, 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;
+ }
+ fCallable = callable;
+ Py_INCREF(fCallable);
+ }
+
+ virtual ~TPythonCallback() {
+ Py_DECREF(fCallable);
+ fCallable = nullptr;
+ }
+
+ virtual PyObject* GetSignature(bool /*show_formalargs*/ = true) {
+ return CPyCppyy_PyUnicode_FromString("*args, **kwargs");
+ }
+ virtual PyObject* GetPrototype(bool /*show_formalargs*/ = true) {
+ return CPyCppyy_PyUnicode_FromString("");
+ }
+ virtual PyObject* GetDocString() {
+ if (PyObject_HasAttrString(fCallable, "__doc__")) {
+ return PyObject_GetAttrString(fCallable, "__doc__");
+ } else {
+ return GetPrototype();
+ }
+ }
+
+ virtual int GetPriority() { return 100; };
+
+ virtual int GetMaxArgs() { return 100; };
+ virtual PyObject* GetCoVarNames() { // TODO: pick these up from the callable
+ Py_RETURN_NONE;
+ }
+ virtual PyObject* GetArgDefault(int /* iarg */) { // TODO: pick these up from the callable
+ Py_RETURN_NONE;
+ }
+
+ virtual PyObject* GetScopeProxy() { // should this be the module ??
+ Py_RETURN_NONE;
+ }
+
+ virtual Cppyy::TCppFuncAddr_t GetFunctionAddress() {
+ return (Cppyy::TCppFuncAddr_t)nullptr;
+ }
+
+ virtual PyCallable* Clone() { return new TPythonCallback(*this); }
+
+ virtual PyObject* Call(
+ CPPInstance*& self, PyObject* args, PyObject* kwds, CallContext* /* ctxt = 0 */) {
+
+ PyObject* newArgs = nullptr;
+ if (self) {
+ Py_ssize_t nargs = PyTuple_Size(args);
+ newArgs = PyTuple_New(nargs+1);
+ Py_INCREF(self);
+ PyTuple_SET_ITEM(newArgs, 0, (PyObject*)self);
+ for (Py_ssize_t iarg = 0; iarg < nargs; ++iarg) {
+ PyObject* pyarg = PyTuple_GET_ITEM(args, iarg);
+ Py_INCREF(pyarg);
+ PyTuple_SET_ITEM(newArgs, iarg+1, pyarg);
+ }
+ } else {
+ Py_INCREF(args);
+ newArgs = args;
+ }
+ return PyObject_Call(fCallable, newArgs, kwds);
+ }
+};
+
+// helper to test whether a method is used in a pseudo-function modus
+static inline bool IsPseudoFunc(CPPOverload* pymeth)
+{
+ return (void*)pymeth == (void*)pymeth->fSelf;
+}
+
+// helper to hash tuple (using tuple hash would cause self-tailing loops)
+static inline uint64_t HashSignature(PyObject* args)
+{
+// Build a hash from the types of the given python function arguments.
+ uint64_t hash = 0;
+
+ int nargs = PyTuple_GET_SIZE(args);
+ for (int 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 = PyTuple_GET_ITEM(args, i);
+ hash += (uint64_t)Py_TYPE(pyobj);
+ if (pyobj->ob_refcnt == 1)
+ hash += (uint64_t)pyobj->ob_refcnt;
+ hash += (hash << 10); hash ^= (hash >> 6);
+ }
+
+ hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15);
+
+ return hash;
+}
+
+// helper to sort on method priority
+static int PriorityCmp(PyCallable* left, PyCallable* right)
+{
+ return left->GetPriority() > right->GetPriority();
+}
+
+// return helper
+static inline void ResetCallState(CPPInstance*& selfnew, CPPInstance* selfold, bool clear)
+{
+ if (selfnew != selfold) {
+ Py_XDECREF(selfnew);
+ selfnew = selfold;
+ }
+
+ if (clear)
+ PyErr_Clear();
+}
+
+// helper to factor out return logic of mp_call
+static inline PyObject* HandleReturn(
+ CPPOverload* pymeth, CPPInstance* oldSelf, PyObject* result)
+{
+
+// special case for python exceptions, propagated through C++ layer
+ if (result) {
+
+ // 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 (pymeth->fSelf)
+ pymeth->fSelf->PythonOwns();
+ }
+
+ // ... or be a method with an object proxy return value
+ else if (CPPInstance_Check(result))
+ ((CPPInstance*)result)->PythonOwns();
+ }
+
+ // if this new object falls inside self, make sure its lifetime is proper
+ if (CPPInstance_Check(pymeth->fSelf) && CPPInstance_Check(result)) {
+ ptrdiff_t offset = (ptrdiff_t)(
+ (CPPInstance*)result)->GetObject() - (ptrdiff_t)pymeth->fSelf->GetObject();
+ if (0 <= offset && offset < (ptrdiff_t)Cppyy::SizeOf(pymeth->fSelf->ObjectIsA())) {
+ if (PyObject_SetAttr(result, PyStrings::gLifeLine, (PyObject*)pymeth->fSelf) == -1)
+ PyErr_Clear(); // ignored
+ }
+ }
+ }
+
+// reset self as necessary to allow re-use of the CPPOverload
+ ResetCallState(pymeth->fSelf, oldSelf, false);
+
+ return result;
+}
+
+
+//= CPyCppyy method proxy object behaviour ===================================
+static PyObject* mp_name(CPPOverload* pymeth, void*)
+{
+ return CPyCppyy_PyUnicode_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*)
+{
+// Build python document string ('__doc__') from all C++-side overloads.
+ CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods;
+
+// collect doc strings
+ int 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_PyUnicode_FromString("\n");
+ for (int i = 1; i < nMethods; ++i) {
+ CPyCppyy_PyUnicode_Append(&doc, separator);
+ CPyCppyy_PyUnicode_AppendAndDel(&doc, methods[i]->GetDocString());
+ }
+ Py_DECREF(separator);
+
+ return doc;
+}
+
+//-----------------------------------------------------------------------------
+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, use of 'meth' is for keeping track whether this
+// proxy is used in the capacity of a method or a function
+ newPyMeth->fSelf = (CPPInstance*)newPyMeth;
+
+ 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* pymeth, void*)
+{
+// Code details are used in module inspect to fill out interactive help()
+#if PY_VERSION_HEX < 0x03000000
+ CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods;
+
+// collect arguments only if there is just 1 overload, otherwise put in a
+// fake *args (see below for co_varnames)
+ PyObject* co_varnames = methods.size() == 1 ? methods[0]->GetCoVarNames() : nullptr;
+ if (!co_varnames) {
+ // TODO: static methods need no 'self' (but is harmless otherwise)
+ co_varnames = PyTuple_New(1 /* self */ + 1 /* fake */);
+ PyTuple_SET_ITEM(co_varnames, 0, CPyCppyy_PyUnicode_FromString("self"));
+ PyTuple_SET_ITEM(co_varnames, 1, CPyCppyy_PyUnicode_FromString("*args"));
+ }
+
+ int co_argcount = PyTuple_Size(co_varnames);
+
+// for now, code object representing the statement 'pass'
+ PyObject* co_code = PyString_FromStringAndSize("d\x00\x00S", 4);
+
+// tuples with all the const literals used in the function
+ PyObject* co_consts = PyTuple_New(0);
+ PyObject* co_names = PyTuple_New(0);
+
+// names, freevars, and cellvars go unused
+ PyObject* co_unused = PyTuple_New(0);
+
+// filename is made-up
+ PyObject* co_filename = PyString_FromString("cppyy.py");
+
+// name is the function name, also through __name__ on the function itself
+ PyObject* co_name = PyString_FromString(pymeth->GetName().c_str());
+
+// firstlineno is the line number of first function code in the containing scope
+
+// lnotab is a packed table that maps instruction count and line number
+ PyObject* co_lnotab = PyString_FromString("\x00\x01\x0c\x01");
+
+ PyObject* code = (PyObject*)PyCode_New(
+ co_argcount, // argcount
+ co_argcount+1, // nlocals
+ 2, // stacksize
+ CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE, // flags
+ co_code, // code
+ co_consts, // consts
+ co_names, // names
+ co_varnames, // varnames
+ co_unused, // freevars
+ co_unused, // cellvars
+ co_filename, // filename
+ co_name, // name
+ 1, // firstlineno
+ co_lnotab); // lnotab
+
+ Py_DECREF(co_lnotab);
+ Py_DECREF(co_name);
+ Py_DECREF(co_unused);
+ Py_DECREF(co_filename);
+ Py_DECREF(co_varnames);
+ Py_DECREF(co_names);
+ Py_DECREF(co_consts);
+ Py_DECREF(co_code);
+
+ return code;
+#else
+// not important for functioning of most code, so not implemented for p3 for now (TODO)
+ pymeth = 0;
+ Py_RETURN_NONE;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+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);
+ }
+ _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;
+}
+
+//-----------------------------------------------------------------------------
+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.
+ if (!value) { // means that _creates is being deleted
+ pymeth->fMethodInfo->fFlags &= ~CallContext::kIsCreator;
+ return 0;
+ }
+
+ long iscreator = PyLong_AsLong(value);
+ if (iscreator == -1 && PyErr_Occurred()) {
+ PyErr_SetString(PyExc_ValueError, "a boolean 1 or 0 is required for _creates");
+ return -1;
+ }
+
+ if (iscreator)
+ pymeth->fMethodInfo->fFlags |= CallContext::kIsCreator;
+ else
+ pymeth->fMethodInfo->fFlags &= ~CallContext::kIsCreator;
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+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;
+}
+
+//-----------------------------------------------------------------------------
+static PyObject* mp_get_manage_smart_ptr(CPPOverload* pymeth, void*)
+{
+// Get '_manage_smart_ptr' boolean, which determines whether or not to
+// manage returned smart pointers intelligently.
+ return PyInt_FromLong(
+ (long)(pymeth->fMethodInfo->fFlags & CallContext::kManageSmartPtr));
+}
+
+//-----------------------------------------------------------------------------
+static int mp_set_manage_smart_ptr(CPPOverload* pymeth, PyObject* value, void*)
+{
+// Set '_manage_smart_ptr' boolean, which determines whether or not to
+// manage returned smart pointers intelligently.
+ long policy = PyLong_AsLong(value);
+ if (policy == -1 && PyErr_Occurred()) {
+ PyErr_SetString(PyExc_ValueError, "a boolean 1 or 0 is required for _manage_smart_ptr");
+ return -1;
+ }
+
+ pymeth->fMethodInfo->fFlags |= CallContext::kManageSmartPtr;
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+static PyObject* mp_getthreaded(CPPOverload* pymeth, void*)
+{
+// Get '_threaded' boolean, which determines whether the GIL will be released.
+ return PyInt_FromLong(
+ (long)(pymeth->fMethodInfo->fFlags & CallContext::kReleaseGIL));
+}
+
+//-----------------------------------------------------------------------------
+static int mp_setthreaded(CPPOverload* pymeth, PyObject* value, void*)
+{
+// Set '_threaded' boolean, which determines whether the GIL will be released.
+ long isthreaded = PyLong_AsLong(value);
+ if (isthreaded == -1 && PyErr_Occurred()) {
+ PyErr_SetString(PyExc_ValueError, "a boolean 1 or 0 is required for _creates");
+ return -1;
+ }
+
+ if (isthreaded)
+ pymeth->fMethodInfo->fFlags |= CallContext::kReleaseGIL;
+ else
+ pymeth->fMethodInfo->fFlags &= ~CallContext::kReleaseGIL;
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+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, nullptr, 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, nullptr, nullptr, nullptr},
+ {(char*)"func_name", (getter)mp_name, nullptr, nullptr, nullptr},
+
+ {(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*)"_manage_smart_ptr", (getter)mp_get_manage_smart_ptr, (setter)mp_set_manage_smart_ptr,
+ (char*)"If a smart pointer is returned, determines management policy.", nullptr},
+ {(char*)"_threaded", (getter)mp_getthreaded, (setter)mp_setthreaded,
+ (char*)"If true, releases GIL on call into C++", nullptr},
+ {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}
+};
+
+//= CPyCppyy method proxy function behavior ==================================
+static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds)
+{
+// Call the appropriate overload of this method.
+
+// if called through im_func pseudo-representation (this can be gamed if the
+// user really wants to ...)
+ if (IsPseudoFunc(pymeth))
+ pymeth->fSelf = nullptr;
+
+ CPPInstance* oldSelf = pymeth->fSelf;
+
+// get local handles to proxy internals
+ auto& methods = pymeth->fMethodInfo->fMethods;
+ auto& dispatchMap = pymeth->fMethodInfo->fDispatchMap;
+ auto& mflags = pymeth->fMethodInfo->fFlags;
+
+ int nMethods = methods.size();
+
+ CallContext ctxt = {0};
+ ctxt.fFlags |= (mflags & CallContext::kUseHeuristics);
+ ctxt.fFlags |= (mflags & CallContext::kUseStrict);
+ ctxt.fFlags |= (mflags & CallContext::kManageSmartPtr);
+ if (!ctxt.fFlags) ctxt.fFlags |= CallContext::sMemoryPolicy;
+ ctxt.fFlags |= (mflags & CallContext::kReleaseGIL);
+
+// simple case
+ if (nMethods == 1) {
+ PyObject* result = methods[0]->Call(pymeth->fSelf, args, kwds, &ctxt);
+ return HandleReturn(pymeth, oldSelf, result);
+ }
+
+// otherwise, handle overloading
+ uint64_t sighash = HashSignature(args);
+
+// look for known signatures ...
+ CPPOverload::DispatchMap_t::iterator m = dispatchMap.find(sighash);
+ if (m != dispatchMap.end()) {
+ int index = m->second;
+ PyObject* result = methods[index]->Call(pymeth->fSelf, args, kwds, &ctxt);
+ result = HandleReturn(pymeth, oldSelf, result);
+
+ if (result != 0)
+ return result;
+
+ // fall through: python is dynamic, and so, the hashing isn't infallible
+ ResetCallState(pymeth->fSelf, oldSelf, true);
+ }
+
+// ... otherwise loop over all methods and find the one that does not fail
+ if (!IsSorted(mflags)) {
+ std::stable_sort(methods.begin(), methods.end(), PriorityCmp);
+ mflags |= CallContext::kIsSorted;
+ }
+
+ std::vector errors;
+ for (int i = 0; i < nMethods; ++i) {
+ PyObject* result = methods[i]->Call(pymeth->fSelf, args, kwds, &ctxt);
+
+ if (result != 0) {
+ // success: update the dispatch map for subsequent calls
+ dispatchMap[sighash] = i;
+ std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear);
+ return HandleReturn(pymeth, oldSelf, result);
+ }
+
+ // failure: 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_PyUnicode_AsString(sig), (char*)"nullptr result without error in mp_call");
+ Py_DECREF(sig);
+ }
+ Utility::FetchError(errors);
+ ResetCallState(pymeth->fSelf, oldSelf, false);
+ }
+
+// first summarize, then add details
+ PyObject* topmsg = CPyCppyy_PyUnicode_FromFormat(
+ "none of the %d overloaded methods succeeded. Full details:", nMethods);
+ SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */);
+
+// report failure
+ return nullptr;
+}
+
+//-----------------------------------------------------------------------------
+static CPPOverload* mp_descrget(CPPOverload* pymeth, CPPInstance* pyobj, PyObject*)
+{
+// Descriptor; create and return a new bound method proxy (language requirement).
+ 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 to be bound to current object (may be nullptr)
+ Py_XINCREF((PyObject*)pyobj);
+ newPyMeth->fSelf = pyobj;
+
+ 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->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);
+
+ if (!IsPseudoFunc(pymeth))
+ Py_CLEAR(pymeth->fSelf);
+ pymeth->fSelf = nullptr;
+
+ if (--(*pymeth->fMethodInfo->fRefCount) <= 0) {
+ delete pymeth->fMethodInfo;
+ }
+
+ 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.
+ return _Py_HashPointer(pymeth->fMethodInfo);
+}
+
+//-----------------------------------------------------------------------------
+static int mp_traverse(CPPOverload* pymeth, visitproc visit, void* args)
+{
+// Garbage collector traverse of held python member objects.
+ if (pymeth->fSelf && ! IsPseudoFunc(pymeth))
+ return visit((PyObject*)pymeth->fSelf, args);
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+static int mp_clear(CPPOverload* pymeth)
+{
+// Garbage collector clear of held python member objects.
+ if (!IsPseudoFunc(pymeth))
+ Py_CLEAR(pymeth->fSelf);
+ pymeth->fSelf = nullptr;
+
+ 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* sigarg)
+{
+// Select and call a specific C++ overload, based on its signature.
+ if (!CPyCppyy_PyUnicode_Check(sigarg)) {
+ PyErr_Format(PyExc_TypeError, "__overload__() argument 1 must be string, not %.50s",
+ sigarg == Py_None ? "None" : Py_TYPE(sigarg)->tp_name);
+ return nullptr;
+ }
+
+ PyObject* sig1 = CPyCppyy_PyUnicode_FromFormat("(%s)", CPyCppyy_PyUnicode_AsString(sigarg));
+
+ CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods;
+ for (auto& meth : methods) {
+
+ PyObject* sig2 = meth->GetSignature(false);
+ if (PyObject_RichCompareBool(sig1, sig2, Py_EQ)) {
+ Py_DECREF(sig2);
+
+ CPPOverload* newmeth = mp_new(nullptr, nullptr, nullptr);
+ CPPOverload::Methods_t vec; vec.push_back(meth->Clone());
+ newmeth->Set(pymeth->fMethodInfo->fName, vec);
+
+ if (pymeth->fSelf && !IsPseudoFunc(pymeth)) {
+ Py_INCREF(pymeth->fSelf);
+ newmeth->fSelf = pymeth->fSelf;
+ }
+
+ Py_DECREF(sig1);
+ return (PyObject*)newmeth;
+ }
+
+ Py_DECREF(sig2);
+ }
+
+ Py_DECREF(sig1);
+ PyErr_Format(PyExc_LookupError,
+ "signature \"%s\" not found", CPyCppyy_PyUnicode_AsString(sigarg));
+ return nullptr;
+}
+
+//= CPyCppyy method proxy access to internals ================================
+static PyObject* mp_add_overload(CPPOverload* pymeth, PyObject* new_overload)
+{
+ TPythonCallback* cb = new TPythonCallback(new_overload);
+ pymeth->AddMethod(cb);
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef mp_methods[] = {
+ {(char*)"__overload__", (PyCFunction)mp_overload, METH_O,
+ (char*)"select overload for dispatch" },
+ {(char*)"__add_overload__", (PyCFunction)mp_add_overload, METH_O,
+ (char*)"add a new overload" },
+ {(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
+ 0, // tp_print
+ 0, // tp_getattr
+ 0, // tp_setattr
+ 0, // tp_compare
+ 0, // tp_repr
+ 0, // tp_as_number
+ 0, // tp_as_sequence
+ 0, // tp_as_mapping
+ (hashfunc)mp_hash, // tp_hash
+ (ternaryfunc)mp_call, // tp_call
+ 0, // tp_str
+ 0, // tp_getattro
+ 0, // tp_setattro
+ 0, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // 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_descrget, // 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
+#if PY_VERSION_HEX >= 0x02030000
+ , 0 // tp_del
+#endif
+#if PY_VERSION_HEX >= 0x02060000
+ , 0 // tp_version_tag
+#endif
+#if PY_VERSION_HEX >= 0x03040000
+ , 0 // tp_finalize
+#endif
+};
+
+} // 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;
+ fMethodInfo->fFlags |= CallContext::kManageSmartPtr;
+
+// 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;
+}
+
+//-----------------------------------------------------------------------------
+void CPyCppyy::CPPOverload::AddMethod(PyCallable* pc)
+{
+// Fill in the data of a freshly created method proxy.
+ fMethodInfo->fMethods.push_back(pc);
+ fMethodInfo->fFlags &= ~CallContext::kIsSorted;
+}
+
+//-----------------------------------------------------------------------------
+void CPyCppyy::CPPOverload::AddMethod(CPPOverload* meth)
+{
+ fMethodInfo->fMethods.insert(fMethodInfo->fMethods.end(),
+ meth->fMethodInfo->fMethods.begin(), meth->fMethodInfo->fMethods.end());
+ fMethodInfo->fFlags &= ~CallContext::kIsSorted;
+}
+
+//-----------------------------------------------------------------------------
+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;
+}
diff --git a/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPOverload.h b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPOverload.h
new file mode 100644
index 0000000000000..ae9845b300eb7
--- /dev/null
+++ b/bindings/pyroot_experimental/cppyy/CPyCppyy/src/CPPOverload.h
@@ -0,0 +1,88 @@
+#ifndef CPYCPPYY_CPPOVERLOAD_H
+#define CPYCPPYY_CPPOVERLOAD_H
+
+// Bindings
+#include "PyCallable.h"
+
+// Standard
+#include