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,508 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """What an installed package's stub declares that a call's arguments may decide the type of.
3
+
4
+ `declarations` reads, from a module's top level: each function whose arguments decide its return
5
+ (`@overload`s, or a return naming a type variable) with its signatures as written; type aliases
6
+ (`X: TypeAlias = ...`, `type X[T] = ...`, `X = A | B`); type variables' bounds and constraints; and
7
+ protocols' members. All as source text, for `constricter.fix.index.stubs.signatures` to resolve across modules
8
+ when a call needs them (`Module` keeps them, pickled into the installed modules' cache).
9
+ """
10
+
11
+ import ast
12
+ from collections.abc import Collection, Iterator, Mapping, Sequence
13
+ from typing import Final, NamedTuple, TypeAlias, cast
14
+
15
+ from constricter.empty import Empty
16
+ from constricter.rules.annotations import node_name
17
+ from constricter.rules.declarations import defined_type_vars
18
+
19
+ # A parameter: its name, kind (`p` positional only, `e` either, `k` keyword only, `a` `*args`,
20
+ # `w` `**kwargs`), whether it has a default, and its annotation's text (`None`: unannotated).
21
+ Param: TypeAlias = tuple[str, str, bool, str | None]
22
+ _OVERLOAD: Final = "overload"
23
+ _TYPE_VARIABLES: Final = frozenset({"TypeVar", "ParamSpec", "TypeVarTuple"})
24
+ _TYPE_VAR: Final = "TypeVar"
25
+ _TYPE_ALIAS: Final = "TypeAlias"
26
+ _PROTOCOL: Final = "Protocol"
27
+ _TYPE_CHECKING: Final = "TYPE_CHECKING"
28
+ _PROPERTIES: Final = frozenset({"property", "cached_property"})
29
+ _BOUND: Final = "bound"
30
+ _STAR: Final = "*"
31
+ _SELF: Final = "Self" # a method returning its instance's own type
32
+
33
+
34
+ class Signature(NamedTuple):
35
+ """One signature: its parameters, its return's text (`None`: unannotated), and its type parameters.
36
+
37
+ `type_params`: its own (`def f[T: int]`), each with its bound's text, or `None`; a `ParamSpec`
38
+ or `TypeVarTuple` has `"*"` for one.
39
+ """
40
+
41
+ params: tuple[Param, ...]
42
+ returns: str | None
43
+ type_params: tuple[tuple[str, str | None], ...] = ()
44
+ self_type: str | None = None # a method's `self` annotation (not `Self`): only such instances have it
45
+
46
+
47
+ class Class(NamedTuple):
48
+ """A class: its type parameters (each with its bound's text, or `None`), and its methods' signatures.
49
+
50
+ `bases`: its bases as written. `methods`: those whose arguments or instance decide their return:
51
+ overloaded, naming a type variable, or any at all for a generic class (its parameters bind them);
52
+ each signature without `self`. `attributes`: its body's annotated names, each one's annotation.
53
+ """
54
+
55
+ params: tuple[tuple[str, str | None], ...]
56
+ bases: tuple[str, ...]
57
+ methods: Mapping[str, tuple[Signature, ...]]
58
+ attributes: Mapping[str, str] = Empty()
59
+
60
+
61
+ class Alias(NamedTuple):
62
+ """A type alias: its value's text, and its own type parameters (`type X[T] = ...`'s; else none)."""
63
+
64
+ value: str
65
+ params: tuple[str, ...] = ()
66
+
67
+
68
+ class Variable(NamedTuple):
69
+ """A type variable: its bound's text, or its constraints joined with `|`; `constrained` says which."""
70
+
71
+ bound: str | None
72
+ constrained: bool = False
73
+
74
+
75
+ class Protocol(NamedTuple):
76
+ """A protocol class: the members an instance needs, and whether one is a property."""
77
+
78
+ members: frozenset[str]
79
+ properties: bool
80
+
81
+
82
+ class Declarations(NamedTuple):
83
+ """What `declarations` reads from a module (see the module docstring)."""
84
+
85
+ signatures: Mapping[str, tuple[Signature, ...]]
86
+ aliases: Mapping[str, Alias]
87
+ variables: Mapping[str, Variable]
88
+ protocols: Mapping[str, Protocol]
89
+ # What another module may import from it, as type checkers read a typed package's interface:
90
+ # its `__all__`, else its public definitions and redundant-alias imports (`from m import x as x`);
91
+ # `None` where a `*` import leaves that open.
92
+ exports: frozenset[str] | None = None
93
+ # Classes generic only through a subscripted base (`class float64(floating[_64Bit])`), each with
94
+ # the names in its bases' subscripts: generic if one is a type variable (see `installed`).
95
+ bases: Mapping[str, frozenset[str]] = Empty()
96
+ classes: Mapping[str, Class] = Empty() # every class, for its methods
97
+
98
+
99
+ def overloads(tree: ast.Module) -> dict[str, tuple[Signature, ...]]:
100
+ """Read a checked module's top-level functions defined with `@overload`, as `declarations` reads a stub's.
101
+
102
+ And its top-level classes' methods, each as `Class.method`, less its `self`: of a class
103
+ defined once.
104
+
105
+ Returns:
106
+ Each one's overloads' signatures, as written.
107
+
108
+ """
109
+ found: dict[str, tuple[Signature, ...]] = _overloads(tree.body)
110
+ names: list[str] = [stmt.name for stmt in tree.body if isinstance(stmt, ast.ClassDef)]
111
+ stmt: ast.stmt
112
+ for stmt in tree.body:
113
+ if isinstance(stmt, ast.ClassDef) and names.count(stmt.name) == 1:
114
+ found.update(
115
+ (f"{stmt.name}.{name}", tuple(_unbound(each) for each in written))
116
+ for name, written in _overloads(stmt.body).items()
117
+ if all(each.params for each in written)
118
+ )
119
+ return found
120
+
121
+
122
+ def _overloads(body: Sequence[ast.stmt]) -> dict[str, tuple[Signature, ...]]:
123
+ signatures: dict[str, list[Signature]] = {}
124
+ overloaded: set[str] = set()
125
+ stmt: ast.stmt
126
+ for stmt in body:
127
+ if isinstance(stmt, ast.FunctionDef):
128
+ _signature(stmt, signatures, overloaded, frozenset())
129
+ return {name: tuple(signatures[name]) for name in sorted(overloaded)}
130
+
131
+
132
+ def kinds(tree: ast.Module, aliases: Collection[str]) -> Declarations | None:
133
+ """Read what a checked module's names stand for in a signature: an alias, a type variable, a protocol.
134
+
135
+ `aliases`: the names it binds once as a type alias (see `modules`): no other assignment is one.
136
+
137
+ Returns:
138
+ Them, as `declarations` reads a stub's, and nothing else of its; `None` for a module with none.
139
+
140
+ """
141
+ found: dict[str, Alias] = {}
142
+ variables: dict[str, Variable] = {}
143
+ protocols: dict[str, Protocol] = {}
144
+ stmt: ast.stmt
145
+ for stmt in tree.body:
146
+ if isinstance(stmt, ast.ClassDef):
147
+ if any(node_name(base) == _PROTOCOL for base in _bases(stmt)):
148
+ protocols[stmt.name] = _protocol(stmt)
149
+ else:
150
+ _assignment(stmt, found, variables)
151
+ kept: dict[str, Alias] = {name: alias for name, alias in found.items() if name in aliases}
152
+ return Declarations({}, kept, variables, protocols) if kept or variables or protocols else None
153
+
154
+
155
+ def declarations(tree: ast.Module) -> Declarations:
156
+ """Read a stub's declarations (see the module docstring).
157
+
158
+ Returns:
159
+ Them.
160
+
161
+ """
162
+ type_vars: frozenset[str] = defined_type_vars(tree)
163
+ signatures: dict[str, list[Signature]] = {}
164
+ overloaded: set[str] = set()
165
+ aliases: dict[str, Alias] = {}
166
+ variables: dict[str, Variable] = {}
167
+ protocols: dict[str, Protocol] = {}
168
+ bases: dict[str, frozenset[str]] = {}
169
+ classes: dict[str, Class] = {}
170
+ names: frozenset[str] | None
171
+ stmt: ast.stmt
172
+ for stmt in (*tree.body, *_checked_classes(tree.body)):
173
+ match stmt:
174
+ case ast.FunctionDef():
175
+ _signature(stmt, signatures, overloaded, type_vars)
176
+ case ast.ClassDef():
177
+ if any(node_name(base) == _PROTOCOL for base in _bases(stmt)):
178
+ protocols[stmt.name] = _protocol(stmt)
179
+ if (names := _subscripted(stmt)) is not None:
180
+ bases[stmt.name] = names
181
+ classes[stmt.name] = _class(stmt, type_vars, variables)
182
+ case _:
183
+ _assignment(stmt, aliases, variables)
184
+ return Declarations(
185
+ {name: tuple(found) for name, found in signatures.items() if found},
186
+ aliases,
187
+ variables,
188
+ protocols,
189
+ _exports(tree),
190
+ bases,
191
+ classes,
192
+ )
193
+
194
+
195
+ def _checked_classes(body: list[ast.stmt]) -> Iterator[ast.ClassDef]:
196
+ """Find the classes a module defines under a top-level `if` a type checker takes the first arm of.
197
+
198
+ `if sys.version_info >= (3, 11) or TYPE_CHECKING:`, by the `TYPE_CHECKING` its test names.
199
+
200
+ Yields:
201
+ Each class of that arm.
202
+
203
+ """
204
+ stmt: ast.stmt
205
+ for stmt in body:
206
+ if isinstance(stmt, ast.If) and any(
207
+ node_name(node) == _TYPE_CHECKING for node in ast.walk(stmt.test)
208
+ ):
209
+ yield from (each for each in stmt.body if isinstance(each, ast.ClassDef))
210
+
211
+
212
+ def _class(node: ast.ClassDef, type_vars: frozenset[str], variables: Mapping[str, Variable]) -> Class:
213
+ """Read a class's type parameters and its methods (see `Class`).
214
+
215
+ Its own (`class C[T]`), else a `Generic[...]` or `Protocol[...]` base's, else the type variables
216
+ its bases' subscripts name, in order.
217
+
218
+ Returns:
219
+ It.
220
+
221
+ """
222
+ own: tuple[tuple[str, str | None], ...] = tuple(_type_params(node))
223
+ subscripts: list[ast.Subscript] = [base for base in node.bases if isinstance(base, ast.Subscript)]
224
+ generic: list[ast.Subscript] = [
225
+ base for base in subscripts if node_name(base.value) in {"Generic", _PROTOCOL}
226
+ ]
227
+ known: frozenset[str] = type_vars.union(variables.keys())
228
+ names: list[str] = list(
229
+ dict.fromkeys(
230
+ found.id
231
+ for base in (generic or subscripts)
232
+ for found in ast.walk(base.slice)
233
+ if isinstance(found, ast.Name) and (generic or found.id in known)
234
+ ),
235
+ )
236
+ params: tuple[tuple[str, str | None], ...] = own or tuple(
237
+ (name, None if name not in variables or variables[name].constrained else variables[name].bound)
238
+ for name in names
239
+ )
240
+ signatures: dict[str, list[Signature]] = {}
241
+ overloaded: set[str] = set()
242
+ stmt: ast.stmt
243
+ for stmt in node.body:
244
+ if isinstance(stmt, ast.FunctionDef) and (stmt.args.posonlyargs or stmt.args.args):
245
+ _signature(
246
+ stmt,
247
+ signatures,
248
+ overloaded,
249
+ known | {_SELF, *(name for name, _ in params)},
250
+ every=bool(params),
251
+ )
252
+ return Class(
253
+ params,
254
+ tuple(ast.unparse(base) for base in node.bases),
255
+ {name: tuple(_unbound(each) for each in found) for name, found in signatures.items() if found},
256
+ {
257
+ each.target.id: ast.unparse(each.annotation)
258
+ for each in node.body
259
+ if isinstance(each, ast.AnnAssign) and isinstance(each.target, ast.Name)
260
+ },
261
+ )
262
+
263
+
264
+ def _unbound(signature: Signature) -> Signature:
265
+ """Drop a method's `self` from its signature, noting whether it's annotated (not as `Self`).
266
+
267
+ Returns:
268
+ The signature.
269
+
270
+ """
271
+ annotation: str | None = signature.params[0][3]
272
+ return signature._replace(
273
+ params=signature.params[1:],
274
+ self_type=None if annotation is None or annotation.rpartition(".")[2] == _SELF else annotation,
275
+ )
276
+
277
+
278
+ def _subscripted(node: ast.ClassDef) -> frozenset[str] | None:
279
+ """Name what a class's subscripted bases pass their type parameters, where that alone makes it generic.
280
+
281
+ Returns:
282
+ Those names; `None` for a class with type parameters of its own, a `Generic[...]` or
283
+ `Protocol[...]` base, or no subscripted base.
284
+
285
+ """
286
+ subscripts: list[ast.Subscript] = [base for base in node.bases if isinstance(base, ast.Subscript)]
287
+ if (
288
+ not subscripts
289
+ or cast("object", getattr(node, "type_params", ()))
290
+ or any(node_name(base.value) in {"Generic", _PROTOCOL} for base in subscripts)
291
+ ):
292
+ return None
293
+ return frozenset(
294
+ found.id for base in subscripts for found in ast.walk(base.slice) if isinstance(found, ast.Name)
295
+ )
296
+
297
+
298
+ def _exports(tree: ast.Module) -> frozenset[str] | None:
299
+ """Name what a typed package's module exports (see `Declarations.exports`).
300
+
301
+ Returns:
302
+ Them, or `None` if a `*` import (without an `__all__`) makes that open.
303
+
304
+ """
305
+ listed: list[str] | None = None
306
+ names: set[str] = set()
307
+ starred: bool = False
308
+ stmt: ast.stmt
309
+ value: ast.expr
310
+ for stmt in tree.body:
311
+ match stmt:
312
+ case ast.Assign(targets=[ast.Name(id="__all__")], value=value):
313
+ listed = _strings(value)
314
+ case ast.AugAssign(target=ast.Name(id="__all__"), op=ast.Add(), value=value):
315
+ listed = [*(listed or []), *_strings(value)]
316
+ case ast.Import() | ast.ImportFrom():
317
+ starred |= any(alias.name == _STAR for alias in stmt.names)
318
+ names.update(alias.name for alias in stmt.names if alias.asname == alias.name)
319
+ case ast.FunctionDef() | ast.AsyncFunctionDef() | ast.ClassDef():
320
+ names.add(stmt.name)
321
+ case ast.Assign() | ast.AnnAssign():
322
+ targets: list[ast.expr] = stmt.targets if isinstance(stmt, ast.Assign) else [stmt.target]
323
+ names.update(target.id for target in targets if isinstance(target, ast.Name))
324
+ case _ if type(stmt).__name__ == _TYPE_ALIAS:
325
+ names.add(_type_alias(stmt)[0])
326
+ case _:
327
+ pass
328
+ if listed is not None:
329
+ return frozenset(listed)
330
+ return None if starred else frozenset(name for name in names if not name.startswith("_"))
331
+
332
+
333
+ def _strings(value: ast.expr) -> list[str]:
334
+ elements: list[ast.expr] = value.elts if isinstance(value, ast.List | ast.Tuple) else []
335
+ return [e.value for e in elements if isinstance(e, ast.Constant) and isinstance(e.value, str)]
336
+
337
+
338
+ def _signature(
339
+ node: ast.FunctionDef,
340
+ signatures: dict[str, list[Signature]],
341
+ overloaded: set[str],
342
+ type_vars: frozenset[str],
343
+ *,
344
+ every: bool = False,
345
+ ) -> None:
346
+ """Add a function's signature: an `@overload` to those before it; a generic one as its only one.
347
+
348
+ With `every` (a generic class's methods), any undecorated one with a return annotation too.
349
+ """
350
+ overload: bool = any(node_name(decorator) == _OVERLOAD for decorator in node.decorator_list)
351
+ if node.name in overloaded:
352
+ if overload:
353
+ signatures[node.name].append(_read(node))
354
+ return # the implementation after the overloads, which callers don't see
355
+ signature: Signature = _read(node)
356
+ if overload:
357
+ overloaded.add(node.name)
358
+ signatures[node.name] = [signature]
359
+ elif not node.decorator_list and (
360
+ signature.type_params or _mentions(node.returns, type_vars) or (every and node.returns is not None)
361
+ ):
362
+ signatures[node.name] = [signature]
363
+ else:
364
+ signatures[node.name] = [] # defined again later: whichever is last is the one
365
+
366
+
367
+ def _read(node: ast.FunctionDef) -> Signature:
368
+ """Read one signature as written.
369
+
370
+ Returns:
371
+ It.
372
+
373
+ """
374
+ args: ast.arguments = node.args
375
+ positional: list[ast.arg] = [*args.posonlyargs, *args.args]
376
+ first_default: int = len(positional) - len(args.defaults)
377
+ params: list[Param] = [
378
+ (arg.arg, "p" if at < len(args.posonlyargs) else "e", at >= first_default, _text(arg.annotation))
379
+ for at, arg in enumerate(positional)
380
+ ]
381
+ if args.vararg is not None:
382
+ params.append((args.vararg.arg, "a", True, _text(args.vararg.annotation)))
383
+ params.extend(
384
+ (arg.arg, "k", default is not None, _text(arg.annotation))
385
+ for arg, default in zip(args.kwonlyargs, args.kw_defaults, strict=True)
386
+ )
387
+ if args.kwarg is not None:
388
+ params.append((args.kwarg.arg, "w", True, _text(args.kwarg.annotation)))
389
+ return Signature(tuple(params), _text(node.returns), tuple(_type_params(node)))
390
+
391
+
392
+ def _type_params(node: ast.AST) -> Iterator[tuple[str, str | None]]:
393
+ """Read a definition's own type parameters (Python 3.12+'s `def f[T: int]`, `type X[T] = ...`).
394
+
395
+ Yields:
396
+ Each one's name and bound (`"*"` for a `ParamSpec` or `TypeVarTuple`).
397
+
398
+ """
399
+ param: ast.AST
400
+ for param in cast("list[ast.AST]", getattr(node, "type_params", [])):
401
+ name: str = cast("str", getattr(param, "name", ""))
402
+ if type(param).__name__ == _TYPE_VAR:
403
+ yield name, _text(cast("ast.expr | None", getattr(param, _BOUND, None)))
404
+ else:
405
+ yield name, "*"
406
+
407
+
408
+ def _mentions(annotation: ast.expr | None, type_vars: frozenset[str]) -> bool:
409
+ return annotation is not None and any(
410
+ isinstance(node, ast.Name) and node.id in type_vars for node in ast.walk(annotation)
411
+ )
412
+
413
+
414
+ def _text(annotation: ast.expr | None) -> str | None:
415
+ return None if annotation is None else ast.unparse(annotation)
416
+
417
+
418
+ def _bases(node: ast.ClassDef) -> Iterator[ast.expr]:
419
+ base: ast.expr
420
+ for base in node.bases:
421
+ yield base.value if isinstance(base, ast.Subscript) else base
422
+
423
+
424
+ def _protocol(node: ast.ClassDef) -> Protocol:
425
+ """Read a protocol's members: what its body defines or declares.
426
+
427
+ Returns:
428
+ It.
429
+
430
+ """
431
+ members: set[str] = set()
432
+ properties: bool = False
433
+ stmt: ast.stmt
434
+ name: str
435
+ for stmt in node.body:
436
+ match stmt:
437
+ case ast.FunctionDef() | ast.AsyncFunctionDef():
438
+ members.add(stmt.name)
439
+ properties |= any(node_name(d) in _PROPERTIES for d in stmt.decorator_list)
440
+ case ast.AnnAssign(target=ast.Name(id=name)):
441
+ members.add(name)
442
+ properties = True # an instance attribute: a class object's is its own
443
+ case _:
444
+ pass
445
+ return Protocol(
446
+ frozenset(members - {"__init__", "__new__", "__slots__", "__class_getitem__"}),
447
+ properties,
448
+ )
449
+
450
+
451
+ def _assignment(stmt: ast.stmt, aliases: dict[str, Alias], variables: dict[str, Variable]) -> None:
452
+ """Read a type alias or a type variable a top-level statement declares, if it declares one."""
453
+ func: ast.expr
454
+ args: list[ast.expr]
455
+ keywords: list[ast.keyword]
456
+ annotation: ast.expr
457
+ name: str
458
+ value: ast.expr
459
+ found: Alias
460
+ match stmt:
461
+ case ast.Assign(
462
+ targets=[ast.Name(id=name)],
463
+ value=ast.Call(func=func, args=args, keywords=keywords),
464
+ ) if node_name(func).removeprefix("_") in _TYPE_VARIABLES:
465
+ bound: ast.expr | None = next((k.value for k in keywords if k.arg == _BOUND), None)
466
+ constraints: list[str] = [ast.unparse(arg) for arg in args[1:]]
467
+ variables[name] = (
468
+ Variable(" | ".join(constraints), constrained=True)
469
+ if constraints
470
+ else Variable(_text(bound) if node_name(func).removeprefix("_") == _TYPE_VAR else "*")
471
+ )
472
+ case ast.AnnAssign(target=ast.Name(id=name), annotation=annotation, value=ast.expr() as value) if (
473
+ node_name(annotation) == _TYPE_ALIAS
474
+ ):
475
+ aliases[name] = Alias(ast.unparse(value))
476
+ case ast.Assign(targets=[ast.Name(id=name)], value=value) if _type_expression(value):
477
+ aliases[name] = Alias(ast.unparse(value))
478
+ case _ if type(stmt).__name__ == _TYPE_ALIAS: # `type X[T] = ...` (3.12+)
479
+ name, found = _type_alias(stmt)
480
+ aliases[name] = found
481
+ case _:
482
+ pass
483
+
484
+
485
+ def _type_alias(stmt: ast.stmt) -> tuple[str, Alias]:
486
+ """Read Python 3.12+'s `type X[T] = ...` (an `ast.TypeAlias`, which 3.11's `ast` doesn't have).
487
+
488
+ Returns:
489
+ Its name, and it.
490
+
491
+ """
492
+ named: ast.Name = cast("ast.Name", getattr(stmt, "name", None))
493
+ value: ast.expr = cast("ast.expr", getattr(stmt, "value", None))
494
+ return named.id, Alias(ast.unparse(value), tuple(param for param, _ in _type_params(stmt)))
495
+
496
+
497
+ def _type_expression(value: ast.expr) -> bool:
498
+ """Check whether an assignment's value may be a type (`A | B`, `list[int]`, `pkg.Class`), not a value.
499
+
500
+ Returns:
501
+ Whether it may.
502
+
503
+ """
504
+ match value:
505
+ case ast.BinOp(op=ast.BitOr()) | ast.Subscript() | ast.Name() | ast.Attribute():
506
+ return True
507
+ case _:
508
+ return False