| 1 | module builder |
| 2 | |
| 3 | import os |
| 4 | import v.token |
| 5 | import v.pref |
| 6 | import v.errors |
| 7 | import v.util |
| 8 | import v.ast |
| 9 | import v.ast.walker |
| 10 | import v.vmod |
| 11 | import v.checker |
| 12 | import v.transformer |
| 13 | import v.comptime |
| 14 | import v.generics |
| 15 | import v.parser |
| 16 | import v.markused |
| 17 | import v.depgraph |
| 18 | import v.callgraph |
| 19 | import v.dotgraph |
| 20 | // import x.json2 |
| 21 | |
| 22 | fn append_map_array(mut items map[string][]string, key string, value string) { |
| 23 | if key !in items { |
| 24 | items[key] = []string{} |
| 25 | } |
| 26 | items[key] << value |
| 27 | } |
| 28 | |
| 29 | pub struct Builder { |
| 30 | pub: |
| 31 | compiled_dir string // contains os.real_path() of the dir of the final file being compiled, or the dir itself when doing `v .` |
| 32 | module_path string |
| 33 | pub mut: |
| 34 | checker &checker.Checker = unsafe { nil } |
| 35 | transformer &transformer.Transformer = unsafe { nil } |
| 36 | comptime &comptime.Comptime = unsafe { nil } |
| 37 | generics &generics.Generics = unsafe { nil } |
| 38 | out_name_c string |
| 39 | out_name_js string |
| 40 | stats_lines int // size of backend generated source code in lines |
| 41 | stats_bytes int // size of backend generated source code in bytes |
| 42 | nr_errors int // accumulated error count of scanner, parser, checker, and builder |
| 43 | nr_warnings int // accumulated warning count of scanner, parser, checker, and builder |
| 44 | nr_notices int // accumulated notice count of scanner, parser, checker, and builder |
| 45 | pref &pref.Preferences = unsafe { nil } |
| 46 | module_search_paths []string |
| 47 | parsed_files []&ast.File |
| 48 | //$if windows { |
| 49 | cached_msvc MsvcResult |
| 50 | //} |
| 51 | table &ast.Table = unsafe { nil } |
| 52 | ccoptions CcompilerOptions |
| 53 | // Note: changes in mod `builtin` force invalidation of every other .v file |
| 54 | mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os` |
| 55 | mod_invalidates_mods map[string][]string // changes in mod `os`, force invalidation of mods, that do `import os` |
| 56 | path_invalidates_mods map[string][]string // changes in a .v file from `os`, invalidates `os` |
| 57 | crun_cache_keys []string // target executable + top level source files; filled in by Builder.should_rebuild |
| 58 | executable_exists bool // if the executable already exists, don't remove new executable after `v run` |
| 59 | str_args string // for parallel_cc mode only, to know which cc args to use (like -I etc) |
| 60 | disable_flto bool |
| 61 | } |
| 62 | |
| 63 | struct CFunctionCallCollector { |
| 64 | mut: |
| 65 | names map[string]bool |
| 66 | } |
| 67 | |
| 68 | fn c_function_call_collector_visit(node &ast.Node, data voidptr) bool { |
| 69 | mut c := unsafe { &CFunctionCallCollector(data) } |
| 70 | if node is ast.Expr && node is ast.CallExpr { |
| 71 | call := node as ast.CallExpr |
| 72 | if call.name.starts_with('C.') { |
| 73 | c.names[call.name] = true |
| 74 | } |
| 75 | } |
| 76 | return true |
| 77 | } |
| 78 | |
| 79 | pub fn new_builder(pref_ &pref.Preferences) Builder { |
| 80 | rdir := os.real_path(pref_.path) |
| 81 | compiled_dir := if os.is_dir(rdir) { rdir } else { os.dir(rdir) } |
| 82 | mut table := ast.new_table() |
| 83 | table.is_fmt = false |
| 84 | if pref_.use_color == .always { |
| 85 | util.emanager.set_support_color(true) |
| 86 | } |
| 87 | if pref_.use_color == .never { |
| 88 | util.emanager.set_support_color(false) |
| 89 | } |
| 90 | table.pointer_size = if pref_.m64 && pref_.backend != .wasm { 8 } else { 4 } |
| 91 | mut msvc := MsvcResult{} |
| 92 | if pref_.ccompiler_type == .msvc || pref.cc_from_string(pref_.ccompiler) == .msvc { |
| 93 | $if windows { |
| 94 | msvc = find_msvc(pref_.m64) or { |
| 95 | MsvcResult{ |
| 96 | valid: false |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | util.timing_set_should_print(pref_.show_timings || pref_.is_verbose) |
| 102 | if pref_.show_callgraph || pref_.show_depgraph { |
| 103 | dotgraph.start_digraph() |
| 104 | } |
| 105 | mut executable_name := pref_.out_name |
| 106 | $if windows { |
| 107 | executable_name += '.exe' |
| 108 | } |
| 109 | return Builder{ |
| 110 | pref: pref_ |
| 111 | table: table |
| 112 | checker: checker.new_checker(table, pref_) |
| 113 | transformer: transformer.new_transformer_with_table(table, pref_) |
| 114 | comptime: comptime.new_comptime_with_table(table, pref_) |
| 115 | generics: generics.new_generics_with_table(table, pref_) |
| 116 | compiled_dir: compiled_dir |
| 117 | cached_msvc: msvc |
| 118 | executable_exists: os.is_file(executable_name) |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | fn (v &Builder) msvc_object_path(path string) string { |
| 123 | path_without_obj_postfix := if path.ends_with('.obj') { |
| 124 | path[..path.len - 4] |
| 125 | } else if path.ends_with('.o') { |
| 126 | path[..path.len - 2] |
| 127 | } else { |
| 128 | path |
| 129 | } |
| 130 | return os.real_path(if v.pref.is_debug { |
| 131 | // MSVC debug builds use /MDd, so they need their own thirdparty object files. |
| 132 | '${path_without_obj_postfix}.debug.obj' |
| 133 | } else { |
| 134 | '${path_without_obj_postfix}.obj' |
| 135 | }) |
| 136 | } |
| 137 | |
| 138 | fn (mut v Builder) msvc_thirdparty_obj_path(mod string, path string, cached_path string) string { |
| 139 | base_path := if cached_path != '' { |
| 140 | cached_path |
| 141 | } else { |
| 142 | // Reuse the cache-derived .o path so different targets/options do not share one .obj. |
| 143 | v.pref.cache_manager.mod_postfix_with_key2cpath(mod, '.o', os.real_path(path)) |
| 144 | } |
| 145 | return v.msvc_object_path(base_path) |
| 146 | } |
| 147 | |
| 148 | pub fn (mut b Builder) interpret_text(code string, v_files []string) ! { |
| 149 | b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref) |
| 150 | b.parsed_files << parser.parse_text(code, '', mut b.table, .skip_comments, b.pref) |
| 151 | if b.should_stop_after_frontend_error() && b.has_frontend_errors() { |
| 152 | exit(1) |
| 153 | } |
| 154 | b.parse_imports() |
| 155 | b.check_unused_imports() |
| 156 | b.print_frontend_builder_errors() |
| 157 | if b.should_stop_after_frontend_error() && b.has_frontend_errors() { |
| 158 | exit(1) |
| 159 | } |
| 160 | |
| 161 | if b.pref.only_check_syntax { |
| 162 | return error_with_code('stop_after_parser', 7001) |
| 163 | } |
| 164 | |
| 165 | b.middle_stages()! |
| 166 | } |
| 167 | |
| 168 | pub fn (mut b Builder) front_stages(v_files []string) ! { |
| 169 | mut timers := util.get_timers() |
| 170 | util.timing_start('ALL_FRONT_STAGES') |
| 171 | defer { |
| 172 | timers.show('ALL_FRONT_STAGES') |
| 173 | } |
| 174 | util.timing_start('PARSE') |
| 175 | |
| 176 | util.timing_start('Builder.front_stages.parse_files') |
| 177 | b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref) |
| 178 | timers.show('Builder.front_stages.parse_files') |
| 179 | if b.should_stop_after_frontend_error() && b.has_frontend_errors() { |
| 180 | exit(1) |
| 181 | } |
| 182 | |
| 183 | b.parse_imports() |
| 184 | b.check_unused_imports() |
| 185 | b.print_frontend_builder_errors() |
| 186 | if b.should_stop_after_frontend_error() && b.has_frontend_errors() { |
| 187 | exit(1) |
| 188 | } |
| 189 | |
| 190 | timers.show('SCAN') |
| 191 | timers.show('PARSE') |
| 192 | timers.show_if_exists('PARSE stmt') |
| 193 | if b.pref.only_check_syntax { |
| 194 | return error_with_code('stop_after_parser', 7001) |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | pub fn (mut b Builder) middle_stages() ! { |
| 199 | util.timing_start('CHECK') |
| 200 | |
| 201 | util.timing_start('Checker.generic_insts_to_concrete') |
| 202 | b.table.generic_insts_to_concrete() |
| 203 | util.timing_measure('Checker.generic_insts_to_concrete') |
| 204 | |
| 205 | b.checker.check_files(b.parsed_files) |
| 206 | util.timing_start('Checker.generic_insts_to_concrete.after_check') |
| 207 | b.table.generic_insts_to_concrete() |
| 208 | util.timing_measure('Checker.generic_insts_to_concrete.after_check') |
| 209 | util.timing_measure('CHECK') |
| 210 | $if trace_type_symbols_after_checker ? { |
| 211 | for t, s in b.table.type_symbols { |
| 212 | println('> t: ${t:10} | s.mod: ${s.mod:-40} | s.name: ${'${s.name#[..30]}':-30} | s.is_builtin: ${s.is_builtin:6} | s.is_pub: ${s.is_pub}') |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | if b.pref.new_generic_solver { |
| 217 | util.timing_start('GENERICS') |
| 218 | b.generics.solve_files(b.parsed_files) |
| 219 | util.timing_start('Checker.generic_insts_to_concrete.after_generics') |
| 220 | b.table.generic_insts_to_concrete() |
| 221 | util.timing_measure('Checker.generic_insts_to_concrete.after_generics') |
| 222 | util.timing_measure('GENERICS') |
| 223 | } |
| 224 | |
| 225 | util.timing_start('COMPTIME') |
| 226 | b.comptime.solve_files(b.parsed_files) |
| 227 | util.timing_measure('COMPTIME') |
| 228 | |
| 229 | if b.pref.dump_defines != '' { |
| 230 | b.dump_defines() |
| 231 | } |
| 232 | mut mcache := vmod.get_cache() |
| 233 | mcache.debug() |
| 234 | b.print_warnings_and_errors() |
| 235 | if b.checker.should_abort { |
| 236 | return error('too many errors/warnings/notices') |
| 237 | } |
| 238 | if b.checker.unresolved_fixed_sizes.len > 0 { |
| 239 | util.timing_start('Checker.update_unresolved_fixed_sizes') |
| 240 | b.checker.update_unresolved_fixed_sizes() |
| 241 | util.timing_measure('Checker.update_unresolved_fixed_sizes') |
| 242 | } |
| 243 | if b.pref.check_only { |
| 244 | return error_with_code('stop_after_checker', 8001) |
| 245 | } |
| 246 | util.timing_start('TRANSFORM') |
| 247 | b.transformer.transform_files(b.parsed_files) |
| 248 | util.timing_measure('TRANSFORM') |
| 249 | |
| 250 | b.table.complete_interface_check() |
| 251 | if b.pref.skip_unused { |
| 252 | markused.mark_used(mut b.table, mut b.pref, b.parsed_files) |
| 253 | } |
| 254 | if b.pref.show_callgraph { |
| 255 | callgraph.show(mut b.table, b.pref, b.parsed_files) |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | pub fn (mut b Builder) front_and_middle_stages(v_files []string) ! { |
| 260 | b.front_stages(v_files)! |
| 261 | b.middle_stages()! |
| 262 | } |
| 263 | |
| 264 | @[inline] |
| 265 | fn (b &Builder) should_stop_after_frontend_error() bool { |
| 266 | return b.pref.fatal_errors |
| 267 | || (b.pref.output_mode == .stdout && !b.pref.check_only && !b.pref.is_vls) |
| 268 | } |
| 269 | |
| 270 | @[inline] |
| 271 | fn (b &Builder) has_frontend_errors() bool { |
| 272 | return b.parsed_files.any(it.errors.len > 0) |
| 273 | } |
| 274 | |
| 275 | // parse all deps from already parsed files |
| 276 | pub fn (mut b Builder) parse_imports() { |
| 277 | util.timing_start(@METHOD) |
| 278 | defer { |
| 279 | util.timing_measure(@METHOD) |
| 280 | } |
| 281 | mut done_imports := []string{} |
| 282 | if b.pref.is_vsh { |
| 283 | done_imports << 'os' |
| 284 | } |
| 285 | // TODO: (joe): decide if this is correct solution. |
| 286 | // in the case of building a module, the actual module files |
| 287 | // are passed via cmd line, so they have already been parsed |
| 288 | // by this stage. note that if one files from a module was |
| 289 | // parsed (but not all of them), then this will cause a problem. |
| 290 | // we could add a list of parsed files instead, but I think |
| 291 | // there is a better solution all around, I will revisit this. |
| 292 | // NOTE: there is a very similar occurrence with the way |
| 293 | // internal module test's work, and this was the reason there |
| 294 | // were issues with duplicate declarations, so we should sort |
| 295 | // that out in a similar way. |
| 296 | for file in b.parsed_files { |
| 297 | if file.mod.name != 'main' && file.mod.name !in done_imports { |
| 298 | done_imports << file.mod.name |
| 299 | } |
| 300 | } |
| 301 | // Note: b.parsed_files is appended in the loop, |
| 302 | // so we can not use the shorter `for in` form. |
| 303 | for i := 0; i < b.parsed_files.len; i++ { |
| 304 | ast_file := b.parsed_files[i] |
| 305 | append_map_array(mut b.path_invalidates_mods, ast_file.path, ast_file.mod.name) |
| 306 | if ast_file.mod.name != 'builtin' { |
| 307 | append_map_array(mut b.mod_invalidates_paths, 'builtin', ast_file.path) |
| 308 | append_map_array(mut b.mod_invalidates_mods, 'builtin', ast_file.mod.name) |
| 309 | } |
| 310 | for imp in ast_file.imports { |
| 311 | mod := imp.mod |
| 312 | append_map_array(mut b.mod_invalidates_paths, mod, ast_file.path) |
| 313 | append_map_array(mut b.mod_invalidates_mods, mod, ast_file.mod.name) |
| 314 | if mod == 'builtin' { |
| 315 | b.parsed_files[i].errors << b.error_with_pos('cannot import module "builtin"', |
| 316 | ast_file.path, imp.pos) |
| 317 | break |
| 318 | } |
| 319 | if mod in done_imports { |
| 320 | continue |
| 321 | } |
| 322 | import_path := b.find_module_path(mod, ast_file.path) or { |
| 323 | // v.parsers[i].error_with_token_index('cannot import module "${mod}" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod)) |
| 324 | // break |
| 325 | b.parsed_files[i].errors << b.error_with_pos('cannot import module "${mod}" (not found)', |
| 326 | ast_file.path, imp.pos) |
| 327 | break |
| 328 | } |
| 329 | v_files := b.v_files_from_dir(import_path) |
| 330 | if v_files.len == 0 { |
| 331 | // v.parsers[i].error_with_token_index('cannot import module "${mod}" (no .v files in "${import_path}")', v.parsers[i].import_ast.get_import_tok_idx(mod)) |
| 332 | b.parsed_files[i].errors << b.error_with_pos('cannot import module "${mod}" (no .v files in "${import_path}")', |
| 333 | ast_file.path, imp.pos) |
| 334 | continue |
| 335 | } |
| 336 | // eprintln('>> ast_file.path: ${ast_file.path} , done: ${done_imports}, `import ${mod}` => ${v_files}') |
| 337 | // Add all imports referenced by these libs |
| 338 | parsed_files := parser.parse_files(v_files, mut b.table, b.pref) |
| 339 | for file in parsed_files { |
| 340 | mut name := file.mod.name |
| 341 | if name == '' { |
| 342 | name = file.mod.short_name |
| 343 | } |
| 344 | sname := name.all_after_last('.') |
| 345 | smod := mod.all_after_last('.') |
| 346 | if sname != smod { |
| 347 | msg := 'bad module definition: ${ast_file.path} imports module "${mod}" but ${file.path} is defined as module `${name}`' |
| 348 | b.parsed_files[i].errors << b.error_with_pos(msg, ast_file.path, imp.pos) |
| 349 | } |
| 350 | } |
| 351 | b.parsed_files << parsed_files |
| 352 | if b.should_stop_after_frontend_error() && parsed_files.any(it.errors.len > 0) { |
| 353 | return |
| 354 | } |
| 355 | done_imports << mod |
| 356 | } |
| 357 | } |
| 358 | b.resolve_deps() |
| 359 | $if trace_parsed_files ? { |
| 360 | b.show_parsed_files() |
| 361 | } |
| 362 | if b.pref.print_v_files { |
| 363 | b.show_parsed_files() |
| 364 | exit(0) |
| 365 | } |
| 366 | if b.pref.print_watched_files { |
| 367 | for p in b.parsed_files { |
| 368 | if p.is_parse_text { |
| 369 | // a generated snippet, `v watch` does not care about those, since they are duplicates for other files |
| 370 | continue |
| 371 | } |
| 372 | println(p.path) |
| 373 | for tp in p.template_paths { |
| 374 | println(tp) |
| 375 | } |
| 376 | } |
| 377 | exit(0) |
| 378 | } |
| 379 | if b.pref.dump_files != '' { |
| 380 | b.dump_files(b.parsed_files.map(it.path)) |
| 381 | } |
| 382 | b.rebuild_modules() |
| 383 | } |
| 384 | |
| 385 | pub fn (mut b Builder) resolve_deps() { |
| 386 | util.timing_start(@METHOD) |
| 387 | defer { |
| 388 | util.timing_measure(@METHOD) |
| 389 | } |
| 390 | graph := b.import_graph() |
| 391 | deps_resolved := graph.resolve() |
| 392 | if b.pref.is_verbose { |
| 393 | eprintln('------ resolved dependencies graph: ------') |
| 394 | eprintln(deps_resolved.display()) |
| 395 | eprintln('------------------------------------------') |
| 396 | } |
| 397 | if b.pref.show_depgraph { |
| 398 | depgraph.show(deps_resolved, b.pref.path) |
| 399 | } |
| 400 | cycles := deps_resolved.display_cycles() |
| 401 | if cycles.len > 1 { |
| 402 | verror('error: import cycle detected between the following modules: \n' + cycles) |
| 403 | } |
| 404 | mut mods := []string{} |
| 405 | for node in deps_resolved.nodes { |
| 406 | mods << node.name |
| 407 | } |
| 408 | b.dump_modules(mods) |
| 409 | if b.pref.is_verbose { |
| 410 | eprintln('------ imported modules: ------') |
| 411 | eprintln(mods.str()) |
| 412 | eprintln('-------------------------------') |
| 413 | } |
| 414 | unsafe { |
| 415 | mut reordered_parsed_files := []&ast.File{} |
| 416 | for m in mods { |
| 417 | for pf in b.parsed_files { |
| 418 | if m == pf.mod.name { |
| 419 | reordered_parsed_files << pf |
| 420 | // eprintln('pf.mod.name: ${pf.mod.name} | pf.path: ${pf.path}') |
| 421 | } |
| 422 | } |
| 423 | } |
| 424 | b.table.modules = mods |
| 425 | b.parsed_files = reordered_parsed_files |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | fn import_alias_for_mod(file &ast.File, mod string) ?string { |
| 430 | for import_m in file.imports { |
| 431 | if import_m.mod == mod { |
| 432 | return import_m.alias |
| 433 | } |
| 434 | } |
| 435 | return none |
| 436 | } |
| 437 | |
| 438 | fn register_used_import(mut file ast.File, alias string) { |
| 439 | if alias !in file.used_imports { |
| 440 | file.used_imports << alias |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | fn file_needs_c_function_call_import_scan(file &ast.File) bool { |
| 445 | for import_m in file.imports { |
| 446 | alias := import_m.alias |
| 447 | if (alias.len == 1 && alias[0] == `_`) || alias in file.used_imports |
| 448 | || alias in file.auto_imports { |
| 449 | continue |
| 450 | } |
| 451 | return true |
| 452 | } |
| 453 | return false |
| 454 | } |
| 455 | |
| 456 | fn (mut b Builder) collect_used_c_function_calls(file &ast.File) map[string]bool { |
| 457 | mut collector := CFunctionCallCollector{ |
| 458 | names: map[string]bool{} |
| 459 | } |
| 460 | walker.inspect(file, &collector, c_function_call_collector_visit) |
| 461 | return collector.names |
| 462 | } |
| 463 | |
| 464 | fn (mut b Builder) mark_imports_used_by_c_function_calls() { |
| 465 | for mut file in b.parsed_files { |
| 466 | if !file_needs_c_function_call_import_scan(file) { |
| 467 | continue |
| 468 | } |
| 469 | used_c_calls := b.collect_used_c_function_calls(file) |
| 470 | if used_c_calls.len == 0 { |
| 471 | continue |
| 472 | } |
| 473 | for c_name, _ in used_c_calls { |
| 474 | if c_fn := b.table.find_fn(c_name) { |
| 475 | alias := import_alias_for_mod(file, c_fn.mod) or { continue } |
| 476 | register_used_import(mut file, alias) |
| 477 | continue |
| 478 | } |
| 479 | c_typ := b.table.find_type(c_name) |
| 480 | if c_typ == 0 { |
| 481 | continue |
| 482 | } |
| 483 | c_sym := b.table.sym(c_typ) |
| 484 | if c_sym.kind == .placeholder { |
| 485 | continue |
| 486 | } |
| 487 | alias := import_alias_for_mod(file, c_sym.mod) or { continue } |
| 488 | register_used_import(mut file, alias) |
| 489 | } |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | fn (mut b Builder) add_unused_import_message(mut file ast.File, message string, pos token.Pos) { |
| 494 | file_path := if pos.file_idx < 0 { file.path } else { b.table.filelist[pos.file_idx] } |
| 495 | if b.pref.warns_are_errors { |
| 496 | err := errors.Error{ |
| 497 | file_path: file_path |
| 498 | pos: pos |
| 499 | reporter: .parser |
| 500 | message: message |
| 501 | } |
| 502 | file.errors << err |
| 503 | if b.pref.output_mode == .stdout && !b.pref.check_only { |
| 504 | util.show_compiler_message('error:', err.CompilerMessage) |
| 505 | } |
| 506 | return |
| 507 | } |
| 508 | if b.pref.skip_warnings { |
| 509 | return |
| 510 | } |
| 511 | wrn := errors.Warning{ |
| 512 | file_path: file_path |
| 513 | pos: pos |
| 514 | reporter: .parser |
| 515 | message: message |
| 516 | } |
| 517 | file.warnings << wrn |
| 518 | if b.pref.output_mode == .stdout && !b.pref.check_only { |
| 519 | util.show_compiler_message('warning:', wrn.CompilerMessage) |
| 520 | } |
| 521 | } |
| 522 | |
| 523 | fn (mut b Builder) check_unused_imports() { |
| 524 | if b.pref.is_repl || b.pref.is_fmt { |
| 525 | return |
| 526 | } |
| 527 | b.mark_imports_used_by_c_function_calls() |
| 528 | for mut file in b.parsed_files { |
| 529 | for import_m in file.imports { |
| 530 | alias := import_m.alias |
| 531 | mod := import_m.mod |
| 532 | if (alias.len == 1 && alias[0] == `_`) || alias in file.used_imports |
| 533 | || alias in file.auto_imports { |
| 534 | continue |
| 535 | } |
| 536 | mod_alias := if alias == mod { alias } else { '${alias} (${mod})' } |
| 537 | b.add_unused_import_message(mut file, |
| 538 | "module '${mod_alias}' is imported but never used. Use `import ${mod_alias} as _`, to silence this warning, or just remove the unused import line", |
| 539 | import_m.mod_pos) |
| 540 | } |
| 541 | } |
| 542 | } |
| 543 | |
| 544 | // graph of all imported modules |
| 545 | pub fn (b &Builder) import_graph() &depgraph.DepGraph { |
| 546 | builtins := util.builtin_module_parts.clone() |
| 547 | mut graph := depgraph.new_dep_graph() |
| 548 | for p in b.parsed_files { |
| 549 | // eprintln('p.path: ${p.path}') |
| 550 | mut deps := []string{} |
| 551 | if p.mod.name !in builtins { |
| 552 | deps << 'builtin' |
| 553 | if b.pref.backend == .c { |
| 554 | // TODO: JavaScript backend doesn't handle os for now |
| 555 | // os import libraries so we exclude anything which could cause a loop |
| 556 | if p.path.ends_with('.vsh') { |
| 557 | deps << 'os' |
| 558 | } |
| 559 | } |
| 560 | } |
| 561 | for m in p.imports { |
| 562 | if m.mod == p.mod.name { |
| 563 | continue |
| 564 | } |
| 565 | deps << m.mod |
| 566 | } |
| 567 | graph.add(p.mod.name, deps) |
| 568 | } |
| 569 | $if trace_import_graph ? { |
| 570 | eprintln(graph.display()) |
| 571 | } |
| 572 | return graph |
| 573 | } |
| 574 | |
| 575 | pub fn (b &Builder) v_files_from_dir(dir string) []string { |
| 576 | if !os.exists(dir) { |
| 577 | if dir == 'compiler' && os.is_dir('vlib') { |
| 578 | println('looks like you are trying to build V with an old command') |
| 579 | println('use `v -o v cmd/v` instead of `v -o v compiler`') |
| 580 | } |
| 581 | verror("${dir} doesn't exist") |
| 582 | } else if !os.is_dir(dir) { |
| 583 | verror("${dir} isn't a directory!") |
| 584 | } |
| 585 | mut files := os.ls(dir) or { panic(err) } |
| 586 | mut source_dir := os.real_path(dir) |
| 587 | if b.pref.is_verbose { |
| 588 | println('v_files_from_dir ("${dir}")') |
| 589 | } |
| 590 | mut res := b.pref.should_compile_filtered_files(dir, files) |
| 591 | if res.len == 0 { |
| 592 | // An explicit `base_url` in v.mod still re-homes the source folder. |
| 593 | if source_root := source_root_from_vmod_root(dir) { |
| 594 | if source_root != dir && os.is_dir(source_root) { |
| 595 | if b.pref.is_verbose { |
| 596 | println('v_files_from_dir ("${source_root}") (v.mod base_url)') |
| 597 | } |
| 598 | files = os.ls(source_root) or { panic(err) } |
| 599 | source_dir = os.real_path(source_root) |
| 600 | res = b.pref.should_compile_filtered_files(source_root, files) |
| 601 | } |
| 602 | } |
| 603 | if res.len == 0 { |
| 604 | report_removed_src_layout_if_any(dir) |
| 605 | } |
| 606 | } |
| 607 | return b.with_same_module_subdir_files(source_dir, res) |
| 608 | } |
| 609 | |
| 610 | // report_removed_src_layout_if_any prints a clear error if `dir` still relies on |
| 611 | // the removed virtual `src/` module layout (sources under `dir/src/` instead of |
| 612 | // at the module root). The explicit `base_url` opt-in in v.mod is unaffected. |
| 613 | fn report_removed_src_layout_if_any(dir string) { |
| 614 | src_dir := os.join_path(dir, 'src') |
| 615 | if !os.is_dir(src_dir) { |
| 616 | return |
| 617 | } |
| 618 | src_files := os.ls(src_dir) or { return } |
| 619 | mut has_v_files := false |
| 620 | for f in src_files { |
| 621 | if f.ends_with('.v') { |
| 622 | has_v_files = true |
| 623 | break |
| 624 | } |
| 625 | } |
| 626 | if !has_v_files { |
| 627 | return |
| 628 | } |
| 629 | verror('the virtual `src/` module directory is no longer supported.\nV found .v source files under ${src_dir}, but will not treat `src/` as a virtual module root anymore.\nPlease move the sources up from `src/` into ${dir}:\n\tmv ${src_dir}/*.v ${dir}/\n\trmdir ${src_dir}\n\nIf you want to split one module across subdirectories after moving the root files, add `subdirs` to v.mod, for example:\n\tsubdirs: [\'admin\', \'repo\', \'commit\', \'ci\', \'security\', \'ssh\', \'user\']') |
| 630 | } |
| 631 | |
| 632 | fn (b &Builder) with_same_module_subdir_files(source_dir string, v_files []string) []string { |
| 633 | mut mcache := vmod.get_cache() |
| 634 | vmod_file_location := mcache.get_by_folder(source_dir) |
| 635 | if vmod_file_location.vmod_file == '' { |
| 636 | return v_files |
| 637 | } |
| 638 | module_source_root := b.module_source_root(vmod_file_location.vmod_folder) |
| 639 | if source_dir != module_source_root { |
| 640 | return v_files |
| 641 | } |
| 642 | manifest := vmod.from_file(vmod_file_location.vmod_file) or { return v_files } |
| 643 | subdirs := manifest.unknown['subdirs'] or { return v_files } |
| 644 | if subdirs.len == 0 { |
| 645 | return v_files |
| 646 | } |
| 647 | mut res := v_files.clone() |
| 648 | mut seen := map[string]bool{} |
| 649 | for file in res { |
| 650 | seen[os.real_path(file)] = true |
| 651 | } |
| 652 | for subdir in subdirs { |
| 653 | normalized_subdir := normalize_same_module_subdir(subdir, manifest.base_url) or { continue } |
| 654 | subdir_path := os.join_path(module_source_root, normalized_subdir) |
| 655 | b.collect_same_module_v_files(vmod_file_location.vmod_folder, subdir_path, mut seen, mut |
| 656 | res) |
| 657 | } |
| 658 | return res |
| 659 | } |
| 660 | |
| 661 | fn (b &Builder) module_source_root(module_root string) string { |
| 662 | real_module_root := os.real_path(module_root) |
| 663 | if source_root := source_root_from_vmod_root(real_module_root) { |
| 664 | if source_root != real_module_root && os.is_dir(source_root) { |
| 665 | return os.real_path(source_root) |
| 666 | } |
| 667 | } |
| 668 | return real_module_root |
| 669 | } |
| 670 | |
| 671 | fn normalize_same_module_subdir(subdir string, base_url string) !string { |
| 672 | mut normalized := os.norm_path(subdir.trim_space().replace('\\', os.path_separator).replace('/', |
| 673 | os.path_separator)) |
| 674 | if normalized == '' || normalized == '.' || os.is_abs_path(normalized) { |
| 675 | return error('invalid subdir') |
| 676 | } |
| 677 | if base_url != '' { |
| 678 | base := os.norm_path(base_url).trim_left(os.path_separator).trim_right(os.path_separator) |
| 679 | if base != '' { |
| 680 | base_prefix := base + os.path_separator |
| 681 | if normalized == base { |
| 682 | return error('invalid subdir') |
| 683 | } |
| 684 | if normalized.starts_with(base_prefix) { |
| 685 | normalized = normalized.all_after(base_prefix) |
| 686 | } |
| 687 | } |
| 688 | } |
| 689 | normalized = normalized.trim_left(os.path_separator).trim_right(os.path_separator) |
| 690 | if normalized == '' { |
| 691 | return error('invalid subdir') |
| 692 | } |
| 693 | parts := normalized.split(os.path_separator) |
| 694 | if '' in parts || '.' in parts || '..' in parts { |
| 695 | return error('invalid subdir') |
| 696 | } |
| 697 | return normalized |
| 698 | } |
| 699 | |
| 700 | fn (b &Builder) collect_same_module_v_files(module_root string, dir string, mut seen map[string]bool, mut res []string) { |
| 701 | if !os.is_dir(dir) { |
| 702 | return |
| 703 | } |
| 704 | real_dir := os.real_path(dir) |
| 705 | if real_dir != os.real_path(module_root) && os.is_file(os.join_path(real_dir, 'v.mod')) { |
| 706 | return |
| 707 | } |
| 708 | mut entries := os.ls(real_dir) or { return } |
| 709 | entries.sort() |
| 710 | for file in b.pref.should_compile_filtered_files(real_dir, entries) { |
| 711 | real_file := os.real_path(file) |
| 712 | if real_file !in seen { |
| 713 | seen[real_file] = true |
| 714 | res << file |
| 715 | } |
| 716 | } |
| 717 | for entry in entries { |
| 718 | subdir_path := os.join_path(real_dir, entry) |
| 719 | if os.is_dir(subdir_path) { |
| 720 | b.collect_same_module_v_files(module_root, subdir_path, mut seen, mut res) |
| 721 | } |
| 722 | } |
| 723 | } |
| 724 | |
| 725 | pub fn (b &Builder) log(s string) { |
| 726 | if b.pref.is_verbose { |
| 727 | println(s) |
| 728 | } |
| 729 | } |
| 730 | |
| 731 | pub fn (b &Builder) info(s string) { |
| 732 | if b.pref.is_verbose { |
| 733 | println(s) |
| 734 | } |
| 735 | } |
| 736 | |
| 737 | @[inline] |
| 738 | pub fn module_path(mod string) string { |
| 739 | // submodule support |
| 740 | return mod.replace('.', os.path_separator) |
| 741 | } |
| 742 | |
| 743 | fn manifest_from_vmod_root(vmod_root string) !vmod.Manifest { |
| 744 | vmod_path := os.join_path(vmod_root, 'v.mod') |
| 745 | if !os.is_file(vmod_path) { |
| 746 | return error('module not found') |
| 747 | } |
| 748 | return vmod.from_file(vmod_path) or { return error('module not found') } |
| 749 | } |
| 750 | |
| 751 | fn source_root_from_vmod_root(vmod_root string) !string { |
| 752 | manifest := manifest_from_vmod_root(vmod_root)! |
| 753 | if manifest.base_url == '' { |
| 754 | return error('module not found') |
| 755 | } |
| 756 | return manifest.source_root(vmod_root) |
| 757 | } |
| 758 | |
| 759 | fn find_module_path_from_vmod_root(vmod_root string, mod string) !string { |
| 760 | manifest := manifest_from_vmod_root(vmod_root)! |
| 761 | if manifest.base_url == '' { |
| 762 | return error('module not found') |
| 763 | } |
| 764 | tail_path := mod_tail_after_vmod_name(mod, manifest.name) or { |
| 765 | return error('module not found') |
| 766 | } |
| 767 | if tail_path == '' { |
| 768 | return error('module not found') |
| 769 | } |
| 770 | try_path := os.join_path(manifest.source_root(vmod_root), tail_path) |
| 771 | if os.is_dir(try_path) { |
| 772 | return try_path |
| 773 | } |
| 774 | return error('module not found') |
| 775 | } |
| 776 | |
| 777 | fn find_module_path_from_search_root(search_path string, mod string) !string { |
| 778 | mod_path := module_path(mod) |
| 779 | try_path := os.join_path_single(search_path, mod_path) |
| 780 | if os.is_dir(try_path) { |
| 781 | return try_path |
| 782 | } |
| 783 | if src_try_path := find_module_path_from_vmod_root(search_path, mod) { |
| 784 | return src_try_path |
| 785 | } |
| 786 | mod_parts := mod.split('.') |
| 787 | for i := mod_parts.len - 1; i > 0; i-- { |
| 788 | candidate_root := os.join_path_single(search_path, mod_parts[..i].join(os.path_separator)) |
| 789 | if !os.is_file(os.join_path(candidate_root, 'v.mod')) { |
| 790 | continue |
| 791 | } |
| 792 | source_root := source_root_from_vmod_root(candidate_root) or { continue } |
| 793 | submodule_path := mod_parts[i..].join(os.path_separator) |
| 794 | src_try_path := os.join_path(source_root, submodule_path) |
| 795 | if os.is_dir(src_try_path) { |
| 796 | return src_try_path |
| 797 | } |
| 798 | } |
| 799 | return error('module not found') |
| 800 | } |
| 801 | |
| 802 | fn mod_tail_after_vmod_name(mod string, vmod_name string) !string { |
| 803 | if vmod_name == '' { |
| 804 | return error('module not found') |
| 805 | } |
| 806 | mod_parts := mod.split('.') |
| 807 | vmod_parts := vmod_name.split('.') |
| 808 | for i := 0; i + vmod_parts.len <= mod_parts.len; i++ { |
| 809 | if i > 1 { |
| 810 | break |
| 811 | } |
| 812 | if mod_parts[i..i + vmod_parts.len].join('.') == vmod_name { |
| 813 | return mod_parts[i + vmod_parts.len..].join(os.path_separator) |
| 814 | } |
| 815 | } |
| 816 | return error('module not found') |
| 817 | } |
| 818 | |
| 819 | fn (b &Builder) module_path_has_v_files(path string) bool { |
| 820 | return b.v_files_from_dir(path).len > 0 |
| 821 | } |
| 822 | |
| 823 | // TODO: try to merge this & util.module functions to create a |
| 824 | // reliable multi use function. see comments in util/module.v |
| 825 | pub fn (b &Builder) find_module_path(mod string, fpath string) !string { |
| 826 | // support @VEXEROOT/v.mod relative paths: |
| 827 | mut mcache := vmod.get_cache() |
| 828 | resolved_fpath := os.real_path(fpath) |
| 829 | vmod_file_location := mcache.get_by_file(resolved_fpath) |
| 830 | mod_path := module_path(mod) |
| 831 | mut module_lookup_paths := []string{} |
| 832 | if vmod_file_location.vmod_file.len != 0 |
| 833 | && vmod_file_location.vmod_folder !in b.module_search_paths { |
| 834 | module_lookup_paths << vmod_file_location.vmod_folder |
| 835 | } |
| 836 | module_lookup_paths << b.module_search_paths |
| 837 | // go up through parents looking for modules a folder. |
| 838 | // we need a proper solution that works most of the time. look at vdoc.get_parent_mod |
| 839 | if resolved_fpath.contains(os.path_separator + 'modules' + os.path_separator) { |
| 840 | parts := resolved_fpath.split(os.path_separator) |
| 841 | for i := parts.len - 2; i >= 0; i-- { |
| 842 | if parts[i] == 'modules' { |
| 843 | module_lookup_paths << parts[0..i + 1].join(os.path_separator) |
| 844 | break |
| 845 | } |
| 846 | } |
| 847 | } |
| 848 | mut empty_module_path := '' |
| 849 | for search_path in module_lookup_paths { |
| 850 | try_path := os.join_path(search_path, mod_path) |
| 851 | if b.pref.is_verbose { |
| 852 | println(' >> trying to find ${mod} in ${try_path} ..') |
| 853 | } |
| 854 | if found_path := find_module_path_from_search_root(search_path, mod) { |
| 855 | if b.module_path_has_v_files(found_path) { |
| 856 | if b.pref.is_verbose { |
| 857 | println(' << found ${found_path} .') |
| 858 | } |
| 859 | return found_path |
| 860 | } |
| 861 | if empty_module_path == '' { |
| 862 | empty_module_path = found_path |
| 863 | } |
| 864 | if b.pref.is_verbose { |
| 865 | println(' << skipped ${found_path} (no .v files) .') |
| 866 | } |
| 867 | } |
| 868 | } |
| 869 | // look up through parents |
| 870 | mut current_dir := os.dir(resolved_fpath) |
| 871 | for { |
| 872 | try_path := os.join_path(current_dir, mod_path) |
| 873 | if b.pref.is_verbose { |
| 874 | println(' >> trying to find ${mod} in ${try_path} ..') |
| 875 | } |
| 876 | if found_path := find_module_path_from_search_root(current_dir, mod) { |
| 877 | if b.module_path_has_v_files(found_path) { |
| 878 | if b.pref.is_verbose { |
| 879 | println(' << found ${found_path} .') |
| 880 | } |
| 881 | return found_path |
| 882 | } |
| 883 | if empty_module_path == '' { |
| 884 | empty_module_path = found_path |
| 885 | } |
| 886 | if b.pref.is_verbose { |
| 887 | println(' << skipped ${found_path} (no .v files) .') |
| 888 | } |
| 889 | } |
| 890 | parent_dir := os.dir(current_dir) |
| 891 | if parent_dir == current_dir { |
| 892 | break |
| 893 | } |
| 894 | current_dir = parent_dir |
| 895 | } |
| 896 | if empty_module_path != '' { |
| 897 | return empty_module_path |
| 898 | } |
| 899 | smodule_lookup_paths := module_lookup_paths.join(', ') |
| 900 | return error('module "${mod}" not found in:\n${smodule_lookup_paths}') |
| 901 | } |
| 902 | |
| 903 | pub fn (b &Builder) show_total_warns_and_errors_stats() { |
| 904 | if b.nr_errors == 0 && b.nr_warnings == 0 && b.nr_notices == 0 { |
| 905 | return |
| 906 | } |
| 907 | if b.pref.is_stats { |
| 908 | mut nr_errors := b.checker.nr_errors |
| 909 | mut nr_warnings := b.checker.nr_warnings |
| 910 | mut nr_notices := b.checker.nr_notices |
| 911 | |
| 912 | if b.pref.check_only { |
| 913 | nr_errors = b.nr_errors |
| 914 | nr_warnings = b.nr_warnings |
| 915 | nr_notices = b.nr_notices |
| 916 | } |
| 917 | |
| 918 | estring := util.bold(nr_errors.str()) |
| 919 | wstring := util.bold(nr_warnings.str()) |
| 920 | nstring := util.bold(nr_notices.str()) |
| 921 | |
| 922 | if b.pref.check_only { |
| 923 | println('summary: ${estring} V errors, ${wstring} V warnings, ${nstring} V notices') |
| 924 | } else { |
| 925 | println('checker summary: ${estring} V errors, ${wstring} V warnings, ${nstring} V notices') |
| 926 | } |
| 927 | } |
| 928 | if !b.pref.is_vls && b.checker.nr_errors > 0 && b.pref.path.ends_with('.v') |
| 929 | && os.is_file(b.pref.path) && !b.pref.path.ends_with('vrepl_temp.v') { |
| 930 | if b.checker.errors.any(it.message.starts_with('unknown ')) { |
| 931 | // Sometimes users try to `v main.v`, when they have several .v files in their project. |
| 932 | // Then, they encounter puzzling errors about missing or unknown types. In this case, |
| 933 | // the intended command may have been `v .` instead, so just suggest that: |
| 934 | old_cmd := util.bold('v ${b.pref.path}') |
| 935 | new_cmd := util.bold('v ${os.dir(b.pref.path)}') |
| 936 | eprintln(util.color('notice', |
| 937 | 'If the code of your project is in a folder with multiple .v files, try `${new_cmd}` instead of `${old_cmd}`')) |
| 938 | } |
| 939 | } |
| 940 | } |
| 941 | |
| 942 | pub fn (mut b Builder) print_warnings_and_errors() { |
| 943 | defer { |
| 944 | b.show_total_warns_and_errors_stats() |
| 945 | } |
| 946 | |
| 947 | for file in b.parsed_files { |
| 948 | b.nr_errors += file.errors.len |
| 949 | b.nr_warnings += file.warnings.len |
| 950 | b.nr_notices += file.notices.len |
| 951 | } |
| 952 | |
| 953 | if b.pref.output_mode == .silent { |
| 954 | if b.nr_errors > 0 { |
| 955 | exit(1) |
| 956 | } |
| 957 | return |
| 958 | } |
| 959 | |
| 960 | if b.pref.check_only { |
| 961 | if !b.pref.skip_notes && !b.pref.json_errors { |
| 962 | for file in b.parsed_files { |
| 963 | for err in file.notices { |
| 964 | kind := if b.pref.is_verbose { |
| 965 | '${err.reporter} notice #${b.nr_notices}:' |
| 966 | } else { |
| 967 | 'notice:' |
| 968 | } |
| 969 | util.show_compiler_message(kind, err.CompilerMessage) |
| 970 | } |
| 971 | } |
| 972 | } |
| 973 | |
| 974 | mut json_errors := []util.JsonError{} |
| 975 | for file in b.parsed_files { |
| 976 | for err in file.errors { |
| 977 | kind := if b.pref.is_verbose { |
| 978 | '${err.reporter} error #${b.nr_errors}:' |
| 979 | } else { |
| 980 | 'error:' |
| 981 | } |
| 982 | |
| 983 | if b.pref.json_errors { |
| 984 | json_errors << util.JsonError{ |
| 985 | message: err.message |
| 986 | path: os.to_slash(err.file_path) |
| 987 | line_nr: err.pos.line_nr + 1 |
| 988 | col: err.pos.col + 1 |
| 989 | } |
| 990 | // util.print_json_error(kind, err.CompilerMessage) |
| 991 | } else { |
| 992 | util.show_compiler_message(kind, err.CompilerMessage) |
| 993 | } |
| 994 | } |
| 995 | } |
| 996 | if b.pref.json_errors { |
| 997 | if !b.pref.is_vls |
| 998 | || b.pref.linfo.method !in [.definition, .completion, .signature_help, .hover] { |
| 999 | util.print_json_errors(json_errors) |
| 1000 | } |
| 1001 | // eprintln(json2.encode_pretty(json_errors)) |
| 1002 | } |
| 1003 | if !b.pref.skip_warnings { |
| 1004 | for file in b.parsed_files { |
| 1005 | for err in file.warnings { |
| 1006 | kind := if b.pref.is_verbose { |
| 1007 | '${err.reporter} warning #${b.nr_warnings}:' |
| 1008 | } else { |
| 1009 | 'warning:' |
| 1010 | } |
| 1011 | util.show_compiler_message(kind, err.CompilerMessage) |
| 1012 | } |
| 1013 | } |
| 1014 | } |
| 1015 | |
| 1016 | b.show_total_warns_and_errors_stats() |
| 1017 | if b.nr_errors > 0 { |
| 1018 | exit(1) |
| 1019 | } |
| 1020 | } |
| 1021 | |
| 1022 | if b.pref.is_verbose && b.checker.nr_warnings > 1 { |
| 1023 | println('${b.checker.nr_warnings} warnings') |
| 1024 | } |
| 1025 | if b.pref.is_verbose && b.checker.nr_notices > 1 { |
| 1026 | println('${b.checker.nr_notices} notices') |
| 1027 | } |
| 1028 | if b.checker.nr_notices > 0 && !b.pref.skip_notes { |
| 1029 | for err in b.checker.notices { |
| 1030 | kind := if b.pref.is_verbose { |
| 1031 | '${err.reporter} notice #${b.checker.nr_notices}:' |
| 1032 | } else { |
| 1033 | 'notice:' |
| 1034 | } |
| 1035 | util.show_compiler_message(kind, err.CompilerMessage) |
| 1036 | } |
| 1037 | } |
| 1038 | if b.checker.nr_warnings > 0 && !b.pref.skip_warnings { |
| 1039 | for err in b.checker.warnings { |
| 1040 | kind := if b.pref.is_verbose { |
| 1041 | '${err.reporter} warning #${b.checker.nr_warnings}:' |
| 1042 | } else { |
| 1043 | 'warning:' |
| 1044 | } |
| 1045 | util.show_compiler_message(kind, err.CompilerMessage) |
| 1046 | } |
| 1047 | } |
| 1048 | |
| 1049 | if b.pref.is_verbose && b.checker.nr_errors > 1 { |
| 1050 | println('${b.checker.nr_errors} errors') |
| 1051 | } |
| 1052 | if b.checker.nr_errors > 0 { |
| 1053 | for err in b.checker.errors { |
| 1054 | kind := if b.pref.is_verbose { |
| 1055 | '${err.reporter} error #${b.checker.nr_errors}:' |
| 1056 | } else { |
| 1057 | 'error:' |
| 1058 | } |
| 1059 | util.show_compiler_message(kind, err.CompilerMessage) |
| 1060 | } |
| 1061 | b.show_total_warns_and_errors_stats() |
| 1062 | exit(1) |
| 1063 | } |
| 1064 | if b.table.redefined_fns.len > 0 { |
| 1065 | mut total_conflicts := 0 |
| 1066 | for fn_name in b.table.redefined_fns { |
| 1067 | // Find where this function was already declared |
| 1068 | mut redefines := []FunctionRedefinition{} |
| 1069 | mut redefine_conflicts := map[string]int{} |
| 1070 | for file in b.parsed_files { |
| 1071 | for stmt in file.stmts { |
| 1072 | if stmt is ast.FnDecl { |
| 1073 | if stmt.name == fn_name { |
| 1074 | fheader := b.table.stringify_fn_decl(&stmt, 'main', |
| 1075 | map[string]string{}, false) |
| 1076 | redefines << FunctionRedefinition{ |
| 1077 | fpath: file.path |
| 1078 | fline: stmt.pos.line_nr |
| 1079 | f: stmt |
| 1080 | fheader: fheader |
| 1081 | } |
| 1082 | redefine_conflicts[fheader]++ |
| 1083 | } |
| 1084 | } |
| 1085 | } |
| 1086 | } |
| 1087 | if redefines.len > 0 { |
| 1088 | util.show_compiler_message('builder error:', |
| 1089 | message: 'redefinition of function `${fn_name}`' |
| 1090 | ) |
| 1091 | for redefine in redefines { |
| 1092 | util.show_compiler_message('conflicting declaration:', |
| 1093 | message: redefine.fheader |
| 1094 | file_path: redefine.fpath |
| 1095 | pos: redefine.f.pos |
| 1096 | ) |
| 1097 | } |
| 1098 | total_conflicts++ |
| 1099 | } |
| 1100 | } |
| 1101 | if total_conflicts > 0 { |
| 1102 | b.show_total_warns_and_errors_stats() |
| 1103 | exit(1) |
| 1104 | } |
| 1105 | } |
| 1106 | } |
| 1107 | |
| 1108 | struct FunctionRedefinition { |
| 1109 | fpath string |
| 1110 | fline int |
| 1111 | fheader string |
| 1112 | f ast.FnDecl |
| 1113 | } |
| 1114 | |
| 1115 | pub fn (b &Builder) error_with_pos(s string, fpath string, pos token.Pos) errors.Error { |
| 1116 | return errors.Error{ |
| 1117 | file_path: fpath |
| 1118 | pos: pos |
| 1119 | reporter: .builder |
| 1120 | message: s |
| 1121 | } |
| 1122 | } |
| 1123 | |
| 1124 | fn (b &Builder) print_frontend_builder_errors() { |
| 1125 | if b.pref.check_only || b.pref.output_mode != .stdout { |
| 1126 | return |
| 1127 | } |
| 1128 | for file in b.parsed_files { |
| 1129 | for err in file.errors { |
| 1130 | if err.reporter == .builder { |
| 1131 | util.show_compiler_message('builder error:', err.CompilerMessage) |
| 1132 | } |
| 1133 | } |
| 1134 | } |
| 1135 | } |
| 1136 | |
| 1137 | @[noreturn] |
| 1138 | pub fn verror(s string) { |
| 1139 | util.verror('builder error', s) |
| 1140 | } |
| 1141 | |
| 1142 | pub fn (mut b Builder) show_parsed_files() { |
| 1143 | for p in b.parsed_files { |
| 1144 | if p.is_parse_text { |
| 1145 | println(p.path + ':parse_text') |
| 1146 | } |
| 1147 | println(p.path) |
| 1148 | } |
| 1149 | } |
| 1150 | |