From b0b20c217331e299c20ab743fbb3dfa4c5eef257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sun, 3 Nov 2024 12:47:36 +0200 Subject: [PATCH] Fixed explicit checks of PEP 604 unions against `types.UnionType` Fixes #467. --- docs/versionhistory.rst | 2 ++ src/typeguard/_checkers.py | 14 ++++---------- tests/test_checkers.py | 12 ++++++++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index bc663f7..3196676 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -17,6 +17,8 @@ This library adheres to - Fixed checking of protocols on the class level (against ``type[SomeProtocol]``) (`#498 `_) - Fixed ``Self`` checks in instance/class methods that have positional-only arguments +- Fixed explicit checks of PEP 604 unions against ``types.UnionType`` + (`#467 `_) **4.4.0** (2024-10-27) diff --git a/src/typeguard/_checkers.py b/src/typeguard/_checkers.py index 5856cf9..40ff2d4 100644 --- a/src/typeguard/_checkers.py +++ b/src/typeguard/_checkers.py @@ -422,6 +422,7 @@ def check_union( ) finally: del errors # avoid creating ref cycle + raise TypeCheckError(f"did not match any element in the union:\n{formatted_errors}") @@ -431,6 +432,9 @@ def check_uniontype( args: tuple[Any, ...], memo: TypeCheckMemo, ) -> None: + if not args: + return check_instance(value, types.UnionType, (), memo) + errors: dict[str, TypeCheckError] = {} try: for type_ in args: @@ -876,16 +880,6 @@ def check_paramspec( pass # No-op for now -def check_instanceof( - value: Any, - origin_type: Any, - args: tuple[Any, ...], - memo: TypeCheckMemo, -) -> None: - if not isinstance(value, origin_type): - raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}") - - def check_type_internal( value: Any, annotation: Any, diff --git a/tests/test_checkers.py b/tests/test_checkers.py index 8a2ef9f..d03080b 100644 --- a/tests/test_checkers.py +++ b/tests/test_checkers.py @@ -1,5 +1,6 @@ import collections.abc import sys +import types from contextlib import nullcontext from functools import partial from io import BytesIO, StringIO @@ -882,6 +883,17 @@ def inner3(): inner3() assert not leaked + @pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType requires 3.10") + def test_raw_uniontype_success(self): + check_type(str | int, types.UnionType) + + @pytest.mark.skipif(sys.version_info < (3, 10), reason="UnionType requires 3.10") + def test_raw_uniontype_fail(self): + with pytest.raises( + TypeCheckError, match="class str is not an instance of types.UnionType" + ): + check_type(str, types.UnionType) + class TestTypevar: def test_bound(self):