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,274 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """An installed method's receiver, matched against the `self` its signature declares (`matches`).
3
+
4
+ For `constricter.fix.libraries.overloads`, which picks such a signature only where its receiver's
5
+ type fits: part by part, a type variable bound to what it stands against (left unbound where it's
6
+ bound two ways), a union by either member, a subscript by its arguments, and a class by where it's
7
+ defined and its ancestors. Each match is `YES`, `NO` or `MAYBE`.
8
+ """
9
+
10
+ import ast
11
+ from collections.abc import Sequence
12
+ from typing import Final, NamedTuple, cast
13
+
14
+ from constricter.fix.core.known import Known
15
+ from constricter.fix.core.signatures import (
16
+ ReadSignature,
17
+ )
18
+ from constricter.fix.libraries import stdlib
19
+
20
+ YES: Final = "y"
21
+ NO: Final = "n"
22
+ MAYBE: Final = "?"
23
+
24
+
25
+ SELF_TYPE: Final = "Self" # a method's receiver's type, in `stdlib.Method.types`
26
+ _ANY_PATH: Final = "typing.Any" # a receiver pattern's anything
27
+ _TUPLE_PATH: Final = "builtins.tuple"
28
+ _BUILTINS_PATH: Final = "builtins."
29
+
30
+
31
+ UNFOLLOWED: Final = "?" # a lineage's base that can't be followed
32
+
33
+
34
+ _CLASHING: Final = "?" # a type variable a receiver's type binds two ways
35
+ _REPEATED: Final = 2 # `tuple[int, ...]`'s arguments
36
+
37
+
38
+ def both(first: str, second: str) -> str:
39
+ """Combine two verdicts that must both hold.
40
+
41
+ Returns:
42
+ `NO` if either is, `MAYBE` if either is, else `YES`.
43
+
44
+ """
45
+ return NO if NO in {first, second} else MAYBE if MAYBE in {first, second} else YES
46
+
47
+
48
+ class Receiver(NamedTuple):
49
+ """A method call's receiver, matched against an installed method's `self` (`ReadSignature.receiver`).
50
+
51
+ Its type (`Self` in `stdlib.Method.types`), as the module writes it, or as the class an alias
52
+ stands for (`Method.matched`); and what the module knows, whose `LibraryNames.lineage` a class
53
+ it names is compared with a pattern's by (where each is defined, and its ancestors). `own`, for
54
+ an alias's: what a type variable may be bound to, the parts the module wrote; the alias's own
55
+ are paths, which it can't.
56
+ """
57
+
58
+ text: str | None
59
+ known: Known
60
+ own: frozenset[str] | None = None
61
+
62
+
63
+ def receiver_of(method: stdlib.Method, known: Known) -> Receiver:
64
+ """Describe a method call's receiver (see `Receiver`).
65
+
66
+ Returns:
67
+ It.
68
+
69
+ """
70
+ written: str | None = method.types.get(SELF_TYPE)
71
+ if method.matched is None or written is None:
72
+ return Receiver(written, known)
73
+ return Receiver(
74
+ method.matched,
75
+ known,
76
+ frozenset(ast.unparse(node) for node in ast.walk(parsed(written)) if isinstance(node, ast.expr)),
77
+ )
78
+
79
+
80
+ def matches(receiver: Receiver, signature: ReadSignature, bound: dict[str, str]) -> str:
81
+ """Match the receiver's type against `signature`'s `self`, binding its type variables into `bound`.
82
+
83
+ Returns:
84
+ `YES`, `NO`, or `MAYBE`.
85
+
86
+ """
87
+ found: dict[str, str] = {}
88
+ verdict: str = _unified(
89
+ receiver,
90
+ parsed(cast("str", signature.receiver)), # `_picked` asks only where there's one
91
+ parsed(cast("str", receiver.text)), # an installed method's receiver always has a type
92
+ dict(signature.bounds),
93
+ found,
94
+ )
95
+ # A variable bound two ways (by two parts, or by the arguments too) is left unbound: a type
96
+ # checker would widen it, or reject the call. So is one bound to what an alias wrote.
97
+ clashing: set[str] = {
98
+ name
99
+ for name, text in found.items()
100
+ if text == _CLASHING
101
+ or bound.get(name, text) != text
102
+ or (receiver.own is not None and text not in receiver.own)
103
+ }
104
+ name: str
105
+ for name in clashing:
106
+ _ = bound.pop(name, None)
107
+ del found[name]
108
+ bound.update(found)
109
+ return MAYBE if clashing and verdict == YES else verdict
110
+
111
+
112
+ def _unified(
113
+ receiver: Receiver,
114
+ pattern: ast.expr,
115
+ actual: ast.expr,
116
+ bounds: dict[str, str],
117
+ found: dict[str, str],
118
+ ) -> str:
119
+ """Match one part of the receiver's type against a pattern's.
120
+
121
+ Returns:
122
+ The verdict.
123
+
124
+ """
125
+ left: ast.expr
126
+ right: ast.expr
127
+ name: str
128
+ match pattern:
129
+ case ast.BinOp(left=left, op=ast.BitOr(), right=right):
130
+ return _either(receiver, [left, right], actual, bounds, found)
131
+ case ast.Name(id=name): # a type variable: bound to it (see `matches` for two ways)
132
+ text: str = ast.unparse(actual)
133
+ if found.setdefault(name, text) != text:
134
+ found[name] = _CLASHING
135
+ return YES if name not in bounds else _either(receiver, [parsed(bounds[name])], actual, {}, {})
136
+ case ast.Subscript():
137
+ return _subscript(receiver, pattern, actual, bounds, found)
138
+ case _:
139
+ return _class(receiver, ast.unparse(pattern), actual)
140
+
141
+
142
+ def _either(
143
+ receiver: Receiver,
144
+ members: Sequence[ast.expr],
145
+ actual: ast.expr,
146
+ bounds: dict[str, str],
147
+ found: dict[str, str],
148
+ ) -> str:
149
+ """Match against a union: `YES` if a member matches (its bindings kept), `NO` if none can.
150
+
151
+ Returns:
152
+ The verdict.
153
+
154
+ """
155
+ verdicts: set[str] = set()
156
+ member: ast.expr
157
+ for member in [part for each in members for part in _union(each)]:
158
+ bound: dict[str, str] = dict(found)
159
+ verdict: str
160
+ if (verdict := _unified(receiver, member, actual, bounds, bound)) == YES:
161
+ found.update(bound)
162
+ return YES
163
+ verdicts.add(verdict)
164
+ return NO if verdicts == {NO} else MAYBE
165
+
166
+
167
+ def _subscript(
168
+ receiver: Receiver,
169
+ pattern: ast.Subscript,
170
+ actual: ast.expr,
171
+ bounds: dict[str, str],
172
+ found: dict[str, str],
173
+ ) -> str:
174
+ """Match a generic class's pattern (`ndarray[tuple[Any, ...], dtype[ScalarT]]`) argument by argument.
175
+
176
+ Returns:
177
+ The verdict: `MAYBE` for a subclass, or the receiver's type without arguments.
178
+
179
+ """
180
+ related: str = _class(
181
+ receiver,
182
+ ast.unparse(pattern.value),
183
+ actual.value if isinstance(actual, ast.Subscript) else actual,
184
+ )
185
+ if (
186
+ related != YES
187
+ or not isinstance(actual, ast.Subscript)
188
+ or ast.unparse(pattern.value) != _defined(receiver, actual.value)
189
+ ):
190
+ return MAYBE if related == YES else related
191
+ wanted: list[ast.expr] = _arguments_of(pattern)
192
+ given: list[ast.expr] = _arguments_of(actual)
193
+ if _repeated(wanted) and _repeated(given): # `tuple[int, ...]` both: their elements
194
+ wanted, given = wanted[:1], given[:1]
195
+ elif _repeated(given):
196
+ return MAYBE # its length unknown
197
+ elif _repeated(wanted):
198
+ wanted = [wanted[0]] * len(given)
199
+ if len(wanted) != len(given):
200
+ return NO if ast.unparse(pattern.value) == _TUPLE_PATH else MAYBE
201
+ verdict: str = YES
202
+ part: ast.expr
203
+ other: ast.expr
204
+ for part, other in zip(wanted, given, strict=True):
205
+ verdict = both(verdict, _unified(receiver, part, other, bounds, found))
206
+ return verdict
207
+
208
+
209
+ def _repeated(args: Sequence[ast.expr]) -> bool:
210
+ return len(args) == _REPEATED and isinstance(args[1], ast.Constant) and args[1].value is Ellipsis
211
+
212
+
213
+ def _class(receiver: Receiver, path: str, actual: ast.expr) -> str:
214
+ """Compare a pattern's class (`numpy.floating`) with the receiver's (`np.float64`), by its lineage.
215
+
216
+ Returns:
217
+ `YES` if it's that class or a subclass, `NO` if its lineage is known and doesn't have it.
218
+
219
+ """
220
+ if path == _ANY_PATH:
221
+ return YES
222
+ lineage: tuple[str, ...] = _lineage(
223
+ receiver,
224
+ actual.value if isinstance(actual, ast.Subscript) else actual,
225
+ )
226
+ if path in lineage:
227
+ return YES
228
+ return MAYBE if not lineage or UNFOLLOWED in lineage else NO
229
+
230
+
231
+ def _defined(receiver: Receiver, actual: ast.expr) -> str:
232
+ lineage: tuple[str, ...] = _lineage(receiver, actual)
233
+ return lineage[0] if lineage else ""
234
+
235
+
236
+ def _lineage(receiver: Receiver, actual: ast.expr) -> tuple[str, ...]:
237
+ """Find where a class the receiver's type names is defined, and its ancestors.
238
+
239
+ Returns:
240
+ Them; none for anything else. A builtin's, just it and `object`.
241
+
242
+ """
243
+ text: str = ast.unparse(actual)
244
+ if isinstance(actual, ast.Name) and receiver.known.is_builtin(text):
245
+ return (f"builtins.{text}", "builtins.object")
246
+ if text.startswith(_BUILTINS_PATH): # an alias's expansion's (`builtins.tuple`)
247
+ return (text, "builtins.object")
248
+ return tuple(receiver.known.names.lineage.get(text, ()))
249
+
250
+
251
+ def _union(pattern: ast.expr) -> list[ast.expr]:
252
+ """Split a union pattern into its members.
253
+
254
+ Returns:
255
+ Them.
256
+
257
+ """
258
+ if isinstance(pattern, ast.BinOp) and isinstance(pattern.op, ast.BitOr):
259
+ return [*_union(pattern.left), *_union(pattern.right)]
260
+ return [pattern]
261
+
262
+
263
+ def _arguments_of(node: ast.Subscript) -> list[ast.expr]:
264
+ return list(node.slice.elts) if isinstance(node.slice, ast.Tuple) else [node.slice]
265
+
266
+
267
+ def parsed(text: str) -> ast.expr:
268
+ """Parse a type's text.
269
+
270
+ Returns:
271
+ Its expression.
272
+
273
+ """
274
+ return ast.parse(text, mode="eval").body