v / vlib / v2 / builder / cache_headers.v
3186 lines · 3029 sloc · 86.56 KB · e7738c112c787d477501fa4a87edd0e1d72159bd
Raw
1module builder
2
3import os
4import v2.ast
5import v2.gen.v
6import v2.parser
7import v2.types
8
9const core_cached_module_paths = [
10 'builtin',
11 'strconv',
12 'strings',
13 'hash',
14 'math.bits',
15 'os',
16 'time',
17 'term',
18 'term.termios',
19 'os.cmdline',
20 'encoding.binary',
21 'crypto.sha256',
22 'strings.textscanner',
23]
24
25const core_cached_module_names = [
26 'builtin',
27 'strconv',
28 'strings',
29 'hash',
30 'bits',
31 'os',
32 'time',
33 'term',
34 'termios',
35 'cmdline',
36 'binary',
37 'sha256',
38 'textscanner',
39]
40
41const builtin_cache_name = 'builtin'
42
43const builtin_cached_module_paths = ['builtin', 'strconv', 'strings', 'hash', 'math.bits']
44
45const builtin_cached_module_names = ['builtin', 'strconv', 'strings', 'hash', 'bits']
46
47const vlib_cache_name = 'vlib'
48
49const vlib_cached_module_paths = [
50 'os',
51 'time',
52 'term',
53 'term.termios',
54 'os.cmdline',
55 'encoding.binary',
56 'crypto.sha256',
57 'strings.textscanner',
58]
59
60const vlib_cached_module_names = [
61 'os',
62 'time',
63 'term',
64 'termios',
65 'cmdline',
66 'binary',
67 'sha256',
68 'textscanner',
69]
70
71const veb_cache_name = 'veb'
72
73const veb_cached_module_paths = [
74 'veb',
75 'fasthttp',
76 'io',
77 'net',
78 'net.http',
79 'net.http.chunked',
80 'net.conv',
81 'net.openssl',
82 'net.socks',
83 'net.ssl',
84 'net.urllib',
85 'compress',
86 'compress.gzip',
87 'compress.zlib',
88 'compress.zstd',
89 'hash.crc32',
90 'math.big',
91 'sync',
92 'sync.stdatomic',
93 'x.json2',
94 'runtime',
95 'arrays',
96 'encoding.html',
97]
98
99const veb_cached_module_names = [
100 'veb',
101 'fasthttp',
102 'io',
103 'net',
104 'http',
105 'chunked',
106 'conv',
107 'openssl',
108 'socks',
109 'ssl',
110 'urllib',
111 'compress',
112 'gzip',
113 'zlib',
114 'zstd',
115 'crc32',
116 'big',
117 'sync',
118 'stdatomic',
119 'json2',
120 'runtime',
121 'arrays',
122 'html',
123]
124
125const v2compiler_cache_name = 'v2compiler'
126
127const imports_cache_name = 'imports'
128
129const virtuals_cache_name = 'virtuals'
130
131struct CachedImportModule {
132 import_path string
133 module_name string
134}
135
136struct CachedVirtualModule {
137 name string
138 header_name string
139 source_files []string
140}
141
142const v2compiler_cached_module_paths = [
143 'v2.ast',
144 'v2.abi',
145 'v2.builder',
146 'v2.errors',
147 'v2.eval',
148 'v2.gen.arm64',
149 'v2.gen.c',
150 'v2.gen.cleanc',
151 'v2.gen.v',
152 'v2.gen.x64',
153 'v2.insel',
154 'v2.markused',
155 'v2.mir',
156 'v2.parser',
157 'v2.pref',
158 'v2.scanner',
159 'v2.ssa',
160 'v2.ssa.optimize',
161 'v2.token',
162 'v2.transformer',
163 'v2.types',
164]
165
166const v2compiler_cached_module_names = [
167 'ast',
168 'abi',
169 'builder',
170 'errors',
171 'eval',
172 'arm64',
173 'c',
174 'cleanc',
175 'v',
176 'x64',
177 'insel',
178 'markused',
179 'mir',
180 'parser',
181 'pref',
182 'scanner',
183 'ssa',
184 'optimize',
185 'token',
186 'transformer',
187 'types',
188]
189
190const core_cache_format = 'cc15'
191
192const core_headers_format = 'vh49'
193
194const core_cache_compiler_dependency_dirs = [
195 'vlib/v2/abi',
196 'vlib/v2/ast',
197 'vlib/v2/builder',
198 'vlib/v2/errors',
199 'vlib/v2/eval',
200 'vlib/v2/gen/arm64',
201 'vlib/v2/gen/c',
202 'vlib/v2/gen/cleanc',
203 'vlib/v2/gen/v',
204 'vlib/v2/gen/x64',
205 'vlib/v2/insel',
206 'vlib/v2/markused',
207 'vlib/v2/mir',
208 'vlib/v2/parser',
209 'vlib/v2/pref',
210 'vlib/v2/scanner',
211 'vlib/v2/ssa',
212 'vlib/v2/ssa/optimize',
213 'vlib/v2/token',
214 'vlib/v2/transformer',
215 'vlib/v2/types',
216 'vlib/v2/util',
217]
218
219const core_cache_compiler_dependency_file_paths = ['cmd/v2/v2.v', 'vlib/net/openssl/openssl_compat.h']
220
221fn (b &Builder) core_cache_dir() string {
222 base := if b.pref.is_prod { 'v2_cleanc_obj_cache_prod' } else { 'v2_cleanc_obj_cache' }
223 root := if b.pref.vroot.len > 0 { b.pref.vroot } else { os.getwd() }
224 root_key := sanitize_cache_part(os.norm_path(os.abs_path(root)))
225 if root_key.len == 0 {
226 return cache_path_join(os.temp_dir(), base)
227 }
228 return cache_path_join(os.temp_dir(), '${base}_${root_key}')
229}
230
231fn (b &Builder) ensure_core_cache_dir() bool {
232 cache_dir := b.core_cache_dir()
233 if !os.exists(cache_dir) {
234 os.mkdir_all(cache_dir, mode: 0o700) or { return false }
235 }
236 if !os.is_dir(cache_dir) {
237 return false
238 }
239 if !os.is_readable(cache_dir) || !os.is_writable(cache_dir) {
240 os.chmod(cache_dir, 0o700) or {}
241 }
242 return os.is_readable(cache_dir) && os.is_writable(cache_dir)
243}
244
245fn (b &Builder) core_cache_obj_path() string {
246 return cache_path_join(b.core_cache_dir(), '${builtin_cache_name}.o')
247}
248
249fn (b &Builder) core_cache_stamp_path() string {
250 return cache_path_join(b.core_cache_dir(), '${builtin_cache_name}.stamp')
251}
252
253fn (b &Builder) core_headers_stamp_path() string {
254 return cache_path_join(b.core_cache_dir(), 'cached_modules.vh.stamp')
255}
256
257fn (b &Builder) imports_headers_stamp_path() string {
258 return cache_path_join(b.core_cache_dir(), '${imports_cache_name}.vh.stamp')
259}
260
261fn (b &Builder) v2compiler_headers_stamp_path() string {
262 return cache_path_join(b.core_cache_dir(), '${v2compiler_cache_name}.vh.stamp')
263}
264
265fn (b &Builder) imports_manifest_path() string {
266 return cache_path_join(b.core_cache_dir(), '${imports_cache_name}.manifest')
267}
268
269fn (b &Builder) virtuals_headers_stamp_path() string {
270 return cache_path_join(b.core_cache_dir(), '${virtuals_cache_name}.vh.stamp')
271}
272
273fn (b &Builder) virtuals_manifest_path() string {
274 return cache_path_join(b.core_cache_dir(), '${virtuals_cache_name}.manifest')
275}
276
277fn (b &Builder) core_header_path(module_name string) string {
278 return cache_path_join(b.core_cache_dir(), '${module_name}.vh')
279}
280
281fn cache_path_join(dir string, file string) string {
282 base := dir.trim_right('/\\')
283 if base == '' {
284 return file
285 }
286 return '${base}/${file}'
287}
288
289fn (b &Builder) core_header_paths() []string {
290 mut paths := []string{cap: core_cached_module_names.len}
291 for module_name in core_cached_module_names {
292 paths << b.core_header_path(module_name)
293 }
294 return paths
295}
296
297fn (b &Builder) cached_header_paths() []string {
298 mut paths := b.core_header_paths()
299 for module_name in veb_cached_module_names {
300 paths << b.core_header_path(module_name)
301 }
302 return paths
303}
304
305fn (b &Builder) v2compiler_header_paths() []string {
306 mut paths := []string{cap: v2compiler_cached_module_names.len}
307 for module_name in v2compiler_cached_module_names {
308 paths << b.core_header_path(module_name)
309 }
310 return paths
311}
312
313fn cached_header_module_paths() []string {
314 mut paths := core_cached_module_paths.clone()
315 for module_path in veb_cached_module_paths {
316 paths << module_path
317 }
318 return paths
319}
320
321fn cached_header_module_names() []string {
322 mut names := core_cached_module_names.clone()
323 for module_name in veb_cached_module_names {
324 names << module_name
325 }
326 return names
327}
328
329fn (b &Builder) core_cached_parse_paths() []string {
330 return b.core_header_paths()
331}
332
333// vlib_only_header_paths returns .vh paths for only the builtin+vlib modules
334// (not v2compiler modules). Used when generating the main .c file to avoid
335// type/function conflicts with the v2compiler.o cached object.
336fn (b &Builder) vlib_only_header_paths() []string {
337 mut paths := []string{cap: builtin_cached_module_names.len + vlib_cached_module_names.len}
338 for module_name in builtin_cached_module_names {
339 paths << b.core_header_path(module_name)
340 }
341 for module_name in vlib_cached_module_names {
342 paths << b.core_header_path(module_name)
343 }
344 return paths
345}
346
347fn (b &Builder) use_builtin_header_for_parse() bool {
348 for file in b.user_files {
349 if os.file_name(file) == 'v2.v' {
350 return false
351 }
352 }
353 return true
354}
355
356fn (b &Builder) module_source_files(modules []string) []string {
357 mut files_set := map[string]bool{}
358 for module_name in modules {
359 module_files := b.source_files_for_module_name(module_name)
360 for file in module_files {
361 files_set[file] = true
362 }
363 }
364 mut files := files_set.keys()
365 files.sort()
366 return files
367}
368
369fn (b &Builder) source_files_for_module_name(module_name string) []string {
370 mut files_set := map[string]bool{}
371 if b.uses_flat_module_enumeration() {
372 // In flat mode b.files is dropped; source the actual parsed file
373 // paths from the flat cursors. This is required for external (non-vlib)
374 // modules whose location the disk-scan fallback below cannot resolve.
375 for i in 0 .. b.flat.files.len {
376 if b.flat_file_module_name(i) != module_name {
377 continue
378 }
379 name := b.flat.file_name(b.flat.files[i])
380 if name == '' || name.ends_with('.vh') {
381 continue
382 }
383 files_set[name] = true
384 }
385 } else {
386 for file in b.files {
387 if ast_file_module_name(file) != module_name {
388 continue
389 }
390 if file.name == '' || file.name.ends_with('.vh') {
391 continue
392 }
393 files_set[file.name] = true
394 }
395 }
396 if files_set.len == 0 {
397 module_path := b.module_name_to_path(module_name)
398 module_dir := b.pref.get_vlib_module_path(module_path)
399 for file in get_v_files_from_dir(module_dir, b.pref.user_defines,
400 b.pref.source_filter_target_os()) {
401 files_set[file] = true
402 }
403 }
404 mut files := files_set.keys()
405 files.sort()
406 return files
407}
408
409fn (b &Builder) core_cache_compiler_dependency_files() []string {
410 root := if b.pref.vroot.len > 0 { b.pref.vroot } else { os.getwd() }
411 mut files_set := map[string]bool{}
412 for rel_dir in core_cache_compiler_dependency_dirs {
413 dir := os.join_path(root, rel_dir)
414 if !os.is_dir(dir) {
415 continue
416 }
417 for file in get_v_files_from_dir(dir, b.pref.user_defines, b.pref.source_filter_target_os()) {
418 files_set[os.norm_path(file)] = true
419 }
420 }
421 for rel_file in core_cache_compiler_dependency_file_paths {
422 file := os.join_path(root, rel_file)
423 if os.exists(file) {
424 files_set[os.norm_path(file)] = true
425 }
426 }
427 mut files := files_set.keys()
428 files.sort()
429 return files
430}
431
432fn (b &Builder) user_entry_stamp_files() []string {
433 mut files_set := map[string]bool{}
434 user_defines := if b.pref != unsafe { nil } { b.pref.user_defines } else { []string{} }
435 for file in b.user_files {
436 if os.is_dir(file) {
437 mut found_parsed_files := false
438 for parsed_file in b.files {
439 if parsed_file.name == '' || parsed_file.name.ends_with('.vh') {
440 continue
441 }
442 relative_path_under_root(file, parsed_file.name) or { continue }
443 files_set[os.norm_path(parsed_file.name)] = true
444 found_parsed_files = true
445 }
446 if found_parsed_files {
447 continue
448 }
449 for source_file in get_user_v_files_from_dir(file, user_defines,
450 b.pref.source_filter_target_os()) {
451 if source_file == '' || source_file.ends_with('.vh') {
452 continue
453 }
454 files_set[os.norm_path(source_file)] = true
455 }
456 continue
457 }
458 files_set[os.norm_path(file)] = true
459 }
460 mut files := files_set.keys()
461 files.sort()
462 return files
463}
464
465fn sanitize_cache_part(name string) string {
466 mut out := []u8{cap: name.len}
467 for ch in name {
468 if (ch >= `a` && ch <= `z`) || (ch >= `A` && ch <= `Z`)
469 || (ch >= `0` && ch <= `9`) || ch == `_` {
470 out << u8(ch)
471 } else {
472 out << `_`
473 }
474 }
475 res := out.bytestr().trim('_')
476 if res == '' {
477 return 'unnamed'
478 }
479 return res
480}
481
482fn sanitize_staged_c_source(source string) string {
483 return sanitize_channel_semaphore_waits(source)
484}
485
486fn sanitize_cached_main_c_source(source string) string {
487 return sanitize_c_typedef_names(sanitize_channel_semaphore_waits(source))
488}
489
490fn sanitize_cached_object_c_source(source string) string {
491 mut sanitized := sanitize_c_typedef_names(source)
492 sanitized = guard_cached_stdatomic_compat_includes(sanitized)
493 return sanitized
494}
495
496fn sanitize_channel_semaphore_waits(source string) string {
497 lines := source.split_into_lines()
498 mut out := []string{cap: lines.len}
499 prefix := 'sync__Semaphore__wait('
500 for line in lines {
501 mut fixed := line
502 if idx := fixed.index(prefix) {
503 arg_start := idx + prefix.len
504 if arg_start < fixed.len && fixed[arg_start] != `&` {
505 arg_end := fixed.index_after(')', arg_start) or { -1 }
506 if arg_end > arg_start {
507 arg := fixed[arg_start..arg_end]
508 if arg.contains('->writesem') || arg.contains('->readsem') {
509 fixed = fixed[..arg_start] + '&' + fixed[arg_start..]
510 }
511 }
512 }
513 }
514 out << fixed
515 }
516 return out.join('\n')
517}
518
519fn sanitize_c_typedef_names(source string) string {
520 mut out := source
521 for name in ['atomic_uintptr_t', 'pthread_rwlockattr_t', 'pthread_condattr_t'] {
522 out = out.replace('struct ${name}', name)
523 }
524 return out
525}
526
527fn guard_cached_stdatomic_compat_includes(source string) string {
528 if !source.contains('/thirdparty/stdatomic/nix/atomic.h') {
529 return source
530 }
531 lines := source.split_into_lines()
532 mut out := []string{cap: lines.len + 8}
533 for line in lines {
534 trimmed := line.trim_space()
535 if trimmed == '#include <stdatomic.h>' {
536 out << '#ifndef __TINYC__'
537 out << line
538 out << '#endif'
539 continue
540 }
541 if trimmed.starts_with('#include ')
542 && trimmed.contains('/thirdparty/stdatomic/nix/atomic.h') {
543 out << '#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)'
544 out << '#define extern static'
545 out << '#endif'
546 out << line
547 out << '#if defined(__TINYC__) && defined(__APPLE__) && defined(__aarch64__)'
548 out << '#undef extern'
549 out << '#endif'
550 continue
551 }
552 out << line
553 }
554 return out.join('\n')
555}
556
557fn virtual_header_name(name string) string {
558 return 'main_${sanitize_cache_part(name)}'
559}
560
561fn (b &Builder) virtual_module_root() string {
562 for file in b.user_files {
563 if file != '' && os.is_dir(file) {
564 return os.norm_path(os.abs_path(file))
565 }
566 }
567 if b.user_files.len > 0 && b.user_files[0] != '' {
568 return os.norm_path(os.abs_path(os.dir(b.user_files[0])))
569 }
570 return os.norm_path(os.getwd())
571}
572
573fn relative_path_under_root(root string, file string) ?string {
574 if root == '' || file == '' {
575 return none
576 }
577 root_norm := os.norm_path(os.abs_path(root)).replace('\\', '/').trim_right('/')
578 file_norm := os.norm_path(os.abs_path(file)).replace('\\', '/')
579 if file_norm == root_norm {
580 return ''
581 }
582 prefix := root_norm + '/'
583 if !file_norm.starts_with(prefix) {
584 return none
585 }
586 return file_norm[prefix.len..]
587}
588
589fn (b &Builder) virtual_main_group_for_path(file string) ?string {
590 rel := relative_path_under_root(b.virtual_module_root(), file) or { return none }
591 if rel == '' || !rel.contains('/') {
592 return none
593 }
594 group := rel.all_before('/')
595 if group == '' || group.starts_with('.') {
596 return none
597 }
598 return group
599}
600
601fn strip_leading_source_attributes(line string) string {
602 mut rest := line.trim_space()
603 for {
604 if rest.starts_with('@[') {
605 end := rest.index(']') or { return rest }
606 rest = rest[end + 1..].trim_space()
607 continue
608 }
609 if rest.starts_with('[') {
610 end := rest.index(']') or { return rest }
611 rest = rest[end + 1..].trim_space()
612 continue
613 }
614 break
615 }
616 return rest
617}
618
619fn source_line_declares_executable_main(line string) bool {
620 mut rest := strip_leading_source_attributes(line)
621 if rest.len == 0 || rest.starts_with('//') {
622 return false
623 }
624 if rest.starts_with('pub ') {
625 rest = rest[4..].trim_space()
626 }
627 if !rest.starts_with('fn') {
628 return false
629 }
630 rest = rest[2..].trim_space()
631 return rest.starts_with('main(') || rest.starts_with('main (')
632}
633
634fn source_file_declares_executable_main(path string) bool {
635 lines := os.read_lines(path) or { return false }
636 for line in lines {
637 if source_line_declares_executable_main(line) {
638 return true
639 }
640 }
641 return false
642}
643
644fn ast_file_declares_executable_main(file ast.File) bool {
645 for stmt in file.stmts {
646 if stmt is ast.FnDecl && !stmt.is_method && !stmt.is_static && stmt.language == .v
647 && stmt.name == 'main' {
648 return true
649 }
650 }
651 return false
652}
653
654// flat_file_declares_executable_main is the flat-cursor analogue of
655// `ast_file_declares_executable_main`: it scans a file's top-level statement
656// cursors for a non-method, non-static, `.v`-language `fn main`. All four
657// conditions must match the legacy predicate so virtual main-module grouping
658// stays bit-identical to the `b.files` path.
659fn flat_file_declares_executable_main(fc ast.FileCursor) bool {
660 stmts := fc.stmts()
661 for j in 0 .. stmts.len() {
662 c := stmts.at(j)
663 if c.kind() != .stmt_fn_decl {
664 continue
665 }
666 if c.flag(ast.flag_is_method) || c.flag(ast.flag_is_static) {
667 continue
668 }
669 if unsafe { ast.Language(int(c.aux())) } != .v {
670 continue
671 }
672 if c.name() == 'main' {
673 return true
674 }
675 }
676 return false
677}
678
679fn (b &Builder) collect_virtual_main_modules() []CachedVirtualModule {
680 mut grouped := map[string][]string{}
681 mut groups_with_main := map[string]bool{}
682 if b.uses_flat_module_enumeration() {
683 for i in 0 .. b.flat.files.len {
684 fc := b.flat.file_cursor(i)
685 name := fc.name()
686 if name == '' || name.ends_with('.vh') || b.flat_file_module_name(i) != 'main' {
687 continue
688 }
689 group := b.virtual_main_group_for_path(name) or { continue }
690 if flat_file_declares_executable_main(fc) {
691 groups_with_main[group] = true
692 continue
693 }
694 mut files := grouped[group] or { []string{} }
695 files << name
696 grouped[group] = files
697 }
698 } else {
699 for file in b.files {
700 if file.name == '' || file.name.ends_with('.vh') || ast_file_module_name(file) != 'main' {
701 continue
702 }
703 group := b.virtual_main_group_for_path(file.name) or { continue }
704 if ast_file_declares_executable_main(file) {
705 groups_with_main[group] = true
706 continue
707 }
708 mut files := grouped[group] or { []string{} }
709 files << file.name
710 grouped[group] = files
711 }
712 }
713 for group, _ in groups_with_main {
714 grouped.delete(group)
715 }
716 return cached_virtual_modules_from_grouped_files(grouped)
717}
718
719fn (b &Builder) collect_virtual_main_modules_from_paths(paths []string) []CachedVirtualModule {
720 mut grouped := map[string][]string{}
721 mut groups_with_main := map[string]bool{}
722 for path in paths {
723 if path == '' || path.ends_with('.vh') || !os.exists(path) {
724 continue
725 }
726 if file_module_name(path) or { '' } != 'main' {
727 continue
728 }
729 group := b.virtual_main_group_for_path(path) or { continue }
730 if source_file_declares_executable_main(path) {
731 groups_with_main[group] = true
732 continue
733 }
734 mut files := grouped[group] or { []string{} }
735 files << path
736 grouped[group] = files
737 }
738 for group, _ in groups_with_main {
739 grouped.delete(group)
740 }
741 return cached_virtual_modules_from_grouped_files(grouped)
742}
743
744fn cached_virtual_modules_from_grouped_files(grouped map[string][]string) []CachedVirtualModule {
745 mut groups := []CachedVirtualModule{cap: grouped.len}
746 mut names := grouped.keys()
747 names.sort()
748 for name in names {
749 mut files := grouped[name].clone()
750 files.sort()
751 groups << CachedVirtualModule{
752 name: name
753 header_name: virtual_header_name(name)
754 source_files: files
755 }
756 }
757 return groups
758}
759
760fn virtual_module_source_files(groups []CachedVirtualModule) []string {
761 mut files := []string{}
762 mut seen := map[string]bool{}
763 for group in groups {
764 for file in group.source_files {
765 norm_file := os.norm_path(file)
766 if norm_file in seen {
767 continue
768 }
769 seen[norm_file] = true
770 files << file
771 }
772 }
773 files.sort()
774 return files
775}
776
777fn virtual_module_names(groups []CachedVirtualModule) []string {
778 mut names := []string{cap: groups.len}
779 for group in groups {
780 names << group.name
781 }
782 return names
783}
784
785fn filter_out_source_files(files []string, excluded []string) []string {
786 mut excluded_set := map[string]bool{}
787 for file in excluded {
788 excluded_set[os.norm_path(file)] = true
789 excluded_set[os.norm_path(os.abs_path(file))] = true
790 }
791 mut out := []string{cap: files.len}
792 for file in files {
793 if source_file_in_set(file, excluded_set) {
794 continue
795 }
796 out << file
797 }
798 return out
799}
800
801fn source_file_in_set(file string, file_set map[string]bool) bool {
802 norm_file := os.norm_path(file)
803 abs_file := os.norm_path(os.abs_path(file))
804 if norm_file in file_set || abs_file in file_set {
805 return true
806 }
807 for excluded, _ in file_set {
808 trimmed := excluded.trim_left('./')
809 if trimmed == '' {
810 continue
811 }
812 if norm_file == trimmed || abs_file == trimmed {
813 return true
814 }
815 if norm_file.ends_with('/${trimmed}') || abs_file.ends_with('/${trimmed}') {
816 return true
817 }
818 }
819 return false
820}
821
822fn (b &Builder) cache_stamp_for_modules(cache_name string, modules []string, cc string, cc_flags string, cc_link_flags string, use_markused bool) string {
823 source_files := b.module_source_files(modules)
824 compiler_files := b.core_cache_compiler_dependency_files()
825 mut lines := []string{cap: source_files.len + compiler_files.len + 10}
826 lines << 'cache=${cache_name}'
827 lines << 'format=${core_cache_format}'
828 lines << 'cc=${cc}'
829 lines << 'cc_flags=${cc_flags}'
830 lines << 'cc_link_flags=${cc_link_flags}'
831 lines << 'use_markused=${use_markused}'
832 lines << 'context_alloc=${b.pref.use_context_allocator}'
833 lines << 'target_os=${b.pref.target_os_or_host()}'
834 // Include user entry files in cache stamp: the transformer injects
835 // helper functions (str, eq, sort comparators) into builtin module AST
836 // based on types from the user's source file. Different source files
837 // produce different generated functions, so the cache must invalidate.
838 for file in b.user_entry_stamp_files() {
839 lines << 'entry:${file}:${os.file_last_mod_unix(file)}'
840 }
841 for file in source_files {
842 lines << '${file}:${os.file_last_mod_unix(file)}'
843 }
844 for file in compiler_files {
845 lines << '${file}:${os.file_last_mod_unix(file)}'
846 }
847 return lines.join('\n')
848}
849
850fn (b &Builder) core_cache_context_stamp() string {
851 mut files := b.user_files.clone()
852 files.sort()
853 mut lines := []string{cap: files.len + 1}
854 for file in files {
855 norm_file := os.norm_path(file)
856 lines << '${norm_file}:${os.file_last_mod_unix(norm_file)}'
857 }
858 lines << 'entry_count=${files.len}'
859 return lines.join('|')
860}
861
862fn (b &Builder) header_stamp_for_modules(modules []string) string {
863 source_files := b.module_source_files(modules)
864 compiler_files := b.core_cache_compiler_dependency_files()
865 entry_files := b.user_entry_stamp_files()
866 mut lines := []string{cap: source_files.len + compiler_files.len + entry_files.len + 4}
867 lines << 'format=${core_headers_format}'
868 lines << 'target_os=${b.pref.target_os_or_host()}'
869 for file in entry_files {
870 lines << 'entry:${file}:${os.file_last_mod_unix(file)}'
871 }
872 for file in source_files {
873 lines << '${file}:${os.file_last_mod_unix(file)}'
874 }
875 for file in compiler_files {
876 lines << '${file}:${os.file_last_mod_unix(file)}'
877 }
878 return lines.join('\n')
879}
880
881fn (b &Builder) cache_dependency_stamp_lines(cache_names []string) []string {
882 mut lines := []string{cap: cache_names.len}
883 for cache_name in cache_names {
884 stamp_path := cache_path_join(b.core_cache_dir(), '${cache_name}.stamp')
885 if !os.exists(stamp_path) {
886 continue
887 }
888 lines << 'dependency:${cache_name}:${os.file_last_mod_unix(stamp_path)}'
889 }
890 return lines
891}
892
893fn (b &Builder) cache_stamp_for_parsed_modules(cache_name string, module_names []string, dependency_cache_names []string, cc string, cc_flags string, cc_link_flags string, use_markused bool) string {
894 source_files := b.module_source_files(module_names)
895 compiler_files := b.core_cache_compiler_dependency_files()
896 dependency_lines := b.cache_dependency_stamp_lines(dependency_cache_names)
897 mut lines := []string{cap: source_files.len + compiler_files.len + module_names.len +
898 dependency_lines.len + 10}
899 lines << 'cache=${cache_name}'
900 lines << 'format=${core_cache_format}'
901 lines << 'cc=${cc}'
902 lines << 'cc_flags=${cc_flags}'
903 lines << 'cc_link_flags=${cc_link_flags}'
904 lines << 'use_markused=${use_markused}'
905 lines << 'context_alloc=${b.pref.use_context_allocator}'
906 lines << 'target_os=${b.pref.target_os_or_host()}'
907 for module_name in module_names {
908 lines << 'module:${module_name}'
909 }
910 lines << dependency_lines
911 for file in b.user_entry_stamp_files() {
912 lines << 'entry:${file}:${os.file_last_mod_unix(file)}'
913 }
914 for file in source_files {
915 lines << 'source:${file}:${os.file_last_mod_unix(file)}'
916 }
917 for file in compiler_files {
918 lines << 'compiler:${file}:${os.file_last_mod_unix(file)}'
919 }
920 return lines.join('\n')
921}
922
923fn (b &Builder) cache_stamp_for_virtual_modules(groups []CachedVirtualModule, dependency_cache_names []string, cc string, cc_flags string, cc_link_flags string, use_markused bool) string {
924 source_files := virtual_module_source_files(groups)
925 compiler_files := b.core_cache_compiler_dependency_files()
926 dependency_lines := b.cache_dependency_stamp_lines(dependency_cache_names)
927 mut lines := []string{cap: source_files.len + compiler_files.len + groups.len +
928 dependency_lines.len + 10}
929 lines << 'cache=${virtuals_cache_name}'
930 lines << 'format=${core_cache_format}'
931 lines << 'cc=${cc}'
932 lines << 'cc_flags=${cc_flags}'
933 lines << 'cc_link_flags=${cc_link_flags}'
934 lines << 'use_markused=${use_markused}'
935 lines << 'context_alloc=${b.pref.use_context_allocator}'
936 lines << 'target_os=${b.pref.target_os_or_host()}'
937 for group in groups {
938 lines << 'virtual:${group.name}:${group.header_name}'
939 }
940 lines << dependency_lines
941 for file in b.user_entry_stamp_files() {
942 lines << 'entry:${file}:${os.file_last_mod_unix(file)}'
943 }
944 for file in source_files {
945 lines << 'source:${file}:${os.file_last_mod_unix(file)}'
946 }
947 for file in compiler_files {
948 lines << 'compiler:${file}:${os.file_last_mod_unix(file)}'
949 }
950 return lines.join('\n')
951}
952
953fn (b &Builder) imports_header_stamp_for_modules(imports []CachedImportModule, module_names []string) string {
954 source_files := b.module_source_files(module_names)
955 compiler_files := b.core_cache_compiler_dependency_files()
956 mut lines := []string{cap: source_files.len + compiler_files.len + imports.len + 6}
957 lines << 'cache=${imports_cache_name}'
958 lines << 'format=${core_headers_format}'
959 lines << 'target_os=${b.pref.target_os_or_host()}'
960 for import_mod in imports {
961 lines << 'import:${import_mod.import_path}:${import_mod.module_name}'
962 }
963 for file in b.user_entry_stamp_files() {
964 lines << 'entry:${file}:${os.file_last_mod_unix(file)}'
965 }
966 for file in source_files {
967 lines << 'source:${file}:${os.file_last_mod_unix(file)}'
968 }
969 for file in compiler_files {
970 lines << 'compiler:${file}:${os.file_last_mod_unix(file)}'
971 }
972 return lines.join('\n')
973}
974
975fn (b &Builder) virtuals_header_stamp_for_modules(groups []CachedVirtualModule) string {
976 source_files := virtual_module_source_files(groups)
977 compiler_files := b.core_cache_compiler_dependency_files()
978 mut lines := []string{cap: source_files.len + compiler_files.len + groups.len + 6}
979 lines << 'cache=${virtuals_cache_name}'
980 lines << 'format=${core_headers_format}'
981 lines << 'target_os=${b.pref.target_os_or_host()}'
982 for group in groups {
983 lines << 'virtual:${group.name}:${group.header_name}'
984 }
985 for file in b.user_entry_stamp_files() {
986 lines << 'entry:${file}:${os.file_last_mod_unix(file)}'
987 }
988 for file in source_files {
989 lines << 'source:${file}:${os.file_last_mod_unix(file)}'
990 }
991 for file in compiler_files {
992 lines << 'compiler:${file}:${os.file_last_mod_unix(file)}'
993 }
994 return lines.join('\n')
995}
996
997fn stamp_tracked_file_line(line string) (string, string, bool) {
998 mut path_start := -1
999 for prefix in ['entry:', 'source:', 'compiler:', 'emit:', 'compiler_exe:'] {
1000 if line.starts_with(prefix) {
1001 path_start = prefix.len
1002 break
1003 }
1004 }
1005 if path_start < 0 {
1006 if line.starts_with('/') || line.starts_with('./') || line.starts_with('../') {
1007 path_start = 0
1008 } else {
1009 return '', '', false
1010 }
1011 }
1012 colon_idx := line.last_index(':') or { return '', '', false }
1013 if colon_idx <= path_start {
1014 return '', '', false
1015 }
1016 return line[path_start..colon_idx], line[colon_idx + 1..], true
1017}
1018
1019fn stamp_file_lines_are_fresh(stamp string) bool {
1020 for line in stamp.split_into_lines() {
1021 file, expected_mtime, tracked := stamp_tracked_file_line(line)
1022 if !tracked {
1023 continue
1024 }
1025 if !os.exists(file) {
1026 return false
1027 }
1028 if '${os.file_last_mod_unix(file)}' != expected_mtime {
1029 return false
1030 }
1031 }
1032 return true
1033}
1034
1035// can_use_cached_core_headers_for_parse checks whether .vh header files
1036// exist, are non-empty, and their stamp matches the current source/compiler
1037// file timestamps. The .o stamp validation is skipped (the gen phase
1038// rebuilds stale .o files via ensure_cached_module_object), but the .vh
1039// stamp IS validated so that stale headers trigger a full parse — otherwise
1040// the gen phase would regenerate .o from incomplete .vh ASTs.
1041fn (b &Builder) can_use_cached_core_headers_for_parse() bool {
1042 if b.pref.no_cache || b.pref.skip_builtin {
1043 return false
1044 }
1045 if b.pref.backend != .cleanc {
1046 return false
1047 }
1048 if !b.ensure_core_cache_dir() {
1049 return false
1050 }
1051 // Validate .vh header stamp (no cc/cc_flags — only source + compiler timestamps).
1052 expected_header_stamp := b.header_stamp_for_modules(cached_header_module_paths())
1053 current_header_stamp := os.read_file(b.core_headers_stamp_path()) or { return false }
1054 if current_header_stamp != expected_header_stamp {
1055 return false
1056 }
1057 for header_path in b.cached_header_paths() {
1058 if !os.exists(header_path) {
1059 return false
1060 }
1061 if os.file_size(header_path) == 0 {
1062 return false
1063 }
1064 }
1065 for cache_name in [builtin_cache_name, vlib_cache_name] {
1066 if !os.exists(cache_path_join(b.core_cache_dir(), '${cache_name}.o'))
1067 || !os.exists(cache_path_join(b.core_cache_dir(), '${cache_name}.stamp')) {
1068 return false
1069 }
1070 }
1071 return true
1072}
1073
1074fn (b &Builder) cached_import_manifest() []CachedImportModule {
1075 manifest := os.read_file(b.imports_manifest_path()) or { return []CachedImportModule{} }
1076 mut imports := []CachedImportModule{}
1077 for line in manifest.split_into_lines() {
1078 if !line.starts_with('import:') {
1079 continue
1080 }
1081 rest := line['import:'.len..]
1082 sep_idx := rest.last_index(':') or { continue }
1083 import_path := rest[..sep_idx]
1084 module_name := rest[sep_idx + 1..]
1085 if import_path.len == 0 || module_name.len == 0 {
1086 continue
1087 }
1088 imports << CachedImportModule{
1089 import_path: import_path
1090 module_name: module_name
1091 }
1092 }
1093 return imports
1094}
1095
1096fn (b &Builder) cached_virtual_manifest() []CachedVirtualModule {
1097 manifest := os.read_file(b.virtuals_manifest_path()) or { return []CachedVirtualModule{} }
1098 mut header_by_group := map[string]string{}
1099 mut files_by_group := map[string][]string{}
1100 for line in manifest.split_into_lines() {
1101 if line.starts_with('virtual:') {
1102 rest := line['virtual:'.len..]
1103 sep_idx := rest.last_index(':') or { continue }
1104 name := rest[..sep_idx]
1105 header_name := rest[sep_idx + 1..]
1106 if name == '' || header_name == '' {
1107 continue
1108 }
1109 header_by_group[name] = header_name
1110 continue
1111 }
1112 if line.starts_with('source:') {
1113 rest := line['source:'.len..]
1114 sep_idx := rest.index(':') or { continue }
1115 name := rest[..sep_idx]
1116 source_file := rest[sep_idx + 1..]
1117 if name == '' || source_file == '' {
1118 continue
1119 }
1120 mut files := files_by_group[name] or { []string{} }
1121 files << source_file
1122 files_by_group[name] = files
1123 }
1124 }
1125 mut groups := []CachedVirtualModule{cap: header_by_group.len}
1126 mut names := header_by_group.keys()
1127 names.sort()
1128 for name in names {
1129 mut files := files_by_group[name] or { []string{} }
1130 files.sort()
1131 if files.len == 0 {
1132 continue
1133 }
1134 groups << CachedVirtualModule{
1135 name: name
1136 header_name: header_by_group[name]
1137 source_files: files
1138 }
1139 }
1140 return groups
1141}
1142
1143fn (b &Builder) can_use_cached_import_headers_for_parse() bool {
1144 // Import header reuse is only safe when the cached manifest covers the full
1145 // transitive import set. The parser currently discovers imports incrementally,
1146 // so a partial manifest can mix cached .vh files with fresh source modules and
1147 // leave the combined imports object stale.
1148 return false
1149}
1150
1151fn (b &Builder) can_use_cached_v2compiler_headers_for_parse() bool {
1152 if !b.is_cmd_v2_self_build() || b.pref.no_cache || b.pref.skip_builtin {
1153 return false
1154 }
1155 if !b.ensure_core_cache_dir() {
1156 return false
1157 }
1158 if !b.can_use_cached_module_bundle_for_parse(v2compiler_cache_name, false) {
1159 return false
1160 }
1161 expected_stamp := b.header_stamp_for_modules(v2compiler_cached_module_paths)
1162 current_stamp := os.read_file(b.v2compiler_headers_stamp_path()) or { return false }
1163 if current_stamp != expected_stamp || !stamp_file_lines_are_fresh(current_stamp) {
1164 return false
1165 }
1166 for header_path in b.v2compiler_header_paths() {
1167 if !os.exists(header_path) || os.file_size(header_path) == 0 {
1168 return false
1169 }
1170 }
1171 return true
1172}
1173
1174// v2compiler_headers_consumed_for_parse reports whether the generated v2compiler .vh module
1175// headers can ever be read back by the parser. v2compiler header parse-reuse is currently
1176// disabled (the generated headers are not yet complete/safe), so generating them is dead work on
1177// a cold self-build. Set V2_V2COMPILER_VH=1 to re-enable generation while iterating on that path.
1178fn (b &Builder) v2compiler_headers_consumed_for_parse() bool {
1179 return os.getenv('V2_V2COMPILER_VH') != ''
1180}
1181
1182fn (b &Builder) can_use_cached_virtual_headers_for_parse(groups []CachedVirtualModule) bool {
1183 if groups.len == 0 || b.pref.no_cache || b.pref.skip_builtin {
1184 return false
1185 }
1186 if !b.ensure_core_cache_dir() {
1187 return false
1188 }
1189 if !os.exists(cache_path_join(b.core_cache_dir(), '${virtuals_cache_name}.o'))
1190 || !os.exists(cache_path_join(b.core_cache_dir(), '${virtuals_cache_name}.stamp')) {
1191 return false
1192 }
1193 expected_stamp := b.virtuals_header_stamp_for_modules(groups)
1194 current_stamp := os.read_file(b.virtuals_headers_stamp_path()) or { return false }
1195 if current_stamp != expected_stamp || !stamp_file_lines_are_fresh(current_stamp) {
1196 return false
1197 }
1198 manifest_groups := b.cached_virtual_manifest()
1199 if manifest_groups.len != groups.len {
1200 return false
1201 }
1202 for group in groups {
1203 header_path := b.core_header_path(group.header_name)
1204 if !os.exists(header_path) || os.file_size(header_path) == 0 {
1205 return false
1206 }
1207 }
1208 return true
1209}
1210
1211fn (b &Builder) replace_virtual_sources_with_headers(files []string, groups []CachedVirtualModule) []string {
1212 if groups.len == 0 {
1213 return files
1214 }
1215 mut source_to_group := map[string]CachedVirtualModule{}
1216 for group in groups {
1217 for file in group.source_files {
1218 source_to_group[os.norm_path(os.abs_path(file))] = group
1219 }
1220 }
1221 mut added_headers := map[string]bool{}
1222 mut out := []string{cap: files.len}
1223 for file in files {
1224 norm_file := os.norm_path(os.abs_path(file))
1225 if group := source_to_group[norm_file] {
1226 if group.header_name !in added_headers {
1227 out << b.core_header_path(group.header_name)
1228 added_headers[group.header_name] = true
1229 }
1230 continue
1231 }
1232 out << file
1233 }
1234 return out
1235}
1236
1237fn (b &Builder) cached_import_parse_path(module_path string) ?string {
1238 if b.can_use_cached_import_headers_for_parse() {
1239 for import_mod in b.cached_import_manifest() {
1240 if import_mod.import_path != module_path {
1241 continue
1242 }
1243 header_path := b.core_header_path(import_mod.module_name)
1244 if !os.exists(header_path) || os.file_size(header_path) == 0 {
1245 return none
1246 }
1247 return header_path
1248 }
1249 }
1250 for i, cached_module_path in veb_cached_module_paths {
1251 if module_path != cached_module_path {
1252 continue
1253 }
1254 if !b.can_use_cached_module_bundle_for_parse(veb_cache_name, true) {
1255 return none
1256 }
1257 if i >= veb_cached_module_names.len {
1258 return none
1259 }
1260 header_path := b.core_header_path(veb_cached_module_names[i])
1261 if !os.exists(header_path) || os.file_size(header_path) == 0 {
1262 return none
1263 }
1264 return header_path
1265 }
1266 if b.can_use_cached_v2compiler_headers_for_parse() {
1267 for i, cached_module_path in v2compiler_cached_module_paths {
1268 if module_path != cached_module_path {
1269 continue
1270 }
1271 if i >= v2compiler_cached_module_names.len {
1272 return none
1273 }
1274 header_path := b.core_header_path(v2compiler_cached_module_names[i])
1275 if !os.exists(header_path) || os.file_size(header_path) == 0 {
1276 return none
1277 }
1278 return header_path
1279 }
1280 }
1281 return none
1282}
1283
1284fn (b &Builder) can_use_cached_core_headers() bool {
1285 if b.pref.no_cache || b.pref.skip_builtin {
1286 return false
1287 }
1288 if !b.ensure_core_cache_dir() {
1289 return false
1290 }
1291 cc := configured_cc(b.pref.vroot)
1292 cc_flags := configured_cflags()
1293 if !b.can_use_cached_module_bundle(builtin_cache_name, builtin_cached_module_paths, cc,
1294 cc_flags, '', false) {
1295 return false
1296 }
1297 if vlib_cached_module_paths.len > 0
1298 && !b.can_use_cached_module_bundle(vlib_cache_name, vlib_cached_module_paths, cc, cc_flags, '', false) {
1299 return false
1300 }
1301 expected_header_stamp := b.header_stamp_for_modules(cached_header_module_paths())
1302 current_header_stamp := os.read_file(b.core_headers_stamp_path()) or { return false }
1303 if current_header_stamp != expected_header_stamp {
1304 return false
1305 }
1306 for header_path in b.cached_header_paths() {
1307 if !os.exists(header_path) {
1308 return false
1309 }
1310 // .vh files must have content — empty headers cause missing
1311 // symbol errors during split compilation.
1312 header_size := os.file_size(header_path)
1313 if header_size == 0 {
1314 return false
1315 }
1316 }
1317 return true
1318}
1319
1320fn (b &Builder) can_use_cached_module_bundle(cache_name string, module_paths []string, cc string, cc_flags string, cc_link_flags string, use_markused bool) bool {
1321 if !b.ensure_core_cache_dir() {
1322 return false
1323 }
1324 obj_file := cache_name + '.o'
1325 stamp_file := cache_name + '.stamp'
1326 obj_path := os.join_path(b.core_cache_dir(), obj_file)
1327 stamp_path := os.join_path(b.core_cache_dir(), stamp_file)
1328 if !os.exists(obj_path) || !os.exists(stamp_path) {
1329 return false
1330 }
1331 expected_cache_stamp := b.cache_stamp_for_modules(cache_name, module_paths, cc, cc_flags,
1332 cc_link_flags, use_markused)
1333 current_cache_stamp := os.read_file(stamp_path) or { return false }
1334 return current_cache_stamp == expected_cache_stamp
1335}
1336
1337fn (b &Builder) can_use_cached_module_bundle_for_parse(cache_name string, use_markused bool) bool {
1338 if !b.ensure_core_cache_dir() {
1339 return false
1340 }
1341 obj_path := cache_path_join(b.core_cache_dir(), '${cache_name}.o')
1342 stamp_path := cache_path_join(b.core_cache_dir(), '${cache_name}.stamp')
1343 if !os.exists(obj_path) || !os.exists(stamp_path) {
1344 return false
1345 }
1346 stamp := os.read_file(stamp_path) or { return false }
1347 return stamp.contains('cache=${cache_name}\n')
1348 && stamp.contains('format=${core_cache_format}\n')
1349 && stamp.contains('use_markused=${use_markused}\n')
1350 && stamp.contains('target_os=${b.pref.target_os_or_host()}\n')
1351}
1352
1353fn (mut b Builder) ensure_core_module_headers() {
1354 if !b.ensure_core_cache_dir() {
1355 return
1356 }
1357 expected_stamp := b.header_stamp_for_modules(cached_header_module_paths())
1358 mut has_headers := true
1359 for header_path in b.cached_header_paths() {
1360 if !os.exists(header_path) {
1361 has_headers = false
1362 break
1363 }
1364 }
1365 mut needs_regen := !has_headers
1366 if has_headers {
1367 current_stamp := os.read_file(b.core_headers_stamp_path()) or { '' }
1368 if current_stamp != expected_stamp {
1369 needs_regen = true
1370 }
1371 }
1372 if !needs_regen {
1373 return
1374 }
1375 header_modules := cached_header_module_paths()
1376 header_source_files := b.parse_module_source_files_for_headers(header_modules)
1377 source_fn_returns := b.source_fn_return_types(header_modules)
1378 for module_name in cached_header_module_names() {
1379 header_ast := b.build_module_header_ast(header_source_files, module_name) or { return }
1380 mut gen := v.new_gen(b.pref)
1381 gen.gen(header_ast)
1382 mut header_source := sanitize_header_source(gen.output_string(), source_fn_returns)
1383 source_fn_decls := b.source_fn_decls_for_module(module_name)
1384 header_source = merge_missing_source_fn_decls(header_source, source_fn_decls)
1385 source_struct_fields := b.source_struct_field_types_for_module(module_name)
1386 header_source = repair_missing_struct_field_types(header_source, source_struct_fields)
1387 if header_source.len == 0 {
1388 // Empty header would cause missing symbols in split compilation.
1389 // Remove any partial headers already written and skip stamp update
1390 // so the next build retries generation.
1391 for cleanup_name in cached_header_module_names() {
1392 os.rm(b.core_header_path(cleanup_name)) or {}
1393 }
1394 os.rm(b.core_headers_stamp_path()) or {}
1395 return
1396 }
1397 if !header_source.ends_with('\n') {
1398 header_source += '\n'
1399 }
1400 os.write_file(b.core_header_path(module_name), header_source) or { return }
1401 }
1402 os.write_file(b.core_headers_stamp_path(), expected_stamp) or {}
1403}
1404
1405fn (mut b Builder) ensure_import_module_headers(module_names []string) {
1406 if module_names.len == 0 || !b.ensure_core_cache_dir() {
1407 return
1408 }
1409 imports := b.import_modules_for_cached_modules(module_names)
1410 if imports.len == 0 {
1411 return
1412 }
1413 expected_stamp := b.imports_header_stamp_for_modules(imports, module_names)
1414 mut has_headers := true
1415 for module_name in module_names {
1416 header_path := b.core_header_path(module_name)
1417 if !os.exists(header_path) || os.file_size(header_path) == 0 {
1418 has_headers = false
1419 break
1420 }
1421 }
1422 if has_headers {
1423 current_stamp := os.read_file(b.imports_headers_stamp_path()) or { '' }
1424 if current_stamp == expected_stamp {
1425 return
1426 }
1427 }
1428 if b.used_import_vh_for_parse {
1429 return
1430 }
1431 header_source_files := b.parse_module_source_files_for_headers(module_names)
1432 source_fn_returns := b.source_fn_return_types(module_names)
1433 for module_name in module_names {
1434 header_ast := b.build_module_header_ast(header_source_files, module_name) or { return }
1435 mut gen := v.new_gen(b.pref)
1436 gen.gen(header_ast)
1437 mut header_source := sanitize_header_source(gen.output_string(), source_fn_returns)
1438 source_fn_decls := b.source_fn_decls_for_module(module_name)
1439 header_source = merge_missing_source_fn_decls(header_source, source_fn_decls)
1440 source_struct_fields := b.source_struct_field_types_for_module(module_name)
1441 header_source = repair_missing_struct_field_types(header_source, source_struct_fields)
1442 if header_source.len == 0 {
1443 for cleanup_name in module_names {
1444 os.rm(b.core_header_path(cleanup_name)) or {}
1445 }
1446 os.rm(b.imports_headers_stamp_path()) or {}
1447 os.rm(b.imports_manifest_path()) or {}
1448 return
1449 }
1450 if !header_source.ends_with('\n') {
1451 header_source += '\n'
1452 }
1453 os.write_file(b.core_header_path(module_name), header_source) or { return }
1454 }
1455 mut manifest_lines := []string{cap: imports.len}
1456 for import_mod in imports {
1457 manifest_lines << 'import:${import_mod.import_path}:${import_mod.module_name}'
1458 }
1459 os.write_file(b.imports_manifest_path(), manifest_lines.join('\n')) or { return }
1460 os.write_file(b.imports_headers_stamp_path(), expected_stamp) or {}
1461}
1462
1463fn (mut b Builder) ensure_v2compiler_module_headers() {
1464 if !b.is_cmd_v2_self_build() || !b.ensure_core_cache_dir() {
1465 return
1466 }
1467 expected_stamp := b.header_stamp_for_modules(v2compiler_cached_module_paths)
1468 mut has_headers := true
1469 for header_path in b.v2compiler_header_paths() {
1470 if !os.exists(header_path) || os.file_size(header_path) == 0 {
1471 has_headers = false
1472 break
1473 }
1474 }
1475 if has_headers {
1476 current_stamp := os.read_file(b.v2compiler_headers_stamp_path()) or { '' }
1477 if current_stamp == expected_stamp {
1478 return
1479 }
1480 }
1481 header_source_files := b.v2compiler_header_source_files()
1482 source_fn_returns := b.source_fn_return_types(v2compiler_cached_module_paths)
1483 for module_name in v2compiler_cached_module_names {
1484 header_ast := b.build_module_header_ast(header_source_files, module_name) or { return }
1485 mut gen := v.new_gen(b.pref)
1486 gen.gen(header_ast)
1487 mut header_source := sanitize_header_source(gen.output_string(), source_fn_returns)
1488 source_struct_fields := b.source_struct_field_types_for_module(module_name)
1489 header_source = repair_missing_struct_field_types(header_source, source_struct_fields)
1490 if header_source.len == 0 {
1491 for cleanup_name in v2compiler_cached_module_names {
1492 os.rm(b.core_header_path(cleanup_name)) or {}
1493 }
1494 os.rm(b.v2compiler_headers_stamp_path()) or {}
1495 return
1496 }
1497 if !header_source.ends_with('\n') {
1498 header_source += '\n'
1499 }
1500 os.write_file(b.core_header_path(module_name), header_source) or { return }
1501 }
1502 os.write_file(b.v2compiler_headers_stamp_path(), expected_stamp) or {}
1503}
1504
1505fn (mut b Builder) v2compiler_header_source_files() []ast.File {
1506 mut needed := map[string]bool{}
1507 for module_name in v2compiler_cached_module_names {
1508 needed[module_name] = true
1509 }
1510 mut found := map[string]bool{}
1511 for file in b.files {
1512 if file.name == '' || file.name.ends_with('.vh') {
1513 continue
1514 }
1515 module_name := ast_file_module_name(file)
1516 if module_name in needed {
1517 found[module_name] = true
1518 }
1519 }
1520 if found.len == needed.len {
1521 return b.files
1522 }
1523 return b.parse_module_source_files_for_headers(v2compiler_cached_module_paths)
1524}
1525
1526fn (mut b Builder) ensure_virtual_module_headers(groups []CachedVirtualModule) {
1527 if groups.len == 0 || !b.ensure_core_cache_dir() {
1528 return
1529 }
1530 expected_stamp := b.virtuals_header_stamp_for_modules(groups)
1531 mut has_headers := true
1532 for group in groups {
1533 header_path := b.core_header_path(group.header_name)
1534 if !os.exists(header_path) || os.file_size(header_path) == 0 {
1535 has_headers = false
1536 break
1537 }
1538 }
1539 if has_headers {
1540 current_stamp := os.read_file(b.virtuals_headers_stamp_path()) or { '' }
1541 if current_stamp == expected_stamp {
1542 return
1543 }
1544 }
1545 if b.used_virtual_vh_for_parse {
1546 return
1547 }
1548 source_files := virtual_module_source_files(groups)
1549 header_source_files := b.parse_source_files_for_headers(source_files)
1550 source_fn_returns := b.source_fn_return_types_for_files(source_files)
1551 for group in groups {
1552 header_ast := b.build_virtual_module_header_ast(header_source_files, group) or { return }
1553 mut gen := v.new_gen(b.pref)
1554 gen.gen(header_ast)
1555 mut header_source := sanitize_header_source(gen.output_string(), source_fn_returns)
1556 source_fn_decls := b.source_fn_decls_for_files(group.source_files)
1557 header_source = merge_missing_source_fn_decls(header_source, source_fn_decls)
1558 source_struct_fields := b.source_struct_field_types_for_files(group.source_files)
1559 header_source = repair_missing_struct_field_types(header_source, source_struct_fields)
1560 if header_source.len == 0 {
1561 for cleanup_group in groups {
1562 os.rm(b.core_header_path(cleanup_group.header_name)) or {}
1563 }
1564 os.rm(b.virtuals_headers_stamp_path()) or {}
1565 os.rm(b.virtuals_manifest_path()) or {}
1566 return
1567 }
1568 if !header_source.ends_with('\n') {
1569 header_source += '\n'
1570 }
1571 os.write_file(b.core_header_path(group.header_name), header_source) or { return }
1572 }
1573 mut manifest_lines := []string{}
1574 for group in groups {
1575 manifest_lines << 'virtual:${group.name}:${group.header_name}'
1576 for file in group.source_files {
1577 manifest_lines << 'source:${group.name}:${file}'
1578 }
1579 }
1580 os.write_file(b.virtuals_manifest_path(), manifest_lines.join('\n')) or { return }
1581 os.write_file(b.virtuals_headers_stamp_path(), expected_stamp) or {}
1582}
1583
1584fn (b &Builder) import_modules_for_cached_modules(module_names []string) []CachedImportModule {
1585 mut module_set := map[string]bool{}
1586 for module_name in module_names {
1587 module_set[module_name] = true
1588 }
1589 mut import_set := map[string]bool{}
1590 mut imports := []CachedImportModule{}
1591 allow_pkgconfig_imports := !b.pref.is_cross_target()
1592 if b.uses_flat_module_enumeration() {
1593 for i in 0 .. b.flat.files.len {
1594 for import_stmt in active_file_imports_from_flat_with_options(&b.flat, b.flat.files[i],
1595 b.pref.user_defines, b.pref.explicit_user_defines,
1596 b.pref.source_filter_target_os(), allow_pkgconfig_imports) {
1597 module_name := import_stmt.name.all_after_last('.')
1598 if module_name !in module_set {
1599 continue
1600 }
1601 key := '${import_stmt.name}:${module_name}'
1602 if key in import_set {
1603 continue
1604 }
1605 import_set[key] = true
1606 imports << CachedImportModule{
1607 import_path: import_stmt.name
1608 module_name: module_name
1609 }
1610 }
1611 }
1612 } else {
1613 for file in b.files {
1614 for import_stmt in active_file_imports_with_options(file, b.pref.user_defines,
1615 b.pref.explicit_user_defines, b.pref.source_filter_target_os(),
1616 allow_pkgconfig_imports) {
1617 module_name := import_stmt.name.all_after_last('.')
1618 if module_name !in module_set {
1619 continue
1620 }
1621 key := '${import_stmt.name}:${module_name}'
1622 if key in import_set {
1623 continue
1624 }
1625 import_set[key] = true
1626 imports << CachedImportModule{
1627 import_path: import_stmt.name
1628 module_name: module_name
1629 }
1630 }
1631 }
1632 }
1633 for i := 1; i < imports.len; i++ {
1634 mut j := i
1635 for j > 0 && imports[j - 1].import_path > imports[j].import_path {
1636 imports[j - 1], imports[j] = imports[j], imports[j - 1]
1637 j--
1638 }
1639 }
1640 return imports
1641}
1642
1643fn (b &Builder) source_fn_return_types(modules []string) map[string]string {
1644 return b.source_fn_return_types_for_files(b.module_source_files(modules))
1645}
1646
1647fn (b &Builder) source_fn_return_types_for_files(files []string) map[string]string {
1648 mut fn_returns := map[string]string{}
1649 for file in files {
1650 lines := os.read_lines(file) or { continue }
1651 for raw_line in lines {
1652 line := raw_line.trim_space()
1653 info := parse_fn_signature_and_return(line) or { continue }
1654 if info.return_type.len > 0 {
1655 fn_returns[info.signature] = info.return_type
1656 }
1657 }
1658 }
1659 return fn_returns
1660}
1661
1662fn (b &Builder) source_fn_decls_for_module(module_name string) map[string]string {
1663 return b.source_fn_decls_for_files(b.source_files_for_module_name(module_name))
1664}
1665
1666fn (b &Builder) source_fn_decls_for_files(files []string) map[string]string {
1667 mut decls := map[string]string{}
1668 for file in files {
1669 lines := os.read_lines(file) or { continue }
1670 mut in_interface := false
1671 mut interface_name := ''
1672 mut interface_is_public := false
1673 mut interface_method_is_mut := false
1674 for raw_line in lines {
1675 line := raw_line.trim_space()
1676 if in_interface {
1677 if line.starts_with('}') {
1678 in_interface = false
1679 interface_name = ''
1680 interface_is_public = false
1681 interface_method_is_mut = false
1682 continue
1683 }
1684 if line.len == 0 || line.starts_with('//') {
1685 continue
1686 }
1687 if line == 'mut:' {
1688 interface_method_is_mut = true
1689 continue
1690 }
1691 if !header_interface_method_line_is_valid(line) {
1692 continue
1693 }
1694 open_idx := line.index('(') or { continue }
1695 close_idx := header_find_matching_paren(line, open_idx) or { continue }
1696 method_name := line[..open_idx].trim_space()
1697 if method_name.len == 0 {
1698 continue
1699 }
1700 params := line[open_idx..close_idx + 1]
1701 mut ret := line[close_idx + 1..].trim_space()
1702 if comment_idx := ret.index('//') {
1703 ret = ret[..comment_idx].trim_space()
1704 }
1705 vis := if interface_is_public { 'pub fn' } else { 'fn' }
1706 mut decl_line := '${vis} (it ${interface_name}) ${method_name}${params}'
1707 if ret.len > 0 {
1708 decl_line += ' ${ret}'
1709 }
1710 if interface_method_is_mut {
1711 decl_line = '${vis} (mut it ${interface_name}) ${method_name}${params}'
1712 if ret.len > 0 {
1713 decl_line += ' ${ret}'
1714 }
1715 }
1716 info := parse_fn_signature_and_return(decl_line) or { continue }
1717 decls[info.signature] = decl_line
1718 continue
1719 }
1720 if line.starts_with('pub interface ') || line.starts_with('interface ') {
1721 if !line.ends_with('{') {
1722 continue
1723 }
1724 mut body := line
1725 interface_is_public = body.starts_with('pub interface ')
1726 if interface_is_public {
1727 body = body[4..].trim_space()
1728 }
1729 if !body.starts_with('interface ') {
1730 continue
1731 }
1732 rest := body['interface '.len..]
1733 interface_name = rest.all_before('{').trim_space()
1734 if interface_name.len == 0 {
1735 continue
1736 }
1737 in_interface = true
1738 interface_method_is_mut = false
1739 continue
1740 }
1741 if !line.starts_with('fn ') && !line.starts_with('pub fn ') {
1742 continue
1743 }
1744 info := parse_fn_signature_and_return(line) or { continue }
1745 mut decl_line := info.signature
1746 if info.return_type.len > 0 {
1747 decl_line += ' ${info.return_type}'
1748 }
1749 decls[info.signature] = decl_line
1750 }
1751 }
1752 return decls
1753}
1754
1755fn (b &Builder) source_struct_field_types_for_module(module_name string) map[string]string {
1756 return b.source_struct_field_types_for_files(b.source_files_for_module_name(module_name))
1757}
1758
1759fn (b &Builder) source_struct_field_types_for_files(files []string) map[string]string {
1760 mut field_types := map[string]string{}
1761 for file in files {
1762 lines := os.read_lines(file) or { continue }
1763 mut in_struct := false
1764 mut struct_name := ''
1765 for raw_line in lines {
1766 mut line := raw_line.trim_space()
1767 if !in_struct {
1768 if sname := header_struct_block_name(line) {
1769 struct_name = sname
1770 in_struct = true
1771 }
1772 continue
1773 }
1774 if line.starts_with('}') {
1775 in_struct = false
1776 struct_name = ''
1777 continue
1778 }
1779 if line.len == 0 || line.starts_with('//') || line.starts_with('[') || line == 'mut:'
1780 || line == 'pub:' || line == 'pub mut:' {
1781 continue
1782 }
1783 if comment_idx := line.index('//') {
1784 line = line[..comment_idx].trim_space()
1785 }
1786 if line.len == 0 {
1787 continue
1788 }
1789 mut lhs := line
1790 if eq_idx := line.index('=') {
1791 lhs = line[..eq_idx].trim_space()
1792 }
1793 tokens := lhs.split(' ').filter(it.len > 0)
1794 if tokens.len < 2 {
1795 continue
1796 }
1797 field_name := tokens[0]
1798 field_type := tokens[1..].join(' ')
1799 field_types['${struct_name}.${field_name}'] = field_type
1800 }
1801 }
1802 return field_types
1803}
1804
1805fn (mut b Builder) parse_module_source_files_for_headers(modules []string) []ast.File {
1806 return b.parse_source_files_for_headers(b.module_source_files(modules))
1807}
1808
1809fn (mut b Builder) parse_source_files_for_headers(source_paths []string) []ast.File {
1810 mut parser_reused := parser.Parser.new(b.pref)
1811 return parser_reused.parse_files(source_paths, mut b.file_set)
1812}
1813
1814fn (b &Builder) build_module_header_ast(source_files []ast.File, module_name string) ?ast.File {
1815 return b.build_header_ast_for_files(source_files, module_name, module_name, []string{})
1816}
1817
1818fn (b &Builder) build_virtual_module_header_ast(source_files []ast.File, group CachedVirtualModule) ?ast.File {
1819 return b.build_header_ast_for_files(source_files, 'main', group.header_name, group.source_files)
1820}
1821
1822fn (b &Builder) build_header_ast_for_files(source_files []ast.File, module_name string, header_name string, allowed_files []string) ?ast.File {
1823 mut allowed_set := map[string]bool{}
1824 for file in allowed_files {
1825 allowed_set[os.norm_path(file)] = true
1826 }
1827 mut found_module := false
1828 mut module_stmt := ast.ModuleStmt{
1829 name: module_name
1830 }
1831 mut import_stmts := []ast.ImportStmt{}
1832 mut import_seen := map[string]bool{}
1833 mut enum_stmts := []ast.Stmt{}
1834 mut type_decl_stmts := []ast.Stmt{}
1835 mut type_decl_seen := map[string]bool{}
1836 mut decl_stmts := []ast.Stmt{}
1837 for file in source_files {
1838 if ast_file_module_name(file) != module_name {
1839 continue
1840 }
1841 if allowed_set.len > 0 && os.norm_path(file.name) !in allowed_set {
1842 continue
1843 }
1844 for stmt in file.stmts {
1845 match stmt {
1846 ast.ModuleStmt {
1847 if !found_module {
1848 module_stmt = stmt
1849 found_module = true
1850 }
1851 }
1852 ast.ImportStmt {
1853 key := import_stmt_cache_key(stmt)
1854 if key !in import_seen {
1855 import_seen[key] = true
1856 import_stmts << stmt
1857 }
1858 }
1859 ast.StructDecl {
1860 mut sfields := []ast.FieldDecl{cap: stmt.fields.len}
1861 for field in stmt.fields {
1862 mut field_typ := field.typ
1863 if field_typ is ast.EmptyExpr {
1864 if inferred_typ := infer_const_type_expr(field.value) {
1865 field_typ = inferred_typ
1866 } else {
1867 continue
1868 }
1869 }
1870 sfields << ast.FieldDecl{
1871 name: field.name
1872 typ: field_typ
1873 value: field.value
1874 attributes: field.attributes
1875 is_public: field.is_public
1876 is_mut: field.is_mut
1877 is_module_mut: field.is_module_mut
1878 is_interface_method: field.is_interface_method
1879 }
1880 }
1881 decl_stmts << ast.Stmt(ast.StructDecl{
1882 is_public: stmt.is_public
1883 is_union: stmt.is_union
1884 implements: stmt.implements
1885 embedded: stmt.embedded
1886 language: stmt.language
1887 name: stmt.name
1888 generic_params: stmt.generic_params
1889 fields: sfields
1890 pos: stmt.pos
1891 })
1892 }
1893 ast.ConstDecl {
1894 mut fields := []ast.FieldInit{}
1895 for field in stmt.fields {
1896 if compact_value := b.header_const_type_expr(module_name, field) {
1897 fields << ast.FieldInit{
1898 name: field.name
1899 value: compact_value
1900 }
1901 }
1902 }
1903 if fields.len > 0 {
1904 decl_stmts << ast.Stmt(ast.ConstDecl{
1905 is_public: stmt.is_public
1906 fields: fields
1907 })
1908 }
1909 }
1910 ast.EnumDecl {
1911 enum_stmts << ast.Stmt(stmt)
1912 }
1913 ast.TypeDecl {
1914 type_decl := b.resolved_header_type_decl(module_name, stmt) or { continue }
1915 type_decl_stmts << ast.Stmt(type_decl)
1916 type_decl_seen[type_decl.name] = true
1917 }
1918 ast.InterfaceDecl {
1919 decl_stmts << ast.Stmt(b.resolved_header_interface_decl(module_name, stmt))
1920 }
1921 ast.GlobalDecl {
1922 mut gfields := []ast.FieldDecl{cap: stmt.fields.len}
1923 for field in stmt.fields {
1924 mut global_typ := field.typ
1925 if global_typ is ast.EmptyExpr {
1926 if inferred_typ := infer_const_type_expr(field.value) {
1927 global_typ = inferred_typ
1928 } else {
1929 continue
1930 }
1931 }
1932 if !header_type_expr_is_usable(global_typ) {
1933 continue
1934 }
1935 gfields << ast.FieldDecl{
1936 name: field.name
1937 typ: global_typ
1938 attributes: field.attributes
1939 is_public: field.is_public
1940 is_mut: field.is_mut
1941 is_module_mut: field.is_module_mut
1942 is_interface_method: field.is_interface_method
1943 }
1944 }
1945 if gfields.len > 0 {
1946 decl_stmts << ast.Stmt(ast.GlobalDecl{
1947 attributes: stmt.attributes
1948 fields: gfields
1949 is_public: stmt.is_public
1950 })
1951 }
1952 }
1953 ast.FnDecl {
1954 resolved_fn := b.resolved_header_fn_decl(module_name, stmt)
1955 if !header_fn_decl_is_usable(resolved_fn) {
1956 continue
1957 }
1958 fn_decl := ast.FnDecl{
1959 attributes: []ast.Attribute{}
1960 is_public: resolved_fn.is_public
1961 is_method: resolved_fn.is_method
1962 is_static: resolved_fn.is_static
1963 receiver: resolved_fn.receiver
1964 language: resolved_fn.language
1965 name: resolved_fn.name
1966 typ: resolved_fn.typ
1967 stmts: []ast.Stmt{}
1968 pos: resolved_fn.pos
1969 }
1970 decl_stmts << ast.Stmt(fn_decl)
1971 }
1972 else {}
1973 }
1974 }
1975 }
1976 if allowed_set.len == 0 {
1977 b.append_source_type_alias_decls(module_name, mut type_decl_stmts, mut type_decl_seen)
1978 } else {
1979 b.append_source_type_alias_decls_for_files(allowed_files, mut type_decl_stmts, mut
1980 type_decl_seen)
1981 }
1982 if !found_module || enum_stmts.len + type_decl_stmts.len + decl_stmts.len == 0 {
1983 return none
1984 }
1985 mut header_stmts := []ast.Stmt{cap: 1 + import_stmts.len + enum_stmts.len +
1986 type_decl_stmts.len + decl_stmts.len}
1987 header_stmts << ast.Stmt(module_stmt)
1988 for import_stmt in import_stmts {
1989 header_stmts << ast.Stmt(import_stmt)
1990 }
1991 header_stmts << enum_stmts
1992 header_stmts << type_decl_stmts
1993 header_stmts << decl_stmts
1994 return ast.File{
1995 mod: module_stmt.name
1996 name: '${header_name}.vh'
1997 stmts: header_stmts
1998 imports: import_stmts
1999 }
2000}
2001
2002fn (b &Builder) resolved_header_interface_decl(module_name string, stmt ast.InterfaceDecl) ast.InterfaceDecl {
2003 if stmt.fields.len > 0 {
2004 return stmt
2005 }
2006 if module_name == 'builtin' && stmt.name == 'IError' {
2007 msg_type := ast.Expr(ast.Type(ast.FnType{
2008 params: []ast.Parameter{}
2009 return_type: type_name_to_ast_expr('string')
2010 }))
2011 code_type := ast.Expr(ast.Type(ast.FnType{
2012 params: []ast.Parameter{}
2013 return_type: type_name_to_ast_expr('int')
2014 }))
2015 return ast.InterfaceDecl{
2016 is_public: stmt.is_public
2017 attributes: stmt.attributes
2018 name: stmt.name
2019 generic_params: stmt.generic_params
2020 embedded: stmt.embedded
2021 fields: [
2022 ast.FieldDecl{
2023 name: 'msg'
2024 typ: msg_type
2025 is_interface_method: true
2026 },
2027 ast.FieldDecl{
2028 name: 'code'
2029 typ: code_type
2030 is_interface_method: true
2031 },
2032 ]
2033 }
2034 }
2035 return stmt
2036}
2037
2038fn (b &Builder) append_source_type_alias_decls(module_name string, mut type_decl_stmts []ast.Stmt, mut type_decl_seen map[string]bool) {
2039 b.append_source_type_alias_decls_for_files(b.source_files_for_module_name(module_name), mut
2040 type_decl_stmts, mut type_decl_seen)
2041}
2042
2043fn (b &Builder) append_source_type_alias_decls_for_files(files []string, mut type_decl_stmts []ast.Stmt, mut type_decl_seen map[string]bool) {
2044 for file in files {
2045 lines := os.read_lines(file) or { continue }
2046 for raw_line in lines {
2047 line := raw_line.trim_space()
2048 if line.len == 0 || line.starts_with('//') {
2049 continue
2050 }
2051 mut is_public := false
2052 mut body := line
2053 if body.starts_with('pub ') {
2054 is_public = true
2055 body = body[4..].trim_space()
2056 }
2057 if !body.starts_with('type ') || !body.contains('=') {
2058 continue
2059 }
2060 rest := body['type '.len..]
2061 eq_idx := rest.index('=') or { continue }
2062 lhs := rest[..eq_idx].trim_space()
2063 mut lhs_tokens := lhs.split_any(' \t')
2064 if lhs_tokens.len == 0 {
2065 continue
2066 }
2067 type_name := lhs_tokens[0]
2068 if type_name.len == 0 || type_name.contains('.') {
2069 continue
2070 }
2071 rhs := rest[eq_idx + 1..].trim_space()
2072 if rhs.len == 0 {
2073 continue
2074 }
2075 source_decl := ast.TypeDecl{
2076 is_public: is_public
2077 language: .v
2078 name: type_name
2079 base_type: type_name_to_ast_expr(rhs)
2080 }
2081 b.set_or_append_type_decl(mut type_decl_stmts, source_decl)
2082 type_decl_seen[type_name] = true
2083 }
2084 }
2085}
2086
2087fn (b &Builder) set_or_append_type_decl(mut type_decl_stmts []ast.Stmt, decl ast.TypeDecl) {
2088 for i, stmt in type_decl_stmts {
2089 if stmt is ast.TypeDecl && stmt.name == decl.name {
2090 type_decl_stmts[i] = ast.Stmt(decl)
2091 return
2092 }
2093 }
2094 type_decl_stmts << ast.Stmt(decl)
2095}
2096
2097fn (b &Builder) resolved_header_type_decl(module_name string, stmt ast.TypeDecl) ?ast.TypeDecl {
2098 if stmt.variants.len > 0 || stmt.base_type !is ast.EmptyExpr {
2099 return stmt
2100 }
2101 base_type_expr := b.lookup_alias_base_type_expr(module_name, stmt.name) or { return none }
2102 return ast.TypeDecl{
2103 is_public: stmt.is_public
2104 language: stmt.language
2105 name: stmt.name
2106 generic_params: stmt.generic_params
2107 base_type: base_type_expr
2108 variants: stmt.variants
2109 }
2110}
2111
2112fn (b &Builder) lookup_alias_base_type_expr(module_name string, type_name string) ?ast.Expr {
2113 if b.env == unsafe { nil } {
2114 return b.lookup_alias_source_type_expr(module_name, type_name)
2115 }
2116 if scope := b.env.get_scope(module_name) {
2117 mut mod_scope := unsafe { scope }
2118 if obj := mod_scope.lookup_parent(type_name, 0) {
2119 obj_typ := obj.typ()
2120 if base_type_name := types.alias_base_type_name(obj_typ) {
2121 return type_name_to_ast_expr(base_type_name)
2122 }
2123 obj_type_name := normalize_header_type_name(types.type_name(obj_typ))
2124 if obj_type_name != '' && obj_type_name != type_name {
2125 return type_name_to_ast_expr(obj_type_name)
2126 }
2127 }
2128 }
2129 return b.lookup_alias_source_type_expr(module_name, type_name)
2130}
2131
2132fn (b &Builder) resolved_header_fn_decl(module_name string, stmt ast.FnDecl) ast.FnDecl {
2133 mut resolved_typ := stmt.typ
2134 if b.env != unsafe { nil } {
2135 if stmt.is_method {
2136 receiver_type_name := header_receiver_type_name(stmt.receiver.typ)
2137 if receiver_type_name != '' {
2138 if fn_typ := b.env.lookup_method(receiver_type_name, stmt.name) {
2139 resolved_typ = merge_header_fn_type(stmt.typ, types.Type(fn_typ))
2140 }
2141 }
2142 } else if scope := b.env.get_scope(module_name) {
2143 mut mod_scope := unsafe { scope }
2144 if obj := mod_scope.lookup_parent(stmt.name, 0) {
2145 obj_typ := obj.typ()
2146 if obj_typ is types.FnType {
2147 resolved_typ = merge_header_fn_type(stmt.typ, obj_typ)
2148 }
2149 }
2150 }
2151 }
2152 return ast.FnDecl{
2153 attributes: stmt.attributes
2154 is_public: stmt.is_public
2155 is_method: stmt.is_method
2156 is_static: stmt.is_static
2157 receiver: stmt.receiver
2158 language: stmt.language
2159 name: stmt.name
2160 typ: resolved_typ
2161 stmts: stmt.stmts
2162 pos: stmt.pos
2163 }
2164}
2165
2166fn header_receiver_type_name(receiver_type_expr ast.Expr) string {
2167 return match receiver_type_expr {
2168 ast.ModifierExpr {
2169 header_receiver_type_name(receiver_type_expr.expr)
2170 }
2171 ast.PrefixExpr {
2172 header_receiver_type_name(receiver_type_expr.expr)
2173 }
2174 else {
2175 receiver_type_expr.name()
2176 }
2177 }
2178}
2179
2180fn merge_header_fn_type(source_fn ast.FnType, resolved_type types.Type) ast.FnType {
2181 match resolved_type {
2182 types.FnType {
2183 mut merged_return := source_fn.return_type
2184 if merged_return is ast.EmptyExpr {
2185 if return_type := resolved_type.get_return_type() {
2186 merged_return = type_name_to_ast_expr(types.type_name(return_type))
2187 }
2188 }
2189 mut merged_params := source_fn.params.clone()
2190 param_types := resolved_type.get_param_types()
2191 if param_types.len > 0 {
2192 limit := if merged_params.len < param_types.len {
2193 merged_params.len
2194 } else {
2195 param_types.len
2196 }
2197 for i in 0 .. limit {
2198 if merged_params[i].typ !is ast.EmptyExpr {
2199 continue
2200 }
2201 param_type := type_name_to_ast_expr(types.type_name(param_types[i]))
2202 merged_params[i] = ast.Parameter{
2203 name: merged_params[i].name
2204 typ: param_type
2205 is_mut: merged_params[i].is_mut
2206 pos: merged_params[i].pos
2207 }
2208 }
2209 }
2210 return ast.FnType{
2211 generic_params: source_fn.generic_params
2212 params: merged_params
2213 return_type: merged_return
2214 }
2215 }
2216 else {
2217 return source_fn
2218 }
2219 }
2220}
2221
2222fn type_name_to_ast_expr(type_name string) ast.Expr {
2223 norm_name := normalize_header_type_name(type_name)
2224 return ast.Expr(ast.Ident{
2225 name: norm_name
2226 })
2227}
2228
2229fn normalize_header_type_name(type_name string) string {
2230 mut name := type_name.trim_space()
2231 if name.starts_with('tuple ') {
2232 name = name['tuple '.len..]
2233 }
2234 return name
2235}
2236
2237fn (b &Builder) module_name_to_path(module_name string) string {
2238 return match module_name {
2239 'bits' { 'math.bits' }
2240 'cmdline' { 'os.cmdline' }
2241 'binary' { 'encoding.binary' }
2242 'sha256' { 'crypto.sha256' }
2243 'textscanner' { 'strings.textscanner' }
2244 'termios' { 'term.termios' }
2245 'http' { 'net.http' }
2246 'chunked' { 'net.http.chunked' }
2247 'conv' { 'net.conv' }
2248 'openssl' { 'net.openssl' }
2249 'socks' { 'net.socks' }
2250 'ssl' { 'net.ssl' }
2251 'urllib' { 'net.urllib' }
2252 'gzip' { 'compress.gzip' }
2253 'zlib' { 'compress.zlib' }
2254 'zstd' { 'compress.zstd' }
2255 'crc32' { 'hash.crc32' }
2256 'big' { 'math.big' }
2257 'stdatomic' { 'sync.stdatomic' }
2258 'json2' { 'x.json2' }
2259 'html' { 'encoding.html' }
2260 'ast' { 'v2.ast' }
2261 'abi' { 'v2.abi' }
2262 'builder' { 'v2.builder' }
2263 'errors' { 'v2.errors' }
2264 'eval' { 'v2.eval' }
2265 'arm64' { 'v2.gen.arm64' }
2266 'c' { 'v2.gen.c' }
2267 'cleanc' { 'v2.gen.cleanc' }
2268 'v' { 'v2.gen.v' }
2269 'x64' { 'v2.gen.x64' }
2270 'insel' { 'v2.insel' }
2271 'markused' { 'v2.markused' }
2272 'mir' { 'v2.mir' }
2273 'parser' { 'v2.parser' }
2274 'pref' { 'v2.pref' }
2275 'scanner' { 'v2.scanner' }
2276 'ssa' { 'v2.ssa' }
2277 'optimize' { 'v2.ssa.optimize' }
2278 'token' { 'v2.token' }
2279 'transformer' { 'v2.transformer' }
2280 'types' { 'v2.types' }
2281 else { module_name }
2282 }
2283}
2284
2285fn (b &Builder) lookup_alias_source_type_expr(module_name string, type_name string) ?ast.Expr {
2286 for file in b.source_files_for_module_name(module_name) {
2287 lines := os.read_lines(file) or { continue }
2288 for raw_line in lines {
2289 line := raw_line.trim_space()
2290 if line.len == 0 || line.starts_with('//') {
2291 continue
2292 }
2293 is_type_decl := line.starts_with('type ${type_name} ')
2294 || line.starts_with('pub type ${type_name} ')
2295 if !is_type_decl || !line.contains('=') {
2296 continue
2297 }
2298 rhs := line.all_after_first('=').trim_space()
2299 if rhs.len == 0 {
2300 continue
2301 }
2302 return type_name_to_ast_expr(rhs)
2303 }
2304 }
2305 return none
2306}
2307
2308fn (b &Builder) header_const_type_expr(module_name string, field ast.FieldInit) ?ast.Expr {
2309 if header_const_value_is_safe(field.value) {
2310 return field.value
2311 }
2312 if typed_expr := b.lookup_const_type_expr(module_name, field.name) {
2313 return typed_expr
2314 }
2315 return infer_const_type_expr(field.value)
2316}
2317
2318fn header_const_value_is_safe(expr ast.Expr) bool {
2319 return match expr {
2320 ast.BasicLiteral, ast.Ident {
2321 true
2322 }
2323 ast.StringLiteral {
2324 !expr.value.contains('\n') && !expr.value.contains('\r')
2325 }
2326 ast.StringInterLiteral {
2327 expr.values.all(!it.contains('\n') && !it.contains('\r'))
2328 }
2329 ast.SelectorExpr {
2330 header_const_selector_lhs_is_safe(expr.lhs)
2331 }
2332 ast.ParenExpr {
2333 header_const_value_is_safe(expr.expr)
2334 }
2335 ast.PrefixExpr {
2336 header_const_value_is_safe(expr.expr)
2337 }
2338 ast.CastExpr {
2339 header_const_value_is_safe(expr.expr)
2340 }
2341 ast.ModifierExpr {
2342 header_const_value_is_safe(expr.expr)
2343 }
2344 ast.InfixExpr {
2345 header_const_value_is_safe(expr.lhs) && header_const_value_is_safe(expr.rhs)
2346 }
2347 ast.CallOrCastExpr {
2348 if !is_type_expr(expr.lhs) {
2349 return false
2350 }
2351 header_const_value_is_safe(expr.expr)
2352 }
2353 ast.ComptimeExpr, ast.IfExpr {
2354 true
2355 }
2356 else {
2357 false
2358 }
2359 }
2360}
2361
2362fn header_const_selector_lhs_is_safe(expr ast.Expr) bool {
2363 return match expr {
2364 ast.Ident {
2365 true
2366 }
2367 ast.SelectorExpr {
2368 header_const_selector_lhs_is_safe(expr.lhs)
2369 }
2370 else {
2371 false
2372 }
2373 }
2374}
2375
2376fn (b &Builder) lookup_const_type_expr(module_name string, const_name string) ?ast.Expr {
2377 if b.env == unsafe { nil } {
2378 return none
2379 }
2380 scope := b.env.get_scope(module_name) or { return none }
2381 mut mod_scope := unsafe { scope }
2382 obj := mod_scope.lookup_parent(const_name, 0) or { return none }
2383 mut type_name := normalize_const_type_name(module_name, types.type_name(obj.typ()))
2384 if type_name.len == 0 || !header_type_name_is_sane(type_name) {
2385 return none
2386 }
2387 if !type_name.contains('.') && b.module_defines_c_type(module_name, type_name) {
2388 type_name = 'C.${type_name}'
2389 }
2390 return ast.Expr(ast.Ident{
2391 name: type_name
2392 })
2393}
2394
2395fn normalize_const_type_name(module_name string, type_name string) string {
2396 mut name := normalize_header_type_name(type_name)
2397 if name.len == 0 || !name.contains('__') {
2398 return name
2399 }
2400 if name.starts_with('_option_') || name.starts_with('_result_') || name.starts_with('Array_')
2401 || name.starts_with('Map_') {
2402 return name
2403 }
2404 idx := name.index('__') or { return name }
2405 if idx <= 0 || idx + 2 >= name.len {
2406 return name
2407 }
2408 mod_prefix := name[..idx]
2409 rest := name[idx + 2..]
2410 if mod_prefix == module_name {
2411 return rest
2412 }
2413 return '${mod_prefix}.${rest}'
2414}
2415
2416fn header_type_name_is_sane(type_name string) bool {
2417 for ch in type_name {
2418 if ch.is_space() {
2419 return false
2420 }
2421 if ch in [`+`, `-`, `*`, `/`, `%`, `=`, `,`, `;`, `:`, `(`, `)`, `{`, `}`] {
2422 return false
2423 }
2424 }
2425 return true
2426}
2427
2428fn (b &Builder) module_defines_c_type(module_name string, type_name string) bool {
2429 mut patterns := []string{cap: 4}
2430 patterns << 'struct C.${type_name}'
2431 patterns << 'pub struct C.${type_name}'
2432 patterns << 'type C.${type_name}'
2433 patterns << 'pub type C.${type_name}'
2434 for file in b.source_files_for_module_name(module_name) {
2435 content := os.read_file(file) or { continue }
2436 for pattern in patterns {
2437 idx := content.index(pattern) or { continue }
2438 // Ensure whole-word match: char after pattern must not be alphanumeric or '_'.
2439 end := idx + pattern.len
2440 if end < content.len {
2441 c := content[end]
2442 if c == `_` || (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`)
2443 || (c >= `0` && c <= `9`) {
2444 continue
2445 }
2446 }
2447 return true
2448 }
2449 }
2450 return false
2451}
2452
2453fn infer_const_type_expr(expr ast.Expr) ?ast.Expr {
2454 return match expr {
2455 ast.IfExpr, ast.ComptimeExpr {
2456 none
2457 }
2458 ast.Type {
2459 ast.Expr(expr)
2460 }
2461 ast.StringLiteral {
2462 ast.Expr(ast.Ident{
2463 name: 'string'
2464 })
2465 }
2466 ast.StringInterLiteral {
2467 ast.Expr(ast.Ident{
2468 name: 'string'
2469 })
2470 }
2471 ast.BasicLiteral {
2472 match expr.kind {
2473 .key_true, .key_false {
2474 ast.Expr(ast.Ident{
2475 name: 'bool'
2476 })
2477 }
2478 .char {
2479 ast.Expr(ast.Ident{
2480 name: 'rune'
2481 })
2482 }
2483 .number {
2484 mut number_type := 'int'
2485 if expr.value.contains('.') {
2486 number_type = 'f64'
2487 }
2488 ast.Expr(ast.Ident{
2489 name: number_type
2490 })
2491 }
2492 else {
2493 none
2494 }
2495 }
2496 }
2497 ast.ArrayInitExpr {
2498 if expr.typ !is ast.EmptyExpr {
2499 expr.typ
2500 } else if expr.exprs.len > 0 {
2501 elem_type := infer_const_type_expr(expr.exprs[0]) or { return none }
2502 ast.Expr(ast.Type(ast.ArrayType{
2503 elem_type: elem_type
2504 }))
2505 } else {
2506 none
2507 }
2508 }
2509 ast.MapInitExpr {
2510 if expr.typ !is ast.EmptyExpr {
2511 expr.typ
2512 } else if expr.keys.len > 0 && expr.vals.len > 0 {
2513 key_expr := infer_const_type_expr(expr.keys[0]) or { return none }
2514 val_expr := infer_const_type_expr(expr.vals[0]) or { return none }
2515 ast.Expr(ast.Type(ast.MapType{
2516 key_type: key_expr
2517 value_type: val_expr
2518 }))
2519 } else {
2520 none
2521 }
2522 }
2523 ast.InitExpr {
2524 expr.typ
2525 }
2526 ast.CallOrCastExpr {
2527 if is_type_expr(expr.lhs) {
2528 expr.lhs
2529 } else {
2530 none
2531 }
2532 }
2533 ast.CastExpr {
2534 expr.typ
2535 }
2536 ast.AsCastExpr {
2537 expr.typ
2538 }
2539 ast.InfixExpr {
2540 if lhs_expr := infer_const_type_expr(expr.lhs) {
2541 lhs_expr
2542 } else {
2543 infer_const_type_expr(expr.rhs)
2544 }
2545 }
2546 ast.ParenExpr {
2547 infer_const_type_expr(expr.expr)
2548 }
2549 ast.PrefixExpr {
2550 infer_const_type_expr(expr.expr)
2551 }
2552 else {
2553 none
2554 }
2555 }
2556}
2557
2558fn is_type_expr(expr ast.Expr) bool {
2559 return match expr {
2560 ast.Type, ast.SelectorExpr {
2561 true
2562 }
2563 ast.Ident {
2564 name := expr.name
2565 name in ['bool', 'byte', 'char', 'f32', 'f64', 'i8', 'i16', 'i32', 'int', 'i64', 'isize', 'rune', 'string', 'u8', 'u16', 'u32', 'u64', 'usize', 'void', 'voidptr', 'byteptr', 'charptr']
2566 || name.starts_with('&') || name.starts_with('[]')
2567 || name.starts_with('?') || name.starts_with('!')
2568 || name.contains('[') || name.contains('__')
2569 || (name.len > 0 && name[0].is_capital())
2570 }
2571 else {
2572 false
2573 }
2574 }
2575}
2576
2577fn import_stmt_cache_key(stmt ast.ImportStmt) string {
2578 mut key := '${stmt.name}|${stmt.alias}|${stmt.is_aliased}'
2579 if stmt.symbols.len > 0 {
2580 mut symbols := []string{cap: stmt.symbols.len}
2581 for symbol in stmt.symbols {
2582 symbols << symbol.name()
2583 }
2584 key += '|${symbols.join(',')}'
2585 }
2586 return key
2587}
2588
2589fn header_fn_decl_is_usable(stmt ast.FnDecl) bool {
2590 if stmt.is_method && !header_type_expr_is_usable(stmt.receiver.typ) {
2591 return false
2592 }
2593 for param in stmt.typ.params {
2594 if !header_type_expr_is_usable(param.typ) {
2595 return false
2596 }
2597 }
2598 if stmt.typ.return_type !is ast.EmptyExpr && !header_type_expr_is_usable(stmt.typ.return_type) {
2599 return false
2600 }
2601 return true
2602}
2603
2604fn header_type_expr_is_usable(expr ast.Expr) bool {
2605 return match expr {
2606 ast.EmptyExpr {
2607 false
2608 }
2609 ast.Ident {
2610 expr.name.len > 0
2611 }
2612 ast.SelectorExpr {
2613 header_type_expr_is_usable(expr.lhs) && expr.rhs.name.len > 0
2614 }
2615 ast.ModifierExpr {
2616 header_type_expr_is_usable(expr.expr)
2617 }
2618 ast.ParenExpr {
2619 header_type_expr_is_usable(expr.expr)
2620 }
2621 ast.PrefixExpr {
2622 header_type_expr_is_usable(expr.expr)
2623 }
2624 ast.IndexExpr {
2625 header_type_expr_is_usable(expr.lhs) && header_type_expr_is_usable(expr.expr)
2626 }
2627 ast.GenericArgs {
2628 if !header_type_expr_is_usable(expr.lhs) {
2629 return false
2630 }
2631 for arg in expr.args {
2632 if !header_type_expr_is_usable(arg) {
2633 return false
2634 }
2635 }
2636 true
2637 }
2638 ast.GenericArgOrIndexExpr {
2639 header_type_expr_is_usable(expr.lhs) && header_type_expr_is_usable(expr.expr)
2640 }
2641 ast.Type {
2642 header_type_node_is_usable(expr)
2643 }
2644 else {
2645 false
2646 }
2647 }
2648}
2649
2650fn header_type_node_is_usable(node ast.Type) bool {
2651 return match node {
2652 ast.ArrayType {
2653 header_type_expr_is_usable(node.elem_type)
2654 }
2655 ast.ArrayFixedType {
2656 header_type_expr_is_usable(node.elem_type)
2657 }
2658 ast.ChannelType {
2659 header_type_expr_is_usable(node.elem_type)
2660 }
2661 ast.FnType {
2662 for param in node.params {
2663 if !header_type_expr_is_usable(param.typ) {
2664 return false
2665 }
2666 }
2667 if node.return_type !is ast.EmptyExpr {
2668 return header_type_expr_is_usable(node.return_type)
2669 }
2670 return true
2671 }
2672 ast.GenericType {
2673 header_type_expr_is_usable(node.name)
2674 }
2675 ast.MapType {
2676 header_type_expr_is_usable(node.key_type) && header_type_expr_is_usable(node.value_type)
2677 }
2678 ast.OptionType {
2679 node.base_type !is ast.EmptyExpr && header_type_expr_is_usable(node.base_type)
2680 }
2681 ast.PointerType {
2682 header_type_expr_is_usable(node.base_type)
2683 }
2684 ast.ResultType {
2685 node.base_type !is ast.EmptyExpr && header_type_expr_is_usable(node.base_type)
2686 }
2687 ast.ThreadType {
2688 node.elem_type is ast.EmptyExpr || header_type_expr_is_usable(node.elem_type)
2689 }
2690 ast.TupleType {
2691 for t in node.types {
2692 if !header_type_expr_is_usable(t) {
2693 return false
2694 }
2695 }
2696 true
2697 }
2698 ast.AnonStructType {
2699 true
2700 }
2701 ast.NilType, ast.NoneType {
2702 true
2703 }
2704 }
2705}
2706
2707struct FnReturnInfo {
2708 signature string
2709 return_type string
2710}
2711
2712fn parse_fn_signature_and_return(line string) ?FnReturnInfo {
2713 mut i := line.index('fn ') or { return none }
2714 i += 3
2715 for i < line.len && line[i].is_space() {
2716 i++
2717 }
2718 // Optional method receiver: fn (<receiver>) name(...)
2719 if i < line.len && line[i] == `(` {
2720 recv_end := header_find_matching_paren(line, i) or { return none }
2721 i = recv_end + 1
2722 for i < line.len && line[i].is_space() {
2723 i++
2724 }
2725 }
2726 mut generic_depth := 0
2727 for i < line.len {
2728 if line[i] == `[` {
2729 generic_depth++
2730 } else if line[i] == `]` && generic_depth > 0 {
2731 generic_depth--
2732 } else if line[i] == `(` && generic_depth == 0 {
2733 break
2734 }
2735 i++
2736 }
2737 if i >= line.len || line[i] != `(` {
2738 return none
2739 }
2740 params_end := header_find_matching_paren(line, i) or { return none }
2741 signature := line[..params_end + 1].trim_space()
2742 mut j := params_end + 1
2743 for j < line.len && line[j].is_space() {
2744 j++
2745 }
2746 mut return_type := line[j..].trim_space()
2747 if return_type.ends_with('{') {
2748 return_type = return_type[..return_type.len - 1].trim_space()
2749 }
2750 if return_type.ends_with(';') {
2751 return_type = return_type[..return_type.len - 1].trim_space()
2752 }
2753 return FnReturnInfo{
2754 signature: signature
2755 return_type: return_type
2756 }
2757}
2758
2759fn restore_fn_return_type_from_source(line string, source_fn_returns map[string]string) string {
2760 trimmed := line.trim_space()
2761 info := parse_fn_signature_and_return(trimmed) or { return line }
2762 if info.return_type.len > 0 {
2763 return line
2764 }
2765 return_type := source_fn_returns[info.signature] or { return line }
2766 mut indent_len := 0
2767 for indent_len < line.len && line[indent_len].is_space() {
2768 indent_len++
2769 }
2770 indent := line[..indent_len]
2771 return '${indent}${info.signature} ${return_type}'
2772}
2773
2774fn merge_missing_source_fn_decls(header_source string, source_fn_decls map[string]string) string {
2775 if source_fn_decls.len == 0 || header_source.len == 0 {
2776 return header_source
2777 }
2778 mut existing := map[string]bool{}
2779 for line in header_source.split_into_lines() {
2780 info := parse_fn_signature_and_return(line.trim_space()) or { continue }
2781 existing[info.signature] = true
2782 }
2783 mut keys := source_fn_decls.keys()
2784 keys.sort()
2785 mut extra := []string{}
2786 for key in keys {
2787 if key in existing {
2788 continue
2789 }
2790 extra << source_fn_decls[key]
2791 }
2792 if extra.len == 0 {
2793 return header_source
2794 }
2795 mut merged := header_source
2796 if !merged.ends_with('\n') {
2797 merged += '\n'
2798 }
2799 merged += extra.join('\n')
2800 merged += '\n'
2801 return merged
2802}
2803
2804fn header_struct_block_name(trimmed string) ?string {
2805 if !trimmed.ends_with('{') {
2806 return none
2807 }
2808 mut body := trimmed
2809 if body.starts_with('pub ') {
2810 body = body[4..].trim_space()
2811 }
2812 if body.starts_with('struct ') {
2813 name := body['struct '.len..].all_before('{').trim_space()
2814 return if name.len > 0 { name } else { none }
2815 }
2816 if body.starts_with('union ') {
2817 name := body['union '.len..].all_before('{').trim_space()
2818 return if name.len > 0 { name } else { none }
2819 }
2820 return none
2821}
2822
2823fn leading_ws(line string) string {
2824 mut i := 0
2825 for i < line.len && line[i].is_space() {
2826 i++
2827 }
2828 return line[..i]
2829}
2830
2831fn repair_missing_struct_field_types(header_source string, source_struct_fields map[string]string) string {
2832 if header_source.len == 0 || source_struct_fields.len == 0 {
2833 return header_source
2834 }
2835 lines := header_source.split_into_lines()
2836 mut out := []string{cap: lines.len}
2837 mut in_struct := false
2838 mut struct_name := ''
2839 for raw_line in lines {
2840 mut line := raw_line
2841 trimmed := line.trim_space()
2842 if !in_struct {
2843 if sname := header_struct_block_name(trimmed) {
2844 struct_name = sname
2845 in_struct = true
2846 }
2847 out << line
2848 continue
2849 }
2850 if trimmed.starts_with('}') {
2851 in_struct = false
2852 struct_name = ''
2853 out << line
2854 continue
2855 }
2856 if trimmed.len == 0 || trimmed.starts_with('//') || trimmed.starts_with('[')
2857 || trimmed == 'mut:' || trimmed == 'pub:' || trimmed == 'pub mut:' {
2858 out << line
2859 continue
2860 }
2861 mut no_comment := trimmed
2862 if comment_idx := no_comment.index('//') {
2863 no_comment = no_comment[..comment_idx].trim_space()
2864 }
2865 if no_comment.len == 0 {
2866 out << line
2867 continue
2868 }
2869 if eq_idx := no_comment.index('=') {
2870 lhs := no_comment[..eq_idx].trim_space()
2871 if header_token_count(lhs) == 1 {
2872 field_name := lhs
2873 field_type := source_struct_fields['${struct_name}.${field_name}'] or { '' }
2874 if field_type.len > 0 {
2875 rhs := no_comment[eq_idx + 1..].trim_space()
2876 out << '${leading_ws(line)}${field_name} ${field_type} = ${rhs}'
2877 continue
2878 }
2879 }
2880 }
2881 out << line
2882 }
2883 return out.join('\n')
2884}
2885
2886fn sanitize_header_source(source string, source_fn_returns map[string]string) string {
2887 lines := source.split_into_lines()
2888 mut out := []string{cap: lines.len}
2889 mut in_global_block := false
2890 mut global_start_line := ''
2891 mut global_body_lines := []string{}
2892 mut in_type_block := false
2893 mut in_enum_block := false
2894 mut in_interface_block := false
2895 for source_line in lines {
2896 mut line := source_line
2897 line = restore_fn_return_type_from_source(line, source_fn_returns)
2898 trimmed := line.trim_space()
2899 if !in_global_block {
2900 if header_starts_type_block(trimmed) {
2901 in_type_block = true
2902 in_enum_block = trimmed.starts_with('enum ') || trimmed.starts_with('pub enum ')
2903 in_interface_block = trimmed.starts_with('interface ')
2904 || trimmed.starts_with('pub interface ')
2905 out << line
2906 continue
2907 }
2908 if in_type_block {
2909 if trimmed == '}' {
2910 in_type_block = false
2911 in_enum_block = false
2912 in_interface_block = false
2913 out << line
2914 continue
2915 }
2916 if !in_enum_block
2917 && header_type_block_line_is_malformed(trimmed, in_interface_block) {
2918 continue
2919 }
2920 }
2921 }
2922 if header_starts_global_block(trimmed) {
2923 in_global_block = true
2924 global_start_line = line
2925 global_body_lines = []string{}
2926 continue
2927 }
2928 if in_global_block {
2929 if trimmed == ')' {
2930 in_global_block = false
2931 if global_body_lines.len > 0 {
2932 out << global_start_line
2933 out << global_body_lines
2934 out << line
2935 }
2936 continue
2937 }
2938 if header_token_count(trimmed) < 2 {
2939 continue
2940 }
2941 global_body_lines << line
2942 continue
2943 }
2944 if (trimmed.starts_with('fn ') || trimmed.starts_with('pub fn '))
2945 && !header_is_c_fn_decl_line(trimmed) && header_fn_decl_line_is_malformed(trimmed) {
2946 continue
2947 }
2948 if (trimmed.starts_with('type ') || trimmed.starts_with('pub type '))
2949 && header_type_decl_line_is_malformed(trimmed) {
2950 continue
2951 }
2952 if (trimmed.starts_with('const ') || trimmed.starts_with('pub const '))
2953 && header_const_decl_line_is_malformed(trimmed) {
2954 continue
2955 }
2956 // Drop stray code lines that are not valid module-level declarations.
2957 // These can leak from the V gen output for complex modules.
2958 if !in_type_block && !in_global_block && trimmed.len > 0
2959 && !header_is_module_level_line(trimmed) {
2960 continue
2961 }
2962 out << line
2963 }
2964 return out.join('\n')
2965}
2966
2967fn header_starts_global_block(trimmed string) bool {
2968 return trimmed == '__global (' || trimmed == 'pub __global ('
2969}
2970
2971fn header_is_module_level_line(trimmed string) bool {
2972 if trimmed.len == 0 {
2973 return true
2974 }
2975 if trimmed.starts_with('//') || trimmed.starts_with('[') || trimmed.starts_with('@[') {
2976 return true
2977 }
2978 if trimmed == '}' || trimmed == ')' || trimmed == 'mut:' || trimmed == 'pub:'
2979 || trimmed == 'pub mut:' {
2980 return true
2981 }
2982 return trimmed.starts_with('module ') || trimmed.starts_with('import ')
2983 || trimmed.starts_with('fn ') || trimmed.starts_with('pub fn ')
2984 || trimmed.starts_with('struct ') || trimmed.starts_with('pub struct ')
2985 || trimmed.starts_with('enum ') || trimmed.starts_with('pub enum ')
2986 || trimmed.starts_with('type ') || trimmed.starts_with('pub type ')
2987 || trimmed.starts_with('const ') || trimmed.starts_with('pub const ')
2988 || trimmed.starts_with('interface ') || trimmed.starts_with('pub interface ')
2989 || trimmed.starts_with('union ') || trimmed.starts_with('pub union ')
2990 || trimmed.starts_with('__global') || trimmed.starts_with('pub __global')
2991}
2992
2993fn header_starts_type_block(trimmed string) bool {
2994 if !trimmed.ends_with('{') {
2995 return false
2996 }
2997 return trimmed.starts_with('struct ') || trimmed.starts_with('pub struct ')
2998 || trimmed.starts_with('union ') || trimmed.starts_with('pub union ')
2999 || trimmed.starts_with('interface ') || trimmed.starts_with('pub interface ')
3000 || trimmed.starts_with('enum ') || trimmed.starts_with('pub enum ')
3001}
3002
3003fn header_is_c_fn_decl_line(trimmed string) bool {
3004 return trimmed.starts_with('fn C.') || trimmed.starts_with('pub fn C.')
3005}
3006
3007fn header_type_block_line_is_malformed(trimmed string, in_interface_block bool) bool {
3008 if trimmed.len == 0 {
3009 return false
3010 }
3011 if trimmed.starts_with('[') || trimmed.starts_with('@[') || trimmed.starts_with('//') {
3012 return false
3013 }
3014 if trimmed == 'mut:' || trimmed == 'pub:' || trimmed == 'pub mut:'
3015 || trimmed == 'pub module_mut:' {
3016 return false
3017 }
3018 if in_interface_block && header_interface_method_line_is_valid(trimmed) {
3019 return false
3020 }
3021 if (trimmed.starts_with('fn ') || trimmed.starts_with('pub fn '))
3022 && header_fn_decl_line_is_malformed(trimmed) {
3023 return true
3024 }
3025 if header_token_count(trimmed) >= 2 {
3026 return false
3027 }
3028 // Single-token lowercase lines in type blocks are almost always fields with missing types.
3029 token := trimmed.trim_space()
3030 if token.len == 0 {
3031 return false
3032 }
3033 first := token[0]
3034 return first >= `a` && first <= `z`
3035}
3036
3037fn header_interface_method_line_is_valid(trimmed string) bool {
3038 open_idx := trimmed.index('(') or { return false }
3039 if open_idx <= 0 {
3040 return false
3041 }
3042 close_idx := header_find_matching_paren(trimmed, open_idx) or { return false }
3043 method_name := trimmed[..open_idx].trim_space()
3044 if method_name.len == 0 || method_name.contains(' ') {
3045 return false
3046 }
3047 after := trimmed[close_idx + 1..].trim_space()
3048 return after.len == 0 || !after.contains('{')
3049}
3050
3051fn header_fn_decl_line_is_malformed(line string) bool {
3052 mut i := line.index('fn ') or { return false }
3053 i += 3
3054 for i < line.len && line[i].is_space() {
3055 i++
3056 }
3057 // Optional method receiver: fn (<receiver>) name(...)
3058 if i < line.len && line[i] == `(` {
3059 recv_end := header_find_matching_paren(line, i) or { return true }
3060 receiver := line[i + 1..recv_end].trim_space()
3061 if receiver.len == 0 {
3062 return true
3063 }
3064 if !header_receiver_decl_is_valid(receiver) {
3065 return true
3066 }
3067 i = recv_end + 1
3068 for i < line.len && line[i].is_space() {
3069 i++
3070 }
3071 }
3072 // Function name
3073 for i < line.len && !line[i].is_space() && line[i] != `(` {
3074 i++
3075 }
3076 for i < line.len && line[i].is_space() {
3077 i++
3078 }
3079 if i >= line.len || line[i] != `(` {
3080 return true
3081 }
3082 params_end := header_find_matching_paren(line, i) or { return true }
3083 params := line[i + 1..params_end]
3084 return header_params_are_malformed(params)
3085}
3086
3087fn header_receiver_decl_is_valid(receiver string) bool {
3088 mut text := receiver
3089 if text.starts_with('mut ') {
3090 text = text[4..].trim_space()
3091 }
3092 if text.starts_with('shared ') {
3093 text = text[7..].trim_space()
3094 }
3095 return header_token_count(text) >= 2
3096}
3097
3098fn header_params_are_malformed(params string) bool {
3099 mut start := 0
3100 mut depth := 0
3101 for i, ch in params {
3102 match ch {
3103 `(`, `[`, `{` {
3104 depth++
3105 }
3106 `)`, `]`, `}` {
3107 depth--
3108 }
3109 `,` {
3110 if depth == 0 {
3111 part := params[start..i].trim_space()
3112 if part.len > 0 && !header_param_decl_is_valid(part) {
3113 return true
3114 }
3115 start = i + 1
3116 }
3117 }
3118 else {}
3119 }
3120 }
3121 last := params[start..].trim_space()
3122 if last.len > 0 && !header_param_decl_is_valid(last) {
3123 return true
3124 }
3125 return false
3126}
3127
3128fn header_param_decl_is_valid(param string) bool {
3129 mut text := param
3130 if text.starts_with('mut ') {
3131 text = text[4..].trim_space()
3132 }
3133 if text.starts_with('shared ') {
3134 text = text[7..].trim_space()
3135 }
3136 if text.starts_with('...') {
3137 return text.len > 3
3138 }
3139 // Header declarations should always carry both parameter name and type.
3140 return header_token_count(text) >= 2
3141}
3142
3143fn header_token_count(text string) int {
3144 mut count := 0
3145 for token in text.split_any(' \t') {
3146 if token.len > 0 {
3147 count++
3148 }
3149 }
3150 return count
3151}
3152
3153fn header_find_matching_paren(text string, start int) ?int {
3154 if start < 0 || start >= text.len || text[start] != `(` {
3155 return none
3156 }
3157 mut depth := 0
3158 for i := start; i < text.len; i++ {
3159 ch := text[i]
3160 if ch == `(` {
3161 depth++
3162 } else if ch == `)` {
3163 depth--
3164 if depth == 0 {
3165 return i
3166 }
3167 }
3168 }
3169 return none
3170}
3171
3172fn header_type_decl_line_is_malformed(line string) bool {
3173 if !line.contains('=') {
3174 return false
3175 }
3176 rhs := line.all_after_first('=').trim_space()
3177 return rhs.len == 0
3178}
3179
3180fn header_const_decl_line_is_malformed(line string) bool {
3181 if !line.contains('=') {
3182 return false
3183 }
3184 rhs := line.all_after_first('=').trim_space()
3185 return rhs.len == 0
3186}
3187