# HG changeset patch
# User Simon King <simon.king@uni-jena.de>
# Date 1303982811 -7200
# Node ID a3255b872cd4d9e4733acb665fd0279ed6c256ba
# Parent  96ba4e37581e095fd0c29e9071e72b9c856c4f9c
#11115: Make cached_in_parent methods work for the element class of categories.

diff --git a/sage/misc/cachefunc.pyx b/sage/misc/cachefunc.pyx
--- a/sage/misc/cachefunc.pyx
+++ b/sage/misc/cachefunc.pyx
@@ -1,4 +1,4 @@
-"""
+r"""
 Cached Functions and Methods
 
 AUTHORS:
@@ -338,7 +338,7 @@
         return sage_getsource(self.f)
 
     def _sage_src_lines_(self):
-        """
+        r"""
         Returns the list of source lines and the first line number
         of the wrapped function.
 
@@ -1013,7 +1013,7 @@
         return self._fix_to_pos(*args,**kwds)
 
     def __get__(self, inst, cls): #cls=None):
-        """
+        r"""
         Get a :class:`CachedMethodCaller` bound to a specific
         instance of the class of the cached method.
 
@@ -1288,7 +1288,7 @@
         self.cache = value
 
     cpdef clear_cache(self):
-        """
+        r"""
         Clear the cache dictionary.
 
         EXAMPLES::
@@ -1672,7 +1672,7 @@
 cached_method = CachedMethod
 
 cdef class CachedInParentMethod(CachedMethod):
-    """
+    r"""
     A decorator that creates a cached version of an instance
     method of a class.
 
@@ -1684,8 +1684,7 @@
 
     This way of caching works only if
 
-    - the instances *have* a parent,
-    - the parent allows assignment of attributes, and
+    - the instances *have* a parent, and
     - the instances are hashable (they are part of the cache key).
 
     NOTE:
@@ -1751,6 +1750,71 @@
 
         sage: a.f.cache is b.f.get_cache() is c.f._cachedmethod._get_instance_cache(c)
         True
+
+    TEST:
+
+    By trac ticket #11115, cached-in-parent methods can be inherited
+    from the element class of a category. That even holds if neither
+    the element nor the parent allow attribute assignment::
+
+        sage: cython_code = ["from sage.structure.parent cimport Parent",
+        ... "from sage.structure.element cimport Element",
+        ... "from sage.all import Category, cached_in_parent_method",
+        ... "cdef class MyElement(Element):",
+        ... "    cdef object x",
+        ... "    def __init__(self,P,x):",
+        ... "        self.x=x",
+        ... "        Element.__init__(self,P)",
+        ... "    def _repr_(self):",
+        ... "        return '<%s>'%self.x",
+        ... "    def __neg__(self):",
+        ... "        return MyElement(self.parent(),-self.x)",
+        ... "    def __hash__(self):",
+        ... "        return hash(self.x)",
+        ... "    def __cmp__(self, other):",
+        ... "        return cmp(self.x, (<MyElement>other).x)",
+        ... "cdef class MyParent(Parent): pass",
+        ... "class MyCategory(Category):",
+        ... "    def super_categories(self):",
+        ... "        return [Objects()]",
+        ... "    class ElementMethods:",
+        ... "        @cached_in_parent_method",
+        ... "        def test_cache(self):",
+        ... "            return -self"]
+        sage: cython('\n'.join(cython_code))
+        sage: C = MyCategory()
+        sage: P = MyParent(category=C)
+        sage: e1 = MyElement(P,5)
+        sage: e2 = MyElement(P,5)
+        sage: e3 = MyElement(P,6)
+    
+    We verify that attribute assignment does not work::
+
+        sage: e1.bla = 1
+        Traceback (most recent call last):
+        ...
+        AttributeError: '...MyElement' object has no attribute 'bla'
+        sage: P.bla = 1
+        Traceback (most recent call last):
+        ...
+        AttributeError: '...MyParent' object has no attribute 'bla'
+
+    Nevertheless, the cached method works, and it returns identical
+    output for equal elements, as expected::
+
+        sage: e1.test_cache()
+        <-5>
+        sage: e1 is e2
+        False
+        sage: e1 == e2
+        True
+        sage: e2.test_cache() is e1.test_cache()
+        True
+        sage: e1 == e3
+        False
+        sage: e2.test_cache() == e3.test_cache()
+        False
+
     """
 
     def __init__(self, f, name=None):
@@ -1850,7 +1914,10 @@
             True
 
         """
-        P = inst.parent()
+        try:
+            P = inst.parent()
+        except AttributeError:
+            return {}
         try:
             return P.__dict__.setdefault(self._cache_name, {})
         except AttributeError:
@@ -2280,6 +2347,7 @@
     We provide the definition in Cython, however, since interactive
     Cython definitions provide introspection by trac ticket #9976, whereas
     Python definitions don't.
+    ::
 
         sage: P.<a,b,c,d> = QQ[]
         sage: I = P*[a,b]
@@ -2329,7 +2397,7 @@
 
     """
     def __getstate__(self):
-        """
+        r"""
         The idea is to remove that might provide a cache to some cached method
         from the return value of the ``__getstate__`` method.
 
