authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2022-11-27 14:17:55+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-01-22 19:00:03+00:00
log5f9186d0ce78ce1eb7db9634849b38d2e90d071e
treeab46e6e77fe60874eb9f52779196ecbd8e4deabc
parent6d71d79dc27ddd6f66913a34fd6cd40691a8c959
signaturelock-open Commit is signed but in an unrecognized format.

AstGen: detect and error on files included in multiple packages

Previously, if a source file was referenced from multiple packages, it just became owned by the first one AstGen happened to reach; this was a problem, because it could lead to inconsistent behaviour in the compiler based on a race condition. This could be fixed by just analyzing such files multiple times - however, it was pointed out by Andrew that it might make more sense to enforce files being part of at most a single package. Having a file in multiple packages would not only impact compile times (due to Sema having to run multiple times on potentially a lot of code) but is also a confusing anti-pattern which more often than not is a mistake on the part of the user. Resolves: #13662

2 files changed, 118 insertions(+), 10 deletions(-)

src/Compilation.zig+61-10
......@@ -639,14 +639,6 @@ pub const AllErrors = struct {
639639 note_i += 1;
640640 }
641641 }
642 if (module_err_msg.src_loc.lazy == .entire_file) {
643 try errors.append(.{
644 .plain = .{
645 .msg = try allocator.dupe(u8, module_err_msg.msg),
646 },
647 });
648 return;
649 }
650642
651643 const reference_trace = try allocator.alloc(Message, module_err_msg.reference_trace.len);
652644 for (reference_trace) |*reference, i| {
......@@ -683,7 +675,7 @@ pub const AllErrors = struct {
683675 .column = @intCast(u32, err_loc.column),
684676 .notes = notes_buf[0..note_i],
685677 .reference_trace = reference_trace,
686 .source_line = try allocator.dupe(u8, err_loc.source_line),
678 .source_line = if (module_err_msg.src_loc.lazy == .entire_file) null else try allocator.dupe(u8, err_loc.source_line),
687679 },
688680 });
689681 }
......@@ -3080,6 +3072,57 @@ pub fn performAllTheWork(
30803072 }
30813073 }
30823074
3075 if (comp.bin_file.options.module) |mod| {
3076 for (mod.import_table.values()) |file| {
3077 if (!file.multi_pkg) continue;
3078 const err = err_blk: {
3079 const notes = try mod.gpa.alloc(Module.ErrorMsg, file.references.items.len);
3080 errdefer mod.gpa.free(notes);
3081
3082 for (notes) |*note, i| {
3083 errdefer for (notes[0..i]) |*n| n.deinit(mod.gpa);
3084 note.* = switch (file.references.items[i]) {
3085 .import => |loc| try Module.ErrorMsg.init(
3086 mod.gpa,
3087 loc,
3088 "imported from package {s}",
3089 .{loc.file_scope.pkg.name},
3090 ),
3091 .root => |pkg| try Module.ErrorMsg.init(
3092 mod.gpa,
3093 .{ .file_scope = file, .parent_decl_node = 0, .lazy = .entire_file },
3094 "root of package {s}",
3095 .{pkg.name},
3096 ),
3097 };
3098 }
3099 errdefer for (notes) |*n| n.deinit(mod.gpa);
3100
3101 const err = try Module.ErrorMsg.create(
3102 mod.gpa,
3103 .{ .file_scope = file, .parent_decl_node = 0, .lazy = .entire_file },
3104 "file exists in multiple packages",
3105 .{},
3106 );
3107 err.notes = notes;
3108 break :err_blk err;
3109 };
3110 errdefer err.destroy(mod.gpa);
3111 try mod.failed_files.putNoClobber(mod.gpa, file, err);
3112 }
3113
3114 // Now that we've reported the errors, we need to deal with
3115 // dependencies. Any file referenced by a multi_pkg file should also be
3116 // marked multi_pkg and have its status set to astgen_failure, as it's
3117 // ambiguous which package they should be analyzed as a part of. We need
3118 // to add this flag after reporting the errors however, as otherwise
3119 // we'd get an error for every single downstream file, which wouldn't be
3120 // very useful.
3121 for (mod.import_table.values()) |file| {
3122 if (file.multi_pkg) file.recursiveMarkMultiPkg(mod);
3123 }
3124 }
3125
30833126 {
30843127 const outdated_and_deleted_decls_frame = tracy.namedFrame("outdated_and_deleted_decls");
30853128 defer outdated_and_deleted_decls_frame.end();
......@@ -3504,7 +3547,15 @@ fn workerAstGenFile(
35043547 comp.mutex.lock();
35053548 defer comp.mutex.unlock();
35063549
3507 break :blk mod.importFile(file, import_path) catch continue;
3550 const res = mod.importFile(file, import_path) catch continue;
3551 if (!res.is_pkg) {
3552 res.file.addReference(mod.*, .{ .import = .{
3553 .file_scope = file,
3554 .parent_decl_node = 0,
3555 .lazy = .{ .token_abs = item.data.token },
3556 } }) catch continue;
3557 }
3558 break :blk res;
35083559 };
35093560 if (import_result.is_new) {
35103561 log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{
src/Module.zig+57
......@@ -1943,6 +1943,10 @@ pub const File = struct {
19431943 zir: Zir,
19441944 /// Package that this file is a part of, managed externally.
19451945 pkg: *Package,
1946 /// Whether this file is a part of multiple packages. This is an error condition which will be reported after AstGen.
1947 multi_pkg: bool = false,
1948 /// List of references to this file, used for multi-package errors.
1949 references: std.ArrayListUnmanaged(Reference) = .{},
19461950
19471951 /// Used by change detection algorithm, after astgen, contains the
19481952 /// set of decls that existed in the previous ZIR but not in the new one.
......@@ -1958,6 +1962,14 @@ pub const File = struct {
19581962 /// successful, this field is unloaded.
19591963 prev_zir: ?*Zir = null,
19601964
1965 /// A single reference to a file.
1966 const Reference = union(enum) {
1967 /// The file is imported directly (i.e. not as a package) with @import.
1968 import: SrcLoc,
1969 /// The file is the root of a package.
1970 root: *Package,
1971 };
1972
19611973 pub fn unload(file: *File, gpa: Allocator) void {
19621974 file.unloadTree(gpa);
19631975 file.unloadSource(gpa);
......@@ -1990,6 +2002,7 @@ pub const File = struct {
19902002 log.debug("deinit File {s}", .{file.sub_file_path});
19912003 file.deleted_decls.deinit(gpa);
19922004 file.outdated_decls.deinit(gpa);
2005 file.references.deinit(gpa);
19932006 if (file.root_decl.unwrap()) |root_decl| {
19942007 mod.destroyDecl(root_decl);
19952008 }
......@@ -2110,6 +2123,44 @@ pub const File = struct {
21102123 else => true,
21112124 };
21122125 }
2126
2127 /// Add a reference to this file during AstGen.
2128 pub fn addReference(file: *File, mod: Module, ref: Reference) !void {
2129 try file.references.append(mod.gpa, ref);
2130
2131 const pkg = switch (ref) {
2132 .import => |loc| loc.file_scope.pkg,
2133 .root => |pkg| pkg,
2134 };
2135 if (pkg != file.pkg) file.multi_pkg = true;
2136 }
2137
2138 /// Mark this file and every file referenced by it as multi_pkg and report an
2139 /// astgen_failure error for them. AstGen must have completed in its entirety.
2140 pub fn recursiveMarkMultiPkg(file: *File, mod: *Module) void {
2141 file.multi_pkg = true;
2142 file.status = .astgen_failure;
2143
2144 std.debug.assert(file.zir_loaded);
2145 const imports_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.imports)];
2146 if (imports_index == 0) return;
2147 const extra = file.zir.extraData(Zir.Inst.Imports, imports_index);
2148
2149 var import_i: u32 = 0;
2150 var extra_index = extra.end;
2151 while (import_i < extra.data.imports_len) : (import_i += 1) {
2152 const item = file.zir.extraData(Zir.Inst.Imports.Item, extra_index);
2153 extra_index = item.end;
2154
2155 const import_path = file.zir.nullTerminatedString(item.data.name);
2156 if (mem.eql(u8, import_path, "builtin")) continue;
2157
2158 const res = mod.importFile(file, import_path) catch continue;
2159 if (!res.is_pkg and !res.file.multi_pkg) {
2160 res.file.recursiveMarkMultiPkg(mod);
2161 }
2162 }
2163 }
21132164};
21142165
21152166/// Represents the contents of a file loaded with `@embedFile`.
......@@ -4690,6 +4741,7 @@ pub fn declareDeclDependency(mod: *Module, depender_index: Decl.Index, dependee_
46904741pub const ImportFileResult = struct {
46914742 file: *File,
46924743 is_new: bool,
4744 is_pkg: bool,
46934745};
46944746
46954747pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult {
......@@ -4709,6 +4761,7 @@ pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult {
47094761 if (gop.found_existing) return ImportFileResult{
47104762 .file = gop.value_ptr.*,
47114763 .is_new = false,
4764 .is_pkg = true,
47124765 };
47134766
47144767 const sub_file_path = try gpa.dupe(u8, pkg.root_src_path);
......@@ -4732,9 +4785,11 @@ pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult {
47324785 .pkg = pkg,
47334786 .root_decl = .none,
47344787 };
4788 try new_file.addReference(mod.*, .{ .root = pkg });
47354789 return ImportFileResult{
47364790 .file = new_file,
47374791 .is_new = true,
4792 .is_pkg = true,
47384793 };
47394794}
47404795
......@@ -4775,6 +4830,7 @@ pub fn importFile(
47754830 if (gop.found_existing) return ImportFileResult{
47764831 .file = gop.value_ptr.*,
47774832 .is_new = false,
4833 .is_pkg = false,
47784834 };
47794835
47804836 const new_file = try gpa.create(File);
......@@ -4820,6 +4876,7 @@ pub fn importFile(
48204876 return ImportFileResult{
48214877 .file = new_file,
48224878 .is_new = true,
4879 .is_pkg = false,
48234880 };
48244881}
48254882