Repository: cython/cython
Pull request: Fix annotation typing of Optional[tuple]
Allow it to use a Py-tuple instead of a ctuple

Fixes https://github.com/cython/cython/issues/5263

Problem statement:
[BUG] Compile error when typing.Optional and tuple[float] used in function annotation
### Describe the bug

Cython compile throwing unexpected error. No error when annotated variable though. Used to work correctly in last alpha (3.0.0a11).

Command:
cythonize -3 test.py    

Traceback:
test.py:10:27: typing.Optional[...] cannot be applied to non-Python type (double)
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Scripts\cythonize.exe\__main__.py", line 7, in <module>
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Lib\site-packages\Cython\Build\Cythonize.py", line 243, in main
    _cython_compile_files(all_paths, options)
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Lib\site-packages\Cython\Build\Cythonize.py", line 70, in _cython_compile_files
    ext_modules = cythonize(
                  ^^^^^^^^^^
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Lib\site-packages\Cython\Build\Dependencies.py", line 1134, in cythonize
    cythonize_one(*args)
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Lib\site-packages\Cython\Build\Dependencies.py", line 1301, in cythonize_one
    raise CompileError(None, pyx_file)
Cython.Compiler.Errors.CompileError: D:\Projects\cyth\test.py

### Code to reproduce the behaviour:

```cython
from typing import Optional, Union

foo: Optional[tuple[float]] = 1.0,  # No error


def bar() -> Optional[tuple[int]]:  # No error
    return 1,


def baz() -> Optional[tuple[float]]:  # Error
    return 1.0,


def qux() -> Union[None, tuple[float]]:  # No error
    return 1.0,

```


### Expected behaviour

_No response_

### Environment

OS: Windows
Python version 3.11
Cython version 3.0.0b1


### Additional context

_No response_

Hints from issue discussion:
Thanks. A lot of this subscripted type annotation code is pretty new so we won't have caught all the corner cases.

I suspect what's happening is
```
tuple[float]
```
Is evaluating to a "c tuple" type. And it's falling to apply `Optional` to that. We should make sure that it doesn't do the "c tuple" change in places where it's expecting a Python type.

Commit message: Fix annotation typing of Optional[tuple]

Allow it to use a Py-tuple instead of a ctuple

Patch to review:
diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py
index b431255e4f5..3e7b6149961 100644
--- a/Cython/Compiler/PyrexTypes.py
+++ b/Cython/Compiler/PyrexTypes.py
@@ -4464,6 +4464,9 @@ def __init__(self, cname, components):
         self.exception_check = True
         self._convert_to_py_code = None
         self._convert_from_py_code = None
+        # equivalent_type must be set now because it isn't available at import time
+        from . import Builtin
+        self.equivalent_type = Builtin.tuple_type
 
     def __str__(self):
         return "(%s)" % ", ".join(str(c) for c in self.components)
diff --git a/tests/run/pep526_variable_annotations.py b/tests/run/pep526_variable_annotations.py
index c778a172074..fa1bc2ecdcb 100644
--- a/tests/run/pep526_variable_annotations.py
+++ b/tests/run/pep526_variable_annotations.py
@@ -280,6 +280,15 @@ def test_use_typing_attributes_as_non_annotations():
     print(y1, str(y2) in allowed_optional_strings)
     print(z1, str(z2) in allowed_optional_strings)
 
+def test_optional_ctuple(x: typing.Optional[tuple[float]]):
+    """
+    Should not be a C-tuple (because these can't be optional
+    >>> test_optional_ctuple((1.0,))
+    tuple object
+    """
+    print(cython.typeof(x) + (" object" if not cython.compiled else ""))
+
+
 try:
     import numpy.typing as npt
     import numpy as np


Code review comments:
diff_hunk: @@ -4464,6 +4464,9 @@ def __init__(self, cname, components):
         self.exception_check = True
         self._convert_to_py_code = None
         self._convert_from_py_code = None
+        # equivalent_type must be set now because it isn't available at import time
+        from . import Builtin
+        self.equivalent_type = Builtin.tuple_type
path: Cython/Compiler/PyrexTypes.py
text: @user1:
If `tuple_type` is really all we need, then let's just import that directly.
```suggestion
        from .Builtin import tuple_type
        self.equivalent_type = tuple_type
```

diff_hunk: @@ -280,6 +280,15 @@ def test_use_typing_attributes_as_non_annotations():
     print(y1, str(y2) in allowed_optional_strings)
     print(z1, str(z2) in allowed_optional_strings)
 
+def test_optional_ctuple(x: typing.Optional[tuple[float]]):
+    """
+    Should not be a C-tuple (because these can't be optional
path: tests/run/pep526_variable_annotations.py
text: @user1:
```suggestion
    Should not be a C-tuple (because these can't be optional)
```