python-constricter 0.4.1__cp311-cp311-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__.cp311-win32.pyd +0 -0
  29. constricter/fix/__init__.py +2 -0
  30. constricter/fix/core/__init__.cp311-win32.pyd +0 -0
  31. constricter/fix/core/__init__.py +2 -0
  32. constricter/fix/core/asked.cp311-win32.pyd +0 -0
  33. constricter/fix/core/asked.py +56 -0
  34. constricter/fix/core/fixes.cp311-win32.pyd +0 -0
  35. constricter/fix/core/fixes.py +317 -0
  36. constricter/fix/core/imports.cp311-win32.pyd +0 -0
  37. constricter/fix/core/imports.py +580 -0
  38. constricter/fix/core/inherited.cp311-win32.pyd +0 -0
  39. constricter/fix/core/inherited.py +311 -0
  40. constricter/fix/core/known.cp311-win32.pyd +0 -0
  41. constricter/fix/core/known.py +730 -0
  42. constricter/fix/core/signatures.cp311-win32.pyd +0 -0
  43. constricter/fix/core/signatures.py +132 -0
  44. constricter/fix/index/__init__.cp311-win32.pyd +0 -0
  45. constricter/fix/index/__init__.py +2 -0
  46. constricter/fix/index/awaits.cp311-win32.pyd +0 -0
  47. constricter/fix/index/awaits.py +90 -0
  48. constricter/fix/index/beyond.cp311-win32.pyd +0 -0
  49. constricter/fix/index/beyond.py +149 -0
  50. constricter/fix/index/callers.cp311-win32.pyd +0 -0
  51. constricter/fix/index/callers.py +155 -0
  52. constricter/fix/index/crossed.cp311-win32.pyd +0 -0
  53. constricter/fix/index/crossed.py +327 -0
  54. constricter/fix/index/decorated.cp311-win32.pyd +0 -0
  55. constricter/fix/index/decorated.py +140 -0
  56. constricter/fix/index/fixtures.cp311-win32.pyd +0 -0
  57. constricter/fix/index/fixtures.py +176 -0
  58. constricter/fix/index/linked.cp311-win32.pyd +0 -0
  59. constricter/fix/index/linked.py +142 -0
  60. constricter/fix/index/loose.cp311-win32.pyd +0 -0
  61. constricter/fix/index/loose.py +103 -0
  62. constricter/fix/index/managed.cp311-win32.pyd +0 -0
  63. constricter/fix/index/managed.py +63 -0
  64. constricter/fix/index/modules.cp311-win32.pyd +0 -0
  65. constricter/fix/index/modules.py +741 -0
  66. constricter/fix/index/offers.cp311-win32.pyd +0 -0
  67. constricter/fix/index/offers.py +252 -0
  68. constricter/fix/index/order.cp311-win32.pyd +0 -0
  69. constricter/fix/index/order.py +155 -0
  70. constricter/fix/index/origins.cp311-win32.pyd +0 -0
  71. constricter/fix/index/origins.py +238 -0
  72. constricter/fix/index/own_methods.cp311-win32.pyd +0 -0
  73. constricter/fix/index/own_methods.py +91 -0
  74. constricter/fix/index/own_overloads.cp311-win32.pyd +0 -0
  75. constricter/fix/index/own_overloads.py +70 -0
  76. constricter/fix/index/own_types.cp311-win32.pyd +0 -0
  77. constricter/fix/index/own_types.py +330 -0
  78. constricter/fix/index/params.cp311-win32.pyd +0 -0
  79. constricter/fix/index/params.py +81 -0
  80. constricter/fix/index/plain.cp311-win32.pyd +0 -0
  81. constricter/fix/index/plain.py +221 -0
  82. constricter/fix/index/project.cp311-win32.pyd +0 -0
  83. constricter/fix/index/project.py +797 -0
  84. constricter/fix/index/sides.cp311-win32.pyd +0 -0
  85. constricter/fix/index/sides.py +160 -0
  86. constricter/fix/index/strings.cp311-win32.pyd +0 -0
  87. constricter/fix/index/strings.py +46 -0
  88. constricter/fix/index/stubs/__init__.cp311-win32.pyd +0 -0
  89. constricter/fix/index/stubs/__init__.py +2 -0
  90. constricter/fix/index/stubs/atoms.cp311-win32.pyd +0 -0
  91. constricter/fix/index/stubs/atoms.py +425 -0
  92. constricter/fix/index/stubs/chained.cp311-win32.pyd +0 -0
  93. constricter/fix/index/stubs/chained.py +69 -0
  94. constricter/fix/index/stubs/classnames.cp311-win32.pyd +0 -0
  95. constricter/fix/index/stubs/classnames.py +124 -0
  96. constricter/fix/index/stubs/declared.cp311-win32.pyd +0 -0
  97. constricter/fix/index/stubs/declared.py +508 -0
  98. constricter/fix/index/stubs/installed.cp311-win32.pyd +0 -0
  99. constricter/fix/index/stubs/installed.py +425 -0
  100. constricter/fix/index/stubs/names.cp311-win32.pyd +0 -0
  101. constricter/fix/index/stubs/names.py +200 -0
  102. constricter/fix/index/stubs/read.cp311-win32.pyd +0 -0
  103. constricter/fix/index/stubs/read.py +70 -0
  104. constricter/fix/index/stubs/signatures.cp311-win32.pyd +0 -0
  105. constricter/fix/index/stubs/signatures.py +656 -0
  106. constricter/fix/index/stubs/stubbed.cp311-win32.pyd +0 -0
  107. constricter/fix/index/stubs/stubbed.py +208 -0
  108. constricter/fix/index/taken.cp311-win32.pyd +0 -0
  109. constricter/fix/index/taken.py +45 -0
  110. constricter/fix/index/tuples.cp311-win32.pyd +0 -0
  111. constricter/fix/index/tuples.py +112 -0
  112. constricter/fix/libraries/__init__.cp311-win32.pyd +0 -0
  113. constricter/fix/libraries/__init__.py +2 -0
  114. constricter/fix/libraries/library.cp311-win32.pyd +0 -0
  115. constricter/fix/libraries/library.py +420 -0
  116. constricter/fix/libraries/opened.cp311-win32.pyd +0 -0
  117. constricter/fix/libraries/opened.py +147 -0
  118. constricter/fix/libraries/overloads.cp311-win32.pyd +0 -0
  119. constricter/fix/libraries/overloads.py +960 -0
  120. constricter/fix/libraries/receivers.cp311-win32.pyd +0 -0
  121. constricter/fix/libraries/receivers.py +274 -0
  122. constricter/fix/libraries/stdlib.cp311-win32.pyd +0 -0
  123. constricter/fix/libraries/stdlib.py +752 -0
  124. constricter/fix/libraries/walks.cp311-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__.cp311-win32.pyd +0 -0
  148. constricter/fix/values/__init__.py +2 -0
  149. constricter/fix/values/aliased.cp311-win32.pyd +0 -0
  150. constricter/fix/values/aliased.py +195 -0
  151. constricter/fix/values/bodies.cp311-win32.pyd +0 -0
  152. constricter/fix/values/bodies.py +76 -0
  153. constricter/fix/values/callables.cp311-win32.pyd +0 -0
  154. constricter/fix/values/callables.py +323 -0
  155. constricter/fix/values/called.cp311-win32.pyd +0 -0
  156. constricter/fix/values/called.py +194 -0
  157. constricter/fix/values/classvars.cp311-win32.pyd +0 -0
  158. constricter/fix/values/classvars.py +629 -0
  159. constricter/fix/values/decided.cp311-win32.pyd +0 -0
  160. constricter/fix/values/decided.py +429 -0
  161. constricter/fix/values/displays.cp311-win32.pyd +0 -0
  162. constricter/fix/values/displays.py +101 -0
  163. constricter/fix/values/doubts.cp311-win32.pyd +0 -0
  164. constricter/fix/values/doubts.py +677 -0
  165. constricter/fix/values/entered.cp311-win32.pyd +0 -0
  166. constricter/fix/values/entered.py +332 -0
  167. constricter/fix/values/fills.cp311-win32.pyd +0 -0
  168. constricter/fix/values/fills.py +446 -0
  169. constricter/fix/values/guesses.cp311-win32.pyd +0 -0
  170. constricter/fix/values/guesses.py +376 -0
  171. constricter/fix/values/hinted.cp311-win32.pyd +0 -0
  172. constricter/fix/values/hinted.py +811 -0
  173. constricter/fix/values/inference.cp311-win32.pyd +0 -0
  174. constricter/fix/values/inference.py +698 -0
  175. constricter/fix/values/loops.cp311-win32.pyd +0 -0
  176. constricter/fix/values/loops.py +335 -0
  177. constricter/fix/values/members.cp311-win32.pyd +0 -0
  178. constricter/fix/values/members.py +578 -0
  179. constricter/fix/values/narrowed.cp311-win32.pyd +0 -0
  180. constricter/fix/values/narrowed.py +220 -0
  181. constricter/fix/values/operated.cp311-win32.pyd +0 -0
  182. constricter/fix/values/operated.py +202 -0
  183. constricter/fix/values/ordered.cp311-win32.pyd +0 -0
  184. constricter/fix/values/ordered.py +74 -0
  185. constricter/fix/values/returned.cp311-win32.pyd +0 -0
  186. constricter/fix/values/returned.py +479 -0
  187. constricter/fix/values/returns.cp311-win32.pyd +0 -0
  188. constricter/fix/values/returns.py +237 -0
  189. constricter/fix/values/shapes.cp311-win32.pyd +0 -0
  190. constricter/fix/values/shapes.py +654 -0
  191. constricter/fix/values/stored.cp311-win32.pyd +0 -0
  192. constricter/fix/values/stored.py +564 -0
  193. constricter/fix/values/targets.cp311-win32.pyd +0 -0
  194. constricter/fix/values/targets.py +419 -0
  195. constricter/fix/values/unchecked.cp311-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__.cp311-win32.pyd +0 -0
  208. constricter/rules/__init__.py +2 -0
  209. constricter/rules/annotations.cp311-win32.pyd +0 -0
  210. constricter/rules/annotations.py +296 -0
  211. constricter/rules/binding.cp311-win32.pyd +0 -0
  212. constricter/rules/binding.py +652 -0
  213. constricter/rules/calls.cp311-win32.pyd +0 -0
  214. constricter/rules/calls.py +460 -0
  215. constricter/rules/checker.cp311-win32.pyd +0 -0
  216. constricter/rules/checker.py +536 -0
  217. constricter/rules/declarations.cp311-win32.pyd +0 -0
  218. constricter/rules/declarations.py +730 -0
  219. constricter/rules/decorators.cp311-win32.pyd +0 -0
  220. constricter/rules/decorators.py +351 -0
  221. constricter/rules/flow.cp311-win32.pyd +0 -0
  222. constricter/rules/flow.py +502 -0
  223. constricter/rules/keys.cp311-win32.pyd +0 -0
  224. constricter/rules/keys.py +79 -0
  225. constricter/rules/late.cp311-win32.pyd +0 -0
  226. constricter/rules/late.py +969 -0
  227. constricter/rules/narrowing.cp311-win32.pyd +0 -0
  228. constricter/rules/narrowing.py +98 -0
  229. constricter/rules/parsed.cp311-win32.pyd +0 -0
  230. constricter/rules/parsed.py +136 -0
  231. constricter/rules/quoted.cp311-win32.pyd +0 -0
  232. constricter/rules/quoted.py +141 -0
  233. constricter/rules/rebinding.cp311-win32.pyd +0 -0
  234. constricter/rules/rebinding.py +169 -0
  235. constricter/rules/recorded.cp311-win32.pyd +0 -0
  236. constricter/rules/recorded.py +168 -0
  237. constricter/rules/redundant.cp311-win32.pyd +0 -0
  238. constricter/rules/redundant.py +160 -0
  239. constricter/rules/scope.cp311-win32.pyd +0 -0
  240. constricter/rules/scope.py +802 -0
  241. constricter/rules/scoped.cp311-win32.pyd +0 -0
  242. constricter/rules/scoped.py +232 -0
  243. constricter/rules/scoping.cp311-win32.pyd +0 -0
  244. constricter/rules/scoping.py +346 -0
  245. constricter/rules/settings.cp311-win32.pyd +0 -0
  246. constricter/rules/settings.py +189 -0
  247. constricter/rules/syntax.cp311-win32.pyd +0 -0
  248. constricter/rules/syntax.py +472 -0
  249. constricter/rules/tables.cp311-win32.pyd +0 -0
  250. constricter/rules/tables.py +169 -0
  251. constricter/rules/walked.cp311-win32.pyd +0 -0
  252. constricter/rules/walked.py +127 -0
  253. constricter/rules/widened.cp311-win32.pyd +0 -0
  254. constricter/rules/widened.py +322 -0
  255. constricter/trace.py +374 -0
  256. e45ce06b2fdd5c43f782__mypyc.cp311-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,460 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """What a module passes checked files' functions whose parameters aren't all annotated (`observed`).
3
+
4
+ Each call to one is recorded with what `--fix` knows each argument's type to be where it's made: in a
5
+ function, by that function's scope; elsewhere (a module or class body, a lambda), by the value alone.
6
+ Only a type made of builtins alone counts (`int`, `list[str]`, not a union): another's spelling may
7
+ mean nothing in the callee's module, and a union is what a check narrows. A guess counts, with what
8
+ it rests on: `--unsafe-fixes` writes it, and the next run would take it as certain. A function
9
+ used any way but called (a callback, a stored reference) escapes: its callers can't all be known.
10
+ A name the calling function binds itself isn't the module's function, whatever it's called.
11
+ """
12
+
13
+ import ast
14
+ import builtins
15
+ from collections.abc import Mapping, Sequence
16
+ from dataclasses import replace
17
+ from typing import Final, cast
18
+
19
+ from constricter.fix.core.known import (
20
+ PARAM,
21
+ Call,
22
+ Callee,
23
+ ImportPlan,
24
+ Inference,
25
+ Known,
26
+ LibraryNames,
27
+ Observed,
28
+ Passed,
29
+ Seeds,
30
+ )
31
+ from constricter.fix.index import callers, fixtures
32
+ from constricter.fix.libraries import stdlib
33
+ from constricter.fix.values.guesses import guessing
34
+ from constricter.fix.values.inference import inference, scalar
35
+ from constricter.rules.annotations import dotted
36
+ from constricter.rules.decorators import FIXTURES, is_fixture
37
+ from constricter.rules.decorators import spelled as decorated_as
38
+ from constricter.rules.flow import members
39
+ from constricter.rules.scope import Scope, guesses_in
40
+ from constricter.rules.scoped import Seeded, Settings
41
+ from constricter.rules.syntax import FunctionDef, Start, has_within, own_nodes
42
+ from constricter.rules.walked import classes, of_type
43
+
44
+ _BUILTINS: Final = frozenset(dir(builtins))
45
+ _TEST: Final = "test" # how pytest's test functions' names start
46
+ _NONE: Final = "None" # says nothing of what the parameter's other callers pass, nor what it's for
47
+ _DEFINED: Final = (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef) # what binds a name, inside
48
+ _REQUEST: Final = "request" # the parameter pytest gives a fixture its request by
49
+ _PARAMS: Final = "params" # `pytest.fixture`'s keyword: the values `request.param` takes in turn
50
+
51
+
52
+ def observed(
53
+ tree: ast.Module,
54
+ scopes: Sequence[Scope],
55
+ known: Known,
56
+ callees: Mapping[str, Callee],
57
+ ) -> Observed:
58
+ """Find what the module passes, and does with, the functions in `callees` (as it spells them).
59
+
60
+ Returns:
61
+ Each one's calls, and those that escape.
62
+
63
+ """
64
+ named: list[tuple[ast.AST, str]]
65
+ if not (named := _naming(tree, callees) if callees else []):
66
+ return Observed()
67
+ owner: dict[int, Scope] # each such node's innermost function scope, by `id()`
68
+ bound: dict[int, frozenset[str]] # each of those functions' own names, by its scope's `id()`
69
+ owner, bound = _owning(scopes, [node for node, _ in named])
70
+ called: set[int] = {id(node.func) for node in cast("list[ast.Call]", of_type(tree, ast.Call))}
71
+ calls: dict[Callee, list[Call]] = {}
72
+ escaped: set[Callee] = set()
73
+ node: ast.AST
74
+ spelled: str
75
+ for node, spelled in named:
76
+ where: Scope | None = owner.get(id(node))
77
+ if where is not None and spelled.partition(".")[0] in bound[id(where)]:
78
+ continue
79
+ if isinstance(node, ast.Call):
80
+ calls.setdefault(callees[spelled], []).append(_call(node, where, known))
81
+ elif id(node) not in called and isinstance(cast("ast.Name", node).ctx, ast.Load):
82
+ escaped.add(callees[spelled])
83
+ return Observed({callee: tuple(found) for callee, found in calls.items()}, frozenset(escaped))
84
+
85
+
86
+ def _owning(
87
+ scopes: Sequence[Scope],
88
+ named: Sequence[ast.AST],
89
+ ) -> tuple[dict[int, Scope], dict[int, frozenset[str]]]:
90
+ """Find the innermost function scope of each node in `named`, and what each such function binds.
91
+
92
+ Only the functions one of them is in are walked: most have none.
93
+
94
+ Returns:
95
+ Each node's scope, by the node's `id()` (none for one outside any function's own body); and
96
+ each of those scopes' function's own names, by the scope's `id()`.
97
+
98
+ """
99
+ wanted: set[int] = {id(node) for node in named}
100
+ starts: list[Start] = sorted(
101
+ (cast("ast.expr", node).lineno, cast("ast.expr", node).col_offset) for node in named
102
+ )
103
+ owner: dict[int, Scope] = {}
104
+ bound: dict[int, frozenset[str]] = {}
105
+ scope: Scope
106
+ for scope in scopes:
107
+ if scope.kind.function is not None and has_within(starts, scope.kind.function):
108
+ nodes: list[ast.AST] = list(own_nodes(scope.kind.function.body))
109
+ owner.update((id(node), scope) for node in nodes if id(node) in wanted)
110
+ bound[id(scope)] = _bound(scope.kind.function, nodes)
111
+ return owner, bound
112
+
113
+
114
+ def _naming(tree: ast.Module, callees: Mapping[str, Callee]) -> list[tuple[ast.AST, str]]:
115
+ """Find the module's calls, names and attributes that spell one of `callees`.
116
+
117
+ From its one shared walk, each looked at only if its last name is one of theirs.
118
+
119
+ Returns:
120
+ Each such node, with what it spells (a call's callee), calls first, each kind in the walk's order.
121
+
122
+ """
123
+ last: frozenset[str] = frozenset(spelled.rpartition(".")[2] for spelled in callees)
124
+ found: list[tuple[ast.AST, str]] = []
125
+ node: ast.AST
126
+ for node in of_type(tree, ast.Call, ast.Name, ast.Attribute):
127
+ target: ast.AST = node.func if isinstance(node, ast.Call) else node
128
+ name: str = (
129
+ target.id
130
+ if isinstance(target, ast.Name)
131
+ else target.attr
132
+ if isinstance(target, ast.Attribute)
133
+ else ""
134
+ )
135
+ spelled: str | None = dotted(cast("ast.expr", target)) if name in last else None
136
+ if spelled is not None and spelled in callees:
137
+ found.append((node, spelled))
138
+ return found
139
+
140
+
141
+ def _bound(
142
+ function: ast.FunctionDef | ast.AsyncFunctionDef,
143
+ nodes: Sequence[ast.AST],
144
+ *,
145
+ imports: bool = True,
146
+ ) -> frozenset[str]:
147
+ """Name what a function binds itself: its parameters, and what it assigns or (`imports`) imports.
148
+
149
+ Returns:
150
+ Them.
151
+
152
+ """
153
+ args: ast.arguments = function.args
154
+ params: list[ast.arg | None] = [*args.posonlyargs, *args.args, *args.kwonlyargs, args.vararg, args.kwarg]
155
+ return frozenset(
156
+ [
157
+ *(arg.arg for arg in params if arg is not None),
158
+ *(node.id for node in nodes if isinstance(node, ast.Name) and not isinstance(node.ctx, ast.Load)),
159
+ *(
160
+ (alias.asname or alias.name).partition(".")[0]
161
+ for node in nodes
162
+ if imports and isinstance(node, ast.Import | ast.ImportFrom)
163
+ for alias in node.names
164
+ ),
165
+ *(node.name for node in nodes if isinstance(node, _DEFINED)),
166
+ ],
167
+ )
168
+
169
+
170
+ def _call(call: ast.Call, where: Scope | None, known: Known) -> Call:
171
+ """Read one call's arguments' types (see the module docstring).
172
+
173
+ Returns:
174
+ Them; a call unpacking `*args` or `**kwargs` is only marked so.
175
+
176
+ """
177
+ if any(isinstance(arg, ast.Starred) for arg in call.args) or any(k.arg is None for k in call.keywords):
178
+ return Call(unpacked=True)
179
+ return Call(
180
+ tuple(_type(arg, where, known) for arg in call.args),
181
+ tuple((keyword.arg or "", _type(keyword.value, where, known)) for keyword in call.keywords),
182
+ )
183
+
184
+
185
+ def _type(value: ast.expr, where: Scope | None, known: Known) -> Passed | None:
186
+ """Type one argument, with builtins alone (see the module docstring).
187
+
188
+ Returns:
189
+ Its type, and what it rests on if it's a guess; or `None`.
190
+
191
+ """
192
+ typed: Inference | None
193
+ guess: tuple[bool, frozenset[str]]
194
+ if where is None:
195
+ typed = inference(value, known, {})
196
+ guess = guessing(value, known, frozenset(), {}, {})
197
+ else:
198
+ typed = inference(value, where.settings.known, where.inferred.types)
199
+ guess = guesses_in(where, [value])
200
+ if typed is None or not plain(typed.annotation, known):
201
+ return None
202
+ return typed.annotation, guess[1] if guess[0] else frozenset()
203
+
204
+
205
+ def plain(annotation: str, known: Known) -> bool:
206
+ """Check an annotation is made of builtins alone, none of them rebound, and isn't a union.
207
+
208
+ Returns:
209
+ Whether it is.
210
+
211
+ """
212
+ tree: ast.expr = ast.parse(annotation, mode="eval").body
213
+ return (
214
+ annotation != _NONE
215
+ and len(members(annotation) or ()) == 1
216
+ and all(
217
+ isinstance(node, ast.Name) and node.id in _BUILTINS and known.is_builtin(node.id)
218
+ for node in ast.walk(tree)
219
+ if isinstance(node, ast.Name | ast.Attribute)
220
+ )
221
+ )
222
+
223
+
224
+ def keyed(tree: ast.Module, typed: Seeds) -> dict[int, Mapping[str, Passed]]:
225
+ """Key what every call passes the module's top-level functions' parameters by each function's `id()`.
226
+
227
+ A nested function or method of the same name isn't the one its callers call.
228
+
229
+ Returns:
230
+ Each function's parameters' types.
231
+
232
+ """
233
+ return {
234
+ id(stmt): typed[stmt.name]
235
+ for stmt in tree.body
236
+ if isinstance(stmt, ast.FunctionDef | ast.AsyncFunctionDef) and stmt.name in typed
237
+ }
238
+
239
+
240
+ def seed_parameters(scope: Scope, func: FunctionDef, named: Sequence[ast.arg]) -> None:
241
+ """Type the unannotated parameters every call passes one type (see `fix.index.callers`), as guesses.
242
+
243
+ Not one the function binds again itself, whose type a guess from its callers wouldn't be after.
244
+ """
245
+ seeded: Seeded = scope.settings.parameters or Seeded()
246
+ typed: Mapping[str, Passed] = seeded.callers.get(id(func), {})
247
+ injected: Mapping[str, Passed] = _injected(seeded, func, scope.settings.known)
248
+ varied: Passed | None = _varied(func)
249
+ if not (typed or injected or varied):
250
+ return
251
+ rebound: set[str] = {
252
+ node.id
253
+ for node in own_nodes(func.body)
254
+ if isinstance(node, ast.Name) and not isinstance(node.ctx, ast.Load)
255
+ }
256
+ arg: ast.arg
257
+ for arg in named:
258
+ given: Passed | None = typed.get(arg.arg)
259
+ if varied is not None and arg.arg == _REQUEST and arg.arg not in rebound:
260
+ scope.inferred.learn(f"{arg.arg}{PARAM}", *varied)
261
+ if arg.annotation is not None or arg.arg in rebound:
262
+ continue
263
+ if arg.arg in injected:
264
+ scope.inferred.learn(arg.arg, *injected[arg.arg])
265
+ elif given is not None and plain(given[0], scope.settings.known):
266
+ scope.inferred.learn(arg.arg, given[0], frozenset({callers.KIND}) | given[1])
267
+
268
+
269
+ def _injected(seeded: Seeded, func: FunctionDef, known: Known) -> Mapping[str, Passed]:
270
+ """Type the parameters pytest gives a test or a fixture: its fixtures' values, and `parametrize`'s.
271
+
272
+ A fixture the module's tests can take (see `fix.index.fixtures`), by its name, or else one of
273
+ pytest's own (`tmp_path`, a `Path`: see `fixtures.STDLIB_OWN`); and, before them, a name
274
+ `@pytest.mark.parametrize` gives literals of one type, on the function or (`Seeded.marked`) its
275
+ class. Guesses (`fixture`).
276
+
277
+ Returns:
278
+ Each such parameter's type and what it rests on; nothing for any other function.
279
+
280
+ """
281
+ if not (func.name.startswith(_TEST) or is_fixture(func)):
282
+ return {}
283
+ classed: Mapping[str, Passed] = seeded.marked.get(id(func), {})
284
+ return {**_own(func, known), **seeded.fixtures, **classed, **_parametrized(func.decorator_list)}
285
+
286
+
287
+ def _own(func: FunctionDef, known: Known) -> dict[str, Passed]:
288
+ """Type the parameters of `func` that name pytest's own fixtures (see `fixtures.STDLIB_OWN`).
289
+
290
+ Returns:
291
+ Each one's type, as the module can write it, a guess resting on `fixture`.
292
+
293
+ """
294
+ plan: ImportPlan | None = known.names.plan
295
+ args: ast.arguments = func.args
296
+ named: set[str] = {arg.arg for arg in (*args.posonlyargs, *args.args, *args.kwonlyargs)}
297
+ found: dict[str, Passed] = {}
298
+ name: str
299
+ for name in sorted(fixtures.STDLIB_OWN.keys() & named):
300
+ spelled: str | None
301
+ if (spelled := None if plan is None else plan.spell(fixtures.STDLIB_OWN[name])) is not None:
302
+ found[name] = (spelled, frozenset({fixtures.KIND}))
303
+ return found
304
+
305
+
306
+ def marked(tree: ast.Module) -> dict[int, Mapping[str, Passed]]:
307
+ """Type the names `@pytest.mark.parametrize` on a class gives each of its methods (see `_parametrized`).
308
+
309
+ Returns:
310
+ Each such method's, by its `id()`: those directly in a class decorated so.
311
+
312
+ """
313
+ found: dict[int, Mapping[str, Passed]] = {}
314
+ node: ast.ClassDef
315
+ for node in classes(tree):
316
+ typed: dict[str, Passed]
317
+ if typed := _parametrized(node.decorator_list):
318
+ found.update(
319
+ (id(stmt), typed)
320
+ for stmt in node.body
321
+ if isinstance(stmt, ast.FunctionDef | ast.AsyncFunctionDef)
322
+ )
323
+ return found
324
+
325
+
326
+ def _varied(func: FunctionDef) -> Passed | None:
327
+ """Type what a fixture's `request.param` gives: its decorator's `params`, literals of one type.
328
+
329
+ Returns:
330
+ The type, a guess resting on `fixture` (a test's indirect `parametrize` may give another); or
331
+ `None` for any other function, or `params` not written out so.
332
+
333
+ """
334
+ params: list[ast.expr] = [
335
+ keyword.value
336
+ for decorator in func.decorator_list
337
+ if isinstance(decorator, ast.Call) and decorated_as(decorator) in FIXTURES
338
+ for keyword in decorator.keywords
339
+ if keyword.arg == _PARAMS
340
+ ]
341
+ cases: list[ast.expr] = list(params[0].elts) if params and isinstance(params[0], ast.List) else []
342
+ types: set[str | None] = {scalar(case) for case in cases}
343
+ only: str | None = types.pop() if len(types) == 1 else None
344
+ return None if only is None or only == _NONE else (only, frozenset({fixtures.KIND}))
345
+
346
+
347
+ def _parametrized(decorators: Sequence[ast.expr]) -> dict[str, Passed]:
348
+ """Type the names `@pytest.mark.parametrize` decorators give a function literals of one type each.
349
+
350
+ `"n"` with `[1, 2]`, or `"n, s"` (or `("n", "s")`) with `[(1, "a"), (2, "b")]`.
351
+
352
+ Returns:
353
+ Each one's type, a guess resting on `fixture`.
354
+
355
+ """
356
+ found: dict[str, Passed] = {}
357
+ decorator: ast.expr
358
+ for decorator in decorators:
359
+ names: list[str]
360
+ rows: list[list[ast.expr]]
361
+ names, rows = _cases(decorator)
362
+ at: int
363
+ name: str
364
+ for at, name in enumerate(names):
365
+ types: set[str | None] = {scalar(row[at]) if len(row) == len(names) else None for row in rows}
366
+ only: str | None = types.pop() if len(types) == 1 else None
367
+ if only is not None and only != _NONE:
368
+ found[name] = (only, frozenset({fixtures.KIND}))
369
+ return found
370
+
371
+
372
+ def _cases(decorator: ast.expr) -> tuple[list[str], list[list[ast.expr]]]:
373
+ """Read a `parametrize` decorator: the names it gives values, and each case's values.
374
+
375
+ Returns:
376
+ Them; no names for any other decorator, or one whose names or cases aren't written out.
377
+
378
+ """
379
+ names: ast.expr
380
+ cases: list[ast.expr]
381
+ parts: list[ast.expr]
382
+ text: str
383
+ match decorator:
384
+ case ast.Call(func=ast.Attribute(attr="parametrize"), args=[names, ast.List(elts=cases), *_]):
385
+ pass
386
+ case _:
387
+ return [], []
388
+ found: list[str]
389
+ match names:
390
+ case ast.Constant(value=str() as text):
391
+ found = [part.strip() for part in text.split(",")]
392
+ case ast.Tuple(elts=parts) | ast.List(elts=parts) if all(
393
+ isinstance(part, ast.Constant) and isinstance(part.value, str) for part in parts
394
+ ):
395
+ found = [str(cast("ast.Constant", part).value) for part in parts]
396
+ case _:
397
+ return [], []
398
+ if len(found) == 1:
399
+ return found, [[case] for case in cases]
400
+ return found, [list(case.elts) if isinstance(case, ast.Tuple | ast.List) else [] for case in cases]
401
+
402
+
403
+ def unshadowed(settings: Settings, function: FunctionDef) -> Settings:
404
+ """Fit the standard-library names a function is checked knowing to what it binds itself.
405
+
406
+ A parameter, local, import or nested definition named `getpid` isn't `os.getpid`, whatever the
407
+ module imports; a name its own imports alone bind is what they import (`import os`, in its
408
+ body). The functions inside it are checked knowing the same.
409
+
410
+ Returns:
411
+ The settings, with those names; the same settings if it binds none of them.
412
+
413
+ """
414
+ names: LibraryNames = settings.known.names
415
+ rebound: Mapping[str, Sequence[Start]] | None = settings.facts.rebound
416
+ lazy: Sequence[Start] | None = settings.facts.lazy
417
+ # Most functions bind none: no name the module imports, and no import. They aren't walked.
418
+ if (
419
+ rebound is not None
420
+ and lazy is not None
421
+ and not has_within(lazy, function)
422
+ and not any(has_within(rebound[name], function) for name in names.stdlib.keys() & rebound.keys())
423
+ ):
424
+ return settings
425
+ nodes: list[ast.AST] = list(own_nodes(function.body))
426
+ bound: frozenset[str] = _bound(function, nodes)
427
+ own: dict[str, str] = _own_imports(nodes, _bound(function, nodes, imports=False))
428
+ if not (names.stdlib.keys() & bound or own):
429
+ return settings
430
+ stdlib_names: dict[str, str] = {
431
+ name: origin for name, origin in names.stdlib.items() if name not in bound
432
+ } | own
433
+ return replace(settings, known=replace(settings.known, names=names._replace(stdlib=stdlib_names)))
434
+
435
+
436
+ def _own_imports(nodes: Sequence[ast.AST], assigned: frozenset[str]) -> dict[str, str]:
437
+ """Map the names a function's own imports bind to what they are in the standard library.
438
+
439
+ Not a name it binds another way too (`assigned`), nor one two imports bind to different things.
440
+
441
+ Returns:
442
+ Each name, mapped to its dotted origin (see `stdlib.origins`).
443
+
444
+ """
445
+ origins: dict[str, set[str | None]] = {}
446
+ node: ast.AST
447
+ for node in nodes:
448
+ if isinstance(node, ast.Import | ast.ImportFrom):
449
+ known: dict[str, str] = stdlib.imported([node])
450
+ alias: ast.alias
451
+ for alias in node.names:
452
+ name: str = (alias.asname or alias.name).partition(".")[0]
453
+ origins.setdefault(name, set()).add(known.get(name))
454
+ return {
455
+ name: origin
456
+ for name, found in origins.items()
457
+ if name not in assigned and len(found) == 1
458
+ for origin in found
459
+ if origin is not None
460
+ }