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,335 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """What a loop binds each time round, and the targets of comprehensions, typed as `--fix` types values.
3
+
4
+ `looped` types a `for` loop's target by what its iterable yields; `looped_parts` each part of an
5
+ `enumerate` or `zip` loop's tuples on its own; `comprehended` the targets of a value's
6
+ comprehensions, as a loop's are. `infer` types a value as `constricter.fix.values.inference` does,
7
+ over the names a scope declared.
8
+ """
9
+
10
+ import ast
11
+ import re
12
+ from collections.abc import Callable, Mapping, Sequence
13
+ from typing import Final, TypeAlias, cast
14
+
15
+ from constricter.fix.core.known import Inference, Known, kinds_of
16
+ from constricter.fix.libraries import overloads, walks
17
+ from constricter.fix.values import called, shapes
18
+ from constricter.fix.values.members import present
19
+ from constricter.fix.values.targets import (
20
+ DICT_VIEWS,
21
+ ENUMERATE,
22
+ FILTER,
23
+ ITER,
24
+ MAP,
25
+ RANGE,
26
+ SAME_ELEMENTS,
27
+ ZIP,
28
+ counted,
29
+ dict_parts,
30
+ element_type,
31
+ iterated,
32
+ iterator_call,
33
+ unpacked,
34
+ )
35
+ from constricter.offences import CONSTRUCTOR
36
+ from constricter.rules.flow import members
37
+
38
+ _GENERIC: Final = "[" # in a type's text: its arguments
39
+ COMPREHENSIONS: Final = (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)
40
+ _UNION: Final = re.compile(r"\||\b(?:None|Optional|Union)\b") # an annotation with a union in it
41
+ _NONE: Final = "None"
42
+ # A predicate's return that narrows what it's given
43
+ _NARROWING: Final = re.compile(r"\b(?:TypeGuard|TypeIs)\b")
44
+ _CALLABLE: Final = "callable" # the builtin predicate that narrows (`TypeIs[Callable[..., object]]`)
45
+ _PREDICATE: Final = 2 # `filter`'s arguments: the predicate and the iterable
46
+ _Comprehension: TypeAlias = ast.ListComp | ast.SetComp | ast.DictComp | ast.GeneratorExp
47
+ # Types a value over what a scope declared, as `constricter.fix.values.inference.inference` does.
48
+ Infer: TypeAlias = Callable[[ast.expr, Known, Mapping[str, str]], Inference | None]
49
+
50
+
51
+ # One part of a loop target (see `looped_parts`): its inference, and the values it came from.
52
+ LoopPart: TypeAlias = tuple[Inference | None, list[ast.expr]]
53
+
54
+
55
+ def rebased(receiver: str, name: str, known: Known) -> str:
56
+ """Read a class of the module's as the generic library class it takes `name` from, given its arguments.
57
+
58
+ `Rows`, under `collections.deque[Row]`, as that: what its instance's subscript, loop or view gives.
59
+
60
+ Returns:
61
+ The base as the class writes it; `receiver` itself for any other.
62
+
63
+ """
64
+ base: str | None = known.class_side.lineage.definer(receiver, name)
65
+ return base if base is not None and _GENERIC in base and _GENERIC not in receiver else receiver
66
+
67
+
68
+ def comprehended(
69
+ value: ast.expr,
70
+ known: Known,
71
+ declared: Mapping[str, str],
72
+ infer: Infer,
73
+ ) -> Mapping[str, str]:
74
+ """Type the targets of every comprehension in `value`, as a loop's are, over what's `declared`.
75
+
76
+ A target whose type isn't known is dropped (it shadows any outer name of the same name).
77
+
78
+ Returns:
79
+ `declared`, with the targets' types: `declared` itself, uncopied, where there's none (most values).
80
+
81
+ """
82
+ return targets_typed(
83
+ [node for node in ast.walk(value) if isinstance(node, COMPREHENSIONS)],
84
+ known,
85
+ declared,
86
+ infer,
87
+ )
88
+
89
+
90
+ def targets_typed(
91
+ comprehensions: Sequence[ast.AST],
92
+ known: Known,
93
+ declared: Mapping[str, str],
94
+ infer: Infer,
95
+ ) -> Mapping[str, str]:
96
+ """Type the targets of `comprehensions`, as a loop's are, over what's `declared` (see `comprehended`).
97
+
98
+ Returns:
99
+ `declared`, with the targets' types: `declared` itself, uncopied, where there are none.
100
+
101
+ """
102
+ if not comprehensions:
103
+ return declared
104
+ inside: dict[str, str] = dict(declared)
105
+ node: ast.AST
106
+ generator: ast.comprehension
107
+ for node in comprehensions:
108
+ for generator in cast("_Comprehension", node).generators:
109
+ found: Inference | None = looped(generator.iter, known, inside, infer)
110
+ name: ast.Name
111
+ part: str | None
112
+ annotation: str | None = None if found is None else found.annotation
113
+ for name, part in unpacked(generator.target, annotation, known.indirect.tuples):
114
+ if part is None:
115
+ _ = inside.pop(name.id, None)
116
+ else:
117
+ inside[name.id] = part
118
+ return inside
119
+
120
+
121
+ def looped(iterable: ast.expr, known: Known, declared: Mapping[str, str], infer: Infer) -> Inference | None:
122
+ """Infer what a `for` loop over `iterable` binds each time round, and say how.
123
+
124
+ `range()` gives `int`s; `enumerate(x)` `tuple[int, T]` and `zip(x, y)` `tuple[T, U]`, given
125
+ `x`'s and `y`'s; `reversed(x)`, `sorted(x)` and `iter(x)` what `x` does; `map(f, x)` what `f`
126
+ returns; a generator expression its element; a `dict`'s `.keys()`, `.values()` and `.items()`
127
+ its keys, values and pairs; and anything else
128
+ whose type is inferred, its elements: a `list`, `set`, `frozenset` or `tuple[T, ...]`'s `T`, a
129
+ tuple's parts where they agree, a `dict`'s keys, a `str`'s `str`s and a `bytes`'s `int`s, and a
130
+ standard-library class's by its `__iter__` (a file's lines). `os.walk(top)` gives a directory's
131
+ path, with its directories' names and its files', as `top` is typed (see `walks.walked`).
132
+
133
+ Returns:
134
+ The element's annotation as source text and its reason, or `None` if it isn't known.
135
+
136
+ """
137
+ call: tuple[str, list[ast.expr]] | None = iterator_call(iterable)
138
+ if call is not None and known.is_builtin(call[0]):
139
+ return _iterator(*call, known, declared, infer)
140
+ view: str
141
+ receiver: ast.expr
142
+ match iterable:
143
+ case ast.Call(func=ast.Attribute(value=receiver, attr=view), args=[]) if view in DICT_VIEWS:
144
+ return dict_view(receiver, view, known, declared, infer)
145
+ case ast.GeneratorExp():
146
+ return _generated(iterable, known, declared, infer)
147
+ case _:
148
+ found: Inference | None
149
+ if (found := infer(iterable, known, declared)) is None:
150
+ return walks.walked(iterable, known, lambda top: infer(top, known, declared))
151
+ kinds: frozenset[str] = kinds_of(found, kind="loop")
152
+ # Of an `X | None`, the `X`'s: `None` has none. A builtin container's or an
153
+ # `Iterable[T]`'s, else a standard-library class's own.
154
+ held: str = rebased(present(found.annotation, ""), "__iter__", known)
155
+ element: Inference | None = overloads.library_element(held, known)
156
+ return element_type(held, f"the elements of {found.reason}", kinds) or (
157
+ None if element is None else element._replace(kinds=kinds | element.kinds)
158
+ )
159
+
160
+
161
+ def _iterator(
162
+ name: str,
163
+ args: list[ast.expr],
164
+ known: Known,
165
+ declared: Mapping[str, str],
166
+ infer: Infer,
167
+ ) -> Inference | None:
168
+ """Infer what one of `ITERATORS`, called with `args`, yields.
169
+
170
+ Returns:
171
+ Its elements' annotation and reason, or `None` if an argument's elements aren't known.
172
+
173
+ """
174
+ if name == RANGE:
175
+ return Inference("int", "`range`, which yields `int`s", frozenset({"loop"}))
176
+ if name == MAP:
177
+ return _mapped(args[0], known)
178
+ if name == FILTER:
179
+ return _filtered(args, known, declared, infer)
180
+ if name in SAME_ELEMENTS or name == ITER:
181
+ return looped(args[0], known, declared, infer) if name != ITER or len(args) == 1 else None
182
+ parts: list[Inference | None] = [looped(arg, known, declared, infer) for arg in counted(name, args)]
183
+ found: list[Inference] = [part for part in parts if part is not None]
184
+ if len(found) != len(parts):
185
+ return None
186
+ annotations: list[str] = ["int"] * (name == ENUMERATE) + [part.annotation for part in found]
187
+ return Inference(f"tuple[{', '.join(annotations)}]", f"`{name}`'s tuples", kinds_of(*found, kind="loop"))
188
+
189
+
190
+ def _filtered(
191
+ args: list[ast.expr],
192
+ known: Known,
193
+ declared: Mapping[str, str],
194
+ infer: Infer,
195
+ ) -> Inference | None:
196
+ """Infer what `filter(predicate, xs)` yields: `xs`'s elements, less `None` for a `None` predicate.
197
+
198
+ Returns:
199
+ The inference, or `None`: where `xs`'s elements aren't known, or the predicate may narrow them
200
+ (see `_keeps`).
201
+
202
+ """
203
+ if len(args) != _PREDICATE:
204
+ return None
205
+ found: Inference | None = looped(args[1], known, declared, infer)
206
+ if found is None or not (shapes.is_none(args[0]) or _keeps(args[0], known)):
207
+ return None
208
+ annotation: str = found.annotation
209
+ if shapes.is_none(args[0]):
210
+ types: frozenset[str] | None = members(annotation)
211
+ rest: list[str]
212
+ if not (rest := sorted((types or frozenset()) - {_NONE})):
213
+ return None
214
+ annotation = annotation if types == frozenset(rest) else " | ".join(rest)
215
+ return Inference(annotation, f"`filter`, which keeps {found.reason}", kinds_of(found, kind="loop"))
216
+
217
+
218
+ def _keeps(predicate: ast.expr, known: Known) -> bool:
219
+ """Check whether `filter`'s `predicate` keeps elements as they are, narrowing none.
220
+
221
+ A `lambda`; a builtin (but `callable`), or a method of a builtin class (`str.isdigit`); or a
222
+ function the module or another checked file defines, declaring a return other than a
223
+ `TypeGuard` or `TypeIs`, or typed by its `return`s.
224
+
225
+ Returns:
226
+ Whether it does; not for any other callable, which may be declared to narrow.
227
+
228
+ """
229
+ name: str
230
+ match predicate:
231
+ case ast.Lambda():
232
+ return True
233
+ case ast.Name(id=name) if known.is_builtin(name):
234
+ return name != _CALLABLE
235
+ case ast.Attribute(value=ast.Name(id=name)) if known.is_builtin(name):
236
+ return True
237
+ case ast.Name() | ast.Attribute():
238
+ callee: str = ast.unparse(predicate)
239
+ declared: str | None = known.calls.get(callee)
240
+ return (
241
+ declared is not None and not _NARROWING.search(declared)
242
+ ) or callee in known.returned.calls
243
+ case _:
244
+ return False
245
+
246
+
247
+ def _generated(
248
+ value: ast.GeneratorExp,
249
+ known: Known,
250
+ declared: Mapping[str, str],
251
+ infer: Infer,
252
+ ) -> Inference | None:
253
+ """Infer what a generator expression yields: its element, its targets typed as a loop's are.
254
+
255
+ Not one with a condition whose element's type has a union in it, which the condition may narrow.
256
+
257
+ Returns:
258
+ The inference, or `None`.
259
+
260
+ """
261
+ found: Inference | None = infer(value.elt, known, comprehended(value, known, declared, infer))
262
+ found = found and shapes.sifted(value, value.elt, found)
263
+ filtered: bool = any(generator.ifs for generator in value.generators)
264
+ if found is None or (filtered and _UNION.search(found.annotation)):
265
+ return None
266
+ loops: list[Inference | None] = [looped(g.iter, known, declared, infer) for g in value.generators]
267
+ return Inference(
268
+ found.annotation,
269
+ "a generator expression's elements",
270
+ kinds_of(found, *loops, kind="comprehension"),
271
+ )
272
+
273
+
274
+ def _mapped(function: ast.expr, known: Known) -> Inference | None:
275
+ """Infer what `map(function, ...)` yields: what `function` returns, whatever it's given.
276
+
277
+ Returns:
278
+ A fixed-return builtin's type (`map(int, parts)`), or a function's declared return; `None`
279
+ for any other callable.
280
+
281
+ """
282
+ call: ast.Call = ast.copy_location(ast.Call(function, [], []), function)
283
+ found: Inference | None = called.typed_call(call, known)
284
+ if found is None or CONSTRUCTOR in found.kinds:
285
+ return None
286
+ return Inference(found.annotation, f"`map`, which yields {found.reason}", kinds_of(found, kind="loop"))
287
+
288
+
289
+ def looped_parts(
290
+ iterable: ast.expr,
291
+ known: Known,
292
+ declared: Mapping[str, str],
293
+ infer: Infer,
294
+ ) -> list[LoopPart] | None:
295
+ """Infer each part of the tuples `enumerate` or `zip` yields on its own, even if the others aren't known.
296
+
297
+ `enumerate`'s index is always an `int`, whatever it counts.
298
+
299
+ Returns:
300
+ Each part's inference (`None` if it isn't known), with the values it came from (to judge
301
+ whether it's a guess, see `iterated`); or `None` if `iterable` isn't such a call.
302
+
303
+ """
304
+ call: tuple[str, list[ast.expr]] | None = iterator_call(iterable)
305
+ if call is None or call[0] not in {ENUMERATE, ZIP} or not known.is_builtin(call[0]):
306
+ return None
307
+ index: list[LoopPart] = (
308
+ [(Inference("int", "`enumerate`'s index", frozenset({"loop"})), [])] if call[0] == ENUMERATE else []
309
+ )
310
+ return index + [(looped(arg, known, declared, infer), iterated(arg)) for arg in counted(*call)]
311
+
312
+
313
+ def dict_view(
314
+ receiver: ast.expr,
315
+ view: str,
316
+ known: Known,
317
+ declared: Mapping[str, str],
318
+ infer: Infer,
319
+ ) -> Inference | None:
320
+ """Infer the elements of a `dict`'s `.keys()`, `.values()` or `.items()`.
321
+
322
+ Returns:
323
+ Them, or `None` if the receiver isn't a `dict` whose type is known.
324
+
325
+ """
326
+ found: Inference | None = infer(receiver, known, declared)
327
+ types: tuple[str, str] | None
328
+ if (types := None if found is None else dict_parts(rebased(found.annotation, view, known))) is None:
329
+ return None
330
+ by_view: dict[str, str] = {
331
+ "keys": types[0],
332
+ "values": types[1],
333
+ "items": f"tuple[{types[0]}, {types[1]}]",
334
+ }
335
+ return Inference(by_view[view], f"a `dict`'s `.{view}()`", kinds_of(found, kind="loop"))