python-constricter 0.4.1__cp312-cp312-win32.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.
Files changed (261) hide show
  1. constricter/__init__.py +55 -0
  2. constricter/__main__.py +26 -0
  3. constricter/_version.py +4 -0
  4. constricter/cli/__init__.py +2 -0
  5. constricter/cli/baseline.py +107 -0
  6. constricter/cli/collecting.py +54 -0
  7. constricter/cli/command.py +547 -0
  8. constricter/cli/config.py +322 -0
  9. constricter/cli/explain.py +104 -0
  10. constricter/cli/lsp/__init__.py +2 -0
  11. constricter/cli/lsp/connection.py +475 -0
  12. constricter/cli/lsp/edits.py +188 -0
  13. constricter/cli/lsp/guard.py +133 -0
  14. constricter/cli/lsp/hints.py +391 -0
  15. constricter/cli/lsp/protocol.py +192 -0
  16. constricter/cli/lsp/remembered.py +81 -0
  17. constricter/cli/lsp/stored.py +200 -0
  18. constricter/cli/options.py +799 -0
  19. constricter/cli/paths.py +151 -0
  20. constricter/cli/reading.py +36 -0
  21. constricter/cli/report.py +397 -0
  22. constricter/cli/rounds.py +328 -0
  23. constricter/cli/runs.py +90 -0
  24. constricter/cli/schedule.py +463 -0
  25. constricter/cli/traced.py +401 -0
  26. constricter/cli/workers.py +286 -0
  27. constricter/empty.py +66 -0
  28. constricter/fix/__init__.cp312-win32.pyd +0 -0
  29. constricter/fix/__init__.py +2 -0
  30. constricter/fix/core/__init__.cp312-win32.pyd +0 -0
  31. constricter/fix/core/__init__.py +2 -0
  32. constricter/fix/core/asked.cp312-win32.pyd +0 -0
  33. constricter/fix/core/asked.py +56 -0
  34. constricter/fix/core/fixes.cp312-win32.pyd +0 -0
  35. constricter/fix/core/fixes.py +317 -0
  36. constricter/fix/core/imports.cp312-win32.pyd +0 -0
  37. constricter/fix/core/imports.py +580 -0
  38. constricter/fix/core/inherited.cp312-win32.pyd +0 -0
  39. constricter/fix/core/inherited.py +311 -0
  40. constricter/fix/core/known.cp312-win32.pyd +0 -0
  41. constricter/fix/core/known.py +730 -0
  42. constricter/fix/core/signatures.cp312-win32.pyd +0 -0
  43. constricter/fix/core/signatures.py +132 -0
  44. constricter/fix/index/__init__.cp312-win32.pyd +0 -0
  45. constricter/fix/index/__init__.py +2 -0
  46. constricter/fix/index/awaits.cp312-win32.pyd +0 -0
  47. constricter/fix/index/awaits.py +90 -0
  48. constricter/fix/index/beyond.cp312-win32.pyd +0 -0
  49. constricter/fix/index/beyond.py +149 -0
  50. constricter/fix/index/callers.cp312-win32.pyd +0 -0
  51. constricter/fix/index/callers.py +155 -0
  52. constricter/fix/index/crossed.cp312-win32.pyd +0 -0
  53. constricter/fix/index/crossed.py +327 -0
  54. constricter/fix/index/decorated.cp312-win32.pyd +0 -0
  55. constricter/fix/index/decorated.py +140 -0
  56. constricter/fix/index/fixtures.cp312-win32.pyd +0 -0
  57. constricter/fix/index/fixtures.py +176 -0
  58. constricter/fix/index/linked.cp312-win32.pyd +0 -0
  59. constricter/fix/index/linked.py +142 -0
  60. constricter/fix/index/loose.cp312-win32.pyd +0 -0
  61. constricter/fix/index/loose.py +103 -0
  62. constricter/fix/index/managed.cp312-win32.pyd +0 -0
  63. constricter/fix/index/managed.py +63 -0
  64. constricter/fix/index/modules.cp312-win32.pyd +0 -0
  65. constricter/fix/index/modules.py +741 -0
  66. constricter/fix/index/offers.cp312-win32.pyd +0 -0
  67. constricter/fix/index/offers.py +252 -0
  68. constricter/fix/index/order.cp312-win32.pyd +0 -0
  69. constricter/fix/index/order.py +155 -0
  70. constricter/fix/index/origins.cp312-win32.pyd +0 -0
  71. constricter/fix/index/origins.py +238 -0
  72. constricter/fix/index/own_methods.cp312-win32.pyd +0 -0
  73. constricter/fix/index/own_methods.py +91 -0
  74. constricter/fix/index/own_overloads.cp312-win32.pyd +0 -0
  75. constricter/fix/index/own_overloads.py +70 -0
  76. constricter/fix/index/own_types.cp312-win32.pyd +0 -0
  77. constricter/fix/index/own_types.py +330 -0
  78. constricter/fix/index/params.cp312-win32.pyd +0 -0
  79. constricter/fix/index/params.py +81 -0
  80. constricter/fix/index/plain.cp312-win32.pyd +0 -0
  81. constricter/fix/index/plain.py +221 -0
  82. constricter/fix/index/project.cp312-win32.pyd +0 -0
  83. constricter/fix/index/project.py +797 -0
  84. constricter/fix/index/sides.cp312-win32.pyd +0 -0
  85. constricter/fix/index/sides.py +160 -0
  86. constricter/fix/index/strings.cp312-win32.pyd +0 -0
  87. constricter/fix/index/strings.py +46 -0
  88. constricter/fix/index/stubs/__init__.cp312-win32.pyd +0 -0
  89. constricter/fix/index/stubs/__init__.py +2 -0
  90. constricter/fix/index/stubs/atoms.cp312-win32.pyd +0 -0
  91. constricter/fix/index/stubs/atoms.py +425 -0
  92. constricter/fix/index/stubs/chained.cp312-win32.pyd +0 -0
  93. constricter/fix/index/stubs/chained.py +69 -0
  94. constricter/fix/index/stubs/classnames.cp312-win32.pyd +0 -0
  95. constricter/fix/index/stubs/classnames.py +124 -0
  96. constricter/fix/index/stubs/declared.cp312-win32.pyd +0 -0
  97. constricter/fix/index/stubs/declared.py +508 -0
  98. constricter/fix/index/stubs/installed.cp312-win32.pyd +0 -0
  99. constricter/fix/index/stubs/installed.py +425 -0
  100. constricter/fix/index/stubs/names.cp312-win32.pyd +0 -0
  101. constricter/fix/index/stubs/names.py +200 -0
  102. constricter/fix/index/stubs/read.cp312-win32.pyd +0 -0
  103. constricter/fix/index/stubs/read.py +70 -0
  104. constricter/fix/index/stubs/signatures.cp312-win32.pyd +0 -0
  105. constricter/fix/index/stubs/signatures.py +656 -0
  106. constricter/fix/index/stubs/stubbed.cp312-win32.pyd +0 -0
  107. constricter/fix/index/stubs/stubbed.py +208 -0
  108. constricter/fix/index/taken.cp312-win32.pyd +0 -0
  109. constricter/fix/index/taken.py +45 -0
  110. constricter/fix/index/tuples.cp312-win32.pyd +0 -0
  111. constricter/fix/index/tuples.py +112 -0
  112. constricter/fix/libraries/__init__.cp312-win32.pyd +0 -0
  113. constricter/fix/libraries/__init__.py +2 -0
  114. constricter/fix/libraries/library.cp312-win32.pyd +0 -0
  115. constricter/fix/libraries/library.py +420 -0
  116. constricter/fix/libraries/opened.cp312-win32.pyd +0 -0
  117. constricter/fix/libraries/opened.py +147 -0
  118. constricter/fix/libraries/overloads.cp312-win32.pyd +0 -0
  119. constricter/fix/libraries/overloads.py +960 -0
  120. constricter/fix/libraries/receivers.cp312-win32.pyd +0 -0
  121. constricter/fix/libraries/receivers.py +274 -0
  122. constricter/fix/libraries/stdlib.cp312-win32.pyd +0 -0
  123. constricter/fix/libraries/stdlib.py +752 -0
  124. constricter/fix/libraries/walks.cp312-win32.pyd +0 -0
  125. constricter/fix/libraries/walks.py +49 -0
  126. constricter/fix/tables/aliases.json +147 -0
  127. constricter/fix/tables/attributes.json +700 -0
  128. constricter/fix/tables/awaited.json +69 -0
  129. constricter/fix/tables/awaited_overloads.json +14 -0
  130. constricter/fix/tables/bases.json +888 -0
  131. constricter/fix/tables/classes.json +1941 -0
  132. constricter/fix/tables/elements.json +156 -0
  133. constricter/fix/tables/functions.json +58 -0
  134. constricter/fix/tables/generic_attributes.json +254 -0
  135. constricter/fix/tables/method_overloads.json +461 -0
  136. constricter/fix/tables/method_signatures.json +954 -0
  137. constricter/fix/tables/methods.json +954 -0
  138. constricter/fix/tables/overloads.json +540 -0
  139. constricter/fix/tables/platform.json +37 -0
  140. constricter/fix/tables/returns.json +1362 -0
  141. constricter/fix/tables/scalar_members.json +16 -0
  142. constricter/fix/tables/scalars.json +2707 -0
  143. constricter/fix/tables/source.json +28 -0
  144. constricter/fix/tables/subscriptable.json +77 -0
  145. constricter/fix/tables/type_parameters.json +178 -0
  146. constricter/fix/tables/variables.json +2962 -0
  147. constricter/fix/values/__init__.cp312-win32.pyd +0 -0
  148. constricter/fix/values/__init__.py +2 -0
  149. constricter/fix/values/aliased.cp312-win32.pyd +0 -0
  150. constricter/fix/values/aliased.py +195 -0
  151. constricter/fix/values/bodies.cp312-win32.pyd +0 -0
  152. constricter/fix/values/bodies.py +76 -0
  153. constricter/fix/values/callables.cp312-win32.pyd +0 -0
  154. constricter/fix/values/callables.py +323 -0
  155. constricter/fix/values/called.cp312-win32.pyd +0 -0
  156. constricter/fix/values/called.py +194 -0
  157. constricter/fix/values/classvars.cp312-win32.pyd +0 -0
  158. constricter/fix/values/classvars.py +629 -0
  159. constricter/fix/values/decided.cp312-win32.pyd +0 -0
  160. constricter/fix/values/decided.py +429 -0
  161. constricter/fix/values/displays.cp312-win32.pyd +0 -0
  162. constricter/fix/values/displays.py +101 -0
  163. constricter/fix/values/doubts.cp312-win32.pyd +0 -0
  164. constricter/fix/values/doubts.py +677 -0
  165. constricter/fix/values/entered.cp312-win32.pyd +0 -0
  166. constricter/fix/values/entered.py +332 -0
  167. constricter/fix/values/fills.cp312-win32.pyd +0 -0
  168. constricter/fix/values/fills.py +446 -0
  169. constricter/fix/values/guesses.cp312-win32.pyd +0 -0
  170. constricter/fix/values/guesses.py +376 -0
  171. constricter/fix/values/hinted.cp312-win32.pyd +0 -0
  172. constricter/fix/values/hinted.py +811 -0
  173. constricter/fix/values/inference.cp312-win32.pyd +0 -0
  174. constricter/fix/values/inference.py +698 -0
  175. constricter/fix/values/loops.cp312-win32.pyd +0 -0
  176. constricter/fix/values/loops.py +335 -0
  177. constricter/fix/values/members.cp312-win32.pyd +0 -0
  178. constricter/fix/values/members.py +578 -0
  179. constricter/fix/values/narrowed.cp312-win32.pyd +0 -0
  180. constricter/fix/values/narrowed.py +220 -0
  181. constricter/fix/values/operated.cp312-win32.pyd +0 -0
  182. constricter/fix/values/operated.py +202 -0
  183. constricter/fix/values/ordered.cp312-win32.pyd +0 -0
  184. constricter/fix/values/ordered.py +74 -0
  185. constricter/fix/values/returned.cp312-win32.pyd +0 -0
  186. constricter/fix/values/returned.py +479 -0
  187. constricter/fix/values/returns.cp312-win32.pyd +0 -0
  188. constricter/fix/values/returns.py +237 -0
  189. constricter/fix/values/shapes.cp312-win32.pyd +0 -0
  190. constricter/fix/values/shapes.py +654 -0
  191. constricter/fix/values/stored.cp312-win32.pyd +0 -0
  192. constricter/fix/values/stored.py +564 -0
  193. constricter/fix/values/targets.cp312-win32.pyd +0 -0
  194. constricter/fix/values/targets.py +419 -0
  195. constricter/fix/values/unchecked.cp312-win32.pyd +0 -0
  196. constricter/fix/values/unchecked.py +278 -0
  197. constricter/jsonc.py +50 -0
  198. constricter/noqa.py +47 -0
  199. constricter/notebook.py +115 -0
  200. constricter/offences.py +436 -0
  201. constricter/overrides.py +24 -0
  202. constricter/plugins/__init__.py +2 -0
  203. constricter/plugins/flake8.py +133 -0
  204. constricter/plugins/pylint.py +186 -0
  205. constricter/py.typed +0 -0
  206. constricter/recording.py +660 -0
  207. constricter/rules/__init__.cp312-win32.pyd +0 -0
  208. constricter/rules/__init__.py +2 -0
  209. constricter/rules/annotations.cp312-win32.pyd +0 -0
  210. constricter/rules/annotations.py +296 -0
  211. constricter/rules/binding.cp312-win32.pyd +0 -0
  212. constricter/rules/binding.py +652 -0
  213. constricter/rules/calls.cp312-win32.pyd +0 -0
  214. constricter/rules/calls.py +460 -0
  215. constricter/rules/checker.cp312-win32.pyd +0 -0
  216. constricter/rules/checker.py +536 -0
  217. constricter/rules/declarations.cp312-win32.pyd +0 -0
  218. constricter/rules/declarations.py +730 -0
  219. constricter/rules/decorators.cp312-win32.pyd +0 -0
  220. constricter/rules/decorators.py +351 -0
  221. constricter/rules/flow.cp312-win32.pyd +0 -0
  222. constricter/rules/flow.py +502 -0
  223. constricter/rules/keys.cp312-win32.pyd +0 -0
  224. constricter/rules/keys.py +79 -0
  225. constricter/rules/late.cp312-win32.pyd +0 -0
  226. constricter/rules/late.py +969 -0
  227. constricter/rules/narrowing.cp312-win32.pyd +0 -0
  228. constricter/rules/narrowing.py +98 -0
  229. constricter/rules/parsed.cp312-win32.pyd +0 -0
  230. constricter/rules/parsed.py +136 -0
  231. constricter/rules/quoted.cp312-win32.pyd +0 -0
  232. constricter/rules/quoted.py +141 -0
  233. constricter/rules/rebinding.cp312-win32.pyd +0 -0
  234. constricter/rules/rebinding.py +169 -0
  235. constricter/rules/recorded.cp312-win32.pyd +0 -0
  236. constricter/rules/recorded.py +168 -0
  237. constricter/rules/redundant.cp312-win32.pyd +0 -0
  238. constricter/rules/redundant.py +160 -0
  239. constricter/rules/scope.cp312-win32.pyd +0 -0
  240. constricter/rules/scope.py +802 -0
  241. constricter/rules/scoped.cp312-win32.pyd +0 -0
  242. constricter/rules/scoped.py +232 -0
  243. constricter/rules/scoping.cp312-win32.pyd +0 -0
  244. constricter/rules/scoping.py +346 -0
  245. constricter/rules/settings.cp312-win32.pyd +0 -0
  246. constricter/rules/settings.py +189 -0
  247. constricter/rules/syntax.cp312-win32.pyd +0 -0
  248. constricter/rules/syntax.py +472 -0
  249. constricter/rules/tables.cp312-win32.pyd +0 -0
  250. constricter/rules/tables.py +169 -0
  251. constricter/rules/walked.cp312-win32.pyd +0 -0
  252. constricter/rules/walked.py +127 -0
  253. constricter/rules/widened.cp312-win32.pyd +0 -0
  254. constricter/rules/widened.py +322 -0
  255. constricter/trace.py +374 -0
  256. e45ce06b2fdd5c43f782__mypyc.cp312-win32.pyd +0 -0
  257. python_constricter-0.4.1.dist-info/METADATA +400 -0
  258. python_constricter-0.4.1.dist-info/RECORD +261 -0
  259. python_constricter-0.4.1.dist-info/WHEEL +4 -0
  260. python_constricter-0.4.1.dist-info/entry_points.txt +5 -0
  261. python_constricter-0.4.1.dist-info/licenses/LICENSE.md +21 -0
@@ -0,0 +1,960 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Standard-library calls whose arguments decide their type: the signatures a call may match.
3
+
4
+ `tables/overloads.json` holds each such function's signatures, in order, as each platform and Python
5
+ version reads them (see `stdlib_tables/overloads.py`): every parameter's kind, whether it has a
6
+ default, and which argument types (`SCALARS`) it certainly takes or refuses; and the return, as a
7
+ template naming classes by their dotted paths and type variables by their names.
8
+
9
+ A type checker picks the first overload whose parameters take a call's arguments. `--fix` types the
10
+ call only when that's certain enough: every signature that may be the one (not certainly refusing
11
+ them, up to the first that certainly takes them; an argument whose type isn't known, or a verdict
12
+ that depends on more than its type, leaves one open) gives the same type, in every variant. A call
13
+ none takes is left to the type checker, which rejects it anyway.
14
+ """
15
+
16
+ import ast
17
+ import builtins
18
+ from collections.abc import Callable, Iterator, Mapping, Sequence
19
+ from functools import lru_cache
20
+ from typing import Final, NamedTuple, TypeAlias, cast
21
+
22
+ from constricter.fix.core.known import SPELLED, Inference, Known
23
+ from constricter.fix.core.signatures import (
24
+ AWAIT,
25
+ CLASS_BINDS,
26
+ CLASS_BOUNDED,
27
+ CLASS_TUPLE,
28
+ CLASS_VERDICT,
29
+ CONTAINER_BINDS,
30
+ CONTAINER_VERDICTS,
31
+ ELEMENT_VERDICTS,
32
+ FUTURE_BINDS,
33
+ OWN,
34
+ OWN_ELEMENTS,
35
+ Accepts,
36
+ Constant,
37
+ Parameter,
38
+ ReadSignature,
39
+ Signature,
40
+ Variant,
41
+ )
42
+ from constricter.fix.libraries import stdlib
43
+ from constricter.fix.libraries.receivers import (
44
+ MAYBE,
45
+ NO,
46
+ SELF_TYPE,
47
+ UNFOLLOWED,
48
+ YES,
49
+ Receiver,
50
+ both,
51
+ matches,
52
+ parsed,
53
+ receiver_of,
54
+ )
55
+ from constricter.rules.walked import walk
56
+
57
+ SCALARS: Final = ("str", "LiteralString", "bytes", "bytearray", "int", "float", "complex", "bool", "None")
58
+ _COLUMNS: Final = {scalar: index for index, scalar in enumerate(SCALARS)}
59
+ _LITERAL_STRING: Final = "LiteralString" # the type of a `str` literal
60
+ _NONE: Final = "None"
61
+ _POSITIONAL: Final = frozenset({"p", "e"})
62
+ _KEYWORD: Final = frozenset({"e", "k"})
63
+ _STAR: Final = "a"
64
+ _STARS: Final = "w"
65
+ _BUILTINS: Final = frozenset({*dir(builtins), _NONE})
66
+ _KIND: Final = "stdlib"
67
+ # The builtin numbers a type checker takes where each of these is declared.
68
+ _PROMOTED: Final = {
69
+ "int": ("bool",),
70
+ "float": ("int", "bool"),
71
+ "complex": ("float", "int", "bool"),
72
+ }
73
+ _GENERATOR: Final = "collections.abc.Generator"
74
+ _ITERATOR: Final = "collections.abc.Iterator"
75
+ _TUPLE: Final = "tuple"
76
+ _ANYTHING: Final = "t" # `Accepts`' key for a parameter any argument binds
77
+ _RETURNED: Final = "r" # `Accepts`' key for a callable parameter a function's return binds
78
+ _Taken: TypeAlias = tuple[str, dict[str, str]] # whether an operand is taken, and what it binds
79
+ _NESTED: Final = "ei" # `Accepts`' key for an iterable of iterables' inner containers
80
+ _AWAITED: Final = "w" # and for an awaitable one, which what awaiting a coroutine's call gives binds
81
+ _CALL: Final = "call" # the fix kind of a declared return
82
+ # The builtin containers whose type arguments bind a parameter's type variable (`Iterable[_T]`'s).
83
+ CONTAINERS: Final = ("list", _TUPLE, "set", "frozenset", "dict") # in the `scalars` table's order
84
+ _CONTAINERS: Final = frozenset(CONTAINERS)
85
+ _SELF: Final = "self" # a signature's key for the type arguments its method's instance must have
86
+ # The builtin classes, each a class as an argument (`assertRaises(ValueError)`).
87
+ _BUILTIN_CLASSES: Final = frozenset(
88
+ name for name in dir(builtins) if isinstance(cast("object", getattr(builtins, name)), type)
89
+ )
90
+ _SEQUENCES: Final = frozenset({"list", "tuple", "set", "frozenset"}) # whose elements `own_e` is asked of
91
+ _TYPE_OF: Final = "type[" # the annotation of a class as a value
92
+ _Infer: TypeAlias = Callable[[ast.expr], Inference | None]
93
+ # A signature that may be the one: its return template and its type variables' types (`None`:
94
+ # a return `--fix` can't write).
95
+ _Picked: TypeAlias = tuple[str, dict[str, str]] | None
96
+
97
+
98
+ class Argument(NamedTuple):
99
+ """One argument of a call: its type among `SCALARS` (`None` if it's none of them, or unknown).
100
+
101
+ `constant`: the literal it is, if it is one (in a 1-tuple, as its value may be `None`);
102
+ `found`: what `--fix` inferred it to be, whose kinds the call's type rests on too; `elements`:
103
+ for a builtin container (`list[str]`), its name and type arguments (`tuple[str, ...]`'s `str`),
104
+ and `inner`, the same of its first one (a `list[list[int]]`'s `list[int]`);
105
+ `reads`: the names, attributes and subscripts in it, as source text; `returns`: for a function
106
+ the module knows the declared return of (`helper`, `u.helper`), that return; `klass`: for a class
107
+ passed as it is (`np.float64`), its name as written; `awaited`: for a coroutine's call, what
108
+ awaiting it gives.
109
+ """
110
+
111
+ type: str | None
112
+ constant: tuple[Constant] | None = None
113
+ found: Inference | None = None
114
+ elements: tuple[str, tuple[str, ...]] | None = None
115
+ inner: tuple[str, tuple[str, ...]] | None = None # its first type argument's, a container too
116
+ reads: tuple[str, ...] = ()
117
+ returns: Inference | None = None
118
+ klass: str | None = None
119
+ awaited: Inference | None = None
120
+ own: tuple[str, ...] = () # its class's lineage, for a class the module names (`LibraryNames.lineage`)
121
+ own_elements: tuple[str, ...] = () # a builtin sequence's or set's elements' class's
122
+ # Whether `klass` is a library's class (a standard-library one, or what a value typed `type[C]`
123
+ # is), which may be outside a `type[T]`'s bound as a builtin one may, and is a class only to a
124
+ # parameter that speaks of classes: to another, it's the value `found` says.
125
+ library: bool = False
126
+ several: bool = False # whether `klass` is the union of a tuple of classes (`(OSError, ValueError)`)
127
+
128
+ @property
129
+ def text(self) -> str | None:
130
+ """Its type as the module writes it, or `None` if unknown (a literal's is its `type`)."""
131
+ return None if self.found is None else self.found.annotation
132
+
133
+
134
+ class Arguments(NamedTuple):
135
+ """A call's arguments: positional, then by keyword."""
136
+
137
+ args: list[Argument]
138
+ keywords: dict[str, Argument]
139
+
140
+
141
+ def chosen(
142
+ name: str,
143
+ call: ast.Call,
144
+ known: Known,
145
+ infer: _Infer,
146
+ method: stdlib.Method | None = None,
147
+ ) -> Inference | None:
148
+ """Type a call to standard-library function `name` by the signatures its arguments may match.
149
+
150
+ `name` is an entry of `OVERLOADS` (of `AWAITED_OVERLOADS`, after `AWAIT`: an `async def`'s call,
151
+ awaited), or `method_signatures` for a `method`'s call on an instance
152
+ (whose type binds its class's type parameters); or an installed package's function, as the
153
+ module calls it (`np.empty`: see `LibraryNames.installed`). `infer` types an argument.
154
+
155
+ Returns:
156
+ The inference, its classes spelled (and imported, if they must be) as the module can; or
157
+ `None` if they don't all agree, or its type can't be written.
158
+
159
+ """
160
+ read: Arguments | None
161
+ if (read := _arguments(call, infer, known)) is None:
162
+ return None
163
+ installed: tuple[ReadSignature, ...] | None = known.names.installed.get(name)
164
+ variants: tuple[tuple[ReadSignature, ...], ...] = _signatures(name) if installed is None else (installed,)
165
+ instance: list[str] | None = None if method is None else method.instance
166
+ picks: list[_Picked] = [
167
+ _receiving(picked, method)
168
+ for variant in variants
169
+ for picked in _picked(
170
+ variant,
171
+ read,
172
+ instance,
173
+ None if method is None else receiver_of(method, known),
174
+ )
175
+ ]
176
+ found: set[str | None] = {None if picked is None else write(picked, known) for picked in picks}
177
+ if len(found) > 1 and None not in found:
178
+ # Where Python versions differ only in which iterator they declare: the one all of them are.
179
+ found = {None if picked is None else write(_iterator(picked), known) for picked in picks}
180
+ annotation: str | None
181
+ if (annotation := found.pop() if len(found) == 1 else None) is None: # none, or several that disagree
182
+ return None
183
+ parts: list[Argument] = [*read.args, *read.keywords.values()]
184
+ return Inference(
185
+ annotation,
186
+ f"`{name}`'s return type, for its arguments",
187
+ frozenset({_KIND if installed is None else _CALL}).union(
188
+ *(part.found.kinds for part in parts if part.found is not None),
189
+ *(part.returns.kinds for part in parts if part.returns is not None),
190
+ ),
191
+ # What a type variable may take as its type: an argument of any type but a scalar's.
192
+ tuple(text for part in parts if part.type is None for text in part.reads),
193
+ )
194
+
195
+
196
+ def _iterator(picked: tuple[str, dict[str, str]]) -> tuple[str, dict[str, str]]:
197
+ """Write a pick whose template is a `Generator[T, ...]` as the `Iterator[T]` every generator is.
198
+
199
+ Returns:
200
+ It; any other pick as it is.
201
+
202
+ """
203
+ head: ast.expr
204
+ index: ast.expr
205
+ match ast.parse(picked[0].removeprefix(SPELLED), mode="eval").body:
206
+ case ast.Subscript(value=ast.Attribute() as head, slice=index) if ast.unparse(head) == _GENERATOR:
207
+ first: ast.expr = index.elts[0] if isinstance(index, ast.Tuple) else index
208
+ return f"{_ITERATOR}[{ast.unparse(first)}]", picked[1]
209
+ case _:
210
+ return picked
211
+
212
+
213
+ def generic_member(receiver: str, name: str, call: ast.Call | None, known: Known) -> Inference | None:
214
+ """Type a generic standard-library class's own attribute or property, bound by the receiver's type.
215
+
216
+ `m.string` on an `re.Match[str]` is a `str`; a method (`call`) is `method_signatures`' to type.
217
+
218
+ Returns:
219
+ The inference, or `None` if the tables don't have it, or a type parameter it names is unbound.
220
+
221
+ """
222
+ found: tuple[str, str, dict[str, str]] | None = (
223
+ stdlib.generic_attribute(receiver, name, known) if call is None else None
224
+ )
225
+ if found is None:
226
+ return None
227
+ annotation: str | None = write((found[1], found[2]), known)
228
+ return (
229
+ None
230
+ if annotation is None
231
+ else Inference(annotation, f"`{found[0]}.{name}`'s annotation in typeshed", frozenset({_KIND}))
232
+ )
233
+
234
+
235
+ def operated(
236
+ method: stdlib.Method,
237
+ operands: tuple[str, str],
238
+ known: Known,
239
+ arguments: tuple[str, ...] | None = None,
240
+ *,
241
+ same: bool = False,
242
+ ) -> Inference | None:
243
+ """Type a binary operator by its left operand's `method` (`__sub__`), for its right operand's type.
244
+
245
+ `operands`: both as `stdlib.operand` names them, a generic class's instance by its class. The
246
+ first signature whose operand takes the right one answers, as a type checker picks it: the same
247
+ type, a class under it, or a number promoted to it (an `int` where a `float` is taken).
248
+ `arguments`: the right operand's type arguments, where it's a generic class's instance, which
249
+ bind the operand's own type variables (`Counter.__add__` takes a `Counter[_S]`); `same`: whether
250
+ it's the left one's own type, as an operand of `Self` must be.
251
+
252
+ Returns:
253
+ The inference, or `None`: no signature certainly takes it, one before that may, the Python
254
+ versions differ, or the right operand's class is under the left's (its reflected method may
255
+ answer first).
256
+
257
+ """
258
+ left: str
259
+ right: str
260
+ left, right = operands
261
+ if left != right and stdlib.inherits(right, left):
262
+ return None
263
+ found: set[str | None] = {
264
+ _answered(
265
+ variant,
266
+ method,
267
+ known,
268
+ lambda member: _taken(member, operands, arguments, method.types, same=same),
269
+ )
270
+ for variant in stdlib.method_signatures()[method.entry]
271
+ }
272
+ annotation: str | None
273
+ if (annotation := found.pop() if len(found) == 1 else None) is None:
274
+ return None
275
+ return Inference(annotation, f"`{method.entry}`'s return type, for its operand", frozenset({_KIND}))
276
+
277
+
278
+ def _answered(
279
+ variant: Variant,
280
+ method: stdlib.Method,
281
+ known: Known,
282
+ taking: Callable[[str], _Taken],
283
+ ) -> str | None:
284
+ """Type an operator by one variant of its method's signatures (see `operated`); `taking`, `_taken`'s.
285
+
286
+ Returns:
287
+ What the first signature taking the right operand returns, or `None`: a signature before it
288
+ can't be read, or may take it.
289
+
290
+ """
291
+ signature: Signature
292
+ for signature in variant:
293
+ takes: str | None
294
+ if (takes := signature.get("takes")) is None:
295
+ return None
296
+ taken: list[_Taken] = [taking(member) for member in _members(parsed(takes))]
297
+ if any(verdict == MAYBE for verdict, _ in taken):
298
+ return None
299
+ bound: dict[str, str] | None
300
+ if (bound := next((binds for verdict, binds in taken if verdict == YES), None)) is not None:
301
+ template: str | None = signature["returns"]
302
+ written: str | None = (
303
+ None if template is None else write((template, {**method.types, **bound}), known)
304
+ )
305
+ return None if written is None else _deduplicated(written)
306
+ return None
307
+
308
+
309
+ def _taken(
310
+ member: str,
311
+ operands: tuple[str, str],
312
+ arguments: tuple[str, ...] | None,
313
+ types: Mapping[str, str],
314
+ *,
315
+ same: bool,
316
+ ) -> _Taken:
317
+ """Decide whether an operand typed `member` takes the right operand, and what that binds.
318
+
319
+ A generic class's instance (`arguments`) is taken by `Self` where it's the left one's own type,
320
+ and by its own class's template (`Counter[_S]`) where each type argument the left one's binds
321
+ (`types`) is its own, binding the others; another class's template (a base's) may take it.
322
+
323
+ Returns:
324
+ `YES`, `NO` or `MAYBE`, and the type variables it binds.
325
+
326
+ """
327
+ left: str
328
+ right: str
329
+ left, right = operands
330
+ if arguments is None:
331
+ return YES if _takes(member, left, right) else NO, {}
332
+ if member == SELF_TYPE:
333
+ return YES if same else NO, {}
334
+ tree: ast.expr = parsed(member)
335
+ # A base's (`dict[_KT, _VT]`, of an `OrderedDict`): the tables may not know it is.
336
+ if ast.unparse(tree.value if isinstance(tree, ast.Subscript) else tree) != right:
337
+ return MAYBE, {}
338
+ params: list[ast.expr] = (
339
+ []
340
+ if not isinstance(tree, ast.Subscript)
341
+ else tree.slice.elts
342
+ if isinstance(tree.slice, ast.Tuple)
343
+ else [tree.slice]
344
+ )
345
+ if len(params) != len(arguments) or not all(isinstance(param, ast.Name) for param in params):
346
+ return MAYBE, {}
347
+ bound: dict[str, str] = {}
348
+ name: str
349
+ argument: str
350
+ for name, argument in zip((cast("ast.Name", param).id for param in params), arguments, strict=True):
351
+ wanted: str | None = types.get(name, bound.get(name))
352
+ if wanted is not None and wanted != argument:
353
+ return NO, {}
354
+ bound[name] = argument
355
+ return YES, bound
356
+
357
+
358
+ def _takes(member: str, left: str, right: str) -> bool:
359
+ """Check whether an operand typed `member` (`Self`: the left operand's own class) takes `right`.
360
+
361
+ Returns:
362
+ Whether it does.
363
+
364
+ """
365
+ wanted: str = left if member == SELF_TYPE else member
366
+ return right == wanted or right in _PROMOTED.get(wanted, ()) or stdlib.inherits(right, wanted)
367
+
368
+
369
+ def _deduplicated(annotation: str) -> str:
370
+ """Write each union in an annotation with each member once (`Counter[str | str]`: `Counter[str]`).
371
+
372
+ Returns:
373
+ The annotation.
374
+
375
+ """
376
+ tree: ast.expr = ast.parse(annotation, mode="eval").body
377
+ node: ast.AST
378
+ for node in ast.walk(tree):
379
+ if isinstance(node, ast.Subscript):
380
+ slices: list[ast.expr] = node.slice.elts if isinstance(node.slice, ast.Tuple) else [node.slice]
381
+ joined: list[ast.expr] = [
382
+ ast.parse(" | ".join(dict.fromkeys(_members(part))), mode="eval").body for part in slices
383
+ ]
384
+ node.slice = ast.Tuple(joined, ast.Load()) if isinstance(node.slice, ast.Tuple) else joined[0]
385
+ return ast.unparse(tree)
386
+
387
+
388
+ def library_element(receiver: str, known: Known) -> Inference | None:
389
+ """Type what iterating a standard-library class's instance gives, bound by the receiver's type.
390
+
391
+ A `for` loop over an `io.TextIOWrapper` binds a `str`, over an `itertools.chain[int]` an `int`.
392
+
393
+ Returns:
394
+ The inference, or `None` if the tables don't have it, or a type parameter it names is unbound.
395
+
396
+ """
397
+ found: tuple[str, str, dict[str, str]] | None = stdlib.element(receiver, known)
398
+ annotation: str | None = None if found is None else write((found[1], found[2]), known)
399
+ return (
400
+ None
401
+ if found is None or annotation is None
402
+ else Inference(annotation, f"the elements of a `{found[0]}`", frozenset({_KIND}))
403
+ )
404
+
405
+
406
+ def _receiving(picked: _Picked, method: stdlib.Method | None) -> _Picked:
407
+ """Add what a method's receiver binds its class's type parameters to (`Method.types`) to a pick.
408
+
409
+ Through an alias, its return's class parameters are first written as the alias has them
410
+ (`Method.templates`).
411
+
412
+ Returns:
413
+ It, or `None` if an argument binds one differently (the call is an error).
414
+
415
+ """
416
+ types: Mapping[str, str] = {} if method is None else method.types
417
+ if picked is None or any(types.get(name, text) != text for name, text in picked[1].items()):
418
+ return None
419
+ return (
420
+ picked[0] if method is None else substituted(picked[0], method.templates),
421
+ {**types, **picked[1]},
422
+ )
423
+
424
+
425
+ def substituted(template: str, types: Mapping[str, str]) -> str:
426
+ """Write a template or pattern with each bare name in `types` replaced by its text.
427
+
428
+ Returns:
429
+ It.
430
+
431
+ """
432
+ if not types:
433
+ return template
434
+ tree: ast.expr = parsed(template)
435
+ if isinstance(tree, ast.Name):
436
+ return types.get(tree.id, template)
437
+ node: ast.AST
438
+ for node in ast.walk(tree): # a node's children are listed before it's changed: no text is replaced twice
439
+ field: str
440
+ for field in node._fields:
441
+ setattr(node, field, _replaced(cast("object", getattr(node, field, None)), types))
442
+ return ast.unparse(tree)
443
+
444
+
445
+ def _replaced(value: object, types: Mapping[str, str]) -> object:
446
+ """Replace a field's name, or each name in its list, by its text in `types`.
447
+
448
+ Returns:
449
+ The field's new value.
450
+
451
+ """
452
+ if isinstance(value, list):
453
+ return [_replaced(item, types) for item in cast("list[object]", value)]
454
+ return parsed(types[value.id]) if isinstance(value, ast.Name) and value.id in types else value
455
+
456
+
457
+ def _arguments(call: ast.Call, infer: _Infer, known: Known) -> Arguments | None:
458
+ """Read a call's arguments.
459
+
460
+ Returns:
461
+ Them; or `None` for a call unpacking `*args` or `**kwargs`, whose arguments can't be matched.
462
+
463
+ """
464
+ if any(isinstance(arg, ast.Starred) for arg in call.args):
465
+ return None
466
+ keywords: dict[str, Argument] = {}
467
+ keyword: ast.keyword
468
+ for keyword in call.keywords:
469
+ if keyword.arg is None:
470
+ return None
471
+ keywords[keyword.arg] = _argument(keyword.value, infer, known)
472
+ return Arguments([_argument(arg, infer, known) for arg in call.args], keywords)
473
+
474
+
475
+ def _argument(value: ast.expr, infer: _Infer, known: Known) -> Argument:
476
+ constant: Constant
477
+ named: tuple[str, bool] | None = _class_named(value, known)
478
+ elts: list[ast.expr]
479
+ match value:
480
+ case ast.Constant(value=None):
481
+ return Argument(_NONE, (None,))
482
+ case ast.Constant(value=bool() | int() | float() | complex() | str() | bytes() as constant):
483
+ kind: str = _LITERAL_STRING if isinstance(constant, str) else type(constant).__name__
484
+ return Argument(kind, (constant,))
485
+ case ast.Name() | ast.Attribute() if named is not None and not named[1]:
486
+ return Argument(None, reads=(named[0],), klass=named[0])
487
+ case ast.Name() | ast.Attribute() if named is not None:
488
+ # A standard-library class, as the module writes it (`csv.Error`, `sqlite.OperationalError`).
489
+ return _inferred(value, infer, known, klass=named[0])
490
+ case ast.Tuple(elts=elts) if elts and all(_class_named(elt, known) is not None for elt in elts):
491
+ # A tuple of classes (`(OSError, ValueError)`): their union, to a parameter that takes one.
492
+ found: list[tuple[str, bool]] = [
493
+ cast("tuple[str, bool]", _class_named(elt, known)) for elt in elts
494
+ ]
495
+ union: str = " | ".join(dict.fromkeys(text for text, _ in found))
496
+ return _inferred(value, infer, known, klass=union)._replace(several=True)
497
+ case _:
498
+ return _inferred(value, infer, known)
499
+
500
+
501
+ def _class_named(value: ast.expr, known: Known) -> tuple[str, bool] | None:
502
+ """Read a name or dotted path as a class the module knows, as written.
503
+
504
+ Returns:
505
+ It, and whether it's a library's (a standard-library class, which may be outside a `type[T]`'s
506
+ bound), or `None` for anything else: the module's own class, an installed package's or a
507
+ builtin, else the standard library's.
508
+
509
+ """
510
+ if not isinstance(value, ast.Name | ast.Attribute):
511
+ return None
512
+ written: str = ast.unparse(value)
513
+ if (
514
+ written in known.classes
515
+ or written in known.names.classes
516
+ or (written in _BUILTIN_CLASSES and known.is_builtin(written))
517
+ ):
518
+ return written, False
519
+ return (written, True) if stdlib.class_argument(value, known) is not None else None
520
+
521
+
522
+ def _inferred(value: ast.expr, infer: _Infer, known: Known, klass: str | None = None) -> Argument:
523
+ """Read an argument by what `--fix` infers it to be.
524
+
525
+ Returns:
526
+ It; `klass` is the class it is where that's known already, else the class a value typed
527
+ `type[C]` is (`self.failureException`, a `type[BaseException]`).
528
+
529
+ """
530
+ found: Inference | None = infer(value)
531
+ typed: str | None = None if found is None else found.annotation
532
+ held: tuple[str, tuple[str, ...]] | None = None if typed is None else _elements(typed, known)
533
+ lineage: Mapping[str, tuple[str, ...]] = known.names.lineage
534
+ return Argument(
535
+ typed if typed in _COLUMNS and typed != _LITERAL_STRING else None,
536
+ found=found,
537
+ elements=held,
538
+ inner=_elements(held[1][0], known) if held and held[1] else None,
539
+ own=lineage.get(typed or "", ()),
540
+ own_elements=lineage.get(held[1][0], ()) if held and held[0] in _SEQUENCES and held[1] else (),
541
+ reads=tuple(
542
+ ast.unparse(node)
543
+ for node in walk(value)
544
+ if isinstance(node, ast.Name | ast.Attribute | ast.Subscript)
545
+ ),
546
+ returns=_function_return(value, known),
547
+ awaited=infer(ast.Await(value)) if isinstance(value, ast.Call | ast.Name) else None,
548
+ klass=_class_of(typed) if klass is None else klass,
549
+ library=True,
550
+ )
551
+
552
+
553
+ def _class_of(typed: str | None) -> str | None:
554
+ """Read the class a value typed `type[C]` is.
555
+
556
+ Returns:
557
+ `C`, if `typed` is `type[C]` with `C` a name or dotted path; else `None`.
558
+
559
+ """
560
+ if typed is None or not typed.startswith(_TYPE_OF) or not typed.endswith("]"):
561
+ return None
562
+ inner: str = typed[len(_TYPE_OF) : -1]
563
+ named: bool = bool(inner) and inner != SELF_TYPE and all(part.isidentifier() for part in inner.split("."))
564
+ return inner if named else None
565
+
566
+
567
+ def _function_return(value: ast.expr, known: Known) -> Inference | None:
568
+ """Type what a function passed as an argument returns, by its declared return (`partial(helper, x)`).
569
+
570
+ Returns:
571
+ The inference, or `None` for anything but a function whose return the module knows.
572
+
573
+ """
574
+ name: str = ast.unparse(value)
575
+ if not isinstance(value, ast.Name | ast.Attribute) or name not in known.calls:
576
+ return None
577
+ return Inference(known.calls[name], f"`{name}`'s declared return type", frozenset({_CALL}))
578
+
579
+
580
+ def _elements(annotation: str, known: Known) -> tuple[str, tuple[str, ...]] | None:
581
+ """Read a builtin container's type arguments (`dict[str, int]`), one for a tuple of one type.
582
+
583
+ Returns:
584
+ Its name and them, or `None` for anything else (a tuple of several types among them).
585
+
586
+ """
587
+ tree: ast.expr = ast.parse(annotation, mode="eval").body
588
+ name: str
589
+ index: ast.expr
590
+ match tree:
591
+ case ast.Subscript(value=ast.Name(id=name), slice=index) if name in _CONTAINERS:
592
+ pass
593
+ case _:
594
+ return None
595
+ if not known.is_builtin(name):
596
+ return None
597
+ texts: list[str] = [ast.unparse(arg) for arg in (index.elts if isinstance(index, ast.Tuple) else [index])]
598
+ if name == _TUPLE: # `tuple[str, ...]`, or `tuple[str, str]`: `str`
599
+ texts = [text for text in texts if text not in {"...", "()"}]
600
+ return (name, (texts[0],)) if len(set(texts)) == 1 else None
601
+ return name, tuple(texts)
602
+
603
+
604
+ @lru_cache(maxsize=512)
605
+ def _signatures(name: str) -> tuple[tuple[ReadSignature, ...], ...]:
606
+ """Read a function's (or method's) variants from the tables, once, each parameter in full.
607
+
608
+ Returns:
609
+ Each variant's signatures.
610
+
611
+ """
612
+ variants: list[Variant] = (
613
+ stdlib.AWAITED_OVERLOADS[name.removeprefix(AWAIT)]
614
+ if name.startswith(AWAIT)
615
+ else stdlib.OVERLOADS.get(name) or stdlib.method_signatures()[name]
616
+ )
617
+ return tuple(
618
+ tuple(
619
+ ReadSignature(
620
+ # Each a list, as JSON holds it, where the tables' types say a tuple.
621
+ tuple(
622
+ _parameter(param) for param in cast("list[Sequence[object] | str]", signature["params"])
623
+ ),
624
+ signature["returns"],
625
+ signature.get("self"),
626
+ )
627
+ for signature in variant
628
+ )
629
+ for variant in variants
630
+ )
631
+
632
+
633
+ def _parameter(written: Sequence[object] | str) -> Parameter:
634
+ """Read a parameter as the tables write it (`"name kind="`: one every signature has alike).
635
+
636
+ Returns:
637
+ It in full.
638
+
639
+ """
640
+ if not isinstance(written, str):
641
+ return cast("Parameter", tuple(written))
642
+ name: str
643
+ kind: str
644
+ name, _, kind = written.partition(" ")
645
+ return name, kind.removesuffix("="), kind.endswith("="), None
646
+
647
+
648
+ def _picked(
649
+ variant: Sequence[ReadSignature],
650
+ read: Arguments,
651
+ instance: Sequence[str] | None,
652
+ receiver: Receiver | None,
653
+ ) -> Iterator[_Picked]:
654
+ """Find the signatures a call may match: up to the first that certainly takes its arguments.
655
+
656
+ `instance`: for a method, its receiver's type arguments (`Pattern[str]`'s), if it has them; a
657
+ signature for another instance type (`self: Pattern[bytes]`) refuses it. An installed method's
658
+ signature declaring its `self` is matched against `receiver`'s type (see `Receiver`).
659
+
660
+ Yields:
661
+ Each one that doesn't certainly refuse them: its return template and type variables' types.
662
+
663
+ """
664
+ signature: ReadSignature
665
+ for signature in variant:
666
+ verdict: str
667
+ bound: dict[str, str]
668
+ verdict, bound = _matched(signature.params, read)
669
+ if signature.instance is not None and signature.instance != instance:
670
+ verdict = NO if instance is not None else MAYBE if verdict == YES else verdict
671
+ if signature.receiver is not None and verdict != NO:
672
+ verdict = both(verdict, MAYBE if receiver is None else matches(receiver, signature, bound))
673
+ if verdict != NO:
674
+ yield None if signature.returns is None else (signature.returns, bound)
675
+ if verdict == YES:
676
+ return
677
+
678
+
679
+ def _matched(params: Sequence[Parameter], read: Arguments) -> tuple[str, dict[str, str]]:
680
+ """Match a call's arguments to one signature's parameters.
681
+
682
+ Returns:
683
+ Whether it takes them (`YES`, `NO`, `MAYBE`), and the type variables they bind.
684
+
685
+ """
686
+ pairs: list[tuple[Parameter, Argument]] | None
687
+ if (pairs := _bound(params, read)) is None:
688
+ return NO, {}
689
+ verdicts: set[str] = set()
690
+ types: dict[str, str] = {}
691
+ clashing: set[str] = set() # type variables two arguments bind differently: unwritable
692
+ param: Parameter
693
+ arg: Argument
694
+ for param, arg in pairs:
695
+ verdict: str = _verdict(param[3], arg)
696
+ verdicts.add(verdict)
697
+ bound: tuple[str, str] | None
698
+ if (bound := None if verdict != YES else _binding(param[3], arg)) is not None:
699
+ variable: str
700
+ text: str
701
+ variable, text = bound
702
+ if types.setdefault(variable, text) != text:
703
+ verdicts.add(MAYBE)
704
+ clashing.add(variable)
705
+ if NO in verdicts:
706
+ return NO, {}
707
+ return (MAYBE if MAYBE in verdicts else YES), {
708
+ name: text for name, text in types.items() if name not in clashing
709
+ }
710
+
711
+
712
+ def _binding(accepts: Accepts | None, arg: Argument) -> tuple[str, str] | None:
713
+ """Find the type variable an argument binds, taken by a parameter that is one (or has one).
714
+
715
+ A scalar by the parameter's own verdicts (`var`); else anything by its type, for a parameter that
716
+ is an unbounded type variable (`t`), and a builtin container by its type argument, for one that
717
+ is a generic class of one (`e`, `of`).
718
+
719
+ Returns:
720
+ Its name and type, or `None`.
721
+
722
+ """
723
+ kind: str | None = arg.type
724
+ if accepts is None or (arg.klass is not None and (CLASS_BINDS in accepts or not arg.library)):
725
+ # A class binds `type[T]`'s `T` (`kv`), or nothing; a library's is a value to any other
726
+ # parameter; a tuple of classes binds it to their union (one taking no tuple refused it, see
727
+ # `_verdict`).
728
+ variable: str | None = None if accepts is None else accepts.get(CLASS_BINDS)
729
+ return None if variable is None or arg.klass is None else (variable, arg.klass)
730
+ if kind is not None:
731
+ binds: str | dict[str, list[str]] = accepts.get("var", {})
732
+ if isinstance(binds, str): # each type binds it to itself; a `str` literal to `str`
733
+ return binds, "str" if kind == _LITERAL_STRING else kind
734
+ found: list[str] | None = binds.get(kind)
735
+ return None if found is None else (found[0], found[1])
736
+ # Anything, by its type (`t`); a function, by its declared return (`r`).
737
+ named: str | None
738
+ text: str | None
739
+ for named, text in (
740
+ (accepts.get(_ANYTHING), arg.text),
741
+ (accepts.get(_RETURNED), None if arg.returns is None else arg.returns.annotation),
742
+ (accepts.get(_AWAITED), None if arg.awaited is None else arg.awaited.annotation),
743
+ (accepts.get(FUTURE_BINDS), None if arg.awaited is None else arg.text),
744
+ ):
745
+ if named is not None and text is not None:
746
+ return named, text
747
+ of: dict[str, int] = accepts.get("of", {})
748
+ elements: tuple[str, tuple[str, ...]] | None = arg.elements
749
+ if elements is not None and elements[0] in of:
750
+ return _element_binding(accepts, arg, elements[1][of[elements[0]]])
751
+ # A bounded type variable a builtin container argument binds, its bound certainly taking it.
752
+ container: str | None = accepts.get(CONTAINER_BINDS)
753
+ return None if container is None or elements is None or arg.text is None else (container, arg.text)
754
+
755
+
756
+ def _bound(params: Sequence[Parameter], read: Arguments) -> list[tuple[Parameter, Argument]] | None:
757
+ """Bind a call's arguments to a signature's parameters, as Python does.
758
+
759
+ Returns:
760
+ Each parameter with its argument (a `*args` or `**kwargs` one with each it takes), or `None` if
761
+ they don't fit: too many, an unknown keyword, one bound twice, a required parameter missing.
762
+
763
+ """
764
+ positional: list[Parameter] = [param for param in params if param[1] in _POSITIONAL]
765
+ star: Parameter | None = next((param for param in params if param[1] == _STAR), None)
766
+ stars: Parameter | None = next((param for param in params if param[1] == _STARS), None)
767
+ pairs: list[tuple[Parameter, Argument]] = []
768
+ index: int
769
+ arg: Argument
770
+ for index, arg in enumerate(read.args):
771
+ taking: Parameter | None
772
+ if (taking := positional[index] if index < len(positional) else star) is None:
773
+ return None
774
+ pairs.append((taking, arg))
775
+ filled: set[str] = {param[0] for param in positional[: len(read.args)]}
776
+ keyword: str
777
+ for keyword, arg in read.keywords.items():
778
+ named: Parameter | None = next(
779
+ (param for param in params if param[0] == keyword and param[1] in _KEYWORD),
780
+ stars,
781
+ )
782
+ if named is None or keyword in filled:
783
+ return None
784
+ filled.add(keyword)
785
+ pairs.append((named, arg))
786
+ required: list[str] = [param[0] for param in params if not param[2] and param[1] not in {_STAR, _STARS}]
787
+ return pairs if set(required) <= filled else None
788
+
789
+
790
+ def _verdict(accepts: Accepts | None, arg: Argument) -> str:
791
+ """Decide whether a parameter takes an argument.
792
+
793
+ Returns:
794
+ `YES`, `NO`, or `MAYBE` (its type unknown, or not enough to tell).
795
+
796
+ """
797
+ if accepts is None:
798
+ return YES
799
+ if arg.klass is not None and (CLASS_VERDICT in accepts or CLASS_BOUNDED in accepts or not arg.library):
800
+ # A builtin or library class may be outside a `type[T]`'s bound (`dtype=bool`, `T` a numpy
801
+ # scalar): the signature that takes it then is another's.
802
+ outside: bool = (arg.library or arg.klass in _BUILTIN_CLASSES) and accepts.get(CLASS_BOUNDED, False)
803
+ untaken: bool = arg.several and not accepts.get(CLASS_TUPLE, False) # a tuple, where one class is
804
+ return NO if untaken else MAYBE if outside else accepts.get(CLASS_VERDICT, MAYBE)
805
+ if arg.constant is not None and any(_same(arg.constant[0], value) for value in accepts.get("lit", [])):
806
+ return YES
807
+ table: str = accepts.get("c", accepts["v"]) if arg.constant is not None else accepts["v"]
808
+ if arg.type is not None:
809
+ return table[_COLUMNS[arg.type]]
810
+ # A checked file's parameter that takes only checked files' classes, by the argument's lineage.
811
+ wanted: list[str] | None
812
+ line: tuple[str, ...]
813
+ for wanted, line in ((accepts.get(OWN), arg.own), (accepts.get(OWN_ELEMENTS), arg.own_elements)):
814
+ if wanted is not None and line:
815
+ return YES if set(wanted) & set(line) else MAYBE if UNFOLLOWED in line else NO
816
+ taken: bool = (
817
+ (_ANYTHING in accepts and arg.text is not None)
818
+ or (FUTURE_BINDS in accepts and arg.awaited is not None and arg.text is not None)
819
+ or (
820
+ arg.elements is not None
821
+ and arg.elements[0] in accepts.get("of", {})
822
+ and (_NESTED not in accepts or _nested(accepts, arg) is not None)
823
+ )
824
+ or (_RETURNED in accepts and arg.returns is not None)
825
+ or (_AWAITED in accepts and arg.awaited is not None)
826
+ )
827
+ return YES if taken else _container_verdict(accepts, arg)
828
+
829
+
830
+ def _element_binding(accepts: Accepts, arg: Argument, element: str) -> tuple[str, str] | None:
831
+ """Bind a parameter's element type variable (`e`) by a container argument's `element`.
832
+
833
+ Of an iterable of iterables (`Iterable[Iterable[_S]]`, `ei`): by that element's own.
834
+
835
+ Returns:
836
+ The variable's name and type, or `None`.
837
+
838
+ """
839
+ if _NESTED not in accepts:
840
+ return accepts.get("e", ""), element
841
+ inner: tuple[str, tuple[str, ...]] | None = _nested(accepts, arg)
842
+ return None if inner is None else (accepts.get("e", ""), inner[1][accepts[_NESTED][inner[0]]])
843
+
844
+
845
+ def _nested(accepts: Accepts, arg: Argument) -> tuple[str, tuple[str, ...]] | None:
846
+ """Find the container an argument's elements are, where a parameter takes an iterable of them (`ei`).
847
+
848
+ Returns:
849
+ Its name and type arguments, or `None` if the elements' type isn't one it takes, or the outer
850
+ container's type argument it reads isn't its first.
851
+
852
+ """
853
+ of: dict[str, int] = accepts.get("of", {})
854
+ inner: tuple[str, tuple[str, ...]] | None = arg.inner
855
+ first: bool = arg.elements is not None and of.get(arg.elements[0]) == 0
856
+ return inner if first and inner is not None and inner[0] in accepts.get(_NESTED, {}) else None
857
+
858
+
859
+ def _container_verdict(accepts: Accepts, arg: Argument) -> str:
860
+ """Decide whether a parameter takes a builtin container argument, by its elements' type where it says.
861
+
862
+ Returns:
863
+ The verdict: `MAYBE` for any other argument, or a parameter that doesn't say.
864
+
865
+ """
866
+ elements: tuple[str, tuple[str, ...]] | None
867
+ if (elements := arg.elements) is None:
868
+ # A parameter bounded by a future takes no coroutine's call: awaitable, but no future.
869
+ return NO if FUTURE_BINDS in accepts and arg.awaited is not None else MAYBE
870
+ verdict: str = accepts.get(CONTAINER_VERDICTS, {}).get(elements[0], MAYBE)
871
+ of: str | None = accepts.get(ELEMENT_VERDICTS, {}).get(elements[0])
872
+ if verdict != YES or of is None:
873
+ return verdict
874
+ element: str = elements[1][0]
875
+ return of[_COLUMNS[element]] if element in _COLUMNS else MAYBE
876
+
877
+
878
+ def _same(constant: Constant, value: Constant) -> bool:
879
+ """Compare a literal argument with a `Literal[...]`'s value, types too (`True` isn't `1`).
880
+
881
+ Returns:
882
+ Whether they're the same.
883
+
884
+ """
885
+ return type(constant) is type(value) and constant == value
886
+
887
+
888
+ def write(picked: tuple[str, dict[str, str]], known: Known) -> str | None:
889
+ """Write a return template as the module can: its type variables bound, its classes spelled.
890
+
891
+ Returns:
892
+ The annotation, each union member once; or `None` if a type variable is unbound, a builtin
893
+ rebound, or a class can't be named. A template that's spelled already (`SPELLED`) is its own.
894
+
895
+ """
896
+ template: str
897
+ types: dict[str, str]
898
+ template, types = picked
899
+ if template.startswith(SPELLED):
900
+ return template.removeprefix(SPELLED)
901
+ tree: ast.expr | None = _rewritten(ast.parse(template, mode="eval").body, types, known)
902
+ return None if tree is None else " | ".join(dict.fromkeys(_members(tree)))
903
+
904
+
905
+ def _rewritten(node: ast.expr, types: Mapping[str, str], known: Known) -> ast.expr | None:
906
+ """Rewrite a freshly parsed template in place: each name and dotted path as `_spelled` has it.
907
+
908
+ A template is names, dotted paths, subscripts, tuples of their arguments, unions and `...`.
909
+
910
+ Returns:
911
+ The tree, or `None` if a name can't be spelled.
912
+
913
+ """
914
+ text: str | None
915
+ parts: list[ast.expr | None]
916
+ match node:
917
+ case ast.Name() | ast.Attribute():
918
+ return (
919
+ None if (text := _spelled(node, types, known)) is None else ast.parse(text, mode="eval").body
920
+ )
921
+ case ast.Subscript():
922
+ parts = [_rewritten(node.value, types, known), _rewritten(node.slice, types, known)]
923
+ node.value, node.slice = parts[0] or node.value, parts[1] or node.slice
924
+ case ast.Tuple():
925
+ parts = [_rewritten(element, types, known) for element in node.elts]
926
+ node.elts = [part for part in parts if part is not None]
927
+ case ast.BinOp():
928
+ parts = [_rewritten(node.left, types, known), _rewritten(node.right, types, known)]
929
+ node.left, node.right = parts[0] or node.left, parts[1] or node.right
930
+ case _:
931
+ parts = []
932
+ return None if None in parts else node
933
+
934
+
935
+ def _spelled(node: ast.Name | ast.Attribute, types: Mapping[str, str], known: Known) -> str | None:
936
+ """Spell one name of a template: a class's dotted path, a bound type variable, a builtin.
937
+
938
+ Returns:
939
+ Its text, or `None`.
940
+
941
+ """
942
+ if isinstance(node, ast.Attribute):
943
+ return None if known.names.plan is None else known.names.plan.spell(ast.unparse(node))
944
+ if node.id in types:
945
+ return types[node.id]
946
+ return node.id if node.id in _BUILTINS and (node.id == _NONE or known.is_builtin(node.id)) else None
947
+
948
+
949
+ def _members(tree: ast.expr) -> Iterator[str]:
950
+ """Walk a union's members (`a | b | c`), not into brackets.
951
+
952
+ Yields:
953
+ Each one's text.
954
+
955
+ """
956
+ if isinstance(tree, ast.BinOp) and isinstance(tree.op, ast.BitOr):
957
+ yield from _members(tree.left)
958
+ yield from _members(tree.right)
959
+ else:
960
+ yield ast.unparse(tree)