Skip to content

Commit 55b9fe9

Browse files
committed
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: ziglang#13662
1 parent 6097165 commit 55b9fe9

File tree

3 files changed

+129
-10
lines changed

3 files changed

+129
-10
lines changed

src/Compilation.zig

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -617,14 +617,6 @@ pub const AllErrors = struct {
617617
note_i += 1;
618618
}
619619
}
620-
if (module_err_msg.src_loc.lazy == .entire_file) {
621-
try errors.append(.{
622-
.plain = .{
623-
.msg = try allocator.dupe(u8, module_err_msg.msg),
624-
},
625-
});
626-
return;
627-
}
628620

629621
const reference_trace = try allocator.alloc(Message, module_err_msg.reference_trace.len);
630622
for (reference_trace) |*reference, i| {
@@ -661,7 +653,7 @@ pub const AllErrors = struct {
661653
.column = @intCast(u32, err_loc.column),
662654
.notes = notes_buf[0..note_i],
663655
.reference_trace = reference_trace,
664-
.source_line = try allocator.dupe(u8, err_loc.source_line),
656+
.source_line = if (module_err_msg.src_loc.lazy == .entire_file) null else try allocator.dupe(u8, err_loc.source_line),
665657
},
666658
});
667659
}
@@ -3051,6 +3043,58 @@ pub fn performAllTheWork(
30513043
}
30523044
}
30533045

3046+
if (comp.bin_file.options.module) |mod| {
3047+
for (mod.import_table.values()) |file| {
3048+
if (file.multi_pkg) {
3049+
const err = err_blk: {
3050+
const notes = try mod.gpa.alloc(Module.ErrorMsg, file.references.items.len);
3051+
errdefer mod.gpa.free(notes);
3052+
3053+
for (notes) |*note, i| {
3054+
errdefer for (notes[0..i]) |*n| n.deinit(mod.gpa);
3055+
note.* = switch (file.references.items[i]) {
3056+
.import => |loc| try Module.ErrorMsg.init(
3057+
mod.gpa,
3058+
loc,
3059+
"imported from package {s}",
3060+
.{loc.file_scope.pkg.getName()},
3061+
),
3062+
.root => |pkg| try Module.ErrorMsg.init(
3063+
mod.gpa,
3064+
.{ .file_scope = file, .parent_decl_node = 0, .lazy = .entire_file },
3065+
"root of package {s}",
3066+
.{pkg.getName()},
3067+
),
3068+
};
3069+
}
3070+
errdefer for (notes) |*n| n.deinit(mod.gpa);
3071+
3072+
const err = try Module.ErrorMsg.create(
3073+
mod.gpa,
3074+
.{ .file_scope = file, .parent_decl_node = 0, .lazy = .entire_file },
3075+
"file exists in multiple packages",
3076+
.{},
3077+
);
3078+
err.notes = notes;
3079+
break :err_blk err;
3080+
};
3081+
errdefer err.destroy(mod.gpa);
3082+
try mod.failed_files.putNoClobber(mod.gpa, file, err);
3083+
}
3084+
}
3085+
3086+
// Now that we've reported the errors, we need to deal with
3087+
// dependencies. Any file referenced by a multi_pkg file should also be
3088+
// marked multi_pkg and have its status set to astgen_failure, as it's
3089+
// ambiguous which package they should be analyzed as a part of. We need
3090+
// to add this flag after reporting the errors however, as otherwise
3091+
// we'd get an error for every single downstream file, which wouldn't be
3092+
// very useful.
3093+
for (mod.import_table.values()) |file| {
3094+
if (file.multi_pkg) file.recursiveMarkMultiPkg(mod);
3095+
}
3096+
}
3097+
30543098
if (!use_stage1) {
30553099
const outdated_and_deleted_decls_frame = tracy.namedFrame("outdated_and_deleted_decls");
30563100
defer outdated_and_deleted_decls_frame.end();
@@ -3484,7 +3528,15 @@ fn workerAstGenFile(
34843528
comp.mutex.lock();
34853529
defer comp.mutex.unlock();
34863530

3487-
break :blk mod.importFile(file, import_path) catch continue;
3531+
const res = mod.importFile(file, import_path) catch continue;
3532+
if (res.is_file) {
3533+
res.file.addReference(mod.*, .{ .import = .{
3534+
.file_scope = file,
3535+
.parent_decl_node = 0,
3536+
.lazy = .{ .token_abs = item.data.token },
3537+
} }) catch continue;
3538+
}
3539+
break :blk res;
34883540
};
34893541
if (import_result.is_new) {
34903542
log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{

src/Module.zig

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,6 +1892,10 @@ pub const File = struct {
18921892
zir: Zir,
18931893
/// Package that this file is a part of, managed externally.
18941894
pkg: *Package,
1895+
/// Whether this file is a part of multiple packages. This is an error condition which will be reported after AstGen.
1896+
multi_pkg: bool = false,
1897+
/// List of references to this file, used for multi-package errors.
1898+
references: std.ArrayListUnmanaged(Reference) = .{},
18951899

18961900
/// Used by change detection algorithm, after astgen, contains the
18971901
/// set of decls that existed in the previous ZIR but not in the new one.
@@ -1907,6 +1911,14 @@ pub const File = struct {
19071911
/// successful, this field is unloaded.
19081912
prev_zir: ?*Zir = null,
19091913

1914+
/// A single reference to a file.
1915+
const Reference = union(enum) {
1916+
/// The file is imported directly (i.e. not as a package) with @import.
1917+
import: SrcLoc,
1918+
/// The file is the root of a package.
1919+
root: *Package,
1920+
};
1921+
19101922
pub fn unload(file: *File, gpa: Allocator) void {
19111923
file.unloadTree(gpa);
19121924
file.unloadSource(gpa);
@@ -1939,6 +1951,7 @@ pub const File = struct {
19391951
log.debug("deinit File {s}", .{file.sub_file_path});
19401952
file.deleted_decls.deinit(gpa);
19411953
file.outdated_decls.deinit(gpa);
1954+
file.references.deinit(gpa);
19421955
if (file.root_decl.unwrap()) |root_decl| {
19431956
mod.destroyDecl(root_decl);
19441957
}
@@ -2059,6 +2072,44 @@ pub const File = struct {
20592072
else => true,
20602073
};
20612074
}
2075+
2076+
/// Add a reference to this file during AstGen.
2077+
pub fn addReference(file: *File, mod: Module, ref: Reference) !void {
2078+
try file.references.append(mod.gpa, ref);
2079+
2080+
const pkg = switch (ref) {
2081+
.import => |loc| loc.file_scope.pkg,
2082+
.root => |pkg| pkg,
2083+
};
2084+
if (pkg != file.pkg) file.multi_pkg = true;
2085+
}
2086+
2087+
/// Mark this file and every file referenced by it as multi_pkg and report an
2088+
/// astgen_failure error for them. AstGen must have completed in its entirety.
2089+
pub fn recursiveMarkMultiPkg(file: *File, mod: *Module) void {
2090+
file.multi_pkg = true;
2091+
file.status = .astgen_failure;
2092+
2093+
std.debug.assert(file.zir_loaded);
2094+
const imports_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.imports)];
2095+
if (imports_index == 0) return;
2096+
const extra = file.zir.extraData(Zir.Inst.Imports, imports_index);
2097+
2098+
var import_i: u32 = 0;
2099+
var extra_index = extra.end;
2100+
while (import_i < extra.data.imports_len) : (import_i += 1) {
2101+
const item = file.zir.extraData(Zir.Inst.Imports.Item, extra_index);
2102+
extra_index = item.end;
2103+
2104+
const import_path = file.zir.nullTerminatedString(item.data.name);
2105+
if (mem.eql(u8, import_path, "builtin")) continue;
2106+
2107+
const res = mod.importFile(file, import_path) catch continue;
2108+
if (res.is_file and !res.file.multi_pkg) {
2109+
res.file.recursiveMarkMultiPkg(mod);
2110+
}
2111+
}
2112+
}
20622113
};
20632114

20642115
/// Represents the contents of a file loaded with `@embedFile`.
@@ -4864,6 +4915,7 @@ pub fn declareDeclDependency(mod: *Module, depender_index: Decl.Index, dependee_
48644915
pub const ImportFileResult = struct {
48654916
file: *File,
48664917
is_new: bool,
4918+
is_file: bool,
48674919
};
48684920

48694921
pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult {
@@ -4883,6 +4935,7 @@ pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult {
48834935
if (gop.found_existing) return ImportFileResult{
48844936
.file = gop.value_ptr.*,
48854937
.is_new = false,
4938+
.is_file = false,
48864939
};
48874940

48884941
const sub_file_path = try gpa.dupe(u8, pkg.root_src_path);
@@ -4906,9 +4959,11 @@ pub fn importPkg(mod: *Module, pkg: *Package) !ImportFileResult {
49064959
.pkg = pkg,
49074960
.root_decl = .none,
49084961
};
4962+
try new_file.addReference(mod.*, .{ .root = pkg });
49094963
return ImportFileResult{
49104964
.file = new_file,
49114965
.is_new = true,
4966+
.is_file = false,
49124967
};
49134968
}
49144969

@@ -4949,6 +5004,7 @@ pub fn importFile(
49495004
if (gop.found_existing) return ImportFileResult{
49505005
.file = gop.value_ptr.*,
49515006
.is_new = false,
5007+
.is_file = true,
49525008
};
49535009

49545010
const new_file = try gpa.create(File);
@@ -4990,6 +5046,7 @@ pub fn importFile(
49905046
return ImportFileResult{
49915047
.file = new_file,
49925048
.is_new = true,
5049+
.is_file = true,
49935050
};
49945051
}
49955052

src/Package.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,13 @@ pub fn addAndAdopt(parent: *Package, gpa: Allocator, name: []const u8, child: *P
124124
child.parent = parent;
125125
return parent.add(gpa, name, child);
126126
}
127+
128+
pub fn getName(pkg: *const Package) []const u8 {
129+
if (pkg.parent) |parent| {
130+
var it = parent.table.iterator();
131+
while (it.next()) |p| {
132+
if (p.value_ptr.* == pkg) return p.key_ptr.*;
133+
}
134+
unreachable;
135+
} else return "root";
136+
}

0 commit comments

Comments
 (0)