crosshair-tool 0.0.99__cp312-cp312-macosx_10_13_x86_64.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 (176) hide show
  1. _crosshair_tracers.cpython-312-darwin.so +0 -0
  2. crosshair/__init__.py +42 -0
  3. crosshair/__main__.py +8 -0
  4. crosshair/_mark_stacks.h +790 -0
  5. crosshair/_preliminaries_test.py +18 -0
  6. crosshair/_tracers.h +94 -0
  7. crosshair/_tracers_pycompat.h +522 -0
  8. crosshair/_tracers_test.py +138 -0
  9. crosshair/abcstring.py +245 -0
  10. crosshair/auditwall.py +190 -0
  11. crosshair/auditwall_test.py +77 -0
  12. crosshair/codeconfig.py +113 -0
  13. crosshair/codeconfig_test.py +117 -0
  14. crosshair/condition_parser.py +1237 -0
  15. crosshair/condition_parser_test.py +497 -0
  16. crosshair/conftest.py +30 -0
  17. crosshair/copyext.py +155 -0
  18. crosshair/copyext_test.py +84 -0
  19. crosshair/core.py +1763 -0
  20. crosshair/core_and_libs.py +149 -0
  21. crosshair/core_regestered_types_test.py +82 -0
  22. crosshair/core_test.py +1316 -0
  23. crosshair/diff_behavior.py +314 -0
  24. crosshair/diff_behavior_test.py +261 -0
  25. crosshair/dynamic_typing.py +346 -0
  26. crosshair/dynamic_typing_test.py +210 -0
  27. crosshair/enforce.py +282 -0
  28. crosshair/enforce_test.py +182 -0
  29. crosshair/examples/PEP316/__init__.py +1 -0
  30. crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
  31. crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
  32. crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
  33. crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
  34. crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
  35. crosshair/examples/PEP316/correct_code/__init__.py +0 -0
  36. crosshair/examples/PEP316/correct_code/arith.py +60 -0
  37. crosshair/examples/PEP316/correct_code/chess.py +77 -0
  38. crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
  39. crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
  40. crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
  41. crosshair/examples/PEP316/correct_code/showcase.py +104 -0
  42. crosshair/examples/__init__.py +0 -0
  43. crosshair/examples/check_examples_test.py +146 -0
  44. crosshair/examples/deal/__init__.py +1 -0
  45. crosshair/examples/icontract/__init__.py +1 -0
  46. crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
  47. crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
  48. crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
  49. crosshair/examples/icontract/correct_code/__init__.py +0 -0
  50. crosshair/examples/icontract/correct_code/arith.py +51 -0
  51. crosshair/examples/icontract/correct_code/showcase.py +94 -0
  52. crosshair/fnutil.py +391 -0
  53. crosshair/fnutil_test.py +75 -0
  54. crosshair/fuzz_core_test.py +516 -0
  55. crosshair/libimpl/__init__.py +0 -0
  56. crosshair/libimpl/arraylib.py +161 -0
  57. crosshair/libimpl/binascii_ch_test.py +30 -0
  58. crosshair/libimpl/binascii_test.py +67 -0
  59. crosshair/libimpl/binasciilib.py +150 -0
  60. crosshair/libimpl/bisectlib_test.py +23 -0
  61. crosshair/libimpl/builtinslib.py +5228 -0
  62. crosshair/libimpl/builtinslib_ch_test.py +1191 -0
  63. crosshair/libimpl/builtinslib_test.py +3735 -0
  64. crosshair/libimpl/codecslib.py +86 -0
  65. crosshair/libimpl/codecslib_test.py +86 -0
  66. crosshair/libimpl/collectionslib.py +264 -0
  67. crosshair/libimpl/collectionslib_ch_test.py +252 -0
  68. crosshair/libimpl/collectionslib_test.py +332 -0
  69. crosshair/libimpl/copylib.py +23 -0
  70. crosshair/libimpl/copylib_test.py +18 -0
  71. crosshair/libimpl/datetimelib.py +2559 -0
  72. crosshair/libimpl/datetimelib_ch_test.py +354 -0
  73. crosshair/libimpl/datetimelib_test.py +112 -0
  74. crosshair/libimpl/decimallib.py +5257 -0
  75. crosshair/libimpl/decimallib_ch_test.py +78 -0
  76. crosshair/libimpl/decimallib_test.py +76 -0
  77. crosshair/libimpl/encodings/__init__.py +23 -0
  78. crosshair/libimpl/encodings/_encutil.py +187 -0
  79. crosshair/libimpl/encodings/ascii.py +44 -0
  80. crosshair/libimpl/encodings/latin_1.py +40 -0
  81. crosshair/libimpl/encodings/utf_8.py +93 -0
  82. crosshair/libimpl/encodings_ch_test.py +83 -0
  83. crosshair/libimpl/fractionlib.py +16 -0
  84. crosshair/libimpl/fractionlib_test.py +80 -0
  85. crosshair/libimpl/functoolslib.py +34 -0
  86. crosshair/libimpl/functoolslib_test.py +56 -0
  87. crosshair/libimpl/hashliblib.py +30 -0
  88. crosshair/libimpl/hashliblib_test.py +18 -0
  89. crosshair/libimpl/heapqlib.py +47 -0
  90. crosshair/libimpl/heapqlib_test.py +21 -0
  91. crosshair/libimpl/importliblib.py +18 -0
  92. crosshair/libimpl/importliblib_test.py +38 -0
  93. crosshair/libimpl/iolib.py +216 -0
  94. crosshair/libimpl/iolib_ch_test.py +128 -0
  95. crosshair/libimpl/iolib_test.py +19 -0
  96. crosshair/libimpl/ipaddresslib.py +8 -0
  97. crosshair/libimpl/itertoolslib.py +44 -0
  98. crosshair/libimpl/itertoolslib_test.py +44 -0
  99. crosshair/libimpl/jsonlib.py +984 -0
  100. crosshair/libimpl/jsonlib_ch_test.py +42 -0
  101. crosshair/libimpl/jsonlib_test.py +51 -0
  102. crosshair/libimpl/mathlib.py +179 -0
  103. crosshair/libimpl/mathlib_ch_test.py +44 -0
  104. crosshair/libimpl/mathlib_test.py +67 -0
  105. crosshair/libimpl/oslib.py +7 -0
  106. crosshair/libimpl/pathliblib_test.py +10 -0
  107. crosshair/libimpl/randomlib.py +178 -0
  108. crosshair/libimpl/randomlib_test.py +120 -0
  109. crosshair/libimpl/relib.py +846 -0
  110. crosshair/libimpl/relib_ch_test.py +169 -0
  111. crosshair/libimpl/relib_test.py +493 -0
  112. crosshair/libimpl/timelib.py +72 -0
  113. crosshair/libimpl/timelib_test.py +82 -0
  114. crosshair/libimpl/typeslib.py +15 -0
  115. crosshair/libimpl/typeslib_test.py +36 -0
  116. crosshair/libimpl/unicodedatalib.py +75 -0
  117. crosshair/libimpl/unicodedatalib_test.py +42 -0
  118. crosshair/libimpl/urlliblib.py +23 -0
  119. crosshair/libimpl/urlliblib_test.py +19 -0
  120. crosshair/libimpl/weakreflib.py +13 -0
  121. crosshair/libimpl/weakreflib_test.py +69 -0
  122. crosshair/libimpl/zliblib.py +15 -0
  123. crosshair/libimpl/zliblib_test.py +13 -0
  124. crosshair/lsp_server.py +261 -0
  125. crosshair/lsp_server_test.py +30 -0
  126. crosshair/main.py +973 -0
  127. crosshair/main_test.py +543 -0
  128. crosshair/objectproxy.py +376 -0
  129. crosshair/objectproxy_test.py +41 -0
  130. crosshair/opcode_intercept.py +601 -0
  131. crosshair/opcode_intercept_test.py +304 -0
  132. crosshair/options.py +218 -0
  133. crosshair/options_test.py +10 -0
  134. crosshair/patch_equivalence_test.py +75 -0
  135. crosshair/path_cover.py +209 -0
  136. crosshair/path_cover_test.py +138 -0
  137. crosshair/path_search.py +161 -0
  138. crosshair/path_search_test.py +52 -0
  139. crosshair/pathing_oracle.py +271 -0
  140. crosshair/pathing_oracle_test.py +21 -0
  141. crosshair/pure_importer.py +27 -0
  142. crosshair/pure_importer_test.py +16 -0
  143. crosshair/py.typed +0 -0
  144. crosshair/register_contract.py +273 -0
  145. crosshair/register_contract_test.py +190 -0
  146. crosshair/simplestructs.py +1165 -0
  147. crosshair/simplestructs_test.py +283 -0
  148. crosshair/smtlib.py +24 -0
  149. crosshair/smtlib_test.py +14 -0
  150. crosshair/statespace.py +1199 -0
  151. crosshair/statespace_test.py +108 -0
  152. crosshair/stubs_parser.py +352 -0
  153. crosshair/stubs_parser_test.py +43 -0
  154. crosshair/test_util.py +329 -0
  155. crosshair/test_util_test.py +26 -0
  156. crosshair/tools/__init__.py +0 -0
  157. crosshair/tools/check_help_in_doc.py +264 -0
  158. crosshair/tools/check_init_and_setup_coincide.py +119 -0
  159. crosshair/tools/generate_demo_table.py +127 -0
  160. crosshair/tracers.py +544 -0
  161. crosshair/tracers_test.py +154 -0
  162. crosshair/type_repo.py +151 -0
  163. crosshair/unicode_categories.py +589 -0
  164. crosshair/unicode_categories_test.py +27 -0
  165. crosshair/util.py +741 -0
  166. crosshair/util_test.py +173 -0
  167. crosshair/watcher.py +307 -0
  168. crosshair/watcher_test.py +107 -0
  169. crosshair/z3util.py +76 -0
  170. crosshair/z3util_test.py +11 -0
  171. crosshair_tool-0.0.99.dist-info/METADATA +144 -0
  172. crosshair_tool-0.0.99.dist-info/RECORD +176 -0
  173. crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
  174. crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
  175. crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
  176. crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
@@ -0,0 +1,346 @@
1
+ import collections.abc
2
+ import sys
3
+ import typing
4
+ from inspect import Parameter, Signature
5
+ from itertools import zip_longest
6
+ from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Type
7
+
8
+ import typing_inspect # type: ignore
9
+
10
+ from crosshair.util import debug # type: ignore
11
+
12
+ _EMPTYSET: frozenset = frozenset()
13
+
14
+
15
+ def origin_of(typ: Type) -> Type:
16
+ if hasattr(typ, "__origin__"):
17
+ return typ.__origin__
18
+ return typ
19
+
20
+
21
+ """
22
+ def _lowest_common_bases(classes):
23
+ # Idea from https://stackoverflow.com/questions/25786566/greatest-common-superclass
24
+
25
+ # pull first class, and start with it's bases
26
+ gen = iter(classes)
27
+ cls = next(gen, None)
28
+ if cls is None:
29
+ return set()
30
+ common = set(cls.__mro__)
31
+
32
+ # find set of ALL ancestor classes,
33
+ # by intersecting MROs of all specified classes
34
+ for cls in gen:
35
+ common.intersection_update(cls.__mro__)
36
+
37
+ # remove any bases which have at least one subclass also in the set,
38
+ # as they aren't part of "minimal" set of common ancestors.
39
+ result = common.copy()
40
+ for cls in common:
41
+ if cls in result:
42
+ result.difference_update(cls.__mro__[1:])
43
+
44
+ # return set
45
+ return result
46
+
47
+ def infer_generic_type(value: object) -> Type:
48
+ if isinstance(value, tuple):
49
+ ...
50
+ """
51
+
52
+
53
+ def unify_callable_args(
54
+ value_types: Sequence[Type],
55
+ recv_types: Sequence[Type],
56
+ bindings: typing.ChainMap[object, Type],
57
+ ) -> bool:
58
+ if value_types == ... or recv_types == ...:
59
+ return True
60
+ if len(value_types) != len(recv_types):
61
+ return False
62
+ for varg, rarg in zip(value_types, recv_types):
63
+ # note reversal here: Callable is contravariant in argument types
64
+ if not unify(rarg, varg, bindings):
65
+ return False
66
+ return True
67
+
68
+
69
+ def unify_dicts(
70
+ value_types: Optional[Dict[object, Type]],
71
+ recv_types: Optional[Dict[object, Type]],
72
+ bindings: typing.ChainMap[object, Type],
73
+ ) -> bool:
74
+ if value_types is None or recv_types is None:
75
+ return False
76
+ writes: Dict[object, Type] = {}
77
+ sub_bindings = bindings.new_child(writes)
78
+ for recv_key, recv_item_type in recv_types.items():
79
+ value_item_type = value_types.pop(recv_key, None)
80
+ if value_item_type is None:
81
+ return False
82
+ if not unify(value_item_type, recv_item_type, sub_bindings):
83
+ return False
84
+ if value_types:
85
+ return False
86
+ bindings.maps.insert(0, writes)
87
+ return True
88
+
89
+
90
+ def unify(
91
+ value_type: Type,
92
+ recv_type: Type,
93
+ bindings: Optional[typing.ChainMap[object, Type]] = None,
94
+ ) -> bool:
95
+ if bindings is None:
96
+ bindings = collections.ChainMap()
97
+ value_type = bindings.get(value_type, value_type)
98
+ recv_type = bindings.get(recv_type, recv_type)
99
+ if value_type == Any or recv_type == Any:
100
+ return True
101
+
102
+ # Unions
103
+ if typing_inspect.is_union_type(value_type):
104
+ for value_subtype in typing_inspect.get_args(value_type):
105
+ writes: Dict[object, Type] = {}
106
+ sub_bindings = bindings.new_child(writes)
107
+ if not unify(value_subtype, recv_type, sub_bindings):
108
+ return False
109
+ # Right now, we just discard the bindings here.
110
+ # In theory, we could save bindings that are unifyable across all iterations.
111
+ return True
112
+ if typing_inspect.is_union_type(recv_type):
113
+ for recv_subtype in typing_inspect.get_args(recv_type):
114
+ writes = {}
115
+ sub_bindings = bindings.new_child(writes)
116
+ if unify(value_type, recv_subtype, sub_bindings):
117
+ bindings.update(writes)
118
+ return True
119
+ return False
120
+
121
+ # TypeVars
122
+ if typing_inspect.is_typevar(recv_type):
123
+ assert recv_type not in bindings
124
+ bindings[recv_type] = value_type
125
+ return True
126
+ if typing_inspect.is_typevar(value_type):
127
+ value_type = object # TODO consider typevar bounds etc?
128
+ vorigin, rorigin = origin_of(value_type), origin_of(recv_type)
129
+
130
+ # TypedDicts
131
+ recv_required_keys: frozenset = getattr(recv_type, "__required_keys__", _EMPTYSET)
132
+ value_required_keys: frozenset = getattr(value_type, "__required_keys__", _EMPTYSET)
133
+ if recv_required_keys or value_required_keys:
134
+ if not recv_required_keys and value_required_keys:
135
+ return False
136
+ recv_fields = recv_type.__annotations__
137
+ value_fields = value_type.__annotations__
138
+
139
+ def filtered_dict(d: dict, fields: frozenset):
140
+ return {k: v for (k, v) in d.items() if k in fields}
141
+
142
+ if not unify_dicts(
143
+ filtered_dict(value_fields, value_required_keys),
144
+ filtered_dict(recv_fields, recv_required_keys),
145
+ bindings,
146
+ ):
147
+ return False
148
+ recv_opt_keys: frozenset = getattr(recv_type, "__optional_keys__", _EMPTYSET)
149
+ value_opt_keys: frozenset = getattr(value_type, "__optional_keys__", _EMPTYSET)
150
+ common_keys = recv_opt_keys & value_opt_keys
151
+ return unify_dicts(
152
+ filtered_dict(value_fields, common_keys),
153
+ filtered_dict(recv_fields, common_keys),
154
+ bindings,
155
+ )
156
+
157
+ # Tuples
158
+ if vorigin is tuple:
159
+ args = getattr(value_type, "__args__", (object, ...))
160
+ if (len(args) == 2 and args[-1] == ...) or len(set(args)) <= 1:
161
+ arg_type = args[0] if args else object
162
+ writes = {}
163
+ sub_bindings = bindings.new_child(writes)
164
+ if unify(Sequence[arg_type], recv_type, sub_bindings): # type:ignore
165
+ bindings.update(writes)
166
+ return True
167
+ if args[-1] == ...:
168
+ value_type = tuple
169
+ if rorigin is tuple:
170
+ args = getattr(recv_type, "__args__", (object, ...))
171
+ if len(args) == 2 and args[-1] == ...:
172
+ arg_type = args[0]
173
+ writes = {}
174
+ sub_bindings = bindings.new_child(writes)
175
+ if unify(value_type, Sequence[arg_type], sub_bindings): # type:ignore
176
+ bindings.update(writes)
177
+ return True
178
+ value_type = tuple
179
+
180
+ if issubclass(vorigin, rorigin):
181
+
182
+ def arg_getter(typ):
183
+ origin = origin_of(typ)
184
+ if not getattr(typ, "__args__", True):
185
+ args = []
186
+ else:
187
+ args = list(typing_inspect.get_args(typ, evaluate=True))
188
+ # if origin == tuple and len(args) == 2 and args[1] == ...:
189
+ # args = [args[0]]
190
+ if issubclass(origin, collections.abc.Callable):
191
+ if not args:
192
+ args = [..., Any]
193
+ return args
194
+
195
+ vargs = arg_getter(value_type)
196
+ rargs = arg_getter(recv_type)
197
+ if issubclass(rorigin, collections.abc.Callable): # type: ignore
198
+ (vcallargs, vcallreturn) = vargs
199
+ (rcallargs, rcallreturn) = rargs
200
+ if not unify(vcallreturn, rcallreturn, bindings):
201
+ return False
202
+ return unify_callable_args(vcallargs, rcallargs, bindings)
203
+ # if one type has type arguments and the other doesn't, we unify whatever types we can:
204
+ if len(vargs) != len(rargs):
205
+ if len(vargs) == 0:
206
+ vargs = [object for _ in rargs]
207
+ else:
208
+ return False
209
+ for varg, targ in zip(vargs, rargs):
210
+ if not unify(varg, targ, bindings):
211
+ return False
212
+ return True
213
+ # print('Failed to unify value type ', value_type, '(origin=', vorigin, ') with recv type ', recv_type, '(origin=', rorigin, ')')
214
+ return False
215
+
216
+
217
+ def get_bindings_from_type_arguments(pytype: Type) -> Mapping[object, type]:
218
+ # NOTE: sadly, this won't work for builtin containers, e.g. `List[int]`
219
+ if hasattr(pytype, "__args__"):
220
+ args = pytype.__args__
221
+ params = typing_inspect.get_parameters(typing_inspect.get_origin(pytype))
222
+ if len(params) == len(args):
223
+ return dict(zip(params, args))
224
+ return {}
225
+
226
+
227
+ if sys.version_info >= (3, 9):
228
+
229
+ def realize(pytype: Type, bindings: Mapping[object, type]) -> object:
230
+ if typing_inspect.is_typevar(pytype):
231
+ return bindings[pytype]
232
+ if not hasattr(pytype, "__args__"):
233
+ return pytype
234
+ newargs: List = []
235
+ for arg in pytype.__args__: # type:ignore
236
+ newargs.append(realize(arg, bindings))
237
+ pytype_origin = origin_of(pytype)
238
+ if pytype_origin in (
239
+ collections.abc.Callable,
240
+ typing.Callable,
241
+ ): # Callable args get flattened
242
+ newargs = [newargs[:-1], newargs[-1]]
243
+ return pytype_origin.__class_getitem__(tuple(newargs))
244
+
245
+ else:
246
+
247
+ def realize(pytype: Type, bindings: Mapping[object, type]) -> object:
248
+ if typing_inspect.is_typevar(pytype):
249
+ return bindings[pytype]
250
+ if not hasattr(pytype, "__args__"):
251
+ return pytype
252
+ newargs: List = []
253
+ for arg in pytype.__args__: # type:ignore
254
+ newargs.append(realize(arg, bindings))
255
+ # print('realizing pytype', repr(pytype), 'newargs', repr(newargs))
256
+ pytype_origin = origin_of(pytype)
257
+ if not hasattr(pytype_origin, "_name"):
258
+ pytype_origin = getattr(typing, pytype._name) # type:ignore
259
+ if pytype_origin is Callable: # Callable args get flattened
260
+ newargs = [newargs[:-1], newargs[-1]]
261
+ return pytype_origin.__getitem__(tuple(newargs))
262
+
263
+
264
+ def isolate_var_params(
265
+ sig: Signature,
266
+ ) -> Tuple[
267
+ List[Parameter], Dict[str, Parameter], Optional[Parameter], Optional[Parameter]
268
+ ]:
269
+ pos_only_params: List[Parameter] = []
270
+ keyword_params: Dict[str, Parameter] = {}
271
+ var_positional: Optional[Parameter] = None
272
+ var_keyword: Optional[Parameter] = None
273
+ for name, param in sig.parameters.items():
274
+ if param.kind == Parameter.VAR_POSITIONAL:
275
+ var_positional = param
276
+ elif param.kind == Parameter.VAR_KEYWORD:
277
+ var_keyword = param
278
+ elif param.kind == Parameter.POSITIONAL_ONLY:
279
+ pos_only_params.append(param)
280
+ else:
281
+ keyword_params[name] = param
282
+ return pos_only_params, keyword_params, var_positional, var_keyword
283
+
284
+
285
+ def intersect_signatures(
286
+ sig1: Signature,
287
+ sig2: Signature,
288
+ ) -> Signature:
289
+ """
290
+ Approximate the intersection of two signatures.
291
+ The resulting signature may be overly loose
292
+ (matching some inputs that neither of the original signatures would match),
293
+ but it should cover all the inputs for each original signature.
294
+
295
+ One minor exception: All arguments that are allowed to be called as
296
+ keyword arguments will be converted to keyword-only arguments.
297
+ We do this to resolve the abiguity when position-or-keyword arguments
298
+ appear in the same position but with different names.
299
+ """
300
+ pos1, key1, var_pos1, var_key1 = isolate_var_params(sig1)
301
+ pos2, key2, var_pos2, var_key2 = isolate_var_params(sig2)
302
+ is_squishy1 = var_pos1 is not None or var_key1 is not None
303
+ is_squishy2 = var_pos2 is not None or var_key2 is not None
304
+ out_params: Dict[str, Parameter] = {}
305
+ for p1, p2 in zip_longest(pos1, pos2):
306
+ if p1 is None:
307
+ if is_squishy1:
308
+ out_params[p2.name] = p2
309
+ elif p2 is None:
310
+ if is_squishy2:
311
+ out_params[p1.name] = p1
312
+ elif unify(p1.annotation, p2.annotation):
313
+ out_params[p1.name] = p1
314
+ else:
315
+ out_params[p2.name] = p2
316
+ for key in [
317
+ k
318
+ for pair in zip_longest(key1.keys(), key2.keys())
319
+ for k in pair
320
+ if k is not None
321
+ ]:
322
+ if key not in key2:
323
+ if is_squishy2:
324
+ out_params[key] = key1[key].replace(kind=Parameter.KEYWORD_ONLY)
325
+ continue
326
+ if key not in key1:
327
+ if is_squishy1:
328
+ out_params[key] = key2[key].replace(kind=Parameter.KEYWORD_ONLY)
329
+ continue
330
+ if unify(key1[key].annotation, key2[key].annotation):
331
+ out_params[key] = key1[key].replace(kind=Parameter.KEYWORD_ONLY)
332
+ else:
333
+ out_params[key] = key2[key].replace(kind=Parameter.KEYWORD_ONLY)
334
+ if var_pos1 and var_pos2:
335
+ out_params[var_pos1.name] = var_pos1
336
+ if var_key1 and var_key2:
337
+ out_params[var_key1.name] = var_key1
338
+ if unify(sig1.return_annotation, sig2.return_annotation):
339
+ out_return_annotation = sig1.return_annotation
340
+ else:
341
+ out_return_annotation = sig2.return_annotation
342
+ result = Signature(
343
+ parameters=list(out_params.values()), return_annotation=out_return_annotation
344
+ )
345
+ debug("Combined __init__ and __new__ signatures", sig1, "and", sig2, "into", result)
346
+ return result
@@ -0,0 +1,210 @@
1
+ import collections
2
+ import sys
3
+ from inspect import Parameter, Signature, signature
4
+ from typing import (
5
+ Callable,
6
+ Dict,
7
+ Generic,
8
+ Iterable,
9
+ List,
10
+ Mapping,
11
+ Optional,
12
+ Sequence,
13
+ Tuple,
14
+ TypeVar,
15
+ Union,
16
+ )
17
+
18
+ import pytest
19
+ from typing_extensions import TypedDict
20
+
21
+ from crosshair.dynamic_typing import (
22
+ get_bindings_from_type_arguments,
23
+ intersect_signatures,
24
+ realize,
25
+ unify,
26
+ )
27
+ from crosshair.options import AnalysisOptionSet
28
+ from crosshair.statespace import CANNOT_CONFIRM
29
+ from crosshair.test_util import check_states
30
+
31
+ _T = TypeVar("_T")
32
+ _U = TypeVar("_U")
33
+
34
+
35
+ def test_raw_tuple():
36
+ bindings = collections.ChainMap()
37
+ assert unify(tuple, Iterable[_T], bindings)
38
+
39
+
40
+ def test_typedicts():
41
+ class A1(TypedDict):
42
+ x: bool
43
+
44
+ class A2(A1, total=False):
45
+ y: bool # can be omitted because total=False
46
+
47
+ class B1(TypedDict):
48
+ x: int
49
+
50
+ class B2(B1, total=False):
51
+ y: str
52
+
53
+ bindings = collections.ChainMap()
54
+ assert unify(A1, B1, bindings)
55
+ assert unify(A2, B1, bindings)
56
+ assert unify(A1, A2, bindings)
57
+ assert unify(B1, B2, bindings)
58
+ assert not unify(A2, B2, bindings) # (because the types for "y" are incompatible)
59
+
60
+
61
+ def test_typevars():
62
+ bindings = collections.ChainMap()
63
+ assert unify(Tuple[int, str, List[int]], Tuple[int, _T, _U], bindings)
64
+
65
+ ret = realize(Mapping[_U, _T], bindings)
66
+ if sys.version_info >= (3, 9):
67
+ assert ret == collections.abc.Mapping[List[int], str]
68
+ else:
69
+ assert ret == Mapping[List[int], str]
70
+
71
+
72
+ def test_bound_vtypears():
73
+ assert unify(Dict[str, int], Dict[_T, _U])
74
+ assert not (unify(Dict[str, int], Dict[_T, _T]))
75
+
76
+
77
+ def test_zero_type_args_ok():
78
+ assert unify(map, Iterable[_T])
79
+ assert not (unify(map, Iterable[int]))
80
+
81
+
82
+ def test_callable():
83
+ bindings = collections.ChainMap()
84
+ assert unify(Callable[[Iterable], bool], Callable[[List], bool], bindings)
85
+
86
+ assert not unify(Callable[[List], bool], Callable[[Iterable], bool], bindings)
87
+ assert unify(Callable[[int, _T], List[int]], Callable[[int, str], _U], bindings)
88
+ if sys.version_info >= (3, 9):
89
+ assert (
90
+ realize(Callable[[_U], _T], bindings)
91
+ == collections.abc.Callable[[List[int]], str]
92
+ )
93
+ else:
94
+ assert realize(Callable[[_U], _T], bindings) == Callable[[List[int]], str]
95
+
96
+
97
+ def test_plain_callable():
98
+ bindings = collections.ChainMap()
99
+ assert unify(Callable[[int, str], List[int]], Callable, bindings)
100
+
101
+
102
+ def test_uniform_tuple():
103
+ bindings = collections.ChainMap()
104
+ assert unify(Tuple[int, int], Tuple[_T, ...], bindings)
105
+ assert bindings[_T] == int
106
+ assert not unify(Tuple[int, str], Tuple[_T, ...], bindings)
107
+
108
+
109
+ def test_tuple():
110
+ bindings = collections.ChainMap()
111
+ assert not unify(tuple, Tuple[int, str], bindings)
112
+
113
+
114
+ def test_union_fail():
115
+ bindings = collections.ChainMap()
116
+ assert not unify(Iterable[int], Union[int, Dict[str, _T]], bindings)
117
+
118
+
119
+ def test_union_ok():
120
+ bindings = collections.ChainMap()
121
+ assert unify(int, Union[str, int], bindings)
122
+ assert unify(Tuple[int, ...], Union[int, Iterable[_T]], bindings)
123
+ assert bindings[_T] == int
124
+
125
+
126
+ def test_union_into_union():
127
+ bindings = collections.ChainMap()
128
+ assert unify(Union[str, int], Union[str, int, float], bindings)
129
+ assert not unify(Union[str, int, float], Union[str, int], bindings)
130
+
131
+
132
+ def test_nested_union():
133
+ bindings = collections.ChainMap()
134
+ assert unify(List[str], Sequence[Union[str, int]], bindings)
135
+
136
+
137
+ class Pair(Generic[_U, _T]):
138
+ def __init__(self, u: _U, t: _T):
139
+ self.u = u
140
+ self.t = t
141
+
142
+
143
+ def test_bindings_from_type_arguments():
144
+ var_mapping = get_bindings_from_type_arguments(Pair[int, str])
145
+ assert var_mapping == {_U: int, _T: str}
146
+ if sys.version_info >= (3, 9):
147
+ assert realize(List[_U], var_mapping) == list[int]
148
+ else:
149
+ assert realize(List[_U], var_mapping) == List[int]
150
+
151
+
152
+ def test_intersect_signatures_basic():
153
+ def f1(x: int, y: str, **kw) -> List[bool]:
154
+ return []
155
+
156
+ def f2(x: bool, *extra: str, **kw) -> List[int]:
157
+ return []
158
+
159
+ intersection = intersect_signatures(signature(f1), signature(f2))
160
+ assert intersection is not None
161
+ assert intersection.parameters == {
162
+ "x": Parameter("x", kind=Parameter.KEYWORD_ONLY, annotation=bool),
163
+ "y": Parameter("y", kind=Parameter.KEYWORD_ONLY, annotation=str),
164
+ "kw": Parameter("kw", kind=Parameter.VAR_KEYWORD),
165
+ }
166
+ assert intersection.return_annotation == List[bool]
167
+
168
+
169
+ def test_intersect_signatures_typevars():
170
+ _T = TypeVar("_T")
171
+
172
+ def f1(cc, *args, **kwds):
173
+ pass
174
+
175
+ def f2(dd, left: Optional[_T], right: Optional[_T]):
176
+ pass
177
+
178
+ intersection = intersect_signatures(signature(f1), signature(f2))
179
+ assert intersection is not None
180
+ expected = {
181
+ "dd": Parameter("dd", kind=Parameter.KEYWORD_ONLY),
182
+ "left": Parameter("left", kind=Parameter.KEYWORD_ONLY, annotation=Optional[_T]),
183
+ "right": Parameter(
184
+ "right", kind=Parameter.KEYWORD_ONLY, annotation=Optional[_T]
185
+ ),
186
+ }
187
+ assert intersection.parameters == expected
188
+
189
+
190
+ @pytest.mark.skip(
191
+ reason="The inspect module doesn't expose runtime type information yet"
192
+ )
193
+ def test_intersect_signature_with_crosshair():
194
+ def check_intersect_signatures(
195
+ sig1: Signature, sig2: Signature, pos_args: List, kw_args: Mapping[str, object]
196
+ ) -> None:
197
+ """post: True"""
198
+
199
+ def _sig_bindable(sig: Signature) -> bool:
200
+ try:
201
+ sig.bind(*pos_args, **kw_args)
202
+ return True
203
+ except TypeError:
204
+ return False
205
+
206
+ if _sig_bindable(sig1) or _sig_bindable(sig2):
207
+ intersection = intersect_signatures(sig1, sig2)
208
+ assert _sig_bindable(intersection)
209
+
210
+ check_states(check_intersect_signatures, CANNOT_CONFIRM)