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,802 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """One scope being checked: what it binds and reports, what `--fix` knows of it, and its late fixes."""
3
+
4
+ import ast
5
+ import re
6
+ from collections.abc import Iterable, Mapping
7
+ from dataclasses import replace
8
+ from typing import Final
9
+
10
+ from constricter.fix.core.imports import guarded_imports, imports_of, through, unwritable
11
+ from constricter.fix.core.known import Hints, ImportPlan, Inference, Typed
12
+ from constricter.fix.libraries import stdlib
13
+ from constricter.fix.libraries.library import chains
14
+ from constricter.fix.values import aliased, callables, fills, hinted
15
+ from constricter.fix.values.doubts import (
16
+ Facts,
17
+ Owner,
18
+ as_declared,
19
+ bare,
20
+ corrected,
21
+ doubts,
22
+ is_constant,
23
+ narrowed_first,
24
+ narrowing,
25
+ says_self,
26
+ tested,
27
+ undeclared,
28
+ )
29
+ from constricter.fix.values.guesses import guessed, guessing
30
+ from constricter.fix.values.inference import inference, inferred
31
+ from constricter.fix.values.members import fits, parsed
32
+ from constricter.fix.values.narrowed import narrowed_at, read_narrowed
33
+ from constricter.offences import (
34
+ LONG_TUPLE,
35
+ MEMBER,
36
+ NESTED_TYPE,
37
+ UNANNOTATED,
38
+ UNANNOTATED_MEMBER,
39
+ UNTYPED_PARAMETERS,
40
+ VAGUE_TYPE,
41
+ WIDEN_KINDS,
42
+ Checks,
43
+ Edit,
44
+ Fix,
45
+ FixPolicy,
46
+ Offence,
47
+ at,
48
+ )
49
+ from constricter.rules import widened
50
+ from constricter.rules.annotations import (
51
+ depth,
52
+ is_composite,
53
+ length,
54
+ node_name,
55
+ roots,
56
+ vague_fits,
57
+ )
58
+ from constricter.rules.flow import Finding, Lifetime, findings, members
59
+ from constricter.rules.quoted import newer, quote
60
+ from constricter.rules.scoped import Assignments, Inferred, Kind, Late, Settings
61
+ from constricter.rules.syntax import FunctionDef
62
+
63
+ _SELF: Final = "self"
64
+ _NONE: Final = "None"
65
+ _CLASSMETHOD: Final = "classmethod"
66
+ _STATICMETHOD: Final = "staticmethod"
67
+ _SELF_WORD: Final = re.compile(r"\bSelf\b") # `Self` in an annotation, `typing.Self` too
68
+ FINAL_KIND: Final = "final" # the fix kind of LVA012's `Final`
69
+ _TYPING_FINAL: Final = "typing.Final"
70
+ _LITERAL_DOUBT: Final = frozenset({"literal"}) # a constant's literal, which `doubts` makes a guess
71
+ _READS: Final = (ast.Name, ast.Attribute, ast.Subscript) # a read of a value a type checker may narrow
72
+
73
+
74
+ class Scope:
75
+ """One function body: names bound so far and offences found."""
76
+
77
+ def __init__(
78
+ self,
79
+ declared: set[str],
80
+ nested: list[FunctionDef],
81
+ settings: Settings,
82
+ kind: "Kind | None" = None,
83
+ ) -> None:
84
+ """Start a scope where `declared` are bound already, collecting nested functions into `nested`."""
85
+ self.declared: set[str] = declared
86
+ self.nested: list[FunctionDef] = nested
87
+ self.settings: Settings = settings
88
+ self.kind: Kind = kind or Kind(UNANNOTATED, fixable=True)
89
+ self.offences: list[Offence] = []
90
+ self.first: list[str] = [] # each first binding the rules cover, typed or not
91
+ # What `--fix` knows of the names bound so far.
92
+ self.inferred: Inferred = Inferred(unions=settings.known.indirect.unions)
93
+ self.flow: dict[str, Lifetime] = {} # every binding of each name, for value flow
94
+ self.assignments: Assignments = Assignments() # for LVA012
95
+
96
+ def lifetime(self, name: str) -> Lifetime:
97
+ """Find `name`'s value-flow record, starting one if it has none.
98
+
99
+ Returns:
100
+ It.
101
+
102
+ """
103
+ return self.flow.setdefault(name, Lifetime())
104
+
105
+ def bind(
106
+ self,
107
+ name: str,
108
+ where: tuple[int, int],
109
+ code: str | None,
110
+ fix: Fix | None = None,
111
+ typed: str | Late | None = None,
112
+ ) -> None:
113
+ """Bind `name` to a value value flow can't see; unless it's already bound, report `code`.
114
+
115
+ `code` is reported at `(line, col)` (`None` means typed), offering `fix` if there's one.
116
+ `typed`: the value's type, if `--fix` knows it (a loop's element), which a fix for the name's
117
+ first binding is held to (see `rebinding`): its text where it's certain, which a name
118
+ declared already is then bound to; else the guess and what it rests on, as `assign` binds one.
119
+ """
120
+ certain: str | None = typed if isinstance(typed, str) else None
121
+ guess: Late | None = (typed, frozenset()) if isinstance(typed, str) else typed
122
+ self.lifetime(name).bind(where, None, guess)
123
+ if name in self.declared:
124
+ self.inferred.rebound(name, certain)
125
+ self._first(name, where, code, fix)
126
+
127
+ def assign(
128
+ self,
129
+ target: ast.Name,
130
+ code: str | None,
131
+ value: ast.expr,
132
+ chained: ast.stmt | None = None,
133
+ ) -> None:
134
+ """Bind `target` to `value` (`name = value`), offering `--fix`'s annotation for it.
135
+
136
+ One of a `chained` assignment's names (`a = b = 0`), which can't be annotated where it's bound,
137
+ is offered a declaration before it instead (`a: int`); not a `Final` one, which needs its value.
138
+ """
139
+ name: str = target.id
140
+ if aliased.factory(value, self.settings.known): # a type: nothing to annotate it with
141
+ self.declared.add(name)
142
+ self.opaque([name])
143
+ return
144
+ again: bool = name in self.declared
145
+ fix: Inference | None
146
+ unsafe: bool
147
+ origins: frozenset[str]
148
+ constant: bool = (
149
+ self.kind.function is None
150
+ and self.kind.owner is None
151
+ and is_constant(name)
152
+ and name in self.settings.facts.passed
153
+ and chained is None
154
+ )
155
+ fix, unsafe, origins = self.valued(value, target.lineno, constant=constant)
156
+ if fix is not None and constant and origins == _LITERAL_DOUBT:
157
+ fix = self._constant(name, fix)
158
+ # Value flow's type is `--fix`'s own, if certain: worked out once, here, for both.
159
+ certain: str | None = certain_type(self, value, (None if fix is None else fix.annotation, unsafe))
160
+ if self.kind.owner is not None:
161
+ fix, unsafe, origins = self._member(name, fix, value)
162
+ if fix is None or not self.writable(fix):
163
+ fix, unsafe, origins = self._unvalued(
164
+ target,
165
+ value,
166
+ (fix, unsafe, origins),
167
+ alias=chained is None and not again,
168
+ )
169
+ # Past `vague`, a type holds no other binding's fix to it: unknown there, and to value flow.
170
+ vague: bool = fix is not None and not self.writable(fix)
171
+ guess: Late | None = (fix.annotation, origins) if fix is not None and unsafe else None
172
+ self.lifetime(name).bind(at(target), certain, None if vague else guess)
173
+ self.assigned(name, at(target))
174
+ if again:
175
+ self.inferred.rebound(name, fix.annotation if fix and vague and not unsafe else certain, guess)
176
+ kind: str | None
177
+ if (kind := fills.empty(value)) is not None:
178
+ _ = self.assignments.empty.setdefault(name, kind)
179
+ if chained is not None and not again:
180
+ _ = self.assignments.chained.setdefault(name, (chained.lineno, chained.col_offset))
181
+ # A wider type only for a name first bound here, by a statement of its own.
182
+ self._first(
183
+ name,
184
+ at(target),
185
+ code,
186
+ self._offered(target, (fix, unsafe, origins), None if chained is not None or again else value),
187
+ )
188
+ if fix is not None and aliased.declares(parsed(fix.annotation)):
189
+ self.inferred.aliases.add(name) # `TypeAlias` isn't its value's type
190
+ elif fix is not None:
191
+ self.inferred.learn(name, fix.annotation, origins if unsafe else None, again=again)
192
+ # A call of no type to write still types what's read of the name (`both.out`), bound once.
193
+ self.inferred.types.hold(
194
+ name,
195
+ value if fix is None and not again and chains(value, self.settings.known) else None,
196
+ )
197
+
198
+ def _unvalued(
199
+ self,
200
+ target: ast.Name,
201
+ value: ast.expr,
202
+ vague: tuple[Inference | None, bool, frozenset[str]],
203
+ *,
204
+ alias: bool,
205
+ ) -> tuple[Inference | None, bool, frozenset[str]]:
206
+ """Type a name its value gives no type: a module's type alias (`alias`: it may be one), or a hint.
207
+
208
+ `vague`: what `valued` made of it, a type too vague to write: the name's still, with neither.
209
+
210
+ Returns:
211
+ The inference, whether it's a guess, and what it rests on, as `valued` does.
212
+
213
+ """
214
+ checks: Checks = self.settings.checks
215
+ found: tuple[Inference, bool, frozenset[str]] | None = (
216
+ aliased.declared(
217
+ target,
218
+ value,
219
+ self.settings.known,
220
+ self.settings.facts,
221
+ (frozenset(self.inferred.aliases), checks.min_python),
222
+ )
223
+ if alias and self.kind.function is None and self.kind.owner is None
224
+ else None
225
+ )
226
+ function: FunctionDef | None = self.kind.function
227
+ owner: str | None = self.kind.owner
228
+ types: Typed = self.inferred.types
229
+ facts: Facts = self.settings.facts
230
+ # In a function; or a module's or a plain class's body, for a name first bound there.
231
+ body: bool = alias and (owner is None or owner in self.settings.known.class_side.plain)
232
+ called: tuple[Inference, ast.expr] | None = (
233
+ None
234
+ if found is not None or vague[0] is not None or not (function is not None or body)
235
+ else callables.aliased(
236
+ target,
237
+ value,
238
+ callables.Site(function, owner, facts.attributed, facts.positional),
239
+ self.settings.known,
240
+ (types, lambda arg: inference(arg, self.settings.known, types)),
241
+ )
242
+ )
243
+ if called is not None and owner is not None: # a class's variable: a guess (see `_member`)
244
+ found = called[0]._replace(kinds=called[0].kinds | {MEMBER}), True, frozenset({MEMBER})
245
+ elif called is not None:
246
+ found = called[0], *guesses_in(self, [called[1]])
247
+ hint: Inference | None
248
+ if found is None and (hint := self.hint(target, value)) is not None:
249
+ found = hint, True, hint.kinds
250
+ return found or vague
251
+
252
+ def _member(
253
+ self,
254
+ name: str,
255
+ fix: Inference | None,
256
+ value: ast.expr,
257
+ ) -> tuple[Inference | None, bool, frozenset[str]]:
258
+ """Offer a class body's fix only for a plain class's variable typed by its `value`: a guess.
259
+
260
+ Returns:
261
+ The inference, whether it's a guess, and what it rests on, as `valued` does; none for any
262
+ other binding of a class body.
263
+
264
+ """
265
+ typed: str | None = self.settings.known.class_side.variables.get(self.kind.owner or "", {}).get(name)
266
+ # The variable's type is its value's, or what a base holds it to (`maxDiff`, an `int | None`).
267
+ held: frozenset[str] = frozenset() if typed is None else self.inferred.members(typed)
268
+ if fix is None and _NONE in held and isinstance(value, ast.Constant) and value.value is None:
269
+ fix = Inference(_NONE, "what a class above it declares it", frozenset())
270
+ # A checked file's class, by name: the index typed it so (`plain.settled`).
271
+ if fix is None and typed is not None and isinstance(value, ast.Name) and typed == f"type[{value.id}]":
272
+ fix = Inference(typed, f"the class `{value.id}` it names", frozenset())
273
+ if fix is None or typed is None or fix.annotation not in held:
274
+ return None, False, frozenset()
275
+ return fix._replace(annotation=typed, kinds=fix.kinds | {MEMBER}), True, frozenset({MEMBER})
276
+
277
+ def valued(
278
+ self,
279
+ value: ast.expr,
280
+ line: int,
281
+ *,
282
+ constant: bool = False,
283
+ ) -> tuple[Inference | None, bool, frozenset[str]]:
284
+ """Infer the annotation of a name bound to `value` on `line`, as a type checker would see it.
285
+
286
+ `constant`: whether the name is an ALL_CAPS module-level one passed to a call (see `doubts`).
287
+
288
+ Returns:
289
+ The inference (`None` where there's none to offer), whether it's a guess, and what a guess
290
+ rests on.
291
+
292
+ """
293
+ facts: Facts = self.settings.facts
294
+ function: FunctionDef | None = self.kind.function
295
+ fix: Inference | None
296
+ if (fix := inference(value, self.settings.known, self.inferred.types)) is not None:
297
+ fix = corrected(value, fix, self._owner(), facts.generics, self.settings.known.names.plan)
298
+ # An alias of a union is narrowed as the union it names.
299
+ named: Inference | None = (
300
+ None
301
+ if fix is None
302
+ else fix._replace(annotation=self.inferred.unions.get(fix.annotation, fix.annotation))
303
+ )
304
+ if named is not None and (
305
+ (
306
+ narrowed_first(value, named)
307
+ and not (named.annotation != _NONE and as_declared(value, function, facts.tests))
308
+ )
309
+ or narrowed_at(facts.narrowed, value, line, union=len(members(named.annotation) or ()) > 1)
310
+ or self._takes_narrowed(value, named, line)
311
+ ):
312
+ fix = None
313
+ if fix is None or named is None:
314
+ return None, False, frozenset()
315
+ unsafe: bool
316
+ origins: frozenset[str]
317
+ unsafe, origins = guesses_in(self, [value])
318
+ if not unsafe:
319
+ origins = doubts(
320
+ value,
321
+ fix,
322
+ constant=constant,
323
+ narrowed=frozenset() if function is None else tested(function, facts.tests),
324
+ )
325
+ unsafe = bool(origins)
326
+ return fix, unsafe, origins
327
+
328
+ def _takes_narrowed(self, value: ast.expr, fix: Inference, line: int) -> bool:
329
+ """Check whether a fix takes its type from a read narrowed at `line`: `deepcopy(x)`, in `if x:`.
330
+
331
+ Or holds one in a display (`[x]`, `[self.x]`, there): its elements are the narrowed type's.
332
+
333
+ Returns:
334
+ Whether it does: the read has another type there than it's declared.
335
+
336
+ """
337
+ types: Mapping[str, str] = self.inferred.types
338
+ held: list[str] = [
339
+ ast.unparse(element)
340
+ for element in (value.elts if isinstance(value, ast.List | ast.Set | ast.Tuple) else ())
341
+ if isinstance(element, _READS)
342
+ ]
343
+ return any(
344
+ read_narrowed(
345
+ self.settings.facts.narrowed,
346
+ read,
347
+ line,
348
+ union=len(self.inferred.members(types.get(read, ""))) > 1,
349
+ )
350
+ for read in (*fix.reads, *held)
351
+ )
352
+
353
+ def _constant(self, name: str, fix: Inference) -> Inference | None:
354
+ """Declare an ALL_CAPS constant passed to a call `Final`, which keeps its literal's `Literal` type.
355
+
356
+ `str` would widen it, where a parameter may take only some values (see `doubts`). A guess
357
+ still (a name bound again is left alone, see `rebinds`), and only where the module can name
358
+ `Final`. What's read of `name` has its literal's type, `fix`'s.
359
+
360
+ Returns:
361
+ The `Final` inference, or `None`.
362
+
363
+ """
364
+ plan: ImportPlan | None = self.settings.known.names.plan
365
+ spelled: str | None = None if plan is None else plan.spell(_TYPING_FINAL)
366
+ self.inferred.learn(name, fix.annotation, _LITERAL_DOUBT)
367
+ return (
368
+ None
369
+ if spelled is None
370
+ else Inference(spelled, f"{fix.reason}, a constant passed to a call", fix.kinds | {FINAL_KIND})
371
+ )
372
+
373
+ def _owner(self) -> Owner | None:
374
+ """Find the class this scope is a method of, if its instance (or class) is a `Self` in it.
375
+
376
+ That's a method whose signature says `Self`: in any other, mypy takes `self` for its class.
377
+
378
+ Returns:
379
+ It, or `None` outside such a method, or in one whose first parameter isn't `self` or a
380
+ classmethod's.
381
+
382
+ """
383
+ function: FunctionDef | None = self.kind.function
384
+ owner: str | None = None if function is None else self.settings.owners.get(id(function))
385
+ if function is None or owner is None:
386
+ return None
387
+ args: list[ast.arg] = [*function.args.posonlyargs, *function.args.args]
388
+ decorators: list[str] = [node_name(d) for d in function.decorator_list]
389
+ if (
390
+ not args
391
+ or _STATICMETHOD in decorators
392
+ or not (args[0].arg == _SELF or decorators == [_CLASSMETHOD])
393
+ or not says_self(function)
394
+ ):
395
+ return None
396
+ return Owner(owner, args[0].arg, self.settings.known.class_side.lineage.selfish_of(owner))
397
+
398
+ def hint(self, target: ast.Name, value: ast.expr | None = None) -> Inference | None:
399
+ """Type `target` by the type checkers' hints for it (`--infer-with`): the first the file can use.
400
+
401
+ `value`: what a plain assignment binds it to. One hinted `TypeAlias` is declared an alias,
402
+ in a module body, where it's written as a type made of others (not a bare class's alias,
403
+ which declared one loses the class's type parameters).
404
+
405
+ Returns:
406
+ The inference (a guess), or `None`.
407
+
408
+ """
409
+ where: tuple[int, int] = (target.lineno, target.end_col_offset or 0)
410
+ composite: bool = value is not None and self.kind.function is None and is_composite(value)
411
+ found: Hints
412
+ for found in self.settings.hints:
413
+ # Not used at all where not offered: what follows from it would rest on it unseen.
414
+ text: str | None = (
415
+ found.types.get(where) if self.settings.checks.fixes.allows(frozenset({found.kind})) else None
416
+ )
417
+ typed: str | None
418
+ if text == hinted.ALIAS:
419
+ if composite and (typed := hinted.type_alias(self.settings.known, target.lineno)) is not None:
420
+ return hinted.inference(typed, found)
421
+ continue
422
+ if (
423
+ text is not None
424
+ and (
425
+ typed := hinted.hinted(
426
+ text,
427
+ self.settings.known,
428
+ nesting=self.settings.checks.nesting,
429
+ # A module body's annotation is evaluated there: only what's bound before it will do.
430
+ before=target.lineno if self.kind.function is None else None,
431
+ offered=found.offered.get(where),
432
+ )
433
+ )
434
+ # A generic class a checker prints bare has arguments it doesn't know.
435
+ and not bare(typed, self.settings.facts.generics)
436
+ and not hinted.renames(target.id, typed, local=self.kind.function is not None)
437
+ and not (self._undeclared(typed) or self._misread(target.id, typed))
438
+ ):
439
+ return hinted.inference(typed, found)
440
+ return None
441
+
442
+ def _misread(self, name: str, annotation: str) -> bool:
443
+ """Check whether a hint is one this scope's function reads otherwise than declared.
444
+
445
+ A union for a name it never tests: used as it is, it's taken for one of its members
446
+ (`opt.cb`, hinted `Option | None`), which the union declared would make an error; tested
447
+ (`if opt is None`), a checker narrows it. And any hint for a name it returns where its
448
+ signature says `Self`: the checker hints the class, which isn't `Self`. And a hint naming
449
+ `Self` where its signature doesn't: pyrefly takes `self` for `Self` everywhere
450
+ (`type(self)(...)`, `self[mask]`), mypy only there, and for its class elsewhere.
451
+
452
+ Returns:
453
+ Whether it is.
454
+
455
+ """
456
+ function: FunctionDef | None
457
+ if (function := self.kind.function) is None:
458
+ return False
459
+ if _SELF_WORD.search(annotation) is not None and not says_self(function):
460
+ return True
461
+ if len(members(annotation) or ()) > 1:
462
+ return name not in narrowing(function)
463
+ return says_self(function) and any(
464
+ isinstance(node.value, ast.Name) and node.value.id == name
465
+ for node in ast.walk(function)
466
+ if isinstance(node, ast.Return)
467
+ )
468
+
469
+ def _undeclared(self, annotation: str) -> bool:
470
+ """Check whether a hint names a type variable this scope's function doesn't declare.
471
+
472
+ See `undeclared`.
473
+
474
+ Returns:
475
+ Whether it does.
476
+
477
+ """
478
+ facts: Facts = self.settings.facts
479
+ function: FunctionDef | None = self.kind.function
480
+ owner: str | None = None if function is None else self.settings.owners.get(id(function))
481
+ return undeclared(annotation, function, facts.type_vars, facts.bound.get(owner or "", frozenset()))
482
+
483
+ def placed(self, name: str, fix: Inference, origins: frozenset[str], *, unsafe: bool) -> Fix | None:
484
+ """Offer `name`'s fix where it can go: at its binding, or declared before a chained assignment.
485
+
486
+ Returns:
487
+ The fix (see `offer`).
488
+
489
+ """
490
+ span: tuple[int, int] | None
491
+ if (span := self.assignments.chained.get(name)) is None:
492
+ return self.offer(fix, origins, unsafe=unsafe)
493
+ return self.offer(fix, origins, unsafe=unsafe, edit=Edit.DECLARE, span=span)
494
+
495
+ def offer(
496
+ self,
497
+ fix: Inference,
498
+ origins: frozenset[str],
499
+ *,
500
+ unsafe: bool,
501
+ edit: Edit = Edit.ANNOTATE,
502
+ span: tuple[int, int] = (0, 0),
503
+ ) -> Fix | None:
504
+ """Offer `fix` as the project's fix policy has it: selected, and a guess unless trusted.
505
+
506
+ Returns:
507
+ The fix, or `None` if a mechanism that decided it isn't selected, or is ignored, or it's
508
+ vaguer than `vague` allows (it would be LVA005).
509
+
510
+ """
511
+ policy: FixPolicy = self.settings.checks.fixes
512
+ widening: bool = bool(fix.kinds & WIDEN_KINDS) # written whatever `vague` allows, and marked
513
+ if not policy.allows(fix.kinds) or not (widening or self.writable(fix)):
514
+ return None
515
+ certain: bool = not unsafe or policy.trusts(origins)
516
+ plan: ImportPlan = self.settings.known.names.plan or ImportPlan({}, frozenset(), 0)
517
+ if self.kind.owner is None: # a class's own body names its classes bare
518
+ fix = fix._replace(annotation=through(fix.annotation, plan, self.settings.facts.nested))
519
+ if unwritable(fix.annotation, plan, self.settings.facts.nested):
520
+ return None
521
+ guarded: tuple[str, ...] = guarded_imports(fix.annotation, plan)
522
+ guard: str | None = ""
523
+ if guarded and plan.checking.block == (0, 0) and (guard := plan.type_checking()) is None:
524
+ return None # nothing can be `TYPE_CHECKING` to import them under
525
+ return Fix(
526
+ fix.annotation,
527
+ fix.reason,
528
+ not certain,
529
+ edit,
530
+ span,
531
+ fix.kinds,
532
+ # With the import `TYPE_CHECKING` takes, for a new block.
533
+ imports=imports_of(f"{fix.annotation} | {guard}" if guard else fix.annotation, plan),
534
+ after=plan.after,
535
+ guarded=guarded,
536
+ guard=guard or "",
537
+ block=plan.checking.block,
538
+ )
539
+
540
+ def _offered(
541
+ self,
542
+ target: ast.Name,
543
+ typed: tuple[Inference | None, bool, frozenset[str]],
544
+ widen: ast.expr | None,
545
+ ) -> Fix | None:
546
+ """Offer `target`'s fix as `typed` has it (see `valued`), else a wider one for its value `widen`.
547
+
548
+ A wider type (see `widened.wider`) only where `fix-widen` asks for it.
549
+
550
+ Returns:
551
+ The fix, or `None`.
552
+
553
+ """
554
+ found: Inference | None
555
+ fix: Fix | None
556
+ if typed[0] is not None and (fix := self.placed(target.id, typed[0], typed[2], unsafe=typed[1])):
557
+ return fix
558
+ if widen is None:
559
+ return None
560
+ self.assignments.plain[target.id] = (target, widen, typed[0])
561
+ if UNTYPED_PARAMETERS not in self.settings.checks.fixes.widen:
562
+ return None
563
+ found = widened.wider(
564
+ self.kind.function,
565
+ (target, widen),
566
+ self.inferred.types,
567
+ self.settings.known.names.plan,
568
+ )
569
+ return None if found is None else self.offer(found, frozenset(), unsafe=False)
570
+
571
+ def writable(self, fix: Inference) -> bool:
572
+ """Check that a type is no vaguer than `vague` allows: one that isn't is known, and never written.
573
+
574
+ Returns:
575
+ Whether it is.
576
+
577
+ """
578
+ return fits(fix.annotation, self.settings.checks.vague)
579
+
580
+ def _first(
581
+ self,
582
+ name: str,
583
+ where: tuple[int, int],
584
+ code: str | None,
585
+ fix: Fix | None,
586
+ ) -> None:
587
+ """Unless `name` is already bound, record its first binding, reporting `code` (`None`: typed).
588
+
589
+ The offence offers `fix`, unless this is a class body (a dataclass's annotation is a field),
590
+ where only a plain class's variable's (`_member`) is.
591
+ """
592
+ if name not in self.declared:
593
+ self.declared.add(name)
594
+ self.first.append(name)
595
+ if code is not None:
596
+ offered: bool = self.kind.fixable or (fix is not None and MEMBER in fix.kinds)
597
+ self.offences.append(Offence(*where, name, code, fix if offered else None))
598
+
599
+ def declare(self, name: str) -> None:
600
+ """Bind `name` by an annotation (`name: T`, `name: T = ...`): a typed first binding."""
601
+ # position is unused: `code` is `None`, so nothing is reported
602
+ self._first(name, (0, 0), None, None)
603
+
604
+ def opaque(self, names: Iterable[str]) -> None:
605
+ """Record bindings whose values value flow can't see: an import, a `def`, `except ... as`."""
606
+ name: str
607
+ for name in names:
608
+ self.lifetime(name).bind((0, 0), None)
609
+
610
+ def mark_escaped(self, escaped: frozenset[str]) -> None:
611
+ """Mark the names something out of sight writes: `escaped` ones, or any in a module or class body.
612
+
613
+ A module or class body's names are state anything can rebind out of its sight (an instance's
614
+ `self.x = ...`, another module's `mod.X = ...`, `monkeypatch`, `globals().update(...)`).
615
+ """
616
+ name: str
617
+ body: bool = self.kind.unannotated == UNANNOTATED_MEMBER
618
+ for name in self.flow.keys() if body else self.flow.keys() & escaped:
619
+ self.flow[name].escaped = True
620
+
621
+ def value_flow(self, escaped: frozenset[str], skipped: frozenset[str]) -> list[Finding]:
622
+ """Compare each name's values with its declared type.
623
+
624
+ `escaped` names are written elsewhere; `skipped` ones aren't compared at all.
625
+
626
+ Returns:
627
+ The findings, for names the rules cover.
628
+
629
+ """
630
+ self.mark_escaped(escaped)
631
+ return [
632
+ found
633
+ for name, lifetime in self.flow.items()
634
+ if self._covered(name) and name not in skipped
635
+ for found in findings(name, lifetime, self.settings.hierarchy)
636
+ ]
637
+
638
+ def assigned(self, name: str, where: tuple[int, int]) -> None:
639
+ """Record a plain assignment to `name` at `where`, for LVA012."""
640
+ self.assignments.found.setdefault(name, []).append((where, self.assignments.looping > 0))
641
+
642
+ def evaluated(self, offence: Offence) -> Offence:
643
+ """Quote a module body's fix that can't be evaluated when the module runs (unless it postpones them).
644
+
645
+ One whose type names what the module imports for type checking alone (under an `if` on a
646
+ flag), or binds only further down (both unbound then, as a quoted annotation's or a
647
+ `# type:` comment's names may be), or subscripts a
648
+ standard-library class that can't be at run time (`itertools.count[int]`). And any scope's
649
+ written as the project's oldest Python (`min-python`) doesn't read a type: `int | None`
650
+ before 3.10, `list[int]` before 3.9.
651
+
652
+ Returns:
653
+ The offence, its fix quoted if it must be.
654
+
655
+ """
656
+ plan: ImportPlan | None = self.settings.known.names.plan
657
+ fix: Fix | None = offence.edit
658
+ if fix is None or plan is None or plan.postponed:
659
+ return offence
660
+ if newer(fix.annotation, self.settings.checks.min_python):
661
+ return replace(offence, edit=fix._replace(annotation=quote(fix.annotation)))
662
+ if self.kind.function is not None:
663
+ return offence
664
+ line: int = fix.span[0] if fix.edit is Edit.DECLARE else offence.line
665
+ names: frozenset[str] = roots(fix.annotation)
666
+ unbound: bool = bool(plan.guarded.keys() & names) or any(
667
+ name in self.settings.facts.checking or plan.defined.get(name, 0) >= line for name in names
668
+ )
669
+ if not unbound and stdlib.evaluable(fix.annotation, self.settings.known):
670
+ return offence
671
+ return replace(offence, edit=fix._replace(annotation=quote(fix.annotation)))
672
+
673
+ def _covered(self, name: str) -> bool:
674
+ """Check whether the rules cover `name` here.
675
+
676
+ Returns:
677
+ Whether they do; a module or class body's dunder names are exempt.
678
+
679
+ """
680
+ return self.kind.unannotated != UNANNOTATED_MEMBER or not (
681
+ name.startswith("__") and name.endswith("__")
682
+ )
683
+
684
+ def reported(self) -> list[Offence]:
685
+ """Filter the offences found.
686
+
687
+ Returns:
688
+ All but those for exempt names.
689
+
690
+ """
691
+ return [self.evaluated(o) for o in self.offences if self._covered(o.name)]
692
+
693
+ def bound(self) -> list[str]:
694
+ """List the first bindings the rules cover.
695
+
696
+ Returns:
697
+ Their names, typed or not.
698
+
699
+ """
700
+ return [name for name in self.first if self._covered(name)]
701
+
702
+ def annotation(self, name: str, annotation: ast.expr, *, marked: bool = False) -> None:
703
+ """Report a vague annotation (LVA005), too deep a one (LVA006), or too long a tuple (LVA011).
704
+
705
+ Vague as far as `vague` allows (see `vague_fits`) isn't LVA005, nor is a widening's own
706
+ (`marked`: see `widened`).
707
+ """
708
+ if not marked and not vague_fits(
709
+ annotation,
710
+ self.settings.checks.vague,
711
+ ):
712
+ self.offences.append(Offence(*at(annotation), name, VAGUE_TYPE))
713
+ if depth(annotation) >= self.settings.checks.nesting:
714
+ self.offences.append(Offence(*at(annotation), name, NESTED_TYPE))
715
+ longest: int
716
+ if (longest := length(annotation)) > self.settings.known.limits.max_length:
717
+ self.offences.append(Offence(*at(annotation), name, LONG_TUPLE, detail=str(longest)))
718
+
719
+ def unannotated(self, type_comment: str | None) -> str | None:
720
+ """Decide the code for an `=` or `with` binding.
721
+
722
+ Returns:
723
+ The code, or `None` if a counted type comment types it.
724
+
725
+ """
726
+ return (
727
+ None if type_comment is not None and self.settings.checks.type_comments else self.kind.unannotated
728
+ )
729
+
730
+
731
+ def guesses_in(scope: Scope, values: Iterable[ast.expr]) -> tuple[bool, frozenset[str]]:
732
+ """Work out whether any of `values`' types is a guess, and what the guesses rest on (see `guessing`).
733
+
734
+ Returns:
735
+ Whether one is, and the guessing mechanisms (`FIX_KINDS`) theirs rest on.
736
+
737
+ """
738
+ unsafe: bool = False
739
+ found: set[str] = set()
740
+ value: ast.expr
741
+ for value in values:
742
+ guess: bool
743
+ origins: frozenset[str]
744
+ guess, origins = guessing(
745
+ value,
746
+ scope.settings.known,
747
+ frozenset(scope.inferred.guesses),
748
+ scope.inferred.origins,
749
+ scope.inferred.types,
750
+ )
751
+ unsafe = unsafe or guess
752
+ found.update(origins)
753
+ return unsafe, frozenset(found)
754
+
755
+
756
+ def certain_type(
757
+ scope: Scope,
758
+ value: ast.expr,
759
+ worked_out: tuple[str | None, bool] | None = None,
760
+ ) -> str | None:
761
+ """Infer `value`'s type for value flow: only a certain `--fix` inference, never a guess.
762
+
763
+ `None` itself (which `--fix` never offers: `x: None = None` says nothing) is `"None"` here. A
764
+ copy of a name typed as a union is unknown: an `isinstance` or `is None` check before it may
765
+ have narrowed the name, which value flow (blind to control flow) can't see. A type vaguer than
766
+ `vague` allows is unknown too: its `Any` fits whatever the name is declared. `worked_out`:
767
+ `--fix`'s annotation for `value` and whether it's a guess, if they're known already.
768
+
769
+ Returns:
770
+ The type as text, or `None` if it's unknown or only a guess.
771
+
772
+ """
773
+ if isinstance(value, ast.Constant) and value.value is None:
774
+ return _NONE
775
+ annotation: str | None
776
+ unsafe: bool
777
+ annotation, unsafe = worked_out or (
778
+ inferred(value, scope.settings.known, scope.inferred.types),
779
+ guessed(value, scope.settings.known, frozenset(scope.inferred.guesses), scope.inferred.types),
780
+ )
781
+ if isinstance(value, _READS) and annotation is not None and len(members(annotation) or ()) > 1:
782
+ return None # a read of a union is narrowed where the code checks it, which value flow can't see
783
+ if unsafe or annotation is None or not vague_fits(parsed(annotation), scope.settings.checks.vague):
784
+ return None
785
+ return annotation
786
+
787
+
788
+ def guessed_type(scope: Scope, value: ast.expr) -> Late | None:
789
+ """Infer `value`'s type where `--fix` only guesses it: what `certain_type` leaves unknown.
790
+
791
+ Returns:
792
+ The type and what the guess rests on; `None` if it's unknown, certain, or vaguer than `vague`
793
+ allows.
794
+
795
+ """
796
+ annotation: str | None = inferred(value, scope.settings.known, scope.inferred.types)
797
+ unsafe: bool
798
+ origins: frozenset[str]
799
+ unsafe, origins = guesses_in(scope, [value])
800
+ if not unsafe or annotation is None or not vague_fits(parsed(annotation), scope.settings.checks.vague):
801
+ return None
802
+ return annotation, origins