From 054c1ad381857f8ae677ab800a5b803ad3f8b99b Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 17:07:06 -0800 Subject: [PATCH 01/12] Add LiteralGeneric. --- typemap/type_eval/_eval_operators.py | 9 +++++++++ typemap/type_eval/_eval_typing.py | 10 ++++++++++ typemap/typing.py | 10 ++++++++++ 3 files changed, 29 insertions(+) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 49a60ab..a682ff3 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -40,6 +40,7 @@ StrSlice, Uncapitalize, Uppercase, + _LiteralGeneric, ) ################################################################## @@ -242,6 +243,14 @@ def _eval_IsSubSimilar(lhs, rhs, *, ctx): return type_eval.issubsimilar(lhs, rhs) +@type_eval.register_evaluator(_LiteralGeneric) +@_lift_evaluated +def _eval_LiteralGeneric(tp, *, ctx): + if isinstance(tp, type): + raise TypeError(f"Expected literal type, got '{tp.__name__}'") + return _LiteralGeneric[tp] + + ################################################################## diff --git a/typemap/type_eval/_eval_typing.py b/typemap/type_eval/_eval_typing.py index 2db3fd7..ee8c53d 100644 --- a/typemap/type_eval/_eval_typing.py +++ b/typemap/type_eval/_eval_typing.py @@ -182,6 +182,16 @@ def eval_typing(obj: typing.Any): result = _eval_types(obj, ctx) if not isinstance(result, list) and result in ctx.known_recursive_types: result = ctx.known_recursive_types[result] + + if isinstance(result, bool): + # Wrap a boolean result with _LiteralGeneric + # This is because `not` calls `__bool__` first so a boolean + # expression like `not _LiteralGeneric[True]` will result `False`, + # not `_LiteralGeneric[False]` as we want. + import typemap.typing as nt + + result = nt._LiteralGeneric[result] + return result diff --git a/typemap/typing.py b/typemap/typing.py index 021483d..dc10b58 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -217,3 +217,13 @@ def IsSubSimilar(self, tps): IsSub = IsSubSimilar + + +class _LiteralGenericAlias(_GenericAlias, _root=True): # type: ignore[call-arg] + def __bool__(self): + return typing.get_args(self)[0] + + +@_SpecialForm +def _LiteralGeneric(self, tp): + return _LiteralGenericAlias(self, tp) From 9c2553d30f46bf09452301e4b76deef320e38fa4 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 17:06:50 -0800 Subject: [PATCH 02/12] Add tests. --- tests/test_type_eval.py | 98 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 9eee8ca..8331a05 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -39,6 +39,7 @@ StrConcat, StrSlice, Uppercase, + _LiteralGeneric, ) from . import format_helper @@ -1026,6 +1027,103 @@ def test_eval_iter_02(): assert d == tuple[int, str, int, str] +type NotLiteralGeneric[T] = not T +type AndLiteralGeneric[L, R] = L and R +type OrLiteralGeneric[L, R] = L or R +type LiteralGenericToLiteral[T] = Literal[True] if T else Literal[False] +type NotLiteralGenericToLiteral[T] = Literal[True] if not T else Literal[False] + + +def test_eval_literal_generic_01(): + d = eval_typing(_LiteralGeneric[True]) + assert d == _LiteralGeneric[True] + + d = eval_typing(_LiteralGeneric[False]) + assert d == _LiteralGeneric[False] + + d = eval_typing(_LiteralGeneric[1]) + assert d == _LiteralGeneric[True] + + d = eval_typing(_LiteralGeneric[0]) + assert d == _LiteralGeneric[False] + + +def test_eval_literal_generic_02(): + d = eval_typing(not _LiteralGeneric[True]) + assert d == _LiteralGeneric[False] + + d = eval_typing(NotLiteralGeneric[_LiteralGeneric[True]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(NotLiteralGeneric[_LiteralGeneric[False]]) + assert d == _LiteralGeneric[True] + + +def test_eval_literal_generic_03(): + d = eval_typing( + AndLiteralGeneric[_LiteralGeneric[True], _LiteralGeneric[True]] + ) + assert d == _LiteralGeneric[True] + + d = eval_typing( + AndLiteralGeneric[_LiteralGeneric[True], _LiteralGeneric[False]] + ) + assert d == _LiteralGeneric[False] + + d = eval_typing( + AndLiteralGeneric[_LiteralGeneric[False], _LiteralGeneric[True]] + ) + assert d == _LiteralGeneric[False] + + d = eval_typing( + AndLiteralGeneric[_LiteralGeneric[False], _LiteralGeneric[False]] + ) + assert d == _LiteralGeneric[False] + + +def test_eval_literal_generic_04(): + d = eval_typing( + OrLiteralGeneric[_LiteralGeneric[True], _LiteralGeneric[True]] + ) + assert d == _LiteralGeneric[True] + + d = eval_typing( + OrLiteralGeneric[_LiteralGeneric[True], _LiteralGeneric[False]] + ) + assert d == _LiteralGeneric[True] + + d = eval_typing( + OrLiteralGeneric[_LiteralGeneric[False], _LiteralGeneric[True]] + ) + assert d == _LiteralGeneric[True] + + d = eval_typing( + OrLiteralGeneric[_LiteralGeneric[False], _LiteralGeneric[False]] + ) + assert d == _LiteralGeneric[False] + + +def test_eval_literal_generic_05(): + d = eval_typing(LiteralGenericToLiteral[_LiteralGeneric[True]]) + assert d == Literal[True] + + d = eval_typing(LiteralGenericToLiteral[_LiteralGeneric[False]]) + assert d == Literal[False] + + +def test_eval_literal_generic_06(): + d = eval_typing(NotLiteralGenericToLiteral[_LiteralGeneric[True]]) + assert d == Literal[False] + + d = eval_typing(NotLiteralGenericToLiteral[_LiteralGeneric[False]]) + assert d == Literal[True] + + +def test_eval_literal_generic_error_01(): + with pytest.raises(TypeError, match="Expected literal type, got 'int'"): + eval_typing(_LiteralGeneric[int]) + + def test_eval_length_01(): d = eval_typing(Length[tuple[int, str]]) assert d == Literal[2] From cb94f8b2c07dd700443e8f24885dfabffc354d7a Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 17:07:12 -0800 Subject: [PATCH 03/12] Fix other tests. --- tests/test_type_eval.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 8331a05..e5b1cb1 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -994,7 +994,7 @@ def test_uppercase_never(): def test_never_is(): d = eval_typing(IsSub[Never, Never]) - assert d is True + assert d == _LiteralGeneric[True] def test_eval_iter_01(): @@ -1150,7 +1150,9 @@ def test_eval_literal_idempotent_01(): def test_is_literal_true_vs_one(): - assert eval_typing(IsSub[Literal[True], Literal[1]]) is False + assert ( + eval_typing(IsSub[Literal[True], Literal[1]]) == _LiteralGeneric[False] + ) def test_callable_to_signature_01(): @@ -1318,7 +1320,7 @@ class AnnoTest: def test_type_eval_annotated_02(): res = eval_typing(IsSub[GetAttr[AnnoTest, Literal["a"]], int]) - assert res is True + assert res == _LiteralGeneric[True] def test_type_eval_annotated_03(): From 4754e953fb3d89dcff9159f412dad3fad7cca3f2 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 17:33:33 -0800 Subject: [PATCH 04/12] Rename _IsGenericAlias to _BoolGenericAlias. --- typemap/typing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/typemap/typing.py b/typemap/typing.py index dc10b58..66c2669 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -197,7 +197,7 @@ def Iter(self, tp): return _IterGenericAlias(self, (tp,)) -class _IsGenericAlias(_GenericAlias, _root=True): # type: ignore[call-arg] +class _BoolGenericAlias(_GenericAlias, _root=True): # type: ignore[call-arg] def __bool__(self): evaluator = special_form_evaluator.get() if evaluator: @@ -208,12 +208,12 @@ def __bool__(self): @_SpecialForm def IsSubtype(self, tps): - return _IsGenericAlias(self, tps) + return _BoolGenericAlias(self, tps) @_SpecialForm def IsSubSimilar(self, tps): - return _IsGenericAlias(self, tps) + return _BoolGenericAlias(self, tps) IsSub = IsSubSimilar From 1f21b1648485fa850dd478701ef082af5017920c Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 20:55:43 -0800 Subject: [PATCH 05/12] Use _LiteralGeneric in IsSubtype and IsSubSimilar. --- typemap/type_eval/_eval_operators.py | 4 ++-- typemap/typing.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index a682ff3..3cf50bd 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -234,13 +234,13 @@ def _eval_Iter(tp, *, ctx): @type_eval.register_evaluator(IsSubtype) @_lift_evaluated def _eval_IsSubtype(lhs, rhs, *, ctx): - return type_eval.issubtype(lhs, rhs) + return _LiteralGeneric[type_eval.issubtype(lhs, rhs)] @type_eval.register_evaluator(IsSubSimilar) @_lift_evaluated def _eval_IsSubSimilar(lhs, rhs, *, ctx): - return type_eval.issubsimilar(lhs, rhs) + return _LiteralGeneric[type_eval.issubsimilar(lhs, rhs)] @type_eval.register_evaluator(_LiteralGeneric) diff --git a/typemap/typing.py b/typemap/typing.py index 66c2669..65abd72 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -201,7 +201,9 @@ class _BoolGenericAlias(_GenericAlias, _root=True): # type: ignore[call-arg] def __bool__(self): evaluator = special_form_evaluator.get() if evaluator: - return evaluator(self) + result = evaluator(self) + # Unwrap _LiteralGeneric + return bool(result) else: return False From 7315944e1fe39b265b611b3a216eea26bc95b626 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 17:36:15 -0800 Subject: [PATCH 06/12] Add Bool. --- tests/test_type_eval.py | 65 ++++++++++++++++++++++++++++ typemap/type_eval/_eval_operators.py | 16 +++++++ typemap/typing.py | 5 +++ 3 files changed, 86 insertions(+) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index e5b1cb1..8d78948 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -20,6 +20,7 @@ from typemap.type_eval import eval_typing from typemap.typing import ( Attrs, + Bool, FromUnion, GenericCallable, GetArg, @@ -1034,6 +1035,70 @@ def test_eval_iter_02(): type NotLiteralGenericToLiteral[T] = Literal[True] if not T else Literal[False] +def test_eval_bool_01(): + d = eval_typing(Bool[Literal[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(Bool[Literal[False]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(Bool[Literal[1]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(Bool[Literal[0]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(Bool[Literal["true"]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(Bool[Literal["false"]]) + assert d == _LiteralGeneric[True] + + +def test_eval_bool_02(): + d = eval_typing(Bool[_LiteralGeneric[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(Bool[_LiteralGeneric[False]]) + assert d == _LiteralGeneric[False] + + +def test_eval_bool_03(): + d = eval_typing(NotLiteralGeneric[Bool[Literal[True]]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(NotLiteralGeneric[Bool[Literal[False]]]) + assert d == _LiteralGeneric[True] + + +type NestedBool0[T] = Bool[T] +type NestedBool1[T] = NestedBool0[Bool[T]] +type NestedBool2[T] = NestedBool1[Bool[T]] +type NestedBool3[T] = NestedBool2[Bool[T]] +type NestedBool4[T] = NestedBool3[Bool[T]] +type NestedBool5[T] = NestedBool4[Bool[T]] + + +def test_eval_bool_04(): + d = eval_typing(NestedBool5[Literal[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(NestedBool5[Literal[False]]) + assert d == _LiteralGeneric[False] + + +type IsIntBool[T] = Bool[IsSub[T, int]] +type IsIntLiteral[T] = Literal[True] if Bool[IsIntBool[T]] else Literal[False] + + +def test_eval_bool_05(): + d = eval_typing(IsIntLiteral[int]) + assert d == Literal[True] + + d = eval_typing(IsIntLiteral[str]) + assert d == Literal[False] + + def test_eval_literal_generic_01(): d = eval_typing(_LiteralGeneric[True]) assert d == _LiteralGeneric[True] diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 3cf50bd..634ce4b 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -17,6 +17,7 @@ ) from typemap.typing import ( Attrs, + Bool, Capitalize, DropAnnotations, FromUnion, @@ -243,6 +244,21 @@ def _eval_IsSubSimilar(lhs, rhs, *, ctx): return _LiteralGeneric[type_eval.issubsimilar(lhs, rhs)] +def _eval_bool_tp(tp): + if _typing_inspect.is_generic_alias(tp): + if tp.__origin__ is typing.Literal: + return _LiteralGeneric[bool(tp.__args__[0])] + elif tp.__origin__ is _LiteralGeneric: + return _LiteralGeneric[bool(tp.__args__[0])] + raise TypeError(f"Expected Literal type, got {tp}") + + +@type_eval.register_evaluator(Bool) +@_lift_evaluated +def _eval_Bool(tp, *, ctx): + return _eval_bool_tp(tp) + + @type_eval.register_evaluator(_LiteralGeneric) @_lift_evaluated def _eval_LiteralGeneric(tp, *, ctx): diff --git a/typemap/typing.py b/typemap/typing.py index 65abd72..ae60f62 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -221,6 +221,11 @@ def IsSubSimilar(self, tps): IsSub = IsSubSimilar +@_SpecialForm +def Bool(self, tp): + return _BoolGenericAlias(self, tp) + + class _LiteralGenericAlias(_GenericAlias, _root=True): # type: ignore[call-arg] def __bool__(self): return typing.get_args(self)[0] From b2a291f92bb7a7bb6f9192df2fb06c167c966cef Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 18:05:28 -0800 Subject: [PATCH 07/12] Add AnyOf. --- tests/test_type_eval.py | 84 ++++++++++++++++++++++++++++ typemap/type_eval/_eval_operators.py | 7 +++ typemap/typing.py | 5 ++ 3 files changed, 96 insertions(+) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 8d78948..11be71f 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -19,6 +19,7 @@ from typemap.type_eval import eval_typing from typemap.typing import ( + AnyOf, Attrs, Bool, FromUnion, @@ -1099,6 +1100,89 @@ def test_eval_bool_05(): assert d == Literal[False] +def test_eval_any_01(): + d = eval_typing(AnyOf[()]) + assert d == _LiteralGeneric[False] + + d = eval_typing(AnyOf[_LiteralGeneric[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AnyOf[_LiteralGeneric[False]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(AnyOf[_LiteralGeneric[True], _LiteralGeneric[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AnyOf[_LiteralGeneric[True], _LiteralGeneric[False]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AnyOf[_LiteralGeneric[False], _LiteralGeneric[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AnyOf[_LiteralGeneric[False], _LiteralGeneric[False]]) + assert d == _LiteralGeneric[False] + + +def test_eval_any_02(): + d = eval_typing(AnyOf[()]) + assert d == _LiteralGeneric[False] + + d = eval_typing(AnyOf[Literal[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AnyOf[Literal[False]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(AnyOf[Literal[True], Literal[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AnyOf[Literal[True], Literal[False]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AnyOf[Literal[False], Literal[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AnyOf[Literal[False], Literal[False]]) + assert d == _LiteralGeneric[False] + + +type ContainsAnyInt[Ts] = AnyOf[*[IsSub[t, int] for t in Iter[Ts]]] +type ContainsAnyIntToLiteral[Ts] = ( + Literal[True] if Bool[ContainsAnyInt[Ts]] else Literal[False] +) + + +def test_eval_any_03(): + d = eval_typing(ContainsAnyInt[tuple[()]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(ContainsAnyInt[tuple[int]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(ContainsAnyInt[tuple[str]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(ContainsAnyInt[tuple[int, int]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(ContainsAnyInt[tuple[int, str]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(ContainsAnyInt[tuple[str, str]]) + assert d == _LiteralGeneric[False] + + +def test_eval_any_04(): + d = eval_typing(ContainsAnyIntToLiteral[tuple[()]]) + assert d == Literal[False] + + d = eval_typing(ContainsAnyIntToLiteral[tuple[int]]) + assert d == Literal[True] + + d = eval_typing(ContainsAnyIntToLiteral[tuple[str]]) + assert d == Literal[False] + + def test_eval_literal_generic_01(): d = eval_typing(_LiteralGeneric[True]) assert d == _LiteralGeneric[True] diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 634ce4b..6fe255b 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -16,6 +16,7 @@ _get_class_type_hint_namespaces, ) from typemap.typing import ( + AnyOf, Attrs, Bool, Capitalize, @@ -259,6 +260,12 @@ def _eval_Bool(tp, *, ctx): return _eval_bool_tp(tp) +@type_eval.register_evaluator(AnyOf) +@_lift_evaluated +def _eval_AnyOf(*tp, ctx): + return _LiteralGeneric[any(_eval_bool_tp(tp) for tp in tp)] + + @type_eval.register_evaluator(_LiteralGeneric) @_lift_evaluated def _eval_LiteralGeneric(tp, *, ctx): diff --git a/typemap/typing.py b/typemap/typing.py index ae60f62..952990e 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -226,6 +226,11 @@ def Bool(self, tp): return _BoolGenericAlias(self, tp) +@_SpecialForm +def AnyOf(self, tp): + return _BoolGenericAlias(self, tp) + + class _LiteralGenericAlias(_GenericAlias, _root=True): # type: ignore[call-arg] def __bool__(self): return typing.get_args(self)[0] From 35838052ca373ad30831e33b9eff66c8af053aa6 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Tue, 27 Jan 2026 21:02:34 -0800 Subject: [PATCH 08/12] Add AllOf. --- tests/test_type_eval.py | 72 ++++++++++++++++++++++++++++ typemap/type_eval/_eval_operators.py | 7 +++ typemap/typing.py | 5 ++ 3 files changed, 84 insertions(+) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 11be71f..fb1d326 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -19,6 +19,7 @@ from typemap.type_eval import eval_typing from typemap.typing import ( + AllOf, AnyOf, Attrs, Bool, @@ -1100,6 +1101,77 @@ def test_eval_bool_05(): assert d == Literal[False] +def test_eval_all_01(): + d = eval_typing(AllOf[()]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AllOf[_LiteralGeneric[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AllOf[_LiteralGeneric[False]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(AllOf[_LiteralGeneric[True], _LiteralGeneric[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AllOf[_LiteralGeneric[True], _LiteralGeneric[False]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(AllOf[_LiteralGeneric[False], _LiteralGeneric[True]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(AllOf[_LiteralGeneric[False], _LiteralGeneric[False]]) + assert d == _LiteralGeneric[False] + + +def test_eval_all_02(): + d = eval_typing(AllOf[Literal[True], Literal[True]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(AllOf[Literal[True], Literal[False]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(AllOf[Literal[False], Literal[True]]) + assert d == _LiteralGeneric[False] + + +type ContainsAllInt[Ts] = AllOf[*[IsSub[t, int] for t in Iter[Ts]]] +type ContainsAllIntToLiteral[Ts] = ( + Literal[True] if Bool[ContainsAllInt[Ts]] else Literal[False] +) + + +def test_eval_all_03(): + d = eval_typing(ContainsAllInt[tuple[()]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(ContainsAllInt[tuple[int]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(ContainsAllInt[tuple[str]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(ContainsAllInt[tuple[int, int]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(ContainsAllInt[tuple[int, str]]) + assert d == _LiteralGeneric[False] + + d = eval_typing(ContainsAllInt[tuple[str, str]]) + assert d == _LiteralGeneric[False] + + +def test_eval_all_04(): + d = eval_typing(ContainsAllIntToLiteral[tuple[()]]) + assert d == Literal[True] + + d = eval_typing(ContainsAllIntToLiteral[tuple[int]]) + assert d == Literal[True] + + d = eval_typing(ContainsAllIntToLiteral[tuple[str]]) + assert d == Literal[False] + + def test_eval_any_01(): d = eval_typing(AnyOf[()]) assert d == _LiteralGeneric[False] diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 6fe255b..2912b40 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -16,6 +16,7 @@ _get_class_type_hint_namespaces, ) from typemap.typing import ( + AllOf, AnyOf, Attrs, Bool, @@ -260,6 +261,12 @@ def _eval_Bool(tp, *, ctx): return _eval_bool_tp(tp) +@type_eval.register_evaluator(AllOf) +@_lift_evaluated +def _eval_AllOf(*tp, ctx): + return _LiteralGeneric[all(_eval_bool_tp(tp) for tp in tp)] + + @type_eval.register_evaluator(AnyOf) @_lift_evaluated def _eval_AnyOf(*tp, ctx): diff --git a/typemap/typing.py b/typemap/typing.py index 952990e..70c9700 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -226,6 +226,11 @@ def Bool(self, tp): return _BoolGenericAlias(self, tp) +@_SpecialForm +def AllOf(self, tp): + return _BoolGenericAlias(self, tp) + + @_SpecialForm def AnyOf(self, tp): return _BoolGenericAlias(self, tp) From 3c639bcf4cc67fdeb950389edc8ff0a75dd1600f Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 28 Jan 2026 09:17:10 -0800 Subject: [PATCH 09/12] Add Matches. --- tests/test_type_eval.py | 94 ++++++++++++++++++++++++++++ typemap/type_eval/_eval_operators.py | 9 +++ typemap/typing.py | 5 ++ 3 files changed, 108 insertions(+) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index fb1d326..4a13b36 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -34,6 +34,7 @@ IsSub, Iter, Length, + Matches, Member, Members, NewProtocol, @@ -1000,6 +1001,99 @@ def test_never_is(): assert d == _LiteralGeneric[True] +def test_matches_01(): + d = eval_typing(Matches[int, int]) + assert d == _LiteralGeneric[True] + + d = eval_typing(Matches[int, str]) + assert d == _LiteralGeneric[False] + + d = eval_typing(Matches[str, int]) + assert d == _LiteralGeneric[False] + + +def test_matches_02(): + class A: + pass + + class B(A): + pass + + class C(B): + pass + + class D(A): + pass + + class X: + pass + + d = eval_typing(Matches[A, A]) + assert d == _LiteralGeneric[True] + + d = eval_typing(Matches[A, B]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[B, A]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[B, C]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[C, B]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[C, D]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[D, C]) + assert d == _LiteralGeneric[False] + + d = eval_typing(Matches[A, X]) + assert d == _LiteralGeneric[False] + + +def test_matches_03(): + class A[T]: + pass + + class B[T](A[T]): + pass + + class C(B[int]): + pass + + class D(A[str]): + pass + + class X: + pass + + d = eval_typing(Matches[A[int], A[int]]) + assert d == _LiteralGeneric[True] + d = eval_typing(Matches[A[int], A[str]]) + assert d == _LiteralGeneric[True] + + d = eval_typing(Matches[A[int], B[int]]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[B[int], A[int]]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[A[int], B[str]]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[B[str], A[int]]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[B[int], C]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[C, B[int]]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[B[str], C]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[C, B[str]]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[C, D]) + assert d == _LiteralGeneric[False] + d = eval_typing(Matches[D, C]) + assert d == _LiteralGeneric[False] + + d = eval_typing(Matches[A[int], X]) + assert d == _LiteralGeneric[False] + + def test_eval_iter_01(): d = eval_typing(Iter[tuple[int, str]]) assert tuple(d) == (int, str) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 2912b40..8b3e43c 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -34,6 +34,7 @@ Iter, Length, Lowercase, + Matches, Member, Members, NewProtocol, @@ -246,6 +247,14 @@ def _eval_IsSubSimilar(lhs, rhs, *, ctx): return _LiteralGeneric[type_eval.issubsimilar(lhs, rhs)] +@type_eval.register_evaluator(Matches) +@_lift_evaluated +def _eval_Matches(lhs, rhs, *, ctx): + return _LiteralGeneric[ + type_eval.issubsimilar(lhs, rhs) and type_eval.issubsimilar(rhs, lhs) + ] + + def _eval_bool_tp(tp): if _typing_inspect.is_generic_alias(tp): if tp.__origin__ is typing.Literal: diff --git a/typemap/typing.py b/typemap/typing.py index 70c9700..1db1a91 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -218,6 +218,11 @@ def IsSubSimilar(self, tps): return _BoolGenericAlias(self, tps) +@_SpecialForm +def Matches(self, tps): + return _BoolGenericAlias(self, tps) + + IsSub = IsSubSimilar From 5670dc020231dd5e1f1b86da235ac95416d3a35d Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 28 Jan 2026 15:43:01 -0800 Subject: [PATCH 10/12] Rename to _BoolLiteralGenericAlias and change base to typing._LiteralGenericAlias. --- tests/test_type_eval.py | 268 +++++++++++++-------------- typemap/type_eval/_eval_operators.py | 24 +-- typemap/type_eval/_eval_typing.py | 15 +- typemap/typing.py | 8 +- 4 files changed, 152 insertions(+), 163 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 4a13b36..6adfded 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -43,7 +43,7 @@ StrConcat, StrSlice, Uppercase, - _LiteralGeneric, + _BoolLiteral, ) from . import format_helper @@ -998,18 +998,18 @@ def test_uppercase_never(): def test_never_is(): d = eval_typing(IsSub[Never, Never]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] def test_matches_01(): d = eval_typing(Matches[int, int]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(Matches[int, str]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[str, int]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] def test_matches_02(): @@ -1029,23 +1029,23 @@ class X: pass d = eval_typing(Matches[A, A]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(Matches[A, B]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[B, A]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[B, C]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[C, B]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[C, D]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[D, C]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[A, X]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] def test_matches_03(): @@ -1065,33 +1065,33 @@ class X: pass d = eval_typing(Matches[A[int], A[int]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(Matches[A[int], A[str]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(Matches[A[int], B[int]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[B[int], A[int]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[A[int], B[str]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[B[str], A[int]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[B[int], C]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[C, B[int]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[B[str], C]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[C, B[str]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[C, D]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[D, C]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Matches[A[int], X]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] def test_eval_iter_01(): @@ -1133,38 +1133,38 @@ def test_eval_iter_02(): def test_eval_bool_01(): d = eval_typing(Bool[Literal[True]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(Bool[Literal[False]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Bool[Literal[1]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(Bool[Literal[0]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(Bool[Literal["true"]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(Bool[Literal["false"]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] def test_eval_bool_02(): - d = eval_typing(Bool[_LiteralGeneric[True]]) - assert d == _LiteralGeneric[True] + d = eval_typing(Bool[_BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing(Bool[_LiteralGeneric[False]]) - assert d == _LiteralGeneric[False] + d = eval_typing(Bool[_BoolLiteral[False]]) + assert d == _BoolLiteral[False] def test_eval_bool_03(): d = eval_typing(NotLiteralGeneric[Bool[Literal[True]]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(NotLiteralGeneric[Bool[Literal[False]]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] type NestedBool0[T] = Bool[T] @@ -1177,10 +1177,10 @@ def test_eval_bool_03(): def test_eval_bool_04(): d = eval_typing(NestedBool5[Literal[True]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(NestedBool5[Literal[False]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] type IsIntBool[T] = Bool[IsSub[T, int]] @@ -1197,36 +1197,36 @@ def test_eval_bool_05(): def test_eval_all_01(): d = eval_typing(AllOf[()]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] - d = eval_typing(AllOf[_LiteralGeneric[True]]) - assert d == _LiteralGeneric[True] + d = eval_typing(AllOf[_BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing(AllOf[_LiteralGeneric[False]]) - assert d == _LiteralGeneric[False] + d = eval_typing(AllOf[_BoolLiteral[False]]) + assert d == _BoolLiteral[False] - d = eval_typing(AllOf[_LiteralGeneric[True], _LiteralGeneric[True]]) - assert d == _LiteralGeneric[True] + d = eval_typing(AllOf[_BoolLiteral[True], _BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing(AllOf[_LiteralGeneric[True], _LiteralGeneric[False]]) - assert d == _LiteralGeneric[False] + d = eval_typing(AllOf[_BoolLiteral[True], _BoolLiteral[False]]) + assert d == _BoolLiteral[False] - d = eval_typing(AllOf[_LiteralGeneric[False], _LiteralGeneric[True]]) - assert d == _LiteralGeneric[False] + d = eval_typing(AllOf[_BoolLiteral[False], _BoolLiteral[True]]) + assert d == _BoolLiteral[False] - d = eval_typing(AllOf[_LiteralGeneric[False], _LiteralGeneric[False]]) - assert d == _LiteralGeneric[False] + d = eval_typing(AllOf[_BoolLiteral[False], _BoolLiteral[False]]) + assert d == _BoolLiteral[False] def test_eval_all_02(): d = eval_typing(AllOf[Literal[True], Literal[True]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(AllOf[Literal[True], Literal[False]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(AllOf[Literal[False], Literal[True]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] type ContainsAllInt[Ts] = AllOf[*[IsSub[t, int] for t in Iter[Ts]]] @@ -1237,22 +1237,22 @@ def test_eval_all_02(): def test_eval_all_03(): d = eval_typing(ContainsAllInt[tuple[()]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(ContainsAllInt[tuple[int]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(ContainsAllInt[tuple[str]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(ContainsAllInt[tuple[int, int]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(ContainsAllInt[tuple[int, str]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(ContainsAllInt[tuple[str, str]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] def test_eval_all_04(): @@ -1268,48 +1268,48 @@ def test_eval_all_04(): def test_eval_any_01(): d = eval_typing(AnyOf[()]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] - d = eval_typing(AnyOf[_LiteralGeneric[True]]) - assert d == _LiteralGeneric[True] + d = eval_typing(AnyOf[_BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[_LiteralGeneric[False]]) - assert d == _LiteralGeneric[False] + d = eval_typing(AnyOf[_BoolLiteral[False]]) + assert d == _BoolLiteral[False] - d = eval_typing(AnyOf[_LiteralGeneric[True], _LiteralGeneric[True]]) - assert d == _LiteralGeneric[True] + d = eval_typing(AnyOf[_BoolLiteral[True], _BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[_LiteralGeneric[True], _LiteralGeneric[False]]) - assert d == _LiteralGeneric[True] + d = eval_typing(AnyOf[_BoolLiteral[True], _BoolLiteral[False]]) + assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[_LiteralGeneric[False], _LiteralGeneric[True]]) - assert d == _LiteralGeneric[True] + d = eval_typing(AnyOf[_BoolLiteral[False], _BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[_LiteralGeneric[False], _LiteralGeneric[False]]) - assert d == _LiteralGeneric[False] + d = eval_typing(AnyOf[_BoolLiteral[False], _BoolLiteral[False]]) + assert d == _BoolLiteral[False] def test_eval_any_02(): d = eval_typing(AnyOf[()]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(AnyOf[Literal[True]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(AnyOf[Literal[False]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(AnyOf[Literal[True], Literal[True]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(AnyOf[Literal[True], Literal[False]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(AnyOf[Literal[False], Literal[True]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(AnyOf[Literal[False], Literal[False]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] type ContainsAnyInt[Ts] = AnyOf[*[IsSub[t, int] for t in Iter[Ts]]] @@ -1320,22 +1320,22 @@ def test_eval_any_02(): def test_eval_any_03(): d = eval_typing(ContainsAnyInt[tuple[()]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(ContainsAnyInt[tuple[int]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(ContainsAnyInt[tuple[str]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] d = eval_typing(ContainsAnyInt[tuple[int, int]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(ContainsAnyInt[tuple[int, str]]) - assert d == _LiteralGeneric[True] + assert d == _BoolLiteral[True] d = eval_typing(ContainsAnyInt[tuple[str, str]]) - assert d == _LiteralGeneric[False] + assert d == _BoolLiteral[False] def test_eval_any_04(): @@ -1350,93 +1350,77 @@ def test_eval_any_04(): def test_eval_literal_generic_01(): - d = eval_typing(_LiteralGeneric[True]) - assert d == _LiteralGeneric[True] + d = eval_typing(_BoolLiteral[True]) + assert d == _BoolLiteral[True] - d = eval_typing(_LiteralGeneric[False]) - assert d == _LiteralGeneric[False] + d = eval_typing(_BoolLiteral[False]) + assert d == _BoolLiteral[False] - d = eval_typing(_LiteralGeneric[1]) - assert d == _LiteralGeneric[True] + d = eval_typing(_BoolLiteral[1]) + assert d == _BoolLiteral[True] - d = eval_typing(_LiteralGeneric[0]) - assert d == _LiteralGeneric[False] + d = eval_typing(_BoolLiteral[0]) + assert d == _BoolLiteral[False] def test_eval_literal_generic_02(): - d = eval_typing(not _LiteralGeneric[True]) - assert d == _LiteralGeneric[False] + d = eval_typing(not _BoolLiteral[True]) + assert d == _BoolLiteral[False] - d = eval_typing(NotLiteralGeneric[_LiteralGeneric[True]]) - assert d == _LiteralGeneric[False] + d = eval_typing(NotLiteralGeneric[_BoolLiteral[True]]) + assert d == _BoolLiteral[False] - d = eval_typing(NotLiteralGeneric[_LiteralGeneric[False]]) - assert d == _LiteralGeneric[True] + d = eval_typing(NotLiteralGeneric[_BoolLiteral[False]]) + assert d == _BoolLiteral[True] def test_eval_literal_generic_03(): - d = eval_typing( - AndLiteralGeneric[_LiteralGeneric[True], _LiteralGeneric[True]] - ) - assert d == _LiteralGeneric[True] + d = eval_typing(AndLiteralGeneric[_BoolLiteral[True], _BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing( - AndLiteralGeneric[_LiteralGeneric[True], _LiteralGeneric[False]] - ) - assert d == _LiteralGeneric[False] + d = eval_typing(AndLiteralGeneric[_BoolLiteral[True], _BoolLiteral[False]]) + assert d == _BoolLiteral[False] - d = eval_typing( - AndLiteralGeneric[_LiteralGeneric[False], _LiteralGeneric[True]] - ) - assert d == _LiteralGeneric[False] + d = eval_typing(AndLiteralGeneric[_BoolLiteral[False], _BoolLiteral[True]]) + assert d == _BoolLiteral[False] - d = eval_typing( - AndLiteralGeneric[_LiteralGeneric[False], _LiteralGeneric[False]] - ) - assert d == _LiteralGeneric[False] + d = eval_typing(AndLiteralGeneric[_BoolLiteral[False], _BoolLiteral[False]]) + assert d == _BoolLiteral[False] def test_eval_literal_generic_04(): - d = eval_typing( - OrLiteralGeneric[_LiteralGeneric[True], _LiteralGeneric[True]] - ) - assert d == _LiteralGeneric[True] + d = eval_typing(OrLiteralGeneric[_BoolLiteral[True], _BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing( - OrLiteralGeneric[_LiteralGeneric[True], _LiteralGeneric[False]] - ) - assert d == _LiteralGeneric[True] + d = eval_typing(OrLiteralGeneric[_BoolLiteral[True], _BoolLiteral[False]]) + assert d == _BoolLiteral[True] - d = eval_typing( - OrLiteralGeneric[_LiteralGeneric[False], _LiteralGeneric[True]] - ) - assert d == _LiteralGeneric[True] + d = eval_typing(OrLiteralGeneric[_BoolLiteral[False], _BoolLiteral[True]]) + assert d == _BoolLiteral[True] - d = eval_typing( - OrLiteralGeneric[_LiteralGeneric[False], _LiteralGeneric[False]] - ) - assert d == _LiteralGeneric[False] + d = eval_typing(OrLiteralGeneric[_BoolLiteral[False], _BoolLiteral[False]]) + assert d == _BoolLiteral[False] def test_eval_literal_generic_05(): - d = eval_typing(LiteralGenericToLiteral[_LiteralGeneric[True]]) + d = eval_typing(LiteralGenericToLiteral[_BoolLiteral[True]]) assert d == Literal[True] - d = eval_typing(LiteralGenericToLiteral[_LiteralGeneric[False]]) + d = eval_typing(LiteralGenericToLiteral[_BoolLiteral[False]]) assert d == Literal[False] def test_eval_literal_generic_06(): - d = eval_typing(NotLiteralGenericToLiteral[_LiteralGeneric[True]]) + d = eval_typing(NotLiteralGenericToLiteral[_BoolLiteral[True]]) assert d == Literal[False] - d = eval_typing(NotLiteralGenericToLiteral[_LiteralGeneric[False]]) + d = eval_typing(NotLiteralGenericToLiteral[_BoolLiteral[False]]) assert d == Literal[True] def test_eval_literal_generic_error_01(): with pytest.raises(TypeError, match="Expected literal type, got 'int'"): - eval_typing(_LiteralGeneric[int]) + eval_typing(_BoolLiteral[int]) def test_eval_length_01(): @@ -1465,9 +1449,7 @@ def test_eval_literal_idempotent_01(): def test_is_literal_true_vs_one(): - assert ( - eval_typing(IsSub[Literal[True], Literal[1]]) == _LiteralGeneric[False] - ) + assert eval_typing(IsSub[Literal[True], Literal[1]]) == _BoolLiteral[False] def test_callable_to_signature_01(): @@ -1635,7 +1617,7 @@ class AnnoTest: def test_type_eval_annotated_02(): res = eval_typing(IsSub[GetAttr[AnnoTest, Literal["a"]], int]) - assert res == _LiteralGeneric[True] + assert res == _BoolLiteral[True] def test_type_eval_annotated_03(): diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 8b3e43c..d074578 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -44,7 +44,7 @@ StrSlice, Uncapitalize, Uppercase, - _LiteralGeneric, + _BoolLiteral, ) ################################################################## @@ -238,19 +238,19 @@ def _eval_Iter(tp, *, ctx): @type_eval.register_evaluator(IsSubtype) @_lift_evaluated def _eval_IsSubtype(lhs, rhs, *, ctx): - return _LiteralGeneric[type_eval.issubtype(lhs, rhs)] + return _BoolLiteral[type_eval.issubtype(lhs, rhs)] @type_eval.register_evaluator(IsSubSimilar) @_lift_evaluated def _eval_IsSubSimilar(lhs, rhs, *, ctx): - return _LiteralGeneric[type_eval.issubsimilar(lhs, rhs)] + return _BoolLiteral[type_eval.issubsimilar(lhs, rhs)] @type_eval.register_evaluator(Matches) @_lift_evaluated def _eval_Matches(lhs, rhs, *, ctx): - return _LiteralGeneric[ + return _BoolLiteral[ type_eval.issubsimilar(lhs, rhs) and type_eval.issubsimilar(rhs, lhs) ] @@ -258,9 +258,9 @@ def _eval_Matches(lhs, rhs, *, ctx): def _eval_bool_tp(tp): if _typing_inspect.is_generic_alias(tp): if tp.__origin__ is typing.Literal: - return _LiteralGeneric[bool(tp.__args__[0])] - elif tp.__origin__ is _LiteralGeneric: - return _LiteralGeneric[bool(tp.__args__[0])] + return _BoolLiteral[bool(tp.__args__[0])] + elif tp.__origin__ is _BoolLiteral: + return _BoolLiteral[bool(tp.__args__[0])] raise TypeError(f"Expected Literal type, got {tp}") @@ -273,21 +273,21 @@ def _eval_Bool(tp, *, ctx): @type_eval.register_evaluator(AllOf) @_lift_evaluated def _eval_AllOf(*tp, ctx): - return _LiteralGeneric[all(_eval_bool_tp(tp) for tp in tp)] + return _BoolLiteral[all(_eval_bool_tp(tp) for tp in tp)] @type_eval.register_evaluator(AnyOf) @_lift_evaluated def _eval_AnyOf(*tp, ctx): - return _LiteralGeneric[any(_eval_bool_tp(tp) for tp in tp)] + return _BoolLiteral[any(_eval_bool_tp(tp) for tp in tp)] -@type_eval.register_evaluator(_LiteralGeneric) +@type_eval.register_evaluator(_BoolLiteral) @_lift_evaluated -def _eval_LiteralGeneric(tp, *, ctx): +def _eval_BoolLiteral(tp, *, ctx): if isinstance(tp, type): raise TypeError(f"Expected literal type, got '{tp.__name__}'") - return _LiteralGeneric[tp] + return _BoolLiteral[tp] ################################################################## diff --git a/typemap/type_eval/_eval_typing.py b/typemap/type_eval/_eval_typing.py index ee8c53d..888b0da 100644 --- a/typemap/type_eval/_eval_typing.py +++ b/typemap/type_eval/_eval_typing.py @@ -184,13 +184,13 @@ def eval_typing(obj: typing.Any): result = ctx.known_recursive_types[result] if isinstance(result, bool): - # Wrap a boolean result with _LiteralGeneric + # Wrap a boolean result with _BoolLiteral # This is because `not` calls `__bool__` first so a boolean - # expression like `not _LiteralGeneric[True]` will result `False`, - # not `_LiteralGeneric[False]` as we want. + # expression like `not _BoolLiteral[True]` will result `False`, + # not `_BoolLiteral[False]` as we want. import typemap.typing as nt - result = nt._LiteralGeneric[result] + result = nt._BoolLiteral[result] return result @@ -347,6 +347,13 @@ def _eval_type_var(obj: typing.TypeVar, ctx: EvalContext): # do there, and doing it puts weird stuff in the caches. @_eval_types_impl.register def _eval_literal(obj: typing_LiteralGenericAlias, ctx: EvalContext): + from typemap.typing import _BoolLiteralGenericAlias + + if isinstance(obj, _BoolLiteralGenericAlias): + # If this is _BoolLiteralGenericAlias, defer to the registered evaluator + if func := _eval_funcs.get(obj.__origin__): + return func(*typing.get_args(obj), ctx=ctx) + return obj diff --git a/typemap/typing.py b/typemap/typing.py index 1db1a91..51e3955 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -1,7 +1,7 @@ import contextvars import typing from typing import Literal -from typing import _GenericAlias # type: ignore +from typing import _GenericAlias, _LiteralGenericAlias # type: ignore _SpecialForm: typing.Any = typing._SpecialForm @@ -241,11 +241,11 @@ def AnyOf(self, tp): return _BoolGenericAlias(self, tp) -class _LiteralGenericAlias(_GenericAlias, _root=True): # type: ignore[call-arg] +class _BoolLiteralGenericAlias(_LiteralGenericAlias, _root=True): # type: ignore[call-arg] def __bool__(self): return typing.get_args(self)[0] @_SpecialForm -def _LiteralGeneric(self, tp): - return _LiteralGenericAlias(self, tp) +def _BoolLiteral(self, tp): + return _BoolLiteralGenericAlias(self, tp) From bb961c5ddd85b681557a202cf070b6b246c40e3a Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 28 Jan 2026 15:58:00 -0800 Subject: [PATCH 11/12] Wrap internal evals that return bool. --- tests/test_type_eval.py | 7 +++++++ typemap/type_eval/_eval_operators.py | 7 +++++++ typemap/type_eval/_eval_typing.py | 19 +++++++++---------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 6adfded..597e1ad 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -1001,6 +1001,13 @@ def test_never_is(): assert d == _BoolLiteral[True] +def test_eval_list_is_sub_01(): + d = eval_typing(list[IsSub[int, str]]) + assert d == list[_BoolLiteral[False]] + d = eval_typing(list[not IsSub[int, str]]) + assert d == list[_BoolLiteral[True]] + + def test_matches_01(): d = eval_typing(Matches[int, int]) assert d == _BoolLiteral[True] diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index d074578..b3c94fa 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -285,8 +285,15 @@ def _eval_AnyOf(*tp, ctx): @type_eval.register_evaluator(_BoolLiteral) @_lift_evaluated def _eval_BoolLiteral(tp, *, ctx): + from typemap.typing import _BoolLiteralGenericAlias + if isinstance(tp, type): raise TypeError(f"Expected literal type, got '{tp.__name__}'") + + # If already wrapped, just return it + if isinstance(tp, _BoolLiteralGenericAlias): + return tp + return _BoolLiteral[tp] diff --git a/typemap/type_eval/_eval_typing.py b/typemap/type_eval/_eval_typing.py index 888b0da..29efd8b 100644 --- a/typemap/type_eval/_eval_typing.py +++ b/typemap/type_eval/_eval_typing.py @@ -182,16 +182,6 @@ def eval_typing(obj: typing.Any): result = _eval_types(obj, ctx) if not isinstance(result, list) and result in ctx.known_recursive_types: result = ctx.known_recursive_types[result] - - if isinstance(result, bool): - # Wrap a boolean result with _BoolLiteral - # This is because `not` calls `__bool__` first so a boolean - # expression like `not _BoolLiteral[True]` will result `False`, - # not `_BoolLiteral[False]` as we want. - import typemap.typing as nt - - result = nt._BoolLiteral[result] - return result @@ -257,6 +247,15 @@ def _eval_types(obj: typing.Any, ctx: EvalContext): ctx.resolved |= {x: x for x in child_ctx.known_recursive_types.keys()} ctx.known_recursive_types |= child_ctx.known_recursive_types + if isinstance(evaled, bool): + # Wrap a boolean result with _BoolLiteral + # This is because `not` calls `__bool__` first so a boolean + # expression like `not _BoolLiteral[True]` will result `False`, + # not `_BoolLiteral[False]` as we want. + import typemap.typing as nt + + evaled = nt._BoolLiteral[evaled] + # Don't cache iterators as they are stateful and can only be consumed once. # This is important for Iter results that may be used multiple times. if not isinstance(evaled, collections.abc.Iterator): From 04cae664df5166b5f5eb1434928c0edea750eee3 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 28 Jan 2026 17:28:57 -0800 Subject: [PATCH 12/12] Properly handle unions. --- tests/test_type_eval.py | 123 ++++++++++++++++++--------- typemap/type_eval/_eval_operators.py | 24 +++--- 2 files changed, 98 insertions(+), 49 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 597e1ad..759b525 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -1146,25 +1146,66 @@ def test_eval_bool_01(): assert d == _BoolLiteral[False] d = eval_typing(Bool[Literal[1]]) - assert d == _BoolLiteral[True] + assert d == _BoolLiteral[False] d = eval_typing(Bool[Literal[0]]) assert d == _BoolLiteral[False] d = eval_typing(Bool[Literal["true"]]) - assert d == _BoolLiteral[True] + assert d == _BoolLiteral[False] d = eval_typing(Bool[Literal["false"]]) - assert d == _BoolLiteral[True] - + assert d == _BoolLiteral[False] -def test_eval_bool_02(): d = eval_typing(Bool[_BoolLiteral[True]]) assert d == _BoolLiteral[True] d = eval_typing(Bool[_BoolLiteral[False]]) assert d == _BoolLiteral[False] + d = eval_typing(Bool[Never]) + assert d == _BoolLiteral[False] + + d = eval_typing(Bool[int]) + assert d == _BoolLiteral[False] + + class C: + pass + + d = eval_typing(Bool[C]) + assert d == _BoolLiteral[False] + + d = eval_typing(Bool[True]) + assert d == _BoolLiteral[True] + + d = eval_typing(Bool[False]) + assert d == _BoolLiteral[False] + + +def test_eval_bool_02(): + d = eval_typing(Bool[Literal[True] | Literal[False]]) + assert d == _BoolLiteral[True] + d = eval_typing(Bool[Literal[False] | Literal[True]]) + assert d == _BoolLiteral[True] + d = eval_typing(Bool[Literal[True] | Never]) + assert d == _BoolLiteral[True] + d = eval_typing(Bool[Never | Literal[True]]) + assert d == _BoolLiteral[True] + d = eval_typing(Bool[Literal[False] | Never]) + assert d == _BoolLiteral[False] + d = eval_typing(Bool[Never | Literal[False]]) + assert d == _BoolLiteral[False] + d = eval_typing(Bool[Literal[True] | int]) + assert d == _BoolLiteral[True] + d = eval_typing(Bool[int | Literal[True]]) + assert d == _BoolLiteral[True] + d = eval_typing(Bool[Literal[False] | int]) + assert d == _BoolLiteral[False] + d = eval_typing(Bool[int | Literal[False]]) + assert d == _BoolLiteral[False] + d = eval_typing(Bool[int | str]) + assert d == _BoolLiteral[False] + def test_eval_bool_03(): d = eval_typing(NotLiteralGeneric[Bool[Literal[True]]]) @@ -1208,30 +1249,45 @@ def test_eval_all_01(): d = eval_typing(AllOf[_BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AllOf[_BoolLiteral[False]]) assert d == _BoolLiteral[False] d = eval_typing(AllOf[_BoolLiteral[True], _BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AllOf[_BoolLiteral[True], _BoolLiteral[False]]) assert d == _BoolLiteral[False] - d = eval_typing(AllOf[_BoolLiteral[False], _BoolLiteral[True]]) assert d == _BoolLiteral[False] - d = eval_typing(AllOf[_BoolLiteral[False], _BoolLiteral[False]]) assert d == _BoolLiteral[False] + d = eval_typing(AllOf[Literal[True] | Literal[False]]) + assert d == _BoolLiteral[True] + d = eval_typing(AllOf[int | Never]) + assert d == _BoolLiteral[False] + d = eval_typing( + AllOf[Literal[0] | Literal[True], Literal[2] | Literal[True]] + ) + assert d == _BoolLiteral[True] + d = eval_typing(AllOf[Literal[0] | Literal[1], Literal[2] | Literal[True]]) + assert d == _BoolLiteral[False] + d = eval_typing(AllOf[Literal[0] | Literal[1], Literal[2] | Literal[3]]) + assert d == _BoolLiteral[False] + def test_eval_all_02(): - d = eval_typing(AllOf[Literal[True], Literal[True]]) + d = eval_typing(AllOf[()]) assert d == _BoolLiteral[True] - d = eval_typing(AllOf[Literal[True], Literal[False]]) + d = eval_typing(AllOf[Literal[True]]) + assert d == _BoolLiteral[True] + d = eval_typing(AllOf[Literal[False]]) assert d == _BoolLiteral[False] + d = eval_typing(AllOf[Literal[True], Literal[True]]) + assert d == _BoolLiteral[True] + d = eval_typing(AllOf[Literal[True], Literal[False]]) + assert d == _BoolLiteral[False] d = eval_typing(AllOf[Literal[False], Literal[True]]) assert d == _BoolLiteral[False] @@ -1248,16 +1304,13 @@ def test_eval_all_03(): d = eval_typing(ContainsAllInt[tuple[int]]) assert d == _BoolLiteral[True] - d = eval_typing(ContainsAllInt[tuple[str]]) assert d == _BoolLiteral[False] d = eval_typing(ContainsAllInt[tuple[int, int]]) assert d == _BoolLiteral[True] - d = eval_typing(ContainsAllInt[tuple[int, str]]) assert d == _BoolLiteral[False] - d = eval_typing(ContainsAllInt[tuple[str, str]]) assert d == _BoolLiteral[False] @@ -1268,7 +1321,6 @@ def test_eval_all_04(): d = eval_typing(ContainsAllIntToLiteral[tuple[int]]) assert d == Literal[True] - d = eval_typing(ContainsAllIntToLiteral[tuple[str]]) assert d == Literal[False] @@ -1279,22 +1331,31 @@ def test_eval_any_01(): d = eval_typing(AnyOf[_BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[_BoolLiteral[False]]) assert d == _BoolLiteral[False] d = eval_typing(AnyOf[_BoolLiteral[True], _BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[_BoolLiteral[True], _BoolLiteral[False]]) assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[_BoolLiteral[False], _BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[_BoolLiteral[False], _BoolLiteral[False]]) assert d == _BoolLiteral[False] + d = eval_typing(AnyOf[Literal[True] | Literal[False]]) + assert d == _BoolLiteral[True] + d = eval_typing(AnyOf[int | Never]) + assert d == _BoolLiteral[False] + d = eval_typing( + AnyOf[Literal[0] | Literal[True], Literal[2] | Literal[True]] + ) + assert d == _BoolLiteral[True] + d = eval_typing(AnyOf[Literal[0] | Literal[1], Literal[2] | Literal[True]]) + assert d == _BoolLiteral[True] + d = eval_typing(AnyOf[Literal[0] | Literal[1], Literal[2] | Literal[3]]) + assert d == _BoolLiteral[False] + def test_eval_any_02(): d = eval_typing(AnyOf[()]) @@ -1302,19 +1363,15 @@ def test_eval_any_02(): d = eval_typing(AnyOf[Literal[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[Literal[False]]) assert d == _BoolLiteral[False] d = eval_typing(AnyOf[Literal[True], Literal[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[Literal[True], Literal[False]]) assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[Literal[False], Literal[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AnyOf[Literal[False], Literal[False]]) assert d == _BoolLiteral[False] @@ -1331,16 +1388,13 @@ def test_eval_any_03(): d = eval_typing(ContainsAnyInt[tuple[int]]) assert d == _BoolLiteral[True] - d = eval_typing(ContainsAnyInt[tuple[str]]) assert d == _BoolLiteral[False] d = eval_typing(ContainsAnyInt[tuple[int, int]]) assert d == _BoolLiteral[True] - d = eval_typing(ContainsAnyInt[tuple[int, str]]) assert d == _BoolLiteral[True] - d = eval_typing(ContainsAnyInt[tuple[str, str]]) assert d == _BoolLiteral[False] @@ -1351,7 +1405,6 @@ def test_eval_any_04(): d = eval_typing(ContainsAnyIntToLiteral[tuple[int]]) assert d == Literal[True] - d = eval_typing(ContainsAnyIntToLiteral[tuple[str]]) assert d == Literal[False] @@ -1359,15 +1412,16 @@ def test_eval_any_04(): def test_eval_literal_generic_01(): d = eval_typing(_BoolLiteral[True]) assert d == _BoolLiteral[True] - d = eval_typing(_BoolLiteral[False]) assert d == _BoolLiteral[False] - d = eval_typing(_BoolLiteral[1]) assert d == _BoolLiteral[True] - d = eval_typing(_BoolLiteral[0]) assert d == _BoolLiteral[False] + d = eval_typing(_BoolLiteral[_BoolLiteral[True]]) + assert d == _BoolLiteral[True] + d = eval_typing(_BoolLiteral[_BoolLiteral[False]]) + assert d == _BoolLiteral[False] def test_eval_literal_generic_02(): @@ -1376,7 +1430,6 @@ def test_eval_literal_generic_02(): d = eval_typing(NotLiteralGeneric[_BoolLiteral[True]]) assert d == _BoolLiteral[False] - d = eval_typing(NotLiteralGeneric[_BoolLiteral[False]]) assert d == _BoolLiteral[True] @@ -1384,13 +1437,10 @@ def test_eval_literal_generic_02(): def test_eval_literal_generic_03(): d = eval_typing(AndLiteralGeneric[_BoolLiteral[True], _BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(AndLiteralGeneric[_BoolLiteral[True], _BoolLiteral[False]]) assert d == _BoolLiteral[False] - d = eval_typing(AndLiteralGeneric[_BoolLiteral[False], _BoolLiteral[True]]) assert d == _BoolLiteral[False] - d = eval_typing(AndLiteralGeneric[_BoolLiteral[False], _BoolLiteral[False]]) assert d == _BoolLiteral[False] @@ -1398,13 +1448,10 @@ def test_eval_literal_generic_03(): def test_eval_literal_generic_04(): d = eval_typing(OrLiteralGeneric[_BoolLiteral[True], _BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(OrLiteralGeneric[_BoolLiteral[True], _BoolLiteral[False]]) assert d == _BoolLiteral[True] - d = eval_typing(OrLiteralGeneric[_BoolLiteral[False], _BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(OrLiteralGeneric[_BoolLiteral[False], _BoolLiteral[False]]) assert d == _BoolLiteral[False] @@ -1412,7 +1459,6 @@ def test_eval_literal_generic_04(): def test_eval_literal_generic_05(): d = eval_typing(LiteralGenericToLiteral[_BoolLiteral[True]]) assert d == Literal[True] - d = eval_typing(LiteralGenericToLiteral[_BoolLiteral[False]]) assert d == Literal[False] @@ -1420,7 +1466,6 @@ def test_eval_literal_generic_05(): def test_eval_literal_generic_06(): d = eval_typing(NotLiteralGenericToLiteral[_BoolLiteral[True]]) assert d == Literal[False] - d = eval_typing(NotLiteralGenericToLiteral[_BoolLiteral[False]]) assert d == Literal[True] diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index b3c94fa..e9c9301 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -255,31 +255,35 @@ def _eval_Matches(lhs, rhs, *, ctx): ] -def _eval_bool_tp(tp): - if _typing_inspect.is_generic_alias(tp): - if tp.__origin__ is typing.Literal: - return _BoolLiteral[bool(tp.__args__[0])] - elif tp.__origin__ is _BoolLiteral: - return _BoolLiteral[bool(tp.__args__[0])] - raise TypeError(f"Expected Literal type, got {tp}") +def _eval_bool_tp(tp, ctx): + if _typing_inspect.is_generic_alias(tp) and tp.__origin__ is _BoolLiteral: + return _BoolLiteral[bool(tp.__args__[0])] + else: + return _BoolLiteral[ + any( + type_eval.issubsimilar(arg, typing.Literal[True]) + and not type_eval.issubsimilar(arg, typing.Never) + for arg in _union_elems(tp, ctx) + ) + ] @type_eval.register_evaluator(Bool) @_lift_evaluated def _eval_Bool(tp, *, ctx): - return _eval_bool_tp(tp) + return _eval_bool_tp(tp, ctx) @type_eval.register_evaluator(AllOf) @_lift_evaluated def _eval_AllOf(*tp, ctx): - return _BoolLiteral[all(_eval_bool_tp(tp) for tp in tp)] + return _BoolLiteral[all(_eval_bool_tp(tp, ctx) for tp in tp)] @type_eval.register_evaluator(AnyOf) @_lift_evaluated def _eval_AnyOf(*tp, ctx): - return _BoolLiteral[any(_eval_bool_tp(tp) for tp in tp)] + return _BoolLiteral[any(_eval_bool_tp(tp, ctx) for tp in tp)] @type_eval.register_evaluator(_BoolLiteral)