From ebdc6459f49a7b24d7570f4adb71d2445cd2d7e6 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 29 Jan 2026 10:00:15 -0800 Subject: [PATCH 1/4] Add TypeName. --- tests/test_type_eval.py | 21 +++++++++++++++++++++ typemap/type_eval/_eval_operators.py | 7 +++++++ typemap/typing.py | 4 ++++ 3 files changed, 32 insertions(+) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 9b239d6..e9d2e92 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -41,6 +41,7 @@ Slice, SpecialFormEllipsis, StrConcat, + TypeName, Uppercase, _BoolLiteral, ) @@ -990,6 +991,26 @@ class Container2[T]: ... assert eval_typing(GetArg[t, Container, Literal[1]]) == Never +def test_eval_typename_01(): + d = eval_typing(TypeName[int]) + assert d == Literal["int"] + d = eval_typing(TypeName[str]) + assert d == Literal["str"] + d = eval_typing(TypeName[list[int]]) + assert d == Literal["list"] + d = eval_typing(TypeName[list[str]]) + assert d == Literal["list"] + + class C: + pass + + d = eval_typing(TypeName[C]) + assert d == Literal["C"] + + d = eval_typing(TypeName[GetA1[int, str]]) + assert d == Literal["int"] + + type _Works[Ts, I] = Literal[True] type Works[Ts] = _Works[Ts, Length[Ts]] diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index df2ac67..b15021f 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -41,6 +41,7 @@ Slice, SpecialFormEllipsis, StrConcat, + TypeName, Uncapitalize, Uppercase, _BoolLiteral, @@ -923,6 +924,12 @@ def _eval_GetArgs(tp, base, *, ctx) -> typing.Any: return tuple[*args] # type: ignore[valid-type] +@type_eval.register_evaluator(TypeName) +@_lift_over_unions +def _eval_TypeName(tp, *, ctx) -> typing.Any: + return typing.Literal[tp.__name__] + + @type_eval.register_evaluator(GetAnnotations) def _eval_GetAnnotations(tp, *, ctx) -> typing.Any: tp = _eval_types(tp, ctx=ctx) diff --git a/typemap/typing.py b/typemap/typing.py index 9db378d..39c09fc 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -147,6 +147,10 @@ class GetArgs[Tp, Base]: pass +class TypeName[T: type]: + pass + + class Length[S: tuple]: pass From 6893b16195dca81fbb1dc167dc63368125e386ef Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 29 Jan 2026 11:47:13 -0800 Subject: [PATCH 2/4] Implement as GetSpecialAttr. --- tests/test_type_eval.py | 73 +++++++++++++++++++++++++--- typemap/type_eval/_eval_operators.py | 27 ++++++++-- typemap/typing.py | 2 +- 3 files changed, 90 insertions(+), 12 deletions(-) diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index e9d2e92..44e3b1b 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -28,6 +28,7 @@ GetMember, GetMemberType, GetName, + GetSpecialAttr, GetType, GetAnnotations, IsSub, @@ -41,7 +42,6 @@ Slice, SpecialFormEllipsis, StrConcat, - TypeName, Uppercase, _BoolLiteral, ) @@ -991,23 +991,82 @@ class Container2[T]: ... assert eval_typing(GetArg[t, Container, Literal[1]]) == Never +class OuterType: + class InnerType: + pass + + def test_eval_typename_01(): - d = eval_typing(TypeName[int]) + d = eval_typing(GetSpecialAttr[int, Literal["__name__"]]) assert d == Literal["int"] - d = eval_typing(TypeName[str]) + d = eval_typing(GetSpecialAttr[str, Literal["__name__"]]) assert d == Literal["str"] - d = eval_typing(TypeName[list[int]]) + d = eval_typing(GetSpecialAttr[list[int], Literal["__name__"]]) assert d == Literal["list"] - d = eval_typing(TypeName[list[str]]) + d = eval_typing(GetSpecialAttr[list[str], Literal["__name__"]]) assert d == Literal["list"] class C: pass - d = eval_typing(TypeName[C]) + d = eval_typing(GetSpecialAttr[OuterType, Literal["__name__"]]) + assert d == Literal["OuterType"] + d = eval_typing(GetSpecialAttr[OuterType.InnerType, Literal["__name__"]]) + assert d == Literal["InnerType"] + d = eval_typing(GetSpecialAttr[C, Literal["__name__"]]) assert d == Literal["C"] - d = eval_typing(TypeName[GetA1[int, str]]) + d = eval_typing(GetSpecialAttr[GetA1[int, str], Literal["__name__"]]) + assert d == Literal["int"] + + +def test_eval_typename_02(): + d = eval_typing(GetSpecialAttr[int, Literal["__module__"]]) + assert d == Literal["builtins"] + d = eval_typing(GetSpecialAttr[str, Literal["__module__"]]) + assert d == Literal["builtins"] + d = eval_typing(GetSpecialAttr[list[int], Literal["__module__"]]) + assert d == Literal["builtins"] + d = eval_typing(GetSpecialAttr[list[str], Literal["__module__"]]) + assert d == Literal["builtins"] + + class C: + pass + + d = eval_typing(GetSpecialAttr[OuterType, Literal["__module__"]]) + assert d == Literal["tests.test_type_eval"] + d = eval_typing(GetSpecialAttr[OuterType.InnerType, Literal["__module__"]]) + assert d == Literal["tests.test_type_eval"] + d = eval_typing(GetSpecialAttr[C, Literal["__module__"]]) + assert d == Literal["tests.test_type_eval"] + + d = eval_typing(GetSpecialAttr[GetA1[int, str], Literal["__module__"]]) + assert d == Literal["builtins"] + + +def test_eval_typename_03(): + d = eval_typing(GetSpecialAttr[int, Literal["__qualname__"]]) + assert d == Literal["int"] + d = eval_typing(GetSpecialAttr[str, Literal["__qualname__"]]) + assert d == Literal["str"] + d = eval_typing(GetSpecialAttr[list[int], Literal["__qualname__"]]) + assert d == Literal["list"] + d = eval_typing(GetSpecialAttr[list[str], Literal["__qualname__"]]) + assert d == Literal["list"] + + class C: + pass + + d = eval_typing(GetSpecialAttr[OuterType, Literal["__qualname__"]]) + assert d == Literal["OuterType"] + d = eval_typing( + GetSpecialAttr[OuterType.InnerType, Literal["__qualname__"]] + ) + assert d == Literal["OuterType.InnerType"] + d = eval_typing(GetSpecialAttr[C, Literal["__qualname__"]]) + assert d == Literal["test_eval_typename_03..C"] + + d = eval_typing(GetSpecialAttr[GetA1[int, str], Literal["__qualname__"]]) assert d == Literal["int"] diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index b15021f..7a0835f 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -27,6 +27,7 @@ GetArgs, GetMember, GetMemberType, + GetSpecialAttr, InitField, IsSubSimilar, IsSubtype, @@ -41,7 +42,6 @@ Slice, SpecialFormEllipsis, StrConcat, - TypeName, Uncapitalize, Uppercase, _BoolLiteral, @@ -924,10 +924,29 @@ def _eval_GetArgs(tp, base, *, ctx) -> typing.Any: return tuple[*args] # type: ignore[valid-type] -@type_eval.register_evaluator(TypeName) +@type_eval.register_evaluator(GetSpecialAttr) @_lift_over_unions -def _eval_TypeName(tp, *, ctx) -> typing.Any: - return typing.Literal[tp.__name__] +def _eval_GetSpecialAttr(tp, attr, *, ctx) -> typing.Any: + if not ( + _typing_inspect.is_generic_alias(attr) + and attr.__origin__ is typing.Literal + and isinstance(attr.__args__[0], str) + ): + raise TypeError( + f"Invalid type argument to GetSpecialAttr: " + f"{attr} is not a string Literal" + ) + if attr.__args__[0] == "__name__": + return typing.Literal[tp.__name__] + elif attr.__args__[0] == "__module__": + return typing.Literal[tp.__module__] + elif attr.__args__[0] == "__qualname__": + return typing.Literal[tp.__qualname__] + else: + raise TypeError( + f"Invalid type argument to GetSpecialAttr: " + f"{attr} must be one of __name__, __module__, or __qualname__" + ) @type_eval.register_evaluator(GetAnnotations) diff --git a/typemap/typing.py b/typemap/typing.py index 39c09fc..3fe980d 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -147,7 +147,7 @@ class GetArgs[Tp, Base]: pass -class TypeName[T: type]: +class GetSpecialAttr[T: type, Attr: str]: pass From 06cf0ed1680111b8def490a427ebb44871a24b29 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 29 Jan 2026 11:56:33 -0800 Subject: [PATCH 3/4] Update pep. --- pep.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pep.rst b/pep.rst index 559ac6c..30f1e2f 100644 --- a/pep.rst +++ b/pep.rst @@ -580,6 +580,13 @@ Basic operators * ``GetMemberType[T, S: Literal[str]]``: Extract the type of the member named ``S`` from the class ``T``. + +* ``GetSpecialAttr[T: type, Attr: Literal[str]]``: Extract the value + of special attribute named ``Attr`` from the class ``T``. Valid + attributes are ``__name__``, ``__module__``, and ``__qualname__``. + Returns the value as a ``Literal[str]``. + + * ``Length[T: tuple]`` - get the length of a tuple as an int literal (or ``Literal[None]`` if it is unbounded) From 4b8001f4a78d1e31b8fa4c6264ff1b65b102f5df Mon Sep 17 00:00:00 2001 From: dnwpark Date: Fri, 30 Jan 2026 10:46:16 -0800 Subject: [PATCH 4/4] Never instead of exception. --- typemap/type_eval/_eval_operators.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 7a0835f..38f4b1a 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -943,10 +943,7 @@ def _eval_GetSpecialAttr(tp, attr, *, ctx) -> typing.Any: elif attr.__args__[0] == "__qualname__": return typing.Literal[tp.__qualname__] else: - raise TypeError( - f"Invalid type argument to GetSpecialAttr: " - f"{attr} must be one of __name__, __module__, or __qualname__" - ) + return typing.Never @type_eval.register_evaluator(GetAnnotations)