Commit b788e10a authored by empyrical's avatar empyrical

Add support for the typeid operator

parent a767e7e8
...@@ -67,7 +67,9 @@ class CythonScope(ModuleScope): ...@@ -67,7 +67,9 @@ class CythonScope(ModuleScope):
name_path = qname.split(u'.') name_path = qname.split(u'.')
scope = self scope = self
while len(name_path) > 1: 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] del name_path[0]
if scope is None: if scope is None:
return None return None
......
...@@ -6178,6 +6178,7 @@ class AttributeNode(ExprNode): ...@@ -6178,6 +6178,7 @@ class AttributeNode(ExprNode):
needs_none_check = True needs_none_check = True
is_memslice_transpose = False is_memslice_transpose = False
is_special_lookup = False is_special_lookup = False
is_py_attr = 0
def as_cython_attribute(self): def as_cython_attribute(self):
if (isinstance(self.obj, NameNode) and if (isinstance(self.obj, NameNode) and
...@@ -6607,7 +6608,7 @@ class AttributeNode(ExprNode): ...@@ -6607,7 +6608,7 @@ class AttributeNode(ExprNode):
else: else:
# result_code contains what is needed, but we may need to insert # result_code contains what is needed, but we may need to insert
# a check and raise an exception # 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 pass
elif self.entry and self.entry.is_cmethod and self.entry.utility_code: elif self.entry and self.entry.is_cmethod and self.entry.utility_code:
# C method implemented as function call with utility code # C method implemented as function call with utility code
...@@ -10140,6 +10141,8 @@ class SizeofTypeNode(SizeofNode): ...@@ -10140,6 +10141,8 @@ class SizeofTypeNode(SizeofNode):
def check_type(self): def check_type(self):
arg_type = self.arg_type arg_type = self.arg_type
if not arg_type:
return
if arg_type.is_pyobject and not arg_type.is_extension_type: if arg_type.is_pyobject and not arg_type.is_extension_type:
error(self.pos, "Cannot take sizeof Python object") error(self.pos, "Cannot take sizeof Python object")
elif arg_type.is_void: elif arg_type.is_void:
...@@ -10184,6 +10187,93 @@ class SizeofVarNode(SizeofNode): ...@@ -10184,6 +10187,93 @@ class SizeofVarNode(SizeofNode):
def generate_result_code(self, code): def generate_result_code(self, code):
pass 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): class TypeofNode(ExprNode):
# Compile-time type of an expression, as a string. # Compile-time type of an expression, as a string.
# #
......
...@@ -635,6 +635,7 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -635,6 +635,7 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
'operator.predecrement' : ExprNodes.inc_dec_constructor(True, '--'), 'operator.predecrement' : ExprNodes.inc_dec_constructor(True, '--'),
'operator.postincrement': ExprNodes.inc_dec_constructor(False, '++'), 'operator.postincrement': ExprNodes.inc_dec_constructor(False, '++'),
'operator.postdecrement': ExprNodes.inc_dec_constructor(False, '--'), 'operator.postdecrement': ExprNodes.inc_dec_constructor(False, '--'),
'operator.typeid' : ExprNodes.TypeidNode,
# For backwards compatibility. # For backwards compatibility.
'address': ExprNodes.AmpersandNode, 'address': ExprNodes.AmpersandNode,
......
...@@ -1030,6 +1030,7 @@ class ModuleScope(Scope): ...@@ -1030,6 +1030,7 @@ class ModuleScope(Scope):
# cpp boolean Compiling a C++ file # cpp boolean Compiling a C++ file
# is_cython_builtin boolean Is this the Cython builtin scope (or a child scope) # is_cython_builtin boolean Is this the Cython builtin scope (or a child scope)
# is_package boolean Is this a package module? (__init__) # is_package boolean Is this a package module? (__init__)
# typeid_variables int Used by the typeid() exception handler
is_module_scope = 1 is_module_scope = 1
has_import_star = 0 has_import_star = 0
...@@ -1069,6 +1070,7 @@ class ModuleScope(Scope): ...@@ -1069,6 +1070,7 @@ class ModuleScope(Scope):
self.cached_builtins = [] self.cached_builtins = []
self.undeclared_cached_builtins = [] self.undeclared_cached_builtins = []
self.namespace_cname = self.module_cname self.namespace_cname = self.module_cname
self.typeid_variables = 0
self._cached_tuple_types = {} self._cached_tuple_types = {}
for var_name in ['__builtins__', '__name__', '__file__', '__doc__', '__path__']: for var_name in ['__builtins__', '__name__', '__file__', '__doc__', '__path__']:
self.declare_var(EncodedString(var_name), py_object_type, None) self.declare_var(EncodedString(var_name), py_object_type, None)
......
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 &)
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()
...@@ -167,6 +167,7 @@ memory_view_index: ':' [':'] [NUMBER] ...@@ -167,6 +167,7 @@ memory_view_index: ':' [':'] [NUMBER]
address: '&' factor address: '&' factor
cast: '<' type ['?'] '>' factor cast: '<' type ['?'] '>' factor
size_of: 'sizeof' '(' (type) ')' size_of: 'sizeof' '(' (type) ')'
type_id: 'typeid' '(' (type) ')'
new_expr: 'new' type '(' [arglist] ')' new_expr: 'new' type '(' [arglist] ')'
# TODO: Restrict cdef_stmt to "top-level" statements. # TODO: Restrict cdef_stmt to "top-level" statements.
......
...@@ -18,6 +18,8 @@ static void __Pyx_CppExn2PyErr() { ...@@ -18,6 +18,8 @@ static void __Pyx_CppExn2PyErr() {
PyErr_SetString(PyExc_MemoryError, exn.what()); PyErr_SetString(PyExc_MemoryError, exn.what());
} catch (const std::bad_cast& exn) { } catch (const std::bad_cast& exn) {
PyErr_SetString(PyExc_TypeError, exn.what()); PyErr_SetString(PyExc_TypeError, exn.what());
} catch (const std::bad_typeid& exn) {
PyErr_SetString(PyExc_TypeError, exn.what());
} catch (const std::domain_error& exn) { } catch (const std::domain_error& exn) {
PyErr_SetString(PyExc_ValueError, exn.what()); PyErr_SetString(PyExc_ValueError, exn.what());
} catch (const std::invalid_argument& exn) { } catch (const std::invalid_argument& exn) {
......
...@@ -545,6 +545,8 @@ The translation is performed according to the following table ...@@ -545,6 +545,8 @@ The translation is performed according to the following table
+-----------------------+---------------------+ +-----------------------+---------------------+
| ``bad_cast`` | ``TypeError`` | | ``bad_cast`` | ``TypeError`` |
+-----------------------+---------------------+ +-----------------------+---------------------+
| ``bad_typeid`` | ``TypeError`` |
+-----------------------+---------------------+
| ``domain_error`` | ``ValueError`` | | ``domain_error`` | ``ValueError`` |
+-----------------------+---------------------+ +-----------------------+---------------------+
| ``invalid_argument`` | ``ValueError`` | | ``invalid_argument`` | ``ValueError`` |
...@@ -601,6 +603,28 @@ you can declare it using the Python @staticmethod decorator, i.e.:: ...@@ -601,6 +603,28 @@ you can declare it using the Python @staticmethod decorator, i.e.::
@staticmethod @staticmethod
void do_something() 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 Caveats and Limitations
======================== ========================
......
...@@ -4,10 +4,11 @@ ...@@ -4,10 +4,11 @@
from cython cimport typeof from cython cimport typeof
cimport cython.operator 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 libc.string cimport const_char
from libcpp cimport bool from libcpp cimport bool
cimport libcpp.typeinfo
cdef out(s, result_type=None): cdef out(s, result_type=None):
print '%s [%s]' % (s.decode('ascii'), result_type) print '%s [%s]' % (s.decode('ascii'), result_type)
...@@ -50,12 +51,15 @@ cdef extern from "cpp_operators_helper.h": ...@@ -50,12 +51,15 @@ cdef extern from "cpp_operators_helper.h":
const_char* operator[](int) const_char* operator[](int)
const_char* operator()(int) const_char* operator()(int)
cppclass TruthClass: cdef cppclass TruthClass:
TruthClass() TruthClass()
TruthClass(bool) TruthClass(bool)
bool operator bool() bool operator bool()
bool value bool value
cdef cppclass TruthSubClass(TruthClass):
pass
def test_unops(): def test_unops():
""" """
>>> test_unops() >>> test_unops()
...@@ -182,3 +186,29 @@ def test_bool_cond(): ...@@ -182,3 +186,29 @@ def test_bool_cond():
assert (TruthClass(False) and TruthClass(True)).value == False assert (TruthClass(False) and TruthClass(True)).value == False
assert (TruthClass(True) and TruthClass(False)).value == False assert (TruthClass(True) and TruthClass(False)).value == False
assert (TruthClass(True) and TruthClass(True)).value == True 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
...@@ -50,6 +50,7 @@ class TruthClass { ...@@ -50,6 +50,7 @@ class TruthClass {
public: public:
TruthClass() : value(false) {} TruthClass() : value(false) {}
TruthClass(bool value) : value(value) {} TruthClass(bool value) : value(value) {}
virtual ~TruthClass() {};
operator bool() { return value; } operator bool() { return value; }
bool value; bool value;
}; };
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment