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
47 changes: 22 additions & 25 deletions pep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -579,18 +579,15 @@ proposing to add that as a notion, but we could.
Boolean operators
'''''''''''''''''

* ``IsSub[T, S]``: Returns a boolean literal type indicating whether
``T`` is a subtype of ``S``.
* ``IsAssignable[T, S]``: Returns a boolean literal type indicating whether
``S`` is assignable to ``T``.

(Actually, I suppose, whether ``S`` is "assignable to" ``T``. We can
certainly bikeshed on this name.)

* ``Matches[T, S]``:
Equivalent to ``IsSub[T, S] and IsSub[S, T]``.
* ``IsEquivalent[T, S]``:
Equivalent to ``IsAssignable[T, S] and IsAssignable[S, T]``.

* ``Bool[T]``: Returns ``Literal[True]`` if ``T`` is also
``Literal[True]`` or a union containing it.
Equivalent to ``IsSub[T, Literal[True]] and not IsSub[T, Never]``.
Equivalent to ``IsAssignable[T, Literal[True]] and not IsAssignable[T, Never]``.

This is useful for invoking "helper aliases" that return a boolean
literal type.
Expand Down Expand Up @@ -1021,7 +1018,7 @@ producing a new target type containing only properties and wrapping

type ConvertField[T] = (
AdjustLink[PropsOnly[PointerArg[T]], T]
if typing.IsSub[T, Link]
if typing.IsAssignable[T, Link]
else PointerArg[T]
)

Expand All @@ -1044,7 +1041,7 @@ we've discussed already.
::

type AdjustLink[Tgt, LinkTy] = (
list[Tgt] if typing.IsSub[LinkTy, MultiLink] else Tgt
list[Tgt] if typing.IsAssignable[LinkTy, MultiLink] else Tgt
)

And the final helper, ``PropsOnly[T]``, generates a new type that
Expand All @@ -1056,7 +1053,7 @@ contains all the ``Property`` attributes of ``T``.
*[
typing.Member[typing.GetName[p], PointerArg[typing.GetType[p]]]
for p in typing.Iter[typing.Attrs[T]]
if typing.IsSub[typing.GetType[p], Property]
if typing.IsAssignable[typing.GetType[p], Property]
]
]

Expand All @@ -1078,7 +1075,7 @@ suite, but here is a possible implementation of just ``Public``
# otherwise we return the type itself.
type GetDefault[Init] = (
GetFieldItem[Init, Literal["default"]]
if typing.IsSub[Init, Field]
if typing.IsAssignable[Init, Field]
else Init
)

Expand All @@ -1092,7 +1089,7 @@ suite, but here is a possible implementation of just ``Public``
GetDefault[typing.GetInit[p]],
]
for p in typing.Iter[typing.Attrs[T]]
if not typing.IsSub[
if not typing.IsAssignable[
Literal[True],
GetFieldItem[typing.GetInit[p], Literal["primary_key"]],
]
Expand Down Expand Up @@ -1131,7 +1128,7 @@ dataclasses-style method generation
# All arguments are keyword-only
# It takes a default if a default is specified in the class
Literal["keyword"]
if typing.IsSub[
if typing.IsAssignable[
GetDefault[typing.GetInit[p]],
Never,
]
Expand Down Expand Up @@ -1165,9 +1162,9 @@ NumPy-style broadcasting

type MergeOne[T, S] = (
T
if typing.Matches[T, S] or typing.Matches[S, Literal[1]]
if typing.IsEquivalent[T, S] or typing.IsEquivalent[S, Literal[1]]
else S
if typing.Matches[T, Literal[1]]
if typing.IsEquivalent[T, Literal[1]]
else typing.RaiseError[Literal["Broadcast mismatch"], T, S]
)

Expand All @@ -1176,7 +1173,7 @@ NumPy-style broadcasting

# Matching on Never here is intentional; it prevents infinite
# recursions when T is not a tuple.
type Empty[T] = typing.IsSub[typing.Length[T], Literal[0]]
type Empty[T] = typing.IsAssignable[typing.Length[T], Literal[0]]

type Broadcast[T, S] = (
S
Expand Down Expand Up @@ -1227,7 +1224,7 @@ Generic Callable

Consider a method with the following signature::

def process[T](self, x: T) -> T if IsSub[T, list] else list[T]:
def process[T](self, x: T) -> T if IsAssignable[T, list] else list[T]:
...

The type of the method is generic, and the generic is bound at the
Expand All @@ -1240,13 +1237,13 @@ unbound type variables and let them be generalized::
type Foo = NewProtocol[
Member[
Literal["process"],
Callable[[T], set[T] if IsSub[T, int] else T]
Callable[[T], set[T] if IsAssignable[T, int] else T]
]
]

The problem is that this is basically incompatible with runtime
evaluation support, since evaluating the alias ``Foo`` will need to
evaluate the ``IsSub``, and so we will lose one side of the
evaluate the ``IsAssignable``, and so we will lose one side of the
conditional at least. Similar problems will happen when evaluating
``Members`` on a class with generic functions. By wrapping the body
in a lambda, we can delay evaluation in both of these cases. (The
Expand Down Expand Up @@ -1356,8 +1353,8 @@ the model more complicated, but a lot of code will read much nicer.
TODO: Should we do this?


Replace ``IsSub`` with something weaker than "assignable to" checking
---------------------------------------------------------------------
Replace ``IsAssignable`` with something weaker than "assignable to" checking
----------------------------------------------------------------------------

Full python typing assignability checking is not fully implementable
at runtime (in particular, even if all the typeshed types for the
Expand All @@ -1371,15 +1368,15 @@ ideally with the contours of that effort well-documented.
An alternative approach would be to have a weaker predicate as the
core primitive.

One possibility would be a "sub-similarity" check: ``IsSubSimilar``
One possibility would be a "sub-similarity" check: ``IsAssignableSimilar``
would do *simple* checking of the *head* of types, essentially,
without looking at type parameters. It would not work with protocols.
It would still lift over unions and would check literals.

We decided it probably was not a good idea to introduce a new notion
that is similar to but not the same as subtyping, and that would need
to either have a long and weird name like ``IsSubSimilar`` or a
misleading short one like ``IsSub``.
to either have a long and weird name like ``IsAssignableSimilar`` or a
misleading short one like ``IsAssignable``.

.. _less_syntax:

Expand Down
34 changes: 17 additions & 17 deletions tests/test_astlike_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
GetArg,
GetName,
GetType,
IsSub,
IsAssignable,
Iter,
Matches,
IsEquivalent,
Member,
NewProtocol,
RaiseError,
Expand Down Expand Up @@ -52,16 +52,16 @@
(
VarArgType[x]
if not any( # Unique to Ls
Matches[VarArgName[x], VarArgName[y]] for y in Iter[Rs]
IsEquivalent[VarArgName[x], VarArgName[y]] for y in Iter[Rs]
)
else GetArg[ # Common to both Ls and Rs
tuple[
*[
(
VarArgType[x]
if IsSub[VarArgType[x], VarArgType[y]]
if IsAssignable[VarArgType[x], VarArgType[y]]
else VarArgType[y]
if IsSub[VarArgType[y], VarArgType[x]]
if IsAssignable[VarArgType[y], VarArgType[x]]
else RaiseError[
typing.Literal[
"Type mismatch for variable"
Expand All @@ -72,7 +72,7 @@
]
)
for y in Iter[Rs]
if Matches[VarArgName[x], VarArgName[y]]
if IsEquivalent[VarArgName[x], VarArgName[y]]
]
],
tuple,
Expand All @@ -86,7 +86,7 @@
x
for x in Iter[Rs]
if not any( # Unique to Rs
Matches[VarArgName[x], VarArgName[y]] for y in Iter[Ls]
IsEquivalent[VarArgName[x], VarArgName[y]] for y in Iter[Ls]
)
],
]
Expand Down Expand Up @@ -133,14 +133,14 @@ def test_astlike_1_combine_varargs_02():
)


type IsAssignable[L, R] = (
IsSub[R, L]
or Bool[Matches[L, float] and Bool[IsFloat[R]]]
or Bool[Matches[L, complex] and Bool[IsComplex[R]]]
type IsNumericAssignable[L, R] = (
IsAssignable[R, L]
or Bool[IsEquivalent[L, float] and Bool[IsFloat[R]]]
or Bool[IsEquivalent[L, complex] and Bool[IsComplex[R]]]
)
type VarIsPresent[V: VarArg, K: BaseTypedDict] = any(
Matches[VarArgName[V], GetName[x]]
and Bool[IsAssignable[VarArgType[V], GetType[x]]]
IsEquivalent[VarArgName[V], GetName[x]]
and Bool[IsNumericAssignable[VarArgType[V], GetType[x]]]
for x in Iter[Attrs[K]]
)
type AllVarsPresent[Vs: tuple[VarArg, ...], K: BaseTypedDict] = all(
Expand Down Expand Up @@ -201,9 +201,9 @@ def test_astlike_1_all_vars_present_05():
assert t == _BoolLiteral[False]


type IsIntegral[T] = IsSub[T, int]
type IsFloat[T] = Bool[IsIntegral[T]] or IsSub[T, float]
type IsComplex[T] = Bool[IsFloat[T]] or IsSub[T, complex]
type IsIntegral[T] = IsAssignable[T, int]
type IsFloat[T] = Bool[IsIntegral[T]] or IsAssignable[T, float]
type IsComplex[T] = Bool[IsFloat[T]] or IsAssignable[T, complex]

type SimpleNumericOp[L, R, OpName: str] = (
int
Expand All @@ -229,7 +229,7 @@ def test_astlike_1_all_vars_present_05():
type Mul[L, R] = ComplexNumericOp[L, R, typing.Literal["*"]]
type TrueDiv[L, R] = (
float
if IsSub[L, int] and IsSub[R, int]
if IsAssignable[L, int] and IsAssignable[R, int]
else ComplexNumericOp[L, R, typing.Literal["/"]]
)
type FloorDiv[L, R] = SimpleNumericOp[L, R, typing.Literal["//"]]
Expand Down
8 changes: 4 additions & 4 deletions tests/test_eval_call_with_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
GetArg,
GetName,
GetType,
IsSub,
IsAssignable,
Iter,
Members,
Param,
Expand Down Expand Up @@ -266,10 +266,10 @@ def func[T](x: C[T]) -> T: ...
GetType[m]
for m in Iter[Members[T]]
if (
IsSub[GetType[m], Callable]
or IsSub[GetType[m], GenericCallable]
IsAssignable[GetType[m], Callable]
or IsAssignable[GetType[m], GenericCallable]
)
and IsSub[GetName[m], N]
and IsAssignable[GetName[m], N]
]
],
tuple,
Expand Down
20 changes: 13 additions & 7 deletions tests/test_fastapilike_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
NewProtocol,
Iter,
Attrs,
IsSub,
IsAssignable,
GetAnnotations,
DropAnnotations,
FromUnion,
Expand Down Expand Up @@ -54,7 +54,7 @@ class _Default:
GetName[p],
DropAnnotations[GetType[p]],
Literal["keyword", "default"]
if IsSub[
if IsAssignable[
Literal[PropQuals.HAS_DEFAULT],
GetAnnotations[GetType[p]],
]
Expand All @@ -76,14 +76,14 @@ class _Default:
# Strip `| None` from a type by iterating over its union components
# and filtering
type NotOptional[T] = Union[
*[x for x in Iter[FromUnion[T]] if not IsSub[x, None]]
*[x for x in Iter[FromUnion[T]] if not IsAssignable[x, None]]
]

# Adjust an attribute type for use in Public below by dropping | None for
# primary keys and stripping all annotations.
type FixPublicType[T] = DropAnnotations[
NotOptional[T]
if IsSub[Literal[PropQuals.PRIMARY], GetAnnotations[T]]
if IsAssignable[Literal[PropQuals.PRIMARY], GetAnnotations[T]]
else T
]

Expand All @@ -94,7 +94,9 @@ class _Default:
*[
Member[GetName[p], FixPublicType[GetType[p]], GetQuals[p]]
for p in Iter[Attrs[T]]
if not IsSub[Literal[PropQuals.HIDDEN], GetAnnotations[GetType[p]]]
if not IsAssignable[
Literal[PropQuals.HIDDEN], GetAnnotations[GetType[p]]
]
]
]

Expand All @@ -103,7 +105,9 @@ class _Default:
*[
Member[GetName[p], GetType[p], GetQuals[p]]
for p in Iter[Attrs[T]]
if not IsSub[Literal[PropQuals.PRIMARY], GetAnnotations[GetType[p]]]
if not IsAssignable[
Literal[PropQuals.PRIMARY], GetAnnotations[GetType[p]]
]
]
]

Expand All @@ -118,7 +122,9 @@ class _Default:
GetQuals[p],
]
for p in Iter[Attrs[T]]
if not IsSub[Literal[PropQuals.PRIMARY], GetAnnotations[GetType[p]]]
if not IsAssignable[
Literal[PropQuals.PRIMARY], GetAnnotations[GetType[p]]
]
]
]

Expand Down
Loading