v / vlib / v2 / gen / cleanc / flag_enum_codegen_test.v
4892 lines · 4221 sloc · 103.51 KB
Raw
1// vtest build: macos
2module cleanc
3
4import os
5import strings
6import v2.ast
7import v2.parser
8import v2.pref as vpref
9import v2.token
10import v2.transformer
11import v2.types
12
13fn generate_c_for_test(code string) string {
14 return generate_c_for_test_files([code])
15}
16
17fn generate_c_for_test_files(sources []string) string {
18 tmp_dir := os.join_path(os.temp_dir(), 'v2_flag_enum_codegen_test_${os.getpid()}')
19 os.rmdir_all(tmp_dir) or {}
20 os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
21 defer {
22 os.rmdir_all(tmp_dir) or {}
23 }
24 mut paths := []string{cap: sources.len}
25 for i, code in sources {
26 tmp_file := os.join_path(tmp_dir, 'file_${i}.v')
27 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
28 paths << tmp_file
29 }
30 prefs := &vpref.Preferences{
31 backend: .cleanc
32 no_parallel: true
33 }
34 mut file_set := token.FileSet.new()
35 mut par := parser.Parser.new(prefs)
36 files := par.parse_files(paths, mut file_set)
37 env := types.Environment.new()
38 mut checker := types.Checker.new(prefs, file_set, env)
39 checker.check_files(files)
40 mut trans := transformer.Transformer.new_with_pref(env, prefs)
41 mut gen := Gen.new_with_env_and_pref(trans.transform_files(files), env, prefs)
42 return gen.gen()
43}
44
45struct CgenTestSource {
46 path string
47 code string
48}
49
50fn generate_c_for_test_sources_with_emit(sources []CgenTestSource, emit_rel_paths []string) string {
51 tmp_dir := os.join_path(os.temp_dir(), 'v2_flag_enum_codegen_test_${os.getpid()}')
52 os.rmdir_all(tmp_dir) or {}
53 os.mkdir_all(tmp_dir) or { panic('failed to create temp dir') }
54 defer {
55 os.rmdir_all(tmp_dir) or {}
56 }
57 mut paths := []string{cap: sources.len}
58 for source in sources {
59 tmp_file := os.join_path(tmp_dir, source.path)
60 os.mkdir_all(os.dir(tmp_file)) or { panic('failed to create temp source dir') }
61 os.write_file(tmp_file, source.code) or { panic('failed to write temp file') }
62 paths << tmp_file
63 }
64 mut emit_files := []string{cap: emit_rel_paths.len}
65 for rel_path in emit_rel_paths {
66 emit_files << os.join_path(tmp_dir, rel_path)
67 }
68 prefs := &vpref.Preferences{
69 backend: .cleanc
70 no_parallel: true
71 }
72 mut file_set := token.FileSet.new()
73 mut par := parser.Parser.new(prefs)
74 files := par.parse_files(paths, mut file_set)
75 env := types.Environment.new()
76 mut checker := types.Checker.new(prefs, file_set, env)
77 checker.check_files(files)
78 mut trans := transformer.Transformer.new_with_pref(env, prefs)
79 mut gen := Gen.new_with_env_and_pref(trans.transform_files(files), env, prefs)
80 gen.set_emit_modules(['main'])
81 gen.set_emit_files(emit_files)
82 return gen.gen()
83}
84
85fn test_result_value_type_preserves_generic_specialization_ptr_suffix() {
86 g := Gen{}
87 assert g.result_value_type('_result_sync__ThreadLocalStorage_T_Array_markdown__Nodeptr') == 'sync__ThreadLocalStorage_T_Array_markdown__Nodeptr'
88 assert g.result_value_type('_result_sqlite__Sqlite3_vfsptr') == 'sqlite__Sqlite3_vfs*'
89
90 mut g2 := Gen{
91 generic_struct_instances: {
92 'orm__QueryBuilder': [
93 GenericStructInstance{
94 params_key: 'array'
95 c_name: 'orm__QueryBuilder_T_array'
96 },
97 ]
98 'sync__ThreadLocalStorage': [
99 GenericStructInstance{
100 params_key: 'Array_markdown__Nodeptr'
101 c_name: 'sync__ThreadLocalStorage_T_Array_markdown__Nodeptr'
102 },
103 ]
104 }
105 }
106 assert g2.result_value_type('_result_orm__QueryBuilder_T_arrayptr') == 'orm__QueryBuilder_T_array*'
107 assert g2.result_value_type('_result_sync__ThreadLocalStorage_T_Array_markdown__Nodeptr') == 'sync__ThreadLocalStorage_T_Array_markdown__Nodeptr'
108}
109
110fn test_generate_c_rewrites_flag_enum_zero_static_call() {
111 csrc := generate_c_for_test('
112@[flag]
113enum Bits {
114 a
115 b
116}
117
118fn main() {
119 mut bits := Bits.zero()
120 bits.set(.a)
121 _ = bits.has(.a)
122}
123')
124 assert csrc.contains('(Bits)(0)')
125 assert !csrc.contains('Bits__zero')
126}
127
128fn test_generate_c_escapes_keyword_struct_fields_in_map_eq() {
129 csrc := generate_c_for_test("
130struct Item {
131 short string
132}
133
134fn main() {
135 a := {
136 'x': Item{short: 'a'}
137 }
138 b := {
139 'x': Item{short: 'b'}
140 }
141 _ = a == b
142}
143")
144 assert csrc.contains('string__eq(va._short, vb._short)')
145 assert !csrc.contains('string__eq(va.short, vb.short)')
146}
147
148fn test_generate_c_uses_concrete_map_method_name_in_generic_comptime_body() {
149 code := [
150 'fn (m map[string]int) query_item(name string) ?int {',
151 ' return m[name]',
152 '}',
153 '',
154 'struct Holder {',
155 ' items map[string]int',
156 '}',
157 '',
158 'fn find_item[T](value T) ?int {',
159 ' @DLR@if T is Holder {',
160 ' return value.items.query_item(@SQ@x@SQ@)',
161 ' }',
162 ' return none',
163 '}',
164 '',
165 'fn main() {',
166 ' _ = find_item[Holder](Holder{})',
167 '}',
168 ].join('\n').replace('@DLR@', '$').replace('@SQ@', "'")
169 csrc := generate_c_for_test(code)
170 assert csrc.contains('Map_string_int__query_item(')
171 assert !csrc.contains('map__query_item(')
172}
173
174fn test_generate_c_records_generic_struct_instantiations_inside_returned_call_args() {
175 csrc := generate_c_for_test_files([
176 '
177module api
178
179pub struct ApiSuccessResponse[T] {
180pub:
181 success bool
182 result T
183}
184',
185 '
186module main
187
188import api
189
190struct FileInfo {
191 name string
192}
193
194fn json[T](j T) int {
195 _ = j
196 return 0
197}
198
199fn handle_bool() int {
200 return json(api.ApiSuccessResponse[bool]{
201 success: true
202 result: true
203 })
204}
205
206fn handle_files() int {
207 mut files := []FileInfo{}
208 return json(api.ApiSuccessResponse[[]FileInfo]{
209 success: true
210 result: files
211 })
212}
213',
214 ])
215 assert csrc.contains('\tbool result;')
216 assert csrc.contains('\tArray_FileInfo result;')
217 assert csrc.contains('json_T_api_ApiSuccessResponse_T_Array_FileInfo(((api__ApiSuccessResponse_T_Array_FileInfo){')
218 assert !csrc.contains('json_T_api_ApiSuccessResponse(((api__ApiSuccessResponse){.success = true,.result = files})')
219}
220
221fn test_called_specialized_name_does_not_remap_concrete_generic_prefix() {
222 g := Gen{
223 called_fn_names: {
224 'json2__Encoder__encode_value_T_api_ApiSuccessResponse_T_bool': true
225 }
226 }
227 if _ := g.called_specialized_name_for_base('json2__Encoder__encode_value_T_api_ApiSuccessResponse') {
228 assert false
229 }
230 assert g.called_specialized_name_for_base('json2__Encoder__encode_value') or { '' } == 'json2__Encoder__encode_value_T_api_ApiSuccessResponse_T_bool'
231}
232
233fn test_generate_c_specializes_untyped_int_generic_arg_as_int() {
234 csrc := generate_c_for_test('
235fn id[T](value T) T {
236 return value
237}
238
239fn main() {
240 _ = id(0)
241}
242')
243 assert csrc.contains('id_T_int(')
244 assert !csrc.contains('id_T_int_literal')
245}
246
247fn test_generate_c_resolves_generic_map_for_in_key_casts() {
248 mut g := Gen{}
249 g.active_generic_types = {
250 'T': types.Type(types.string_)
251 }
252 assert g.expr_type_to_c(ast.Ident{
253 name: 'T*'
254 }) == 'string*'
255 assert g.expr_type_to_c(ast.Ident{
256 name: 'T**'
257 }) == 'string**'
258}
259
260fn test_generate_c_passes_json_decode_type_marker_as_null() {
261 mut g := Gen{
262 sb: strings.new_builder(64)
263 emitted_types: {
264 'body_Foo': true
265 }
266 fn_param_is_ptr: {
267 'json__decode': [true, false]
268 }
269 }
270 g.gen_call_arg('json__decode', 0, ast.PrefixExpr{
271 op: .amp
272 expr: ast.Ident{
273 name: 'Foo'
274 }
275 })
276 assert g.sb.str() == 'NULL'
277}
278
279fn test_generate_c_uses_argument_lowering_for_json_module_decode_fallback() {
280 csrc := generate_c_for_test_files([
281 '
282module json
283
284pub fn decode(typ voidptr, s string) !voidptr {
285 return 0
286}
287',
288 '
289module main
290
291import json
292
293struct Foo {}
294
295fn main() {
296 _ := json.decode(Foo, "") or { return }
297}
298',
299 ])
300 assert csrc.contains('json__decode(NULL,')
301 assert !csrc.contains('json__decode(&Foo,')
302}
303
304fn test_generate_c_wraps_json_decode_result_for_or_default_value() {
305 csrc := generate_c_for_test_files([
306 '
307module json
308
309pub fn decode(typ voidptr, s string) !voidptr {
310 return 0
311}
312',
313 '
314module main
315
316import json
317
318struct Foo {
319 name string
320}
321
322fn main() {
323 foo := json.decode(Foo, "") or { Foo{} }
324 _ = foo
325}
326',
327 ])
328 assert csrc.contains('_result_voidptr _json_decode_raw_')
329 assert csrc.contains('_result_Foo _json_decode_res_')
330 assert csrc.contains('_result_ok(_json_decode_ptr_')
331 assert !csrc.contains('(*(void**)(((u8*)(&_or_t')
332}
333
334fn test_generate_c_uses_lhs_enum_type_for_sumtype_smartcast_enum_shorthand_compare() {
335 csrc := generate_c_for_test('
336enum MD_BLOCKTYPE {
337 md_block_doc
338 md_block_code
339}
340
341type ParentType = MD_BLOCKTYPE | string
342
343fn main() {
344 parent := ParentType(MD_BLOCKTYPE.md_block_code)
345 if parent is MD_BLOCKTYPE && parent == .md_block_code {
346 _ := true
347 }
348}
349')
350 assert csrc.contains('MD_BLOCKTYPE__md_block_code')
351 assert !csrc.contains('T__md_block_code')
352}
353
354fn test_generate_c_preamble_rwmutex_has_lazy_init_field() {
355 prefs := &vpref.Preferences{
356 backend: .cleanc
357 }
358 env := types.Environment.new()
359 mut gen := Gen.new_with_env_and_pref([]ast.File{}, env, prefs)
360 gen.write_preamble()
361 csrc := gen.sb.str()
362 assert csrc.contains('typedef struct sync__RwMutex { pthread_rwlock_t mutex; u32 inited; } sync__RwMutex;')
363}
364
365fn test_generate_c_specializes_generic_method_from_payload_arg_not_receiver() {
366 csrc := generate_c_for_test('
367struct App {}
368struct Payload {}
369
370fn (mut app App) dispatch[T](id int, payload T) {
371 _ = id
372 _ = payload
373}
374
375fn main() {
376 mut app := App{}
377 app.dispatch(1, Payload{})
378}
379')
380 assert csrc.contains('App__dispatch_T_Payload(')
381 assert !csrc.contains('App__dispatch_T_App(')
382}
383
384fn test_generate_c_qualifies_local_module_generic_call_in_specialized_body() {
385 csrc := generate_c_for_test('
386module veb
387
388pub struct RunParams {}
389struct App {}
390struct Context {}
391
392pub fn run_at[A, X](mut app A, params RunParams) ! {
393 run_new[A, X](mut app, params)!
394}
395
396pub fn run_new[A, X](mut app A, params RunParams) ! {
397 _ = app
398 _ = params
399}
400
401fn boot() {
402 mut app := App{}
403 run_at[App, Context](mut app, RunParams{}) or { return }
404}
405
406fn direct() {
407 mut app := App{}
408 run_new[App, Context](mut app, RunParams{}) or { return }
409}
410')
411 assert csrc.contains('veb__run_new_T_veb_App_veb_Context(app, params)')
412 assert !csrc.contains('\trun_new_T_A_X(')
413 assert !csrc.contains('veb__run_new(app, params)')
414 assert !csrc.contains('veb__run_new_T_A_X(app, params)')
415}
416
417fn test_generate_c_uses_map_smartcast_for_map_for_in_key_values() {
418 csrc := generate_c_for_test('
419type Any = []int | map[string]int
420
421fn values(f Any) []int {
422 if f is []int {
423 return f
424 } else if f is map[string]int {
425 mut arr := []int{}
426 for _, v in f {
427 arr << v
428 }
429 return arr
430 }
431 return []int{}
432}
433')
434 assert csrc.contains('Map_string_int _map_map_')
435 assert csrc.contains('f._data._Map_string_int')
436 assert csrc.contains('Map_string_int*)(f._data._Map_string_int')
437 assert !csrc.contains('f._data._Array_int))->key_values')
438 assert !csrc.contains('= ((((Array_int*)(f._data._Array_int))')
439}
440
441fn test_generate_c_uses_map_smartcast_for_recursive_map_for_in_key_values() {
442 csrc := generate_c_for_test('
443module json2
444
445type Any = []Any | map[string]Any
446
447fn values(f Any) []Any {
448 if f is []Any {
449 return f
450 } else if f is map[string]Any {
451 mut arr := []Any{}
452 for _, v in f {
453 arr << v
454 }
455 return arr
456 }
457 return []Any{}
458}
459')
460 assert csrc.contains('Map_string_json2__Any _map_map_')
461 assert csrc.contains('f._data._Map_string_json2__Any')
462 assert csrc.contains('Map_string_json2__Any*)(f._data._Map_string_json2__Any')
463 assert !csrc.contains('= ((((Array_json2__Any*)(f._data._Array_json2__Any))')
464}
465
466fn test_markused_runtime_keep_rules_include_transitive_runtime_dependencies() {
467 assert is_builtin_runtime_keep_file('/tmp/vlib/builtin/builtin.c.v')
468 assert is_builtin_runtime_keep_file('/tmp/vlib/math/bits/bits.c.v')
469 assert should_keep_builtin_string_decl(ast.FnDecl{
470 name: 'compare_lower_strings'
471 })
472 assert should_keep_builtin_string_decl(ast.FnDecl{
473 name: 'compare_strings_by_len'
474 })
475}
476
477fn test_collected_c_directives_follow_emit_modules() {
478 mut g := Gen.new([
479 ast.File{
480 name: 'core.v'
481 stmts: [
482 ast.ModuleStmt{
483 name: 'core'
484 },
485 ast.Directive{
486 name: 'include'
487 value: '"core_impl.c"'
488 },
489 ]
490 },
491 ast.File{
492 name: 'feature.v'
493 stmts: [
494 ast.ModuleStmt{
495 name: 'feature'
496 },
497 ast.Directive{
498 name: 'include'
499 value: '<feature.h>'
500 },
501 ast.Directive{
502 name: 'include'
503 value: '"feature_impl.c"'
504 },
505 ]
506 },
507 ])
508 g.set_emit_modules(['core'])
509 g.write_preamble()
510 csrc := g.sb.str()
511 assert csrc.contains('#include "core_impl.c"')
512 assert csrc.contains('#include <feature.h>')
513 assert !csrc.contains('#include "feature_impl.c"')
514}
515
516fn test_gen_call_arg_keeps_ierror_for_ierror_param() {
517 mut g := Gen{
518 sb: strings.new_builder(16)
519 fn_param_types: {
520 'uses': ['IError']
521 }
522 runtime_local_types: {
523 'err': 'struct IError'
524 }
525 }
526 g.gen_call_arg('uses', 0, ast.Ident{
527 name: 'err'
528 })
529 assert g.sb.str() == 'err'
530}
531
532fn test_header_type_const_emits_extern_ierror() {
533 mut g := Gen{
534 sb: strings.new_builder(64)
535 emitted_types: {
536 'body_IError': true
537 }
538 fn_owner_file: map[string]int{}
539 const_exprs: map[string]string{}
540 }
541 g.cur_module = 'net'
542 g.gen_const_decl_extern(ast.ConstDecl{
543 fields: [
544 ast.FieldInit{
545 name: 'err_timed_out'
546 value: ast.Ident{
547 name: 'IError'
548 }
549 },
550 ]
551 })
552 csrc := g.sb.str()
553 assert csrc.contains('extern IError net__err_timed_out;')
554 assert !csrc.contains('#define net__err_timed_out IError')
555}
556
557fn test_header_type_const_emits_extern_qualified_type() {
558 mut g := Gen{
559 sb: strings.new_builder(64)
560 emitted_types: {
561 'body_os__File': true
562 }
563 fn_owner_file: map[string]int{}
564 const_exprs: map[string]string{}
565 }
566 g.cur_module = 'log'
567 g.gen_const_decl_extern(ast.ConstDecl{
568 fields: [
569 ast.FieldInit{
570 name: 'stderr'
571 value: ast.SelectorExpr{
572 lhs: ast.Expr(ast.Ident{
573 name: 'os'
574 })
575 rhs: ast.Ident{
576 name: 'File'
577 }
578 }
579 },
580 ]
581 })
582 csrc := g.sb.str()
583 assert csrc.contains('extern os__File log__stderr;')
584 assert !csrc.contains('#define log__stderr os__File')
585}
586
587fn test_gen_return_propagates_struct_ierror_local_from_result_function() {
588 mut g := Gen{
589 sb: strings.new_builder(64)
590 cur_fn_ret_type: '_result_int'
591 runtime_local_types: {
592 'e': 'struct IError'
593 }
594 }
595 g.gen_stmt(ast.ReturnStmt{
596 exprs: [ast.Expr(ast.Ident{
597 name: 'e'
598 })]
599 })
600 assert g.sb.str() == 'return (_result_int){ .is_error=true, .err=e };\n'
601}
602
603fn test_get_str_fn_for_type_prefers_concrete_str_before_alias_base() {
604 mut g := Gen.new([]ast.File{})
605 g.alias_base_types['openssl__SSLConn'] = 'mbedtls__SSLConn'
606 g.alias_base_types['mbedtls__SSLConn'] = 'openssl__SSLConn'
607 g.fn_return_types['openssl__SSLConn_str'] = 'string'
608 g.fn_return_types['mbedtls__SSLConn_str'] = 'string'
609 assert g.get_str_fn_for_type('openssl__SSLConn') or { '' } == 'openssl__SSLConn_str'
610 assert g.get_str_fn_for_type('mbedtls__SSLConn') or { '' } == 'mbedtls__SSLConn_str'
611}
612
613fn test_get_str_fn_for_type_stops_on_alias_cycles_without_str_fn() {
614 mut g := Gen.new([]ast.File{})
615 g.alias_base_types['openssl__SSLConn'] = 'mbedtls__SSLConn'
616 g.alias_base_types['mbedtls__SSLConn'] = 'openssl__SSLConn'
617 assert g.get_str_fn_for_type('openssl__SSLConn') or { '' } == ''
618}
619
620fn test_generate_c_treats_generic_arg_or_index_on_known_fn_as_fn_value() {
621 mut g := Gen{
622 sb: strings.new_builder(64)
623 cur_module: 'veb'
624 fn_return_types: {
625 'veb__handler': 'int'
626 }
627 }
628 g.expr(ast.GenericArgOrIndexExpr{
629 lhs: ast.Ident{
630 name: 'handler'
631 }
632 expr: ast.Ident{
633 name: 'A'
634 }
635 })
636 assert g.sb.str() == 'veb__handler'
637
638 g.sb = strings.new_builder(64)
639 g.gen_index_expr(ast.IndexExpr{
640 lhs: ast.Ident{
641 name: 'handler'
642 }
643 expr: ast.Ident{
644 name: 'A'
645 }
646 })
647 assert g.sb.str() == 'veb__handler'
648}
649
650fn test_generate_c_casts_generic_function_type_alias_call_expr() {
651 mut g := Gen{
652 sb: strings.new_builder(64)
653 cur_module: 'veb'
654 fn_type_aliases: {
655 'veb__MiddlewareHandler': true
656 }
657 }
658 cast_lhs := ast.GenericArgOrIndexExpr{
659 lhs: ast.Ident{
660 name: 'MiddlewareHandler'
661 }
662 expr: ast.Ident{
663 name: 'T'
664 }
665 }
666 g.call_expr(cast_lhs, [ast.Expr(ast.Ident{
667 name: 'handler'
668 })])
669 assert g.sb.str() == '((veb__MiddlewareHandler)(handler))'
670
671 g.sb = strings.new_builder(64)
672 g.gen_assign_stmt(ast.AssignStmt{
673 op: .decl_assign
674 lhs: [ast.Expr(ast.Ident{
675 name: 'func'
676 })]
677 rhs: [
678 ast.Expr(ast.CallExpr{
679 lhs: cast_lhs
680 args: [ast.Expr(ast.Ident{
681 name: 'handler'
682 })]
683 }),
684 ]
685 })
686 assert g.sb.str() == 'veb__MiddlewareHandler func = ((veb__MiddlewareHandler)(handler));\n'
687
688 g.sb = strings.new_builder(64)
689 g.call_expr(ast.Ident{
690 name: 'MiddlewareHandler_T'
691 }, [ast.Expr(ast.Ident{
692 name: 'handler'
693 })])
694 assert g.sb.str() == '((veb__MiddlewareHandler)(handler))'
695}
696
697fn test_generate_c_resolves_specialized_generic_receiver_method() {
698 mut g := Gen{
699 fn_return_types: {
700 'dep__Middleware_T_Context__use': 'void'
701 }
702 fn_param_is_ptr: {
703 'dep__Middleware_T_Context__use': [true, false]
704 }
705 }
706 assert g.resolve_method_on_concrete_type('dep__Middleware', 'use') or { '' } == 'dep__Middleware_T_Context__use'
707}
708
709fn test_generate_c_emits_builtin_string_field_selector_as_field_access() {
710 mut g := Gen{
711 sb: strings.new_builder(64)
712 runtime_local_types: {
713 's': 'string'
714 }
715 }
716 g.expr(ast.SelectorExpr{
717 lhs: ast.Ident{
718 name: 's'
719 }
720 rhs: ast.Ident{
721 name: 'str'
722 }
723 })
724 assert g.sb.str() == 's.str'
725
726 csrc := generate_c_for_test("
727const digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999'
728
729fn first_digit(i int) u8 {
730 return unsafe { digit_pairs.str[i] }
731}
732
733fn first_byte(s string, i int) u8 {
734 return unsafe { s.str[i] }
735}
736
737struct RE {
738 flag int
739}
740
741@[direct_array_access; inline]
742fn (re &RE) get_char(in_txt string, i int) (u32, int) {
743 ini := unsafe { in_txt.str[i] }
744 if (re.flag & 1) != 0 || (ini & 0x80) == 0 {
745 return u32(ini), 1
746 }
747 mut tmp := 0
748 mut ch := u32(0)
749 for tmp < 2 {
750 ch = (ch << 8) | unsafe { in_txt.str[i + tmp] }
751 tmp++
752 }
753 return ch, 2
754}
755")
756 assert csrc.contains('return digit_pairs.str[')
757 assert csrc.contains('return (s).str[') || csrc.contains('return s.str[')
758 assert csrc.contains('u8 ini = in_txt.str[') || csrc.contains('u8 ini = (in_txt).str[')
759 assert csrc.contains('| in_txt.str[') || csrc.contains('| (in_txt).str[')
760 assert !csrc.contains('return string__str')
761 assert !csrc.contains('u8 ini = string__str')
762}
763
764fn test_generate_c_uses_pointer_selector_for_channel_field_len() {
765 csrc := generate_c_for_test('
766struct DB {}
767
768struct Pool {
769 connections chan DB
770}
771
772fn (mut pool Pool) close() {
773 for _ in 0 .. pool.connections.len {
774 _ = <-pool.connections or { break }
775 }
776 }
777')
778 assert csrc.contains('atomic_load_u32(&((sync__Channel*)(pool->connections))->read_avail)')
779 assert !csrc.contains('pool->connections.len')
780 assert !csrc.contains('pool->connections->len')
781}
782
783fn test_generate_c_does_not_use_short_embedded_owner_for_qualified_field_type() {
784 csrc := generate_c_for_test_files([
785 '
786module dep
787
788pub struct Config {
789pub:
790 cert string
791}
792',
793 '
794module wrap
795
796import dep
797
798pub struct Config {
799 dep.Config
800}
801',
802 '
803module app
804
805import dep
806
807pub struct RunParams {
808pub:
809 ssl_config dep.Config
810}
811
812fn ssl_enabled(params RunParams) bool {
813 return params.ssl_config.cert != ""
814}
815',
816 ])
817 assert csrc.contains('params.ssl_config.cert')
818 assert !csrc.contains('params.ssl_config.Config.cert')
819}
820
821fn test_generate_c_does_not_default_pointer_to_map_with_map_value() {
822 csrc := generate_c_for_test('
823struct Context {
824 custom_mime_types_ref &map[string]string = unsafe { nil }
825 custom_mime_types map[string]string
826}
827
828fn make() &Context {
829 return &Context{}
830}
831')
832 assert csrc.contains('.custom_mime_types = new_map(')
833 assert !csrc.contains('.custom_mime_types_ref = new_map(')
834}
835
836fn test_generate_c_initializes_embedded_struct_map_defaults() {
837 csrc := generate_c_for_test('
838struct StaticHandler {
839 static_files map[string]string
840}
841
842struct App {
843 StaticHandler
844 version string
845}
846
847fn make() &App {
848 return &App{
849 version: "dev"
850 }
851}
852')
853 assert csrc.contains('.StaticHandler.static_files = new_map(')
854}
855
856fn test_generate_c_initializes_imported_embedded_struct_map_defaults() {
857 csrc := generate_c_for_test_files([
858 '
859module web
860
861pub struct StaticHandler {
862pub mut:
863 static_files map[string]string
864}
865',
866 '
867module main
868
869import web
870
871struct App {
872 web.StaticHandler
873 version string
874}
875
876fn make() &App {
877 return &App{
878 version: "dev"
879 }
880}
881',
882 ])
883 assert csrc.contains('.StaticHandler.static_files = new_map(')
884}
885
886fn test_generate_c_declares_generic_struct_literal_with_caller_context_type() {
887 csrc := generate_c_for_test_files([
888 '
889module veb
890
891pub struct Context {}
892
893pub fn make[X]() {
894 mut user_context := X{}
895 _ = user_context
896}
897',
898 '
899module main
900
901import veb
902
903pub struct Context {
904 veb.Context
905}
906
907fn boot() {
908 veb.make[Context]()
909}
910',
911 ])
912 assert csrc.contains('Context user_context = ((Context)')
913 assert !csrc.contains('veb__Context user_context = ((Context)')
914}
915
916fn test_generate_c_skips_promoted_embedded_defaults_when_embedded_field_is_initialized() {
917 csrc := generate_c_for_test_files([
918 '
919module framework
920
921pub struct Context {
922pub mut:
923 content_type string
924}
925
926pub fn make[X](ctx &Context) {
927 mut user_context := X{
928 Context: ctx
929 }
930 _ = user_context
931}
932',
933 '
934module main
935
936import framework
937
938pub struct Context {
939 framework.Context
940}
941
942fn boot() {
943 mut ctx := &framework.Context{}
944 framework.make[Context](ctx)
945}
946',
947 ])
948 mut user_context_line := ''
949 for line in csrc.split('\n') {
950 if line.contains('Context user_context =') {
951 user_context_line = line
952 break
953 }
954 }
955 assert user_context_line.contains('Context user_context =')
956 assert !user_context_line.contains(',.content_type =')
957}
958
959fn test_generate_c_initializes_nontrivial_globals_at_runtime() {
960 csrc := generate_c_for_test('
961@[has_globals]
962module main
963
964const default_name = "tenant_id"
965
966struct Null {}
967
968type Primitive = Null | int
969
970const null_primitive = Primitive(Null{})
971
972struct State {
973mut:
974 field_name string
975 current Primitive
976}
977
978__global state = State{
979 field_name: default_name
980 current: null_primitive
981}
982
983fn main() {
984 _ = state.field_name
985}
986')
987 assert csrc.contains('State state;')
988 assert !csrc.contains('State state = ((State)')
989 assert csrc.contains('void __v_init_consts_main()')
990 assert csrc.contains('state = ((State){')
991}
992
993fn test_generate_c_initializes_const_map_literals_at_runtime() {
994 csrc := generate_c_for_test("
995const mime_types = {
996 '.css': 'text/css'
997 '.js': 'text/javascript'
998}
999
1000fn main() {
1001 _ = mime_types
1002}
1003")
1004 assert csrc.contains('Map_string_string mime_types = {0};')
1005 assert csrc.contains('void __v_init_consts_main()')
1006 assert csrc.contains('mime_types = new_map_init_noscan_value')
1007}
1008
1009fn test_generate_c_lowers_typeof_generic_idx_in_const_init() {
1010 csrc := generate_c_for_test('
1011const num64 = [typeof[i64]().idx, typeof[u64]().idx]
1012const type_string = typeof[string]().idx
1013
1014fn main() {
1015 _ := num64.len + type_string
1016}
1017')
1018 assert !csrc.contains('typeof_T_')
1019 assert csrc.contains('{8, 13}')
1020 assert csrc.contains('20')
1021}
1022
1023fn test_generate_c_passes_empty_array_for_empty_variadic_method_call() {
1024 csrc := generate_c_for_test('
1025struct Box {}
1026struct Err {}
1027
1028fn (b Box) close(errs ...Err) {}
1029
1030fn main() {
1031 b := Box{}
1032 b.close()
1033}
1034')
1035 assert !csrc.contains('Box__close(b);')
1036 assert csrc.contains('Box__close(b, ')
1037 assert csrc.contains('new_array_from_c_array(0, 0, sizeof(Err)')
1038}
1039
1040fn test_generate_c_passes_empty_array_for_empty_channel_close() {
1041 csrc := generate_c_for_test('
1042fn main() {
1043 ch := chan bool{cap: 1}
1044 ch.close()
1045}
1046')
1047 assert !csrc.contains('((void(*)())ch->close)()')
1048 assert !csrc.contains('chan__close(ch);')
1049 assert !csrc.contains('sync__Channel__close(ch);')
1050 assert csrc.contains('sync__Channel__close(ch, ') || csrc.contains('chan__close(ch, ')
1051 assert csrc.contains('new_array_from_c_array(0, 0, sizeof(IError)')
1052}
1053
1054fn test_generate_c_keeps_fixed_array_zero_global_static() {
1055 csrc := generate_c_for_test('
1056@[has_globals]
1057module main
1058
1059const depth = 4
1060
1061__global stack = [depth]int{}
1062
1063fn main() {
1064 _ = stack[0]
1065}
1066')
1067 assert csrc.contains('Array_fixed_int_4 stack = {0};')
1068 assert !csrc.contains('memcpy(stack,')
1069 assert !csrc.contains('(array){0}')
1070}
1071
1072fn test_generate_c_resolves_const_backed_fixed_array_global_extern_len() {
1073 csrc := generate_c_for_test('
1074@[has_globals]
1075module main
1076
1077const depth = 4
1078
1079__global (
1080 stack [depth]int
1081)
1082
1083fn main() {
1084 _ = stack[0]
1085}
1086')
1087 assert csrc.contains('extern int stack[4];')
1088 assert !csrc.contains('extern int stack[depth];')
1089}
1090
1091fn test_generate_c_uses_wrapped_fixed_array_return_for_fn_type_params() {
1092 csrc := generate_c_for_test('
1093fn call(cb fn () [4]u8) [4]u8 {
1094 return cb()
1095}
1096
1097fn make() [4]u8 {
1098 return [4]u8{}
1099}
1100
1101fn main() {
1102 _ = call(make)
1103}
1104')
1105 assert csrc.contains('_v_Array_fixed_u8_4 (*cb)(void)')
1106 assert !csrc.contains('call(Array_fixed_u8_4 (*cb)(void))')
1107}
1108
1109fn test_generate_c_filters_lifetime_params_from_generic_struct_binding() {
1110 csrc := generate_c_for_test('
1111struct Value {
1112 n int
1113}
1114
1115struct Ref[^a, T] {
1116 value T
1117}
1118
1119struct Holder[^a] {
1120 item Ref[^a, Value]
1121}
1122')
1123 assert csrc.contains('struct Ref {')
1124 assert csrc.contains('Value value;')
1125 assert !csrc.contains('T value;')
1126}
1127
1128fn test_generate_c_lowers_pointer_type_params_receivers_fields_and_generics() {
1129 csrc := generate_c_for_test('
1130struct Foo {
1131 value int
1132}
1133
1134struct Node[T] {
1135 value T
1136}
1137
1138struct Holder {
1139 item &Foo
1140 node &Node[Foo]
1141}
1142
1143fn ptr_value(foo &Foo) int {
1144 return foo.value
1145}
1146
1147fn (foo &Foo) method_value() int {
1148 return foo.value
1149}
1150
1151fn main() {
1152 foo := Foo{}
1153 _ := ptr_value(&foo)
1154 _ := foo.method_value()
1155}
1156')
1157 assert csrc.contains('Foo* item;')
1158 assert csrc.contains('Node_T_Foo* node;')
1159 assert csrc.contains('Foo value;')
1160 assert csrc.contains('ptr_value(Foo* foo)')
1161 assert csrc.contains('Foo__method_value(Foo* foo)')
1162 assert !csrc.contains('int item;')
1163 assert !csrc.contains('int ptr_value(int foo)')
1164}
1165
1166fn test_generate_c_match_on_enum_does_not_constrain_branch_array_literals() {
1167 csrc := generate_c_for_test('
1168enum Choice {
1169 color
1170 none
1171}
1172
1173fn choices(id Choice) []string {
1174 return match id {
1175 .color { ["never", "auto"] }
1176 .none { []string{} }
1177 }
1178}
1179')
1180 assert csrc.contains('sizeof(string)')
1181 assert !csrc.contains('&(Choice[')
1182}
1183
1184fn test_generate_c_struct_eq_recurses_into_nested_string_fields() {
1185 csrc := generate_c_for_test('
1186struct Encoding {
1187 label string
1188}
1189
1190enum Kind {
1191 auto
1192 disabled
1193 some
1194}
1195
1196struct Mode {
1197 kind Kind = .auto
1198 encoding Encoding
1199}
1200
1201fn same(a Mode, b Mode) bool {
1202 return a == b
1203}
1204')
1205 assert csrc.contains('string__eq(')
1206 assert csrc.contains('.encoding.label')
1207}
1208
1209fn test_generate_c_emits_generic_method_helper_specialization_body() {
1210 csrc := generate_c_for_test('
1211struct Mapper {}
1212
1213struct Schema {}
1214
1215fn (mut m Mapper) helper[T]() int {
1216 return 1
1217}
1218
1219fn (mut m Mapper) parse[T]() int {
1220 return m.helper[T]()
1221}
1222
1223fn main() {
1224 mut m := Mapper{}
1225 _ = m.parse[Schema]()
1226}
1227')
1228 assert csrc.contains('int Mapper__parse_T_Schema(Mapper* m) {')
1229 assert csrc.contains('int Mapper__helper_T_Schema(Mapper* m) {')
1230}
1231
1232fn test_generate_c_specializes_implicit_generic_function_calls_from_all_args() {
1233 csrc := generate_c_for_test('
1234struct Left {}
1235struct RightA {}
1236struct RightB {}
1237
1238fn pair[T, U](x T, y U) int {
1239 _ = x
1240 _ = y
1241 return 1
1242}
1243
1244fn main() {
1245 left := Left{}
1246 a := RightA{}
1247 b := RightB{}
1248 _ = pair(left, a)
1249 _ = pair(left, b)
1250}
1251')
1252 assert csrc.contains('int pair_T_Left_RightA(Left x, RightA y);')
1253 assert csrc.contains('int pair_T_Left_RightB(Left x, RightB y);')
1254 assert csrc.contains('pair_T_Left_RightA(left, a)')
1255 assert csrc.contains('pair_T_Left_RightB(left, b)')
1256 assert !csrc.contains('pair(left, a)')
1257 assert !csrc.contains('pair(left, b)')
1258}
1259
1260fn test_generate_c_does_not_specialize_plain_generic_function_for_struct_fields() {
1261 csrc := generate_c_for_test('
1262enum Pattern {
1263 a
1264}
1265
1266struct Thing {
1267 pattern Pattern
1268 named bool
1269}
1270
1271fn (thing Thing) find_at() int {
1272 _ = thing
1273 return 1
1274}
1275
1276fn find[M](matcher M) int {
1277 return matcher.find_at()
1278}
1279
1280fn main() {
1281 thing := Thing{}
1282 _ = find(thing)
1283}
1284')
1285 assert csrc.contains('int find_T_Thing(Thing matcher) {')
1286 assert csrc.contains('return Thing__find_at(matcher);')
1287 assert !csrc.contains('find_T_Pattern')
1288 assert !csrc.contains('find_T_bool')
1289 assert !csrc.contains('Pattern__find_at')
1290 assert !csrc.contains('bool__find_at')
1291}
1292
1293fn test_generate_c_specializes_nested_generic_calls_from_active_bindings() {
1294 csrc := generate_c_for_test('
1295struct Matcher {}
1296struct Captures {}
1297
1298fn outer[M, T](matcher M, mut caps T) {
1299 inner(matcher, mut caps)
1300}
1301
1302fn inner[M, T](matcher M, mut caps T) {
1303 _ = matcher
1304 _ = caps
1305}
1306
1307fn main() {
1308 matcher := Matcher{}
1309 mut caps := Captures{}
1310 outer(matcher, mut caps)
1311}
1312')
1313 assert csrc.contains('void outer_T_Matcher_Captures(Matcher matcher, Captures* caps) {')
1314 assert csrc.contains('inner_T_Matcher_Captures(matcher, caps);')
1315 assert csrc.contains('void inner_T_Matcher_Captures(Matcher matcher, Captures* caps) {')
1316 assert !csrc.contains('inner(matcher, caps)')
1317}
1318
1319fn test_generate_c_emits_value_receiver_methods_used_by_interface_wrappers() {
1320 csrc := generate_c_for_test('
1321interface Counter {
1322 len() int
1323}
1324
1325struct Counts {}
1326
1327fn (counts Counts) len() int {
1328 _ = counts
1329 return 1
1330}
1331
1332fn use_counter(counter Counter) int {
1333 return counter.len()
1334}
1335
1336fn main() {
1337 counts := Counts{}
1338 _ = use_counter(counts)
1339}
1340')
1341 assert csrc.contains('int Counts__len(Counts counts) {')
1342 assert csrc.contains('static int __iface_wrap_Counter_Counts_len(void* _obj) {')
1343 assert csrc.contains('return Counts__len(*(((Counts*)_obj)));')
1344}
1345
1346fn test_generate_c_preserves_void_result_or_block_side_effects() {
1347 csrc := generate_c_for_test('
1348fn may_fail() ? {
1349 return none
1350}
1351
1352fn main() {
1353 mut saw_error := false
1354 may_fail() or {
1355 saw_error = true
1356 }
1357 _ = saw_error
1358}
1359')
1360 assert csrc.contains('saw_error = true;')
1361}
1362
1363fn test_generate_c_passes_mut_generic_param_address_as_existing_pointer() {
1364 csrc := generate_c_for_test('
1365struct Captures {}
1366
1367fn visit[T](mut value T, cb fn (&T)) {
1368 cb(&value)
1369}
1370
1371fn main() {
1372 mut captures := Captures{}
1373 visit(mut captures, fn (_captures &Captures) {})
1374}
1375')
1376 assert csrc.contains('cb(value);')
1377 assert !csrc.contains('cb(&value);')
1378}
1379
1380fn test_generate_c_captures_mut_param_as_existing_pointer() {
1381 csrc := generate_c_for_test('
1382fn touch(mut dst []u8) {
1383 dst << u8(1)
1384}
1385
1386fn outer(mut dst []u8) {
1387 cb := fn [mut dst] () {
1388 touch(mut dst)
1389 }
1390 cb()
1391}
1392
1393fn main() {
1394 mut dst := []u8{}
1395 outer(mut dst)
1396}
1397')
1398 assert csrc.contains('_capture_0 = dst;')
1399 assert !csrc.contains('_capture_0 = &dst;')
1400}
1401
1402fn test_generate_c_fn_literal_capture_preserves_fn_pointer_return_type() {
1403 csrc := generate_c_for_test('
1404fn visit(cb fn (int) bool) bool {
1405 return cb(1)
1406}
1407
1408fn outer(matched fn (int) bool) bool {
1409 return visit(fn [matched] (n int) bool {
1410 return matched(n)
1411 })
1412}
1413')
1414 assert csrc.contains('static bool (*_anon_fn_')
1415 assert csrc.contains('bool (*matched)(int) = _anon_fn_')
1416 assert csrc.contains('return matched(n);')
1417 assert !csrc.contains('((void (*)(int))matched)(n)')
1418}
1419
1420fn test_generate_c_rewrites_continue_in_generic_comptime_field_loop() {
1421 code := [
1422 'struct Schema {',
1423 ' skip int',
1424 ' keep int',
1425 '}',
1426 '',
1427 'fn count_fields[T]() int {',
1428 ' mut n := 0',
1429 ' @DLR@for field in T.fields {',
1430 ' if field.name == @SQ@skip@SQ@ {',
1431 ' continue',
1432 ' }',
1433 ' n++',
1434 ' }',
1435 ' return n',
1436 '}',
1437 '',
1438 'fn main() {',
1439 ' _ = count_fields[Schema]()',
1440 '}',
1441 ].join('\n').replace('@DLR@', '$').replace('@SQ@', "'")
1442 csrc := generate_c_for_test(code)
1443 assert csrc.contains('goto __v_ctf_continue_')
1444 assert csrc.contains('__v_ctf_continue_')
1445}
1446
1447fn test_generate_c_exposes_generic_comptime_field_attrs() {
1448 code := [
1449 'struct Schema {',
1450 ' field int @[repeats; short: u]',
1451 '}',
1452 '',
1453 'fn attrs[T]() []string {',
1454 ' mut out := []string{}',
1455 ' @DLR@for field in T.fields {',
1456 ' out = field.attrs',
1457 ' }',
1458 ' return out',
1459 '}',
1460 '',
1461 'fn main() {',
1462 ' _ = attrs[Schema]()',
1463 '}',
1464 ].join('\n').replace('@DLR@', '$')
1465 csrc := generate_c_for_test(code)
1466 assert csrc.contains('"repeats"')
1467 assert csrc.contains('"short: u"')
1468}
1469
1470fn test_generate_c_struct_default_does_not_write_string_value_to_pointer_field() {
1471 csrc := generate_c_for_test('
1472struct Builder[^a] {
1473 glob &^a string
1474}
1475
1476fn make[^a]() &Builder[^a] {
1477 return &Builder[^a]{}
1478}
1479
1480fn (mut b Builder[^a]) touch[^a]() &Builder[^a] {
1481 return &b
1482}
1483')
1484 assert csrc.contains('string* glob;')
1485 assert !csrc.contains('.glob = (string)')
1486}
1487
1488fn test_generate_c_specializes_plain_method_on_each_generic_receiver_instance() {
1489 csrc := generate_c_for_test('
1490enum MatchKind {
1491 none
1492 hit
1493}
1494
1495struct ValueA {}
1496struct ValueB {}
1497
1498struct Match[T] {
1499 kind MatchKind = .none
1500 value T
1501}
1502
1503fn first() Match[ValueA] {
1504 return Match[ValueA]{
1505 kind: .hit
1506 value: ValueA{}
1507 }
1508}
1509
1510fn second() Match[ValueB] {
1511 return Match[ValueB]{
1512 kind: .hit
1513 value: ValueB{}
1514 }
1515}
1516
1517fn (m Match[T]) is_hit() bool {
1518 return m.kind == .hit
1519}
1520
1521fn main() {
1522 _ = first().is_hit()
1523 _ = second().is_hit()
1524}
1525')
1526 assert csrc.contains('bool Match_T_ValueA__is_hit_T_ValueA(Match_T_ValueA m);')
1527 assert csrc.contains('bool Match_T_ValueA__is_hit_T_ValueA(Match_T_ValueA m) {')
1528 assert csrc.contains('bool Match_T_ValueB__is_hit_T_ValueB(Match_T_ValueB m);')
1529 assert csrc.contains('bool Match_T_ValueB__is_hit_T_ValueB(Match_T_ValueB m) {')
1530 assert csrc.contains('return ((Match_T_ValueB){.kind = MatchKind__hit')
1531 assert csrc.contains('Match_T_ValueA__is_hit_T_ValueA(first())')
1532 assert csrc.contains('Match_T_ValueB__is_hit_T_ValueB(second())')
1533 assert !csrc.contains('Match__is_hit(')
1534}
1535
1536fn test_generate_c_declares_generic_struct_literal_with_specialized_type() {
1537 csrc := generate_c_for_test('
1538struct Other {}
1539struct Value {}
1540
1541struct Match[T] {
1542 value T
1543}
1544
1545fn make_other() Match[Other] {
1546 return Match[Other]{}
1547}
1548
1549fn make_match() Match[Value] {
1550 return Match[Value]{}
1551}
1552
1553fn main() {
1554 _ = make_other()
1555 mut mat := Match[Value]{}
1556 mat = make_match()
1557}
1558')
1559 assert csrc.contains('Match_T_Value mat = ((Match_T_Value){0});')
1560 assert csrc.contains('mat = make_match();')
1561 assert !csrc.contains('Match mat = ((Match_T_Value){0});')
1562}
1563
1564fn test_generate_c_declares_lifetime_generic_struct_literal_with_specialized_type() {
1565 csrc := generate_c_for_test('
1566struct Other {}
1567struct IgnoreMatch[^a] {}
1568
1569struct Match[T] {
1570 value T
1571}
1572
1573fn make_other() Match[Other] {
1574 return Match[Other]{}
1575}
1576
1577fn matched[^a]() Match[IgnoreMatch[^a]] {
1578 _ = make_other()
1579 mut mat := Match[IgnoreMatch[^a]]{}
1580 return mat
1581}
1582
1583fn main() {
1584 _ = matched()
1585 }
1586 ')
1587 assert csrc.contains('Match_T_IgnoreMatch matched()')
1588 assert !csrc.contains('\tMatch mat = ((Match_T_IgnoreMatch){0});')
1589}
1590
1591fn test_generate_c_uses_specialized_lifetime_generic_type_for_if_expr_and_array_literal() {
1592 csrc := generate_c_for_test('
1593struct Other {}
1594struct IgnoreMatch[^a] {}
1595
1596struct Match[T] {
1597 value T
1598}
1599
1600fn make_other() Match[Other] {
1601 return Match[Other]{}
1602}
1603
1604fn make_match[^a]() Match[IgnoreMatch[^a]] {
1605 return Match[IgnoreMatch[^a]]{}
1606}
1607
1608fn matched[^a](flag bool) Match[IgnoreMatch[^a]] {
1609 _ = make_other()
1610 a := make_match()
1611 b := make_match()
1612 mut selected := if flag {
1613 make_match()
1614 } else {
1615 Match[IgnoreMatch[^a]]{}
1616 }
1617 for mat in [a, b, selected] {
1618 return mat
1619 }
1620 return Match[IgnoreMatch[^a]]{}
1621}
1622')
1623 assert csrc.contains('Match_T_IgnoreMatch selected = ({ Match_T_IgnoreMatch _if_expr_t')
1624 assert csrc.contains('new_array_from_c_array(3, 3, sizeof(Match_T_IgnoreMatch)')
1625 assert csrc.contains('Match_T_IgnoreMatch mat = ')
1626 assert !csrc.contains('new_array_from_c_array(3, 3, sizeof(int)')
1627}
1628
1629fn test_generate_c_registers_tuple_match_expr_destructure_type() {
1630 csrc := generate_c_for_test('
1631enum Encoding {
1632 zstd
1633 gzip
1634}
1635
1636fn zstd_bytes() []u8 {
1637 return [u8(1)]
1638}
1639
1640fn gzip_bytes() []u8 {
1641 return [u8(2)]
1642}
1643
1644fn selected(encoding Encoding) string {
1645 compressed, ext, encoding_name := match encoding {
1646 .zstd {
1647 c := zstd_bytes()
1648 c, ".zst", "zstd"
1649 }
1650 .gzip {
1651 c := gzip_bytes()
1652 c, ".gz", "gzip"
1653 }
1654 }
1655 _ = compressed
1656 return ext + encoding_name
1657}
1658')
1659 assert csrc.contains('Tuple_Array_u8_string_string _tuple_tmp_')
1660 assert csrc.contains('Tuple_Array_u8_string_string _if_expr_t')
1661 assert csrc.contains('_if_expr_t') && csrc.contains(' = ((Tuple_Array_u8_string_string){')
1662 assert !csrc.contains('int _if_expr_t')
1663}
1664
1665fn test_scan_expr_registers_array_tuple_alias_before_type_emit() {
1666 mut gen := Gen.new([])
1667 gen.runtime_local_types['c'] = 'Array_u8'
1668 gen.emitted_types['body_array'] = true
1669 gen.emitted_types['body_string'] = true
1670 gen.scan_expr_for_generic_types(ast.Expr(ast.Tuple{
1671 exprs: [
1672 ast.Expr(ast.Ident{
1673 name: 'c'
1674 }),
1675 ast.Expr(ast.StringLiteral{
1676 value: '".gz"'
1677 }),
1678 ast.Expr(ast.StringLiteral{
1679 value: '"gzip"'
1680 }),
1681 ]
1682 }))
1683 gen.register_tuple_alias(['Array_u8', 'string', 'string'])
1684 gen.emit_tuple_aliases()
1685 assert gen.tuple_aliases['Tuple_Array_u8_string_string'] == ['Array_u8', 'string', 'string']
1686 assert gen.sb.str().contains('typedef struct Tuple_Array_u8_string_string')
1687}
1688
1689fn test_generate_c_emits_late_generic_tuple_alias_before_forward_decl() {
1690 mut gen := Gen.new([])
1691 gen.emitted_types['body_array'] = true
1692 gen.tuple_aliases['Tuple_Array_Foo_Array_Foo'] = ['Array_Foo', 'Array_Foo']
1693 gen.fn_return_types['arrays__partition_T_Foo'] = 'Tuple_Array_Foo_Array_Foo'
1694 gen.gen_fn_head_with_name(ast.FnDecl{
1695 name: 'partition'
1696 }, 'arrays__partition_T_Foo')
1697 gen.sb.writeln(';')
1698 csrc := gen.sb.str()
1699 tuple_decl_idx := csrc.index('typedef struct Tuple_Array_Foo_Array_Foo') or {
1700 assert false
1701 return
1702 }
1703 partition_decl_idx := csrc.index('Tuple_Array_Foo_Array_Foo arrays__partition_T_Foo') or {
1704 assert false
1705 return
1706 }
1707 assert tuple_decl_idx < partition_decl_idx
1708}
1709
1710fn test_generate_c_keeps_imported_module_generic_call_name() {
1711 csrc := generate_c_for_test_files([
1712 '
1713module arrays
1714
1715pub fn uniq[T](a []T) []T {
1716 return a
1717}
1718',
1719 '
1720module http
1721
1722import arrays
1723
1724 pub fn keys(res []string) []string {
1725 return arrays.uniq(res)
1726 }
1727',
1728 ])
1729 assert csrc.contains('Array_string arrays__uniq_T_string(Array_string a)')
1730 assert csrc.contains('return arrays__uniq_T_string(res);')
1731 assert !csrc.contains('http__uniq_T_string')
1732}
1733
1734fn test_generate_c_prefers_local_nongeneric_function_over_external_generic_short_name() {
1735 csrc := generate_c_for_test('
1736module strings
1737
1738fn min(a int, b int, c int) int {
1739 return a
1740}
1741
1742pub fn pick(a int, b int, c int) int {
1743 return min(a, b, c)
1744}
1745')
1746 assert csrc.contains('return strings__min(a, b, c);')
1747 assert !csrc.contains('min_T_int')
1748}
1749
1750fn test_generate_c_fixed_array_index_expr_has_element_type() {
1751 csrc := generate_c_for_test('
1752const month_days = [31, 28, 31]!
1753
1754fn max_day(month int) int {
1755 value := month_days[month - 1] + 1
1756 return value
1757}
1758')
1759 assert csrc.contains('static const int month_days[3] = {31, 28, 31};')
1760 assert !csrc.contains('static const int month_days[3] = (int[3])')
1761 assert csrc.contains('int value = (month_days')
1762 assert !csrc.contains('fixed_int_3 value')
1763}
1764
1765fn test_generate_c_resolves_const_fixed_array_struct_field_len() {
1766 csrc := generate_c_for_test('
1767const max_connection_size = 65536
1768const buf_size = max_connection_size
1769
1770struct Conn {
1771 read_buf [buf_size]u8
1772}
1773')
1774 assert csrc.contains('u8 read_buf[65536];')
1775 assert !csrc.contains('u8 read_buf[buf_size];')
1776}
1777
1778fn test_generate_c_emits_late_generic_map_alias_before_struct_body() {
1779 prefs := &vpref.Preferences{
1780 backend: .cleanc
1781 }
1782 env := types.Environment.new()
1783 mut gen := Gen.new_with_env_and_pref([]ast.File{}, env, prefs)
1784 gen.emit_map_alias_decl('Map_Map_string_string_u8')
1785 gen.emit_map_alias_decl('Map_Map_string_string_u8')
1786 gen.emit_map_alias_decl('Map_string_veb__Route*')
1787 gen.emit_map_alias_decl('Map_string_veb__Route*')
1788 csrc := gen.sb.str()
1789 assert csrc.count('typedef map Map_Map_string_string_u8;') == 1
1790 assert csrc.contains('string Map_Map_string_string_u8_str(Map_Map_string_string_u8 m);')
1791 assert csrc.contains('bool Map_Map_string_string_u8_map_eq(Map_Map_string_string_u8 a, Map_Map_string_string_u8 b);')
1792 assert csrc.count('typedef map Map_string_veb__Route;') == 1
1793 assert !csrc.contains('typedef map Map_string_veb__Route*;')
1794}
1795
1796fn test_parse_map_kv_types_prefers_known_nested_map_alias_key() {
1797 mut gen := Gen.new([])
1798 gen.map_aliases['Map_string_string'] = true
1799 key_type, value_type := gen.parse_map_kv_types('Map_string_string_Array_int')
1800 assert key_type == 'Map_string_string'
1801 assert value_type == 'Array_int'
1802}
1803
1804fn test_emit_map_str_uses_pointer_for_fixed_array_key() {
1805 mut gen := Gen.new([])
1806 gen.sb = strings.new_builder(1024)
1807 gen.map_aliases['Map_Array_fixed_u8_4_int'] = true
1808 gen.emitted_types['alias_Map_Array_fixed_u8_4_int'] = true
1809 gen.emit_map_str_functions()
1810 csrc := gen.sb.str()
1811 assert csrc.contains('u8* key_ptr = (u8*)DenseArray__key(&m.key_values, i);')
1812 assert !csrc.contains('Array_fixed_u8_4 key = *')
1813}
1814
1815fn test_parse_map_kv_types_prefers_longest_known_generic_key() {
1816 mut gen := Gen.new([])
1817 gen.emitted_types['body_api__ApiSuccessResponse'] = true
1818 gen.emitted_types['body_api__ApiSuccessResponse_T_Array_FileInfo'] = true
1819 key_type, value_type :=
1820 gen.parse_map_kv_types('api__ApiSuccessResponse_T_Array_FileInfo_Array_int')
1821 assert key_type == 'api__ApiSuccessResponse_T_Array_FileInfo'
1822 assert value_type == 'Array_int'
1823}
1824
1825fn test_map_alias_key_value_types_prefers_collected_module_qualified_value() {
1826 mut gen := Gen.new([])
1827 gen.collected_map_types['Map_string_ssa__Type'] = MapTypeInfo{
1828 key_c_type: 'string'
1829 value_c_type: 'ssa__Type'
1830 }
1831 key_type, value_type := gen.map_alias_key_value_types('Map_string_ssa__Type')
1832 assert key_type == 'string'
1833 assert value_type == 'ssa__Type'
1834}
1835
1836fn test_qualify_module_local_generic_c_name_preserves_collected_map_info_for_option_payload() {
1837 mut gen := Gen.new([])
1838 gen.cur_module = 'cleanc'
1839 gen.collected_map_types['Map_string_types__Type'] = MapTypeInfo{
1840 key_c_type: 'string'
1841 value_c_type: 'types__Type'
1842 }
1843 qualified := gen.qualify_module_local_generic_c_name('_option_Map_string_types__Type')
1844 assert qualified == '_option_Map_string_types__Type'
1845 assert !qualified.contains('cleanc__string')
1846 assert !qualified.contains('types__cleanc__Type')
1847}
1848
1849fn test_map_alias_filter_accepts_module_qualified_key_type() {
1850 mut gen := Gen.new([])
1851 gen.cache_bundle_name = 'v2compiler'
1852 gen.emit_modules['ssa'] = true
1853 assert gen.alias_type_belongs_to_emit_modules('Map_ssa__TypeID_bool')
1854 prefixes := gen.module_prefixes_in_c_name('Map_ssa__TypeID_bool')
1855 assert prefixes == ['ssa']
1856}
1857
1858fn test_generate_c_qualifies_imported_symbol_type() {
1859 prefs := &vpref.Preferences{
1860 backend: .cleanc
1861 }
1862 env := types.Environment.new()
1863 file := ast.File{
1864 name: 'imported_symbol_type_test.v'
1865 mod: 'markdown'
1866 imports: [
1867 ast.ImportStmt{
1868 name: 'datatypes'
1869 alias: 'datatypes'
1870 symbols: [ast.Expr(ast.Ident{
1871 name: 'Stack'
1872 })]
1873 },
1874 ]
1875 }
1876 mut gen := Gen.new_with_env_and_pref([file], env, prefs)
1877 gen.cur_file_name = file.name
1878 gen.cur_module = file.mod
1879 assert gen.expr_type_to_c(ast.Ident{
1880 name: 'Stack'
1881 }) == 'datatypes__Stack'
1882 assert gen.expr_type_to_c(ast.Ident{
1883 name: 'markdown__Stack'
1884 }) == 'datatypes__Stack'
1885}
1886
1887fn test_generate_c_indexes_mut_array_receiver_through_pointer_data() {
1888 csrc := generate_c_for_test('
1889fn (mut a []string) touch_each() {
1890 for mut s in a {
1891 s = s.clone()
1892 }
1893}
1894
1895fn main() {
1896 mut items := ["x"]
1897 items.touch_each()
1898}
1899 ')
1900 assert csrc.contains('touch_each')
1901 assert !csrc.contains('(a).data')
1902}
1903
1904fn test_generate_c_indexes_variadic_string_param_as_array() {
1905 csrc := generate_c_for_test('
1906fn use_patterns(patterns ...string) {
1907 for pattern in patterns {
1908 _ = pattern
1909 }
1910}
1911
1912fn main() {
1913 use_patterns("*.v")
1914}
1915')
1916 assert csrc.contains('use_patterns')
1917 assert !csrc.contains('(patterns).str')
1918}
1919
1920fn test_generate_c_drops_leaked_static_type_receiver_in_constructor_call() {
1921 csrc := generate_c_for_test('
1922struct Match {}
1923
1924fn Match.new(start int, end int) Match {
1925 _ = start
1926 _ = end
1927 return Match{}
1928}
1929
1930fn build(end int) []Match {
1931 return [Match.new(0, end)]
1932}
1933')
1934 assert csrc.contains('Match__new(0, end)')
1935 assert !csrc.contains('Match__new(Match,')
1936}
1937
1938fn test_generate_c_lowers_map_literal_for_in_before_array_fallback() {
1939 csrc := generate_c_for_test("
1940fn collect() []string {
1941 mut res := []string{}
1942 for label, v in {
1943 'days': 24
1944 'h': 1
1945 } {
1946 res << '\${v}\${label}'
1947 }
1948 return res
1949}
1950")
1951 assert csrc.contains('DenseArray__key')
1952 assert csrc.contains('string label =')
1953 assert !csrc.contains('for (int label = 0;')
1954 assert !csrc.contains('+ label))')
1955}
1956
1957fn test_generate_c_lowers_map_index_assign_after_empty_map_literal_decl() {
1958 csrc := generate_c_for_test('
1959struct Inst {
1960 typ Kind
1961 target int
1962}
1963
1964enum Kind {
1965 split
1966 jmp
1967 other
1968}
1969
1970struct Compiler {
1971mut:
1972 prog []Inst
1973}
1974
1975fn (mut c Compiler) mark() {
1976 mut targets := map[int]bool{}
1977 for inst in c.prog {
1978 if inst.typ == .split || inst.typ == .jmp {
1979 targets[inst.target] = true
1980 }
1981 }
1982}
1983')
1984 assert csrc.contains('map__set(&targets')
1985 assert !csrc.contains('cannot resolve map type for index expr')
1986}
1987
1988fn test_generate_c_lowers_late_generic_selector_string_slice_and_map_assign() {
1989 csrc := generate_c_for_test('
1990struct Info {
1991 position int
1992 length int
1993}
1994
1995struct Decoder {
1996 json string
1997}
1998
1999fn (mut decoder Decoder) decode_map[V](mut val map[string]V) {
2000 key_info := Info{
2001 position: 0
2002 length: 3
2003 }
2004 key_str := decoder.json[key_info.position + 1..key_info.position + key_info.length - 1]
2005 mut map_value := V{}
2006 val[key_str] = map_value
2007}
2008
2009fn main() {
2010 mut decoder := Decoder{
2011 json: "\'x\'"
2012 }
2013 mut vals := map[string]string{}
2014 decoder.decode_map(mut vals)
2015}
2016')
2017 assert csrc.contains('string key_str = string__substr')
2018 assert csrc.contains('map__set(')
2019 assert !csrc.contains('array key_str = array__slice(decoder->json')
2020 assert !csrc.contains('map__get(&(val)')
2021}
2022
2023fn test_generate_c_specializes_comptime_field_generic_call_by_field_type() {
2024 csrc := generate_c_for_test('
2025fn struct_field_should_encode[T](val T) bool {
2026 _ = val
2027 return true
2028}
2029
2030fn encode_fields[T](val T) bool {
2031 mut ok := true
2032 $for field in T.fields {
2033 ok = struct_field_should_encode(val.$(field.name))
2034 }
2035 return ok
2036}
2037
2038struct Repo {
2039 id int
2040 unix i64
2041 description string
2042}
2043
2044fn main() {
2045 _ = struct_field_should_encode(1)
2046 _ = encode_fields(Repo{
2047 id: 1
2048 unix: 2
2049 description: "x"
2050 })
2051}
2052')
2053 assert csrc.contains('struct_field_should_encode_T_string(val.description)')
2054 assert csrc.contains('val._unix')
2055 assert !csrc.contains('val.unix')
2056 assert !csrc.contains('struct_field_should_encode_T_Repo(val.description)')
2057}
2058
2059fn test_generate_c_lowers_comptime_field_selector_method_receiver() {
2060 csrc := generate_c_for_test('
2061fn clear_fields[T](mut val T) {
2062 $for field in T.fields {
2063 $if field.unaliased_typ is $array_dynamic {
2064 val.$(field.name).clear()
2065 }
2066 }
2067}
2068
2069struct Response {
2070 result []int
2071}
2072
2073fn main() {
2074 mut response := Response{
2075 result: [1]
2076 }
2077 clear_fields(mut response)
2078}
2079')
2080 assert csrc.contains('val->result')
2081 assert !csrc.contains('Response__result(val)')
2082}
2083
2084fn test_generate_c_uses_statement_temps_for_nested_map_index_assign_defaults() {
2085 csrc := generate_c_for_test("
2086fn build() map[string]map[string]string {
2087 mut res := map[string]map[string]string{}
2088 lang := 'en'
2089 key := 'msg'
2090 val := 'Hello'
2091 res[lang][key] = val
2092 return res
2093}
2094")
2095 assert csrc.contains('map__get_and_set(&res')
2096 assert !csrc.contains('map__get_and_set(&res, ((void*)(&lang)), ((void*)(({')
2097}
2098
2099fn test_generate_c_lowers_mut_array_for_in_after_cap_only_array_literal_decl() {
2100 csrc := generate_c_for_test('
2101struct Inst {
2102mut:
2103 n int
2104}
2105
2106fn rewrite() []Inst {
2107 mut new_prog := []Inst{cap: 4}
2108 new_prog << Inst{
2109 n: 1
2110 }
2111 for mut inst in new_prog {
2112 inst.n = 2
2113 }
2114 return new_prog
2115}
2116')
2117 assert csrc.contains('Inst* inst')
2118 assert !csrc.contains('for (; ; )')
2119}
2120
2121fn test_generate_c_uses_string_substr_for_array_string_index_slice() {
2122 csrc := generate_c_for_test("
2123const names = ['January']!
2124
2125fn short() string {
2126 return names[0][0..3]
2127}
2128
2129fn main() {
2130 _ = short()
2131}
2132")
2133 assert csrc.contains('string__substr(')
2134 assert !csrc.contains('array__slice(names')
2135}
2136
2137fn test_array_append_elem_type_accepts_runtime_array_pointer() {
2138 mut gen := Gen.new([])
2139 gen.runtime_local_types['field_infos'] = 'array*'
2140 is_append, elem_type := gen.array_append_elem_type(ast.Expr(ast.Ident{
2141 name: 'field_infos'
2142 }), ast.Expr(ast.InitExpr{
2143 typ: ast.Ident{
2144 name: 'FieldInfo'
2145 }
2146 }))
2147 assert is_append
2148 assert elem_type == 'FieldInfo'
2149}
2150
2151fn test_generate_c_lowers_map_field_for_in_with_ignored_key() {
2152 csrc := generate_c_for_test('
2153struct Def {
2154 name string
2155}
2156
2157struct Builder {
2158 types map[string]Def
2159}
2160
2161fn collect(builder Builder) []Def {
2162 mut defs := []Def{}
2163 for _, def in builder.types {
2164 defs << def
2165 }
2166 return defs
2167}
2168
2169fn main() {
2170 _ = collect(Builder{
2171 types: map[string]Def{}
2172 })
2173}
2174')
2175 assert csrc.contains('DenseArray__value')
2176 assert !csrc.contains('cannot resolve map type for index expr')
2177}
2178
2179fn test_generate_c_uses_string_methods_after_branch_assignment() {
2180 csrc := generate_c_for_test('
2181fn count_matches(files []string, pat string) int {
2182 mut count := 0
2183 for file in files {
2184 mut f := ""
2185 if file.contains("/") {
2186 parts := file.split("/")
2187 f = parts[parts.len - 1]
2188 } else {
2189 f = file
2190 }
2191 if f.starts_with(pat) || f.ends_with(pat) {
2192 count++
2193 }
2194 }
2195 return count
2196}
2197
2198fn main() {
2199 _ = count_matches(["abc"], "a")
2200}
2201')
2202 assert csrc.contains('string__starts_with(f, pat)')
2203 assert csrc.contains('string__ends_with(f, pat)')
2204 assert !csrc.contains('int__starts_with')
2205 assert !csrc.contains('int__ends_with')
2206}
2207
2208fn test_generate_c_lowers_sort_comparator_on_array_selector() {
2209 csrc := generate_c_for_test('
2210struct Def {
2211mut:
2212 globs []string
2213}
2214
2215fn ordered(def Def) Def {
2216 mut cloned := def
2217 cloned.globs.sort(a < b)
2218 return cloned
2219}
2220
2221fn main() {
2222 _ = ordered(Def{})
2223}
2224')
2225 assert csrc.contains('array__sort_with_compare')
2226 assert csrc.contains('compare_strings')
2227 assert !csrc.contains('array__sort(&cloned.globs, (a < b))')
2228}
2229
2230fn test_generate_c_emits_sort_comparator_for_file_filtered_main() {
2231 csrc := generate_c_for_test_sources_with_emit([
2232 CgenTestSource{
2233 path: 'cached/issue.v'
2234 code: '
2235module main
2236
2237struct Issue {
2238 created_at int
2239}
2240
2241fn cached_route(mut issues []Issue) {
2242 issues.sort(a.created_at > b.created_at)
2243}
2244'
2245 },
2246 CgenTestSource{
2247 path: 'issue.v'
2248 code: '
2249module main
2250
2251fn root_route(mut issues []Issue) {
2252 issues.sort(a.created_at > b.created_at)
2253}
2254
2255fn main() {
2256 mut issues := []Issue{}
2257 root_route(mut issues)
2258}
2259'
2260 },
2261 ], ['issue.v'])
2262 assert csrc.contains('array__sort_with_compare(issues, (FnSortCB)__sort_cmp_Issue_by_created_at_desc)')
2263 assert csrc.contains('int __sort_cmp_Issue_by_created_at_desc(Issue* a, Issue* b);')
2264 assert csrc.contains('int __sort_cmp_Issue_by_created_at_desc(Issue* a, Issue* b) {')
2265}
2266
2267fn test_generate_c_uses_string_methods_after_if_expr_assignment() {
2268 csrc := generate_c_for_test('
2269fn count_matches(files []string, dir string, pat string) int {
2270 mut count := 0
2271 for file in files {
2272 mut fpath := file
2273 f := if file.contains("/") {
2274 pathwalk := file.split("/")
2275 pathwalk[pathwalk.len - 1]
2276 } else {
2277 fpath = if dir == "." { file } else { dir + "/" + file }
2278 file
2279 }
2280 if f.starts_with(pat) || f.ends_with(pat) || f.contains(pat) {
2281 count++
2282 }
2283 _ = fpath
2284 }
2285 return count
2286}
2287
2288fn main() {
2289 _ = count_matches(["abc"], ".", "a")
2290}
2291')
2292 assert csrc.contains('string__starts_with(f, pat)')
2293 assert csrc.contains('string__ends_with(f, pat)')
2294 assert csrc.contains('string__contains(f, pat)')
2295 assert !csrc.contains('int__starts_with')
2296 assert !csrc.contains('int__ends_with')
2297 assert !csrc.contains('int__contains')
2298}
2299
2300fn test_generate_c_lowers_promoted_embedded_method_receiver() {
2301 csrc := generate_c_for_test('
2302struct Base {}
2303
2304fn (mut base Base) get_cookie(key string) ?string {
2305 return key
2306}
2307
2308struct Context {
2309 Base
2310}
2311
2312fn read_cookie(mut ctx Context) string {
2313 return ctx.get_cookie("session") or { "" }
2314}
2315
2316fn main() {
2317 mut ctx := Context{}
2318 _ = read_cookie(mut ctx)
2319}
2320')
2321 assert csrc.contains('Base__get_cookie(&ctx->Base,')
2322 assert !csrc.contains('Context__get_cookie(ctx')
2323}
2324
2325fn test_generate_c_lowers_module_qualified_static_type_method() {
2326 csrc := generate_c_for_test_files([
2327 '
2328module git
2329
2330pub struct Git {}
2331
2332pub fn Git.exec(args []string) int {
2333 _ = args
2334 return 0
2335}
2336',
2337 '
2338module main
2339
2340import git
2341
2342fn main() {
2343 _ = git.Git.exec(["status"])
2344}
2345',
2346 ])
2347 assert csrc.contains('git__Git__exec(new_array_from_c_array')
2348 assert !csrc.contains('git__Git.exec')
2349 assert !csrc.contains('git__Git, new_array_from_c_array')
2350}
2351
2352fn test_generate_c_adds_implicit_veb_context_param() {
2353 csrc := generate_c_for_test_files([
2354 '
2355module veb
2356
2357pub struct Result {}
2358
2359pub struct Context {}
2360
2361pub fn (mut ctx Context) redirect(path string) Result {
2362 _ = path
2363 return Result{}
2364}
2365',
2366 '
2367module main
2368
2369import veb
2370
2371struct App {}
2372
2373struct Context {
2374 veb.Context
2375}
2376
2377pub fn (mut app App) index() veb.Result {
2378 return ctx.redirect("/register")
2379}
2380',
2381 ])
2382 assert csrc.contains('veb__Result App__index(App* app, Context* ctx)')
2383 assert csrc.contains('veb__Context__redirect(&ctx->Context,')
2384 assert !csrc.contains('veb__Result App__index(App* app) {')
2385}
2386
2387fn test_generate_c_lowers_implicit_veb_context_promoted_generic_method() {
2388 csrc := generate_c_for_test_files([
2389 '
2390module veb
2391
2392pub struct Result {}
2393
2394pub struct Context {}
2395
2396pub fn (mut ctx Context) json[T](j T) Result {
2397 _ = j
2398 return Result{}
2399}
2400',
2401 '
2402module main
2403
2404import veb
2405
2406struct App {}
2407
2408struct Response[T] {
2409 success bool
2410 result T
2411}
2412
2413struct Context {
2414 veb.Context
2415}
2416
2417pub fn (mut app App) index() veb.Result {
2418 _ = app
2419 return ctx.json(Response[string]{
2420 success: true
2421 result: "ok"
2422 })
2423}
2424',
2425 ])
2426 assert csrc.contains('veb__Result App__index(App* app, Context* ctx)')
2427 assert csrc.contains('return veb__Context__json_T')
2428 assert !csrc.contains('return veb__Context__json(')
2429 assert !csrc.contains('ctx->json')
2430}
2431
2432fn test_generate_c_passes_implicit_veb_context_to_handler_calls() {
2433 csrc := generate_c_for_test_files([
2434 '
2435module veb
2436
2437pub struct Result {}
2438
2439pub struct Context {}
2440',
2441 '
2442module main
2443
2444import veb
2445
2446struct App {}
2447
2448struct Context {
2449 veb.Context
2450}
2451
2452pub fn (mut app App) repo_settings(username string) veb.Result {
2453 _ = username
2454 return veb.Result{}
2455}
2456
2457pub fn (mut app App) handle(username string) veb.Result {
2458 return app.repo_settings(mut ctx, username)
2459}
2460',
2461 ])
2462 assert csrc.contains('veb__Result App__repo_settings(App* app, Context* ctx, string username)')
2463 assert csrc.contains('App__repo_settings(app, ctx, username)')
2464 assert !csrc.contains('App__repo_settings(app, (*ctx), username)')
2465}
2466
2467fn test_generate_c_lowers_promoted_embedded_generic_method_receiver() {
2468 csrc := generate_c_for_test_files([
2469 '
2470module veb
2471
2472pub struct Result {}
2473
2474pub struct Context {}
2475
2476pub fn (mut ctx Context) json[T](j T) Result {
2477 _ = j
2478 return Result{}
2479}
2480',
2481 '
2482module main
2483
2484import veb
2485
2486struct Context {
2487 veb.Context
2488}
2489
2490fn handle(mut ctx Context) veb.Result {
2491 return ctx.json("ok")
2492}
2493',
2494 ])
2495 assert csrc.contains('veb__Context__json_T_string(&ctx->Context,')
2496 assert !csrc.contains('ctx->json')
2497}
2498
2499fn test_generate_c_lowers_promoted_embedded_generic_method_call() {
2500 csrc := generate_c_for_test_files([
2501 '
2502module dep
2503
2504pub struct Options[T] {
2505pub:
2506 handler fn (mut ctx T) bool @[required]
2507}
2508
2509pub struct Middleware[T] {
2510mut:
2511 handlers []voidptr
2512}
2513
2514pub fn (mut m Middleware[T]) use(options Options[T]) {
2515 _ = T.name
2516 m.handlers << voidptr(options.handler)
2517}
2518',
2519 '
2520module main
2521
2522import dep
2523
2524struct App {
2525 dep.Middleware[Context]
2526}
2527
2528struct Context {}
2529
2530fn (mut app App) before_request(mut ctx Context) bool {
2531 _ = ctx
2532 return true
2533}
2534
2535fn main() {
2536 mut app := App{}
2537 app.use(handler: app.before_request)
2538}
2539',
2540 ])
2541 assert csrc.contains('dep__Middleware_T_Context__use(&app.Middleware_T_Context,')
2542 || csrc.contains('dep__Middleware_T_Context__use_T_Context(&app.Middleware_T_Context,')
2543 assert csrc.contains('((bool (*)(Context*))_bound_method_')
2544 assert csrc.contains('_bound_recv_')
2545 assert !csrc.contains('app.use')
2546 assert !csrc.contains('app.before_request')
2547}
2548
2549fn test_generate_c_infers_generic_type_name_selector_as_string() {
2550 csrc := generate_c_for_test('
2551struct Table {
2552 name string
2553}
2554
2555struct GitHubRepoInfo {}
2556struct GitHubContributor {}
2557struct GitHubIssue {}
2558
2559enum Lang {
2560 en
2561}
2562
2563fn table_from_struct[T]() Table {
2564 mut table_name := T.name
2565 if bracket_pos := table_name.index("[") {
2566 table_name = table_name[..bracket_pos]
2567 }
2568 table_name = table_name.to_lower()
2569 return Table{
2570 name: table_name
2571 }
2572}
2573
2574fn boot() {
2575 _ = table_from_struct[GitHubRepoInfo]()
2576 _ = table_from_struct[[]GitHubContributor]()
2577 _ = table_from_struct[[]GitHubIssue]()
2578 _ = table_from_struct[map[string]string]()
2579 _ = table_from_struct[Lang]()
2580}
2581')
2582 assert csrc.contains('string table_name = (string){.str = "GitHubRepoInfo"')
2583 assert csrc.contains('string table_name = (string){.str = "[]GitHubContributor"')
2584 assert csrc.contains('string table_name = (string){.str = "[]GitHubIssue"')
2585 assert csrc.contains('string table_name = (string){.str = "map[string]string"')
2586 assert csrc.contains('string table_name = (string){.str = "Lang"')
2587 assert csrc.contains('string__index(table_name')
2588 assert csrc.contains('_option_int _or_t')
2589 assert csrc.contains('.state == 0')
2590 assert csrc.contains('int bracket_pos =')
2591 assert csrc.contains('string__to_lower(table_name)')
2592 assert !csrc.contains('if (string__index(table_name')
2593 assert !csrc.contains('_option_int bracket_pos = string__index(table_name')
2594 assert !csrc.contains('GitHubRepoInfo table_name = (string)')
2595 assert !csrc.contains('Array_GitHubContributor table_name = (string)')
2596 assert !csrc.contains('Array_GitHubIssue table_name = (string)')
2597 assert !csrc.contains('Map_string_string table_name = (string)')
2598 assert !csrc.contains('Lang__str(T)')
2599}
2600
2601fn test_generate_c_lowers_comptime_field_metadata_values() {
2602 csrc := generate_c_for_test('
2603struct TableField {
2604 typ int
2605}
2606
2607const time_ = -2
2608const enum_ = -3
2609const type_idx = {
2610 "int": 7
2611}
2612
2613struct Item {
2614 name string
2615 count int
2616}
2617
2618fn struct_meta[T]() []TableField {
2619 mut meta := []TableField{}
2620 $for field in T.fields {
2621 if !field.is_embed {
2622 mut field_type := field.typ
2623 if typeof(field).name.contains("time.Time") {
2624 field_type = time_
2625 } else if field.is_struct {
2626 field_type = type_idx["int"]
2627 } else if field.is_enum {
2628 field_type = enum_
2629 }
2630 meta << TableField{
2631 typ: field_type
2632 }
2633 }
2634 }
2635 return meta
2636}
2637
2638fn boot() {
2639 _ = struct_meta[Item]()
2640}
2641')
2642 assert csrc.contains('__type_info field_type = 18')
2643 assert csrc.contains('__type_info field_type = 7')
2644 assert !csrc.contains('string__contains((string){.str = "field"')
2645 assert !csrc.contains('field.is_embed')
2646 assert !csrc.contains('field.is_struct')
2647 assert !csrc.contains('field.is_enum')
2648 assert !csrc.contains('string field_type =')
2649}
2650
2651fn test_generate_c_mangles_unimported_module_const_in_embedded_struct_default() {
2652 csrc := generate_c_for_test_files([
2653 '
2654module dep
2655
2656pub const default_timeout = 100
2657
2658pub struct Config {
2659pub:
2660 read_timeout int = default_timeout
2661}
2662',
2663 '
2664module wrap
2665
2666import dep
2667
2668pub struct Config {
2669 dep.Config
2670}
2671',
2672 '
2673module main
2674
2675import wrap
2676
2677fn make() wrap.Config {
2678 return wrap.Config{}
2679}
2680',
2681 ])
2682 assert csrc.contains('.Config.read_timeout = dep__default_timeout')
2683 assert !csrc.contains('dep.default_timeout')
2684}
2685
2686fn test_generate_c_does_not_rewrap_module_qualified_explicit_sumtype_cast_in_result_return() {
2687 csrc := generate_c_for_test_files([
2688 '
2689module orm
2690
2691pub struct Null {}
2692
2693pub type Primitive = Null | bool | []Primitive
2694',
2695 '
2696module pg
2697
2698import orm
2699
2700fn make() !orm.Primitive {
2701 return orm.Primitive(true)
2702}
2703',
2704 ])
2705 assert csrc.contains('orm__Primitive _val = ((orm__Primitive){._tag = 1, ._data._bool')
2706 assert !csrc.contains('orm__Primitive _val = ((orm__Primitive){._tag = 2,._data._Array_orm__Primitive')
2707}
2708
2709fn test_generate_c_uses_qualified_array_payload_for_module_sumtype_as_cast() {
2710 csrc := generate_c_for_test_files([
2711 '
2712module orm
2713
2714pub struct Null {}
2715
2716pub type Primitive = Null | bool | []Primitive
2717
2718fn primitive_kind(value Primitive) int {
2719 return match value {
2720 Null {
2721 0
2722 }
2723 bool {
2724 1
2725 }
2726 []Primitive {
2727 if value.len > 0 {
2728 primitive_kind(value[0])
2729 } else {
2730 0
2731 }
2732 }
2733 }
2734}
2735',
2736 ])
2737 assert csrc.contains('value._data._Array_orm__Primitive')
2738 assert !csrc.contains('value._data._orm__Array_Primitive')
2739 assert !csrc.contains('Array_Primitive*')
2740 assert !csrc.contains('(Array_Primitive){0}')
2741}
2742
2743fn test_generate_c_dereferences_heap_float_sumtype_payload_casts() {
2744 csrc := generate_c_for_test('
2745struct Null {}
2746
2747type Primitive = Null | f32 | f64 | int
2748
2749fn as_int(data Primitive) int {
2750 return match data {
2751 Null {
2752 0
2753 }
2754 f32 {
2755 int(f32(data))
2756 }
2757 f64 {
2758 int(f64(data))
2759 }
2760 int {
2761 data
2762 }
2763 }
2764}
2765')
2766 assert csrc.contains('*((f32*)(data._data._f32))')
2767 assert csrc.contains('*((f64*)(data._data._f64))')
2768 assert !csrc.contains('(f32)(data._data._f32)')
2769 assert !csrc.contains('(f64)(data._data._f64)')
2770}
2771
2772fn test_generate_c_specializes_generic_call_from_array_generic_param() {
2773 csrc := generate_c_for_test_files([
2774 '
2775module orm
2776
2777pub struct Null {}
2778
2779pub type Primitive = Null | []bool
2780',
2781 '
2782module pg
2783
2784import orm
2785
2786fn handle_array[T](data []T) {
2787 _ = data
2788}
2789
2790fn handle(data orm.Primitive) {
2791 match data {
2792 orm.Null {}
2793 []bool {
2794 handle_array(data)
2795 }
2796 }
2797}
2798',
2799 ])
2800 assert csrc.contains('pg__handle_array_T_bool(')
2801 assert !csrc.contains('pg__handle_array(data')
2802}
2803
2804fn test_generate_c_uses_expected_array_payload_for_sumtype_generic_call() {
2805 csrc := generate_c_for_test_files([
2806 '
2807module time
2808
2809pub struct Time {}
2810',
2811 '
2812module orm
2813
2814import time
2815
2816pub struct Null {}
2817pub struct InfixType {}
2818
2819pub type Primitive = Null | time.Time | []time.Time | []InfixType
2820
2821fn array_kind[T](value []T) int {
2822 return value.len
2823}
2824
2825fn primitive_kind(value Primitive) int {
2826 return match value {
2827 Null {
2828 0
2829 }
2830 time.Time {
2831 1
2832 }
2833 []time.Time {
2834 array_kind(value)
2835 }
2836 []InfixType {
2837 array_kind(value)
2838 }
2839 }
2840}
2841',
2842 ])
2843 assert csrc.contains('orm__array_kind_T_time_Time(')
2844 assert !csrc.contains('orm__array_kind_T_time_Time(((((time__Time*)')
2845 assert csrc.contains('orm__array_kind_T_orm_InfixType(')
2846 assert !csrc.contains('orm__array_kind(((((Array_orm__InfixType*)')
2847}
2848
2849fn test_generate_c_keeps_builtin_copy_unspecialized() {
2850 csrc := generate_c_for_test_files([
2851 '
2852module arrays
2853
2854pub fn copy[T](mut dst []T, src []T) int {
2855 _ = dst
2856 _ = src
2857 return 0
2858}
2859',
2860 '
2861module main
2862
2863import arrays
2864
2865fn copy(mut dst []u8, src []u8) int {
2866 _ = dst
2867 _ = src
2868 return 0
2869}
2870
2871fn copy_bytes(mut dst []u8, src []u8) int {
2872 return copy(mut dst, src)
2873}
2874',
2875 ])
2876 assert csrc.contains('return copy(dst, src)')
2877 assert !csrc.contains('return copy_T_u8')
2878}
2879
2880fn test_generate_c_resolves_private_method_when_public_method_shares_suffix() {
2881 csrc := generate_c_for_test('
2882module os
2883
2884struct Process {}
2885
2886fn (mut p Process) is_pending() bool {
2887 return p._is_pending()
2888}
2889
2890fn (mut p Process) _is_pending() bool {
2891 return true
2892}
2893')
2894 assert csrc.contains('return os__Process___is_pending(p);')
2895 assert !csrc.contains('return os__Process__is_pending(p);')
2896}
2897
2898fn test_should_emit_builtin_print_backtrace_with_markused() {
2899 env := types.Environment.new()
2900 gen := Gen{
2901 env: env
2902 used_fn_keys: {
2903 'other': true
2904 }
2905 }
2906 assert gen.should_emit_fn_decl('builtin', ast.FnDecl{
2907 name: 'print_backtrace'
2908 })
2909}
2910
2911fn test_should_emit_time_duration_methods_with_markused() {
2912 env := types.Environment.new()
2913 gen := Gen{
2914 env: env
2915 used_fn_keys: {
2916 'other': true
2917 }
2918 }
2919 assert gen.should_emit_fn_decl('time', ast.FnDecl{
2920 name: 'microseconds'
2921 is_method: true
2922 receiver: ast.Parameter{
2923 name: 'd'
2924 typ: ast.Expr(ast.Ident{
2925 name: 'Duration'
2926 })
2927 }
2928 })
2929}
2930
2931fn test_should_emit_sync_channel_helpers_with_markused() {
2932 env := types.Environment.new()
2933 gen := Gen{
2934 env: env
2935 used_fn_keys: {
2936 'other': true
2937 }
2938 }
2939 for helper in ['try_pop_priv', 'try_push_priv', 'new_channel_st', 'new_spin_lock'] {
2940 assert gen.should_emit_fn_decl('sync', ast.FnDecl{
2941 name: helper
2942 })
2943 }
2944 assert gen.should_emit_fn_decl('sync', ast.FnDecl{
2945 name: 'try_wait'
2946 is_method: true
2947 })
2948}
2949
2950fn test_generate_c_specializes_bare_generic_placeholder_call() {
2951 csrc := generate_c_for_test('
2952module datatypes
2953
2954@[heap]
2955struct Node[T] {
2956 value T
2957 next &Node[T] = unsafe { 0 }
2958}
2959
2960fn none_node[T](init bool) &Node[T] {
2961 return &Node[T]{
2962 value: T{}
2963 }
2964}
2965
2966fn root_node[T](value T) &Node[T] {
2967 return &Node[T]{
2968 value: value
2969 next: none_node[T](false)
2970 }
2971}
2972
2973fn use() {
2974 _ = root_node[f64](1.0)
2975}
2976')
2977 assert csrc.contains('datatypes__none_node_T_f64(false)')
2978 assert !csrc.contains('datatypes__none_node_T(false)')
2979}
2980
2981fn test_generate_c_resolves_short_module_generic_token() {
2982 csrc := generate_c_for_test('
2983module orm
2984
2985struct InfixType {}
2986
2987type Primitive = []InfixType | int
2988
2989fn array_primitive[T](value []T) int {
2990 return value.len
2991}
2992
2993fn primitive(value Primitive) int {
2994 if value is []InfixType {
2995 return array_primitive(value)
2996 }
2997 return 0
2998}
2999')
3000 assert csrc.contains('orm__array_primitive_T_orm_InfixType')
3001 assert !csrc.contains('return array_primitive_T_InfixType')
3002}
3003
3004fn test_generate_c_appends_for_in_pointer_value_to_array() {
3005 csrc := generate_c_for_test('
3006struct PullRequest {
3007 id int
3008}
3009
3010fn use(prs []PullRequest) []PullRequest {
3011 mut out := []PullRequest{}
3012 for mut pr in prs {
3013 pr.id = pr.id + 1
3014 out << pr
3015 }
3016 return out
3017}
3018')
3019 assert csrc.contains('array__push(((array*)(&out)), pr);')
3020 assert !csrc.contains('&(PullRequest[1]){pr}')
3021}
3022
3023fn test_generate_c_uses_concrete_generic_value_for_sumtype_cast_in_for_in() {
3024 csrc := generate_c_for_test_files([
3025 '
3026module orm
3027
3028pub struct Null {}
3029
3030pub type Primitive = Null | bool | string
3031
3032pub fn primitive_array[T](values []T) []Primitive {
3033 mut out := []Primitive{cap: values.len}
3034 for value in values {
3035 out << Primitive(value)
3036 }
3037 return out
3038}
3039',
3040 '
3041module main
3042
3043import orm
3044
3045fn main() {
3046 _ = orm.primitive_array([true])
3047 _ = orm.primitive_array(["x"])
3048}
3049',
3050 ])
3051 start := csrc.index('Array_orm__Primitive orm__primitive_array_T_string(Array_string values) {') or {
3052 panic('missing string specialization')
3053 }
3054 tail := csrc[start..]
3055 end := tail.index('\n}\n') or { panic('missing string specialization end') }
3056 body := tail[..end]
3057 assert body.contains('._data._string')
3058 assert !body.contains('._data._bool')
3059}
3060
3061fn test_generate_c_uses_specialized_receiver_for_generic_method_body_call() {
3062 mut gen := Gen{
3063 sb: strings.new_builder(64)
3064 fn_return_types: {
3065 'Box_T_f64__get_value': 'f64'
3066 }
3067 fn_param_is_ptr: {
3068 'Box_T_f64__get_value': [true]
3069 }
3070 fn_param_types: {
3071 'Box_T_f64__get_value': ['Box_T_f64*']
3072 }
3073 runtime_local_types: {
3074 'b': 'Box_T_f64*'
3075 }
3076 }
3077 gen.call_expr(ast.Ident{
3078 name: 'Box__get_value'
3079 }, [
3080 ast.Expr(ast.Ident{
3081 name: 'b'
3082 }),
3083 ])
3084 assert gen.sb.str() == 'Box_T_f64__get_value(b)'
3085}
3086
3087fn test_generate_c_uses_specialized_receiver_for_lowered_address_method_call() {
3088 mut gen := Gen{
3089 sb: strings.new_builder(64)
3090 fn_return_types: {
3091 'Box__get_value': 'f64'
3092 'Box_T_f64__get_value': 'f64'
3093 }
3094 fn_param_is_ptr: {
3095 'Box__get_value': [true]
3096 'Box_T_f64__get_value': [true]
3097 }
3098 fn_param_types: {
3099 'Box__get_value': ['Box*']
3100 'Box_T_f64__get_value': ['Box_T_f64*']
3101 }
3102 runtime_local_types: {
3103 'b': 'Box_T_f64'
3104 }
3105 }
3106 gen.call_expr(ast.Ident{
3107 name: 'Box__get_value'
3108 }, [
3109 ast.Expr(ast.PrefixExpr{
3110 op: token.Token.amp
3111 expr: ast.Ident{
3112 name: 'b'
3113 }
3114 }),
3115 ])
3116 assert gen.sb.str() == 'Box_T_f64__get_value(&b)'
3117}
3118
3119fn test_generate_c_emits_nested_generic_result_struct_specialization() {
3120 csrc := generate_c_for_test('
3121struct Item {
3122 name string
3123}
3124
3125struct DecodeResult[T] {
3126 matched bool
3127 value T
3128}
3129
3130struct Decoder {}
3131
3132fn decode_struct_key[T](mut decoder Decoder, val T) !DecodeResult[T] {
3133 _ = decoder
3134 return DecodeResult[T]{
3135 matched: true
3136 value: val
3137 }
3138}
3139
3140fn (mut decoder Decoder) decode_value[T](mut val T) ! {
3141 decode_result := decode_struct_key(mut decoder, val)!
3142 if decode_result.matched {
3143 val = decode_result.value
3144 }
3145}
3146
3147fn use_decode(mut decoder Decoder, mut item Item) ! {
3148 decoder.decode_value(mut item)!
3149}
3150 ')
3151 assert csrc.contains('\n_result_DecodeResult_T_Item decode_struct_key_T_Item(Decoder* decoder, Item val) {')
3152 assert csrc.count('decode_struct_key_T_Item') >= 2
3153 assert !csrc.contains('decode_struct_key(decoder,')
3154 assert csrc.contains('_result_DecodeResult_T_Item _or_')
3155 assert csrc.contains('DecodeResult_T_Item decode_result =')
3156}
3157
3158fn test_generate_c_inferrs_nested_generic_method_from_mut_map_param() {
3159 csrc := generate_c_for_test('
3160struct Decoder {}
3161
3162fn (mut decoder Decoder) decode_map[V](mut val map[string]V) ! {
3163 _ = decoder
3164 _ = val
3165}
3166
3167fn (mut decoder Decoder) decode_value[T](mut val T) ! {
3168 $if T is $map {
3169 decoder.decode_map(mut val)!
3170 }
3171}
3172
3173fn use_decode(mut decoder Decoder, mut data map[string]string) ! {
3174 decoder.decode_value(mut data)!
3175}
3176')
3177 assert csrc.contains('\n_result_void Decoder__decode_map_T_string(Decoder* decoder, Map_string_string* val) {')
3178 assert csrc.contains('_result_void _or_t')
3179 assert csrc.contains('Decoder__decode_map_T_string(decoder, val)')
3180 assert !csrc.contains('Decoder__decode_map(decoder, val)')
3181}
3182
3183fn test_generate_c_specializes_nested_generic_method_from_active_generic_param() {
3184 csrc := generate_c_for_test('
3185struct Decoder {}
3186
3187fn (mut decoder Decoder) decode_string[T](mut val T) ! {
3188 _ = decoder
3189 _ = val
3190}
3191
3192fn (mut decoder Decoder) decode_value[T](mut val T) ! {
3193 $if T.unaliased_typ is voidptr {
3194 return
3195 } $else $if T.unaliased_typ is string {
3196 decoder.decode_string(mut val)!
3197 }
3198}
3199
3200fn use_decode(mut decoder Decoder, mut data string) ! {
3201 decoder.decode_value(mut data)!
3202}
3203')
3204 assert csrc.contains('\n_result_void Decoder__decode_string_T_string(Decoder* decoder, string* val) {')
3205 assert csrc.contains('Decoder__decode_string_T_string(decoder, val)')
3206 assert !csrc.contains('Decoder__decode_string_T_T')
3207}
3208
3209fn test_generate_c_matches_module_qualified_comptime_type_selector() {
3210 csrc := generate_c_for_test('
3211module time
3212
3213struct Time {
3214 unix i64
3215}
3216
3217struct Decoder {}
3218
3219fn (mut decoder Decoder) decode_time(mut val Time) ! {
3220 _ = decoder
3221 _ = val
3222}
3223
3224fn (mut decoder Decoder) decode_struct(mut val Time) ! {
3225 _ = decoder
3226 _ = val
3227}
3228
3229fn (mut decoder Decoder) decode_value[T](mut val T) ! {
3230 $if T.unaliased_typ is time.Time {
3231 decoder.decode_time(mut val)!
3232 } $else $if T.unaliased_typ is $struct {
3233 decoder.decode_struct(mut val)!
3234 }
3235}
3236
3237fn use_decode(mut decoder Decoder, mut t Time) ! {
3238 decoder.decode_value(mut t)!
3239}
3240')
3241 assert csrc.contains('time__Decoder__decode_time(decoder, val)')
3242 time_specialization := csrc.all_after('_result_void time__Decoder__decode_value_T_time_Time(time__Decoder* decoder, time__Time* val) {')
3243 .all_before('_result_void time__Decoder__decode_value_T_')
3244 assert !time_specialization.contains('time__Decoder__decode_struct(decoder, val)')
3245}
3246
3247fn test_generate_c_passes_local_value_address_to_mut_receiver_method() {
3248 csrc := generate_c_for_test('
3249module time
3250
3251struct Time {
3252 unix i64
3253}
3254
3255fn (mut t Time) from_json_string(raw string) ! {
3256 _ = t
3257 _ = raw
3258}
3259
3260fn decode_time() ! {
3261 mut decoded_time := Time{}
3262 decoded_time.from_json_string("1")!
3263}
3264')
3265 assert csrc.contains('time__Time__from_json_string(&decoded_time,')
3266 assert !csrc.contains('time__Time__from_json_string(decoded_time,')
3267}
3268
3269fn test_generate_c_specializes_comptime_field_generic_method_call_from_field_type() {
3270 csrc := generate_c_for_test('
3271struct NestedConfig {
3272 name string
3273}
3274
3275struct Config {
3276 repo string
3277 port int
3278 nested NestedConfig
3279 tags []string
3280}
3281
3282struct Decoder {}
3283
3284fn (mut decoder Decoder) decode_value[T](mut val T) ! {
3285 $if T.unaliased_typ is string {
3286 _ = val
3287 } $else $if T.unaliased_typ is int {
3288 _ = val
3289 } $else $if T.unaliased_typ is $struct {
3290 $for field in T.fields {
3291 mut decoded_field_value := val.$(field.name)
3292 decoder.decode_value(mut decoded_field_value)!
3293 val.$(field.name) = decoded_field_value
3294 }
3295 }
3296}
3297
3298fn use_config(mut decoder Decoder, mut config Config) ! {
3299 decoder.decode_value(mut config)!
3300}
3301')
3302 assert csrc.contains('Decoder__decode_value_T_string(decoder, &decoded_field_value)')
3303 assert csrc.contains('Decoder__decode_value_T_int(decoder, &decoded_field_value)')
3304 assert csrc.contains('Decoder__decode_value_T_NestedConfig(decoder, &decoded_field_value)')
3305 assert csrc.contains('Decoder__decode_value_T_Array_string(decoder, &decoded_field_value)')
3306 assert csrc.contains('\n_result_void Decoder__decode_value_T_NestedConfig(Decoder* decoder, NestedConfig* val) {')
3307 assert csrc.contains('\n_result_void Decoder__decode_value_T_Array_string(Decoder* decoder, Array_string* val) {')
3308 assert !csrc.contains('Decoder__decode_value_T_Config(decoder, &decoded_field_value)')
3309}
3310
3311fn test_generate_c_keeps_json2_decode_struct_key_struct_specialization_with_existing_specs() {
3312 csrc := generate_c_for_test('
3313module json2
3314
3315struct Item {
3316 name string
3317}
3318
3319struct ValueInfo {}
3320
3321struct StructKeyDecodeResult[T] {
3322 matched bool
3323 value T
3324}
3325
3326struct Decoder {}
3327
3328fn decode_struct_key[T](mut decoder Decoder, val T, key_info ValueInfo, prefix string, mut seen_required []string) !StructKeyDecodeResult[T] {
3329 _ = decoder
3330 _ = key_info
3331 _ = prefix
3332 _ = seen_required
3333 return StructKeyDecodeResult[T]{
3334 matched: true
3335 value: val
3336 }
3337}
3338
3339fn (mut decoder Decoder) decode_value[T](mut val T) ! {
3340 mut seen_required := []string{}
3341 decode_result := decode_struct_key(mut decoder, val, ValueInfo{}, "", mut seen_required)!
3342 if decode_result.matched {
3343 val = decode_result.value
3344 }
3345}
3346
3347fn seed_f64(mut decoder Decoder, mut seen_required []string) ! {
3348 _ = decode_struct_key(mut decoder, 1.25, ValueInfo{}, "", mut seen_required)!
3349}
3350
3351fn use_decode(mut decoder Decoder, mut item Item) ! {
3352 decoder.decode_value(mut item)!
3353}
3354 ')
3355 assert csrc.contains('\n_result_json2__StructKeyDecodeResult_T_json2_Item json2__decode_struct_key_T_json2_Item(')
3356 assert csrc.contains('struct _result_json2__StructKeyDecodeResult_T_json2_Item')
3357 assert csrc.contains('json2__decode_struct_key_T_json2_Item(decoder, (*val)')
3358 assert !csrc.contains('json2__decode_struct_key(decoder,')
3359}
3360
3361fn test_generic_struct_decl_type_counts_as_placeholder_for_fallback_specs() {
3362 assert type_contains_generic_placeholder(types.Type(types.Struct{
3363 name: 'api__ApiSuccessResponse'
3364 generic_params: ['T']
3365 }))
3366 assert type_contains_generic_placeholder(types.Type(types.Struct{
3367 name: 'api__ApiSuccessResponse'
3368 generic_params: ['T']
3369 fields: [
3370 types.Field{
3371 name: 'result'
3372 typ: types.Type(types.NamedType('T'))
3373 },
3374 ]
3375 }))
3376 assert !type_contains_generic_placeholder(types.Type(types.Struct{
3377 name: 'api__ApiSuccessResponse'
3378 generic_params: ['T']
3379 fields: [
3380 types.Field{
3381 name: 'result'
3382 typ: types.Type(types.string_)
3383 },
3384 ]
3385 }))
3386 assert !type_contains_generic_placeholder(types.Type(types.Struct{
3387 name: 'api__ApiSuccessResponse_T_bool'
3388 generic_params: ['T']
3389 }))
3390}
3391
3392fn test_generic_placeholder_scan_ignores_malformed_alias_payload() {
3393 mut malformed := types.Type(types.Alias{})
3394 unsafe {
3395 *(&u64(&malformed)) = 0
3396 *(&u64(&u8(&malformed) + 8)) = 0
3397 }
3398 assert !type_contains_generic_placeholder(malformed)
3399}
3400
3401fn test_generate_c_handles_recursive_heap_struct_generic_placeholder_scan() {
3402 csrc := generate_c_for_test('
3403 @[heap]
3404struct Node {
3405mut:
3406 children []&Node
3407}
3408
3409fn keep[T](value T) T {
3410 return value
3411}
3412
3413fn main() {
3414 node := &Node{}
3415 _ = keep(node.children)
3416}
3417')
3418 assert csrc.contains('struct Node')
3419}
3420
3421fn test_generate_c_specializes_embedded_generic_method_for_primary_generic_struct_instance() {
3422 csrc := generate_c_for_test('
3423struct Result {}
3424
3425struct BaseContext {}
3426
3427fn (mut ctx BaseContext) json[T](j T) Result {
3428 _ = ctx
3429 _ = j
3430 return Result{}
3431}
3432
3433struct ApiSuccessResponse[T] {
3434 success bool
3435 result T
3436}
3437
3438struct Context {
3439 BaseContext
3440}
3441
3442fn handler(mut ctx Context) Result {
3443 return ctx.json(ApiSuccessResponse[string]{
3444 success: true
3445 result: "ok"
3446 })
3447}
3448')
3449 assert csrc.contains('\nResult BaseContext__json_T_ApiSuccessResponse_T_string(BaseContext* ctx, ApiSuccessResponse_T_string j) {')
3450 assert csrc.contains('return BaseContext__json_T_ApiSuccessResponse_T_string(&ctx->BaseContext, ((ApiSuccessResponse_T_string){')
3451 assert !csrc.contains('BaseContext__json(ctx.BaseContext')
3452 assert !csrc.contains('BaseContext__json(ctx->BaseContext')
3453}
3454
3455fn test_generate_c_uses_primary_generic_struct_as_nested_generic_field_type() {
3456 csrc := generate_c_for_test('
3457struct ApiSuccessResponse[T] {
3458 success bool
3459 result T
3460}
3461
3462struct StructKeyDecodeResult[T] {
3463 matched bool
3464 value T
3465}
3466
3467fn decode_struct_key[T](val T) !StructKeyDecodeResult[T] {
3468 return StructKeyDecodeResult[T]{
3469 matched: true
3470 value: val
3471 }
3472}
3473
3474fn seed_f64() ! {
3475 _ = decode_struct_key(1.25)!
3476}
3477
3478fn use(mut response ApiSuccessResponse[string]) ! {
3479 decode_result := decode_struct_key(response)!
3480 if decode_result.matched {
3481 response = decode_result.value
3482 }
3483}
3484')
3485 assert csrc.contains('\nstruct StructKeyDecodeResult_T_ApiSuccessResponse {\n\tbool matched;\n\tApiSuccessResponse value;\n};')
3486 assert !csrc.contains('\nstruct StructKeyDecodeResult_T_ApiSuccessResponse {\n\tbool matched;\n\tf64 value;\n};')
3487}
3488
3489fn test_generate_c_discovers_explicit_generic_method_call_inside_specialized_generic_body() {
3490 csrc := generate_c_for_test('
3491struct Decoder {}
3492
3493struct Context {
3494 name string
3495}
3496
3497fn (mut decoder Decoder) cached_struct_field_infos[T]() []int {
3498 _ = decoder
3499 return []int{}
3500}
3501
3502fn (mut decoder Decoder) decode_value[T](mut val T) ! {
3503 _ = val
3504 _ = decoder.cached_struct_field_infos[T]()
3505}
3506
3507fn use_decode(mut decoder Decoder, mut ctx Context) ! {
3508 decoder.decode_value(mut ctx)!
3509}
3510')
3511 assert csrc.contains('\nArray_int Decoder__cached_struct_field_infos_T_Context(Decoder* decoder) {')
3512 assert csrc.contains('Decoder__cached_struct_field_infos_T_Context(decoder)')
3513 assert !csrc.contains('Decoder__cached_struct_field_infos_T(decoder)')
3514}
3515
3516fn test_generate_c_emits_json2_cached_field_infos_for_embedded_struct_decode_value() {
3517 csrc := generate_c_for_test('
3518module json2
3519
3520struct Decoder {}
3521
3522struct Embedded {}
3523
3524struct Context {
3525 Embedded
3526 name string
3527}
3528
3529struct Known {
3530 value int
3531}
3532
3533fn (mut decoder Decoder) cached_struct_field_infos[T]() []int {
3534 mut infos := []int{}
3535 $for field in T.fields {
3536 infos << field.name.len
3537 }
3538 return infos
3539}
3540
3541fn (mut decoder Decoder) decode_value[T](mut val T) ! {
3542 _ = val
3543 mut has_embeds := false
3544 $for field in T.fields {
3545 $if field.is_embed {
3546 has_embeds = true
3547 }
3548 }
3549 if has_embeds {
3550 _ = 1
3551 } else {
3552 field_infos := unsafe { decoder.cached_struct_field_infos[T]() }
3553 _ = field_infos
3554 }
3555}
3556
3557fn seed(mut decoder Decoder) {
3558 _ = unsafe { decoder.cached_struct_field_infos[Known]() }
3559}
3560
3561fn use_decode(mut decoder Decoder, mut ctx Context) ! {
3562 decoder.decode_value(mut ctx)!
3563}
3564')
3565 assert csrc.contains('json2__Decoder__cached_struct_field_infos_T_json2_Context(json2__Decoder* decoder) {')
3566 assert csrc.contains('json2__Decoder__cached_struct_field_infos_T_json2_Context(decoder)')
3567}
3568
3569fn test_generate_c_keeps_json2_encode_primitive_specialization_with_existing_specs() {
3570 csrc := generate_c_for_test_files([
3571 '
3572module json2
3573
3574@[params]
3575pub struct EncoderOptions {}
3576
3577pub fn encode[T](val T, config EncoderOptions) string {
3578 _ = val
3579 _ = config
3580 return ""
3581}
3582',
3583 '
3584module main
3585
3586import json2 as json
3587
3588fn seed() string {
3589 return json.encode("x")
3590}
3591
3592fn dispatch[T](payload T) string {
3593 return json.encode(payload)
3594}
3595
3596fn use() {
3597 _ = dispatch(i64(1))
3598}
3599',
3600 ])
3601 assert csrc.contains('string json2__encode_T_i64(i64 val, json2__EncoderOptions config) {')
3602 assert csrc.contains('json2__encode_T_i64(payload, ((json2__EncoderOptions){0}))')
3603 assert !csrc.contains('return encode_T_i64(payload')
3604 assert !csrc.contains('= encode_T_i64(payload')
3605 assert !csrc.contains('((EncoderOptions){0})')
3606}
3607
3608fn test_generate_c_specializes_json2_encode_value_per_encode_specialization() {
3609 csrc := generate_c_for_test_files([
3610 '
3611module json2
3612
3613@[params]
3614pub struct EncoderOptions {}
3615
3616struct Encoder {
3617mut:
3618 output []u8
3619}
3620
3621pub fn encode[T](val T, config EncoderOptions) string {
3622 _ = config
3623 mut encoder := Encoder{}
3624 return wrap_int(encoder.encode_value[T](val))
3625}
3626
3627fn (mut encoder Encoder) encode_value[T](val T) int {
3628 _ = val
3629 encoder.output << `x`
3630 return encoder.output.len
3631}
3632
3633fn wrap_int(n int) string {
3634 _ = n
3635 return ""
3636}
3637',
3638 '
3639module main
3640
3641import json2
3642
3643struct Request {
3644 value string
3645}
3646
3647fn use() {
3648 _ = json2.encode(Request{
3649 value: "x"
3650 }, json2.EncoderOptions{})
3651 _ = json2.encode(1, json2.EncoderOptions{})
3652}
3653',
3654 ])
3655 assert csrc.contains('int json2__Encoder__encode_value_T_Request(json2__Encoder* encoder, Request val)')
3656 assert csrc.contains('int json2__Encoder__encode_value_T_int(json2__Encoder* encoder, int val)')
3657 assert !csrc.contains('json2__Encoder__encode_value_T_Request_int')
3658}
3659
3660fn test_generate_c_skips_json2_void_specialization_from_voidptr_encode_path() {
3661 csrc := generate_c_for_test('
3662module json2
3663
3664struct Encoder {}
3665
3666fn (mut encoder Encoder) encode_value[T](val T) {
3667 _ = encoder
3668 $if T.unaliased_typ is voidptr {
3669 unsafe {
3670 encoder.encode_value(*val)
3671 }
3672 }
3673}
3674
3675fn (mut encoder Encoder) encode_struct_fields[T](val T) bool {
3676 _ = encoder
3677 _ = val
3678 return true
3679}
3680
3681fn use(mut encoder Encoder, data voidptr) {
3682 encoder.encode_value(data)
3683}
3684')
3685 assert csrc.contains('void json2__Encoder__encode_value_T_voidptr(json2__Encoder* encoder, void* val)')
3686 assert !csrc.contains('json2__Encoder__encode_value_T_void(')
3687 assert !csrc.contains('json2__Encoder__encode_struct_fields_T_void(')
3688}
3689
3690fn test_json2_encode_fallback_type_skips_void_but_keeps_voidptr() {
3691 mut fallback_types := map[string]types.Type{}
3692 add_json2_encode_value_fallback_type(mut fallback_types, types.Type(types.void_))
3693 add_json2_encode_value_fallback_type(mut fallback_types, types.Type(types.Pointer{
3694 base_type: types.Type(types.void_)
3695 }))
3696 assert 'void' !in fallback_types
3697 assert fallback_types.len == 1
3698}
3699
3700fn test_generate_c_lowers_generic_map_for_in_with_concrete_value_type() {
3701 csrc := generate_c_for_test('
3702module json2
3703
3704struct Encoder {}
3705
3706fn (mut encoder Encoder) encode_string(value string) {
3707 _ = encoder
3708 _ = value
3709}
3710
3711fn (mut encoder Encoder) encode_value[T](val T) {
3712 $if T.unaliased_typ is $map {
3713 for key, value in val {
3714 encoder.encode_string(key)
3715 encoder.encode_value(value)
3716 }
3717 }
3718}
3719
3720fn use(mut encoder Encoder, data map[string]string) {
3721 encoder.encode_value(data)
3722}
3723')
3724 assert csrc.contains('string key = *((string*)(DenseArray__key(')
3725 assert csrc.contains('string value = *((string*)(DenseArray__value(')
3726 assert csrc.contains('json2__Encoder__encode_value_T_string(encoder, value)')
3727 assert !csrc.contains('for (; ;) {')
3728}
3729
3730fn test_generate_c_emits_json2_struct_field_helper_for_array_element_type() {
3731 csrc := generate_c_for_test('
3732module json2
3733
3734struct Item {
3735 value string
3736}
3737
3738struct Encoder {}
3739
3740fn (mut encoder Encoder) encode_struct_fields[T](val T) bool {
3741 _ = encoder
3742 _ = val
3743 return true
3744}
3745
3746fn (mut encoder Encoder) encode_value[T](val T) {
3747 $if T.unaliased_typ is $array {
3748 for item in val {
3749 encoder.encode_value(item)
3750 }
3751 } $else $if T.unaliased_typ is $struct {
3752 _ = encoder.encode_struct_fields[T](val)
3753 }
3754}
3755
3756fn use(mut encoder Encoder, data []Item) {
3757 encoder.encode_value(data)
3758}
3759')
3760 assert csrc.contains('void json2__Encoder__encode_value_T_Array_json2_Item(json2__Encoder* encoder, Array_json2__Item val)')
3761 assert csrc.contains('void json2__Encoder__encode_value_T_json2_Item(json2__Encoder* encoder, json2__Item val)')
3762 assert csrc.contains('bool json2__Encoder__encode_struct_fields_T_json2_Item(json2__Encoder* encoder, json2__Item val)')
3763}
3764
3765fn test_generate_c_qualifies_unique_default_param_helper_call() {
3766 csrc := generate_c_for_test_files([
3767 '
3768module zstd
3769
3770pub fn default_c_level() int {
3771 return 3
3772}
3773
3774@[params]
3775pub struct CompressParams {
3776pub:
3777 compression_level int = default_c_level()
3778}
3779
3780pub fn compress(params CompressParams) int {
3781 return params.compression_level
3782}
3783',
3784 '
3785module main
3786
3787import zstd
3788
3789fn use() {
3790 _ = zstd.compress(zstd.CompressParams{})
3791}
3792',
3793 ])
3794 assert csrc.contains('.compression_level = zstd__default_c_level()')
3795 assert !csrc.contains('.compression_level = default_c_level()')
3796}
3797
3798fn test_generate_c_qualifies_local_array_generic_sumtype_call() {
3799 csrc := generate_c_for_test('
3800module orm
3801
3802struct InfixType {
3803 right int
3804}
3805
3806type Primitive = InfixType | []InfixType | int
3807
3808fn tenant_filter_array_primitive_type[T](value []T) int {
3809 _ = value
3810 return 1
3811}
3812
3813fn tenant_filter_primitive_type(value Primitive) int {
3814 return match value {
3815 InfixType {
3816 2
3817 }
3818 []InfixType {
3819 tenant_filter_array_primitive_type(value)
3820 }
3821 int {
3822 3
3823 }
3824 }
3825}
3826')
3827 assert csrc.contains('orm__tenant_filter_array_primitive_type_T_orm_InfixType(')
3828 assert !csrc.contains('tenant_filter_array_primitive_type_T_InfixType(')
3829}
3830
3831fn test_generate_c_resolves_bare_generic_helper_in_default_generic_receiver_method() {
3832 csrc := generate_c_for_test('
3833module datatypes
3834
3835@[heap]
3836struct BSTreeNode[T] {
3837 value T
3838}
3839
3840fn new_none_node[T](init bool) &BSTreeNode[T] {
3841 _ = init
3842 return &BSTreeNode[T]{}
3843}
3844
3845struct BSTree[T] {
3846 root &BSTreeNode[T] = unsafe { 0 }
3847}
3848
3849fn (bst &BSTree[T]) min_node() &BSTreeNode[T] {
3850 if unsafe { bst.root == 0 } {
3851 return new_none_node[T](false)
3852 }
3853 return bst.root
3854}
3855
3856fn use(bst &BSTree[f64]) &BSTreeNode[f64] {
3857 return bst.min_node()
3858}
3859')
3860 assert csrc.contains('return datatypes__new_none_node_T_f64(false);')
3861 assert !csrc.contains('return new_none_node_T(false);')
3862}
3863
3864fn test_explicit_generic_call_does_not_inherit_current_receiver_method_suffix() {
3865 mut gen := Gen.new([])
3866 gen.cur_module = 'sync'
3867 gen.cur_fn_c_name = 'sync__ThreadLocalStorage_T_int__get'
3868 gen.active_generic_types['T'] = types.Type(types.int_)
3869 gen.fn_return_types['sync__convert_voidptr_to_t_T_int'] = '_result_int'
3870 gen.fn_param_types['sync__convert_voidptr_to_t_T_int'] = ['void*']
3871 gen.fn_param_is_ptr['sync__convert_voidptr_to_t_T_int'] = [false]
3872 gen.sb = strings.new_builder(64)
3873 lhs := ast.Expr(ast.GenericArgOrIndexExpr{
3874 lhs: ast.Expr(ast.Ident{
3875 name: 'convert_voidptr_to_t'
3876 })
3877 expr: ast.Expr(ast.Ident{
3878 name: 'T'
3879 })
3880 })
3881 arg := ast.Expr(ast.Ident{
3882 name: 'value'
3883 })
3884 assert gen.try_emit_explicit_generic_call(lhs, [arg])
3885 out := gen.sb.str()
3886 assert out == 'sync__convert_voidptr_to_t_T_int(value)'
3887 assert !out.contains('__get')
3888}
3889
3890fn test_generate_c_keeps_builtin_attribute_kind_enum_shorthand_compare() {
3891 csrc := generate_c_for_test('
3892struct Marker {}
3893
3894type Primitive = Marker | string
3895
3896enum AttributeKind {
3897 plain
3898 string
3899}
3900
3901struct Attr {
3902 name string
3903 has_arg bool
3904 arg string
3905 kind AttributeKind
3906}
3907
3908struct Field {
3909 name string
3910 attrs []Attr
3911}
3912
3913fn attr_is_string(attr Attr) bool {
3914 return attr.kind == .string
3915}
3916
3917fn sql_field_name(field Field) string {
3918 mut name := field.name
3919 for attr in field.attrs {
3920 if attr.name == field.name && attr.has_arg && attr.kind == .string {
3921 name = attr.arg
3922 break
3923 }
3924 }
3925 return name
3926}
3927')
3928 assert csrc.contains('(attr.kind == AttributeKind__string)')
3929 assert !csrc.contains('attr.kind._tag')
3930}
3931
3932fn test_gen_comptime_veb_html_returns_result_value() {
3933 mut gen := Gen.new([])
3934 gen.gen_comptime_expr(ast.ComptimeExpr{
3935 expr: ast.Expr(ast.CallExpr{
3936 lhs: ast.Expr(ast.SelectorExpr{
3937 lhs: ast.Expr(ast.Ident{
3938 name: 'veb'
3939 })
3940 rhs: ast.Ident{
3941 name: 'html'
3942 }
3943 })
3944 args: [
3945 ast.Expr(ast.StringLiteral{
3946 value: '"index.html"'
3947 }),
3948 ]
3949 })
3950 })
3951 assert gen.sb.str() == '(veb__Result){0}'
3952}
3953
3954fn test_find_struct_decl_info_prefers_exact_name_before_short_collision() {
3955 other_file := ast.File{
3956 mod: 'other'
3957 name: 'other.v'
3958 stmts: [
3959 ast.Stmt(ast.ModuleStmt{
3960 name: 'other'
3961 }),
3962 ast.Stmt(ast.StructDecl{
3963 name: 'Context'
3964 }),
3965 ]
3966 }
3967 main_file := ast.File{
3968 mod: 'main'
3969 name: 'main.v'
3970 stmts: [
3971 ast.Stmt(ast.ModuleStmt{
3972 name: 'main'
3973 }),
3974 ast.Stmt(ast.StructDecl{
3975 name: 'Context'
3976 }),
3977 ]
3978 }
3979 mut gen := Gen.new([other_file, main_file])
3980 gen.cur_module = 'main'
3981 info := gen.find_struct_decl_info_by_c_name('Context') or { panic('missing Context') }
3982 assert info.mod == 'main'
3983 assert info.file_name == 'main.v'
3984}
3985
3986fn test_generate_c_orders_c_type_aliases_before_fn_aliases() {
3987 csrc := generate_c_for_test('
3988module sqlite
3989
3990type Sig2 = fn (&Sqlite3_file, &int) int
3991
3992pub type Sqlite3_file = C.sqlite3_file
3993
3994type Fn_sqlite3_syscall_ptr = fn ()
3995
3996pub type Sqlite3_vfs = C.sqlite3_vfs
3997
3998type Sqlite3_vfs_set_system_call_fn = fn (&Sqlite3_vfs, &char, Fn_sqlite3_syscall_ptr) int
3999
4000pub struct C.sqlite3_file {}
4001
4002pub struct C.sqlite3_vfs {}
4003')
4004 file_alias_pos := csrc.index('typedef struct sqlite3_file sqlite__Sqlite3_file;') or {
4005 panic('missing sqlite file alias')
4006 }
4007 sig_pos := csrc.index('typedef int (*sqlite__Sig2)(sqlite__Sqlite3_file*, int*);') or {
4008 panic('missing Sig2 alias')
4009 }
4010 assert file_alias_pos < sig_pos
4011 assert csrc.contains('typedef int (*sqlite__Sqlite3_vfs_set_system_call_fn)(sqlite__Sqlite3_vfs*, char*, sqlite__Fn_sqlite3_syscall_ptr);')
4012 assert !csrc.contains('Fn_sqlite3_syscall_*')
4013}
4014
4015fn test_generate_c_wraps_result_error_from_embedded_message_error() {
4016 csrc := generate_c_for_test('
4017interface IError {
4018 msg() string
4019 code() int
4020}
4021
4022struct MessageError {
4023 msg string
4024 code int
4025}
4026
4027fn (err MessageError) msg() string {
4028 return err.msg
4029}
4030
4031fn (err MessageError) code() int {
4032 return err.code
4033}
4034
4035struct SQLError {
4036 MessageError
4037}
4038
4039fn connect() !int {
4040 return &SQLError{
4041 msg: "bad"
4042 code: 1
4043 }
4044}
4045
4046fn error_message() IError {
4047 return SQLError{
4048 msg: "bad"
4049 code: 2
4050 }
4051}
4052')
4053 assert csrc.contains('return (_result_int){ .is_error=true, .err=((IError){._object = (void*)')
4054 assert csrc.contains('.msg = IError_SQLError_msg_wrapper')
4055 assert csrc.contains('return MessageError__msg(((SQLError*)_obj)->MessageError);')
4056 assert csrc.contains('static string __iface_wrap_IError_SQLError_msg(void* _obj)')
4057 assert csrc.contains('return MessageError__code(((SQLError*)_obj)->MessageError);')
4058 assert csrc.contains('return ({ SQLError* _ierr_obj')
4059 assert csrc.contains('.type_name = IError_SQLError_type_name_wrapper')
4060 assert !csrc.contains('(IError){._object = (void*)&_iface_obj')
4061}
4062
4063fn test_generate_c_wraps_ierror_const_from_embedded_error() {
4064 csrc := generate_c_for_test('
4065interface IError {
4066 msg() string
4067 code() int
4068}
4069
4070struct Error {}
4071
4072fn (err Error) msg() string {
4073 return ""
4074}
4075
4076fn (err Error) code() int {
4077 return 0
4078}
4079
4080struct None__ {
4081 Error
4082}
4083
4084const none__ = IError(&None__{})
4085
4086fn use_none() IError {
4087 return none__
4088}
4089')
4090 assert csrc.contains('static string IError_None___msg_wrapper(void* _obj);')
4091 assert csrc.contains('#define none__ ((IError){')
4092 assert csrc.contains('.msg = IError_None___msg_wrapper')
4093 assert csrc.contains('.code = IError_None___code_wrapper')
4094 assert !csrc.contains('None____msg')
4095 assert !csrc.contains('None____code')
4096}
4097
4098fn test_generate_c_wraps_interface_field_default_expr() {
4099 csrc := generate_c_for_test('
4100interface Speaker {
4101 speak() string
4102}
4103
4104struct Person {}
4105
4106fn (p Person) speak() string {
4107 return ""
4108}
4109
4110const default_person = Person{}
4111
4112struct Holder {
4113 speaker Speaker = default_person
4114 name string
4115}
4116
4117fn make() Holder {
4118 return Holder{}
4119}
4120')
4121 assert csrc.contains('.speaker = ((Speaker){')
4122 assert csrc.contains('.speak =')
4123 assert csrc.contains('Person__speak')
4124 assert !csrc.contains('.speaker = default_person')
4125}
4126
4127fn test_generate_c_emits_pack_pragmas_for_packed_struct() {
4128 csrc := generate_c_for_test("
4129@[_pack: '1']
4130struct Packet {
4131 tag u8
4132 len u32
4133}
4134")
4135 assert csrc.contains('#pragma pack(push, 1)\nstruct Packet {')
4136 assert csrc.contains('u32 len;')
4137 assert csrc.contains('};\n#pragma pack(pop)')
4138}
4139
4140fn test_generate_c_keeps_stdatomic_atomicval_on_integer_fallback() {
4141 stdatomic_src := "
4142module stdatomic
4143
4144pub struct AtomicVal[T] {
4145 val T
4146}
4147
4148fn panic(msg string) {}
4149
4150pub fn new_atomic[T](val T) &AtomicVal[T] {
4151 return &AtomicVal[T]{
4152 val: val
4153 }
4154}
4155
4156pub fn (mut a AtomicVal[T]) add(delta T) T {
4157 @DLR@if T is int {
4158 return delta
4159 }
4160 panic('unreachable')
4161}
4162"
4163 main_src := '
4164module main
4165
4166import stdatomic
4167
4168fn main() {
4169 mut counter := stdatomic.new_atomic(0)
4170 counter.add(1)
4171}
4172'
4173 csrc := generate_c_for_test_files([stdatomic_src.replace('@DLR@', '$'), main_src])
4174 assert csrc.contains('int stdatomic__AtomicVal_T_int__add_T_int(stdatomic__AtomicVal_T_int* a, int delta)')
4175 assert csrc.contains('return delta;')
4176 assert csrc.contains('stdatomic__AtomicVal_T_int__add_T_int(counter, 1)')
4177 assert !csrc.contains('stdatomic__AtomicVal_T_f64__add')
4178}
4179
4180fn test_generate_c_specializes_generic_function_value_in_struct_initializer() {
4181 transport_src := '
4182module transport
4183
4184pub type RequestHandler = fn (req int) int
4185
4186pub struct ServerConfig {
4187pub:
4188 handler RequestHandler
4189}
4190
4191pub fn serve(config ServerConfig) int {
4192 return config.handler(7)
4193}
4194'
4195 web_src := '
4196module web
4197
4198import transport
4199
4200pub fn run_new[A, X](app A) int {
4201 _ = app
4202 config := transport.ServerConfig{
4203 handler: parallel_request_handler[A, X]
4204 }
4205 return transport.serve(config)
4206}
4207
4208fn parallel_request_handler[A, X](req int) int {
4209 return route[A, X](req)
4210}
4211
4212fn route[A, X](req int) int {
4213 return req
4214}
4215'
4216 main_src := '
4217module main
4218
4219import web
4220
4221struct App {}
4222struct Context {}
4223
4224fn main() {
4225 _ = web.run_new[App, Context](App{})
4226}
4227'
4228 csrc := generate_c_for_test_files([transport_src, web_src, main_src])
4229 assert csrc.contains('int web__parallel_request_handler_T_App_Context(int req)')
4230 assert csrc.contains('return web__route_T_App_Context(req);')
4231 assert csrc.contains('.handler = ((transport__RequestHandler)web__parallel_request_handler_T_App_Context)')
4232 assert !csrc.contains('.handler = web__parallel_request_handler,')
4233}
4234
4235fn test_generate_c_lowers_slice_receiver_before_fallback_method_call() {
4236 csrc := generate_c_for_test('
4237fn main() {
4238 text := "abc"
4239 _ = text[..2].contains("a")
4240}
4241')
4242 assert csrc.contains('string__substr')
4243}
4244
4245fn test_generate_c_infers_generic_slice_bytestr_return_type() {
4246 csrc := generate_c_for_test('
4247fn read_head[T](req T, buf []u8) string {
4248 _ = req
4249 head := buf[..1].bytestr()
4250 return head
4251}
4252
4253fn main() {
4254 _ = read_head(0, [u8(65)])
4255}
4256')
4257 assert csrc.contains('string head = Array_u8__bytestr(array__slice(buf, 0, 1));')
4258 assert !csrc.contains('int head = Array_u8__bytestr')
4259}
4260
4261fn test_generate_c_infers_unsafe_deref_cast_value_type() {
4262 csrc := generate_c_for_test('
4263struct RequestParams {
4264 port int
4265}
4266
4267fn read_params(ptr voidptr) RequestParams {
4268 params := unsafe { *(&RequestParams(ptr)) }
4269 return params
4270}
4271
4272fn main() {
4273 params := RequestParams{
4274 port: 1
4275 }
4276 _ = read_params(voidptr(¶ms))
4277}
4278')
4279 assert csrc.contains('RequestParams params = *(((RequestParams*)(ptr)));')
4280 assert !csrc.contains('RequestParams* params = *(((RequestParams*)(ptr)));')
4281}
4282
4283fn test_generate_c_infers_generic_unsafe_deref_cast_value_type() {
4284 csrc := generate_c_for_test('
4285struct RequestParams {
4286 port int
4287}
4288
4289struct Request {
4290 user_data voidptr
4291}
4292
4293fn read_params[T](req Request) RequestParams {
4294 params := unsafe { *(&RequestParams(req.user_data)) }
4295 return params
4296}
4297
4298fn main() {
4299 params := RequestParams{
4300 port: 1
4301 }
4302 req := Request{
4303 user_data: voidptr(¶ms)
4304 }
4305 _ = read_params[int](req)
4306}
4307')
4308 assert csrc.contains('RequestParams params = *(((RequestParams*)(req.user_data)));')
4309 assert !csrc.contains('RequestParams* params = *(((RequestParams*)(req.user_data)));')
4310}
4311
4312fn test_generate_c_keeps_http_header_get_result_in_generic_function() {
4313 csrc := generate_c_for_test('
4314struct Header {}
4315
4316fn (h Header) get(key int) !string {
4317 _ = h
4318 _ = key
4319 return ""
4320}
4321
4322struct Request {
4323 header Header
4324}
4325
4326fn read_host[T](req Request) string {
4327 host := req.header.get(0) or { "" }
4328 return host
4329}
4330
4331fn main() {
4332 _ = read_host[int](Request{})
4333}
4334')
4335 assert csrc.contains('_result_string _or_')
4336 assert !csrc.contains('void* _or_')
4337 assert !csrc.contains('int host =')
4338}
4339
4340fn test_generate_c_keeps_imported_http_header_get_result_for_or_default() {
4341 csrc := generate_c_for_test_files([
4342 '
4343module http
4344
4345pub enum CommonHeader {
4346 host
4347 accept
4348}
4349
4350pub struct Header {}
4351
4352pub fn (h Header) get(key CommonHeader) !string {
4353 _ = h
4354 _ = key
4355 return ""
4356}
4357
4358pub struct Request {
4359pub:
4360 header Header
4361}
4362',
4363 '
4364module main
4365
4366import http
4367
4368fn handle(req http.Request) string {
4369 host_with_port := req.header.get(.host) or { "" }
4370 return host_with_port
4371}
4372',
4373 ])
4374 assert csrc.contains('_result_string _or_')
4375 assert !csrc.contains('void* _or_')
4376 assert !csrc.contains('int host_with_port =')
4377}
4378
4379fn test_generate_c_keeps_imported_http_header_get_result_in_generic_function() {
4380 csrc := generate_c_for_test_files([
4381 '
4382module http
4383
4384pub enum CommonHeader {
4385 host
4386}
4387
4388pub struct Header {}
4389
4390pub fn (h Header) get(key CommonHeader) !string {
4391 _ = h
4392 _ = key
4393 return ""
4394}
4395
4396pub struct Request {
4397pub:
4398 header Header
4399}
4400',
4401 '
4402module main
4403
4404import http
4405
4406fn handle[T](req http.Request) string {
4407 host_with_port := req.header.get(.host) or { "" }
4408 return host_with_port
4409}
4410
4411fn main() {
4412 _ = handle[int](http.Request{})
4413}
4414',
4415 ])
4416 assert csrc.contains('_result_string _or_')
4417 assert csrc.contains('string host_with_port =')
4418 assert !csrc.contains('void* _or_')
4419 assert !csrc.contains('int host_with_port =')
4420}
4421
4422fn test_generate_c_lowers_generic_promoted_embedded_field_selector() {
4423 csrc := generate_c_for_test('
4424struct Base {
4425 value int
4426}
4427
4428struct Wrapper {
4429 Base
4430}
4431
4432fn read_value[T](x &T) int {
4433 return x.value
4434}
4435
4436fn main() {
4437 w := Wrapper{
4438 Base: Base{
4439 value: 7
4440 }
4441 }
4442 _ = read_value[Wrapper](&w)
4443}
4444')
4445 assert csrc.contains('return x->Base.value;')
4446 assert !csrc.contains('return x->value;')
4447}
4448
4449fn test_generate_c_lowers_generic_promoted_embedded_field_with_context_name_collision() {
4450 csrc := generate_c_for_test_files([
4451 '
4452module http
4453
4454pub enum CommonHeader {
4455 accept
4456}
4457
4458pub struct Header {}
4459
4460pub fn (h Header) get(key CommonHeader) !string {
4461 _ = h
4462 _ = key
4463 return ""
4464}
4465
4466pub struct Request {
4467pub:
4468 header Header
4469}
4470',
4471 '
4472module veb
4473
4474import http
4475
4476pub struct Context {
4477pub:
4478 req http.Request
4479}
4480
4481pub fn serve[X](user_context &X) string {
4482 accept_header := user_context.req.header.get(.accept) or { "" }
4483 return accept_header
4484}
4485',
4486 '
4487module main
4488
4489import veb
4490
4491pub struct Context {
4492 veb.Context
4493}
4494
4495fn boot(mut ctx Context) {
4496 _ = veb.serve[Context](&ctx)
4497}
4498',
4499 ])
4500 assert csrc.contains('http__Header__get(user_context->Context.req.header, http__CommonHeader__accept)')
4501 assert csrc.contains('_result_string _or_')
4502 assert csrc.contains('string accept_header =')
4503 assert !csrc.contains('http__Response accept_header =')
4504 assert !csrc.contains('http__Header__get(user_context->req.header')
4505}
4506
4507fn test_generate_c_keeps_bare_main_context_specialization_for_generic_init() {
4508 csrc := generate_c_for_test_files([
4509 '
4510module veb
4511
4512pub struct Context {
4513pub:
4514 content_type string
4515}
4516
4517pub fn new_user_context[X](ctx &Context) X {
4518 return X{
4519 Context: ctx
4520 }
4521}
4522
4523pub fn new_user_context_decl[X](ctx &Context) X {
4524 mut user_context := X{
4525 Context: ctx
4526 }
4527 return user_context
4528}
4529',
4530 '
4531module main
4532
4533import veb
4534
4535pub struct Context {
4536 veb.Context
4537pub:
4538 path string
4539}
4540
4541fn boot() {
4542 base := veb.Context{}
4543 _ = veb.new_user_context[Context](&base)
4544 _ = veb.new_user_context_decl[Context](&base)
4545}
4546',
4547 ])
4548 assert csrc.contains('\nContext veb__new_user_context_T_Context(veb__Context* ctx) {')
4549 assert csrc.contains('return ((Context){.Context = *ctx')
4550 assert !csrc.contains('return ((veb__Context){.Context = (*(ctx))')
4551 assert csrc.contains('\nContext veb__new_user_context_decl_T_Context(veb__Context* ctx) {')
4552 assert csrc.contains('Context user_context = ((Context){.Context = *ctx')
4553 assert !csrc.contains('Context user_context = ((veb__Context){.Context = (*(ctx))')
4554}
4555
4556fn test_generate_c_dereferences_pointer_for_embedded_struct_init_field() {
4557 csrc := generate_c_for_test('
4558struct Base {
4559 value int
4560}
4561
4562struct Wrapper {
4563 Base
4564}
4565
4566fn make_wrapper(ctx &Base) Wrapper {
4567 return Wrapper{
4568 Base: ctx
4569 }
4570}
4571
4572fn main() {
4573 base := Base{
4574 value: 7
4575 }
4576 _ = make_wrapper(&base)
4577}
4578')
4579 assert csrc.contains('.Base = *ctx')
4580 assert !csrc.contains('.Base = ctx')
4581}
4582
4583fn test_generate_c_lowers_string_plus_assign() {
4584 csrc := generate_c_for_test('
4585fn append_slash() string {
4586 mut asked_path := "docs"
4587 asked_path += "/"
4588 return asked_path
4589}
4590
4591fn main() {
4592 _ = append_slash()
4593}
4594')
4595 assert csrc.contains('asked_path = string__plus(asked_path, (string){.str = "/",')
4596 assert !csrc.contains('asked_path += (string){.str = "/",')
4597}
4598
4599fn test_generate_c_keeps_map_or_pointer_value_type() {
4600 csrc := generate_c_for_test('
4601struct Item {
4602 value int
4603}
4604
4605fn read_item(items map[int]&Item) &Item {
4606 item := items[1] or { return &Item{} }
4607 return item
4608}
4609
4610fn main() {
4611 mut items := map[int]&Item{}
4612 items[1] = &Item{
4613 value: 7
4614 }
4615 _ = read_item(items)
4616}
4617')
4618 assert csrc.contains('Item* item = *((Item**)')
4619 assert !csrc.contains('Item item = *((Item**)')
4620}
4621
4622fn test_generate_c_lowers_selector_map_or_without_result_temp() {
4623 csrc := generate_c_for_test('
4624struct StaticHandler {
4625 static_files map[string]string
4626}
4627
4628struct Context {
4629 value int
4630}
4631
4632fn serve[X](app StaticHandler, mut ctx X, asked_path string) bool {
4633 static_handler := app
4634 static_file := static_handler.static_files[asked_path] or { return false }
4635 _ = ctx
4636 return static_file != ""
4637}
4638
4639fn main() {
4640 sh := StaticHandler{
4641 static_files: map[string]string{}
4642 }
4643 mut ctx := Context{}
4644 _ = serve[Context](sh, mut ctx, "/")
4645}
4646')
4647 assert csrc.contains('map__get_check')
4648 assert csrc.contains('string static_file = *((string*)')
4649 assert !csrc.contains('string _or_t')
4650}
4651
4652fn test_generate_c_preserves_explicit_generic_arg_for_array_result_forwarder() {
4653 mut g := Gen{
4654 sb: strings.new_builder(64)
4655 runtime_local_types: {
4656 'rng': 'Rng'
4657 'items': 'Array_Array_u8'
4658 }
4659 active_generic_types: {
4660 'T': types.Type(types.Array{
4661 elem_type: types.Type(types.Alias{
4662 name: 'u8'
4663 base_type: types.Type(types.Primitive{
4664 props: .integer | .unsigned
4665 size: 8
4666 })
4667 })
4668 })
4669 }
4670 fn_return_types: {
4671 'Rng__elem_T_Array_u8': '_result_Array_u8'
4672 }
4673 fn_param_is_ptr: {
4674 'Rng__elem_T_Array_u8': [true, false]
4675 }
4676 }
4677 g.call_expr(ast.GenericArgOrIndexExpr{
4678 lhs: ast.SelectorExpr{
4679 lhs: ast.Ident{
4680 name: 'rng'
4681 }
4682 rhs: ast.Ident{
4683 name: 'elem'
4684 }
4685 }
4686 expr: ast.Ident{
4687 name: 'T'
4688 }
4689 }, [ast.Expr(ast.Ident{
4690 name: 'items'
4691 })])
4692 assert g.sb.str() == 'Rng__elem_T_Array_u8(&rng, items)'
4693}
4694
4695fn test_generate_c_wraps_fixed_array_result_with_memcpy() {
4696 csrc := generate_c_for_test('
4697fn first(items [][4]u8) ![4]u8 {
4698 return items[0]
4699}
4700
4701fn empty() ![4]u8 {
4702 return [4]u8{}
4703}
4704')
4705 assert csrc.contains('Array_fixed_u8_4 _val = {0}; memcpy(_val, ((Array_fixed_u8_4*)items.data)[((int)(0))], sizeof(_val));')
4706 assert !csrc.contains('Array_fixed_u8_4 _val = ((Array_fixed_u8_4*)items.data)[0];')
4707 assert !csrc.contains('memcpy(_val, {0}, sizeof(_val));')
4708}
4709
4710fn test_generate_c_lowers_overloaded_mod_operator() {
4711 mut g := Gen{
4712 sb: strings.new_builder(64)
4713 runtime_local_types: {
4714 'a': 'big__Integer'
4715 'b': 'big__Integer'
4716 }
4717 fn_return_types: {
4718 'big__Integer__op_mod': 'big__Integer'
4719 }
4720 }
4721 g.expr(ast.InfixExpr{
4722 op: .mod
4723 lhs: ast.Ident{
4724 name: 'a'
4725 }
4726 rhs: ast.Ident{
4727 name: 'b'
4728 }
4729 })
4730 assert g.sb.str() == 'big__Integer__op_mod(a, b)'
4731}
4732
4733fn test_generate_c_keeps_pointer_array_for_in_element_type() {
4734 csrc := generate_c_for_test('
4735struct Item {
4736 name string
4737}
4738
4739fn first(items []&Item) string {
4740 for item in items {
4741 return item.name
4742 }
4743 return ""
4744}
4745
4746fn main() {
4747 item := &Item{
4748 name: "x"
4749 }
4750 _ = first([item])
4751}
4752')
4753 assert csrc.contains('Item* item = ((Item**)(items.data))[_idx_item];')
4754 assert !csrc.contains('void* item = ((void**)(items).data)')
4755}
4756
4757fn test_generate_c_specializes_nested_generic_call_with_function_type_arg() {
4758 json_src := '
4759module json2
4760
4761pub struct EncoderOptions {}
4762
4763pub fn encode[T](val T, config EncoderOptions) string {
4764 _ = val
4765 _ = config
4766 return ""
4767}
4768'
4769 main_src := '
4770module main
4771
4772import json2
4773
4774fn value_fn(n int) string {
4775 _ = n
4776 return ""
4777}
4778
4779struct App {}
4780
4781fn (mut app App) dispatch[T](j T) string {
4782 _ = app
4783 return json2.encode(j)
4784}
4785
4786fn main() {
4787 mut app := App{}
4788 _ = app.dispatch(value_fn)
4789}
4790'
4791 csrc := generate_c_for_test_files([json_src, main_src])
4792 assert csrc.contains('json2__encode_T_fn_')
4793 assert csrc.contains('App__dispatch_T_fn_')
4794 assert csrc.contains('return json2__encode_T_fn_')
4795 assert csrc.contains('((json2__EncoderOptions){0})')
4796 assert !csrc.contains('((EncoderOptions){0})')
4797}
4798
4799fn test_generate_c_renames_anonymous_function_params() {
4800 csrc := generate_c_for_test('
4801fn check(_ int, _ int) int {
4802 return 0
4803}
4804
4805fn main() {
4806 _ = check(1, 2)
4807}
4808')
4809 assert csrc.contains('int check(int _arg0, int _arg1)')
4810 assert !csrc.contains('int check(int _, int _)')
4811}
4812
4813fn test_generate_c_emits_late_generic_result_alias_before_forward_decl() {
4814 csrc := generate_c_for_test('
4815fn values[T](x T) ![]T {
4816 _ = x
4817 return []T{}
4818}
4819
4820fn wrapper[U](x U) ![]U {
4821 return values[U](x)
4822}
4823
4824fn main() {
4825 _ = wrapper[voidptr](unsafe { nil }) or { return }
4826}
4827')
4828 alias_pos := csrc.index('typedef struct _result_Array_voidptr _result_Array_voidptr;') or {
4829 assert false
4830 return
4831 }
4832 decl_pos := csrc.index('_result_Array_voidptr values_T_voidptr(void* x);') or {
4833 assert false
4834 return
4835 }
4836 assert alias_pos < decl_pos
4837}
4838
4839fn test_generate_c_emits_late_generic_struct_result_alias_before_forward_decl() {
4840 csrc := generate_c_for_test('
4841module json2
4842
4843struct Label {
4844 name string
4845}
4846
4847struct ValueInfo {}
4848
4849struct StructKeyDecodeResult[T] {
4850 matched bool
4851 value T
4852}
4853
4854struct Decoder {}
4855
4856fn decode_struct_key[T](mut decoder Decoder, val T, key_info ValueInfo, prefix string, mut seen_required []string) !StructKeyDecodeResult[T] {
4857 _ = decoder
4858 _ = key_info
4859 _ = prefix
4860 _ = seen_required
4861 return StructKeyDecodeResult[T]{
4862 matched: true
4863 value: val
4864 }
4865}
4866
4867fn (mut decoder Decoder) decode_array[T](mut val []T) ! {
4868 mut seen_required := []string{}
4869 decode_result := decode_struct_key(mut decoder, val, ValueInfo{}, "", mut seen_required)!
4870 if decode_result.matched {
4871 val = decode_result.value
4872 }
4873}
4874
4875fn seed_f64(mut decoder Decoder, mut seen_required []string) ! {
4876 _ = decode_struct_key(mut decoder, 1.25, ValueInfo{}, "", mut seen_required)!
4877}
4878
4879fn use_decode(mut decoder Decoder, mut labels []Label) ! {
4880 decoder.decode_array(mut labels)!
4881}
4882')
4883 alias_pos := csrc.index('typedef struct _result_json2__StructKeyDecodeResult_T_Array') or {
4884 assert false
4885 return
4886 }
4887 decl_pos := csrc.index(' json2__decode_struct_key_T_Array') or {
4888 assert false
4889 return
4890 }
4891 assert alias_pos < decl_pos
4892}
4893