Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
80 changes: 80 additions & 0 deletions tests/test_type_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
GetMember,
GetMemberType,
GetName,
GetSpecialAttr,
GetType,
GetAnnotations,
IsSub,
Expand Down Expand Up @@ -990,6 +991,85 @@ 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(GetSpecialAttr[int, Literal["__name__"]])
assert d == Literal["int"]
d = eval_typing(GetSpecialAttr[str, Literal["__name__"]])
assert d == Literal["str"]
d = eval_typing(GetSpecialAttr[list[int], Literal["__name__"]])
assert d == Literal["list"]
d = eval_typing(GetSpecialAttr[list[str], Literal["__name__"]])
assert d == Literal["list"]

class C:
pass

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(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.<locals>.C"]

d = eval_typing(GetSpecialAttr[GetA1[int, str], Literal["__qualname__"]])
assert d == Literal["int"]


type _Works[Ts, I] = Literal[True]
type Works[Ts] = _Works[Ts, Length[Ts]]

Expand Down
23 changes: 23 additions & 0 deletions typemap/type_eval/_eval_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
GetArgs,
GetMember,
GetMemberType,
GetSpecialAttr,
InitField,
IsSubSimilar,
IsSubtype,
Expand Down Expand Up @@ -923,6 +924,28 @@ def _eval_GetArgs(tp, base, *, ctx) -> typing.Any:
return tuple[*args] # type: ignore[valid-type]


@type_eval.register_evaluator(GetSpecialAttr)
@_lift_over_unions
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:
return typing.Never


@type_eval.register_evaluator(GetAnnotations)
def _eval_GetAnnotations(tp, *, ctx) -> typing.Any:
tp = _eval_types(tp, ctx=ctx)
Expand Down
4 changes: 4 additions & 0 deletions typemap/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ class GetArgs[Tp, Base]:
pass


class GetSpecialAttr[T: type, Attr: str]:
pass


class Length[S: tuple]:
pass

Expand Down