Skip to main content

portable_conformance_json/testdata/
portable_conformance.rs

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        // Widening: a whole int satisfies a float parameter. This is the
472        // half of harn#6267 that payload schemas used to get wrong.
473        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        // harn#9189: cleanup runs once for errors raised by a callee, not
484        // only for an inline `throw`. Runtime failures such as division by
485        // zero are terminal in the portable kernel, so only throws appear.
486        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        // harn#9201: `?` returns an `Err` through pending cleanup. The
519        // `finally` overrides the early return, so the result only reads
520        // `cleanup` when that cleanup ran.
521        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
551/// Runtime failures that every portable executor must agree on.
552///
553/// Unlike [`PURE_CASES`], these are expected to fail after a successful
554/// compile. The int←float case is the drift that used to slip past
555/// `browser_worker_matches_native_portable_corpus_exactly` (harn#6267).
556pub 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];