diff --git a/examples/1158-diagnostics-import-dir-file-ambiguous.sx b/examples/1158-diagnostics-import-dir-file-ambiguous.sx new file mode 100644 index 0000000..0fab5f5 --- /dev/null +++ b/examples/1158-diagnostics-import-dir-file-ambiguous.sx @@ -0,0 +1,11 @@ +// An extensionless #import that matches BOTH a `.sx` file and a sibling +// directory of the same name is ambiguous — the compiler refuses to pick +// silently and asks for the explicit `.sx` spelling. `modules/std` is the +// canonical collision: `modules/std.sx` (the prelude) sits next to +// `modules/std/` (mem/fs/process/...). + +#import "modules/std"; + +main :: () -> s32 { + 0 +} diff --git a/examples/expected/1158-diagnostics-import-dir-file-ambiguous.exit b/examples/expected/1158-diagnostics-import-dir-file-ambiguous.exit new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/examples/expected/1158-diagnostics-import-dir-file-ambiguous.exit @@ -0,0 +1 @@ +1 diff --git a/examples/expected/1158-diagnostics-import-dir-file-ambiguous.stderr b/examples/expected/1158-diagnostics-import-dir-file-ambiguous.stderr new file mode 100644 index 0000000..d5e4c28 --- /dev/null +++ b/examples/expected/1158-diagnostics-import-dir-file-ambiguous.stderr @@ -0,0 +1,5 @@ +error: ambiguous import 'modules/std': both a file 'modules/std.sx' and a directory 'modules/std' exist — write "modules/std.sx" to import the file + --> examples/1158-diagnostics-import-dir-file-ambiguous.sx:7:1 + | + 7 | #import "modules/std"; + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/expected/1158-diagnostics-import-dir-file-ambiguous.stdout b/examples/expected/1158-diagnostics-import-dir-file-ambiguous.stdout new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/expected/1158-diagnostics-import-dir-file-ambiguous.stdout @@ -0,0 +1 @@ + diff --git a/readme.md b/readme.md index 7ee85ea..b5d7146 100644 --- a/readme.md +++ b/readme.md @@ -398,6 +398,10 @@ Direct C header import: math :: #import "modules/math"; // namespaced import (directory: all .sx files merged) ``` +A path that matches both a file and a same-named sibling directory +(`modules/std.sx` next to `modules/std/`) is rejected as ambiguous — write the +`.sx` path to import the file. + When two flat-imported modules each define a function of the same name, every module's own code binds its OWN author — a bare call resolves to the same-name function in the caller's module (or in its single flat import that provides it). diff --git a/specs.md b/specs.md index cdcd45c..c9b5963 100644 --- a/specs.md +++ b/specs.md @@ -2495,6 +2495,8 @@ pkg :: #import "modules/math"; // namespaced — all .sx files merged under pk Directory imports scan only the top level of the specified directory (non-recursive). Files are processed in alphabetical order for deterministic builds. Files within the directory may `#import` each other or external files. +If an extensionless path matches both a file and a sibling directory of the same name (`modules/std.sx` next to `modules/std/`), the import is an error — write the `.sx` path to import the file. Exception: a file importing its own companion directory (`X.sx` importing `X/`) is not ambiguous; the directory is the only sensible target. + Namespaced declarations are accessed with dot notation: ```sx std.print("hello"); diff --git a/src/imports.zig b/src/imports.zig index 0ca35a5..eadcb9f 100644 --- a/src/imports.zig +++ b/src/imports.zig @@ -1074,7 +1074,32 @@ pub fn resolveImports( try cache.put(resolved_path, result); break :blk result; } else |_| { - // File read failed — try as directory import + // File read failed — try as directory import. An extensionless + // path that names a directory next to a same-named `.sx` file + // is ambiguous: require the explicit `.sx` spelling for the + // file rather than silently picking the directory. Exception: + // when the sibling `.sx` is the importing file itself (a test + // importing its own companion directory), the directory is the + // only sensible target. + const sibling_sx = try std.fmt.allocPrint(allocator, "{s}.sx", .{resolved_path}); + const sibling_exists = if (std.mem.eql(u8, sibling_sx, file_path)) + false + else if (std.Io.Dir.readFileAlloc(.cwd(), io, sibling_sx, allocator, .limited(10 * 1024 * 1024))) |_| + true + else |_| + false; + if (sibling_exists) { + const is_dir = if (std.Io.Dir.openDir(.cwd(), io, resolved_path, .{})) |d| dir_blk: { + d.close(io); + break :dir_blk true; + } else |_| false; + if (is_dir) { + if (diagnostics) |diags| { + diags.addFmt(.err, decl.span, "ambiguous import '{s}': both a file '{s}.sx' and a directory '{s}' exist — write \"{s}.sx\" to import the file", .{ imp.path, imp.path, imp.path, imp.path }); + } + return error.ImportError; + } + } const result = resolveDirectoryImport(allocator, io, resolved_path, chain, cache, source_map, diagnostics, decl.span, stdlib_paths, import_graph, flat_import_graph, comptime_ctx) catch { if (diagnostics) |diags| { diags.addFmt(.err, decl.span, "cannot read import '{s}' (not a file or directory)", .{resolved_path});