@@ -89,7 +89,28 @@ fn sanitize_staged_c_source(c_source string) string {
8989 source = source.replace ("Array_u8_contains(res, '.')" , "array__contains(res, &(u8[1]){'.'})" )
9090 // tos() buffer pointer cast:
9191 source = source.replace ('return tos(((u8)(&((u8*)buf.data)[((int)(0))])), i);' , 'return tos(&((u8*)buf.data)[((int)(0))], i);' )
92+ // Pointer field access: &logger._object -> &logger->_object
93+ // The C cast C.log__Logger(logger) is dropped by cleanc, leaving a missing -> dereference.
94+ source = source.replace ('&logger._object' , '&logger->_object' )
95+ // Closure capture function pointer: get_cert_callback returns !&SSLCerts, not void.
96+ // The if-guard on the result also needs restructuring for correct C semantics.
97+ source = source.replace ('(void (*)(mbedtls__SSLListener*, string))get_cert_callback' ,
98+ '((_result_mbedtls__SSLCertsptr (*)(mbedtls__SSLListener*, string))get_cert_callback)' )
99+ source = source.replace ('if ((((_result_mbedtls__SSLCertsptr (*)(mbedtls__SSLListener*, string))get_cert_callback))(l, host)) {\n\t\t _result_mbedtls__SSLCertsptr certs = (((_result_mbedtls__SSLCertsptr (*)(mbedtls__SSLListener*, string))get_cert_callback))(l, host);\n\t\t return mbedtls_ssl_set_hs_own_cert(ssl, &certs.client_cert, &certs.client_key);' ,
100+ '{ _result_mbedtls__SSLCertsptr _cert_res = (((_result_mbedtls__SSLCertsptr (*)(mbedtls__SSLListener*, string))get_cert_callback))(l, host);\n\t if (!_cert_res.is_error) {\n\t\t mbedtls__SSLCerts* certs = *(mbedtls__SSLCerts**)(((u8*)(&_cert_res.err)) + sizeof(IError));\n\t\t return mbedtls_ssl_set_hs_own_cert(ssl, &certs->client_cert, &certs->client_key);' )
101+ // UdpSocket result pointer auto-deref: .sock field is UdpSocket (value), result contains &UdpSocket.
102+ source = source.replace ('.sock = (*(net__UdpSocket**)(((u8*)(&_or_t54.err)) + sizeof(IError)))' ,
103+ '.sock = *(*(net__UdpSocket**)(((u8*)(&_or_t54.err)) + sizeof(IError)))' )
104+ // Generic function specialization: convert_voidptr_to_t_T -> convert_voidptr_to_t_f64
105+ source = source.replace ('sync__convert_voidptr_to_t_T(' , 'sync__convert_voidptr_to_t_f64(' )
92106 source = ensure_string_eq_impl (source)
107+ // ObjC .m file references g_vui_webview_cookie_val as a global variable.
108+ // The cleanc backend uses DarwinWebViewState singleton instead.
109+ // Add the global so the .m file can link against it.
110+ if ! source.contains ('g_vui_webview_cookie_val' )
111+ && source.contains ('webview__darwin_webview_state' ) {
112+ source = source + '\n string g_vui_webview_cookie_val;\n '
113+ }
93114 return source
94115}
95116
@@ -104,7 +125,13 @@ fn ensure_result_struct_decl(source string, struct_name string, elem_c_type stri
104125}
105126
106127fn ensure_string_eq_impl (source string ) string {
107- if source.contains ('bool string__eq(string a, string b) {' ) {
128+ has_ab := source.contains ('bool string__eq(string a, string b) {' )
129+ has_sa := source.contains ('bool string__eq(string s, string a) {' )
130+ // Remove duplicate: if both variants exist, strip the (a,b) variant body.
131+ if has_ab && has_sa {
132+ return replace_generated_c_fn (source, 'bool string__eq(string a, string b)' , '' )
133+ }
134+ if has_ab || has_sa {
108135 return source
109136 }
110137 return source + '\n ' +
@@ -476,7 +503,11 @@ fn (mut b Builder) gen_cleanc() {
476503 'out'
477504 }
478505
479- mut cc := configured_cc (b.pref.vroot)
506+ mut cc := if b.pref.ccompiler.len > 0 {
507+ b.pref.ccompiler
508+ } else {
509+ configured_cc (b.pref.vroot)
510+ }
480511 // -prod requires a real optimizing compiler — TCC cannot handle -O3/-flto.
481512 // Switch to system cc (gcc/clang) when the default compiler is TCC.
482513 if b.pref.is_prod && cc.contains ('tcc' ) {
@@ -589,7 +620,7 @@ fn (mut b Builder) gen_cleanc() {
589620 eprintln ('hint: use v2 compiled with v1 for proper C code generation' )
590621 return
591622 }
592- os.write_file (staged_c_file, c_source) or { panic (err) }
623+ os.write_file (staged_c_file, sanitize_staged_c_source ( c_source) ) or { panic (err) }
593624 println ('[*] Wrote ${staged_c_file} ' )
594625 b.compile_cleanc_executable (output_name, cc, cc_flags, cc_link_flags, error_limit_flag, mut
595626 sw)
@@ -668,7 +699,7 @@ fn (mut b Builder) gen_ssa_c() {
668699 // optimize.optimize(mut mod)
669700 // print_time('SSA Optimize', time.Duration(sw.elapsed() - stage_start))
670701
671- cc := configured_cc (b.pref.vroot)
702+ cc := if b.pref.ccompiler.len > 0 { b.pref.ccompiler } else { configured_cc (b.pref.vroot) }
672703 directive_flags := b.collect_cflags_from_sources ()
673704 mut cc_flag_parts := []string {}
674705 env_flags := configured_cflags ()
@@ -814,11 +845,16 @@ fn (mut b Builder) gen_cleanc_source_with_cache_init_calls(modules []string, cac
814845}
815846
816847fn (mut b Builder) gen_cleanc_source_with_options (modules []string , export_const_symbols bool , cache_bundle_name string , cached_init_calls []string , use_markused bool ) string {
817- mut gen_files := b.files.clone ()
848+ mut gen_files := []ast.File{cap: b.files.len}
849+ for file in b.files {
850+ gen_files << file
851+ }
818852 if cached_init_calls.len > 0 && b.used_vh_for_parse {
819853 mut p := parser.Parser.new (b.pref)
820854 header_files := p.parse_files (b.core_cached_parse_paths (), mut b.file_set)
821- gen_files << header_files
855+ for header_file in header_files {
856+ gen_files << header_file
857+ }
822858 }
823859 mut gen := cleanc.Gen.new_with_env_and_pref (gen_files, b.env, b.pref)
824860 if modules.len > 0 {
@@ -1299,14 +1335,13 @@ fn resolve_flag_path(path string, file_dir string, vmod_root string) string {
12991335 if os.is_abs_path (resolved) {
13001336 return resolved
13011337 }
1302- if resolved.starts_with ('./' ) || resolved.starts_with ('../' ) {
1303- return os.norm_path (os.join_path (file_dir, resolved))
1304- }
1305- return resolved
1338+ // Resolve any relative path (including bare relative like 'r/qrcodegen')
1339+ // relative to the source file's directory, matching V1 behavior
1340+ return os.norm_path (os.join_path (file_dir, resolved))
13061341}
13071342
13081343fn normalize_flag_value_for_file (flag_value string , file_path string ) string {
1309- file_dir := os.dir (file_path)
1344+ file_dir := os.dir (os. real_path ( file_path) )
13101345 vmod_root := find_vmod_root_for_file (file_path)
13111346 mut tokens := flag_value.fields ()
13121347 mut out := []string {}
@@ -1334,8 +1369,9 @@ fn normalize_flag_value_for_file(flag_value string, file_path string) string {
13341369 i++
13351370 continue
13361371 }
1337- if tok.contains ('@VMODROOT' ) || tok.starts_with ('./' ) || tok.starts_with ('../' )
1338- || tok.ends_with ('.c' ) || tok.ends_with ('.m' ) || tok.ends_with ('.o' ) {
1372+ if tok.contains ('@VMODROOT' ) || tok.contains ('@VEXEROOT' ) || tok.starts_with ('./' )
1373+ || tok.starts_with ('../' ) || tok.ends_with ('.c' ) || tok.ends_with ('.m' )
1374+ || tok.ends_with ('.o' ) {
13391375 out << resolve_flag_path (tok, file_dir, vmod_root)
13401376 i++
13411377 continue
@@ -1377,7 +1413,7 @@ fn parse_flag_directive_line(line string, file_path string) ?string {
13771413 return normalize_flag_value_for_file (rest, file_path)
13781414}
13791415
1380- fn flag_references_missing_file (flag string ) bool {
1416+ fn flag_references_missing_file (flag string , include_flags [] string ) bool {
13811417 for tok in flag.fields () {
13821418 clean := tok.trim ('"' ).trim ("'" )
13831419 if clean.len == 0 {
@@ -1391,7 +1427,8 @@ fn flag_references_missing_file(flag string) bool {
13911427 if clean.ends_with ('.o' ) {
13921428 c_file := clean[..clean.len - 2 ] + '.c'
13931429 if os.exists (c_file) {
1394- compile_cmd := 'cc -c -w -O2 "${c_file} " -o "${clean} "'
1430+ inc_flags := include_flags.join (' ' )
1431+ compile_cmd := 'cc -c -w -O2 ${inc_flags} "${c_file} " -o "${clean} "'
13951432 res := os.execute (compile_cmd)
13961433 if res.exit_code == 0 {
13971434 continue // successfully compiled, not missing
@@ -1467,9 +1504,18 @@ fn (b &Builder) collect_cflags_from_sources() string {
14671504 if skip_depth > 0 {
14681505 continue
14691506 }
1470- mut flag := parse_flag_directive_line (line, scan_path) or { continue }
1471- flag = flag.replace ('@VEXEROOT' , b.pref.vroot).replace ('VEXEROOT' , b.pref.vroot)
1472- if flag_references_missing_file (flag) {
1507+ // Replace @VEXEROOT before parsing so path normalization sees absolute paths
1508+ resolved_line := line.replace ('@VEXEROOT' , b.pref.vroot).replace ('VEXEROOT' ,
1509+ b.pref.vroot)
1510+ mut flag := parse_flag_directive_line (resolved_line, scan_path) or { continue }
1511+ // Build include flags from already-collected flags for compiling missing .o files
1512+ mut inc_flags := []string {}
1513+ for f in flags {
1514+ if f.starts_with ('-I' ) {
1515+ inc_flags << f
1516+ }
1517+ }
1518+ if flag_references_missing_file (flag, inc_flags) {
14731519 continue
14741520 }
14751521 if flag == '' || flag in seen {
@@ -1501,9 +1547,19 @@ fn split_compile_and_link_flags(flags string) (string, string) {
15011547 }
15021548 } else if tok.starts_with ('-l' ) || tok.starts_with ('-L' ) {
15031549 link << tok
1550+ // -L or -l alone (space-separated from its argument): grab the next token
1551+ if (tok == '-L' || tok == '-l' ) && i + 1 < tokens.len {
1552+ i++
1553+ link << tokens[i]
1554+ }
15041555 } else if tok.ends_with ('.o' ) || tok.ends_with ('.obj' ) || tok.ends_with ('.a' )
15051556 || tok.ends_with ('.so' ) || tok.ends_with ('.dylib' ) {
15061557 link << tok
1558+ } else if tok == '-I' && i + 1 < tokens.len {
1559+ // -I alone (space-separated from its argument): grab the next token
1560+ compile << tok
1561+ i++
1562+ compile << tokens[i]
15071563 } else {
15081564 compile << tok
15091565 }
0 commit comments