ticket #607: access inner fields of CPython's builtin objects
authorStefan Behnel <scoder@users.berlios.de>
Fri, 3 Dec 2010 18:14:08 +0000 (19:14 +0100)
committerStefan Behnel <scoder@users.berlios.de>
Fri, 3 Dec 2010 18:14:08 +0000 (19:14 +0100)
--HG--
rename : tests/broken/builtinslice.pyx => tests/run/builtinslice.pyx

Cython/Compiler/Builtin.py
Cython/Compiler/ExprNodes.py
Cython/Compiler/PyrexTypes.py
Cython/Compiler/Symtab.py
tests/broken/builtinslice.pyx [deleted file]
tests/run/builtincomplex.pyx [new file with mode: 0644]
tests/run/builtinslice.pyx [new file with mode: 0644]
tests/run/isinstance.pyx

index 2fedf6aef799951788a8239097e4d40550e8115b..06e96b94100ebefa7cfa48f15d8f5e326b9073f8 100644 (file)
@@ -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
index 5c00a1ddbaadc554b6380bdb6d4da8e8836e8796..f6966165526f8a89121b8d9a835a877b27135533 100755 (executable)
@@ -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):
index 4462ca31f2f19df503bfd7852269f6024678826f..1ff5a0213679ef13eb2848ee79008f2e3c08ca11 100755 (executable)
@@ -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):
     #
index 5ee19ae76e12ce7a240555492c84d9feba5c524b..4253bbefba3a14587320e128662eb8ba88578598 100644 (file)
@@ -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 (file)
index 5b9d634..0000000
+++ /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 (file)
index 0000000..80aef59
--- /dev/null
@@ -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 (file)
index 0000000..73f05a9
--- /dev/null
@@ -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)
index b9ae36fef5d6abc9b88149a29fd82943bb9d7466..858f9ab2f60d39971959432171e07fbb35ff9f55 100644 (file)
@@ -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