Skip to content

Commit 5f9186d

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 6d71d79 commit 5f9186d

File tree

2 files changed

+118
-10
lines changed

2 files changed

+118
-10
lines changed

src/Compilation.zig

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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_
46904741
pub const ImportFileResult = struct {
46914742
file: *File,
46924743
is_new: bool,
4744+
is_pkg: bool,
46934745
};
46944746

46954747
pub 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

0 commit comments

Comments
 (0)