From f458192e56b13500ff6eb7c3e94dcf48240f4170 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 9 Apr 2021 23:17:50 -0700 Subject: [PATCH] stage2: entry point via std lib and proper updated file detection Instead of Module setting up the root_scope with the root source file, instead, Module relies on the package table graph being set up properly, and inside `update()`, it does the equivalent of `_ = @import("std");`. This, in term, imports start.zig, which has the logic to call main (or not). `Module` no longer has `root_scope` - the root source file is no longer special, it's just in the package table mapped to "root". I also went ahead and implemented proper detection of updated files. mtime, inode, size, and source hash are kept in `Scope.File`. During an update, iterate over `import_table` and stat each file to find out which ones are updated. The source hash is redundant with the source hash used by the struct decl that corresponds to the file, so it should be removed in a future commit before merging the branch. * AstGen: add "previously declared here" notes for variables shadowing decls. * Parse imports as structs. Module now calls `AstGen.structDeclInner`, which is called by `AstGen.containerDecl`. - `importFile` is a bit kludgy with how it handles the top level Decl that kinda gets merged into the struct decl at the end of the function. Be on the look out for bugs related to that as well as possibly cleaner ways to implement this. * Module: factor out lookupDeclName into lookupIdentifier and lookupNa * Rename `Scope.Container` to `Scope.Namespace`. * Delete some dead code. This branch won't work until `usingnamespace` is implemented because it relies on `@import("builtin").OutputMode` and `OutputMode` comes from a `usingnamespace`. --- BRANCH_TODO | 96 +++++ src/AstGen.zig | 184 +++++---- src/Compilation.zig | 98 ++--- src/Module.zig | 678 +++++++++++++++++++------------- src/Sema.zig | 139 +++---- src/codegen.zig | 4 +- src/link/Elf.zig | 4 +- src/link/MachO/DebugSymbols.zig | 4 +- src/type.zig | 35 +- test/stage2/test.zig | 22 +- 10 files changed, 757 insertions(+), 507 deletions(-) create mode 100644 BRANCH_TODO diff --git a/BRANCH_TODO b/BRANCH_TODO new file mode 100644 index 0000000000..04b876c8b0 --- /dev/null +++ b/BRANCH_TODO @@ -0,0 +1,96 @@ + * get rid of failed_root_src_file + + + const container_name_hash: Scope.NameHash = if (found_pkg) |pkg| + pkg.namespace_hash + else + std.zig.hashName(cur_pkg.namespace_hash, "/", resolved_path); + + file_scope.* = .{ + .root_container = .{ + .parent = null, + .file_scope = file_scope, + .decls = .{}, + .ty = struct_ty, + .parent_name_hash = container_name_hash, + }, + }; + mod.analyzeContainer(&file_scope.root_container) catch |err| switch (err) { + error.AnalysisFail => { + assert(mod.comp.totalErrorCount() != 0); + }, + else => |e| return e, + }; + return file_scope; + + + + // Until then we simulate a full cache miss. Source files could have been loaded + // for any reason; to force a refresh we unload now. + module.unloadFile(module.root_scope); + module.failed_root_src_file = null; + module.analyzeNamespace(&module.root_scope.root_container) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.totalErrorCount() != 0); + }, + error.OutOfMemory => return error.OutOfMemory, + else => |e| { + module.failed_root_src_file = e; + }, + }; + + // TODO only analyze imports if they are still referenced + for (module.import_table.items()) |entry| { + module.unloadFile(entry.value); + module.analyzeNamespace(&entry.value.root_container) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.totalErrorCount() != 0); + }, + else => |e| return e, + }; + } + + +pub fn createContainerDecl( + mod: *Module, + scope: *Scope, + base_token: std.zig.ast.TokenIndex, + decl_arena: *std.heap.ArenaAllocator, + typed_value: TypedValue, +) !*Decl { + const scope_decl = scope.ownerDecl().?; + const name = try mod.getAnonTypeName(scope, base_token); + defer mod.gpa.free(name); + const name_hash = scope.namespace().fullyQualifiedNameHash(name); + const src_hash: std.zig.SrcHash = undefined; + const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_node, name_hash, src_hash); + const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); + + decl_arena_state.* = decl_arena.state; + new_decl.typed_value = .{ + .most_recent = .{ + .typed_value = typed_value, + .arena = decl_arena_state, + }, + }; + new_decl.analysis = .complete; + new_decl.generation = mod.generation; + + return new_decl; +} + +fn getAnonTypeName(mod: *Module, scope: *Scope, base_token: std.zig.ast.TokenIndex) ![]u8 { + // TODO add namespaces, generic function signatrues + const tree = scope.tree(); + const token_tags = tree.tokens.items(.tag); + const base_name = switch (token_tags[base_token]) { + .keyword_struct => "struct", + .keyword_enum => "enum", + .keyword_union => "union", + .keyword_opaque => "opaque", + else => unreachable, + }; + const loc = tree.tokenLocation(0, base_token); + return std.fmt.allocPrint(mod.gpa, "{s}:{d}:{d}", .{ base_name, loc.line, loc.column }); +} + diff --git a/src/AstGen.zig b/src/AstGen.zig index 3fe182fc2c..827f545c1b 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1420,6 +1420,7 @@ fn varDecl( return mod.failNode(scope, var_decl.ast.align_node, "TODO implement alignment on locals", .{}); } const astgen = gz.astgen; + const gpa = mod.gpa; const tree = gz.tree(); const token_tags = tree.tokens.items(.tag); @@ -1438,7 +1439,7 @@ fn varDecl( const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{ ident_name, }); - errdefer msg.destroy(mod.gpa); + errdefer msg.destroy(gpa); try mod.errNote(scope, local_val.src, msg, "previous definition is here", .{}); break :msg msg; }; @@ -1453,7 +1454,7 @@ fn varDecl( const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{ ident_name, }); - errdefer msg.destroy(mod.gpa); + errdefer msg.destroy(gpa); try mod.errNote(scope, local_ptr.src, msg, "previous definition is here", .{}); break :msg msg; }; @@ -1467,9 +1468,19 @@ fn varDecl( } // Namespace vars shadowing detection - if (mod.lookupDeclName(scope, ident_name)) |_| { - // TODO add note for other definition - return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name}); + if (mod.lookupIdentifier(scope, ident_name)) |decl| { + const msg = msg: { + const msg = try mod.errMsg( + scope, + name_src, + "redeclaration of '{s}'", + .{ident_name}, + ); + errdefer msg.destroy(gpa); + try mod.errNoteNonLazy(decl.srcLoc(), msg, "previously declared here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); } if (var_decl.ast.init_node == 0) { return mod.fail(scope, name_src, "variables must be initialized", .{}); @@ -1503,7 +1514,7 @@ fn varDecl( .force_comptime = gz.force_comptime, .astgen = astgen, }; - defer init_scope.instructions.deinit(mod.gpa); + defer init_scope.instructions.deinit(gpa); var resolve_inferred_alloc: zir.Inst.Ref = .none; var opt_type_inst: zir.Inst.Ref = .none; @@ -1529,7 +1540,7 @@ fn varDecl( // Move the init_scope instructions into the parent scope, eliding // the alloc instruction and the store_to_block_ptr instruction. const expected_len = parent_zir.items.len + init_scope.instructions.items.len - 2; - try parent_zir.ensureCapacity(mod.gpa, expected_len); + try parent_zir.ensureCapacity(gpa, expected_len); for (init_scope.instructions.items) |src_inst| { if (astgen.indexToRef(src_inst) == init_scope.rl_ptr) continue; if (zir_tags[src_inst] == .store_to_block_ptr) { @@ -1554,7 +1565,7 @@ fn varDecl( // Move the init_scope instructions into the parent scope, swapping // store_to_block_ptr for store_to_inferred_ptr. const expected_len = parent_zir.items.len + init_scope.instructions.items.len; - try parent_zir.ensureCapacity(mod.gpa, expected_len); + try parent_zir.ensureCapacity(gpa, expected_len); for (init_scope.instructions.items) |src_inst| { if (zir_tags[src_inst] == .store_to_block_ptr) { if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) { @@ -1798,6 +1809,91 @@ fn arrayTypeSentinel(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.I return rvalue(gz, scope, rl, result, node); } +pub fn structDeclInner( + gz: *GenZir, + scope: *Scope, + node: ast.Node.Index, + container_decl: ast.full.ContainerDecl, + tag: zir.Inst.Tag, +) InnerError!zir.Inst.Ref { + if (container_decl.ast.members.len == 0) { + return gz.addPlNode(tag, node, zir.Inst.StructDecl{ .fields_len = 0 }); + } + + const astgen = gz.astgen; + const mod = astgen.mod; + const gpa = mod.gpa; + const tree = gz.tree(); + const node_tags = tree.nodes.items(.tag); + + var fields_data = ArrayListUnmanaged(u32){}; + defer fields_data.deinit(gpa); + + // field_name and field_type are both mandatory + try fields_data.ensureCapacity(gpa, container_decl.ast.members.len * 2); + + // We only need this if there are greater than 16 fields. + var bit_bag = ArrayListUnmanaged(u32){}; + defer bit_bag.deinit(gpa); + + var cur_bit_bag: u32 = 0; + var field_index: usize = 0; + for (container_decl.ast.members) |member_node| { + const member = switch (node_tags[member_node]) { + .container_field_init => tree.containerFieldInit(member_node), + .container_field_align => tree.containerFieldAlign(member_node), + .container_field => tree.containerField(member_node), + else => continue, + }; + if (field_index % 16 == 0 and field_index != 0) { + try bit_bag.append(gpa, cur_bit_bag); + cur_bit_bag = 0; + } + if (member.comptime_token) |comptime_token| { + return mod.failTok(scope, comptime_token, "TODO implement comptime struct fields", .{}); + } + try fields_data.ensureCapacity(gpa, fields_data.items.len + 4); + + const field_name = try gz.identAsString(member.ast.name_token); + fields_data.appendAssumeCapacity(field_name); + + const field_type = try typeExpr(gz, scope, member.ast.type_expr); + fields_data.appendAssumeCapacity(@enumToInt(field_type)); + + const have_align = member.ast.align_expr != 0; + const have_value = member.ast.value_expr != 0; + cur_bit_bag = (cur_bit_bag >> 2) | + (@as(u32, @boolToInt(have_align)) << 30) | + (@as(u32, @boolToInt(have_value)) << 31); + + if (have_align) { + const align_inst = try comptimeExpr(gz, scope, .{ .ty = .u32_type }, member.ast.align_expr); + fields_data.appendAssumeCapacity(@enumToInt(align_inst)); + } + if (have_value) { + const default_inst = try comptimeExpr(gz, scope, .{ .ty = field_type }, member.ast.value_expr); + fields_data.appendAssumeCapacity(@enumToInt(default_inst)); + } + + field_index += 1; + } + if (field_index == 0) { + return gz.addPlNode(tag, node, zir.Inst.StructDecl{ .fields_len = 0 }); + } + const empty_slot_count = 16 - (field_index % 16); + cur_bit_bag >>= @intCast(u5, empty_slot_count * 2); + + const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{ + .fields_len = @intCast(u32, container_decl.ast.members.len), + }); + try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + + bit_bag.items.len + 1 + fields_data.items.len); + astgen.extra.appendSliceAssumeCapacity(bit_bag.items); // Likely empty. + astgen.extra.appendAssumeCapacity(cur_bit_bag); + astgen.extra.appendSliceAssumeCapacity(fields_data.items); + return result; +} + fn containerDecl( gz: *GenZir, scope: *Scope, @@ -1827,76 +1923,10 @@ fn containerDecl( .keyword_extern => zir.Inst.Tag.struct_decl_extern, else => unreachable, } else zir.Inst.Tag.struct_decl; - if (container_decl.ast.members.len == 0) { - const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{ - .fields_len = 0, - }); - return rvalue(gz, scope, rl, result, node); - } assert(arg_inst == .none); - var fields_data = ArrayListUnmanaged(u32){}; - defer fields_data.deinit(gpa); - // field_name and field_type are both mandatory - try fields_data.ensureCapacity(gpa, container_decl.ast.members.len * 2); - - // We only need this if there are greater than 16 fields. - var bit_bag = ArrayListUnmanaged(u32){}; - defer bit_bag.deinit(gpa); - - var cur_bit_bag: u32 = 0; - var field_index: usize = 0; - for (container_decl.ast.members) |member_node| { - const member = switch (node_tags[member_node]) { - .container_field_init => tree.containerFieldInit(member_node), - .container_field_align => tree.containerFieldAlign(member_node), - .container_field => tree.containerField(member_node), - else => continue, - }; - if (field_index % 16 == 0 and field_index != 0) { - try bit_bag.append(gpa, cur_bit_bag); - cur_bit_bag = 0; - } - if (member.comptime_token) |comptime_token| { - return mod.failTok(scope, comptime_token, "TODO implement comptime struct fields", .{}); - } - try fields_data.ensureCapacity(gpa, fields_data.items.len + 4); - - const field_name = try gz.identAsString(member.ast.name_token); - fields_data.appendAssumeCapacity(field_name); - - const field_type = try typeExpr(gz, scope, member.ast.type_expr); - fields_data.appendAssumeCapacity(@enumToInt(field_type)); - - const have_align = member.ast.align_expr != 0; - const have_value = member.ast.value_expr != 0; - cur_bit_bag = (cur_bit_bag >> 2) | - (@as(u32, @boolToInt(have_align)) << 30) | - (@as(u32, @boolToInt(have_value)) << 31); - - if (have_align) { - const align_inst = try comptimeExpr(gz, scope, .{ .ty = .u32_type }, member.ast.align_expr); - fields_data.appendAssumeCapacity(@enumToInt(align_inst)); - } - if (have_value) { - const default_inst = try comptimeExpr(gz, scope, .{ .ty = field_type }, member.ast.value_expr); - fields_data.appendAssumeCapacity(@enumToInt(default_inst)); - } - - field_index += 1; - } - const empty_slot_count = 16 - (field_index % 16); - cur_bit_bag >>= @intCast(u5, empty_slot_count * 2); - - const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{ - .fields_len = @intCast(u32, container_decl.ast.members.len), - }); - try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + - bit_bag.items.len + 1 + fields_data.items.len); - astgen.extra.appendSliceAssumeCapacity(bit_bag.items); // Likely empty. - astgen.extra.appendAssumeCapacity(cur_bit_bag); - astgen.extra.appendSliceAssumeCapacity(fields_data.items); + const result = try structDeclInner(gz, scope, node, container_decl, tag); return rvalue(gz, scope, rl, result, node); }, .keyword_union => { @@ -2930,7 +2960,7 @@ pub const SwitchProngSrc = union(enum) { ) LazySrcLoc { @setCold(true); const switch_node = decl.relativeToNodeIndex(switch_node_offset); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const main_tokens = tree.nodes.items(.main_token); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); @@ -3692,7 +3722,7 @@ fn identifier( }; } - const decl = mod.lookupDeclName(scope, ident_name) orelse { + const decl = mod.lookupIdentifier(scope, ident_name) orelse { // TODO insert a "dependency on the non-existence of a decl" here to make this // compile error go away when the decl is introduced. This data should be in a global // sparse map since it is only relevant when a compile error occurs. diff --git a/src/Compilation.zig b/src/Compilation.zig index 722b307c16..c4adfb0e02 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -385,7 +385,7 @@ pub const AllErrors = struct { const notes = try arena.allocator.alloc(Message, module_err_msg.notes.len); for (notes) |*note, i| { const module_note = module_err_msg.notes[i]; - const source = try module_note.src_loc.fileScope().getSource(module); + const source = try module_note.src_loc.fileScope().getSource(module.gpa); const byte_offset = try module_note.src_loc.byteOffset(); const loc = std.zig.findLineColumn(source, byte_offset); const sub_file_path = module_note.src_loc.fileScope().sub_file_path; @@ -400,7 +400,7 @@ pub const AllErrors = struct { }, }; } - const source = try module_err_msg.src_loc.fileScope().getSource(module); + const source = try module_err_msg.src_loc.fileScope().getSource(module.gpa); const byte_offset = try module_err_msg.src_loc.byteOffset(); const loc = std.zig.findLineColumn(source, byte_offset); const sub_file_path = module_err_msg.src_loc.fileScope().sub_file_path; @@ -1049,37 +1049,18 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { // However we currently do not have serialization of such metadata, so for now // we set up an empty Module that does the entire compilation fresh. - const root_scope = try gpa.create(Module.Scope.File); - errdefer gpa.destroy(root_scope); - - const struct_ty = try Type.Tag.empty_struct.create(gpa, &root_scope.root_container); - root_scope.* = .{ - // TODO this is duped so it can be freed in Container.deinit - .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path), - .source = .{ .unloaded = {} }, - .tree = undefined, - .status = .never_loaded, - .pkg = root_pkg, - .root_container = .{ - .file_scope = root_scope, - .decls = .{}, - .ty = struct_ty, - .parent_name_hash = root_pkg.namespace_hash, - }, - }; - const module = try arena.create(Module); errdefer module.deinit(); module.* = .{ .gpa = gpa, .comp = comp, .root_pkg = root_pkg, - .root_scope = root_scope, .zig_cache_artifact_directory = zig_cache_artifact_directory, .emit_h = options.emit_h, .error_name_list = try std.ArrayListUnmanaged([]const u8).initCapacity(gpa, 1), }; module.error_name_list.appendAssumeCapacity("(no error)"); + break :blk module; } else blk: { if (options.emit_h != null) return error.NoZigModuleForCHeader; @@ -1485,31 +1466,50 @@ pub fn update(self: *Compilation) !void { module.compile_log_text.shrinkAndFree(module.gpa, 0); module.generation += 1; - // TODO Detect which source files changed. - // Until then we simulate a full cache miss. Source files could have been loaded - // for any reason; to force a refresh we unload now. - module.unloadFile(module.root_scope); - module.failed_root_src_file = null; - module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, - error.OutOfMemory => return error.OutOfMemory, - else => |e| { - module.failed_root_src_file = e; - }, - }; - - // TODO only analyze imports if they are still referenced + // Detect which source files changed. for (module.import_table.items()) |entry| { - module.unloadFile(entry.value); - module.analyzeContainer(&entry.value.root_container) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, + const file = entry.value; + var f = try file.pkg.root_src_directory.handle.openFile(file.sub_file_path, .{}); + defer f.close(); + + // TODO handle error here by populating a retryable compile error + const stat = try f.stat(); + const unchanged_metadata = + stat.size == file.stat_size and + stat.mtime == file.stat_mtime and + stat.inode == file.stat_inode; + + if (unchanged_metadata) { + log.debug("unmodified metadata of file: {s}", .{file.sub_file_path}); + continue; + } + + const prev_hash = file.source_hash; + file.unloadSource(module.gpa); + // TODO handle error here by populating a retryable compile error + try file.finishGettingSource(module.gpa, f, stat); + assert(file.source_loaded); + if (mem.eql(u8, &prev_hash, &file.source_hash)) { + file.updateTreeToNewSource(); + log.debug("unmodified source hash of file: {s}", .{file.sub_file_path}); + continue; + } + + log.debug("source contents changed: {s}", .{file.sub_file_path}); + if (file.status == .unloaded_parse_failure) { + module.failed_files.swapRemove(file).?.value.destroy(module.gpa); + } + file.unloadTree(module.gpa); + + module.analyzeFile(file) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => continue, else => |e| return e, }; } + + // Simulate `_ = @import("std");` which in turn imports start.zig. + _ = try module.importFile(module.root_pkg, "std"); } } @@ -1551,7 +1551,9 @@ pub fn update(self: *Compilation) !void { // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { if (self.bin_file.options.module) |module| { - module.root_scope.unload(self.gpa); + for (module.import_table.items()) |entry| { + entry.value.unload(self.gpa); + } } } } @@ -1580,13 +1582,13 @@ pub fn totalErrorCount(self: *Compilation) usize { // the previous parse success, including compile errors, but we cannot // emit them until the file succeeds parsing. for (module.failed_decls.items()) |entry| { - if (entry.key.container.file_scope.status == .unloaded_parse_failure) { + if (entry.key.namespace.file_scope.status == .unloaded_parse_failure) { continue; } total += 1; } for (module.emit_h_failed_decls.items()) |entry| { - if (entry.key.container.file_scope.status == .unloaded_parse_failure) { + if (entry.key.namespace.file_scope.status == .unloaded_parse_failure) { continue; } total += 1; @@ -1641,7 +1643,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.failed_decls.items()) |entry| { - if (entry.key.container.file_scope.status == .unloaded_parse_failure) { + if (entry.key.namespace.file_scope.status == .unloaded_parse_failure) { // Skip errors for Decls within files that had a parse failure. // We'll try again once parsing succeeds. continue; @@ -1649,7 +1651,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.emit_h_failed_decls.items()) |entry| { - if (entry.key.container.file_scope.status == .unloaded_parse_failure) { + if (entry.key.namespace.file_scope.status == .unloaded_parse_failure) { // Skip errors for Decls within files that had a parse failure. // We'll try again once parsing succeeds. continue; diff --git a/src/Module.zig b/src/Module.zig index 96b490e2a1..ab0cf3c10f 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -26,6 +26,7 @@ const trace = @import("tracy.zig").trace; const AstGen = @import("AstGen.zig"); const Sema = @import("Sema.zig"); const target_util = @import("target.zig"); +const Cache = @import("Cache.zig"); /// General-purpose allocator. Used for both temporary and long-term storage. gpa: *Allocator, @@ -35,8 +36,6 @@ comp: *Compilation, zig_cache_artifact_directory: Compilation.Directory, /// Pointer to externally managed resource. `null` if there is no zig file being compiled. root_pkg: *Package, -/// Module owns this resource. -root_scope: *Scope.File, /// It's rare for a decl to be exported, so we save memory by having a sparse map of /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. @@ -52,6 +51,12 @@ symbol_exports: std.StringArrayHashMapUnmanaged(*Export) = .{}, export_owners: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{}, /// Maps fully qualified namespaced names to the Decl struct for them. decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{}, +/// The set of all the files in the Module. We keep track of this in order to iterate +/// over it and check which source files have been modified on the file system when +/// an update is requested, as well as to cache `@import` results. +/// Keys are fully resolved file paths. This table owns the keys and values. +import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{}, + /// We optimize memory usage for a compilation with no compile errors by storing the /// error messages and mapping outside of `Decl`. /// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator. @@ -85,9 +90,6 @@ global_error_set: std.StringHashMapUnmanaged(ErrorInt) = .{}, /// Corresponds with `global_error_set`. error_name_list: ArrayListUnmanaged([]const u8) = .{}, -/// Keys are fully qualified paths -import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{}, - /// Incrementing integer used to compare against the corresponding Decl /// field to determine whether a Decl's status applies to an ongoing update, or a /// previous analysis. @@ -147,15 +149,16 @@ pub const Decl = struct { /// This is necessary for mapping them to an address in the output file. /// Memory owned by this decl, using Module's allocator. name: [*:0]const u8, - /// The direct parent container of the Decl. + /// The direct parent namespace of the Decl. /// Reference to externally owned memory. - container: *Scope.Container, + /// This is `null` for the Decl that represents a `File`. + namespace: *Scope.Namespace, /// An integer that can be checked against the corresponding incrementing /// generation field of Module. This is used to determine whether `complete` status /// represents pre- or post- re-analysis. generation: u32, - /// The AST Node index or ZIR Inst index that contains this declaration. + /// The AST node index of this declaration. /// Must be recomputed when the corresponding source file is modified. src_node: ast.Node.Index, @@ -273,22 +276,22 @@ pub const Decl = struct { } pub fn srcToken(decl: Decl) u32 { - const tree = &decl.container.file_scope.tree; + const tree = &decl.namespace.file_scope.tree; return tree.firstToken(decl.src_node); } pub fn srcByteOffset(decl: Decl) u32 { - const tree = &decl.container.file_scope.tree; + const tree = &decl.namespace.file_scope.tree; return tree.tokens.items(.start)[decl.srcToken()]; } pub fn fullyQualifiedNameHash(decl: Decl) Scope.NameHash { - return decl.container.fullyQualifiedNameHash(mem.spanZ(decl.name)); + return decl.namespace.fullyQualifiedNameHash(mem.spanZ(decl.name)); } pub fn renderFullyQualifiedName(decl: Decl, writer: anytype) !void { const unqualified_name = mem.spanZ(decl.name); - return decl.container.renderFullyQualifiedName(unqualified_name, writer); + return decl.namespace.renderFullyQualifiedName(unqualified_name, writer); } pub fn getFullyQualifiedName(decl: Decl, gpa: *Allocator) ![]u8 { @@ -330,7 +333,7 @@ pub const Decl = struct { } pub fn getFileScope(decl: Decl) *Scope.File { - return decl.container.file_scope; + return decl.namespace.file_scope; } pub fn getEmitH(decl: *Decl, module: *Module) *EmitH { @@ -377,7 +380,7 @@ pub const Struct = struct { /// Set of field names in declaration order. fields: std.StringArrayHashMapUnmanaged(Field), /// Represents the declarations inside this struct. - container: Scope.Container, + namespace: Scope.Namespace, /// Offset from `owner_decl`, points to the struct AST node. node_offset: i32, @@ -434,7 +437,7 @@ pub const EnumFull = struct { /// If this hash map is empty, it means the enum tags are auto-numbered. values: ValueMap, /// Represents the declarations inside this struct. - container: Scope.Container, + namespace: Scope.Namespace, /// Offset from `owner_decl`, points to the enum decl AST node. node_offset: i32, @@ -521,7 +524,7 @@ pub const Scope = struct { .local_val => return scope.cast(LocalVal).?.gen_zir.astgen.arena, .local_ptr => return scope.cast(LocalPtr).?.gen_zir.astgen.arena, .file => unreachable, - .container => unreachable, + .namespace => unreachable, .decl_ref => unreachable, } } @@ -533,7 +536,7 @@ pub const Scope = struct { .local_val => scope.cast(LocalVal).?.gen_zir.astgen.decl, .local_ptr => scope.cast(LocalPtr).?.gen_zir.astgen.decl, .file => null, - .container => null, + .namespace => null, .decl_ref => scope.cast(DeclRef).?.decl, }; } @@ -545,36 +548,21 @@ pub const Scope = struct { .local_val => scope.cast(LocalVal).?.gen_zir.astgen.decl, .local_ptr => scope.cast(LocalPtr).?.gen_zir.astgen.decl, .file => null, - .container => null, + .namespace => null, .decl_ref => scope.cast(DeclRef).?.decl, }; } - /// Asserts the scope has a parent which is a Container and returns it. - pub fn namespace(scope: *Scope) *Container { + /// Asserts the scope has a parent which is a Namespace and returns it. + pub fn namespace(scope: *Scope) *Namespace { switch (scope.tag) { - .block => return scope.cast(Block).?.sema.owner_decl.container, - .gen_zir => return scope.cast(GenZir).?.astgen.decl.container, - .local_val => return scope.cast(LocalVal).?.gen_zir.astgen.decl.container, - .local_ptr => return scope.cast(LocalPtr).?.gen_zir.astgen.decl.container, - .file => return &scope.cast(File).?.root_container, - .container => return scope.cast(Container).?, - .decl_ref => return scope.cast(DeclRef).?.decl.container, - } - } - - /// Must generate unique bytes with no collisions with other decls. - /// The point of hashing here is only to limit the number of bytes of - /// the unique identifier to a fixed size (16 bytes). - pub fn fullyQualifiedNameHash(scope: *Scope, name: []const u8) NameHash { - switch (scope.tag) { - .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .file => unreachable, - .container => return scope.cast(Container).?.fullyQualifiedNameHash(name), - .decl_ref => unreachable, + .block => return scope.cast(Block).?.sema.owner_decl.namespace, + .gen_zir => return scope.cast(GenZir).?.astgen.decl.namespace, + .local_val => return scope.cast(LocalVal).?.gen_zir.astgen.decl.namespace, + .local_ptr => return scope.cast(LocalPtr).?.gen_zir.astgen.decl.namespace, + .file => return scope.cast(File).?.namespace, + .namespace => return scope.cast(Namespace).?, + .decl_ref => return scope.cast(DeclRef).?.decl.namespace, } } @@ -582,12 +570,12 @@ pub const Scope = struct { pub fn tree(scope: *Scope) *const ast.Tree { switch (scope.tag) { .file => return &scope.cast(File).?.tree, - .block => return &scope.cast(Block).?.src_decl.container.file_scope.tree, + .block => return &scope.cast(Block).?.src_decl.namespace.file_scope.tree, .gen_zir => return scope.cast(GenZir).?.tree(), - .local_val => return &scope.cast(LocalVal).?.gen_zir.astgen.decl.container.file_scope.tree, - .local_ptr => return &scope.cast(LocalPtr).?.gen_zir.astgen.decl.container.file_scope.tree, - .container => return &scope.cast(Container).?.file_scope.tree, - .decl_ref => return &scope.cast(DeclRef).?.decl.container.file_scope.tree, + .local_val => return &scope.cast(LocalVal).?.gen_zir.astgen.decl.namespace.file_scope.tree, + .local_ptr => return &scope.cast(LocalPtr).?.gen_zir.astgen.decl.namespace.file_scope.tree, + .namespace => return &scope.cast(Namespace).?.file_scope.tree, + .decl_ref => return &scope.cast(DeclRef).?.decl.namespace.file_scope.tree, } } @@ -599,16 +587,16 @@ pub const Scope = struct { .local_val => return scope.cast(LocalVal).?.gen_zir, .local_ptr => return scope.cast(LocalPtr).?.gen_zir, .file => unreachable, - .container => unreachable, + .namespace => unreachable, .decl_ref => unreachable, }; } - /// Asserts the scope has a parent which is a Container or File and + /// Asserts the scope has a parent which is a Namespace or File and /// returns the sub_file_path field. pub fn subFilePath(base: *Scope) []const u8 { switch (base.tag) { - .container => return @fieldParentPtr(Container, "base", base).file_scope.sub_file_path, + .namespace => return @fieldParentPtr(Namespace, "base", base).file_scope.sub_file_path, .file => return @fieldParentPtr(File, "base", base).sub_file_path, .block => unreachable, .gen_zir => unreachable, @@ -618,30 +606,18 @@ pub const Scope = struct { } } - pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 { - switch (base.tag) { - .container => return @fieldParentPtr(Container, "base", base).file_scope.getSource(module), - .file => return @fieldParentPtr(File, "base", base).getSource(module), - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .block => unreachable, - .decl_ref => unreachable, - } - } - /// When called from inside a Block Scope, chases the src_decl, not the owner_decl. pub fn getFileScope(base: *Scope) *Scope.File { var cur = base; while (true) { cur = switch (cur.tag) { - .container => return @fieldParentPtr(Container, "base", cur).file_scope, + .namespace => return @fieldParentPtr(Namespace, "base", cur).file_scope, .file => return @fieldParentPtr(File, "base", cur), .gen_zir => @fieldParentPtr(GenZir, "base", cur).parent, .local_val => @fieldParentPtr(LocalVal, "base", cur).parent, .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent, - .block => return @fieldParentPtr(Block, "base", cur).src_decl.container.file_scope, - .decl_ref => return @fieldParentPtr(DeclRef, "base", cur).decl.container.file_scope, + .block => return @fieldParentPtr(Block, "base", cur).src_decl.namespace.file_scope, + .decl_ref => return @fieldParentPtr(DeclRef, "base", cur).decl.namespace.file_scope, }; } } @@ -657,8 +633,8 @@ pub const Scope = struct { pub const Tag = enum { /// .zig source code. file, - /// struct, enum or union, every .file contains one of these. - container, + /// Namespace owned by structs, enums, unions, and opaques for decls. + namespace, block, gen_zir, local_val, @@ -669,37 +645,44 @@ pub const Scope = struct { decl_ref, }; - pub const Container = struct { - pub const base_tag: Tag = .container; + /// The container that structs, enums, unions, and opaques have. + pub const Namespace = struct { + pub const base_tag: Tag = .namespace; base: Scope = Scope{ .tag = base_tag }, + parent: ?*Namespace, file_scope: *Scope.File, parent_name_hash: NameHash, - - /// Direct children of the file. - decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{}, + /// Will be a struct, enum, union, or opaque. ty: Type, + /// Direct children of the namespace. Used during an update to detect + /// which decls have been added/removed from source. + decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{}, - pub fn deinit(cont: *Container, gpa: *Allocator) void { - cont.decls.deinit(gpa); - // TODO either Container of File should have an arena for sub_file_path and ty - gpa.destroy(cont.ty.castTag(.empty_struct).?); - gpa.free(cont.file_scope.sub_file_path); - cont.* = undefined; + pub fn deinit(ns: *Namespace, gpa: *Allocator) void { + ns.decls.deinit(gpa); + ns.* = undefined; } - pub fn removeDecl(cont: *Container, child: *Decl) void { - _ = cont.decls.swapRemove(child); + pub fn removeDecl(ns: *Namespace, child: *Decl) void { + _ = ns.decls.swapRemove(child); } - pub fn fullyQualifiedNameHash(cont: *Container, name: []const u8) NameHash { - return std.zig.hashName(cont.parent_name_hash, ".", name); + /// Must generate unique bytes with no collisions with other decls. + /// The point of hashing here is only to limit the number of bytes of + /// the unique identifier to a fixed size (16 bytes). + pub fn fullyQualifiedNameHash(ns: Namespace, name: []const u8) NameHash { + return std.zig.hashName(ns.parent_name_hash, ".", name); } - pub fn renderFullyQualifiedName(cont: Container, name: []const u8, writer: anytype) !void { + pub fn renderFullyQualifiedName(ns: Namespace, name: []const u8, writer: anytype) !void { // TODO this should render e.g. "std.fs.Dir.OpenOptions" return writer.writeAll(name); } + + pub fn getDecl(ns: Namespace) *Decl { + return ns.ty.getOwnerDecl(); + } }; pub const File = struct { @@ -711,46 +694,54 @@ pub const Scope = struct { unloaded_parse_failure, loaded_success, }, - + source_loaded: bool, /// Relative to the owning package's root_src_dir. - /// Reference to external memory, not owned by File. + /// Memory is stored in gpa, owned by File. sub_file_path: []const u8, - source: union(enum) { - unloaded: void, - bytes: [:0]const u8, - }, + /// Whether this is populated depends on `source_loaded`. + source: [:0]const u8, + /// Whether this is populated depends on `status`. + stat_size: u64, + /// Whether this is populated depends on `status`. + stat_inode: std.fs.File.INode, + /// Whether this is populated depends on `status`. + stat_mtime: i128, + /// Whether this is populated depends on `status`. + source_hash: Cache.BinDigest, /// Whether this is populated or not depends on `status`. tree: ast.Tree, /// Package that this file is a part of, managed externally. pkg: *Package, - - root_container: Container, + /// The namespace of the struct that represents this file. + namespace: *Namespace, pub fn unload(file: *File, gpa: *Allocator) void { - switch (file.status) { - .unloaded_parse_failure, - .never_loaded, - .unloaded_success, - => { - file.status = .unloaded_success; - }, + file.unloadTree(gpa); + file.unloadSource(gpa); + } - .loaded_success => { - file.tree.deinit(gpa); - file.status = .unloaded_success; - }, + pub fn unloadTree(file: *File, gpa: *Allocator) void { + if (file.status == .loaded_success) { + file.tree.deinit(gpa); } - switch (file.source) { - .bytes => |bytes| { - gpa.free(bytes); - file.source = .{ .unloaded = {} }; - }, - .unloaded => {}, + file.status = .unloaded_success; + } + + pub fn unloadSource(file: *File, gpa: *Allocator) void { + if (file.source_loaded) { + file.source_loaded = false; + gpa.free(file.source); + } + } + + pub fn updateTreeToNewSource(file: *File) void { + assert(file.source_loaded); + if (file.status == .loaded_success) { + file.tree.source = file.source; } } pub fn deinit(file: *File, gpa: *Allocator) void { - file.root_container.deinit(gpa); file.unload(gpa); file.* = undefined; } @@ -765,22 +756,44 @@ pub const Scope = struct { std.debug.print("{s}:{d}:{d}\n", .{ file.sub_file_path, loc.line + 1, loc.column + 1 }); } - pub fn getSource(file: *File, module: *Module) ![:0]const u8 { - switch (file.source) { - .unloaded => { - const source = try file.pkg.root_src_directory.handle.readFileAllocOptions( - module.gpa, - file.sub_file_path, - std.math.maxInt(u32), - null, - 1, - 0, - ); - file.source = .{ .bytes = source }; - return source; - }, - .bytes => |bytes| return bytes, - } + pub fn getSource(file: *File, gpa: *Allocator) ![:0]const u8 { + if (file.source_loaded) return file.source; + + // Keep track of inode, file size, mtime, hash so we can detect which files + // have been modified when an incremental update is requested. + var f = try file.pkg.root_src_directory.handle.openFile(file.sub_file_path, .{}); + defer f.close(); + + const stat = try f.stat(); + + try file.finishGettingSource(gpa, f, stat); + assert(file.source_loaded); + return file.source; + } + + pub fn finishGettingSource( + file: *File, + gpa: *Allocator, + f: std.fs.File, + stat: std.fs.File.Stat, + ) !void { + if (stat.size > std.math.maxInt(u32)) + return error.FileTooBig; + + const source = try gpa.allocSentinel(u8, stat.size, 0); + const amt = try f.readAll(source); + if (amt != stat.size) + return error.UnexpectedEndOfFile; + + var hasher = Cache.hasher_init; + hasher.update(source); + hasher.final(&file.source_hash); + + file.stat_size = stat.size; + file.stat_inode = stat.inode; + file.stat_mtime = stat.mtime; + file.source = source; + file.source_loaded = true; } }; @@ -859,7 +872,7 @@ pub const Scope = struct { } pub fn getFileScope(block: *Block) *Scope.File { - return block.src_decl.container.file_scope; + return block.src_decl.namespace.file_scope; } pub fn addNoOp( @@ -1110,7 +1123,7 @@ pub const Scope = struct { } pub fn tree(gz: *const GenZir) *const ast.Tree { - return &gz.astgen.decl.container.file_scope.tree; + return &gz.astgen.decl.namespace.file_scope.tree; } pub fn setBreakResultLoc(gz: *GenZir, parent_rl: AstGen.ResultLoc) void { @@ -1678,7 +1691,7 @@ pub const SrcLoc = struct { .node_offset_switch_range, .node_offset_fn_type_cc, .node_offset_fn_type_ret_ty, - => src_loc.container.decl.container.file_scope, + => src_loc.container.decl.namespace.file_scope, }; } @@ -1706,14 +1719,14 @@ pub const SrcLoc = struct { .token_offset => |tok_off| { const decl = src_loc.container.decl; const tok_index = decl.srcToken() + tok_off; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, .node_offset, .node_offset_bin_op => |node_off| { const decl = src_loc.container.decl; const node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const main_tokens = tree.nodes.items(.main_token); const tok_index = main_tokens[node]; const token_starts = tree.tokens.items(.start); @@ -1722,7 +1735,7 @@ pub const SrcLoc = struct { .node_offset_back2tok => |node_off| { const decl = src_loc.container.decl; const node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const tok_index = tree.firstToken(node) - 2; const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; @@ -1730,7 +1743,7 @@ pub const SrcLoc = struct { .node_offset_var_decl_ty => |node_off| { const decl = src_loc.container.decl; const node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_tags = tree.nodes.items(.tag); const full = switch (node_tags[node]) { .global_var_decl => tree.globalVarDecl(node), @@ -1750,7 +1763,7 @@ pub const SrcLoc = struct { }, .node_offset_builtin_call_arg0 => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1766,7 +1779,7 @@ pub const SrcLoc = struct { }, .node_offset_builtin_call_arg1 => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1782,7 +1795,7 @@ pub const SrcLoc = struct { }, .node_offset_array_access_index => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1793,7 +1806,7 @@ pub const SrcLoc = struct { }, .node_offset_slice_sentinel => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1810,7 +1823,7 @@ pub const SrcLoc = struct { }, .node_offset_call_func => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1837,7 +1850,7 @@ pub const SrcLoc = struct { }, .node_offset_field_name => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1850,7 +1863,7 @@ pub const SrcLoc = struct { }, .node_offset_deref_ptr => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1860,7 +1873,7 @@ pub const SrcLoc = struct { }, .node_offset_asm_source => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1876,7 +1889,7 @@ pub const SrcLoc = struct { }, .node_offset_asm_ret_ty => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -1894,7 +1907,7 @@ pub const SrcLoc = struct { .node_offset_for_cond, .node_offset_if_cond => |node_off| { const decl = src_loc.container.decl; const node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_tags = tree.nodes.items(.tag); const src_node = switch (node_tags[node]) { .if_simple => tree.ifSimple(node).ast.cond_expr, @@ -1914,7 +1927,7 @@ pub const SrcLoc = struct { .node_offset_bin_lhs => |node_off| { const decl = src_loc.container.decl; const node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const src_node = node_datas[node].lhs; const main_tokens = tree.nodes.items(.main_token); @@ -1925,7 +1938,7 @@ pub const SrcLoc = struct { .node_offset_bin_rhs => |node_off| { const decl = src_loc.container.decl; const node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const src_node = node_datas[node].rhs; const main_tokens = tree.nodes.items(.main_token); @@ -1937,7 +1950,7 @@ pub const SrcLoc = struct { .node_offset_switch_operand => |node_off| { const decl = src_loc.container.decl; const node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const src_node = node_datas[node].lhs; const main_tokens = tree.nodes.items(.main_token); @@ -1949,7 +1962,7 @@ pub const SrcLoc = struct { .node_offset_switch_special_prong => |node_off| { const decl = src_loc.container.decl; const switch_node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const main_tokens = tree.nodes.items(.main_token); @@ -1976,7 +1989,7 @@ pub const SrcLoc = struct { .node_offset_switch_range => |node_off| { const decl = src_loc.container.decl; const switch_node = decl.relativeToNodeIndex(node_off); - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const main_tokens = tree.nodes.items(.main_token); @@ -2006,7 +2019,7 @@ pub const SrcLoc = struct { .node_offset_fn_type_cc => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -2026,7 +2039,7 @@ pub const SrcLoc = struct { .node_offset_fn_type_ret_ty => |node_off| { const decl = src_loc.container.decl; - const tree = decl.container.file_scope.base.tree(); + const tree = decl.namespace.file_scope.base.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = decl.relativeToNodeIndex(node_off); @@ -2351,7 +2364,6 @@ pub fn deinit(mod: *Module) void { mod.export_owners.deinit(gpa); mod.symbol_exports.deinit(gpa); - mod.root_scope.destroy(gpa); var it = mod.global_error_set.iterator(); while (it.next()) |entry| { @@ -2362,6 +2374,7 @@ pub fn deinit(mod: *Module) void { mod.error_name_list.deinit(gpa); for (mod.import_table.items()) |entry| { + gpa.free(entry.key); entry.value.destroy(gpa); } mod.import_table.deinit(gpa); @@ -2465,7 +2478,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool { const tracy = trace(@src()); defer tracy.end(); - const tree = try mod.getAstTree(decl.container.file_scope); + const tree = try mod.getAstTree(decl.namespace.file_scope); const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); const decl_node = decl.src_node; @@ -2516,7 +2529,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool { var gen_scope: Scope.GenZir = .{ .force_comptime = true, - .parent = &decl.container.base, + .parent = &decl.namespace.base, .astgen = &astgen, }; defer gen_scope.instructions.deinit(mod.gpa); @@ -2590,7 +2603,7 @@ fn astgenAndSemaFn( var fn_type_scope: Scope.GenZir = .{ .force_comptime = true, - .parent = &decl.container.base, + .parent = &decl.namespace.base, .astgen = &fn_type_astgen, }; defer fn_type_scope.instructions.deinit(mod.gpa); @@ -2829,7 +2842,7 @@ fn astgenAndSemaFn( var gen_scope: Scope.GenZir = .{ .force_comptime = false, - .parent = &decl.container.base, + .parent = &decl.namespace.base, .astgen = &astgen, }; defer gen_scope.instructions.deinit(mod.gpa); @@ -3035,7 +3048,7 @@ fn astgenAndSemaVarDecl( var gen_scope: Scope.GenZir = .{ .force_comptime = true, - .parent = &decl.container.base, + .parent = &decl.namespace.base, .astgen = &astgen, }; defer gen_scope.instructions.deinit(mod.gpa); @@ -3104,7 +3117,7 @@ fn astgenAndSemaVarDecl( var type_scope: Scope.GenZir = .{ .force_comptime = true, - .parent = &decl.container.base, + .parent = &decl.namespace.base, .astgen = &astgen, }; defer type_scope.instructions.deinit(mod.gpa); @@ -3220,46 +3233,48 @@ pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !u3 return @intCast(u32, gop.index); } -pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree { +pub fn getAstTree(mod: *Module, file: *Scope.File) !*const ast.Tree { const tracy = trace(@src()); defer tracy.end(); - switch (root_scope.status) { + switch (file.status) { .never_loaded, .unloaded_success => { - try mod.failed_files.ensureCapacity(mod.gpa, mod.failed_files.items().len + 1); + const gpa = mod.gpa; - const source = try root_scope.getSource(mod); + try mod.failed_files.ensureCapacity(gpa, mod.failed_files.items().len + 1); + + const source = try file.getSource(gpa); var keep_tree = false; - root_scope.tree = try std.zig.parse(mod.gpa, source); - defer if (!keep_tree) root_scope.tree.deinit(mod.gpa); + file.tree = try std.zig.parse(gpa, source); + defer if (!keep_tree) file.tree.deinit(gpa); - const tree = &root_scope.tree; + const tree = &file.tree; if (tree.errors.len != 0) { const parse_err = tree.errors[0]; - var msg = std.ArrayList(u8).init(mod.gpa); + var msg = std.ArrayList(u8).init(gpa); defer msg.deinit(); const token_starts = tree.tokens.items(.start); try tree.renderError(parse_err, msg.writer()); - const err_msg = try mod.gpa.create(ErrorMsg); + const err_msg = try gpa.create(ErrorMsg); err_msg.* = .{ .src_loc = .{ - .container = .{ .file_scope = root_scope }, + .container = .{ .file_scope = file }, .lazy = .{ .byte_abs = token_starts[parse_err.token] }, }, .msg = msg.toOwnedSlice(), }; - mod.failed_files.putAssumeCapacityNoClobber(root_scope, err_msg); - root_scope.status = .unloaded_parse_failure; + mod.failed_files.putAssumeCapacityNoClobber(file, err_msg); + file.status = .unloaded_parse_failure; return error.AnalysisFail; } - root_scope.status = .loaded_success; + file.status = .loaded_success; keep_tree = true; return tree; @@ -3267,30 +3282,186 @@ pub fn getAstTree(mod: *Module, root_scope: *Scope.File) !*const ast.Tree { .unloaded_parse_failure => return error.AnalysisFail, - .loaded_success => return &root_scope.tree, + .loaded_success => return &file.tree, } } -pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { +pub fn importFile(mod: *Module, cur_pkg: *Package, import_string: []const u8) !*Scope.File { + const gpa = mod.gpa; + + const cur_pkg_dir_path = cur_pkg.root_src_directory.path orelse "."; + const found_pkg = cur_pkg.table.get(import_string); + + const resolved_path = if (found_pkg) |pkg| + try std.fs.path.resolve(gpa, &[_][]const u8{ pkg.root_src_directory.path orelse ".", pkg.root_src_path }) + else + try std.fs.path.resolve(gpa, &[_][]const u8{ cur_pkg_dir_path, import_string }); + var keep_resolved_path = false; + defer if (!keep_resolved_path) gpa.free(resolved_path); + + const gop = try mod.import_table.getOrPut(gpa, resolved_path); + if (gop.found_existing) return gop.entry.value; + + if (found_pkg == null) { + const resolved_root_path = try std.fs.path.resolve(gpa, &[_][]const u8{cur_pkg_dir_path}); + defer gpa.free(resolved_root_path); + + if (!mem.startsWith(u8, resolved_path, resolved_root_path)) { + return error.ImportOutsidePkgPath; + } + } + + const new_file = try gpa.create(Scope.File); + gop.entry.value = new_file; + new_file.* = .{ + .sub_file_path = resolved_path, + .source = undefined, + .source_hash = undefined, + .source_loaded = false, + .stat_size = undefined, + .stat_inode = undefined, + .stat_mtime = undefined, + .tree = undefined, + .status = .never_loaded, + .pkg = found_pkg orelse cur_pkg, + .namespace = undefined, + }; + keep_resolved_path = true; + + const tree = try mod.getAstTree(new_file); + + const parent_name_hash: Scope.NameHash = if (found_pkg) |pkg| + pkg.namespace_hash + else + std.zig.hashName(cur_pkg.namespace_hash, "/", resolved_path); + + // We need a Decl to pass to AstGen and collect dependencies. But ultimately we + // want to pass them on to the Decl for the struct that represents the file. + var tmp_namespace: Scope.Namespace = .{ + .parent = null, + .file_scope = new_file, + .parent_name_hash = parent_name_hash, + .ty = Type.initTag(.type), + }; + const top_decl = try mod.createNewDecl( + &tmp_namespace, + resolved_path, + 0, + parent_name_hash, + new_file.source_hash, + ); + defer { + mod.decl_table.removeAssertDiscard(parent_name_hash); + top_decl.destroy(mod); + } + + var gen_scope_arena = std.heap.ArenaAllocator.init(gpa); + defer gen_scope_arena.deinit(); + + var astgen = try AstGen.init(mod, top_decl, &gen_scope_arena.allocator); + defer astgen.deinit(); + + var gen_scope: Scope.GenZir = .{ + .force_comptime = true, + .parent = &new_file.base, + .astgen = &astgen, + }; + defer gen_scope.instructions.deinit(gpa); + + const container_decl: ast.full.ContainerDecl = .{ + .layout_token = null, + .ast = .{ + .main_token = undefined, + .enum_token = null, + .members = tree.rootDecls(), + .arg = 0, + }, + }; + + const struct_decl_ref = try AstGen.structDeclInner( + &gen_scope, + &gen_scope.base, + 0, + container_decl, + .struct_decl, + ); + _ = try gen_scope.addBreak(.break_inline, 0, struct_decl_ref); + + var code = try gen_scope.finish(); + defer code.deinit(gpa); + if (std.builtin.mode == .Debug and mod.comp.verbose_ir) { + code.dump(gpa, "import", &gen_scope.base, 0) catch {}; + } + + var sema: Sema = .{ + .mod = mod, + .gpa = gpa, + .arena = &gen_scope_arena.allocator, + .code = code, + .inst_map = try gen_scope_arena.allocator.alloc(*ir.Inst, code.instructions.len), + .owner_decl = top_decl, + .func = null, + .owner_func = null, + .param_inst_list = &.{}, + }; + var block_scope: Scope.Block = .{ + .parent = null, + .sema = &sema, + .src_decl = top_decl, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + }; + defer block_scope.instructions.deinit(gpa); + + const init_inst_zir_ref = try sema.rootAsRef(&block_scope); + const analyzed_struct_inst = try sema.resolveInst(init_inst_zir_ref); + assert(analyzed_struct_inst.ty.zigTypeTag() == .Type); + const val = analyzed_struct_inst.value().?; + const struct_ty = try val.toType(&gen_scope_arena.allocator); + const struct_decl = struct_ty.getOwnerDecl(); + + new_file.namespace = struct_ty.getNamespace().?; + new_file.namespace.parent = null; + new_file.namespace.parent_name_hash = tmp_namespace.parent_name_hash; + + // Transfer the dependencies to `owner_decl`. + assert(top_decl.dependants.count() == 0); + for (top_decl.dependencies.items()) |entry| { + const dep = entry.key; + dep.removeDependant(top_decl); + if (dep == struct_decl) continue; + _ = try mod.declareDeclDependency(struct_decl, dep); + } + + try mod.analyzeFile(new_file); + return new_file; +} + +pub fn analyzeFile(mod: *Module, file: *Scope.File) !void { + return mod.analyzeNamespace(file.namespace); +} + +pub fn analyzeNamespace(mod: *Module, namespace: *Scope.Namespace) !void { const tracy = trace(@src()); defer tracy.end(); // We may be analyzing it for the first time, or this may be // an incremental update. This code handles both cases. - const tree = try mod.getAstTree(container_scope.file_scope); + const tree = try mod.getAstTree(namespace.file_scope); const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); const decls = tree.rootDecls(); try mod.comp.work_queue.ensureUnusedCapacity(decls.len); - try container_scope.decls.ensureCapacity(mod.gpa, decls.len); + try namespace.decls.ensureCapacity(mod.gpa, decls.len); - // Keep track of the decls that we expect to see in this file so that + // Keep track of the decls that we expect to see in this namespace so that // we know which ones have been deleted. var deleted_decls = std.AutoArrayHashMap(*Decl, void).init(mod.gpa); defer deleted_decls.deinit(); - try deleted_decls.ensureCapacity(container_scope.decls.items().len); - for (container_scope.decls.items()) |entry| { + try deleted_decls.ensureCapacity(namespace.decls.items().len); + for (namespace.decls.items()) |entry| { deleted_decls.putAssumeCapacityNoClobber(entry.key, {}); } @@ -3310,7 +3481,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { .fn_proto_simple => { var params: [1]ast.Node.Index = undefined; try mod.semaContainerFn( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3320,7 +3491,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { ); }, .fn_proto_multi => try mod.semaContainerFn( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3331,7 +3502,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { .fn_proto_one => { var params: [1]ast.Node.Index = undefined; try mod.semaContainerFn( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3341,7 +3512,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { ); }, .fn_proto => try mod.semaContainerFn( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3355,7 +3526,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { .fn_proto_simple => { var params: [1]ast.Node.Index = undefined; try mod.semaContainerFn( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3365,7 +3536,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { ); }, .fn_proto_multi => try mod.semaContainerFn( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3376,7 +3547,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { .fn_proto_one => { var params: [1]ast.Node.Index = undefined; try mod.semaContainerFn( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3386,7 +3557,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { ); }, .fn_proto => try mod.semaContainerFn( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3396,7 +3567,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { ), .global_var_decl => try mod.semaContainerVar( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3404,7 +3575,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { tree.globalVarDecl(decl_node), ), .local_var_decl => try mod.semaContainerVar( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3412,7 +3583,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { tree.localVarDecl(decl_node), ), .simple_var_decl => try mod.semaContainerVar( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3420,7 +3591,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { tree.simpleVarDecl(decl_node), ), .aligned_var_decl => try mod.semaContainerVar( - container_scope, + namespace, &deleted_decls, &outdated_decls, decl_node, @@ -3433,11 +3604,11 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { const name = try std.fmt.allocPrint(mod.gpa, "__comptime_{d}", .{name_index}); defer mod.gpa.free(name); - const name_hash = container_scope.fullyQualifiedNameHash(name); + const name_hash = namespace.fullyQualifiedNameHash(name); const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node)); - const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash); - container_scope.decls.putAssumeCapacity(new_decl, {}); + const new_decl = try mod.createNewDecl(namespace, name, decl_node, name_hash, contents_hash); + namespace.decls.putAssumeCapacity(new_decl, {}); mod.comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); }, @@ -3483,7 +3654,7 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void { fn semaContainerFn( mod: *Module, - container_scope: *Scope.Container, + namespace: *Scope.Namespace, deleted_decls: *std.AutoArrayHashMap(*Decl, void), outdated_decls: *std.AutoArrayHashMap(*Decl, void), decl_node: ast.Node.Index, @@ -3500,25 +3671,25 @@ fn semaContainerFn( @panic("TODO missing function name"); }; const name = tree.tokenSlice(name_token); // TODO use identifierTokenString - const name_hash = container_scope.fullyQualifiedNameHash(name); + const name_hash = namespace.fullyQualifiedNameHash(name); const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node)); if (mod.decl_table.get(name_hash)) |decl| { - // Update the AST Node index of the decl, even if its contents are unchanged, it may + // Update the AST node of the decl; even if its contents are unchanged, it may // have been re-ordered. const prev_src_node = decl.src_node; decl.src_node = decl_node; if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; const msg = try ErrorMsg.create(mod.gpa, .{ - .container = .{ .file_scope = container_scope.file_scope }, + .container = .{ .file_scope = namespace.file_scope }, .lazy = .{ .token_abs = name_token }, - }, "redefinition of '{s}'", .{decl.name}); + }, "redeclaration of '{s}'", .{decl.name}); errdefer msg.destroy(mod.gpa); const other_src_loc: SrcLoc = .{ - .container = .{ .file_scope = decl.container.file_scope }, + .container = .{ .file_scope = decl.namespace.file_scope }, .lazy = .{ .node_abs = prev_src_node }, }; - try mod.errNoteNonLazy(other_src_loc, msg, "previous definition here", .{}); + try mod.errNoteNonLazy(other_src_loc, msg, "previously declared here", .{}); try mod.failed_decls.putNoClobber(mod.gpa, decl, msg); } else { if (!srcHashEql(decl.contents_hash, contents_hash)) { @@ -3542,8 +3713,8 @@ fn semaContainerFn( } } } else { - const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash); - container_scope.decls.putAssumeCapacity(new_decl, {}); + const new_decl = try mod.createNewDecl(namespace, name, decl_node, name_hash, contents_hash); + namespace.decls.putAssumeCapacity(new_decl, {}); if (fn_proto.extern_export_token) |maybe_export_token| { const token_tags = tree.tokens.items(.tag); if (token_tags[maybe_export_token] == .keyword_export) { @@ -3556,7 +3727,7 @@ fn semaContainerFn( fn semaContainerVar( mod: *Module, - container_scope: *Scope.Container, + namespace: *Scope.Namespace, deleted_decls: *std.AutoArrayHashMap(*Decl, void), outdated_decls: *std.AutoArrayHashMap(*Decl, void), decl_node: ast.Node.Index, @@ -3568,7 +3739,7 @@ fn semaContainerVar( const name_token = var_decl.ast.mut_token + 1; const name = tree.tokenSlice(name_token); // TODO identifierTokenString - const name_hash = container_scope.fullyQualifiedNameHash(name); + const name_hash = namespace.fullyQualifiedNameHash(name); const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node)); if (mod.decl_table.get(name_hash)) |decl| { // Update the AST Node index of the decl, even if its contents are unchanged, it may @@ -3578,23 +3749,23 @@ fn semaContainerVar( if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; const msg = try ErrorMsg.create(mod.gpa, .{ - .container = .{ .file_scope = container_scope.file_scope }, + .container = .{ .file_scope = namespace.file_scope }, .lazy = .{ .token_abs = name_token }, - }, "redefinition of '{s}'", .{decl.name}); + }, "redeclaration of '{s}'", .{decl.name}); errdefer msg.destroy(mod.gpa); const other_src_loc: SrcLoc = .{ - .container = .{ .file_scope = decl.container.file_scope }, + .container = .{ .file_scope = decl.namespace.file_scope }, .lazy = .{ .node_abs = prev_src_node }, }; - try mod.errNoteNonLazy(other_src_loc, msg, "previous definition here", .{}); + try mod.errNoteNonLazy(other_src_loc, msg, "previously declared here", .{}); try mod.failed_decls.putNoClobber(mod.gpa, decl, msg); } else if (!srcHashEql(decl.contents_hash, contents_hash)) { try outdated_decls.put(decl, {}); decl.contents_hash = contents_hash; } } else { - const new_decl = try mod.createNewDecl(&container_scope.base, name, decl_node, name_hash, contents_hash); - container_scope.decls.putAssumeCapacity(new_decl, {}); + const new_decl = try mod.createNewDecl(namespace, name, decl_node, name_hash, contents_hash); + namespace.decls.putAssumeCapacity(new_decl, {}); if (var_decl.extern_export_token) |maybe_export_token| { const token_tags = tree.tokens.items(.tag); if (token_tags[maybe_export_token] == .keyword_export) { @@ -3624,7 +3795,7 @@ pub fn deleteDecl( // Remove from the namespace it resides in. In the case of an anonymous Decl it will // not be present in the set, and this does nothing. - decl.container.removeDecl(decl); + decl.namespace.removeDecl(decl); const name_hash = decl.fullyQualifiedNameHash(); mod.decl_table.removeAssertDiscard(name_hash); @@ -3786,7 +3957,7 @@ fn markOutdatedDecl(mod: *Module, decl: *Decl) !void { fn allocateNewDecl( mod: *Module, - scope: *Scope, + namespace: *Scope.Namespace, src_node: ast.Node.Index, contents_hash: std.zig.SrcHash, ) !*Decl { @@ -3802,7 +3973,7 @@ fn allocateNewDecl( new_decl.* = .{ .name = "", - .container = scope.namespace(), + .namespace = namespace, .src_node = src_node, .typed_value = .{ .never_succeeded = {} }, .analysis = .unreferenced, @@ -3832,14 +4003,14 @@ fn allocateNewDecl( fn createNewDecl( mod: *Module, - scope: *Scope, + namespace: *Scope.Namespace, decl_name: []const u8, src_node: ast.Node.Index, name_hash: Scope.NameHash, contents_hash: std.zig.SrcHash, ) !*Decl { try mod.decl_table.ensureCapacity(mod.gpa, mod.decl_table.items().len + 1); - const new_decl = try mod.allocateNewDecl(scope, src_node, contents_hash); + const new_decl = try mod.allocateNewDecl(namespace, src_node, contents_hash); errdefer mod.gpa.destroy(new_decl); new_decl.name = try mem.dupeZ(mod.gpa, u8, decl_name); mod.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl); @@ -3930,7 +4101,7 @@ pub fn analyzeExport( ); errdefer msg.destroy(mod.gpa); try mod.errNote( - &other_export.owner_decl.container.base, + &other_export.owner_decl.namespace.base, other_export.src, msg, "other symbol here", @@ -4050,9 +4221,10 @@ pub fn createAnonymousDecl( const scope_decl = scope.ownerDecl().?; const name = try std.fmt.allocPrint(mod.gpa, "{s}__anon_{d}", .{ scope_decl.name, name_index }); defer mod.gpa.free(name); - const name_hash = scope.namespace().fullyQualifiedNameHash(name); + const namespace = scope_decl.namespace; + const name_hash = namespace.fullyQualifiedNameHash(name); const src_hash: std.zig.SrcHash = undefined; - const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_node, name_hash, src_hash); + const new_decl = try mod.createNewDecl(namespace, name, scope_decl.src_node, name_hash, src_hash); const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); decl_arena_state.* = decl_arena.state; @@ -4076,55 +4248,30 @@ pub fn createAnonymousDecl( return new_decl; } -pub fn createContainerDecl( - mod: *Module, - scope: *Scope, - base_token: std.zig.ast.TokenIndex, - decl_arena: *std.heap.ArenaAllocator, - typed_value: TypedValue, -) !*Decl { - const scope_decl = scope.ownerDecl().?; - const name = try mod.getAnonTypeName(scope, base_token); - defer mod.gpa.free(name); - const name_hash = scope.namespace().fullyQualifiedNameHash(name); - const src_hash: std.zig.SrcHash = undefined; - const new_decl = try mod.createNewDecl(scope, name, scope_decl.src_node, name_hash, src_hash); - const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); - - decl_arena_state.* = decl_arena.state; - new_decl.typed_value = .{ - .most_recent = .{ - .typed_value = typed_value, - .arena = decl_arena_state, - }, - }; - new_decl.analysis = .complete; - new_decl.generation = mod.generation; - - return new_decl; -} - -fn getAnonTypeName(mod: *Module, scope: *Scope, base_token: std.zig.ast.TokenIndex) ![]u8 { - // TODO add namespaces, generic function signatrues - const tree = scope.tree(); - const token_tags = tree.tokens.items(.tag); - const base_name = switch (token_tags[base_token]) { - .keyword_struct => "struct", - .keyword_enum => "enum", - .keyword_union => "union", - .keyword_opaque => "opaque", - else => unreachable, - }; - const loc = tree.tokenLocation(0, base_token); - return std.fmt.allocPrint(mod.gpa, "{s}:{d}:{d}", .{ base_name, loc.line, loc.column }); -} - fn getNextAnonNameIndex(mod: *Module) usize { return @atomicRmw(usize, &mod.next_anon_name_index, .Add, 1, .Monotonic); } -pub fn lookupDeclName(mod: *Module, scope: *Scope, ident_name: []const u8) ?*Decl { - const namespace = scope.namespace(); +/// This looks up a bare identifier in the given scope. This will walk up the tree of namespaces +/// in scope and check each one for the identifier. +pub fn lookupIdentifier(mod: *Module, scope: *Scope, ident_name: []const u8) ?*Decl { + var namespace = scope.namespace(); + while (true) { + if (mod.lookupInNamespace(namespace, ident_name)) |decl| { + return decl; + } + namespace = namespace.parent orelse break; + } + return null; +} + +/// This looks up a member of a specific namespace. It is affected by `usingnamespace` but +/// only for ones in the specified namespace. +pub fn lookupInNamespace( + mod: *Module, + namespace: *Scope.Namespace, + ident_name: []const u8, +) ?*Decl { const name_hash = namespace.fullyQualifiedNameHash(ident_name); return mod.decl_table.get(name_hash); } @@ -4271,7 +4418,7 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) In mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.astgen.decl, err_msg); }, .file => unreachable, - .container => unreachable, + .namespace => unreachable, .decl_ref => { const decl_ref = scope.cast(Scope.DeclRef).?; decl_ref.decl.analysis = .sema_failure; @@ -4683,10 +4830,3 @@ pub fn parseStrLit( }, } } - -pub fn unloadFile(mod: *Module, file_scope: *Scope.File) void { - if (file_scope.status == .unloaded_parse_failure) { - mod.failed_files.swapRemove(file_scope).?.value.destroy(mod.gpa); - } - file_scope.unload(mod.gpa); -} diff --git a/src/Sema.zig b/src/Sema.zig index 98bff5bf23..e8d3a72c64 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -609,10 +609,11 @@ fn zirStructDecl( .owner_decl = sema.owner_decl, .fields = fields_map, .node_offset = inst_data.src_node, - .container = .{ + .namespace = .{ + .parent = sema.owner_decl.namespace, + .parent_name_hash = new_decl.fullyQualifiedNameHash(), .ty = struct_ty, .file_scope = block.getFileScope(), - .parent_name_hash = new_decl.fullyQualifiedNameHash(), }, }; return sema.analyzeDeclVal(block, src, new_decl); @@ -3640,42 +3641,43 @@ fn zirHasDecl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError const mod = sema.mod; const arena = sema.arena; - const container_scope = container_type.getContainerScope() orelse return mod.fail( + const namespace = container_type.getNamespace() orelse return mod.fail( &block.base, lhs_src, "expected struct, enum, union, or opaque, found '{}'", .{container_type}, ); - if (mod.lookupDeclName(&container_scope.base, decl_name)) |decl| { - // TODO if !decl.is_pub and inDifferentFiles() return false - return mod.constBool(arena, src, true); - } else { - return mod.constBool(arena, src, false); + if (mod.lookupInNamespace(namespace, decl_name)) |decl| { + if (decl.is_pub or decl.namespace.file_scope == block.base.namespace().file_scope) { + return mod.constBool(arena, src, true); + } } + return mod.constBool(arena, src, false); } fn zirImport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); + const mod = sema.mod; const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand = try sema.resolveConstString(block, operand_src, inst_data.operand); - const file_scope = sema.analyzeImport(block, src, operand) catch |err| switch (err) { + const file = mod.importFile(block.getFileScope().pkg, operand) catch |err| switch (err) { error.ImportOutsidePkgPath => { - return sema.mod.fail(&block.base, src, "import of file outside package path: '{s}'", .{operand}); + return mod.fail(&block.base, src, "import of file outside package path: '{s}'", .{operand}); }, error.FileNotFound => { - return sema.mod.fail(&block.base, src, "unable to find '{s}'", .{operand}); + return mod.fail(&block.base, src, "unable to find '{s}'", .{operand}); }, else => { // TODO: make sure this gets retried and not cached - return sema.mod.fail(&block.base, src, "unable to open '{s}': {s}", .{ operand, @errorName(err) }); + return mod.fail(&block.base, src, "unable to open '{s}': {s}", .{ operand, @errorName(err) }); }, }; - return sema.mod.constType(sema.arena, src, file_scope.root_container.ty); + return mod.constType(sema.arena, src, file.namespace.ty); } fn zirShl(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { @@ -4707,21 +4709,9 @@ fn namedFieldPtr( }); }, .Struct, .Opaque, .Union => { - if (child_type.getContainerScope()) |container_scope| { - if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| { - if (!decl.is_pub and !(decl.container.file_scope == block.base.namespace().file_scope)) - return mod.fail(&block.base, src, "'{s}' is private", .{field_name}); - return sema.analyzeDeclRef(block, src, decl); - } - - // TODO this will give false positives for structs inside the root file - if (container_scope.file_scope == mod.root_scope) { - return mod.fail( - &block.base, - src, - "root source file has no member named '{s}'", - .{field_name}, - ); + if (child_type.getNamespace()) |namespace| { + if (try sema.analyzeNamespaceLookup(block, src, namespace, field_name)) |inst| { + return inst; } } // TODO add note: declared here @@ -4736,11 +4726,9 @@ fn namedFieldPtr( }); }, .Enum => { - if (child_type.getContainerScope()) |container_scope| { - if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| { - if (!decl.is_pub and !(decl.container.file_scope == block.base.namespace().file_scope)) - return mod.fail(&block.base, src, "'{s}' is private", .{field_name}); - return sema.analyzeDeclRef(block, src, decl); + if (child_type.getNamespace()) |namespace| { + if (try sema.analyzeNamespaceLookup(block, src, namespace, field_name)) |inst| { + return inst; } } const field_index = child_type.enumFieldIndex(field_name) orelse { @@ -4778,6 +4766,32 @@ fn namedFieldPtr( return mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty}); } +fn analyzeNamespaceLookup( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + namespace: *Scope.Namespace, + decl_name: []const u8, +) InnerError!?*Inst { + const mod = sema.mod; + const gpa = sema.gpa; + if (mod.lookupInNamespace(namespace, decl_name)) |decl| { + if (!decl.is_pub and decl.namespace.file_scope != block.getFileScope()) { + const msg = msg: { + const msg = try mod.errMsg(&block.base, src, "'{s}' is not marked 'pub'", .{ + decl_name, + }); + errdefer msg.destroy(gpa); + try mod.errNoteNonLazy(decl.srcLoc(), msg, "declared here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + } + return try sema.analyzeDeclRef(block, src, decl); + } + return null; +} + fn analyzeStructFieldPtr( sema: *Sema, block: *Scope.Block, @@ -5326,65 +5340,6 @@ fn analyzeSlice( return sema.mod.fail(&block.base, src, "TODO implement analysis of slice", .{}); } -fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_string: []const u8) !*Scope.File { - const cur_pkg = block.getFileScope().pkg; - const cur_pkg_dir_path = cur_pkg.root_src_directory.path orelse "."; - const found_pkg = cur_pkg.table.get(target_string); - - const resolved_path = if (found_pkg) |pkg| - try std.fs.path.resolve(sema.gpa, &[_][]const u8{ pkg.root_src_directory.path orelse ".", pkg.root_src_path }) - else - try std.fs.path.resolve(sema.gpa, &[_][]const u8{ cur_pkg_dir_path, target_string }); - errdefer sema.gpa.free(resolved_path); - - if (sema.mod.import_table.get(resolved_path)) |cached_import| { - sema.gpa.free(resolved_path); - return cached_import; - } - - if (found_pkg == null) { - const resolved_root_path = try std.fs.path.resolve(sema.gpa, &[_][]const u8{cur_pkg_dir_path}); - defer sema.gpa.free(resolved_root_path); - - if (!mem.startsWith(u8, resolved_path, resolved_root_path)) { - return error.ImportOutsidePkgPath; - } - } - - // TODO Scope.Container arena for ty and sub_file_path - const file_scope = try sema.gpa.create(Scope.File); - errdefer sema.gpa.destroy(file_scope); - const struct_ty = try Type.Tag.empty_struct.create(sema.gpa, &file_scope.root_container); - errdefer sema.gpa.destroy(struct_ty.castTag(.empty_struct).?); - - const container_name_hash: Scope.NameHash = if (found_pkg) |pkg| - pkg.namespace_hash - else - std.zig.hashName(cur_pkg.namespace_hash, "/", resolved_path); - - file_scope.* = .{ - .sub_file_path = resolved_path, - .source = .{ .unloaded = {} }, - .tree = undefined, - .status = .never_loaded, - .pkg = found_pkg orelse cur_pkg, - .root_container = .{ - .file_scope = file_scope, - .decls = .{}, - .ty = struct_ty, - .parent_name_hash = container_name_hash, - }, - }; - sema.mod.analyzeContainer(&file_scope.root_container) catch |err| switch (err) { - error.AnalysisFail => { - assert(sema.mod.comp.totalErrorCount() != 0); - }, - else => |e| return e, - }; - try sema.mod.import_table.put(sema.gpa, file_scope.sub_file_path, file_scope); - return file_scope; -} - /// Asserts that lhs and rhs types are both numeric. fn cmpNumeric( sema: *Sema, diff --git a/src/codegen.zig b/src/codegen.zig index a345fd8058..f77de4c87d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -411,8 +411,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try branch_stack.append(.{}); const src_data: struct { lbrace_src: usize, rbrace_src: usize, source: []const u8 } = blk: { - const container_scope = module_fn.owner_decl.container; - const tree = container_scope.file_scope.tree; + const namespace = module_fn.owner_decl.namespace; + const tree = namespace.file_scope.tree; const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index f60a7423a2..173ffdab68 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2223,7 +2223,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { try dbg_line_buffer.ensureCapacity(26); const line_off: u28 = blk: { - const tree = decl.container.file_scope.tree; + const tree = decl.namespace.file_scope.tree; const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); @@ -2749,7 +2749,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec if (self.llvm_object) |_| return; - const tree = decl.container.file_scope.tree; + const tree = decl.namespace.file_scope.tree; const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index b05f0e1a77..4c6b71eed4 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -904,7 +904,7 @@ pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const M const tracy = trace(@src()); defer tracy.end(); - const tree = decl.container.file_scope.tree; + const tree = decl.namespace.file_scope.tree; const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); @@ -953,7 +953,7 @@ pub fn initDeclDebugBuffers( try dbg_line_buffer.ensureCapacity(26); const line_off: u28 = blk: { - const tree = decl.container.file_scope.tree; + const tree = decl.namespace.file_scope.tree; const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); const token_starts = tree.tokens.items(.start); diff --git a/src/type.zig b/src/type.zig index 74c38f7e0d..d05fa0f5e3 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2052,11 +2052,11 @@ pub const Type = extern union { (self.isSinglePointer() and self.elemType().zigTypeTag() == .Array); } - /// Returns null if the type has no container. - pub fn getContainerScope(self: Type) ?*Module.Scope.Container { + /// Returns null if the type has no namespace. + pub fn getNamespace(self: Type) ?*Module.Scope.Namespace { return switch (self.tag()) { - .@"struct" => &self.castTag(.@"struct").?.data.container, - .enum_full => &self.castTag(.enum_full).?.data.container, + .@"struct" => &self.castTag(.@"struct").?.data.namespace, + .enum_full => &self.castTag(.enum_full).?.data.namespace, .empty_struct => self.castTag(.empty_struct).?.data, .@"opaque" => &self.castTag(.@"opaque").?.data, @@ -2226,6 +2226,29 @@ pub const Type = extern union { } } + pub fn getOwnerDecl(ty: Type) *Module.Decl { + switch (ty.tag()) { + .enum_full, .enum_nonexhaustive => { + const enum_full = ty.cast(Payload.EnumFull).?.data; + return enum_full.owner_decl; + }, + .enum_simple => { + const enum_simple = ty.castTag(.enum_simple).?.data; + return enum_simple.owner_decl; + }, + .@"struct" => { + const struct_obj = ty.castTag(.@"struct").?.data; + return struct_obj.owner_decl; + }, + .error_set => { + const error_set = ty.castTag(.error_set).?.data; + return error_set.owner_decl; + }, + .@"opaque" => @panic("TODO"), + else => unreachable, + } + } + /// Asserts the type is an enum. pub fn enumHasInt(ty: Type, int: Value, target: Target) bool { const S = struct { @@ -2564,12 +2587,12 @@ pub const Type = extern union { /// Most commonly used for files. pub const ContainerScope = struct { base: Payload, - data: *Module.Scope.Container, + data: *Module.Scope.Namespace, }; pub const Opaque = struct { base: Payload = .{ .tag = .@"opaque" }, - data: Module.Scope.Container, + data: Module.Scope.Namespace, }; pub const Struct = struct { diff --git a/test/stage2/test.zig b/test/stage2/test.zig index b4bc1a413e..5fec836038 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1048,7 +1048,7 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); try case.files.append(.{ - .src = + .src = \\pub fn print() void { \\ asm volatile ("syscall" \\ : @@ -1082,10 +1082,14 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , - &.{":2:25: error: 'print' is private"}, + &.{ + ":2:25: error: 'print' is not marked 'pub'", + "print.zig:2:1: note: declared here", + }, ); try case.files.append(.{ - .src = + .src = + \\// dummy comment to make print be on line 2 \\fn print() void { \\ asm volatile ("syscall" \\ : @@ -1102,22 +1106,22 @@ pub fn addCases(ctx: *TestContext) !void { }); } - ctx.compileError("function redefinition", linux_x64, + ctx.compileError("function redeclaration", linux_x64, \\// dummy comment \\fn entry() void {} \\fn entry() void {} , &[_][]const u8{ - ":3:4: error: redefinition of 'entry'", - ":2:1: note: previous definition here", + ":3:4: error: redeclaration of 'entry'", + ":2:1: note: previously declared here", }); - ctx.compileError("global variable redefinition", linux_x64, + ctx.compileError("global variable redeclaration", linux_x64, \\// dummy comment \\var foo = false; \\var foo = true; , &[_][]const u8{ - ":3:5: error: redefinition of 'foo'", - ":2:1: note: previous definition here", + ":3:5: error: redeclaration of 'foo'", + ":2:1: note: previously declared here", }); ctx.compileError("compileError", linux_x64,