Repository: cython/cython

Issue:
[BUG] Incorrect type inference for floats
### Describe the bug

According to the [docs](https://cython.readthedocs.io/en/latest/src/userguide/language_basics.html#types):

> int, long, and float are not available for static typing and instead interpreted as C ``int``, ``long``, and ``float`` respectively

The following code snippet infers `attr` to be a Python object instead of a float.
```python
import cython

cdef class Test:
    cdef float attr

    @cython.infer_types.verbose(True)
    cdef float test(self):
        attr = self.attr
        return attr
```
Looking at the c code that is generated, cython seems to think that `self.attr` is a double at first instead of a float: `PyFloat_FromDouble(__pyx_v_self->attr);`


The inference is correct when the type is a `double` or an `int`. Is this a bug or am I misinterpreting the docs? 

### Environment

OS: Windows
Python version 3.10.2
Cython version 3.0.0a11


Discussion:
> `PyFloat_FromDouble(__pyx_v_self->attr);`

This is the correct way to create a Python float object from a C float (or double) value. Look for `float attr` in the generated C code, you'll probably find it in the object struct of your `Test` type.
I think this is probably a bug:

```
attr = self.attr
```

Here we're initializing `attr` with a known C float, so `attr` should be inferred as a C float.

There might be a good reason for it but it definitely looks wrong at first glance.
Ok, sorry, I misread the bug description then (too many `attr` names in the example).

It might have to do with the fact that we often infer C double for Python float. I'm not so sure if the user intention is clear in the example. If you do any arithmetic on the variable afterwards, you'd probably end up with a C double value instead of a C float value at some point, unless you're very careful typing literals as C float instead of C double.

In any case, inferring the `attr` variable as Python float object in your example seems wrong. We should at least infer a C floating point type for it, preferably C float.
Apologies for the crude example. This might be a better one:

```cython
cdef class Rectangle:
    cdef float x_len, y_len

    @cython.infer_types.verbose(True)
    cdef float get_area(self):
        area = self.x_len * self.y_len
        return area
```
`area` is inferred to be a Python object, despite being the product of two floats.

Also, just for the record, manually typing it works fine:
`cdef float area = self.x_len * self.y_len` generates the correct c code: `__pyx_v_area = (__pyx_v_self->x_len * __pyx_v_self->y_len);`

Resolving patch:
diff --git a/Cython/Compiler/TypeInference.py b/Cython/Compiler/TypeInference.py
--- a/Cython/Compiler/TypeInference.py
+++ b/Cython/Compiler/TypeInference.py
@@ -560,9 +560,11 @@ def safe_spanning_type(types, might_overflow, scope):
             return py_object_type
         else:
             return result_type
-    elif result_type is PyrexTypes.c_double_type:
+    elif (result_type is PyrexTypes.c_double_type or
+            result_type is PyrexTypes.c_float_type):
         # Python's float type is just a C double, so it's safe to use
-        # the C type instead
+        # the C type instead. Similarly if given a C float, it leads to
+        # a small loss of precision vs Python but is otherwise the same
         return result_type
     elif result_type is PyrexTypes.c_bint_type:
         # find_spanning_type() only returns 'bint' for clean boolean


Test patch:
diff --git a/tests/run/pep526_variable_annotations.py b/tests/run/pep526_variable_annotations.py
--- a/tests/run/pep526_variable_annotations.py
+++ b/tests/run/pep526_variable_annotations.py
@@ -181,7 +181,7 @@ def test_tuple(a: typing.Tuple[cython.int, cython.float], b: typing.Tuple[cython
     >>> test_tuple((1, 1.0), (1, 1.0), (1, 1.0))
     int
     int
-    Python object
+    float
     Python object
     (int, float)
     tuple object
@@ -195,7 +195,10 @@ def test_tuple(a: typing.Tuple[cython.int, cython.float], b: typing.Tuple[cython
 
     print(cython.typeof(z))
     print("int" if cython.compiled and cython.typeof(x[0]) == "Python object" else cython.typeof(x[0]))  # FIXME: infer Python int
-    print(cython.typeof(p) if cython.compiled or cython.typeof(p) != 'float' else "Python object")  # FIXME: infer C double
+    if cython.compiled:
+        print(cython.typeof(p))
+    else:
+        print('float' if cython.typeof(p) == 'float' else cython.typeof(p))
     print(cython.typeof(x[1]) if cython.compiled or cython.typeof(p) != 'float' else "Python object")  # FIXME: infer C double
     print(cython.typeof(a) if cython.compiled or cython.typeof(a) != 'tuple' else "(int, float)")
     print(cython.typeof(x) + (" object" if not cython.compiled else ""))
diff --git a/tests/run/type_inference.pyx b/tests/run/type_inference.pyx
--- a/tests/run/type_inference.pyx
+++ b/tests/run/type_inference.pyx
@@ -531,6 +531,12 @@ def safe_only():
     cdef int c_int = 1
     assert typeof(abs(c_int)) == "int", typeof(abs(c_int))
 
+    # float can be inferred
+    cdef float fl = 5.0
+    from_fl = fl
+    assert typeof(from_fl) == "float", typeof(from_fl)
+
+
 @infer_types(None)
 def safe_c_functions():
     """