v / vlib / v2 / gen / cleanc / result_option_codegen_test.v
2232 lines · 1921 sloc · 46.84 KB · 164b30309e6337b95ec2baacacd0f101fafd3d97
Raw
1// vtest build: macos
2module cleanc
3
4import os
5import strings
6import v2.ast
7import v2.markused
8import v2.parser
9import v2.pref as vpref
10import v2.token
11import v2.transformer
12import v2.types
13
14fn generate_result_option_c_for_test(code string) string {
15 return generate_result_option_c_for_test_files([code])
16}
17
18fn generate_result_option_c_for_test_files(sources []string) string {
19 tmp_dir := os.join_path(os.temp_dir(), 'v2_result_option_codegen_test_${os.getpid()}')
20 os.rmdir_all(tmp_dir) or {}
21 os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
22 defer {
23 os.rmdir_all(tmp_dir) or {}
24 }
25 mut paths := []string{cap: sources.len}
26 for i, code in sources {
27 tmp_file := os.join_path(tmp_dir, 'file_${i}.v')
28 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
29 paths << tmp_file
30 }
31 prefs := &vpref.Preferences{
32 backend: .cleanc
33 no_parallel: true
34 }
35 mut file_set := token.FileSet.new()
36 mut par := parser.Parser.new(prefs)
37 files := par.parse_files(paths, mut file_set)
38 env := types.Environment.new()
39 mut checker := types.Checker.new(prefs, file_set, env)
40 checker.check_files(files)
41 mut trans := transformer.Transformer.new_with_pref(env, prefs)
42 mut gen := Gen.new_with_env_and_pref(trans.transform_files(files), env, prefs)
43 return gen.gen()
44}
45
46fn generate_markused_c_for_test(code string) string {
47 tmp_file := os.join_path(os.temp_dir(), 'v2_markused_codegen_test_${os.getpid()}.v')
48 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
49 defer {
50 os.rm(tmp_file) or {}
51 }
52 prefs := &vpref.Preferences{
53 backend: .cleanc
54 no_parallel: true
55 }
56 mut file_set := token.FileSet.new()
57 mut par := parser.Parser.new(prefs)
58 files := par.parse_files([tmp_file], mut file_set)
59 env := types.Environment.new()
60 mut checker := types.Checker.new(prefs, file_set, env)
61 checker.check_files(files)
62 mut trans := transformer.Transformer.new_with_pref(env, prefs)
63 gen_files := trans.transform_files(files)
64 used := markused.mark_used(gen_files, env)
65 mut gen := Gen.new_with_env_and_pref(gen_files, env, prefs)
66 gen.set_used_fn_keys(used)
67 return gen.gen()
68}
69
70fn generate_ownership_markused_c_for_test(code string) string {
71 tmp_file := os.join_path(os.temp_dir(), 'v2_ownership_markused_codegen_test_${os.getpid()}.v')
72 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
73 defer {
74 os.rm(tmp_file) or {}
75 }
76 prefs := &vpref.Preferences{
77 backend: .cleanc
78 no_parallel: true
79 ownership: true
80 }
81 mut file_set := token.FileSet.new()
82 mut par := parser.Parser.new(prefs)
83 files := par.parse_files([tmp_file], mut file_set)
84 env := types.Environment.new()
85 mut checker := types.Checker.new(prefs, file_set, env)
86 checker.check_files(files)
87 mut trans := transformer.Transformer.new_with_pref(env, prefs)
88 gen_files := trans.transform_files(files)
89 used := markused.mark_used(gen_files, env)
90 mut gen := Gen.new_with_env_and_pref(gen_files, env, prefs)
91 gen.set_used_fn_keys(used)
92 return gen.gen()
93}
94
95fn generate_parallel_worker_c_for_test(code string) string {
96 tmp_file := os.join_path(os.temp_dir(), 'v2_parallel_worker_codegen_test_${os.getpid()}.v')
97 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
98 defer {
99 os.rm(tmp_file) or {}
100 }
101 prefs := &vpref.Preferences{
102 backend: .cleanc
103 no_parallel: false
104 ownership: true
105 }
106 mut file_set := token.FileSet.new()
107 mut par := parser.Parser.new(prefs)
108 files := par.parse_files([tmp_file], mut file_set)
109 env := types.Environment.new()
110 mut checker := types.Checker.new(prefs, file_set, env)
111 checker.check_files(files)
112 mut trans := transformer.Transformer.new_with_pref(env, prefs)
113 gen_files := trans.transform_files(files)
114 used := markused.mark_used(gen_files, env)
115 mut gen := Gen.new_with_env_and_pref(gen_files, env, prefs)
116 gen.set_used_fn_keys(used)
117 gen.gen_passes_1_to_4()
118 emit_indices := gen.gen_pass5_pre()
119 mut worker := gen.new_pass5_worker(emit_indices, 0)
120 worker.gen_pass5_files(emit_indices)
121 gen.merge_pass5_worker(worker)
122 gen.gen_pass5_post()
123 return gen.gen_finalize()
124}
125
126fn test_generate_c_hoists_if_expr_temp_before_result_propagation_call() {
127 csrc := generate_result_option_c_for_test('
128struct Params {
129 checksum bool
130}
131
132struct Ctx {}
133
134fn (mut c Ctx) set(v int) ! {
135}
136
137fn new_ctx(params Params) !Ctx {
138 mut c := Ctx{}
139 c.set(if params.checksum { 1 } else { 0 })!
140 return c
141}
142')
143 if_pos := csrc.index('int _if_t') or { panic('missing if-expression temp') }
144 or_pos := csrc.index('_result_void _or_t') or { panic('missing result propagation temp') }
145 assert if_pos < or_pos
146 assert csrc.contains('Ctx__set(&c, _if_t')
147}
148
149fn test_generate_c_keeps_option_wrapper_for_fn_value_if_guard() {
150 csrc := generate_result_option_c_for_test("
151fn with_name_to_index(name_to_index fn (string) ?int) {
152 if index := name_to_index('foo') {
153 _ = index
154 }
155}
156")
157 assert csrc.contains('_option_int _or_t')
158 assert csrc.contains('if ((_or_t')
159 assert !csrc.contains('void* _or_t')
160}
161
162fn test_generate_c_keeps_option_wrapper_for_or_block_temp() {
163 csrc := generate_result_option_c_for_test('
164fn maybe_index() ?int {
165 return 3
166}
167
168fn find_stop() int {
169 stop_index := maybe_index() or { -1 }
170 return stop_index
171}
172')
173 assert csrc.contains('_option_int _or_t')
174 assert csrc.contains('stop_index')
175}
176
177fn test_generate_c_ignores_direct_option_or_expr_stmt_payload() {
178 csrc := generate_result_option_c_for_test('
179struct User {}
180
181fn maybe_user() ?User {
182 return User{}
183}
184
185fn main() {
186 maybe_user() or {}
187}
188')
189 assert csrc.contains('_option_User _or_t')
190 assert csrc.contains('.state != 0')
191 assert !csrc.contains('User*)(((u8*)(&_or_t')
192}
193
194fn test_generate_c_preserves_string_slice_type_for_followup_index_or() {
195 csrc := generate_result_option_c_for_test('
196fn (s string) find_text(needle string) ?int {
197 _ = s
198 _ = needle
199 return 0
200}
201
202fn find_symbol(sframe string) int {
203 symbol_start := sframe.find_text("0x") or { -1 }
204 rest := sframe[symbol_start..]
205 space_after_addr := rest.find_text(" ") or { -1 }
206 symbol_and_offset := rest[space_after_addr + 1..]
207 plus_pos := symbol_and_offset.find_text(" + ") or { -1 }
208 return plus_pos
209}
210 ')
211 assert csrc.contains('string symbol_and_offset = string__substr(rest')
212 assert !csrc.contains('array symbol_and_offset = array__slice(rest')
213 assert csrc.contains('.state != 0')
214}
215
216fn test_generate_c_declares_struct_result_or_value_with_struct_type() {
217 csrc := generate_result_option_c_for_test('
218struct Glob {}
219
220fn new_glob() !Glob {
221 return Glob{}
222}
223
224fn build() !Glob {
225 glob := new_glob() or { return err }
226 return glob
227}
228')
229 assert csrc.contains('Glob glob =')
230 assert !csrc.contains('int glob =')
231}
232
233fn test_generate_c_declares_static_method_result_or_value_with_struct_type() {
234 csrc := generate_result_option_c_for_test('
235struct Glob {}
236
237fn Glob.new(pattern string) !Glob {
238 _ = pattern
239 return Glob{}
240}
241
242fn build() !Glob {
243 glob := Glob.new("*.v") or { return err }
244 return glob
245}
246')
247 assert csrc.contains('Glob glob =')
248 assert !csrc.contains('int glob =')
249 assert !csrc.contains('Glob__new(Glob,')
250}
251
252fn test_generate_c_unwraps_module_result_or_inside_if_expr_branch() {
253 csrc := generate_result_option_c_for_test_files([
254 '
255module codec
256
257pub fn decompress(data []u8) ![]u8 {
258 return data
259}
260',
261 '
262module main
263
264import codec
265
266fn decode(input []u8, flag bool) []u8 {
267 mut decoded := input
268 decoded = if flag {
269 codec.decompress(decoded) or { return decoded }
270 } else {
271 decoded
272 }
273 return decoded
274}
275',
276 ])
277 assert csrc.contains('_result_Array_u8 _or_t')
278 assert csrc.contains('(*(Array_u8*)(((u8*)(&_or_t')
279 assert !csrc.contains('int _if_expr_t')
280}
281
282fn test_generate_c_unwraps_result_or_in_nested_if_expr_branch() {
283 csrc := generate_result_option_c_for_test_files([
284 '
285module codec
286
287pub fn decompress(data []u8, check bool) ![]u8 {
288 _ = check
289 return data
290}
291
292pub fn inflate(data []u8) ![]u8 {
293 return data
294}
295',
296 '
297module main
298
299import codec
300
301fn decode(input []u8, encoding string) []u8 {
302 mut decoded := input
303 decoded = if encoding == "gzip" || encoding == "x-gzip" {
304 codec.decompress(decoded, true) or { return input }
305 } else {
306 if encoding == "deflate" {
307 codec.inflate(decoded) or { return input }
308 } else {
309 decoded
310 }
311 }
312 return decoded
313}
314',
315 ])
316 assert csrc.count('_result_Array_u8 _or_t') >= 2
317 assert csrc.contains('(*(Array_u8*)(((u8*)(&_or_t')
318 assert !csrc.contains('= ({ int _if_expr_t')
319}
320
321fn test_generate_c_recovers_or_payload_when_final_expr_is_empty() {
322 mut gen := Gen{
323 sb: strings.new_builder(256)
324 fn_return_types: {
325 'gzip__decompress': '_result_Array_u8'
326 }
327 }
328 gen.gen_unsafe_expr(ast.UnsafeExpr{
329 stmts: [
330 ast.Stmt(ast.AssignStmt{
331 op: .decl_assign
332 lhs: [ast.Expr(ast.Ident{
333 name: '_or_t1'
334 })]
335 rhs: [
336 ast.Expr(ast.CallExpr{
337 lhs: ast.Ident{
338 name: 'gzip__decompress'
339 }
340 }),
341 ]
342 }),
343 ast.Stmt(ast.ExprStmt{
344 expr: ast.IfExpr{
345 cond: ast.SelectorExpr{
346 lhs: ast.Ident{
347 name: '_or_t1'
348 }
349 rhs: ast.Ident{
350 name: 'is_error'
351 }
352 }
353 stmts: []ast.Stmt{}
354 }
355 }),
356 ast.Stmt(ast.ExprStmt{
357 expr: ast.empty_expr
358 }),
359 ]
360 })
361 csrc := gen.sb.str()
362 assert csrc.contains('_result_Array_u8 _or_t1 = gzip__decompress();')
363 assert csrc.contains('(*(Array_u8*)(((u8*)(&_or_t1.err)) + sizeof(IError)))')
364}
365
366fn test_generate_c_passes_mut_arg_by_address_to_fn_pointer_param() {
367 csrc := generate_result_option_c_for_test('
368fn render(replacement fn (string, mut []string)) int {
369 mut out := []string{}
370 replacement("x", mut out)
371 return 0
372}
373')
374 assert csrc.contains('replacement((string){.str = "x"')
375 assert csrc.contains(', &out);')
376 assert !csrc.contains(', out);')
377}
378
379fn test_generate_c_resolves_fn_literal_param_type_for_string_interpolation() {
380 csrc := generate_result_option_c_for_test('
381fn render(replacement fn (string, mut []string)) {
382 mut out := []string{}
383 replacement("x", mut out)
384}
385
386fn demo() {
387 render(fn (name string, mut out []string) {
388 out << "<\${name}>"
389 })
390}
391')
392 assert csrc.contains('"<%s>"')
393 assert !csrc.contains('"<%d>"')
394}
395
396fn test_generate_c_resolves_selector_string_field_type_for_string_interpolation() {
397 csrc := generate_result_option_c_for_test('
398struct Alias {
399 name string
400 description string
401}
402
403fn render(alias Alias) string {
404 return "\${alias.name}:\\"\${alias.description}\\""
405}
406')
407 assert csrc.contains('"%s:\\"%s\\""')
408 assert !csrc.contains('"%d:\\"%d\\""')
409}
410
411fn test_generate_c_resolves_for_in_selector_string_field_type_for_string_interpolation() {
412 csrc := generate_result_option_c_for_test('
413struct Alias {
414 name string
415 description string
416}
417
418fn aliases() []Alias {
419 return [
420 Alias{
421 name: "vscode"
422 description: "VS Code scheme (vscode://)"
423 },
424 ]
425}
426
427fn render() string {
428 mut last := ""
429 for alias in aliases() {
430 last = " \${alias.name}:\\"\${alias.description}\\""
431 }
432 return last
433}
434')
435 assert csrc.contains('" %s:\\"%s\\""')
436 assert !csrc.contains('" %d:\\"%d\\""')
437}
438
439fn test_generate_c_expands_builtin_option_clone_if_guard() {
440 csrc := generate_result_option_c_for_test('
441interface IClone {}
442
443struct Bag implements IClone {
444 name ?string
445}
446
447fn copy_bag(b Bag) Bag {
448 return b.clone()
449}
450 ')
451 assert csrc.contains('builtin__Option_string__clone')
452 assert csrc.contains('.state == 0')
453 assert !csrc.contains('if (s)')
454 assert !csrc.contains('string _val = builtin__Option_string__clone')
455}
456
457fn test_generate_c_does_not_wrap_builtin_option_clone_for_multiple_option_fields() {
458 csrc := generate_result_option_c_for_test('
459interface IClone {}
460
461struct Env implements IClone {
462 host ?string
463 prefix ?string
464}
465
466fn copy_env(e Env) Env {
467 return e.clone()
468}
469 ')
470 assert csrc.contains('builtin__Option_string__clone')
471 assert !csrc.contains('string _val = builtin__Option_string__clone')
472}
473
474fn test_generate_c_wraps_struct_field_option_value() {
475 csrc := generate_result_option_c_for_test('
476struct Ref {
477 value int
478}
479
480struct Holder {
481 item ?&Ref
482}
483
484fn make(r &Ref) Holder {
485 return Holder{
486 item: r
487 }
488}
489')
490 assert csrc.contains('_option_Refptr item;')
491 assert csrc.contains('_option_Refptr _opt = (_option_Refptr){ .state = 2 }; Ref* _val = r; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt;')
492 assert !csrc.contains('.item = r')
493}
494
495fn test_generate_c_wraps_module_lifetime_alias_value_for_option_field() {
496 csrc := generate_result_option_c_for_test('
497module sample
498
499struct Item {}
500
501type ItemRef[^a] = &^a Item
502
503struct Holder[^a] {
504 item ?ItemRef[^a]
505}
506
507fn make[^a](item ItemRef[^a]) Holder[^a] {
508 return Holder[^a]{
509 item: item
510 }
511}
512')
513 assert csrc.contains('_option_sample__ItemRef item;')
514 assert csrc.contains('_option_sample__ItemRef _opt = (_option_sample__ItemRef){ .state = 2 }; sample__ItemRef _val = item; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt;')
515 assert !csrc.contains('_option_ItemRef')
516 assert !csrc.contains('.item = item')
517}
518
519fn test_generate_c_wraps_deref_value_for_option_string_field() {
520 csrc := generate_result_option_c_for_test('
521struct Holder {
522 item ?string
523}
524
525struct Parser {
526 glob &string
527}
528
529fn mk_error(p Parser) Holder {
530 return Holder{
531 item: *p.glob
532 }
533}
534 ')
535 assert csrc.contains('_option_string item;')
536 assert csrc.contains('_option_string _opt = (_option_string){ .state = 2 }; string _val = *')
537 assert csrc.contains('glob; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt;')
538 assert !csrc.contains('.item = *(p).glob')
539}
540
541fn test_generate_c_wraps_string_literal_for_option_string_field() {
542 csrc := generate_result_option_c_for_test('
543struct Holder {
544 item ?string
545}
546
547fn mk_holder() Holder {
548 return Holder{
549 item: "--"
550 }
551}
552')
553 assert csrc.contains('_option_string item;')
554 assert csrc.contains('_option_string _opt = (_option_string){ .state = 2 }; string _val = (string){.str = "--"')
555 assert !csrc.contains('.item = (string){.str = "--"')
556}
557
558fn test_generate_c_interface_cast_strips_pointer_for_method_symbol() {
559 csrc := generate_result_option_c_for_test('
560interface Handle {
561 len() int
562}
563
564struct File {}
565
566fn (mut f File) len() int {
567 _ = f
568 return 1
569}
570
571fn consume(h &Handle) int {
572 return h.len()
573}
574
575fn demo(mut file File) int {
576 return consume(&file)
577}
578 ')
579 assert csrc.contains('.len = (int (*)(void*))File__len')
580 assert !csrc.contains('File*__len')
581}
582
583fn test_generate_c_heap_boxes_addr_of_call_or_cast_interface() {
584 csrc := generate_result_option_c_for_test('
585interface Runner {
586 next() int
587}
588
589struct Concrete {}
590
591fn (mut c Concrete) next() int {
592 return 7
593}
594
595fn make() &Runner {
596 mut c := &Concrete{}
597 return &Runner(c)
598}
599
600fn demo() int {
601 r := make()
602 return r.next()
603}
604')
605 assert csrc.contains('Runner* _iface_t = (Runner*)malloc(sizeof(Runner))')
606 assert csrc.contains('.next = (int (*)(void*))Concrete__next')
607 assert !csrc.contains('return ((Runner*)(c));')
608}
609
610fn test_ierror_concrete_base_prefers_current_module_qualified_method() {
611 mut env := types.Environment.new()
612 env.set_expr_type(42, types.Type(types.Struct{
613 name: 'TimeParseError'
614 }))
615 mut gen := Gen.new_with_env([], env)
616 gen.cur_module = 'time'
617 gen.fn_return_types['time__TimeParseError__msg'] = 'string'
618 base := gen.ierror_concrete_base_for_expr(ast.Expr(ast.Ident{
619 name: 'parse_err'
620 pos: token.Pos{
621 id: 42
622 }
623 }))
624 assert base == 'time__TimeParseError'
625}
626
627fn test_ierror_concrete_base_finds_qualified_method_outside_current_module() {
628 mut env := types.Environment.new()
629 env.set_expr_type(46, types.Type(types.Struct{
630 name: 'TimeParseError'
631 }))
632 mut gen := Gen.new_with_env([], env)
633 gen.cur_module = 'main'
634 gen.fn_return_types['time__TimeParseError__msg'] = 'string'
635 base := gen.ierror_concrete_base_for_expr(ast.Expr(ast.Ident{
636 name: 'parse_err'
637 pos: token.Pos{
638 id: 46
639 }
640 }))
641 assert base == 'time__TimeParseError'
642}
643
644fn test_get_expr_type_prefers_explicit_module_qualified_init_expr_type() {
645 mut env := types.Environment.new()
646 env.set_expr_type(43, types.Type(types.Struct{
647 name: 'TimeParseError'
648 }))
649 mut gen := Gen.new_with_env([], env)
650 gen.cur_module = 'time'
651 gen.emitted_types['body_time__TimeParseError'] = true
652 expr := ast.Expr(ast.InitExpr{
653 typ: ast.Ident{
654 name: 'TimeParseError'
655 }
656 pos: token.Pos{
657 id: 43
658 }
659 })
660 assert gen.get_expr_type(expr) == 'time__TimeParseError'
661}
662
663fn test_get_expr_type_prefers_module_qualified_addr_of_init_expr_type() {
664 mut env := types.Environment.new()
665 env.set_expr_type(44, types.Type(types.Pointer{
666 base_type: types.Struct{
667 name: 'TimeParseError'
668 }
669 }))
670 mut gen := Gen.new_with_env([], env)
671 gen.cur_module = 'time'
672 gen.emitted_types['body_time__TimeParseError'] = true
673 expr := ast.Expr(ast.PrefixExpr{
674 op: .amp
675 expr: ast.InitExpr{
676 typ: ast.Ident{
677 name: 'TimeParseError'
678 }
679 }
680 pos: token.Pos{
681 id: 44
682 }
683 })
684 assert gen.get_expr_type(expr) == 'time__TimeParseError*'
685}
686
687fn test_interface_concrete_type_prefers_module_qualified_addr_of_init_expr_type() {
688 mut env := types.Environment.new()
689 env.set_expr_type(45, types.Type(types.Pointer{
690 base_type: types.Struct{
691 name: 'TimeParseError'
692 }
693 }))
694 mut gen := Gen.new_with_env([], env)
695 gen.cur_module = 'time'
696 gen.emitted_types['body_time__TimeParseError'] = true
697 expr := ast.Expr(ast.PrefixExpr{
698 op: .amp
699 expr: ast.InitExpr{
700 typ: ast.Ident{
701 name: 'TimeParseError'
702 }
703 }
704 pos: token.Pos{
705 id: 45
706 }
707 })
708 assert gen.concrete_type_for_interface_value('IError', expr) == 'time__TimeParseError*'
709}
710
711fn test_interface_concrete_type_prefers_module_qualified_wrapped_addr_of_init_expr_type() {
712 mut gen := Gen.new([])
713 gen.cur_module = 'time'
714 gen.emitted_types['body_time__TimeParseError'] = true
715 expr := ast.Expr(ast.CastExpr{
716 typ: ast.Ident{
717 name: 'IError'
718 }
719 expr: ast.ParenExpr{
720 expr: ast.PrefixExpr{
721 op: .amp
722 expr: ast.InitExpr{
723 typ: ast.Ident{
724 name: 'TimeParseError'
725 }
726 }
727 }
728 }
729 })
730 assert gen.concrete_type_for_interface_value('IError', expr) == 'time__TimeParseError*'
731}
732
733fn test_generate_c_initializes_omitted_option_struct_fields_to_none() {
734 csrc := generate_result_option_c_for_test('
735struct Holder {
736 item ?int
737 name string
738}
739
740fn make_named() Holder {
741 return Holder{
742 name: "x"
743 }
744}
745
746fn make_empty() Holder {
747 return Holder{}
748}
749 ')
750 assert csrc.contains('_option_int){ .state = 2 }')
751 assert !csrc.contains('return ((Holder){0})')
752}
753
754fn test_generate_c_initializes_omitted_option_struct_payload_fields_to_none() {
755 csrc := generate_result_option_c_for_test('
756interface IClone {}
757
758struct Item implements IClone {
759 name string
760}
761
762struct Holder implements IClone {
763 name string
764 item ?Item
765}
766
767fn make_named() Holder {
768 return Holder{
769 name: "x"
770 }
771}
772
773fn make_empty() Holder {
774 return Holder{}
775}
776 ')
777 assert csrc.contains('struct _option_Item')
778 assert csrc.contains('_option_Item){ .state = 2 }')
779 assert !csrc.contains('.item = ((_option_Item){.state = 2,.name =')
780}
781
782fn test_generate_c_initializes_omitted_option_struct_field_in_named_literal_to_none() {
783 csrc := generate_result_option_c_for_test('
784module sample
785
786interface IClone {}
787
788struct Encoding implements IClone {
789 label string
790}
791
792struct Config implements IClone {
793 line_number bool
794 encoding ?Encoding
795 max_matches ?u64
796}
797
798fn make_config() Config {
799 return Config{
800 line_number: true
801 max_matches: none
802 }
803}
804 ')
805 assert csrc.contains('struct _option_sample__Encoding')
806 assert csrc.contains('_option_sample__Encoding){ .state = 2 }')
807 assert !csrc.contains('.encoding = ((_option_sample__Encoding){.state = 2,.label =')
808}
809
810fn test_generate_c_compares_struct_option_fields_fieldwise() {
811 csrc := generate_result_option_c_for_test('
812struct Mode {
813 after ?usize
814}
815
816fn same(a Mode, b Mode) bool {
817 return a == b
818}
819')
820 assert csrc.contains('_cmp_l_')
821 assert csrc.contains('.after.state == _cmp_r_')
822 assert csrc.contains('.after.state != 0 ||')
823 assert !csrc.contains('memcmp(&_cmp_l_')
824}
825
826fn test_generate_c_initializes_fixed_array_field_from_param_array() {
827 csrc := generate_result_option_c_for_test('
828struct Holder {
829 bitmap [4]u32
830}
831
832fn make(bitmap [4]u32) Holder {
833 return Holder{
834 bitmap: bitmap
835 }
836}
837')
838 assert csrc.contains('.bitmap = {bitmap[0], bitmap[1], bitmap[2], bitmap[3]}')
839 assert !csrc.contains('.bitmap = bitmap')
840}
841
842fn test_generate_c_initializes_fixed_array_field_from_local_array() {
843 csrc := generate_result_option_c_for_test('
844struct Holder {
845 bitmap [4]u32
846}
847
848fn make() Holder {
849 mut bitmap := [4]u32{}
850 return Holder{
851 bitmap: bitmap
852 }
853}
854')
855 assert csrc.contains('.bitmap = {bitmap[0], bitmap[1], bitmap[2], bitmap[3]}')
856 assert !csrc.contains('.bitmap = bitmap')
857}
858
859fn test_generate_c_declares_specialized_generic_option_return() {
860 csrc := generate_result_option_c_for_test('
861module sample
862
863struct Item {}
864
865struct Match[T] {
866 value T
867 has_value bool
868}
869
870fn (m Match[T]) inner() ?T {
871 if !m.has_value {
872 return none
873 }
874 return m.value
875}
876
877fn use(m Match[Item]) bool {
878 if value := m.inner() {
879 _ = value
880 return true
881 }
882 return false
883}
884 ')
885 assert csrc.contains('typedef struct _option_sample__Item _option_sample__Item;')
886 assert csrc.contains('struct _option_sample__Item')
887 assert csrc.contains('_option_sample__Item sample__Match_T_sample_Item__inner_T_sample_Item')
888}
889
890fn test_generate_c_expands_lifetime_generic_option_field_if_guard() {
891 csrc := generate_result_option_c_for_test('
892struct Searcher {}
893
894struct Core[^s] {
895 searcher &^s Searcher
896mut:
897 binary_byte_offset_ ?usize
898 line_number ?u64
899}
900
901fn (core Core[^s]) binary_byte_offset[^s]() ?u64 {
902 if offset := core.binary_byte_offset_ {
903 return u64(offset)
904 }
905 return none
906}
907
908fn (mut core Core[^s]) detect_binary[^s]() bool {
909 if _ := core.binary_byte_offset_ {
910 return true
911 }
912 return false
913}
914
915fn (mut core Core[^s]) count_lines[^s]() {
916 if line_number := core.line_number {
917 core.line_number = line_number + 1
918 }
919}
920
921fn use_it(searcher &Searcher) {
922 mut core := Core{
923 searcher: searcher
924 }
925 _ = core.binary_byte_offset()
926 _ = core.detect_binary()
927 core.count_lines()
928}
929
930fn main() {
931 s := Searcher{}
932 use_it(&s)
933}
934')
935 assert csrc.contains('_option_usize _or_t')
936 assert csrc.contains('usize offset = (*(usize*)')
937 assert csrc.contains('u64 line_number = (*(u64*)')
938 assert !csrc.contains('if (core.binary_byte_offset_)')
939 assert !csrc.contains('if (core->binary_byte_offset_)')
940 assert !csrc.contains('if (core->line_number)')
941 assert !csrc.contains('u64 _val = ({ _option_u64')
942}
943
944fn test_generate_c_expands_result_unwrap_method_receiver_if_guard() {
945 csrc := generate_result_option_c_for_test('
946struct FallibleUsize {
947 has_value bool
948 value usize
949}
950
951fn FallibleUsize.some(value usize) FallibleUsize {
952 return FallibleUsize{
953 has_value: true
954 value: value
955 }
956}
957
958fn (opt FallibleUsize) get() ?usize {
959 if !opt.has_value {
960 return none
961 }
962 return opt.value
963}
964
965struct Core[^s] {
966 marker &^s int
967}
968
969fn (mut core Core[^s]) shortest_match[^s]() !FallibleUsize {
970 _ = core
971 return FallibleUsize.some(1)
972}
973
974fn (mut core Core[^s]) use_it[^s]() !bool {
975 if _ := core.shortest_match()!.get() {
976 return true
977 }
978 return false
979}
980
981fn main() {
982 x := 1
983 mut core := Core{
984 marker: &x
985 }
986 _ = core.use_it() or { false }
987}
988')
989 assert csrc.contains('_result_FallibleUsize _or_t')
990 assert csrc.contains('_option_usize _or_t')
991 assert csrc.contains('FallibleUsize__get((*(FallibleUsize*)')
992 assert !csrc.contains('array__get(((FallibleUsize)')
993}
994
995fn test_generate_c_uses_branch_local_type_for_if_expr_result() {
996 csrc := generate_result_option_c_for_test('
997struct Match {
998 start_ usize
999 end_ usize
1000}
1001
1002fn Match.new(start usize, end usize) Match {
1003 return Match{
1004 start_: start
1005 end_: end
1006 }
1007}
1008
1009fn (m Match) start() usize {
1010 return m.start_
1011}
1012
1013fn (m Match) end() usize {
1014 return m.end_
1015}
1016
1017fn (m Match) is_empty() bool {
1018 return m.start_ == m.end_
1019}
1020
1021struct FallibleMatch {
1022 has_value bool
1023 value Match
1024}
1025
1026fn (opt FallibleMatch) get() ?Match {
1027 if !opt.has_value {
1028 return none
1029 }
1030 return opt.value
1031}
1032
1033fn use_it(maybe FallibleMatch) bool {
1034 invert_match := if line := maybe.get() {
1035 range := Match.new(0, line.start())
1036 range
1037 } else {
1038 range := Match.new(0, 10)
1039 range
1040 }
1041 return invert_match.is_empty()
1042}
1043
1044fn main() {
1045 _ = use_it(FallibleMatch{})
1046}
1047')
1048 assert csrc.contains('Match invert_match = ({')
1049 assert !csrc.contains('int invert_match = ({')
1050 assert csrc.contains('Match__is_empty(invert_match)')
1051 assert !csrc.contains('int__is_empty(invert_match)')
1052}
1053
1054fn test_generate_c_emits_c_struct_option_and_result_payload_wrappers() {
1055 csrc := generate_result_option_c_for_test('
1056struct C.Widget {
1057 x int
1058}
1059
1060interface IError {}
1061
1062fn optional_widget(w ?C.Widget) ?C.Widget {
1063 return none
1064}
1065
1066fn result_widget() !C.Widget {
1067 return C.Widget{}
1068}
1069')
1070 assert csrc.contains('typedef struct _option_struct_Widget _option_struct_Widget;')
1071 assert csrc.contains('typedef struct _result_struct_Widget _result_struct_Widget;')
1072 assert csrc.contains('struct _option_struct_Widget { u8 state; IError err; u8 data[sizeof(struct Widget) > 1 ? sizeof(struct Widget) : 1]; };')
1073 assert csrc.contains('struct _result_struct_Widget { bool is_error; IError err; u8 data[sizeof(struct Widget) > 1 ? sizeof(struct Widget) : 1]; };')
1074 assert csrc.contains('struct Widget _val = ((struct Widget){0});')
1075 assert !csrc.contains('struct_Widget _val')
1076 assert !csrc.contains('sizeof(struct_Widget)')
1077}
1078
1079fn test_generate_c_wraps_tuple_call_return_in_option_tuple_return() {
1080 csrc := generate_result_option_c_for_test('
1081fn decode() (string, string) {
1082 return "user", "pass"
1083}
1084
1085fn credentials() ?(string, string) {
1086 return decode()
1087}
1088')
1089 assert csrc.contains('Tuple_string_string _val = decode();')
1090 assert !csrc.contains('.arg0 = decode()')
1091}
1092
1093fn test_generate_c_emits_struct_str_function_when_interpolated() {
1094 csrc := generate_result_option_c_for_test('
1095struct Thing {
1096 x int
1097}
1098
1099fn msg(t Thing) string {
1100 return "\${t}"
1101}
1102')
1103 assert csrc.contains('string Thing__str(Thing s)')
1104 assert csrc.contains('Thing__str(t).str')
1105}
1106
1107fn test_generate_c_does_not_emit_unused_error_method_with_unwalked_str_dependency() {
1108 csrc := generate_markused_c_for_test('
1109struct Term {
1110 x int
1111}
1112
1113struct MyError {
1114 term Term
1115}
1116
1117fn (err MyError) msg() string {
1118 return "\${err.term}"
1119}
1120
1121fn (err MyError) code() int {
1122 _ = err
1123 return 0
1124}
1125
1126fn unused() ! {
1127 return MyError{}
1128}
1129
1130fn test_unused_error() {
1131 assert true
1132}
1133 ')
1134 assert !csrc.contains('string MyError__msg(MyError err)')
1135 assert !csrc.contains('Term__str((err).term)')
1136}
1137
1138fn test_generate_c_does_not_force_emit_unused_interface_candidate_method_body() {
1139 csrc := generate_markused_c_for_test('
1140interface Describer {
1141 msg() string
1142}
1143
1144struct Inner {
1145 text string
1146}
1147
1148fn (inner Inner) str() string {
1149 return inner.text
1150}
1151
1152struct ErrorLike {
1153 inner Inner
1154}
1155
1156fn (err ErrorLike) msg() string {
1157 return err.inner.str()
1158}
1159
1160fn main() {
1161 _ = 1
1162}
1163 ')
1164 assert !csrc.contains('string ErrorLike__msg(ErrorLike err)')
1165 assert !csrc.contains('Inner__str((err).inner)')
1166}
1167
1168fn test_generate_c_emits_used_struct_operator_method_body_with_markused() {
1169 csrc := generate_markused_c_for_test('
1170struct Stats {
1171 n int
1172}
1173
1174fn (left Stats) + (right Stats) Stats {
1175 return Stats{
1176 n: left.n + right.n
1177 }
1178}
1179
1180fn test_stats_plus() {
1181 left := Stats{
1182 n: 1
1183 }
1184 right := Stats{
1185 n: 2
1186 }
1187 sum := left + right
1188 assert sum.n == 3
1189}
1190')
1191 assert csrc.contains('Stats Stats__op_plus(Stats left, Stats right)')
1192 assert csrc.count('Stats__op_plus(') >= 2
1193}
1194
1195fn test_generate_c_lowers_struct_operator_compound_assignment() {
1196 csrc := generate_markused_c_for_test('
1197struct Stats {
1198 n int
1199}
1200
1201fn (left Stats) * (right Stats) Stats {
1202 return Stats{
1203 n: left.n * right.n
1204 }
1205}
1206
1207fn test_stats_mul_assign() {
1208 mut y := Stats{
1209 n: 2
1210 }
1211 x := Stats{
1212 n: 3
1213 }
1214 y *= x
1215 assert y.n == 6
1216}
1217')
1218 assert csrc.contains('Stats Stats__op_mul(Stats left, Stats right)')
1219 assert csrc.contains('y = Stats__op_mul(y, x);')
1220 assert !csrc.contains('y *= x;')
1221}
1222
1223fn test_generate_c_borrows_option_field_unwrap_payload_without_temp() {
1224 csrc := generate_result_option_c_for_test('
1225struct Holder {
1226 value ?string
1227}
1228
1229fn (h &Holder) value_ref() ?&string {
1230 if h.value != none {
1231 return unsafe { &h.value? }
1232 }
1233 return none
1234}
1235 ')
1236 assert !csrc.contains('.is_error')
1237 assert !csrc.contains('_or_t')
1238 assert csrc.contains('value.state != 0')
1239 assert csrc.contains('value.err)) + sizeof(IError)))')
1240}
1241
1242fn test_generate_c_returns_custom_error_from_result_function_as_error() {
1243 csrc := generate_result_option_c_for_test('
1244struct SizeError {}
1245
1246fn (err SizeError) msg() string {
1247 return "bad size"
1248}
1249
1250fn (err SizeError) code() int {
1251 return 7
1252}
1253
1254fn make_error() SizeError {
1255 return SizeError{}
1256}
1257
1258fn parse() !u64 {
1259 return make_error()
1260}
1261 ')
1262 assert csrc.contains('return (_result_u64){ .is_error=true, .err=')
1263 assert csrc.contains('IError_SizeError_msg_wrapper')
1264 assert csrc.contains('SizeError* _ierr_obj')
1265 assert !csrc.contains('u64 _val = main__make_error()')
1266}
1267
1268fn test_generate_c_returns_ierror_local_from_result_function_as_error() {
1269 csrc := generate_result_option_c_for_test('
1270interface IError {
1271 msg() string
1272 code() int
1273}
1274
1275struct MyError {}
1276
1277fn (err MyError) msg() string {
1278 return "bad"
1279}
1280
1281fn (err MyError) code() int {
1282 return 1
1283}
1284
1285fn make_error() IError {
1286 return MyError{}
1287}
1288
1289fn fail() !int {
1290 e := make_error()
1291 return e
1292}
1293 ')
1294 assert csrc.contains('return (_result_int){ .is_error=true, .err=e };')
1295 assert !csrc.contains('int _val = e')
1296}
1297
1298fn test_generate_c_returns_option_or_error_fallback_from_result_function_as_error() {
1299 csrc := generate_result_option_c_for_test('
1300interface IError {
1301 msg() string
1302}
1303
1304struct MessageError {}
1305
1306fn (err MessageError) msg() string {
1307 return "missing"
1308}
1309
1310fn error(msg string) IError {
1311 return MessageError{}
1312}
1313
1314struct Payload {
1315 value int
1316}
1317
1318fn maybe_payload() ?Payload {
1319 return none
1320}
1321
1322fn fail() !Payload {
1323 return maybe_payload() or { error("missing") }
1324}
1325 ')
1326 assert csrc.contains('_option_Payload _or_t')
1327 assert csrc.contains('return (_result_Payload){ .is_error=true, .err=')
1328 assert !csrc.contains(' = main__error(')
1329}
1330
1331fn test_generate_c_keeps_concrete_error_literal_when_context_is_concrete() {
1332 csrc := generate_result_option_c_for_test('
1333struct MyError {}
1334
1335fn (err MyError) msg() string {
1336 return "bad"
1337}
1338
1339fn (err MyError) code() int {
1340 return 1
1341}
1342
1343fn main() {
1344 mut errors := []MyError{}
1345 err := MyError{}
1346 errors << err
1347}
1348 ')
1349 assert csrc.contains('MyError err = ((MyError){')
1350 assert csrc.contains('array__push(((array*)(&errors)), &(MyError[1]){err});')
1351 assert !csrc.contains('IError err =')
1352 assert !csrc.contains('&(MyError[1]){((IError)')
1353}
1354
1355fn test_generate_c_decl_assign_from_option_err_uses_ierror() {
1356 csrc := generate_result_option_c_for_test('
1357struct MyError {}
1358
1359fn (err MyError) msg() string {
1360 return "bad"
1361}
1362
1363fn (err MyError) code() int {
1364 return 1
1365}
1366
1367fn maybe_value() ?int {
1368 return MyError{}
1369}
1370
1371fn main() {
1372 _ := maybe_value() or {
1373 err := err
1374 _ = err
1375 return
1376 }
1377}
1378 ')
1379 assert csrc.contains('IError err = _or_t')
1380 assert !csrc.contains('MyError err = _or_t')
1381}
1382
1383fn test_generate_c_keeps_option_if_guard_err_as_concrete_error_ref() {
1384 csrc := generate_result_option_c_for_test('
1385struct MyError {}
1386
1387fn (err MyError) msg() string {
1388 return "bad"
1389}
1390
1391fn maybe_error() ?&MyError {
1392 return &MyError{}
1393}
1394
1395fn read() string {
1396 if err := maybe_error() {
1397 return err.msg()
1398 }
1399 return ""
1400}
1401 ')
1402 assert csrc.contains('MyError__msg((*err))')
1403 assert !csrc.contains('err->_object')
1404 assert !csrc.contains('MyError__msg((*err),')
1405}
1406
1407fn test_generate_c_casts_concrete_arg_for_mut_interface_param() {
1408 csrc := generate_result_option_c_for_test('
1409interface Reader {
1410mut:
1411 read(mut []u8) !int
1412}
1413
1414struct ByteReader {}
1415
1416fn (mut rdr ByteReader) read(mut buf []u8) !int {
1417 return 0
1418}
1419
1420fn consume(mut rdr Reader) !int {
1421 mut buf := []u8{len: 4}
1422 return rdr.read(mut buf)
1423}
1424
1425fn demo() !int {
1426 mut rdr := ByteReader{}
1427 return consume(mut rdr)
1428}
1429 ')
1430 assert csrc.contains('Reader* _iface_t')
1431 assert csrc.contains('ByteReader__read')
1432 assert !csrc.contains('consume(&rdr)')
1433}
1434
1435fn test_generate_c_unwraps_interface_method_result_payload_for_propagation() {
1436 csrc := generate_result_option_c_for_test('
1437interface Reader {
1438mut:
1439 read(mut []u8) !int
1440}
1441
1442fn read_full(mut reader Reader, mut buf []u8) ! {
1443 mut offset := 0
1444 for offset < buf.len {
1445 n := reader.read(mut buf[offset..])!
1446 offset += n
1447 }
1448}
1449 ')
1450 assert csrc.contains('_result_int _or_t')
1451 assert csrc.contains('int n = (*(int*)')
1452 assert !csrc.contains('int n = ;')
1453}
1454
1455fn test_generate_c_unwraps_generic_comptime_interface_method_result_payload() {
1456 csrc := generate_result_option_c_for_test_files([
1457 '
1458module core
1459
1460import printer
1461
1462struct BufferWriter implements printer.WriteColor {}
1463
1464fn (mut w BufferWriter) write(buf []u8) !int {
1465 _ = w
1466 return buf.len
1467}
1468
1469fn demo() !int {
1470 mut standard := printer.Standard.new(BufferWriter{})
1471 return standard.write([]u8{})
1472}
1473',
1474 '
1475module printer
1476
1477pub interface WriteColor {
1478mut:
1479 write([]u8) !int
1480}
1481
1482pub struct CounterWriter[W] {
1483mut:
1484 wtr W
1485}
1486
1487pub fn CounterWriter.new[W](wtr W) CounterWriter[W] {
1488 return CounterWriter[W]{
1489 wtr: wtr
1490 }
1491}
1492
1493pub fn (mut w CounterWriter[W]) write(buf []u8) !int {
1494 $if W is WriteColor {
1495 n := w.wtr.write(buf)!
1496 return n
1497 } $else {
1498 _ = buf
1499 return 0
1500 }
1501}
1502
1503pub struct Standard[W] {
1504mut:
1505 wtr CounterWriter[W]
1506}
1507
1508pub fn Standard.new[W](wtr W) Standard[W] {
1509 return Standard[W]{
1510 wtr: CounterWriter.new(wtr)
1511 }
1512}
1513
1514pub fn (mut s Standard[W]) write(buf []u8) !int {
1515 return s.wtr.write(buf)
1516}
1517 ',
1518 ])
1519 assert csrc.contains('_result_int printer__CounterWriter_T_core_BufferWriter__write_T_core_BufferWriter')
1520 assert csrc.contains('core__BufferWriter__write(&w->wtr, buf)')
1521 assert csrc.contains('int n = (*(int*)')
1522 assert !csrc.contains('int n = ;')
1523}
1524
1525fn test_generate_c_preserves_static_lifetime_constructor_and_interface_pointer_field() {
1526 csrc := generate_result_option_c_for_test('
1527interface Reader {
1528mut:
1529 read(mut []u8) !int
1530}
1531
1532struct ByteReader {}
1533
1534fn (mut rdr ByteReader) read(mut buf []u8) !int {
1535 return 0
1536}
1537
1538struct Config {}
1539
1540struct TranscodingReader[^r] {
1541mut:
1542 rdr &^r Reader
1543 config Config
1544}
1545
1546fn TranscodingReader.new[^r](rdr &^r Reader, config Config) TranscodingReader[^r] {
1547 return TranscodingReader[^r]{
1548 rdr: rdr
1549 config: config
1550 }
1551}
1552
1553fn (mut rdr TranscodingReader[^r]) read[^r](mut buf []u8) !int {
1554 return rdr.rdr.read(mut buf)
1555}
1556
1557struct LineBuffer {}
1558
1559struct LineBufferReader[^r, ^b] {
1560mut:
1561 rdr &^r Reader
1562 buf &^b LineBuffer
1563}
1564
1565fn LineBufferReader.new[^r, ^b](rdr &^r Reader, buf &^b LineBuffer) LineBufferReader[^r, ^b] {
1566 return LineBufferReader[^r, ^b]{
1567 rdr: rdr
1568 buf: buf
1569 }
1570}
1571
1572fn use_it(mut read_from Reader, config Config, buf &LineBuffer) ! {
1573 mut decoded := TranscodingReader.new(&read_from, config)
1574 mut rdr := LineBufferReader.new(&decoded, buf)
1575 _ = rdr
1576}
1577 ')
1578 assert csrc.contains('return ((TranscodingReader){.rdr = rdr,.config = config})')
1579 assert csrc.contains('return ((LineBufferReader){.rdr = rdr,.buf = buf})')
1580 assert csrc.contains('LineBufferReader rdr = LineBufferReader__new(')
1581 assert !csrc.contains('TranscodingReader rdr = TranscodingReader__new')
1582 assert !csrc.contains('Config__read')
1583 assert !csrc.contains('LineBuffer__read')
1584}
1585
1586fn test_generate_c_preserves_c_pointer_cast_selector_field_access() {
1587 csrc := generate_result_option_c_for_test('
1588@[typedef]
1589struct C.log__Logger {
1590mut:
1591 _object voidptr
1592}
1593
1594interface Logger {
1595mut:
1596 free()
1597}
1598
1599fn raw_object(logger &Logger) voidptr {
1600 unsafe {
1601 pobject := &C.log__Logger(logger)._object
1602 return pobject
1603 }
1604}
1605 ')
1606 assert csrc.contains('pobject = ((log__Logger*)(logger))->_object;')
1607 assert !csrc.contains('voidptr* pobject')
1608 assert !csrc.contains('&logger->_object')
1609}
1610
1611fn test_generate_c_preserves_embedded_error_concrete_type_name() {
1612 csrc := generate_result_option_c_for_test('
1613struct Error {}
1614
1615fn (err Error) msg() string {
1616 return ""
1617}
1618
1619fn (err Error) code() int {
1620 return 0
1621}
1622
1623struct Eof {
1624 Error
1625}
1626
1627fn read() !int {
1628 return Eof{}
1629}
1630
1631fn demo() bool {
1632 _ := read() or {
1633 return err is Eof
1634 }
1635 return false
1636}
1637 ')
1638 assert csrc.contains('IError_Eof_type_name_wrapper')
1639 assert csrc.contains('IError_Eof_msg_wrapper')
1640 assert !csrc.contains('.type_name = IError_Error_type_name_wrapper')
1641}
1642
1643fn test_generate_c_does_not_emit_nested_generic_structs_with_placeholder_args() {
1644 csrc := generate_result_option_c_for_test('
1645struct NoColor[W] {
1646mut:
1647 wtr W
1648}
1649
1650struct CounterWriter[W] {
1651mut:
1652 wtr W
1653}
1654
1655struct Summary[W] {
1656mut:
1657 wtr CounterWriter[W]
1658}
1659
1660fn build_no_color[W](wtr W) Summary[NoColor[W]] {
1661 _ = wtr
1662 return Summary[NoColor[W]]{}
1663}
1664')
1665 assert !csrc.contains('struct CounterWriter {\n\tNoColor wtr;')
1666 assert !csrc.contains('struct Summary {\n\tCounterWriter wtr;')
1667}
1668
1669fn test_generate_c_orders_nested_generic_struct_dependencies() {
1670 csrc := generate_result_option_c_for_test('
1671struct PlainWriter {}
1672
1673struct NoColor[W] {
1674mut:
1675 wtr W
1676}
1677
1678struct CounterWriter[W] {
1679mut:
1680 wtr W
1681}
1682
1683struct Direct[W] {
1684mut:
1685 wtr CounterWriter[W]
1686}
1687
1688struct JSON[W] {
1689mut:
1690 wtr CounterWriter[NoColor[W]]
1691}
1692
1693fn build_direct[W](wtr W) Direct[W] {
1694 _ = wtr
1695 return Direct[W]{}
1696}
1697
1698fn build[W](wtr W) JSON[W] {
1699 _ = wtr
1700 return JSON[W]{}
1701}
1702
1703fn demo() {
1704 _ := build_direct(PlainWriter{})
1705 _ := build(PlainWriter{})
1706}
1707 ')
1708 dep_pos := csrc.index('struct CounterWriter_T_NoColor_T_PlainWriter {') or {
1709 panic('missing concrete nested generic struct body')
1710 }
1711 user_pos := csrc.index('struct JSON_T_PlainWriter {') or {
1712 panic('missing generic user struct body')
1713 }
1714 assert dep_pos < user_pos
1715}
1716
1717fn test_generate_c_preserves_primitive_generic_struct_binding() {
1718 csrc := generate_result_option_c_for_test('
1719struct Box[T] {
1720 value T
1721}
1722
1723fn make() Box[int] {
1724 return Box[int]{
1725 value: 1
1726 }
1727}
1728
1729fn main() {
1730 box := make()
1731 _ = box.value
1732}
1733')
1734 assert csrc.contains('struct Box {\n\tint value;')
1735 assert csrc.contains('Box make()')
1736 assert !csrc.contains('f64 make()')
1737 assert !csrc.contains('f64 box = make()')
1738}
1739
1740fn test_generate_c_preserves_nested_primitive_generic_struct_binding() {
1741 csrc := generate_result_option_c_for_test('
1742struct Inner[T] {
1743 value T
1744}
1745
1746struct Outer[T] {
1747 inner Inner[T]
1748}
1749
1750fn make() Outer[int] {
1751 return Outer[int]{
1752 inner: Inner[int]{
1753 value: 1
1754 }
1755 }
1756}
1757
1758fn main() {
1759 outer := make()
1760 _ = outer.inner.value
1761}
1762 ')
1763 assert csrc.contains('struct Inner {\n\tint value;')
1764 assert csrc.contains('struct Outer {\n\tInner inner;')
1765 || csrc.contains('struct Outer {\n\tInner_T_int inner;')
1766 assert csrc.contains('Outer make()')
1767 assert !csrc.contains('f64 make()')
1768 assert !csrc.contains('f64 outer = make()')
1769 assert !csrc.contains('((f64){.inner = ((f64){.value = 1})})')
1770}
1771
1772fn test_generate_c_skips_interface_clone_for_incomplete_generic_implementor() {
1773 csrc := generate_result_option_c_for_test('
1774interface Writer {
1775mut:
1776 write() !int
1777}
1778
1779struct Wrapper[W] {
1780mut:
1781 wtr W
1782}
1783
1784fn (mut w Wrapper[W]) write() !int {
1785 _ = w
1786 return 0
1787}
1788
1789fn wrap[W](wtr W) Wrapper[W] {
1790 return Wrapper[W]{
1791 wtr: wtr
1792 }
1793}
1794')
1795 assert !csrc.contains('sizeof(Wrapper)')
1796 assert !csrc.contains('Wrapper__write(Wrapper* w) {')
1797}
1798
1799fn test_generate_c_names_generic_sum_type_variant_fields_from_base_type() {
1800 csrc := generate_result_option_c_for_test('
1801struct A[T] {
1802 x T
1803}
1804
1805struct B[T] {
1806 x T
1807}
1808
1809type P = A[int] | B[int]
1810
1811fn make() P {
1812 return P(A[int]{
1813 x: 1
1814 })
1815}
1816')
1817 assert csrc.contains('void* _A;')
1818 assert csrc.contains('void* _B;')
1819 assert csrc.contains('._data._A =')
1820 assert !csrc.contains('void* _v0;')
1821 assert !csrc.contains('._data._v0 =')
1822}
1823
1824fn test_generate_c_does_not_rewrap_explicit_sumtype_cast_in_result_return() {
1825 csrc := generate_result_option_c_for_test('
1826struct Null {}
1827
1828type Primitive = Null | bool | []Primitive
1829
1830fn make() !Primitive {
1831 return Primitive(true)
1832}
1833')
1834 assert csrc.contains('Primitive _val = ((Primitive){._tag = 1, ._data._bool')
1835 assert !csrc.contains('Primitive _val = ((Primitive){._tag = 2,._data._Array_Primitive')
1836}
1837
1838fn test_generate_c_uses_qualified_ierror_wrapper_for_module_error() {
1839 csrc := generate_result_option_c_for_test('
1840module sample
1841
1842struct ParseError {}
1843
1844fn (err ParseError) msg() string {
1845 return ""
1846}
1847
1848fn (err ParseError) code() int {
1849 return 0
1850}
1851
1852fn read() !int {
1853 return ParseError{}
1854}
1855')
1856 assert csrc.contains('IError_sample__ParseError_msg_wrapper')
1857 assert !csrc.contains('IError_ParseError_msg_wrapper')
1858}
1859
1860fn test_generate_c_uses_qualified_ierror_wrapper_for_module_ierror_return() {
1861 csrc := generate_result_option_c_for_test('
1862module sample
1863
1864interface IError {
1865 msg() string
1866}
1867
1868struct ParseError {}
1869
1870fn (err ParseError) msg() string {
1871 return ""
1872}
1873
1874fn parse_error() IError {
1875 return ParseError{}
1876}
1877')
1878 assert csrc.contains('.msg = IError_sample__ParseError_msg_wrapper')
1879 assert !csrc.contains('.msg = IError_ParseError_msg_wrapper')
1880}
1881
1882fn test_sum_type_variant_field_name_preserves_module_qualified_variant() {
1883 mut gen := Gen.new([])
1884 gen.sum_type_variants['ast__Stmt'] = ['ast__FnDecl', 'ast__EmptyStmt']
1885 assert gen.sum_type_variant_field_name('ast__Stmt', 'FnDecl') == 'ast__FnDecl'
1886 assert gen.sum_type_variant_field_name('ast__Stmt', 'ast__FnDecl') == 'ast__FnDecl'
1887 gen.sum_type_variants['json2__Any'] = ['Array_json2__Any', 'Map_string_json2__Any']
1888 assert gen.sum_type_variant_field_name('json2__Any', 'Array_json2__Any') == 'Array_json2__Any'
1889 assert gen.sum_type_variant_field_name('json2__Any', 'Map_string_json2__Any') == 'Map_string_json2__Any'
1890 assert gen.sum_type_variant_field_name('json2__Any', 'Map_string_Any') == 'Map_string_json2__Any'
1891 gen.sum_type_variants['ast__Type'] = ['ast__FnType']
1892 assert gen.sum_type_variant_field_name('ast__Type', 'types__FnType') == 'types__FnType'
1893 if _ := gen.sum_variant_tag_path('ast__Type', 'types__FnType', []string{}) {
1894 assert false
1895 } else {
1896 assert true
1897 }
1898}
1899
1900fn test_sum_data_variant_selector_field_preserves_module_qualified_variant() {
1901 mut gen := Gen.new([])
1902 gen.sum_type_variants['ast__Stmt'] = ['ast__FnDecl', 'ast__EmptyStmt']
1903 gen.remember_runtime_local_type('stmt', 'ast__Stmt')
1904 sel := ast.SelectorExpr{
1905 lhs: ast.SelectorExpr{
1906 lhs: ast.Ident{
1907 name: 'stmt'
1908 }
1909 rhs: ast.Ident{
1910 name: '_data'
1911 }
1912 }
1913 rhs: ast.Ident{
1914 name: '_FnDecl'
1915 }
1916 }
1917 assert gen.sum_data_variant_selector_field(sel) or { '' } == '_ast__FnDecl'
1918}
1919
1920fn test_sum_narrowed_selector_uses_payload_base_type_for_pointer_env_type() {
1921 mut env := types.Environment.new()
1922 env.set_expr_type(51, types.Type(types.Pointer{
1923 base_type: types.Struct{
1924 name: 'ast__Ident'
1925 }
1926 }))
1927 mut gen := Gen.new_with_env([], env)
1928 gen.sum_type_variants['ast__Expr'] = ['ast__Ident']
1929 gen.remember_runtime_local_type('rhs', 'ast__Expr')
1930 gen.expr(ast.Expr(ast.SelectorExpr{
1931 lhs: ast.Ident{
1932 name: 'rhs'
1933 pos: token.Pos{
1934 id: 51
1935 }
1936 }
1937 rhs: ast.Ident{
1938 name: 'name'
1939 }
1940 }))
1941 out := gen.sb.str()
1942 assert out.contains('((ast__Ident*)')
1943 assert !out.contains('ast__Ident**')
1944}
1945
1946fn test_sum_cast_keeps_selector_with_declared_sum_field_type() {
1947 mut env := types.Environment.new()
1948 env.set_expr_type(62, types.Type(types.Struct{
1949 name: 'ast__SelectorExpr'
1950 }))
1951 mut gen := Gen.new_with_env([], env)
1952 gen.sum_type_variants['ast__Expr'] = ['ast__Ident', 'ast__SelectorExpr']
1953 gen.struct_field_types['ast__CallExpr.lhs'] = 'ast__Expr'
1954 gen.remember_runtime_local_type('expr', 'ast__CallExpr')
1955 sel := ast.Expr(ast.SelectorExpr{
1956 lhs: ast.Ident{
1957 name: 'expr'
1958 }
1959 rhs: ast.Ident{
1960 name: 'lhs'
1961 }
1962 pos: token.Pos{
1963 id: 62
1964 }
1965 })
1966 gen.gen_type_cast_expr('ast__Expr', sel)
1967 assert gen.sb.str() == 'expr.lhs'
1968}
1969
1970fn test_generate_c_uses_payload_base_type_for_smartcast_map_key_selector() {
1971 csrc := generate_result_option_c_for_test('
1972struct Ident {
1973 name string
1974}
1975
1976struct SelectorExpr {
1977 lhs Expr
1978}
1979
1980type Expr = Ident | SelectorExpr
1981
1982fn lookup(mut const_types map[string]string, rhs Expr) {
1983 if rhs is Ident {
1984 if ct := const_types[rhs.name] {
1985 _ = ct
1986 }
1987 }
1988}
1989')
1990 assert csrc.contains('map__get_check')
1991 assert !csrc.contains('Ident**')
1992}
1993
1994fn test_generate_c_uses_nested_sum_smartcast_for_selector_field_methods() {
1995 csrc := generate_result_option_c_for_test('
1996struct BasicLiteral {
1997 kind int
1998 value string
1999}
2000
2001struct SelectorExpr {
2002 pos Pos
2003}
2004
2005struct Pos {
2006 id int
2007}
2008
2009fn (p Pos) is_valid() bool {
2010 return p.id > 0
2011}
2012
2013type Expr = BasicLiteral | SelectorExpr
2014
2015struct Field {
2016 value Expr
2017}
2018
2019fn is_float_field(field Field) bool {
2020 return field.value is BasicLiteral && field.value.kind == 1
2021 && field.value.value.contains(".")
2022}
2023
2024fn has_valid_pos(expr Expr) bool {
2025 if expr is SelectorExpr {
2026 return expr.pos.is_valid()
2027 }
2028 return false
2029}
2030')
2031 assert csrc.contains('string__contains(')
2032 assert !csrc.contains('int__contains(')
2033 assert !csrc.contains('field.value.value')
2034 assert csrc.contains('Pos__is_valid(')
2035 assert !csrc.contains('int__is_valid(')
2036}
2037
2038fn test_generate_c_keeps_declared_sum_selector_field_cast_identity() {
2039 csrc := generate_result_option_c_for_test('
2040struct Ident {
2041 name string
2042}
2043
2044struct SelectorExpr {
2045 lhs Expr
2046}
2047
2048struct EmptyExpr {}
2049
2050struct CallExpr {
2051 lhs Expr
2052}
2053
2054type Expr = EmptyExpr | Ident | SelectorExpr
2055
2056fn keep_lhs(expr CallExpr) Expr {
2057 if expr.lhs is SelectorExpr {
2058 return Expr(expr.lhs)
2059 }
2060 return Expr(EmptyExpr{})
2061}
2062')
2063 assert !csrc.contains('= expr.lhs; memdup')
2064}
2065
2066fn test_generate_c_passes_declared_sum_selector_field_arg_without_rewrap() {
2067 csrc := generate_result_option_c_for_test('
2068struct Ident {
2069 name string
2070}
2071
2072struct SelectorExpr {
2073 lhs Expr
2074}
2075
2076struct EmptyExpr {}
2077
2078struct CallExpr {
2079 lhs Expr
2080}
2081
2082type Expr = EmptyExpr | Ident | SelectorExpr
2083
2084fn use_expr(arg Expr) {
2085 _ = arg
2086}
2087
2088fn keep_lhs(expr CallExpr) {
2089 if expr.lhs is SelectorExpr {
2090 use_expr(expr.lhs)
2091 }
2092}
2093')
2094 assert !csrc.contains('= expr.lhs; memdup')
2095}
2096
2097fn test_generate_c_initializes_fixed_array_struct_field_from_literal() {
2098 csrc := generate_result_option_c_for_test('
2099struct Process {
2100 stdio_fd [3]int
2101}
2102
2103fn new_process() &Process {
2104 return &Process{
2105 stdio_fd: [-1, -1, -1]!
2106 }
2107}
2108')
2109 assert csrc.contains('.stdio_fd = {-1, -1, -1}')
2110 assert !csrc.contains('{{-1, -1, -1}[0]')
2111}
2112
2113fn test_generate_c_renames_builtin_panic_call() {
2114 csrc := generate_result_option_c_for_test('
2115fn panic(s string) {
2116 _ = s
2117}
2118
2119fn main() {
2120 panic("x")
2121}
2122')
2123 assert csrc.contains('v_panic((string){.str = "x"')
2124 assert !csrc.contains('\tpanic((string){.str = "x"')
2125}
2126
2127fn test_generate_c_panics_for_bang_in_non_result_main() {
2128 csrc := generate_result_option_c_for_test('
2129struct App {}
2130
2131fn new_app() !&App {
2132 return &App{}
2133}
2134
2135fn main() {
2136 mut app := new_app()!
2137 _ = app
2138}
2139')
2140 assert csrc.contains('v_panic(IError__str(err));')
2141 assert !csrc.contains('return err;')
2142}
2143
2144fn test_generate_c_returns_none_for_result_or_none_in_option_return() {
2145 csrc := generate_result_option_c_for_test('
2146fn find_path() !string {
2147 return "git"
2148}
2149
2150fn get_path() ?string {
2151 return find_path() or { none }
2152}
2153')
2154 assert csrc.contains('return (_option_string){ .state = 2 };')
2155 assert !csrc.contains('/* [TODO] Type */ 0')
2156}
2157
2158fn test_generate_c_lowers_sql_create_table_to_orm_driver_call() {
2159 csrc := generate_result_option_c_for_test_files([
2160 '
2161import orm
2162
2163struct DB {}
2164
2165fn (db DB) create(table orm.Table, fields []orm.TableField) ! {}
2166
2167enum RepoStatus {
2168 open
2169 closed
2170}
2171
2172struct Repo {
2173 id int @[primary; sql: serial]
2174 name string @[unique: "repo"]
2175 status RepoStatus
2176 skip_me string @[skip]
2177}
2178
2179fn create(mut db DB) ! {
2180 sql db {
2181 create table Repo
2182 }!
2183}
2184',
2185 '
2186module orm
2187
2188pub const enum_ = -3
2189pub const type_string = 20
2190pub const type_idx = {
2191 "int": 7
2192}
2193
2194pub struct Table {}
2195
2196pub struct TableField {}
2197',
2198 ])
2199 assert csrc.contains('DB__create(')
2200 assert csrc.contains('orm__Table')
2201 assert csrc.contains('orm__TableField')
2202 assert csrc.contains('"repo"')
2203 assert csrc.contains('"id"')
2204 assert csrc.contains('"primary"')
2205 assert csrc.contains('"sql"')
2206 assert csrc.contains('"serial"')
2207 assert csrc.contains('"unique"')
2208 assert csrc.contains('.typ = 7')
2209 assert csrc.contains('.typ = -3')
2210 assert !csrc.contains('"skip_me"')
2211 assert !csrc.contains('((_result_void){0})')
2212 assert !csrc.contains('/* [TODO] SqlExpr */ 0')
2213}
2214
2215fn test_generate_c_sql_or_array_placeholder_has_no_result_probe() {
2216 csrc := generate_result_option_c_for_test('
2217struct DB {}
2218struct TwoFactor {}
2219
2220fn load(mut db DB) []TwoFactor {
2221 rows := sql db {
2222 select from TwoFactor
2223 } or {
2224 []TwoFactor{}
2225 }
2226 return rows
2227}
2228')
2229 assert csrc.contains('Array_TwoFactor rows = ((Array_TwoFactor){0});')
2230 assert !csrc.contains('Array_TwoFactor _or_t')
2231 assert !csrc.contains('.is_error')
2232}
2233