From b788e10a35670dc19cc0f5748ab3d97cf61d8cad Mon Sep 17 00:00:00 2001
From: empyrical <empyrical@users.noreply.github.com>
Date: Fri, 5 Aug 2016 16:17:57 -0600
Subject: [PATCH] Add support for the typeid operator

---
 Cython/Compiler/CythonScope.py            |  4 +-
 Cython/Compiler/ExprNodes.py              | 92 ++++++++++++++++++++++-
 Cython/Compiler/ParseTreeTransforms.py    |  1 +
 Cython/Compiler/Symtab.py                 |  2 +
 Cython/Includes/libcpp/typeindex.pxd      | 15 ++++
 Cython/Includes/libcpp/typeinfo.pxd       | 10 +++
 Cython/Parser/Grammar                     |  1 +
 Cython/Utility/CppSupport.cpp             |  2 +
 docs/src/userguide/wrapping_CPlusPlus.rst | 24 ++++++
 tests/run/cpp_operators.pyx               | 34 ++++++++-
 tests/run/cpp_operators_helper.h          |  1 +
 11 files changed, 182 insertions(+), 4 deletions(-)
 create mode 100644 Cython/Includes/libcpp/typeindex.pxd
 create mode 100644 Cython/Includes/libcpp/typeinfo.pxd

diff --git a/Cython/Compiler/CythonScope.py b/Cython/Compiler/CythonScope.py
index d39588fca..00b912a81 100644
--- a/Cython/Compiler/CythonScope.py
+++ b/Cython/Compiler/CythonScope.py
@@ -67,7 +67,9 @@ class CythonScope(ModuleScope):
         name_path = qname.split(u'.')
         scope = self
         while len(name_path) > 1:
-            scope = scope.lookup_here(name_path[0]).as_module
+            scope = scope.lookup_here(name_path[0])
+            if scope:
+                scope = scope.as_module
             del name_path[0]
             if scope is None:
                 return None
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index 7e71cdae6..d0371a81d 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -6178,6 +6178,7 @@ class AttributeNode(ExprNode):
     needs_none_check = True
     is_memslice_transpose = False
     is_special_lookup = False
+    is_py_attr = 0
 
     def as_cython_attribute(self):
         if (isinstance(self.obj, NameNode) and
@@ -6607,7 +6608,7 @@ class AttributeNode(ExprNode):
         else:
             # result_code contains what is needed, but we may need to insert
             # a check and raise an exception
-            if self.obj.type.is_extension_type:
+            if self.obj.type and self.obj.type.is_extension_type:
                 pass
             elif self.entry and self.entry.is_cmethod and self.entry.utility_code:
                 # C method implemented as function call with utility code
@@ -10140,6 +10141,8 @@ class SizeofTypeNode(SizeofNode):
 
     def check_type(self):
         arg_type = self.arg_type
+        if not arg_type:
+            return
         if arg_type.is_pyobject and not arg_type.is_extension_type:
             error(self.pos, "Cannot take sizeof Python object")
         elif arg_type.is_void:
@@ -10184,6 +10187,93 @@ class SizeofVarNode(SizeofNode):
     def generate_result_code(self, code):
         pass
 
+
+class TypeidNode(ExprNode):
+    #  C++ typeid operator applied to a type or variable
+    #
+    #  operand       ExprNode
+    #  arg_type      ExprNode
+    #  is_variable   boolean
+    #  mangle_cname  string
+
+    type = PyrexTypes.error_type
+
+    subexprs = ['operand']
+
+    arg_type = None
+    is_variable = None
+    mangle_cname = None
+
+    def get_type_info_type(self, env):
+        if env.is_module_scope:
+            env_module = env
+        else:
+            env_module = env.outer_scope
+        for module in env_module.cimported_modules:
+            if module.qualified_name == 'libcpp.typeinfo':
+                type_info = module.lookup('type_info')
+                type_info = PyrexTypes.c_ref_type(PyrexTypes.c_const_type(type_info.type))
+                return type_info
+        return None
+
+    def analyse_types(self, env):
+        type_info = self.get_type_info_type(env)
+        if not type_info:
+            self.error("The 'libcpp.typeinfo' module must be cimported to use the typeid() operator")
+            return self
+        self.type = type_info
+        as_type = self.operand.analyse_as_type(env)
+        if as_type:
+            self.arg_type = as_type
+            self.is_type = True
+        else:
+            self.arg_type = self.operand.analyse_types(env)
+            self.is_type = False
+            if self.arg_type.type.is_pyobject:
+                self.error("Cannot use typeid on a Python object")
+                return self
+            elif self.arg_type.type.is_void:
+                self.error("Cannot use typeid on void")
+                return self
+            elif not self.arg_type.type.is_complete():
+                self.error("Cannot use typeid on incomplete type '%s'" % self.arg_type.type)
+                return self
+        if env.is_module_scope:
+            env_module = env
+        else:
+            env_module = env.outer_scope
+        env_module.typeid_variables += 1
+        self.mangle_cname = "%s_typeid_%s" % (
+            env_module.module_cname, env_module.typeid_variables)
+        env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp"))
+        return self
+
+    def error(self, mess):
+        error(self.pos, mess)
+        self.type = PyrexTypes.error_type
+        self.result_code = "<error>"
+
+    def check_const(self):
+        return True
+
+    def calculate_result_code(self):
+        return "(*%s)" % self.mangle_cname
+
+    def generate_result_code(self, code):
+        if self.is_type:
+            if self.arg_type.is_extension_type:
+                # the size of the pointer is boring
+                # we want the size of the actual struct
+                arg_code = self.arg_type.declaration_code("", deref=1)
+            else:
+                arg_code = self.arg_type.empty_declaration_code()
+        else:
+            arg_code = self.arg_type.result()
+        code.putln("const std::type_info *%s;" % self.mangle_cname)
+        translate_cpp_exception(code, self.pos,
+            "%s = &typeid(%s);" % (self.mangle_cname, arg_code),
+            None, self.in_nogil_context)
+
 class TypeofNode(ExprNode):
     #  Compile-time type of an expression, as a string.
     #
diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py
index 71c228df0..40e143b2d 100644
--- a/Cython/Compiler/ParseTreeTransforms.py
+++ b/Cython/Compiler/ParseTreeTransforms.py
@@ -635,6 +635,7 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
         'operator.predecrement' : ExprNodes.inc_dec_constructor(True, '--'),
         'operator.postincrement': ExprNodes.inc_dec_constructor(False, '++'),
         'operator.postdecrement': ExprNodes.inc_dec_constructor(False, '--'),
+        'operator.typeid'       : ExprNodes.TypeidNode,
 
         # For backwards compatibility.
         'address': ExprNodes.AmpersandNode,
diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py
index 16a975938..12722a5f4 100644
--- a/Cython/Compiler/Symtab.py
+++ b/Cython/Compiler/Symtab.py
@@ -1030,6 +1030,7 @@ class ModuleScope(Scope):
     # cpp                  boolean            Compiling a C++ file
     # is_cython_builtin    boolean            Is this the Cython builtin scope (or a child scope)
     # is_package           boolean            Is this a package module? (__init__)
+    # typeid_variables     int                Used by the typeid() exception handler
 
     is_module_scope = 1
     has_import_star = 0
@@ -1069,6 +1070,7 @@ class ModuleScope(Scope):
         self.cached_builtins = []
         self.undeclared_cached_builtins = []
         self.namespace_cname = self.module_cname
+        self.typeid_variables = 0
         self._cached_tuple_types = {}
         for var_name in ['__builtins__', '__name__', '__file__', '__doc__', '__path__']:
             self.declare_var(EncodedString(var_name), py_object_type, None)
diff --git a/Cython/Includes/libcpp/typeindex.pxd b/Cython/Includes/libcpp/typeindex.pxd
new file mode 100644
index 000000000..d5b7e9149
--- /dev/null
+++ b/Cython/Includes/libcpp/typeindex.pxd
@@ -0,0 +1,15 @@
+from libcpp cimport bool
+from .typeinfo cimport type_info
+
+# This class is C++11-only
+cdef extern from "<typeindex>" namespace "std" nogil:
+    cdef cppclass type_index:
+        type_index(const type_info &)
+        const char* name()
+        size_t hash_code()
+        bool operator==(const type_index &)
+        bool operator!=(const type_index &)
+        bool operator<(const type_index &)
+        bool operator<=(const type_index &)
+        bool operator>(const type_index &)
+        bool operator>=(const type_index &)
diff --git a/Cython/Includes/libcpp/typeinfo.pxd b/Cython/Includes/libcpp/typeinfo.pxd
new file mode 100644
index 000000000..9118e0064
--- /dev/null
+++ b/Cython/Includes/libcpp/typeinfo.pxd
@@ -0,0 +1,10 @@
+from libcpp cimport bool
+
+cdef extern from "<typeinfo>" namespace "std" nogil:
+    cdef cppclass type_info:
+        const char* name()
+        int before(const type_info&)
+        bool operator==(const type_info&)
+        bool operator!=(const type_info&)
+        # C++11-only
+        size_t hash_code()
diff --git a/Cython/Parser/Grammar b/Cython/Parser/Grammar
index 8ce663ee6..ebfa9c875 100644
--- a/Cython/Parser/Grammar
+++ b/Cython/Parser/Grammar
@@ -167,6 +167,7 @@ memory_view_index: ':' [':'] [NUMBER]
 address: '&' factor
 cast: '<' type ['?'] '>' factor
 size_of: 'sizeof' '(' (type) ')'
+type_id: 'typeid' '(' (type) ')'
 new_expr: 'new' type '(' [arglist] ')'
 
 # TODO: Restrict cdef_stmt to "top-level" statements.
diff --git a/Cython/Utility/CppSupport.cpp b/Cython/Utility/CppSupport.cpp
index ab2f2b5b2..b3de6fc0f 100644
--- a/Cython/Utility/CppSupport.cpp
+++ b/Cython/Utility/CppSupport.cpp
@@ -18,6 +18,8 @@ static void __Pyx_CppExn2PyErr() {
     PyErr_SetString(PyExc_MemoryError, exn.what());
   } catch (const std::bad_cast& exn) {
     PyErr_SetString(PyExc_TypeError, exn.what());
+  } catch (const std::bad_typeid& exn) {
+    PyErr_SetString(PyExc_TypeError, exn.what());
   } catch (const std::domain_error& exn) {
     PyErr_SetString(PyExc_ValueError, exn.what());
   } catch (const std::invalid_argument& exn) {
diff --git a/docs/src/userguide/wrapping_CPlusPlus.rst b/docs/src/userguide/wrapping_CPlusPlus.rst
index 59deff919..7c8194ff5 100644
--- a/docs/src/userguide/wrapping_CPlusPlus.rst
+++ b/docs/src/userguide/wrapping_CPlusPlus.rst
@@ -545,6 +545,8 @@ The translation is performed according to the following table
 +-----------------------+---------------------+
 | ``bad_cast``          | ``TypeError``       |
 +-----------------------+---------------------+
+| ``bad_typeid``        | ``TypeError``       |
++-----------------------+---------------------+
 | ``domain_error``      | ``ValueError``      |
 +-----------------------+---------------------+
 | ``invalid_argument``  | ``ValueError``      |
@@ -601,6 +603,28 @@ you can declare it using the Python @staticmethod decorator, i.e.::
         @staticmethod
         void do_something()
 
+RTTI and typeid()
+=================
+
+Cython has support for the ``typeid(...)`` operator. To use it, you need to import
+it and the ``libcpp.typeinfo`` module first:
+
+    from cython.operator cimport typeid
+    from libcpp.typeinfo cimport type_info
+
+The ``typeid(...)`` operator returns an object of the type ``const type_info &``.
+
+If you want to store a type_info value in a C variable, you will need to store it
+as a pointer rather than a reference:
+
+    cdef const type_info* info = &typeid(MyClass)
+
+If an invalid type is passed to ``typeid``, it will throw an ``std::bad_typeid``
+exception which is converted into a ``TypeError`` exception in Python.
+
+An additional C++11-only RTTI-related class, ``std::type_index``, is available
+in ``libcpp.typeindex``.
+
 
 Caveats and Limitations
 ========================
diff --git a/tests/run/cpp_operators.pyx b/tests/run/cpp_operators.pyx
index 492395410..4ee230288 100644
--- a/tests/run/cpp_operators.pyx
+++ b/tests/run/cpp_operators.pyx
@@ -4,10 +4,11 @@
 from cython cimport typeof
 
 cimport cython.operator
-from cython.operator cimport dereference as deref
+from cython.operator cimport typeid, dereference as deref
 
 from libc.string cimport const_char
 from libcpp cimport bool
+cimport libcpp.typeinfo
 
 cdef out(s, result_type=None):
     print '%s [%s]' % (s.decode('ascii'), result_type)
@@ -50,12 +51,15 @@ cdef extern from "cpp_operators_helper.h":
         const_char* operator[](int)
         const_char* operator()(int)
 
-    cppclass TruthClass:
+    cdef cppclass TruthClass:
         TruthClass()
         TruthClass(bool)
         bool operator bool()
         bool value
 
+cdef cppclass TruthSubClass(TruthClass):
+    pass
+
 def test_unops():
     """
     >>> test_unops()
@@ -182,3 +186,29 @@ def test_bool_cond():
     assert (TruthClass(False) and TruthClass(True)).value == False
     assert (TruthClass(True) and TruthClass(False)).value == False
     assert (TruthClass(True) and TruthClass(True)).value == True
+
+def test_typeid_op():
+    """
+    >>> test_typeid_op()
+    """
+    cdef TruthClass* test_1 = new TruthClass()
+    cdef TruthSubClass* test_2 = new TruthSubClass()
+    cdef TruthClass* test_3 = <TruthClass*> test_2
+    cdef TruthClass* test_4 = <TruthClass*> 0
+
+    assert typeid(TruthClass).name()
+    assert typeid(test_1).name()
+    assert typeid(TruthSubClass).name()
+    assert typeid(test_2).name()
+    assert typeid(TruthClass).name()
+    assert typeid(test_3).name()
+    assert typeid(TruthSubClass).name()
+    assert typeid(deref(test_2)).name()
+
+    try:
+        typeid(deref(test_4))
+        assert False
+    except TypeError:
+        assert True
+
+    del test_1, test_2
diff --git a/tests/run/cpp_operators_helper.h b/tests/run/cpp_operators_helper.h
index 62710da4e..36685c5f1 100644
--- a/tests/run/cpp_operators_helper.h
+++ b/tests/run/cpp_operators_helper.h
@@ -50,6 +50,7 @@ class TruthClass {
 public:
   TruthClass() : value(false) {}
   TruthClass(bool value) : value(value) {}
+  virtual ~TruthClass() {};
   operator bool() { return value; }
   bool value;
 };
-- 
2.30.9