From 91fe69f69123c19868cbc7a57ecc3c3737978a5a Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 4 Feb 2026 17:58:21 -0800 Subject: [PATCH 1/2] Rename IsSub to IsAssignable and Matches to IsEquivalent Assignability is the right technical term, and unfortunately the abbreviations are fraught. --- pep.rst | 45 +++++++------- tests/test_astlike_1.py | 34 +++++------ tests/test_eval_call_with_types.py | 8 +-- tests/test_fastapilike_1.py | 20 +++--- tests/test_fastapilike_2.py | 20 +++--- tests/test_nplike.py | 6 +- tests/test_qblike.py | 6 +- tests/test_qblike_2.py | 6 +- tests/test_qblike_3.py | 38 +++++++----- tests/test_type_dir.py | 22 ++++--- tests/test_type_eval.py | 91 ++++++++++++++-------------- typemap/type_eval/_eval_operators.py | 12 ++-- typemap/typing.py | 4 +- 13 files changed, 168 insertions(+), 144 deletions(-) diff --git a/pep.rst b/pep.rst index 2db410c..85ca2de 100644 --- a/pep.rst +++ b/pep.rst @@ -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. @@ -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] ) @@ -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 @@ -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] ] ] @@ -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 ) @@ -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"]], ] @@ -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, ] @@ -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] ) @@ -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 @@ -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 @@ -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 @@ -1356,7 +1353,7 @@ 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 @@ -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: diff --git a/tests/test_astlike_1.py b/tests/test_astlike_1.py index f4e2728..78fe89b 100644 --- a/tests/test_astlike_1.py +++ b/tests/test_astlike_1.py @@ -11,9 +11,9 @@ GetArg, GetName, GetType, - IsSub, + IsAssignable, Iter, - Matches, + IsEquivalent, Member, NewProtocol, RaiseError, @@ -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" @@ -72,7 +72,7 @@ ] ) for y in Iter[Rs] - if Matches[VarArgName[x], VarArgName[y]] + if IsEquivalent[VarArgName[x], VarArgName[y]] ] ], tuple, @@ -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] ) ], ] @@ -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( @@ -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 @@ -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["//"]] diff --git a/tests/test_eval_call_with_types.py b/tests/test_eval_call_with_types.py index c2ed54a..4ab0d75 100644 --- a/tests/test_eval_call_with_types.py +++ b/tests/test_eval_call_with_types.py @@ -8,7 +8,7 @@ GetArg, GetName, GetType, - IsSub, + IsAssignable, Iter, Members, Param, @@ -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, diff --git a/tests/test_fastapilike_1.py b/tests/test_fastapilike_1.py index ea76af7..9a18b5b 100644 --- a/tests/test_fastapilike_1.py +++ b/tests/test_fastapilike_1.py @@ -9,7 +9,7 @@ NewProtocol, Iter, Attrs, - IsSub, + IsAssignable, GetAnnotations, DropAnnotations, FromUnion, @@ -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]], ] @@ -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 ] @@ -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]] + ] ] ] @@ -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]] + ] ] ] @@ -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]] + ] ] ] diff --git a/tests/test_fastapilike_2.py b/tests/test_fastapilike_2.py index a3e9fb4..76f0329 100644 --- a/tests/test_fastapilike_2.py +++ b/tests/test_fastapilike_2.py @@ -36,14 +36,20 @@ class Field[T: FieldArgs](typing.InitField[T]): # Strip `| None` from a type by iterating over its union components # and filtering type NotOptional[T] = Union[ - *[x for x in typing.Iter[typing.FromUnion[T]] if not typing.IsSub[x, None]] + *[ + x + for x in typing.Iter[typing.FromUnion[T]] + if not typing.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, Init] = ( NotOptional[T] - if typing.IsSub[Literal[True], GetFieldItem[Init, Literal["primary_key"]]] + if typing.IsAssignable[ + Literal[True], GetFieldItem[Init, Literal["primary_key"]] + ] else T ) @@ -58,7 +64,7 @@ class Field[T: FieldArgs](typing.InitField[T]): typing.GetQuals[p], ] for p in typing.Iter[typing.Attrs[T]] - if not typing.IsSub[ + if not typing.IsAssignable[ Literal[True], GetFieldItem[typing.GetInit[p], Literal["hidden"]] ] ] @@ -75,7 +81,7 @@ class Field[T: FieldArgs](typing.InitField[T]): # 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 ) @@ -89,7 +95,7 @@ class Field[T: FieldArgs](typing.InitField[T]): 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"]], ] @@ -123,7 +129,7 @@ class Field[T: FieldArgs](typing.InitField[T]): Literal[None], ] 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"]], ] @@ -147,7 +153,7 @@ class Field[T: FieldArgs](typing.InitField[T]): # 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, ] diff --git a/tests/test_nplike.py b/tests/test_nplike.py index 8014355..db948f0 100644 --- a/tests/test_nplike.py +++ b/tests/test_nplike.py @@ -16,9 +16,9 @@ def __add__[*Shape2]( 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] ) @@ -27,7 +27,7 @@ def __add__[*Shape2]( # 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 diff --git a/tests/test_qblike.py b/tests/test_qblike.py index 39200ca..d1993cb 100644 --- a/tests/test_qblike.py +++ b/tests/test_qblike.py @@ -12,7 +12,7 @@ NewProtocol, Iter, Attrs, - IsSub, + IsAssignable, GetType, Member, GetName, @@ -32,12 +32,12 @@ class Link[T]: type PropsOnly[T] = NewProtocol[ - *[p for p in Iter[Attrs[T]] if IsSub[GetType[p], Property]] + *[p for p in Iter[Attrs[T]] if IsAssignable[GetType[p], Property]] ] # Conditional type alias! type FilterLinks[T] = ( - Link[PropsOnly[GetArg[T, Link, Literal[0]]]] if IsSub[T, Link] else T + Link[PropsOnly[GetArg[T, Link, Literal[0]]]] if IsAssignable[T, Link] else T ) diff --git a/tests/test_qblike_2.py b/tests/test_qblike_2.py index 8bcc57d..11f31d5 100644 --- a/tests/test_qblike_2.py +++ b/tests/test_qblike_2.py @@ -82,7 +82,7 @@ def select[ModelT, K: typing.BaseTypedDict]( type ConvertField[T] = ( AdjustLink[PropsOnly[PointerArg[T]], T] - if typing.IsSub[T, Link] + if typing.IsAssignable[T, Link] else PointerArg[T] ) @@ -104,7 +104,7 @@ def select[ModelT, K: typing.BaseTypedDict]( """ 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 @@ -115,7 +115,7 @@ def select[ModelT, K: typing.BaseTypedDict]( *[ 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] ] ] diff --git a/tests/test_qblike_3.py b/tests/test_qblike_3.py index 1cd323f..ceeed17 100644 --- a/tests/test_qblike_3.py +++ b/tests/test_qblike_3.py @@ -24,9 +24,9 @@ GetType, GetInit, InitField, - IsSub, + IsAssignable, Iter, - Matches, + IsEquivalent, Member, NewProtocol, Slice, @@ -106,7 +106,7 @@ class Select[Post, tuple[...]]: # Type Helpers -type ReplaceNever[T, D] = T if not IsSub[T, Never] else D +type ReplaceNever[T, D] = T if not IsAssignable[T, Never] else D type GetInitFieldItem[T: InitField, K, Default] = ReplaceNever[ GetMemberType[GetArg[T, InitField, Literal[0]], K], Default ] @@ -165,7 +165,7 @@ class column[Args: ColumnArgs](InitField[Args]): type ColumnInitIsAutoincrement[Init] = GetInitFieldItem[ Init, Literal["autoincrement"], Literal[False] ] -type ColumnInitHasDefault[Init] = not IsSub[ +type ColumnInitHasDefault[Init] = not IsAssignable[ GetInitFieldItem[Init, Literal["default"], Never], Never ] @@ -173,8 +173,10 @@ class column[Args: ColumnArgs](InitField[Args]): not Bool[ColumnInitIsNullable[GetInit[M]]] or Bool[ColumnInitIsAutoincrement[GetInit[M]]] or ( - IsSub[FieldPyType[GetType[M]], list] - and IsSub[GetArg[FieldPyType[GetType[M]], list, Literal[0]], Table] + IsAssignable[FieldPyType[GetType[M]], list] + and IsAssignable[ + GetArg[FieldPyType[GetType[M]], list, Literal[0]], Table + ] ) ) @@ -219,18 +221,24 @@ class DbLinkSource[Args: DbLinkSourceArgs](InitField[Args]): *[ m for m in Iter[Attrs[T]] - if any(IsSub[GetName[m], f] for f in Iter[FieldNames]) + if any(IsAssignable[GetName[m], f] for f in Iter[FieldNames]) ] ] -type EntryIsTable[E: QueryEntry, T: Table] = Matches[EntryTable[E], T] +type EntryIsTable[E: QueryEntry, T: Table] = IsEquivalent[EntryTable[E], T] type EntriesHasTable[Es: tuple[QueryEntry, ...], T: Table] = any( Bool[EntryIsTable[e, T]] for e in Iter[Es] ) type MakeQueryEntryAllFields[T: Table] = QueryEntry[ T, - tuple[*[GetName[m] for m in Iter[Attrs[T]] if IsSub[GetType[m], Field]],], + tuple[ + *[ + GetName[m] + for m in Iter[Attrs[T]] + if IsAssignable[GetType[m], Field] + ], + ], ] type MakeQueryEntryNamedFields[ T: Table, @@ -241,8 +249,10 @@ class DbLinkSource[Args: DbLinkSourceArgs](InitField[Args]): *[ GetName[m] for m in Iter[Attrs[T]] - if IsSub[GetType[m], Field] - and any(IsSub[FieldName[GetType[m]], f] for f in Iter[FieldNames]) + if IsAssignable[GetType[m], Field] + and any( + IsAssignable[FieldName[GetType[m]], f] for f in Iter[FieldNames] + ) ], ], ] @@ -278,11 +288,11 @@ class DbLinkSource[Args: DbLinkSourceArgs](InitField[Args]): ] type AddEntries[Entries, News: tuple[Table | Field, ...]] = ( Entries - if IsSub[Length[News], Literal[0]] + if IsAssignable[Length[News], Literal[0]] else AddEntries[ ( AddTable[Entries, GetArg[News, tuple, Literal[0]]] - if IsSub[GetArg[News, tuple, Literal[0]], Table] + if IsAssignable[GetArg[News, tuple, Literal[0]], Table] else AddField[Entries, GetArg[News, tuple, Literal[0]]] ), Slice[News, Literal[1], Literal[None]], @@ -318,7 +328,7 @@ class Query[Es: tuple[QueryEntry[Table, tuple[Member]], ...]]: EntryTable[GetArg[Es, tuple, Literal[0]]], EntryFields[GetArg[Es, tuple, Literal[0]]], ] - if IsSub[Literal[1], Length[Es]] + if IsAssignable[Literal[1], Length[Es]] else NewProtocol[ *[ Member[ diff --git a/tests/test_type_dir.py b/tests/test_type_dir.py index 3f7c29b..6b00d76 100644 --- a/tests/test_type_dir.py +++ b/tests/test_type_dir.py @@ -11,7 +11,7 @@ GetQuals, GetType, InitField, - IsSub, + IsAssignable, Iter, Member, Members, @@ -80,7 +80,9 @@ class Final(Mine, Ordinary, Wrapper[float], AnotherBase[float], Last[int]): pass -type BaseArg[T] = GetArg[T, Base, Literal[0]] if IsSub[T, Base] else Never +type BaseArg[T] = ( + GetArg[T, Base, Literal[0]] if IsAssignable[T, Base] else Never +) type AllOptional[T] = NewProtocol[ @@ -101,7 +103,7 @@ class Final(Mine, Ordinary, Wrapper[float], AnotherBase[float], Last[int]): ] type Prims[T] = NewProtocol[ - *[p for p in Iter[Attrs[T]] if IsSub[GetType[p], int | str]] + *[p for p in Iter[Attrs[T]] if IsAssignable[GetType[p], int | str]] ] type NoLiterals1[T] = NewProtocol[ @@ -114,7 +116,7 @@ class Final(Mine, Ordinary, Wrapper[float], AnotherBase[float], Last[int]): for t in Iter[FromUnion[GetType[p]]] # XXX: 'typing.Literal' is not *really* a type... # Maybe we can't do this, which maybe is fine. - if not IsSub[t, Literal] + if not IsAssignable[t, Literal] ] ], GetQuals[p], @@ -130,10 +132,10 @@ class Final(Mine, Ordinary, Wrapper[float], AnotherBase[float], Last[int]): type IsLiteral[T] = ( Literal[True] if ( - (IsSub[T, str] and not IsSub[str, T]) - or (IsSub[T, bytes] and not IsSub[bytes, T]) - or (IsSub[T, bool] and not IsSub[bool, T]) - or (IsSub[T, int] and not IsSub[int, T]) + (IsAssignable[T, str] and not IsAssignable[str, T]) + or (IsAssignable[T, bytes] and not IsAssignable[bytes, T]) + or (IsAssignable[T, bool] and not IsAssignable[bool, T]) + or (IsAssignable[T, int] and not IsAssignable[int, T]) # XXX: enum, None ) else Literal[False] @@ -149,8 +151,8 @@ class Final(Mine, Ordinary, Wrapper[float], AnotherBase[float], Last[int]): for t in Iter[FromUnion[GetType[p]]] # XXX: 'typing.Literal' is not *really* a type... # Maybe we can't do this, which maybe is fine. - # if not IsSubtype[t, Literal] - if not IsSub[IsLiteral[t], Literal[True]] + # if not IsAssignabletype[t, Literal] + if not IsAssignable[IsLiteral[t], Literal[True]] ] ], GetQuals[p], diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 779446c..d88abdb 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -33,10 +33,10 @@ GetSpecialAttr, GetType, GetAnnotations, - IsSub, + IsAssignable, Iter, Length, - Matches, + IsEquivalent, Member, Members, NewProtocol, @@ -72,7 +72,7 @@ class F_int(F[int]): *[ ( Member[GetName[p], OrGotcha[GetType[p]]] - if not IsSub[GetType[p], A] + if not IsAssignable[GetType[p], A] else Member[GetName[p], OrGotcha[MapRecursive[A]]] ) for p in Iter[tuple[*Attrs[A], *Attrs[F_int]]] @@ -512,12 +512,12 @@ def test_eval_getarg_callable_02(): GetType[p] for p in Iter[Members[T]] if ( - IsSub[GetType[p], Callable] - or IsSub[GetType[p], staticmethod] - or IsSub[GetType[p], classmethod] - or IsSub[GetType[p], GenericCallable] + IsAssignable[GetType[p], Callable] + or IsAssignable[GetType[p], staticmethod] + or IsAssignable[GetType[p], classmethod] + or IsAssignable[GetType[p], GenericCallable] ) - and IsSub[Name, GetName[p]] + and IsAssignable[Name, GetName[p]] ], ], tuple, @@ -1089,25 +1089,25 @@ def test_uppercase_never(): def test_never_is(): - d = eval_typing(IsSub[Never, Never]) + d = eval_typing(IsAssignable[Never, Never]) assert d == _BoolLiteral[True] def test_eval_list_is_sub_01(): - d = eval_typing(list[IsSub[int, str]]) + d = eval_typing(list[IsAssignable[int, str]]) assert d == list[_BoolLiteral[False]] - d = eval_typing(list[not IsSub[int, str]]) + d = eval_typing(list[not IsAssignable[int, str]]) assert d == list[_BoolLiteral[True]] def test_matches_01(): - d = eval_typing(Matches[int, int]) + d = eval_typing(IsEquivalent[int, int]) assert d == _BoolLiteral[True] - d = eval_typing(Matches[int, str]) + d = eval_typing(IsEquivalent[int, str]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[str, int]) + d = eval_typing(IsEquivalent[str, int]) assert d == _BoolLiteral[False] @@ -1127,23 +1127,23 @@ class D(A): class X: pass - d = eval_typing(Matches[A, A]) + d = eval_typing(IsEquivalent[A, A]) assert d == _BoolLiteral[True] - d = eval_typing(Matches[A, B]) + d = eval_typing(IsEquivalent[A, B]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[B, A]) + d = eval_typing(IsEquivalent[B, A]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[B, C]) + d = eval_typing(IsEquivalent[B, C]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[C, B]) + d = eval_typing(IsEquivalent[C, B]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[C, D]) + d = eval_typing(IsEquivalent[C, D]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[D, C]) + d = eval_typing(IsEquivalent[D, C]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[A, X]) + d = eval_typing(IsEquivalent[A, X]) assert d == _BoolLiteral[False] @@ -1163,33 +1163,33 @@ class D(A[str]): class X: pass - d = eval_typing(Matches[A[int], A[int]]) + d = eval_typing(IsEquivalent[A[int], A[int]]) assert d == _BoolLiteral[True] - d = eval_typing(Matches[A[int], A[str]]) + d = eval_typing(IsEquivalent[A[int], A[str]]) assert d == _BoolLiteral[True] - d = eval_typing(Matches[A[int], B[int]]) + d = eval_typing(IsEquivalent[A[int], B[int]]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[B[int], A[int]]) + d = eval_typing(IsEquivalent[B[int], A[int]]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[A[int], B[str]]) + d = eval_typing(IsEquivalent[A[int], B[str]]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[B[str], A[int]]) + d = eval_typing(IsEquivalent[B[str], A[int]]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[B[int], C]) + d = eval_typing(IsEquivalent[B[int], C]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[C, B[int]]) + d = eval_typing(IsEquivalent[C, B[int]]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[B[str], C]) + d = eval_typing(IsEquivalent[B[str], C]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[C, B[str]]) + d = eval_typing(IsEquivalent[C, B[str]]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[C, D]) + d = eval_typing(IsEquivalent[C, D]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[D, C]) + d = eval_typing(IsEquivalent[D, C]) assert d == _BoolLiteral[False] - d = eval_typing(Matches[A[int], X]) + d = eval_typing(IsEquivalent[A[int], X]) assert d == _BoolLiteral[False] @@ -1323,7 +1323,7 @@ def test_eval_bool_04(): assert d == _BoolLiteral[False] -type IsIntBool[T] = Bool[IsSub[T, int]] +type IsIntBool[T] = Bool[IsAssignable[T, int]] type IsIntLiteral[T] = Literal[True] if Bool[IsIntBool[T]] else Literal[False] @@ -1397,19 +1397,19 @@ def test_eval_bool_literal_06(): def test_eval_bool_literal_07(): - d = eval_typing(IsSub[_BoolLiteral[True], Literal[True]]) + d = eval_typing(IsAssignable[_BoolLiteral[True], Literal[True]]) assert d == _BoolLiteral[True] - d = eval_typing(IsSub[_BoolLiteral[False], Literal[False]]) + d = eval_typing(IsAssignable[_BoolLiteral[False], Literal[False]]) assert d == _BoolLiteral[True] - d = eval_typing(IsSub[Literal[True], _BoolLiteral[True]]) + d = eval_typing(IsAssignable[Literal[True], _BoolLiteral[True]]) assert d == _BoolLiteral[True] - d = eval_typing(IsSub[Literal[False], _BoolLiteral[False]]) + d = eval_typing(IsAssignable[Literal[False], _BoolLiteral[False]]) assert d == _BoolLiteral[True] - d = eval_typing(Matches[_BoolLiteral[True], Literal[True]]) + d = eval_typing(IsEquivalent[_BoolLiteral[True], Literal[True]]) assert d == _BoolLiteral[True] - d = eval_typing(Matches[_BoolLiteral[False], Literal[False]]) + d = eval_typing(IsEquivalent[_BoolLiteral[False], Literal[False]]) assert d == _BoolLiteral[True] @@ -1481,7 +1481,10 @@ def test_eval_literal_idempotent_01(): def test_is_literal_true_vs_one(): - assert eval_typing(IsSub[Literal[True], Literal[1]]) == _BoolLiteral[False] + assert ( + eval_typing(IsAssignable[Literal[True], Literal[1]]) + == _BoolLiteral[False] + ) def test_callable_to_signature_01(): @@ -1648,7 +1651,7 @@ class AnnoTest: def test_type_eval_annotated_02(): - res = eval_typing(IsSub[GetMemberType[AnnoTest, Literal["a"]], int]) + res = eval_typing(IsAssignable[GetMemberType[AnnoTest, Literal["a"]], int]) assert res == _BoolLiteral[True] diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 38b95a2..9b20a78 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -29,11 +29,11 @@ GetMemberType, GetSpecialAttr, InitField, - IsSub, + IsAssignable, + IsEquivalent, Iter, Length, Lowercase, - Matches, Member, Members, NewProtocol, @@ -235,15 +235,15 @@ def _eval_Iter(tp, *, ctx): # N.B: These handle unions on their own -@type_eval.register_evaluator(IsSub) +@type_eval.register_evaluator(IsAssignable) @_lift_evaluated -def _eval_IsSub(lhs, rhs, *, ctx): +def _eval_IsAssignable(lhs, rhs, *, ctx): return _BoolLiteral[type_eval.issubtype(lhs, rhs)] -@type_eval.register_evaluator(Matches) +@type_eval.register_evaluator(IsEquivalent) @_lift_evaluated -def _eval_Matches(lhs, rhs, *, ctx): +def _eval_IsEquivalent(lhs, rhs, *, ctx): return _BoolLiteral[ type_eval.issubtype(lhs, rhs) and type_eval.issubtype(rhs, lhs) ] diff --git a/typemap/typing.py b/typemap/typing.py index 951f0d6..dd781f0 100644 --- a/typemap/typing.py +++ b/typemap/typing.py @@ -280,12 +280,12 @@ def __bool__(self): @_SpecialForm -def IsSub(self, tps): +def IsAssignable(self, tps): return _BoolGenericAlias(self, tps) @_SpecialForm -def Matches(self, tps): +def IsEquivalent(self, tps): return _BoolGenericAlias(self, tps) From d14644d078923e0cc83f385ae82735c86631c706 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Wed, 4 Feb 2026 18:20:43 -0800 Subject: [PATCH 2/2] fix build --- pep.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep.rst b/pep.rst index 85ca2de..a86b09f 100644 --- a/pep.rst +++ b/pep.rst @@ -1354,7 +1354,7 @@ TODO: Should we do this? 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