@@ -1011,6 +1011,28 @@ fn (b &Builder) inject_cached_core_forward_decls(source string) string {
10111011 if decls == '' {
10121012 return source
10131013 }
1014+ // Collect names already defined in the main source (typedefs and macros)
1015+ // to avoid duplicate/conflicting injected declarations.
1016+ mut existing_names := map [string ]bool {}
1017+ for src_line in source.split_into_lines () {
1018+ trimmed := src_line.trim_space ()
1019+ if trimmed.starts_with ('typedef ' ) && trimmed.ends_with (';' ) {
1020+ name := extract_typedef_name (trimmed)
1021+ if name.len > 0 {
1022+ existing_names[name] = true
1023+ }
1024+ } else if trimmed.starts_with ('#define ' ) {
1025+ // Extract macro name: "#define NAME" or "#define NAME(..."
1026+ rest := trimmed[8 ..]
1027+ mut end := 0
1028+ for end < rest.len && rest[end] != `(` && ! rest[end].is_space () {
1029+ end++
1030+ }
1031+ if end > 0 {
1032+ existing_names[rest[..end]] = true
1033+ }
1034+ }
1035+ }
10141036 mut lines := source.split_into_lines ()
10151037 mut out := []string {cap: lines.len + decls.count ('\n ' ) + 2 }
10161038 mut saw_cached_decl := false
@@ -1023,9 +1045,24 @@ fn (b &Builder) inject_cached_core_forward_decls(source string) string {
10231045 }
10241046 if saw_cached_decl && line == '' && ! inserted {
10251047 for decl_line in decls.split_into_lines () {
1026- if decl_line != '' {
1027- out << decl_line
1048+ if decl_line == '' {
1049+ continue
1050+ }
1051+ // Skip declarations whose name conflicts with main source.
1052+ if decl_line.starts_with ('typedef ' ) {
1053+ name := extract_typedef_name (decl_line)
1054+ if name.len > 0 && name in existing_names {
1055+ continue
1056+ }
1057+ } else {
1058+ // Function forward declaration — extract fn name and skip
1059+ // if it conflicts with a #define macro in the main source.
1060+ fn_name := extract_fn_decl_name (decl_line)
1061+ if fn_name.len > 0 && fn_name in existing_names {
1062+ continue
1063+ }
10281064 }
1065+ out << decl_line
10291066 }
10301067 out << ''
10311068 inserted = true
@@ -1037,63 +1074,119 @@ fn (b &Builder) inject_cached_core_forward_decls(source string) string {
10371074 return out.join ('\n ' )
10381075}
10391076
1077+ fn extract_typedef_name (line string ) string {
1078+ // For "typedef <something> Name;" or "typedef struct Name { ... } Name;"
1079+ // extract the name just before the final ';'.
1080+ trimmed := line.trim_right ('; ' )
1081+ // The typedef name is the last space-separated token, but handle
1082+ // attribute suffixes like __attribute__((...))).
1083+ if trimmed.ends_with (')' ) {
1084+ // Typedef with attribute — find name before __attribute__
1085+ attr_idx := trimmed.index ('__attribute__' ) or { return '' }
1086+ before_attr := trimmed[..attr_idx].trim_space ()
1087+ last_space := before_attr.last_index (' ' ) or { return before_attr }
1088+ return before_attr[last_space + 1 ..]
1089+ }
1090+ if trimmed.ends_with ('}' ) {
1091+ // "typedef struct X { ... } X" — find name after '}'
1092+ brace_end := trimmed.last_index ('}' ) or { return '' }
1093+ return trimmed[brace_end + 1 ..].trim_space ()
1094+ }
1095+ last_space := trimmed.last_index (' ' ) or { return trimmed }
1096+ return trimmed[last_space + 1 ..]
1097+ }
1098+
1099+ fn extract_fn_decl_name (line string ) string {
1100+ // Extract function name from a C forward declaration like:
1101+ // "string time__FormatTime__str(time__FormatTime e);"
1102+ // The name is the token before '('.
1103+ paren_idx := line.index_u8 (`(` )
1104+ if paren_idx < = 0 {
1105+ return ''
1106+ }
1107+ before_paren := line[..paren_idx].trim_space ()
1108+ last_space := before_paren.last_index (' ' ) or { return before_paren }
1109+ name := before_paren[last_space + 1 ..]
1110+ // Skip pointer prefixes
1111+ if name.len > 0 && name[0 ] == `*` {
1112+ return name[1 ..]
1113+ }
1114+ return name
1115+ }
1116+
10401117fn (b &Builder) cached_core_forward_decls () string {
10411118 cache_dir := b.core_cache_dir ()
10421119 mut seen := map [string ]bool {}
1043- mut decls := []string {}
1120+ mut typedefs := []string {}
1121+ mut fn_decls := []string {}
10441122 for cache_name in [builtin_cache_name, vlib_cache_name] {
10451123 c_path := os.join_path (cache_dir, '${cache_name }.c' )
10461124 if ! os.exists (c_path) {
10471125 continue
10481126 }
1049- for decl in top_level_c_forward_decls (c_path) {
1050- if decl in seen {
1127+ tds , fds := top_level_c_decls (c_path)
1128+ for td in tds {
1129+ if td in seen {
10511130 continue
10521131 }
1053- seen[decl] = true
1054- decls << decl
1132+ seen[td] = true
1133+ typedefs << td
1134+ }
1135+ for fd in fds {
1136+ if fd in seen {
1137+ continue
1138+ }
1139+ seen[fd] = true
1140+ fn_decls << fd
10551141 }
10561142 }
1057- return decls.join ('\n ' )
1143+ mut all := []string {cap: typedefs.len + fn_decls.len}
1144+ all << typedefs
1145+ all << fn_decls
1146+ return all.join ('\n ' )
10581147}
10591148
1060- fn top_level_c_forward_decls (c_path string ) []string {
1061- lines := os.read_lines (c_path) or { return []string {} }
1062- mut decls := []string {}
1149+ // top_level_c_decls extracts typedef declarations and function forward
1150+ // declarations from a cached C source file. Returns (typedefs, fn_decls).
1151+ fn top_level_c_decls (c_path string ) ([]string , []string ) {
1152+ lines := os.read_lines (c_path) or { return []string {}, []string {} }
1153+ mut typedefs := []string {}
1154+ mut fn_decls := []string {}
10631155 for raw_line in lines {
10641156 if raw_line.len == 0 || raw_line[0 ].is_space () {
10651157 continue
10661158 }
10671159 line := raw_line.trim_space ()
1160+ // Collect typedef lines (struct/union/array/map forward typedefs).
1161+ if line.starts_with ('typedef ' ) && line.ends_with (';' ) {
1162+ typedefs << line
1163+ continue
1164+ }
1165+ // Collect function forward declarations.
10681166 if ! line.ends_with (');' ) || ! line.contains ('(' ) {
10691167 continue
10701168 }
1071- if line.starts_with ('#' ) || line.starts_with ('typedef ' ) || line.starts_with ('struct ' )
1072- || line.starts_with ('union ' ) || line.starts_with ('enum ' )
1073- || line.starts_with ('return ' ) || line.starts_with ('if ' ) || line.starts_with ('for ' )
1074- || line.starts_with ('while ' ) || line.starts_with ('switch ' ) {
1169+ if line.starts_with ('#' ) || line.starts_with ('struct ' ) || line.starts_with ('union ' )
1170+ || line.starts_with ('enum ' ) || line.starts_with ('return ' ) || line.starts_with ('if ' )
1171+ || line.starts_with ('for ' ) || line.starts_with ('while ' ) || line.starts_with ('switch ' ) {
10751172 continue
10761173 }
1077- // Skip expression fragments that start with '(' or '*'
10781174 if line[0 ] == `(` || line[0 ] == `*` {
10791175 continue
10801176 }
1081- // Skip global variable definitions (contain '=') - only extract function declarations.
10821177 if line.contains ('=' ) {
10831178 continue
10841179 }
1085- // A forward declaration has at least a return type and function name before '('.
1086- // Reject bare function calls like 'memset(...)' or 'tos(...)'.
10871180 paren_idx := line.index_u8 (`(` )
10881181 if paren_idx > 0 {
10891182 before_paren := line[..paren_idx].trim_space ()
10901183 if ! before_paren.contains (' ' ) && ! before_paren.contains ('*' ) {
10911184 continue
10921185 }
10931186 }
1094- decls << line
1187+ fn_decls << line
10951188 }
1096- return decls
1189+ return typedefs, fn_decls
10971190}
10981191
10991192fn (mut b Builder) ensure_cached_module_object (cache_dir string , cache_name string , module_paths []string , emit_modules []string , cc string , cc_flags string , error_limit_flag string ) ! string {
@@ -1305,11 +1398,34 @@ fn flag_references_missing_file(flag string) bool {
13051398fn (b &Builder) collect_cflags_from_sources () string {
13061399 mut flags := []string {}
13071400 mut seen := map [string ]bool {}
1401+ mut scanned_files := map [string ]bool {}
1402+ // Collect source file paths to scan. When .vh headers were used for
1403+ // parsing, b.files references the .vh summaries which lack #flag
1404+ // directives. Always include the original core module source files
1405+ // so that directive flags (e.g. -I paths) are never lost.
1406+ mut scan_paths := []string {}
13081407 for file in b.files {
1309- if file.name == '' {
1408+ if file.name != '' {
1409+ scan_paths << file.name
1410+ }
1411+ }
1412+ if ! b.pref.skip_builtin {
1413+ for module_path in core_cached_module_paths {
1414+ vlib_path := b.pref.get_vlib_module_path (module_path)
1415+ module_files := get_v_files_from_dir (vlib_path, b.pref.user_defines)
1416+ for mf in module_files {
1417+ if mf ! in scanned_files {
1418+ scan_paths << mf
1419+ }
1420+ }
1421+ }
1422+ }
1423+ for scan_path in scan_paths {
1424+ if scan_path == '' || scan_path in scanned_files {
13101425 continue
13111426 }
1312- lines := os.read_lines (file.name) or { continue }
1427+ scanned_files[scan_path] = true
1428+ lines := os.read_lines (scan_path) or { continue }
13131429 // Track $if nesting to skip flags inside non-matching comptime blocks.
13141430 // skip_depth > 0 means we are inside a non-matching $if block.
13151431 mut skip_depth := 0
@@ -1340,7 +1456,7 @@ fn (b &Builder) collect_cflags_from_sources() string {
13401456 if skip_depth > 0 {
13411457 continue
13421458 }
1343- mut flag := parse_flag_directive_line (line, file.name ) or { continue }
1459+ mut flag := parse_flag_directive_line (line, scan_path ) or { continue }
13441460 flag = flag.replace ('@VEXEROOT' , b.pref.vroot).replace ('VEXEROOT' , b.pref.vroot)
13451461 if flag_references_missing_file (flag) {
13461462 continue
0 commit comments