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,578 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """What a member of a value whose type is known gives: an attribute, a method call, a subscript.
3
+
4
+ `inference` works out the receiver's type (a local, `self.index`, `f()`, `x[0]`, anything it can
5
+ type), and `member` looks the member up on it through `SOURCES`, in order: the first that knows it
6
+ decides. Each source is certain, whatever the call's arguments are; a receiver that's only guessed
7
+ makes its member a guess too, as `guesses` judges.
8
+ """
9
+
10
+ import ast
11
+ from collections.abc import Callable, Sequence
12
+ from functools import lru_cache
13
+ from typing import TYPE_CHECKING, Final, TypeAlias
14
+
15
+ from constricter.fix.core.imports import added_dotted
16
+ from constricter.fix.core.known import ImportPlan, Inference, Known
17
+ from constricter.fix.core.signatures import AWAIT
18
+ from constricter.fix.libraries import overloads, stdlib
19
+ from constricter.fix.libraries.library import library_awaited
20
+ from constricter.fix.values.returns import METHOD_RETURNS, element_method, uniform_method
21
+ from constricter.fix.values.targets import sole
22
+ from constricter.rules.annotations import dotted, node_name, roots, vague_fits
23
+ from constricter.rules.keys import key, optional
24
+ from constricter.rules.quoted import unqualified
25
+
26
+ if TYPE_CHECKING:
27
+ from constricter.fix.core.inherited import Lineage
28
+
29
+ _ATTRIBUTE: Final = "attribute" # the fix kind of an attribute's annotation
30
+ _METHOD: Final = "method" # the fix kind of a method's return type
31
+ _AWAIT: Final = "await" # and of what awaiting a checked file's `async def` gives
32
+ _RETURNED: Final = "returned" # and of what `return`s alone decide
33
+ _GENERIC: Final = "[" # in a base's text: a generic class's arguments
34
+ _SLICE: Final = "slice" # an index of this type slices
35
+ _INT: Final = "int"
36
+ _STR: Final = "str"
37
+ _SUBSCRIPT: Final = "subscript" # the fix kind of a subscript
38
+ _OPTIONAL: Final = "Optional"
39
+ # One way to type a member: given the receiver's type as text, the member's name, and the call
40
+ # (`None` for an attribute), its inference, or `None` if this source doesn't know it.
41
+ MemberSource: TypeAlias = Callable[[str, str, ast.Call | None, Known], Inference | None]
42
+
43
+
44
+ @lru_cache(maxsize=4096)
45
+ def parsed(annotation: str) -> ast.expr:
46
+ """Parse an annotation `--fix` wrote or read, once for all its lookups.
47
+
48
+ `annotation` is always `ast.unparse`'s own output (an annotation, or an earlier inference),
49
+ never user text, so it's always valid Python. The tree is shared: only read it.
50
+
51
+ Returns:
52
+ Its expression.
53
+
54
+ """
55
+ return ast.parse(annotation, mode="eval").body
56
+
57
+
58
+ @lru_cache(maxsize=4096) # a module's fixes are a few types, each asked again and again
59
+ def fits(annotation: str, level: int) -> bool:
60
+ """Check that a type, as text, is no vaguer than `level` allows (see `vague_fits`).
61
+
62
+ Returns:
63
+ Whether it is.
64
+
65
+ """
66
+ return vague_fits(parsed(annotation), level)
67
+
68
+
69
+ def class_of(receiver: str) -> str | None:
70
+ """Read the class a `type[C]` names (what `cls` is in a classmethod of `C`).
71
+
72
+ Returns:
73
+ `C`, or `None` if `receiver` isn't a `type[...]` of a plain name.
74
+
75
+ """
76
+ name: str
77
+ match parsed(receiver):
78
+ case ast.Subscript(value=ast.Name(id="type"), slice=ast.Name(id=name)):
79
+ return name
80
+ case _:
81
+ return None
82
+
83
+
84
+ def present(receiver: str, attr: str) -> str:
85
+ """Read the `X` of a receiver typed `X | None` (or `Optional[X]`), whose member `attr` is looked up.
86
+
87
+ Only an `X` has the member: a type checker has narrowed the receiver to it there, or reports the
88
+ access. Not a member `None` has too (`__class__`), nor a union of more types.
89
+
90
+ Returns:
91
+ The `X` as text; any other receiver as it is.
92
+
93
+ """
94
+ kept: ast.expr
95
+ head: ast.expr
96
+ match parsed(receiver):
97
+ case _ if attr.startswith("__"):
98
+ return receiver
99
+ case (
100
+ ast.BinOp(left=kept, op=ast.BitOr(), right=ast.Constant(value=None))
101
+ | ast.BinOp(left=ast.Constant(value=None), op=ast.BitOr(), right=kept)
102
+ ) if not isinstance(kept, ast.BinOp):
103
+ return ast.unparse(kept)
104
+ case ast.Subscript(value=head, slice=kept) if node_name(head) == _OPTIONAL:
105
+ return ast.unparse(kept)
106
+ case _:
107
+ return receiver
108
+
109
+
110
+ def _class_side(receiver: str, name: str, call: ast.Call | None, known: Known) -> Inference | None:
111
+ """Type a class's own attribute or classmethod, on a `type[C]` (`cls.limit`, `cls.build()`).
112
+
113
+ Returns:
114
+ Its inference, or `None`.
115
+
116
+ """
117
+ owner: str | None
118
+ if (owner := class_of(receiver)) is None:
119
+ return None
120
+ if call is None:
121
+ found: str | None = known.class_side.attributes.get(owner, {}).get(name)
122
+ return (
123
+ None
124
+ if found is None
125
+ else Inference(found, _annotation_of(receiver, name), frozenset({_ATTRIBUTE}))
126
+ )
127
+ found = known.class_side.methods.get(owner, {}).get(name)
128
+ return None if found is None else Inference(found, _declared_return(receiver, name), frozenset({_METHOD}))
129
+
130
+
131
+ def _fixed(receiver: str, name: str, call: ast.Call | None, _known: Known) -> Inference | None:
132
+ """Type a `str`, `bytes`, `int` or `float` method with a fixed return type (`METHOD_RETURNS`).
133
+
134
+ Returns:
135
+ Its inference, or `None`.
136
+
137
+ """
138
+ found: str | None = None if call is None else METHOD_RETURNS.get(receiver, {}).get(name)
139
+ return (
140
+ None
141
+ if found is None
142
+ else Inference(found, f"`{receiver}.{name}`'s fixed return type", frozenset({_METHOD}))
143
+ )
144
+
145
+
146
+ def _declared(
147
+ receiver: str,
148
+ name: str,
149
+ call: ast.Call | None,
150
+ known: Known,
151
+ prefix: str = "",
152
+ ) -> Inference | None:
153
+ """Type an annotated attribute or property, or a method's declared return, of a known class.
154
+
155
+ One this module defines, or another checked file does (see `Known.classes`, `Known.methods`). A
156
+ member the class doesn't define is the base's that does (see `Lineage`); one declared to return
157
+ `Self` there gives the receiver's own class, and one of another file's class returning that
158
+ class, which may be its `Self`, nothing. `prefix`: before a method's name in the tables, for an
159
+ `async def`'s (`AWAIT`): what awaiting its call gives.
160
+
161
+ Returns:
162
+ Its inference, or `None`.
163
+
164
+ """
165
+ found: str | None
166
+ if (found := None if call is not None else known.classes.get(receiver, {}).get(name)) is not None:
167
+ return _attribute(found, receiver, name)
168
+ lineage: Lineage = known.class_side.lineage
169
+ owner: str | None = _declarer(receiver, name, known) if call is None else lineage.definer(receiver, name)
170
+ found = (known.methods if call is not None else known.classes).get(owner or "", {}).get(prefix + name)
171
+ if found is None or owner is None:
172
+ return None
173
+ if call is None:
174
+ # Another file's base's: not one typed as that class, which may be its property's `Self`.
175
+ return None if found == owner else _attribute(found, owner, name)
176
+ if owner != receiver and name in lineage.selfish.get(owner, ()):
177
+ found = receiver
178
+ elif owner != receiver and owner not in lineage.bound and found == owner:
179
+ return None # another file's class, or its `Self`: the receiver's own class
180
+ return Inference(found, _declared_return(owner, name), frozenset({_METHOD}))
181
+
182
+
183
+ def _attribute(found: str, owner: str, name: str) -> Inference | None:
184
+ """Offer an attribute's declared type, less the `ClassVar` or `Final` around it.
185
+
186
+ The tables keep those: a subclass's variable a base declares a `ClassVar` isn't one to annotate.
187
+
188
+ Returns:
189
+ The inference; `None` for a bare `Final`, which leaves the type to the value.
190
+
191
+ """
192
+ text: str = unqualified(parsed(found))
193
+ return Inference(text, _annotation_of(owner, name), frozenset({_ATTRIBUTE})) if text else None
194
+
195
+
196
+ def awaited(value: ast.expr, known: Known, infer: Callable[[ast.expr], Inference | None]) -> Inference | None:
197
+ """Infer `await` of a call: a checked file's `async def`'s, or a standard-library coroutine's.
198
+
199
+ A function's by its declared return (`known.indirect.awaits`), and a method's on a receiver whose
200
+ type `infer` knows, the class's own or the base's that defines it (see `_declared`); else as
201
+ `library_awaited` has it.
202
+
203
+ Returns:
204
+ The inference, or `None`.
205
+
206
+ """
207
+ func: ast.expr
208
+ receiver: ast.expr
209
+ name: str
210
+ match value:
211
+ case ast.Await(value=ast.Call(func=ast.Name() | ast.Attribute() as func)) if (
212
+ dotted(func) in known.indirect.awaits
213
+ ):
214
+ spelled: str = ast.unparse(func)
215
+ reason: str = f"`{spelled}`'s declared return type, awaited"
216
+ return Inference(known.indirect.awaits[spelled], reason, frozenset({_AWAIT}))
217
+ case ast.Await(value=ast.Call(func=ast.Attribute(value=receiver, attr=name) as func)):
218
+ owner: Inference | None = infer(receiver)
219
+ found: Inference | None = (
220
+ None
221
+ if owner is None
222
+ else _declared(present(owner.annotation, name), name, ast.Call(func, [], []), known, AWAIT)
223
+ )
224
+ if owner is not None and found is not None:
225
+ kinds: frozenset[str] = found.kinds | {_AWAIT} | owner.kinds - {"copy"}
226
+ return found._replace(reason=f"{found.reason}, awaited", kinds=kinds)
227
+ case _:
228
+ pass
229
+ return _awaited_returns(value, known, infer) or library_awaited(value, known, infer)
230
+
231
+
232
+ def _awaited_returns(
233
+ value: ast.expr,
234
+ known: Known,
235
+ infer: Callable[[ast.expr], Inference | None],
236
+ ) -> Inference | None:
237
+ """Infer `await` of a call to a checked file's `async def` declaring no return, by its `return`s.
238
+
239
+ A function's, or a method's on a receiver whose type `infer` knows (see `awaited_origins`).
240
+
241
+ Returns:
242
+ The inference, or `None`.
243
+
244
+ """
245
+ call: ast.Call
246
+ match value:
247
+ case ast.Await(value=ast.Call() as call):
248
+ found: tuple[str, str, frozenset[str] | None] | None
249
+ if (found := _loose_await(call, known, infer)) is None:
250
+ return None
251
+ reason: str = f"`{found[0].replace(AWAIT, '')}`'s `return`s, awaited"
252
+ return Inference(found[1], reason, (found[2] or frozenset()) | {_RETURNED})
253
+ case _:
254
+ return None
255
+
256
+
257
+ def awaited_origins(
258
+ call: ast.Call,
259
+ known: Known,
260
+ infer: Callable[[ast.expr], Inference | None],
261
+ ) -> frozenset[str] | None:
262
+ """Name what awaiting `call` rests on as a guess, where an `async def`'s `return`s type it.
263
+
264
+ A function's `return`s' own guesses; a method's too, and `returned`: a subclass may override it.
265
+
266
+ Returns:
267
+ Them (none: it's certain), or `None` for any other call.
268
+
269
+ """
270
+ found: tuple[str, str, frozenset[str] | None] | None
271
+ if (found := _loose_await(call, known, infer)) is None:
272
+ return None
273
+ rests: frozenset[str] = known.returned.guesses.get(found[0], frozenset())
274
+ return rests if found[2] is None else rests | {_RETURNED}
275
+
276
+
277
+ def _loose_await(
278
+ call: ast.Call,
279
+ known: Known,
280
+ infer: Callable[[ast.expr], Inference | None],
281
+ ) -> tuple[str, str, frozenset[str] | None] | None:
282
+ """Find the `async def` declaring no return that `call` calls, typed by its `return`s (see `returned`).
283
+
284
+ Returns:
285
+ Its key in `Returned.guesses`, what awaiting it gives, and for a method the fix kinds its
286
+ receiver adds (`None`: a function); or `None`.
287
+
288
+ """
289
+ receiver: ast.expr
290
+ name: str
291
+ callee: str | None = dotted(call.func)
292
+ if callee is not None and f"{AWAIT}{callee}" in known.returned.calls:
293
+ return f"{AWAIT}{callee}", known.returned.calls[f"{AWAIT}{callee}"], None
294
+ match call.func:
295
+ case ast.Attribute(value=receiver, attr=name) if any(
296
+ f"{AWAIT}{name}" in methods for methods in known.returned.methods.values()
297
+ ):
298
+ owner: Inference | None = infer(receiver)
299
+ defined: tuple[str, str] | None = (
300
+ None
301
+ if owner is None
302
+ else returned_method(present(owner.annotation, name), f"{AWAIT}{name}", known)
303
+ )
304
+ if owner is None or defined is None:
305
+ return None
306
+ return f"{defined[0]}.{AWAIT}{name}", defined[1], owner.kinds - {"copy"}
307
+ case _:
308
+ return None
309
+
310
+
311
+ def _declarer(receiver: str, name: str, known: Known) -> str | None:
312
+ """Find the base whose declared attribute `name` an instance of `receiver` has.
313
+
314
+ The first of its order that declares it (in a method too, `self.x: T`, which no class body
315
+ binds), unless `receiver` or a class of the module's before it binds the name another way.
316
+
317
+ Returns:
318
+ It, or `None`.
319
+
320
+ """
321
+ lineage: Lineage = known.class_side.lineage
322
+ if name in lineage.bound.get(receiver, ()):
323
+ return None
324
+ base: str
325
+ for base in lineage.order.get(receiver, ()):
326
+ if name in known.classes.get(base, {}):
327
+ return base
328
+ if name in lineage.bound.get(base, ()):
329
+ break
330
+ return None
331
+
332
+
333
+ def _elements(receiver: str, name: str, call: ast.Call | None, _known: Known) -> Inference | None:
334
+ """Type a builtin container's method by the receiver's type: its element's, or one all of them give.
335
+
336
+ Returns:
337
+ Its inference, or `None` (see `element_method`, `uniform_method`).
338
+
339
+ """
340
+ root: ast.expr = parsed(receiver)
341
+ found: str | None = (
342
+ None
343
+ if call is None
344
+ else element_method(root, receiver, call, name) or uniform_method(root, receiver, name)
345
+ )
346
+ return (
347
+ None
348
+ if found is None
349
+ else Inference(found, f"`{receiver}.{name}` on its element types", frozenset({_METHOD}))
350
+ )
351
+
352
+
353
+ def _library_base(receiver: str, name: str, call: ast.Call | None, known: Known) -> Inference | None:
354
+ """Type a member a class of the module's takes from a standard-library base (see `stdlib.bases`).
355
+
356
+ `self.id()` in a `unittest.TestCase`: the base's that the class's order ends at, where no class
357
+ before it binds the name (see `Lineage`); `self.data` under a `UserDict[str, bytes]`.
358
+
359
+ Returns:
360
+ Its inference, or `None`.
361
+
362
+ """
363
+ owner: str | None = known.class_side.lineage.definer(receiver, name)
364
+ if owner is None or owner == receiver:
365
+ return None
366
+ if _GENERIC in owner: # a generic one, given its arguments: they type its attributes
367
+ return None if call is not None else overloads.generic_member(owner, name, call, known)
368
+ return stdlib.library_member(owner, name, call, known, inherited=True)
369
+
370
+
371
+ # Where a member's type can come from, in the order they're asked: the one place to add another.
372
+ SOURCES: Final[tuple[MemberSource, ...]] = (
373
+ _class_side,
374
+ _fixed,
375
+ _declared,
376
+ _elements,
377
+ stdlib.library_member,
378
+ overloads.generic_member,
379
+ _library_base,
380
+ )
381
+
382
+
383
+ def member(receiver: str, name: str, call: ast.Call | None, known: Known) -> Inference | None:
384
+ """Type the member `name` of a value typed `receiver`: an attribute, or (`call`) a method's call.
385
+
386
+ Returns:
387
+ The first of `SOURCES`' inferences, or `None` if none knows it.
388
+
389
+ """
390
+ source: MemberSource
391
+ found: Inference | None
392
+ for source in SOURCES:
393
+ if (found := source(receiver, name, call, known)) is not None:
394
+ return found
395
+ return None
396
+
397
+
398
+ def keyed(receiver: str, index: ast.expr, known: Known) -> Inference | None:
399
+ """Type `d["key"]`, `d` a value typed as a `TypedDict` class: the key's declared type.
400
+
401
+ The class's own key, or one it takes from a base (see `keys.key`, `_declared`).
402
+
403
+ Returns:
404
+ Its inference, or `None` for any other index or receiver, or a key the class doesn't declare.
405
+
406
+ """
407
+ name: str
408
+ match index:
409
+ case ast.Constant(value=str() as name):
410
+ found: Inference | None = _declared(receiver, key(name), None, known)
411
+ reason: str = f"`{receiver}`'s `{name}` key"
412
+ return None if found is None else Inference(found.annotation, reason, frozenset({_SUBSCRIPT}))
413
+ case _:
414
+ return None
415
+
416
+
417
+ def may_miss(receiver: str, index: ast.expr, known: Known) -> bool:
418
+ """Check whether a `TypedDict` class's literal key may be missing (see `keys.optional`).
419
+
420
+ Returns:
421
+ Whether it may: `d.get("key")` is then `None` too.
422
+
423
+ """
424
+ return isinstance(index, ast.Constant) and (
425
+ _declared(receiver, optional(str(index.value)), None, known) is not None
426
+ )
427
+
428
+
429
+ def returned_method(receiver: str, name: str, known: Known) -> tuple[str, str] | None:
430
+ """Look up a method of a value typed `receiver` typed only by its `return`s (a guess, see `Returned`).
431
+
432
+ The receiver's class's own, or the base's that defines it (see `Lineage`): but not one whose
433
+ type names that base, which may be the receiver's own class (`return self`). A class named by
434
+ an import a fix added (`Tool`, by `from tools import Tool`) is looked up as the module spells
435
+ it (`tools.Tool`): it's a standard-library class another checked file defines. An `async def`'s
436
+ is under `AWAIT` before its name: what awaiting its call gives.
437
+
438
+ Returns:
439
+ The class that defines it and its type, or `None` if it isn't one (a certain source is asked
440
+ first, see `member`).
441
+
442
+ """
443
+ owner: str | None = known.class_side.lineage.definer(receiver, name.removeprefix(AWAIT))
444
+ found: str | None = known.returned.methods.get(owner or "", {}).get(name)
445
+ plan: ImportPlan | None = known.names.plan
446
+ if found is None and owner == receiver and plan is not None and receiver in plan.added:
447
+ found = known.returned.methods.get(added_dotted(plan.added[receiver]), {}).get(name)
448
+ if found is None or owner is None or (owner != receiver and owner in roots(found)):
449
+ return None
450
+ return owner, found
451
+
452
+
453
+ def partial_method(receiver: str, name: str, known: Known) -> str | None:
454
+ """Look up a method of a value typed `receiver` declared to return a tuple with a vague part.
455
+
456
+ The receiver's class's own, or the base's that defines it (see `Lineage`, `Partial`).
457
+
458
+ Returns:
459
+ Its return, or `None` if it isn't one.
460
+
461
+ """
462
+ owner: str | None = known.class_side.lineage.definer(receiver, name)
463
+ return known.indirect.partial.methods.get(owner or "", {}).get(name)
464
+
465
+
466
+ def assigned_owner(receiver: str, name: str, known: Known) -> str | None:
467
+ """Find the class whose assignments type the attribute `name` of a value typed `receiver`.
468
+
469
+ The receiver's own class, or the first of its order that has one, unless a class of the
470
+ module's before it binds the name in its body.
471
+
472
+ Returns:
473
+ It, or `None`.
474
+
475
+ """
476
+ lineage: Lineage = known.class_side.lineage
477
+ owner: str
478
+ for owner in (receiver, *lineage.order.get(receiver, ())):
479
+ if name in known.returned.attributes.get(owner, {}):
480
+ return owner
481
+ if name in lineage.bound.get(owner, ()):
482
+ break
483
+ return None
484
+
485
+
486
+ def assigned_attribute(receiver: str, name: str, known: Known) -> str | None:
487
+ """Look up an attribute of a value typed `receiver` typed by its assignments alone (see `assigned_owner`).
488
+
489
+ Returns:
490
+ Its type, or `None` if it isn't one (a certain source is asked first, see `member`).
491
+
492
+ """
493
+ owner: str | None = assigned_owner(receiver, name, known)
494
+ return None if owner is None else known.returned.attributes[owner][name]
495
+
496
+
497
+ def class_variable(receiver: str, name: str, known: Known) -> str | None:
498
+ """Look up a plain class's variable typed by its value alone (see `constricter.fix.values.classvars`).
499
+
500
+ On an instance of the class, or the class itself (`type[C]`); one of a base of the module's that
501
+ binds it, too.
502
+
503
+ Returns:
504
+ Its type, or `None` if it isn't one (a certain source is asked first, see `member`), or is
505
+ one of another file's that this one can't write.
506
+
507
+ """
508
+ owner: str | None = known.class_side.lineage.definer(class_of(receiver) or receiver, name)
509
+ return known.class_side.variables.get(owner or "", {}).get(name) or None
510
+
511
+
512
+ def subscripted(container: str, node: ast.Subscript, index: str | None) -> str | None:
513
+ """Infer `container[...]`'s type, given `container`'s own type as text, and the index's (`index`).
514
+
515
+ A slice (`x[1:2]`, or an index typed `slice`) of a `list`, `str`, `bytes` or `tuple[T, ...]` is
516
+ the same type as `container` itself; an index typed `int` into one is its element type (a
517
+ `bytes`'s, an `int`), and one of another or no known type decides nothing: it may be a slice. Any
518
+ index into a `dict` is its value type. A fixed-length `tuple[T1, T2]`'s part is
519
+ the one a literal index names (`pair[0]`, `pair[-1]`).
520
+
521
+ Returns:
522
+ The annotation as source text, or `None` if the subscript doesn't decide one.
523
+
524
+ """
525
+ sliced: bool = isinstance(node.slice, ast.Slice) or index == _SLICE
526
+ element: ast.expr
527
+ last: ast.expr
528
+ name: str
529
+ match parsed(container):
530
+ case ast.Name(id="str" | "bytes" as name):
531
+ # A `bytes`'s plain index is one byte's `int`; an index of unknown type may be a slice.
532
+ return container if sliced or name == _STR else _INT if index == _INT else None
533
+ case ast.Subscript(value=ast.Name(id="list" | "List"), slice=element):
534
+ return container if sliced else ast.unparse(sole(element)) if index == _INT else None
535
+ case ast.Subscript(
536
+ value=ast.Name(id="dict" | "Dict"),
537
+ slice=ast.Tuple(elts=[_, element]),
538
+ ) if not sliced:
539
+ return ast.unparse(element)
540
+ case ast.Subscript(
541
+ value=ast.Name(id="tuple" | "Tuple"),
542
+ slice=ast.Tuple(elts=[element, last]),
543
+ ) if isinstance(last, ast.Constant) and last.value is Ellipsis:
544
+ return container if sliced else ast.unparse(element) if index == _INT else None
545
+ case ast.Subscript(value=ast.Name(id="tuple" | "Tuple"), slice=element) if not sliced:
546
+ return _part(element.elts if isinstance(element, ast.Tuple) else [element], node.slice)
547
+ case _:
548
+ return None
549
+
550
+
551
+ def _part(parts: Sequence[ast.expr], index: ast.expr) -> str | None:
552
+ """Pick the part of a fixed-length tuple's type that a literal `index` names (`0`, `-1`).
553
+
554
+ Returns:
555
+ It, as text; `None` for any other index, one past the tuple's ends, or a tuple that unpacks
556
+ another (`tuple[int, *Ts]`).
557
+
558
+ """
559
+ at: int
560
+ match index:
561
+ case ast.Constant(value=bool()) | ast.UnaryOp(operand=ast.Constant(value=bool())):
562
+ return None # an `int` to Python, and no index to a type checker
563
+ case ast.Constant(value=int() as at):
564
+ pass
565
+ case ast.UnaryOp(op=ast.USub(), operand=ast.Constant(value=int() as at)):
566
+ at = -at
567
+ case _:
568
+ return None
569
+ starred: bool = any(isinstance(part, ast.Starred) for part in parts)
570
+ return None if starred or not -len(parts) <= at < len(parts) else ast.unparse(parts[at])
571
+
572
+
573
+ def _annotation_of(receiver: str, name: str) -> str:
574
+ return f"the annotation of `{receiver}.{name}`"
575
+
576
+
577
+ def _declared_return(receiver: str, name: str) -> str:
578
+ return f"`{receiver}.{name}`'s declared return type"