python-constricter 0.4.1__cp311-cp311-win_amd64.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__.cp311-win_amd64.pyd +0 -0
  29. constricter/fix/__init__.py +2 -0
  30. constricter/fix/core/__init__.cp311-win_amd64.pyd +0 -0
  31. constricter/fix/core/__init__.py +2 -0
  32. constricter/fix/core/asked.cp311-win_amd64.pyd +0 -0
  33. constricter/fix/core/asked.py +56 -0
  34. constricter/fix/core/fixes.cp311-win_amd64.pyd +0 -0
  35. constricter/fix/core/fixes.py +317 -0
  36. constricter/fix/core/imports.cp311-win_amd64.pyd +0 -0
  37. constricter/fix/core/imports.py +580 -0
  38. constricter/fix/core/inherited.cp311-win_amd64.pyd +0 -0
  39. constricter/fix/core/inherited.py +311 -0
  40. constricter/fix/core/known.cp311-win_amd64.pyd +0 -0
  41. constricter/fix/core/known.py +730 -0
  42. constricter/fix/core/signatures.cp311-win_amd64.pyd +0 -0
  43. constricter/fix/core/signatures.py +132 -0
  44. constricter/fix/index/__init__.cp311-win_amd64.pyd +0 -0
  45. constricter/fix/index/__init__.py +2 -0
  46. constricter/fix/index/awaits.cp311-win_amd64.pyd +0 -0
  47. constricter/fix/index/awaits.py +90 -0
  48. constricter/fix/index/beyond.cp311-win_amd64.pyd +0 -0
  49. constricter/fix/index/beyond.py +149 -0
  50. constricter/fix/index/callers.cp311-win_amd64.pyd +0 -0
  51. constricter/fix/index/callers.py +155 -0
  52. constricter/fix/index/crossed.cp311-win_amd64.pyd +0 -0
  53. constricter/fix/index/crossed.py +327 -0
  54. constricter/fix/index/decorated.cp311-win_amd64.pyd +0 -0
  55. constricter/fix/index/decorated.py +140 -0
  56. constricter/fix/index/fixtures.cp311-win_amd64.pyd +0 -0
  57. constricter/fix/index/fixtures.py +176 -0
  58. constricter/fix/index/linked.cp311-win_amd64.pyd +0 -0
  59. constricter/fix/index/linked.py +142 -0
  60. constricter/fix/index/loose.cp311-win_amd64.pyd +0 -0
  61. constricter/fix/index/loose.py +103 -0
  62. constricter/fix/index/managed.cp311-win_amd64.pyd +0 -0
  63. constricter/fix/index/managed.py +63 -0
  64. constricter/fix/index/modules.cp311-win_amd64.pyd +0 -0
  65. constricter/fix/index/modules.py +741 -0
  66. constricter/fix/index/offers.cp311-win_amd64.pyd +0 -0
  67. constricter/fix/index/offers.py +252 -0
  68. constricter/fix/index/order.cp311-win_amd64.pyd +0 -0
  69. constricter/fix/index/order.py +155 -0
  70. constricter/fix/index/origins.cp311-win_amd64.pyd +0 -0
  71. constricter/fix/index/origins.py +238 -0
  72. constricter/fix/index/own_methods.cp311-win_amd64.pyd +0 -0
  73. constricter/fix/index/own_methods.py +91 -0
  74. constricter/fix/index/own_overloads.cp311-win_amd64.pyd +0 -0
  75. constricter/fix/index/own_overloads.py +70 -0
  76. constricter/fix/index/own_types.cp311-win_amd64.pyd +0 -0
  77. constricter/fix/index/own_types.py +330 -0
  78. constricter/fix/index/params.cp311-win_amd64.pyd +0 -0
  79. constricter/fix/index/params.py +81 -0
  80. constricter/fix/index/plain.cp311-win_amd64.pyd +0 -0
  81. constricter/fix/index/plain.py +221 -0
  82. constricter/fix/index/project.cp311-win_amd64.pyd +0 -0
  83. constricter/fix/index/project.py +797 -0
  84. constricter/fix/index/sides.cp311-win_amd64.pyd +0 -0
  85. constricter/fix/index/sides.py +160 -0
  86. constricter/fix/index/strings.cp311-win_amd64.pyd +0 -0
  87. constricter/fix/index/strings.py +46 -0
  88. constricter/fix/index/stubs/__init__.cp311-win_amd64.pyd +0 -0
  89. constricter/fix/index/stubs/__init__.py +2 -0
  90. constricter/fix/index/stubs/atoms.cp311-win_amd64.pyd +0 -0
  91. constricter/fix/index/stubs/atoms.py +425 -0
  92. constricter/fix/index/stubs/chained.cp311-win_amd64.pyd +0 -0
  93. constricter/fix/index/stubs/chained.py +69 -0
  94. constricter/fix/index/stubs/classnames.cp311-win_amd64.pyd +0 -0
  95. constricter/fix/index/stubs/classnames.py +124 -0
  96. constricter/fix/index/stubs/declared.cp311-win_amd64.pyd +0 -0
  97. constricter/fix/index/stubs/declared.py +508 -0
  98. constricter/fix/index/stubs/installed.cp311-win_amd64.pyd +0 -0
  99. constricter/fix/index/stubs/installed.py +425 -0
  100. constricter/fix/index/stubs/names.cp311-win_amd64.pyd +0 -0
  101. constricter/fix/index/stubs/names.py +200 -0
  102. constricter/fix/index/stubs/read.cp311-win_amd64.pyd +0 -0
  103. constricter/fix/index/stubs/read.py +70 -0
  104. constricter/fix/index/stubs/signatures.cp311-win_amd64.pyd +0 -0
  105. constricter/fix/index/stubs/signatures.py +656 -0
  106. constricter/fix/index/stubs/stubbed.cp311-win_amd64.pyd +0 -0
  107. constricter/fix/index/stubs/stubbed.py +208 -0
  108. constricter/fix/index/taken.cp311-win_amd64.pyd +0 -0
  109. constricter/fix/index/taken.py +45 -0
  110. constricter/fix/index/tuples.cp311-win_amd64.pyd +0 -0
  111. constricter/fix/index/tuples.py +112 -0
  112. constricter/fix/libraries/__init__.cp311-win_amd64.pyd +0 -0
  113. constricter/fix/libraries/__init__.py +2 -0
  114. constricter/fix/libraries/library.cp311-win_amd64.pyd +0 -0
  115. constricter/fix/libraries/library.py +420 -0
  116. constricter/fix/libraries/opened.cp311-win_amd64.pyd +0 -0
  117. constricter/fix/libraries/opened.py +147 -0
  118. constricter/fix/libraries/overloads.cp311-win_amd64.pyd +0 -0
  119. constricter/fix/libraries/overloads.py +960 -0
  120. constricter/fix/libraries/receivers.cp311-win_amd64.pyd +0 -0
  121. constricter/fix/libraries/receivers.py +274 -0
  122. constricter/fix/libraries/stdlib.cp311-win_amd64.pyd +0 -0
  123. constricter/fix/libraries/stdlib.py +752 -0
  124. constricter/fix/libraries/walks.cp311-win_amd64.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__.cp311-win_amd64.pyd +0 -0
  148. constricter/fix/values/__init__.py +2 -0
  149. constricter/fix/values/aliased.cp311-win_amd64.pyd +0 -0
  150. constricter/fix/values/aliased.py +195 -0
  151. constricter/fix/values/bodies.cp311-win_amd64.pyd +0 -0
  152. constricter/fix/values/bodies.py +76 -0
  153. constricter/fix/values/callables.cp311-win_amd64.pyd +0 -0
  154. constricter/fix/values/callables.py +323 -0
  155. constricter/fix/values/called.cp311-win_amd64.pyd +0 -0
  156. constricter/fix/values/called.py +194 -0
  157. constricter/fix/values/classvars.cp311-win_amd64.pyd +0 -0
  158. constricter/fix/values/classvars.py +629 -0
  159. constricter/fix/values/decided.cp311-win_amd64.pyd +0 -0
  160. constricter/fix/values/decided.py +429 -0
  161. constricter/fix/values/displays.cp311-win_amd64.pyd +0 -0
  162. constricter/fix/values/displays.py +101 -0
  163. constricter/fix/values/doubts.cp311-win_amd64.pyd +0 -0
  164. constricter/fix/values/doubts.py +677 -0
  165. constricter/fix/values/entered.cp311-win_amd64.pyd +0 -0
  166. constricter/fix/values/entered.py +332 -0
  167. constricter/fix/values/fills.cp311-win_amd64.pyd +0 -0
  168. constricter/fix/values/fills.py +446 -0
  169. constricter/fix/values/guesses.cp311-win_amd64.pyd +0 -0
  170. constricter/fix/values/guesses.py +376 -0
  171. constricter/fix/values/hinted.cp311-win_amd64.pyd +0 -0
  172. constricter/fix/values/hinted.py +811 -0
  173. constricter/fix/values/inference.cp311-win_amd64.pyd +0 -0
  174. constricter/fix/values/inference.py +698 -0
  175. constricter/fix/values/loops.cp311-win_amd64.pyd +0 -0
  176. constricter/fix/values/loops.py +335 -0
  177. constricter/fix/values/members.cp311-win_amd64.pyd +0 -0
  178. constricter/fix/values/members.py +578 -0
  179. constricter/fix/values/narrowed.cp311-win_amd64.pyd +0 -0
  180. constricter/fix/values/narrowed.py +220 -0
  181. constricter/fix/values/operated.cp311-win_amd64.pyd +0 -0
  182. constricter/fix/values/operated.py +202 -0
  183. constricter/fix/values/ordered.cp311-win_amd64.pyd +0 -0
  184. constricter/fix/values/ordered.py +74 -0
  185. constricter/fix/values/returned.cp311-win_amd64.pyd +0 -0
  186. constricter/fix/values/returned.py +479 -0
  187. constricter/fix/values/returns.cp311-win_amd64.pyd +0 -0
  188. constricter/fix/values/returns.py +237 -0
  189. constricter/fix/values/shapes.cp311-win_amd64.pyd +0 -0
  190. constricter/fix/values/shapes.py +654 -0
  191. constricter/fix/values/stored.cp311-win_amd64.pyd +0 -0
  192. constricter/fix/values/stored.py +564 -0
  193. constricter/fix/values/targets.cp311-win_amd64.pyd +0 -0
  194. constricter/fix/values/targets.py +419 -0
  195. constricter/fix/values/unchecked.cp311-win_amd64.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__.cp311-win_amd64.pyd +0 -0
  208. constricter/rules/__init__.py +2 -0
  209. constricter/rules/annotations.cp311-win_amd64.pyd +0 -0
  210. constricter/rules/annotations.py +296 -0
  211. constricter/rules/binding.cp311-win_amd64.pyd +0 -0
  212. constricter/rules/binding.py +652 -0
  213. constricter/rules/calls.cp311-win_amd64.pyd +0 -0
  214. constricter/rules/calls.py +460 -0
  215. constricter/rules/checker.cp311-win_amd64.pyd +0 -0
  216. constricter/rules/checker.py +536 -0
  217. constricter/rules/declarations.cp311-win_amd64.pyd +0 -0
  218. constricter/rules/declarations.py +730 -0
  219. constricter/rules/decorators.cp311-win_amd64.pyd +0 -0
  220. constricter/rules/decorators.py +351 -0
  221. constricter/rules/flow.cp311-win_amd64.pyd +0 -0
  222. constricter/rules/flow.py +502 -0
  223. constricter/rules/keys.cp311-win_amd64.pyd +0 -0
  224. constricter/rules/keys.py +79 -0
  225. constricter/rules/late.cp311-win_amd64.pyd +0 -0
  226. constricter/rules/late.py +969 -0
  227. constricter/rules/narrowing.cp311-win_amd64.pyd +0 -0
  228. constricter/rules/narrowing.py +98 -0
  229. constricter/rules/parsed.cp311-win_amd64.pyd +0 -0
  230. constricter/rules/parsed.py +136 -0
  231. constricter/rules/quoted.cp311-win_amd64.pyd +0 -0
  232. constricter/rules/quoted.py +141 -0
  233. constricter/rules/rebinding.cp311-win_amd64.pyd +0 -0
  234. constricter/rules/rebinding.py +169 -0
  235. constricter/rules/recorded.cp311-win_amd64.pyd +0 -0
  236. constricter/rules/recorded.py +168 -0
  237. constricter/rules/redundant.cp311-win_amd64.pyd +0 -0
  238. constricter/rules/redundant.py +160 -0
  239. constricter/rules/scope.cp311-win_amd64.pyd +0 -0
  240. constricter/rules/scope.py +802 -0
  241. constricter/rules/scoped.cp311-win_amd64.pyd +0 -0
  242. constricter/rules/scoped.py +232 -0
  243. constricter/rules/scoping.cp311-win_amd64.pyd +0 -0
  244. constricter/rules/scoping.py +346 -0
  245. constricter/rules/settings.cp311-win_amd64.pyd +0 -0
  246. constricter/rules/settings.py +189 -0
  247. constricter/rules/syntax.cp311-win_amd64.pyd +0 -0
  248. constricter/rules/syntax.py +472 -0
  249. constricter/rules/tables.cp311-win_amd64.pyd +0 -0
  250. constricter/rules/tables.py +169 -0
  251. constricter/rules/walked.cp311-win_amd64.pyd +0 -0
  252. constricter/rules/walked.py +127 -0
  253. constricter/rules/widened.cp311-win_amd64.pyd +0 -0
  254. constricter/rules/widened.py +322 -0
  255. constricter/trace.py +374 -0
  256. e45ce06b2fdd5c43f782__mypyc.cp311-win_amd64.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,237 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Return types the language fixes: builtins', and `str`, `bytes`, `list`, `set` and `dict` methods'."""
3
+
4
+ import ast
5
+ from typing import Final
6
+
7
+ from constricter.fix.values.targets import sole
8
+
9
+ # Builtins whose return type is fixed by the language, whatever their argument: safe to infer, not
10
+ # a guess (unlike a capitalised call, which could really be a generic class or a factory function).
11
+ BUILTIN_RETURNS: Final = {
12
+ "all": "bool",
13
+ "any": "bool",
14
+ "ascii": "str",
15
+ "bin": "str",
16
+ "bool": "bool",
17
+ "bytearray": "bytearray",
18
+ "bytes": "bytes",
19
+ "callable": "bool",
20
+ "chr": "str",
21
+ "complex": "complex",
22
+ "dir": "list[str]",
23
+ "float": "float",
24
+ "format": "str",
25
+ "hasattr": "bool",
26
+ "hash": "int",
27
+ "hex": "str",
28
+ "id": "int",
29
+ "input": "str",
30
+ "int": "int",
31
+ "isinstance": "bool",
32
+ "issubclass": "bool",
33
+ "len": "int",
34
+ "object": "object",
35
+ "oct": "str",
36
+ "ord": "int",
37
+ "range": "range",
38
+ "repr": "str",
39
+ "str": "str",
40
+ }
41
+
42
+
43
+ # `str`/`bytes` methods whose return type is fixed by the language, whatever their arguments: safe
44
+ # to infer for a call on an already-typed local, not a guess.
45
+ _STR_METHODS: Final = {
46
+ "capitalize": "str",
47
+ "casefold": "str",
48
+ "center": "str",
49
+ "count": "int",
50
+ "encode": "bytes",
51
+ "endswith": "bool",
52
+ "expandtabs": "str",
53
+ "find": "int",
54
+ "format": "str",
55
+ "format_map": "str",
56
+ "index": "int",
57
+ "isalnum": "bool",
58
+ "isalpha": "bool",
59
+ "isascii": "bool",
60
+ "isdecimal": "bool",
61
+ "isdigit": "bool",
62
+ "isidentifier": "bool",
63
+ "islower": "bool",
64
+ "isnumeric": "bool",
65
+ "isprintable": "bool",
66
+ "isspace": "bool",
67
+ "istitle": "bool",
68
+ "isupper": "bool",
69
+ "join": "str",
70
+ "ljust": "str",
71
+ "lower": "str",
72
+ "lstrip": "str",
73
+ "partition": "tuple[str, str, str]",
74
+ "removeprefix": "str",
75
+ "removesuffix": "str",
76
+ "replace": "str",
77
+ "rfind": "int",
78
+ "rindex": "int",
79
+ "rjust": "str",
80
+ "rpartition": "tuple[str, str, str]",
81
+ "rsplit": "list[str]",
82
+ "rstrip": "str",
83
+ "split": "list[str]",
84
+ "splitlines": "list[str]",
85
+ "startswith": "bool",
86
+ "strip": "str",
87
+ "swapcase": "str",
88
+ "title": "str",
89
+ "translate": "str",
90
+ "upper": "str",
91
+ "zfill": "str",
92
+ }
93
+
94
+
95
+ _BYTES_METHODS: Final = {
96
+ "capitalize": "bytes",
97
+ "center": "bytes",
98
+ "count": "int",
99
+ "decode": "str",
100
+ "endswith": "bool",
101
+ "expandtabs": "bytes",
102
+ "find": "int",
103
+ "hex": "str",
104
+ "index": "int",
105
+ "isalnum": "bool",
106
+ "isalpha": "bool",
107
+ "isascii": "bool",
108
+ "isdigit": "bool",
109
+ "islower": "bool",
110
+ "isspace": "bool",
111
+ "istitle": "bool",
112
+ "isupper": "bool",
113
+ "join": "bytes",
114
+ "ljust": "bytes",
115
+ "lower": "bytes",
116
+ "lstrip": "bytes",
117
+ "partition": "tuple[bytes, bytes, bytes]",
118
+ "removeprefix": "bytes",
119
+ "removesuffix": "bytes",
120
+ "replace": "bytes",
121
+ "rfind": "int",
122
+ "rindex": "int",
123
+ "rjust": "bytes",
124
+ "rpartition": "tuple[bytes, bytes, bytes]",
125
+ "rsplit": "list[bytes]",
126
+ "rstrip": "bytes",
127
+ "split": "list[bytes]",
128
+ "splitlines": "list[bytes]",
129
+ "startswith": "bool",
130
+ "strip": "bytes",
131
+ "swapcase": "bytes",
132
+ "title": "bytes",
133
+ "translate": "bytes",
134
+ "upper": "bytes",
135
+ "zfill": "bytes",
136
+ }
137
+
138
+
139
+ _INT_METHODS: Final = {
140
+ "as_integer_ratio": "tuple[int, int]",
141
+ "bit_count": "int",
142
+ "bit_length": "int",
143
+ "conjugate": "int",
144
+ "to_bytes": "bytes",
145
+ }
146
+ _FLOAT_METHODS: Final = {
147
+ "as_integer_ratio": "tuple[int, int]",
148
+ "conjugate": "float",
149
+ "hex": "str",
150
+ "is_integer": "bool",
151
+ }
152
+ METHOD_RETURNS: Final = {
153
+ "str": _STR_METHODS,
154
+ "bytes": _BYTES_METHODS,
155
+ "int": _INT_METHODS,
156
+ "float": _FLOAT_METHODS,
157
+ }
158
+ # Methods that give the same type whatever their container holds, and whatever they're passed.
159
+ _COUNTS: Final = frozenset({"count", "index"}) # a `list`'s or a `tuple`'s: an `int`
160
+ _SET_TESTS: Final = frozenset({"isdisjoint", "issubset", "issuperset"}) # a `bool`
161
+ _SET_KEEPS: Final = frozenset({"difference", "intersection"}) # the receiver's own type
162
+
163
+
164
+ def element_method(root: ast.expr, receiver: str, call: ast.Call, method: str) -> str | None:
165
+ """Infer a `list`, `set` or `dict` method call's type from the receiver's own type parameters.
166
+
167
+ `receiver` is the receiver's type as text, and `root` that parsed.
168
+
169
+ `copy()` is the receiver's type; `pop()` a `list`'s or `set`'s element (with an optional index
170
+ for a `list`); `pop(key)`, `setdefault(key, value)` and `get(key)` a `dict`'s value (`get` as
171
+ `V | None`); `popitem()` its `tuple[K, V]`. A call with any other arguments (`pop(key, default)`,
172
+ a keyword) can return something else, so it decides nothing.
173
+
174
+ Returns:
175
+ The annotation as source text, or `None` if the call doesn't decide one.
176
+
177
+ """
178
+ call_shape: tuple[str, int | None] = (method, None if call.keywords else len(call.args))
179
+ element: ast.expr
180
+ key: ast.expr
181
+ match root:
182
+ case ast.Subscript(value=ast.Name(id="list" | "List" | "set" | "Set" | "dict" | "Dict")) if (
183
+ call_shape == ("copy", 0)
184
+ ):
185
+ return receiver
186
+ case ast.Subscript(value=ast.Name(id="list" | "List"), slice=element) if call_shape in {
187
+ ("pop", 0),
188
+ ("pop", 1),
189
+ }:
190
+ return ast.unparse(sole(element))
191
+ case ast.Subscript(value=ast.Name(id="set" | "Set"), slice=element) if call_shape == ("pop", 0):
192
+ return ast.unparse(sole(element))
193
+ case ast.Subscript(value=ast.Name(id="dict" | "Dict"), slice=ast.Tuple(elts=[key, element])):
194
+ return _dict_method(call_shape, key, element)
195
+ case _:
196
+ return None
197
+
198
+
199
+ def uniform_method(root: ast.expr, receiver: str, method: str) -> str | None:
200
+ """Infer a builtin container's method call that gives one type whatever it holds, or is passed.
201
+
202
+ A `list`'s or `tuple`'s `count` and `index` are `int`s, a `set`'s or `frozenset`'s `issubset`
203
+ and its like `bool`s, and its `difference` and `intersection` the receiver's own type.
204
+
205
+ Returns:
206
+ The annotation as source text, or `None` for any other method or receiver.
207
+
208
+ """
209
+ match root:
210
+ case ast.Subscript(value=ast.Name(id="list" | "List" | "tuple" | "Tuple")) if method in _COUNTS:
211
+ return "int"
212
+ case ast.Subscript(value=ast.Name(id="set" | "Set" | "frozenset" | "FrozenSet")) if (
213
+ method in _SET_TESTS | _SET_KEEPS
214
+ ):
215
+ return "bool" if method in _SET_TESTS else receiver
216
+ case _:
217
+ return None
218
+
219
+
220
+ def _dict_method(call_shape: tuple[str, int | None], key: ast.expr, value: ast.expr) -> str | None:
221
+ """Infer a `dict[key, value]` method call's type, by its name and positional argument count.
222
+
223
+ Returns:
224
+ The annotation as source text, or `None` if the call doesn't decide one.
225
+
226
+ """
227
+ match call_shape:
228
+ case ("pop", 1) | ("setdefault", 2):
229
+ return ast.unparse(value)
230
+ case ("get", 1) if not any(
231
+ isinstance(node, ast.Constant) and isinstance(node.value, str) for node in ast.walk(value)
232
+ ): # a string (forward-reference) value type can't take `| None` where it's evaluated
233
+ return f"{ast.unparse(value)} | None"
234
+ case ("popitem", 0):
235
+ return f"tuple[{ast.unparse(key)}, {ast.unparse(value)}]"
236
+ case _:
237
+ return None