From ac2dac9e99568a7582b00d091afaffde7ed79681 Mon Sep 17 00:00:00 2001
From: Peter Todd <pete@petertodd.org>
Date: Fri, 2 May 2008 04:22:48 -0400
Subject: [PATCH] First stage of __getattribute__ special method support.

Works with test cases for a single class, have not dealt with subclass issues
yet.
---
 Cython/Compiler/ModuleNode.py  | 35 ++++++++++++--------
 Cython/Compiler/TypeSlots.py   |  2 +-
 tests/run/__getattribute__.pyx | 60 ++++++++++++++++++++++++++++++++++
 3 files changed, 83 insertions(+), 14 deletions(-)
 create mode 100644 tests/run/__getattribute__.pyx

diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
index ade6af228..2b63c919f 100644
--- a/Cython/Compiler/ModuleNode.py
+++ b/Cython/Compiler/ModuleNode.py
@@ -666,7 +666,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
                         self.generate_ass_subscript_function(scope, code)
                     if scope.defines_any(["__setslice__", "__delslice__"]):
                         self.generate_ass_slice_function(scope, code)
-                    if scope.defines_any(["__getattr__"]):
+                    if scope.defines_any(["__getattr__","__getattribute__"]):
                         self.generate_getattro_function(scope, code)
                     if scope.defines_any(["__setattr__", "__delattr__"]):
                         self.generate_setattro_function(scope, code)
@@ -1030,27 +1030,36 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
             "}")
 
     def generate_getattro_function(self, scope, code):
-        # First try to get the attribute using PyObject_GenericGetAttr.
+        # First try to get the attribute using __getattribute__, if defined, or
+        # PyObject_GenericGetAttr.
+        #
         # If that raises an AttributeError, call the user's __getattr__
-        # method.
-        entry = scope.lookup_here("__getattr__")
+        # method, if defined.
+        getattr_entry = scope.lookup_here("__getattr__")
+        getattribute_entry = scope.lookup_here("__getattribute__")
         code.putln("")
         code.putln(
             "static PyObject *%s(PyObject *o, PyObject *n) {"
                 % scope.mangle_internal("tp_getattro"))
-        code.putln(
+        if getattribute_entry is not None:
+            code.putln(
+                "PyObject *v = %s(o, n);" %
+                    getattribute_entry.func_cname)
+        else:
+            code.putln(
                 "PyObject *v = PyObject_GenericGetAttr(o, n);")
-        code.putln(
+        if getattr_entry is not None:
+            code.putln(
                 "if (!v && PyErr_ExceptionMatches(PyExc_AttributeError)) {")
-        code.putln(
-                    "PyErr_Clear();")
-        code.putln(
-                    "v = %s(o, n);" %
-                        entry.func_cname)
-        code.putln(
+            code.putln(
+                "PyErr_Clear();")
+            code.putln(
+                "v = %s(o, n);" %
+                    getattr_entry.func_cname)
+            code.putln(
                 "}")
         code.putln(
-                "return v;")
+            "return v;")
         code.putln(
             "}")
     
diff --git a/Cython/Compiler/TypeSlots.py b/Cython/Compiler/TypeSlots.py
index 29179b75b..52c41c71e 100644
--- a/Cython/Compiler/TypeSlots.py
+++ b/Cython/Compiler/TypeSlots.py
@@ -610,7 +610,7 @@ slot_table = (
     MethodSlot(callfunc, "tp_call", "__call__"),
     MethodSlot(reprfunc, "tp_str", "__str__"),
     
-    SyntheticSlot("tp_getattro", ["__getattr__"], "0"), #"PyObject_GenericGetAttr"),
+    SyntheticSlot("tp_getattro", ["__getattr__","__getattribute__"], "0"), #"PyObject_GenericGetAttr"),
     SyntheticSlot("tp_setattro", ["__setattr__", "__delattr__"], "0"), #"PyObject_GenericSetAttr"),
 
     SuiteSlot(PyBufferProcs, "PyBufferProcs", "tp_as_buffer"),
diff --git a/tests/run/__getattribute__.pyx b/tests/run/__getattribute__.pyx
new file mode 100644
index 000000000..5ed494a49
--- /dev/null
+++ b/tests/run/__getattribute__.pyx
@@ -0,0 +1,60 @@
+__doc__ = """
+__getattribute__ and __getattr__ special methods for a single class.
+
+    >>> a = just_getattribute()
+    >>> a.bar
+    'bar'
+    >>> a.invalid
+    Traceback (most recent call last):
+    AttributeError
+
+    >>> a = just_getattr()
+    >>> a.foo
+    10
+    >>> a.bar
+    'bar'
+    >>> a.invalid
+    Traceback (most recent call last):
+    AttributeError
+
+    >>> a = both()
+    >>> a.foo
+    10
+    >>> a.bar
+    'bar'
+    >>> a.invalid
+    Traceback (most recent call last):
+    AttributeError
+"""
+
+cdef class just_getattribute:
+    def __getattribute__(self,n):
+        if n == 'bar':
+            return n
+        else:
+            raise AttributeError
+
+cdef class just_getattr:
+    cdef readonly int foo
+    def __init__(self):
+        self.foo = 10
+    def __getattr__(self,n):
+        if n == 'bar':
+            return n
+        else:
+            raise AttributeError
+
+cdef class both:
+    cdef readonly int foo
+    def __init__(self):
+        self.foo = 10
+    def __getattribute__(self,n):
+        if n == 'foo':
+            return self.foo
+        else:
+            raise AttributeError
+    def __getattr__(self,n):
+        if n == 'bar':
+            return n
+        else:
+            raise AttributeError
-- 
2.30.9