summaryrefslogtreecommitdiff
path: root/tests/run/pep526_variable_annotations.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/run/pep526_variable_annotations.py')
-rw-r--r--tests/run/pep526_variable_annotations.py222
1 files changed, 191 insertions, 31 deletions
diff --git a/tests/run/pep526_variable_annotations.py b/tests/run/pep526_variable_annotations.py
index 9a43f35f6..05d8c60b1 100644
--- a/tests/run/pep526_variable_annotations.py
+++ b/tests/run/pep526_variable_annotations.py
@@ -1,22 +1,31 @@
# cython: language_level=3
# mode: run
-# tag: pure3.6, pep526, pep484, warnings
+# tag: pure3.7, pep526, pep484, warnings
+
+# for the benefit of the pure tests, don't require annotations
+# to be evaluated
+from __future__ import annotations
import cython
from typing import Dict, List, TypeVar, Optional, Generic, Tuple
+
try:
+ import typing
+ from typing import Set as _SET_
from typing import ClassVar
except ImportError:
- ClassVar = Optional # fake it in Py3.5
+ pass # this should allow Cython to interpret the directives even when the module doesn't exist
var = 1 # type: annotation
-var: int = 2
-fvar: float = 1.2
+var: cython.int = 2
+fvar: cython.float = 1.2
some_number: cython.int # variable without initial value
-some_list: List[int] = [] # variable with initial value
-t: Tuple[int, ...] = (1, 2, 3)
+some_list: List[cython.int] = [] # variable with initial value
+another_list: list[cython.int] = []
+t: Tuple[cython.int, ...] = (1, 2, 3)
+t2: tuple[cython.int, ...]
body: Optional[List[str]]
descr_only : "descriptions are allowed but ignored"
@@ -31,11 +40,11 @@ def f():
(2, 1.5, [], (1, 2, 3))
"""
var = 1 # type: annotation
- var: int = 2
- fvar: float = 1.5
+ var: cython.int = 2
+ fvar: cython.float = 1.5
some_number: cython.int # variable without initial value
- some_list: List[int] = [] # variable with initial value
- t: Tuple[int, ...] = (1, 2, 3)
+ some_list: List[cython.int] = [] # variable with initial value
+ t: Tuple[cython.int, ...] = (1, 2, 3)
body: Optional[List[str]]
descr_only: "descriptions are allowed but ignored"
@@ -51,10 +60,12 @@ class BasicStarship(object):
'Picard'
>>> bs.stats
{}
+ >>> BasicStarship.stats
+ {}
"""
captain: str = 'Picard' # instance variable with default
damage: cython.int # instance variable without default
- stats: ClassVar[Dict[str, int]] = {} # class variable
+ stats: ClassVar[Dict[str, cython.int]] = {} # class variable
descr_only: "descriptions are allowed but ignored"
def __init__(self, damage):
@@ -70,7 +81,7 @@ class BasicStarshipExt(object):
"""
captain: str = 'Picard' # instance variable with default
damage: cython.int # instance variable without default
- stats: ClassVar[Dict[str, int]] = {} # class variable
+ stats: ClassVar[Dict[str, cython.int]] = {} # class variable
descr_only: "descriptions are allowed but ignored"
def __init__(self, damage):
@@ -117,13 +128,9 @@ def iter_declared_dict(d):
>>> iter_declared_dict(d)
7.0
- >>> class D(object):
- ... def __getitem__(self, x): return 2
- ... def __iter__(self): return iter([1, 2, 3])
- >>> iter_declared_dict(D())
- 6.0
+ # specialized "compiled" test in module-level __doc__
"""
- typed_dict : Dict[float, float] = d
+ typed_dict : Dict[cython.float, cython.float] = d
s = 0.0
for key in typed_dict:
s += d[key]
@@ -134,17 +141,13 @@ def iter_declared_dict(d):
"//WhileStatNode",
"//WhileStatNode//DictIterationNextNode",
)
-def iter_declared_dict_arg(d : Dict[float, float]):
+def iter_declared_dict_arg(d : Dict[cython.float, cython.float]):
"""
>>> d = {1.1: 2.5, 3.3: 4.5}
>>> iter_declared_dict_arg(d)
7.0
- >>> class D(object):
- ... def __getitem__(self, x): return 2
- ... def __iter__(self): return iter([1, 2, 3])
- >>> iter_declared_dict_arg(D())
- 6.0
+ # module level "compiled" test in __doc__ below
"""
s = 0.0
for key in d:
@@ -152,12 +155,169 @@ def iter_declared_dict_arg(d : Dict[float, float]):
return s
+def literal_list_ptr():
+ """
+ >>> literal_list_ptr()
+ 4
+ """
+ a : cython.p_int = [1, 2, 3, 4, 5]
+ return a[3]
+
+
+def test_subscripted_types():
+ """
+ >>> test_subscripted_types()
+ dict object
+ dict object
+ list object
+ list object
+ list object
+ set object
+ """
+ a1: typing.Dict[cython.int, cython.float] = {}
+ a2: dict[cython.int, cython.float] = {}
+ b1: List[cython.int] = []
+ b2: list[cython.int] = []
+ b3: List = [] # doesn't need to be subscripted
+ c: _SET_[object] = set()
+
+ print(cython.typeof(a1) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(a2) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(b1) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(b2) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(b3) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(c) + (" object" if not cython.compiled else ""))
+
+# because tuple is specifically special cased to go to ctuple where possible
+def test_tuple(a: typing.Tuple[cython.int, cython.float], b: typing.Tuple[cython.int, ...],
+ c: Tuple[cython.int, object] # cannot be a ctuple
+ ):
+ """
+ >>> test_tuple((1, 1.0), (1, 1.0), (1, 1.0))
+ int
+ int
+ float
+ Python object
+ (int, float)
+ tuple object
+ tuple object
+ tuple object
+ tuple object
+ """
+ x: typing.Tuple[int, float] = (a[0], a[1]) # note: Python int/float, not cython.int/float
+ y: Tuple[cython.int, ...] = (1,2.)
+ plain_tuple: Tuple = ()
+ z = a[0] # should infer to C int
+ p = x[1] # should infer to Python float -> C double
+
+ 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
+ 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 ""))
+ print(cython.typeof(y) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(c) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(plain_tuple) + (" object" if not cython.compiled else ""))
+
+
+# because tuple is specifically special cased to go to ctuple where possible
+def test_tuple_without_typing(a: tuple[cython.int, cython.float], b: tuple[cython.int, ...],
+ c: tuple[cython.int, object] # cannot be a ctuple
+ ):
+ """
+ >>> test_tuple_without_typing((1, 1.0), (1, 1.0), (1, 1.0))
+ int
+ int
+ float
+ Python object
+ (int, float)
+ tuple object
+ tuple object
+ tuple object
+ tuple object
+ """
+ x: tuple[int, float] = (a[0], a[1]) # note: Python int/float, not cython.int/float
+ y: tuple[cython.int, ...] = (1,2.)
+ plain_tuple: tuple = ()
+ z = a[0] # should infer to C int
+ p = x[1] # should infer to Python float -> C double
+
+ 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 "float") # FIXME: infer C double/PyFloat from Py type
+ 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 ""))
+ print(cython.typeof(y) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(c) + (" object" if not cython.compiled else ""))
+ print(cython.typeof(plain_tuple) + (" object" if not cython.compiled else ""))
+
+
+def test_use_typing_attributes_as_non_annotations():
+ """
+ >>> test_use_typing_attributes_as_non_annotations()
+ typing.Tuple typing.Tuple[int]
+ typing.Optional True
+ typing.Optional True
+ """
+ x1 = typing.Tuple
+ x2 = typing.Tuple[int]
+ y1 = typing.Optional
+ y2 = typing.Optional[typing.Dict]
+ z1 = Optional
+ z2 = Optional[Dict]
+ # The result of printing "Optional[type]" is slightly version-dependent
+ # so accept both possible forms
+ allowed_optional_strings = [
+ "typing.Union[typing.Dict, NoneType]",
+ "typing.Optional[typing.Dict]"
+ ]
+ print(x1, x2)
+ 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
+except ImportError:
+ # we can't actually use numpy typing right now, it was just part
+ # of a reproducer that caused a compiler crash. We don't need it
+ # available to use it in annotations, so don't fail if it's not there
+ pass
+
+def list_float_to_numpy(z: List[float]) -> List[npt.NDArray[np.float64]]:
+ # since we're not actually requiring numpy, don't make the return type match
+ assert cython.typeof(z) == 'list'
+ return [z[0]]
+
+if cython.compiled:
+ __doc__ = """
+ # passing non-dicts to variables declared as dict now fails
+ >>> class D(object):
+ ... def __getitem__(self, x): return 2
+ ... def __iter__(self): return iter([1, 2, 3])
+ >>> iter_declared_dict(D()) # doctest:+IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ TypeError: Expected dict, got D
+ >>> iter_declared_dict_arg(D()) # doctest:+IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ TypeError: Expected dict, got D
+ """
+
_WARNINGS = """
-37:19: Unknown type declaration in annotation, ignoring
-38:12: Unknown type declaration in annotation, ignoring
-39:18: Unknown type declaration in annotation, ignoring
-73:19: Unknown type declaration in annotation, ignoring
-# FIXME: these are sort-of evaluated now, so the warning is misleading
-126:21: Unknown type declaration in annotation, ignoring
-137:35: Unknown type declaration in annotation, ignoring
"""