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,