python-constricter 0.4.1__cp311-cp311-win_amd64.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-win_amd64.pyd +0 -0
  29. constricter/fix/__init__.py +2 -0
  30. constricter/fix/core/__init__.cp311-win_amd64.pyd +0 -0
  31. constricter/fix/core/__init__.py +2 -0
  32. constricter/fix/core/asked.cp311-win_amd64.pyd +0 -0
  33. constricter/fix/core/asked.py +56 -0
  34. constricter/fix/core/fixes.cp311-win_amd64.pyd +0 -0
  35. constricter/fix/core/fixes.py +317 -0
  36. constricter/fix/core/imports.cp311-win_amd64.pyd +0 -0
  37. constricter/fix/core/imports.py +580 -0
  38. constricter/fix/core/inherited.cp311-win_amd64.pyd +0 -0
  39. constricter/fix/core/inherited.py +311 -0
  40. constricter/fix/core/known.cp311-win_amd64.pyd +0 -0
  41. constricter/fix/core/known.py +730 -0
  42. constricter/fix/core/signatures.cp311-win_amd64.pyd +0 -0
  43. constricter/fix/core/signatures.py +132 -0
  44. constricter/fix/index/__init__.cp311-win_amd64.pyd +0 -0
  45. constricter/fix/index/__init__.py +2 -0
  46. constricter/fix/index/awaits.cp311-win_amd64.pyd +0 -0
  47. constricter/fix/index/awaits.py +90 -0
  48. constricter/fix/index/beyond.cp311-win_amd64.pyd +0 -0
  49. constricter/fix/index/beyond.py +149 -0
  50. constricter/fix/index/callers.cp311-win_amd64.pyd +0 -0
  51. constricter/fix/index/callers.py +155 -0
  52. constricter/fix/index/crossed.cp311-win_amd64.pyd +0 -0
  53. constricter/fix/index/crossed.py +327 -0
  54. constricter/fix/index/decorated.cp311-win_amd64.pyd +0 -0
  55. constricter/fix/index/decorated.py +140 -0
  56. constricter/fix/index/fixtures.cp311-win_amd64.pyd +0 -0
  57. constricter/fix/index/fixtures.py +176 -0
  58. constricter/fix/index/linked.cp311-win_amd64.pyd +0 -0
  59. constricter/fix/index/linked.py +142 -0
  60. constricter/fix/index/loose.cp311-win_amd64.pyd +0 -0
  61. constricter/fix/index/loose.py +103 -0
  62. constricter/fix/index/managed.cp311-win_amd64.pyd +0 -0
  63. constricter/fix/index/managed.py +63 -0
  64. constricter/fix/index/modules.cp311-win_amd64.pyd +0 -0
  65. constricter/fix/index/modules.py +741 -0
  66. constricter/fix/index/offers.cp311-win_amd64.pyd +0 -0
  67. constricter/fix/index/offers.py +252 -0
  68. constricter/fix/index/order.cp311-win_amd64.pyd +0 -0
  69. constricter/fix/index/order.py +155 -0
  70. constricter/fix/index/origins.cp311-win_amd64.pyd +0 -0
  71. constricter/fix/index/origins.py +238 -0
  72. constricter/fix/index/own_methods.cp311-win_amd64.pyd +0 -0
  73. constricter/fix/index/own_methods.py +91 -0
  74. constricter/fix/index/own_overloads.cp311-win_amd64.pyd +0 -0
  75. constricter/fix/index/own_overloads.py +70 -0
  76. constricter/fix/index/own_types.cp311-win_amd64.pyd +0 -0
  77. constricter/fix/index/own_types.py +330 -0
  78. constricter/fix/index/params.cp311-win_amd64.pyd +0 -0
  79. constricter/fix/index/params.py +81 -0
  80. constricter/fix/index/plain.cp311-win_amd64.pyd +0 -0
  81. constricter/fix/index/plain.py +221 -0
  82. constricter/fix/index/project.cp311-win_amd64.pyd +0 -0
  83. constricter/fix/index/project.py +797 -0
  84. constricter/fix/index/sides.cp311-win_amd64.pyd +0 -0
  85. constricter/fix/index/sides.py +160 -0
  86. constricter/fix/index/strings.cp311-win_amd64.pyd +0 -0
  87. constricter/fix/index/strings.py +46 -0
  88. constricter/fix/index/stubs/__init__.cp311-win_amd64.pyd +0 -0
  89. constricter/fix/index/stubs/__init__.py +2 -0
  90. constricter/fix/index/stubs/atoms.cp311-win_amd64.pyd +0 -0
  91. constricter/fix/index/stubs/atoms.py +425 -0
  92. constricter/fix/index/stubs/chained.cp311-win_amd64.pyd +0 -0
  93. constricter/fix/index/stubs/chained.py +69 -0
  94. constricter/fix/index/stubs/classnames.cp311-win_amd64.pyd +0 -0
  95. constricter/fix/index/stubs/classnames.py +124 -0
  96. constricter/fix/index/stubs/declared.cp311-win_amd64.pyd +0 -0
  97. constricter/fix/index/stubs/declared.py +508 -0
  98. constricter/fix/index/stubs/installed.cp311-win_amd64.pyd +0 -0
  99. constricter/fix/index/stubs/installed.py +425 -0
  100. constricter/fix/index/stubs/names.cp311-win_amd64.pyd +0 -0
  101. constricter/fix/index/stubs/names.py +200 -0
  102. constricter/fix/index/stubs/read.cp311-win_amd64.pyd +0 -0
  103. constricter/fix/index/stubs/read.py +70 -0
  104. constricter/fix/index/stubs/signatures.cp311-win_amd64.pyd +0 -0
  105. constricter/fix/index/stubs/signatures.py +656 -0
  106. constricter/fix/index/stubs/stubbed.cp311-win_amd64.pyd +0 -0
  107. constricter/fix/index/stubs/stubbed.py +208 -0
  108. constricter/fix/index/taken.cp311-win_amd64.pyd +0 -0
  109. constricter/fix/index/taken.py +45 -0
  110. constricter/fix/index/tuples.cp311-win_amd64.pyd +0 -0
  111. constricter/fix/index/tuples.py +112 -0
  112. constricter/fix/libraries/__init__.cp311-win_amd64.pyd +0 -0
  113. constricter/fix/libraries/__init__.py +2 -0
  114. constricter/fix/libraries/library.cp311-win_amd64.pyd +0 -0
  115. constricter/fix/libraries/library.py +420 -0
  116. constricter/fix/libraries/opened.cp311-win_amd64.pyd +0 -0
  117. constricter/fix/libraries/opened.py +147 -0
  118. constricter/fix/libraries/overloads.cp311-win_amd64.pyd +0 -0
  119. constricter/fix/libraries/overloads.py +960 -0
  120. constricter/fix/libraries/receivers.cp311-win_amd64.pyd +0 -0
  121. constricter/fix/libraries/receivers.py +274 -0
  122. constricter/fix/libraries/stdlib.cp311-win_amd64.pyd +0 -0
  123. constricter/fix/libraries/stdlib.py +752 -0
  124. constricter/fix/libraries/walks.cp311-win_amd64.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-win_amd64.pyd +0 -0
  148. constricter/fix/values/__init__.py +2 -0
  149. constricter/fix/values/aliased.cp311-win_amd64.pyd +0 -0
  150. constricter/fix/values/aliased.py +195 -0
  151. constricter/fix/values/bodies.cp311-win_amd64.pyd +0 -0
  152. constricter/fix/values/bodies.py +76 -0
  153. constricter/fix/values/callables.cp311-win_amd64.pyd +0 -0
  154. constricter/fix/values/callables.py +323 -0
  155. constricter/fix/values/called.cp311-win_amd64.pyd +0 -0
  156. constricter/fix/values/called.py +194 -0
  157. constricter/fix/values/classvars.cp311-win_amd64.pyd +0 -0
  158. constricter/fix/values/classvars.py +629 -0
  159. constricter/fix/values/decided.cp311-win_amd64.pyd +0 -0
  160. constricter/fix/values/decided.py +429 -0
  161. constricter/fix/values/displays.cp311-win_amd64.pyd +0 -0
  162. constricter/fix/values/displays.py +101 -0
  163. constricter/fix/values/doubts.cp311-win_amd64.pyd +0 -0
  164. constricter/fix/values/doubts.py +677 -0
  165. constricter/fix/values/entered.cp311-win_amd64.pyd +0 -0
  166. constricter/fix/values/entered.py +332 -0
  167. constricter/fix/values/fills.cp311-win_amd64.pyd +0 -0
  168. constricter/fix/values/fills.py +446 -0
  169. constricter/fix/values/guesses.cp311-win_amd64.pyd +0 -0
  170. constricter/fix/values/guesses.py +376 -0
  171. constricter/fix/values/hinted.cp311-win_amd64.pyd +0 -0
  172. constricter/fix/values/hinted.py +811 -0
  173. constricter/fix/values/inference.cp311-win_amd64.pyd +0 -0
  174. constricter/fix/values/inference.py +698 -0
  175. constricter/fix/values/loops.cp311-win_amd64.pyd +0 -0
  176. constricter/fix/values/loops.py +335 -0
  177. constricter/fix/values/members.cp311-win_amd64.pyd +0 -0
  178. constricter/fix/values/members.py +578 -0
  179. constricter/fix/values/narrowed.cp311-win_amd64.pyd +0 -0
  180. constricter/fix/values/narrowed.py +220 -0
  181. constricter/fix/values/operated.cp311-win_amd64.pyd +0 -0
  182. constricter/fix/values/operated.py +202 -0
  183. constricter/fix/values/ordered.cp311-win_amd64.pyd +0 -0
  184. constricter/fix/values/ordered.py +74 -0
  185. constricter/fix/values/returned.cp311-win_amd64.pyd +0 -0
  186. constricter/fix/values/returned.py +479 -0
  187. constricter/fix/values/returns.cp311-win_amd64.pyd +0 -0
  188. constricter/fix/values/returns.py +237 -0
  189. constricter/fix/values/shapes.cp311-win_amd64.pyd +0 -0
  190. constricter/fix/values/shapes.py +654 -0
  191. constricter/fix/values/stored.cp311-win_amd64.pyd +0 -0
  192. constricter/fix/values/stored.py +564 -0
  193. constricter/fix/values/targets.cp311-win_amd64.pyd +0 -0
  194. constricter/fix/values/targets.py +419 -0
  195. constricter/fix/values/unchecked.cp311-win_amd64.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-win_amd64.pyd +0 -0
  208. constricter/rules/__init__.py +2 -0
  209. constricter/rules/annotations.cp311-win_amd64.pyd +0 -0
  210. constricter/rules/annotations.py +296 -0
  211. constricter/rules/binding.cp311-win_amd64.pyd +0 -0
  212. constricter/rules/binding.py +652 -0
  213. constricter/rules/calls.cp311-win_amd64.pyd +0 -0
  214. constricter/rules/calls.py +460 -0
  215. constricter/rules/checker.cp311-win_amd64.pyd +0 -0
  216. constricter/rules/checker.py +536 -0
  217. constricter/rules/declarations.cp311-win_amd64.pyd +0 -0
  218. constricter/rules/declarations.py +730 -0
  219. constricter/rules/decorators.cp311-win_amd64.pyd +0 -0
  220. constricter/rules/decorators.py +351 -0
  221. constricter/rules/flow.cp311-win_amd64.pyd +0 -0
  222. constricter/rules/flow.py +502 -0
  223. constricter/rules/keys.cp311-win_amd64.pyd +0 -0
  224. constricter/rules/keys.py +79 -0
  225. constricter/rules/late.cp311-win_amd64.pyd +0 -0
  226. constricter/rules/late.py +969 -0
  227. constricter/rules/narrowing.cp311-win_amd64.pyd +0 -0
  228. constricter/rules/narrowing.py +98 -0
  229. constricter/rules/parsed.cp311-win_amd64.pyd +0 -0
  230. constricter/rules/parsed.py +136 -0
  231. constricter/rules/quoted.cp311-win_amd64.pyd +0 -0
  232. constricter/rules/quoted.py +141 -0
  233. constricter/rules/rebinding.cp311-win_amd64.pyd +0 -0
  234. constricter/rules/rebinding.py +169 -0
  235. constricter/rules/recorded.cp311-win_amd64.pyd +0 -0
  236. constricter/rules/recorded.py +168 -0
  237. constricter/rules/redundant.cp311-win_amd64.pyd +0 -0
  238. constricter/rules/redundant.py +160 -0
  239. constricter/rules/scope.cp311-win_amd64.pyd +0 -0
  240. constricter/rules/scope.py +802 -0
  241. constricter/rules/scoped.cp311-win_amd64.pyd +0 -0
  242. constricter/rules/scoped.py +232 -0
  243. constricter/rules/scoping.cp311-win_amd64.pyd +0 -0
  244. constricter/rules/scoping.py +346 -0
  245. constricter/rules/settings.cp311-win_amd64.pyd +0 -0
  246. constricter/rules/settings.py +189 -0
  247. constricter/rules/syntax.cp311-win_amd64.pyd +0 -0
  248. constricter/rules/syntax.py +472 -0
  249. constricter/rules/tables.cp311-win_amd64.pyd +0 -0
  250. constricter/rules/tables.py +169 -0
  251. constricter/rules/walked.cp311-win_amd64.pyd +0 -0
  252. constricter/rules/walked.py +127 -0
  253. constricter/rules/widened.cp311-win_amd64.pyd +0 -0
  254. constricter/rules/widened.py +322 -0
  255. constricter/trace.py +374 -0
  256. e45ce06b2fdd5c43f782__mypyc.cp311-win_amd64.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,351 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Decorators that give back the function they decorate, whose return still types its calls.
3
+
4
+ One the standard library has (`functools.cache`, `abc.abstractmethod`, `unittest.skipIf(...)`,
5
+ ...), or a function whose own signature says so: it takes `F` and returns `F`, or (a factory, called
6
+ to decorate) returns a `Callable[[F], F]`, where `F` is a type variable, or a `Callable[P, T]`
7
+ returning one. Or, unannotated, one whose body says so: every `return` gives its one parameter back
8
+ (`def _id(obj): return obj`), or what such a decorator gives (`return impl_detail(**g)(test)`); a
9
+ factory's each gives such a decorator (`return unittest.skipUnless(ok, "why")`, `return _id`).
10
+ """
11
+
12
+ import ast
13
+ import re
14
+ from collections.abc import Mapping, Sequence
15
+ from typing import Final, NamedTuple, TypeAlias
16
+
17
+ from constricter.rules.syntax import child_statements, import_bindings
18
+
19
+ _CALLED: Final = "()" # after a decorator's name, as it's spelled when it's called to decorate
20
+ # How a pytest fixture's decorator is spelled (see `spelled`).
21
+ FIXTURES: Final = frozenset({"pytest.fixture", "pytest.fixture()", "fixture", "fixture()"})
22
+ _CALLABLE: Final = "Callable"
23
+ _DOTTED: Final = re.compile(r"[^\W\d]\w*(?:\.[^\W\d]\w*)*") # `name`, `pkg.util.name`
24
+ _Use: TypeAlias = tuple[bool, bool] # whether a decorator decorates bare, and called
25
+ _Uses: TypeAlias = dict[str, _Use] # each decorator's, by its name
26
+ # The standard library's, by where each is from.
27
+ _BARE: Final = (True, False)
28
+ _EITHER: Final = (True, True)
29
+ _FACTORY: Final = (False, True)
30
+ _LIBRARY: Final[dict[str, _Uses]] = {
31
+ "functools": {"cache": _BARE, "lru_cache": _EITHER, "wraps": _FACTORY},
32
+ "abc": {"abstractmethod": _BARE},
33
+ "typing": {"final": _BARE, "override": _BARE},
34
+ "typing_extensions": {"final": _BARE, "override": _BARE, "deprecated": _FACTORY},
35
+ "warnings": {"deprecated": _FACTORY},
36
+ "unittest": {"expectedFailure": _BARE, "skip": _FACTORY, "skipIf": _FACTORY, "skipUnless": _FACTORY},
37
+ }
38
+ _NO_TYPE_VARS: Final = frozenset[str]() # an unannotated pass's: its body, not a signature, says so
39
+
40
+
41
+ class Pass(NamedTuple):
42
+ """A function that gives back what it decorates, by its signature.
43
+
44
+ `called`: whether it's a factory, called to decorate (`@named("x")`); `type_vars`: the names its
45
+ signature takes for type variables, which it is one only if they are.
46
+ """
47
+
48
+ called: bool
49
+ type_vars: frozenset[str]
50
+
51
+ def spelled(self, name: str) -> str:
52
+ """Spell its use as a decorator, as `spelled` reads one.
53
+
54
+ Returns:
55
+ `name`, with `()` after it for a factory.
56
+
57
+ """
58
+ return f"{name}{_CALLED}" if self.called else name
59
+
60
+
61
+ class Held(NamedTuple):
62
+ """A function's declared return, held back for decorators its module can't vouch for alone.
63
+
64
+ `decorators`: those, as `spelled` reads them.
65
+ """
66
+
67
+ returns: str
68
+ decorators: tuple[str, ...]
69
+
70
+
71
+ def is_fixture(function: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
72
+ """Check whether a function is a pytest fixture: decorated `pytest.fixture`, called or not.
73
+
74
+ Returns:
75
+ Whether it is.
76
+
77
+ """
78
+ return any(spelled(decorator) in FIXTURES for decorator in function.decorator_list)
79
+
80
+
81
+ def spelled(decorator: ast.expr) -> str | None:
82
+ """Spell a decorator as written: `cache`, `functools.cache`, and `named()` for one that's called.
83
+
84
+ Returns:
85
+ It, or `None` for anything but a dotted name or a call of one.
86
+
87
+ """
88
+ func: ast.expr
89
+ match decorator:
90
+ case ast.Call(func=func):
91
+ name: str | None = _dotted(func)
92
+ return None if name is None else f"{name}{_CALLED}"
93
+ case _:
94
+ return _dotted(decorator)
95
+
96
+
97
+ def _dotted(node: ast.expr) -> str | None:
98
+ """Spell a name, or a chain of attributes on one, as written.
99
+
100
+ Returns:
101
+ It, or `None` if `node` is anything else.
102
+
103
+ """
104
+ text: str = ast.unparse(node)
105
+ return text if _DOTTED.fullmatch(text) else None
106
+
107
+
108
+ def passes(tree: ast.Module) -> dict[str, Pass]:
109
+ """Find the module's own top-level functions that give back the function they decorate.
110
+
111
+ A plain `def`, defined once, undecorated: one parameter annotated as its return is (bare), or a
112
+ return of `Callable[[A], A]` (a factory), where `A` is a name or a `Callable[..., T]`. Or one
113
+ with no annotation at all whose every `return` says so (see `_inferred`), by the standard
114
+ library's decorators the module imports and its own found before it, until no more are.
115
+
116
+ Returns:
117
+ Each one's name, and how it's used (see `Pass`).
118
+
119
+ """
120
+ counts: dict[str, int] = {}
121
+ found: dict[str, Pass] = {}
122
+ plain: list[ast.FunctionDef] = []
123
+ stmt: ast.stmt
124
+ for stmt in tree.body:
125
+ if isinstance(stmt, ast.FunctionDef | ast.AsyncFunctionDef):
126
+ counts[stmt.name] = counts.get(stmt.name, 0) + 1
127
+ read: Pass | None = _pass(stmt) if isinstance(stmt, ast.FunctionDef) else None
128
+ if read is not None and not stmt.decorator_list:
129
+ found[stmt.name] = read
130
+ elif isinstance(stmt, ast.FunctionDef) and not stmt.decorator_list and _unannotated(stmt):
131
+ plain.append(stmt)
132
+ found = {name: read for name, read in found.items() if counts[name] == 1}
133
+ library: frozenset[str] = _library(tree)
134
+ pending: list[ast.FunctionDef] = [func for func in plain if counts[func.name] == 1]
135
+ for _ in pending: # each pass reads at least one more, or ends: as many passes as functions at most
136
+ more: dict[str, Pass] = {}
137
+ func: ast.FunctionDef
138
+ for func in pending:
139
+ if func.name not in found and (read := _inferred(func, library, found)) is not None:
140
+ more[func.name] = read
141
+ if not more:
142
+ break
143
+ found.update(more)
144
+ return found
145
+
146
+
147
+ def _unannotated(func: ast.FunctionDef) -> bool:
148
+ """Check whether a function annotates nothing: no parameter, and no return.
149
+
150
+ Returns:
151
+ Whether it does.
152
+
153
+ """
154
+ args: ast.arguments = func.args
155
+ stars: list[ast.arg] = [arg for arg in (args.vararg, args.kwarg) if arg is not None]
156
+ named: list[ast.arg] = [*args.posonlyargs, *args.args, *args.kwonlyargs, *stars]
157
+ return func.returns is None and all(arg.annotation is None for arg in named)
158
+
159
+
160
+ def _inferred(func: ast.FunctionDef, library: frozenset[str], found: Mapping[str, Pass]) -> Pass | None:
161
+ """Read an unannotated function's `return`s as a decorator's that gives back what it's given.
162
+
163
+ `library` spells the standard library's such decorators as the module imports them, `found` the
164
+ module's own read so far. A bare one has one parameter and returns it, or a factory's call's
165
+ call on it; a factory returns a factory's call, or a bare one's name. Every `return` of its own
166
+ (not a nested function's) must, and it must have one.
167
+
168
+ Returns:
169
+ How it's used, or `None` if its `return`s don't say so.
170
+
171
+ """
172
+ returns: list[ast.Return] = _returns(func.body)
173
+ if not returns or any(stmt.value is None for stmt in returns):
174
+ return None
175
+ factories: frozenset[str] = frozenset(
176
+ {text for text in library if text.endswith(_CALLED)}
177
+ | {read.spelled(name) for name, read in found.items() if read.called},
178
+ )
179
+ bare: frozenset[str] = frozenset(name for name, read in found.items() if not read.called)
180
+ args: ast.arguments = func.args
181
+ named: list[ast.arg] = [*args.posonlyargs, *args.args]
182
+ alone: bool = len(named) == 1 and not (args.kwonlyargs or args.vararg or args.kwarg)
183
+ param: str | None = named[0].arg if alone else None
184
+ values: list[ast.expr] = [stmt.value for stmt in returns if stmt.value is not None]
185
+ if param is not None and all(_gives_back(value, param, factories) for value in values):
186
+ return Pass(called=False, type_vars=_NO_TYPE_VARS)
187
+ if all(_gives_decorator(value, factories, bare) for value in values):
188
+ return Pass(called=True, type_vars=_NO_TYPE_VARS)
189
+ return None
190
+
191
+
192
+ def _returns(body: Sequence[ast.stmt]) -> list[ast.Return]:
193
+ """Find a function's own `return` statements: not a nested function's or class's.
194
+
195
+ Returns:
196
+ Them, in source order.
197
+
198
+ """
199
+ found: list[ast.Return] = []
200
+ stmt: ast.stmt
201
+ for stmt in body:
202
+ if isinstance(stmt, ast.Return):
203
+ found.append(stmt)
204
+ elif not isinstance(stmt, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
205
+ found.extend(_returns(child_statements(stmt)))
206
+ return found
207
+
208
+
209
+ def _gives_back(value: ast.expr, param: str, factories: frozenset[str]) -> bool:
210
+ """Check whether a `return`'s value is the parameter itself, or a factory's decorator applied to it.
211
+
212
+ Returns:
213
+ Whether it is (`return test`, `return impl_detail(cpython=True)(test)`).
214
+
215
+ """
216
+ name: str
217
+ inner: ast.Call
218
+ match value:
219
+ case ast.Name(id=name):
220
+ return name == param
221
+ case ast.Call(func=ast.Call() as inner, args=[ast.Name(id=name)], keywords=[]):
222
+ return name == param and spelled(inner) in factories
223
+ case _:
224
+ return False
225
+
226
+
227
+ def _gives_decorator(value: ast.expr, factories: frozenset[str], bare: frozenset[str]) -> bool:
228
+ """Check whether a `return`'s value is a decorator that gives a function back.
229
+
230
+ Returns:
231
+ Whether it is: a factory's call (`unittest.skip("why")`), or a bare one's name (`_id`).
232
+
233
+ """
234
+ name: str
235
+ match value:
236
+ case ast.Call():
237
+ return spelled(value) in factories
238
+ case ast.Name(id=name):
239
+ return name in bare
240
+ case _:
241
+ return False
242
+
243
+
244
+ def _library(tree: ast.Module) -> frozenset[str]:
245
+ """Spell the standard library's decorators that give a function back, as the module imports them.
246
+
247
+ Returns:
248
+ Each one as `spelled` reads it: `cache`, `functools.lru_cache()`.
249
+
250
+ """
251
+ found: set[str] = set()
252
+ bound: str
253
+ origin: str
254
+ for bound, origin, _ in import_bindings(tree.body):
255
+ module: str
256
+ name: str
257
+ module, _, name = origin.rpartition(".")
258
+ uses: _Uses = (
259
+ {f"{bound}.{known}": use for known, use in _LIBRARY[origin].items()}
260
+ if origin in _LIBRARY
261
+ else {bound: _LIBRARY[module][name]}
262
+ if name in _LIBRARY.get(module, {})
263
+ else {}
264
+ )
265
+ text: str
266
+ bare: bool
267
+ called: bool
268
+ for text, (bare, called) in uses.items():
269
+ found.update([text] * bare + [f"{text}{_CALLED}"] * called)
270
+ return frozenset(found)
271
+
272
+
273
+ def _pass(func: ast.FunctionDef) -> Pass | None:
274
+ """Read a function's signature as a decorator's that gives back what it's given.
275
+
276
+ Returns:
277
+ How it's used, or `None` if its signature doesn't say so.
278
+
279
+ """
280
+ back: ast.expr | None = func.returns
281
+ taken: ast.expr | None = _only(func.args)
282
+ bare: bool = back is not None and taken is not None and ast.dump(taken) == ast.dump(back)
283
+ given: ast.expr | None = back if bare else _decorated(back)
284
+ names: frozenset[str] | None = None if given is None else _type_vars(given)
285
+ return None if names is None else Pass(called=not bare, type_vars=names)
286
+
287
+
288
+ def _decorated(returns: ast.expr | None) -> ast.expr | None:
289
+ """Read what a factory's declared return says its decorator takes and gives back.
290
+
291
+ Returns:
292
+ `Callable[[A], A]`'s `A`, or `None` for any other annotation.
293
+
294
+ """
295
+ given: ast.expr
296
+ back: ast.expr
297
+ match returns:
298
+ case ast.Subscript(slice=ast.Tuple(elts=[ast.List(elts=[given]), back])) if (
299
+ _dotted(returns.value) or ""
300
+ ).endswith(_CALLABLE) and ast.dump(given) == ast.dump(back):
301
+ return given
302
+ case _:
303
+ return None
304
+
305
+
306
+ def _only(args: ast.arguments) -> ast.expr | None:
307
+ """Read the annotation of a signature's one parameter.
308
+
309
+ Returns:
310
+ It, or `None` if it has more than one parameter, or the one isn't annotated.
311
+
312
+ """
313
+ plain: bool = not (args.kwonlyargs or args.vararg or args.kwarg)
314
+ named: list[ast.arg] = [*args.posonlyargs, *args.args]
315
+ return named[0].annotation if plain and len(named) == 1 else None
316
+
317
+
318
+ def _type_vars(annotation: ast.expr) -> frozenset[str] | None:
319
+ """Name what an annotation a decorator takes and gives back must have for type variables.
320
+
321
+ Returns:
322
+ The name itself (`F`); or, of a `Callable[P, T]`, `T` and (if it's a name) `P`; `None` for any
323
+ other annotation.
324
+
325
+ """
326
+ name: str
327
+ taken: ast.expr
328
+ match annotation:
329
+ case ast.Name(id=name):
330
+ return frozenset({name})
331
+ case ast.Subscript(slice=ast.Tuple(elts=[taken, ast.Name(id=name)])) if (
332
+ _dotted(annotation.value) or ""
333
+ ).endswith(_CALLABLE):
334
+ return frozenset({name, taken.id} if isinstance(taken, ast.Name) else {name})
335
+ case _:
336
+ return None
337
+
338
+
339
+ def passing(tree: ast.Module, type_vars: frozenset[str]) -> frozenset[str]:
340
+ """Spell the decorators the module alone vouches for: its uses of them a declared return survives.
341
+
342
+ The standard library's, as it imports them, and its own (see `passes`) whose type variables are
343
+ all among `type_vars`, the module's.
344
+
345
+ Returns:
346
+ Each one as `spelled` reads it: `cache`, `functools.lru_cache()`.
347
+
348
+ """
349
+ return _library(tree) | {
350
+ read.spelled(name) for name, read in passes(tree).items() if read.type_vars <= type_vars
351
+ }