subtype 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
subtype/__init__.py ADDED
@@ -0,0 +1,1797 @@
1
+ __all__ = ["isinstance", "subtype"]
2
+ import abc
3
+ import annotationlib
4
+ import ast
5
+ import builtins
6
+ import collections.abc
7
+ import enum
8
+ import functools as ft
9
+ import inspect
10
+ import sys
11
+ import types
12
+ import typing as tp
13
+ import weakref
14
+ from builtins import isinstance as isinstance_
15
+
16
+ _RECURSION_LIMIT = 16
17
+ _TypeParameter: tp.TypeAlias = tp.TypeVar | tp.TypeVarTuple | tp.ParamSpec
18
+ _MemberPairs: tp.TypeAlias = tuple[
19
+ tuple[tuple[inspect.Signature, ...], tuple[inspect.Signature, ...]], ...
20
+ ]
21
+
22
+
23
+ def _is_indeterminate_tuple(tup: type[tuple], /):
24
+ return (
25
+ (tp.get_origin(tup) or tup) is tuple
26
+ and len(args := tp.get_args(tup)) == 2
27
+ and args[-1] is Ellipsis
28
+ )
29
+
30
+
31
+ def _is_unpacked(typx, /) -> bool:
32
+ return getattr(typx, "__unpacked__", False)
33
+
34
+
35
+ def _unpack_tuple_items(typx, /) -> tuple[tp.Any, ...]:
36
+ if tp.get_origin(typx) is tp.Unpack and not tp.is_typeddict(
37
+ tup := tp.get_args(typx)[0]
38
+ ):
39
+ if tp.get_origin(tup) is tuple:
40
+ # Unpack[Tuple[...]] => *tuple[...]
41
+ [typx] = tuple[*tp.get_args(tup)]
42
+ else:
43
+ typx = tup
44
+ if _is_unpacked(typx) and not _is_indeterminate_tuple(typx):
45
+ return tp.get_args(typx)
46
+ return (typx,)
47
+
48
+
49
+ def _tuple_entries(typx: type[tuple], /) -> list[tuple[tp.Any, bool]] | None:
50
+ """Return the element types of typx, each paired with whether it is
51
+ indeterminate, or None if typx unpacks a TypeVarTuple.
52
+ """
53
+ if not hasattr(typx, "__args__"):
54
+ # tuple => tuple[object, ...]
55
+ return [(object, True)]
56
+ if _is_indeterminate_tuple(typx):
57
+ return [(tp.get_args(typx)[0], True)]
58
+ entries = []
59
+ for arg in tp.get_args(typx):
60
+ for x in _unpack_tuple_items(arg):
61
+ if isinstance_(x, tp.TypeVarTuple):
62
+ return
63
+ if _is_unpacked(x) and _is_indeterminate_tuple(x):
64
+ entries.append((tp.get_args(x)[0], True))
65
+ else:
66
+ entries.append((x, False))
67
+ return entries
68
+
69
+
70
+ def _adjust_tuple_entries(
71
+ dst: list[tuple[tp.Any, bool]], src: list[tuple[tp.Any, bool]], /
72
+ ) -> bool:
73
+ """Pack the src entries that match the indeterminate entry of dst into one,
74
+ and return whether dst and src then have the same length.
75
+ """
76
+ dst_i, src_i = (
77
+ next((i for i, (_, indeterminate) in enumerate(x) if indeterminate), -1)
78
+ for x in (dst, src)
79
+ )
80
+ if src_i >= 0:
81
+ if src[src_i][0] is tp.Any:
82
+ # an indeterminate Any expands to the length of dst
83
+ while len(src) < len(dst):
84
+ src.insert(src_i, src[src_i])
85
+ if len(src) > len(dst):
86
+ del src[src_i]
87
+ src_i = -1
88
+ elif dst_i < 0:
89
+ return False
90
+ if dst_i >= 0 and dst[dst_i][0] is tp.Any:
91
+ while len(dst) < len(src):
92
+ dst.insert(dst_i, dst[dst_i])
93
+ n = len(src) - len(dst) + 1
94
+ if dst_i >= 0 and n >= 0 and (src_i < 0 or dst_i <= src_i < dst_i + n):
95
+ packed = [t for t, _ in src[dst_i : dst_i + n]]
96
+ src[dst_i : dst_i + n] = [(tp.Union[*packed] if packed else tp.Any, False)]
97
+ return len(dst) == len(src)
98
+
99
+
100
+ def _get_value_or_forwardref(f: tp.Callable, /):
101
+ return annotationlib.call_evaluate_function(f, annotationlib.Format.FORWARDREF)
102
+
103
+
104
+ def _add_default_params(
105
+ src_param_lst: tuple[_TypeParameter, ...], dst_params: dict[_TypeParameter, tp.Any]
106
+ ):
107
+ dst_params.update(
108
+ (param, _get_value_or_forwardref(param.evaluate_default))
109
+ for param in src_param_lst
110
+ if param.has_default()
111
+ if param.evaluate_default
112
+ )
113
+
114
+
115
+ def _get_dst_params(typx, /):
116
+ src_param_lst = getattr(typx, "__type_params__", ())
117
+ if not src_param_lst:
118
+ return {}
119
+ src_args = tp.get_args(typx)
120
+ dst_params = dict(zip(src_param_lst, src_args))
121
+ if unresolved := src_param_lst[len(src_args) :]:
122
+ _add_default_params(unresolved, dst_params)
123
+ return dst_params
124
+
125
+
126
+ def _make_typevars_bound(
127
+ typx, /, type_params: dict[_TypeParameter, tp.Any] | None = None, recursion_count=0
128
+ ):
129
+ if type_params is None:
130
+ type_params = {}
131
+ if type(typx) is tp.ForwardRef:
132
+ try:
133
+ typx = typx.evaluate(type_params=tuple(type_params))
134
+ except NameError:
135
+ return typx
136
+ if recursion_count > _RECURSION_LIMIT:
137
+ return object
138
+ unpacked = _is_unpacked(typx)
139
+ if args := tp.get_args(typx):
140
+ type_params = _get_dst_params(typx) | type_params
141
+ origin = tp.get_origin(typx) or typx
142
+ if isinstance_(origin, tp.TypeAliasType):
143
+ typx = _make_typevars_bound(
144
+ _get_value_or_forwardref(origin.evaluate_value),
145
+ type_params,
146
+ recursion_count + 1,
147
+ )
148
+ origin = tp.get_origin(typx) or typx
149
+ args = tp.get_args(typx)
150
+ if hasattr(typx, "copy_with"):
151
+ copy_with = typx.copy_with
152
+ elif not hasattr(origin, "__class_getitem__"):
153
+ return origin
154
+ else:
155
+ copy_with = origin.__class_getitem__
156
+ new_args = []
157
+ for arg in args:
158
+ new_arg = _make_typevars_bound(
159
+ (
160
+ type_params.get(arg, arg)
161
+ if isinstance_(arg, _TypeParameter)
162
+ else arg
163
+ ),
164
+ type_params,
165
+ recursion_count + 1,
166
+ )
167
+ new_args.extend(_unpack_tuple_items(new_arg))
168
+ typx = copy_with(tuple(new_args))
169
+ # handle *tuple[...] star-unpack form
170
+ if unpacked and type(typx) is types.GenericAlias and not _is_unpacked(typx):
171
+ typx = next(iter(typx))
172
+ return typx
173
+
174
+
175
+ class _ForwardRefSubscriptTransformer(ast.NodeTransformer):
176
+ def __init__(
177
+ self,
178
+ *,
179
+ globals: tp.Mapping[str, tp.Any] | None = None,
180
+ locals: tp.Mapping[str, tp.Any] | None = None,
181
+ ):
182
+ self._ns = types.MappingProxyType(
183
+ {**builtins.__dict__, **(globals or {}), **(locals or {})}
184
+ )
185
+
186
+ def visit_Subscript(self, node):
187
+ self.generic_visit(node)
188
+ # types like slice, zip, and dict_items are subscriptable in
189
+ # annotations, but do not define __class_getitem__ at runtime.
190
+ if (
191
+ isinstance_(node.value, ast.Name)
192
+ and node.value.id in self._ns
193
+ and not hasattr(self._ns[node.value.id], "__class_getitem__")
194
+ ):
195
+ return ast.Call(
196
+ ast.Name("__generic_alias__", ast.Load()), [node.value, node.slice], []
197
+ )
198
+ return node
199
+
200
+ @classmethod
201
+ def evaluate_forwardref(
202
+ cls,
203
+ forwardref: tp.ForwardRef,
204
+ *,
205
+ globals: dict[str, tp.Any] | None = None,
206
+ locals: tp.Mapping[str, tp.Any] | None = None,
207
+ ):
208
+ if globals is None:
209
+ globals = forwardref.__globals__
210
+ if locals is None:
211
+ locals = {}
212
+ else:
213
+ locals = dict(locals)
214
+ if isinstance_(forwardref.__cell__, dict):
215
+ for cell_name, cell in forwardref.__cell__.items():
216
+ try:
217
+ cell_value = cell.cell_contents
218
+ except ValueError:
219
+ pass
220
+ else:
221
+ locals.setdefault(cell_name, cell_value)
222
+ if forwardref.__extra_names__:
223
+ locals.update(forwardref.__extra_names__)
224
+ locals["__generic_alias__"] = types.GenericAlias
225
+ if (node := forwardref.__ast_node__) is None:
226
+ node = ast.parse(forwardref.__forward_arg__, mode="eval").body
227
+ tree = ast.fix_missing_locations(
228
+ cls(globals=globals, locals=locals).visit(ast.Expression(node))
229
+ )
230
+ code = compile(tree, "<ast>", "eval")
231
+ return eval(code, globals, locals)
232
+
233
+
234
+ def _evaluate(typx: tp.TypeAliasType | tp.ForwardRef, /):
235
+ if isinstance_(typx, tp.ForwardRef):
236
+ return _ForwardRefSubscriptTransformer.evaluate_forwardref(typx)
237
+ if tp.get_args(typx):
238
+ return _make_typevars_bound(typx)
239
+ return typx.__value__
240
+
241
+
242
+ class _SupportsGeneric[T = type[tp.Any]](tp.Protocol):
243
+ __origin__: T
244
+ __args__: tuple[object, ...]
245
+
246
+
247
+ class _AssignFlag(enum.IntFlag):
248
+ DEFAULT = 0
249
+ # src must also accept dst
250
+ INVARIANT = enum.auto()
251
+ # dst and src are swapped
252
+ CONTRAVARIANT = enum.auto()
253
+
254
+
255
+ class _AssignContext(tp.TypedDict, total=False):
256
+ assuming: frozenset[tuple[subtype, tp.Any]]
257
+ assumed_variance: tp.Mapping[type, _AssignFlag]
258
+ recursion_count: int
259
+ seen: set[tuple[int, tp.Any]]
260
+ self_class: tp.Any
261
+
262
+
263
+ class _checker(abc.ABC):
264
+ def __init__(self, typx: _SupportsGeneric, /):
265
+ self.__args__ = typx.__args__
266
+
267
+ @abc.abstractmethod
268
+ def __call__(self, obj: tp.Any, /, **ctx: tp.Unpack[_AssignContext]) -> bool: ...
269
+
270
+
271
+ @ft.cache
272
+ def _builtin_callables():
273
+ import _collections_abc
274
+
275
+ out = {
276
+ k: v
277
+ for k, v in map(
278
+ lambda k, d=(_collections_abc.__dict__ | builtins.__dict__): (k, d[k]),
279
+ _collections_abc.__dict__.keys() ^ builtins.__dict__.keys(),
280
+ )
281
+ if callable(v)
282
+ if hasattr(builtins, k) or not k.startswith("_")
283
+ }
284
+ return types.MappingProxyType(out)
285
+
286
+
287
+ @ft.cache
288
+ def _load_stubs():
289
+ class TypeAliasTransformer(_ForwardRefSubscriptTransformer):
290
+ def visit_AnnAssign(self, node: ast.AnnAssign) -> ast.stmt:
291
+ # check if this is a self-referential TypeAlias
292
+ if (
293
+ isinstance_(node.target, ast.Name)
294
+ and isinstance_(node.annotation, ast.Name)
295
+ and node.annotation.id == "TypeAlias"
296
+ and node.value is not None
297
+ and any(
298
+ isinstance_(n, ast.Name) and n.id == node.target.id
299
+ for n in ast.walk(node.value)
300
+ )
301
+ ):
302
+ # convert to PEP 695 type statement to lazily evaluate
303
+ # the self-reference.
304
+ return ast.copy_location(
305
+ ast.TypeAlias(
306
+ ast.Name(node.target.id, ast.Store()), [], node.value
307
+ ),
308
+ node,
309
+ )
310
+ return node
311
+
312
+ class OverloadCollector(TypeAliasTransformer):
313
+ def __init__(self, module: types.ModuleType, /):
314
+ super().__init__(globals=_builtin_callables())
315
+ self._module = module
316
+ # __builtins__ fallback for the stub definitions _body deletes
317
+ self._runtime = builtins.__dict__ | {"overload": tp.overload} | vars(module)
318
+ self.overloads = None
319
+ self.classes = {}
320
+
321
+ def collect(self, node, /):
322
+ res = ast.fix_missing_locations(self.visit(node))
323
+ name = self._module.__name__
324
+ prev = tp._overload_registry
325
+ try:
326
+ self.overloads = tp._overload_registry.__class__(
327
+ tp._overload_registry.default_factory
328
+ )
329
+ tp._overload_registry = self.overloads
330
+ exec(
331
+ compile(res, "<ast>", "exec", dont_inherit=True),
332
+ {
333
+ "__name__": name,
334
+ "__builtins__": self._runtime,
335
+ "__stub_class__": self._register,
336
+ "__generic_alias__": types.GenericAlias,
337
+ },
338
+ )
339
+ finally:
340
+ tp._overload_registry = prev
341
+ return self.overloads[name]
342
+
343
+ def _register(self, typx: type, /):
344
+ self.classes[typx.__name__] = typx
345
+ return typx
346
+
347
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> ast.ImportFrom:
348
+ if node.module == "typing":
349
+ # type_check_only does not exist at runtime
350
+ node.names = [a for a in node.names if a.name != "type_check_only"]
351
+ return node
352
+
353
+ def _body(self, body: list[ast.stmt], depth: int = 0) -> list[ast.stmt]:
354
+ out: list[ast.stmt] = []
355
+ for s in body:
356
+ if isinstance_(s, ast.If):
357
+ s.body = self._body(s.body, depth)
358
+ s.orelse = self._body(s.orelse, depth) if s.orelse else []
359
+ out.append(s)
360
+ elif isinstance_(s, ast.ClassDef):
361
+ if s.name in self._runtime:
362
+ bases = s.bases
363
+ s.bases, s.keywords, s.decorator_list = [], [], []
364
+ s.body = self._body(s.body, depth + 1)
365
+ if bases and not depth:
366
+ # evaluated once the module no longer binds the
367
+ # stub definitions
368
+ s.body.append(
369
+ ast.AnnAssign(
370
+ ast.Name("__orig_bases__", ast.Store()),
371
+ self.visit(ast.Tuple(bases, ast.Load())),
372
+ None,
373
+ simple=1,
374
+ )
375
+ )
376
+ s.decorator_list = [ast.Name("__stub_class__", ast.Load())]
377
+ out += [s, ast.Delete([ast.Name(s.name, ast.Del())])]
378
+ else:
379
+ s.decorator_list = [
380
+ d
381
+ for d in s.decorator_list
382
+ if not (
383
+ isinstance_(d, ast.Name) and d.id == "type_check_only"
384
+ )
385
+ ]
386
+ out.append(s)
387
+ elif isinstance_(s, (ast.FunctionDef, ast.AsyncFunctionDef)):
388
+ if depth or s.name in self._runtime:
389
+ is_prop = False
390
+ for d in s.decorator_list:
391
+ if isinstance_(d, ast.Name) and d.id in {
392
+ "property",
393
+ "overload",
394
+ }:
395
+ is_prop = d.id == "property"
396
+ break
397
+ if (
398
+ isinstance_(d, ast.Attribute)
399
+ and getattr(d.value, "id", None) == s.name
400
+ ):
401
+ is_prop = True
402
+ break
403
+ else:
404
+ s.decorator_list.append(ast.Name("overload", ast.Load()))
405
+ if not is_prop:
406
+ self.generic_visit(s)
407
+ out.append(s)
408
+ if not depth:
409
+ out.append(ast.Delete([ast.Name(s.name, ast.Del())]))
410
+ else:
411
+ out.append(s)
412
+ elif not depth:
413
+ out.append(self.visit(s))
414
+ return out or [ast.Pass()]
415
+
416
+ def visit_Module(self, node: ast.Module) -> ast.Module:
417
+ node.body = self._body(node.body)
418
+ return node
419
+
420
+ import _collections_abc
421
+ from importlib.machinery import SourceFileLoader
422
+ from importlib.resources import as_file, files
423
+ from importlib.util import module_from_spec, spec_from_loader
424
+
425
+ class PyiLoader(SourceFileLoader):
426
+ def source_to_code(self, data, path, *, _optimize=-1):
427
+ tree = TypeAliasTransformer().visit(ast.parse(data, path))
428
+ return compile(
429
+ ast.fix_missing_locations(tree), path, "exec", dont_inherit=True
430
+ )
431
+
432
+ def path_stats(self, path):
433
+ st = super().path_stats(path)
434
+ from os import stat
435
+
436
+ return {**st, "mtime": max(st["mtime"], stat(__file__).st_mtime)}
437
+
438
+ def runtime_class(module, name):
439
+ owner = vars(module).get(name)
440
+ owner = tp.get_origin(owner) or owner
441
+ return owner if isinstance_(owner, type) else None
442
+
443
+ STUBS = files(__name__) / "typeshed" / "stdlib"
444
+ out = {}
445
+ bases = {}
446
+ with as_file(STUBS / "_typeshed" / "__init__.pyi") as path:
447
+ spec = spec_from_loader(
448
+ "_typeshed", PyiLoader("_typeshed", str(path)), is_package=True
449
+ )
450
+ assert spec is not None
451
+ assert spec.loader is not None
452
+ sys.modules["_typeshed"] = mod = module_from_spec(spec)
453
+ vars(mod)["__generic_alias__"] = types.GenericAlias
454
+ try:
455
+ spec.loader.exec_module(mod)
456
+ for stub, module in (
457
+ ("builtins.pyi", builtins),
458
+ ("_collections_abc.pyi", _collections_abc),
459
+ ("typing.pyi", tp),
460
+ ):
461
+ collector = OverloadCollector(module)
462
+ collected = collector.collect(ast.parse((STUBS / stub).read_bytes()))
463
+ for qualname, ovs in collected.items():
464
+ head, sep, name = qualname.partition(".")
465
+ if not sep:
466
+ owner, name = module, head
467
+ elif (owner := runtime_class(module, head)) is None:
468
+ continue
469
+ out.setdefault(owner, {})[name] = tuple(ovs.values())
470
+ for name, typx in collector.classes.items():
471
+ if (owner := runtime_class(module, name)) is not None:
472
+ bases[owner] = annotationlib.get_annotations(
473
+ typx, format=annotationlib.Format.FORWARDREF
474
+ )["__orig_bases__"]
475
+ finally:
476
+ del sys.modules["_typeshed"]
477
+ overloads = {owner: types.MappingProxyType(d) for owner, d in out.items()}
478
+ return types.MappingProxyType(overloads), types.MappingProxyType(bases)
479
+
480
+
481
+ def _stub_overloads() -> (
482
+ types.MappingProxyType[
483
+ type | types.ModuleType,
484
+ types.MappingProxyType[str, tuple[types.FunctionType, ...]],
485
+ ]
486
+ ):
487
+ """Map runtime classes and modules to their stub overloads, by name."""
488
+ return _load_stubs()[0]
489
+
490
+
491
+ def _stub_bases() -> types.MappingProxyType[type, tuple[tp.Any, ...]]:
492
+ """Map runtime classes to their generic bases in the stubs."""
493
+ return _load_stubs()[1]
494
+
495
+
496
+ @ft.cache
497
+ def _stub_functions() -> (
498
+ types.MappingProxyType[types.FunctionType, tuple[types.FunctionType, ...]]
499
+ ):
500
+ """Map runtime Python functions to their stub overloads."""
501
+ out = {}
502
+ for owner, members in _stub_overloads().items():
503
+ for name, ovs in members.items():
504
+ attr = vars(owner).get(name)
505
+ # unwrap __func__ from a classmethod or staticmethod
506
+ if isinstance_(func := getattr(attr, "__func__", attr), types.FunctionType):
507
+ out[func] = ovs
508
+ return types.MappingProxyType(out)
509
+
510
+
511
+ def _resolve_call(obj, /) -> tuple[tp.Sequence[tp.Callable], bool]:
512
+ """Return the overloads that calling obj calls, and whether the call binds
513
+ their self or cls parameter.
514
+ """
515
+
516
+ def lookup(mro, /, *names: str):
517
+ for typx in mro:
518
+ if (members := _stub_overloads().get(typx)) is not None:
519
+ decls = [members[name] for name in names if name in members]
520
+ else:
521
+ attrs = (
522
+ (name, vars(typx)[name]) for name in names if name in vars(typx)
523
+ )
524
+ decls = [
525
+ (
526
+ _get_member_overloads(typx, name)
527
+ if isinstance_(getattr(a, "__func__", a), types.FunctionType)
528
+ else ()
529
+ )
530
+ for name, a in attrs
531
+ ]
532
+ if decls:
533
+ return max(decls, key=len)
534
+ return ()
535
+
536
+ objtype = type(obj)
537
+ if isinstance_(obj, types.FunctionType) or hasattr(objtype, "__func__"):
538
+ ovs = tp.get_overloads(obj) or _stub_functions().get(
539
+ getattr(obj, "__func__", obj), ()
540
+ )
541
+ return ovs, hasattr(objtype, "__self__")
542
+ if hasattr(objtype, "__self__"):
543
+ # __self__ is None for C static methods, so get the module name
544
+ # or self type from __reduce__ instead.
545
+ match obj.__reduce__():
546
+ case str(name):
547
+ # sys.modules[obj.__module__][name] => function
548
+ members = _stub_overloads().get(sys.modules[obj.__module__], {})
549
+ return members.get(name, ()), False
550
+ case _, (m_self, name):
551
+ # getattr(m_self, name) => unbound method
552
+ if isinstance_(m_self, type):
553
+ # include the metaclass in attribute lookup
554
+ mro = (*m_self.__mro__, *type(m_self).__mro__)
555
+ else:
556
+ mro = type(m_self).__mro__
557
+ # special case for tp_new: __new__ is a C static method
558
+ # where __self__ is m_self
559
+ is_bound = getattr(obj, "__self__", None) is m_self
560
+ return lookup(mro, name), is_bound and name != "__new__"
561
+ if hasattr(objtype, "__objclass__"):
562
+ # unbound C method descriptor
563
+ return lookup(obj.__objclass__.__mro__, obj.__name__), False
564
+ owner = next(typx for typx in objtype.__mro__ if "__call__" in vars(typx))
565
+ call = vars(owner)["__call__"]
566
+ if call is vars(type)["__call__"]:
567
+ # the constructor type.__call__ delegates to
568
+ return lookup(obj.__mro__, "__new__", "__init__"), True
569
+ # binding loses the owner that overload-only declarations are keyed by
570
+ if isinstance_(getattr(call, "__func__", call), types.FunctionType) and (
571
+ ovs := _get_member_overloads(owner, "__call__")
572
+ ):
573
+ return ovs, not isinstance_(ovs[0], staticmethod)
574
+ if hasattr(type(call), "__get__"):
575
+ call = call.__get__(obj, objtype)
576
+ return _resolve_call(call)
577
+
578
+
579
+ @ft.lru_cache
580
+ def _get_signatures(*funcs: tp.Callable, is_bound=False):
581
+ sigs = map(
582
+ ft.partial(
583
+ inspect.signature, annotation_format=annotationlib.Format.FORWARDREF
584
+ ),
585
+ # unwrap __func__ from a classmethod or staticmethod
586
+ (getattr(func, "__func__", func) for func in funcs),
587
+ )
588
+ if is_bound:
589
+ sigs = (sig.replace(parameters=[*sig.parameters.values()][1:]) for sig in sigs)
590
+ return tuple(sigs)
591
+
592
+
593
+ def _get_overloads(obj: tp.Callable, /) -> tuple[inspect.Signature, ...]:
594
+ if not callable(obj):
595
+ raise TypeError
596
+ ovs, is_bound = _resolve_call(obj)
597
+ if ovs:
598
+ sigs = _get_signatures(*ovs, is_bound=is_bound)
599
+ elif isinstance_(obj, (type, types.FunctionType)):
600
+ sigs = _get_signatures(obj)
601
+ else:
602
+ sigs = (
603
+ inspect.signature(obj, annotation_format=annotationlib.Format.FORWARDREF),
604
+ )
605
+ if isinstance_(obj, type):
606
+ # an unannotated or __init__ signature returns Self
607
+ return tuple(
608
+ (
609
+ sig.replace(return_annotation=tp.Self)
610
+ if sig.return_annotation in (None, inspect.Signature.empty)
611
+ else sig
612
+ )
613
+ for sig in sigs
614
+ )
615
+ return sigs
616
+
617
+
618
+ @ft.lru_cache
619
+ def _generic_bases(typx: type, /) -> tuple[tuple[_TypeParameter, ...], tuple]:
620
+ """Return the type parameters and generic bases of typx."""
621
+ if (bases := vars(typx).get("__orig_bases__")) is None:
622
+ bases = _stub_bases().get(typx, typx.__bases__)
623
+ if "_fields" in vars(typx) and builtins.tuple in typx.__bases__:
624
+ # a namedtuple's tuple base has one element per field
625
+ annotations = annotationlib.get_annotations(
626
+ typx, format=annotationlib.Format.FORWARDREF
627
+ )
628
+ tuple_base = tuple.__class_getitem__(
629
+ tuple(annotations.get(f, tp.Any) for f in typx._fields)
630
+ )
631
+ bases = tuple(
632
+ tuple_base if b in (builtins.tuple, tp.NamedTuple) else b for b in bases
633
+ )
634
+ for base in bases:
635
+ if tp.get_origin(base) in (tp.Generic, tp.Protocol):
636
+ return tp.get_args(base), bases
637
+ # only subclasses of _GenericAlias have a __parameters__ attribute
638
+ params = (
639
+ p for b in bases if tp.get_origin(b) for p in getattr(b, "__parameters__", ())
640
+ )
641
+ return tuple(dict.fromkeys(params)), bases
642
+
643
+
644
+ def _is_protocol(typx: type, /) -> bool:
645
+ _, bases = _generic_bases(typx)
646
+ return any((tp.get_origin(b) or b) is tp.Protocol for b in bases)
647
+
648
+
649
+ def _default_arg(param: tp.TypeVar, /, *, erased: bool):
650
+ if erased:
651
+ return tp.Any
652
+ if param.has_default():
653
+ return param.__default__
654
+ if param.__constraints__:
655
+ return tp.Union[param.__constraints__]
656
+ return object if param.__bound__ is None else param.__bound__
657
+
658
+
659
+ def _type_args(
660
+ typx, params: tuple[_TypeParameter, ...], /, *, erased: bool
661
+ ) -> dict[tp.TypeVar, tp.Any] | None:
662
+ """Map the type parameters of typx's origin to the arguments typx passes
663
+ to them, or return None if a parameter is not a TypeVar.
664
+ """
665
+ args = tp.get_args(typx)
666
+ if tp.get_origin(typx) is builtins.tuple and hasattr(typx, "__args__"):
667
+ if (entries := _tuple_entries(typx)) is None:
668
+ return
669
+ # squash tuple type params into a union
670
+ # tuple[str, int] => (str | int,)
671
+ # tuple[()] => (Never,)
672
+ args = (tp.Union[*(t for t, _ in entries)] if entries else tp.Never,)
673
+ subst: dict[tp.TypeVar, tp.Any] = {}
674
+ for i, param in enumerate(params):
675
+ if not isinstance_(param, tp.TypeVar):
676
+ return
677
+ arg = args[i] if i < len(args) else _default_arg(param, erased=erased)
678
+ subst[param] = tp.Any if arg is ... else arg
679
+ return subst
680
+
681
+
682
+ def _substitute(typx, subst: tp.Mapping, /):
683
+ if isinstance_(typx, tp.TypeVar):
684
+ return subst.get(typx, typx)
685
+ if tp.get_origin(typx) and (params := getattr(typx, "__parameters__", ())):
686
+ return typx[*(subst.get(p, p) for p in params)]
687
+ return typx
688
+
689
+
690
+ def _substitute_signature(sig: inspect.Signature, subst: tp.Mapping, /):
691
+ return sig.replace(
692
+ parameters=[
693
+ p.replace(annotation=_substitute(p.annotation, subst))
694
+ for p in sig.parameters.values()
695
+ ],
696
+ return_annotation=_substitute(sig.return_annotation, subst),
697
+ )
698
+
699
+
700
+ def _free_params(typx, /) -> tuple[_TypeParameter, ...]:
701
+ if isinstance_(typx, _TypeParameter):
702
+ return (typx,)
703
+ if tp.get_origin(typx):
704
+ return getattr(typx, "__parameters__", ())
705
+ return ()
706
+
707
+
708
+ def _specialize(src, dst: type, /, *, erased: bool):
709
+ """Return the base of src whose origin is dst, with the type arguments of
710
+ src substituted, or None if no generic base of src leads to dst.
711
+ """
712
+ is_protocol = _is_protocol(dst)
713
+ stack: list[tuple[tp.Any, tp.Mapping]] = [(src, {})]
714
+ while stack:
715
+ typx, subst = stack.pop()
716
+ origin = tp.get_origin(typx) or typx
717
+ if origin is dst:
718
+ return _substitute(typx, subst)
719
+ if not (isinstance_(origin, type) and (is_protocol or issubclass(origin, dst))):
720
+ continue
721
+ params, bases = _generic_bases(origin)
722
+ if (args := _type_args(typx, params, erased=erased)) is None:
723
+ continue
724
+ # map type params of origin to args of src
725
+ subst = {p: _substitute(a, subst) for p, a in args.items()}
726
+ stack += [(base, subst) for base in bases[::-1]]
727
+
728
+
729
+ def _map_args(src, dst: type, /, *, erased: bool) -> dict[tp.TypeVar, tp.Any] | None:
730
+ """Map the type parameters of dst to the arguments src passes to them, or
731
+ return None if no generic base of src leads to dst.
732
+ """
733
+ if (base := _specialize(src, dst, erased=erased)) is not None:
734
+ params, _ = _generic_bases(dst)
735
+ return _type_args(base, params, erased=erased)
736
+
737
+
738
+ def _erase(typx: type, /):
739
+ """Return typx with the type arguments of a value of it at runtime."""
740
+ params, _ = _generic_bases(typx)
741
+ return types.GenericAlias(typx, (tp.Any,) * len(params)) if params else typx
742
+
743
+
744
+ def _get_member_overloads(owner: type, name: str, /) -> tp.Sequence[tp.Callable]:
745
+ func = owner.__dict__[name]
746
+ func = getattr(func, "__func__", func)
747
+ if func is tp._overload_dummy:
748
+ # only exists as overloads; no runtime function
749
+ return list(
750
+ tp._overload_registry.get(owner.__module__, {})
751
+ .get(f"{owner.__qualname__}.{name}", {})
752
+ .values()
753
+ )
754
+ return tp.get_overloads(func)
755
+
756
+
757
+ def _member_signatures(
758
+ typx: type, name: str, /
759
+ ) -> tuple[type, tuple[inspect.Signature, ...]] | None:
760
+ """Return the class in the MRO of typx that declares the method name, with
761
+ the signatures of its overloads bound to an instance, or None if no class
762
+ declares it.
763
+ """
764
+ for owner in typx.__mro__:
765
+ if (members := _stub_overloads().get(owner)) is not None:
766
+ if name in members:
767
+ return owner, _get_signatures(*members[name], is_bound=True)
768
+ continue
769
+ attr = vars(owner).get(name)
770
+ if isinstance_(getattr(attr, "__func__", attr), types.FunctionType):
771
+ ovs = _get_member_overloads(owner, name) or (attr,)
772
+ is_bound = not isinstance_(ovs[0], staticmethod)
773
+ return owner, _get_signatures(*ovs, is_bound=is_bound)
774
+
775
+
776
+ def _protocol_members(typx: type, /) -> set[str]:
777
+ """Return the names of the methods that the protocol typx declares."""
778
+ names = set()
779
+ for base in typx.__mro__:
780
+ if not _is_protocol(base):
781
+ continue
782
+ if (members := _stub_overloads().get(base)) is not None:
783
+ names |= members.keys()
784
+ else:
785
+ names |= {
786
+ name
787
+ for name in base.__protocol_attrs__
788
+ if isinstance_(
789
+ getattr(attr := vars(base).get(name), "__func__", attr),
790
+ types.FunctionType,
791
+ )
792
+ }
793
+ return names
794
+
795
+
796
+ def _assign_type(dst: subtype, src, /, **ctx: tp.Unpack[_AssignContext]) -> bool:
797
+ if src == dst.__subtype_form__:
798
+ return True
799
+ match tp.get_origin(src) or src:
800
+ case None:
801
+ return _assign_type(dst, types.NoneType, **ctx)
802
+ case tp.Any | tp.Never | tp.NoReturn:
803
+ return True
804
+ case tp.Union:
805
+ return all(_assign_type(dst, arg, **ctx) for arg in src.__args__)
806
+ case tp.Literal if len(src.__args__) > 1:
807
+ return all(
808
+ _assign_type(dst, tp.Literal[arg], **ctx) for arg in src.__args__
809
+ )
810
+ case tp.Annotated:
811
+ return _assign_type(dst, src.__origin__, **ctx)
812
+ case tp.TypeAliasType() | tp.ForwardRef():
813
+ if "assuming" not in ctx:
814
+ ctx["assuming"] = frozenset()
815
+ # a self-referential alias is its own cyclic lower bound
816
+ if (dst, src) in ctx["assuming"]:
817
+ return True
818
+ ctx["assuming"] |= {(dst, src)}
819
+ return _assign_type(dst, _evaluate(src), **ctx)
820
+ case tp.NewType():
821
+ return _assign_type(dst, src.__supertype__, **ctx)
822
+ case tp.LiteralString:
823
+ return _assign_type(dst, builtins.str, **ctx)
824
+ case tp.Self:
825
+ return _assign_type(dst, ctx.get("self_class", object), **ctx)
826
+ case tp.TypeVar():
827
+ if src.__constraints__:
828
+ return all(_assign_type(dst, c, **ctx) for c in src.__constraints__)
829
+ return _assign_type(dst, src.__bound__ or object, **ctx)
830
+ case builtins.type if tp.get_args(src):
831
+ [arg] = tp.get_args(src)
832
+ if tp.get_origin(arg) is tp.Union:
833
+ return all(_assign_type(dst, type[a], **ctx) for a in arg.__args__)
834
+ if isinstance_(arg, tp.TypeVar):
835
+ if arg.__constraints__:
836
+ return all(
837
+ _assign_type(dst, type[c], **ctx) for c in arg.__constraints__
838
+ )
839
+ return _assign_type(dst, type[arg.__bound__ or object], **ctx)
840
+ return dst.__subtype_subclasscheck__(src, **ctx)
841
+
842
+
843
+ def _assign_with_variance(
844
+ dst, src, flags: int = _AssignFlag.DEFAULT, /, **ctx: tp.Unpack[_AssignContext]
845
+ ) -> bool:
846
+ if dst == src:
847
+ return True
848
+ if flags & _AssignFlag.CONTRAVARIANT:
849
+ dst, src = src, dst
850
+ return _assign_type(subtype(dst), src, **ctx) and (
851
+ not flags & _AssignFlag.INVARIANT or _assign_type(subtype(src), dst, **ctx)
852
+ )
853
+
854
+
855
+ def _declared_types(typx: type, /) -> list[tuple[tp.Any, _AssignFlag]]:
856
+ """Return the types declared in the members and generic bases of typx,
857
+ each with the variance its assignment requires.
858
+ """
859
+ out = []
860
+ annotations = annotationlib.get_annotations(
861
+ typx, format=annotationlib.Format.FORWARDREF
862
+ )
863
+ for name, form in annotations.items():
864
+ final = False
865
+ while tp.get_origin(form) in (tp.ClassVar, tp.Final):
866
+ final |= tp.get_origin(form) is tp.Final
867
+ [form] = tp.get_args(form)
868
+ dunder = name.startswith("__") and name.endswith("__")
869
+ if final or (name.startswith("_") and not dunder):
870
+ out.append((form, _AssignFlag.DEFAULT))
871
+ else:
872
+ out.append((form, _AssignFlag.INVARIANT))
873
+ for name, attr in vars(typx).items():
874
+ if name in ("__new__", "__init__"):
875
+ continue
876
+ if isinstance_(attr, property):
877
+ funcs = (attr.fget, attr.fset, attr.fdel)
878
+ else:
879
+ funcs = (attr,)
880
+ for func in funcs:
881
+ func = getattr(func, "__func__", func)
882
+ if not isinstance_(func, types.FunctionType):
883
+ continue
884
+ if isinstance_(attr, property):
885
+ ovs = tp.get_overloads(func) or (func,)
886
+ else:
887
+ ovs = _get_member_overloads(typx, name) or (attr,)
888
+ is_bound = not isinstance_(ovs[0], staticmethod)
889
+ for sig in _get_signatures(*ovs, is_bound=is_bound):
890
+ out += [
891
+ (p.annotation, _AssignFlag.CONTRAVARIANT)
892
+ for p in sig.parameters.values()
893
+ ]
894
+ out.append((sig.return_annotation, _AssignFlag.DEFAULT))
895
+ _, bases = _generic_bases(typx)
896
+ out += [
897
+ (base, _AssignFlag.DEFAULT)
898
+ for base in bases
899
+ if tp.get_origin(base) not in (tp.Generic, tp.Protocol, builtins.tuple)
900
+ ]
901
+ return out
902
+
903
+
904
+ def _assign_class_to_self(
905
+ dst: _SupportsGeneric, src: _SupportsGeneric, /, **ctx: tp.Unpack[_AssignContext]
906
+ ) -> bool:
907
+ params, _ = _generic_bases(dst.__origin__)
908
+ dst_subst, src_subst = (dict(zip(params, x.__args__)) for x in (dst, src))
909
+ return all(
910
+ _assign_with_variance(
911
+ _substitute(form, dst_subst), _substitute(form, src_subst), flags, **ctx
912
+ )
913
+ for form, flags in _declared_types(dst.__origin__)
914
+ )
915
+
916
+
917
+ class _VarianceDummy:
918
+ pass
919
+
920
+
921
+ def _infer_variance(
922
+ typx, /, **ctx: tp.Unpack[_AssignContext]
923
+ ) -> dict[tp.TypeVar, _AssignFlag]:
924
+ assumed_variance = ctx.setdefault("assumed_variance", {})
925
+ params, _ = _generic_bases(typx)
926
+ out = {}
927
+ for i, param in enumerate(params):
928
+ if not (isinstance_(param, tp.TypeVar) and param.__infer_variance__):
929
+ continue
930
+ bounds: list[type[tp.Any]] = [object, types.new_class(param.__name__)]
931
+ bound_args = [[_VarianceDummy] * len(params) for _ in range(len(bounds))]
932
+ for x, args in zip(bounds, bound_args):
933
+ args[i] = x
934
+ upper, lower = (typx[*b] for b in bound_args)
935
+ if _assign_class_to_self(
936
+ upper,
937
+ lower,
938
+ assumed_variance={**assumed_variance, typx: _AssignFlag.DEFAULT},
939
+ self_class=typx,
940
+ ):
941
+ out[param] = _AssignFlag.DEFAULT
942
+ elif _assign_class_to_self(
943
+ lower,
944
+ upper,
945
+ assumed_variance={**assumed_variance, typx: _AssignFlag.CONTRAVARIANT},
946
+ self_class=typx,
947
+ ):
948
+ out[param] = _AssignFlag.CONTRAVARIANT
949
+ else:
950
+ out[param] = _AssignFlag.INVARIANT
951
+ return out
952
+
953
+
954
+ def _variance(
955
+ typx: type, param: tp.TypeVar, /, **ctx: tp.Unpack[_AssignContext]
956
+ ) -> _AssignFlag:
957
+ assumed_variance = ctx.setdefault("assumed_variance", {})
958
+ if (assumed := assumed_variance.get(typx)) is not None:
959
+ return assumed
960
+ if param.__covariant__:
961
+ return _AssignFlag.DEFAULT
962
+ if param.__contravariant__:
963
+ return _AssignFlag.CONTRAVARIANT
964
+ if not param.__infer_variance__:
965
+ return _AssignFlag.INVARIANT
966
+ if (variances := _inferred_variance.get(typx)) is None:
967
+ variances = _inferred_variance[typx] = _infer_variance(typx, **ctx)
968
+ return variances[param]
969
+
970
+
971
+ class _literal_instancecheck(_checker):
972
+ def __call__(self, obj, /, **__):
973
+ return any(type(obj) is type(a) and obj == a for a in self.__args__)
974
+
975
+
976
+ class _literal_subclasscheck(_literal_instancecheck):
977
+ def __call__(self, obj, /, **ctx):
978
+ return tp.get_origin(obj) is tp.Literal and all(
979
+ map(ft.partial(super().__call__, **ctx), tp.get_args(obj))
980
+ )
981
+
982
+
983
+ class _nominal_instancecheck(_checker):
984
+ def __init__(self, typx: type, /):
985
+ self.__args__ = (typx,)
986
+
987
+ def __call__(self, obj, /, **__):
988
+ return isinstance_(obj, self.__args__)
989
+
990
+
991
+ class _nominal_subclasscheck(_nominal_instancecheck):
992
+ def __call__(self, obj, /, **ctx):
993
+ if tp.get_origin(obj) is tp.Literal:
994
+ return all(map(ft.partial(super().__call__, **ctx), tp.get_args(obj)))
995
+ return issubclass(tp.get_origin(obj) or obj, self.__args__)
996
+
997
+
998
+ def _assign_tuple(dst, src, /, **ctx: tp.Unpack[_AssignContext]) -> bool:
999
+ origin = tp.get_origin(src) or src
1000
+ if not (isinstance_(origin, type) and issubclass(origin, builtins.tuple)):
1001
+ return False
1002
+ base = _specialize(src, builtins.tuple, erased=False)
1003
+ if (
1004
+ base is None
1005
+ or (src_entries := _tuple_entries(base)) is None
1006
+ or (dst_entries := _tuple_entries(dst)) is None
1007
+ ):
1008
+ return True
1009
+ if not _adjust_tuple_entries(dst_entries, src_entries):
1010
+ return False
1011
+ return all(
1012
+ _assign_with_variance(d, s, **ctx)
1013
+ for (d, _), (s, _) in zip(dst_entries, src_entries)
1014
+ )
1015
+
1016
+
1017
+ class _tuple_instancecheck(_checker):
1018
+ def __init__(self, typx: type[tuple], /):
1019
+ self.__form__ = typx
1020
+
1021
+ def __call__(self, obj, /, **ctx):
1022
+ if not isinstance_(obj, builtins.tuple):
1023
+ return False
1024
+ if (entries := _tuple_entries(self.__form__)) is None:
1025
+ return True
1026
+ [*items] = zip(*entries)
1027
+ if not items:
1028
+ return not obj
1029
+ [[*ts], det] = items
1030
+ i = next((i for i, indeterminate in enumerate(det) if indeterminate), None)
1031
+ if i is not None:
1032
+ ts[i : i + 1] *= len(obj) - len(entries) + 1
1033
+ return len(ts) == len(obj) and all(
1034
+ subtype(t).__subtype_instancecheck__(v, **ctx) for t, v in zip(ts, obj)
1035
+ )
1036
+
1037
+
1038
+ class _tuple_subclasscheck(_tuple_instancecheck):
1039
+ def __call__(self, obj, /, **ctx):
1040
+ return _assign_tuple(self.__form__, obj, **ctx)
1041
+
1042
+
1043
+ def _callable_signature(typx: _SupportsGeneric, /) -> inspect.Signature:
1044
+ params, ret = tp.get_args(typx)
1045
+ if tp.get_origin(params) is tp.Concatenate:
1046
+ *params, tail = tp.get_args(params)
1047
+ elif isinstance_(params, list):
1048
+ tail = None
1049
+ else:
1050
+ params, tail = [], params
1051
+ sig = [
1052
+ inspect.Parameter(f"_{i}", inspect.Parameter.POSITIONAL_ONLY, annotation=t)
1053
+ for i, t in enumerate(params)
1054
+ ]
1055
+ if tail is ...:
1056
+ sig += [
1057
+ inspect.Parameter("args", inspect.Parameter.VAR_POSITIONAL),
1058
+ inspect.Parameter("kwargs", inspect.Parameter.VAR_KEYWORD),
1059
+ ]
1060
+ elif isinstance_(tail, tp.ParamSpec):
1061
+ sig += [
1062
+ inspect.Parameter(
1063
+ "args", inspect.Parameter.VAR_POSITIONAL, annotation=tail.args
1064
+ ),
1065
+ inspect.Parameter(
1066
+ "kwargs", inspect.Parameter.VAR_KEYWORD, annotation=tail.kwargs
1067
+ ),
1068
+ ]
1069
+ return inspect.Signature(sig, return_annotation=ret)
1070
+
1071
+
1072
+ def _assign_signature(
1073
+ dst: inspect.Signature, src: inspect.Signature, /, **ctx: tp.Unpack[_AssignContext]
1074
+ ) -> bool:
1075
+ """Return whether src is assignable to dst, the signature of a Callable form."""
1076
+
1077
+ def annotation(typx, /):
1078
+ return tp.Any if typx is inspect.Parameter.empty else typx
1079
+
1080
+ shared = {
1081
+ p
1082
+ for typx in (
1083
+ dst.return_annotation,
1084
+ *(q.annotation for q in dst.parameters.values()),
1085
+ )
1086
+ for p in _free_params(typx)
1087
+ }
1088
+ solution: dict[_TypeParameter, tp.Any] = {}
1089
+ src_params = [*src.parameters.values()]
1090
+ for dst_param in dst.parameters.values():
1091
+ if dst_param.kind is not inspect.Parameter.POSITIONAL_ONLY:
1092
+ src_params = []
1093
+ break
1094
+ if not src_params or src_params[0].kind > inspect.Parameter.VAR_POSITIONAL:
1095
+ return False
1096
+ if src_params[0].kind is inspect.Parameter.VAR_POSITIONAL:
1097
+ src_param = src_params[0]
1098
+ else:
1099
+ src_param = src_params.pop(0)
1100
+ src_annotation = annotation(src_param.annotation)
1101
+ if not shared.intersection(_free_params(src_annotation)):
1102
+ _solve(src_annotation, dst_param.annotation, solution)
1103
+ if not _assign_with_variance(
1104
+ dst_param.annotation,
1105
+ _substitute(src_annotation, solution),
1106
+ _AssignFlag.CONTRAVARIANT,
1107
+ **ctx,
1108
+ ):
1109
+ return False
1110
+ if not all(
1111
+ p.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
1112
+ or p.default is not inspect.Parameter.empty
1113
+ for p in src_params
1114
+ ):
1115
+ return False
1116
+ if not all(
1117
+ _assign_type(subtype(p), t, **ctx)
1118
+ for p, t in solution.items()
1119
+ if isinstance_(p, tp.TypeVar)
1120
+ ):
1121
+ return False
1122
+ return _assign_with_variance(
1123
+ dst.return_annotation,
1124
+ _substitute(annotation(src.return_annotation), solution),
1125
+ **ctx,
1126
+ )
1127
+
1128
+
1129
+ def _specialize_member(
1130
+ typx, name: str, /, *, erased=False
1131
+ ) -> tuple[inspect.Signature, ...] | None:
1132
+ """Return the signatures of the method name of typx, with the type
1133
+ arguments of typx substituted, or None if no class in its MRO declares it.
1134
+ """
1135
+ if (member := _member_signatures(tp.get_origin(typx) or typx, name)) is None:
1136
+ return
1137
+ owner, sigs = member
1138
+ subst = _map_args(typx, owner, erased=erased) or {}
1139
+ return tuple(_substitute_signature(s, subst) for s in sigs)
1140
+
1141
+
1142
+ def _type_call_signatures(typx, /) -> tuple[inspect.Signature, ...] | None:
1143
+ """Return the signatures of calling a value of typx, or None if a value of
1144
+ typx is not callable.
1145
+ """
1146
+ origin = tp.get_origin(typx) or typx
1147
+ if origin is collections.abc.Callable and tp.get_args(typx):
1148
+ return (_callable_signature(typx),)
1149
+ if origin is builtins.type and tp.get_args(typx):
1150
+ # a value of type[X] calls the constructor of X
1151
+ [arg] = tp.get_args(typx)
1152
+ return _get_overloads(tp.get_origin(arg) or arg)
1153
+ if not (isinstance_(origin, type) and issubclass(origin, collections.abc.Callable)):
1154
+ return
1155
+ if not tp.get_args(typx) and (params := _generic_bases(origin)[0]):
1156
+ typx = types.GenericAlias(
1157
+ origin, tuple(p.__default__ if p.has_default() else p for p in params)
1158
+ )
1159
+ if (sigs := _specialize_member(typx, "__call__")) is not None:
1160
+ return sigs
1161
+ call = inspect.getattr_static(origin, "__call__")
1162
+ ovs, _ = _resolve_call(call)
1163
+ return _get_signatures(*(ovs or (call,)), is_bound=True)
1164
+
1165
+
1166
+ def _self_class(src, /, *, erased=False):
1167
+ """Return the type that Self binds to in the members of src."""
1168
+ if (tp.get_origin(src) or src) is builtins.type and tp.get_args(src):
1169
+ # a value of type[X] is the class X
1170
+ [arg] = tp.get_args(src)
1171
+ return arg
1172
+ return _erase(src) if erased else src
1173
+
1174
+
1175
+ class _callable_instancecheck(_checker):
1176
+ def __init__(self, typx: _SupportsGeneric, /):
1177
+ self.signatures = _type_call_signatures(typx) or ()
1178
+
1179
+ def __call__(self, obj, /, **ctx):
1180
+ if not callable(obj):
1181
+ return False
1182
+ # bind Self to the self class of the callable
1183
+ owner = getattr(obj, "__self__", obj)
1184
+ ctx["self_class"] = owner if isinstance_(owner, type) else type(owner)
1185
+ sigs = _get_overloads(obj)
1186
+ return all(
1187
+ any(_assign_signature(d, s, **ctx) for s in sigs) for d in self.signatures
1188
+ )
1189
+
1190
+
1191
+ class _callable_subclasscheck(_callable_instancecheck):
1192
+ def __call__(self, obj, /, **ctx):
1193
+ if (sigs := _type_call_signatures(obj)) is None:
1194
+ return False
1195
+ ctx["self_class"] = _self_class(obj)
1196
+ return all(
1197
+ any(_assign_signature(d, s, **ctx) for s in sigs) for d in self.signatures
1198
+ )
1199
+
1200
+
1201
+ @ft.lru_cache
1202
+ def _member_pairs(dst, src, /, *, erased=False) -> _MemberPairs | None:
1203
+ """Pair the signatures of each method of the protocol dst with those of the
1204
+ same method of src, with the type arguments of both substituted, or return
1205
+ None if src does not declare one of them.
1206
+ """
1207
+ origin = tp.get_origin(src) or src
1208
+ pairs = []
1209
+ for name in _protocol_members(tp.get_origin(dst)):
1210
+ dst_sigs = _specialize_member(dst, name)
1211
+ if name == "__call__":
1212
+ # Callable[...] and type[X] are called through their signature
1213
+ # and constructor, not through a __call__ member
1214
+ src_sigs = _type_call_signatures(_erase(src) if erased else src)
1215
+ else:
1216
+ src_sigs = _specialize_member(src, name, erased=erased)
1217
+ if not isinstance_(origin, type) or dst_sigs is None or src_sigs is None:
1218
+ return
1219
+ pairs.append((dst_sigs, src_sigs))
1220
+ return tuple(pairs)
1221
+
1222
+
1223
+ def _probe(check: tp.Callable[[tp.Any, type], bool], obj, dst: type, /) -> bool:
1224
+ """Return False if dst is a runtime-checkable protocol that obj does not
1225
+ satisfy, which the runtime decides by member names alone.
1226
+ """
1227
+ if not _is_protocol(dst):
1228
+ return True
1229
+ try:
1230
+ return check(obj, dst)
1231
+ except TypeError:
1232
+ return True
1233
+
1234
+
1235
+ @ft.lru_cache
1236
+ def _issubclass(src, dst: type, /) -> bool:
1237
+ try:
1238
+ return issubclass(tp.get_origin(src) or src, dst)
1239
+ except TypeError:
1240
+ # dst is a protocol that is not runtime-checkable
1241
+ return False
1242
+
1243
+
1244
+ def _assign_protocol(
1245
+ dst: _SupportsGeneric,
1246
+ src,
1247
+ pairs: _MemberPairs,
1248
+ /,
1249
+ *,
1250
+ erased=False,
1251
+ **ctx: tp.Unpack[_AssignContext],
1252
+ ) -> bool:
1253
+ if "assuming" not in ctx:
1254
+ ctx["assuming"] = frozenset()
1255
+ # a self-referential member is its own cyclic lower bound.
1256
+ # e.g., the stub for Iterator.__iter__ returns itself:
1257
+ #
1258
+ # __iter__(self) -> Iterator[_T_co]: ...
1259
+ #
1260
+ if (item := (subtype(dst), src)) in ctx["assuming"]:
1261
+ return True
1262
+ ctx["assuming"] |= {item}
1263
+ ctx["self_class"] = _self_class(src, erased=erased)
1264
+ return all(
1265
+ any(_assign_signature(d, s, **ctx) for s in src_sigs)
1266
+ for dst_sigs, src_sigs in pairs
1267
+ for d in dst_sigs
1268
+ )
1269
+
1270
+
1271
+ def _assign_generic(
1272
+ dst: _SupportsGeneric, src, /, *, erased=False, **ctx: tp.Unpack[_AssignContext]
1273
+ ):
1274
+ if not _probe(issubclass, tp.get_origin(src) or src, dst.__origin__):
1275
+ return False
1276
+ src_args = _map_args(src, dst.__origin__, erased=erased)
1277
+ if (
1278
+ src_args is None
1279
+ and _is_protocol(dst.__origin__)
1280
+ and (pairs := _member_pairs(dst, src, erased=erased)) is not None
1281
+ ):
1282
+ return _assign_protocol(dst, src, pairs, erased=erased, **ctx)
1283
+ if not _issubclass(src, dst.__origin__):
1284
+ return False
1285
+ dst_args = _map_args(dst, dst.__origin__, erased=False)
1286
+ if src_args is None or dst_args is None:
1287
+ return True
1288
+ return all(
1289
+ _assign_with_variance(
1290
+ dst_arg, src_args[p], _variance(dst.__origin__, p, **ctx), **ctx
1291
+ )
1292
+ for p, dst_arg in dst_args.items()
1293
+ )
1294
+
1295
+
1296
+ def _solve_signature(
1297
+ dst: inspect.Signature,
1298
+ src: tp.Iterable[inspect.Signature],
1299
+ solution: dict[_TypeParameter, tp.Any],
1300
+ /,
1301
+ *,
1302
+ self_class: tp.Any = object,
1303
+ assuming: frozenset = frozenset(),
1304
+ ) -> bool:
1305
+ """Bind the type parameters in the return type of dst to the return type of
1306
+ the first signature in src whose parameters accept those of dst.
1307
+ """
1308
+ # the parameters of dst select the signature but bind nothing
1309
+ unbound = {
1310
+ p: tp.Any for q in dst.parameters.values() for p in _free_params(q.annotation)
1311
+ }
1312
+ selector = _substitute_signature(dst, unbound).replace(return_annotation=tp.Any)
1313
+ for sig in src:
1314
+ if _assign_signature(selector, sig, self_class=self_class):
1315
+ ret = sig.return_annotation
1316
+ if ret is inspect.Signature.empty:
1317
+ ret = tp.Any
1318
+ elif ret is tp.Self:
1319
+ ret = self_class
1320
+ return _solve(dst.return_annotation, ret, solution, assuming=assuming)
1321
+ return False
1322
+
1323
+
1324
+ def _solve(
1325
+ dst,
1326
+ src,
1327
+ solution: dict[_TypeParameter, tp.Any],
1328
+ /,
1329
+ *,
1330
+ erased=False,
1331
+ assuming: frozenset = frozenset(),
1332
+ ) -> bool:
1333
+ """Bind the type parameters in dst to the types src passes to them, and
1334
+ return whether src is assignable to dst.
1335
+ """
1336
+ if isinstance_(dst, tp.TypeVar):
1337
+ solution[dst] = tp.Union[solution[dst], src] if dst in solution else src
1338
+ return True
1339
+ if not (params := _free_params(dst)):
1340
+ return _assign_type(subtype(dst), src, self_class=src)
1341
+ if src is tp.Any:
1342
+ for p in params:
1343
+ solution.setdefault(p, tp.Any)
1344
+ return True
1345
+ origin = tp.get_origin(dst)
1346
+ if origin is tp.Union:
1347
+ fixed = [a for a in dst.__args__ if not _free_params(a)]
1348
+ # a bare TypeVar accepts any member of src, so it is tried last
1349
+ free = sorted(
1350
+ (a for a in dst.__args__ if _free_params(a)),
1351
+ key=lambda a: isinstance_(a, tp.TypeVar),
1352
+ )
1353
+ for s in src.__args__ if tp.get_origin(src) is tp.Union else (src,):
1354
+ if any(_assign_type(subtype(f), s) for f in fixed):
1355
+ continue
1356
+ # a member of dst selects s by its erasure, which binds nothing
1357
+ member = next(
1358
+ (
1359
+ a
1360
+ for a in free
1361
+ if _assign_type(
1362
+ subtype(_substitute(a, dict.fromkeys(_free_params(a), tp.Any))),
1363
+ s,
1364
+ )
1365
+ ),
1366
+ None,
1367
+ )
1368
+ if member is None or not _solve(member, s, solution, assuming=assuming):
1369
+ return False
1370
+ return True
1371
+ if tp.get_origin(src) is tp.Union:
1372
+ return all(_solve(dst, s, solution, assuming=assuming) for s in src.__args__)
1373
+ if not isinstance_(origin, type):
1374
+ return False
1375
+ # a self-referential member is its own cyclic lower bound.
1376
+ if (dst, src) in assuming:
1377
+ return True
1378
+ assuming |= {(dst, src)}
1379
+ if (base := _specialize(src, origin, erased=erased)) is not None:
1380
+ params, _ = _generic_bases(origin)
1381
+ src_args = _type_args(base, params, erased=erased)
1382
+ dst_args = _type_args(dst, params, erased=False)
1383
+ if src_args is None or dst_args is None:
1384
+ return True
1385
+ return all(
1386
+ _solve(a, src_args[p], solution, assuming=assuming)
1387
+ for p, a in dst_args.items()
1388
+ )
1389
+ if not _is_protocol(origin) or not _probe(
1390
+ issubclass, tp.get_origin(src) or src, origin
1391
+ ):
1392
+ return False
1393
+ if (pairs := _member_pairs(dst, src, erased=erased)) is None:
1394
+ return _issubclass(src, origin)
1395
+ self_class = _self_class(src, erased=erased)
1396
+ return all(
1397
+ _solve_signature(
1398
+ d, src_sigs, solution, self_class=self_class, assuming=assuming
1399
+ )
1400
+ for dst_sigs, src_sigs in pairs
1401
+ for d in dst_sigs
1402
+ )
1403
+
1404
+
1405
+ def _solve_value(dst, obj, solution: dict[_TypeParameter, tp.Any], /) -> bool:
1406
+ """Bind the type parameters in dst to the types obj passes to them, and
1407
+ return whether obj is an instance of dst.
1408
+ """
1409
+ if not _free_params(dst):
1410
+ return isinstance_(obj, subtype(dst))
1411
+ if (tp.get_origin(dst) or dst) is collections.abc.Callable:
1412
+ if not callable(obj):
1413
+ return False
1414
+ try:
1415
+ sigs = _get_overloads(obj)
1416
+ except ValueError:
1417
+ return True
1418
+ owner = getattr(obj, "__self__", obj)
1419
+ return _solve_signature(
1420
+ _callable_signature(dst),
1421
+ sigs,
1422
+ solution,
1423
+ self_class=owner if isinstance_(owner, type) else type(owner),
1424
+ )
1425
+ if (src := _infer_type(obj)) is not None:
1426
+ ok, bound = _solve_cached(dst, src, frozenset(solution.items()))
1427
+ else:
1428
+ ok, bound = _solve_cached(
1429
+ dst, type(obj), frozenset(solution.items()), erased=True
1430
+ )
1431
+ params, _ = _generic_bases(type(obj))
1432
+ if (
1433
+ ok
1434
+ and params
1435
+ and isinstance_(obj, collections.abc.Collection)
1436
+ and (args := _map_args(dst, collections.abc.Iterable, erased=False))
1437
+ ):
1438
+ # dst is Iterable and obj is a Collection, so can be walked
1439
+ # to obtain runtime types without exhausting the iterable.
1440
+ [item] = args.values()
1441
+ bound = frozenset(solution.items())
1442
+ for t in set(map(type, obj)):
1443
+ ok, bound = _solve_cached(item, t, bound, erased=True)
1444
+ if not ok:
1445
+ return False
1446
+ if ok:
1447
+ solution.update(bound)
1448
+ return ok
1449
+
1450
+
1451
+ @ft.lru_cache
1452
+ def _solve_cached(dst, src, bound: frozenset, /, *, erased=False):
1453
+ """Return the result of _solve and the solution it extends bound to."""
1454
+ solution = dict(bound)
1455
+ res = _solve(dst, src, solution, erased=erased)
1456
+ return res, frozenset(solution.items())
1457
+
1458
+
1459
+ def _infer_type(obj, /):
1460
+ """Return the type of obj with the type arguments it was created with, or
1461
+ None if they are unknown.
1462
+ """
1463
+ if (src := getattr(obj, "__dict__", {}).get("__orig_class__")) is not None:
1464
+ return src
1465
+ try:
1466
+ reduced = obj.__reduce__()
1467
+ except TypeError:
1468
+ return
1469
+ # obj is equivalent to the result of calling func with args
1470
+ match reduced:
1471
+ case (func, tuple(args), *_) if callable(func):
1472
+ pass
1473
+ case _:
1474
+ return
1475
+ try:
1476
+ sigs = _get_overloads(func)
1477
+ except ValueError:
1478
+ return
1479
+ for sig in sigs:
1480
+ try:
1481
+ bound = sig.bind(*args)
1482
+ except TypeError:
1483
+ continue
1484
+ solution = {}
1485
+ params = ((sig.parameters[k], v) for k, v in bound.arguments.items())
1486
+ if not all(
1487
+ _solve_value(p.annotation, v, solution)
1488
+ for p, value in params
1489
+ if p.annotation is not p.empty
1490
+ for v in (value if p.kind is p.VAR_POSITIONAL else (value,))
1491
+ ):
1492
+ continue
1493
+ ret = sig.return_annotation
1494
+ if ret is sig.empty:
1495
+ return
1496
+ if ret is tp.Self:
1497
+ if not isinstance_(func, type):
1498
+ return
1499
+ type_params, _ = _generic_bases(func)
1500
+ ret = types.GenericAlias(func, type_params) if type_params else func
1501
+ return _substitute(ret, {p: solution.get(p, tp.Any) for p in _free_params(ret)})
1502
+
1503
+
1504
+ class _generic_instancecheck(_checker):
1505
+ def __init__(self, typx: _SupportsGeneric, /):
1506
+ self.__form__ = typx
1507
+ self.call = (
1508
+ _callable_instancecheck(typx)
1509
+ if "__call__" in _protocol_members(typx.__origin__)
1510
+ else None
1511
+ )
1512
+
1513
+ def __call__(self, obj, /, **ctx):
1514
+ if not _probe(isinstance_, obj, self.__form__.__origin__):
1515
+ return False
1516
+ # _assign_generic only sees type(obj), whose __call__ is type.__call__
1517
+ # for a class and FunctionType.__call__ for a function
1518
+ if not (self.call is None or self.call(obj, **ctx)):
1519
+ return False
1520
+ if (src := _infer_type(obj)) is not None:
1521
+ return _assign_generic(self.__form__, src, **ctx)
1522
+ if not _assign_generic(self.__form__, type(obj), erased=True, **ctx):
1523
+ return False
1524
+ getitem = _specialize_member(self.__form__, "__getitem__") or ()
1525
+ if getitem and callable(getattr(obj, "keys", None)):
1526
+ kts = [
1527
+ a
1528
+ for s in _specialize_member(self.__form__, "keys") or ()
1529
+ for a in (
1530
+ _map_args(
1531
+ s.return_annotation, collections.abc.Iterable, erased=False
1532
+ )
1533
+ or {}
1534
+ ).values()
1535
+ ]
1536
+ kt = tp.Union[*kts] if kts else object
1537
+ vt = tp.Union[
1538
+ *(
1539
+ (
1540
+ tp.Any
1541
+ if s.return_annotation is inspect.Signature.empty
1542
+ else s.return_annotation
1543
+ )
1544
+ for s in getitem
1545
+ )
1546
+ ]
1547
+ d = obj if isinstance_(obj, collections.abc.Mapping) else {**obj}
1548
+ return all(
1549
+ subtype(collections.abc.Collection[t]).__subtype_instancecheck__(
1550
+ xs, **ctx
1551
+ )
1552
+ for t, xs in zip((kt, vt), (d.keys(), d.values()))
1553
+ )
1554
+ if rets := [
1555
+ (
1556
+ tp.Any
1557
+ if s.return_annotation is inspect.Signature.empty
1558
+ else s.return_annotation
1559
+ )
1560
+ for s in getitem
1561
+ if (p := next(iter(s.parameters.values()), None)) is not None
1562
+ and (
1563
+ p.annotation is inspect.Parameter.empty
1564
+ or _assign_type(subtype(p.annotation), int)
1565
+ )
1566
+ ]:
1567
+ item = subtype(tp.Union[*rets])
1568
+ elif isinstance_(obj, collections.abc.Collection) and (
1569
+ args := _map_args(self.__form__, collections.abc.Iterable, erased=False)
1570
+ ):
1571
+ [item] = map(subtype, args.values())
1572
+ else:
1573
+ return True
1574
+ ts = set(map(type, obj))
1575
+ nested = {
1576
+ t
1577
+ for t in ts
1578
+ if _issubclass(t, collections.abc.Collection) and _generic_bases(t)[0]
1579
+ }
1580
+ # the check of each nested item covers its type
1581
+ if not all(_assign_type(item, _erase(t), **ctx) for t in ts - nested):
1582
+ return False
1583
+ recursion_count = ctx.setdefault("recursion_count", 0)
1584
+ if (seen := ctx.get("seen")) is None:
1585
+ seen = ctx["seen"] = set()
1586
+ key = (id(obj), self.__form__)
1587
+ # a cycle has occurred, and is assignable to itself.
1588
+ if not nested or recursion_count > _RECURSION_LIMIT or key in seen:
1589
+ return True
1590
+ seen.add(key)
1591
+ try:
1592
+ return all(
1593
+ item.__subtype_instancecheck__(
1594
+ x,
1595
+ **(ctx | {"self_class": t, "recursion_count": recursion_count + 1}),
1596
+ )
1597
+ for x in obj
1598
+ if (t := type(x)) in nested
1599
+ )
1600
+ finally:
1601
+ seen.discard(key)
1602
+
1603
+
1604
+ class _generic_subclasscheck(_generic_instancecheck):
1605
+ def __call__(self, obj, /, **ctx):
1606
+ if tp.get_origin(obj) is tp.Literal:
1607
+ return all(map(ft.partial(super().__call__, **ctx), tp.get_args(obj)))
1608
+ return _assign_generic(self.__form__, obj, **ctx)
1609
+
1610
+
1611
+ class _type_instancecheck(_checker):
1612
+ def __init__(self, typx: _SupportsGeneric, /):
1613
+ [arg] = typx.__args__
1614
+ self.__form__ = subtype(arg)
1615
+
1616
+ def __call__(self, obj, /, **ctx):
1617
+ return isinstance_(obj, type) and _assign_type(self.__form__, obj, **ctx)
1618
+
1619
+
1620
+ class _type_subclasscheck(_type_instancecheck):
1621
+ def __call__(self, obj, /, **ctx):
1622
+ origin = tp.get_origin(obj) or obj
1623
+ if origin is builtins.type:
1624
+ [arg] = tp.get_args(obj) or [tp.Any]
1625
+ elif isinstance_(origin, type) and issubclass(origin, builtins.type):
1626
+ arg = object
1627
+ else:
1628
+ return False
1629
+ return _assign_type(self.__form__, arg, **ctx)
1630
+
1631
+
1632
+ class _typevar_instancecheck(_checker):
1633
+ def __init__(self, typx: tp.TypeVar, /):
1634
+ self.__form__ = typx
1635
+
1636
+ def bounds(self) -> tuple[tp.Any, ...]:
1637
+ return self.__form__.__constraints__ or (self.__form__.__bound__ or object,)
1638
+
1639
+ def __call__(self, obj, /, **ctx):
1640
+ return any(
1641
+ subtype(t).__subtype_instancecheck__(obj, **ctx) for t in self.bounds()
1642
+ )
1643
+
1644
+
1645
+ class _typevar_subclasscheck(_typevar_instancecheck):
1646
+ def __call__(self, obj, /, **ctx):
1647
+ return any(_assign_type(subtype(t), obj, **ctx) for t in self.bounds())
1648
+
1649
+
1650
+ _subtypes = weakref.WeakValueDictionary()
1651
+ _inferred_variance = weakref.WeakKeyDictionary()
1652
+
1653
+
1654
+ class subtype(type):
1655
+ __subtype_form__: tp.Any
1656
+ __subtype_instancecheck__: _checker
1657
+ __subtype_subclasscheck__: _checker
1658
+
1659
+ @tp.no_type_check
1660
+ @staticmethod
1661
+ def __new__(mcls, typx, /):
1662
+ if isinstance_(typx, mcls):
1663
+ if type(typx) is mcls:
1664
+ return typx
1665
+ typx = typx.__subtype_form__
1666
+ # ParamSpecArgs and ParamSpecKwargs are not hashable
1667
+ if not callable(getattr(type(typx), "__hash__", None)):
1668
+ key = type(typx), tp.get_origin(typx)
1669
+ else:
1670
+ key = typx
1671
+ if (cls := _subtypes.get(key)) is not None:
1672
+ return cls
1673
+
1674
+ i_check = "__subtype_instancecheck__"
1675
+ s_check = "__subtype_subclasscheck__"
1676
+ namespace = {"__subtype_form__": typx}
1677
+ match tp.get_origin(typx) or typx:
1678
+ case None:
1679
+ return mcls(types.NoneType)
1680
+ case tp.Annotated:
1681
+ return mcls(typx.__origin__)
1682
+ case tp.Any:
1683
+ namespace.update(
1684
+ dict.fromkeys((i_check, s_check), lambda _, **__: True)
1685
+ )
1686
+ case tp.Never | tp.NoReturn:
1687
+ namespace.update(
1688
+ dict.fromkeys((i_check, s_check), lambda _, **__: False)
1689
+ )
1690
+ case tp.Literal:
1691
+ namespace[i_check] = _literal_instancecheck(typx)
1692
+ namespace[s_check] = _literal_subclasscheck(typx)
1693
+ case tp.Union:
1694
+ args = tuple(getattr(a, "__subtype_form__", a) for a in typx.__args__)
1695
+ if args != typx.__args__:
1696
+ return mcls(tp.Union[args])
1697
+ members = tuple(map(mcls, typx.__args__))
1698
+ namespace[i_check] = lambda obj, **ctx: any(
1699
+ m.__subtype_instancecheck__(obj, **ctx) for m in members
1700
+ )
1701
+ namespace[s_check] = lambda src, **ctx: any(
1702
+ _assign_type(m, src, **ctx) for m in members
1703
+ )
1704
+ case tp.TypeAliasType() | tp.ForwardRef():
1705
+ # resolve on first check, so recursive references terminate
1706
+ resolved = ft.cache(lambda: mcls(_evaluate(typx)))
1707
+ namespace[i_check] = lambda obj, **ctx: (
1708
+ resolved().__subtype_instancecheck__(obj, **ctx)
1709
+ )
1710
+ namespace[s_check] = lambda src, **ctx: _assign_type(
1711
+ resolved(), src, **ctx
1712
+ )
1713
+ case tp.Self: # type: ignore
1714
+ namespace[i_check] = lambda obj, **ctx: mcls(
1715
+ ctx.get("self_class", tp.Any)
1716
+ ).__subtype_instancecheck__(obj, **ctx)
1717
+ namespace[s_check] = lambda src, **ctx: _assign_type(
1718
+ mcls(ctx.get("self_class", tp.Any)), src, **ctx
1719
+ )
1720
+ case tp.NewType():
1721
+ supertype = mcls(typx.__supertype__)
1722
+ namespace[i_check] = lambda obj, **ctx: (
1723
+ supertype.__subtype_instancecheck__(obj, **ctx)
1724
+ )
1725
+ # _assign_type admits typx and the NewTypes derived from it
1726
+ namespace[s_check] = lambda _, **__: False
1727
+ # tuple[()] has no arguments but is not the bare tuple class
1728
+ case builtins.tuple if hasattr(typx, "__args__"):
1729
+ namespace[i_check] = _tuple_instancecheck(typx)
1730
+ namespace[s_check] = _tuple_subclasscheck(typx)
1731
+ case type() as origin if _is_protocol(origin) and not tp.get_args(typx):
1732
+ alias = types.GenericAlias(origin, ())
1733
+ namespace[i_check] = _generic_instancecheck(alias)
1734
+ namespace[s_check] = _generic_subclasscheck(alias)
1735
+ case type() if not tp.get_args(typx):
1736
+ namespace[i_check] = _nominal_instancecheck(typx)
1737
+ namespace[s_check] = _nominal_subclasscheck(typx)
1738
+ case collections.abc.Callable:
1739
+ namespace[i_check] = _callable_instancecheck(typx)
1740
+ namespace[s_check] = _callable_subclasscheck(typx)
1741
+ case builtins.type:
1742
+ namespace[i_check] = _type_instancecheck(typx)
1743
+ namespace[s_check] = _type_subclasscheck(typx)
1744
+ case type():
1745
+ namespace[i_check] = _generic_instancecheck(typx)
1746
+ namespace[s_check] = _generic_subclasscheck(typx)
1747
+ case tp.TypeVar():
1748
+ namespace[i_check] = _typevar_instancecheck(typx)
1749
+ namespace[s_check] = _typevar_subclasscheck(typx)
1750
+ # the origin of P.args / P.kwargs is a ParamSpec reference.
1751
+ # key is tuple[type[_], P] here, since typx is not hashable.
1752
+ case tp.ParamSpec() if key is not typx:
1753
+ namespace.update(
1754
+ dict.fromkeys((i_check, s_check), lambda _, **__: True)
1755
+ )
1756
+ # TODO: implement these
1757
+ case str() | tp.LiteralString | tp.TypeGuard | tp.TypeIs:
1758
+ raise NotImplementedError
1759
+ case _:
1760
+ raise TypeError
1761
+ return _subtypes.setdefault(
1762
+ key, super().__new__(mcls, mcls.__name__, (), namespace)
1763
+ )
1764
+
1765
+ def __repr__(cls):
1766
+ return "{0.__name__}({0.__subtype_form__})".format(cls)
1767
+
1768
+ def __instancecheck__(cls, obj, /):
1769
+ return cls.__subtype_instancecheck__(
1770
+ obj,
1771
+ self_class=getattr(obj, "__dict__", {}).get("__orig_class__", type(obj)),
1772
+ )
1773
+
1774
+ def __subclasscheck__(cls, src, /):
1775
+ return _assign_type(cls, src, self_class=src)
1776
+
1777
+ @tp.final
1778
+ def __call__(cls) -> tp.Never:
1779
+ raise TypeError(f"cannot instantiate {type(cls).__qualname__} objects")
1780
+
1781
+ def __or__(cls, other, /):
1782
+ if isinstance_(other, mcls := type(cls)):
1783
+ return mcls(cls.__subtype_form__ | other.__subtype_form__)
1784
+ try:
1785
+ return mcls(cls.__subtype_form__ | other)
1786
+ except NotImplementedError, TypeError:
1787
+ return NotImplemented
1788
+
1789
+ def __ror__(cls, other, /):
1790
+ try:
1791
+ return cls.__class__(other | cls.__subtype_form__)
1792
+ except NotImplementedError, TypeError:
1793
+ return NotImplemented
1794
+
1795
+
1796
+ def isinstance(obj: object, typx: tp.Any, /) -> bool:
1797
+ return isinstance_(obj, subtype(typx))