From 42fc2c0dcbfb385eb6fbbb5723a2cad01db1cf81 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 21 Jun 2026 19:08:14 +0300 Subject: [PATCH] compiler: fix generic sumtype aliases and duplicate imports (#27523) --- vlib/v/builder/builder.v | 45 ++++-- vlib/v/builder/builder_test.v | 80 +++++++++ vlib/v/checker/check_types.v | 5 +- vlib/v/checker/match.v | 152 +++++++++++++++--- ...c_sumtype_alias_distinct_variant_match.out | 15 ++ ...ic_sumtype_alias_distinct_variant_match.vv | 15 ++ vlib/v/gen/c/fn.v | 11 +- ...eric_sumtype_alias_match_regression_test.v | 48 ++++++ .../generic_sumtype_alias_regression_test.v | 10 ++ vlib/v/util/module.v | 16 +- 10 files changed, 356 insertions(+), 41 deletions(-) create mode 100644 vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.out create mode 100644 vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.vv create mode 100644 vlib/v/tests/sumtypes/generic_sumtype_alias_match_regression_test.v create mode 100644 vlib/v/tests/sumtypes/generic_sumtype_alias_regression_test.v diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index d407992a8..02c2445de 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -284,6 +284,8 @@ pub fn (mut b Builder) parse_imports() { util.timing_measure(@METHOD) } mut done_imports := []string{} + mut done_import_path_modules := map[string][]string{} + mut done_import_path_files := map[string][]string{} if b.pref.is_vsh { done_imports << 'os' } @@ -331,6 +333,15 @@ pub fn (mut b Builder) parse_imports() { ast_file.path, imp.pos) break } + import_path_key := comparable_real_path(import_path) + if import_path_key in done_import_path_modules { + for module_idx, name in done_import_path_modules[import_path_key] { + b.validate_imported_module_name(i, ast_file.path, mod, + done_import_path_files[import_path_key][module_idx], name, imp.pos) + } + done_imports << mod + continue + } 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)) @@ -341,23 +352,21 @@ pub fn (mut b Builder) parse_imports() { // 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) + mut imported_module_names := []string{} + mut imported_module_paths := []string{} 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) - } + name := imported_file_module_name(file) + b.validate_imported_module_name(i, ast_file.path, mod, file.path, name, imp.pos) + imported_module_names << name + imported_module_paths << file.path } b.parsed_files << parsed_files if b.should_stop_after_frontend_error() && parsed_files.any(it.errors.len > 0) { return } done_imports << mod + done_import_path_modules[import_path_key] = imported_module_names + done_import_path_files[import_path_key] = imported_module_paths } } b.resolve_deps() @@ -431,6 +440,22 @@ pub fn (mut b Builder) resolve_deps() { } } +fn imported_file_module_name(file ast.File) string { + if file.mod.name != '' { + return file.mod.name + } + return file.mod.short_name +} + +fn (mut b Builder) validate_imported_module_name(importer_idx int, importer_path string, mod string, imported_path string, name string, import_pos token.Pos) { + sname := name.all_after_last('.') + smod := mod.all_after_last('.') + if sname != smod { + msg := 'bad module definition: ${importer_path} imports module "${mod}" but ${imported_path} is defined as module `${name}`' + b.parsed_files[importer_idx].errors << b.error_with_pos(msg, importer_path, import_pos) + } +} + fn import_alias_for_mod(file &ast.File, mod string) ?string { for import_m in file.imports { if import_m.mod == mod { diff --git a/vlib/v/builder/builder_test.v b/vlib/v/builder/builder_test.v index 85a5a3149..85b49177f 100644 --- a/vlib/v/builder/builder_test.v +++ b/vlib/v/builder/builder_test.v @@ -216,6 +216,86 @@ fn test_empty_local_dir_does_not_shadow_vlib_module() { assert res.output.trim_space() == 'true' } +fn test_imports_with_same_resolved_path_are_parsed_once() { + os.chdir(test_path)! + workspace := os.join_path(test_path, 'run_duplicate_resolved_import_path') + project_dir := os.join_path(workspace, 'dupmod') + defer { + os.chdir(test_path) or {} + os.rmdir_all(workspace) or {} + } + os.mkdir_all(os.join_path(project_dir, 'providers'))! + os.write_file(os.join_path(project_dir, 'v.mod'), "Module {\n\tname: 'dupmod'\n}\n")! + os.write_file(os.join_path(project_dir, 'dupmod.v'), 'module dupmod + +import providers + +pub fn make_client() providers.Client { + return providers.Client{} +} +')! + os.write_file(os.join_path(project_dir, 'main_test.v'), 'module main + +import dupmod +import dupmod.providers + +fn test_client() { + _ := dupmod.make_client() + _ := providers.Client{} +} +')! + os.write_file(os.join_path(project_dir, 'providers', 'client.v'), 'module providers + +pub struct Client {} +')! + os.chdir(project_dir)! + + res := os.execute('${os.quoted_path(vexe)} -check main_test.v') + assert res.exit_code == 0, res.output + assert !res.output.contains('cannot register struct') +} + +fn test_duplicate_resolved_import_path_still_validates_module_name() { + $if windows { + return + } + os.chdir(test_path)! + workspace := os.join_path(test_path, 'run_duplicate_resolved_import_path_validation') + project_dir := os.join_path(workspace, 'project') + foo_dir := os.join_path(project_dir, 'modules', 'foo') + bar_dir := os.join_path(project_dir, 'modules', 'bar') + defer { + os.chdir(test_path) or {} + os.rmdir_all(workspace) or {} + } + os.mkdir_all(foo_dir)! + os.write_file(os.join_path(project_dir, 'v.mod'), "Module {\n\tname: 'project'\n}\n")! + os.write_file(os.join_path(foo_dir, 'foo.v'), 'module foo + +pub fn value() int { + return 1 +} +')! + os.symlink(foo_dir, bar_dir) or { return } + os.write_file(os.join_path(project_dir, 'main.v'), 'module main + +import foo +import bar + +fn main() { + println(foo.value()) + println(bar.value()) +} +')! + os.chdir(project_dir)! + + res := os.execute('${os.quoted_path(vexe)} -check main.v') + assert res.exit_code != 0, res.output + assert res.output.contains('bad module definition'), res.output + assert res.output.contains('imports module "bar"'), res.output + assert res.output.contains('defined as module `foo`'), res.output +} + fn test_removed_src_layout_error_mentions_vmod_subdirs() { os.chdir(test_path)! project_dir := os.join_path(test_path, 'run_removed_src_project') diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 6e5d0d713..597048fa2 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -1908,8 +1908,9 @@ fn (mut c Checker) infer_fn_generic_types(func &ast.Fn, mut node ast.CallExpr) { s := c.table.type_to_str(typ) println('inferred `${func.name}[${s}]`') } - inferred_types << c.unwrap_generic(typ) - node.concrete_types << typ + concrete_typ := ast.mktyp(c.unwrap_generic(typ)) + inferred_types << concrete_typ + node.concrete_types << concrete_typ } if c.table.register_fn_concrete_types(func.fkey(), inferred_types) { diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index 192d776c2..26da2ab03 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -684,6 +684,110 @@ fn (mut c Checker) get_match_case_literal_value(mut expr ast.Expr) ?i64 { return none } +fn (mut c Checker) match_sumtype_has_variant(parent ast.Type, variant ast.Type) bool { + if c.table.sumtype_has_variant_recursive(parent, variant, true) { + return true + } + if c.table.sym(parent).kind == .sum_type { + return false + } + for candidate in c.match_sumtype_matchable_variants(parent) { + if c.match_sumtype_variant_is_handled(candidate, variant) { + return true + } + } + return false +} + +fn (mut c Checker) match_sumtype_matchable_variants(parent ast.Type) []ast.Type { + if c.table.sym(parent).kind == .sum_type { + return c.table.sumtype_matchable_variants(parent) + } + mut variants := []ast.Type{} + mut seen := map[u32]bool{} + c.collect_match_sumtype_matchable_variants(parent, mut seen, mut variants) + return variants +} + +fn (mut c Checker) collect_match_sumtype_matchable_variants(parent ast.Type, mut seen map[u32]bool, mut variants []ast.Type) { + for variant in c.concrete_sumtype_variants(parent) { + key := u32(variant) + if key in seen { + continue + } + seen[key] = true + variants << variant + if c.concrete_sumtype_variants(variant).len > 0 { + c.collect_match_sumtype_matchable_variants(variant, mut seen, mut variants) + } + } +} + +fn (mut c Checker) match_sumtype_missing_variants(parent ast.Type, handled []ast.Type) []ast.Type { + if c.table.sym(parent).kind == .sum_type { + return c.table.sumtype_missing_variants(parent, handled) + } + mut missing := []ast.Type{} + mut seen := map[u32]bool{} + c.collect_match_sumtype_missing_variants(parent, handled, mut seen, mut missing) + return missing +} + +fn (mut c Checker) collect_match_sumtype_missing_variants(parent ast.Type, handled []ast.Type, mut seen map[u32]bool, mut missing []ast.Type) { + if c.match_sumtype_variant_is_handled_by(parent, handled) { + return + } + for variant in c.concrete_sumtype_variants(parent) { + if c.match_sumtype_variant_is_handled_by(variant, handled) { + continue + } + if c.concrete_sumtype_variants(variant).len > 0 { + c.collect_match_sumtype_missing_variants(variant, handled, mut seen, mut missing) + } else { + key := u32(variant) + if key !in seen { + seen[key] = true + missing << variant + } + } + } +} + +fn (mut c Checker) match_sumtype_variant_is_handled_by(variant ast.Type, handled []ast.Type) bool { + for handled_variant in handled { + if c.match_sumtype_variant_is_handled(variant, handled_variant) { + return true + } + } + return false +} + +fn (mut c Checker) match_sumtype_variant_is_handled(variant ast.Type, handled ast.Type) bool { + if variant.idx() == handled.idx() && variant.has_flag(.option) == handled.has_flag(.option) + && variant.nr_muls() == handled.nr_muls() { + return true + } + handled_sym := c.table.sym(handled) + if handled_sym.info is ast.Alias { + mut parent_type := handled_sym.info.parent_type.set_nr_muls(handled.nr_muls()) + if handled.has_flag(.option) { + parent_type = parent_type.set_flag(.option) + } + if handled.has_flag(.result) { + parent_type = parent_type.set_flag(.result) + } + return c.match_sumtype_variant_is_handled(variant, parent_type) + } + variant_sym := c.table.sym(variant) + if variant_sym.info is ast.FnType && handled_sym.info is ast.FnType { + return + c.table.fn_type_source_signature(variant_sym.info.func) == c.table.fn_type_source_signature(handled_sym.info.func) + && variant.has_flag(.option) == handled.has_flag(.option) + && variant.nr_muls() == handled.nr_muls() + } + return false +} + fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSymbol, cond_final_sym ast.TypeSymbol) { c.expected_type = node.expected_type if node.cond_type.idx() == 0 { @@ -694,6 +798,8 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym is_alias_to_matchable_type := cond_type_sym.kind == .alias && cond_final_sym.kind in [.interface, .sum_type] cond_match_sym := if is_alias_to_matchable_type { cond_final_sym } else { cond_type_sym } + sumtype_match_variants := c.match_sumtype_matchable_variants(cond_match_type) + is_cond_match_sumtype := sumtype_match_variants.len > 0 mut enum_ref_checked := false mut is_comptime_value_match := false // branch_exprs is a histogram of how many times @@ -908,12 +1014,12 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym } } } - } else if cond_match_sym.info is ast.SumType { - if !c.table.sumtype_has_variant_recursive(cond_match_type, expr_type, true) { + } else if is_cond_match_sumtype { + if !c.match_sumtype_has_variant(cond_match_type, expr_type) { expr_str := c.table.type_to_str(expr_type) expect_str := c.table.type_to_str(node.cond_type) sumtype_variant_names := - c.table.sumtype_matchable_variants(cond_match_type).map(c.table.type_to_str_using_aliases(it, {})) + sumtype_match_variants.map(c.table.type_to_str_using_aliases(it, {})) suggestion := util.new_suggestion(expr_str, sumtype_variant_names) c.error(suggestion.say('`${expect_str}` has no variant `${expr_str}`'), expr.pos()) @@ -941,7 +1047,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym } // when match is type matching, then register smart cast for every branch if expr_types.len > 0 { - if cond_match_sym.kind in [.sum_type, .interface] { + if is_cond_match_sumtype || cond_match_sym.kind == .interface { mut expr_type := ast.no_type if expr_types.len > 1 { mut agg_name := strings.new_builder(20) @@ -999,34 +1105,34 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym } } } else { - match cond_match_sym.info { - ast.SumType { - for v in c.table.sumtype_missing_variants(cond_match_type, branch_expr_types) { - is_exhaustive = false - unhandled << '`${c.table.type_to_str(v)}`' - } + if is_cond_match_sumtype { + for v in c.match_sumtype_missing_variants(cond_match_type, branch_expr_types) { + is_exhaustive = false + unhandled << '`${c.table.type_to_str(v)}`' } - // - ast.Enum { - for v in cond_match_sym.info.vals { - mut is_handled := v in branch_exprs - if !is_handled && is_multi_allowed_enum_match { - if enum_val := c.table.find_enum_field_val(cond_match_sym.name, v) { - is_handled = enum_val in branch_enum_values + } else { + match cond_match_sym.info { + ast.Enum { + for v in cond_match_sym.info.vals { + mut is_handled := v in branch_exprs + if !is_handled && is_multi_allowed_enum_match { + if enum_val := c.table.find_enum_field_val(cond_match_sym.name, v) { + is_handled = enum_val in branch_enum_values + } + } + if !is_handled { + is_exhaustive = false + unhandled << '`.${v}`' } } - if !is_handled { + if cond_match_sym.info.is_flag { is_exhaustive = false - unhandled << '`.${v}`' } } - if cond_match_sym.info.is_flag { + else { is_exhaustive = false } } - else { - is_exhaustive = false - } } } if node.branches.len == 0 { diff --git a/vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.out b/vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.out new file mode 100644 index 000000000..641d995a6 --- /dev/null +++ b/vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.out @@ -0,0 +1,15 @@ +vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.vv:8:3: error: `GenericAliasBox[string]` has no variant `GenericAliasB`. +Did you mean `GenericAliasA`? + 6 | value := GenericAliasBox[string](GenericAliasA(1)) + 7 | match value { + 8 | GenericAliasB { + | ~~~~~~~~~~~~~ + 9 | println('bad') + 10 | } +vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.vv:7:2: error: match must be exhaustive (add match branches for: `GenericAliasA` or `else {}` at the end) + 5 | fn main() { + 6 | value := GenericAliasBox[string](GenericAliasA(1)) + 7 | match value { + | ~~~~~~~~~~~~~ + 8 | GenericAliasB { + 9 | println('bad') diff --git a/vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.vv b/vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.vv new file mode 100644 index 000000000..802c0fca6 --- /dev/null +++ b/vlib/v/checker/tests/generic_sumtype_alias_distinct_variant_match.vv @@ -0,0 +1,15 @@ +type GenericAliasA = int +type GenericAliasB = int +type GenericAliasBox[T] = GenericAliasA | T + +fn main() { + value := GenericAliasBox[string](GenericAliasA(1)) + match value { + GenericAliasB { + println('bad') + } + string { + println(value) + } + } +} diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index ae6e0278d..3cc3a9b41 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -410,8 +410,9 @@ fn (mut g Gen) resolve_current_fn_generic_type(typ ast.Type) ast.Type { if generic_names.len == 0 || g.cur_concrete_types.len == 0 { return g.unwrap_generic(typ) } + concrete_types := g.cur_concrete_types.map(ast.mktyp(it)) mut muttable := unsafe { &ast.Table(g.table) } - if resolved := muttable.convert_generic_type(typ, generic_names, g.cur_concrete_types) { + if resolved := muttable.convert_generic_type(typ, generic_names, concrete_types) { return g.unwrap_generic(resolved) } return g.unwrap_generic(g.recheck_concrete_type(typ)) @@ -2017,7 +2018,13 @@ fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic if i >= param_count { break } - mut typ := g.unwrap_generic(param.typ) + mut typ := if !param.typ.has_flag(.variadic) && g.cur_concrete_types.len > 0 + && param.orig_typ != 0 && (param.orig_typ.has_flag(.generic) + || g.type_has_unresolved_generic_parts(param.orig_typ)) { + g.resolve_current_fn_generic_type(param.orig_typ) + } else { + g.unwrap_generic(param.typ) + } if g.pref.translated && g.file.is_translated && param.typ.has_flag(.variadic) { typ = g.table.sym(typ).array_info().elem_type.set_flag(.variadic) } diff --git a/vlib/v/tests/sumtypes/generic_sumtype_alias_match_regression_test.v b/vlib/v/tests/sumtypes/generic_sumtype_alias_match_regression_test.v new file mode 100644 index 000000000..84c625263 --- /dev/null +++ b/vlib/v/tests/sumtypes/generic_sumtype_alias_match_regression_test.v @@ -0,0 +1,48 @@ +struct GenericSumtypeAliasMatchItem {} + +type GenericSumtypeAliasMatchValue[T] = string | int | T + +fn generic_sumtype_alias_match_exhaustive[T](value GenericSumtypeAliasMatchValue[T]) int { + return match value { + string { 1 } + int { 2 } + T { 3 } + } +} + +fn test_generic_sumtype_alias_match_with_generic_variant_is_exhaustive() { + assert generic_sumtype_alias_match_exhaustive(GenericSumtypeAliasMatchItem{}) == 3 +} + +type GenericSumtypeAliasNestedInner = int | string +type GenericSumtypeAliasNestedOuter[T] = GenericSumtypeAliasNestedInner | T + +fn generic_sumtype_alias_match_nested_variants[T](value GenericSumtypeAliasNestedOuter[T]) int { + return match value { + int { 1 } + string { 2 } + T { 3 } + } +} + +fn test_generic_sumtype_alias_match_expands_nested_sumtype_variants() { + assert generic_sumtype_alias_match_nested_variants[bool](1) == 1 + assert generic_sumtype_alias_match_nested_variants[bool](true) == 3 +} + +type GenericSumtypeAliasFnBox[T] = fn (int) int | T + +fn generic_sumtype_alias_match_increment(value int) int { + return value + 1 +} + +fn generic_sumtype_alias_match_fn_variant[T](value GenericSumtypeAliasFnBox[T]) int { + return match value { + fn (int) int { value(41) } + T { 0 } + } +} + +fn test_generic_sumtype_alias_match_function_variant_by_signature() { + assert generic_sumtype_alias_match_fn_variant[string](generic_sumtype_alias_match_increment) == 42 +} diff --git a/vlib/v/tests/sumtypes/generic_sumtype_alias_regression_test.v b/vlib/v/tests/sumtypes/generic_sumtype_alias_regression_test.v new file mode 100644 index 000000000..5e4035368 --- /dev/null +++ b/vlib/v/tests/sumtypes/generic_sumtype_alias_regression_test.v @@ -0,0 +1,10 @@ +type GenericSumtypeAliasValue[T] = string | int | T + +fn generic_sumtype_alias_accepts_int_literal[T](value GenericSumtypeAliasValue[T]) int { + _ = value + return 0 +} + +fn test_generic_sumtype_alias_accepts_int_literal() { + assert generic_sumtype_alias_accepts_int_literal(5) == 0 +} diff --git a/vlib/v/util/module.v b/vlib/v/util/module.v index 8ab31055e..96e72964a 100644 --- a/vlib/v/util/module.v +++ b/vlib/v/util/module.v @@ -28,7 +28,7 @@ pub fn qualify_import(pref_ &pref.Preferences, mod string, file_path string) str for search_path in mod_paths { try_path := os.join_path_single(search_path, mod_path) if os.is_dir(try_path) { - if m1 := mod_path_to_full_name(pref_, mod, try_path) { + if m1 := import_path_to_full_name(pref_, mod, try_path) { trace_qualify(@FN, mod, file_path, 'import_res 1', m1, try_path) // > qualify_import: term | file_path: /v/vls/server/diagnostics.v | => import_res 1: term ; /v/cleanv/vlib/term return m1 @@ -41,7 +41,7 @@ pub fn qualify_import(pref_ &pref.Preferences, mod string, file_path string) str } else { os.join_path_single(os.getwd(), file_path) } - if m1 := mod_path_to_full_name(pref_, mod, abs_file_path) { + if m1 := import_path_to_full_name(pref_, mod, abs_file_path) { trace_qualify(@FN, mod, file_path, 'import_res 2', m1, abs_file_path) // > qualify_module: analyzer | file_path: /v/vls/analyzer/store.v | => module_res 2: analyzer ; clean_file_path - getwd == mod // > qualify_import: analyzer.depgraph | file_path: /v/vls/analyzer/store.v | => import_res 2: analyzer.depgraph ; /v/vls/analyzer/store.v @@ -116,6 +116,14 @@ pub fn qualify_module(pref_ &pref.Preferences, mod string, file_path string) str // 2022-01-30 it leads to path differences, and the / version on windows triggers a module lookip bug, // 2022-01-30 leading to completely different errors) fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !string { + return mod_path_to_full_name_with_options(pref_, mod, path, false) +} + +fn import_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !string { + return mod_path_to_full_name_with_options(pref_, mod, path, true) +} + +fn mod_path_to_full_name_with_options(pref_ &pref.Preferences, mod string, path string, allow_shorter_name bool) !string { // TODO: explore using `pref.lookup_path` & `os.vmodules_paths()` // absolute paths instead of 'vlib' & '.vmodules' mut vmod_folders := ['vlib', '.vmodules', 'modules'] @@ -169,7 +177,7 @@ fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !stri relative_parts := real_try_path.all_after(prefix).split(os.path_separator) mod_full_name := normalize_base_url_mod_name(relative_parts.join('.'), try_path) - if mod_full_name.len < mod.len { + if !allow_shorter_name && mod_full_name.len < mod.len { return mod } if !module_name_has_empty_part(mod_full_name) { @@ -197,7 +205,7 @@ fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !stri if last_v_mod > -1 { mod_full_name := normalize_base_url_mod_name(try_path_parts[last_v_mod..].join('.'), try_path) - if mod_full_name.len < mod.len { + if !allow_shorter_name && mod_full_name.len < mod.len { return mod } if !module_name_has_empty_part(mod_full_name) { -- 2.39.5