From 5326aee8e8bc4f7ac2472d95bbfb125a19393cef Mon Sep 17 00:00:00 2001
From: Xavier Thompson <xavier.thompson@nexedi.com>
Date: Thu, 10 Sep 2020 15:58:50 +0200
Subject: [PATCH] Introduce 'mutable' specifier from C++

---
 Cython/Compiler/ModuleNode.py | 2 ++
 Cython/Compiler/Nodes.py      | 8 ++++++++
 Cython/Compiler/Parsing.py    | 2 +-
 Cython/Compiler/Symtab.py     | 2 ++
 4 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py
index cc0d08e99..f9be333b2 100644
--- a/Cython/Compiler/ModuleNode.py
+++ b/Cython/Compiler/ModuleNode.py
@@ -1628,6 +1628,8 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
                 code.putln("%s;" % dunder_activate_entry.type.declaration_code(dunder_activate_entry.cname))
             for attr in scope.var_entries:
                 cname = attr.cname
+                if attr.is_mutable:
+                    code.put("mutable ")
                 if attr.type.is_cfunction and attr.type.is_static_method:
                     code.put("static ")
                 elif attr.name == "<init>":
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
index 17c4f0013..41d3cfcee 100644
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -1423,6 +1423,8 @@ class CVarDefNode(StatNode):
                             cfunc_declarator.args = []
                         cfunc_declarator.skipped_self = None
 
+                if 'mutable' in self.modifiers:
+                    error(self.pos, "Functions cannot be 'mutable'")
                 self.entry = dest_scope.declare_cfunction(
                     name, type, declarator.pos,
                     cname=cname, visibility=self.visibility, in_pxd=self.in_pxd,
@@ -1447,6 +1449,10 @@ class CVarDefNode(StatNode):
                     api=self.api, is_cdef=1)
                 if Options.docstrings:
                     self.entry.doc = embed_position(self.pos, self.doc)
+                if 'mutable' in self.modifiers:
+                    if not dest_scope.is_cpp_class_scope:
+                        error(self.pos, "Specifier 'mutable' can only apply to cppclass variable members")
+                    self.entry.is_mutable = 1
 
 
 class CStructOrUnionDefNode(StatNode):
@@ -2689,6 +2695,8 @@ class CFuncDefNode(FuncDefNode):
         typ.is_static_method = self.is_static_method
         typ.is_cyp_class_method = self.is_cyp_class_method
 
+        if 'mutable' in self.modifiers:
+            error(self.pos, "Functions cannot be 'mutable'")
         self.entry = env.declare_cfunction(
             name, typ, self.pos,
             cname=cname, visibility=self.visibility, api=self.api,
diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py
index 90be754ea..4f239c5c4 100644
--- a/Cython/Compiler/Parsing.py
+++ b/Cython/Compiler/Parsing.py
@@ -3332,7 +3332,7 @@ def p_visibility(s, prev_visibility):
     return visibility
 
 def p_c_modifiers(s):
-    if s.sy == 'IDENT' and s.systring in ('inline',):
+    if s.sy == 'IDENT' and s.systring in ('inline', 'mutable'):
         modifier = s.systring
         s.next()
         return [modifier] + p_c_modifiers(s)
diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py
index 21bfde3f9..d514d6af7 100644
--- a/Cython/Compiler/Symtab.py
+++ b/Cython/Compiler/Symtab.py
@@ -105,6 +105,7 @@ class Entry(object):
     # is_cclass        boolean    Is an extension class
     # is_cpp_class     boolean    Is a C++ class
     # is_const         boolean    Is a constant
+    # is_mutable       boolean    Is a mutable attribute
     # is_property      boolean    Is a property of an extension type:
     # doc_cname        string or None  C const holding the docstring
     # getter_cname     string          C func for getting property
@@ -201,6 +202,7 @@ class Entry(object):
     is_cclass = 0
     is_cpp_class = 0
     is_const = 0
+    is_mutable = 0
     is_property = 0
     is_cproperty = 0
     doc_cname = None
-- 
2.30.9