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