v / vlib / v2 / gen / cleanc / target_codegen_test.v
2825 lines · 2413 sloc · 94.25 KB · 3690f882f624c3d82b8c36d38434baf10f68ce4c
Raw
1module cleanc
2
3import os
4import v2.ast
5import v2.parser
6import v2.pref as vpref
7import v2.token
8import v2.transformer
9import v2.types
10
11fn new_target_test_gen(target_os string, user_defines []string) &Gen {
12 return new_target_test_gen_with_freestanding(target_os, user_defines, false)
13}
14
15fn new_target_test_gen_with_freestanding(target_os string, user_defines []string, freestanding bool) &Gen {
16 return new_target_test_gen_with_options(target_os, user_defines, freestanding, false)
17}
18
19fn new_target_test_gen_with_options(target_os string, user_defines []string, freestanding bool, prealloc bool) &Gen {
20 return new_target_test_gen_with_options_and_hooks(target_os, user_defines, freestanding,
21 prealloc, [])
22}
23
24fn new_target_test_gen_with_options_and_hooks(target_os string, user_defines []string, freestanding bool, prealloc bool, freestanding_hooks []string) &Gen {
25 return new_target_test_gen_with_options_hooks_and_skip_builtin(target_os, user_defines,
26 freestanding, prealloc, freestanding_hooks, false)
27}
28
29fn new_target_test_gen_with_options_hooks_and_skip_builtin(target_os string, user_defines []string, freestanding bool, prealloc bool, freestanding_hooks []string, skip_builtin bool) &Gen {
30 prefs := &vpref.Preferences{
31 backend: .cleanc
32 target_os: target_os
33 freestanding: freestanding
34 prealloc: prealloc
35 skip_builtin: skip_builtin
36 user_defines: user_defines
37 explicit_user_defines: user_defines.clone()
38 freestanding_hooks: freestanding_hooks
39 no_parallel: true
40 }
41 env := types.Environment.new()
42 return Gen.new_with_env_and_pref([]ast.File{}, env, prefs)
43}
44
45fn freestanding_hook_defines(hooks []string) []string {
46 mut defines := ['freestanding', 'freestanding_hooks']
47 for hook in hooks {
48 defines << 'freestanding_${hook}'
49 defines << 'freestanding_hooks_${hook}'
50 }
51 return defines
52}
53
54fn c_directive_output_for_target(cond string, target_os string, user_defines []string) string {
55 return c_directive_output_for_target_with_explicit(cond, target_os, user_defines,
56 user_defines.clone(), false)
57}
58
59fn c_directive_output_for_target_with_explicit(cond string, target_os string, user_defines []string, explicit_user_defines []string, freestanding bool) string {
60 prefs := &vpref.Preferences{
61 backend: .cleanc
62 target_os: target_os
63 freestanding: freestanding
64 user_defines: user_defines
65 explicit_user_defines: explicit_user_defines
66 no_parallel: true
67 }
68 env := types.Environment.new()
69 mut g := Gen.new_with_env_and_pref([]ast.File{}, env, prefs)
70 mut seen := map[string]bool{}
71 g.emit_directive(ast.Directive{
72 name: 'include'
73 value: '<target_marker.h>'
74 ct_cond: cond
75 }, 'target_test.v', true, mut seen)
76 return g.sb.str()
77}
78
79fn c_directive_output_for_freestanding_target(cond string, target_os string) string {
80 return c_directive_output_for_target_with_explicit(cond, target_os, [
81 'freestanding',
82 ], [], true)
83}
84
85fn preamble_for_target(target_os string, user_defines []string) string {
86 mut g := new_target_test_gen(target_os, user_defines)
87 g.set_emit_modules(['main'])
88 g.write_preamble()
89 return g.sb.str()
90}
91
92fn preamble_for_freestanding_field(target_os string) string {
93 mut g := new_target_test_gen_with_freestanding(target_os, [], true)
94 g.set_emit_modules(['main'])
95 g.write_preamble()
96 return g.sb.str()
97}
98
99fn full_preamble_for_target(target_os string, user_defines []string) string {
100 mut g := new_target_test_gen(target_os, user_defines)
101 g.write_preamble()
102 return g.sb.str()
103}
104
105fn full_preamble_for_freestanding_field(target_os string) string {
106 mut g := new_target_test_gen_with_freestanding(target_os, [], true)
107 g.write_preamble()
108 return g.sb.str()
109}
110
111fn full_preamble_for_options(target_os string, user_defines []string, freestanding bool, prealloc bool) string {
112 mut g := new_target_test_gen_with_options(target_os, user_defines, freestanding, prealloc)
113 g.write_preamble()
114 return g.sb.str()
115}
116
117fn full_preamble_for_freestanding_hooks(hooks []string) string {
118 mut g := new_target_test_gen_with_options_and_hooks('linux', freestanding_hook_defines(hooks),
119 true, false, hooks)
120 g.write_preamble()
121 return g.sb.str()
122}
123
124fn runtime_fallbacks_for_called_functions(names []string) string {
125 mut g := new_target_test_gen('linux', [])
126 g.add_called_fn_names(names)
127 g.emit_missing_runtime_fallbacks()
128 return g.sb.str()
129}
130
131fn runtime_fallbacks_for_called_functions_for_target(target_os string, names []string) string {
132 mut g := new_target_test_gen(target_os, [])
133 g.add_called_fn_names(names)
134 g.emit_missing_runtime_fallbacks()
135 return g.sb.str()
136}
137
138fn runtime_fallbacks_for_called_functions_with_options(names []string, user_defines []string, freestanding bool) string {
139 mut g := new_target_test_gen_with_freestanding('linux', user_defines, freestanding)
140 g.add_called_fn_names(names)
141 g.emit_missing_runtime_fallbacks()
142 return g.sb.str()
143}
144
145fn runtime_fallbacks_for_called_functions_with_hooks(names []string, hooks []string) string {
146 mut g := new_target_test_gen_with_options_and_hooks('linux', freestanding_hook_defines(hooks),
147 true, false, hooks)
148 g.add_called_fn_names(names)
149 g.emit_missing_runtime_fallbacks()
150 return g.sb.str()
151}
152
153fn runtime_fallbacks_for_existing_c_source(csrc string) string {
154 mut g := new_target_test_gen('linux', [])
155 g.sb.write_string(csrc)
156 g.emit_missing_runtime_fallbacks()
157 return g.sb.str()
158}
159
160fn runtime_fallbacks_for_existing_c_source_with_options(csrc string, user_defines []string, freestanding bool) string {
161 mut g := new_target_test_gen_with_freestanding('linux', user_defines, freestanding)
162 g.sb.write_string(csrc)
163 g.emit_missing_runtime_fallbacks()
164 return g.sb.str()
165}
166
167fn runtime_fallbacks_for_existing_c_source_with_hooks(csrc string, hooks []string) string {
168 mut g := new_target_test_gen_with_options_and_hooks('linux', freestanding_hook_defines(hooks),
169 true, false, hooks)
170 g.sb.write_string(csrc)
171 g.emit_missing_runtime_fallbacks()
172 return g.sb.str()
173}
174
175fn runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin(csrc string, hooks []string) string {
176 mut g := new_target_test_gen_with_options_hooks_and_skip_builtin('linux',
177 freestanding_hook_defines(hooks), true, false, hooks, true)
178 g.sb.write_string(csrc)
179 g.emit_missing_runtime_fallbacks()
180 return g.sb.str()
181}
182
183fn soa_companion_for_options(user_defines []string, freestanding bool) string {
184 mut g := new_target_test_gen_with_freestanding('linux', user_defines, freestanding)
185 g.gen_soa_companion('Point', types.Struct{
186 name: 'Point'
187 fields: [
188 types.Field{
189 name: 'x'
190 typ: types.Type(types.Primitive{
191 props: .integer
192 size: 32
193 })
194 },
195 ]
196 })
197 return g.sb.str()
198}
199
200fn soa_companion_for_hooks(hooks []string) string {
201 mut g := new_target_test_gen_with_options_and_hooks('linux', freestanding_hook_defines(hooks),
202 true, false, hooks)
203 g.gen_soa_companion('Point', types.Struct{
204 name: 'Point'
205 fields: [
206 types.Field{
207 name: 'x'
208 typ: types.Type(types.Primitive{
209 props: .integer
210 size: 32
211 })
212 },
213 ]
214 })
215 return g.sb.str()
216}
217
218fn fixed_array_voidptr_str_write_for_options(user_defines []string, freestanding bool) string {
219 mut g := new_target_test_gen_with_freestanding('linux', user_defines, freestanding)
220 g.emit_fixed_array_str_write('items', 'voidptr', 2)
221 return g.sb.str()
222}
223
224fn fixed_array_voidptr_str_write_for_hooks(hooks []string) string {
225 mut g := new_target_test_gen_with_options_and_hooks('linux', freestanding_hook_defines(hooks),
226 true, false, hooks)
227 g.emit_fixed_array_str_write('items', 'voidptr', 2)
228 return g.sb.str()
229}
230
231fn unresolved_generic_stub_for_options(user_defines []string, freestanding bool) string {
232 mut g := new_target_test_gen_with_freestanding('linux', user_defines, freestanding)
233 g.emit_unresolved_generic_stub('missing_generic_T', 'int')
234 return g.sb.str()
235}
236
237fn unresolved_generic_stub_for_hooks(hooks []string) string {
238 mut g := new_target_test_gen_with_options_and_hooks('linux', freestanding_hook_defines(hooks),
239 true, false, hooks)
240 g.emit_unresolved_generic_stub('missing_generic_T', 'int')
241 return g.sb.str()
242}
243
244fn generated_c_for_target_program(name string, source string) string {
245 return generated_c_for_target_program_with_options(name, source, 'linux', false, false)
246}
247
248fn generated_c_for_target_program_with_options(name string, source string, target_os string, freestanding bool, skip_builtin bool) string {
249 return generated_c_for_target_program_with_defines(name, source, target_os, [], freestanding,
250 skip_builtin)
251}
252
253fn generated_c_for_target_program_with_defines(name string, source string, target_os string, user_defines []string, freestanding bool, skip_builtin bool) string {
254 return generated_c_for_target_program_with_defines_and_hooks(name, source, target_os,
255 user_defines, freestanding, skip_builtin, [])
256}
257
258fn generated_c_for_trace_calls_import_with_defines(name string, user_defines []string) string {
259 source := 'module main
260
261import v.trace_calls
262
263pub struct C.FILE {}
264
265pub struct C.timespec {
266pub mut:
267 tv_sec i64
268 tv_nsec i64
269}
270
271__global C.stderr &C.FILE
272
273pub const C.CLOCK_MONOTONIC int
274
275fn C.fprintf(fstream &C.FILE, const_format &char, opt ...voidptr) i32
276fn C.fflush(fstream &C.FILE) i32
277fn C.clock_gettime(i32, &C.timespec) i32
278fn C.pthread_self() usize
279
280fn main() {
281 trace_calls.on_call("probe")
282}
283'
284 trace_calls_file := os.join_path(os.getwd(), 'vlib', 'v', 'trace_calls', 'tracing_calls.c.v')
285 return generated_c_for_target_program_with_extra_files(name, source, 'linux', user_defines,
286 false, false, [trace_calls_file])
287}
288
289fn generated_c_for_target_program_with_extra_files(name string, source string, target_os string, user_defines []string, freestanding bool, skip_builtin bool, extra_files []string) string {
290 tmp_file := os.join_path(os.temp_dir(), 'v2_cleanc_target_codegen_${name}_${os.getpid()}.v')
291 os.write_file(tmp_file, source) or { panic(err) }
292 defer {
293 os.rm(tmp_file) or {}
294 }
295 prefs := &vpref.Preferences{
296 backend: .cleanc
297 target_os: target_os
298 freestanding: freestanding
299 skip_builtin: skip_builtin
300 user_defines: user_defines
301 explicit_user_defines: user_defines.clone()
302 no_parallel: true
303 }
304 mut paths := [tmp_file]
305 paths << extra_files
306 mut file_set := token.FileSet.new()
307 mut par := parser.Parser.new(prefs)
308 files := par.parse_files(paths, mut file_set)
309 env := types.Environment.new()
310 mut checker := types.Checker.new(prefs, file_set, env)
311 checker.check_files(files)
312 mut trans := transformer.Transformer.new_with_pref(env, prefs)
313 trans.set_file_set(file_set)
314 transformed_files := trans.transform_files(files)
315 mut gen := Gen.new_with_env_and_pref(transformed_files, env, prefs)
316 return gen.gen()
317}
318
319fn generated_c_for_target_program_with_hooks(name string, source string, hooks []string) string {
320 return generated_c_for_target_program_with_defines_and_hooks(name, source, 'linux',
321 freestanding_hook_defines(hooks), true, true, hooks)
322}
323
324fn generated_c_for_target_program_with_defines_and_hooks(name string, source string, target_os string, user_defines []string, freestanding bool, skip_builtin bool, freestanding_hooks []string) string {
325 tmp_file := os.join_path(os.temp_dir(), 'v2_cleanc_target_codegen_${name}_${os.getpid()}.v')
326 os.write_file(tmp_file, source) or { panic(err) }
327 defer {
328 os.rm(tmp_file) or {}
329 }
330 prefs := &vpref.Preferences{
331 backend: .cleanc
332 target_os: target_os
333 freestanding: freestanding
334 skip_builtin: skip_builtin
335 user_defines: user_defines
336 explicit_user_defines: user_defines.clone()
337 freestanding_hooks: freestanding_hooks
338 no_parallel: true
339 }
340 mut file_set := token.FileSet.new()
341 mut par := parser.Parser.new(prefs)
342 files := par.parse_files([tmp_file], mut file_set)
343 env := types.Environment.new()
344 mut checker := types.Checker.new(prefs, file_set, env)
345 checker.check_files(files)
346 mut trans := transformer.Transformer.new_with_pref(env, prefs)
347 trans.set_file_set(file_set)
348 transformed_files := trans.transform_files(files)
349 mut gen := Gen.new_with_env_and_pref(transformed_files, env, prefs)
350 return gen.gen()
351}
352
353fn assert_no_hosted_runtime_fallbacks(csrc string) {
354 for marker in [
355 '__attribute__((weak)) u64 __at_least_one',
356 '__attribute__((weak)) Array_string arguments()',
357 '__attribute__((weak)) void _write_buf_to_fd',
358 '__attribute__((weak)) void _writeln_to_fd',
359 '__attribute__((weak)) void eprint',
360 '__attribute__((weak)) void flush_stdout',
361 '__attribute__((weak)) void flush_stderr',
362 '__attribute__((weak)) bool Array_int_contains',
363 '__attribute__((weak)) bool Array_string_contains',
364 '__attribute__((weak)) int Array_string_index',
365 '__attribute__((weak)) string Array_int_str',
366 '__attribute__((weak)) void DenseArray__zeros_to_end',
367 '__attribute__((weak)) int bits__leading_zeros_64',
368 '__attribute__((weak)) int bits__trailing_zeros_32',
369 '__attribute__((weak)) int bits__trailing_zeros_64',
370 '__attribute__((weak)) u32 bits__rotate_left_32',
371 '__attribute__((weak)) u8* malloc_noscan',
372 '__attribute__((weak)) void* memdup',
373 '__attribute__((weak)) f64 f64_abs',
374 '__attribute__((weak)) string f64__str',
375 '__attribute__((weak)) string f64__strg',
376 '__attribute__((weak)) string f32__str',
377 '__attribute__((weak)) string f32__strg',
378 ] {
379 assert !csrc.contains(marker), marker
380 }
381}
382
383fn assert_no_hosted_output_runtime(csrc string) {
384 assert !csrc.contains('isize written = write(fd, ptr, remaining_bytes)')
385 assert !csrc.contains('fflush(')
386 assert !csrc.contains('fflush(stdout)')
387 assert !csrc.contains('fflush(stderr)')
388}
389
390fn assert_no_hosted_alloc_runtime(csrc string) {
391 assert !csrc.contains('return malloc(n);')
392 assert !csrc.contains(' = malloc(')
393 assert !csrc.contains('\tfree(')
394 assert !csrc.contains('realloc_data(')
395 assert !csrc.contains(' = realloc(')
396}
397
398fn assert_no_raw_heap_runtime(csrc string) {
399 for marker in ['*)malloc(', ' = malloc(', '\tmalloc(', 'return malloc(', 'calloc(', '*)realloc(',
400 ' = realloc(', '\trealloc(', '\tfree(', ' free('] {
401 assert !csrc.contains(marker), marker
402 }
403}
404
405fn assert_freestanding_alloc_refusal_without_hosted_heap(csrc string) {
406 assert csrc.contains('_Static_assert(0, "${freestanding_missing_alloc_hook_message}")')
407 assert_no_raw_heap_runtime(csrc)
408}
409
410fn assert_freestanding_format_refusal_without_hosted_formatting(csrc string) {
411 assert csrc.contains('_Static_assert(0, "${freestanding_missing_format_hook_message}")')
412 assert_no_hosted_formatting_or_abort_runtime(csrc)
413}
414
415fn assert_freestanding_heap_runtime_refusal(csrc string, helper string) {
416 assert csrc.contains('_Static_assert(0, "${freestanding_missing_heap_runtime_message}: ${helper}")')
417}
418
419fn assert_freestanding_output_refusal_without_hosted_io(csrc string) {
420 assert csrc.contains('_Static_assert(0, "${freestanding_missing_output_hook_message}")')
421 assert_no_hosted_output_runtime(csrc)
422}
423
424fn assert_freestanding_panic_refusal_without_hosted_exit(csrc string) {
425 assert csrc.contains('_Static_assert(0, "${freestanding_missing_panic_hook_message}")')
426 assert !csrc.contains('v_platform_panic')
427 assert !csrc.contains('exit(')
428}
429
430fn assert_no_hosted_formatting_or_abort_runtime(csrc string) {
431 for marker in ['snprintf(', 'sprintf(', 'printf(', 'fprintf(', 'fputs(', 'stderr', 'abort('] {
432 assert !csrc.contains(marker), marker
433 }
434}
435
436fn assert_no_os_runtime_headers(csrc string) {
437 for marker in [
438 '#include <stdio.h>',
439 '#include <stdlib.h>',
440 '#include <unistd.h>',
441 '#include <windows.h>',
442 '#include <dirent.h>',
443 '#include <pthread.h>',
444 '#include <mach/mach.h>',
445 '#include <termios.h>',
446 '#include <sys/wait.h>',
447 '#include <sys/ioctl.h>',
448 'extern char** environ;',
449 'pthread_rwlock_t',
450 'SRWLOCK',
451 ] {
452 assert !csrc.contains(marker), marker
453 }
454}
455
456fn assert_c_marker_before(csrc string, before string, after string) {
457 before_idx := csrc.index(before) or { panic('missing marker: ${before}') }
458 after_idx := csrc.index(after) or { panic('missing marker: ${after}') }
459 assert before_idx < after_idx
460}
461
462fn assert_no_gettid_compat(csrc string) {
463 assert !csrc.contains('_GNU_SOURCE')
464 assert !csrc.contains('SYS_gettid')
465 assert !csrc.contains('v_cleanc_gettid')
466 assert !csrc.contains('#define gettid')
467}
468
469fn test_eval_comptime_flag_uses_target_os_preference() {
470 windows_gen := new_target_test_gen('windows', [])
471 assert windows_gen.eval_comptime_flag('windows')
472 assert !windows_gen.eval_comptime_flag('linux')
473 assert !windows_gen.eval_comptime_flag('macos')
474
475 linux_gen := new_target_test_gen('linux', [])
476 assert linux_gen.eval_comptime_flag('linux')
477 assert !linux_gen.eval_comptime_flag('windows')
478
479 macos_gen := new_target_test_gen('darwin', [])
480 assert macos_gen.eval_comptime_flag('macos')
481 assert macos_gen.eval_comptime_flag('darwin')
482 assert macos_gen.eval_comptime_flag('mac')
483 assert macos_gen.eval_comptime_flag('bsd')
484
485 freebsd_gen := new_target_test_gen('freebsd', [])
486 assert freebsd_gen.eval_comptime_flag('bsd')
487}
488
489fn test_eval_comptime_flag_uses_target_os_preference_for_extended_targets() {
490 for target in [
491 'freebsd',
492 'openbsd',
493 'netbsd',
494 'dragonfly',
495 'android',
496 'termux',
497 'ios',
498 'solaris',
499 'qnx',
500 'serenity',
501 'plan9',
502 'vinix',
503 ] {
504 gen := new_target_test_gen(target, [])
505 assert gen.eval_comptime_flag(target)
506 assert !gen.eval_comptime_flag('linux')
507 }
508
509 cross_gen := new_target_test_gen('cross', [])
510 assert cross_gen.eval_comptime_flag('cross')
511 assert !cross_gen.eval_comptime_flag('linux')
512
513 freestanding_gen := new_target_test_gen_with_freestanding('linux', [], true)
514 assert freestanding_gen.eval_comptime_flag('freestanding')
515}
516
517fn test_freestanding_none_comptime_selects_no_concrete_os() {
518 gen := new_target_test_gen_with_freestanding('none', [], true)
519 assert gen.eval_comptime_flag('freestanding')
520 assert gen.eval_comptime_flag('none')
521 assert !gen.eval_comptime_flag('linux')
522 assert !gen.eval_comptime_flag('windows')
523 assert !gen.eval_comptime_flag('macos')
524 assert !gen.eval_comptime_flag('darwin')
525 assert !gen.eval_comptime_flag('cross')
526
527 hosted_with_define := new_target_test_gen('linux', ['none'])
528 assert !hosted_with_define.eval_comptime_flag('none')
529}
530
531fn test_c_directives_use_target_os_preference() {
532 assert c_directive_output_for_target('windows', 'windows', []).contains('#include <target_marker.h>')
533 assert !c_directive_output_for_target('linux', 'windows', []).contains('#include <target_marker.h>')
534 assert c_directive_output_for_target('linux', 'linux', []).contains('#include <target_marker.h>')
535 assert !c_directive_output_for_target('windows', 'linux', []).contains('#include <target_marker.h>')
536 assert c_directive_output_for_target('mac', 'macos', []).contains('#include <target_marker.h>')
537 assert c_directive_output_for_target('darwin', 'macos', []).contains('#include <target_marker.h>')
538 assert c_directive_output_for_target('bsd', 'freebsd', []).contains('#include <target_marker.h>')
539}
540
541fn test_c_directives_use_extended_target_os_preference() {
542 for target in [
543 'freebsd',
544 'openbsd',
545 'netbsd',
546 'dragonfly',
547 'android',
548 'termux',
549 'ios',
550 'solaris',
551 'qnx',
552 'serenity',
553 'plan9',
554 'vinix',
555 ] {
556 assert c_directive_output_for_target(target, target, []).contains('#include <target_marker.h>')
557 assert !c_directive_output_for_target('linux', target, []).contains('#include <target_marker.h>')
558 }
559}
560
561struct CrossGuardCase {
562 cond string
563 guard string
564}
565
566fn test_c_directives_keep_cross_portable() {
567 assert c_directive_output_for_target('', 'cross', []).contains('#include <target_marker.h>')
568 cross_src := c_directive_output_for_target('cross', 'cross', [])
569 assert cross_src.contains('#include <target_marker.h>')
570 assert !cross_src.contains('#if')
571
572 linux_src := c_directive_output_for_target('linux', 'cross', [])
573 assert linux_src.contains('#if defined(__linux__)')
574 assert linux_src.contains('#include <target_marker.h>')
575 assert linux_src.contains('#endif')
576
577 windows_src := c_directive_output_for_target('windows', 'cross', [])
578 assert windows_src.contains('#if defined(_WIN32)')
579 assert windows_src.contains('#include <target_marker.h>')
580 assert windows_src.contains('#endif')
581
582 macos_src := c_directive_output_for_target('macos', 'cross', [])
583 assert macos_src.contains('#if ${apple_macos_cross_guard}')
584 assert macos_src.contains('#include <target_marker.h>')
585 assert macos_src.contains('#endif')
586}
587
588fn test_c_directives_keep_cross_extended_targets_portable() {
589 for case in [
590 CrossGuardCase{'mac', apple_macos_cross_guard},
591 CrossGuardCase{'darwin', apple_macos_cross_guard},
592 CrossGuardCase{'freebsd', 'defined(__FreeBSD__)'},
593 CrossGuardCase{'openbsd', 'defined(__OpenBSD__)'},
594 CrossGuardCase{'netbsd', 'defined(__NetBSD__)'},
595 CrossGuardCase{'dragonfly', 'defined(__DragonFly__)'},
596 CrossGuardCase{'android', 'defined(__ANDROID__)'},
597 CrossGuardCase{'termux', 'defined(__TERMUX__)'},
598 CrossGuardCase{'ios', apple_ios_cross_guard},
599 CrossGuardCase{'solaris', 'defined(__sun)'},
600 CrossGuardCase{'qnx', 'defined(__QNX__)'},
601 CrossGuardCase{'serenity', 'defined(__serenity__)'},
602 CrossGuardCase{'plan9', 'defined(__plan9__)'},
603 CrossGuardCase{'vinix', 'defined(__vinix__)'},
604 ] {
605 src := c_directive_output_for_target(case.cond, 'cross', [])
606 assert src.contains('#if ${case.guard}')
607 assert src.contains('#include <target_marker.h>')
608 assert src.contains('#endif')
609 }
610
611 bsd_src := c_directive_output_for_target('bsd', 'cross', [])
612 assert bsd_src.contains(apple_macos_cross_guard)
613 assert bsd_src.contains('defined(__FreeBSD__)')
614 assert bsd_src.contains('defined(__OpenBSD__)')
615 assert bsd_src.contains('defined(__NetBSD__)')
616 assert bsd_src.contains('defined(__DragonFly__)')
617 assert !bsd_src.contains('defined(__APPLE__) ||')
618 assert bsd_src.contains('#include <target_marker.h>')
619}
620
621fn test_c_directives_keep_cross_complex_os_conditions_portable() {
622 not_linux_src := c_directive_output_for_target('!linux', 'cross', [])
623 assert not_linux_src.contains('#if !(defined(__linux__))')
624 assert not_linux_src.contains('#include <target_marker.h>')
625
626 and_src := c_directive_output_for_target('linux && !windows', 'cross', [])
627 assert and_src.contains('#if (defined(__linux__)) && (!(defined(_WIN32)))')
628 assert and_src.contains('#include <target_marker.h>')
629
630 cross_and_src := c_directive_output_for_target('cross && linux', 'cross', [])
631 assert cross_and_src.contains('#if defined(__linux__)')
632 assert cross_and_src.contains('#include <target_marker.h>')
633 assert !cross_and_src.contains('cross')
634
635 feature_and_src := c_directive_output_for_target('feature && linux', 'cross', [
636 'feature',
637 ])
638 assert feature_and_src.contains('#if defined(__linux__)')
639 assert feature_and_src.contains('#include <target_marker.h>')
640 assert !feature_and_src.contains('feature')
641
642 not_feature_and_src := c_directive_output_for_target('!feature && linux', 'cross', [
643 'feature',
644 ])
645 assert !not_feature_and_src.contains('#include <target_marker.h>')
646
647 missing_feature_and_src := c_directive_output_for_target('feature && linux', 'cross', [])
648 assert !missing_feature_and_src.contains('#include <target_marker.h>')
649
650 or_src := c_directive_output_for_target('linux || windows', 'cross', [])
651 assert or_src.contains('#if (defined(__linux__)) || (defined(_WIN32))')
652 assert or_src.contains('#include <target_marker.h>')
653
654 not_and_src := c_directive_output_for_target('!windows && linux', 'cross', [])
655 assert not_and_src.contains('#if (!(defined(_WIN32))) && (defined(__linux__))')
656 assert not_and_src.contains('#include <target_marker.h>')
657
658 or_and_src := c_directive_output_for_target('linux || windows && macos', 'cross', [])
659 assert or_and_src.contains('#if (defined(__linux__)) || ((defined(_WIN32)) && (${apple_macos_cross_guard}))')
660 assert or_and_src.contains('#include <target_marker.h>')
661
662 not_group_src := c_directive_output_for_target('!(linux || windows) && macos', 'cross', [])
663 assert not_group_src.contains('#if (!((defined(__linux__)) || (defined(_WIN32)))) && (${apple_macos_cross_guard})')
664 assert not_group_src.contains('#include <target_marker.h>')
665
666 assert c_directive_output_for_target('linux || windows && macos', 'linux', []).contains('#include <target_marker.h>')
667 assert !c_directive_output_for_target('linux || windows && macos', 'macos', []).contains('#include <target_marker.h>')
668 assert c_directive_output_for_target('!(linux || windows) && macos', 'macos', []).contains('#include <target_marker.h>')
669 assert !c_directive_output_for_target('!(linux || windows) && macos', 'windows', []).contains('#include <target_marker.h>')
670}
671
672fn test_c_directives_keep_termux_distinct_from_android() {
673 assert c_directive_output_for_target('termux', 'termux', []).contains('#include <target_marker.h>')
674 assert c_directive_output_for_target('linux || termux', 'termux', []).contains('#include <target_marker.h>')
675 assert !c_directive_output_for_target('android', 'termux', []).contains('#include <target_marker.h>')
676 assert !c_directive_output_for_target('android && !termux', 'termux', []).contains('#include <target_marker.h>')
677 assert c_directive_output_for_target('android && !termux', 'android', []).contains('#include <target_marker.h>')
678}
679
680fn test_cross_optional_termux_flag_named_like_os_is_not_os_guarded() {
681 assert !c_directive_output_for_target('termux ?', 'cross', []).contains('#include <target_marker.h>')
682 optional_directive_src := c_directive_output_for_target('termux ?', 'cross', [
683 'termux',
684 ])
685 assert optional_directive_src.contains('#include <target_marker.h>')
686 assert !optional_directive_src.contains('defined(__TERMUX__)')
687}
688
689fn test_optional_target_mode_directives_ignore_synthesized_defines() {
690 assert !c_directive_output_for_target_with_explicit('cross ?', 'cross', ['cross'], [], false).contains('#include <target_marker.h>')
691 assert c_directive_output_for_target_with_explicit('cross ?', 'cross', ['cross'], [
692 'cross',
693 ], false).contains('#include <target_marker.h>')
694
695 assert !c_directive_output_for_freestanding_target('freestanding ?', 'linux').contains('#include <target_marker.h>')
696 assert c_directive_output_for_target_with_explicit('freestanding ?', 'linux', [
697 'freestanding',
698 ], ['freestanding'], true).contains('#include <target_marker.h>')
699
700 assert !c_directive_output_for_target_with_explicit('none ?', 'none', [
701 'freestanding',
702 ], [], true).contains('#include <target_marker.h>')
703 assert c_directive_output_for_target_with_explicit('none ?', 'none', [
704 'freestanding',
705 ], ['none'], true).contains('#include <target_marker.h>')
706}
707
708fn test_comptime_if_directives_support_infix_os_conditions() {
709 source := 'module main
710
711\$if linux || windows {
712 #include <active_or_marker.h>
713}
714
715 \$if linux && !windows {
716 #include <active_and_marker.h>
717 }
718
719 \$if macos {
720 #include <active_macos_marker.h>
721 }
722
723 \$if cross && linux {
724 #include <cross_linux_marker.h>
725 }
726
727fn main() {}
728'
729 linux_src := generated_c_for_target_program_with_options('comptime_if_infix_linux', source,
730 'linux', false, false)
731 assert linux_src.contains('#include <active_or_marker.h>')
732 assert linux_src.contains('#include <active_and_marker.h>')
733 assert !linux_src.contains('#include <active_macos_marker.h>')
734 assert !linux_src.contains('#include <cross_linux_marker.h>')
735
736 macos_src := generated_c_for_target_program_with_options('comptime_if_infix_macos', source,
737 'macos', false, false)
738 assert !macos_src.contains('#include <active_or_marker.h>')
739 assert !macos_src.contains('#include <active_and_marker.h>')
740 assert macos_src.contains('#include <active_macos_marker.h>')
741 assert !macos_src.contains('#include <cross_linux_marker.h>')
742
743 windows_src := generated_c_for_target_program_with_options('comptime_if_infix_windows', source,
744 'windows', false, false)
745 assert windows_src.contains('#include <active_or_marker.h>')
746 assert !windows_src.contains('#include <active_and_marker.h>')
747 assert !windows_src.contains('#include <active_macos_marker.h>')
748 assert !windows_src.contains('#include <cross_linux_marker.h>')
749
750 cross_src := generated_c_for_target_program_with_options('comptime_if_infix_cross', source,
751 'cross', false, false)
752 assert cross_src.contains('defined(__linux__)')
753 assert cross_src.contains('defined(_WIN32)')
754 assert cross_src.contains('#include <active_or_marker.h>')
755 assert cross_src.contains('!(defined(_WIN32))')
756 assert cross_src.contains('#include <active_and_marker.h>')
757 assert cross_src.contains(apple_macos_cross_guard)
758 assert cross_src.contains('#include <active_macos_marker.h>')
759 assert cross_src.contains('#if defined(__linux__)\n#include <cross_linux_marker.h>\n#endif')
760}
761
762fn test_comptime_if_pkgconfig_directives_select_available_branch() {
763 if !vpref.comptime_pkgconfig_value('sqlite3') {
764 return
765 }
766 source := 'module main
767
768\$if \$pkgconfig("sqlite3") {
769 #include "sqlite3.h"
770} \$else \$if darwin {
771 #include <sqlite_darwin_fallback_marker.h>
772}
773
774fn main() {}
775'
776 macos_src := generated_c_for_target_program_with_options('pkgconfig_sqlite3_directive_macos',
777 source, 'macos', false, false)
778 assert macos_src.contains('#include "sqlite3.h"')
779 assert !macos_src.contains('#include <sqlite_darwin_fallback_marker.h>')
780}
781
782fn test_cross_comptime_if_else_directives_do_not_emit_selected_else_unguarded() {
783 source := 'module main
784
785\$if linux {
786 #include <linux_marker.h>
787} \$else {
788 #include <fallback_marker.h>
789}
790
791fn main() {}
792'
793 cross_src := generated_c_for_target_program_with_options('comptime_if_else_cross', source,
794 'cross', false, false)
795 assert cross_src.contains('#if defined(__linux__)\n#include <linux_marker.h>\n#endif')
796 assert cross_src.contains('#if !(defined(__linux__))\n#include <fallback_marker.h>\n#endif')
797 assert cross_src.count('#include <fallback_marker.h>') == 1
798 assert cross_src.count('#include <linux_marker.h>') == 1
799}
800
801fn test_cross_comptime_directive_scope_preserves_nested_preprocessor_blocks() {
802 source := 'module main
803
804\$if linux {
805 #ifdef HAVE_FOO
806 #include <foo.h>
807 #endif
808}
809
810fn main() {}
811'
812 cross_src := generated_c_for_target_program_with_options('nested_preprocessor_cross', source,
813 'cross', false, false)
814 assert cross_src.contains('#if defined(__linux__)\n#ifdef HAVE_FOO\n#include <foo.h>\n#endif\n#endif')
815 assert !cross_src.contains('#if defined(__linux__)\n#ifdef HAVE_FOO\n#endif\n#if defined(__linux__)')
816 assert cross_src.count('#include <foo.h>') == 1
817}
818
819fn test_cross_user_define_comptime_directives_reduce_to_os_guards() {
820 source := 'module main
821
822\$if feature && linux {
823 #include <feature_linux.h>
824}
825
826\$if !feature && linux {
827 #include <disabled_feature_linux.h>
828}
829
830\$if missing_feature && linux {
831 #include <missing_feature_linux.h>
832}
833
834fn main() {}
835'
836 feature_src := generated_c_for_target_program_with_defines('feature_directive_cross', source,
837 'cross', ['feature'], false, false)
838 assert feature_src.contains('#if defined(__linux__)\n#include <feature_linux.h>\n#endif')
839 assert feature_src.count('#include <feature_linux.h>') == 1
840 assert !feature_src.contains('#include <disabled_feature_linux.h>')
841 assert !feature_src.contains('#include <missing_feature_linux.h>')
842 assert !feature_src.contains('feature && linux')
843}
844
845fn test_cross_optional_user_flag_named_like_os_is_not_os_guarded() {
846 assert !c_directive_output_for_target('linux ?', 'cross', []).contains('#include <target_marker.h>')
847 optional_directive_src := c_directive_output_for_target('linux ?', 'cross', [
848 'linux',
849 ])
850 assert optional_directive_src.contains('#include <target_marker.h>')
851 assert !optional_directive_src.contains('defined(__linux__)')
852
853 source := 'module main
854
855\$if linux ? {
856 #include <optional_linux_marker.h>
857}
858
859fn main() {}
860'
861 missing_src := generated_c_for_target_program_with_options('optional_linux_missing_cross',
862 source, 'cross', false, false)
863 assert !missing_src.contains('#include <optional_linux_marker.h>')
864
865 defined_src := generated_c_for_target_program_with_defines('optional_linux_defined_cross',
866 source, 'cross', ['linux'], false, false)
867 assert defined_src.contains('#include <optional_linux_marker.h>')
868 assert !defined_src.contains('#if defined(__linux__)\n#include <optional_linux_marker.h>')
869}
870
871fn test_optional_user_flag_named_like_os_is_not_target_os_flag() {
872 source := 'module main
873
874\$if linux {
875 #include <linux_marker.h>
876}
877
878\$if linux ? {
879 #include <optional_linux_marker.h>
880}
881
882\$if linux ? || windows {
883 #include <optional_linux_or_windows_marker.h>
884}
885
886fn main() {}
887'
888 linux_src := generated_c_for_target_program_with_options('optional_linux_plain_linux', source,
889 'linux', false, false)
890 assert linux_src.contains('#include <linux_marker.h>')
891 assert !linux_src.contains('#include <optional_linux_marker.h>')
892 assert !linux_src.contains('#include <optional_linux_or_windows_marker.h>')
893
894 linux_defined_src := generated_c_for_target_program_with_defines('optional_linux_defined_linux',
895 source, 'linux', ['linux'], false, false)
896 assert linux_defined_src.contains('#include <linux_marker.h>')
897 assert linux_defined_src.contains('#include <optional_linux_marker.h>')
898 assert linux_defined_src.contains('#include <optional_linux_or_windows_marker.h>')
899
900 windows_src := generated_c_for_target_program_with_options('optional_linux_windows', source,
901 'windows', false, false)
902 assert !windows_src.contains('#include <linux_marker.h>')
903 assert !windows_src.contains('#include <optional_linux_marker.h>')
904 assert windows_src.contains('#include <optional_linux_or_windows_marker.h>')
905}
906
907fn test_c_directives_keep_freestanding_user_os_directives_for_concrete_target() {
908 assert c_directive_output_for_target('', 'linux', ['freestanding']).contains('#include <target_marker.h>')
909 assert c_directive_output_for_target('freestanding', 'linux', ['freestanding']).contains('#include <target_marker.h>')
910 assert c_directive_output_for_target('linux', 'linux', ['freestanding']).contains('#include <target_marker.h>')
911 assert !c_directive_output_for_target('windows', 'linux', ['freestanding']).contains('#include <target_marker.h>')
912 assert c_directive_output_for_target('windows', 'windows', ['freestanding']).contains('#include <target_marker.h>')
913 assert c_directive_output_for_freestanding_target('freestanding', 'linux').contains('#include <target_marker.h>')
914}
915
916fn test_freestanding_none_c_directives_select_only_freestanding() {
917 assert c_directive_output_for_freestanding_target('freestanding', 'none').contains('#include <target_marker.h>')
918 for cond in ['linux', 'windows', 'macos', 'darwin', 'cross'] {
919 src := c_directive_output_for_freestanding_target(cond, 'none')
920 assert !src.contains('#include <target_marker.h>'), cond
921 }
922}
923
924fn test_freestanding_none_comptime_if_directives_select_only_freestanding() {
925 source := 'module main
926
927\$if freestanding {
928 #include <freestanding_none_marker.h>
929}
930
931\$if linux {
932 #include <linux_none_marker.h>
933}
934
935\$if windows {
936 #include <windows_none_marker.h>
937}
938
939\$if macos {
940 #include <macos_none_marker.h>
941}
942
943\$if cross {
944 #include <cross_none_marker.h>
945}
946
947fn main() {}
948'
949 src := generated_c_for_target_program_with_options('freestanding_none_comptime_if', source,
950 'none', true, true)
951 assert src.contains('#include <freestanding_none_marker.h>')
952 assert !src.contains('#include <linux_none_marker.h>')
953 assert !src.contains('#include <windows_none_marker.h>')
954 assert !src.contains('#include <macos_none_marker.h>')
955 assert !src.contains('#include <cross_none_marker.h>')
956 assert_no_os_runtime_headers(src)
957}
958
959fn test_preamble_specializes_apple_includes_by_target() {
960 linux_src := preamble_for_target('linux', [])
961 assert !linux_src.contains('__APPLE__')
962 assert !linux_src.contains('#include <mach/mach.h>')
963
964 windows_src := preamble_for_target('windows', [])
965 assert !windows_src.contains('__APPLE__')
966 assert !windows_src.contains('#include <mach/mach.h>')
967
968 macos_src := preamble_for_target('macos', [])
969 assert macos_src.contains('#include <mach/mach.h>')
970 assert !macos_src.contains('#ifdef __APPLE__')
971
972 cross_src := preamble_for_target('cross', [])
973 assert cross_src.contains('#if ${apple_macos_cross_guard}')
974 assert !cross_src.contains('#ifdef __APPLE__')
975 assert cross_src.contains('#include <mach/mach.h>')
976 assert cross_src.contains('#if defined(_WIN32)\n#include <windows.h>\n#else\n#include <dirent.h>\n#include <pthread.h>\n#endif')
977 assert cross_src.contains('#if defined(_WIN32)\ntypedef struct sync__RwMutex { SRWLOCK mutex; u32 inited; } sync__RwMutex;')
978 assert cross_src.contains('#else\ntypedef struct sync__RwMutex { pthread_rwlock_t mutex; u32 inited; } sync__RwMutex;')
979 full_cross_src := full_preamble_for_target('cross', [])
980 assert full_cross_src.contains('#if defined(_WIN32)\n#include <windows.h>\n#else\n#include <unistd.h>')
981 assert full_cross_src.contains('#include <pthread.h>\n#include <sys/time.h>')
982 assert full_cross_src.contains('extern char** environ;\n#endif')
983}
984
985fn test_linux_preamble_declares_v_owned_gettid_helper_before_c_calls() {
986 src := full_preamble_for_target('linux', [])
987 assert src.contains(linux_gettid_feature_define)
988 assert src.contains('#include <unistd.h>')
989 assert src.contains(linux_gettid_helper)
990 assert !src.contains('#define gettid')
991 assert_c_marker_before(src, '#define _GNU_SOURCE', '#include <stdio.h>')
992 assert_c_marker_before(src, '#include <unistd.h>', '#include <sys/syscall.h>')
993 assert_c_marker_before(src, '#include <sys/syscall.h>',
994 'static inline uint32_t v_cleanc_gettid(void)')
995}
996
997fn test_linux_minimal_preamble_declares_v_owned_gettid_helper() {
998 src := preamble_for_target('linux', [])
999 assert src.contains(linux_gettid_feature_define)
1000 assert src.contains('#include <unistd.h>')
1001 assert src.contains(linux_gettid_helper)
1002 assert !src.contains('#define gettid')
1003 assert_c_marker_before(src, '#define _GNU_SOURCE', '#include <stdio.h>')
1004 assert_c_marker_before(src, '#include <unistd.h>', '#include <sys/syscall.h>')
1005 assert_c_marker_before(src, '#include <sys/syscall.h>',
1006 'static inline uint32_t v_cleanc_gettid(void)')
1007}
1008
1009fn test_gettid_linux_compat_does_not_leak_to_non_linux_targets() {
1010 for target in ['windows', 'macos', 'cross', 'freebsd', 'android', 'termux'] {
1011 minimal_src := preamble_for_target(target, [])
1012 full_src := full_preamble_for_target(target, [])
1013 assert_no_gettid_compat(minimal_src)
1014 assert_no_gettid_compat(full_src)
1015 }
1016
1017 freestanding_src := full_preamble_for_options('linux', [], true, false)
1018 assert_no_gettid_compat(freestanding_src)
1019 assert_no_os_runtime_headers(freestanding_src)
1020}
1021
1022fn test_gettid_linux_compat_respects_no_gettid_and_musl_defines() {
1023 for define in ['no_gettid', 'musl'] {
1024 assert_no_gettid_compat(preamble_for_target('linux', [define]))
1025 assert_no_gettid_compat(full_preamble_for_target('linux', [define]))
1026 }
1027}
1028
1029fn test_generated_linux_gettid_call_has_compat_preamble() {
1030 source := 'module main
1031
1032fn C.gettid() u32
1033
1034fn thread_id() u32 {
1035 return C.gettid()
1036}
1037
1038fn main() {
1039 _ = thread_id()
1040}
1041'
1042 src := generated_c_for_target_program_with_options('linux_gettid_call', source, 'linux', false,
1043 false)
1044 assert src.contains(linux_gettid_feature_define)
1045 assert src.contains(linux_gettid_helper)
1046 assert !src.contains('#define gettid')
1047 assert src.contains('return v_cleanc_gettid();')
1048 assert !src.contains('return gettid();')
1049 assert_c_marker_before(src, '#define _GNU_SOURCE', '#include <stdio.h>')
1050 assert_c_marker_before(src, 'static inline uint32_t v_cleanc_gettid(void)',
1051 'return v_cleanc_gettid();')
1052}
1053
1054fn test_generated_musl_optional_gettid_branch_is_not_selected() {
1055 source := 'module main
1056
1057fn C.gettid() u32
1058
1059fn thread_id() u32 {
1060 $if linux && !musl ? {
1061 return C.gettid()
1062 } $else {
1063 return 0
1064 }
1065}
1066
1067fn main() {
1068 _ = thread_id()
1069}
1070'
1071 src := generated_c_for_target_program_with_defines('linux_musl_gettid_branch', source, 'linux', [
1072 'musl',
1073 ], false, false)
1074 assert_no_gettid_compat(src)
1075 assert !src.contains('return gettid();')
1076 assert src.contains('return 0;')
1077}
1078
1079fn test_generated_no_gettid_optional_branch_does_not_emit_compat() {
1080 source := 'module main
1081
1082fn C.gettid() u32
1083
1084fn thread_id() u32 {
1085 $if no_gettid ? {
1086 return 0
1087 } $else $if linux && !musl ? {
1088 return C.gettid()
1089 } $else {
1090 return 1
1091 }
1092}
1093
1094fn main() {
1095 _ = thread_id()
1096}
1097'
1098 src := generated_c_for_target_program_with_defines('linux_no_gettid_branch', source, 'linux', [
1099 'no_gettid',
1100 ], false, false)
1101 assert_no_gettid_compat(src)
1102 assert !src.contains('return gettid();')
1103 assert src.contains('return 0;')
1104}
1105
1106fn test_trace_calls_import_respects_no_gettid_and_musl() {
1107 for define in ['no_gettid', 'musl'] {
1108 src := generated_c_for_trace_calls_import_with_defines('trace_calls_${define}_import', [
1109 define,
1110 ])
1111 assert_no_gettid_compat(src)
1112 assert !src.contains('gettid(')
1113 assert src.contains('pthread_self()')
1114 }
1115}
1116
1117fn test_label_followed_by_result_or_temp_emits_null_statement() {
1118 src := generated_c_for_target_program('label_before_result_or_temp', '
1119fn maybe_label_value() !string {
1120 return "ok"
1121}
1122
1123fn main() {
1124 start_no_time:
1125 value := maybe_label_value() or { return }
1126 _ = value
1127}
1128')
1129 assert src.contains('start_no_time:;')
1130 assert src.contains('_result_string _or_t')
1131 assert !src.contains('start_no_time:\n\t_result_string _or_t')
1132}
1133
1134fn test_generated_sort_comparator_casts_named_fn_to_fnsortcb() {
1135 src := generated_c_for_target_program('sort_generated_comparator_fnsortcb_cast', '
1136struct RepIndex {
1137 idx int
1138}
1139
1140fn main() {
1141 mut idxs := [RepIndex{idx: 2}, RepIndex{idx: 1}]
1142 idxs.sort(a.idx < b.idx)
1143}
1144')
1145 assert src.contains('int __sort_cmp_RepIndex_by_idx_asc(RepIndex* a, RepIndex* b);')
1146 assert src.contains('array__sort_with_compare(idxs, (FnSortCB)__sort_cmp_RepIndex_by_idx_asc);')
1147 assert !src.contains('array__sort_with_compare(idxs, __sort_cmp_RepIndex_by_idx_asc);')
1148}
1149
1150fn test_sort_with_compare_named_comparator_casts_to_fnsortcb() {
1151 src := generated_c_for_target_program('sort_named_comparator_fnsortcb_cast', '
1152type FnSortCB = fn (voidptr, voidptr) int
1153
1154struct Item {
1155 value int
1156}
1157
1158fn array__sort_with_compare(mut items []Item, callback FnSortCB)
1159
1160fn compare_items(a &Item, b &Item) int {
1161 return a.value - b.value
1162}
1163
1164fn main() {
1165 mut items := [Item{value: 2}, Item{value: 1}]
1166 array__sort_with_compare(mut items, compare_items)
1167}
1168')
1169 assert src.contains('array__sort_with_compare(&items, (FnSortCB)compare_items);')
1170 assert !src.contains('array__sort_with_compare(&items, compare_items);')
1171}
1172
1173fn test_sort_with_compare_same_name_non_fnsortcb_callback_is_not_cast_to_fnsortcb() {
1174 src := generated_c_for_target_program('sort_same_name_non_fnsortcb_callback_no_cast', '
1175struct Item {
1176 value int
1177}
1178
1179type ItemCallback = fn (&Item, &Item) int
1180
1181fn array__sort_with_compare(mut items []Item, callback ItemCallback)
1182
1183fn compare_items(a &Item, b &Item) int {
1184 return a.value - b.value
1185}
1186
1187fn main() {
1188 mut items := [Item{value: 2}, Item{value: 1}]
1189 array__sort_with_compare(mut items, compare_items)
1190}
1191')
1192 assert src.contains('array__sort_with_compare(&items, compare_items);')
1193 assert !src.contains('(FnSortCB)compare_items')
1194}
1195
1196fn test_sort_with_compare_module_qualified_comparator_casts_to_fnsortcb() {
1197 other_file := os.join_path(os.temp_dir(),
1198 'v2_cleanc_target_codegen_sort_other_${os.getpid()}.v')
1199 os.write_file(other_file, '
1200module other
1201
1202pub struct Item {
1203pub:
1204 value int
1205}
1206
1207pub fn compare_items(a &Item, b &Item) int {
1208 return a.value - b.value
1209}
1210 ') or {
1211 panic(err)
1212 }
1213 defer {
1214 os.rm(other_file) or {}
1215 }
1216 src := generated_c_for_target_program_with_extra_files('sort_module_qualified_comparator_fnsortcb_cast', '
1217module main
1218
1219import other
1220
1221type FnSortCB = fn (voidptr, voidptr) int
1222
1223fn array__sort_with_compare(mut items []other.Item, callback FnSortCB)
1224
1225fn main() {
1226 mut items := [other.Item{value: 2}, other.Item{value: 1}]
1227 array__sort_with_compare(mut items, other.compare_items)
1228}
1229 ',
1230 'linux', [], false, false, [other_file])
1231 assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__compare_items);')
1232 assert !src.contains('array__sort_with_compare(&items, other__compare_items);')
1233}
1234
1235fn test_sort_with_compare_import_alias_comparator_casts_to_real_module_fnsortcb() {
1236 other_file := os.join_path(os.temp_dir(),
1237 'v2_cleanc_target_codegen_sort_other_alias_${os.getpid()}.v')
1238 os.write_file(other_file, '
1239module other
1240
1241pub struct Item {
1242pub:
1243 value int
1244}
1245
1246pub fn compare_items(a &Item, b &Item) int {
1247 return a.value - b.value
1248}
1249 ') or {
1250 panic(err)
1251 }
1252 defer {
1253 os.rm(other_file) or {}
1254 }
1255 src := generated_c_for_target_program_with_extra_files('sort_import_alias_comparator_fnsortcb_cast', '
1256module main
1257
1258import other as oth
1259
1260type FnSortCB = fn (voidptr, voidptr) int
1261
1262fn array__sort_with_compare(mut items []oth.Item, callback FnSortCB)
1263
1264fn main() {
1265 mut items := [oth.Item{value: 2}, oth.Item{value: 1}]
1266 array__sort_with_compare(mut items, oth.compare_items)
1267}
1268 ',
1269 'linux', [], false, false, [other_file])
1270 assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__compare_items);')
1271 assert !src.contains('array__sort_with_compare(&items, other__compare_items);')
1272 assert !src.contains('(FnSortCB)oth__compare_items')
1273}
1274
1275fn test_sort_with_compare_module_global_callback_casts_to_fnsortcb() {
1276 other_file := os.join_path(os.temp_dir(),
1277 'v2_cleanc_target_codegen_sort_other_global_callback_${os.getpid()}.v')
1278 os.write_file(other_file, '
1279module other
1280
1281pub struct Item {
1282pub:
1283 value int
1284}
1285
1286pub type ItemCallback = fn (&Item, &Item) int
1287
1288pub __global callback ItemCallback
1289 ') or {
1290 panic(err)
1291 }
1292 defer {
1293 os.rm(other_file) or {}
1294 }
1295 src := generated_c_for_target_program_with_extra_files('sort_module_global_callback_fnsortcb_cast', '
1296module main
1297
1298import other
1299
1300type FnSortCB = fn (voidptr, voidptr) int
1301
1302fn array__sort_with_compare(mut items []other.Item, callback FnSortCB)
1303
1304fn main() {
1305 mut items := [other.Item{value: 2}, other.Item{value: 1}]
1306 array__sort_with_compare(mut items, other.callback)
1307}
1308 ',
1309 'linux', [], false, false, [other_file])
1310 assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__callback);')
1311 assert !src.contains('array__sort_with_compare(&items, other__callback);')
1312}
1313
1314fn test_sort_with_compare_import_alias_global_callback_casts_to_real_module_fnsortcb() {
1315 other_file := os.join_path(os.temp_dir(),
1316 'v2_cleanc_target_codegen_sort_other_alias_global_callback_${os.getpid()}.v')
1317 os.write_file(other_file, '
1318module other
1319
1320pub struct Item {
1321pub:
1322 value int
1323}
1324
1325pub type ItemCallback = fn (&Item, &Item) int
1326
1327pub __global callback ItemCallback
1328 ') or {
1329 panic(err)
1330 }
1331 defer {
1332 os.rm(other_file) or {}
1333 }
1334 src := generated_c_for_target_program_with_extra_files('sort_import_alias_global_callback_fnsortcb_cast', '
1335module main
1336
1337import other as oth
1338
1339type FnSortCB = fn (voidptr, voidptr) int
1340
1341fn array__sort_with_compare(mut items []oth.Item, callback FnSortCB)
1342
1343fn main() {
1344 mut items := [oth.Item{value: 2}, oth.Item{value: 1}]
1345 array__sort_with_compare(mut items, oth.callback)
1346}
1347 ',
1348 'linux', [], false, false, [other_file])
1349 assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__callback);')
1350 assert !src.contains('array__sort_with_compare(&items, other__callback);')
1351 assert !src.contains('(FnSortCB)oth__callback')
1352}
1353
1354fn test_sort_with_compare_module_const_callback_casts_to_fnsortcb() {
1355 other_file := os.join_path(os.temp_dir(),
1356 'v2_cleanc_target_codegen_sort_other_const_callback_${os.getpid()}.v')
1357 os.write_file(other_file, '
1358module other
1359
1360pub struct Item {
1361pub:
1362 value int
1363}
1364
1365pub type ItemCallback = fn (&Item, &Item) int
1366
1367pub fn compare_items(a &Item, b &Item) int {
1368 return a.value - b.value
1369}
1370
1371pub const callback = ItemCallback(compare_items)
1372 ') or {
1373 panic(err)
1374 }
1375 defer {
1376 os.rm(other_file) or {}
1377 }
1378 src := generated_c_for_target_program_with_extra_files('sort_module_const_callback_fnsortcb_cast', '
1379module main
1380
1381import other
1382
1383type FnSortCB = fn (voidptr, voidptr) int
1384
1385fn array__sort_with_compare(mut items []other.Item, callback FnSortCB)
1386
1387fn main() {
1388 mut items := [other.Item{value: 2}, other.Item{value: 1}]
1389 array__sort_with_compare(mut items, other.callback)
1390}
1391 ',
1392 'linux', [], false, false, [other_file])
1393 assert src.contains('array__sort_with_compare(&items, (FnSortCB)other__callback);')
1394 assert !src.contains('array__sort_with_compare(&items, other__callback);')
1395}
1396
1397fn test_sort_with_compare_module_global_non_fn_value_is_not_cast_to_fnsortcb() {
1398 other_file := os.join_path(os.temp_dir(),
1399 'v2_cleanc_target_codegen_sort_other_global_non_fn_${os.getpid()}.v')
1400 os.write_file(other_file, '
1401module other
1402
1403pub struct Item {
1404pub:
1405 value int
1406}
1407
1408pub __global callback int
1409 ') or {
1410 panic(err)
1411 }
1412 defer {
1413 os.rm(other_file) or {}
1414 }
1415 src := generated_c_for_target_program_with_extra_files('sort_module_global_non_fn_no_fnsortcb_cast', '
1416module main
1417
1418import other
1419
1420type FnSortCB = fn (voidptr, voidptr) int
1421
1422fn array__sort_with_compare(mut items []other.Item, callback FnSortCB)
1423
1424fn main() {
1425 mut items := [other.Item{value: 2}, other.Item{value: 1}]
1426 array__sort_with_compare(mut items, other.callback)
1427}
1428 ',
1429 'linux', [], false, false, [other_file])
1430 assert src.contains('array__sort_with_compare(&items, other__callback);')
1431 assert !src.contains('(FnSortCB)other__callback')
1432}
1433
1434fn test_sort_with_compare_local_selector_shadowing_module_is_not_cast_to_fnsortcb() {
1435 other_file := os.join_path(os.temp_dir(),
1436 'v2_cleanc_target_codegen_sort_other_shadowed_module_${os.getpid()}.v')
1437 os.write_file(other_file, '
1438module other
1439
1440pub struct Item {
1441pub:
1442 value int
1443}
1444
1445pub type ItemCallback = fn (&Item, &Item) int
1446
1447pub __global callback ItemCallback
1448 ') or {
1449 panic(err)
1450 }
1451 defer {
1452 os.rm(other_file) or {}
1453 }
1454 src := generated_c_for_target_program_with_extra_files('sort_local_selector_shadowed_module_no_fnsortcb_cast', '
1455module main
1456
1457import other
1458
1459type FnSortCB = fn (voidptr, voidptr) int
1460
1461type ItemCallback = fn (&other.Item, &other.Item) int
1462
1463struct Holder {
1464 callback ItemCallback
1465}
1466
1467fn array__sort_with_compare(mut items []other.Item, callback FnSortCB)
1468
1469fn compare_items(a &other.Item, b &other.Item) int {
1470 return a.value - b.value
1471}
1472
1473fn main() {
1474 mut items := [other.Item{value: 2}, other.Item{value: 1}]
1475 other := Holder{
1476 callback: compare_items
1477 }
1478 array__sort_with_compare(mut items, other.callback)
1479}
1480 ',
1481 'linux', [], false, false, [other_file])
1482 assert src.contains('array__sort_with_compare(&items, other.callback);')
1483 assert !src.contains('array__sort_with_compare(&items, (FnSortCB)other.callback);')
1484 assert !src.contains('(FnSortCB)other__callback')
1485}
1486
1487fn test_sort_with_compare_selector_field_callback_is_not_cast_to_fnsortcb() {
1488 src := generated_c_for_target_program('sort_selector_field_callback_no_fnsortcb_cast', '
1489type FnSortCB = fn (voidptr, voidptr) int
1490
1491struct Item {
1492 value int
1493}
1494
1495type ItemCallback = fn (&Item, &Item) int
1496
1497struct Holder {
1498 callback ItemCallback
1499}
1500
1501fn array__sort_with_compare(mut items []Item, callback FnSortCB)
1502
1503fn compare_items(a &Item, b &Item) int {
1504 return a.value - b.value
1505}
1506
1507fn main() {
1508 mut items := [Item{value: 2}, Item{value: 1}]
1509 holder := Holder{
1510 callback: compare_items
1511 }
1512 array__sort_with_compare(mut items, holder.callback)
1513}
1514')
1515 assert src.contains('array__sort_with_compare(&items, holder.callback);')
1516 assert !src.contains('array__sort_with_compare(&items, (FnSortCB)holder.callback);')
1517 assert !src.contains('(FnSortCB)holder__callback')
1518}
1519
1520fn test_sorted_with_compare_named_comparator_casts_to_fnsortcb() {
1521 src := generated_c_for_target_program('sorted_named_comparator_fnsortcb_cast', '
1522type FnSortCB = fn (voidptr, voidptr) int
1523
1524struct Item {
1525 value int
1526}
1527
1528fn array__sorted_with_compare(items []Item, callback FnSortCB) []Item
1529
1530fn compare_items(a &Item, b &Item) int {
1531 return a.value - b.value
1532}
1533
1534fn main() {
1535 items := [Item{value: 2}, Item{value: 1}]
1536 sorted := array__sorted_with_compare(items, compare_items)
1537 _ = sorted
1538}
1539')
1540 assert src.contains('array__sorted_with_compare(items, (FnSortCB)compare_items);')
1541 assert !src.contains('array__sorted_with_compare(items, compare_items);')
1542}
1543
1544fn test_string_sort_helpers_cast_named_comparators_to_fnsortcb() {
1545 src := generated_c_for_target_program('string_sort_helpers_fnsortcb_cast', '
1546type FnSortCB = fn (voidptr, voidptr) int
1547
1548fn array__sort_with_compare(mut words []string, callback FnSortCB)
1549
1550fn compare_lower_strings(a &string, b &string) int {
1551 return 0
1552}
1553
1554fn compare_strings_by_len(a &string, b &string) int {
1555 return 0
1556}
1557
1558fn (mut words []string) sort_ignore_case() {
1559 array__sort_with_compare(mut words, compare_lower_strings)
1560}
1561
1562fn (mut words []string) sort_by_len() {
1563 array__sort_with_compare(mut words, compare_strings_by_len)
1564}
1565
1566fn main() {
1567 mut words := ["Beta", "alpha", "z"]
1568 words.sort_ignore_case()
1569 words.sort_by_len()
1570}
1571')
1572 assert src.contains('Array_string__sort_ignore_case(&words);')
1573 assert src.contains('Array_string__sort_by_len(&words);')
1574 assert src.contains('array__sort_with_compare(words, (FnSortCB)compare_lower_strings);')
1575 assert src.contains('array__sort_with_compare(words, (FnSortCB)compare_strings_by_len);')
1576 assert !src.contains('array__sort_with_compare(words, compare_lower_strings);')
1577 assert !src.contains('array__sort_with_compare(words, compare_strings_by_len);')
1578}
1579
1580fn test_sort_with_compare_capturing_fn_literal_casts_statement_expr_to_fnsortcb() {
1581 src := generated_c_for_target_program('sort_capturing_fn_literal_fnsortcb_cast', '
1582type FnSortCB = fn (voidptr, voidptr) int
1583
1584struct Item {
1585 value int
1586}
1587
1588fn array__sort_with_compare(mut items []Item, callback FnSortCB)
1589
1590fn main() {
1591 bias := 1
1592 mut items := [Item{value: 2}, Item{value: 1}]
1593 array__sort_with_compare(mut items, fn [bias] (a &Item, b &Item) int {
1594 return bias + a.value - b.value
1595 })
1596}
1597')
1598 assert src.contains('array__sort_with_compare(&items, (FnSortCB)({')
1599 assert src.contains('_anon_fn_')
1600 assert src.contains('_capture_0 = bias;')
1601 assert !src.contains('array__sort_with_compare(&items, ({')
1602}
1603
1604fn test_sorted_with_compare_fn_literal_casts_to_fnsortcb() {
1605 src := generated_c_for_target_program('sorted_fn_literal_fnsortcb_cast', '
1606type FnSortCB = fn (voidptr, voidptr) int
1607
1608struct Item {
1609 value int
1610}
1611
1612fn array__sorted_with_compare(items []Item, callback FnSortCB) []Item
1613
1614fn main() {
1615 items := [Item{value: 2}, Item{value: 1}]
1616 sorted := array__sorted_with_compare(items, fn (a &Item, b &Item) int {
1617 return a.value - b.value
1618 })
1619 _ = sorted
1620}
1621')
1622 assert src.contains('array__sorted_with_compare(items, (FnSortCB)_anon_fn_')
1623 assert !src.contains('array__sorted_with_compare(items, _anon_fn_')
1624}
1625
1626fn test_sort_with_compare_nil_callback_is_not_cast_to_fnsortcb() {
1627 src := generated_c_for_target_program('sort_nil_callback_no_fnsortcb_cast', '
1628type FnSortCB = fn (voidptr, voidptr) int
1629
1630struct Item {
1631 value int
1632}
1633
1634fn array__sort_with_compare(mut items []Item, callback FnSortCB)
1635
1636fn main() {
1637 mut items := [Item{value: 2}, Item{value: 1}]
1638 array__sort_with_compare(mut items, nil)
1639}
1640')
1641 assert src.contains('array__sort_with_compare(&items, NULL);')
1642 assert !src.contains('(FnSortCB)nil')
1643 assert !src.contains('array__sort_with_compare(&items, (FnSortCB)')
1644}
1645
1646fn test_non_sort_callback_arg_is_not_cast_to_fnsortcb() {
1647 src := generated_c_for_target_program('non_sort_callback_no_fnsortcb_cast', '
1648struct Item {
1649 value int
1650}
1651
1652type ItemCallback = fn (&Item, &Item) int
1653
1654fn compare_items(a &Item, b &Item) int {
1655 return a.value - b.value
1656}
1657
1658fn use_callback(callback ItemCallback) {
1659 _ = callback
1660}
1661
1662fn main() {
1663 use_callback(compare_items)
1664}
1665')
1666 assert src.contains('use_callback(compare_items);')
1667 assert !src.contains('(FnSortCB)compare_items')
1668}
1669
1670fn test_non_sort_fn_literal_callback_arg_is_not_cast_to_fnsortcb() {
1671 src := generated_c_for_target_program('non_sort_fn_literal_callback_no_fnsortcb_cast', '
1672struct Item {
1673 value int
1674}
1675
1676type ItemCallback = fn (&Item, &Item) int
1677
1678fn use_callback(callback ItemCallback) {
1679 _ = callback
1680}
1681
1682fn main() {
1683 bias := 1
1684 use_callback(fn [bias] (a &Item, b &Item) int {
1685 return bias + a.value - b.value
1686 })
1687}
1688')
1689 assert src.contains('use_callback(({')
1690 assert src.contains('_anon_fn_')
1691 assert src.contains('_capture_0 = bias;')
1692 assert !src.contains('(FnSortCB)')
1693 assert !src.contains('use_callback((FnSortCB)')
1694}
1695
1696fn test_fn_pointer_alias_cast_from_voidptr_uses_alias_value_cast() {
1697 src := generated_c_for_target_program('fn_pointer_alias_voidptr_cast', '
1698module os
1699
1700type Signal = int
1701type SignalHandler = fn (Signal)
1702
1703fn handler_from_ptr(prev_handler voidptr) !SignalHandler {
1704 return SignalHandler(prev_handler)
1705}
1706')
1707 assert src.contains('_result_os__SignalHandler os__handler_from_ptr(void* prev_handler)')
1708 assert src.contains('os__SignalHandler _val = ((os__SignalHandler)(prev_handler));')
1709 assert !src.contains('os__SignalHandler _val = ((os__SignalHandler*)(prev_handler));')
1710}
1711
1712fn test_option_fn_pointer_alias_cast_from_voidptr_uses_alias_value_cast() {
1713 src := generated_c_for_target_program('option_fn_pointer_alias_voidptr_cast', '
1714module os
1715
1716type Signal = int
1717type SignalHandler = fn (Signal)
1718
1719fn handler_from_ptr(prev_handler voidptr) ?SignalHandler {
1720 return SignalHandler(prev_handler)
1721}
1722')
1723 assert src.contains('_option_os__SignalHandler os__handler_from_ptr(void* prev_handler)')
1724 assert src.contains('os__SignalHandler _val = ((os__SignalHandler)(prev_handler));')
1725 assert !src.contains('os__SignalHandler _val = ((os__SignalHandler*)(prev_handler));')
1726}
1727
1728fn test_explicit_pointer_to_fn_pointer_alias_cast_preserves_star() {
1729 src := generated_c_for_target_program('fn_pointer_alias_explicit_pointer_cast', '
1730module os
1731
1732type Signal = int
1733type SignalHandler = fn (Signal)
1734
1735fn handler_ptr_from_ptr(prev_handler voidptr) &SignalHandler {
1736 return &SignalHandler(prev_handler)
1737}
1738')
1739 assert src.contains('os__SignalHandler* os__handler_ptr_from_ptr(void* prev_handler)')
1740 assert src.contains('return ((os__SignalHandler*)(prev_handler));')
1741 assert !src.contains('return ((os__SignalHandler)(prev_handler));')
1742}
1743
1744fn test_result_pointer_to_fn_pointer_alias_cast_preserves_payload_pointer() {
1745 src := generated_c_for_target_program('result_fn_pointer_alias_explicit_pointer_cast', '
1746module os
1747
1748type Signal = int
1749type SignalHandler = fn (Signal)
1750
1751fn handler_ptr_from_ptr(prev_handler voidptr) !&SignalHandler {
1752 return &SignalHandler(prev_handler)
1753}
1754')
1755 assert src.contains('_result_os__SignalHandlerptr os__handler_ptr_from_ptr(void* prev_handler)')
1756 assert src.contains('os__SignalHandler* _val = ((os__SignalHandler*)(prev_handler));')
1757 assert !src.contains('os__SignalHandler _val = ((os__SignalHandler)(prev_handler));')
1758}
1759
1760fn test_option_pointer_to_fn_pointer_alias_cast_preserves_payload_pointer() {
1761 src := generated_c_for_target_program('option_fn_pointer_alias_explicit_pointer_cast', '
1762module os
1763
1764type Signal = int
1765type SignalHandler = fn (Signal)
1766
1767fn handler_ptr_from_ptr(prev_handler voidptr) ?&SignalHandler {
1768 return &SignalHandler(prev_handler)
1769}
1770')
1771 assert src.contains('_option_os__SignalHandlerptr os__handler_ptr_from_ptr(void* prev_handler)')
1772 assert src.contains('os__SignalHandler* _val = ((os__SignalHandler*)(prev_handler));')
1773 assert !src.contains('os__SignalHandler _val = ((os__SignalHandler)(prev_handler));')
1774}
1775
1776fn test_non_fn_pointer_result_payload_keeps_pointer_cast() {
1777 src := generated_c_for_target_program('non_fn_pointer_result_payload', '
1778module os
1779
1780struct Payload {
1781 value int
1782}
1783
1784fn payload_from_ptr(raw voidptr) !&Payload {
1785 return &Payload(raw)
1786}
1787')
1788 assert src.contains('_result_os__Payloadptr os__payload_from_ptr(void* raw)')
1789 assert src.contains('os__Payload* _val = ((os__Payload*)(raw));')
1790 assert !src.contains('os__Payload _val = ((os__Payload)(raw));')
1791}
1792
1793fn test_fn_pointer_alias_helper_has_no_suffix_fallback() {
1794 g := Gen{
1795 fn_type_aliases: {
1796 'os__SignalHandler': true
1797 }
1798 }
1799 assert g.c_type_is_fn_pointer_alias('os__SignalHandler')
1800 assert !g.c_type_is_fn_pointer_alias('other__SignalHandler')
1801 assert !g.c_type_is_fn_pointer_alias('os__SignalHandlerptr')
1802 assert !g.c_type_is_fn_pointer_alias('os__HandlerFn')
1803 assert g.result_value_type('_result_os__SignalHandlerptr') == 'os__SignalHandler*'
1804 assert g.result_value_type('_result_os__Payloadptr') == 'os__Payload*'
1805}
1806
1807fn test_inter_module_non_fn_alias_cast_is_not_remapped_to_fn_alias_homonym() {
1808 foo_file := os.join_path(os.temp_dir(),
1809 'v2_cleanc_target_codegen_signalhandler_foo_${os.getpid()}.v')
1810 bar_file := os.join_path(os.temp_dir(),
1811 'v2_cleanc_target_codegen_signalhandler_bar_${os.getpid()}.v')
1812 os.write_file(foo_file, '
1813module foo
1814
1815pub type SignalHandler = fn (int)
1816 ') or { panic(err) }
1817 os.write_file(bar_file, '
1818module bar
1819
1820pub type SignalHandler = int
1821 ') or { panic(err) }
1822 defer {
1823 os.rm(foo_file) or {}
1824 os.rm(bar_file) or {}
1825 }
1826 src := generated_c_for_target_program_with_extra_files('inter_module_signalhandler_alias_collision', '
1827module main
1828
1829import bar
1830import foo
1831
1832fn keep_foo(handler foo.SignalHandler) {
1833 _ = handler
1834}
1835
1836fn cast_bar() bar.SignalHandler {
1837 return bar.SignalHandler(1)
1838}
1839 ',
1840 'linux', [], false, false, [foo_file, bar_file])
1841 assert src.contains('typedef void (*foo__SignalHandler)(int);')
1842 assert src.contains('typedef int bar__SignalHandler;')
1843 assert src.contains('return ((bar__SignalHandler)(1));')
1844 assert !src.contains('return ((foo__SignalHandler)(1));')
1845 assert !src.contains('return ((os__SignalHandler)(1));')
1846}
1847
1848fn test_module_local_non_fn_alias_cast_prefers_local_alias_over_main_fn_alias_homonym() {
1849 bar_file := os.join_path(os.temp_dir(),
1850 'v2_cleanc_target_codegen_signalhandler_local_bar_${os.getpid()}.v')
1851 os.write_file(bar_file, '
1852module bar
1853
1854pub type SignalHandler = int
1855
1856pub fn cast_local() SignalHandler {
1857 return SignalHandler(1)
1858}
1859 ') or {
1860 panic(err)
1861 }
1862 defer {
1863 os.rm(bar_file) or {}
1864 }
1865 src := generated_c_for_target_program_with_extra_files('module_local_signalhandler_alias_collision', '
1866module main
1867
1868type SignalHandler = fn (int)
1869
1870fn main() {
1871}
1872 ',
1873 'linux', [], false, false, [bar_file])
1874 assert src.contains('typedef void (*SignalHandler)(int);')
1875 assert src.contains('typedef int bar__SignalHandler;')
1876 assert src.contains('return ((bar__SignalHandler)(1));')
1877 assert !src.contains('return ((SignalHandler)(1));')
1878 assert !src.contains('return ((foo__SignalHandler)(1));')
1879}
1880
1881fn test_optional_sumtype_field_initializer_wraps_cast_value_as_option() {
1882 src := generated_c_for_target_program('optional_sumtype_field_initializer', '
1883type Type = Bool | Int
1884
1885struct Bool {}
1886struct Int {}
1887
1888struct FnType {
1889 return_type ?Type
1890}
1891
1892fn make_fn_type() FnType {
1893 return FnType{
1894 return_type: Type(Bool{})
1895 }
1896}
1897
1898fn main() {
1899 _ = make_fn_type()
1900}
1901')
1902 assert src.contains('struct FnType {\n\t_option_Type return_type;\n};')
1903 assert src.contains('.return_type = ({ _option_Type _opt = (_option_Type){ .state = 2 }; Type _val = ((Type){')
1904 assert src.contains('_option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; })')
1905 assert !src.contains('.return_type = ((Type){')
1906 assert !src.contains('.return_type = ((main__Type){')
1907}
1908
1909fn test_freestanding_minimal_preamble_avoids_implicit_os_runtime_headers() {
1910 src := preamble_for_freestanding_field('linux')
1911 assert src.contains('#include <stdbool.h>')
1912 assert src.contains('#include <stdint.h>')
1913 assert src.contains('#include <stddef.h>')
1914 assert src.contains('#include <string.h>')
1915 assert !src.contains('#include <stdio.h>')
1916 assert !src.contains('#include <stdlib.h>')
1917 assert !src.contains('#include <windows.h>')
1918 assert !src.contains('#include <dirent.h>')
1919 assert !src.contains('#include <pthread.h>')
1920 assert !src.contains('#include <mach/mach.h>')
1921 assert src.contains('typedef struct sync__RwMutex { u32 inited; } sync__RwMutex;')
1922 assert !src.contains('pthread_rwlock_t')
1923}
1924
1925fn test_freestanding_full_preamble_avoids_implicit_os_runtime_headers() {
1926 src := full_preamble_for_freestanding_field('linux')
1927 assert src.contains('#include <stdbool.h>')
1928 assert src.contains('#include <stdint.h>')
1929 assert src.contains('#include <stddef.h>')
1930 assert src.contains('#include <string.h>')
1931 assert !src.contains('#include <unistd.h>')
1932 assert !src.contains('#include <windows.h>')
1933 assert !src.contains('#include <pthread.h>')
1934 assert !src.contains('#include <dirent.h>')
1935 assert !src.contains('#include <termios.h>')
1936 assert !src.contains('#include <sys/wait.h>')
1937 assert src.contains('typedef struct sync__RwMutex { u32 inited; } sync__RwMutex;')
1938 assert !src.contains('pthread_rwlock_t')
1939}
1940
1941fn test_freestanding_field_full_preamble_avoids_implicit_os_runtime_headers() {
1942 src := full_preamble_for_freestanding_field('linux')
1943 assert src.contains('#include <stdbool.h>')
1944 assert src.contains('#include <stdint.h>')
1945 assert src.contains('#include <stddef.h>')
1946 assert src.contains('#include <string.h>')
1947 assert !src.contains('#include <unistd.h>')
1948 assert !src.contains('#include <windows.h>')
1949 assert !src.contains('#include <pthread.h>')
1950 assert !src.contains('#include <dirent.h>')
1951 assert !src.contains('#include <termios.h>')
1952 assert !src.contains('#include <sys/wait.h>')
1953 assert src.contains('typedef struct sync__RwMutex { u32 inited; } sync__RwMutex;')
1954 assert !src.contains('pthread_rwlock_t')
1955}
1956
1957fn test_user_define_freestanding_does_not_enable_freestanding_codegen() {
1958 src := full_preamble_for_target('linux', ['freestanding'])
1959 assert src.contains('#include <unistd.h>')
1960 assert src.contains('#include <pthread.h>')
1961 assert src.contains('#include <stdio.h>')
1962 assert src.contains('#include <stdlib.h>')
1963 assert src.contains('pthread_rwlock_t')
1964 assert !src.contains('typedef struct sync__RwMutex { u32 inited; } sync__RwMutex;')
1965}
1966
1967fn test_freestanding_none_preamble_avoids_implicit_os_runtime_headers() {
1968 src := full_preamble_for_options('none', [], true, false)
1969 assert src.contains('#include <stdbool.h>')
1970 assert src.contains('#include <stdint.h>')
1971 assert src.contains('#include <stddef.h>')
1972 assert src.contains('#include <string.h>')
1973 assert src.contains('typedef struct sync__RwMutex { u32 inited; } sync__RwMutex;')
1974 assert_no_os_runtime_headers(src)
1975}
1976
1977fn test_freestanding_prealloc_preamble_avoids_implicit_free_contract() {
1978 freestanding_src := full_preamble_for_options('linux', [], true, true)
1979 assert !freestanding_src.contains('#define _VPREALLOC (1)')
1980 assert !freestanding_src.contains('static inline void _v_cfree')
1981 assert !freestanding_src.contains('#define free(p)')
1982
1983 hosted_src := full_preamble_for_options('linux', [], false, true)
1984 assert hosted_src.contains('#define _VPREALLOC (1)')
1985 assert hosted_src.contains('static inline void _v_cfree(void *p) { free(p); }')
1986 assert hosted_src.contains('#define free(p) ((void)(p), (void)0)')
1987}
1988
1989fn test_freestanding_platform_hooks_preamble_declares_requested_hooks_without_hosted_headers() {
1990 generic_src := full_preamble_for_options('linux', ['freestanding_hooks'], true, false)
1991 assert !generic_src.contains('v_platform_write')
1992 assert !generic_src.contains('v_platform_panic')
1993 assert !generic_src.contains('v_platform_malloc')
1994
1995 spoofed_src := full_preamble_for_options('linux', ['freestanding_hooks', 'freestanding_output',
1996 'freestanding_panic', 'freestanding_alloc'], true, false)
1997 assert !spoofed_src.contains('v_platform_write')
1998 assert !spoofed_src.contains('v_platform_panic')
1999 assert !spoofed_src.contains('v_platform_malloc')
2000
2001 output_src := full_preamble_for_freestanding_hooks(['output'])
2002 assert output_src.contains('isize v_platform_write(int stream, const u8* buf, isize len);')
2003 assert !output_src.contains('v_platform_panic')
2004 assert !output_src.contains('v_platform_malloc')
2005 assert !output_src.contains('#include <stdio.h>')
2006 assert !output_src.contains('#include <stdlib.h>')
2007 assert !output_src.contains('#include <unistd.h>')
2008
2009 panic_src := full_preamble_for_freestanding_hooks(['panic'])
2010 assert panic_src.contains('void v_platform_panic(const u8* msg, isize len);')
2011 assert !panic_src.contains('v_platform_write')
2012 assert !panic_src.contains('v_platform_malloc')
2013
2014 alloc_src := full_preamble_for_freestanding_hooks(['alloc'])
2015 assert alloc_src.contains('void* v_platform_malloc(isize n);')
2016 assert alloc_src.contains('void* v_platform_realloc(void* ptr, isize n);')
2017 assert alloc_src.contains('void v_platform_free(void* ptr);')
2018 assert !alloc_src.contains('v_platform_write')
2019 assert !alloc_src.contains('v_platform_panic')
2020
2021 minimal_src := full_preamble_for_freestanding_hooks(['output', 'panic', 'alloc'])
2022 assert minimal_src.contains('isize v_platform_write(int stream, const u8* buf, isize len);')
2023 assert minimal_src.contains('void v_platform_panic(const u8* msg, isize len);')
2024 assert minimal_src.contains('void* v_platform_malloc(isize n);')
2025 assert minimal_src.contains('void* v_platform_realloc(void* ptr, isize n);')
2026 assert minimal_src.contains('void v_platform_free(void* ptr);')
2027 assert !minimal_src.contains('#include <stdio.h>')
2028 assert !minimal_src.contains('#include <stdlib.h>')
2029 assert !minimal_src.contains('#include <unistd.h>')
2030}
2031
2032fn test_windows_minimal_preamble_avoids_posix_headers() {
2033 src := preamble_for_target('windows', [])
2034 assert !src.contains('#include <dirent.h>')
2035 assert !src.contains('#include <pthread.h>')
2036 assert !src.contains('#include <mach/mach.h>')
2037 assert src.contains('#include <windows.h>')
2038 assert src.contains('typedef struct sync__RwMutex { SRWLOCK mutex; u32 inited; } sync__RwMutex;')
2039 assert src.contains('AcquireSRWLockExclusive')
2040 assert !src.contains('static inline void sync__RwMutex_lock(sync__RwMutex* m) { (void)m; }')
2041 assert !src.contains('pthread_rwlock_t')
2042 assert src.contains('#include <stdio.h>')
2043 assert src.contains('#include <stdlib.h>')
2044}
2045
2046fn test_windows_full_preamble_avoids_posix_headers() {
2047 src := full_preamble_for_target('windows', [])
2048 assert !src.contains('#include <unistd.h>')
2049 assert !src.contains('#include <sys/wait.h>')
2050 assert !src.contains('#include <termios.h>')
2051 assert !src.contains('#include <sys/ioctl.h>')
2052 assert !src.contains('#include <dirent.h>')
2053 assert !src.contains('#include <pthread.h>')
2054 assert src.contains('#include <windows.h>')
2055 assert src.contains('typedef struct sync__RwMutex { SRWLOCK mutex; u32 inited; } sync__RwMutex;')
2056 assert src.contains('AcquireSRWLockExclusive')
2057 assert !src.contains('static inline void sync__RwMutex_lock(sync__RwMutex* m) { (void)m; }')
2058 assert !src.contains('pthread_rwlock_t')
2059 assert src.contains('#include <stdio.h>')
2060 assert src.contains('#include <stdlib.h>')
2061 assert src.contains('#include <time.h>')
2062}
2063
2064fn test_minimal_generated_c_does_not_emit_unused_runtime_fallbacks() {
2065 csrc := generated_c_for_target_program('minimal_runtime_fallbacks', 'module main
2066
2067fn main() {}
2068')
2069 assert csrc.contains('int main(')
2070 assert_no_hosted_runtime_fallbacks(csrc)
2071 assert !csrc.contains('__v_live_init')
2072}
2073
2074fn test_freestanding_skip_builtin_minimal_does_not_emit_hosted_runtime_fallbacks() {
2075 csrc := generated_c_for_target_program_with_options('freestanding_minimal_runtime_fallbacks', 'module main
2076
2077fn main() {}
2078',
2079 'linux', true, true)
2080 assert csrc.contains('int main(')
2081 assert_no_hosted_runtime_fallbacks(csrc)
2082 assert !csrc.contains('__v_live_init')
2083}
2084
2085fn test_live_reload_detects_live_functions_in_active_comptime_blocks() {
2086 csrc := generated_c_for_target_program('active_live_comptime', 'module main
2087
2088\$if linux {
2089 @[live]
2090 fn hot_reload() {}
2091}
2092
2093fn main() {}
2094')
2095 assert csrc.contains('void __v_live_init(void);')
2096 assert csrc.contains('__v_live_init();')
2097}
2098
2099fn test_live_reload_ignores_live_functions_in_inactive_comptime_blocks() {
2100 csrc := generated_c_for_target_program('inactive_live_comptime', 'module main
2101
2102\$if windows {
2103 @[live]
2104 fn hot_reload() {}
2105}
2106
2107fn main() {}
2108')
2109 assert !csrc.contains('__v_live_init')
2110}
2111
2112fn test_runtime_fallbacks_emit_arguments_only_when_referenced() {
2113 minimal_src := runtime_fallbacks_for_called_functions([])
2114 assert !minimal_src.contains('__attribute__((weak)) Array_string arguments()')
2115
2116 args_src := runtime_fallbacks_for_called_functions(['arguments'])
2117 assert args_src.contains('__attribute__((weak)) Array_string arguments()')
2118 assert !args_src.contains('__attribute__((weak)) void _write_buf_to_fd')
2119}
2120
2121fn test_freestanding_without_alloc_hook_arguments_refuses_without_hosted_alloc() {
2122 src := runtime_fallbacks_for_called_functions_with_hooks(['arguments'], ['output'])
2123 assert src.contains('__attribute__((weak)) Array_string arguments()')
2124 assert !src.contains('__new_array_with_default_noscan')
2125 assert !src.contains('tos_clone')
2126 assert !src.contains('array__push')
2127 assert_freestanding_alloc_refusal_without_hosted_heap(src)
2128}
2129
2130fn test_runtime_fallbacks_emit_stdout_helpers_only_when_printing_is_referenced() {
2131 print_src := runtime_fallbacks_for_called_functions(['println'])
2132 assert print_src.contains('__attribute__((weak)) void _write_buf_to_fd')
2133 assert print_src.contains('__attribute__((weak)) void _writeln_to_fd')
2134 assert print_src.contains('isize written = write(fd, ptr, remaining_bytes)')
2135 assert !print_src.contains('WriteFile(')
2136 assert !print_src.contains('__attribute__((weak)) Array_string arguments()')
2137 assert !print_src.contains('__attribute__((weak)) void eprint')
2138 assert !print_src.contains('__attribute__((weak)) void flush_stdout')
2139 assert !print_src.contains('__attribute__((weak)) void flush_stderr')
2140}
2141
2142fn test_windows_runtime_fallbacks_use_writefile_for_stdout_helpers() {
2143 src := runtime_fallbacks_for_called_functions_for_target('windows', ['println'])
2144 assert src.contains('__attribute__((weak)) void _write_buf_to_fd')
2145 assert !src.contains('write(fd, ptr, remaining_bytes)')
2146 assert src.contains('GetStdHandle(fd == 2 ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE)')
2147 assert src.contains('WriteFile(handle, ptr, (DWORD)remaining_bytes, &win_written, NULL)')
2148}
2149
2150fn test_cross_runtime_fallbacks_guard_windows_stdout_helpers() {
2151 src := runtime_fallbacks_for_called_functions_for_target('cross', ['println'])
2152 assert src.contains('__attribute__((weak)) void _write_buf_to_fd')
2153 assert src.contains('#if defined(_WIN32)')
2154 assert src.contains('GetStdHandle(fd == 2 ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE)')
2155 assert src.contains('WriteFile(handle, ptr, (DWORD)remaining_bytes, &win_written, NULL)')
2156 assert src.contains('#else\n\t\tisize written = write(fd, ptr, remaining_bytes);\n#endif')
2157}
2158
2159fn test_runtime_fallbacks_emit_eprint_and_flush_only_when_referenced() {
2160 minimal_src := runtime_fallbacks_for_called_functions([])
2161 assert !minimal_src.contains('__attribute__((weak)) void eprint')
2162 assert !minimal_src.contains('__attribute__((weak)) void flush_stdout')
2163 assert !minimal_src.contains('__attribute__((weak)) void flush_stderr')
2164
2165 eprint_src := runtime_fallbacks_for_called_functions(['eprint'])
2166 assert eprint_src.contains('__attribute__((weak)) void eprint')
2167 assert eprint_src.contains('__attribute__((weak)) void _write_buf_to_fd')
2168 assert eprint_src.contains('__attribute__((weak)) void flush_stdout')
2169 assert eprint_src.contains('__attribute__((weak)) void flush_stderr')
2170}
2171
2172fn test_runtime_fallbacks_emit_structural_helpers_only_when_referenced() {
2173 minimal_src := runtime_fallbacks_for_called_functions([])
2174 assert_no_hosted_runtime_fallbacks(minimal_src)
2175
2176 memdup_src := runtime_fallbacks_for_called_functions(['memdup'])
2177 assert memdup_src.contains('__attribute__((weak)) u8* malloc_noscan')
2178 assert memdup_src.contains('__attribute__((weak)) void* memdup')
2179
2180 array_src := runtime_fallbacks_for_called_functions(['Array_string_contains',
2181 'Array_string_index', 'Array_int_str'])
2182 assert array_src.contains('__attribute__((weak)) bool Array_string_contains')
2183 assert array_src.contains('__attribute__((weak)) int Array_string_index')
2184 assert array_src.contains('__attribute__((weak)) string Array_int_str')
2185 assert !array_src.contains('__attribute__((weak)) u8* malloc_noscan')
2186
2187 bits_src := runtime_fallbacks_for_called_functions(['__at_least_one', 'bits__leading_zeros_64',
2188 'bits__rotate_left_32', 'f32__str'])
2189 assert bits_src.contains('__attribute__((weak)) u64 __at_least_one')
2190 assert bits_src.contains('__attribute__((weak)) int bits__leading_zeros_64')
2191 assert bits_src.contains('__attribute__((weak)) u32 bits__rotate_left_32')
2192 assert bits_src.contains('__attribute__((weak)) string f64__str')
2193 assert bits_src.contains('__attribute__((weak)) string f32__str')
2194}
2195
2196fn test_runtime_fallbacks_emit_float_strg_dependency_closure() {
2197 f32_strg_src := runtime_fallbacks_for_called_functions(['f32__strg'])
2198 f64_str_idx := f32_strg_src.index('__attribute__((weak)) string f64__str(f64 x)') or {
2199 panic('missing f64__str fallback')
2200 }
2201 f64_strg_idx := f32_strg_src.index('__attribute__((weak)) string f64__strg') or {
2202 panic('missing f64__strg fallback')
2203 }
2204 f32_strg_idx := f32_strg_src.index('__attribute__((weak)) string f32__strg') or {
2205 panic('missing f32__strg fallback')
2206 }
2207 assert f32_strg_src.contains('__attribute__((weak)) u8* malloc_noscan')
2208 assert f64_str_idx < f64_strg_idx
2209 assert f64_strg_idx < f32_strg_idx
2210 assert f32_strg_src.contains('\treturn f64__str(x);')
2211 assert f32_strg_src.contains('\treturn f64__strg((f64)x);')
2212
2213 f64_strg_src := runtime_fallbacks_for_called_functions(['f64__strg'])
2214 f64_str_direct_idx := f64_strg_src.index('__attribute__((weak)) string f64__str(f64 x)') or {
2215 panic('missing f64__str fallback for f64__strg')
2216 }
2217 f64_strg_direct_idx := f64_strg_src.index('__attribute__((weak)) string f64__strg') or {
2218 panic('missing f64__strg fallback for direct reference')
2219 }
2220 assert f64_strg_src.contains('__attribute__((weak)) u8* malloc_noscan')
2221 assert f64_str_direct_idx < f64_strg_direct_idx
2222
2223 existing_c_src :=
2224 runtime_fallbacks_for_existing_c_source('void keep_generated_code(void) { f32__strg((f32)0); }')
2225 existing_f64_str_idx := existing_c_src.index('__attribute__((weak)) string f64__str(f64 x)') or {
2226 panic('missing f64__str fallback for existing C reference')
2227 }
2228 existing_f64_strg_idx := existing_c_src.index('__attribute__((weak)) string f64__strg') or {
2229 panic('missing f64__strg fallback for existing C reference')
2230 }
2231 existing_f32_strg_idx := existing_c_src.index('__attribute__((weak)) string f32__strg') or {
2232 panic('missing f32__strg fallback for existing C reference')
2233 }
2234 assert existing_f64_str_idx < existing_f64_strg_idx
2235 assert existing_f64_strg_idx < existing_f32_strg_idx
2236}
2237
2238fn test_float_str_fallbacks_rely_on_kept_builtin_float_runtime() {
2239 for path in [
2240 'vlib/builtin/float.c.v',
2241 'vlib/strconv/ftoa.c.v',
2242 'vlib/strconv/f32_str.c.v',
2243 'vlib/strconv/f64_str.c.v',
2244 ] {
2245 assert is_builtin_runtime_keep_file(path), path
2246 }
2247}
2248
2249fn test_runtime_fallbacks_emit_structural_helpers_for_existing_c_references() {
2250 src :=
2251 runtime_fallbacks_for_existing_c_source('void keep_generated_code(void) { memdup(0, 0); Array_string_contains((Array_string){0}, (string){0}); }')
2252 assert src.contains('void keep_generated_code(void)')
2253 assert src.contains('__attribute__((weak)) u8* malloc_noscan')
2254 assert src.contains('__attribute__((weak)) void* memdup')
2255 assert src.contains('__attribute__((weak)) bool Array_string_contains')
2256 assert !src.contains('__attribute__((weak)) bool Array_int_contains')
2257}
2258
2259fn test_freestanding_output_hooks_runtime_fallbacks_avoid_hosted_io() {
2260 src := runtime_fallbacks_for_called_functions_with_hooks(['println', 'eprint'], [
2261 'output',
2262 ])
2263 assert src.contains('__attribute__((weak)) void _write_buf_to_fd')
2264 assert src.contains('v_platform_write(fd, ptr, remaining_bytes)')
2265 assert src.contains('__attribute__((weak)) void _writeln_to_fd')
2266 assert src.contains('__attribute__((weak)) void eprint')
2267 assert src.contains('__attribute__((weak)) void flush_stdout')
2268 assert src.contains('__attribute__((weak)) void flush_stderr')
2269 assert_no_hosted_output_runtime(src)
2270}
2271
2272fn test_freestanding_without_output_hook_runtime_fallbacks_refuse_without_hosted_io() {
2273 src := runtime_fallbacks_for_called_functions_with_hooks(['println', 'eprint'], [
2274 'panic',
2275 ])
2276 assert src.contains('__attribute__((weak)) void _write_buf_to_fd')
2277 assert src.contains('__attribute__((weak)) void _writeln_to_fd')
2278 assert src.contains('__attribute__((weak)) void eprint')
2279 assert src.contains('__attribute__((weak)) void flush_stdout')
2280 assert src.contains('__attribute__((weak)) void flush_stderr')
2281 assert !src.contains('v_platform_write')
2282 assert_freestanding_output_refusal_without_hosted_io(src)
2283}
2284
2285fn test_freestanding_spoofed_hook_defines_do_not_call_platform_hooks() {
2286 src := runtime_fallbacks_for_called_functions_with_options(['println', 'panic', 'memdup'], [
2287 'freestanding_hooks',
2288 'freestanding_output',
2289 'freestanding_panic',
2290 'freestanding_alloc',
2291 ], true)
2292 assert !src.contains('v_platform_write')
2293 assert !src.contains('v_platform_panic')
2294 assert !src.contains('v_platform_malloc')
2295 assert_freestanding_alloc_refusal_without_hosted_heap(src)
2296}
2297
2298fn test_freestanding_output_hook_non_string_print_refuses_without_hosted_formatting() {
2299 src := generated_c_for_target_program_with_hooks('freestanding_output_print_non_string', 'module main
2300
2301fn println(v int) {}
2302
2303fn main() {
2304 println(123)
2305}
2306 ', [
2307 'output',
2308 ])
2309 assert !src.contains('int__str(')
2310 assert_freestanding_format_refusal_without_hosted_formatting(src)
2311}
2312
2313fn test_freestanding_output_hook_voidptr_print_refuses_without_hosted_formatting() {
2314 src := generated_c_for_target_program_with_hooks('freestanding_output_print_voidptr', 'module main
2315
2316fn print(v voidptr) {}
2317
2318fn main() {
2319 print(voidptr(0))
2320}
2321 ', [
2322 'output',
2323 ])
2324 assert !src.contains('int__str(')
2325 assert_freestanding_format_refusal_without_hosted_formatting(src)
2326}
2327
2328fn test_freestanding_string_interpolation_refuses_without_hosted_formatting() {
2329 src := generated_c_for_target_program_with_hooks('freestanding_string_interpolation', "module main
2330
2331fn main() {
2332 _ := '\${1}'
2333}
2334 ", [
2335 'alloc',
2336 ])
2337 assert !src.contains('snprintf(')
2338 assert !src.contains('memdup(')
2339 assert_freestanding_format_refusal_without_hosted_formatting(src)
2340 assert_no_raw_heap_runtime(src)
2341}
2342
2343fn test_freestanding_alloc_hooks_runtime_fallbacks_avoid_hosted_alloc() {
2344 src := runtime_fallbacks_for_called_functions_with_hooks(['memdup', 'DenseArray__zeros_to_end'], [
2345 'alloc',
2346 ])
2347 assert src.contains('__attribute__((weak)) u8* malloc_noscan')
2348 assert src.contains('return (u8*)v_platform_malloc(n);')
2349 assert src.contains('__attribute__((weak)) void* memdup')
2350 assert src.contains('void* res = v_platform_malloc(sz);')
2351 assert src.contains('__attribute__((weak)) void DenseArray__zeros_to_end')
2352 assert src.contains('void* tmp_value = v_platform_malloc(d->value_bytes);')
2353 assert src.contains('v_platform_free(tmp_value);')
2354 assert src.contains('v_platform_realloc(d->values, d->value_bytes * d->cap);')
2355 assert_no_hosted_alloc_runtime(src)
2356 assert_no_raw_heap_runtime(src)
2357}
2358
2359fn test_freestanding_without_alloc_hook_heap_helpers_refuse_without_hosted_alloc() {
2360 g := new_target_test_gen_with_freestanding('linux', [
2361 'freestanding_hooks',
2362 'freestanding_output',
2363 'freestanding_panic',
2364 ], true)
2365 malloc_expr := g.c_heap_malloc_call('n')
2366 realloc_expr := g.c_heap_realloc_call('p', 'n')
2367 free_expr := g.c_heap_free_call('p')
2368 helper_src := '${malloc_expr}\n${realloc_expr}\n${free_expr}'
2369 assert helper_src.contains('(void*)0')
2370 assert helper_src.contains('(void)0')
2371 assert_freestanding_alloc_refusal_without_hosted_heap(helper_src)
2372}
2373
2374fn test_freestanding_without_alloc_hook_runtime_heap_paths_refuse_without_hosted_alloc() {
2375 src := runtime_fallbacks_for_existing_c_source_with_hooks('void keep_generated_code(void) { memdup(0, 1); DenseArray__zeros_to_end(0); }', [
2376 'output',
2377 'panic',
2378 ])
2379 assert src.contains('__attribute__((weak)) void* memdup')
2380 assert src.contains('__attribute__((weak)) void DenseArray__zeros_to_end')
2381 assert src.contains('void* res = ({ _Static_assert')
2382 assert src.contains('d->values = ({ _Static_assert')
2383 assert_freestanding_alloc_refusal_without_hosted_heap(src)
2384}
2385
2386fn test_freestanding_skip_builtin_runtime_heap_helpers_refuse_in_generated_c() {
2387 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) { array__push(0, 0); string__plus((string){0}, (string){0}); new_map(); }', [
2388 'output',
2389 'panic',
2390 'alloc',
2391 ])
2392 assert_freestanding_heap_runtime_refusal(src, 'array__push')
2393 assert_freestanding_heap_runtime_refusal(src, 'string__plus')
2394 assert_freestanding_heap_runtime_refusal(src, 'new_map')
2395 assert_no_raw_heap_runtime(src)
2396}
2397
2398fn test_freestanding_skip_builtin_runtime_heap_helper_table_refuses_each_helper() {
2399 for helper in freestanding_heap_runtime_helper_names() {
2400 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) { ${helper}(); }', [
2401 'output',
2402 'panic',
2403 'alloc',
2404 ])
2405 assert_freestanding_heap_runtime_refusal(src, helper)
2406 assert !src.contains('__attribute__((weak))')
2407 }
2408}
2409
2410fn test_runtime_symbol_scan_ignores_comments_and_string_literals() {
2411 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) {
2412 const char* literal = "array__push(0, 0)";
2413 /* array__push(0, 0); */
2414 // &array__push
2415 array__push_many(0, 0, 0);
2416}', [
2417 'output',
2418 'panic',
2419 'alloc',
2420 ])
2421 assert !src.contains('_Static_assert(0, "${freestanding_missing_heap_runtime_message}: array__push")')
2422 assert_freestanding_heap_runtime_refusal(src, 'array__push_many')
2423}
2424
2425fn test_runtime_symbol_scan_detects_bare_callback_identifier() {
2426 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) {
2427 array__sort_with_compare(0, compare_strings);
2428}', [
2429 'output',
2430 'panic',
2431 'alloc',
2432 ])
2433 assert_freestanding_heap_runtime_refusal(src, 'array__sort_with_compare')
2434 assert_freestanding_heap_runtime_refusal(src, 'compare_strings')
2435}
2436
2437fn test_freestanding_skip_builtin_structural_array_fallbacks_refuse_clearly() {
2438 for helper in ['Array_int_contains', 'Array_string_contains', 'Array_string_index',
2439 'Array_int_str'] {
2440 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) { ${helper}(); }', [
2441 'output',
2442 'panic',
2443 'alloc',
2444 ])
2445 assert_freestanding_heap_runtime_refusal(src, helper)
2446 }
2447}
2448
2449fn test_freestanding_skip_builtin_ierror_str_refuses_with_panic_hook() {
2450 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) { v_panic(IError__str((IError){0})); }', [
2451 'output',
2452 'panic',
2453 'alloc',
2454 ])
2455 assert src.contains('v_platform_panic(s.str, s.len);')
2456 assert_freestanding_heap_runtime_refusal(src, 'IError__str')
2457}
2458
2459fn test_freestanding_skip_builtin_string_lt_from_v_code_refuses_runtime_helper() {
2460 src := generated_c_for_target_program_with_hooks('freestanding_string_lt_runtime_helper', 'module main
2461
2462fn main() {
2463 a := "a"
2464 b := "b"
2465 _ := a < b
2466}
2467 ', [
2468 'output',
2469 'panic',
2470 'alloc',
2471 ])
2472 assert_freestanding_heap_runtime_refusal(src, 'string__lt')
2473}
2474
2475fn test_freestanding_skip_builtin_string_concat_from_v_code_refuses_runtime_helper() {
2476 src := generated_c_for_target_program_with_hooks('freestanding_string_concat_runtime_helper', 'module main
2477
2478fn main() {
2479 a := "a"
2480 b := "b"
2481 c := "c"
2482 _ := a + b + c
2483}
2484 ', [
2485 'output',
2486 'panic',
2487 'alloc',
2488 ])
2489 assert_freestanding_heap_runtime_refusal(src, 'string__plus_two')
2490}
2491
2492fn test_freestanding_skip_builtin_string_method_runtime_helpers_refuse_in_generated_c() {
2493 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) {
2494 string__contains((string){0}, (string){0});
2495 string__starts_with((string){0}, (string){0});
2496 string__ends_with((string){0}, (string){0});
2497 string__clone((string){0});
2498 string__split((string){0}, (string){0});
2499}', [
2500 'output',
2501 'panic',
2502 'alloc',
2503 ])
2504 assert_freestanding_heap_runtime_refusal(src, 'string__contains')
2505 assert_freestanding_heap_runtime_refusal(src, 'string__starts_with')
2506 assert_freestanding_heap_runtime_refusal(src, 'string__ends_with')
2507 assert_freestanding_heap_runtime_refusal(src, 'string__clone')
2508 assert_freestanding_heap_runtime_refusal(src, 'string__split')
2509}
2510
2511fn test_freestanding_skip_builtin_array_equality_from_v_code_refuses_runtime_helper() {
2512 src := generated_c_for_target_program_with_hooks('freestanding_array_eq_runtime_helper', 'module main
2513
2514fn main() {
2515 a := [1]
2516 b := [1]
2517 _ := a == b
2518}
2519 ', [
2520 'output',
2521 'panic',
2522 'alloc',
2523 ])
2524 assert_freestanding_heap_runtime_refusal(src, '__v2_array_eq')
2525}
2526
2527fn test_freestanding_skip_builtin_array_sort_runtime_helper_refuses_in_generated_c() {
2528 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) { array__sort(0, 0); array__sort_with_compare(0, 0); }', [
2529 'output',
2530 'panic',
2531 'alloc',
2532 ])
2533 assert_freestanding_heap_runtime_refusal(src, 'array__sort')
2534 assert_freestanding_heap_runtime_refusal(src, 'array__sort_with_compare')
2535}
2536
2537fn test_freestanding_skip_builtin_map_equality_from_v_code_refuses_runtime_helpers() {
2538 src := generated_c_for_target_program_with_hooks('freestanding_map_eq_runtime_helpers', 'module main
2539
2540fn main() {
2541 a := {
2542 "x": 1
2543 }
2544 b := {
2545 "x": 1
2546 }
2547 _ := a == b
2548}
2549 ', [
2550 'output',
2551 'panic',
2552 'alloc',
2553 ])
2554 assert_freestanding_heap_runtime_refusal(src, 'map__exists')
2555 assert_freestanding_heap_runtime_refusal(src, 'map__get')
2556 assert_freestanding_heap_runtime_refusal(src, 'DenseArray__has_index')
2557 assert_freestanding_heap_runtime_refusal(src, 'DenseArray__key')
2558 assert_freestanding_heap_runtime_refusal(src, 'DenseArray__value')
2559}
2560
2561fn test_freestanding_skip_builtin_map_method_runtime_helpers_refuse_in_generated_c() {
2562 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) {
2563 map__clear(0);
2564 map__delete(0, 0);
2565 map__keys(0);
2566 map__reserve(0, 0);
2567 map__move(0);
2568 map__values(0);
2569}', [
2570 'output',
2571 'panic',
2572 'alloc',
2573 ])
2574 assert_freestanding_heap_runtime_refusal(src, 'map__clear')
2575 assert_freestanding_heap_runtime_refusal(src, 'map__delete')
2576 assert_freestanding_heap_runtime_refusal(src, 'map__keys')
2577 assert_freestanding_heap_runtime_refusal(src, 'map__reserve')
2578 assert_freestanding_heap_runtime_refusal(src, 'map__move')
2579 assert_freestanding_heap_runtime_refusal(src, 'map__values')
2580}
2581
2582fn test_freestanding_skip_builtin_generic_array_contains_from_v_code_refuses_runtime_helper() {
2583 src := generated_c_for_target_program_with_hooks('freestanding_generic_array_contains_runtime_helper', 'module main
2584
2585struct Point {
2586 x int
2587}
2588
2589fn main() {
2590 points := [Point{
2591 x: 1
2592 }]
2593 needle := Point{
2594 x: 1
2595 }
2596 _ := needle in points
2597}
2598 ', [
2599 'output',
2600 'panic',
2601 'alloc',
2602 ])
2603 assert src.contains('_Static_assert(0, "${freestanding_missing_heap_runtime_message}: Array_')
2604 assert src.contains('_contains")')
2605 assert !src.contains('__attribute__((weak)) bool Array_')
2606 assert !src.contains('array__get(')
2607}
2608
2609fn test_freestanding_skip_builtin_string_substr_or_runtime_helper_refuses_in_generated_c() {
2610 src := runtime_fallbacks_for_existing_c_source_with_hooks_skip_builtin('void keep_generated_code(void) { string__substr_or((string){0}, 0, 1, (string){0}); }', [
2611 'output',
2612 'panic',
2613 'alloc',
2614 ])
2615 assert_freestanding_heap_runtime_refusal(src, 'string__substr_or')
2616}
2617
2618fn test_freestanding_panic_hook_runtime_fallback_avoids_hosted_exit() {
2619 src := runtime_fallbacks_for_called_functions_with_hooks(['panic'], ['panic'])
2620 assert src.contains('__attribute__((weak)) void v_panic(string s)')
2621 assert src.contains('v_platform_panic(s.str, s.len);')
2622 assert src.contains('for (;;) {}')
2623 assert !src.contains('C.exit')
2624 assert !src.contains('exit(')
2625}
2626
2627fn test_freestanding_without_panic_hook_runtime_fallback_refuses_without_unresolved_symbol() {
2628 src := runtime_fallbacks_for_called_functions_with_hooks(['panic'], ['output'])
2629 assert src.contains('__attribute__((weak)) void v_panic(string s)')
2630 assert src.contains('for (;;) {}')
2631 assert_freestanding_panic_refusal_without_hosted_exit(src)
2632}
2633
2634fn test_freestanding_hooks_for_existing_c_references_do_not_drop_generated_c() {
2635 src := runtime_fallbacks_for_existing_c_source_with_hooks('void keep_generated_code(void) { v_panic((string){0}); memdup(0, 1); }', [
2636 'output',
2637 'panic',
2638 'alloc',
2639 ])
2640 assert src.contains('void keep_generated_code(void)')
2641 assert src.contains('v_platform_panic(s.str, s.len);')
2642 assert src.contains('void* res = v_platform_malloc(sz);')
2643 assert_no_hosted_alloc_runtime(src)
2644 assert_no_raw_heap_runtime(src)
2645}
2646
2647fn test_freestanding_alloc_hook_generated_c_heap_address_uses_platform_alloc() {
2648 src := generated_c_for_target_program_with_hooks('freestanding_alloc_heap_address', 'module main
2649
2650struct Point {
2651 x int
2652}
2653
2654fn main() {
2655 p := &Point{
2656 x: 1
2657 }
2658 _ := p.x
2659}
2660 ', [
2661 'alloc',
2662 ])
2663 assert src.contains('v_platform_malloc(sizeof(Point))')
2664 assert_no_raw_heap_runtime(src)
2665}
2666
2667fn test_freestanding_without_alloc_hook_heap_address_refuses_without_hosted_alloc() {
2668 src := generated_c_for_target_program_with_hooks('freestanding_missing_alloc_heap_address', 'module main
2669
2670struct Point {
2671 x int
2672}
2673
2674fn main() {
2675 p := &Point{
2676 x: 1
2677 }
2678 _ := p.x
2679}
2680 ', [
2681 'output',
2682 'panic',
2683 ])
2684 assert_freestanding_alloc_refusal_without_hosted_heap(src)
2685}
2686
2687fn test_freestanding_without_alloc_hook_returned_local_clone_refuses_without_hosted_alloc() {
2688 src := generated_c_for_target_program_with_hooks('freestanding_missing_alloc_returned_local_clone', 'module main
2689
2690struct Point {
2691 x int
2692}
2693
2694fn make() &Point {
2695 value := Point{
2696 x: 1
2697 }
2698 ptr := &value
2699 return ptr
2700}
2701
2702fn main() {
2703 p := make()
2704 _ := p.x
2705}
2706 ', [
2707 'output',
2708 'panic',
2709 ])
2710 assert_freestanding_alloc_refusal_without_hosted_heap(src)
2711}
2712
2713fn test_freestanding_alloc_hook_interface_heap_cast_uses_platform_alloc() {
2714 src := generated_c_for_target_program_with_hooks('freestanding_alloc_interface_heap_cast', 'module main
2715
2716interface Runner {
2717 next() int
2718}
2719
2720struct Concrete {}
2721
2722fn (mut c Concrete) next() int {
2723 _ = c
2724 return 7
2725}
2726
2727fn make() &Runner {
2728 mut c := Concrete{}
2729 return &Runner(c)
2730}
2731
2732fn main() {
2733 r := make()
2734 _ := r.next()
2735}
2736 ', [
2737 'alloc',
2738 ])
2739 assert src.contains('Runner* _iface_t = (Runner*)v_platform_malloc(sizeof(Runner))')
2740 assert_no_raw_heap_runtime(src)
2741}
2742
2743fn test_freestanding_without_alloc_hook_interface_heap_cast_refuses_without_hosted_alloc() {
2744 src := generated_c_for_target_program_with_hooks('freestanding_missing_alloc_interface_heap_cast', 'module main
2745
2746interface Runner {
2747 next() int
2748}
2749
2750struct Concrete {}
2751
2752fn (mut c Concrete) next() int {
2753 _ = c
2754 return 7
2755}
2756
2757fn make() &Runner {
2758 mut c := Concrete{}
2759 return &Runner(c)
2760}
2761
2762fn main() {
2763 r := make()
2764 _ := r.next()
2765}
2766 ', [
2767 'output',
2768 'panic',
2769 ])
2770 assert_freestanding_alloc_refusal_without_hosted_heap(src)
2771}
2772
2773fn test_freestanding_alloc_hook_soa_helpers_use_platform_alloc() {
2774 src := soa_companion_for_hooks(['alloc'])
2775 assert src.contains('v_platform_malloc(cap * sizeof(int))')
2776 assert src.contains('memset(soa.x, 0, cap * sizeof(int));')
2777 assert src.contains('v_platform_realloc(soa->x, new_cap * sizeof(int))')
2778 assert src.contains('v_platform_free(soa->x);')
2779 assert_no_raw_heap_runtime(src)
2780}
2781
2782fn test_freestanding_without_alloc_hook_soa_helpers_refuse_without_hosted_alloc() {
2783 src := soa_companion_for_hooks(['output', 'panic'])
2784 assert src.contains('soa.x = (int*)({ _Static_assert')
2785 assert src.contains('soa->x = (int*)({ _Static_assert')
2786 assert src.contains('({ _Static_assert(0, "${freestanding_missing_alloc_hook_message}"); (void)0; });')
2787 assert_freestanding_alloc_refusal_without_hosted_heap(src)
2788}
2789
2790fn test_freestanding_minimal_hooks_runtime_and_heap_paths_avoid_raw_alloc() {
2791 hooks := ['output', 'panic', 'alloc']
2792 runtime_src := runtime_fallbacks_for_existing_c_source_with_hooks('void keep_generated_code(void) { v_panic((string){0}); memdup(0, 1); DenseArray__zeros_to_end(0); }',
2793 hooks)
2794 assert runtime_src.contains('v_platform_panic(s.str, s.len);')
2795 assert runtime_src.contains('v_platform_malloc(sz);')
2796 assert runtime_src.contains('v_platform_realloc(d->values, d->value_bytes * d->cap);')
2797 assert_no_raw_heap_runtime(runtime_src)
2798
2799 soa_src := soa_companion_for_hooks(hooks)
2800 assert soa_src.contains('v_platform_malloc(cap * sizeof(int))')
2801 assert soa_src.contains('v_platform_realloc(soa->x, new_cap * sizeof(int))')
2802 assert_no_raw_heap_runtime(soa_src)
2803}
2804
2805fn test_freestanding_fixed_array_voidptr_stringification_avoids_hosted_formatting() {
2806 src := fixed_array_voidptr_str_write_for_hooks(['alloc'])
2807 assert src.contains('uintptr_t _addr = (uintptr_t)(items[0]);')
2808 assert src.contains('"0123456789abcdef"')
2809 assert src.contains('strings__Builder__write_string(&sb')
2810 assert_no_hosted_formatting_or_abort_runtime(src)
2811}
2812
2813fn test_freestanding_unresolved_generic_stub_uses_panic_hook_without_hosted_abort() {
2814 src := unresolved_generic_stub_for_hooks(['panic'])
2815 assert src.contains('v_platform_panic((const u8*)')
2816 assert src.contains('for (;;) {}')
2817 assert !src.contains('#error')
2818 assert_no_hosted_formatting_or_abort_runtime(src)
2819}
2820
2821fn test_freestanding_unresolved_generic_stub_without_panic_hook_refuses_without_hosted_abort() {
2822 src := unresolved_generic_stub_for_hooks(['output'])
2823 assert src.contains('#error "v2: unresolved generic stub requires freestanding panic platform hook"')
2824 assert_no_hosted_formatting_or_abort_runtime(src)
2825}
2826