From a11b3250dac7a7d8ec1187ddc9ac128754a7c49d Mon Sep 17 00:00:00 2001
From: Enric Tejedor Saavedra <enric.tejedor.saavedra@cern.ch>
Date: Mon, 18 Feb 2019 17:51:35 +0100
Subject: [PATCH] [Exp PyROOT] Pythonize TClass::DynamicCast to provide right
 binding

TClass::DynamicCast returns a void* that the user still has to cast.
This pythonisation provides the requested binding if the cast succeeded.
---
 .../pyroot_experimental/PyROOT/CMakeLists.txt |  9 +-
 .../python/ROOT/pythonization/_tclass.py      | 23 +++++
 .../PyROOT/src/PyROOTModule.cxx               |  2 +
 .../PyROOT/src/PyROOTPythonize.h              | 17 +++-
 .../PyROOT/src/TClassPyz.cxx                  | 95 +++++++++++++++++++
 5 files changed, 137 insertions(+), 9 deletions(-)
 create mode 100644 bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/_tclass.py
 create mode 100644 bindings/pyroot_experimental/PyROOT/src/TClassPyz.cxx

diff --git a/bindings/pyroot_experimental/PyROOT/CMakeLists.txt b/bindings/pyroot_experimental/PyROOT/CMakeLists.txt
index 891f3027ba4..556e2f1bce1 100644
--- a/bindings/pyroot_experimental/PyROOT/CMakeLists.txt
+++ b/bindings/pyroot_experimental/PyROOT/CMakeLists.txt
@@ -26,14 +26,15 @@ set(sources
   src/PyROOTModule.cxx
   src/PyROOTStrings.cxx
   src/PyROOTWrapper.cxx
+  src/GenericPyz.cxx
+  src/RVecPyz.cxx
+  src/TClassPyz.cxx
+  src/TClonesArrayPyz.cxx
   src/TDirectoryPyz.cxx
   src/TFilePyz.cxx
   src/TTreePyz.cxx
-  src/TClonesArrayPyz.cxx
-  src/GenericPyz.cxx
-  src/RVecPyz.cxx
-  src/PyzPythonHelpers.cxx
   src/PyzCppHelpers.cxx
+  src/PyzPythonHelpers.cxx
 )
 
 file(COPY python/ROOT DESTINATION ${localruntimedir})
diff --git a/bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/_tclass.py b/bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/_tclass.py
new file mode 100644
index 00000000000..7f716689b41
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/python/ROOT/pythonization/_tclass.py
@@ -0,0 +1,23 @@
+# Author: Enric Tejedor CERN  02/2019
+
+################################################################################
+# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.                      #
+# All rights reserved.                                                         #
+#                                                                              #
+# For the licensing terms see $ROOTSYS/LICENSE.                                #
+# For the list of contributors see $ROOTSYS/README/CREDITS.                    #
+################################################################################
+
+from ROOT import pythonization
+import cppyy
+from libROOTPython import AddTClassDynamicCastPyz
+
+
+@pythonization(lazy = False)
+def pythonize_tclass():
+    klass = cppyy.gbl.TClass
+
+    # DynamicCast
+    AddTClassDynamicCastPyz(klass)
+
+    return True
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx b/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx
index 141722ac759..060fbdc1108 100644
--- a/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx
@@ -45,6 +45,8 @@ static PyMethodDef gPyROOTMethods[] = {{(char *)"AddDirectoryWritePyz", (PyCFunc
                                         (char *)"Allow to access branches as tree attributes"},
                                        {(char *)"AddFileOpenPyz", (PyCFunction)PyROOT::AddFileOpenPyz, METH_VARARGS,
                                         (char *)"Make TFile::Open a constructor, adjusting for example the reference count"},
+                                        {(char *)"AddTClassDynamicCastPyz", (PyCFunction)PyROOT::AddTClassDynamicCastPyz, METH_VARARGS,
+                                        (char *)"Cast the void* returned by TClass::DynamicCast to the right type"},
                                        {(char *)"SetBranchAddressPyz", (PyCFunction)PyROOT::SetBranchAddressPyz, METH_VARARGS,
                                         (char *)"Fully enable the use of TTree::SetBranchAddress from Python"},
                                        {(char *)"BranchPyz", (PyCFunction)PyROOT::BranchPyz, METH_VARARGS,
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTPythonize.h b/bindings/pyroot_experimental/PyROOT/src/PyROOTPythonize.h
index ec7761a51ad..344881370c3 100644
--- a/bindings/pyroot_experimental/PyROOT/src/PyROOTPythonize.h
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTPythonize.h
@@ -17,17 +17,24 @@
 namespace PyROOT {
 
 PyObject *AddPrettyPrintingPyz(PyObject *self, PyObject *args);
-PyObject *AddDirectoryWritePyz(PyObject *self, PyObject *args);
+
 PyObject *AddDirectoryAttrSyntaxPyz(PyObject *self, PyObject *args);
-PyObject *AddBranchAttrSyntax(PyObject *self, PyObject *args);
+PyObject *AddDirectoryWritePyz(PyObject *self, PyObject *args);
 PyObject *AddFileOpenPyz(PyObject *self, PyObject *args);
-PyObject *AddSetItemTCAPyz(PyObject *self, PyObject *args);
-PyObject *SetBranchAddressPyz(PyObject *self, PyObject *args);
+
+PyObject *AddBranchAttrSyntax(PyObject *self, PyObject *args);
 PyObject *BranchPyz(PyObject *self, PyObject *args);
+PyObject *SetBranchAddressPyz(PyObject *self, PyObject *args);
+
+PyObject *AddTClassDynamicCastPyz(PyObject *self, PyObject *args);
+
+PyObject *AddSetItemTCAPyz(PyObject *self, PyObject *args);
+
+PyObject *AsRVec(PyObject *self, PyObject *obj);
+
 PyObject *GetEndianess(PyObject *self);
 PyObject *GetVectorDataPointer(PyObject *self, PyObject *args);
 PyObject *GetSizeOfType(PyObject *self, PyObject *args);
-PyObject *AsRVec(PyObject *self, PyObject *obj);
 
 } // namespace PyROOT
 
diff --git a/bindings/pyroot_experimental/PyROOT/src/TClassPyz.cxx b/bindings/pyroot_experimental/PyROOT/src/TClassPyz.cxx
new file mode 100644
index 00000000000..886522e47d8
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/TClassPyz.cxx
@@ -0,0 +1,95 @@
+// Author: Enric Tejedor CERN  02/2019
+// Original PyROOT code by Wim Lavrijsen, LBL
+
+/*************************************************************************
+ * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
+ * All rights reserved.                                                  *
+ *                                                                       *
+ * For the licensing terms see $ROOTSYS/LICENSE.                         *
+ * For the list of contributors see $ROOTSYS/README/CREDITS.             *
+ *************************************************************************/
+
+// Bindings
+#include "CPyCppyy.h"
+#include "PyROOTPythonize.h"
+#include "PyROOTStrings.h"
+#include "CPPInstance.h"
+#include "Utility.h"
+#include "ProxyWrappers.h"
+#include "PyzCppHelpers.hxx"
+
+// ROOT
+#include "TClass.h"
+
+using namespace CPyCppyy;
+
+// Cast the void* returned by TClass::DynamicCast to the right type
+PyObject *TClassDynamicCastPyz(CPPInstance *self, PyObject *args)
+{
+   // Parse arguments
+   CPPInstance *pyclass = nullptr;
+   PyObject *pyobject = nullptr;
+   int up = 1;
+   if (!PyArg_ParseTuple(args, const_cast<char *>("O!O|i:DynamicCast"),
+                         &CPPInstance_Type, &pyclass,
+                         &pyobject,
+                         &up))
+      return nullptr;
+
+   // Perform actual cast - calls default implementation of DynamicCast
+   auto meth = PyObject_GetAttr((PyObject *)self, PyROOT::PyStrings::gTClassDynCast);
+   auto ptr = meth ? PyObject_Call(meth, args, nullptr) : nullptr;
+   Py_XDECREF(meth);
+
+   // Simply forward in case of call failure
+   if (!ptr)
+      return nullptr;
+
+   // Retrieve object address
+   void *address = nullptr;
+   if (CPPInstance_Check(pyobject)) {
+      address = ((CPPInstance *)pyobject)->GetObject();
+   } else if (PyInt_Check(pyobject) || PyLong_Check(pyobject)) {
+      address = (void *)PyLong_AsLong(pyobject);
+   } else {
+      Utility::GetBuffer(pyobject, '*', 1, address, false);
+   }
+
+   if (PyErr_Occurred()) {
+      // Error getting object address, just return the void* wrapper
+      PyErr_Clear();
+      return ptr;
+   }
+
+   // Now use binding to return a usable class
+   TClass *klass = nullptr;
+   if (up) {
+      // Upcast: result is a base
+      klass = (TClass *)GetTClass(pyclass)->DynamicCast(TClass::Class(), pyclass->GetObject());
+   } else {
+      // Downcast: result is a derived
+      klass = (TClass *)GetTClass(self)->DynamicCast(TClass::Class(), self->GetObject());
+   }
+
+   PyObject *result = BindCppObjectNoCast(address, Cppyy::GetScope(klass->GetName()));
+   Py_DECREF(ptr);
+
+   return result;
+}
+
+////////////////////////////////////////////////////////////////////////////
+/// \brief Add pythonization for TClass::DynamicCast.
+/// \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.
+///
+/// TClass::DynamicCast returns a void* that the user still has to cast (it
+/// will have the proper offset, though). Fix this by providing the requested
+/// binding if the cast succeeded.
+PyObject *PyROOT::AddTClassDynamicCastPyz(PyObject * /* self */, PyObject *args)
+{
+   PyObject *pyclass = PyTuple_GetItem(args, 0);
+   Utility::AddToClass(pyclass, "_TClass__DynamicCast", "DynamicCast");
+   Utility::AddToClass(pyclass, "DynamicCast", (PyCFunction)TClassDynamicCastPyz);
+   Py_RETURN_NONE;
+}
\ No newline at end of file
-- 
GitLab