diff options
| author | Veikka Tuominen <git@vexu.eu> | 2023-01-23 14:25:42 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-01-23 14:25:42 +0200 |
| commit | 220020599cc11764eb9ed32025dd506f2affedda (patch) | |
| tree | 4467fe3ff1f919eec89bafac7435fe58cf9a876a /src | |
| parent | d395127552ab6f04b7b044b2a309e0d1e8977775 (diff) | |
| parent | 5f9186d0ce78ce1eb7db9634849b38d2e90d071e (diff) | |
| download | zig-220020599cc11764eb9ed32025dd506f2affedda.tar.gz zig-220020599cc11764eb9ed32025dd506f2affedda.zip | |
Merge pull request #13670 from mlugg/fix/astgen-ambiguous-package
AstGen: detect and error on files included in multiple packages
Diffstat (limited to 'src')
| -rw-r--r-- | src/Compilation.zig | 85 | ||||
| -rw-r--r-- | src/Module.zig | 62 | ||||
| -rw-r--r-- | src/Package.zig | 39 | ||||
| -rw-r--r-- | src/Sema.zig | 16 | ||||
| -rw-r--r-- | src/main.zig | 25 | ||||
| -rw-r--r-- | src/test.zig | 1 |
6 files changed, 172 insertions, 56 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index 60dcc55b7b..09c6e1c686 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -639,14 +639,6 @@ pub const AllErrors = struct { note_i += 1; } } - if (module_err_msg.src_loc.lazy == .entire_file) { - try errors.append(.{ - .plain = .{ - .msg = try allocator.dupe(u8, module_err_msg.msg), - }, - }); - return; - } const reference_trace = try allocator.alloc(Message, module_err_msg.reference_trace.len); for (reference_trace) |*reference, i| { @@ -683,7 +675,7 @@ pub const AllErrors = struct { .column = @intCast(u32, err_loc.column), .notes = notes_buf[0..note_i], .reference_trace = reference_trace, - .source_line = try allocator.dupe(u8, err_loc.source_line), + .source_line = if (module_err_msg.src_loc.lazy == .entire_file) null else try allocator.dupe(u8, err_loc.source_line), }, }); } @@ -1610,6 +1602,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { const builtin_pkg = try Package.createWithDir( gpa, + "builtin", zig_cache_artifact_directory, null, "builtin.zig", @@ -1618,6 +1611,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { const std_pkg = try Package.createWithDir( gpa, + "std", options.zig_lib_directory, "std", "std.zig", @@ -1625,11 +1619,14 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { errdefer std_pkg.destroy(gpa); const root_pkg = if (options.is_test) root_pkg: { + // TODO: we currently have two packages named 'root' here, which is weird. This + // should be changed as part of the resolution of #12201 const test_pkg = if (options.test_runner_path) |test_runner| - try Package.create(gpa, null, test_runner) + try Package.create(gpa, "root", null, test_runner) else try Package.createWithDir( gpa, + "root", options.zig_lib_directory, null, "test_runner.zig", @@ -1640,9 +1637,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { } else main_pkg; errdefer if (options.is_test) root_pkg.destroy(gpa); - try main_pkg.addAndAdopt(gpa, "builtin", builtin_pkg); - try main_pkg.add(gpa, "root", root_pkg); - try main_pkg.addAndAdopt(gpa, "std", std_pkg); + try main_pkg.addAndAdopt(gpa, builtin_pkg); + try main_pkg.add(gpa, root_pkg); + try main_pkg.addAndAdopt(gpa, std_pkg); const main_pkg_is_std = m: { const std_path = try std.fs.path.resolve(arena, &[_][]const u8{ @@ -3075,6 +3072,57 @@ pub fn performAllTheWork( } } + if (comp.bin_file.options.module) |mod| { + for (mod.import_table.values()) |file| { + if (!file.multi_pkg) continue; + const err = err_blk: { + const notes = try mod.gpa.alloc(Module.ErrorMsg, file.references.items.len); + errdefer mod.gpa.free(notes); + + for (notes) |*note, i| { + errdefer for (notes[0..i]) |*n| n.deinit(mod.gpa); + note.* = switch (file.references.items[i]) { + .import => |loc| try Module.ErrorMsg.init( + mod.gpa, + loc, + "imported from package {s}", + .{loc.file_scope.pkg.name}, + ), + .root => |pkg| try Module.ErrorMsg.init( + mod.gpa, + .{ .file_scope = file, .parent_decl_node = 0, .lazy = .entire_file }, + "root of package {s}", + .{pkg.name}, + ), + }; + } + errdefer for (notes) |*n| n.deinit(mod.gpa); + + const err = try Module.ErrorMsg.create( + mod.gpa, + .{ .file_scope = file, .parent_decl_node = 0, .lazy = .entire_file }, + "file exists in multiple packages", + .{}, + ); + err.notes = notes; + break :err_blk err; + }; + errdefer err.destroy(mod.gpa); + try mod.failed_files.putNoClobber(mod.gpa, file, err); + } + + // Now that we've reported the errors, we need to deal with + // dependencies. Any file referenced by a multi_pkg file should also be + // marked multi_pkg and have its status set to astgen_failure, as it's + // ambiguous which package they should be analyzed as a part of. We need + // to add this flag after reporting the errors however, as otherwise + // we'd get an error for every single downstream file, which wouldn't be + // very useful. + for (mod.import_table.values()) |file| { + if (file.multi_pkg) file.recursiveMarkMultiPkg(mod); + } + } + { const outdated_and_deleted_decls_frame = tracy.namedFrame("outdated_and_deleted_decls"); defer outdated_and_deleted_decls_frame.end(); @@ -3499,7 +3547,15 @@ fn workerAstGenFile( comp.mutex.lock(); defer comp.mutex.unlock(); - break :blk mod.importFile(file, import_path) catch continue; + const res = mod.importFile(file, import_path) catch continue; + if (!res.is_pkg) { + res.file.addReference(mod.*, .{ .import = .{ + .file_scope = file, + .parent_decl_node = 0, + .lazy = .{ .token_abs = item.data.token }, + } }) catch continue; + } + break :blk res; }; if (import_result.is_new) { log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{ @@ -5327,6 +5383,7 @@ fn buildOutputFromZig( var main_pkg: Package = .{ .root_src_directory = comp.zig_lib_directory, .root_src_path = src_basename, + .name = "root", }; defer main_pkg.deinitTable(comp.gpa); const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len]; diff --git a/src/Module.zig b/src/Module.zig index f2f51907cb..b17c140231 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1943,6 +1943,10 @@ pub const File = struct { zir: Zir, /// Package that this file is a part of, managed externally. pkg: *Package, + /// Whether this file is a part of multiple packages. This is an error condition which will be reported after AstGen. + multi_pkg: bool = false, + /// List of references to this file, used for multi-package errors. + references: std.ArrayListUnmanaged(Reference) = .{}, /// Used by change detection algorithm, after astgen, contains the /// set of decls that existed in the previous ZIR but not in the new one. @@ -1958,6 +1962,14 @@ pub const File = struct { /// successful, this field is unloaded. prev_zir: ?*Zir = null, + /// A single reference to a file. + const Reference = union(enum) { + /// The file is imported directly (i.e. not as a package) with @import. + import: SrcLoc, + /// The file is the root of a package. + root: *Package, + }; + pub fn unload(file: *File, gpa: Allocator) void { file.unloadTree(gpa); file.unloadSource(gpa); @@ -1990,6 +2002,7 @@ pub const File = struct { log.debug("deinit File {s}", .{file.sub_file_path}); file.deleted_decls.deinit(gpa); file.outdated_decls.deinit(gpa); + file.references.deinit(gpa); if (file.root_decl.unwrap()) |root_decl| { mod.destroyDecl(root_decl); } @@ -2110,6 +2123,44 @@ pub const File = struct { else => true, }; } + + /// Add a reference to this file during AstGen. + pub fn addReference(file: *File, mod: Module, ref: Reference) !void { + try file.references.append(mod.gpa, ref); + + const pkg = switch (ref) { + .import => |loc| loc.file_scope.pkg, + .root => |pkg| pkg, + }; + if (pkg != file.pkg) file.multi_pkg = true; + } + + /// Mark this file and every file referenced by it as multi_pkg and report an + /// astgen_failure error for them. AstGen must have completed in its entirety. + pub fn recursiveMarkMultiPkg(file: *File, mod: *Module) void { + file.multi_pkg = true; + file.status = .astgen_failure; + + std.debug.assert(file.zir_loaded); + const imports_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.imports)]; + if (imports_index == 0) return; + const extra = file.zir.extraData(Zir.Inst.Imports, imports_index); + + var import_i: u32 = 0; + var extra_index = extra.end; + while (import_i < extra.data.imports_len) : (import_i += 1) { + const item = file.zir.extraData(Zir.Inst.Imports.Item, extra_index); + extra_index = item.end; + + const import_path = file.zir.nullTerminatedString(item.data.name); + if (mem.eql(u8, import_path, "builtin")) continue; + + const res = mod.importFile(file, import_path) catch continue; + if (!res.is_pkg and !res.file.multi_pkg) { + res.file.recursiveMarkMultiPkg(mod); + } + } + } }; /// Represents the contents of a file loaded with `@embedFile`. @@ -3220,16 +3271,11 @@ pub fn deinit(mod: *Module) void { // The callsite of `Compilation.create` owns the `main_pkg`, however // Module owns the builtin and std packages that it adds. if (mod.main_pkg.table.fetchRemove("builtin")) |kv| { - gpa.free(kv.key); kv.value.destroy(gpa); } if (mod.main_pkg.table.fetchRemove("std")) |kv| { - gpa.free(kv.key); kv.value.destroy(gpa); } - if (mod.main_pkg.table.fetchRemove("root")) |kv| { - gpa.free(kv.key); - } if (mod.root_pkg != mod.main_pkg) { mod.root_pkg.destroy(gpa); } @@ -4695,6 +4741,7 @@ pub fn declareDeclDependency(mod: *Module, depender_index: Decl.Index, dependee_ pub const ImportFileResult = struct { file: *File, is_new: bool, + is_pkg: bool, }; pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult { @@ -4714,6 +4761,7 @@ pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult { if (gop.found_existing) return ImportFileResult{ .file = gop.value_ptr.*, .is_new = false, + .is_pkg = true, }; const sub_file_path = try gpa.dupe(u8, pkg.root_src_path); @@ -4737,9 +4785,11 @@ pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult { .pkg = pkg, .root_decl = .none, }; + try new_file.addReference(mod.*, .{ .root = pkg }); return ImportFileResult{ .file = new_file, .is_new = true, + .is_pkg = true, }; } @@ -4780,6 +4830,7 @@ pub fn importFile( if (gop.found_existing) return ImportFileResult{ .file = gop.value_ptr.*, .is_new = false, + .is_pkg = false, }; const new_file = try gpa.create(File); @@ -4825,6 +4876,7 @@ pub fn importFile( return ImportFileResult{ .file = new_file, .is_new = true, + .is_pkg = false, }; } diff --git a/src/Package.zig b/src/Package.zig index 23a0549aa7..f8823cc74e 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -24,10 +24,13 @@ table: Table = .{}, parent: ?*Package = null, /// Whether to free `root_src_directory` on `destroy`. root_src_directory_owned: bool = false, +/// This information can be recovered from 'table', but it's more convenient to store on the package. +name: []const u8, /// Allocate a Package. No references to the slices passed are kept. pub fn create( gpa: Allocator, + name: []const u8, /// Null indicates the current working directory root_src_dir_path: ?[]const u8, /// Relative to root_src_dir_path @@ -42,6 +45,9 @@ pub fn create( const owned_src_path = try gpa.dupe(u8, root_src_path); errdefer gpa.free(owned_src_path); + const owned_name = try gpa.dupe(u8, name); + errdefer gpa.free(owned_name); + ptr.* = .{ .root_src_directory = .{ .path = owned_dir_path, @@ -49,6 +55,7 @@ pub fn create( }, .root_src_path = owned_src_path, .root_src_directory_owned = true, + .name = owned_name, }; return ptr; @@ -56,6 +63,7 @@ pub fn create( pub fn createWithDir( gpa: Allocator, + name: []const u8, directory: Compilation.Directory, /// Relative to `directory`. If null, means `directory` is the root src dir /// and is owned externally. @@ -69,6 +77,9 @@ pub fn createWithDir( const owned_src_path = try gpa.dupe(u8, root_src_path); errdefer gpa.free(owned_src_path); + const owned_name = try gpa.dupe(u8, name); + errdefer gpa.free(owned_name); + if (root_src_dir_path) |p| { const owned_dir_path = try directory.join(gpa, &[1][]const u8{p}); errdefer gpa.free(owned_dir_path); @@ -80,12 +91,14 @@ pub fn createWithDir( }, .root_src_directory_owned = true, .root_src_path = owned_src_path, + .name = owned_name, }; } else { ptr.* = .{ .root_src_directory = directory, .root_src_directory_owned = false, .root_src_path = owned_src_path, + .name = owned_name, }; } return ptr; @@ -95,6 +108,7 @@ pub fn createWithDir( /// inside its table; the caller is responsible for calling destroy() on them. pub fn destroy(pkg: *Package, gpa: Allocator) void { gpa.free(pkg.root_src_path); + gpa.free(pkg.name); if (pkg.root_src_directory_owned) { // If root_src_directory.path is null then the handle is the cwd() @@ -111,24 +125,18 @@ pub fn destroy(pkg: *Package, gpa: Allocator) void { /// Only frees memory associated with the table. pub fn deinitTable(pkg: *Package, gpa: Allocator) void { - var it = pkg.table.keyIterator(); - while (it.next()) |key| { - gpa.free(key.*); - } - pkg.table.deinit(gpa); } -pub fn add(pkg: *Package, gpa: Allocator, name: []const u8, package: *Package) !void { +pub fn add(pkg: *Package, gpa: Allocator, package: *Package) !void { try pkg.table.ensureUnusedCapacity(gpa, 1); - const name_dupe = try gpa.dupe(u8, name); - pkg.table.putAssumeCapacityNoClobber(name_dupe, package); + pkg.table.putAssumeCapacityNoClobber(package.name, package); } -pub fn addAndAdopt(parent: *Package, gpa: Allocator, name: []const u8, child: *Package) !void { +pub fn addAndAdopt(parent: *Package, gpa: Allocator, child: *Package) !void { assert(child.parent == null); // make up your mind, who is the parent?? child.parent = parent; - return parent.add(gpa, name, child); + return parent.add(gpa, child); } pub const build_zig_basename = "build.zig"; @@ -237,7 +245,7 @@ pub fn fetchAndAddDependencies( sub_prefix, ); - try addAndAdopt(pkg, gpa, fqn, sub_pkg); + try addAndAdopt(pkg, gpa, sub_pkg); try dependencies_source.writer().print(" pub const {s} = @import(\"{}\");\n", .{ std.zig.fmtId(fqn), std.zig.fmtEscapes(fqn), @@ -249,6 +257,7 @@ pub fn fetchAndAddDependencies( pub fn createFilePkg( gpa: Allocator, + name: []const u8, cache_directory: Compilation.Directory, basename: []const u8, contents: []const u8, @@ -269,7 +278,7 @@ pub fn createFilePkg( const o_dir_sub_path = "o" ++ fs.path.sep_str ++ hex_digest; try renameTmpIntoCache(cache_directory.handle, tmp_dir_sub_path, o_dir_sub_path); - return createWithDir(gpa, cache_directory, o_dir_sub_path, basename); + return createWithDir(gpa, name, cache_directory, o_dir_sub_path, basename); } fn fetchAndUnpack( @@ -312,6 +321,9 @@ fn fetchAndUnpack( const owned_src_path = try gpa.dupe(u8, build_zig_basename); errdefer gpa.free(owned_src_path); + const owned_name = try gpa.dupe(u8, fqn); + errdefer gpa.free(owned_name); + const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path}); errdefer gpa.free(build_root); @@ -326,6 +338,7 @@ fn fetchAndUnpack( }, .root_src_directory_owned = true, .root_src_path = owned_src_path, + .name = owned_name, }; return ptr; @@ -414,7 +427,7 @@ fn fetchAndUnpack( std.zig.fmtId(fqn), std.zig.fmtEscapes(build_root), }); - return createWithDir(gpa, global_cache_directory, pkg_dir_sub_path, build_zig_basename); + return createWithDir(gpa, fqn, global_cache_directory, pkg_dir_sub_path, build_zig_basename); } fn reportError( diff --git a/src/Sema.zig b/src/Sema.zig index 124af6c131..2e57de2406 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5211,6 +5211,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr } const c_import_pkg = Package.create( sema.gpa, + "c_import", // TODO: should we make this unique? null, c_import_res.out_zig_path, ) catch |err| switch (err) { @@ -11663,20 +11664,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. }, error.PackageNotFound => { const cur_pkg = block.getFileScope().pkg; - const parent = if (cur_pkg == sema.mod.main_pkg or cur_pkg == sema.mod.root_pkg) - "root" - else if (cur_pkg.parent) |parent| blk: { - var it = parent.table.iterator(); - while (it.next()) |pkg| { - if (pkg.value_ptr.* == cur_pkg) { - break :blk pkg.key_ptr.*; - } - } - unreachable; - } else { - return sema.fail(block, operand_src, "no package named '{s}' available", .{operand}); - }; - return sema.fail(block, operand_src, "no package named '{s}' available within package '{s}'", .{ operand, parent }); + return sema.fail(block, operand_src, "no package named '{s}' available within package '{s}'", .{ operand, cur_pkg.name }); }, else => { // TODO: these errors are file system errors; make sure an update() will diff --git a/src/main.zig b/src/main.zig index a012a51300..fdc761ac92 100644 --- a/src/main.zig +++ b/src/main.zig @@ -857,6 +857,7 @@ fn buildOutputType( var pkg_tree_root: Package = .{ .root_src_directory = .{ .path = null, .handle = fs.cwd() }, .root_src_path = &[0]u8{}, + .name = &[0]u8{}, }; defer freePkgTree(gpa, &pkg_tree_root, false); var cur_pkg: *Package = &pkg_tree_root; @@ -947,6 +948,7 @@ fn buildOutputType( const new_cur_pkg = Package.create( gpa, + pkg_name, fs.path.dirname(pkg_path), fs.path.basename(pkg_path), ) catch |err| { @@ -958,7 +960,7 @@ fn buildOutputType( } else if (cur_pkg.table.get(pkg_name)) |prev| { fatal("unable to add package '{s}' -> '{s}': already exists as '{s}", .{ pkg_name, pkg_path, prev.root_src_path }); } - try cur_pkg.addAndAdopt(gpa, pkg_name, new_cur_pkg); + try cur_pkg.addAndAdopt(gpa, new_cur_pkg); cur_pkg = new_cur_pkg; } else if (mem.eql(u8, arg, "--pkg-end")) { cur_pkg = cur_pkg.parent orelse @@ -2841,14 +2843,14 @@ fn buildOutputType( if (main_pkg_path) |unresolved_main_pkg_path| { const p = try introspect.resolvePath(arena, unresolved_main_pkg_path); if (p.len == 0) { - break :blk try Package.create(gpa, null, src_path); + break :blk try Package.create(gpa, "root", null, src_path); } else { const rel_src_path = try fs.path.relative(arena, p, src_path); - break :blk try Package.create(gpa, p, rel_src_path); + break :blk try Package.create(gpa, "root", p, rel_src_path); } } else { const root_src_dir_path = fs.path.dirname(src_path); - break :blk Package.create(gpa, root_src_dir_path, fs.path.basename(src_path)) catch |err| { + break :blk Package.create(gpa, "root", root_src_dir_path, fs.path.basename(src_path)) catch |err| { if (root_src_dir_path) |p| { fatal("unable to open '{s}': {s}", .{ p, @errorName(err) }); } else { @@ -4093,6 +4095,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi var main_pkg: Package = .{ .root_src_directory = zig_lib_directory, .root_src_path = "build_runner.zig", + .name = "root", }; if (!build_options.omit_pkg_fetching_code) { @@ -4133,20 +4136,22 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi const deps_pkg = try Package.createFilePkg( gpa, + "@dependencies", local_cache_directory, "dependencies.zig", dependencies_source.items, ); mem.swap(Package.Table, &main_pkg.table, &deps_pkg.table); - try main_pkg.addAndAdopt(gpa, "@dependencies", deps_pkg); + try main_pkg.addAndAdopt(gpa, deps_pkg); } var build_pkg: Package = .{ .root_src_directory = build_directory, .root_src_path = build_zig_basename, + .name = "@build", }; - try main_pkg.addAndAdopt(gpa, "@build", &build_pkg); + try main_pkg.addAndAdopt(gpa, &build_pkg); const comp = Compilation.create(gpa, .{ .zig_lib_directory = zig_lib_directory, @@ -4381,7 +4386,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void .root_decl = .none, }; - file.pkg = try Package.create(gpa, null, file.sub_file_path); + file.pkg = try Package.create(gpa, "root", null, file.sub_file_path); defer file.pkg.destroy(gpa); file.zir = try AstGen.generate(gpa, file.tree); @@ -4592,7 +4597,7 @@ fn fmtPathFile( .root_decl = .none, }; - file.pkg = try Package.create(fmt.gpa, null, file.sub_file_path); + file.pkg = try Package.create(fmt.gpa, "root", null, file.sub_file_path); defer file.pkg.destroy(fmt.gpa); if (stat.size > max_src_size) @@ -5304,7 +5309,7 @@ pub fn cmdAstCheck( file.stat.size = source.len; } - file.pkg = try Package.create(gpa, null, file.sub_file_path); + file.pkg = try Package.create(gpa, "root", null, file.sub_file_path); defer file.pkg.destroy(gpa); file.tree = try std.zig.parse(gpa, file.source); @@ -5423,7 +5428,7 @@ pub fn cmdChangelist( .root_decl = .none, }; - file.pkg = try Package.create(gpa, null, file.sub_file_path); + file.pkg = try Package.create(gpa, "root", null, file.sub_file_path); defer file.pkg.destroy(gpa); const source = try arena.allocSentinel(u8, @intCast(usize, stat.size), 0); diff --git a/src/test.zig b/src/test.zig index 08a62b891f..b25a6c1e78 100644 --- a/src/test.zig +++ b/src/test.zig @@ -1497,6 +1497,7 @@ pub const TestContext = struct { var main_pkg: Package = .{ .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir }, .root_src_path = tmp_src_path, + .name = "root", }; defer main_pkg.table.deinit(allocator); |
