Repository: cython/cython
Pull request: Fix class pointer annotations
Fixes #4514

It looks like annotations attached to namenodes weren't correctly
processed with "InterpretCompilerDirectivesTransform"

Problem statement:
[BUG] Compilation fails when class attribute is annotated using `cython.pointer()`
**Describe the bug**
Compiler fails to compile when class attribute annotation is using `cython.pointer()`.

**To Reproduce**
Code to reproduce the behaviour:
```cython
import cython

A = cython.declare(cython.int, 3)

@cython.cclass
class Test:
    test: cython.pointer(cython.int)

    def __init__(self):
        self.test = cython.address(A)
```
Compilation fails with:
```
$ cython -3a test.py

Error compiling Cython file:
------------------------------------------------------------
...
@cython.cclass
class Test:
    test: cython.pointer(cython.int)

    def __init__(self):
        self.test = cython.address(A)
                         ^
------------------------------------------------------------

test.py:11:26: Cannot convert 'int *' to Python object
```
**Expected behavior**

Compilation should be successful.

**Environment (please complete the following information):**
 - OS: macOS
 - Python version: 3.9.7
 - Cython version: master

**Additional context**
All following code snippets compiles succesfully:
* cython version
```cython
import cython

cdef int A = 3

cdef class Test:
    cdef int *test

    def __init__(self):
        self.test = &A
```
* pure python version using `cython.p_int` annotation:
```python
import cython

A = cython.declare(cython.int, 3)

@cython.cclass
class Test:
    test: cython.p_int

    def __init__(self):
        self.test = cython.address(A)
```

Hints from issue discussion:

Commit message: Fix class pointer annotations

Fixes #4514

It looks like annotations attached to namenodes weren't correctly
processed with "InterpretCompilerDirectivesTransform"

Patch to review:
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index f6814cb5b50..a3722ad68c5 100644
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -1915,6 +1915,7 @@ class NameNode(AtomicExprNode):
     #  cf_maybe_null   boolean   Maybe uninitialized before this node
     #  allow_null      boolean   Don't raise UnboundLocalError
     #  nogil           boolean   Whether it is used in a nogil context
+    #  annotation      AnnotationNode or None   Deliberately not a subexpr
 
     is_name = True
     is_cython_module = False
@@ -1928,6 +1929,7 @@ class NameNode(AtomicExprNode):
     allow_null = False
     nogil = False
     inferred_type = None
+    annotation = None
 
     def as_cython_attribute(self):
         return self.cython_attribute
diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py
index 2edde9ba4bd..24a9e7b8e14 100644
--- a/Cython/Compiler/ParseTreeTransforms.py
+++ b/Cython/Compiler/ParseTreeTransforms.py
@@ -913,6 +913,8 @@ def visit_SingleAssignmentNode(self, node):
         return node
 
     def visit_NameNode(self, node):
+        if node.annotation:
+            node.annotation = self.visit(node.annotation)
         if node.name in self.cython_module_names:
             node.is_cython_module = True
         else:
diff --git a/tests/run/annotation_typing.pyx b/tests/run/annotation_typing.pyx
index 7b988c112df..b4a7b540d79 100644
--- a/tests/run/annotation_typing.pyx
+++ b/tests/run/annotation_typing.pyx
@@ -278,6 +278,21 @@ def call_take_ptr():
     python_dict = {"abc": 123}
     take_ptr(cython.cast(cython.pointer(PyObject), python_dict))
 
+@cython.cclass
+class HasPtr:
+    """
+    >>> HasPtr()
+    HasPtr(1, 1)
+    """
+    a: cython.pointer(cython.int)
+    b: cython.int
+
+    def __init__(self):
+        self.b = 1
+        self.a = cython.address(self.b)
+    def __repr__(self):
+        return f"HasPtr({self.a[0]}, {self.b})"
+
 
 _WARNINGS = """
 9:32: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.


Code review comments:
diff_hunk: @@ -1928,6 +1929,7 @@ class NameNode(AtomicExprNode):
     allow_null = False
     nogil = False
     inferred_type = None
+    annotation = None
path: Cython/Compiler/ExprNodes.py
text: @user1:
I don't think we need to define it here. Every `ExprNode` has an `annotation` attribute.