Skip to content

Commit 9f27f3f

Browse files
authored
Finish changing everything over to dot notation for Member (#101)
- **Update the PEP draft to reflect commiting to dot member notation** - **Update all of the tests to use dot notation for Member**
1 parent 8bf834b commit 9f27f3f

15 files changed

+284
-276
lines changed

pep.rst

Lines changed: 101 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,9 @@ imported qualified or with some other name)
496496
# *[... for t in ...] arguments
497497
| <ident>[<variadic-type-arg> +]
498498

499+
# Type member access (associated type access?)
500+
| <type>.<name>
501+
499502
| GenericCallable[<type>, lambda <args>: <type>]
500503

501504
# Type conditional checks are boolean compositions of
@@ -524,8 +527,8 @@ imported qualified or with some other name)
524527
(``<type-bool-for>`` is identical to ``<type-for>`` except that the
525528
result type is a ``<type-bool>`` instead of a ``<type>``.)
526529

527-
There are three core syntactic features introduced: type booleans,
528-
conditional types and unpacked comprehension types.
530+
There are three and a half core syntactic features introduced: type booleans,
531+
conditional types, unpacked comprehension types, and type member access.
529532

530533
:ref:`"Generic callables" <generic-callable>` are also technically a
531534
syntactic feature, but are discussed as an operator.
@@ -572,6 +575,18 @@ comprehension iterating over the arguments of tuple type ``iter_ty``.
572575
The comprehension may also have ``if`` clauses, which filter in the
573576
usual way.
574577

578+
Type member access
579+
''''''''''''''''''
580+
581+
The ``Member`` and ``Param`` types introduced to represent class
582+
members and function params have "associated" type members, which can
583+
be accessed by dot notation: ``m.name``, ``m.type``, etc.
584+
585+
This operation is not lifted over union types. Using it on the wrong
586+
sort of type will be an error. (At least, it must be that way at
587+
runtime, and we probably want typechecking to match.)
588+
589+
575590
Type operators
576591
--------------
577592

@@ -674,13 +689,14 @@ Object inspection
674689
of classes. Its type parameters encode the information about each
675690
member.
676691

677-
* ``N`` is the name, as a literal string type
678-
* ``T`` is the type
679-
* ``Q`` is a union of qualifiers (see ``MemberQuals`` below)
692+
* ``N`` is the name, as a literal string type. Accessable with ``.name``.
693+
* ``T`` is the type. Accessable with ``.type``.
694+
* ``Q`` is a union of qualifiers (see ``MemberQuals`` below). Accessable with ``.quals``.
680695
* ``Init`` is the literal type of the attribute initializer in the
681-
class (see :ref:`InitField <init-field>`)
696+
class (see :ref:`InitField <init-field>`). Accessable with ``.init``.
682697
* ``D`` is the defining class of the member. (That is, which class
683-
the member is inherited from. Always ``Never``, for a ``TypedDict``)
698+
the member is inherited from. Always ``Never``, for a ``TypedDict``).
699+
Accessable with ``.definer``.
684700

685701
* ``MemberQuals = Literal['ClassVar', 'Final', 'NotRequired', 'ReadOnly']`` -
686702
``MemberQuals`` is the type of "qualifiers" that can apply to a
@@ -694,16 +710,6 @@ qualifier. ``staticmethod`` and ``classmethod`` will return
694710
``staticmethod`` and ``classmethod`` types, which are subscriptable as
695711
of 3.14.
696712

697-
We also have helpers for extracting the fields of ``Members``; they
698-
are all definable in terms of ``GetArg``. (Some of them are shared
699-
with ``Param``, discussed below.)
700-
701-
* ``GetName[T: Member | Param]``
702-
* ``GetType[T: Member | Param]``
703-
* ``GetQuals[T: Member | Param]``
704-
* ``GetInit[T: Member]``
705-
* ``GetDefiner[T: Member]``
706-
707713
All of the operators in this section are :ref:`lifted over union types
708714
<lifting>`.
709715

@@ -795,10 +801,10 @@ Callable inspection and creation
795801
``Callable`` types always have their arguments exposed in the extended
796802
Callable format discussed above.
797803

798-
The names, type, and qualifiers share getter operations with
799-
``Member``.
804+
The names, type, and qualifiers share associated type names with
805+
``Member`` (``.name``, ``.type``, and ``.quals``).
800806

801-
TODO: Should we make ``GetInit`` be literal types of default parameter
807+
TODO: Should we make ``.init`` be literal types of default parameter
802808
values too?
803809

804810
.. _generic-callable:
@@ -947,8 +953,7 @@ those cases, we add a new hook to ``typing``:
947953
it before being returned.
948954

949955
If set to ``None`` (the default), the boolean operators will return
950-
``False`` while ``Iter`` will evaluate to
951-
``iter(typing.TypeVarTuple("_IterDummy"))``.
956+
``False`` while ``Iter`` will evaluate to ``iter(())``.
952957

953958

954959
There has been some discussion of adding a ``Format.AST`` mode for
@@ -997,7 +1002,7 @@ The ``**kwargs: Unpack[K]`` is part of this proposal, and allows
9971002
type-annotated attribute of ``K``, while calling ``NewProtocol`` with
9981003
``Member`` arguments constructs a new structural type.
9991004

1000-
``GetName`` is a getter operator that fetches the name of a ``Member``
1005+
``c.name`` fetches the name of the ``Member`` bound to the variable ``c``
10011006
as a literal type--all of these mechanisms lean very heavily on literal types.
10021007
``GetMemberType`` gets the type of an attribute from a class.
10031008

@@ -1011,8 +1016,8 @@ as a literal type--all of these mechanisms lean very heavily on literal types.
10111016
typing.NewProtocol[
10121017
*[
10131018
typing.Member[
1014-
typing.GetName[c],
1015-
ConvertField[typing.GetMemberType[ModelT, typing.GetName[c]]],
1019+
c.name,
1020+
ConvertField[typing.GetMemberType[ModelT, c.name]],
10161021
]
10171022
for c in typing.Iter[typing.Attrs[K]]
10181023
]
@@ -1066,9 +1071,9 @@ contains all the ``Property`` attributes of ``T``.
10661071

10671072
type PropsOnly[T] = typing.NewProtocol[
10681073
*[
1069-
typing.Member[typing.GetName[p], PointerArg[typing.GetType[p]]]
1074+
typing.Member[p.name, PointerArg[p.type]]
10701075
for p in typing.Iter[typing.Attrs[T]]
1071-
if typing.IsAssignable[typing.GetType[p], Property]
1076+
if typing.IsAssignable[p.type, Property]
10721077
]
10731078
]
10741079

@@ -1098,15 +1103,15 @@ suite, but here is a possible implementation of just ``Public``
10981103
type Create[T] = typing.NewProtocol[
10991104
*[
11001105
typing.Member[
1101-
typing.GetName[p],
1102-
typing.GetType[p],
1103-
typing.GetQuals[p],
1104-
GetDefault[typing.GetInit[p]],
1106+
p.name,
1107+
p.type,
1108+
p.quals,
1109+
GetDefault[p.init],
11051110
]
11061111
for p in typing.Iter[typing.Attrs[T]]
11071112
if not typing.IsAssignable[
11081113
Literal[True],
1109-
GetFieldItem[typing.GetInit[p], Literal["primary_key"]],
1114+
GetFieldItem[p.init, Literal["primary_key"]],
11101115
]
11111116
]
11121117
]
@@ -1138,13 +1143,13 @@ dataclasses-style method generation
11381143
typing.Param[Literal["self"], Self],
11391144
*[
11401145
typing.Param[
1141-
typing.GetName[p],
1142-
typing.GetType[p],
1146+
p.name,
1147+
p.type,
11431148
# All arguments are keyword-only
11441149
# It takes a default if a default is specified in the class
11451150
Literal["keyword"]
11461151
if typing.IsAssignable[
1147-
GetDefault[typing.GetInit[p]],
1152+
GetDefault[p.init],
11481153
Never,
11491154
]
11501155
else Literal["keyword", "default"],
@@ -1325,75 +1330,6 @@ AKA '"Rejected" Ideas That Maybe We Should Actually Do?'
13251330

13261331
Very interested in feedback about these!
13271332

1328-
The first one in particular I think has a lot of upside.
1329-
1330-
Support dot notation to access ``Member`` components
1331-
----------------------------------------------------
1332-
1333-
Code would read quite a bit nicer if we could write ``m.name`` instead
1334-
of ``GetName[m]``.
1335-
With dot notation, ``PropsOnly`` (from
1336-
:ref:`the query builder example <qb-impl>`) would look like::
1337-
1338-
type PropsOnly[T] = typing.NewProtocol[
1339-
*[
1340-
typing.Member[p.name, PointerArg[p.type]]
1341-
for p in typing.Iter[typing.Attrs[T]]
1342-
if typing.IsAssignable[p.type, Property]
1343-
]
1344-
]
1345-
1346-
Which is a fair bit nicer.
1347-
1348-
1349-
We considered this but initially rejected it in part due to runtime
1350-
implementation concerns: an expression like ``Member[Literal["x"],
1351-
int].name`` would need to return an object that captures both the
1352-
content of the type alias while maintaining the ``_GenericAlias`` of
1353-
the applied class so that type variables may be substituted for.
1354-
1355-
We were mistaken about the runtime evaluation difficulty,
1356-
though: if we required a special base class in order for a type to use
1357-
this feature, it should work without too much trouble, and without
1358-
causing any backporting or compatibility problems.
1359-
1360-
We wouldn't be able to have the operation lift over unions or the like
1361-
(unless we were willing to modify ``__getattr__`` for
1362-
``types.UnionType`` and ``typing._UnionGenericAlias`` to do so!)
1363-
1364-
Or maybe it would be fine to have it only work on variables, and then
1365-
no special support would be required at the definition site.
1366-
1367-
That just leaves semantic and philosophical concerns: it arguably makes
1368-
the model more complicated, but a lot of code will read much nicer.
1369-
1370-
What would the mechanism be?
1371-
''''''''''''''''''''''''''''
1372-
1373-
A general mechanism to support this might look
1374-
like::
1375-
1376-
class Member[
1377-
N: str,
1378-
T,
1379-
Q: MemberQuals = typing.Never,
1380-
I = typing.Never,
1381-
D = typing.Never
1382-
]:
1383-
type name = N
1384-
type tp = T
1385-
type quals = Q
1386-
type init = I
1387-
type definer = D
1388-
1389-
Where ``type`` aliases defined in a class can be accessed by dot notation.
1390-
1391-
1392-
Another option would be to skip introducing a general mechanism (for
1393-
now, at least), but at least make dot notation work on ``Member`` and
1394-
``Param``, which will be extremely common.
1395-
1396-
13971333
Dictionary comprehension based syntax for creating typed dicts and protocols
13981334
----------------------------------------------------------------------------
13991335

@@ -1486,6 +1422,38 @@ difference? Combined with dictionary-comprehensions and dot notation
14861422
(The user-defined type alias ``PointerArg`` still must be called with
14871423
brackets, despite being basically a helper operator.)
14881424

1425+
Have a general mechanism for dot-notation accessible associated types
1426+
---------------------------------------------------------------------
1427+
1428+
The main proposal is currently silent about exactly *how* ``Member``
1429+
and ``Param`` will have associated types for ``.name`` and ``.type``.
1430+
1431+
We could just make it work for those particular types, or we could
1432+
introduce a general mechansim that might look something like::
1433+
1434+
@typing.has_associated_types
1435+
class Member[
1436+
N: str,
1437+
T,
1438+
Q: MemberQuals = typing.Never,
1439+
I = typing.Never,
1440+
D = typing.Never
1441+
]:
1442+
type name = N
1443+
type tp = T
1444+
type quals = Q
1445+
type init = I
1446+
type definer = D
1447+
1448+
1449+
The decorator (or a base class) is needed if we want the dot notation
1450+
for the associated types to be able to work at runtime, since we need
1451+
to customize the behavior of ``__getattr__`` on the
1452+
``typing._GenericAlias`` produced by the class so that it captures
1453+
both the type parameters to ``Member`` and the alias.
1454+
1455+
(Though possibly we could change the behavior of ``_GenericAlias``
1456+
itself to avoid the need for that.)
14891457

14901458
Rejected Ideas
14911459
==============
@@ -1567,6 +1535,35 @@ worse. Supporting filtering while mapping would make it even more bad
15671535

15681536
We can explore other options too if needed.
15691537

1538+
1539+
Don't use dot notation to access ``Member`` components
1540+
------------------------------------------------------
1541+
1542+
Earlier versions of this PEP draft omitted the ability to write
1543+
``m.name`` and similar on ``Member`` and ``Param`` components, and
1544+
instead relied on helper operators such as ``typing.GetName`` (that
1545+
could be implemented under the hood using ``typing.GetArg`` or
1546+
``typing.GetMemberType``).
1547+
1548+
The potential advantage here is reducing the number of new constructs
1549+
being added to the type language, and avoiding needing to either
1550+
introduce a new general mechanism for associated types or having a
1551+
special-case for ``Member``.
1552+
1553+
``PropsOnly`` (from :ref:`the query builder example <qb-impl>`) would
1554+
look like::
1555+
1556+
type PropsOnly[T] = typing.NewProtocol[
1557+
*[
1558+
typing.Member[typing.GetName[p], PointerArg[typing.GetType[p]]]
1559+
for p in typing.Iter[typing.Attrs[T]]
1560+
if typing.IsAssignable[typing.GetType[p], Property]
1561+
]
1562+
]
1563+
1564+
Everyone hated how this looked a lot.
1565+
1566+
15701567
Perform type manipulations with normal Python functions
15711568
-------------------------------------------------------
15721569

tests/test_astlike_1.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
BaseTypedDict,
1010
Bool,
1111
GetArg,
12-
GetName,
13-
GetType,
1412
IsAssignable,
1513
Iter,
1614
IsEquivalent,
@@ -139,8 +137,8 @@ def test_astlike_1_combine_varargs_02():
139137
or Bool[IsEquivalent[L, complex] and Bool[IsComplex[R]]]
140138
)
141139
type VarIsPresent[V: VarArg, K: BaseTypedDict] = any(
142-
IsEquivalent[VarArgName[V], GetName[x]]
143-
and Bool[IsNumericAssignable[VarArgType[V], GetType[x]]]
140+
IsEquivalent[VarArgName[V], x.name]
141+
and Bool[IsNumericAssignable[VarArgType[V], x.type]]
144142
for x in Iter[Attrs[K]]
145143
)
146144
type AllVarsPresent[Vs: tuple[VarArg, ...], K: BaseTypedDict] = all(

tests/test_call.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
BaseTypedDict,
99
NewProtocol,
1010
Member,
11-
GetName,
1211
Iter,
1312
)
1413

@@ -18,7 +17,7 @@
1817
def func[*T, K: BaseTypedDict](
1918
*args: Unpack[T],
2019
**kwargs: Unpack[K],
21-
) -> NewProtocol[*[Member[GetName[c], int] for c in Iter[Attrs[K]]]]: ...
20+
) -> NewProtocol[*[Member[c.name, int] for c in Iter[Attrs[K]]]]: ...
2221

2322

2423
def test_call_1():

tests/test_eval_call_with_types.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
from typemap_extensions import (
77
GenericCallable,
88
GetArg,
9-
GetName,
10-
GetType,
119
IsAssignable,
1210
Iter,
1311
Members,
@@ -263,13 +261,13 @@ def func[T](x: C[T]) -> T: ...
263261
type GetCallableMember[T, N: str] = GetArg[
264262
tuple[
265263
*[
266-
GetType[m]
264+
m.type
267265
for m in Iter[Members[T]]
268266
if (
269-
IsAssignable[GetType[m], Callable]
270-
or IsAssignable[GetType[m], GenericCallable]
267+
IsAssignable[m.type, Callable]
268+
or IsAssignable[m.type, GenericCallable]
271269
)
272-
and IsAssignable[GetName[m], N]
270+
and IsAssignable[m.name, N]
273271
]
274272
],
275273
tuple,

0 commit comments

Comments
 (0)