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
14 changes: 9 additions & 5 deletions pep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -510,8 +510,8 @@ Basic operators
cannot be.


* ``GetAttr[T, S: Literal[str]]``: Extract the type of the member
named ``S`` from the class ``T``.
* ``GetMemberType[T, S: Literal[str]]``: Extract the type of the
member named ``S`` from the class ``T``.

* ``Length[T: tuple]`` - get the length of a tuple as an int literal
(or ``Literal[None]`` if it is unbounded)
Expand Down Expand Up @@ -540,6 +540,9 @@ Object inspection
* ``Attrs[T]``: like ``Members[T]`` but only returns attributes (not
methods).

* ``GetMember[T, S: Literal[str]]``: Produces a ``Member`` type for the
member named ``S`` from the class ``T``.

* ``Member[N: Literal[str], T, Q: MemberQuals, Init, D]``: ``Member``,
is a simple type, not an operator, that is used to describe members
of classes. Its type parameters encode the information about each
Expand Down Expand Up @@ -837,7 +840,7 @@ type-annotated attribute of ``K``, while calling ``NewProtocol`` with

``GetName`` is a getter operator that fetches the name of a ``Member``
as a literal type--all of these mechanisms lean very heavily on literal types.
``GetAttr`` gets the type of an attribute from a class.
``GetMemberType`` gets the type of an attribute from a class.

::

Expand All @@ -850,7 +853,7 @@ as a literal type--all of these mechanisms lean very heavily on literal types.
*[
Member[
GetName[c],
ConvertField[GetAttr[ModelT, GetName[c]]],
ConvertField[GetMemberType[ModelT, GetName[c]]],
]
for c in Iter[Attrs[K]]
]
Expand Down Expand Up @@ -1119,7 +1122,8 @@ We could do potentially better but it would require more meachinery.
* ``Member[T]``, when statically checking a type alias, could be
treated as having some type like ``tuple[Member[KeyOf[T], object,
str, ..., ...], ...]``
* ``GetAttr[T, S: KeyOf[T]]`` - but this isn't supported yet. TS supports it.
* ``GetMemberType[T, S: KeyOf[T]]`` - but this isn't supported yet.
TS supports it.
* We would also need to do context sensitive type bound inference


Expand Down
4 changes: 2 additions & 2 deletions tests/test_fastapilike_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
IsSub,
FromUnion,
GetArg,
GetAttr,
GetMemberType,
GetType,
GetName,
GetQuals,
Expand Down Expand Up @@ -46,7 +46,7 @@ class Field[T: FieldArgs](InitField[T]):
####

# TODO: Should this go into the stdlib?
type GetFieldItem[T: InitField, K] = GetAttr[
type GetFieldItem[T: InitField, K] = GetMemberType[
GetArg[T, InitField, Literal[0]], K
]

Expand Down
6 changes: 3 additions & 3 deletions tests/test_qblike.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
GetType,
Member,
GetName,
GetAttr,
GetMemberType,
GetArg,
)

Expand Down Expand Up @@ -49,7 +49,7 @@ def select[K: BaseTypedDict](
*[
Member[
GetName[c],
FilterLinks[GetAttr[A, GetName[c]]],
FilterLinks[GetMemberType[A, GetName[c]]],
]
for c in Iter[Attrs[K]]
]
Expand Down Expand Up @@ -118,7 +118,7 @@ class select[...]:
z: tests.test_qblike.Link[tests.test_qblike.PropsOnly[tests.test_qblike.Tgt]]
""")

res = eval_typing(GetAttr[ret, Literal["z"]])
res = eval_typing(GetMemberType[ret, Literal["z"]])
tgt = res.__args__[0]
# XXX: this should probably be pre-evaluated already?
tgt = eval_typing(tgt)
Expand Down
8 changes: 4 additions & 4 deletions tests/test_qblike_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
GetType,
Member,
GetName,
GetAttr,
GetMemberType,
GetArg,
)

Expand Down Expand Up @@ -58,7 +58,7 @@ class MultiLink[T](Link[T]):

``GetName`` is a getter operator that fetches the name of a ``Member``
as a literal type--all of these mechanisms lean very heavily on literal types.
``GetAttr`` gets the type of an attribute from a class.
``GetMemberType`` gets the type of an attribute from a class.

"""

Expand All @@ -72,7 +72,7 @@ def select[ModelT, K: BaseTypedDict](
*[
Member[
GetName[c],
ConvertField[GetAttr[ModelT, GetName[c]]],
ConvertField[GetMemberType[ModelT, GetName[c]]],
]
for c in Iter[Attrs[K]]
]
Expand Down Expand Up @@ -198,7 +198,7 @@ class select[...]:
posts: list[tests.test_qblike_2.PropsOnly[tests.test_qblike_2.Post]]
""")

res = eval_typing(GetAttr[ret, Literal["posts"]])
res = eval_typing(GetMemberType[ret, Literal["posts"]])
tgt = res.__args__[0]
# XXX: this should probably be pre-evaluated already?
fmt = format_helper.format_class(tgt)
Expand Down
34 changes: 25 additions & 9 deletions tests/test_type_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
GenericCallable,
GetArg,
GetArgs,
GetAttr,
GetMember,
GetMemberType,
GetName,
GetType,
GetAnnotations,
Expand Down Expand Up @@ -198,27 +199,27 @@ def test_eval_arg_order():


def test_type_getattr_union_1():
d = eval_typing(GetAttr[TA | TB, Literal["x"]])
d = eval_typing(GetMemberType[TA | TB, Literal["x"]])
assert d == int | str


def test_type_getattr_union_2():
d = eval_typing(GetAttr[TA, Literal["x"] | Literal["y"]])
d = eval_typing(GetMemberType[TA, Literal["x"] | Literal["y"]])
assert d == int | list[float]


def test_type_getattr_union_3():
d = eval_typing(GetAttr[TA | TB, Literal["x"] | Literal["y"]])
d = eval_typing(GetMemberType[TA | TB, Literal["x"] | Literal["y"]])
assert d == int | list[float] | str | list[object]


def test_type_getattr_union_4():
d = eval_typing(GetAttr[TA, Literal["x", "y"]])
d = eval_typing(GetMemberType[TA, Literal["x", "y"]])
assert d == int | list[float]


def test_type_getattr_union_5():
d = eval_typing(GetAttr[TA, Literal["x", "y"] | Literal["z"]])
d = eval_typing(GetMemberType[TA, Literal["x", "y"] | Literal["z"]])
assert d == int | list[float] | TB


Expand Down Expand Up @@ -379,6 +380,21 @@ def test_type_from_union_06():
assert _is_generic_permutation(n, tuple[int, list[IntTree]])


def test_getmember_01():
d = eval_typing(GetMember[TA, Literal["x"]])
assert d == Member[Literal["x"], int, Never, Never, TA]
d = eval_typing(GetMemberType[TA, Literal["a"]])
assert d == Never

d = eval_typing(GetMember[TA | TB, Literal["x"]])
assert d == (
Member[Literal["x"], int, Never, Never, TA]
| Member[Literal["x"], str, Never, Never, TB]
)
d = eval_typing(GetMember[TA | TB, Literal[""]])
assert d == Never


def test_getarg_never():
d = eval_typing(GetArg[Never, object, Literal[0]])
assert d is Never
Expand Down Expand Up @@ -1537,15 +1553,15 @@ class AnnoTest:


def test_type_eval_annotated_02():
res = eval_typing(IsSub[GetAttr[AnnoTest, Literal["a"]], int])
res = eval_typing(IsSub[GetMemberType[AnnoTest, Literal["a"]], int])
assert res == _BoolLiteral[True]


def test_type_eval_annotated_03():
res = eval_typing(Uppercase[GetAttr[AnnoTest, Literal["b"]]])
res = eval_typing(Uppercase[GetMemberType[AnnoTest, Literal["b"]]])
assert res == Literal["TEST"]


def test_type_eval_annotated_04():
res = eval_typing(GetAnnotations[GetAttr[AnnoTest, Literal["b"]]])
res = eval_typing(GetAnnotations[GetMemberType[AnnoTest, Literal["b"]]])
assert res == Literal["blah"]
43 changes: 30 additions & 13 deletions typemap/type_eval/_eval_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
GetAnnotations,
GetArg,
GetArgs,
GetAttr,
GetMember,
GetMemberType,
InitField,
IsSubSimilar,
IsSubtype,
Expand Down Expand Up @@ -657,19 +658,20 @@ def _get_function_hint_namespaces(func, receiver_type=None):
return globalns, localns


def _hint_to_member(n, t, qs, init, d, *, ctx):
return Member[
typing.Literal[n],
_eval_types(t, ctx),
_mk_literal_union(*qs),
init,
d,
]


def _hints_to_members(hints, ctx):
"""Convert a hints dictionary to a tuple of Member types."""
return tuple[
*[
Member[
typing.Literal[n],
_eval_types(t, ctx),
_mk_literal_union(*qs),
init,
d,
]
for n, (t, qs, init, d) in hints.items()
]
*[_hint_to_member(n, *hint, ctx=ctx) for n, hint in hints.items()]
]


Expand All @@ -690,6 +692,21 @@ def _eval_Members(tp, *, ctx):
return _hints_to_members(hints, ctx)


@type_eval.register_evaluator(GetMember)
@_lift_over_unions
def _eval_GetMember(tp, prop, *, ctx):
# XXX: extras?
name = _from_literal(prop)
hints = {
**get_annotated_type_hints(tp, include_extras=True),
**get_annotated_method_hints(tp),
}
if name in hints:
return _hint_to_member(name, *hints[name], ctx=ctx)
else:
return typing.Never


##################################################################


Expand All @@ -705,9 +722,9 @@ def _eval_FromUnion(tp, *, ctx):
##################################################################


@type_eval.register_evaluator(GetAttr)
@type_eval.register_evaluator(GetMemberType)
@_lift_over_unions
def _eval_GetAttr(tp, prop, *, ctx):
def _eval_GetMemberType(tp, prop, *, ctx):
# XXX: extras?
name = _from_literal(prop)
hints = {
Expand Down
16 changes: 10 additions & 6 deletions typemap/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ class Param[N: str | None, T, Q: ParamQuals = typing.Never]:
type KwargsParam[T] = Param[Literal[None], T, Literal["**"]]


type GetName[T: Member | Param] = GetAttr[T, Literal["name"]]
type GetType[T: Member | Param] = GetAttr[T, Literal["typ"]]
type GetQuals[T: Member | Param] = GetAttr[T, Literal["quals"]]
type GetInit[T: Member] = GetAttr[T, Literal["init"]]
type GetDefiner[T: Member] = GetAttr[T, Literal["definer"]]
type GetName[T: Member | Param] = GetMemberType[T, Literal["name"]]
type GetType[T: Member | Param] = GetMemberType[T, Literal["typ"]]
type GetQuals[T: Member | Param] = GetMemberType[T, Literal["quals"]]
type GetInit[T: Member] = GetMemberType[T, Literal["init"]]
type GetDefiner[T: Member] = GetMemberType[T, Literal["definer"]]


class Attrs[T]:
Expand All @@ -131,7 +131,11 @@ class FromUnion[T]:
pass


class GetAttr[Lhs, Prop]:
class GetMember[Lhs, Prop]:
pass


class GetMemberType[Lhs, Prop]:
pass


Expand Down