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,677 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Where a type checker sees a value otherwise than `--fix` infers it, and what `--fix` does then.
3
+
4
+ - A copy, attribute or subscript of a union may be narrowed where it's read (`if x is not None:`,
5
+ `isinstance`), which a type checker sees and `--fix` doesn't; so may a comprehension's elements, by
6
+ its condition (`[c for c in cs if isinstance(c, Column)]`, whatever their type), and a read whose
7
+ type a call takes as its own (`deque([x])`). Their fixes are guesses.
8
+ - An ALL_CAPS module-level name is a constant to pyright, which keeps its literal's type
9
+ (`Literal["r"]`) where `str` would widen it: a guess too, where the module passes it to a call
10
+ or a default (see `passed`), for a parameter that may take only some values.
11
+ - `self`, and a method declared to return `Self` called on `self` or `cls`, are `Self`, not the class
12
+ (a subclass's `self` isn't its base): written as the module already spells `Self`, or not at all.
13
+ - A generic class the module defines, written bare (`list[Mapper]`, as `self` makes one), is missing
14
+ its type arguments: not offered.
15
+ """
16
+
17
+ import ast
18
+ import bisect
19
+ from collections.abc import Mapping, Sequence
20
+ from dataclasses import dataclass
21
+ from functools import lru_cache
22
+ from types import MappingProxyType
23
+ from typing import Final, NamedTuple, TypeAlias, cast
24
+
25
+ from constricter.fix.core.imports import checking, inner_imports, nested_classes, rebound_names
26
+ from constricter.fix.core.known import ImportPlan, Inference, Passed
27
+ from constricter.fix.values.narrowed import Regions, regions
28
+ from constricter.rules.annotations import node_name, roots
29
+ from constricter.rules.declarations import generic_classes
30
+ from constricter.rules.flow import members
31
+ from constricter.rules.quoted import parsed
32
+ from constricter.rules.syntax import FunctionDef, Start, within
33
+ from constricter.rules.walked import classes, of_type, walk
34
+
35
+ # What a copy, an attribute and a subscript of a narrowable union rest on, as guesses.
36
+ _Read: TypeAlias = type[ast.expr]
37
+ _READS: Final[dict[_Read, str]] = {
38
+ ast.Name: "copy",
39
+ ast.Attribute: "attribute",
40
+ ast.Subscript: "subscript",
41
+ }
42
+ # Where a function tests a value, a type checker may narrow it (`isinstance`, `is None`, a `TypeGuard`).
43
+ _TESTS: Final = (ast.If, ast.While, ast.Assert, ast.IfExp, ast.comprehension, ast.Match)
44
+ _SELF: Final = "Self"
45
+ _STDLIB: Final = "stdlib" # the fix kind of what typeshed declares
46
+ _COMPREHENSION: Final = "comprehension"
47
+ _LITERAL: Final = "literal"
48
+ _NONE: Final = "None"
49
+ _OPTIONAL: Final = "Optional"
50
+ _COMPREHENSIONS: Final = (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
51
+ _Comprehension: TypeAlias = ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp
52
+ _UNIONS: Final = frozenset({"Optional", "Union"})
53
+ _SELF_ORIGINS: Final = frozenset({"typing.Self", "typing_extensions.Self"})
54
+ _SELF_ITSELF: Final = "{}" # how a value that is `Self` is written, `{}` standing for it
55
+ _CLASS_OF: Final = "type[{}]" # how `type(self)` is
56
+ _INSTANCE: Final = "self" # a method's first parameter: any other name is a classmethod's class
57
+ _EXTENSIONS: Final = "typing_extensions"
58
+ _TYPING: Final = frozenset({"typing", _EXTENSIONS})
59
+
60
+
61
+ @dataclass(frozen=True, eq=False) # hashed by identity: a module's, cached with each function
62
+ class Tests:
63
+ """What a module's tests (see `_TESTS`) read, where a type checker may narrow it (see `tests`)."""
64
+
65
+ starts: tuple[Start, ...] = () # where each read's test starts, in source order
66
+ texts: tuple[str, ...] = () # each read, as source text
67
+
68
+
69
+ class Facts(NamedTuple):
70
+ """What of a module decides what a type checker sees otherwise than `--fix` infers.
71
+
72
+ Each class's methods declared to return a bare `Self` (`self_returns`), its generic classes
73
+ (`generic_classes`), and the names it passes to a call or a default (`passed`).
74
+ """
75
+
76
+ selfish: Mapping[str, frozenset[str]] = MappingProxyType({})
77
+ generics: frozenset[str] = frozenset()
78
+ passed: frozenset[str] = frozenset()
79
+ # What it imports under a top-level `if` on a flag (`TYPE_CHECKING`), unbound when it runs.
80
+ checking: frozenset[str] = frozenset()
81
+ tests: Tests = Tests() # what its tests read (see `tests`)
82
+ narrowed: Regions = MappingProxyType({}) # where each value is narrowed (see `narrowed.regions`)
83
+ inner: tuple[int, ...] = () # the lines functions and lambdas start on, sorted (see `inner_starts`)
84
+ # The functions `@contextmanager` makes context managers, its own and those it imports: what
85
+ # `with` gives of each, and what that rests on if it's a guess (see `entered`).
86
+ managers: Mapping[str, Passed] = MappingProxyType({})
87
+ type_vars: frozenset[str] = frozenset() # its type variables, its own and those it imports
88
+ # Those each class's bases name (`class Row(Generic[_TP])`), bound in its methods.
89
+ bound: Mapping[str, frozenset[str]] = MappingProxyType({})
90
+ # The names it binds more than once, and where (see `imports.rebound_names`): all a function
91
+ # can shadow, each only where one of its bindings is.
92
+ rebound: Mapping[str, Sequence[Start]] | None = None
93
+ # Where its functions' own imports start, in source order (see `imports.inner_imports`).
94
+ lazy: Sequence[Start] | None = None
95
+ # The other checked files' functions it calls that declare no return (see `Outside.untyped`).
96
+ untyped: frozenset[str] = frozenset()
97
+ # The names it reads an attribute of (`run`, in `run.cache_clear()`), anywhere in it.
98
+ attributed: frozenset[str] = frozenset()
99
+ # Its functions' parameters' types, where a `Callable` can list them (see `callables.signatures`).
100
+ positional: Mapping[str, tuple[str, ...]] = MappingProxyType({})
101
+ # The classes defined in a class's body, and the class naming each (see `imports.nested_classes`).
102
+ nested: Mapping[str, str] = MappingProxyType({})
103
+ excused: tuple[int, ...] = () # the lines (from 0) a type-ignore comment is on, sorted (`late.excusing`)
104
+
105
+
106
+ def facts(
107
+ tree: ast.Module,
108
+ selfish: Mapping[str, frozenset[str]],
109
+ generics: frozenset[str],
110
+ managers: Mapping[str, Passed],
111
+ type_vars: frozenset[str],
112
+ ) -> Facts:
113
+ """Read a module's `Facts`.
114
+
115
+ `selfish`: its `self_returns`; `generics`: the generic classes it names that others define;
116
+ `managers`: its `entered.managers`, and those it imports; `type_vars`: its type variables.
117
+
118
+ Returns:
119
+ Them.
120
+
121
+ """
122
+ return Facts(
123
+ selfish,
124
+ generic_classes(tree) | generics,
125
+ passed(tree),
126
+ checking(tree),
127
+ tests(tree),
128
+ regions(tree),
129
+ inner_starts(tree),
130
+ managers,
131
+ type_vars,
132
+ _bound_vars(tree, type_vars),
133
+ rebound_names(tree),
134
+ [(stmt.lineno, stmt.col_offset) for stmt in inner_imports(tree)],
135
+ nested=nested_classes(tree),
136
+ )
137
+
138
+
139
+ def _bound_vars(tree: ast.Module, type_vars: frozenset[str]) -> dict[str, frozenset[str]]:
140
+ """Map each class to the type variables its bases name, which its methods may use.
141
+
142
+ Returns:
143
+ Each such class's name, and them.
144
+
145
+ """
146
+ found: dict[str, frozenset[str]] = {}
147
+ node: ast.ClassDef
148
+ for node in classes(tree) if type_vars else ():
149
+ named: frozenset[str] = frozenset(
150
+ name.id for base in node.bases for name in walk(base) if isinstance(name, ast.Name)
151
+ )
152
+ if named & type_vars:
153
+ found[node.name] = found.get(node.name, frozenset()) | (named & type_vars)
154
+ return found
155
+
156
+
157
+ def undeclared(
158
+ annotation: str,
159
+ function: FunctionDef | None,
160
+ known: frozenset[str],
161
+ bound: frozenset[str],
162
+ ) -> bool:
163
+ """Check whether an annotation names a type variable its function doesn't declare.
164
+
165
+ One of the module's (`known`) that neither the function's signature names nor its class binds
166
+ (`bound`) is unbound there, to a type checker (`proc: type[Row[_TP]]`); a module body declares
167
+ none.
168
+
169
+ Returns:
170
+ Whether it does.
171
+
172
+ """
173
+ named: frozenset[str] = roots(annotation) & known
174
+ if not named or function is None:
175
+ return bool(named)
176
+ declared: set[str] = {
177
+ node.id for part in _signature(function) for node in ast.walk(part) if isinstance(node, ast.Name)
178
+ }
179
+ return not named <= (bound | declared)
180
+
181
+
182
+ class Owner(NamedTuple):
183
+ """The class a method belongs to, the name its instance (or class) goes by, and its `Self` methods."""
184
+
185
+ name: str
186
+ first: str # `self`, or a classmethod's first parameter
187
+ selfish: frozenset[str] # its methods declared to return a bare `Self`, those it inherits too
188
+
189
+
190
+ def doubts(value: ast.expr, found: Inference, *, constant: bool, narrowed: frozenset[str]) -> frozenset[str]:
191
+ """Find what makes `found`, a certain inference of `value`, a guess (see the module docstring).
192
+
193
+ `constant`: whether the value is bound to an ALL_CAPS module-level name; `narrowed`: what the
194
+ function tests somewhere (`isinstance(x, C)`, `x is None`, `is_c(x)`), where a type checker
195
+ narrows it, whatever its type: the value itself, or a read whose type it takes (`found.reads`).
196
+
197
+ Returns:
198
+ The guessing mechanisms (`FIX_KINDS`) it then rests on; none if it stays certain.
199
+
200
+ """
201
+ kind: str | None = _READS.get(type(value))
202
+ if kind is not None and (ast.unparse(value) in narrowed or len(members(found.annotation) or ()) > 1):
203
+ return frozenset({kind})
204
+ if narrowed.intersection(found.reads):
205
+ return frozenset({_READS[ast.Name]})
206
+ if (
207
+ isinstance(value, _COMPREHENSIONS)
208
+ and any(g.ifs for g in value.generators)
209
+ and (_has_union(found) or _sifted(value))
210
+ ):
211
+ return frozenset({_COMPREHENSION})
212
+ if constant and _literal(value):
213
+ return frozenset({_LITERAL})
214
+ return frozenset()
215
+
216
+
217
+ def narrowed_first(value: ast.expr, found: Inference) -> bool:
218
+ """Check whether code reading `value` almost always narrows it first, so `found` is best not offered.
219
+
220
+ A copy, attribute or subscript of an `X | None`, and a filtered comprehension over one, is nearly
221
+ always checked for `None` before it's used (an `assert`, an early `return`, a walrus), and a
222
+ checker then takes it for the `X` it's narrowed to: declaring the union breaks that (but see
223
+ `as_declared`). A bare `None` (a copy of a name only ever `None`) says nothing.
224
+
225
+ Returns:
226
+ Whether it is.
227
+
228
+ """
229
+ if found.annotation == _NONE:
230
+ return True
231
+ optional: bool = _NONE in (members(found.annotation) or ())
232
+ filtered: bool = isinstance(value, _COMPREHENSIONS) and any(g.ifs for g in value.generators)
233
+ return (isinstance(value, tuple(_READS)) and optional) or (filtered and _has_none(found.annotation))
234
+
235
+
236
+ def as_declared(value: ast.expr, function: FunctionDef | None, found: Tests) -> bool:
237
+ """Check whether a read (`x`, `self.x`, `d[k]`) has, to a type checker, the type it's declared.
238
+
239
+ Nothing in its function narrows it: no test of it or of what it's read of (`self.conn`, in
240
+ `self.conn.pool`), and no store of either, which narrows as a test does.
241
+
242
+ Returns:
243
+ Whether it has; not for a module's or a class body's read.
244
+
245
+ """
246
+ if function is None or not isinstance(value, tuple(_READS)):
247
+ return False
248
+ text: str = ast.unparse(value)
249
+ return not any(
250
+ text == each or text.startswith((f"{each}.", f"{each}["))
251
+ for each in tested(function, found) | _stored(function)
252
+ )
253
+
254
+
255
+ @lru_cache(maxsize=64) # asked of each read of an `X | None` in the function's scope
256
+ def _stored(function: FunctionDef) -> frozenset[str]:
257
+ """List what a function stores, the functions inside it too: `x`, `self.x`, `d[k]`.
258
+
259
+ Returns:
260
+ Each, as source text.
261
+
262
+ """
263
+ return frozenset(
264
+ ast.unparse(node)
265
+ for node in walk(function)
266
+ if isinstance(node, ast.Name | ast.Attribute | ast.Subscript) and not isinstance(node.ctx, ast.Load)
267
+ )
268
+
269
+
270
+ def _sifted(value: _Comprehension) -> bool:
271
+ """Check whether a comprehension keeps only what's of some class: `[x for x in xs if isinstance(x, C)]`.
272
+
273
+ Returns:
274
+ Whether one of its conditions is an `isinstance` of a name it binds: its elements are narrowed.
275
+
276
+ """
277
+ bound: set[str] = {
278
+ node.id for each in value.generators for node in ast.walk(each.target) if isinstance(node, ast.Name)
279
+ }
280
+ return any(
281
+ _instance_checked(node) in bound
282
+ for each in value.generators
283
+ for test in each.ifs
284
+ for node in ast.walk(test)
285
+ )
286
+
287
+
288
+ def _instance_checked(node: ast.AST) -> str: # the `x` of `isinstance(x, C)`, else nothing
289
+ name: str
290
+ match node:
291
+ case ast.Call(func=ast.Name(id="isinstance"), args=[ast.Name(id=name), *_]):
292
+ return name
293
+ case _:
294
+ return ""
295
+
296
+
297
+ def _has_none(annotation: str) -> bool:
298
+ """Check whether an annotation allows `None` anywhere in it (`list[int | None]`, `list[Optional[str]]`).
299
+
300
+ Returns:
301
+ Whether it does.
302
+
303
+ """
304
+ # `annotation` is always `ast.unparse`'s own output, so it's always valid Python to parse back.
305
+ return any(
306
+ (isinstance(node, ast.Constant) and node.value is None)
307
+ or (isinstance(node, ast.Name | ast.Attribute) and node_name(node) == _OPTIONAL)
308
+ for node in ast.walk(ast.parse(annotation, mode="eval"))
309
+ )
310
+
311
+
312
+ def inner_starts(tree: ast.Module) -> tuple[int, ...]:
313
+ """List the lines the module's functions and lambdas start on, for `contains_inner` to look up.
314
+
315
+ Returns:
316
+ Them, sorted.
317
+
318
+ """
319
+ inner: list[FunctionDef | ast.Lambda] = cast(
320
+ "list[FunctionDef | ast.Lambda]",
321
+ of_type(tree, ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda),
322
+ )
323
+ return tuple(sorted(node.lineno for node in inner))
324
+
325
+
326
+ def contains_inner(function: FunctionDef, starts: tuple[int, ...]) -> bool:
327
+ """Check whether a function has a function or lambda inside it, by the lines they start on.
328
+
329
+ Returns:
330
+ Whether one starts after its own line and by its last: most functions have none, and needn't
331
+ be walked for one.
332
+
333
+ """
334
+ return bisect.bisect_right(starts, function.lineno) < bisect.bisect_right(
335
+ starts,
336
+ function.end_lineno or function.lineno,
337
+ )
338
+
339
+
340
+ def tests(tree: ast.Module) -> Tests:
341
+ """Find what the module's tests read, once for all its functions (see `tested`).
342
+
343
+ Returns:
344
+ Each name, attribute and subscript in a test (an `if`'s condition, a comprehension's, a
345
+ `match`'s subject), as source text, by where its test starts.
346
+
347
+ """
348
+ found: list[tuple[Start, str]] = sorted(
349
+ ((test.lineno, test.col_offset), ast.unparse(read))
350
+ for node in of_type(tree, *_TESTS)
351
+ for test in _tested_parts(node)
352
+ for read in walk(test)
353
+ if isinstance(read, ast.Name | ast.Attribute | ast.Subscript)
354
+ )
355
+ return Tests(tuple(start for start, _ in found), tuple(text for _, text in found))
356
+
357
+
358
+ @lru_cache(maxsize=64) # asked by each assignment in the function's scope
359
+ def tested(function: FunctionDef, found: Tests) -> frozenset[str]:
360
+ """List what a function tests, where a type checker may narrow it: `x`, `self.x`, `d[k]`.
361
+
362
+ Its own tests and those of the functions inside it, its decorators' too; from the module's
363
+ (`found`, see `tests`), by the function's span of the source.
364
+
365
+ Returns:
366
+ Each name, attribute and subscript in a test, as source text.
367
+
368
+ """
369
+ begin: Start | None = (
370
+ (function.decorator_list[0].lineno, function.decorator_list[0].col_offset)
371
+ if function.decorator_list
372
+ else None
373
+ )
374
+ return frozenset(found.texts[within(found.starts, function, begin)])
375
+
376
+
377
+ @lru_cache(maxsize=64) # asked by each hinted union in the function's scope
378
+ def narrowing(function: FunctionDef) -> frozenset[str]:
379
+ """List the names a function's tests narrow themselves: `x is None`, `isinstance(x, C)`, `if x`.
380
+
381
+ Not one a test only reads (`if x.ready`), as `tested` lists too.
382
+
383
+ Returns:
384
+ Them.
385
+
386
+ """
387
+ return frozenset(
388
+ name
389
+ for node in walk(function)
390
+ if isinstance(node, _TESTS)
391
+ for test in _tested_parts(node)
392
+ for name in _narrowed(test)
393
+ )
394
+
395
+
396
+ def _narrowed(test: ast.expr) -> list[str]:
397
+ """Find the names one test narrows, through `and`, `or` and `not`.
398
+
399
+ Returns:
400
+ Them.
401
+
402
+ """
403
+ name: str
404
+ values: list[ast.expr]
405
+ operand: ast.expr
406
+ match test:
407
+ case ast.BoolOp(values=values):
408
+ return [name for value in values for name in _narrowed(value)]
409
+ case ast.UnaryOp(op=ast.Not(), operand=operand):
410
+ return _narrowed(operand)
411
+ case (
412
+ ast.Name(id=name)
413
+ | ast.Compare(left=ast.Name(id=name), comparators=[ast.Constant(value=None)])
414
+ | ast.Call(func=ast.Name(id="isinstance"), args=[ast.Name(id=name), *_])
415
+ ):
416
+ return [name]
417
+ case _:
418
+ return []
419
+
420
+
421
+ def _tested_parts(node: ast.AST) -> list[ast.expr]:
422
+ """Find what one of `_TESTS` tests: an `if`'s condition, a comprehension's, a `match`'s subject.
423
+
424
+ Returns:
425
+ Them.
426
+
427
+ """
428
+ test: ast.expr
429
+ ifs: list[ast.expr]
430
+ match node:
431
+ case ast.If(test=test) | ast.While(test=test) | ast.Assert(test=test) | ast.IfExp(test=test):
432
+ return [test]
433
+ case ast.comprehension(ifs=ifs):
434
+ return list(ifs)
435
+ case _: # a `match` (see `_TESTS`)
436
+ return [cast("ast.Match", node).subject]
437
+
438
+
439
+ def says_self(function: FunctionDef) -> bool:
440
+ """Check whether a function's signature mentions `Self` (`-> Self`, `other: Self`, `list[Self]`).
441
+
442
+ In any other method, mypy takes `self` for its class, not `Self`.
443
+
444
+ Returns:
445
+ Whether it does.
446
+
447
+ """
448
+ return any(
449
+ node_name(node) == _SELF
450
+ for annotation in _signature(function)
451
+ for node in ast.walk(annotation)
452
+ if isinstance(node, ast.Name | ast.Attribute)
453
+ )
454
+
455
+
456
+ def _signature(function: FunctionDef) -> list[ast.expr]:
457
+ """List the annotations of a function's signature: its parameters', and its return's.
458
+
459
+ Returns:
460
+ Them, one in quotes as the expression it quotes.
461
+
462
+ """
463
+ args: ast.arguments = function.args
464
+ annotations: list[ast.expr | None] = [
465
+ function.returns,
466
+ *(
467
+ arg.annotation
468
+ for arg in (*args.posonlyargs, *args.args, *args.kwonlyargs, args.vararg, args.kwarg)
469
+ if arg
470
+ ),
471
+ ]
472
+ return [parsed(annotation) for annotation in annotations if annotation is not None]
473
+
474
+
475
+ def passed(tree: ast.Module) -> frozenset[str]:
476
+ """Find the names a module passes to a call (`f(X)`, `f(code=X)`) or gives a parameter as its default.
477
+
478
+ Where a constant's `Literal` type matters: a parameter may take only some values
479
+ (`code: Literal["invalid-type", ...]`), which `str` doesn't say.
480
+
481
+ Returns:
482
+ Them.
483
+
484
+ """
485
+ names: set[str] = set()
486
+ node: ast.AST
487
+ for node in of_type(tree, ast.Call, ast.arguments):
488
+ values: list[ast.expr | None] = (
489
+ [*node.args, *(keyword.value for keyword in node.keywords)]
490
+ if isinstance(node, ast.Call)
491
+ else [*cast("ast.arguments", node).defaults, *cast("ast.arguments", node).kw_defaults]
492
+ )
493
+ names.update(value.id for value in values if isinstance(value, ast.Name))
494
+ return frozenset(names)
495
+
496
+
497
+ def is_constant(name: str) -> bool:
498
+ """Check whether pyright takes a name for a constant: letters all capitals (`MAX`, `_DEFAULT_MODE`).
499
+
500
+ Returns:
501
+ Whether it does.
502
+
503
+ """
504
+ return name.upper() == name and any(char.isalpha() for char in name)
505
+
506
+
507
+ def corrected(
508
+ value: ast.expr,
509
+ found: Inference,
510
+ owner: Owner | None,
511
+ generics: frozenset[str],
512
+ plan: ImportPlan | None,
513
+ ) -> Inference | None:
514
+ """Write `found` as a type checker sees it: `Self` where the value is one; nothing for a bare generic.
515
+
516
+ Returns:
517
+ The inference to offer, or `None` if there's none to.
518
+
519
+ """
520
+ written: str | None
521
+ if (written := None if owner is None else _selfish(value, found, owner)) is not None:
522
+ spelled: str | None = None if plan is None else spelled_self(plan)
523
+ return (
524
+ None
525
+ if spelled is None
526
+ else found._replace(annotation=written.format(spelled), reason=f"{found.reason}: `Self`")
527
+ )
528
+ return None if bare(found.annotation, generics) else found
529
+
530
+
531
+ def _selfish(value: ast.expr, found: Inference, owner: Owner) -> str | None:
532
+ """Find how `value`'s type is written with `Self`, if it's one typed as the class.
533
+
534
+ `self`, or a `Self` method called on `self`, `cls` or `type(self)`; `type(self)` or
535
+ `self.__class__`, a `type[Self]`; what `cls()`, either of those called, or `__new__` given one
536
+ constructs; a standard-library function's call given `self` that returns its class
537
+ (`copy.copy(self)`); and a conditional of two of them (`self if inplace else self.copy()`).
538
+
539
+ Returns:
540
+ Its annotation, `{}` standing for `Self`; or `None` if it isn't one.
541
+
542
+ """
543
+ receiver: ast.expr
544
+ method: str
545
+ func: ast.expr
546
+ args: list[ast.expr]
547
+ template: str | None = None
548
+ match value:
549
+ case ast.Name() | ast.Call(func=ast.Name()) | ast.Attribute() if (
550
+ template := _own(value, owner.first)
551
+ ) is not None:
552
+ pass
553
+ case ast.Call(func=func) if _constructs(func, owner.first):
554
+ template = _SELF_ITSELF
555
+ case ast.Call(func=ast.Attribute(attr="__new__"), args=[func, *_]) if _constructs(func, owner.first):
556
+ template = _SELF_ITSELF
557
+ case ast.Call(func=ast.Attribute(value=receiver, attr=method)) if (
558
+ method in owner.selfish and _own(receiver, owner.first) is not None
559
+ ):
560
+ template = _SELF_ITSELF
561
+ # The class only by a type variable its argument binds: the standard library names no other's.
562
+ case ast.Call(args=[*args]) if _STDLIB in found.kinds and any(
563
+ _own(arg, owner.first) == _SELF_ITSELF for arg in args
564
+ ):
565
+ template = _SELF_ITSELF
566
+ case ast.IfExp() if {_selfish(side, found, owner) for side in (value.body, value.orelse)} == {
567
+ _SELF_ITSELF,
568
+ }:
569
+ template = _SELF_ITSELF
570
+ case _:
571
+ pass
572
+ return template if template is not None and found.annotation == template.format(owner.name) else None
573
+
574
+
575
+ def _constructs(func: ast.expr, first: str) -> bool:
576
+ """Check whether a callee is the method's own class: a classmethod's `cls`, or `type(self)`.
577
+
578
+ Returns:
579
+ Whether it is.
580
+
581
+ """
582
+ return (isinstance(func, ast.Name) and func.id == first != _INSTANCE) or _own(func, first) == _CLASS_OF
583
+
584
+
585
+ def _own(value: ast.expr, first: str) -> str | None:
586
+ """Find how a method's own instance or class is typed with `Self`: `self` or `cls`, or its class.
587
+
588
+ Its class: `type(self)`, or `self.__class__`.
589
+
590
+ Returns:
591
+ Its annotation, `{}` standing for `Self`; or `None` if `value` is neither.
592
+
593
+ """
594
+ name: str
595
+ match value:
596
+ case ast.Name(id=name) if name == first:
597
+ return _SELF_ITSELF
598
+ case (
599
+ ast.Call(func=ast.Name(id="type"), args=[ast.Name(id=name)])
600
+ | ast.Attribute(value=ast.Name(id=name), attr="__class__")
601
+ ) if name == first:
602
+ return _CLASS_OF
603
+ case _:
604
+ return None
605
+
606
+
607
+ def spelled_self(plan: ImportPlan) -> str | None:
608
+ """Name `Self` as the module already imports it (`Self`, `te.Self`, `typing.Self`).
609
+
610
+ Through an import for type checking alone too, and `typing_extensions` before `typing`. Not an
611
+ import added for it: `typing.Self` is Python 3.11's, and the module may run on older ones.
612
+
613
+ Returns:
614
+ The name, or `None` if it doesn't import it.
615
+
616
+ """
617
+ bound: dict[str, str] = {**plan.checking.bound, **plan.bound}
618
+ modules: dict[str, str] = {origin: name for name, origin in bound.items() if origin in _TYPING}
619
+ name: str | None = next((name for name, origin in bound.items() if origin in _SELF_ORIGINS), None)
620
+ if name is None and modules:
621
+ name = f"{modules.get(_EXTENSIONS) or next(iter(modules.values()))}.Self"
622
+ return name
623
+
624
+
625
+ def bare(annotation: str, generics: frozenset[str]) -> bool:
626
+ """Check whether an annotation names one of `generics` (as the module spells them) unsubscripted.
627
+
628
+ Returns:
629
+ Whether it does: `Box`, `util.OrderedSet`, but not `Box[int]`.
630
+
631
+ """
632
+ return bool(generics) and _bare(annotation, generics)
633
+
634
+
635
+ @lru_cache(maxsize=4096) # a module's fixes are a few types, each asked again and again
636
+ def _bare(annotation: str, generics: frozenset[str]) -> bool:
637
+ # `annotation` is always `ast.unparse`'s own output, so it's always valid Python to parse back.
638
+ tree: ast.expr = ast.parse(annotation, mode="eval").body
639
+ inner: set[int] = {
640
+ id(node.value) for node in walk(tree) if isinstance(node, ast.Subscript | ast.Attribute)
641
+ }
642
+ return any(
643
+ isinstance(node, ast.Name | ast.Attribute) and id(node) not in inner and ast.unparse(node) in generics
644
+ for node in walk(tree)
645
+ )
646
+
647
+
648
+ def _has_union(found: Inference) -> bool:
649
+ """Check whether an annotation has a union anywhere in it (`list[int | None]`, `Optional[str]`).
650
+
651
+ Returns:
652
+ Whether it has.
653
+
654
+ """
655
+ # `found.annotation` is always `ast.unparse`'s own output, so it's always valid Python to parse back.
656
+ return any(
657
+ (isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr))
658
+ or (isinstance(node, ast.Name) and node.id in _UNIONS)
659
+ or (isinstance(node, ast.Attribute) and node.attr in _UNIONS)
660
+ for node in ast.walk(ast.parse(found.annotation, mode="eval"))
661
+ )
662
+
663
+
664
+ def _literal(value: ast.expr) -> bool:
665
+ """Check whether `value` is a literal whose own type a `Literal` can be (a string, a number, a `bool`).
666
+
667
+ Returns:
668
+ Whether it is.
669
+
670
+ """
671
+ match value:
672
+ case ast.Constant(value=str() | bytes() | int()): # `bool` is an `int`
673
+ return True
674
+ case ast.UnaryOp(op=ast.USub(), operand=ast.Constant(value=int())):
675
+ return True
676
+ case _:
677
+ return False