module builder import os import v.token import v.pref import v.errors import v.util import v.ast import v.ast.walker import v.vmod import v.checker import v.transformer import v.comptime import v.generics import v.parser import v.markused import v.depgraph import v.callgraph import v.dotgraph // import x.json2 fn append_map_array(mut items map[string][]string, key string, value string) { if key !in items { items[key] = []string{} } items[key] << value } pub struct Builder { pub: compiled_dir string // contains os.real_path() of the dir of the final file being compiled, or the dir itself when doing `v .` module_path string pub mut: checker &checker.Checker = unsafe { nil } transformer &transformer.Transformer = unsafe { nil } comptime &comptime.Comptime = unsafe { nil } generics &generics.Generics = unsafe { nil } out_name_c string out_name_js string stats_lines int // size of backend generated source code in lines stats_bytes int // size of backend generated source code in bytes nr_errors int // accumulated error count of scanner, parser, checker, and builder nr_warnings int // accumulated warning count of scanner, parser, checker, and builder nr_notices int // accumulated notice count of scanner, parser, checker, and builder pref &pref.Preferences = unsafe { nil } module_search_paths []string parsed_files []&ast.File //$if windows { cached_msvc MsvcResult //} table &ast.Table = unsafe { nil } ccoptions CcompilerOptions // Note: changes in mod `builtin` force invalidation of every other .v file mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os` mod_invalidates_mods map[string][]string // changes in mod `os`, force invalidation of mods, that do `import os` path_invalidates_mods map[string][]string // changes in a .v file from `os`, invalidates `os` crun_cache_keys []string // target executable + top level source files; filled in by Builder.should_rebuild executable_exists bool // if the executable already exists, don't remove new executable after `v run` str_args string // for parallel_cc mode only, to know which cc args to use (like -I etc) disable_flto bool } struct CFunctionCallCollector { mut: names map[string]bool } fn c_function_call_collector_visit(node &ast.Node, data voidptr) bool { mut c := unsafe { &CFunctionCallCollector(data) } if node is ast.Expr && node is ast.CallExpr { call := node as ast.CallExpr if call.name.starts_with('C.') { c.names[call.name] = true } } return true } pub fn new_builder(pref_ &pref.Preferences) Builder { rdir := os.real_path(pref_.path) compiled_dir := if os.is_dir(rdir) { rdir } else { os.dir(rdir) } mut table := ast.new_table() table.is_fmt = false if pref_.use_color == .always { util.emanager.set_support_color(true) } if pref_.use_color == .never { util.emanager.set_support_color(false) } table.pointer_size = if pref_.m64 && pref_.backend != .wasm { 8 } else { 4 } mut msvc := MsvcResult{} if pref_.ccompiler_type == .msvc || pref.cc_from_string(pref_.ccompiler) == .msvc { $if windows { msvc = find_msvc(pref_.m64) or { MsvcResult{ valid: false } } } } util.timing_set_should_print(pref_.show_timings || pref_.is_verbose) if pref_.show_callgraph || pref_.show_depgraph { dotgraph.start_digraph() } mut executable_name := pref_.out_name $if windows { executable_name += '.exe' } return Builder{ pref: pref_ table: table checker: checker.new_checker(table, pref_) transformer: transformer.new_transformer_with_table(table, pref_) comptime: comptime.new_comptime_with_table(table, pref_) generics: generics.new_generics_with_table(table, pref_) compiled_dir: compiled_dir cached_msvc: msvc executable_exists: os.is_file(executable_name) } } fn (v &Builder) msvc_object_path(path string) string { path_without_obj_postfix := if path.ends_with('.obj') { path[..path.len - 4] } else if path.ends_with('.o') { path[..path.len - 2] } else { path } return os.real_path(if v.pref.is_debug { // MSVC debug builds use /MDd, so they need their own thirdparty object files. '${path_without_obj_postfix}.debug.obj' } else { '${path_without_obj_postfix}.obj' }) } fn (mut v Builder) msvc_thirdparty_obj_path(mod string, path string, cached_path string) string { base_path := if cached_path != '' { cached_path } else { // Reuse the cache-derived .o path so different targets/options do not share one .obj. v.pref.cache_manager.mod_postfix_with_key2cpath(mod, '.o', os.real_path(path)) } return v.msvc_object_path(base_path) } pub fn (mut b Builder) interpret_text(code string, v_files []string) ! { b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref) b.parsed_files << parser.parse_text(code, '', mut b.table, .skip_comments, b.pref) if b.should_stop_after_frontend_error() && b.has_frontend_errors() { exit(1) } b.parse_imports() b.check_unused_imports() b.print_frontend_builder_errors() if b.should_stop_after_frontend_error() && b.has_frontend_errors() { exit(1) } if b.pref.only_check_syntax { return error_with_code('stop_after_parser', 7001) } b.middle_stages()! } pub fn (mut b Builder) front_stages(v_files []string) ! { mut timers := util.get_timers() util.timing_start('ALL_FRONT_STAGES') defer { timers.show('ALL_FRONT_STAGES') } util.timing_start('PARSE') util.timing_start('Builder.front_stages.parse_files') b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref) timers.show('Builder.front_stages.parse_files') if b.should_stop_after_frontend_error() && b.has_frontend_errors() { exit(1) } b.parse_imports() b.check_unused_imports() b.print_frontend_builder_errors() if b.should_stop_after_frontend_error() && b.has_frontend_errors() { exit(1) } timers.show('SCAN') timers.show('PARSE') timers.show_if_exists('PARSE stmt') if b.pref.only_check_syntax { return error_with_code('stop_after_parser', 7001) } } pub fn (mut b Builder) middle_stages() ! { util.timing_start('CHECK') util.timing_start('Checker.generic_insts_to_concrete') b.table.generic_insts_to_concrete() util.timing_measure('Checker.generic_insts_to_concrete') b.checker.check_files(b.parsed_files) util.timing_start('Checker.generic_insts_to_concrete.after_check') b.table.generic_insts_to_concrete() util.timing_measure('Checker.generic_insts_to_concrete.after_check') util.timing_measure('CHECK') $if trace_type_symbols_after_checker ? { for t, s in b.table.type_symbols { 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}') } } if b.pref.new_generic_solver { util.timing_start('GENERICS') b.generics.solve_files(b.parsed_files) util.timing_start('Checker.generic_insts_to_concrete.after_generics') b.table.generic_insts_to_concrete() util.timing_measure('Checker.generic_insts_to_concrete.after_generics') util.timing_measure('GENERICS') } util.timing_start('COMPTIME') b.comptime.solve_files(b.parsed_files) util.timing_measure('COMPTIME') if b.pref.dump_defines != '' { b.dump_defines() } mut mcache := vmod.get_cache() mcache.debug() b.print_warnings_and_errors() if b.checker.should_abort { return error('too many errors/warnings/notices') } if b.checker.unresolved_fixed_sizes.len > 0 { util.timing_start('Checker.update_unresolved_fixed_sizes') b.checker.update_unresolved_fixed_sizes() util.timing_measure('Checker.update_unresolved_fixed_sizes') } if b.pref.check_only { return error_with_code('stop_after_checker', 8001) } util.timing_start('TRANSFORM') b.transformer.transform_files(b.parsed_files) util.timing_measure('TRANSFORM') b.table.complete_interface_check() if b.pref.skip_unused { markused.mark_used(mut b.table, mut b.pref, b.parsed_files) } if b.pref.show_callgraph { callgraph.show(mut b.table, b.pref, b.parsed_files) } } pub fn (mut b Builder) front_and_middle_stages(v_files []string) ! { b.front_stages(v_files)! b.middle_stages()! } @[inline] fn (b &Builder) should_stop_after_frontend_error() bool { return b.pref.fatal_errors || (b.pref.output_mode == .stdout && !b.pref.check_only && !b.pref.is_vls) } @[inline] fn (b &Builder) has_frontend_errors() bool { return b.parsed_files.any(it.errors.len > 0) } // parse all deps from already parsed files pub fn (mut b Builder) parse_imports() { util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) } mut done_imports := []string{} if b.pref.is_vsh { done_imports << 'os' } // TODO: (joe): decide if this is correct solution. // in the case of building a module, the actual module files // are passed via cmd line, so they have already been parsed // by this stage. note that if one files from a module was // parsed (but not all of them), then this will cause a problem. // we could add a list of parsed files instead, but I think // there is a better solution all around, I will revisit this. // NOTE: there is a very similar occurrence with the way // internal module test's work, and this was the reason there // were issues with duplicate declarations, so we should sort // that out in a similar way. for file in b.parsed_files { if file.mod.name != 'main' && file.mod.name !in done_imports { done_imports << file.mod.name } } // Note: b.parsed_files is appended in the loop, // so we can not use the shorter `for in` form. for i := 0; i < b.parsed_files.len; i++ { ast_file := b.parsed_files[i] append_map_array(mut b.path_invalidates_mods, ast_file.path, ast_file.mod.name) if ast_file.mod.name != 'builtin' { append_map_array(mut b.mod_invalidates_paths, 'builtin', ast_file.path) append_map_array(mut b.mod_invalidates_mods, 'builtin', ast_file.mod.name) } for imp in ast_file.imports { mod := imp.mod append_map_array(mut b.mod_invalidates_paths, mod, ast_file.path) append_map_array(mut b.mod_invalidates_mods, mod, ast_file.mod.name) if mod == 'builtin' { b.parsed_files[i].errors << b.error_with_pos('cannot import module "builtin"', ast_file.path, imp.pos) break } if mod in done_imports { continue } import_path := b.find_module_path(mod, ast_file.path) or { // v.parsers[i].error_with_token_index('cannot import module "${mod}" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod)) // break b.parsed_files[i].errors << b.error_with_pos('cannot import module "${mod}" (not found)', ast_file.path, imp.pos) break } v_files := b.v_files_from_dir(import_path) if v_files.len == 0 { // 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)) b.parsed_files[i].errors << b.error_with_pos('cannot import module "${mod}" (no .v files in "${import_path}")', ast_file.path, imp.pos) continue } // eprintln('>> ast_file.path: ${ast_file.path} , done: ${done_imports}, `import ${mod}` => ${v_files}') // Add all imports referenced by these libs parsed_files := parser.parse_files(v_files, mut b.table, b.pref) for file in parsed_files { mut name := file.mod.name if name == '' { name = file.mod.short_name } sname := name.all_after_last('.') smod := mod.all_after_last('.') if sname != smod { msg := 'bad module definition: ${ast_file.path} imports module "${mod}" but ${file.path} is defined as module `${name}`' b.parsed_files[i].errors << b.error_with_pos(msg, ast_file.path, imp.pos) } } b.parsed_files << parsed_files if b.should_stop_after_frontend_error() && parsed_files.any(it.errors.len > 0) { return } done_imports << mod } } b.resolve_deps() $if trace_parsed_files ? { b.show_parsed_files() } if b.pref.print_v_files { b.show_parsed_files() exit(0) } if b.pref.print_watched_files { for p in b.parsed_files { if p.is_parse_text { // a generated snippet, `v watch` does not care about those, since they are duplicates for other files continue } println(p.path) for tp in p.template_paths { println(tp) } } exit(0) } if b.pref.dump_files != '' { b.dump_files(b.parsed_files.map(it.path)) } b.rebuild_modules() } pub fn (mut b Builder) resolve_deps() { util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) } graph := b.import_graph() deps_resolved := graph.resolve() if b.pref.is_verbose { eprintln('------ resolved dependencies graph: ------') eprintln(deps_resolved.display()) eprintln('------------------------------------------') } if b.pref.show_depgraph { depgraph.show(deps_resolved, b.pref.path) } cycles := deps_resolved.display_cycles() if cycles.len > 1 { verror('error: import cycle detected between the following modules: \n' + cycles) } mut mods := []string{} for node in deps_resolved.nodes { mods << node.name } b.dump_modules(mods) if b.pref.is_verbose { eprintln('------ imported modules: ------') eprintln(mods.str()) eprintln('-------------------------------') } unsafe { mut reordered_parsed_files := []&ast.File{} for m in mods { for pf in b.parsed_files { if m == pf.mod.name { reordered_parsed_files << pf // eprintln('pf.mod.name: ${pf.mod.name} | pf.path: ${pf.path}') } } } b.table.modules = mods b.parsed_files = reordered_parsed_files } } fn import_alias_for_mod(file &ast.File, mod string) ?string { for import_m in file.imports { if import_m.mod == mod { return import_m.alias } } return none } fn register_used_import(mut file ast.File, alias string) { if alias !in file.used_imports { file.used_imports << alias } } fn file_needs_c_function_call_import_scan(file &ast.File) bool { for import_m in file.imports { alias := import_m.alias if (alias.len == 1 && alias[0] == `_`) || alias in file.used_imports || alias in file.auto_imports { continue } return true } return false } fn (mut b Builder) collect_used_c_function_calls(file &ast.File) map[string]bool { mut collector := CFunctionCallCollector{ names: map[string]bool{} } walker.inspect(file, &collector, c_function_call_collector_visit) return collector.names } fn (mut b Builder) mark_imports_used_by_c_function_calls() { for mut file in b.parsed_files { if !file_needs_c_function_call_import_scan(file) { continue } used_c_calls := b.collect_used_c_function_calls(file) if used_c_calls.len == 0 { continue } for c_name, _ in used_c_calls { if c_fn := b.table.find_fn(c_name) { alias := import_alias_for_mod(file, c_fn.mod) or { continue } register_used_import(mut file, alias) continue } c_typ := b.table.find_type(c_name) if c_typ == 0 { continue } c_sym := b.table.sym(c_typ) if c_sym.kind == .placeholder { continue } alias := import_alias_for_mod(file, c_sym.mod) or { continue } register_used_import(mut file, alias) } } } fn (mut b Builder) add_unused_import_message(mut file ast.File, message string, pos token.Pos) { file_path := if pos.file_idx < 0 { file.path } else { b.table.filelist[pos.file_idx] } if b.pref.warns_are_errors { err := errors.Error{ file_path: file_path pos: pos reporter: .parser message: message } file.errors << err if b.pref.output_mode == .stdout && !b.pref.check_only { util.show_compiler_message('error:', err.CompilerMessage) } return } if b.pref.skip_warnings { return } wrn := errors.Warning{ file_path: file_path pos: pos reporter: .parser message: message } file.warnings << wrn if b.pref.output_mode == .stdout && !b.pref.check_only { util.show_compiler_message('warning:', wrn.CompilerMessage) } } fn (mut b Builder) check_unused_imports() { if b.pref.is_repl || b.pref.is_fmt { return } b.mark_imports_used_by_c_function_calls() for mut file in b.parsed_files { for import_m in file.imports { alias := import_m.alias mod := import_m.mod if (alias.len == 1 && alias[0] == `_`) || alias in file.used_imports || alias in file.auto_imports { continue } mod_alias := if alias == mod { alias } else { '${alias} (${mod})' } b.add_unused_import_message(mut file, "module '${mod_alias}' is imported but never used. Use `import ${mod_alias} as _`, to silence this warning, or just remove the unused import line", import_m.mod_pos) } } } // graph of all imported modules pub fn (b &Builder) import_graph() &depgraph.DepGraph { builtins := util.builtin_module_parts.clone() mut graph := depgraph.new_dep_graph() for p in b.parsed_files { // eprintln('p.path: ${p.path}') mut deps := []string{} if p.mod.name !in builtins { deps << 'builtin' if b.pref.backend == .c { // TODO: JavaScript backend doesn't handle os for now // os import libraries so we exclude anything which could cause a loop if p.path.ends_with('.vsh') { deps << 'os' } } } for m in p.imports { if m.mod == p.mod.name { continue } deps << m.mod } graph.add(p.mod.name, deps) } $if trace_import_graph ? { eprintln(graph.display()) } return graph } pub fn (b &Builder) v_files_from_dir(dir string) []string { if !os.exists(dir) { if dir == 'compiler' && os.is_dir('vlib') { println('looks like you are trying to build V with an old command') println('use `v -o v cmd/v` instead of `v -o v compiler`') } verror("${dir} doesn't exist") } else if !os.is_dir(dir) { verror("${dir} isn't a directory!") } mut files := os.ls(dir) or { panic(err) } mut source_dir := os.real_path(dir) if b.pref.is_verbose { println('v_files_from_dir ("${dir}")') } mut res := b.pref.should_compile_filtered_files(dir, files) if res.len == 0 { // An explicit `base_url` in v.mod still re-homes the source folder. if source_root := source_root_from_vmod_root(dir) { if source_root != dir && os.is_dir(source_root) { if b.pref.is_verbose { println('v_files_from_dir ("${source_root}") (v.mod base_url)') } files = os.ls(source_root) or { panic(err) } source_dir = os.real_path(source_root) res = b.pref.should_compile_filtered_files(source_root, files) } } if res.len == 0 { report_removed_src_layout_if_any(dir) } } return b.with_same_module_subdir_files(source_dir, res) } // report_removed_src_layout_if_any prints a clear error if `dir` still relies on // the removed virtual `src/` module layout (sources under `dir/src/` instead of // at the module root). The explicit `base_url` opt-in in v.mod is unaffected. fn report_removed_src_layout_if_any(dir string) { src_dir := os.join_path(dir, 'src') if !os.is_dir(src_dir) { return } src_files := os.ls(src_dir) or { return } mut has_v_files := false for f in src_files { if f.ends_with('.v') { has_v_files = true break } } if !has_v_files { return } 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\']') } fn (b &Builder) with_same_module_subdir_files(source_dir string, v_files []string) []string { mut mcache := vmod.get_cache() vmod_file_location := mcache.get_by_folder(source_dir) if vmod_file_location.vmod_file == '' { return v_files } module_source_root := b.module_source_root(vmod_file_location.vmod_folder) if source_dir != module_source_root { return v_files } manifest := vmod.from_file(vmod_file_location.vmod_file) or { return v_files } subdirs := manifest.unknown['subdirs'] or { return v_files } if subdirs.len == 0 { return v_files } mut res := v_files.clone() mut seen := map[string]bool{} for file in res { seen[os.real_path(file)] = true } for subdir in subdirs { normalized_subdir := normalize_same_module_subdir(subdir, manifest.base_url) or { continue } subdir_path := os.join_path(module_source_root, normalized_subdir) b.collect_same_module_v_files(vmod_file_location.vmod_folder, subdir_path, mut seen, mut res) } return res } fn (b &Builder) module_source_root(module_root string) string { real_module_root := os.real_path(module_root) if source_root := source_root_from_vmod_root(real_module_root) { if source_root != real_module_root && os.is_dir(source_root) { return os.real_path(source_root) } } return real_module_root } fn normalize_same_module_subdir(subdir string, base_url string) !string { mut normalized := os.norm_path(subdir.trim_space().replace('\\', os.path_separator).replace('/', os.path_separator)) if normalized == '' || normalized == '.' || os.is_abs_path(normalized) { return error('invalid subdir') } if base_url != '' { base := os.norm_path(base_url).trim_left(os.path_separator).trim_right(os.path_separator) if base != '' { base_prefix := base + os.path_separator if normalized == base { return error('invalid subdir') } if normalized.starts_with(base_prefix) { normalized = normalized.all_after(base_prefix) } } } normalized = normalized.trim_left(os.path_separator).trim_right(os.path_separator) if normalized == '' { return error('invalid subdir') } parts := normalized.split(os.path_separator) if '' in parts || '.' in parts || '..' in parts { return error('invalid subdir') } return normalized } fn (b &Builder) collect_same_module_v_files(module_root string, dir string, mut seen map[string]bool, mut res []string) { if !os.is_dir(dir) { return } real_dir := os.real_path(dir) if real_dir != os.real_path(module_root) && os.is_file(os.join_path(real_dir, 'v.mod')) { return } mut entries := os.ls(real_dir) or { return } entries.sort() for file in b.pref.should_compile_filtered_files(real_dir, entries) { real_file := os.real_path(file) if real_file !in seen { seen[real_file] = true res << file } } for entry in entries { subdir_path := os.join_path(real_dir, entry) if os.is_dir(subdir_path) { b.collect_same_module_v_files(module_root, subdir_path, mut seen, mut res) } } } pub fn (b &Builder) log(s string) { if b.pref.is_verbose { println(s) } } pub fn (b &Builder) info(s string) { if b.pref.is_verbose { println(s) } } @[inline] pub fn module_path(mod string) string { // submodule support return mod.replace('.', os.path_separator) } fn manifest_from_vmod_root(vmod_root string) !vmod.Manifest { vmod_path := os.join_path(vmod_root, 'v.mod') if !os.is_file(vmod_path) { return error('module not found') } return vmod.from_file(vmod_path) or { return error('module not found') } } fn source_root_from_vmod_root(vmod_root string) !string { manifest := manifest_from_vmod_root(vmod_root)! if manifest.base_url == '' { return error('module not found') } return manifest.source_root(vmod_root) } fn find_module_path_from_vmod_root(vmod_root string, mod string) !string { manifest := manifest_from_vmod_root(vmod_root)! if manifest.base_url == '' { return error('module not found') } tail_path := mod_tail_after_vmod_name(mod, manifest.name) or { return error('module not found') } if tail_path == '' { return error('module not found') } try_path := os.join_path(manifest.source_root(vmod_root), tail_path) if os.is_dir(try_path) { return try_path } return error('module not found') } fn find_module_path_from_search_root(search_path string, mod string) !string { mod_path := module_path(mod) try_path := os.join_path_single(search_path, mod_path) if os.is_dir(try_path) { return try_path } if src_try_path := find_module_path_from_vmod_root(search_path, mod) { return src_try_path } mod_parts := mod.split('.') for i := mod_parts.len - 1; i > 0; i-- { candidate_root := os.join_path_single(search_path, mod_parts[..i].join(os.path_separator)) if !os.is_file(os.join_path(candidate_root, 'v.mod')) { continue } source_root := source_root_from_vmod_root(candidate_root) or { continue } submodule_path := mod_parts[i..].join(os.path_separator) src_try_path := os.join_path(source_root, submodule_path) if os.is_dir(src_try_path) { return src_try_path } } return error('module not found') } fn mod_tail_after_vmod_name(mod string, vmod_name string) !string { if vmod_name == '' { return error('module not found') } mod_parts := mod.split('.') vmod_parts := vmod_name.split('.') for i := 0; i + vmod_parts.len <= mod_parts.len; i++ { if i > 1 { break } if mod_parts[i..i + vmod_parts.len].join('.') == vmod_name { return mod_parts[i + vmod_parts.len..].join(os.path_separator) } } return error('module not found') } fn (b &Builder) module_path_has_v_files(path string) bool { return b.v_files_from_dir(path).len > 0 } // TODO: try to merge this & util.module functions to create a // reliable multi use function. see comments in util/module.v pub fn (b &Builder) find_module_path(mod string, fpath string) !string { // support @VEXEROOT/v.mod relative paths: mut mcache := vmod.get_cache() resolved_fpath := os.real_path(fpath) vmod_file_location := mcache.get_by_file(resolved_fpath) mod_path := module_path(mod) mut module_lookup_paths := []string{} if vmod_file_location.vmod_file.len != 0 && vmod_file_location.vmod_folder !in b.module_search_paths { module_lookup_paths << vmod_file_location.vmod_folder } module_lookup_paths << b.module_search_paths // go up through parents looking for modules a folder. // we need a proper solution that works most of the time. look at vdoc.get_parent_mod if resolved_fpath.contains(os.path_separator + 'modules' + os.path_separator) { parts := resolved_fpath.split(os.path_separator) for i := parts.len - 2; i >= 0; i-- { if parts[i] == 'modules' { module_lookup_paths << parts[0..i + 1].join(os.path_separator) break } } } mut empty_module_path := '' for search_path in module_lookup_paths { try_path := os.join_path(search_path, mod_path) if b.pref.is_verbose { println(' >> trying to find ${mod} in ${try_path} ..') } if found_path := find_module_path_from_search_root(search_path, mod) { if b.module_path_has_v_files(found_path) { if b.pref.is_verbose { println(' << found ${found_path} .') } return found_path } if empty_module_path == '' { empty_module_path = found_path } if b.pref.is_verbose { println(' << skipped ${found_path} (no .v files) .') } } } // look up through parents mut current_dir := os.dir(resolved_fpath) for { try_path := os.join_path(current_dir, mod_path) if b.pref.is_verbose { println(' >> trying to find ${mod} in ${try_path} ..') } if found_path := find_module_path_from_search_root(current_dir, mod) { if b.module_path_has_v_files(found_path) { if b.pref.is_verbose { println(' << found ${found_path} .') } return found_path } if empty_module_path == '' { empty_module_path = found_path } if b.pref.is_verbose { println(' << skipped ${found_path} (no .v files) .') } } parent_dir := os.dir(current_dir) if parent_dir == current_dir { break } current_dir = parent_dir } if empty_module_path != '' { return empty_module_path } smodule_lookup_paths := module_lookup_paths.join(', ') return error('module "${mod}" not found in:\n${smodule_lookup_paths}') } pub fn (b &Builder) show_total_warns_and_errors_stats() { if b.nr_errors == 0 && b.nr_warnings == 0 && b.nr_notices == 0 { return } if b.pref.is_stats { mut nr_errors := b.checker.nr_errors mut nr_warnings := b.checker.nr_warnings mut nr_notices := b.checker.nr_notices if b.pref.check_only { nr_errors = b.nr_errors nr_warnings = b.nr_warnings nr_notices = b.nr_notices } estring := util.bold(nr_errors.str()) wstring := util.bold(nr_warnings.str()) nstring := util.bold(nr_notices.str()) if b.pref.check_only { println('summary: ${estring} V errors, ${wstring} V warnings, ${nstring} V notices') } else { println('checker summary: ${estring} V errors, ${wstring} V warnings, ${nstring} V notices') } } if !b.pref.is_vls && b.checker.nr_errors > 0 && b.pref.path.ends_with('.v') && os.is_file(b.pref.path) && !b.pref.path.ends_with('vrepl_temp.v') { if b.checker.errors.any(it.message.starts_with('unknown ')) { // Sometimes users try to `v main.v`, when they have several .v files in their project. // Then, they encounter puzzling errors about missing or unknown types. In this case, // the intended command may have been `v .` instead, so just suggest that: old_cmd := util.bold('v ${b.pref.path}') new_cmd := util.bold('v ${os.dir(b.pref.path)}') eprintln(util.color('notice', 'If the code of your project is in a folder with multiple .v files, try `${new_cmd}` instead of `${old_cmd}`')) } } } pub fn (mut b Builder) print_warnings_and_errors() { defer { b.show_total_warns_and_errors_stats() } for file in b.parsed_files { b.nr_errors += file.errors.len b.nr_warnings += file.warnings.len b.nr_notices += file.notices.len } if b.pref.output_mode == .silent { if b.nr_errors > 0 { exit(1) } return } if b.pref.check_only { if !b.pref.skip_notes && !b.pref.json_errors { for file in b.parsed_files { for err in file.notices { kind := if b.pref.is_verbose { '${err.reporter} notice #${b.nr_notices}:' } else { 'notice:' } util.show_compiler_message(kind, err.CompilerMessage) } } } mut json_errors := []util.JsonError{} for file in b.parsed_files { for err in file.errors { kind := if b.pref.is_verbose { '${err.reporter} error #${b.nr_errors}:' } else { 'error:' } if b.pref.json_errors { json_errors << util.JsonError{ message: err.message path: os.to_slash(err.file_path) line_nr: err.pos.line_nr + 1 col: err.pos.col + 1 } // util.print_json_error(kind, err.CompilerMessage) } else { util.show_compiler_message(kind, err.CompilerMessage) } } } if b.pref.json_errors { if !b.pref.is_vls || b.pref.linfo.method !in [.definition, .completion, .signature_help, .hover] { util.print_json_errors(json_errors) } // eprintln(json2.encode_pretty(json_errors)) } if !b.pref.skip_warnings { for file in b.parsed_files { for err in file.warnings { kind := if b.pref.is_verbose { '${err.reporter} warning #${b.nr_warnings}:' } else { 'warning:' } util.show_compiler_message(kind, err.CompilerMessage) } } } b.show_total_warns_and_errors_stats() if b.nr_errors > 0 { exit(1) } } if b.pref.is_verbose && b.checker.nr_warnings > 1 { println('${b.checker.nr_warnings} warnings') } if b.pref.is_verbose && b.checker.nr_notices > 1 { println('${b.checker.nr_notices} notices') } if b.checker.nr_notices > 0 && !b.pref.skip_notes { for err in b.checker.notices { kind := if b.pref.is_verbose { '${err.reporter} notice #${b.checker.nr_notices}:' } else { 'notice:' } util.show_compiler_message(kind, err.CompilerMessage) } } if b.checker.nr_warnings > 0 && !b.pref.skip_warnings { for err in b.checker.warnings { kind := if b.pref.is_verbose { '${err.reporter} warning #${b.checker.nr_warnings}:' } else { 'warning:' } util.show_compiler_message(kind, err.CompilerMessage) } } if b.pref.is_verbose && b.checker.nr_errors > 1 { println('${b.checker.nr_errors} errors') } if b.checker.nr_errors > 0 { for err in b.checker.errors { kind := if b.pref.is_verbose { '${err.reporter} error #${b.checker.nr_errors}:' } else { 'error:' } util.show_compiler_message(kind, err.CompilerMessage) } b.show_total_warns_and_errors_stats() exit(1) } if b.table.redefined_fns.len > 0 { mut total_conflicts := 0 for fn_name in b.table.redefined_fns { // Find where this function was already declared mut redefines := []FunctionRedefinition{} mut redefine_conflicts := map[string]int{} for file in b.parsed_files { for stmt in file.stmts { if stmt is ast.FnDecl { if stmt.name == fn_name { fheader := b.table.stringify_fn_decl(&stmt, 'main', map[string]string{}, false) redefines << FunctionRedefinition{ fpath: file.path fline: stmt.pos.line_nr f: stmt fheader: fheader } redefine_conflicts[fheader]++ } } } } if redefines.len > 0 { util.show_compiler_message('builder error:', message: 'redefinition of function `${fn_name}`' ) for redefine in redefines { util.show_compiler_message('conflicting declaration:', message: redefine.fheader file_path: redefine.fpath pos: redefine.f.pos ) } total_conflicts++ } } if total_conflicts > 0 { b.show_total_warns_and_errors_stats() exit(1) } } } struct FunctionRedefinition { fpath string fline int fheader string f ast.FnDecl } pub fn (b &Builder) error_with_pos(s string, fpath string, pos token.Pos) errors.Error { return errors.Error{ file_path: fpath pos: pos reporter: .builder message: s } } fn (b &Builder) print_frontend_builder_errors() { if b.pref.check_only || b.pref.output_mode != .stdout { return } for file in b.parsed_files { for err in file.errors { if err.reporter == .builder { util.show_compiler_message('builder error:', err.CompilerMessage) } } } } @[noreturn] pub fn verror(s string) { util.verror('builder error', s) } pub fn (mut b Builder) show_parsed_files() { for p in b.parsed_files { if p.is_parse_text { println(p.path + ':parse_text') } println(p.path) } }