diff options
Diffstat (limited to 'tests/run/pep526_variable_annotations.py')
-rw-r--r-- | tests/run/pep526_variable_annotations.py | 222 |
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 """ |