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,132 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """The signatures `--fix` matches a call against: the standard library's tables', and installed packages'.
3
+
4
+ What each parameter takes (`Accepts`), and a signature as the tables hold it (`Signature`) and as
5
+ it's matched (`ReadSignature`); see `constricter.fix.libraries.overloads`, which matches them,
6
+ `constricter.fix.libraries.stdlib`, whose tables hold them, and `constricter.fix.index.stubs.stubbed`,
7
+ which reads an installed package's.
8
+ """
9
+
10
+ from typing import Final, NamedTuple, NotRequired, Required, TypeAlias, TypedDict
11
+
12
+ # `Accepts`' keys for an installed package's parameter (see `constricter.fix.index.stubs.signatures`): a
13
+ # class passed as the argument, and a builtin container.
14
+ # Before a path, an `async def`'s entry for `overloads.chosen`; before a method's name in
15
+ # `method_overloads`, an `async def` method's: its signatures' returns are what awaiting a call gives.
16
+ AWAIT: Final = "await "
17
+ CLASS_VERDICT: Final = "k"
18
+ CLASS_BINDS: Final = "kv"
19
+ CLASS_BOUNDED: Final = "kb"
20
+ CLASS_TUPLE: Final = "kt"
21
+ CONTAINER_VERDICTS: Final = "b"
22
+ ELEMENT_VERDICTS: Final = "be"
23
+ CONTAINER_BINDS: Final = "bc"
24
+ OWN: Final = "own"
25
+ OWN_ELEMENTS: Final = "own_e"
26
+ FUTURE_BINDS: Final = "f"
27
+
28
+ Constant: TypeAlias = bool | int | float | complex | str | bytes | None # a literal's value
29
+
30
+
31
+ class Accepts(TypedDict, total=False):
32
+ """Which argument types a parameter takes (see `constricter.fix.libraries.overloads`).
33
+
34
+ `v`: a verdict (`y`, `n`, `?`) per `overloads.SCALARS` type, for an argument that isn't a
35
+ literal; `c`: for a literal not among `lit` (its `Literal[...]` values), where that differs;
36
+ `var`: the type variable the parameter is, and its type, for an argument of each type; or just
37
+ its name, where each binds it to its own type (a `str` literal's `str`). `t`: the type variable
38
+ the parameter is, unbounded, so any argument binds it to its own type (`copy.copy(x)`). `e`, for
39
+ a parameter that is a generic class of one type variable (`Iterable[_T]`): that variable, which
40
+ an argument of a builtin container in `of` (`list[str]`) binds to its type argument at that index;
41
+ `ei`, for one of a generic class of another (`Iterable[Iterable[_S]]`): the container that type
42
+ argument must be in turn, and the index of its own that binds the variable (`list[list[int]]`).
43
+ `r`: the type variable a callable parameter returns (`Callable[..., _T]`), which a function
44
+ argument binds to its declared return (`functools.partial(helper, 1)`). `w`: the type variable
45
+ an awaitable parameter gives awaited (`Coroutine[Any, Any, _T]`), which a coroutine's call binds
46
+ to what awaiting it gives (`asyncio.run(main())`). `k`: for an installed
47
+ package's parameter, a verdict for a class passed as the argument (`dtype=np.float64`), `kv`
48
+ the type variable it binds to that class (`type[_T]`'s `_T`), `kb` whether that variable
49
+ is bounded: a builtin class may be outside its bound, and another signature's to take; and `kt`
50
+ whether a tuple of classes is taken too (`type[_E] | tuple[type[_E], ...]`), which binds the
51
+ variable to their union. `b`: a
52
+ verdict per builtin container argument (`tuple`), and `be`, where the parameter says what its
53
+ elements must be, a verdict per `SCALARS` type for them; `bc`: the bounded type variable a
54
+ container argument it takes binds. `f`: the type variable the parameter is, bounded by a future
55
+ (`asyncio.ensure_future`'s `_FT`), which a future or a task binds to its type, and no
56
+ coroutine's call does.
57
+ `own`: for a checked file's parameter that takes nothing but checked files' classes, their paths,
58
+ which an argument's class is matched against by its lineage; `own_e`: those a builtin sequence's
59
+ or set's elements must be, for one that takes iterables of them (see `index.own_types`).
60
+ """
61
+
62
+ v: Required[str]
63
+ c: str
64
+ lit: list[Constant]
65
+ var: str | dict[str, list[str]]
66
+ t: str
67
+ e: str
68
+ of: dict[str, int]
69
+ ei: dict[str, int]
70
+ r: str
71
+ w: str
72
+ k: str
73
+ kv: str
74
+ kb: bool
75
+ kt: bool
76
+ b: dict[str, str]
77
+ be: dict[str, str]
78
+ bc: str
79
+ f: str
80
+ own: list[str]
81
+ own_e: list[str]
82
+
83
+
84
+ # A parameter: its name, kind (`p` positional, `e` either, `k` keyword, `a` `*args`, `w` `**kwargs`),
85
+ # whether it has a default, and what it takes (`None`: whatever every signature takes there). The
86
+ # tables write one every signature has alike as `"name kind"`, `=` after it if it has a default.
87
+ Parameter: TypeAlias = tuple[str, str, bool, Accepts | None]
88
+
89
+
90
+ class Signature(TypedDict):
91
+ """One signature of a function whose arguments decide its type: its parameters, and its return.
92
+
93
+ The return is a template (see `constricter.fix.libraries.overloads`), or `None` if `--fix` can't write it.
94
+ `self`: for a generic class's method declaring its instance's type (`self: Pattern[str]`), the
95
+ type arguments that instance must have. `takes`: for an operator's method (`__add__`), its one
96
+ operand's type as a template, or `None` if `--fix` can't write it.
97
+ """
98
+
99
+ params: list[Parameter | str]
100
+ returns: str | None
101
+ self: NotRequired[list[str]]
102
+ takes: NotRequired[str | None]
103
+
104
+
105
+ Variant: TypeAlias = list[Signature] # one configuration's signatures, in order
106
+
107
+
108
+ class ReadSignature(NamedTuple):
109
+ """One signature, read: its parameters in full, its return template, and its `self`'s type arguments."""
110
+
111
+ params: tuple[Parameter, ...]
112
+ returns: str | None
113
+ instance: list[str] | None
114
+ # An installed class's method declaring its `self`: the type its receiver must have, as a pattern
115
+ # (classes by where they're defined, `typing.Any` for anything, type variables bare), and those
116
+ # variables' bounds, likewise (see `constricter.fix.index.stubs.signatures`).
117
+ receiver: str | None = None
118
+ bounds: tuple[tuple[str, str], ...] = ()
119
+
120
+
121
+ class Expansion(NamedTuple):
122
+ """A public alias of an installed generic class (`npt.NDArray`), as a receiver's type is written.
123
+
124
+ `params`: its own type parameters, renamed apart from any a method names (`_alias0`), which the
125
+ receiver's type arguments bind; `receiver`: what it stands for, as a pattern naming them (see
126
+ `ReadSignature.receiver`); `templates`: the class's type parameters, each with a template naming
127
+ them (`np.dtype[_alias0]`), where one can be written.
128
+ """
129
+
130
+ params: tuple[str, ...]
131
+ receiver: str
132
+ templates: tuple[tuple[str, str], ...]
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """What the checked files and installed packages define, for each other: the cross-file index."""
@@ -0,0 +1,90 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Cross-module `--fix` for `await` of a call to another checked file's `async def`.
3
+
4
+ Found through the calling file's imports and the re-exports between them (`await load(url)`,
5
+ `await client.load(url)` after `import pkg.client as client`); its declared return types the
6
+ awaited call where the file can write it (see `project.portable`), or else its `return`s do, once
7
+ its file is checked (see `constricter.fix.values.returned`). A method's is its class's (see
8
+ `rules.tables`).
9
+ """
10
+
11
+ from pathlib import Path
12
+
13
+ from constricter.fix.core.known import Guarded, Returns
14
+ from constricter.fix.core.signatures import AWAIT
15
+ from constricter.fix.index import project, sides
16
+ from constricter.fix.index.modules import SUFFIX, Index, Module, module_name
17
+
18
+
19
+ def calls(catalog: Index, path: Path, guarded: dict[str, Guarded]) -> dict[str, str]:
20
+ """Type what awaiting the calls the file at `path` makes to other checked files' `async def`s gives.
21
+
22
+ `guarded`: the names its imported types need imported for type checking (see `Guarded`), which
23
+ these types' are added to.
24
+
25
+ Returns:
26
+ Each call's name as written (`load`, `client.load`), mapped to its type; none for a file
27
+ `catalog` doesn't have.
28
+
29
+ """
30
+ found: dict[str, str] = {}
31
+ target: Module | None
32
+ if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
33
+ return found
34
+ spelled: str
35
+ defined: tuple[Module, str]
36
+ for spelled, defined in sides.called(catalog.modules, target, _awaitable):
37
+ if defined[1] in defined[0].awaits:
38
+ declared: dict[str, str] = {spelled: defined[0].awaits[defined[1]]}
39
+ found.update(project.portable(catalog.modules, (target, defined), spelled, declared, guarded))
40
+ return found
41
+
42
+
43
+ def returned(catalog: Index, path: Path, guarded: dict[str, Guarded], found: Returns) -> Returns:
44
+ """Add what awaiting the file's calls to other checked files' undeclared `async def`s gives to `found`.
45
+
46
+ By their `return`s, for modules already checked, each under `AWAIT` before the call's name as
47
+ written, with what it rests on if it's a guess.
48
+
49
+ Returns:
50
+ `found`, with them; `found` itself for a file `catalog` doesn't have, or one that calls none.
51
+
52
+ """
53
+ awaited: dict[str, str] = {}
54
+ guesses: dict[str, frozenset[str]] = {}
55
+ target: Module | None
56
+ if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
57
+ return found
58
+ spelled: str
59
+ defined: tuple[Module, str]
60
+ for spelled, defined in sides.called(catalog.modules, target, _awaitable):
61
+ key: str
62
+ if (key := f"{AWAIT}{defined[1]}") not in defined[0].returned.calls:
63
+ continue
64
+ typed: dict[str, str] = {spelled: defined[0].returned.calls[key]}
65
+ kept: dict[str, str]
66
+ if kept := project.portable(catalog.modules, (target, defined), spelled, typed, guarded):
67
+ awaited[f"{AWAIT}{spelled}"] = kept[spelled]
68
+ if key in defined[0].returned.guesses:
69
+ guesses[f"{AWAIT}{spelled}"] = defined[0].returned.guesses[key]
70
+ if not awaited:
71
+ return found
72
+ return found._replace(calls={**found.calls, **awaited}, guesses={**found.guesses, **guesses})
73
+
74
+
75
+ def needs(catalog: Index, module: Module) -> set[str]:
76
+ """Find the other modules whose `async def`s `module` calls that only their `return`s could type.
77
+
78
+ Returns:
79
+ Their names: checked before it, what awaiting each gives is known.
80
+
81
+ """
82
+ return {
83
+ defined[0].name
84
+ for _, defined in sides.called(catalog.modules, module, _awaitable)
85
+ if defined[1] in defined[0].unawaited
86
+ } - {module.name}
87
+
88
+
89
+ def _awaitable(module: Module, name: str) -> bool: # whether it defines an `async def` of the name
90
+ return name in module.awaits or name in module.unawaited
@@ -0,0 +1,149 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """The standard-library class another checked file's class inherits from, out of a file's sight.
3
+
4
+ A file's class under another file's (`django.test.TestCase`) can't see what that one inherits from:
5
+ the index can. `library_bases` follows each of a file's classes' bases through the checked files
6
+ and the installed packages, to the standard-library class its lines of bases end at
7
+ (`unittest.TestCase`), with the names the classes on the way bind: any other member is the
8
+ library class's, as `constricter.fix.core.inherited.Lineage.definer` then says. A package that
9
+ declares no types is read for its classes' bases alone (`installed.unseen`). Two lines each ending
10
+ at a library class give both, in order, where they share no ancestor.
11
+ """
12
+
13
+ from collections.abc import Mapping, Sequence
14
+ from pathlib import Path
15
+ from typing import TYPE_CHECKING, Final
16
+
17
+ from constricter.fix.core.inherited import SEVERAL, Beyond
18
+ from constricter.fix.index import plain
19
+ from constricter.fix.index.modules import SUFFIX, Index, Module, module_name, takes
20
+ from constricter.fix.index.stubs import installed
21
+ from constricter.fix.libraries import stdlib
22
+ from constricter.fix.values import classvars
23
+
24
+ if TYPE_CHECKING:
25
+ from constricter.fix.index.stubs.declared import Class
26
+
27
+ _DOT: Final = "."
28
+ _OBJECT: Final = "object"
29
+ _DEPTH: Final = 8 # how many classes a line of bases is followed through
30
+
31
+
32
+ def library_bases(catalog: Index, path: Path) -> dict[str, Beyond]:
33
+ """Find where the line of each base of the file at `path`'s classes ends, for one another module defines.
34
+
35
+ Returns:
36
+ Each such base, as the file writes it, with the standard-library class its line of single
37
+ bases ends at (one the tables hold whole; `""` for a line that ends at none, a mixin's) and
38
+ the names bound on the way; none for a base whose line has a class of several bases or one
39
+ out of sight, nor for a file `catalog` doesn't have.
40
+
41
+ """
42
+ target: Module | None
43
+ if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
44
+ return {}
45
+ written: set[str] = {base for bases in target.bases.values() for base in bases} - {classvars.SPECIAL}
46
+ ends: dict[str, Beyond | None] = {
47
+ base: _end(catalog.modules, plain.resolved(catalog.modules, target, base))
48
+ for base in sorted(written)
49
+ if base not in target.bases
50
+ }
51
+ return {base: end for base, end in ends.items() if end is not None}
52
+
53
+
54
+ def _end(modules: Mapping[str, Module], defined: str) -> Beyond | None:
55
+ """Follow a class another module defines (`defined`: its dotted path) up its bases.
56
+
57
+ Returns:
58
+ The standard-library class it ends at (`""`: none, a class with no base), and the names the
59
+ classes on the way bind; `None` if `defined` is no indexed module's class, or its line
60
+ doesn't end.
61
+
62
+ """
63
+ found: Beyond | None = _line(modules, defined, _DEPTH)
64
+ return None if found is None or (found[0] and not found[1]) else found
65
+
66
+
67
+ def _line(modules: Mapping[str, Module], defined: str, depth: int) -> Beyond | None:
68
+ """Follow a class's bases to where they end: at a library class, any mixins beside it.
69
+
70
+ A class of several bases ends where those that reach a library class do, the others (each
71
+ ending at none) binding what they bind before them. An installed package that declares no
72
+ types is read for its classes' bases (see `installed.unseen`).
73
+
74
+ Returns:
75
+ The standard-library class (the tables hold whole; `""`: none; several, `SEVERAL` between
76
+ them) and the names bound on the way; `None` for a base out of sight, two lines reaching
77
+ library classes that share an ancestor, or one too deep.
78
+
79
+ """
80
+ name: str = defined.rpartition(_DOT)[2]
81
+ module: Module | None = modules.get(defined.rpartition(_DOT)[0])
82
+ library: str | None = stdlib.held_whole(defined)
83
+ if module is None and library is None:
84
+ module = installed.unseen(defined.rpartition(_DOT)[0])
85
+ bases: list[str] | None = None if module is None else _bases(module, name)
86
+ if module is not None and bases is None and depth and takes(module, name): # a re-export: followed
87
+ onward: tuple[str, str | None] = module.names[name]
88
+ return _line(modules, _DOT.join(part for part in onward if part), depth - 1)
89
+ if module is None or bases is None:
90
+ return None if library is None else (library, frozenset())
91
+ hidden: frozenset[str] = module.bound[name] | {name}
92
+ ends: list[Beyond | None] = [
93
+ _line(
94
+ modules,
95
+ f"{module.name}{_DOT}{base}" if base in module.bound else plain.resolved(modules, module, base),
96
+ depth - 1,
97
+ )
98
+ if depth
99
+ else None
100
+ for base in bases
101
+ ]
102
+ reached: list[str] = [end[0] for end in ends if end is not None and end[0]]
103
+ lines: list[frozenset[str]] = [stdlib.lines(path) for each in reached for path in each.split(SEVERAL)]
104
+ if None in ends or len(frozenset[str]().union(*lines)) != sum(len(line) for line in lines):
105
+ return None
106
+ return (SEVERAL.join(reached), hidden.union(*(end[1] for end in ends if end is not None)))
107
+
108
+
109
+ def _bases(module: Module, name: str) -> list[str] | None:
110
+ """List the bases of a class `module` (a checked file's, or an installed package's) defines once.
111
+
112
+ Returns:
113
+ Them as written, `object` left out; `None` if it defines no such class.
114
+
115
+ """
116
+ if name not in module.bound:
117
+ return None
118
+ declared: Class | None = None if module.declared is None else module.declared.classes.get(name)
119
+ written: Sequence[str] = module.bases.get(name, ()) if declared is None else declared.bases
120
+ return [base for base in written if base not in {classvars.SPECIAL, _OBJECT}]
121
+
122
+
123
+ def emptied(catalog: Index, path: Path) -> dict[str, Mapping[str, str]]:
124
+ """Find the attributes the file at `path`'s classes take bound to an empty container from another file's.
125
+
126
+ A base another checked file defines, which binds one so and does no more with it (see
127
+ `Module.emptied`): what the file's own classes' methods add to it types it for them.
128
+
129
+ Returns:
130
+ Each such class's attributes' kinds, by its name; none for a file `catalog` doesn't have.
131
+
132
+ """
133
+ target: Module | None
134
+ if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
135
+ return {}
136
+ found: dict[str, Mapping[str, str]] = {}
137
+ name: str
138
+ bases: tuple[str, ...]
139
+ for name, bases in target.bases.items():
140
+ kinds: dict[str, str] = {}
141
+ base: str
142
+ for base in bases:
143
+ defined: str = "" if base in target.bases else plain.resolved(catalog.modules, target, base)
144
+ module: Module | None = catalog.modules.get(defined.rpartition(_DOT)[0])
145
+ if module is not None and not module.installed:
146
+ kinds.update(module.emptied.get(defined.rpartition(_DOT)[2], {}))
147
+ if kinds:
148
+ found[name] = kinds
149
+ return found
@@ -0,0 +1,155 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Unannotated parameters typed by their callers: what every call in the checked files passes each one.
3
+
4
+ Once every file is checked, each module's `Observed` calls to the checked files' open functions (see
5
+ `params.open_functions`) are matched to their parameters, as Python binds them. A parameter left
6
+ unannotated is typed when every call passes it, each an argument of the same known type
7
+ (`rules.calls` says which count); not if a call leaves it to its default, can't be matched, or
8
+ unpacks its arguments, nor if the function escapes (a callback), and never for a function no file
9
+ calls. The files defining them are then checked again knowing those types, as guesses (`callers`):
10
+ a caller outside the checked files may pass anything.
11
+ """
12
+
13
+ from collections.abc import Iterable, Mapping, Sequence
14
+ from dataclasses import replace
15
+ from pathlib import Path
16
+ from typing import Final, TypeAlias
17
+
18
+ from constricter.fix.core.known import Call, Callee, Observed, Origin, Passed, Seeds
19
+ from constricter.fix.index import project
20
+ from constricter.fix.index.modules import SUFFIX, Index
21
+ from constricter.fix.index.params import Param
22
+
23
+ KIND: Final = "callers" # the guessing mechanism (`FIX_KINDS`)
24
+ # A module's functions' typed parameters: each one's type and what it rests on, by name, by function.
25
+ Parameters: TypeAlias = dict[str, dict[str, Passed]]
26
+ _POSITIONAL: Final = frozenset({"p", "e"})
27
+ _KEYWORD: Final = frozenset({"e", "k"})
28
+ _UNPASSED: Final = "" # a parameter a call leaves to its default
29
+
30
+
31
+ def parameters(observations: Iterable[Observed], catalog: Index) -> dict[str, Parameters]:
32
+ """Type the checked files' functions' unannotated parameters by every call to them (see the docstring).
33
+
34
+ Returns:
35
+ Each typed parameter's type, by its function and module.
36
+
37
+ """
38
+ calls: dict[Callee, list[Call]] = {}
39
+ escaped: set[Callee] = set()
40
+ observed: Observed
41
+ for observed in observations:
42
+ escaped.update(observed.escaped)
43
+ callee: Callee
44
+ found: tuple[Call, ...]
45
+ for callee, found in observed.calls.items():
46
+ calls.setdefault(callee, []).extend(found)
47
+ typed: dict[str, Parameters] = {}
48
+ each: list[Call]
49
+ for callee, each in calls.items():
50
+ module: str
51
+ function: str
52
+ module, function = callee
53
+ params: tuple[Param, ...] | None = (
54
+ None
55
+ if callee in escaped or module not in catalog.modules
56
+ else catalog.modules[module].open.get(function)
57
+ )
58
+ types: dict[str, Passed]
59
+ if types := {} if params is None else _typed(params, each):
60
+ typed.setdefault(module, {})[function] = types
61
+ return typed
62
+
63
+
64
+ def _typed(params: Sequence[Param], calls: Sequence[Call]) -> dict[str, Passed]:
65
+ """Type a function's unannotated parameters by `calls`: each one's, if every call passes it the same.
66
+
67
+ Returns:
68
+ Each typed parameter's type, and what its arguments' types rest on together.
69
+
70
+ """
71
+ bound: list[dict[str, Passed | None] | None] = [_bound(params, call) for call in calls]
72
+ found: dict[str, Passed] = {}
73
+ param: Param
74
+ for param in params:
75
+ name: str = param[0]
76
+ passed: list[Passed | None] = [
77
+ None if one is None else one.get(name, (_UNPASSED, frozenset())) for one in bound
78
+ ]
79
+ types: set[str | None] = {None if one is None else one[0] for one in passed}
80
+ only: str | None = types.pop() if len(types) == 1 else None
81
+ if not param[3] and only:
82
+ found[name] = (only, frozenset().union(*(one[1] for one in passed if one is not None)))
83
+ return found
84
+
85
+
86
+ def _bound(params: Sequence[Param], call: Call) -> dict[str, Passed | None] | None:
87
+ """Bind a call's arguments' types to a function's parameters, as Python binds the arguments.
88
+
89
+ Returns:
90
+ Each parameter's argument's type (`None`: unknown), by name, a parameter it leaves to its
91
+ default missing; or `None` if they can't be bound (unpacked, too many, an unknown keyword, one
92
+ bound twice): a call that fails, or can't be read.
93
+
94
+ """
95
+ positional: list[str] = [param[0] for param in params if param[1] in _POSITIONAL]
96
+ if call.unpacked or len(call.positional) > len(positional):
97
+ return None
98
+ bound: dict[str, Passed | None] = dict(zip(positional, call.positional, strict=False))
99
+ keyword: str
100
+ typed: Passed | None
101
+ for keyword, typed in call.keywords:
102
+ if keyword in bound or not any(param[0] == keyword and param[1] in _KEYWORD for param in params):
103
+ return None
104
+ bound[keyword] = typed
105
+ return bound
106
+
107
+
108
+ def callees(catalog: project.Index, path: Path) -> dict[str, Callee]:
109
+ """Find the checked files' functions the file at `path` may call whose parameters aren't all annotated.
110
+
111
+ Its own (`f`), and those it imports (`f`, `u.f`, through re-exports); not an installed package's.
112
+
113
+ Returns:
114
+ Each, as the file spells it, mapped to its module and name; nothing for a file `catalog` doesn't
115
+ have.
116
+
117
+ """
118
+ target: project.Module | None
119
+ if path.suffix != SUFFIX or (target := catalog.modules.get(project.module_name(path))) is None:
120
+ return {}
121
+ found: dict[str, Callee] = {name: (target.name, name) for name in target.open}
122
+ key: str
123
+ origin: Origin
124
+ for key, origin in project.spellings(catalog, target, project.OPEN):
125
+ defined: tuple[project.Module, str] | None = project.definition(catalog.modules, origin, project.OPEN)
126
+ if defined is not None and not defined[0].installed:
127
+ found[key] = (defined[0].name, defined[1])
128
+ return found
129
+
130
+
131
+ def own_parameters(catalog: project.Index, path: Path) -> Seeds:
132
+ """Find what every call passes the unannotated parameters of the file at `path`'s functions.
133
+
134
+ Returns:
135
+ Each function's parameters' types, by name (see `with_parameters`).
136
+
137
+ """
138
+ target: project.Module | None = (
139
+ catalog.modules.get(project.module_name(path)) if path.suffix == SUFFIX else None
140
+ )
141
+ return {} if target is None else target.parameters
142
+
143
+
144
+ def with_parameters(catalog: project.Index, found: Mapping[str, Seeds]) -> project.Index:
145
+ """Record what every call passes checked modules' functions' unannotated parameters (by module name).
146
+
147
+ Returns:
148
+ The index, with them.
149
+
150
+ """
151
+ modules: dict[str, project.Module] = dict(catalog.modules)
152
+ name: str
153
+ for name in found.keys() & modules.keys():
154
+ modules[name] = replace(modules[name], parameters=found[name])
155
+ return catalog._replace(modules=modules)