1pub struct PureCase {
2 pub id: &'static str,
3 pub source: &'static str,
4 pub entry: &'static str,
5 pub input_json: &'static str,
6 pub expected_json: &'static str,
7}
8
9pub const PURE_CASES: &[PureCase] = &[
10 PureCase {
11 id: "truthiness-empty-bytes",
12 source: r"
13 fn truth(input: bytes) {
14 if input { return [!input, input ? true : false] }
15 return [!input, false]
16 }
17 ",
18 entry: "truth",
19 input_json: r#"{"$bytes":[]}"#,
20 expected_json: "[true,false]",
21 },
22 PureCase {
23 id: "truthiness-nonempty-bytes-with-zero-byte",
24 source: r"
25 fn truth(input: bytes) {
26 if input { return [!input, input ? true : false] }
27 return [!input, false]
28 }
29 ",
30 entry: "truth",
31 input_json: r#"{"$bytes":[0]}"#,
32 expected_json: "[false,true]",
33 },
34 PureCase {
35 id: "truthiness-nan-folding-and-runtime-branches",
36 source: r"
37 fn truth(input: float) {
38 let nan = input / input
39 let branch = false
40 if nan { branch = true }
41 return [!(0.0 / 0.0), !nan, (0.0 / 0.0) ? true : false,
42 nan ? true : false, (0.0 / 0.0) && true, nan && true,
43 (0.0 / 0.0) || false, nan || false, branch, !input]
44 }
45 ",
46 entry: "truth",
47 input_json: "0.0",
48 expected_json: "[false,false,true,true,true,true,true,true,true,true]",
49 },
50 PureCase {
51 id: "truthiness-duration-and-empty-values",
52 source: r"
53 fn truth(input: {zero: int, one: int, text: string, items: list<int>, record: dict<string, int>}) {
54 return [!0ms, !1ms, !input.zero, !input.one, !input.text, !input.items, !input.record,
55 !nil, !false, !true, ![], !({}), !0.0, !1.0]
56 }
57 ",
58 entry: "truth",
59 input_json: r#"{"zero":0,"one":1,"text":"","items":[],"record":{}}"#,
60 expected_json:
61 "[true,false,true,false,true,true,true,true,true,false,true,true,true,false]",
62 },
63 PureCase {
64 id: "runtime-string-repetition",
65 source: r#"
66 fn repeat(input: {text: string, count: int}) {
67 return [input.text * input.count, input.count * input.text, input.text * 0, input.text * -1, "" * input.count]
68 }
69 "#,
70 entry: "repeat",
71 input_json: r#"{"text":"λx","count":3}"#,
72 expected_json: r#"["λxλxλx","λxλxλx","","",""]"#,
73 },
74 PureCase {
75 id: "runtime-empty-string-repetition-with-huge-count",
76 source: r"fn repeat(input: {text: string, count: int}) { return input.text * input.count }",
77 entry: "repeat",
78 input_json: r#"{"text":"","count":9223372036854775807}"#,
79 expected_json: r#""""#,
80 },
81 PureCase {
82 id: "runtime-integer-exponent-promotes-beyond-u32",
83 source: r"fn power(input: int) { return [1 ** input, (-1) ** input, 2 ** -input] }",
84 entry: "power",
85 input_json: "4294967296",
86 expected_json: "[1.0,1.0,0.0]",
87 },
88 PureCase {
89 id: "runtime-float-integer-exponent-matches-folded-result",
90 source: r"
91 fn power(input: {base: float, exponent: int}) {
92 return [input.base ** input.exponent == 1.1 ** 7, input.base ** -input.exponent == 1.1 ** -7]
93 }
94 ",
95 entry: "power",
96 input_json: r#"{"base":1.1,"exponent":7}"#,
97 expected_json: "[true,true]",
98 },
99 PureCase {
100 id: "runtime-numeric-modulo-with-nonzero-divisors",
101 source: r"
102 fn modulo(input: {integer: int, floating: float}) {
103 return [input.integer % 3, input.integer % 2.5, input.floating % 3, input.floating % 2.5]
104 }
105 ",
106 entry: "modulo",
107 input_json: r#"{"integer":7,"floating":7.5}"#,
108 expected_json: "[1,2.0,1.5,0.0]",
109 },
110 PureCase {
111 id: "typed-record-pick-preserves-nil-and-copies-values",
112 source: r#"
113 type Input = {name: string, age: int, absent: nil, nested: {count: int}}
114 fn project(input: Input) -> {name: string, absent: nil, original: int, copied: int} {
115 let selected = pick(input, ["name", "absent", "nested", "name"])
116 selected.nested.count = 99
117 return {name: selected.name, absent: selected.absent, original: input.nested.count, copied: selected.nested.count}
118 }
119 "#,
120 entry: "project",
121 input_json: r#"{"name":"Ada","age":37,"absent":null,"nested":{"count":1}}"#,
122 expected_json: r#"{"name":"Ada","absent":null,"original":1,"copied":99}"#,
123 },
124 PureCase {
125 id: "typed-record-pick-runtime-keys-and-missing-values",
126 source: r"
127 fn project(input: {data: dict<string, int>, keys: list<string>}) -> dict<string, int> {
128 return pick(input.data, input.keys)
129 }
130 ",
131 entry: "project",
132 input_json: r#"{"data":{"a":1,"b":2},"keys":["b","missing","b"]}"#,
133 expected_json: r#"{"b":2}"#,
134 },
135 PureCase {
136 id: "typed-record-pick-empty",
137 source: r"fn project(input: {name: string}) -> {} { return pick(input, []) }",
138 entry: "project",
139 input_json: r#"{"name":"Ada"}"#,
140 expected_json: "{}",
141 },
142 PureCase {
143 id: "named-record-reducer",
144 source: r#"
145 type State = {count: int, history: list<int>}
146 type Event = {kind: string, amount: int}
147 type Input = {state: State, event: Event}
148
149 fn reduce(input: Input) -> State {
150 if input.event.kind == "reset" {
151 return {count: 0, history: input.state.history + [0]}
152 }
153 const next = input.state.count + input.event.amount
154 return {count: next, history: input.state.history + [next]}
155 }
156 "#,
157 entry: "reduce",
158 input_json: r#"{"state":{"count":2,"history":[1,2]},"event":{"kind":"increment","amount":3}}"#,
159 expected_json: r#"{"count":5,"history":[1,2,5]}"#,
160 },
161 PureCase {
162 id: "closure-capture-and-control-flow",
163 source: r"
164 type TransformInput = {value: int, offset: int, enabled: bool}
165 type TransformResult = {value: int, enabled: bool}
166 fn transform(input: TransformInput) -> TransformResult {
167 const offset = input.offset
168 const add_offset = {value -> value + offset}
169 if input.enabled {
170 return {value: add_offset(input.value), enabled: true}
171 }
172 return {value: input.value, enabled: false}
173 }
174 ",
175 entry: "transform",
176 input_json: r#"{"value":7,"offset":4,"enabled":true}"#,
177 expected_json: r#"{"enabled":true,"value":11}"#,
178 },
179 PureCase {
180 id: "recursive-and-sibling-functions",
181 source: r"
182 fn fib(n: int) -> int {
183 if n <= 1 { return n }
184 return fib(n - 1) + fib(n - 2)
185 }
186 fn solve(input: int) -> int { return fib(input) }
187 ",
188 entry: "solve",
189 input_json: "7",
190 expected_json: "13",
191 },
192 PureCase {
193 id: "forward-declared-capturing-function",
194 source: r"
195 fn reduce(input: int) -> int {
196 const offset = 3
197 return add_offset(input)
198 fn add_offset(value: int) -> int { return value + offset }
199 }
200 ",
201 entry: "reduce",
202 input_json: "4",
203 expected_json: "7",
204 },
205 PureCase {
206 id: "typed-rest-and-sibling-call",
207 source: r"
208 fn collect(...values: int) -> list<int> { return values }
209 fn reduce(input: int) -> list<int> { return collect(input, 2) }
210 ",
211 entry: "reduce",
212 input_json: "7",
213 expected_json: "[7,2]",
214 },
215 PureCase {
216 id: "mutating-methods-rewrite-their-place",
217 source: r#"
218 type MutateInput = {items: list<int>, meta: dict<string, int>}
219 fn mutate(input: MutateInput) {
220 let items = input.items
221 const alias = items
222 items.push(4)
223 const last = items.pop()
224 items.insert(0, 0)
225 items.extend([9])
226 items.reverse()
227 let state = {stack: [1, 2], meta: input.meta}
228 const top = state.stack.pop()
229 const removed = state.meta.remove("a")
230 return {
231 items: items,
232 alias: alias,
233 last: last,
234 top: top,
235 removed: removed,
236 state: state,
237 }
238 }
239 "#,
240 entry: "mutate",
241 input_json: r#"{"items":[1,2,3],"meta":{"a":1,"b":2}}"#,
242 expected_json: r#"{"alias":[1,2,3],"items":[9,3,2,1,0],"last":4,"removed":1,"state":{"meta":{"b":2},"stack":[1]},"top":2}"#,
243 },
244 PureCase {
245 id: "list-string-and-record-operations",
246 source: r"
247 type SummaryInput = {
248 left: list<int>,
249 right: list<int>,
250 prefix: string,
251 name: string,
252 needle: int,
253 meta: dict<string, int>,
254 }
255 fn summarize(input: SummaryInput) {
256 return {
257 items: input.left + input.right,
258 title: input.prefix + input.name,
259 found: input.left.contains(input.needle),
260 fields: input.meta.len(),
261 }
262 }
263 ",
264 entry: "summarize",
265 input_json: r#"{"left":[1,2],"right":[3],"prefix":"Harn ","name":"Kernel","needle":2,"meta":{"a":1,"b":2}}"#,
266 expected_json: r#"{"fields":2,"found":true,"items":[1,2,3],"title":"Harn Kernel"}"#,
267 },
268 PureCase {
269 id: "list-ordering-runtime-and-constant-folding",
270 source: r"
271 type ListComparisonInput = {left: list<int>, right: list<int>}
272 fn compare_lists(input: ListComparisonInput) {
273 return {
274 runtime_less: input.left < input.right,
275 runtime_equal: input.left <= input.left,
276 constant_less: [1, 2] < [1, 3],
277 constant_greater: [2] > [1, 9],
278 }
279 }
280 ",
281 entry: "compare_lists",
282 input_json: r#"{"left":[1,2],"right":[1,3]}"#,
283 expected_json: r#"{"constant_greater":true,"constant_less":true,"runtime_equal":true,"runtime_less":true}"#,
284 },
285 PureCase {
286 id: "mixed-type-equality-is-structural-not-ordering",
287 source: r"
288 fn compare(input: {value: string}) {
289 return {
290 string_is_not_nil: input.value != nil,
291 string_is_not_int: input.value != 7,
292 nil_is_nil: nil == nil,
293 numeric_cross_kind: 1 == 1.0,
294 }
295 }
296 ",
297 entry: "compare",
298 input_json: r#"{"value":"ui://portable"}"#,
299 expected_json: r#"{"nil_is_nil":true,"numeric_cross_kind":true,"string_is_not_int":true,"string_is_not_nil":true}"#,
300 },
301 PureCase {
302 id: "structured-throw-catch",
303 source: r#"
304 fn validate(input: {value: int}) {
305 try {
306 if input.value < 0 {
307 throw {code: "negative", value: input.value}
308 }
309 return {ok: true, value: input.value}
310 } catch error {
311 return {ok: false, value: error.value, code: error.code}
312 }
313 }
314 "#,
315 entry: "validate",
316 input_json: r#"{"value":-9}"#,
317 expected_json: r#"{"code":"negative","ok":false,"value":-9}"#,
318 },
319 PureCase {
320 id: "negative-index-and-slice",
321 source: r"
322 type ChoiceInput = {values: list<int>, text: string}
323 fn choose(input: ChoiceInput) {
324 return {
325 last: input.values[-1],
326 middle: input.values[-4:-1],
327 suffix: input.text[-3:],
328 }
329 }
330 ",
331 entry: "choose",
332 input_json: r#"{"values":[1,2,3,4,5],"text":"kernel"}"#,
333 expected_json: r#"{"last":5,"middle":[2,3,4],"suffix":"nel"}"#,
334 },
335 PureCase {
336 id: "module-capture-property-mutation",
337 source: r"
338 let state = {count: 0}
339 fn reduce(input: {count: int}) {
340 state.count = input.count
341 return {count: state.count}
342 }
343 ",
344 entry: "reduce",
345 input_json: r#"{"count":9}"#,
346 expected_json: r#"{"count":9}"#,
347 },
348 PureCase {
349 id: "iteration-and-copy-on-write-mutation",
350 source: r#"
351 type IterationState = {count: int, tags: list<string>}
352 type IterationInput = {state: IterationState, values: list<int>}
353 fn reduce(input: IterationInput) -> IterationState {
354 let state = input.state
355 for value in input.values {
356 state.count = state.count + value
357 }
358 state.tags[0] = "updated"
359 return state
360 }
361 "#,
362 entry: "reduce",
363 input_json: r#"{"state":{"count":1,"tags":["old"]},"values":[2,3,4]}"#,
364 expected_json: r#"{"count":10,"tags":["updated"]}"#,
365 },
366 PureCase {
367 id: "renderer-string-and-option-primitives",
368 source: r#"
369 type RenderInput = {name: string, validation?: dict<string, bool>}
370 fn reduce(input: RenderInput) {
371 const options = {allow_network: false}.merge(input.validation ?? {})
372 const name = trim(input.name)
373 return {
374 encoded: replace(json_stringify(name), "<", "\\u003c"),
375 portable: starts_with(name, "Portable"),
376 options: options,
377 }
378 }
379 "#,
380 entry: "reduce",
381 input_json: r#"{"name":" Portable <Harn> ","validation":{"allow_host_bridge":true}}"#,
382 expected_json: r#"{"encoded":"\"Portable \\u003cHarn>\"","options":{"allow_host_bridge":true,"allow_network":false},"portable":true}"#,
383 },
384 PureCase {
385 id: "artifact-regex-hash-and-secret-safety-primitives",
386 source: r#"
387 fn inspect(input: string) {
388 const captures = regex_captures("(?is)<body\\b([^>]*)>(.*?)</body>", input)
389 return {
390 body: captures[0].groups[1],
391 scripts: regex_match("(?is)<script\\b", input),
392 text: trim(regex_replace("(?is)<[^>]+>", " ", input)),
393 digest: sha256("abc"),
394 clean: len(secret_scan(input)) == 0,
395 }
396 }
397 "#,
398 entry: "inspect",
399 input_json: r#""<body class='app'>Portable <b>Harn</b></body>""#,
400 expected_json: r#"{"body":"Portable <b>Harn</b>","clean":true,"digest":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad","scripts":null,"text":"Portable Harn"}"#,
401 },
402 PureCase {
403 id: "target-independent-path-joining",
404 source: r#"
405 fn paths(input: {root: string}) {
406 return {
407 host: path_join(input.root, ".harn", "state.json"),
408 reset: path_join("ignored", "/absolute", "file"),
409 }
410 }
411 "#,
412 entry: "paths",
413 input_json: r#"{"root":"C:\\workspace"}"#,
414 expected_json: r#"{"host":"C:/workspace/.harn/state.json","reset":"/absolute/file"}"#,
415 },
416 PureCase {
417 id: "compiled-secret-pattern-catalog",
418 source: r#"
419 fn inspect(input: string) {
420 const catalog = secret_pattern_catalog()
421 let high_count = 0
422 let contextual = false
423 let detector = ""
424 let pattern = ""
425 for row in catalog {
426 if row.precision == "high" { high_count = high_count + 1 }
427 if row.precision == "heuristic" { contextual = true }
428 if row.detector == "aws-access-key-id" {
429 detector = row.detector
430 pattern = row.regex
431 }
432 }
433 if pattern == "" { throw "missing AWS detector" }
434 return {
435 measured: high_count >= 10,
436 contextual: contextual,
437 detector: detector,
438 accepted: regex_match(pattern, "AKIA" + input) != nil,
439 rejected: regex_match(pattern, "ordinary text") == nil,
440 }
441 }
442 "#,
443 entry: "inspect",
444 input_json: r#""ABCDEFGHIJKLMNOP""#,
445 expected_json: r#"{"accepted":true,"contextual":true,"detector":"aws-access-key-id","measured":true,"rejected":true}"#,
446 },
447 PureCase {
448 id: "result-enum-match-and-propagation",
449 source: r#"
450 fn divide(value: int, divisor: int) -> Result<int, string> {
451 if divisor == 0 { return Result.Err("division by zero") }
452 return Result.Ok(value / divisor)
453 }
454 fn halve(value: int, divisor: int) -> Result<int, string> {
455 const divided: int = divide(value, divisor)?
456 return Result.Ok(divided / 2)
457 }
458 fn reduce(input: int) {
459 const result = halve(12, input)
460 match result {
461 Result.Ok(value) -> { return [result.variant, result.fields, value] }
462 Result.Err(message) -> { return [result.variant, result.fields, message] }
463 }
464 }
465 "#,
466 entry: "reduce",
467 input_json: "3",
468 expected_json: r#"["Ok",[2],2]"#,
469 },
470 PureCase {
471 id: "float-param-accepts-int",
474 source: r"
475 fn takes_float(x: float) -> float { return x + 0.0 }
476 fn reduce(input: int) -> float { return takes_float(input) }
477 ",
478 entry: "reduce",
479 input_json: "3",
480 expected_json: "3.0",
481 },
482 PureCase {
483 id: "cleanup-runs-once-for-callee-errors",
487 source: r#"
488 fn fail(message: string) -> int { throw message }
489 fn relay(message: string) -> int { return fail(message) + 1 }
490 fn reduce(input: list<string>) -> {finally_runs: int, defer_runs: int, caught: list<string>} {
491 let finally_runs = 0
492 let defer_runs = 0
493 let caught = []
494 for label in input {
495 try {
496 defer { defer_runs = defer_runs + 1 }
497 try {
498 if label == "direct" {
499 fail(label)
500 } else {
501 relay(label)
502 }
503 } finally {
504 finally_runs = finally_runs + 1
505 }
506 } catch (error) {
507 caught = caught + [error]
508 }
509 }
510 return {finally_runs: finally_runs, defer_runs: defer_runs, caught: caught}
511 }
512 "#,
513 entry: "reduce",
514 input_json: r#"["direct","relayed"]"#,
515 expected_json: r#"{"finally_runs":2,"defer_runs":2,"caught":["direct","relayed"]}"#,
516 },
517 PureCase {
518 id: "try-operator-runs-pending-finally",
522 source: r#"
523 fn check(value: int) -> Result<int, string> {
524 if value < 0 { return Result.Err("negative") }
525 return Result.Ok(value)
526 }
527 fn guarded(value: int) -> Result<int, string> {
528 try {
529 const checked = check(value)?
530 return Result.Ok(checked + 1)
531 } finally {
532 if value < 0 { return Result.Err("cleanup") }
533 }
534 return Result.Err("fell through")
535 }
536 fn reduce(input: list<int>) -> list<list<any>> {
537 let rendered = []
538 for value in input {
539 const result = guarded(value)
540 rendered = rendered + [[result.variant, result.fields]]
541 }
542 return rendered
543 }
544 "#,
545 entry: "reduce",
546 input_json: "[-1,4]",
547 expected_json: r#"[["Err",["cleanup"]],["Ok",[5]]]"#,
548 },
549];
550
551pub struct RuntimeFailureCase {
557 pub id: &'static str,
558 pub source: &'static str,
559 pub entry: &'static str,
560 pub input_json: &'static str,
561 pub expected_code: &'static str,
562}
563
564pub const RUNTIME_FAILURE_CASES: &[RuntimeFailureCase] = &[
565 RuntimeFailureCase {
566 id: "string-repetition-rejects-output-over-byte-limit",
567 source: r"fn repeat(input: {text: string, count: int}) { return input.text * input.count }",
568 entry: "repeat",
569 input_json: r#"{"text":"ab","count":524289}"#,
570 expected_code: "value_byte_limit",
571 },
572 RuntimeFailureCase {
573 id: "string-repetition-rejects-overflowing-output-length",
574 source: r"fn repeat(input: {text: string, count: int}) { return input.count * input.text }",
575 entry: "repeat",
576 input_json: r#"{"text":"abc","count":9223372036854775807}"#,
577 expected_code: "value_byte_limit",
578 },
579 RuntimeFailureCase {
580 id: "integer-modulo-by-integer-zero",
581 source: r"fn modulo(input: {left: int, right: int}) { return input.left % input.right }",
582 entry: "modulo",
583 input_json: r#"{"left":7,"right":0}"#,
584 expected_code: "division_by_zero",
585 },
586 RuntimeFailureCase {
587 id: "float-modulo-by-integer-zero",
588 source: r"fn modulo(input: {left: float, right: int}) { return input.left % input.right }",
589 entry: "modulo",
590 input_json: r#"{"left":7.5,"right":0}"#,
591 expected_code: "division_by_zero",
592 },
593 RuntimeFailureCase {
594 id: "integer-modulo-by-float-zero",
595 source: r"fn modulo(input: {left: int, right: float}) { return input.left % input.right }",
596 entry: "modulo",
597 input_json: r#"{"left":7,"right":0.0}"#,
598 expected_code: "division_by_zero",
599 },
600 RuntimeFailureCase {
601 id: "float-modulo-by-negative-float-zero",
602 source: r"fn modulo(input: {left: float, right: float}) { return input.left % input.right }",
603 entry: "modulo",
604 input_json: r#"{"left":7.5,"right":-0.0}"#,
605 expected_code: "division_by_zero",
606 },
607 RuntimeFailureCase {
608 id: "declared-error-survives-transparent-rethrows",
609 source: r"
610 fn fail(input: int) -> any throws int { throw input }
611 fn reduce(input: int) -> any throws int {
612 try { return retry 2 { try* fail(input) } }
613 finally { try { throw 8 } catch (_ignored) {} }
614 }
615 ",
616 entry: "reduce",
617 input_json: "7",
618 expected_code: "harn_declared_throw",
619 },
620 RuntimeFailureCase {
621 id: "legacy-error-stays-runtime-under-declared-wrapper",
622 source: r"
623 fn fail(input: int) -> any { throw input }
624 fn reduce(input: int) -> any throws int {
625 try { return retry 2 { try* fail(input) } }
626 finally { try { throw 8 } catch (_ignored) {} }
627 }
628 ",
629 entry: "reduce",
630 input_json: "7",
631 expected_code: "harn_throw",
632 },
633 RuntimeFailureCase {
634 id: "pick-rejects-non-record-source",
635 source: r#"fn project(input: any) { return pick(input, ["name"]) }"#,
636 entry: "project",
637 input_json: "42",
638 expected_code: "builtin_type",
639 },
640 RuntimeFailureCase {
641 id: "pick-rejects-non-string-key",
642 source: r"fn project(input: {name: string, keys: list<any>}) { return pick(input, input.keys) }",
643 entry: "project",
644 input_json: r#"{"name":"Ada","keys":[1]}"#,
645 expected_code: "builtin_type",
646 },
647 RuntimeFailureCase {
648 id: "pick-rejects-wrong-arity",
649 source: r"fn project(args: list<any>) { return pick(...args) }",
650 entry: "project",
651 input_json: r#"[{"name":"Ada"}]"#,
652 expected_code: "builtin_type",
653 },
654 RuntimeFailureCase {
655 id: "int-param-rejects-float",
656 source: r"
657 fn takes_int(n: int) -> int { return n }
658 // Deliberate unchecked host boundary: the failure belongs to the
659 // portable runtime parameter contract.
660 fn reduce(input: any) { return takes_int(input) }
661 ",
662 entry: "reduce",
663 input_json: "2.5",
664 expected_code: "argument_type",
665 },
666];
667
668pub struct InvalidCase {
669 pub id: &'static str,
670 pub source: &'static str,
671 pub entry: &'static str,
672 pub expected_code: &'static str,
673}
674
675pub const INVALID_CASES: &[InvalidCase] = &[
676 InvalidCase {
677 id: "pick-unknown-field",
678 source: r#"fn project(input: {name: string}) { return pick(input, ["nmae"]) }"#,
679 entry: "project",
680 expected_code: "compile_frontend",
681 },
682 InvalidCase {
683 id: "pick-dynamic-fields-are-not-required",
684 source: r"fn project(input: {name: string}, keys: list<string>) -> {name: string} { return pick(input, keys) }",
685 entry: "project",
686 expected_code: "compile_frontend",
687 },
688 InvalidCase {
689 id: "frontend-syntax-error",
690 source: "fn reduce( {",
691 entry: "reduce",
692 expected_code: "compile_frontend",
693 },
694 InvalidCase {
695 id: "invalid-mutable-local-program",
696 source: r"
697 fn reduce(input) {
698 var value = input
699 value = value + 1
700 return value
701 }
702 ",
703 entry: "reduce",
704 expected_code: "compile_frontend",
705 },
706];
707
708#[derive(Clone, Copy)]
709pub struct PackageCompilationCase {
710 pub id: &'static str,
711 pub source: &'static str,
712 pub entry: &'static str,
713 pub expected_ok: bool,
714}
715
716impl PackageCompilationCase {
717 pub fn package(&self) -> harn_kernel::PortableSourcePackage {
718 harn_kernel::PortableSourcePackage {
719 root_source: self.source.to_string(),
720 root_imports: Vec::new(),
721 modules: Vec::new(),
722 }
723 }
724
725 pub fn compile(&self) -> Result<harn_kernel::ProgramArtifact, Vec<harn_kernel::Diagnostic>> {
726 let result = harn_kernel::compile_source_package(
727 self.package(),
728 self.entry,
729 harn_kernel::EntryKind::Function,
730 );
731 assert_eq!(
732 result.is_ok(),
733 self.expected_ok,
734 "{} compilation decision drifted",
735 self.id
736 );
737 if let Err(diagnostics) = &result {
738 assert!(
739 !diagnostics.is_empty(),
740 "{} has no failure diagnostic",
741 self.id
742 );
743 }
744 result
745 }
746}
747
748pub fn package_compilation_cases() -> impl Iterator<Item = PackageCompilationCase> {
749 INVALID_CASES
750 .iter()
751 .map(|case| PackageCompilationCase {
752 id: case.id,
753 source: case.source,
754 entry: case.entry,
755 expected_ok: false,
756 })
757 .chain(PACKAGE_COMPILATION_CASES.iter().copied())
758}
759
760pub const PACKAGE_COMPILATION_CASES: &[PackageCompilationCase] = &[
761 PackageCompilationCase {
762 id: "removed-provider-wrapper-alias",
763 source: "fn reduce(input: string) { return with_rate_limit(input) }",
764 entry: "reduce",
765 expected_ok: false,
766 },
767 PackageCompilationCase {
768 id: "retained-provider-call-alias",
769 source: "fn reduce(input: string) { return llm_call(input) }",
770 entry: "reduce",
771 expected_ok: true,
772 },
773 PackageCompilationCase {
774 id: "retained-provider-call-method",
775 source: "fn reduce(harness: Harness, input: string) { return harness.llm.call(input) }",
776 entry: "reduce",
777 expected_ok: true,
778 },
779];