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 +1797 -0
- subtype/__init__.pyi +18 -0
- subtype-0.1.0.dist-info/METADATA +96 -0
- subtype-0.1.0.dist-info/RECORD +6 -0
- subtype-0.1.0.dist-info/WHEEL +5 -0
- subtype-0.1.0.dist-info/top_level.txt +1 -0
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))
|