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.
- _crosshair_tracers.cpython-312-darwin.so +0 -0
- crosshair/__init__.py +42 -0
- crosshair/__main__.py +8 -0
- crosshair/_mark_stacks.h +790 -0
- crosshair/_preliminaries_test.py +18 -0
- crosshair/_tracers.h +94 -0
- crosshair/_tracers_pycompat.h +522 -0
- crosshair/_tracers_test.py +138 -0
- crosshair/abcstring.py +245 -0
- crosshair/auditwall.py +190 -0
- crosshair/auditwall_test.py +77 -0
- crosshair/codeconfig.py +113 -0
- crosshair/codeconfig_test.py +117 -0
- crosshair/condition_parser.py +1237 -0
- crosshair/condition_parser_test.py +497 -0
- crosshair/conftest.py +30 -0
- crosshair/copyext.py +155 -0
- crosshair/copyext_test.py +84 -0
- crosshair/core.py +1763 -0
- crosshair/core_and_libs.py +149 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +1316 -0
- crosshair/diff_behavior.py +314 -0
- crosshair/diff_behavior_test.py +261 -0
- crosshair/dynamic_typing.py +346 -0
- crosshair/dynamic_typing_test.py +210 -0
- crosshair/enforce.py +282 -0
- crosshair/enforce_test.py +182 -0
- crosshair/examples/PEP316/__init__.py +1 -0
- crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
- crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
- crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
- crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
- crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
- crosshair/examples/PEP316/correct_code/__init__.py +0 -0
- crosshair/examples/PEP316/correct_code/arith.py +60 -0
- crosshair/examples/PEP316/correct_code/chess.py +77 -0
- crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
- crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
- crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
- crosshair/examples/PEP316/correct_code/showcase.py +104 -0
- crosshair/examples/__init__.py +0 -0
- crosshair/examples/check_examples_test.py +146 -0
- crosshair/examples/deal/__init__.py +1 -0
- crosshair/examples/icontract/__init__.py +1 -0
- crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
- crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
- crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
- crosshair/examples/icontract/correct_code/__init__.py +0 -0
- crosshair/examples/icontract/correct_code/arith.py +51 -0
- crosshair/examples/icontract/correct_code/showcase.py +94 -0
- crosshair/fnutil.py +391 -0
- crosshair/fnutil_test.py +75 -0
- crosshair/fuzz_core_test.py +516 -0
- crosshair/libimpl/__init__.py +0 -0
- crosshair/libimpl/arraylib.py +161 -0
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +23 -0
- crosshair/libimpl/builtinslib.py +5228 -0
- crosshair/libimpl/builtinslib_ch_test.py +1191 -0
- crosshair/libimpl/builtinslib_test.py +3735 -0
- crosshair/libimpl/codecslib.py +86 -0
- crosshair/libimpl/codecslib_test.py +86 -0
- crosshair/libimpl/collectionslib.py +264 -0
- crosshair/libimpl/collectionslib_ch_test.py +252 -0
- crosshair/libimpl/collectionslib_test.py +332 -0
- crosshair/libimpl/copylib.py +23 -0
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +2559 -0
- crosshair/libimpl/datetimelib_ch_test.py +354 -0
- crosshair/libimpl/datetimelib_test.py +112 -0
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/__init__.py +23 -0
- crosshair/libimpl/encodings/_encutil.py +187 -0
- crosshair/libimpl/encodings/ascii.py +44 -0
- crosshair/libimpl/encodings/latin_1.py +40 -0
- crosshair/libimpl/encodings/utf_8.py +93 -0
- crosshair/libimpl/encodings_ch_test.py +83 -0
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +34 -0
- crosshair/libimpl/functoolslib_test.py +56 -0
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +47 -0
- crosshair/libimpl/heapqlib_test.py +21 -0
- crosshair/libimpl/importliblib.py +18 -0
- crosshair/libimpl/importliblib_test.py +38 -0
- crosshair/libimpl/iolib.py +216 -0
- crosshair/libimpl/iolib_ch_test.py +128 -0
- crosshair/libimpl/iolib_test.py +19 -0
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib.py +44 -0
- crosshair/libimpl/itertoolslib_test.py +44 -0
- crosshair/libimpl/jsonlib.py +984 -0
- crosshair/libimpl/jsonlib_ch_test.py +42 -0
- crosshair/libimpl/jsonlib_test.py +51 -0
- crosshair/libimpl/mathlib.py +179 -0
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +67 -0
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +178 -0
- crosshair/libimpl/randomlib_test.py +120 -0
- crosshair/libimpl/relib.py +846 -0
- crosshair/libimpl/relib_ch_test.py +169 -0
- crosshair/libimpl/relib_test.py +493 -0
- crosshair/libimpl/timelib.py +72 -0
- crosshair/libimpl/timelib_test.py +82 -0
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib.py +75 -0
- crosshair/libimpl/unicodedatalib_test.py +42 -0
- crosshair/libimpl/urlliblib.py +23 -0
- crosshair/libimpl/urlliblib_test.py +19 -0
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +261 -0
- crosshair/lsp_server_test.py +30 -0
- crosshair/main.py +973 -0
- crosshair/main_test.py +543 -0
- crosshair/objectproxy.py +376 -0
- crosshair/objectproxy_test.py +41 -0
- crosshair/opcode_intercept.py +601 -0
- crosshair/opcode_intercept_test.py +304 -0
- crosshair/options.py +218 -0
- crosshair/options_test.py +10 -0
- crosshair/patch_equivalence_test.py +75 -0
- crosshair/path_cover.py +209 -0
- crosshair/path_cover_test.py +138 -0
- crosshair/path_search.py +161 -0
- crosshair/path_search_test.py +52 -0
- crosshair/pathing_oracle.py +271 -0
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer.py +27 -0
- crosshair/pure_importer_test.py +16 -0
- crosshair/py.typed +0 -0
- crosshair/register_contract.py +273 -0
- crosshair/register_contract_test.py +190 -0
- crosshair/simplestructs.py +1165 -0
- crosshair/simplestructs_test.py +283 -0
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +1199 -0
- crosshair/statespace_test.py +108 -0
- crosshair/stubs_parser.py +352 -0
- crosshair/stubs_parser_test.py +43 -0
- crosshair/test_util.py +329 -0
- crosshair/test_util_test.py +26 -0
- crosshair/tools/__init__.py +0 -0
- crosshair/tools/check_help_in_doc.py +264 -0
- crosshair/tools/check_init_and_setup_coincide.py +119 -0
- crosshair/tools/generate_demo_table.py +127 -0
- crosshair/tracers.py +544 -0
- crosshair/tracers_test.py +154 -0
- crosshair/type_repo.py +151 -0
- crosshair/unicode_categories.py +589 -0
- crosshair/unicode_categories_test.py +27 -0
- crosshair/util.py +741 -0
- crosshair/util_test.py +173 -0
- crosshair/watcher.py +307 -0
- crosshair/watcher_test.py +107 -0
- crosshair/z3util.py +76 -0
- crosshair/z3util_test.py +11 -0
- crosshair_tool-0.0.99.dist-info/METADATA +144 -0
- crosshair_tool-0.0.99.dist-info/RECORD +176 -0
- crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
- crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
- crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
- 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)
|