From: Stefan Behnel Date: Fri, 3 Dec 2010 18:14:08 +0000 (+0100) Subject: ticket #607: access inner fields of CPython's builtin objects X-Git-Tag: 0.14.alpha0~31 X-Git-Url: http://git.tremily.us/gitweb.cgi?a=commitdiff_plain;h=97cb7314a658f73a5a51f778021ccd52e77660f4;p=cython.git ticket #607: access inner fields of CPython's builtin objects --HG-- rename : tests/broken/builtinslice.pyx => tests/run/builtinslice.pyx --- diff --git a/Cython/Compiler/Builtin.py b/Cython/Compiler/Builtin.py index 2fedf6ae..06e96b94 100644 --- a/Cython/Compiler/Builtin.py +++ b/Cython/Compiler/Builtin.py @@ -300,6 +300,22 @@ class _BuiltinOverride(object): self.func_type, self.sig = func_type, sig self.utility_code = utility_code +class BuiltinAttribute(object): + def __init__(self, py_name, cname=None, field_type=None, field_type_name=None): + self.py_name = py_name + self.cname = cname or py_name + self.field_type_name = field_type_name # can't do the lookup before the type is declared! + self.field_type = field_type + + def declare_in_type(self, self_type): + if self.field_type_name is not None: + # lazy type lookup + field_type = builtin_scope.lookup(self.field_type_name).type + else: + field_type = self.field_type or PyrexTypes.py_object_type + entry = self_type.scope.declare(self.py_name, self.cname, field_type, None, 'private') + entry.is_variable = True + class BuiltinFunction(_BuiltinOverride): def declare_in_scope(self, scope): func_type, sig = self.func_type, self.sig @@ -426,9 +442,10 @@ builtin_types_table = [ ("long", "PyLong_Type", []), ("float", "PyFloat_Type", []), -# Until we have a way to access attributes of a type, -# we don't want to make this one builtin. -# ("complex", "PyComplex_Type", []), + ("complex", "PyComplex_Type", [BuiltinAttribute('cval', field_type_name = 'Py_complex'), + BuiltinAttribute('real', 'cval.real', field_type = PyrexTypes.c_double_type), + BuiltinAttribute('imag', 'cval.imag', field_type = PyrexTypes.c_double_type), + ]), ("bytes", "PyBytes_Type", []), ("str", "PyString_Type", []), @@ -447,7 +464,10 @@ builtin_types_table = [ BuiltinMethod("values","T", "O", "PyDict_Values"), # FIXME: Py3 mode? BuiltinMethod("copy", "T", "T", "PyDict_Copy")]), - ("slice", "PySlice_Type", []), + ("slice", "PySlice_Type", [BuiltinAttribute('start'), + BuiltinAttribute('stop'), + BuiltinAttribute('step'), + ]), # ("file", "PyFile_Type", []), # not in Py3 ("set", "PySet_Type", [BuiltinMethod("clear", "T", "i", "PySet_Clear"), @@ -480,6 +500,10 @@ builtin_structs_table = [ ("strides", PyrexTypes.c_py_ssize_t_ptr_type), ("suboffsets", PyrexTypes.c_py_ssize_t_ptr_type), ("internal", PyrexTypes.c_void_ptr_type), + ]), + ('Py_complex', 'Py_complex', + [('real', PyrexTypes.c_double_type), + ('imag', PyrexTypes.c_double_type), ]) ] @@ -497,7 +521,13 @@ def init_builtin_types(): global builtin_types for name, cname, methods in builtin_types_table: utility = builtin_utility_code.get(name) - the_type = builtin_scope.declare_builtin_type(name, cname, utility) + if name == 'frozenset': + objstruct_cname = 'PySetObject' + elif name == 'bool': + objstruct_cname = None + else: + objstruct_cname = 'Py%sObject' % name.capitalize() + the_type = builtin_scope.declare_builtin_type(name, cname, utility, objstruct_cname) builtin_types[name] = the_type for method in methods: method.declare_in_type(the_type) @@ -512,9 +542,9 @@ def init_builtin_structs(): name, "struct", scope, 1, None, cname = cname) def init_builtins(): + init_builtin_structs() init_builtin_funcs() init_builtin_types() - init_builtin_structs() global list_type, tuple_type, dict_type, set_type, frozenset_type global bytes_type, str_type, unicode_type global float_type, bool_type, type_type, complex_type diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 5c00a1dd..f6966165 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -3466,7 +3466,7 @@ class AttributeNode(ExprNode): if obj_type.is_ptr or obj_type.is_array: obj_type = obj_type.base_type self.op = "->" - elif obj_type.is_extension_type: + elif obj_type.is_extension_type or obj_type.is_builtin_type: self.op = "->" else: self.op = "." @@ -3558,6 +3558,9 @@ class AttributeNode(ExprNode): elif obj.type.is_complex: return "__Pyx_C%s(%s)" % (self.member.upper(), obj_code) else: + if obj.type.is_builtin_type and self.entry and self.entry.is_variable: + # accessing a field of a builtin type, need to cast better than result_as() does + obj_code = obj.type.cast_code(obj.result(), to_object_struct = True) return "%s%s%s" % (obj_code, self.op, self.member) def generate_result_code(self, code): diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py index 4462ca31..1ff5a021 100755 --- a/Cython/Compiler/PyrexTypes.py +++ b/Cython/Compiler/PyrexTypes.py @@ -373,16 +373,18 @@ class PyObjectType(PyrexType): return cname class BuiltinObjectType(PyObjectType): + # objstruct_cname string Name of PyObject struct is_builtin_type = 1 has_attributes = 1 base_type = None module_name = '__builtin__' - def __init__(self, name, cname): + def __init__(self, name, cname, objstruct_cname=None): self.name = name self.cname = cname self.typeptr_cname = "&" + cname + self.objstruct_cname = objstruct_cname def set_scope(self, scope): self.scope = scope @@ -445,6 +447,11 @@ class BuiltinObjectType(PyObjectType): entity_code = "*%s" % entity_code return self.base_declaration_code(base_code, entity_code) + def cast_code(self, expr_code, to_object_struct = False): + return "((%s*)%s)" % ( + to_object_struct and self.objstruct_cname or "PyObject", # self.objstruct_cname may be None + expr_code) + class PyExtensionType(PyObjectType): # diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index 5ee19ae7..4253bbef 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -741,9 +741,9 @@ class BuiltinScope(Scope): entry.as_variable = var_entry return entry - def declare_builtin_type(self, name, cname, utility_code = None): + def declare_builtin_type(self, name, cname, utility_code = None, objstruct_cname = None): name = EncodedString(name) - type = PyrexTypes.BuiltinObjectType(name, cname) + type = PyrexTypes.BuiltinObjectType(name, cname, objstruct_cname) scope = CClassScope(name, outer_scope=None, visibility='extern') scope.directives = {} if name == 'bool': diff --git a/tests/broken/builtinslice.pyx b/tests/broken/builtinslice.pyx deleted file mode 100644 index 5b9d6342..00000000 --- a/tests/broken/builtinslice.pyx +++ /dev/null @@ -1,10 +0,0 @@ -cdef int f() except -1: - cdef slice s - cdef object z - cdef int i - z = slice - s = slice(1, 2, 3) - z = slice.indices() - i = s.start - i = s.stop - i = s.step diff --git a/tests/run/builtincomplex.pyx b/tests/run/builtincomplex.pyx new file mode 100644 index 00000000..80aef59c --- /dev/null +++ b/tests/run/builtincomplex.pyx @@ -0,0 +1,32 @@ + +cimport cython + +@cython.test_assert_path_exists('//TupleNode//CoerceToPyTypeNode//AttributeNode') +def complex_attributes(): + """ + >>> complex_attributes() + (1.0, 2.0) + """ + cdef complex c = 1+2j + return (c.real, c.imag) + +@cython.test_assert_path_exists('//TupleNode//CoerceToPyTypeNode//AttributeNode') +def complex_attributes_assign(): + """ + >>> complex_attributes_assign() + (10.0, 20.0) + """ + cdef complex c = 1+2j + c.real, c.imag = 10, 20 + return (c.real, c.imag) + +@cython.test_assert_path_exists('//TupleNode//CoerceToPyTypeNode//AttributeNode') +def complex_cstruct_assign(): + """ + >>> complex_cstruct_assign() + (10.0, 20.0) + """ + cdef complex c = 1+2j + cval = &c.cval + cval.real, cval.imag = 10, 20 + return (c.real, c.imag) diff --git a/tests/run/builtinslice.pyx b/tests/run/builtinslice.pyx new file mode 100644 index 00000000..73f05a97 --- /dev/null +++ b/tests/run/builtinslice.pyx @@ -0,0 +1,47 @@ +cimport cython + +def unbound_method_lookup(): + """ + >>> unbound_method_lookup() + """ + ignore = slice.indices + +@cython.test_assert_path_exists('//SingleAssignmentNode//AttributeNode[@is_py_attr = False]') +@cython.test_fail_if_path_exists('//SingleAssignmentNode//AttributeNode[@is_py_attr = True]') +def typed_slice(): + """ + >>> typed_slice() + (1, 2, 3) + """ + cdef slice s + cdef object z + cdef Py_ssize_t a,b,c + + z = slice + s = slice(1, 2, 3) + s.indices + + a = s.start + b = s.stop + c = s.step + + return (a,b,c) + +@cython.test_fail_if_path_exists('//SingleAssignmentNode//AttributeNode[@is_py_attr = False]') +def plain_object_slice(): + """ + >>> plain_object_slice() + (1, 2, 3) + """ + cdef object s + cdef object z + cdef Py_ssize_t a,b,c + + s = slice(1, 2, 3) + s.indices + + a = s.start + b = s.stop + c = s.step + + return (a,b,c) diff --git a/tests/run/isinstance.pyx b/tests/run/isinstance.pyx index b9ae36fe..858f9ab2 100644 --- a/tests/run/isinstance.pyx +++ b/tests/run/isinstance.pyx @@ -18,7 +18,6 @@ def test_non_optimised(): assert isinstance(A(), foo) assert isinstance(0, (int, long)) assert not isinstance(u"xyz", (int, long)) - assert isinstance(complex(), complex) # FIXME: this should be optimised, too! return True @cython.test_assert_path_exists('//PythonCapiCallNode', @@ -46,6 +45,7 @@ def test_optimised(): assert isinstance(dict(), dict) assert isinstance(set(), set) assert isinstance(slice(0), slice) + assert isinstance(complex(), complex) assert not isinstance(u"foo", int) assert isinstance(A, type) return True