python-constricter 0.4.1__cp313-cp313-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__.cp313-win32.pyd +0 -0
  29. constricter/fix/__init__.py +2 -0
  30. constricter/fix/core/__init__.cp313-win32.pyd +0 -0
  31. constricter/fix/core/__init__.py +2 -0
  32. constricter/fix/core/asked.cp313-win32.pyd +0 -0
  33. constricter/fix/core/asked.py +56 -0
  34. constricter/fix/core/fixes.cp313-win32.pyd +0 -0
  35. constricter/fix/core/fixes.py +317 -0
  36. constricter/fix/core/imports.cp313-win32.pyd +0 -0
  37. constricter/fix/core/imports.py +580 -0
  38. constricter/fix/core/inherited.cp313-win32.pyd +0 -0
  39. constricter/fix/core/inherited.py +311 -0
  40. constricter/fix/core/known.cp313-win32.pyd +0 -0
  41. constricter/fix/core/known.py +730 -0
  42. constricter/fix/core/signatures.cp313-win32.pyd +0 -0
  43. constricter/fix/core/signatures.py +132 -0
  44. constricter/fix/index/__init__.cp313-win32.pyd +0 -0
  45. constricter/fix/index/__init__.py +2 -0
  46. constricter/fix/index/awaits.cp313-win32.pyd +0 -0
  47. constricter/fix/index/awaits.py +90 -0
  48. constricter/fix/index/beyond.cp313-win32.pyd +0 -0
  49. constricter/fix/index/beyond.py +149 -0
  50. constricter/fix/index/callers.cp313-win32.pyd +0 -0
  51. constricter/fix/index/callers.py +155 -0
  52. constricter/fix/index/crossed.cp313-win32.pyd +0 -0
  53. constricter/fix/index/crossed.py +327 -0
  54. constricter/fix/index/decorated.cp313-win32.pyd +0 -0
  55. constricter/fix/index/decorated.py +140 -0
  56. constricter/fix/index/fixtures.cp313-win32.pyd +0 -0
  57. constricter/fix/index/fixtures.py +176 -0
  58. constricter/fix/index/linked.cp313-win32.pyd +0 -0
  59. constricter/fix/index/linked.py +142 -0
  60. constricter/fix/index/loose.cp313-win32.pyd +0 -0
  61. constricter/fix/index/loose.py +103 -0
  62. constricter/fix/index/managed.cp313-win32.pyd +0 -0
  63. constricter/fix/index/managed.py +63 -0
  64. constricter/fix/index/modules.cp313-win32.pyd +0 -0
  65. constricter/fix/index/modules.py +741 -0
  66. constricter/fix/index/offers.cp313-win32.pyd +0 -0
  67. constricter/fix/index/offers.py +252 -0
  68. constricter/fix/index/order.cp313-win32.pyd +0 -0
  69. constricter/fix/index/order.py +155 -0
  70. constricter/fix/index/origins.cp313-win32.pyd +0 -0
  71. constricter/fix/index/origins.py +238 -0
  72. constricter/fix/index/own_methods.cp313-win32.pyd +0 -0
  73. constricter/fix/index/own_methods.py +91 -0
  74. constricter/fix/index/own_overloads.cp313-win32.pyd +0 -0
  75. constricter/fix/index/own_overloads.py +70 -0
  76. constricter/fix/index/own_types.cp313-win32.pyd +0 -0
  77. constricter/fix/index/own_types.py +330 -0
  78. constricter/fix/index/params.cp313-win32.pyd +0 -0
  79. constricter/fix/index/params.py +81 -0
  80. constricter/fix/index/plain.cp313-win32.pyd +0 -0
  81. constricter/fix/index/plain.py +221 -0
  82. constricter/fix/index/project.cp313-win32.pyd +0 -0
  83. constricter/fix/index/project.py +797 -0
  84. constricter/fix/index/sides.cp313-win32.pyd +0 -0
  85. constricter/fix/index/sides.py +160 -0
  86. constricter/fix/index/strings.cp313-win32.pyd +0 -0
  87. constricter/fix/index/strings.py +46 -0
  88. constricter/fix/index/stubs/__init__.cp313-win32.pyd +0 -0
  89. constricter/fix/index/stubs/__init__.py +2 -0
  90. constricter/fix/index/stubs/atoms.cp313-win32.pyd +0 -0
  91. constricter/fix/index/stubs/atoms.py +425 -0
  92. constricter/fix/index/stubs/chained.cp313-win32.pyd +0 -0
  93. constricter/fix/index/stubs/chained.py +69 -0
  94. constricter/fix/index/stubs/classnames.cp313-win32.pyd +0 -0
  95. constricter/fix/index/stubs/classnames.py +124 -0
  96. constricter/fix/index/stubs/declared.cp313-win32.pyd +0 -0
  97. constricter/fix/index/stubs/declared.py +508 -0
  98. constricter/fix/index/stubs/installed.cp313-win32.pyd +0 -0
  99. constricter/fix/index/stubs/installed.py +425 -0
  100. constricter/fix/index/stubs/names.cp313-win32.pyd +0 -0
  101. constricter/fix/index/stubs/names.py +200 -0
  102. constricter/fix/index/stubs/read.cp313-win32.pyd +0 -0
  103. constricter/fix/index/stubs/read.py +70 -0
  104. constricter/fix/index/stubs/signatures.cp313-win32.pyd +0 -0
  105. constricter/fix/index/stubs/signatures.py +656 -0
  106. constricter/fix/index/stubs/stubbed.cp313-win32.pyd +0 -0
  107. constricter/fix/index/stubs/stubbed.py +208 -0
  108. constricter/fix/index/taken.cp313-win32.pyd +0 -0
  109. constricter/fix/index/taken.py +45 -0
  110. constricter/fix/index/tuples.cp313-win32.pyd +0 -0
  111. constricter/fix/index/tuples.py +112 -0
  112. constricter/fix/libraries/__init__.cp313-win32.pyd +0 -0
  113. constricter/fix/libraries/__init__.py +2 -0
  114. constricter/fix/libraries/library.cp313-win32.pyd +0 -0
  115. constricter/fix/libraries/library.py +420 -0
  116. constricter/fix/libraries/opened.cp313-win32.pyd +0 -0
  117. constricter/fix/libraries/opened.py +147 -0
  118. constricter/fix/libraries/overloads.cp313-win32.pyd +0 -0
  119. constricter/fix/libraries/overloads.py +960 -0
  120. constricter/fix/libraries/receivers.cp313-win32.pyd +0 -0
  121. constricter/fix/libraries/receivers.py +274 -0
  122. constricter/fix/libraries/stdlib.cp313-win32.pyd +0 -0
  123. constricter/fix/libraries/stdlib.py +752 -0
  124. constricter/fix/libraries/walks.cp313-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__.cp313-win32.pyd +0 -0
  148. constricter/fix/values/__init__.py +2 -0
  149. constricter/fix/values/aliased.cp313-win32.pyd +0 -0
  150. constricter/fix/values/aliased.py +195 -0
  151. constricter/fix/values/bodies.cp313-win32.pyd +0 -0
  152. constricter/fix/values/bodies.py +76 -0
  153. constricter/fix/values/callables.cp313-win32.pyd +0 -0
  154. constricter/fix/values/callables.py +323 -0
  155. constricter/fix/values/called.cp313-win32.pyd +0 -0
  156. constricter/fix/values/called.py +194 -0
  157. constricter/fix/values/classvars.cp313-win32.pyd +0 -0
  158. constricter/fix/values/classvars.py +629 -0
  159. constricter/fix/values/decided.cp313-win32.pyd +0 -0
  160. constricter/fix/values/decided.py +429 -0
  161. constricter/fix/values/displays.cp313-win32.pyd +0 -0
  162. constricter/fix/values/displays.py +101 -0
  163. constricter/fix/values/doubts.cp313-win32.pyd +0 -0
  164. constricter/fix/values/doubts.py +677 -0
  165. constricter/fix/values/entered.cp313-win32.pyd +0 -0
  166. constricter/fix/values/entered.py +332 -0
  167. constricter/fix/values/fills.cp313-win32.pyd +0 -0
  168. constricter/fix/values/fills.py +446 -0
  169. constricter/fix/values/guesses.cp313-win32.pyd +0 -0
  170. constricter/fix/values/guesses.py +376 -0
  171. constricter/fix/values/hinted.cp313-win32.pyd +0 -0
  172. constricter/fix/values/hinted.py +811 -0
  173. constricter/fix/values/inference.cp313-win32.pyd +0 -0
  174. constricter/fix/values/inference.py +698 -0
  175. constricter/fix/values/loops.cp313-win32.pyd +0 -0
  176. constricter/fix/values/loops.py +335 -0
  177. constricter/fix/values/members.cp313-win32.pyd +0 -0
  178. constricter/fix/values/members.py +578 -0
  179. constricter/fix/values/narrowed.cp313-win32.pyd +0 -0
  180. constricter/fix/values/narrowed.py +220 -0
  181. constricter/fix/values/operated.cp313-win32.pyd +0 -0
  182. constricter/fix/values/operated.py +202 -0
  183. constricter/fix/values/ordered.cp313-win32.pyd +0 -0
  184. constricter/fix/values/ordered.py +74 -0
  185. constricter/fix/values/returned.cp313-win32.pyd +0 -0
  186. constricter/fix/values/returned.py +479 -0
  187. constricter/fix/values/returns.cp313-win32.pyd +0 -0
  188. constricter/fix/values/returns.py +237 -0
  189. constricter/fix/values/shapes.cp313-win32.pyd +0 -0
  190. constricter/fix/values/shapes.py +654 -0
  191. constricter/fix/values/stored.cp313-win32.pyd +0 -0
  192. constricter/fix/values/stored.py +564 -0
  193. constricter/fix/values/targets.cp313-win32.pyd +0 -0
  194. constricter/fix/values/targets.py +419 -0
  195. constricter/fix/values/unchecked.cp313-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__.cp313-win32.pyd +0 -0
  208. constricter/rules/__init__.py +2 -0
  209. constricter/rules/annotations.cp313-win32.pyd +0 -0
  210. constricter/rules/annotations.py +296 -0
  211. constricter/rules/binding.cp313-win32.pyd +0 -0
  212. constricter/rules/binding.py +652 -0
  213. constricter/rules/calls.cp313-win32.pyd +0 -0
  214. constricter/rules/calls.py +460 -0
  215. constricter/rules/checker.cp313-win32.pyd +0 -0
  216. constricter/rules/checker.py +536 -0
  217. constricter/rules/declarations.cp313-win32.pyd +0 -0
  218. constricter/rules/declarations.py +730 -0
  219. constricter/rules/decorators.cp313-win32.pyd +0 -0
  220. constricter/rules/decorators.py +351 -0
  221. constricter/rules/flow.cp313-win32.pyd +0 -0
  222. constricter/rules/flow.py +502 -0
  223. constricter/rules/keys.cp313-win32.pyd +0 -0
  224. constricter/rules/keys.py +79 -0
  225. constricter/rules/late.cp313-win32.pyd +0 -0
  226. constricter/rules/late.py +969 -0
  227. constricter/rules/narrowing.cp313-win32.pyd +0 -0
  228. constricter/rules/narrowing.py +98 -0
  229. constricter/rules/parsed.cp313-win32.pyd +0 -0
  230. constricter/rules/parsed.py +136 -0
  231. constricter/rules/quoted.cp313-win32.pyd +0 -0
  232. constricter/rules/quoted.py +141 -0
  233. constricter/rules/rebinding.cp313-win32.pyd +0 -0
  234. constricter/rules/rebinding.py +169 -0
  235. constricter/rules/recorded.cp313-win32.pyd +0 -0
  236. constricter/rules/recorded.py +168 -0
  237. constricter/rules/redundant.cp313-win32.pyd +0 -0
  238. constricter/rules/redundant.py +160 -0
  239. constricter/rules/scope.cp313-win32.pyd +0 -0
  240. constricter/rules/scope.py +802 -0
  241. constricter/rules/scoped.cp313-win32.pyd +0 -0
  242. constricter/rules/scoped.py +232 -0
  243. constricter/rules/scoping.cp313-win32.pyd +0 -0
  244. constricter/rules/scoping.py +346 -0
  245. constricter/rules/settings.cp313-win32.pyd +0 -0
  246. constricter/rules/settings.py +189 -0
  247. constricter/rules/syntax.cp313-win32.pyd +0 -0
  248. constricter/rules/syntax.py +472 -0
  249. constricter/rules/tables.cp313-win32.pyd +0 -0
  250. constricter/rules/tables.py +169 -0
  251. constricter/rules/walked.cp313-win32.pyd +0 -0
  252. constricter/rules/walked.py +127 -0
  253. constricter/rules/widened.cp313-win32.pyd +0 -0
  254. constricter/rules/widened.py +322 -0
  255. constricter/trace.py +374 -0
  256. e45ce06b2fdd5c43f782__mypyc.cp313-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,296 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """What an annotation says (LVA005, LVA006, LVA011).
3
+
4
+ An annotation's vague parts, nesting depth and longest fixed-length tuple, the names it's written
5
+ with, and whether it's free of some. What a module declares is `constricter.rules.declarations`.
6
+ """
7
+
8
+ import ast
9
+ import re
10
+ from collections.abc import Mapping
11
+ from functools import lru_cache
12
+ from typing import Final
13
+
14
+ from constricter.rules.quoted import parsed
15
+
16
+ _VAGUE: Final = frozenset({"Any", "object"})
17
+ _UNIONS: Final = frozenset({"Optional", "Union"}) # a union's members, as a subscript's arguments
18
+ # `collections.abc`'s generic classes (`typing` has each too).
19
+ ABSTRACT: Final = frozenset(
20
+ {
21
+ "AsyncGenerator",
22
+ "AsyncIterable",
23
+ "AsyncIterator",
24
+ "Awaitable",
25
+ "Callable",
26
+ "Collection",
27
+ "Container",
28
+ "Coroutine",
29
+ "Generator",
30
+ "ItemsView",
31
+ "Iterable",
32
+ "Iterator",
33
+ "KeysView",
34
+ "Mapping",
35
+ "MutableMapping",
36
+ "MutableSequence",
37
+ "MutableSet",
38
+ "Reversible",
39
+ "Sequence",
40
+ "ValuesView",
41
+ },
42
+ )
43
+ # Generics that say little without their parameters.
44
+ GENERICS: Final = ABSTRACT | frozenset(
45
+ {
46
+ "AbstractSet",
47
+ "ChainMap",
48
+ "Counter",
49
+ "DefaultDict",
50
+ "Deque",
51
+ "Dict",
52
+ "FrozenSet",
53
+ "List",
54
+ "Match",
55
+ "OrderedDict",
56
+ "Pattern",
57
+ "Set",
58
+ "Tuple",
59
+ "Type",
60
+ "defaultdict",
61
+ "deque",
62
+ "dict",
63
+ "frozenset",
64
+ "list",
65
+ "set",
66
+ "tuple",
67
+ "type",
68
+ },
69
+ )
70
+ _TUPLES: Final = frozenset({"tuple", "Tuple"}) # the annotations that list one type per element
71
+
72
+
73
+ def is_composite(value: ast.expr) -> bool:
74
+ """Check whether a value is written as a type made of others: a subscript, or a union.
75
+
76
+ What a type alias's assignment binds (`Json = dict[str, "Json"] | str`), but for a bare class's
77
+ (`Alias = Class`).
78
+
79
+ Returns:
80
+ Whether it is.
81
+
82
+ """
83
+ return isinstance(value, ast.Subscript) or (
84
+ isinstance(value, ast.BinOp) and isinstance(value.op, ast.BitOr)
85
+ )
86
+
87
+
88
+ def node_name(node: ast.AST) -> str:
89
+ """Read a `Name`'s or `Attribute`'s simple name.
90
+
91
+ Returns:
92
+ It, or `""` if `node` is neither.
93
+
94
+ """
95
+ name: str
96
+ match node:
97
+ case ast.Name(id=name) | ast.Attribute(attr=name):
98
+ return name
99
+ case _:
100
+ return ""
101
+
102
+
103
+ def dotted(node: ast.expr) -> str | None:
104
+ """Spell a name, or a chain of attributes on one (`pkg.util.f`), as written.
105
+
106
+ Returns:
107
+ It, or `None` if `node` is anything else (`f().g`, `x[0].g`).
108
+
109
+ """
110
+ name: str
111
+ value: ast.expr
112
+ base: str | None
113
+ match node:
114
+ case ast.Name(id=name):
115
+ return name
116
+ case ast.Attribute(value=value, attr=name) if (base := dotted(value)) is not None:
117
+ return f"{base}.{name}"
118
+ case _:
119
+ return None
120
+
121
+
122
+ def is_vague(annotation: ast.expr) -> bool:
123
+ """Check an annotation for vague types.
124
+
125
+ Returns:
126
+ Whether it has `Any`, `object` or a generic without its parameters in it.
127
+
128
+ """
129
+ root: ast.expr = parsed(annotation)
130
+ subscripted: set[int] = {id(node.value) for node in ast.walk(root) if isinstance(node, ast.Subscript)}
131
+ node: ast.AST
132
+ for node in ast.walk(root):
133
+ name: str = node_name(node)
134
+ if name in _VAGUE or (name in GENERICS and id(node) not in subscripted):
135
+ return True
136
+ return False
137
+
138
+
139
+ def vague_parts(annotation: ast.expr) -> tuple[int, bool]:
140
+ """Count an annotation's vague parts (see `is_vague`), and say whether one stands alone.
141
+
142
+ Alone: the annotation itself or a member of its union (`Any`, `Any | None`, `Optional[object]`),
143
+ where it says nothing at all. Anywhere else it's inside a type that says the rest
144
+ (`tuple[str, Any]`), as a generic without its parameters is (`list`).
145
+
146
+ Returns:
147
+ How many there are, and whether one does.
148
+
149
+ """
150
+ root: ast.expr = parsed(annotation)
151
+ subscripted: set[int] = {id(node.value) for node in ast.walk(root) if isinstance(node, ast.Subscript)}
152
+ count: int = sum(
153
+ node_name(node) in _VAGUE or (node_name(node) in GENERICS and id(node) not in subscripted)
154
+ for node in ast.walk(root)
155
+ )
156
+ return count, any(node_name(member) in _VAGUE for member in _union_members(root))
157
+
158
+
159
+ def _union_members(node: ast.expr) -> list[ast.expr]:
160
+ """Flatten an annotation's outermost union: `A | B`, `Optional[A]`, `Union[A, B]`.
161
+
162
+ Returns:
163
+ Its members; the annotation itself, if it's no union.
164
+
165
+ """
166
+ left: ast.expr
167
+ right: ast.expr
168
+ head: ast.expr
169
+ inner: ast.expr
170
+ match node:
171
+ case ast.BinOp(op=ast.BitOr(), left=left, right=right):
172
+ return [*_union_members(left), *_union_members(right)]
173
+ case ast.Subscript(value=head, slice=inner) if node_name(head) in _UNIONS:
174
+ parts: list[ast.expr] = inner.elts if isinstance(inner, ast.Tuple) else [inner]
175
+ return [member for part in parts for member in _union_members(part)]
176
+ case _:
177
+ return [node]
178
+
179
+
180
+ def vague_fits(annotation: ast.expr, level: int) -> bool:
181
+ """Check whether an annotation is no vaguer than `level` allows (`vague`, LVA005's).
182
+
183
+ Below 0, no vague part at all. At 0, one, inside a type that says the rest (`tuple[str, Any]`).
184
+ From 1, `level + 1` of them (`tuple[Any, Any]` at 1), and one alone (`Any`).
185
+
186
+ Returns:
187
+ Whether it is.
188
+
189
+ """
190
+ count: int
191
+ alone: bool
192
+ count, alone = vague_parts(annotation)
193
+ return not count or (count <= level + 1 and (level > 0 or not alone))
194
+
195
+
196
+ def length(annotation: ast.expr) -> int:
197
+ """Measure the longest fixed-length tuple an annotation lists, one type per element.
198
+
199
+ `tuple[int, str]` is 2; `tuple[int, ...]` (any length) and anything but a tuple are 0.
200
+
201
+ Returns:
202
+ The most elements any `tuple[...]` or `Tuple[...]` in it lists.
203
+
204
+ """
205
+ node: ast.AST
206
+ head: ast.expr
207
+ elements: list[ast.expr]
208
+ lengths: list[int] = [0]
209
+ for node in ast.walk(parsed(annotation)):
210
+ match node:
211
+ case ast.Subscript(value=head, slice=ast.Tuple(elts=elements)) if node_name(head) in _TUPLES:
212
+ lengths.append(0 if _variadic(elements) else len(elements))
213
+ case ast.Subscript(value=head) if node_name(head) in _TUPLES:
214
+ lengths.append(1)
215
+ case _:
216
+ pass
217
+ return max(lengths)
218
+
219
+
220
+ def _variadic(elements: list[ast.expr]) -> bool:
221
+ """Check whether a tuple annotation's elements end in `...` (`tuple[int, ...]`: any length).
222
+
223
+ Returns:
224
+ Whether they do.
225
+
226
+ """
227
+ # `tuple[()]` (the empty tuple) has no elements at all.
228
+ return bool(elements) and isinstance(elements[-1], ast.Constant) and elements[-1].value is Ellipsis
229
+
230
+
231
+ def depth(annotation: ast.expr) -> int:
232
+ """Measure how deeply an annotation's subscripts nest.
233
+
234
+ Returns:
235
+ The depth: `dict[str, list[int]]` is 2.
236
+
237
+ """
238
+ node: ast.expr = parsed(annotation)
239
+ inner: ast.expr
240
+ parts: list[ast.expr]
241
+ left: ast.expr
242
+ right: ast.expr
243
+ match node:
244
+ case ast.Subscript(slice=inner):
245
+ return 1 + depth(inner)
246
+ case ast.Tuple(elts=parts) | ast.List(elts=parts):
247
+ return max((depth(part) for part in parts), default=0)
248
+ case ast.BinOp(left=left, right=right):
249
+ return max(depth(left), depth(right))
250
+ case _:
251
+ return 0
252
+
253
+
254
+ def free_of(types: Mapping[str, str], type_vars: frozenset[str]) -> dict[str, str]:
255
+ """Drop the types that mention one of `type_vars` (a call's type then depends on its arguments).
256
+
257
+ Returns:
258
+ The others.
259
+
260
+ """
261
+ return {name: annotation for name, annotation in types.items() if not type_vars & set(words(annotation))}
262
+
263
+
264
+ def free_of_all(
265
+ tables: Mapping[str, Mapping[str, str]],
266
+ type_vars: frozenset[str],
267
+ ) -> dict[str, dict[str, str]]:
268
+ """Drop each class's member types that mention one of `type_vars` (see `free_of`).
269
+
270
+ Returns:
271
+ The others, per class.
272
+
273
+ """
274
+ return {owner: free_of(types, type_vars) for owner, types in tables.items()}
275
+
276
+
277
+ def words(annotation: str) -> list[str]:
278
+ """Split an annotation into the words it's written with (`dict[str, Row]`: `dict`, `str`, `Row`).
279
+
280
+ Returns:
281
+ Them, in order.
282
+
283
+ """
284
+ return [word for word in re.split(r"\W+", annotation) if word]
285
+
286
+
287
+ @lru_cache(maxsize=4096) # asked of each fix's annotation several times: a few thousand distinct
288
+ def roots(annotation: str) -> frozenset[str]:
289
+ """Find the names an annotation (maybe a string one) is written with.
290
+
291
+ Returns:
292
+ The names: `m.Row` gives `m`; none for a string that isn't an expression.
293
+
294
+ """
295
+ tree: ast.expr = parsed(ast.parse(annotation, mode="eval").body)
296
+ return frozenset(node.id for node in ast.walk(tree) if isinstance(node, ast.Name))