Repository: cython/cython

Issue:
[BUG] Cythonizing fails due to AttributeError when calling a cpdef function from a cimport'ed module 
**Describe the bug**
Not sure if this is related to #1317 or #3193. In Module 2 if I first hold a reference to a cpdef function from (cimported) Module 1, and then call that function (via the reference), the compiler errors out during cythonizing with the following output:
```
$ cythonize -i -3 test_cpdef_func_ptr2.pyx
Compiling /home/leofang/dev/test_cpdef_func_ptr2.pyx because it changed.
[1/1] Cythonizing /home/leofang/dev/test_cpdef_func_ptr2.pyx
Traceback (most recent call last):
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/bin/cythonize", line 11, in <module>
    sys.exit(main())
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Build/Cythonize.py", line 223, in main
    cython_compile(path, options)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Build/Cythonize.py", line 106, in cython_compile
    **options.options)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Build/Dependencies.py", line 1102, in cythonize
    cythonize_one(*args)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Build/Dependencies.py", line 1208, in cythonize_one
    result = compile_single(pyx_file, options, full_module_name=full_module_name)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Main.py", line 727, in compile_single
    return run_pipeline(source, options, full_module_name)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Main.py", line 515, in run_pipeline
    err, enddata = Pipeline.run_pipeline(pipeline, source)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Pipeline.py", line 355, in run_pipeline
    data = run(phase, data)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Pipeline.py", line 335, in run
    return phase(data)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Pipeline.py", line 52, in generate_pyx_code_stage
    module_node.process_implementation(options, result)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/ModuleNode.py", line 143, in process_implementation
    self.generate_c_code(env, options, result)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/ModuleNode.py", line 385, in generate_c_code
    self.body.generate_function_definitions(env, code)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Nodes.py", line 442, in generate_function_definitions
    stat.generate_function_definitions(env, code)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Nodes.py", line 442, in generate_function_definitions
    stat.generate_function_definitions(env, code)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Nodes.py", line 3174, in generate_function_definitions
    FuncDefNode.generate_function_definitions(self, env, code)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Nodes.py", line 1981, in generate_function_definitions
    self.generate_function_body(env, code)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Nodes.py", line 1743, in generate_function_body
    self.body.generate_execution_code(code)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Nodes.py", line 448, in generate_execution_code
    stat.generate_execution_code(code)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/Nodes.py", line 5988, in generate_execution_code
    value.generate_evaluation_code(code)
  File "/home/leofang/miniconda3/envs/cupy_cuda112_dev/lib/python3.7/site-packages/Cython/Compiler/ExprNodes.py", line 6052, in generate_evaluation_code
    if value and value.is_attribute and value.obj.type.is_pyobject:
AttributeError: 'NoneType' object has no attribute 'is_pyobject'
```

**To Reproduce**
Cythonizing the second module gives the above error:

`test_cpdef_func_ptr1.pxd`:
```cython
cpdef int f() except? -1
```

`test_cpdef_func_ptr1.pyx`:
```cython
cpdef int f() except? -1:
    return 123
```

`test_cpdef_func_ptr2.pyx`:
```cython
cimport test_cpdef_func_ptr1

cpdef g():
    x = test_cpdef_func_ptr1.f
    return x()
```

**Expected behavior**
If in the 2nd module I instead cimport only `f` from the 1st module and then do `x = f`, it'd work. I expect the same behavior for my above snippet.

**Environment (please complete the following information):**
 - OS: Linux
 - Python version: 3.7.9
 - Cython version: 0.29.21

**Additional context**
N/A


Discussion:
This also breaks with `cdef` methods so it isn't `cpdef` special-casing

Resolving patch:
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -6214,7 +6214,7 @@ def attribute_is_likely_method(attr):
             # not an attribute itself, but might have been assigned from one (e.g. bound method)
             for assignment in self.function.cf_state:
                 value = assignment.rhs
-                if value and value.is_attribute and value.obj.type.is_pyobject:
+                if value and value.is_attribute and value.obj.type and value.obj.type.is_pyobject:
                     if attribute_is_likely_method(value):
                         likely_method = 'likely'
                         break
@@ -6938,7 +6938,11 @@ def infer_type(self, env):
         # FIXME: this is way too redundant with analyse_types()
         node = self.analyse_as_cimported_attribute_node(env, target=False)
         if node is not None:
-            return node.entry.type
+            if node.entry.type and node.entry.type.is_cfunction:
+                # special-case - function converted to pointer
+                return PyrexTypes.CPtrType(node.entry.type)
+            else:
+                return node.entry.type
         node = self.analyse_as_type_attribute(env)
         if node is not None:
             return node.entry.type


Test patch:
diff --git a/tests/run/cimport.srctree b/tests/run/cimport.srctree
--- a/tests/run/cimport.srctree
+++ b/tests/run/cimport.srctree
@@ -41,6 +41,8 @@ ctypedef int my_int
 
 ######## a.pyx ########
 
+
+
 from other cimport (
     A,
     foo,
@@ -48,7 +50,17 @@ from other cimport (
 print(A, foo(10))
 
 cimport other
-print(other.A, other.foo(10))
+
+cdef call_fooptr(int (*fptr)(int)):
+    return fptr(10)
+
+def call_other_foo():
+    x = other.foo  # GH4000 - failed because other was untyped
+    return call_fooptr(x) # check that x is correctly resolved as a function pointer
+    
+
+
+print(other.A, other.foo(10), call_other_foo())
 
 from pkg cimport sub
 cdef sub.my_int a = 100