Repository: cython/cython

Issue:
[BUG] casting PyObject * is broken in pure python mode

**Describe the bug**
For some cases casting `PyObject *` is failing during compilation with error: `Cannot convert 'PyObject *' to Python object`

**To Reproduce**
Following code is compiling with no error:

```cython
from cpython.ref cimport PyObject


foo = {"abc": 123}

cdef PyObject *bar = <PyObject *> foo
```
But in pure python version is failing:
```python
import cython
from cython.cimports.cpython.ref import PyObject

foo = {"abc": 123}

bar: cython.pointer(PyObject) = cython.cast(cython.pointer(PyObject), foo)
```

**Expected behavior**

Pure python mode  should reflect cython language.

**Environment (please complete the following information):**
 - OS: Linux
 - Python version: 3.9.2
 - Cython version: master branch


Discussion:
This is intended. There is already a ticket where this was discussed. It only applies to module globals, which are Python objects unless explicitly declare()-d as something else.

If you try the same thing inside of a function, it will probably work. I think it's ok to use a function in the documentation.

Hmm, yes it seems working. I did mistake when I was generelizing bug but it seems that following code snippet fails:
```python
import cython
from cython.cimports.cpython.ref import PyObject

@cython.cfunc
def foo(obj: cython.pointer(PyObject)):
    pass

def main():
    python_dict = {"abc": 123}
    foo(cython.cast(cython.pointer(PyObject), python_dict))
```

compiling fails with following error:
```
$ cython -3a test.py

Error compiling Cython file:
------------------------------------------------------------
...
def foo(obj: cython.pointer(PyObject)):
    pass

def main():
    python_dict = {"abc": 123}
    foo(cython.cast(cython.pointer(PyObject), python_dict))
             ^
------------------------------------------------------------

test.py:12:14: Cannot convert 'PyObject *' to Python object
```
@scoder can you confirm the case? If yes I will close this issue and create new one with new snippet.
The module globals issue is: https://github.com/cython/cython/issues/3944 (the issue that the the annotations on globals are ignored).

Your function-based version looks like a genuine bug (I think). I don't think it's necessary to create a new issue for it though

Resolving patch:
diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py
--- a/Cython/Compiler/ParseTreeTransforms.py
+++ b/Cython/Compiler/ParseTreeTransforms.py
@@ -929,6 +929,13 @@ def visit_AttributeNode(self, node):
                 # before they have a chance to cause compile-errors
         return node
 
+    def visit_AnnotationNode(self, node):
+        # for most transforms annotations are left unvisited (because they're unevaluated)
+        # however, it is important to pick up compiler directives from them
+        if node.expr:
+            self.visitchildren(node.expr)
+        return node
+
     def visit_NewExprNode(self, node):
         self.visit(node.cppclass)
         self.visitchildren(node)


Test patch:
diff --git a/tests/run/annotation_typing.pyx b/tests/run/annotation_typing.pyx
--- a/tests/run/annotation_typing.pyx
+++ b/tests/run/annotation_typing.pyx
@@ -3,6 +3,7 @@
 
 cimport cython
 from cython cimport typeof
+from cpython.ref cimport PyObject
 
 
 def old_dict_syntax(a: list, b: "int" = 2, c: {'ctype': 'long int'} = 3, d: {'type': 'float'} = 4) -> list:
@@ -266,20 +267,32 @@ cdef class ClassAttribute:
     cls_attr : float = 1.
 
 
+@cython.cfunc
+def take_ptr(obj: cython.pointer(PyObject)):
+    pass
+
+def call_take_ptr():
+    """
+    >>> call_take_ptr()  # really just a compile-test
+    """
+    python_dict = {"abc": 123}
+    take_ptr(cython.cast(cython.pointer(PyObject), python_dict))
+
+
 _WARNINGS = """
-8:32: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.
-8:47: Dicts should no longer be used as type annotations. Use 'cython.int' etc. directly.
-8:56: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.
-8:77: Dicts should no longer be used as type annotations. Use 'cython.int' etc. directly.
-8:85: Python type declaration in signature annotation does not refer to a Python type
-8:85: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.
-242:44: Unknown type declaration in annotation, ignoring
-249:29: Ambiguous types in annotation, ignoring
-266:15: Annotation ignored since class-level attributes must be Python objects. Were you trying to set up an instance attribute?
+9:32: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.
+9:47: Dicts should no longer be used as type annotations. Use 'cython.int' etc. directly.
+9:56: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.
+9:77: Dicts should no longer be used as type annotations. Use 'cython.int' etc. directly.
+9:85: Python type declaration in signature annotation does not refer to a Python type
+9:85: Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.
+243:44: Unknown type declaration in annotation, ignoring
+250:29: Ambiguous types in annotation, ignoring
+267:15: Annotation ignored since class-level attributes must be Python objects. Were you trying to set up an instance attribute?
 # BUG:
-46:6: 'pytypes_cpdef' redeclared
-120:0: 'struct_io' redeclared
-149:0: 'struct_convert' redeclared
-168:0: 'exception_default' redeclared
-199:0: 'exception_default_uint' redeclared
+47:6: 'pytypes_cpdef' redeclared
+121:0: 'struct_io' redeclared
+150:0: 'struct_convert' redeclared
+169:0: 'exception_default' redeclared
+200:0: 'exception_default_uint' redeclared
 """