annoscript 0.1.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.
- annoscript/__init__.py +1 -0
- annoscript/_backend_profiles.py +17 -0
- annoscript/_client_common.py +287 -0
- annoscript/_data.py +37 -0
- annoscript/_dispatch_pool.py +47 -0
- annoscript/_env.py +19 -0
- annoscript/_examples.py +34 -0
- annoscript/_expressions.py +93 -0
- annoscript/_plan_viewer.html +953 -0
- annoscript/_remote_jobs.py +211 -0
- annoscript/_request_adapters.py +71 -0
- annoscript/_speculation.py +321 -0
- annoscript/_test_artifacts.py +33 -0
- annoscript/_test_evaluation_http.py +117 -0
- annoscript/_test_ir_runtime.py +138 -0
- annoscript/_test_job_process.py +166 -0
- annoscript/_test_plan_runtime.py +331 -0
- annoscript/_test_remote_client.py +46 -0
- annoscript/_test_remote_server.py +75 -0
- annoscript/_test_synthetic.py +67 -0
- annoscript/anno.py +270 -0
- annoscript/backends/__init__.py +13 -0
- annoscript/backends/_batch_wire.py +26 -0
- annoscript/backends/_bounded_grammar.py +97 -0
- annoscript/backends/_example_layout.py +23 -0
- annoscript/backends/_generation_policy.py +116 -0
- annoscript/backends/_grammar.py +83 -0
- annoscript/backends/_preview.py +13 -0
- annoscript/backends/_prompt.py +115 -0
- annoscript/backends/_shared_rules.py +38 -0
- annoscript/backends/adapters/__init__.py +17 -0
- annoscript/backends/adapters/chat.py +39 -0
- annoscript/backends/adapters/decision.py +119 -0
- annoscript/backends/base.py +427 -0
- annoscript/backends/config.py +67 -0
- annoscript/backends/decision.py +150 -0
- annoscript/backends/llm.py +338 -0
- annoscript/client.py +247 -0
- annoscript/client_tests.py +17 -0
- annoscript/compiler.py +708 -0
- annoscript/cross_version_tests.py +68 -0
- annoscript/fixtures/request-adapters-v1.json +4741 -0
- annoscript/formatter.py +171 -0
- annoscript/grammar_tests.py +176 -0
- annoscript/inspect.py +77 -0
- annoscript/ir.py +458 -0
- annoscript/job_cross_version_tests.py +118 -0
- annoscript/job_protocol.py +180 -0
- annoscript/jobs.py +255 -0
- annoscript/metrics.py +153 -0
- annoscript/p0_grammar_tests.py +147 -0
- annoscript/passes/__init__.py +40 -0
- annoscript/passes/_analysis.py +178 -0
- annoscript/passes/control_flow.py +68 -0
- annoscript/passes/dead_assignments.py +44 -0
- annoscript/passes/folding.py +104 -0
- annoscript/passes/propagation.py +53 -0
- annoscript/passes/temporaries.py +45 -0
- annoscript/plan.py +542 -0
- annoscript/plan_formatter.py +452 -0
- annoscript/plan_lowering.py +571 -0
- annoscript/preview.py +115 -0
- annoscript/remote.py +304 -0
- annoscript/runtime.py +861 -0
- annoscript/server/__init__.py +1 -0
- annoscript/server/__main__.py +49 -0
- annoscript/server/_plan_cache.py +79 -0
- annoscript/server/api.py +146 -0
- annoscript/server/metrics.py +140 -0
- annoscript/server/migrate.py +61 -0
- annoscript/server/releases.py +143 -0
- annoscript/server/store.py +429 -0
- annoscript/server/worker.py +290 -0
- annoscript/substring_tests.py +79 -0
- annoscript/test_advisory_lengths.py +115 -0
- annoscript/test_backends.py +181 -0
- annoscript/test_client.py +349 -0
- annoscript/test_compact_batch.py +95 -0
- annoscript/test_compiler.py +444 -0
- annoscript/test_decision_adapters.py +410 -0
- annoscript/test_decision_wire.py +190 -0
- annoscript/test_diagnostics.py +97 -0
- annoscript/test_engineering_p0.py +191 -0
- annoscript/test_environment.py +65 -0
- annoscript/test_example_backends.py +251 -0
- annoscript/test_examples.py +149 -0
- annoscript/test_field_names.py +221 -0
- annoscript/test_formatter.py +153 -0
- annoscript/test_fusion_scheduling.py +109 -0
- annoscript/test_generation_policy.py +180 -0
- annoscript/test_ir.py +148 -0
- annoscript/test_jobs.py +371 -0
- annoscript/test_jobs_server.py +511 -0
- annoscript/test_llm_batch.py +428 -0
- annoscript/test_metrics.py +59 -0
- annoscript/test_metrics_server.py +261 -0
- annoscript/test_minimal_fusion.py +59 -0
- annoscript/test_nullable_generate.py +169 -0
- annoscript/test_passes.py +408 -0
- annoscript/test_plan.py +522 -0
- annoscript/test_plan_constraints.py +305 -0
- annoscript/test_plan_formatter.py +272 -0
- annoscript/test_prompt_layout.py +140 -0
- annoscript/test_qwen_backend.py +181 -0
- annoscript/test_qwen_pilot_regression.py +180 -0
- annoscript/test_release_client.py +20 -0
- annoscript/test_remote.py +308 -0
- annoscript/test_request_adapters.py +295 -0
- annoscript/test_requirements.py +228 -0
- annoscript/test_rule_dedup.py +67 -0
- annoscript/test_runtime.py +314 -0
- annoscript/test_runtime_client.py +133 -0
- annoscript/test_source_fields.py +111 -0
- annoscript/test_speculation.py +458 -0
- annoscript/test_substring_backend.py +81 -0
- annoscript/test_synthetic_example.py +61 -0
- annoscript-0.1.0.dist-info/METADATA +145 -0
- annoscript-0.1.0.dist-info/RECORD +128 -0
- annoscript-0.1.0.dist-info/WHEEL +4 -0
- annoscript-0.1.0.dist-info/licenses/LICENSE +21 -0
- annoscript_examples/__init__.py +1 -0
- annoscript_examples/fixtures.py +3 -0
- annoscript_examples/request.execution_plan.json +3017 -0
- annoscript_examples/request.json +858 -0
- annoscript_examples/request.py +37 -0
- annoscript_examples/request_preview.decision.json +23 -0
- annoscript_examples/request_preview.generation.json +20 -0
- annoscript_examples/run.py +24 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Optimization regressions, including IR that did not come from our compiler."""
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import replace
|
|
5
|
+
import json
|
|
6
|
+
import random
|
|
7
|
+
import unittest
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
from annoscript import ir
|
|
11
|
+
from annoscript._test_ir_runtime import evaluate
|
|
12
|
+
from annoscript.compiler import compile_program
|
|
13
|
+
from annoscript.passes import optimize_program
|
|
14
|
+
from annoscript.passes._analysis import walk_statements
|
|
15
|
+
from annoscript.passes.control_flow import simplify_control_flow
|
|
16
|
+
from annoscript.passes.dead_assignments import eliminate_dead_assignments
|
|
17
|
+
from annoscript.passes.folding import fold_constants
|
|
18
|
+
from annoscript.passes.propagation import propagate_values
|
|
19
|
+
from annoscript.passes.temporaries import merge_temporaries
|
|
20
|
+
from annoscript.test_compiler import Probe
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
L = ir.Literal
|
|
24
|
+
V = ir.Var
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def alias_snapshot(anno):
|
|
28
|
+
data = {"value": "before"}
|
|
29
|
+
alias = data
|
|
30
|
+
first = data["value"]
|
|
31
|
+
alias["value"] = "after"
|
|
32
|
+
return [first, data, alias]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def consume_iterator(anno):
|
|
36
|
+
iterator = enumerate(anno.state)
|
|
37
|
+
alias = iterator
|
|
38
|
+
result = []
|
|
39
|
+
for index, value in iterator:
|
|
40
|
+
result = result + [value]
|
|
41
|
+
for index, value in alias:
|
|
42
|
+
result = result + ["must not happen"]
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def loop_snapshots(anno):
|
|
47
|
+
current = "initial"
|
|
48
|
+
initial = current
|
|
49
|
+
result = []
|
|
50
|
+
for value in anno.state:
|
|
51
|
+
previous = current
|
|
52
|
+
current = value
|
|
53
|
+
result = result + [previous, current, initial]
|
|
54
|
+
return [current, result]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def unused_failure(anno):
|
|
58
|
+
anno.generate("before", max_length=512)
|
|
59
|
+
unused = int(anno.state)
|
|
60
|
+
anno.generate("after", max_length=512)
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PassTests(unittest.TestCase):
|
|
65
|
+
def setUp(self):
|
|
66
|
+
self.serial = 0
|
|
67
|
+
self.span = ir.SourceSpan("unavailable.module", "entry", 1, 1, 2, 1)
|
|
68
|
+
self.source = ir.Source(self.span, (self.span,))
|
|
69
|
+
|
|
70
|
+
def s(self, cls, *args, **kwargs):
|
|
71
|
+
self.serial += 1
|
|
72
|
+
return cls(*args, **kwargs, id=f"statement-{self.serial}", source=self.source)
|
|
73
|
+
|
|
74
|
+
def program(self, *body):
|
|
75
|
+
return ir.Program("entry", ir.ProgramConfig("中文规则"), tuple(body), self.span)
|
|
76
|
+
|
|
77
|
+
def outcome(self, program, state=None):
|
|
78
|
+
context = Probe(deepcopy(state))
|
|
79
|
+
try:
|
|
80
|
+
value = evaluate(program.to_dict(), context)
|
|
81
|
+
# Preserve distinctions such as False/0 and -0.0/0.0 in comparisons.
|
|
82
|
+
outcome = ("return", json.dumps(value, ensure_ascii=False))
|
|
83
|
+
except Exception as exc:
|
|
84
|
+
outcome = (type(exc).__name__, str(exc))
|
|
85
|
+
return outcome, context.calls, context.state
|
|
86
|
+
|
|
87
|
+
def equivalent(self, program, states=(None,), transform=optimize_program):
|
|
88
|
+
before = program.to_json()
|
|
89
|
+
optimized = transform(program)
|
|
90
|
+
for state in states:
|
|
91
|
+
with self.subTest(state=state):
|
|
92
|
+
self.assertEqual(self.outcome(program, state), self.outcome(optimized, state))
|
|
93
|
+
self.assertEqual(program.to_json(), before)
|
|
94
|
+
return optimized
|
|
95
|
+
|
|
96
|
+
def test_copy_rebinding_preserves_old_value(self):
|
|
97
|
+
program = self.program(
|
|
98
|
+
self.s(ir.Assign, "a", ir.State()),
|
|
99
|
+
self.s(ir.Assign, "b", V("a")),
|
|
100
|
+
self.s(ir.Assign, "c", V("b")),
|
|
101
|
+
self.s(ir.Assign, "a", L("new")),
|
|
102
|
+
self.s(ir.Return, ir.ListExpr((V("a"), V("b"), V("c")))),
|
|
103
|
+
)
|
|
104
|
+
self.equivalent(program, ("old", None), propagate_values)
|
|
105
|
+
self.equivalent(program, ("old", None))
|
|
106
|
+
|
|
107
|
+
def test_branch_join_preserves_scalar_types_and_signed_zero(self):
|
|
108
|
+
for left, right in ((True, 1), (1, 1.0), (0.0, -0.0)):
|
|
109
|
+
program = self.program(
|
|
110
|
+
self.s(ir.If, ir.State(), (self.s(ir.Assign, "x", L(left)),),
|
|
111
|
+
(self.s(ir.Assign, "x", L(right)),)),
|
|
112
|
+
self.s(ir.Return, V("x")),
|
|
113
|
+
)
|
|
114
|
+
result = self.equivalent(program, (True, False))
|
|
115
|
+
self.assertIsInstance(result.body[-1].value, ir.Var)
|
|
116
|
+
|
|
117
|
+
def test_branch_with_return_does_not_pollute_join(self):
|
|
118
|
+
program = self.program(
|
|
119
|
+
self.s(ir.If, ir.State(), (self.s(ir.Return, L("early")),),
|
|
120
|
+
(self.s(ir.Assign, "x", L("late")),)),
|
|
121
|
+
self.s(ir.Return, V("x")),
|
|
122
|
+
)
|
|
123
|
+
result = self.equivalent(program, (False, True), propagate_values)
|
|
124
|
+
self.assertEqual(result.body[-1].value, L("late"))
|
|
125
|
+
|
|
126
|
+
def test_copy_and_constant_loop_invalidation(self):
|
|
127
|
+
program = self.program(
|
|
128
|
+
self.s(ir.Assign, "value", L("first")),
|
|
129
|
+
self.s(ir.Assign, "snapshot", V("value")),
|
|
130
|
+
self.s(ir.Assign, "last", L(None)),
|
|
131
|
+
self.s(ir.For, ir.NameTarget("value"), ir.State(), (
|
|
132
|
+
self.s(ir.Assign, "last", V("value")),
|
|
133
|
+
)),
|
|
134
|
+
self.s(ir.Return, ir.ListExpr((V("value"), V("snapshot"), V("last")))),
|
|
135
|
+
)
|
|
136
|
+
self.equivalent(program, ([], ["a"], ["a", "b"]))
|
|
137
|
+
|
|
138
|
+
def test_loop_liveness_keeps_previous_iteration(self):
|
|
139
|
+
program = self.program(
|
|
140
|
+
self.s(ir.Assign, "a", L(1)),
|
|
141
|
+
self.s(ir.Assign, "b", L(2)),
|
|
142
|
+
self.s(ir.For, ir.NameTarget("i"), ir.State(), (
|
|
143
|
+
self.s(ir.Assign, "previous", V("a")),
|
|
144
|
+
self.s(ir.Assign, "a", V("b")),
|
|
145
|
+
self.s(ir.Assign, "b", ir.Binary("add", V("previous"), V("b"))),
|
|
146
|
+
)),
|
|
147
|
+
self.s(ir.Return, ir.TupleExpr((V("a"), V("b")))),
|
|
148
|
+
)
|
|
149
|
+
self.equivalent(program, ([], [0], [0, 0, 0, 0]), eliminate_dead_assignments)
|
|
150
|
+
self.equivalent(program, ([], [0], [0, 0, 0, 0]))
|
|
151
|
+
|
|
152
|
+
def test_loop_only_bindings_can_still_be_unbound(self):
|
|
153
|
+
program = self.program(
|
|
154
|
+
self.s(ir.For, ir.NameTarget("i"), ir.State(), (
|
|
155
|
+
self.s(ir.Assign, "x", V("i")),
|
|
156
|
+
)),
|
|
157
|
+
self.s(ir.Assign, "unused", V("x")),
|
|
158
|
+
self.s(ir.Return, L(None)),
|
|
159
|
+
)
|
|
160
|
+
self.equivalent(program, ([], [1]))
|
|
161
|
+
|
|
162
|
+
def test_scalar_folding_and_fstrings(self):
|
|
163
|
+
expressions = (
|
|
164
|
+
ir.Unary("negative", L(3)),
|
|
165
|
+
ir.Binary("add", L("中"), L("文")),
|
|
166
|
+
ir.Compare(L(1), ("lt", "lt"), (L(2), L(3))),
|
|
167
|
+
ir.Intrinsic("len", (L("中文"),)),
|
|
168
|
+
ir.Intrinsic("int", (L("ff"),), (("base", L(16)),)),
|
|
169
|
+
ir.Intrinsic("str.strip", (L(" abc "),)),
|
|
170
|
+
ir.FormatString((L("label_"), ir.FormatValue(L("brand")))),
|
|
171
|
+
ir.FormatValue(L("中文"), "a", L("")),
|
|
172
|
+
)
|
|
173
|
+
for expression in expressions:
|
|
174
|
+
program = self.program(self.s(ir.Return, expression))
|
|
175
|
+
result = self.equivalent(program, transform=fold_constants)
|
|
176
|
+
self.assertIsInstance(result.body[0].value, ir.Literal)
|
|
177
|
+
|
|
178
|
+
def test_folding_retains_nonempty_and_dynamic_format_specs(self):
|
|
179
|
+
for spec in (L("03d"), ir.State()):
|
|
180
|
+
program = self.program(self.s(ir.Return, ir.FormatValue(L(3), spec=spec)))
|
|
181
|
+
result = self.equivalent(program, ("03d",), fold_constants)
|
|
182
|
+
self.assertIsInstance(result.body[0].value, ir.FormatValue)
|
|
183
|
+
|
|
184
|
+
def test_folding_retains_exceptions_and_nonfinite_results(self):
|
|
185
|
+
expressions = (
|
|
186
|
+
ir.Intrinsic("int", (L("bad"),)),
|
|
187
|
+
ir.Binary("add", L(1), L("bad")),
|
|
188
|
+
ir.Binary("add", L(1e308), L(1e308)),
|
|
189
|
+
ir.FormatValue(L("x"), spec=L(None)),
|
|
190
|
+
ir.Compare(L(1), ("lt",), (L("x"),)),
|
|
191
|
+
)
|
|
192
|
+
for expression in expressions:
|
|
193
|
+
program = self.program(self.s(ir.Return, expression))
|
|
194
|
+
result = self.equivalent(program, transform=fold_constants)
|
|
195
|
+
self.assertEqual(result.body[0].value, expression)
|
|
196
|
+
|
|
197
|
+
def test_folding_does_not_create_large_constants(self):
|
|
198
|
+
expression = ir.Binary("add", L("x" * 40000), L("y" * 40000))
|
|
199
|
+
program = self.program(self.s(ir.Return, expression))
|
|
200
|
+
self.assertEqual(fold_constants(program).body[0].value, expression)
|
|
201
|
+
|
|
202
|
+
def test_folding_leaves_non_scalar_intrinsics(self):
|
|
203
|
+
for name, args in (("range", (L(2),)), ("enumerate", (L("ab"),)),
|
|
204
|
+
("json.dumps", (L(1),))):
|
|
205
|
+
expression = ir.Intrinsic(name, args)
|
|
206
|
+
program = self.program(self.s(ir.Assign, "value", expression), self.s(ir.Return, L(None)))
|
|
207
|
+
self.assertEqual(fold_constants(program), program)
|
|
208
|
+
|
|
209
|
+
def test_short_circuit_does_not_evaluate_skipped_operands(self):
|
|
210
|
+
bad = ir.Intrinsic("int", (L("bad"),))
|
|
211
|
+
for expr in (ir.BoolOp("and", (L(False), bad)), ir.BoolOp("or", (L("yes"), bad)),
|
|
212
|
+
ir.Compare(L(3), ("lt", "lt"), (L(2), bad))):
|
|
213
|
+
program = self.program(self.s(ir.Return, expr))
|
|
214
|
+
result = self.equivalent(program)
|
|
215
|
+
self.assertIsInstance(result.body[0].value, ir.Literal)
|
|
216
|
+
|
|
217
|
+
def test_control_flow_removes_only_unreachable_inference(self):
|
|
218
|
+
program = self.program(
|
|
219
|
+
self.s(ir.If, L(False), (self.s(ir.Infer, "bad", "generate", (L("never"),), ()),)),
|
|
220
|
+
self.s(ir.Infer, "unused", "generate", (L("always"),), ()),
|
|
221
|
+
self.s(ir.Return, L(None)),
|
|
222
|
+
self.s(ir.Infer, "bad2", "generate", (L("after return"),), ()),
|
|
223
|
+
)
|
|
224
|
+
result = self.equivalent(program)
|
|
225
|
+
calls = [s for s in walk_statements(result.body) if isinstance(s, ir.Infer)]
|
|
226
|
+
self.assertEqual([s.args[0].value for s in calls], ["always"])
|
|
227
|
+
|
|
228
|
+
def completion_program(self, initial=False, last=True):
|
|
229
|
+
return self.program(
|
|
230
|
+
self.s(ir.Assign, "arbitrary_flag", L(initial)),
|
|
231
|
+
self.s(ir.Assign, "result", L(None)),
|
|
232
|
+
self.s(ir.If, ir.State(), (
|
|
233
|
+
self.s(ir.Assign, "result", L("early")),
|
|
234
|
+
self.s(ir.Assign, "arbitrary_flag", L(last)),
|
|
235
|
+
)),
|
|
236
|
+
self.s(ir.If, ir.Unary("not", V("arbitrary_flag")), (
|
|
237
|
+
self.s(ir.Infer, "generated", "generate", (L("late"),), ()),
|
|
238
|
+
self.s(ir.Assign, "result", ir.Field(V("generated"), "text")),
|
|
239
|
+
)),
|
|
240
|
+
self.s(ir.Return, V("result")),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def test_completion_guards_form_one_if_else(self):
|
|
244
|
+
program = self.completion_program()
|
|
245
|
+
result = self.equivalent(program, (True, False))
|
|
246
|
+
branches = [s for s in walk_statements(result.body) if isinstance(s, ir.If)]
|
|
247
|
+
self.assertEqual(len(branches), 1)
|
|
248
|
+
self.assertTrue(branches[0].else_body)
|
|
249
|
+
self.assertNotIn("arbitrary_flag", result.to_json())
|
|
250
|
+
self.assertEqual(branches[0].id, program.body[2].id)
|
|
251
|
+
|
|
252
|
+
def test_completion_guard_requires_exact_boolean_states(self):
|
|
253
|
+
for initial, last in ((True, True), (False, False), (0, 1)):
|
|
254
|
+
program = self.completion_program(initial, last)
|
|
255
|
+
result = self.equivalent(program, (True, False), simplify_control_flow)
|
|
256
|
+
self.assertEqual(sum(isinstance(s, ir.If) for s in result.body), 2)
|
|
257
|
+
|
|
258
|
+
def test_dead_copies_are_removed_but_failing_reads_remain(self):
|
|
259
|
+
program = self.program(
|
|
260
|
+
self.s(ir.Assign, "x", L(1)),
|
|
261
|
+
self.s(ir.Assign, "unused_copy", V("x")),
|
|
262
|
+
self.s(ir.Assign, "unused_read", ir.GetItem(ir.State(), L("missing"))),
|
|
263
|
+
self.s(ir.Return, L(None)),
|
|
264
|
+
)
|
|
265
|
+
result = self.equivalent(program, ({}, {"missing": 1}), eliminate_dead_assignments)
|
|
266
|
+
self.assertEqual(len(result.body), 2)
|
|
267
|
+
self.assertEqual(result.body[0].target, "unused_read")
|
|
268
|
+
|
|
269
|
+
def test_dead_unbound_reads_and_unpack_failures_remain(self):
|
|
270
|
+
programs = (
|
|
271
|
+
self.program(self.s(ir.Assign, "unused", V("absent")), self.s(ir.Return, L(None))),
|
|
272
|
+
self.program(self.s(ir.Unpack, ir.TupleTarget((ir.NameTarget("a"), ir.NameTarget("b"))),
|
|
273
|
+
ir.State()), self.s(ir.Return, L(None))),
|
|
274
|
+
self.program(self.s(ir.Assign, "unused", ir.DictExpr(((ir.ListExpr(()), L(1)),))),
|
|
275
|
+
self.s(ir.Return, L(None))),
|
|
276
|
+
)
|
|
277
|
+
for program in programs:
|
|
278
|
+
result = self.equivalent(program, ([1], [1, 2]))
|
|
279
|
+
self.assertEqual(len(result.body), 2)
|
|
280
|
+
|
|
281
|
+
def test_adjacent_whole_expression_merges_keep_consumer_metadata(self):
|
|
282
|
+
for cls in (ir.Assign, ir.Return, ir.If, ir.For):
|
|
283
|
+
producer = self.s(ir.Assign, "temporary", ir.State())
|
|
284
|
+
if cls is ir.Assign:
|
|
285
|
+
consumer = self.s(cls, "target", V("temporary"))
|
|
286
|
+
elif cls is ir.Return:
|
|
287
|
+
consumer = self.s(cls, V("temporary"))
|
|
288
|
+
elif cls is ir.If:
|
|
289
|
+
consumer = self.s(cls, V("temporary"), ())
|
|
290
|
+
else:
|
|
291
|
+
consumer = self.s(cls, ir.NameTarget("i"), V("temporary"), ())
|
|
292
|
+
program = self.program(producer, consumer, self.s(ir.Return, L(None)))
|
|
293
|
+
result = self.equivalent(program, ([], [1]), merge_temporaries)
|
|
294
|
+
self.assertEqual(result.body[0].id, consumer.id)
|
|
295
|
+
self.assertEqual(result.body[0].source, consumer.source)
|
|
296
|
+
self.assertEqual(len(result.body), 2)
|
|
297
|
+
|
|
298
|
+
def test_merging_does_not_move_failure_into_short_circuit(self):
|
|
299
|
+
program = self.program(
|
|
300
|
+
self.s(ir.Assign, "temporary", ir.Intrinsic("int", (L("bad"),))),
|
|
301
|
+
self.s(ir.Return, ir.BoolOp("and", (L(False), V("temporary")))),
|
|
302
|
+
)
|
|
303
|
+
self.assertEqual(merge_temporaries(program), program)
|
|
304
|
+
self.equivalent(program)
|
|
305
|
+
|
|
306
|
+
def test_merging_does_not_cross_inference_or_change_argument_order(self):
|
|
307
|
+
program = self.program(
|
|
308
|
+
self.s(ir.Assign, "temporary", ir.Intrinsic("int", (ir.State(),))),
|
|
309
|
+
self.s(ir.Infer, "response", "generate", (L("after computation"),), ()),
|
|
310
|
+
self.s(ir.Return, V("temporary")),
|
|
311
|
+
)
|
|
312
|
+
self.assertEqual(merge_temporaries(program), program)
|
|
313
|
+
self.equivalent(program, ("bad", "123"))
|
|
314
|
+
program = self.program(
|
|
315
|
+
self.s(ir.Assign, "temporary", ir.Intrinsic("int", (ir.State(),))),
|
|
316
|
+
self.s(ir.Infer, "response", "generate", (V("temporary"),), ()),
|
|
317
|
+
self.s(ir.Return, L(None)),
|
|
318
|
+
)
|
|
319
|
+
self.assertEqual(merge_temporaries(program), program)
|
|
320
|
+
|
|
321
|
+
def test_constraint_expressions_and_bindings_are_preserved(self):
|
|
322
|
+
constraint = self.s(ir.Constrain, ir.Compare(ir.Intrinsic("len", (V("text"),)),
|
|
323
|
+
("gt",), (L(0),)), V("message"))
|
|
324
|
+
program = self.program(
|
|
325
|
+
self.s(ir.Infer, "response", "generate", (L("before"),), ()),
|
|
326
|
+
self.s(ir.Assign, "text", ir.State()),
|
|
327
|
+
self.s(ir.Assign, "message", L("不能为空")),
|
|
328
|
+
constraint,
|
|
329
|
+
self.s(ir.Infer, "second", "generate", (L("after"),), ()),
|
|
330
|
+
self.s(ir.Return, V("text")),
|
|
331
|
+
)
|
|
332
|
+
result = self.equivalent(program, ("", "ok"))
|
|
333
|
+
self.assertEqual([s for s in result.body if isinstance(s, ir.Constrain)], [constraint])
|
|
334
|
+
self.assertTrue(any(isinstance(s, ir.Assign) and s.target == "message" for s in result.body))
|
|
335
|
+
self.assertTrue(any(isinstance(s, ir.Assign) and s.target == "text" for s in result.body))
|
|
336
|
+
|
|
337
|
+
def test_constraints_are_not_assumptions_and_constant_constraints_stay(self):
|
|
338
|
+
for condition in (True, False):
|
|
339
|
+
constraint = self.s(ir.Constrain, L(condition), L("failure"))
|
|
340
|
+
program = self.program(constraint, self.s(ir.Return, L(None)))
|
|
341
|
+
result = self.equivalent(program)
|
|
342
|
+
self.assertEqual(result.body[0], constraint)
|
|
343
|
+
|
|
344
|
+
def test_shared_container_and_iterator_semantics_match_python(self):
|
|
345
|
+
for function, states in ((alias_snapshot, (None,)),
|
|
346
|
+
(consume_iterator, ([], ["a", "b"])),
|
|
347
|
+
(loop_snapshots, ([], ["a"], ["a", "b", "c"]))):
|
|
348
|
+
program = compile_program(function, optimize=False)
|
|
349
|
+
result = self.equivalent(program, states)
|
|
350
|
+
for state in states:
|
|
351
|
+
expected = function(Probe(deepcopy(state)))
|
|
352
|
+
self.assertEqual(evaluate(result.to_dict(), Probe(deepcopy(state))), expected)
|
|
353
|
+
|
|
354
|
+
def test_model_call_prefix_and_runtime_failures_match_python(self):
|
|
355
|
+
raw = compile_program(unused_failure, optimize=False)
|
|
356
|
+
self.equivalent(raw, ("bad", "2"))
|
|
357
|
+
for state in ("bad", "2"):
|
|
358
|
+
context = Probe(state)
|
|
359
|
+
try:
|
|
360
|
+
value = unused_failure(context)
|
|
361
|
+
expected = ("return", json.dumps(value))
|
|
362
|
+
except Exception as exc:
|
|
363
|
+
expected = (type(exc).__name__, str(exc))
|
|
364
|
+
actual, calls, _ = self.outcome(optimize_program(raw), state)
|
|
365
|
+
self.assertEqual(actual, expected)
|
|
366
|
+
self.assertEqual(calls, context.calls)
|
|
367
|
+
|
|
368
|
+
def test_determinism_idempotence_roundtrip_and_no_source_execution(self):
|
|
369
|
+
raw = compile_program(loop_snapshots, optimize=False)
|
|
370
|
+
with patch("annoscript.compiler.compile_program", side_effect=AssertionError("must not compile")),\
|
|
371
|
+
patch.object(Probe, "generate", side_effect=AssertionError("must not execute")):
|
|
372
|
+
restored = ir.Program.from_json(raw.to_json())
|
|
373
|
+
result = self.equivalent(restored, ([], ["a", "b"]))
|
|
374
|
+
self.assertEqual(result.to_json(), optimize_program(raw).to_json())
|
|
375
|
+
self.assertEqual(result.to_json(), optimize_program(result).to_json())
|
|
376
|
+
self.assertEqual(result.to_json(), ir.Program.from_json(result.to_json()).to_json())
|
|
377
|
+
original_nodes = {s.id: s for s in walk_statements(raw.body)}
|
|
378
|
+
nodes = list(walk_statements(result.body))
|
|
379
|
+
self.assertEqual(len(nodes), len({s.id for s in nodes}))
|
|
380
|
+
for statement in nodes:
|
|
381
|
+
self.assertEqual(statement.source, original_nodes[statement.id].source)
|
|
382
|
+
self.assertEqual(raw.config, result.config)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def test_seeded_rebinding_and_branch_programs(self):
|
|
386
|
+
rng = random.Random(807)
|
|
387
|
+
names = ("a", "b", "c")
|
|
388
|
+
for _ in range(60):
|
|
389
|
+
body = [self.s(ir.Assign, name, L(rng.randrange(5))) for name in names]
|
|
390
|
+
for _ in range(8):
|
|
391
|
+
target, source = rng.choice(names), rng.choice(names)
|
|
392
|
+
assignment = self.s(ir.Assign, target, V(source))
|
|
393
|
+
if rng.choice((True, False)):
|
|
394
|
+
body.append(self.s(ir.If, ir.State(), (assignment,), (
|
|
395
|
+
self.s(ir.Assign, target, L(rng.randrange(5))),
|
|
396
|
+
)))
|
|
397
|
+
else:
|
|
398
|
+
body.append(assignment)
|
|
399
|
+
body.append(self.s(ir.Return, ir.ListExpr(tuple(map(V, names)))))
|
|
400
|
+
self.equivalent(self.program(*body), (True, False))
|
|
401
|
+
|
|
402
|
+
def test_public_api_rejects_non_program(self):
|
|
403
|
+
with self.assertRaisesRegex(TypeError, "ir.Program"):
|
|
404
|
+
optimize_program({})
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
if __name__ == "__main__":
|
|
408
|
+
unittest.main()
|