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,797 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Cross-module `--fix`: the functions and classes other checked files define.
3
+
4
+ `index` reads every file once (see `constricter.fix.index.modules`). `calls` then gives a file the
5
+ return type of each function it imports (`from m import f`, `import m as a` then `a.f()`), and
6
+ `crossed.imported` those and each imported class's attributes and methods, but only where every
7
+ name in a type means the same thing in the file as where it was written: otherwise the fix would name
8
+ something undefined, or something else. A name the file doesn't bind that way is imported where
9
+ the type is written from under `if TYPE_CHECKING:` (`Guarded`): that import can't make a cycle.
10
+
11
+ What a module's unannotated functions return is known only once it's checked: the CLI checks the
12
+ files in `order.plan`'s order, each after those whose unannotated functions it calls (`needs`), and
13
+ adds each module's (`with_returned`) to the index for the files after it.
14
+ """
15
+
16
+ import ast
17
+ import builtins
18
+ import sys
19
+ from collections.abc import Callable, Iterable, Iterator, Mapping
20
+ from dataclasses import dataclass, field
21
+ from functools import lru_cache
22
+ from pathlib import Path
23
+ from typing import Final, NamedTuple, TypeAlias
24
+
25
+ from constricter.empty import Empty
26
+ from constricter.fix.core.known import Classes, Guarded, Origin, Partial, Returns
27
+ from constricter.fix.index.modules import (
28
+ SUFFIX,
29
+ Index,
30
+ Module,
31
+ classes_under,
32
+ exported,
33
+ index,
34
+ indexed,
35
+ module_name,
36
+ read,
37
+ spliced,
38
+ takes,
39
+ written_under,
40
+ )
41
+ from constricter.fix.index.strings import quoted
42
+ from constricter.rules.annotations import roots
43
+
44
+ __all__ = [
45
+ "Imported",
46
+ "Index",
47
+ "Module",
48
+ "index",
49
+ "indexed",
50
+ "module_name",
51
+ "read",
52
+ "spliced",
53
+ ] # the index's, re-exported
54
+
55
+ BUILTINS: Final = frozenset(dir(builtins))
56
+ _STDLIB: Final = sys.stdlib_module_names
57
+ _BUILTINS_MODULE: Final = "builtins"
58
+ NO_TYPE: Final = "" # a class variable's type the file reading it can't write
59
+ HOPS: Final = 5 # how many re-exports (`from .util import f` in an `__init__`) to follow
60
+ _DOT: Final = "."
61
+ _UNDERSCORE: Final = "_"
62
+ _FUNCTION: Final = "function"
63
+ DECORATOR: Final = "decorator" # a function that gives back the one it decorates
64
+ CLASS: Final = "class"
65
+ ALIAS: Final = "alias" # a type alias (see `modules.Module.aliases`)
66
+ _TYPES: Final = frozenset({CLASS, ALIAS}) # the kinds a fix may write a name of that the file doesn't
67
+ _REEXPORTED: Final = "re-exported" # in the memo's key after a kind: what a package names of its submodules'
68
+ _TYPE_VAR: Final = "type variable"
69
+ _RETURNED: Final = "returned" # an unannotated function its `return`s type
70
+ UNANNOTATED: Final = "unannotated" # an unannotated function, typed or not
71
+ PARTIAL: Final = "partial" # a function whose declared return only an unpacking can use
72
+ OPEN: Final = "open" # a function with a parameter left unannotated (see `params.open_functions`)
73
+
74
+
75
+ class Imported(NamedTuple):
76
+ """What a file's imports from other checked files offer `--fix`, each keyed as the file spells it."""
77
+
78
+ calls: dict[str, str]
79
+ classes: Classes
80
+ returned: Returns = Returns()
81
+ guarded: Mapping[str, Guarded] = (
82
+ Empty()
83
+ ) # names their types need imported to type check (pickled: a `dict`)
84
+ generics: frozenset[str] = frozenset() # its generic classes, as the file spells them
85
+ # Its plain classes' variables typed by their values, by class (see `constricter.fix.values.classvars`).
86
+ members: Mapping[str, Mapping[str, str]] = Empty()
87
+ partial: Partial = Partial() # the returns only an unpacking can use
88
+
89
+
90
+ def _origin(module: Module, name: str) -> Origin | None:
91
+ if name in module.names:
92
+ return module.names[name]
93
+ return (_BUILTINS_MODULE, name) if name in BUILTINS else None
94
+
95
+
96
+ def definition(
97
+ modules: Mapping[str, Module],
98
+ origin: Origin,
99
+ kind: str,
100
+ hops: int = HOPS,
101
+ ) -> tuple[Module, str] | None:
102
+ """Follow `origin` (through re-exports) to the module that defines it as a `kind` (function, class).
103
+
104
+ As `_canonical` does: through what a module imports for type checking alone too, as a package's
105
+ `__init__` does what it imports only when it's asked for (pydantic's `TypeAdapter`).
106
+
107
+ Returns:
108
+ That module and the name, or `None`.
109
+
110
+ """
111
+ module: Module | None = modules.get(origin[0])
112
+ attribute: str | None = origin[1]
113
+ if module is None or attribute is None or not hops:
114
+ return None
115
+ if attribute in _kind(module, kind):
116
+ return module, attribute
117
+ onward: Origin | None = module.names.get(attribute) or module.guarded.get(attribute)
118
+ return definition(modules, onward, kind, hops - 1) if onward and onward[0] != module.name else None
119
+
120
+
121
+ def _kind(module: Module, kind: str) -> Iterable[str]:
122
+ """List what `module` defines of a `kind`: functions (declared or typed by their `return`s), classes...
123
+
124
+ Returns:
125
+ Their names.
126
+
127
+ """
128
+ found: _Defined | None = _KINDS.get(kind)
129
+ return module.type_vars if found is None else found(module)
130
+
131
+
132
+ _Defined: TypeAlias = Callable[[Module], Iterable[str]] # what a module defines of one kind
133
+ # What a module defines of each kind (see `_kind`): any other kind is its type variables.
134
+ _KINDS: Final[Mapping[str, _Defined]] = {
135
+ _FUNCTION: lambda module: module.returns,
136
+ CLASS: lambda module: module.classes,
137
+ ALIAS: lambda module: module.aliases,
138
+ _RETURNED: lambda module: module.returned.calls,
139
+ UNANNOTATED: lambda module: module.unannotated,
140
+ OPEN: lambda module: module.open,
141
+ PARTIAL: lambda module: module.partial,
142
+ DECORATOR: lambda module: module.passes,
143
+ "signatures": lambda module: module.overloads if module.declared is None else module.declared.signatures,
144
+ }
145
+
146
+
147
+ def type_vars(catalog: Index, path: Path) -> frozenset[str]:
148
+ """Find the names the file at `path` imports that are type variables where they're defined.
149
+
150
+ Followed through re-exports, as calls are; for the checker to leave alone the file's own
151
+ functions whose declared return mentions one (`def f(x: T) -> T`, with `from ._typing import T`).
152
+
153
+ Returns:
154
+ Them, as the file binds them; none for a file `catalog` doesn't have.
155
+
156
+ """
157
+ target: Module | None
158
+ if path.suffix != SUFFIX or (target := catalog.modules.get(module_name(path))) is None:
159
+ return frozenset()
160
+ return frozenset(
161
+ local for local in {*target.names, *target.guarded} if is_type_var(catalog.modules, target, local)
162
+ )
163
+
164
+
165
+ def is_type_var(modules: Mapping[str, Module], module: Module, name: str) -> bool:
166
+ """Check whether `name` is a type variable in `module`: its own, or one it imports from a checked file.
167
+
168
+ Returns:
169
+ Whether it is.
170
+
171
+ """
172
+ origin: Origin | None = module.names.get(name) or module.guarded.get(name)
173
+ return name in module.type_vars or (
174
+ origin is not None and origin[0] != module.name and definition(modules, origin, _TYPE_VAR) is not None
175
+ )
176
+
177
+
178
+ def calls(catalog: Index, path: Path, guarded: dict[str, Guarded] | None = None) -> dict[str, str]:
179
+ """Return, for the file at `path`, the return type of each function it imports whose type it can name.
180
+
181
+ Returns:
182
+ Each call's name as written (`helper`, `u.helper`, `pkg.util.helper`), mapped to its type; nothing
183
+ for a file `catalog` doesn't have (a notebook, standard input). Without `guarded` to record the
184
+ names its types need imported for type checking (see `Guarded`), only types needing none.
185
+
186
+ """
187
+ return {key: annotation for key, annotation, _ in typed_calls(catalog, path, _FUNCTION, guarded)}
188
+
189
+
190
+ def returned(catalog: Index, path: Path, guarded: dict[str, Guarded] | None = None) -> Returns:
191
+ """Return, for the file at `path`, what the unannotated functions it imports return, as `calls` does.
192
+
193
+ Only those of modules already checked (`Module.returned`), each with what it rests on if a guess.
194
+
195
+ Returns:
196
+ Each call's type, and a guessed one's origins; nothing for a file `catalog` doesn't have.
197
+
198
+ """
199
+ types: dict[str, str] = {}
200
+ guesses: dict[str, frozenset[str]] = {}
201
+ key: str
202
+ annotation: str
203
+ defined: tuple[Module, str]
204
+ for key, annotation, defined in typed_calls(catalog, path, _RETURNED, guarded):
205
+ types[key] = annotation
206
+ if defined[1] in defined[0].returned.guesses:
207
+ guesses[key] = defined[0].returned.guesses[defined[1]]
208
+ return Returns(types, guesses)
209
+
210
+
211
+ def typed_calls(
212
+ catalog: Index,
213
+ path: Path,
214
+ kind: str,
215
+ guarded: dict[str, Guarded] | None,
216
+ ) -> Iterator[tuple[str, str, tuple[Module, str]]]:
217
+ """Find the functions of a `kind` (declared or `returned`) the file at `path` imports, typed.
218
+
219
+ Yields:
220
+ Each call's name as written, its type, and the module and name that define it, for each whose
221
+ type the file can write (see `_portable_call`).
222
+
223
+ """
224
+ name: str = module_name(path)
225
+ modules: dict[str, Module] = catalog.modules
226
+ target: Module | None
227
+ if path.suffix != SUFFIX or (target := modules.get(name)) is None:
228
+ return
229
+ # Only what it calls, or (declaring its return) passes on (`partial(helper, 1)`): all that reads one.
230
+ wanted: frozenset[str] = target.called | target.passed if kind == _FUNCTION else target.called
231
+ key: str
232
+ origin: Origin
233
+ for key, origin in spellings(catalog, target, kind):
234
+ found: tuple[str, tuple[Module, str]] | None
235
+ if key in wanted and (found := _portable_call(modules, target, origin, kind, guarded)) is not None:
236
+ yield key, *found
237
+
238
+
239
+ def spellings(catalog: Index, target: Module, kind: str) -> Iterator[tuple[str, Origin]]:
240
+ """Find what `target` imports that other modules may define as a `kind`, as it spells each call.
241
+
242
+ Yields:
243
+ Each name as written (`helper`, `u.helper`, `pkg.util.helper`), and where it's from: a module's
244
+ function, or a declared one it imports in turn (`pd.array`), only where the file writes it.
245
+
246
+ """
247
+ local: str
248
+ origin: Origin
249
+ for local, origin in resolved(catalog, target.names):
250
+ if origin[1] is not None and origin[0] != target.name:
251
+ yield local, origin
252
+ elif origin[1] is None: # a module: `u.f()`, or `pkg.util.f()` after `import pkg.util`
253
+ if kind in _TYPES: # a fix may write one the file doesn't yet: every one
254
+ yield from classes_under(catalog, local, origin[0], kind)
255
+ yield from _named_under(catalog, local, origin[0], kind)
256
+ continue
257
+ key: str
258
+ sub: str
259
+ function: str
260
+ for key, sub, function in written_under(target, local): # of all it has, those the file calls
261
+ other: Module | None = catalog.modules.get(origin[0] + sub)
262
+ followed: bool = kind == _FUNCTION and other is not None and takes(other, function)
263
+ if other is not None and (followed or function in _kind(other, kind)):
264
+ yield key, (other.name, function)
265
+
266
+
267
+ def _named_under(catalog: Index, local: str, package: str, kind: str) -> Iterator[tuple[str, Origin]]:
268
+ """Spell the classes (or aliases) `package` imports from its submodules, as a file importing it may.
269
+
270
+ `local`: the name the file imports it as.
271
+
272
+ `pd.DataFrame`, by `from pandas.core.api import DataFrame` in `pandas/__init__.py`: the class
273
+ `pandas.core.frame` defines, which `classes_under` spells only by its own module.
274
+
275
+ Returns:
276
+ Each spelling, and the module that defines it and its name there.
277
+
278
+ """
279
+ key: tuple[str, str] = (package, f"{kind} {_REEXPORTED}")
280
+ found: list[tuple[str, Origin]] | None
281
+ if (found := None if catalog.memo is None else catalog.memo.under.get(key)) is None:
282
+ top: Module | None = catalog.modules.get(package)
283
+ named: Iterable[tuple[str, Origin]] = () if top is None else exported(top).items()
284
+ found = []
285
+ name: str
286
+ origin: Origin
287
+ for name, origin in named:
288
+ defined: tuple[Module, str] | None
289
+ if not origin[0].startswith(package + _DOT):
290
+ continue
291
+ if defined := definition(catalog.modules, origin, kind):
292
+ found.append((f"{_DOT}{name}", (defined[0].name, defined[1])))
293
+ if catalog.memo is not None:
294
+ catalog.memo.under[key] = found
295
+ return ((f"{local}{suffix}", origin) for suffix, origin in found)
296
+
297
+
298
+ def resolved(catalog: Index, names: Mapping[str, Origin]) -> Iterator[tuple[str, Origin]]:
299
+ """Take each name imported from a package that is its submodule (`from pkg import util`) as that module.
300
+
301
+ Unless the package itself defines the name (a function, class or type).
302
+
303
+ Yields:
304
+ Each name, and what it refers to.
305
+
306
+ """
307
+ local: str
308
+ origin: Origin
309
+ for local, origin in names.items():
310
+ package: Module | None
311
+ submodule: str = f"{origin[0]}.{origin[1]}" if origin[0] else origin[1] or ""
312
+ if (
313
+ origin[1] is not None
314
+ and submodule in catalog.modules
315
+ and not (
316
+ (package := catalog.modules.get(origin[0])) is not None
317
+ and any(origin[1] in _kind(package, kind) for kind in (_FUNCTION, UNANNOTATED, CLASS))
318
+ )
319
+ ):
320
+ yield local, (submodule, None)
321
+ else:
322
+ yield local, origin
323
+
324
+
325
+ def _portable_call(
326
+ modules: Mapping[str, Module],
327
+ target: Module,
328
+ origin: Origin,
329
+ kind: str,
330
+ guarded: dict[str, Guarded] | None,
331
+ ) -> tuple[str, tuple[Module, str]] | None:
332
+ """Type a call to `origin`, a function of a `kind`, if `target` can write its type (see `_respelled`).
333
+
334
+ Returns:
335
+ Its type, as `target` writes it, and the module and name that define it; or `None`.
336
+
337
+ """
338
+ defined: tuple[Module, str] | None
339
+ if (defined := definition(modules, origin, kind)) is None:
340
+ return None
341
+ declared: dict[str, Mapping[str, str]] = {_FUNCTION: defined[0].returns, PARTIAL: defined[0].partial}
342
+ annotation: str = declared.get(kind, defined[0].returned.calls)[defined[1]]
343
+ respelled: str | None = _respelled(modules, target, defined[0], annotation, guarded)
344
+ return None if respelled is None else (respelled, defined)
345
+
346
+
347
+ def _respelled(
348
+ modules: Mapping[str, Module],
349
+ target: Module,
350
+ defined: Module,
351
+ annotation: str,
352
+ guarded: dict[str, Guarded] | None,
353
+ ) -> str | None:
354
+ """Write a type from module `defined` in `target`.
355
+
356
+ As it is, if every name in it means the same in both; else, with `guarded` to record them in,
357
+ each other name as `target` imports what it refers to (under any name), or by an import to add
358
+ under `if TYPE_CHECKING:` (see `Guarded`), if nothing else in `target` has that name. Never one
359
+ naming a type variable (see `is_type_var`), an alias its module assigns twice (a variable, to a
360
+ type checker), a builtin `target` rebinds, what `defined` doesn't import at its top level (nor
361
+ define), a checked file's generic class without its arguments, or
362
+ in a string left inside it (an `Annotated`'s metadata) what means anything else in `target`.
363
+
364
+ Returns:
365
+ The type, or `None`.
366
+
367
+ """
368
+ names: frozenset[str] = roots(annotation)
369
+ if _refused(modules, target, defined, annotation):
370
+ return None
371
+ if all(_same(target, defined, root) for root in names):
372
+ return annotation
373
+ if guarded is None:
374
+ return None
375
+ renamed: dict[str, str] = {}
376
+ found: dict[str, Guarded] = {}
377
+ root: str
378
+ for root in {root for root in names if not _same(target, defined, root)}:
379
+ origin: Origin | None = _where(defined, root)
380
+ origin = None if origin is None else _public(modules, origin)
381
+ name: str | None
382
+ if (
383
+ origin is None
384
+ or origin[0] == _BUILTINS_MODULE
385
+ or (name := _named(modules, target, origin, root, {**guarded, **found})) is None
386
+ ):
387
+ return None
388
+ renamed[root] = name
389
+ needed: Guarded | None
390
+ if (needed := _import_for(target, origin, name, {**found, **guarded})) is not None:
391
+ found[name] = needed
392
+ guarded.update(found)
393
+ return _renamed(annotation, renamed)
394
+
395
+
396
+ def spelled_in(
397
+ catalog: Index,
398
+ target: Module,
399
+ defined: Module,
400
+ annotation: str,
401
+ guarded: dict[str, Guarded],
402
+ ) -> str | None:
403
+ """Write a type from module `defined` in `target` (see `_respelled`).
404
+
405
+ Returns:
406
+ The type, or `None`.
407
+
408
+ """
409
+ return _respelled(catalog.modules, target, defined, annotation, guarded)
410
+
411
+
412
+ def _refused(modules: Mapping[str, Module], target: Module, defined: Module, annotation: str) -> bool:
413
+ """Check whether a type from module `defined` is one never written in `target` (see `_respelled`).
414
+
415
+ Returns:
416
+ Whether it is.
417
+
418
+ """
419
+ return (
420
+ any(
421
+ is_type_var(modules, defined, root) or _is_rebound(modules, defined, root)
422
+ for root in roots(annotation)
423
+ )
424
+ or _bare(modules, defined, annotation)
425
+ # A name in a string left inside it stays as it is: no import is added for it, and it isn't renamed.
426
+ or any(not _same(target, defined, root) for root in quoted(annotation))
427
+ )
428
+
429
+
430
+ def _import_for(target: Module, origin: Origin, name: str, known: Mapping[str, Guarded]) -> Guarded | None:
431
+ """Find the import for type checking a type's `name`, as `_named` spells `origin`, takes in `target`.
432
+
433
+ `known`: those other types' names take already.
434
+
435
+ Returns:
436
+ It: the file's own (no statement), or one to add; `None` for a name an import it runs binds,
437
+ or one written through a module it imports (`m.Row`).
438
+
439
+ """
440
+ if name in target.guarded:
441
+ return Guarded(target.guarded[name], None)
442
+ if _DOT in name or name in target.names:
443
+ return None
444
+ return known.get(name) or Guarded(origin, _statement(origin, name))
445
+
446
+
447
+ def _is_rebound(modules: Mapping[str, Module], defined: Module, name: str) -> bool:
448
+ """Check whether `name`, in module `defined`, is one its own module assigns twice (see `Module.rebound`).
449
+
450
+ Returns:
451
+ Whether it is: `defined`'s own, or what it imports from an indexed module.
452
+
453
+ """
454
+ origin: Origin | None = _where(defined, name)
455
+ found: Origin | None = None if origin is None else _canonical(modules, origin)
456
+ return name in defined.rebound or (
457
+ found is not None and found[0] in modules and found[1] in modules[found[0]].rebound
458
+ )
459
+
460
+
461
+ def _bare(modules: Mapping[str, Module], defined: Module, annotation: str) -> bool:
462
+ """Check whether a type from module `defined` names a checked file's generic class unsubscripted.
463
+
464
+ Returns:
465
+ Whether it does: the class's arguments are missing.
466
+
467
+ """
468
+ return any(is_generic(modules, _where(defined, bare)) for bare in _unsubscripted(annotation))
469
+
470
+
471
+ def is_generic(modules: Mapping[str, Module], origin: Origin | None) -> bool:
472
+ """Check whether `origin` is (through re-exports) an indexed module's generic class.
473
+
474
+ Returns:
475
+ Whether it is.
476
+
477
+ """
478
+ found: Origin | None = None if origin is None else _canonical(modules, origin)
479
+ return found is not None and found[0] in modules and found[1] in modules[found[0]].generics
480
+
481
+
482
+ @lru_cache(maxsize=4096)
483
+ def _unsubscripted(annotation: str) -> tuple[str, ...]:
484
+ """Name the names an annotation writes without type arguments (`Box` in `list[Box]`, not `list`).
485
+
486
+ Returns:
487
+ Them, in order.
488
+
489
+ """
490
+ tree: ast.expr = ast.parse(annotation, mode="eval").body
491
+ subscripted: set[int] = {id(node.value) for node in ast.walk(tree) if isinstance(node, ast.Subscript)}
492
+ return tuple(
493
+ node.id for node in ast.walk(tree) if isinstance(node, ast.Name) and id(node) not in subscripted
494
+ )
495
+
496
+
497
+ def _where(module: Module, name: str) -> Origin | None:
498
+ """Find what `name` refers to in `module`: at run time, for type checking alone, or a builtin.
499
+
500
+ Returns:
501
+ Its origin, or `None`.
502
+
503
+ """
504
+ if name in module.names:
505
+ return module.names[name]
506
+ return module.guarded.get(name) or module.returned.names.get(name) or _origin(module, name)
507
+
508
+
509
+ @dataclass
510
+ class _Memo:
511
+ """What `_canonical` and `_public` found, for the index they were last asked about.
512
+
513
+ Each file's `imported` asks them about the same origins again (every member of every class it
514
+ imports): with installed packages indexed too, that was most of a run's time.
515
+ """
516
+
517
+ modules: Mapping[str, Module] | None = None
518
+ canonical: dict[Origin, Origin] = field(default_factory=dict[Origin, Origin])
519
+ public: dict[Origin, Origin] = field(default_factory=dict[Origin, Origin])
520
+
521
+ def of(self, modules: Mapping[str, Module]) -> "_Memo":
522
+ """Keep what's found for `modules` alone (an index changes as files are checked).
523
+
524
+ Returns:
525
+ This memo, emptied if it was another index's.
526
+
527
+ """
528
+ if modules is not self.modules:
529
+ self.modules = modules
530
+ self.canonical = {}
531
+ self.public = {}
532
+ return self
533
+
534
+
535
+ _MEMO: Final = _Memo()
536
+
537
+
538
+ def canonical_origin(modules: Mapping[str, Module], origin: Origin) -> Origin:
539
+ """Follow `origin` through indexed modules' re-exports to where it's defined (see `_canonical`).
540
+
541
+ Returns:
542
+ That origin.
543
+
544
+ """
545
+ return _canonical(modules, origin)
546
+
547
+
548
+ def public_origin(modules: Mapping[str, Module], origin: Origin) -> Origin:
549
+ """Find where an installed package's public module re-exports `origin` (see `_public`).
550
+
551
+ Returns:
552
+ That origin, or `origin` itself.
553
+
554
+ """
555
+ return _public(modules, origin)
556
+
557
+
558
+ def _canonical(modules: Mapping[str, Module], origin: Origin) -> Origin:
559
+ """Follow `origin` through checked modules' re-exports to where it's defined.
560
+
561
+ Returns:
562
+ That origin, or `origin` itself when it isn't a re-export of a checked module.
563
+
564
+ """
565
+ found: dict[Origin, Origin] = _MEMO.of(modules).canonical
566
+ if origin not in found:
567
+ found[origin] = _followed(modules, origin, HOPS)
568
+ return found[origin]
569
+
570
+
571
+ def _followed(modules: Mapping[str, Module], origin: Origin, hops: int) -> Origin:
572
+ """Follow `origin` through up to `hops` re-exports (see `_canonical`).
573
+
574
+ Returns:
575
+ Where it leads.
576
+
577
+ """
578
+ module: Module | None = modules.get(origin[0])
579
+ if module is None or origin[1] is None or not hops:
580
+ return origin
581
+ onward: Origin | None = (
582
+ module.names.get(origin[1]) or module.guarded.get(origin[1]) or _starred(modules, module, origin[1])
583
+ )
584
+ return _followed(modules, onward, hops - 1) if onward and onward[0] != module.name else origin
585
+
586
+
587
+ def _starred(modules: Mapping[str, Module], module: Module, name: str) -> Origin | None:
588
+ """Find which module `module` takes `name` from by importing everything from it (`from .main import *`).
589
+
590
+ The last that binds it, as the imports run; a name with a leading underscore no such import takes.
591
+
592
+ Returns:
593
+ The name there, or `None`.
594
+
595
+ """
596
+ star: str
597
+ for star in reversed(module.stars):
598
+ other: Module | None = modules.get(star)
599
+ if other is not None and not name.startswith("_") and name in exported(other):
600
+ return star, name
601
+ return None
602
+
603
+
604
+ def _named(
605
+ modules: Mapping[str, Module],
606
+ target: Module,
607
+ origin: Origin,
608
+ name: str,
609
+ guarded: Mapping[str, Guarded],
610
+ ) -> str | None:
611
+ """Name `origin` in `target`: as an import it has names it (preferring `name`), else `name` if free.
612
+
613
+ An import of the thing itself, else of a module that has it (`core_schema.CoreSchema`, as a type
614
+ checker's hint is written; `pytest.MonkeyPatch`, by the public module `origin` names). A new
615
+ import only from a module certain to resolve: a checked file's, an installed package's public
616
+ one (not `numpy._typing`), or the standard library's (a third-party one the type's file imports
617
+ may not be installed where the type checker runs).
618
+
619
+ Returns:
620
+ The name, or `None` if `target` imports nothing for it and binds `name` to something else, or
621
+ what it's from may not resolve.
622
+
623
+ """
624
+ wanted: Origin = _canonical(modules, origin)
625
+ known: dict[str, Origin] = {**target.guarded, **{n: g.origin for n, g in guarded.items()}, **target.names}
626
+ matches: list[str] = sorted(
627
+ (local for local, where in known.items() if _canonical(modules, where) == wanted),
628
+ key=lambda local: local != name,
629
+ )
630
+ if matches:
631
+ return matches[0]
632
+ module: Module | None = modules.get(origin[0])
633
+ # The module `origin` names has it when it runs, not for type checking alone.
634
+ runs: bool = module is not None and origin[1] in module.names
635
+ through: str | None = _through(modules, target, wanted) or (
636
+ _through(modules, target, origin) if runs else None
637
+ )
638
+ if through is not None:
639
+ return through
640
+ public: bool = module is not None and not (module.installed and _private(origin[0]))
641
+ resolves: bool = public or origin[0].partition(".")[0] in _STDLIB
642
+ return None if name in known or name in BUILTINS or not resolves else name
643
+
644
+
645
+ def _through(modules: Mapping[str, Module], target: Module, wanted: Origin) -> str | None:
646
+ """Name `wanted` through the module defining it, if `target` imports that module to run.
647
+
648
+ `m.Row`, after `import pkg.m as m` or `from pkg import m`. Not a module's own name, which
649
+ importing its package needn't bind; nor through a name `target` binds as a value somewhere (a
650
+ local `m`), which isn't the module there.
651
+
652
+ Returns:
653
+ The dotted name, or `None` if `target` imports no such module.
654
+
655
+ """
656
+ if wanted[1] is None or f"{wanted[0]}.{wanted[1]}" in modules:
657
+ return None
658
+ local: str
659
+ where: Origin
660
+ for local, where in target.names.items():
661
+ if local not in target.shadowed and wanted[0] == (
662
+ where[0] if where[1] is None else f"{where[0]}.{where[1]}"
663
+ ):
664
+ return f"{local}.{wanted[1]}"
665
+ return None
666
+
667
+
668
+ def _public(modules: Mapping[str, Module], origin: Origin) -> Origin:
669
+ """Find where an installed package's public module re-exports what `origin` names from a private one.
670
+
671
+ `numpy._core.multiarray`'s `ndarray` as `numpy`'s: its shortest public module binding the name to
672
+ the same thing.
673
+
674
+ Returns:
675
+ That origin; `origin` itself if it isn't in an installed package's private module, or no
676
+ public one re-exports it.
677
+
678
+ """
679
+ module: Module | None = modules.get(origin[0])
680
+ if module is None or not module.installed or origin[1] is None or not _private(origin[0]):
681
+ return origin
682
+ found: dict[Origin, Origin] = _MEMO.of(modules).public
683
+ if origin not in found:
684
+ found[origin] = _reexported(modules, origin)
685
+ return found[origin]
686
+
687
+
688
+ def _reexported(modules: Mapping[str, Module], origin: Origin) -> Origin:
689
+ """Find the shortest public module of an installed package re-exporting `origin` (see `_public`).
690
+
691
+ The package's own, or the one its private twin is for (`pytest`, for `_pytest`).
692
+
693
+ Returns:
694
+ Its origin, or `origin` itself if there's none.
695
+
696
+ """
697
+ wanted: Origin = _canonical(modules, origin)
698
+ top: str = origin[0].partition(_DOT)[0].lstrip(_UNDERSCORE)
699
+ found: list[str] = sorted(
700
+ (
701
+ name
702
+ for name, other in modules.items()
703
+ if other.installed
704
+ and name.partition(_DOT)[0].lstrip(_UNDERSCORE) == top
705
+ and not _private(name)
706
+ and origin[1] in other.names
707
+ and (
708
+ other.declared is None
709
+ or other.declared.exports is None
710
+ or origin[1] in other.declared.exports
711
+ )
712
+ and _canonical(modules, other.names[origin[1]]) == wanted
713
+ ),
714
+ key=lambda name: (name.count("."), name),
715
+ )
716
+ return (found[0], origin[1]) if found else origin
717
+
718
+
719
+ def _private(module: str) -> bool:
720
+ """Check whether a module's path has a private part (`numpy._typing`).
721
+
722
+ Returns:
723
+ Whether it has.
724
+
725
+ """
726
+ return any(part.startswith("_") for part in module.split("."))
727
+
728
+
729
+ def _statement(origin: Origin, name: str) -> str:
730
+ """Write the import that binds `name` to `origin`.
731
+
732
+ Returns:
733
+ It: `from m import T`, `from m import T as U`, `import m`, `import m as n`.
734
+
735
+ """
736
+ module: str
737
+ attribute: str | None
738
+ module, attribute = origin
739
+ if attribute is None:
740
+ return f"import {module}" + ("" if module == name else f" as {name}")
741
+ return f"from {module} import {attribute}" + ("" if attribute == name else f" as {name}")
742
+
743
+
744
+ def _renamed(annotation: str, names: Mapping[str, str]) -> str:
745
+ """Rename names in an annotation.
746
+
747
+ Returns:
748
+ The annotation.
749
+
750
+ """
751
+ tree: ast.expr = ast.parse(annotation, mode="eval").body
752
+ node: ast.AST
753
+ for node in ast.walk(tree): # a fresh tree: renamed in place
754
+ if isinstance(node, ast.Name):
755
+ node.id = names.get(node.id, node.id)
756
+ return ast.unparse(tree)
757
+
758
+
759
+ def _same(target: Module, defined: Module, name: str) -> bool:
760
+ """Compare what `name` refers to in both modules.
761
+
762
+ Returns:
763
+ Whether it's something, and the same thing.
764
+
765
+ """
766
+ origin: Origin | None = _origin(target, name)
767
+ return origin is not None and origin == _origin(defined, name)
768
+
769
+
770
+ def portable(
771
+ modules: Mapping[str, Module],
772
+ where: tuple[Module, tuple[Module, str]],
773
+ key: str,
774
+ types: Mapping[str, str],
775
+ guarded: dict[str, Guarded],
776
+ ) -> dict[str, str]:
777
+ """Keep the types a class's members have that the file (`where[0]`) can write (see `_respelled`).
778
+
779
+ One that is the class itself (`where[1]`) is written as the file spells it (`key`).
780
+
781
+ Returns:
782
+ Each kept member's type.
783
+
784
+ """
785
+ target: Module
786
+ defined: tuple[Module, str]
787
+ target, defined = where
788
+ kept: dict[str, str] = {}
789
+ member: str
790
+ annotation: str
791
+ for member, annotation in types.items():
792
+ respelled: str | None
793
+ if annotation == defined[1]:
794
+ kept[member] = key
795
+ elif (respelled := _respelled(modules, target, defined[0], annotation, guarded)) is not None:
796
+ kept[member] = respelled
797
+ return kept