Commit 482b995a49

Andrew Kelley <andrew@ziglang.org>
2021-04-09 04:05:05
stage2: blaze the trail for std lib integration
This branch adds "builtin" and "std" to the import table when using the self-hosted backend. "builtin" gains one additional item: ``` pub const zig_is_stage2 = true; // false when using stage1 backend ``` This allows the std lib to do conditional compilation based on detecting which backend is being used. This will be removed from builtin as soon as self-hosted catches up to feature parity with stage1. Keep a sharp eye out - people are going to be tempted to abuse this. The general rule of thumb is do not use `builtin.zig_is_stage2`. However this commit breaks the rule so that we can gain limited start.zig support as we incrementally improve the self-hosted compiler. This commit also implements `fullyQualifiedNameHash` and related functionality, which effectively puts all Decls in their proper namespaces. `fullyQualifiedName` is not yet implemented. Stop printing "todo" log messages for test decls unless we are in test mode. Add "previous definition here" error notes for Decl name collisions. This commit does not bring us yet to a newly passing test case. Here's what I'm working towards: ```zig const std = @import("std"); export fn main() c_int { const a = std.fs.base64_alphabet[0]; return a - 'A'; } ``` Current output: ``` $ ./zig-cache/bin/zig build-exe test.zig test.zig:3:1: error: TODO implement more analyze elemptr zig-cache/lib/zig/std/start.zig:38:46: error: TODO implement structInitExpr ty ``` So the next steps are clear: * Sema: improve elemptr * AstGen: implement structInitExpr
1 parent b9e508c
lib/std/start.zig
@@ -7,7 +7,7 @@
 
 const root = @import("root");
 const std = @import("std.zig");
-const builtin = std.builtin;
+const builtin = @import("builtin");
 const assert = std.debug.assert;
 const uefi = std.os.uefi;
 const tlcsprng = @import("crypto/tlcsprng.zig");
@@ -17,39 +17,101 @@ var argc_argv_ptr: [*]usize = undefined;
 const start_sym_name = if (builtin.arch.isMIPS()) "__start" else "_start";
 
 comptime {
-    if (builtin.output_mode == .Lib and builtin.link_mode == .Dynamic) {
-        if (builtin.os.tag == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
-            @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
+    // The self-hosted compiler is not fully capable of handling all of this start.zig file.
+    // Until then, we have simplified logic here for self-hosted. TODO remove this once
+    // self-hosted is capable enough to handle all of the real start.zig logic.
+    if (builtin.zig_is_stage2) {
+        if (builtin.output_mode == .Exe) {
+            if (builtin.link_libc or builtin.object_format == .c) {
+                if (!@hasDecl(root, "main")) {
+                    @export(main2, "main");
+                }
+            } else {
+                if (!@hasDecl(root, "_start")) {
+                    @export(_start2, "_start");
+                }
+            }
         }
-    } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
-        if (builtin.link_libc and @hasDecl(root, "main")) {
-            if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) {
-                @export(main, .{ .name = "main", .linkage = .Weak });
+    } else {
+        if (builtin.output_mode == .Lib and builtin.link_mode == .Dynamic) {
+            if (builtin.os.tag == .windows and !@hasDecl(root, "_DllMainCRTStartup")) {
+                @export(_DllMainCRTStartup, .{ .name = "_DllMainCRTStartup" });
             }
-        } else if (builtin.os.tag == .windows) {
-            if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
-                !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
-            {
-                @export(WinStartup, .{ .name = "wWinMainCRTStartup" });
-            } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
-                !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
-            {
-                @compileError("WinMain not supported; declare wWinMain or main instead");
-            } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
-                !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
-            {
-                @export(wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
+        } else if (builtin.output_mode == .Exe or @hasDecl(root, "main")) {
+            if (builtin.link_libc and @hasDecl(root, "main")) {
+                if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) {
+                    @export(main, .{ .name = "main", .linkage = .Weak });
+                }
+            } else if (builtin.os.tag == .windows) {
+                if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
+                    !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
+                {
+                    @export(WinStartup, .{ .name = "wWinMainCRTStartup" });
+                } else if (@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and
+                    !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup"))
+                {
+                    @compileError("WinMain not supported; declare wWinMain or main instead");
+                } else if (@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup") and
+                    !@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup"))
+                {
+                    @export(wWinMainCRTStartup, .{ .name = "wWinMainCRTStartup" });
+                }
+            } else if (builtin.os.tag == .uefi) {
+                if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" });
+            } else if (builtin.arch.isWasm() and builtin.os.tag == .freestanding) {
+                if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name });
+            } else if (builtin.os.tag != .other and builtin.os.tag != .freestanding) {
+                if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
             }
-        } else if (builtin.os.tag == .uefi) {
-            if (!@hasDecl(root, "EfiMain")) @export(EfiMain, .{ .name = "EfiMain" });
-        } else if (builtin.arch.isWasm() and builtin.os.tag == .freestanding) {
-            if (!@hasDecl(root, start_sym_name)) @export(wasm_freestanding_start, .{ .name = start_sym_name });
-        } else if (builtin.os.tag != .other and builtin.os.tag != .freestanding) {
-            if (!@hasDecl(root, start_sym_name)) @export(_start, .{ .name = start_sym_name });
         }
     }
 }
 
+// Simplified start code for stage2 until it supports more language features ///
+
+fn main2() callconv(.C) c_int {
+    root.main();
+    return 0;
+}
+
+fn _start2() callconv(.Naked) noreturn {
+    root.main();
+    exit2(0);
+}
+
+fn exit2(code: u8) noreturn {
+    switch (builtin.arch) {
+        .x86_64 => {
+            asm volatile ("syscall"
+                :
+                : [number] "{rax}" (231),
+                  [arg1] "{rdi}" (code)
+                : "rcx", "r11", "memory"
+            );
+        },
+        .arm => {
+            asm volatile ("svc #0"
+                :
+                : [number] "{r7}" (1),
+                  [arg1] "{r0}" (code)
+                : "memory"
+            );
+        },
+        .aarch64 => {
+            asm volatile ("svc #0"
+                :
+                : [number] "{x8}" (93),
+                  [arg1] "{x0}" (code)
+                : "memory", "cc"
+            );
+        },
+        else => @compileError("TODO"),
+    }
+    unreachable;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 fn _DllMainCRTStartup(
     hinstDLL: std.os.windows.HINSTANCE,
     fdwReason: std.os.windows.DWORD,
lib/std/start2.zig
@@ -1,58 +0,0 @@
-const root = @import("root");
-const builtin = @import("builtin");
-
-comptime {
-    if (builtin.output_mode == 0) { // OutputMode.Exe
-        if (builtin.link_libc or builtin.object_format == 5) { // ObjectFormat.c
-            if (!@hasDecl(root, "main")) {
-                @export(otherMain, "main");
-            }
-        } else {
-            if (!@hasDecl(root, "_start")) {
-                @export(otherStart, "_start");
-            }
-        }
-    }
-}
-
-// FIXME: Cannot call this function `main`, because `fully qualified names`
-//        have not been implemented yet.
-fn otherMain() callconv(.C) c_int {
-    root.zigMain();
-    return 0;
-}
-
-// FIXME: Cannot call this function `_start`, because `fully qualified names`
-//        have not been implemented yet.
-fn otherStart() callconv(.Naked) noreturn {
-    root.zigMain();
-    otherExit();
-}
-
-// FIXME: Cannot call this function `exit`, because `fully qualified names`
-//        have not been implemented yet.
-fn otherExit() noreturn {
-    if (builtin.arch == 31) { // x86_64
-        asm volatile ("syscall"
-            :
-            : [number] "{rax}" (231),
-              [arg1] "{rdi}" (0)
-            : "rcx", "r11", "memory"
-        );
-    } else if (builtin.arch == 0) { // arm
-        asm volatile ("svc #0"
-            :
-            : [number] "{r7}" (1),
-              [arg1] "{r0}" (0)
-            : "memory"
-        );
-    } else if (builtin.arch == 2) { // aarch64
-        asm volatile ("svc #0"
-            :
-            : [number] "{x8}" (93),
-              [arg1] "{x0}" (0)
-            : "memory", "cc"
-        );
-    } else @compileError("not yet supported!");
-    unreachable;
-}
lib/std/std.zig
@@ -92,7 +92,7 @@ pub const zig = @import("zig.zig");
 pub const start = @import("start.zig");
 
 // This forces the start.zig file to be imported, and the comptime logic inside that
-// file decides whether to export any appropriate start symbols.
+// file decides whether to export any appropriate start symbols, and call main.
 comptime {
     _ = start;
 }
lib/std/zig.zig
@@ -18,16 +18,19 @@ pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;
 
 pub const SrcHash = [16]u8;
 
-/// If the source is small enough, it is used directly as the hash.
-/// If it is long, blake3 hash is computed.
 pub fn hashSrc(src: []const u8) SrcHash {
     var out: SrcHash = undefined;
-    if (src.len <= @typeInfo(SrcHash).Array.len) {
-        std.mem.copy(u8, &out, src);
-        std.mem.set(u8, out[src.len..], 0);
-    } else {
-        std.crypto.hash.Blake3.hash(src, &out, .{});
-    }
+    std.crypto.hash.Blake3.hash(src, &out, .{});
+    return out;
+}
+
+pub fn hashName(parent_hash: SrcHash, sep: []const u8, name: []const u8) SrcHash {
+    var out: SrcHash = undefined;
+    var hasher = std.crypto.hash.Blake3.init(.{});
+    hasher.update(&parent_hash);
+    hasher.update(sep);
+    hasher.update(name);
+    hasher.final(&out);
     return out;
 }
 
src/stage1/codegen.cpp
@@ -9137,6 +9137,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
     buf_appendf(contents, "pub const position_independent_executable = %s;\n", bool_to_str(g->have_pie));
     buf_appendf(contents, "pub const strip_debug_info = %s;\n", bool_to_str(g->strip_debug_symbols));
     buf_appendf(contents, "pub const code_model = CodeModel.default;\n");
+    buf_appendf(contents, "pub const zig_is_stage2 = false;\n");
 
     {
         TargetSubsystem detected_subsystem = detect_subsystem(g);
src/Compilation.zig
@@ -906,38 +906,61 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
                     artifact_sub_dir,
             };
 
-            // TODO when we implement serialization and deserialization of incremental compilation metadata,
-            // this is where we would load it. We have open a handle to the directory where
-            // the output either already is, or will be.
+            // If we rely on stage1, we must not redundantly add these packages.
+            const use_stage1 = build_options.is_stage1 and use_llvm;
+            if (!use_stage1) {
+                const builtin_pkg = try Package.createWithDir(
+                    gpa,
+                    zig_cache_artifact_directory,
+                    null,
+                    "builtin.zig",
+                );
+                errdefer builtin_pkg.destroy(gpa);
+
+                const std_pkg = try Package.createWithDir(
+                    gpa,
+                    options.zig_lib_directory,
+                    "std",
+                    "std.zig",
+                );
+                errdefer std_pkg.destroy(gpa);
+
+                try root_pkg.addAndAdopt(gpa, "builtin", builtin_pkg);
+                try root_pkg.add(gpa, "root", root_pkg);
+                try root_pkg.addAndAdopt(gpa, "std", std_pkg);
+
+                try std_pkg.add(gpa, "builtin", builtin_pkg);
+                try std_pkg.add(gpa, "root", root_pkg);
+            }
+
+            // TODO when we implement serialization and deserialization of incremental
+            // compilation metadata, this is where we would load it. We have open a handle
+            // to the directory where the output either already is, or will be.
             // 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 = rs: {
-                if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) {
-                    const root_scope = try gpa.create(Module.Scope.File);
-                    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,
-                        },
-                    };
-                    break :rs root_scope;
-                } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) {
-                    return error.ZirFilesUnsupported;
-                } else {
-                    unreachable;
-                }
+            // TODO remove CLI support for .zir files and then we can remove this error
+            // handling and assertion.
+            if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) return error.ZirFilesUnsupported;
+            assert(mem.endsWith(u8, root_pkg.root_src_path, ".zig"));
+
+            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);
@@ -1339,7 +1362,8 @@ pub fn update(self: *Compilation) !void {
         self.c_object_work_queue.writeItemAssumeCapacity(entry.key);
     }
 
-    const use_stage1 = build_options.omit_stage2 or build_options.is_stage1 and self.bin_file.options.use_llvm;
+    const use_stage1 = build_options.omit_stage2 or
+        (build_options.is_stage1 and self.bin_file.options.use_llvm);
     if (!use_stage1) {
         if (self.bin_file.options.module) |module| {
             module.compile_log_text.shrinkAndFree(module.gpa, 0);
@@ -2840,6 +2864,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8
 
     const target = comp.getTarget();
     const generic_arch_name = target.cpu.arch.genericName();
+    const use_stage1 = build_options.omit_stage2 or
+        (build_options.is_stage1 and comp.bin_file.options.use_llvm);
 
     @setEvalBranchQuota(4000);
     try buffer.writer().print(
@@ -2852,6 +2878,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8
         \\/// Zig version. When writing code that supports multiple versions of Zig, prefer
         \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.
         \\pub const zig_version = try @import("std").SemanticVersion.parse("{s}");
+        \\pub const zig_is_stage2 = {};
         \\
         \\pub const output_mode = OutputMode.{};
         \\pub const link_mode = LinkMode.{};
@@ -2865,6 +2892,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8
         \\
     , .{
         build_options.version,
+        !use_stage1,
         std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)),
         std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)),
         comp.bin_file.options.is_test,
@@ -3074,6 +3102,7 @@ fn buildOutputFromZig(
             .handle = special_dir,
         },
         .root_src_path = src_basename,
+        .namespace_hash = Package.root_namespace_hash,
     };
     const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len];
     const target = comp.getTarget();
src/main.zig
@@ -599,15 +599,15 @@ fn buildOutputType(
     var test_exec_args = std.ArrayList(?[]const u8).init(gpa);
     defer test_exec_args.deinit();
 
-    const pkg_tree_root = try gpa.create(Package);
     // This package only exists to clean up the code parsing --pkg-begin and
     // --pkg-end flags. Use dummy values that are safe for the destroy call.
-    pkg_tree_root.* = .{
+    var pkg_tree_root: Package = .{
         .root_src_directory = .{ .path = null, .handle = fs.cwd() },
         .root_src_path = &[0]u8{},
+        .namespace_hash = Package.root_namespace_hash,
     };
-    defer pkg_tree_root.destroy(gpa);
-    var cur_pkg: *Package = pkg_tree_root;
+    defer freePkgTree(gpa, &pkg_tree_root, false);
+    var cur_pkg: *Package = &pkg_tree_root;
 
     switch (arg_mode) {
         .build, .translate_c, .zig_test, .run => {
@@ -658,8 +658,7 @@ fn buildOutputType(
                         ) catch |err| {
                             fatal("Failed to add package at path {s}: {s}", .{ pkg_path, @errorName(err) });
                         };
-                        new_cur_pkg.parent = cur_pkg;
-                        try cur_pkg.add(gpa, pkg_name, new_cur_pkg);
+                        try cur_pkg.addAndAdopt(gpa, pkg_name, new_cur_pkg);
                         cur_pkg = new_cur_pkg;
                     } else if (mem.eql(u8, arg, "--pkg-end")) {
                         cur_pkg = cur_pkg.parent orelse
@@ -1747,6 +1746,7 @@ fn buildOutputType(
     if (root_pkg) |pkg| {
         pkg.table = pkg_tree_root.table;
         pkg_tree_root.table = .{};
+        pkg.namespace_hash = pkg_tree_root.namespace_hash;
     }
 
     const self_exe_path = try fs.selfExePathAlloc(arena);
@@ -2151,6 +2151,18 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !voi
     }
 }
 
+fn freePkgTree(gpa: *Allocator, pkg: *Package, free_parent: bool) void {
+    {
+        var it = pkg.table.iterator();
+        while (it.next()) |kv| {
+            freePkgTree(gpa, kv.value, true);
+        }
+    }
+    if (free_parent) {
+        pkg.destroy(gpa);
+    }
+}
+
 fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !void {
     if (!build_options.have_llvm)
         fatal("cannot translate-c: compiler built without LLVM extensions", .{});
@@ -2505,6 +2517,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
                 .handle = try zig_lib_directory.handle.openDir(std_special, .{}),
             },
             .root_src_path = "build_runner.zig",
+            .namespace_hash = Package.root_namespace_hash,
         };
         defer root_pkg.root_src_directory.handle.close();
 
@@ -2550,8 +2563,9 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
         var build_pkg: Package = .{
             .root_src_directory = build_directory,
             .root_src_path = build_zig_basename,
+            .namespace_hash = undefined,
         };
-        try root_pkg.table.put(arena, "@build", &build_pkg);
+        try root_pkg.addAndAdopt(arena, "@build", &build_pkg);
 
         var global_cache_directory: Compilation.Directory = l: {
             const p = override_global_cache_dir orelse try introspect.resolveGlobalCacheDir(arena);
src/Module.zig
@@ -678,6 +678,7 @@ pub const Scope = struct {
         base: Scope = Scope{ .tag = base_tag },
 
         file_scope: *Scope.File,
+        parent_name_hash: NameHash,
 
         /// Direct children of the file.
         decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
@@ -696,8 +697,7 @@ pub const Scope = struct {
         }
 
         pub fn fullyQualifiedNameHash(cont: *Container, name: []const u8) NameHash {
-            // TODO container scope qualified names.
-            return std.zig.hashSrc(name);
+            return std.zig.hashName(cont.parent_name_hash, ".", name);
         }
 
         pub fn renderFullyQualifiedName(cont: Container, name: []const u8, writer: anytype) !void {
@@ -2513,6 +2513,7 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool {
 
                 const block_expr = node_datas[decl_node].lhs;
                 _ = try AstGen.comptimeExpr(&gen_scope, &gen_scope.base, .none, block_expr);
+                _ = try gen_scope.addBreak(.break_inline, 0, .void_value);
 
                 const code = try gen_scope.finish();
                 if (std.builtin.mode == .Debug and mod.comp.verbose_ir) {
@@ -3468,7 +3469,9 @@ pub fn analyzeContainer(mod: *Module, container_scope: *Scope.Container) !void {
         ),
 
         .test_decl => {
-            log.err("TODO: analyze test decl", .{});
+            if (mod.comp.bin_file.options.is_test) {
+                log.err("TODO: analyze test decl", .{});
+            }
         },
         .@"usingnamespace" => {
             log.err("TODO: analyze usingnamespace decl", .{});
@@ -3532,6 +3535,7 @@ fn semaContainerFn(
                 .lazy = .{ .token_abs = name_tok },
             }, "redefinition of '{s}'", .{decl.name});
             errdefer msg.destroy(mod.gpa);
+            try mod.errNoteNonLazy(decl.srcLoc(), msg, "previous definition here", .{});
             try mod.failed_decls.putNoClobber(mod.gpa, decl, msg);
         } else {
             if (!srcHashEql(decl.contents_hash, contents_hash)) {
@@ -3589,12 +3593,13 @@ fn semaContainerVar(
         decl.src_index = decl_i;
         if (deleted_decls.swapRemove(decl) == null) {
             decl.analysis = .sema_failure;
-            const err_msg = try ErrorMsg.create(mod.gpa, .{
+            const msg = try ErrorMsg.create(mod.gpa, .{
                 .container = .{ .file_scope = container_scope.file_scope },
                 .lazy = .{ .token_abs = name_token },
             }, "redefinition of '{s}'", .{decl.name});
-            errdefer err_msg.destroy(mod.gpa);
-            try mod.failed_decls.putNoClobber(mod.gpa, decl, err_msg);
+            errdefer msg.destroy(mod.gpa);
+            try mod.errNoteNonLazy(decl.srcLoc(), msg, "previous definition 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;
src/Package.zig
@@ -4,18 +4,29 @@ const std = @import("std");
 const fs = std.fs;
 const mem = std.mem;
 const Allocator = mem.Allocator;
+const assert = std.debug.assert;
 
 const Compilation = @import("Compilation.zig");
+const Module = @import("Module.zig");
 
 pub const Table = std.StringHashMapUnmanaged(*Package);
 
+pub const root_namespace_hash: Module.Scope.NameHash = .{
+    0, 0, 6, 6, 6, 0, 0, 0,
+    6, 9, 0, 0, 0, 4, 2, 0,
+};
+
 root_src_directory: Compilation.Directory,
 /// Relative to `root_src_directory`. May contain path separators.
 root_src_path: []const u8,
 table: Table = .{},
 parent: ?*Package = null,
+namespace_hash: Module.Scope.NameHash,
+/// Whether to free `root_src_directory` on `destroy`.
+root_src_directory_owned: bool = false,
 
 /// Allocate a Package. No references to the slices passed are kept.
+/// Don't forget to set `namespace_hash` later.
 pub fn create(
     gpa: *Allocator,
     /// Null indicates the current working directory
@@ -38,27 +49,69 @@ pub fn create(
             .handle = if (owned_dir_path) |p| try fs.cwd().openDir(p, .{}) else fs.cwd(),
         },
         .root_src_path = owned_src_path,
+        .root_src_directory_owned = true,
+        .namespace_hash = undefined,
     };
 
     return ptr;
 }
 
-/// Free all memory associated with this package and recursively call destroy
-/// on all packages in its table
+pub fn createWithDir(
+    gpa: *Allocator,
+    directory: Compilation.Directory,
+    /// Relative to `directory`. If null, means `directory` is the root src dir
+    /// and is owned externally.
+    root_src_dir_path: ?[]const u8,
+    /// Relative to root_src_dir_path
+    root_src_path: []const u8,
+) !*Package {
+    const ptr = try gpa.create(Package);
+    errdefer gpa.destroy(ptr);
+
+    const owned_src_path = try gpa.dupe(u8, root_src_path);
+    errdefer gpa.free(owned_src_path);
+
+    if (root_src_dir_path) |p| {
+        const owned_dir_path = try directory.join(gpa, &[1][]const u8{p});
+        errdefer gpa.free(owned_dir_path);
+
+        ptr.* = .{
+            .root_src_directory = .{
+                .path = owned_dir_path,
+                .handle = try directory.handle.openDir(p, .{}),
+            },
+            .root_src_directory_owned = true,
+            .root_src_path = owned_src_path,
+            .namespace_hash = undefined,
+        };
+    } else {
+        ptr.* = .{
+            .root_src_directory = directory,
+            .root_src_directory_owned = false,
+            .root_src_path = owned_src_path,
+            .namespace_hash = undefined,
+        };
+    }
+    return ptr;
+}
+
+/// Free all memory associated with this package. It does not destroy any packages
+/// inside its table; the caller is responsible for calling destroy() on them.
 pub fn destroy(pkg: *Package, gpa: *Allocator) void {
     gpa.free(pkg.root_src_path);
 
-    // If root_src_directory.path is null then the handle is the cwd()
-    // which shouldn't be closed.
-    if (pkg.root_src_directory.path) |p| {
-        gpa.free(p);
-        pkg.root_src_directory.handle.close();
+    if (pkg.root_src_directory_owned) {
+        // If root_src_directory.path is null then the handle is the cwd()
+        // which shouldn't be closed.
+        if (pkg.root_src_directory.path) |p| {
+            gpa.free(p);
+            pkg.root_src_directory.handle.close();
+        }
     }
 
     {
         var it = pkg.table.iterator();
         while (it.next()) |kv| {
-            kv.value.destroy(gpa);
             gpa.free(kv.key);
         }
     }
@@ -72,3 +125,10 @@ pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package)
     const name_dupe = try mem.dupe(gpa, u8, name);
     pkg.table.putAssumeCapacityNoClobber(name_dupe, package);
 }
+
+pub fn addAndAdopt(parent: *Package, gpa: *Allocator, name: []const u8, child: *Package) !void {
+    assert(child.parent == null); // make up your mind, who is the parent??
+    child.parent = parent;
+    child.namespace_hash = std.zig.hashName(parent.namespace_hash, ":", name);
+    return parent.add(gpa, name, child);
+}
src/Sema.zig
@@ -598,6 +598,10 @@ fn zirStructDecl(
     const struct_obj = try new_decl_arena.allocator.create(Module.Struct);
     const struct_ty = try Type.Tag.@"struct".create(&new_decl_arena.allocator, struct_obj);
     const struct_val = try Value.Tag.ty.create(&new_decl_arena.allocator, struct_ty);
+    const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{
+        .ty = Type.initTag(.type),
+        .val = struct_val,
+    });
     struct_obj.* = .{
         .owner_decl = sema.owner_decl,
         .fields = fields_map,
@@ -605,12 +609,9 @@ fn zirStructDecl(
         .container = .{
             .ty = struct_ty,
             .file_scope = block.getFileScope(),
+            .parent_name_hash = new_decl.fullyQualifiedNameHash(),
         },
     };
-    const new_decl = try sema.mod.createAnonymousDecl(&block.base, &new_decl_arena, .{
-        .ty = Type.initTag(.type),
-        .val = struct_val,
-    });
     return sema.analyzeDeclVal(block, src, new_decl);
 }
 
@@ -5298,9 +5299,9 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin
         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)) |some| {
+    if (sema.mod.import_table.get(resolved_path)) |cached_import| {
         sema.gpa.free(resolved_path);
-        return some;
+        return cached_import;
     }
 
     if (found_pkg == null) {
@@ -5318,6 +5319,11 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin
     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 = {} },
@@ -5328,6 +5334,7 @@ fn analyzeImport(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, target_strin
             .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) {