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,311 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Which class an instance's member comes from: its own, or the base that defines it.
3
+
4
+ A class the module defines takes a member its body doesn't bind from its bases, in method
5
+ resolution order, as far as the module sees: its own classes, each defined once and not generic,
6
+ then a class another checked file or the standard library's tables define, whose members are
7
+ known whole. The order ends there, or before the first base out of sight (an installed package's,
8
+ subscripted, computed), which may define anything.
9
+ """
10
+
11
+ import ast
12
+ import builtins
13
+ from collections import Counter
14
+ from collections.abc import Iterator, Mapping, Sequence
15
+ from types import MappingProxyType
16
+ from typing import Final, NamedTuple, TypeAlias, cast
17
+
18
+ from constricter.fix.core.signatures import AWAIT
19
+ from constricter.rules.annotations import node_name
20
+ from constricter.rules.declarations import generic_classes
21
+ from constricter.rules.syntax import FUNCTION_DEFS, child_statements, expressions
22
+ from constricter.rules.walked import classes, walk
23
+
24
+ # Bases that give an instance no member a method call could reach.
25
+ _EMPTY: Final = frozenset({"object", "Generic", "Protocol", "ABC"})
26
+ # The standard-library class a line of bases ends at (`""`: at none, the line known whole), and the
27
+ # names the classes before it bind. Several classes, where two lines each end at one: their paths,
28
+ # in order, `SEVERAL` between them.
29
+ Beyond: TypeAlias = tuple[str, frozenset[str]]
30
+ SEVERAL: Final = ","
31
+ # A standard-library class held whole: its members' names, and its own and its ancestors' paths.
32
+ Library: TypeAlias = tuple[frozenset[str], frozenset[str]]
33
+
34
+
35
+ class Lineage(NamedTuple):
36
+ """The module's classes' ancestry, as far as it sees.
37
+
38
+ `order`: each class's method resolution order after itself, up to the first base out of sight,
39
+ or a class another checked file or the standard library defines (as the module spells it), which
40
+ ends it; `bound`: the names each class's body binds; `selfish`: its methods declared to return a
41
+ bare `Self` (see `self_returns`), which an inheriting class's instance gives as its own class.
42
+ `beyond`: for a base another checked file defines, the standard-library class its own bases end
43
+ at, and the names bound on the way there (see `constricter.fix.index.beyond`); one whose bases
44
+ end at none (a mixin) doesn't end the order, which goes on past it for what it doesn't bind.
45
+ `library`: the standard-library classes held whole that an order goes on past, or that a line
46
+ of bases ends at beside another (see `SEVERAL`), each with its members' names.
47
+ """
48
+
49
+ order: Mapping[str, tuple[str, ...]] = MappingProxyType({})
50
+ bound: Mapping[str, frozenset[str]] = MappingProxyType({})
51
+ selfish: Mapping[str, frozenset[str]] = MappingProxyType({})
52
+ beyond: Mapping[str, Beyond] = MappingProxyType({})
53
+ library: Mapping[str, frozenset[str]] = MappingProxyType({})
54
+
55
+ def definer(self, receiver: str, name: str) -> str | None:
56
+ """Find the class whose `name` an instance of `receiver` has.
57
+
58
+ Returns:
59
+ `receiver` itself if it binds `name`, or isn't one of the module's classes; else the first
60
+ class of its order that does, or that another file defines; `None` if none in sight does.
61
+ For another file's class whose bases end at a standard-library class, that class, where
62
+ nothing on the way binds `name` (see `beyond`).
63
+
64
+ """
65
+ found: str | None = (
66
+ receiver
67
+ if receiver not in self.bound or name in self.bound[receiver]
68
+ else next((base for base in self.order[receiver] if self._binds(base, name)), None)
69
+ )
70
+ library: Beyond | None = self.beyond.get(found or "")
71
+ if library is None or name in library[1]:
72
+ return found
73
+ ends: list[str] = library[0].split(SEVERAL)
74
+ # Of several library classes, the first that has the member: the others' is behind it.
75
+ return (
76
+ next((end for end in ends if len(ends) == 1 or name in self.library.get(end, ())), None) or None
77
+ )
78
+
79
+ def _binds(self, base: str, name: str) -> bool:
80
+ """Check whether `base`, of a class's order, is where the search for `name` ends.
81
+
82
+ Returns:
83
+ Whether it binds it, or may: another file's class, but one known whole that doesn't.
84
+
85
+ """
86
+ end: Beyond | None = self.beyond.get(base)
87
+ if end is not None and not end[0]:
88
+ return name in end[1]
89
+ if base in self.library:
90
+ return name in self.library[base]
91
+ return base not in self.bound or name in self.bound[base]
92
+
93
+ def selfish_of(self, owner: str) -> frozenset[str]:
94
+ """Name the methods an instance of `owner` has that are declared to return a bare `Self`.
95
+
96
+ Returns:
97
+ Its own, and those it takes from its bases in sight.
98
+
99
+ """
100
+ return self.selfish.get(owner, frozenset()).union(
101
+ name
102
+ for base in self.order.get(owner, ())
103
+ for name in self.selfish.get(base, ())
104
+ if self.definer(owner, name) == base
105
+ )
106
+
107
+ def flattened(self, methods: Mapping[str, Mapping[str, str]]) -> dict[str, dict[str, str]]:
108
+ """Add to each class's `methods` (their return types, by name) those it takes from its bases.
109
+
110
+ Returns:
111
+ Each class's own and inherited methods; one declared to return `Self` gives the class itself.
112
+
113
+ """
114
+ found: dict[str, dict[str, str]] = {owner: dict(own) for owner, own in methods.items()}
115
+ owner: str
116
+ bases: tuple[str, ...]
117
+ for owner, bases in self.order.items():
118
+ hidden: set[str] = set(self.bound[owner])
119
+ base: str
120
+ for base in bases:
121
+ name: str
122
+ annotation: str
123
+ for name, annotation in methods.get(base, {}).items():
124
+ if name.removeprefix(AWAIT) not in hidden:
125
+ selfish: bool = name in self.selfish.get(base, ())
126
+ found.setdefault(owner, {})[name] = owner if selfish else annotation
127
+ hidden |= self.bound.get(base, frozenset()) | self.beyond.get(base, ("", frozenset()))[1]
128
+ hidden |= self.library.get(base, frozenset())
129
+ return found
130
+
131
+
132
+ def lineage(
133
+ tree: ast.Module,
134
+ selfish: Mapping[str, frozenset[str]],
135
+ imported: frozenset[str],
136
+ beyond: Mapping[str, Beyond] | None = None,
137
+ library: Mapping[str, Library] | None = None,
138
+ ) -> Lineage:
139
+ """Read the ancestry of the classes the module defines once each.
140
+
141
+ `selfish`: its `self_returns`; `imported`: the classes other checked files and the standard
142
+ library define, as it spells them; `beyond`: see `Lineage`; `library`: the standard-library
143
+ classes held whole among them (see `Library`), and those `beyond` ends at beside another. An
144
+ order goes on past one of those to the next base, but one that shares an ancestor with it,
145
+ whose place between them the tables don't say.
146
+
147
+ Returns:
148
+ It (see `Lineage`).
149
+
150
+ """
151
+ nodes: Sequence[ast.ClassDef] = classes(tree)
152
+ counts: Counter[str] = Counter(node.name for node in nodes)
153
+ generic: frozenset[str] = generic_classes(tree)
154
+ # Each class's bases as written: one out of sight is no key here.
155
+ parents: dict[str, list[str]] = {
156
+ node.name: [ast.unparse(base) for base in node.bases if node_name(base) not in _EMPTY]
157
+ for node in nodes
158
+ if counts[node.name] == 1
159
+ }
160
+ # A class of the module's is seen through; so is another file's known whole (see `Lineage.beyond`).
161
+ seen: frozenset[str] = (frozenset(parents) - (generic - _given(nodes, imported))).union(
162
+ base for base, end in (beyond or {}).items() if not end[0]
163
+ )
164
+ order: dict[str, tuple[str, ...]] = {}
165
+ held: Mapping[str, Library] = library or {}
166
+ passed: set[str] = set() # the library classes an order goes on past
167
+ name: str
168
+ for name in parents:
169
+ after: list[str] = (_order(name, parents, frozenset()) or [name])[1:]
170
+ order[name] = tuple(after[: _sighted(after, (seen, imported), held, passed)])
171
+ return Lineage(
172
+ order,
173
+ {node.name: frozenset(_bound(node.body)) for node in nodes if node.name in parents},
174
+ selfish,
175
+ MappingProxyType({}) if beyond is None else beyond,
176
+ {base: held[base][0] for base in passed | several(beyond or {}) if base in held},
177
+ )
178
+
179
+
180
+ def _sighted(
181
+ after: Sequence[str],
182
+ known: tuple[frozenset[str], frozenset[str]],
183
+ held: Mapping[str, Library],
184
+ passed: set[str],
185
+ ) -> int:
186
+ """Count how many of a class's ancestors (`after`, in order) are in sight.
187
+
188
+ `known`: the classes seen through, and those another file or the standard library defines.
189
+ One of those is the last in sight, what it inherits its own to say; but a library class
190
+ `held` whole, which the next may follow: each one gone past is added to `passed`.
191
+
192
+ Returns:
193
+ How many.
194
+
195
+ """
196
+ lines: frozenset[str] = frozenset() # the library classes so far, and their ancestors
197
+ last: int = 0
198
+ base: str
199
+ for base in after:
200
+ if base in known[0]:
201
+ last += 1
202
+ continue
203
+ last += base in known[1] and not held.get(base, (None, frozenset[str]()))[1] & lines
204
+ if base not in held or held[base][1] & lines or base == after[-1]:
205
+ break
206
+ lines |= held[base][1]
207
+ passed.add(base)
208
+ return last
209
+
210
+
211
+ def several(beyond: Mapping[str, Beyond]) -> frozenset[str]:
212
+ """Name the library classes lines of bases end at beside another (see `SEVERAL`).
213
+
214
+ Returns:
215
+ Their paths.
216
+
217
+ """
218
+ return frozenset(end for found, _ in beyond.values() if SEVERAL in found for end in found.split(SEVERAL))
219
+
220
+
221
+ def _given(nodes: Sequence[ast.ClassDef], imported: frozenset[str]) -> frozenset[str]:
222
+ """Name the classes that are generic only by a library base given its arguments: not generic at all.
223
+
224
+ `class Rows(collections.deque[Row])`: each subscripted base one of `imported`, written with
225
+ builtin types, dotted names and the module's own classes alone (no type variable's name).
226
+
227
+ Returns:
228
+ Their names.
229
+
230
+ """
231
+ plain: frozenset[str] = frozenset(dir(builtins)) | {node.name for node in nodes}
232
+ return frozenset(
233
+ node.name
234
+ for node in nodes
235
+ if not cast("object", getattr(node, "type_params", ())) # Python 3.12+'s `class C[T]`
236
+ and all(
237
+ ast.unparse(base) in imported
238
+ and all(part.id in plain for part in ast.walk(base.slice) if isinstance(part, ast.Name))
239
+ for base in node.bases
240
+ if isinstance(base, ast.Subscript)
241
+ )
242
+ )
243
+
244
+
245
+ def _order(name: str, parents: Mapping[str, list[str]], above: frozenset[str]) -> list[str] | None:
246
+ """Linearise a class's ancestry as Python does (C3), a base out of sight standing for itself alone.
247
+
248
+ `above`: the classes this one is being ordered for, which it can't be a base of.
249
+
250
+ Returns:
251
+ The class, then its ancestors in method resolution order; or `None` if they have no order.
252
+
253
+ """
254
+ if name not in parents:
255
+ return [name]
256
+ if name in above:
257
+ return None
258
+ orders: list[list[str]] = []
259
+ base: str
260
+ for base in parents[name]:
261
+ found: list[str] | None
262
+ if (found := _order(base, parents, above | {name})) is None:
263
+ return None
264
+ orders.append(found)
265
+ merged: list[str] | None = _merged([*orders, list(parents[name])])
266
+ return None if merged is None else [name, *merged]
267
+
268
+
269
+ def _merged(orders: list[list[str]]) -> list[str] | None:
270
+ """Merge linearisations, each one's order kept: the first head that no other's tail holds, each time.
271
+
272
+ Returns:
273
+ The merged order, or `None` if they disagree.
274
+
275
+ """
276
+ left: list[list[str]]
277
+ if not (left := [order for order in orders if order]):
278
+ return []
279
+ head: str | None = next(
280
+ (order[0] for order in left if not any(order[0] in other[1:] for other in left)),
281
+ None,
282
+ )
283
+ if head is None:
284
+ return None
285
+ rest: list[str] | None = _merged([order[1:] if order[0] == head else order for order in left])
286
+ return None if rest is None else [head, *rest]
287
+
288
+
289
+ def _bound(body: Sequence[ast.stmt]) -> Iterator[str]:
290
+ """Name what a class's body binds, through its compound statements: not what its methods do.
291
+
292
+ Yields:
293
+ Each function's and nested class's name, and each name an assignment, an import or any
294
+ other statement stores to.
295
+
296
+ """
297
+ stmt: ast.stmt
298
+ for stmt in body:
299
+ if isinstance(stmt, (*FUNCTION_DEFS, ast.ClassDef)):
300
+ yield stmt.name
301
+ elif isinstance(stmt, ast.Import | ast.ImportFrom):
302
+ yield from ((alias.asname or alias.name).partition(".")[0] for alias in stmt.names)
303
+ else:
304
+ part: ast.AST
305
+ for part in expressions(stmt):
306
+ yield from (
307
+ node.id
308
+ for node in walk(part)
309
+ if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store)
310
+ )
311
+ yield from _bound(child_statements(stmt))