diff --git a/stubs/invoke/@tests/test_cases/check_task.py b/stubs/invoke/@tests/test_cases/check_task.py index 6ad7b8cd0..06b96ad79 100644 --- a/stubs/invoke/@tests/test_cases/check_task.py +++ b/stubs/invoke/@tests/test_cases/check_task.py @@ -1,4 +1,5 @@ # pyright: reportUnnecessaryTypeIgnoreComment=true +from __future__ import annotations from invoke import Context, task diff --git a/test_cases/README.md b/test_cases/README.md index 4ff8949d1..b154c0d2e 100644 --- a/test_cases/README.md +++ b/test_cases/README.md @@ -79,20 +79,26 @@ is because the purpose of this folder is to test the implications of typeshed changes for end users, who will mainly be using `.py` files rather than `.pyi` files. -Another difference to the rest of typeshed is that the test cases in this -directory cannot always use modern syntax for type hints. +Another difference to the rest of typeshed +(which stems from the fact that the test-case files are all `.py` files +rather than `.pyi` files) +is that the test cases cannot always use modern syntax for type hints. +While we can use `from __future__ import annotations` to enable the use of +modern typing syntax wherever possible, +type checkers may (correctly) emit errors if PEP 604 syntax or PEP 585 syntax +is used in a runtime context on lower versions of Python. For example: -For example, PEP 604 -syntax (unions with a pipe `|` operator) is new in Python 3.10. While this -syntax can be used on older Python versions in a `.pyi` file, code using this -syntax will fail at runtime on Python <=3.9. Since the test cases all use `.py` -extensions, and since the tests need to pass on all Python versions >=3.7, PEP -604 syntax cannot be used in a test case. Use `typing.Union` and -`typing.Optional` instead. +```python +from __future__ import annotations -PEP 585 syntax can also not be used in the `test_cases` directory. Use -`typing.Tuple` instead of `tuple`, `typing.Callable` instead of -`collections.abc.Callable`, and `typing.Match` instead of `re.Match` (etc.). +from typing_extensions import assert_type + +x: str | int # PEP 604 syntax: okay on Python >=3.7, due to __future__ annotations +assert_type(x, str | int) # Will fail at runtime on Python <3.10 (use typing.Union instead) + +y: dict[str, int] # PEP 585 syntax: okay on Python >= 3.7, due to __future__ annotations +assert_type(y, dict[str, int]) # Will fail at runtime on Python <3.9 (use typing.Dict instead) +``` ### Version-dependent tests diff --git a/test_cases/stdlib/asyncio/check_coroutines.py b/test_cases/stdlib/asyncio/check_coroutines.py index e2c3d19e6..cfee8de0e 100644 --- a/test_cases/stdlib/asyncio/check_coroutines.py +++ b/test_cases/stdlib/asyncio/check_coroutines.py @@ -1,13 +1,15 @@ +from __future__ import annotations + from asyncio import iscoroutinefunction from collections.abc import Awaitable, Callable, Coroutine -from typing import Any, Union +from typing import Any from typing_extensions import assert_type def test_iscoroutinefunction( x: Callable[[str, int], Coroutine[str, int, bytes]], y: Callable[[str, int], Awaitable[bytes]], - z: Callable[[str, int], Union[str, Awaitable[bytes]]], + z: Callable[[str, int], str | Awaitable[bytes]], xx: object, ) -> None: diff --git a/test_cases/stdlib/asyncio/check_gather.py b/test_cases/stdlib/asyncio/check_gather.py index c64af0b11..6b7d3ce3d 100644 --- a/test_cases/stdlib/asyncio/check_gather.py +++ b/test_cases/stdlib/asyncio/check_gather.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio from typing import Any, Awaitable, List, Tuple, Union from typing_extensions import assert_type @@ -21,7 +23,7 @@ async def test_gather(awaitable1: Awaitable[int], awaitable2: Awaitable[str]) -> c = await asyncio.gather(awaitable1, awaitable2, awaitable1, awaitable1, awaitable1, awaitable1) assert_type(c, List[Any]) - awaitables_list: List[Awaitable[int]] = [awaitable1] + awaitables_list: list[Awaitable[int]] = [awaitable1] d = await asyncio.gather(*awaitables_list) assert_type(d, List[Any]) diff --git a/test_cases/stdlib/builtins/check_dict.py b/test_cases/stdlib/builtins/check_dict.py index 3ba6ce89a..2161bb264 100644 --- a/test_cases/stdlib/builtins/check_dict.py +++ b/test_cases/stdlib/builtins/check_dict.py @@ -1,4 +1,6 @@ -from typing import Dict, Generic, Iterable, Tuple, TypeVar +from __future__ import annotations + +from typing import Dict, Generic, Iterable, TypeVar from typing_extensions import assert_type # These do follow `__init__` overloads order: @@ -6,7 +8,7 @@ from typing_extensions import assert_type # mypy raises: 'Need type annotation for "bad"' # pyright is fine with it. # bad = dict() -good: Dict[str, str] = dict() +good: dict[str, str] = dict() assert_type(good, Dict[str, str]) assert_type(dict(arg=1), Dict[str, int]) @@ -16,7 +18,7 @@ _VT = TypeVar("_VT") class KeysAndGetItem(Generic[_KT, _VT]): - data: Dict[_KT, _VT] + data: dict[_KT, _VT] def keys(self) -> Iterable[_KT]: return self.data.keys() @@ -33,15 +35,15 @@ kt2: KeysAndGetItem[str, int] = KeysAndGetItem() assert_type(dict(kt2, arg=1), Dict[str, int]) -def test_iterable_tuple_overload(x: Iterable[Tuple[int, str]]) -> Dict[int, str]: +def test_iterable_tuple_overload(x: Iterable[tuple[int, str]]) -> dict[int, str]: return dict(x) -i1: Iterable[Tuple[int, str]] = [(1, "a"), (2, "b")] +i1: Iterable[tuple[int, str]] = [(1, "a"), (2, "b")] test_iterable_tuple_overload(i1) dict(i1, arg="a") # type: ignore -i2: Iterable[Tuple[str, int]] = [("a", 1), ("b", 2)] +i2: Iterable[tuple[str, int]] = [("a", 1), ("b", 2)] assert_type(dict(i2, arg=1), Dict[str, int]) i3: Iterable[str] = ["a.b"] diff --git a/test_cases/stdlib/builtins/check_iteration.py b/test_cases/stdlib/builtins/check_iteration.py index 7195c8bcd..3d6096353 100644 --- a/test_cases/stdlib/builtins/check_iteration.py +++ b/test_cases/stdlib/builtins/check_iteration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Iterator from typing_extensions import assert_type diff --git a/test_cases/stdlib/builtins/check_list.py b/test_cases/stdlib/builtins/check_list.py index 793d7951b..4113f5c66 100644 --- a/test_cases/stdlib/builtins/check_list.py +++ b/test_cases/stdlib/builtins/check_list.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import List, Union from typing_extensions import assert_type diff --git a/test_cases/stdlib/builtins/check_object.py b/test_cases/stdlib/builtins/check_object.py index f2b56e4e4..60df1143f 100644 --- a/test_cases/stdlib/builtins/check_object.py +++ b/test_cases/stdlib/builtins/check_object.py @@ -1,9 +1,11 @@ -from typing import Any, Tuple, Union +from __future__ import annotations + +from typing import Any # The following should pass without error (see #6661): class Diagnostic: - def __reduce__(self) -> Union[str, Tuple[Any, ...]]: + def __reduce__(self) -> str | tuple[Any, ...]: res = super().__reduce__() if isinstance(res, tuple) and len(res) >= 3: res[2]["_info"] = 42 diff --git a/test_cases/stdlib/builtins/check_pow.py b/test_cases/stdlib/builtins/check_pow.py index c1ff7497e..da06229f4 100644 --- a/test_cases/stdlib/builtins/check_pow.py +++ b/test_cases/stdlib/builtins/check_pow.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from decimal import Decimal from fractions import Fraction from typing import Any diff --git a/test_cases/stdlib/builtins/check_sum.py b/test_cases/stdlib/builtins/check_sum.py index 0f256fd2f..1228ff13b 100644 --- a/test_cases/stdlib/builtins/check_sum.py +++ b/test_cases/stdlib/builtins/check_sum.py @@ -1,26 +1,28 @@ +from __future__ import annotations + from typing import Any, List, Union from typing_extensions import Literal, assert_type class Foo: - def __add__(self, other: Any) -> "Foo": + def __add__(self, other: Any) -> Foo: return Foo() class Bar: - def __radd__(self, other: Any) -> "Bar": + def __radd__(self, other: Any) -> Bar: return Bar() class Baz: - def __add__(self, other: Any) -> "Baz": + def __add__(self, other: Any) -> Baz: return Baz() - def __radd__(self, other: Any) -> "Baz": + def __radd__(self, other: Any) -> Baz: return Baz() -literal_list: List[Literal[0, 1]] = [0, 1, 1] +literal_list: list[Literal[0, 1]] = [0, 1, 1] assert_type(sum([2, 4]), int) assert_type(sum([3, 5], 4), int) diff --git a/test_cases/stdlib/builtins/check_tuple.py b/test_cases/stdlib/builtins/check_tuple.py index bf066feb2..bc0d8db28 100644 --- a/test_cases/stdlib/builtins/check_tuple.py +++ b/test_cases/stdlib/builtins/check_tuple.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Tuple from typing_extensions import assert_type diff --git a/test_cases/stdlib/check_codecs.py b/test_cases/stdlib/check_codecs.py index 0657416bc..19e663cee 100644 --- a/test_cases/stdlib/check_codecs.py +++ b/test_cases/stdlib/check_codecs.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import codecs from typing_extensions import assert_type diff --git a/test_cases/stdlib/check_contextlib.py b/test_cases/stdlib/check_contextlib.py index 26253326b..648661bca 100644 --- a/test_cases/stdlib/check_contextlib.py +++ b/test_cases/stdlib/check_contextlib.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from contextlib import ExitStack from typing_extensions import assert_type diff --git a/test_cases/stdlib/check_logging.py b/test_cases/stdlib/check_logging.py index dd572538f..f51edd19d 100644 --- a/test_cases/stdlib/check_logging.py +++ b/test_cases/stdlib/check_logging.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from typing import Any diff --git a/test_cases/stdlib/check_threading.py b/test_cases/stdlib/check_threading.py index 9b67ac540..eddfc2549 100644 --- a/test_cases/stdlib/check_threading.py +++ b/test_cases/stdlib/check_threading.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import _threading_local import threading diff --git a/test_cases/stdlib/check_unittest.py b/test_cases/stdlib/check_unittest.py index b8a16e7ef..7b0de9b30 100644 --- a/test_cases/stdlib/check_unittest.py +++ b/test_cases/stdlib/check_unittest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import unittest from datetime import datetime, timedelta from decimal import Decimal @@ -58,14 +60,14 @@ class Eggs: class Ham: - def __lt__(self, other: "Ham") -> bool: + def __lt__(self, other: Ham) -> bool: if not isinstance(other, Ham): return NotImplemented return True class Bacon: - def __gt__(self, other: "Bacon") -> bool: + def __gt__(self, other: Bacon) -> bool: if not isinstance(other, Bacon): return NotImplemented return True diff --git a/test_cases/stdlib/typing/check_all.py b/test_cases/stdlib/typing/check_all.py index 3519912e1..9a5165383 100644 --- a/test_cases/stdlib/typing/check_all.py +++ b/test_cases/stdlib/typing/check_all.py @@ -1,3 +1,5 @@ +from __future__ import annotations + # pyright: reportWildcardImportFromLibrary=false """ diff --git a/test_cases/stdlib/typing/check_pattern.py b/test_cases/stdlib/typing/check_pattern.py index 69978c4aa..ec5c1c4f6 100644 --- a/test_cases/stdlib/typing/check_pattern.py +++ b/test_cases/stdlib/typing/check_pattern.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Match, Optional, Pattern from typing_extensions import assert_type diff --git a/tests/check_consistent.py b/tests/check_consistent.py index 4b2931524..2a5e8d93a 100755 --- a/tests/check_consistent.py +++ b/tests/check_consistent.py @@ -85,9 +85,10 @@ def check_test_cases() -> None: bad_test_case_filename = 'Files in a `test_cases` directory must have names starting with "check_"; got "{}"' for file in testcase_dir.rglob("*.py"): assert file.stem.startswith("check_"), bad_test_case_filename.format(file) + with open(file, encoding="UTF-8") as f: + lines = {line.strip() for line in f} + assert "from __future__ import annotations" in lines, "Test-case files should use modern typing syntax where possible" if package_name != "stdlib": - with open(file, encoding="UTF-8") as f: - lines = {line.strip() for line in f} pyright_setting_not_enabled_msg = ( f'Third-party test-case file "{file}" must have ' f'"# pyright: reportUnnecessaryTypeIgnoreComment=true" ' @@ -205,7 +206,7 @@ def check_requirements() -> None: precommit_requirements = get_precommit_requirements() no_txt_entry_msg = "All pre-commit requirements must also be listed in `requirements-tests.txt` (missing {requirement!r})" for requirement, specifier in precommit_requirements.items(): - assert requirement in requirements_txt_requirements, no_txt_entry_msg.format(requirement) + assert requirement in requirements_txt_requirements, no_txt_entry_msg.format(requirement=requirement) specifier_mismatch = ( f'Specifier "{specifier}" for {requirement!r} in `.pre-commit-config.yaml` ' f'does not match specifier "{requirements_txt_requirements[requirement]}" in `requirements-tests.txt`'