shrinkray 0.0.0__py3-none-any.whl → 25.12.26.0__py3-none-any.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.
- shrinkray/__main__.py +130 -960
- shrinkray/cli.py +70 -0
- shrinkray/display.py +75 -0
- shrinkray/formatting.py +108 -0
- shrinkray/passes/bytes.py +217 -10
- shrinkray/passes/clangdelta.py +47 -17
- shrinkray/passes/definitions.py +84 -4
- shrinkray/passes/genericlanguages.py +61 -7
- shrinkray/passes/json.py +6 -0
- shrinkray/passes/patching.py +65 -57
- shrinkray/passes/python.py +66 -23
- shrinkray/passes/sat.py +505 -91
- shrinkray/passes/sequences.py +26 -6
- shrinkray/problem.py +206 -27
- shrinkray/process.py +49 -0
- shrinkray/reducer.py +187 -25
- shrinkray/state.py +599 -0
- shrinkray/subprocess/__init__.py +24 -0
- shrinkray/subprocess/client.py +253 -0
- shrinkray/subprocess/protocol.py +190 -0
- shrinkray/subprocess/worker.py +491 -0
- shrinkray/tui.py +915 -0
- shrinkray/ui.py +72 -0
- shrinkray/work.py +34 -6
- {shrinkray-0.0.0.dist-info → shrinkray-25.12.26.0.dist-info}/METADATA +44 -27
- shrinkray-25.12.26.0.dist-info/RECORD +33 -0
- {shrinkray-0.0.0.dist-info → shrinkray-25.12.26.0.dist-info}/WHEEL +2 -1
- shrinkray-25.12.26.0.dist-info/entry_points.txt +3 -0
- shrinkray-25.12.26.0.dist-info/top_level.txt +1 -0
- shrinkray/learning.py +0 -221
- shrinkray-0.0.0.dist-info/RECORD +0 -22
- shrinkray-0.0.0.dist-info/entry_points.txt +0 -3
- {shrinkray-0.0.0.dist-info → shrinkray-25.12.26.0.dist-info/licenses}/LICENSE +0 -0
shrinkray/passes/python.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any, cast
|
|
2
3
|
|
|
3
4
|
import libcst
|
|
4
5
|
import libcst.matchers as m
|
|
@@ -8,7 +9,7 @@ from shrinkray.problem import ReductionProblem
|
|
|
8
9
|
from shrinkray.work import NotFound
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def is_python(source: AnyStr) -> bool:
|
|
12
|
+
def is_python[AnyStr: (str, bytes)](source: AnyStr) -> bool:
|
|
12
13
|
try:
|
|
13
14
|
libcst.parse_module(source)
|
|
14
15
|
return True
|
|
@@ -27,10 +28,20 @@ async def libcst_transform(
|
|
|
27
28
|
Replacement,
|
|
28
29
|
],
|
|
29
30
|
) -> None:
|
|
31
|
+
"""Apply a LibCST transformation to matching nodes in Python source.
|
|
32
|
+
|
|
33
|
+
Parses the test case as Python, finds all nodes matching the given
|
|
34
|
+
matcher, and applies the transformer function to each. Uses binary
|
|
35
|
+
search to efficiently find which transformations maintain interestingness.
|
|
36
|
+
"""
|
|
37
|
+
|
|
30
38
|
class CM(codemod.VisitorBasedCodemodCommand):
|
|
31
|
-
def __init__(
|
|
39
|
+
def __init__(
|
|
40
|
+
self, context: codemod.CodemodContext, start_index: int, end_index: int
|
|
41
|
+
):
|
|
32
42
|
super().__init__(context)
|
|
33
|
-
self.
|
|
43
|
+
self.start_index = start_index
|
|
44
|
+
self.end_index = end_index
|
|
34
45
|
self.current_index = 0
|
|
35
46
|
self.fired = False
|
|
36
47
|
|
|
@@ -39,8 +50,9 @@ async def libcst_transform(
|
|
|
39
50
|
# and we use this generically in a way that makes it hard to type correctly.
|
|
40
51
|
@m.leave(matcher)
|
|
41
52
|
def maybe_change_node(self, _, updated_node): # type: ignore
|
|
42
|
-
if self.current_index
|
|
53
|
+
if self.start_index <= self.current_index < self.end_index:
|
|
43
54
|
self.fired = True
|
|
55
|
+
self.current_index += 1
|
|
44
56
|
return transformer(updated_node)
|
|
45
57
|
else:
|
|
46
58
|
self.current_index += 1
|
|
@@ -53,14 +65,14 @@ async def libcst_transform(
|
|
|
53
65
|
|
|
54
66
|
context = codemod.CodemodContext()
|
|
55
67
|
|
|
56
|
-
counting_mod = CM(context, -1)
|
|
68
|
+
counting_mod = CM(context, -1, -1)
|
|
57
69
|
counting_mod.transform_module(module)
|
|
58
70
|
|
|
59
71
|
n = counting_mod.current_index + 1
|
|
60
72
|
|
|
61
|
-
async def can_apply(
|
|
73
|
+
async def can_apply(start: int, end: int) -> bool:
|
|
62
74
|
nonlocal n
|
|
63
|
-
if
|
|
75
|
+
if start >= n:
|
|
64
76
|
return False
|
|
65
77
|
initial_test_case = problem.current_test_case
|
|
66
78
|
try:
|
|
@@ -69,7 +81,7 @@ async def libcst_transform(
|
|
|
69
81
|
n = 0
|
|
70
82
|
return False
|
|
71
83
|
|
|
72
|
-
codemod_i = CM(context,
|
|
84
|
+
codemod_i = CM(context, start, end)
|
|
73
85
|
try:
|
|
74
86
|
transformed = codemod_i.transform_module(module)
|
|
75
87
|
except libcst.CSTValidationError:
|
|
@@ -80,7 +92,7 @@ async def libcst_transform(
|
|
|
80
92
|
raise
|
|
81
93
|
|
|
82
94
|
if not codemod_i.fired:
|
|
83
|
-
n =
|
|
95
|
+
n = start
|
|
84
96
|
return False
|
|
85
97
|
|
|
86
98
|
transformed_test_case = transformed.code.encode(transformed.encoding)
|
|
@@ -95,12 +107,22 @@ async def libcst_transform(
|
|
|
95
107
|
i = 0
|
|
96
108
|
while i < n:
|
|
97
109
|
try:
|
|
98
|
-
i = await problem.work.find_first_value(
|
|
110
|
+
i = await problem.work.find_first_value(
|
|
111
|
+
range(i, n), lambda i: can_apply(i, i + 1)
|
|
112
|
+
)
|
|
113
|
+
await problem.work.find_large_integer(lambda k: can_apply(i + 1, i + 1 + k))
|
|
114
|
+
i += 1
|
|
99
115
|
except NotFound:
|
|
100
116
|
break
|
|
101
117
|
|
|
102
118
|
|
|
103
119
|
async def lift_indented_constructs(problem: ReductionProblem[bytes]) -> None:
|
|
120
|
+
"""Replace control structures with their body contents.
|
|
121
|
+
|
|
122
|
+
For if/while/try/with statements, removes the else/except clauses and
|
|
123
|
+
then tries to replace the entire construct with just its body. This
|
|
124
|
+
"lifts" the indented code out of its containing structure.
|
|
125
|
+
"""
|
|
104
126
|
await libcst_transform(
|
|
105
127
|
problem,
|
|
106
128
|
m.OneOf(m.While(), m.If(), m.Try()),
|
|
@@ -115,6 +137,11 @@ async def lift_indented_constructs(problem: ReductionProblem[bytes]) -> None:
|
|
|
115
137
|
|
|
116
138
|
|
|
117
139
|
async def delete_statements(problem: ReductionProblem[bytes]) -> None:
|
|
140
|
+
"""Try to delete simple statement lines from Python source.
|
|
141
|
+
|
|
142
|
+
Removes individual statements (assignments, expressions, imports, etc.)
|
|
143
|
+
one at a time to find which can be eliminated.
|
|
144
|
+
"""
|
|
118
145
|
await libcst_transform(
|
|
119
146
|
problem,
|
|
120
147
|
m.SimpleStatementLine(),
|
|
@@ -123,6 +150,11 @@ async def delete_statements(problem: ReductionProblem[bytes]) -> None:
|
|
|
123
150
|
|
|
124
151
|
|
|
125
152
|
async def replace_statements_with_pass(problem: ReductionProblem[bytes]) -> None:
|
|
153
|
+
"""Replace statement lines with 'pass' statements.
|
|
154
|
+
|
|
155
|
+
Useful when a statement can't be deleted outright (e.g., it's the only
|
|
156
|
+
statement in a block) but can be replaced with a no-op.
|
|
157
|
+
"""
|
|
126
158
|
await libcst_transform(
|
|
127
159
|
problem,
|
|
128
160
|
m.SimpleStatementLine(),
|
|
@@ -134,6 +166,11 @@ ELLIPSIS_STATEMENT = libcst.parse_statement("...")
|
|
|
134
166
|
|
|
135
167
|
|
|
136
168
|
async def replace_bodies_with_ellipsis(problem: ReductionProblem[bytes]) -> None:
|
|
169
|
+
"""Replace indented block bodies with '...' (ellipsis).
|
|
170
|
+
|
|
171
|
+
Replaces function bodies, class bodies, and other indented blocks
|
|
172
|
+
with just an ellipsis statement. Useful for stubbing out implementations.
|
|
173
|
+
"""
|
|
137
174
|
await libcst_transform(
|
|
138
175
|
problem,
|
|
139
176
|
m.IndentedBlock(),
|
|
@@ -142,6 +179,12 @@ async def replace_bodies_with_ellipsis(problem: ReductionProblem[bytes]) -> None
|
|
|
142
179
|
|
|
143
180
|
|
|
144
181
|
async def strip_annotations(problem: ReductionProblem[bytes]) -> None:
|
|
182
|
+
"""Remove type annotations from Python source.
|
|
183
|
+
|
|
184
|
+
Strips return type annotations from functions, parameter annotations,
|
|
185
|
+
and converts annotated assignments to plain assignments. Type annotations
|
|
186
|
+
are often unnecessary for reproducing bugs.
|
|
187
|
+
"""
|
|
145
188
|
await libcst_transform(
|
|
146
189
|
problem,
|
|
147
190
|
m.FunctionDef(),
|
|
@@ -152,19 +195,19 @@ async def strip_annotations(problem: ReductionProblem[bytes]) -> None:
|
|
|
152
195
|
m.Param(),
|
|
153
196
|
lambda x: x.with_changes(annotation=None),
|
|
154
197
|
)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
libcst.Assign(
|
|
160
|
-
targets=[libcst.AssignTarget(target=
|
|
161
|
-
value=
|
|
162
|
-
semicolon=
|
|
198
|
+
|
|
199
|
+
def ann_assign_to_assign(x: libcst.CSTNode) -> Replacement:
|
|
200
|
+
ann = cast(libcst.AnnAssign, x)
|
|
201
|
+
if ann.value is not None:
|
|
202
|
+
return libcst.Assign(
|
|
203
|
+
targets=[libcst.AssignTarget(target=ann.target)],
|
|
204
|
+
value=ann.value,
|
|
205
|
+
semicolon=ann.semicolon,
|
|
163
206
|
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
207
|
+
else:
|
|
208
|
+
return libcst.RemoveFromParent()
|
|
209
|
+
|
|
210
|
+
await libcst_transform(problem, m.AnnAssign(), ann_assign_to_assign)
|
|
168
211
|
|
|
169
212
|
|
|
170
213
|
PYTHON_PASSES = [
|