| author | |
| committer | |
| log | 3767b08039b86c798dc7f43a1659ad10b65b248f |
| tree | 1cab306aab6df4977f2b9ce6b789fe9013fe0476 |
| parent | 8fa47bb904c888dadf20af5eb72b9643eed2bfea |
| parent | fcf8d5ada28c02722575cc78d41171692a227061 |
| signature |
incremental: handle `@embedFile`19 files changed, 467 insertions(+), 208 deletions(-)
lib/std/fs/path.zig+30-1| ... | ... | @@ -18,7 +18,6 @@ const debug = std.debug; |
| 18 | 18 | const assert = debug.assert; |
| 19 | 19 | const testing = std.testing; |
| 20 | 20 | const mem = std.mem; |
| 21 | const fmt = std.fmt; | |
| 22 | 21 | const ascii = std.ascii; |
| 23 | 22 | const Allocator = mem.Allocator; |
| 24 | 23 | const math = std.math; |
| ... | ... | @@ -147,6 +146,36 @@ pub fn joinZ(allocator: Allocator, paths: []const []const u8) ![:0]u8 { |
| 147 | 146 | return out[0 .. out.len - 1 :0]; |
| 148 | 147 | } |
| 149 | 148 | |
| 149 | pub fn fmtJoin(paths: []const []const u8) std.fmt.Formatter(formatJoin) { | |
| 150 | return .{ .data = paths }; | |
| 151 | } | |
| 152 | ||
| 153 | fn formatJoin(paths: []const []const u8, comptime fmt: []const u8, options: std.fmt.FormatOptions, w: anytype) !void { | |
| 154 | _ = fmt; | |
| 155 | _ = options; | |
| 156 | ||
| 157 | const first_path_idx = for (paths, 0..) |p, idx| { | |
| 158 | if (p.len != 0) break idx; | |
| 159 | } else return; | |
| 160 | ||
| 161 | try w.writeAll(paths[first_path_idx]); // first component | |
| 162 | var prev_path = paths[first_path_idx]; | |
| 163 | for (paths[first_path_idx + 1 ..]) |this_path| { | |
| 164 | if (this_path.len == 0) continue; // skip empty components | |
| 165 | const prev_sep = isSep(prev_path[prev_path.len - 1]); | |
| 166 | const this_sep = isSep(this_path[0]); | |
| 167 | if (!prev_sep and !this_sep) { | |
| 168 | try w.writeByte(sep); | |
| 169 | } | |
| 170 | if (prev_sep and this_sep) { | |
| 171 | try w.writeAll(this_path[1..]); // skip redundant separator | |
| 172 | } else { | |
| 173 | try w.writeAll(this_path); | |
| 174 | } | |
| 175 | prev_path = this_path; | |
| 176 | } | |
| 177 | } | |
| 178 | ||
| 150 | 179 | fn testJoinMaybeZUefi(paths: []const []const u8, expected: []const u8, zero: bool) !void { |
| 151 | 180 | const uefiIsSep = struct { |
| 152 | 181 | fn isSep(byte: u8) bool { |
src/Compilation.zig+23-60| ... | ... | @@ -154,10 +154,6 @@ win32_resource_work_queue: if (dev.env.supports(.win32_resource)) std.fifo.Linea |
| 154 | 154 | /// since the last compilation, as well as scan for `@import` and queue up |
| 155 | 155 | /// additional jobs corresponding to those new files. |
| 156 | 156 | astgen_work_queue: std.fifo.LinearFifo(Zcu.File.Index, .Dynamic), |
| 157 | /// These jobs are to inspect the file system stat() and if the embedded file has changed | |
| 158 | /// on disk, mark the corresponding Decl outdated and queue up an `analyze_decl` | |
| 159 | /// task for it. | |
| 160 | embed_file_work_queue: std.fifo.LinearFifo(*Zcu.EmbedFile, .Dynamic), | |
| 161 | 157 | |
| 162 | 158 | /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator. |
| 163 | 159 | /// This data is accessed by multiple threads and is protected by `mutex`. |
| ... | ... | @@ -1465,7 +1461,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil |
| 1465 | 1461 | .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa), |
| 1466 | 1462 | .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa) else .{}, |
| 1467 | 1463 | .astgen_work_queue = std.fifo.LinearFifo(Zcu.File.Index, .Dynamic).init(gpa), |
| 1468 | .embed_file_work_queue = std.fifo.LinearFifo(*Zcu.EmbedFile, .Dynamic).init(gpa), | |
| 1469 | 1464 | .c_source_files = options.c_source_files, |
| 1470 | 1465 | .rc_source_files = options.rc_source_files, |
| 1471 | 1466 | .cache_parent = cache, |
| ... | ... | @@ -1920,7 +1915,6 @@ pub fn destroy(comp: *Compilation) void { |
| 1920 | 1915 | comp.c_object_work_queue.deinit(); |
| 1921 | 1916 | comp.win32_resource_work_queue.deinit(); |
| 1922 | 1917 | comp.astgen_work_queue.deinit(); |
| 1923 | comp.embed_file_work_queue.deinit(); | |
| 1924 | 1918 | |
| 1925 | 1919 | comp.windows_libs.deinit(gpa); |
| 1926 | 1920 | |
| ... | ... | @@ -2235,11 +2229,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { |
| 2235 | 2229 | } |
| 2236 | 2230 | } |
| 2237 | 2231 | |
| 2238 | // Put a work item in for checking if any files used with `@embedFile` changed. | |
| 2239 | try comp.embed_file_work_queue.ensureUnusedCapacity(zcu.embed_table.count()); | |
| 2240 | for (zcu.embed_table.values()) |embed_file| { | |
| 2241 | comp.embed_file_work_queue.writeItemAssumeCapacity(embed_file); | |
| 2242 | } | |
| 2243 | 2232 | if (comp.file_system_inputs) |fsi| { |
| 2244 | 2233 | const ip = &zcu.intern_pool; |
| 2245 | 2234 | for (zcu.embed_table.values()) |embed_file| { |
| ... | ... | @@ -3223,9 +3212,6 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { |
| 3223 | 3212 | try addZirErrorMessages(&bundle, file); |
| 3224 | 3213 | } |
| 3225 | 3214 | } |
| 3226 | for (zcu.failed_embed_files.values()) |error_msg| { | |
| 3227 | try addModuleErrorMsg(zcu, &bundle, error_msg.*); | |
| 3228 | } | |
| 3229 | 3215 | var sorted_failed_analysis: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, *Zcu.ErrorMsg).DataList.Slice = s: { |
| 3230 | 3216 | const SortOrder = struct { |
| 3231 | 3217 | zcu: *Zcu, |
| ... | ... | @@ -3804,9 +3790,10 @@ fn performAllTheWorkInner( |
| 3804 | 3790 | } |
| 3805 | 3791 | } |
| 3806 | 3792 | |
| 3807 | while (comp.embed_file_work_queue.readItem()) |embed_file| { | |
| 3808 | comp.thread_pool.spawnWg(&astgen_wait_group, workerCheckEmbedFile, .{ | |
| 3809 | comp, embed_file, | |
| 3793 | for (0.., zcu.embed_table.values()) |ef_index_usize, ef| { | |
| 3794 | const ef_index: Zcu.EmbedFile.Index = @enumFromInt(ef_index_usize); | |
| 3795 | comp.thread_pool.spawnWgId(&astgen_wait_group, workerCheckEmbedFile, .{ | |
| 3796 | comp, ef_index, ef, | |
| 3810 | 3797 | }); |
| 3811 | 3798 | } |
| 3812 | 3799 | } |
| ... | ... | @@ -4369,33 +4356,33 @@ fn workerUpdateBuiltinZigFile( |
| 4369 | 4356 | }; |
| 4370 | 4357 | } |
| 4371 | 4358 | |
| 4372 | fn workerCheckEmbedFile(comp: *Compilation, embed_file: *Zcu.EmbedFile) void { | |
| 4373 | comp.detectEmbedFileUpdate(embed_file) catch |err| { | |
| 4374 | comp.reportRetryableEmbedFileError(embed_file, err) catch |oom| switch (oom) { | |
| 4375 | // Swallowing this error is OK because it's implied to be OOM when | |
| 4376 | // there is a missing `failed_embed_files` error message. | |
| 4377 | error.OutOfMemory => {}, | |
| 4378 | }; | |
| 4379 | return; | |
| 4359 | fn workerCheckEmbedFile(tid: usize, comp: *Compilation, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) void { | |
| 4360 | comp.detectEmbedFileUpdate(@enumFromInt(tid), ef_index, ef) catch |err| switch (err) { | |
| 4361 | error.OutOfMemory => { | |
| 4362 | comp.mutex.lock(); | |
| 4363 | defer comp.mutex.unlock(); | |
| 4364 | comp.setAllocFailure(); | |
| 4365 | }, | |
| 4380 | 4366 | }; |
| 4381 | 4367 | } |
| 4382 | 4368 | |
| 4383 | fn detectEmbedFileUpdate(comp: *Compilation, embed_file: *Zcu.EmbedFile) !void { | |
| 4369 | fn detectEmbedFileUpdate(comp: *Compilation, tid: Zcu.PerThread.Id, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) !void { | |
| 4384 | 4370 | const zcu = comp.zcu.?; |
| 4385 | const ip = &zcu.intern_pool; | |
| 4386 | var file = try embed_file.owner.root.openFile(embed_file.sub_file_path.toSlice(ip), .{}); | |
| 4387 | defer file.close(); | |
| 4371 | const pt: Zcu.PerThread = .activate(zcu, tid); | |
| 4372 | defer pt.deactivate(); | |
| 4373 | ||
| 4374 | const old_val = ef.val; | |
| 4375 | const old_err = ef.err; | |
| 4388 | 4376 | |
| 4389 | const stat = try file.stat(); | |
| 4377 | try pt.updateEmbedFile(ef, null); | |
| 4390 | 4378 | |
| 4391 | const unchanged_metadata = | |
| 4392 | stat.size == embed_file.stat.size and | |
| 4393 | stat.mtime == embed_file.stat.mtime and | |
| 4394 | stat.inode == embed_file.stat.inode; | |
| 4379 | if (ef.val != .none and ef.val == old_val) return; // success, value unchanged | |
| 4380 | if (ef.val == .none and old_val == .none and ef.err == old_err) return; // failure, error unchanged | |
| 4395 | 4381 | |
| 4396 | if (unchanged_metadata) return; | |
| 4382 | comp.mutex.lock(); | |
| 4383 | defer comp.mutex.unlock(); | |
| 4397 | 4384 | |
| 4398 | @panic("TODO: handle embed file incremental update"); | |
| 4385 | try zcu.markDependeeOutdated(.not_marked_po, .{ .embed_file = ef_index }); | |
| 4399 | 4386 | } |
| 4400 | 4387 | |
| 4401 | 4388 | pub fn obtainCObjectCacheManifest( |
| ... | ... | @@ -4797,30 +4784,6 @@ fn reportRetryableWin32ResourceError( |
| 4797 | 4784 | } |
| 4798 | 4785 | } |
| 4799 | 4786 | |
| 4800 | fn reportRetryableEmbedFileError( | |
| 4801 | comp: *Compilation, | |
| 4802 | embed_file: *Zcu.EmbedFile, | |
| 4803 | err: anyerror, | |
| 4804 | ) error{OutOfMemory}!void { | |
| 4805 | const zcu = comp.zcu.?; | |
| 4806 | const gpa = zcu.gpa; | |
| 4807 | const src_loc = embed_file.src_loc; | |
| 4808 | const ip = &zcu.intern_pool; | |
| 4809 | const err_msg = try Zcu.ErrorMsg.create(gpa, src_loc, "unable to load '{}/{s}': {s}", .{ | |
| 4810 | embed_file.owner.root, | |
| 4811 | embed_file.sub_file_path.toSlice(ip), | |
| 4812 | @errorName(err), | |
| 4813 | }); | |
| 4814 | ||
| 4815 | errdefer err_msg.destroy(gpa); | |
| 4816 | ||
| 4817 | { | |
| 4818 | comp.mutex.lock(); | |
| 4819 | defer comp.mutex.unlock(); | |
| 4820 | try zcu.failed_embed_files.putNoClobber(gpa, embed_file, err_msg); | |
| 4821 | } | |
| 4822 | } | |
| 4823 | ||
| 4824 | 4787 | fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Progress.Node) !void { |
| 4825 | 4788 | if (comp.config.c_frontend == .aro) { |
| 4826 | 4789 | return comp.failCObj(c_object, "aro does not support compiling C objects yet", .{}); |
src/InternPool.zig+9| ... | ... | @@ -42,6 +42,10 @@ nav_ty_deps: std.AutoArrayHashMapUnmanaged(Nav.Index, DepEntry.Index), |
| 42 | 42 | /// * a container type requiring resolution (invalidated when the type must be recreated at a new index) |
| 43 | 43 | /// Value is index into `dep_entries` of the first dependency on this interned value. |
| 44 | 44 | interned_deps: std.AutoArrayHashMapUnmanaged(Index, DepEntry.Index), |
| 45 | /// Dependencies on an embedded file. | |
| 46 | /// Introduced by `@embedFile`; invalidated when the file changes. | |
| 47 | /// Value is index into `dep_entries` of the first dependency on this `Zcu.EmbedFile`. | |
| 48 | embed_file_deps: std.AutoArrayHashMapUnmanaged(Zcu.EmbedFile.Index, DepEntry.Index), | |
| 45 | 49 | /// Dependencies on the full set of names in a ZIR namespace. |
| 46 | 50 | /// Key refers to a `struct_decl`, `union_decl`, etc. |
| 47 | 51 | /// Value is index into `dep_entries` of the first dependency on this namespace. |
| ... | ... | @@ -90,6 +94,7 @@ pub const empty: InternPool = .{ |
| 90 | 94 | .nav_val_deps = .empty, |
| 91 | 95 | .nav_ty_deps = .empty, |
| 92 | 96 | .interned_deps = .empty, |
| 97 | .embed_file_deps = .empty, | |
| 93 | 98 | .namespace_deps = .empty, |
| 94 | 99 | .namespace_name_deps = .empty, |
| 95 | 100 | .memoized_state_main_deps = .none, |
| ... | ... | @@ -824,6 +829,7 @@ pub const Dependee = union(enum) { |
| 824 | 829 | nav_val: Nav.Index, |
| 825 | 830 | nav_ty: Nav.Index, |
| 826 | 831 | interned: Index, |
| 832 | embed_file: Zcu.EmbedFile.Index, | |
| 827 | 833 | namespace: TrackedInst.Index, |
| 828 | 834 | namespace_name: NamespaceNameKey, |
| 829 | 835 | memoized_state: MemoizedStateStage, |
| ... | ... | @@ -875,6 +881,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI |
| 875 | 881 | .nav_val => |x| ip.nav_val_deps.get(x), |
| 876 | 882 | .nav_ty => |x| ip.nav_ty_deps.get(x), |
| 877 | 883 | .interned => |x| ip.interned_deps.get(x), |
| 884 | .embed_file => |x| ip.embed_file_deps.get(x), | |
| 878 | 885 | .namespace => |x| ip.namespace_deps.get(x), |
| 879 | 886 | .namespace_name => |x| ip.namespace_name_deps.get(x), |
| 880 | 887 | .memoized_state => |stage| switch (stage) { |
| ... | ... | @@ -945,6 +952,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend |
| 945 | 952 | .nav_val => ip.nav_val_deps, |
| 946 | 953 | .nav_ty => ip.nav_ty_deps, |
| 947 | 954 | .interned => ip.interned_deps, |
| 955 | .embed_file => ip.embed_file_deps, | |
| 948 | 956 | .namespace => ip.namespace_deps, |
| 949 | 957 | .namespace_name => ip.namespace_name_deps, |
| 950 | 958 | .memoized_state => comptime unreachable, |
| ... | ... | @@ -6682,6 +6690,7 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void { |
| 6682 | 6690 | ip.nav_val_deps.deinit(gpa); |
| 6683 | 6691 | ip.nav_ty_deps.deinit(gpa); |
| 6684 | 6692 | ip.interned_deps.deinit(gpa); |
| 6693 | ip.embed_file_deps.deinit(gpa); | |
| 6685 | 6694 | ip.namespace_deps.deinit(gpa); |
| 6686 | 6695 | ip.namespace_name_deps.deinit(gpa); |
| 6687 | 6696 |
src/Sema.zig+14-6| ... | ... | @@ -13964,6 +13964,8 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A |
| 13964 | 13964 | defer tracy.end(); |
| 13965 | 13965 | |
| 13966 | 13966 | const pt = sema.pt; |
| 13967 | const zcu = pt.zcu; | |
| 13968 | ||
| 13967 | 13969 | const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; |
| 13968 | 13970 | const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0); |
| 13969 | 13971 | const name = try sema.resolveConstString(block, operand_src, inst_data.operand, .{ .simple = .operand_embedFile }); |
| ... | ... | @@ -13972,18 +13974,24 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A |
| 13972 | 13974 | return sema.fail(block, operand_src, "file path name cannot be empty", .{}); |
| 13973 | 13975 | } |
| 13974 | 13976 | |
| 13975 | const val = pt.embedFile(block.getFileScope(pt.zcu), name, operand_src) catch |err| switch (err) { | |
| 13977 | const ef_idx = pt.embedFile(block.getFileScope(zcu), name) catch |err| switch (err) { | |
| 13976 | 13978 | error.ImportOutsideModulePath => { |
| 13977 | 13979 | return sema.fail(block, operand_src, "embed of file outside package path: '{s}'", .{name}); |
| 13978 | 13980 | }, |
| 13979 | else => { | |
| 13980 | // TODO: these errors are file system errors; make sure an update() will | |
| 13981 | // retry this and not cache the file system error, which may be transient. | |
| 13982 | return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ name, @errorName(err) }); | |
| 13981 | error.CurrentWorkingDirectoryUnlinked => { | |
| 13982 | // TODO: this should be some kind of retryable failure, in case the cwd is put back | |
| 13983 | return sema.fail(block, operand_src, "unable to resolve '{s}': working directory has been unlinked", .{name}); | |
| 13983 | 13984 | }, |
| 13985 | error.OutOfMemory => |e| return e, | |
| 13984 | 13986 | }; |
| 13987 | try sema.declareDependency(.{ .embed_file = ef_idx }); | |
| 13988 | ||
| 13989 | const result = ef_idx.get(zcu); | |
| 13990 | if (result.val == .none) { | |
| 13991 | return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ name, @errorName(result.err.?) }); | |
| 13992 | } | |
| 13985 | 13993 | |
| 13986 | return Air.internedToRef(val); | |
| 13994 | return Air.internedToRef(result.val); | |
| 13987 | 13995 | } |
| 13988 | 13996 | |
| 13989 | 13997 | fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
src/Zcu.zig+22-11| ... | ... | @@ -143,8 +143,6 @@ compile_log_sources: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct { |
| 143 | 143 | /// Using a map here for consistency with the other fields here. |
| 144 | 144 | /// The ErrorMsg memory is owned by the `File`, using Module's general purpose allocator. |
| 145 | 145 | failed_files: std.AutoArrayHashMapUnmanaged(*File, ?*ErrorMsg) = .empty, |
| 146 | /// The ErrorMsg memory is owned by the `EmbedFile`, using Module's general purpose allocator. | |
| 147 | failed_embed_files: std.AutoArrayHashMapUnmanaged(*EmbedFile, *ErrorMsg) = .empty, | |
| 148 | 146 | failed_exports: std.AutoArrayHashMapUnmanaged(Export.Index, *ErrorMsg) = .empty, |
| 149 | 147 | /// If analysis failed due to a cimport error, the corresponding Clang errors |
| 150 | 148 | /// are stored here. |
| ... | ... | @@ -898,13 +896,23 @@ pub const File = struct { |
| 898 | 896 | }; |
| 899 | 897 | |
| 900 | 898 | pub const EmbedFile = struct { |
| 901 | /// Relative to the owning module's root directory. | |
| 902 | sub_file_path: InternPool.NullTerminatedString, | |
| 903 | 899 | /// Module that this file is a part of, managed externally. |
| 904 | 900 | owner: *Package.Module, |
| 905 | stat: Cache.File.Stat, | |
| 901 | /// Relative to the owning module's root directory. | |
| 902 | sub_file_path: InternPool.NullTerminatedString, | |
| 903 | ||
| 904 | /// `.none` means the file was not loaded, so `stat` is undefined. | |
| 906 | 905 | val: InternPool.Index, |
| 907 | src_loc: LazySrcLoc, | |
| 906 | /// If this is `null` and `val` is `.none`, the file has never been loaded. | |
| 907 | err: ?(std.fs.File.OpenError || std.fs.File.StatError || std.fs.File.ReadError || error{UnexpectedEof}), | |
| 908 | stat: Cache.File.Stat, | |
| 909 | ||
| 910 | pub const Index = enum(u32) { | |
| 911 | _, | |
| 912 | pub fn get(idx: Index, zcu: *const Zcu) *EmbedFile { | |
| 913 | return zcu.embed_table.values()[@intFromEnum(idx)]; | |
| 914 | } | |
| 915 | }; | |
| 908 | 916 | }; |
| 909 | 917 | |
| 910 | 918 | /// This struct holds data necessary to construct API-facing `AllErrors.Message`. |
| ... | ... | @@ -2464,11 +2472,6 @@ pub fn deinit(zcu: *Zcu) void { |
| 2464 | 2472 | } |
| 2465 | 2473 | zcu.failed_files.deinit(gpa); |
| 2466 | 2474 | |
| 2467 | for (zcu.failed_embed_files.values()) |msg| { | |
| 2468 | msg.destroy(gpa); | |
| 2469 | } | |
| 2470 | zcu.failed_embed_files.deinit(gpa); | |
| 2471 | ||
| 2472 | 2475 | for (zcu.failed_exports.values()) |value| { |
| 2473 | 2476 | value.destroy(gpa); |
| 2474 | 2477 | } |
| ... | ... | @@ -3887,6 +3890,14 @@ fn formatDependee(data: struct { dependee: InternPool.Dependee, zcu: *Zcu }, com |
| 3887 | 3890 | .func => |f| return writer.print("ies('{}')", .{ip.getNav(f.owner_nav).fqn.fmt(ip)}), |
| 3888 | 3891 | else => unreachable, |
| 3889 | 3892 | }, |
| 3893 | .embed_file => |ef_idx| { | |
| 3894 | const ef = ef_idx.get(zcu); | |
| 3895 | return writer.print("embed_file('{s}')", .{std.fs.path.fmtJoin(&.{ | |
| 3896 | ef.owner.root.root_dir.path orelse "", | |
| 3897 | ef.owner.root.sub_path, | |
| 3898 | ef.sub_file_path.toSlice(ip), | |
| 3899 | })}); | |
| 3900 | }, | |
| 3890 | 3901 | .namespace => |ti| { |
| 3891 | 3902 | const info = ti.resolveFull(ip) orelse { |
| 3892 | 3903 | return writer.writeAll("namespace(<lost>)"); |
src/Zcu/PerThread.zig+143-90| ... | ... | @@ -2117,32 +2117,32 @@ pub fn embedFile( |
| 2117 | 2117 | pt: Zcu.PerThread, |
| 2118 | 2118 | cur_file: *Zcu.File, |
| 2119 | 2119 | import_string: []const u8, |
| 2120 | src_loc: Zcu.LazySrcLoc, | |
| 2121 | ) !InternPool.Index { | |
| 2120 | ) error{ | |
| 2121 | OutOfMemory, | |
| 2122 | ImportOutsideModulePath, | |
| 2123 | CurrentWorkingDirectoryUnlinked, | |
| 2124 | }!Zcu.EmbedFile.Index { | |
| 2122 | 2125 | const zcu = pt.zcu; |
| 2123 | 2126 | const gpa = zcu.gpa; |
| 2124 | 2127 | |
| 2125 | if (cur_file.mod.deps.get(import_string)) |pkg| { | |
| 2128 | if (cur_file.mod.deps.get(import_string)) |mod| { | |
| 2126 | 2129 | const resolved_path = try std.fs.path.resolve(gpa, &.{ |
| 2127 | pkg.root.root_dir.path orelse ".", | |
| 2128 | pkg.root.sub_path, | |
| 2129 | pkg.root_src_path, | |
| 2130 | mod.root.root_dir.path orelse ".", | |
| 2131 | mod.root.sub_path, | |
| 2132 | mod.root_src_path, | |
| 2130 | 2133 | }); |
| 2131 | var keep_resolved_path = false; | |
| 2132 | defer if (!keep_resolved_path) gpa.free(resolved_path); | |
| 2134 | errdefer gpa.free(resolved_path); | |
| 2133 | 2135 | |
| 2134 | 2136 | const gop = try zcu.embed_table.getOrPut(gpa, resolved_path); |
| 2135 | errdefer { | |
| 2136 | assert(std.mem.eql(u8, zcu.embed_table.pop().key, resolved_path)); | |
| 2137 | keep_resolved_path = false; | |
| 2138 | } | |
| 2139 | if (gop.found_existing) return gop.value_ptr.*.val; | |
| 2140 | keep_resolved_path = true; | |
| 2137 | errdefer assert(std.mem.eql(u8, zcu.embed_table.pop().key, resolved_path)); | |
| 2141 | 2138 | |
| 2142 | const sub_file_path = try gpa.dupe(u8, pkg.root_src_path); | |
| 2143 | errdefer gpa.free(sub_file_path); | |
| 2139 | if (gop.found_existing) { | |
| 2140 | gpa.free(resolved_path); // we're not using this key | |
| 2141 | return @enumFromInt(gop.index); | |
| 2142 | } | |
| 2144 | 2143 | |
| 2145 | return pt.newEmbedFile(pkg, sub_file_path, resolved_path, gop.value_ptr, src_loc); | |
| 2144 | gop.value_ptr.* = try pt.newEmbedFile(mod, mod.root_src_path, resolved_path); | |
| 2145 | return @enumFromInt(gop.index); | |
| 2146 | 2146 | } |
| 2147 | 2147 | |
| 2148 | 2148 | // The resolved path is used as the key in the table, to detect if a file |
| ... | ... | @@ -2154,17 +2154,15 @@ pub fn embedFile( |
| 2154 | 2154 | "..", |
| 2155 | 2155 | import_string, |
| 2156 | 2156 | }); |
| 2157 | ||
| 2158 | var keep_resolved_path = false; | |
| 2159 | defer if (!keep_resolved_path) gpa.free(resolved_path); | |
| 2157 | errdefer gpa.free(resolved_path); | |
| 2160 | 2158 | |
| 2161 | 2159 | const gop = try zcu.embed_table.getOrPut(gpa, resolved_path); |
| 2162 | errdefer { | |
| 2163 | assert(std.mem.eql(u8, zcu.embed_table.pop().key, resolved_path)); | |
| 2164 | keep_resolved_path = false; | |
| 2160 | errdefer assert(std.mem.eql(u8, zcu.embed_table.pop().key, resolved_path)); | |
| 2161 | ||
| 2162 | if (gop.found_existing) { | |
| 2163 | gpa.free(resolved_path); // we're not using this key | |
| 2164 | return @enumFromInt(gop.index); | |
| 2165 | 2165 | } |
| 2166 | if (gop.found_existing) return gop.value_ptr.*.val; | |
| 2167 | keep_resolved_path = true; | |
| 2168 | 2166 | |
| 2169 | 2167 | const resolved_root_path = try std.fs.path.resolve(gpa, &.{ |
| 2170 | 2168 | cur_file.mod.root.root_dir.path orelse ".", |
| ... | ... | @@ -2172,101 +2170,156 @@ pub fn embedFile( |
| 2172 | 2170 | }); |
| 2173 | 2171 | defer gpa.free(resolved_root_path); |
| 2174 | 2172 | |
| 2175 | const sub_file_path = p: { | |
| 2176 | const relative = try std.fs.path.relative(gpa, resolved_root_path, resolved_path); | |
| 2177 | errdefer gpa.free(relative); | |
| 2178 | ||
| 2179 | if (!isUpDir(relative) and !std.fs.path.isAbsolute(relative)) { | |
| 2180 | break :p relative; | |
| 2181 | } | |
| 2182 | return error.ImportOutsideModulePath; | |
| 2173 | const sub_file_path = std.fs.path.relative(gpa, resolved_root_path, resolved_path) catch |err| switch (err) { | |
| 2174 | error.Unexpected => unreachable, | |
| 2175 | else => |e| return e, | |
| 2183 | 2176 | }; |
| 2184 | 2177 | defer gpa.free(sub_file_path); |
| 2185 | 2178 | |
| 2186 | return pt.newEmbedFile(cur_file.mod, sub_file_path, resolved_path, gop.value_ptr, src_loc); | |
| 2179 | if (isUpDir(sub_file_path) or std.fs.path.isAbsolute(sub_file_path)) { | |
| 2180 | return error.ImportOutsideModulePath; | |
| 2181 | } | |
| 2182 | ||
| 2183 | gop.value_ptr.* = try pt.newEmbedFile(cur_file.mod, sub_file_path, resolved_path); | |
| 2184 | return @enumFromInt(gop.index); | |
| 2187 | 2185 | } |
| 2188 | 2186 | |
| 2189 | /// https://github.com/ziglang/zig/issues/14307 | |
| 2190 | fn newEmbedFile( | |
| 2187 | pub fn updateEmbedFile( | |
| 2191 | 2188 | pt: Zcu.PerThread, |
| 2192 | pkg: *Module, | |
| 2193 | sub_file_path: []const u8, | |
| 2194 | resolved_path: []const u8, | |
| 2195 | result: **Zcu.EmbedFile, | |
| 2196 | src_loc: Zcu.LazySrcLoc, | |
| 2197 | ) !InternPool.Index { | |
| 2189 | ef: *Zcu.EmbedFile, | |
| 2190 | /// If not `null`, the interned file data is stored here, if it was loaded. | |
| 2191 | /// `newEmbedFile` uses this to add the file to the `whole` cache manifest. | |
| 2192 | ip_str_out: ?*?InternPool.String, | |
| 2193 | ) Allocator.Error!void { | |
| 2194 | pt.updateEmbedFileInner(ef, ip_str_out) catch |err| switch (err) { | |
| 2195 | error.OutOfMemory => |e| return e, | |
| 2196 | else => |e| { | |
| 2197 | ef.val = .none; | |
| 2198 | ef.err = e; | |
| 2199 | ef.stat = undefined; | |
| 2200 | }, | |
| 2201 | }; | |
| 2202 | } | |
| 2203 | ||
| 2204 | fn updateEmbedFileInner( | |
| 2205 | pt: Zcu.PerThread, | |
| 2206 | ef: *Zcu.EmbedFile, | |
| 2207 | ip_str_out: ?*?InternPool.String, | |
| 2208 | ) !void { | |
| 2209 | const tid = pt.tid; | |
| 2198 | 2210 | const zcu = pt.zcu; |
| 2199 | 2211 | const gpa = zcu.gpa; |
| 2200 | 2212 | const ip = &zcu.intern_pool; |
| 2201 | 2213 | |
| 2202 | const new_file = try gpa.create(Zcu.EmbedFile); | |
| 2203 | errdefer gpa.destroy(new_file); | |
| 2204 | ||
| 2205 | var file = try pkg.root.openFile(sub_file_path, .{}); | |
| 2214 | var file = try ef.owner.root.openFile(ef.sub_file_path.toSlice(ip), .{}); | |
| 2206 | 2215 | defer file.close(); |
| 2207 | 2216 | |
| 2208 | const actual_stat = try file.stat(); | |
| 2209 | const stat: Cache.File.Stat = .{ | |
| 2210 | .size = actual_stat.size, | |
| 2211 | .inode = actual_stat.inode, | |
| 2212 | .mtime = actual_stat.mtime, | |
| 2213 | }; | |
| 2214 | const size = std.math.cast(usize, actual_stat.size) orelse return error.Overflow; | |
| 2215 | ||
| 2216 | const strings = ip.getLocal(pt.tid).getMutableStrings(gpa); | |
| 2217 | const bytes = try strings.addManyAsSlice(try std.math.add(usize, size, 1)); | |
| 2218 | const actual_read = try file.readAll(bytes[0][0..size]); | |
| 2219 | if (actual_read != size) return error.UnexpectedEndOfFile; | |
| 2220 | bytes[0][size] = 0; | |
| 2217 | const stat: Cache.File.Stat = .fromFs(try file.stat()); | |
| 2221 | 2218 | |
| 2222 | const comp = zcu.comp; | |
| 2223 | switch (comp.cache_use) { | |
| 2224 | .whole => |whole| if (whole.cache_manifest) |man| { | |
| 2225 | const copied_resolved_path = try gpa.dupe(u8, resolved_path); | |
| 2226 | errdefer gpa.free(copied_resolved_path); | |
| 2227 | whole.cache_manifest_mutex.lock(); | |
| 2228 | defer whole.cache_manifest_mutex.unlock(); | |
| 2229 | try man.addFilePostContents(copied_resolved_path, bytes[0][0..size], stat); | |
| 2230 | }, | |
| 2231 | .incremental => {}, | |
| 2219 | if (ef.val != .none) { | |
| 2220 | const old_stat = ef.stat; | |
| 2221 | const unchanged_metadata = | |
| 2222 | stat.size == old_stat.size and | |
| 2223 | stat.mtime == old_stat.mtime and | |
| 2224 | stat.inode == old_stat.inode; | |
| 2225 | if (unchanged_metadata) return; | |
| 2232 | 2226 | } |
| 2233 | 2227 | |
| 2234 | const array_ty = try pt.intern(.{ .array_type = .{ | |
| 2228 | const size = std.math.cast(usize, stat.size) orelse return error.FileTooBig; | |
| 2229 | const size_plus_one = std.math.add(usize, size, 1) catch return error.FileTooBig; | |
| 2230 | ||
| 2231 | // The loaded bytes of the file, including a sentinel 0 byte. | |
| 2232 | const ip_str: InternPool.String = str: { | |
| 2233 | const strings = ip.getLocal(tid).getMutableStrings(gpa); | |
| 2234 | const old_len = strings.mutate.len; | |
| 2235 | errdefer strings.shrinkRetainingCapacity(old_len); | |
| 2236 | const bytes = (try strings.addManyAsSlice(size_plus_one))[0]; | |
| 2237 | const actual_read = try file.readAll(bytes[0..size]); | |
| 2238 | if (actual_read != size) return error.UnexpectedEof; | |
| 2239 | bytes[size] = 0; | |
| 2240 | break :str try ip.getOrPutTrailingString(gpa, tid, @intCast(bytes.len), .maybe_embedded_nulls); | |
| 2241 | }; | |
| 2242 | if (ip_str_out) |p| p.* = ip_str; | |
| 2243 | ||
| 2244 | const array_ty = try pt.arrayType(.{ | |
| 2235 | 2245 | .len = size, |
| 2236 | 2246 | .sentinel = .zero_u8, |
| 2237 | 2247 | .child = .u8_type, |
| 2238 | } }); | |
| 2248 | }); | |
| 2249 | const ptr_ty = try pt.singleConstPtrType(array_ty); | |
| 2250 | ||
| 2239 | 2251 | const array_val = try pt.intern(.{ .aggregate = .{ |
| 2240 | .ty = array_ty, | |
| 2241 | .storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, pt.tid, @intCast(bytes[0].len), .maybe_embedded_nulls) }, | |
| 2252 | .ty = array_ty.toIntern(), | |
| 2253 | .storage = .{ .bytes = ip_str }, | |
| 2242 | 2254 | } }); |
| 2243 | ||
| 2244 | const ptr_ty = (try pt.ptrType(.{ | |
| 2245 | .child = array_ty, | |
| 2246 | .flags = .{ | |
| 2247 | .alignment = .none, | |
| 2248 | .is_const = true, | |
| 2249 | .address_space = .generic, | |
| 2250 | }, | |
| 2251 | })).toIntern(); | |
| 2252 | 2255 | const ptr_val = try pt.intern(.{ .ptr = .{ |
| 2253 | .ty = ptr_ty, | |
| 2256 | .ty = ptr_ty.toIntern(), | |
| 2254 | 2257 | .base_addr = .{ .uav = .{ |
| 2255 | 2258 | .val = array_val, |
| 2256 | .orig_ty = ptr_ty, | |
| 2259 | .orig_ty = ptr_ty.toIntern(), | |
| 2257 | 2260 | } }, |
| 2258 | 2261 | .byte_offset = 0, |
| 2259 | 2262 | } }); |
| 2260 | 2263 | |
| 2261 | result.* = new_file; | |
| 2264 | ef.val = ptr_val; | |
| 2265 | ef.err = null; | |
| 2266 | ef.stat = stat; | |
| 2267 | } | |
| 2268 | ||
| 2269 | fn newEmbedFile( | |
| 2270 | pt: Zcu.PerThread, | |
| 2271 | mod: *Module, | |
| 2272 | /// The path of the file to embed relative to the root of `mod`. | |
| 2273 | sub_file_path: []const u8, | |
| 2274 | /// The resolved path of the file to embed. | |
| 2275 | resolved_path: []const u8, | |
| 2276 | ) !*Zcu.EmbedFile { | |
| 2277 | const zcu = pt.zcu; | |
| 2278 | const comp = zcu.comp; | |
| 2279 | const gpa = zcu.gpa; | |
| 2280 | const ip = &zcu.intern_pool; | |
| 2281 | ||
| 2282 | if (comp.file_system_inputs) |fsi| | |
| 2283 | try comp.appendFileSystemInput(fsi, mod.root, sub_file_path); | |
| 2284 | ||
| 2285 | const new_file = try gpa.create(Zcu.EmbedFile); | |
| 2286 | errdefer gpa.destroy(new_file); | |
| 2287 | ||
| 2262 | 2288 | new_file.* = .{ |
| 2289 | .owner = mod, | |
| 2263 | 2290 | .sub_file_path = try ip.getOrPutString(gpa, pt.tid, sub_file_path, .no_embedded_nulls), |
| 2264 | .owner = pkg, | |
| 2265 | .stat = stat, | |
| 2266 | .val = ptr_val, | |
| 2267 | .src_loc = src_loc, | |
| 2291 | .val = .none, | |
| 2292 | .err = null, | |
| 2293 | .stat = undefined, | |
| 2268 | 2294 | }; |
| 2269 | return ptr_val; | |
| 2295 | ||
| 2296 | var opt_ip_str: ?InternPool.String = null; | |
| 2297 | try pt.updateEmbedFile(new_file, &opt_ip_str); | |
| 2298 | ||
| 2299 | // Add the file contents to the `whole` cache manifest if necessary. | |
| 2300 | cache: { | |
| 2301 | const whole = switch (zcu.comp.cache_use) { | |
| 2302 | .whole => |whole| whole, | |
| 2303 | .incremental => break :cache, | |
| 2304 | }; | |
| 2305 | const man = whole.cache_manifest orelse break :cache; | |
| 2306 | const ip_str = opt_ip_str orelse break :cache; | |
| 2307 | ||
| 2308 | const copied_resolved_path = try gpa.dupe(u8, resolved_path); | |
| 2309 | errdefer gpa.free(copied_resolved_path); | |
| 2310 | ||
| 2311 | const array_len = Value.fromInterned(new_file.val).typeOf(zcu).childType(zcu).arrayLen(zcu); | |
| 2312 | ||
| 2313 | whole.cache_manifest_mutex.lock(); | |
| 2314 | defer whole.cache_manifest_mutex.unlock(); | |
| 2315 | ||
| 2316 | man.addFilePostContents(copied_resolved_path, ip_str.toSlice(array_len, ip), new_file.stat) catch |err| switch (err) { | |
| 2317 | error.Unexpected => unreachable, | |
| 2318 | else => |e| return e, | |
| 2319 | }; | |
| 2320 | } | |
| 2321 | ||
| 2322 | return new_file; | |
| 2270 | 2323 | } |
| 2271 | 2324 | |
| 2272 | 2325 | pub fn scanNamespace( |
test/incremental/add_decl+1-1| ... | ... | @@ -39,7 +39,7 @@ pub fn main() !void { |
| 39 | 39 | } |
| 40 | 40 | const foo = "good morning\n"; |
| 41 | 41 | const bar = "good evening\n"; |
| 42 | #expect_error=ignored | |
| 42 | #expect_error=main.zig:3:37: error: use of undeclared identifier 'qux' | |
| 43 | 43 | |
| 44 | 44 | #update=add missing declaration |
| 45 | 45 | #file=main.zig |
test/incremental/add_decl_namespaced+2-1| ... | ... | @@ -39,7 +39,8 @@ pub fn main() !void { |
| 39 | 39 | } |
| 40 | 40 | const foo = "good morning\n"; |
| 41 | 41 | const bar = "good evening\n"; |
| 42 | #expect_error=ignored | |
| 42 | #expect_error=main.zig:3:44: error: root source file struct 'main' has no member named 'qux' | |
| 43 | #expect_error=main.zig:1:1: note: struct declared here | |
| 43 | 44 | |
| 44 | 45 | #update=add missing declaration |
| 45 | 46 | #file=main.zig |
test/incremental/change_embed_file created+46| ... | ... | @@ -0,0 +1,46 @@ |
| 1 | #target=x86_64-linux-selfhosted | |
| 2 | #target=x86_64-linux-cbe | |
| 3 | #target=x86_64-windows-cbe | |
| 4 | #target=wasm32-wasi-selfhosted | |
| 5 | #update=initial version | |
| 6 | #file=main.zig | |
| 7 | const std = @import("std"); | |
| 8 | const string = @embedFile("string.txt"); | |
| 9 | pub fn main() !void { | |
| 10 | try std.io.getStdOut().writeAll(string); | |
| 11 | } | |
| 12 | #file=string.txt | |
| 13 | Hello, World! | |
| 14 | #expect_stdout="Hello, World!\n" | |
| 15 | ||
| 16 | #update=change file contents | |
| 17 | #file=string.txt | |
| 18 | Hello again, World! | |
| 19 | #expect_stdout="Hello again, World!\n" | |
| 20 | ||
| 21 | #update=delete file | |
| 22 | #rm_file=string.txt | |
| 23 | #expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound | |
| 24 | ||
| 25 | #update=remove reference to file | |
| 26 | #file=main.zig | |
| 27 | const std = @import("std"); | |
| 28 | const string = @embedFile("string.txt"); | |
| 29 | pub fn main() !void { | |
| 30 | try std.io.getStdOut().writeAll("a hardcoded string\n"); | |
| 31 | } | |
| 32 | #expect_stdout="a hardcoded string\n" | |
| 33 | ||
| 34 | #update=re-introduce reference to file | |
| 35 | #file=main.zig | |
| 36 | const std = @import("std"); | |
| 37 | const string = @embedFile("string.txt"); | |
| 38 | pub fn main() !void { | |
| 39 | try std.io.getStdOut().writeAll(string); | |
| 40 | } | |
| 41 | #expect_error=main.zig:2:27: error: unable to open 'string.txt': FileNotFound | |
| 42 | ||
| 43 | #update=recreate file | |
| 44 | #file=string.txt | |
| 45 | We're back, World! | |
| 46 | #expect_stdout="We're back, World!\n" |
test/incremental/change_enum_tag_type+1-1| ... | ... | @@ -39,7 +39,7 @@ comptime { |
| 39 | 39 | std.debug.assert(@TypeOf(@intFromEnum(Foo.e)) == Tag); |
| 40 | 40 | } |
| 41 | 41 | const std = @import("std"); |
| 42 | #expect_error=ignored | |
| 42 | #expect_error=main.zig:7:5: error: enumeration value '4' too large for type 'u2' | |
| 43 | 43 | #update=increase tag size |
| 44 | 44 | #file=main.zig |
| 45 | 45 | const Tag = u3; |
test/incremental/compile_error_then_log+5-2| ... | ... | @@ -4,19 +4,22 @@ |
| 4 | 4 | #target=wasm32-wasi-selfhosted |
| 5 | 5 | #update=initial version with compile error |
| 6 | 6 | #file=main.zig |
| 7 | pub fn main() void {} | |
| 7 | 8 | comptime { |
| 8 | 9 | @compileError("this is an error"); |
| 9 | 10 | } |
| 10 | 11 | comptime { |
| 11 | 12 | @compileLog("this is a log"); |
| 12 | 13 | } |
| 13 | #expect_error=ignored | |
| 14 | #expect_error=main.zig:3:5: error: this is an error | |
| 15 | ||
| 14 | 16 | #update=remove the compile error |
| 15 | 17 | #file=main.zig |
| 18 | pub fn main() void {} | |
| 16 | 19 | comptime { |
| 17 | 20 | //@compileError("this is an error"); |
| 18 | 21 | } |
| 19 | 22 | comptime { |
| 20 | 23 | @compileLog("this is a log"); |
| 21 | 24 | } |
| 22 | #expect_error=ignored | |
| 25 | #expect_error=main.zig:6:5: error: found compile log statement |
test/incremental/delete_comptime_decls+5-2| ... | ... | @@ -26,7 +26,10 @@ comptime { |
| 26 | 26 | const slice = array[3..2]; |
| 27 | 27 | _ = slice; |
| 28 | 28 | } |
| 29 | #expect_error=ignored | |
| 29 | #expect_error=main.zig:5:32: error: end index 6 out of bounds for slice of length 4 +1 (sentinel) | |
| 30 | #expect_error=main.zig:10:28: error: end index 6 out of bounds for array of length 4 +1 (sentinel) | |
| 31 | #expect_error=main.zig:15:28: error: end index 5 out of bounds for array of length 4 | |
| 32 | #expect_error=main.zig:20:25: error: start index 3 is larger than end index 2 | |
| 30 | 33 | |
| 31 | 34 | #update=delete and modify comptime decls |
| 32 | 35 | #file=main.zig |
| ... | ... | @@ -38,4 +41,4 @@ comptime { |
| 38 | 41 | const y = x[0..runtime_len]; |
| 39 | 42 | _ = y; |
| 40 | 43 | } |
| 41 | #expect_error=ignored | |
| 44 | #expect_error=main.zig:6:16: error: slice of null pointer |
test/incremental/fix_astgen_failure+2-2| ... | ... | @@ -11,7 +11,7 @@ pub fn main() !void { |
| 11 | 11 | pub fn hello() !void { |
| 12 | 12 | try std.io.getStdOut().writeAll("Hello, World!\n"); |
| 13 | 13 | } |
| 14 | #expect_error=ignored | |
| 14 | #expect_error=foo.zig:2:9: error: use of undeclared identifier 'std' | |
| 15 | 15 | #update=fix the error |
| 16 | 16 | #file=foo.zig |
| 17 | 17 | const std = @import("std"); |
| ... | ... | @@ -25,7 +25,7 @@ const std = @import("std"); |
| 25 | 25 | pub fn hello() !void { |
| 26 | 26 | try std.io.getStdOut().writeAll(hello_str); |
| 27 | 27 | } |
| 28 | #expect_error=ignored | |
| 28 | #expect_error=foo.zig:3:37: error: use of undeclared identifier 'hello_str' | |
| 29 | 29 | #update=fix the new error |
| 30 | 30 | #file=foo.zig |
| 31 | 31 | const std = @import("std"); |
test/incremental/fix_many_errors+20-1| ... | ... | @@ -24,7 +24,26 @@ export fn f6() void { @compileError("f6"); } |
| 24 | 24 | export fn f7() void { @compileError("f7"); } |
| 25 | 25 | export fn f8() void { @compileError("f8"); } |
| 26 | 26 | export fn f9() void { @compileError("f9"); } |
| 27 | #expect_error=ignored | |
| 27 | #expect_error=main.zig:2:12: error: c0 | |
| 28 | #expect_error=main.zig:3:12: error: c1 | |
| 29 | #expect_error=main.zig:4:12: error: c2 | |
| 30 | #expect_error=main.zig:5:12: error: c3 | |
| 31 | #expect_error=main.zig:6:12: error: c4 | |
| 32 | #expect_error=main.zig:7:12: error: c5 | |
| 33 | #expect_error=main.zig:8:12: error: c6 | |
| 34 | #expect_error=main.zig:9:12: error: c7 | |
| 35 | #expect_error=main.zig:10:12: error: c8 | |
| 36 | #expect_error=main.zig:11:12: error: c9 | |
| 37 | #expect_error=main.zig:12:23: error: f0 | |
| 38 | #expect_error=main.zig:13:23: error: f1 | |
| 39 | #expect_error=main.zig:14:23: error: f2 | |
| 40 | #expect_error=main.zig:15:23: error: f3 | |
| 41 | #expect_error=main.zig:16:23: error: f4 | |
| 42 | #expect_error=main.zig:17:23: error: f5 | |
| 43 | #expect_error=main.zig:18:23: error: f6 | |
| 44 | #expect_error=main.zig:19:23: error: f7 | |
| 45 | #expect_error=main.zig:20:23: error: f8 | |
| 46 | #expect_error=main.zig:21:23: error: f9 | |
| 28 | 47 | #update=fix all the errors |
| 29 | 48 | #file=main.zig |
| 30 | 49 | pub fn main() !void {} |
test/incremental/remove_enum_field+2-1| ... | ... | @@ -23,4 +23,5 @@ pub fn main() !void { |
| 23 | 23 | try std.io.getStdOut().writer().print("{}\n", .{@intFromEnum(MyEnum.foo)}); |
| 24 | 24 | } |
| 25 | 25 | const std = @import("std"); |
| 26 | #expect_error=ignored | |
| 26 | #expect_error=main.zig:6:73: error: enum 'main.MyEnum' has no member named 'foo' | |
| 27 | #expect_error=main.zig:1:16: note: enum declared here |
test/incremental/remove_invalid_union_backing_enum+2-1| ... | ... | @@ -15,7 +15,8 @@ pub fn main() void { |
| 15 | 15 | const u: U = .{ .a = 123 }; |
| 16 | 16 | _ = u; |
| 17 | 17 | } |
| 18 | #expect_error=ignored | |
| 18 | #expect_error=main.zig:6:5: error: no field named 'd' in enum 'main.E' | |
| 19 | #expect_error=main.zig:1:11: note: enum declared here | |
| 19 | 20 | #update=remove invalid backing enum |
| 20 | 21 | #file=main.zig |
| 21 | 22 | const U = union { |
test/incremental/temporary_parse_error+1-1| ... | ... | @@ -11,7 +11,7 @@ pub fn main() !void {} |
| 11 | 11 | #update=introduce parse error |
| 12 | 12 | #file=main.zig |
| 13 | 13 | pub fn main() !void { |
| 14 | #expect_error=ignored | |
| 14 | #expect_error=main.zig:2:1: error: expected statement, found 'EOF' | |
| 15 | 15 | |
| 16 | 16 | #update=fix parse error |
| 17 | 17 | #file=main.zig |
test/incremental/unreferenced_error+1-1| ... | ... | @@ -18,7 +18,7 @@ pub fn main() !void { |
| 18 | 18 | try std.io.getStdOut().writeAll(a); |
| 19 | 19 | } |
| 20 | 20 | const a = @compileError("bad a"); |
| 21 | #expect_error=ignored | |
| 21 | #expect_error=main.zig:5:11: error: bad a | |
| 22 | 22 | |
| 23 | 23 | #update=remove error reference |
| 24 | 24 | #file=main.zig |
tools/incr-check.zig+138-26| ... | ... | @@ -340,19 +340,63 @@ const Eval = struct { |
| 340 | 340 | } |
| 341 | 341 | |
| 342 | 342 | fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void { |
| 343 | switch (update.outcome) { | |
| 343 | const expected_errors = switch (update.outcome) { | |
| 344 | 344 | .unknown => return, |
| 345 | .compile_errors => |expected_errors| { | |
| 346 | for (expected_errors) |expected_error| { | |
| 347 | _ = expected_error; | |
| 348 | @panic("TODO check if the expected error matches the compile errors"); | |
| 349 | } | |
| 350 | }, | |
| 345 | .compile_errors => |expected_errors| expected_errors, | |
| 351 | 346 | .stdout, .exit_code => { |
| 352 | 347 | const color: std.zig.Color = .auto; |
| 353 | 348 | error_bundle.renderToStdErr(color.renderOptions()); |
| 354 | 349 | eval.fatal("update '{s}': unexpected compile errors", .{update.name}); |
| 355 | 350 | }, |
| 351 | }; | |
| 352 | ||
| 353 | var expected_idx: usize = 0; | |
| 354 | ||
| 355 | for (error_bundle.getMessages()) |err_idx| { | |
| 356 | if (expected_idx == expected_errors.len) { | |
| 357 | const color: std.zig.Color = .auto; | |
| 358 | error_bundle.renderToStdErr(color.renderOptions()); | |
| 359 | eval.fatal("update '{s}': more errors than expected", .{update.name}); | |
| 360 | } | |
| 361 | eval.checkOneError(update, error_bundle, expected_errors[expected_idx], false, err_idx); | |
| 362 | expected_idx += 1; | |
| 363 | ||
| 364 | for (error_bundle.getNotes(err_idx)) |note_idx| { | |
| 365 | if (expected_idx == expected_errors.len) { | |
| 366 | const color: std.zig.Color = .auto; | |
| 367 | error_bundle.renderToStdErr(color.renderOptions()); | |
| 368 | eval.fatal("update '{s}': more error notes than expected", .{update.name}); | |
| 369 | } | |
| 370 | eval.checkOneError(update, error_bundle, expected_errors[expected_idx], true, note_idx); | |
| 371 | expected_idx += 1; | |
| 372 | } | |
| 373 | } | |
| 374 | } | |
| 375 | ||
| 376 | fn checkOneError( | |
| 377 | eval: *Eval, | |
| 378 | update: Case.Update, | |
| 379 | eb: std.zig.ErrorBundle, | |
| 380 | expected: Case.ExpectedError, | |
| 381 | is_note: bool, | |
| 382 | err_idx: std.zig.ErrorBundle.MessageIndex, | |
| 383 | ) void { | |
| 384 | const err = eb.getErrorMessage(err_idx); | |
| 385 | if (err.src_loc == .none) @panic("TODO error message with no source location"); | |
| 386 | if (err.count != 1) @panic("TODO error message with count>1"); | |
| 387 | const msg = eb.nullTerminatedString(err.msg); | |
| 388 | const src = eb.getSourceLocation(err.src_loc); | |
| 389 | const filename = eb.nullTerminatedString(src.src_path); | |
| 390 | ||
| 391 | if (expected.is_note != is_note or | |
| 392 | !std.mem.eql(u8, expected.filename, filename) or | |
| 393 | expected.line != src.line + 1 or | |
| 394 | expected.column != src.column + 1 or | |
| 395 | !std.mem.eql(u8, expected.msg, msg)) | |
| 396 | { | |
| 397 | const color: std.zig.Color = .auto; | |
| 398 | eb.renderToStdErr(color.renderOptions()); | |
| 399 | eval.fatal("update '{s}': compile error did not match expected error", .{update.name}); | |
| 356 | 400 | } |
| 357 | 401 | } |
| 358 | 402 | |
| ... | ... | @@ -595,11 +639,11 @@ const Case = struct { |
| 595 | 639 | }; |
| 596 | 640 | |
| 597 | 641 | const ExpectedError = struct { |
| 598 | file_name: ?[]const u8 = null, | |
| 599 | line: ?u32 = null, | |
| 600 | column: ?u32 = null, | |
| 601 | msg_exact: ?[]const u8 = null, | |
| 602 | msg_substring: ?[]const u8 = null, | |
| 642 | is_note: bool, | |
| 643 | filename: []const u8, | |
| 644 | line: u32, | |
| 645 | column: u32, | |
| 646 | msg: []const u8, | |
| 603 | 647 | }; |
| 604 | 648 | |
| 605 | 649 | fn parse(arena: Allocator, bytes: []const u8) !Case { |
| ... | ... | @@ -608,6 +652,7 @@ const Case = struct { |
| 608 | 652 | var targets: std.ArrayListUnmanaged(Target) = .empty; |
| 609 | 653 | var updates: std.ArrayListUnmanaged(Update) = .empty; |
| 610 | 654 | var changes: std.ArrayListUnmanaged(FullContents) = .empty; |
| 655 | var deletes: std.ArrayListUnmanaged([]const u8) = .empty; | |
| 611 | 656 | var it = std.mem.splitScalar(u8, bytes, '\n'); |
| 612 | 657 | var line_n: usize = 1; |
| 613 | 658 | var root_source_file: ?[]const u8 = null; |
| ... | ... | @@ -647,33 +692,42 @@ const Case = struct { |
| 647 | 692 | if (updates.items.len > 0) { |
| 648 | 693 | const last_update = &updates.items[updates.items.len - 1]; |
| 649 | 694 | last_update.changes = try changes.toOwnedSlice(arena); |
| 695 | last_update.deletes = try deletes.toOwnedSlice(arena); | |
| 650 | 696 | } |
| 651 | 697 | try updates.append(arena, .{ |
| 652 | 698 | .name = val, |
| 653 | 699 | .outcome = .unknown, |
| 654 | 700 | }); |
| 655 | 701 | } else if (std.mem.eql(u8, key, "file")) { |
| 656 | if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n}); | |
| 702 | if (updates.items.len == 0) fatal("line {d}: file directive before update", .{line_n}); | |
| 657 | 703 | |
| 658 | 704 | if (root_source_file == null) |
| 659 | 705 | root_source_file = val; |
| 660 | 706 | |
| 661 | const start_index = it.index.?; | |
| 662 | const src = while (true) : (line_n += 1) { | |
| 663 | const old = it; | |
| 664 | const next_line = it.next() orelse fatal("line {d}: unexpected EOF", .{line_n}); | |
| 665 | if (std.mem.startsWith(u8, next_line, "#")) { | |
| 666 | const end_index = old.index.?; | |
| 667 | const src = bytes[start_index..end_index]; | |
| 668 | it = old; | |
| 669 | break src; | |
| 670 | } | |
| 671 | }; | |
| 707 | // Because Windows is so excellent, we need to convert CRLF to LF, so | |
| 708 | // can't just slice into the input here. How delightful! | |
| 709 | var src: std.ArrayListUnmanaged(u8) = .empty; | |
| 710 | ||
| 711 | while (true) { | |
| 712 | const next_line_raw = it.peek() orelse fatal("line {d}: unexpected EOF", .{line_n}); | |
| 713 | const next_line = std.mem.trimRight(u8, next_line_raw, "\r"); | |
| 714 | if (std.mem.startsWith(u8, next_line, "#")) break; | |
| 715 | ||
| 716 | _ = it.next(); | |
| 717 | line_n += 1; | |
| 718 | ||
| 719 | try src.ensureUnusedCapacity(arena, next_line.len + 1); | |
| 720 | src.appendSliceAssumeCapacity(next_line); | |
| 721 | src.appendAssumeCapacity('\n'); | |
| 722 | } | |
| 672 | 723 | |
| 673 | 724 | try changes.append(arena, .{ |
| 674 | 725 | .name = val, |
| 675 | .bytes = src, | |
| 726 | .bytes = src.items, | |
| 676 | 727 | }); |
| 728 | } else if (std.mem.eql(u8, key, "rm_file")) { | |
| 729 | if (updates.items.len == 0) fatal("line {d}: rm_file directive before update", .{line_n}); | |
| 730 | try deletes.append(arena, val); | |
| 677 | 731 | } else if (std.mem.eql(u8, key, "expect_stdout")) { |
| 678 | 732 | if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n}); |
| 679 | 733 | const last_update = &updates.items[updates.items.len - 1]; |
| ... | ... | @@ -687,7 +741,24 @@ const Case = struct { |
| 687 | 741 | if (updates.items.len == 0) fatal("line {d}: expect directive before update", .{line_n}); |
| 688 | 742 | const last_update = &updates.items[updates.items.len - 1]; |
| 689 | 743 | if (last_update.outcome != .unknown) fatal("line {d}: conflicting expect directive", .{line_n}); |
| 690 | last_update.outcome = .{ .compile_errors = &.{} }; | |
| 744 | ||
| 745 | var errors: std.ArrayListUnmanaged(ExpectedError) = .empty; | |
| 746 | try errors.append(arena, parseExpectedError(val, line_n)); | |
| 747 | while (true) { | |
| 748 | const next_line = it.peek() orelse break; | |
| 749 | if (!std.mem.startsWith(u8, next_line, "#")) break; | |
| 750 | var new_line_it = std.mem.splitScalar(u8, next_line, '='); | |
| 751 | const new_key = new_line_it.first()[1..]; | |
| 752 | const new_val = std.mem.trimRight(u8, new_line_it.rest(), "\r"); | |
| 753 | if (new_val.len == 0) break; | |
| 754 | if (!std.mem.eql(u8, new_key, "expect_error")) break; | |
| 755 | ||
| 756 | _ = it.next(); | |
| 757 | line_n += 1; | |
| 758 | try errors.append(arena, parseExpectedError(new_val, line_n)); | |
| 759 | } | |
| 760 | ||
| 761 | last_update.outcome = .{ .compile_errors = errors.items }; | |
| 691 | 762 | } else { |
| 692 | 763 | fatal("line {d}: unrecognized key '{s}'", .{ line_n, key }); |
| 693 | 764 | } |
| ... | ... | @@ -701,6 +772,7 @@ const Case = struct { |
| 701 | 772 | if (changes.items.len > 0) { |
| 702 | 773 | const last_update = &updates.items[updates.items.len - 1]; |
| 703 | 774 | last_update.changes = changes.items; // arena so no need for toOwnedSlice |
| 775 | last_update.deletes = deletes.items; | |
| 704 | 776 | } |
| 705 | 777 | |
| 706 | 778 | return .{ |
| ... | ... | @@ -736,3 +808,43 @@ fn waitChild(child: *std.process.Child, eval: *Eval) void { |
| 736 | 808 | .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}), |
| 737 | 809 | } |
| 738 | 810 | } |
| 811 | ||
| 812 | fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError { | |
| 813 | // #expect_error=foo.zig:1:2: error: the error message | |
| 814 | // #expect_error=foo.zig:1:2: note: and a note | |
| 815 | ||
| 816 | const fatal = std.process.fatal; | |
| 817 | ||
| 818 | var it = std.mem.splitScalar(u8, str, ':'); | |
| 819 | const filename = it.first(); | |
| 820 | const line_str = it.next() orelse fatal("line {d}: incomplete error specification", .{l}); | |
| 821 | const column_str = it.next() orelse fatal("line {d}: incomplete error specification", .{l}); | |
| 822 | const error_or_note_str = std.mem.trim( | |
| 823 | u8, | |
| 824 | it.next() orelse fatal("line {d}: incomplete error specification", .{l}), | |
| 825 | " ", | |
| 826 | ); | |
| 827 | const message = std.mem.trim(u8, it.rest(), " "); | |
| 828 | if (filename.len == 0) fatal("line {d}: empty filename", .{l}); | |
| 829 | if (message.len == 0) fatal("line {d}: empty error message", .{l}); | |
| 830 | const is_note = if (std.mem.eql(u8, error_or_note_str, "error")) | |
| 831 | false | |
| 832 | else if (std.mem.eql(u8, error_or_note_str, "note")) | |
| 833 | true | |
| 834 | else | |
| 835 | fatal("line {d}: expeted 'error' or 'note', found '{s}'", .{ l, error_or_note_str }); | |
| 836 | ||
| 837 | const line = std.fmt.parseInt(u32, line_str, 10) catch | |
| 838 | fatal("line {d}: invalid line number '{s}'", .{ l, line_str }); | |
| 839 | ||
| 840 | const column = std.fmt.parseInt(u32, column_str, 10) catch | |
| 841 | fatal("line {d}: invalid column number '{s}'", .{ l, column_str }); | |
| 842 | ||
| 843 | return .{ | |
| 844 | .is_note = is_note, | |
| 845 | .filename = filename, | |
| 846 | .line = line, | |
| 847 | .column = column, | |
| 848 | .msg = message, | |
| 849 | }; | |
| 850 | } |