From 9265d6eab7ad2772c422118bcc0b885633077ea2 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Fri, 30 Jan 2026 14:06:56 -0800 Subject: [PATCH] Implement RaiseError --- pep.rst | 4 +++- tests/test_fastapilike_1.py | 2 +- tests/test_type_eval.py | 26 ++++++++++++++++++++++++++ typemap/type_eval/__init__.py | 3 +++ typemap/type_eval/_eval_operators.py | 25 +++++++++++++++++++++++++ typemap/typing.py | 13 +++++++++++++ 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/pep.rst b/pep.rst index 559ac6c..ac22bcf 100644 --- a/pep.rst +++ b/pep.rst @@ -778,10 +778,12 @@ All of the operators in this section are :ref:`lifted over union types Raise error ''''''''''' -* ``RaiseError[S: Literal[str]]``: If this type needs to be evaluated +* ``RaiseError[S: Literal[str], *Ts]``: If this type needs to be evaluated to determine some actual type, generate a type error with the provided message. + Any additional type arguments should be included in the message. + Update class '''''''''''' diff --git a/tests/test_fastapilike_1.py b/tests/test_fastapilike_1.py index 3b79a7f..fbb2172 100644 --- a/tests/test_fastapilike_1.py +++ b/tests/test_fastapilike_1.py @@ -78,7 +78,7 @@ class _Default: type AddInit[T] = NewProtocol[ InitFnType[T], - *Members[T]], + *Members[T], ] but we struggle here because typing wants to unpack the Members tuple diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 9b239d6..2833b9f 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -1565,3 +1565,29 @@ def test_type_eval_annotated_03(): def test_type_eval_annotated_04(): res = eval_typing(GetAnnotations[GetMemberType[AnnoTest, Literal["b"]]]) assert res == Literal["blah"] + + +############## +# RaiseError tests + +from typemap.typing import RaiseError +from typemap.type_eval import TypeMapError + + +def test_raise_error_basic(): + with pytest.raises(TypeMapError, match="Test error message"): + eval_typing(RaiseError[Literal["Test error message"]]) + + +def test_raise_error_with_types(): + with pytest.raises(TypeMapError, match="Broadcast mismatch.*int.*str"): + eval_typing(RaiseError[Literal["Broadcast mismatch"], int, str]) + + +def test_raise_error_with_literal_types(): + with pytest.raises( + TypeMapError, match="Shape mismatch.*Literal\\[4\\].*Literal\\[3\\]" + ): + eval_typing( + RaiseError[Literal["Shape mismatch"], Literal[4], Literal[3]] + ) diff --git a/typemap/type_eval/__init__.py b/typemap/type_eval/__init__.py index 3d2a217..3c1484e 100644 --- a/typemap/type_eval/__init__.py +++ b/typemap/type_eval/__init__.py @@ -14,6 +14,8 @@ # This one is imported for registering handlers from . import _eval_operators # noqa +from ._eval_operators import TypeMapError + __all__ = ( "eval_typing", @@ -23,6 +25,7 @@ "flatten_class", "issubtype", "issubsimilar", + "TypeMapError", "_EvalProxy", "_get_current_context", ) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index df2ac67..cab2d2e 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -38,6 +38,7 @@ Members, NewProtocol, Param, + RaiseError, Slice, SpecialFormEllipsis, StrConcat, @@ -997,6 +998,30 @@ def func(*args, ctx): ################################################################## +class TypeMapError(TypeError): + """Exception raised when RaiseError is evaluated.""" + + pass + + +@type_eval.register_evaluator(RaiseError) +@_lift_evaluated +def _eval_RaiseError(msg, *extra_types, ctx): + """Evaluate RaiseError by raising a TypeMapError. + + RaiseError[S, *Ts] raises a type error with message S, + including the string representations of any extra type arguments. + """ + message = _from_literal(msg) + if extra_types: + type_strs = ", ".join(repr(t) for t in extra_types) + message = f"{message}: {type_strs}" + raise TypeMapError(message) + + +################################################################## + + def _add_quals(typ, quals): for qual in (typing.ClassVar, typing.Final): if type_eval.issubsimilar(typing.Literal[qual.__name__], quals): diff --git a/typemap/typing.py b/typemap/typing.py index 9db378d..ddca390 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -179,6 +179,19 @@ class NewProtocol[*T]: pass +class RaiseError[S: str, *Ts]: + """Raise a type error with the given message when evaluated. + + RaiseError[S: Literal[str], *Ts]: If this type needs to be evaluated + to determine some actual type, generate a type error with the + provided message. + + Any additional type arguments should be included in the message. + """ + + pass + + ################################################################## # TODO: type better