diff --git a/doc/docgen.zig b/doc/docgen.zig index 93cc316de18b..675dcccf70b5 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -1305,7 +1305,7 @@ fn genHtml( defer root_node.end(); var env_map = try process.getEnvMap(allocator); - try env_map.put("ZIG_DEBUG_COLOR", "1"); + try env_map.put("YES_COLOR", "1"); const host = try std.zig.system.NativeTargetInfo.detect(.{}); const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe, opt_zig_lib_dir); diff --git a/doc/langref.html.in b/doc/langref.html.in index 392bfe2bbc13..0b37db19971f 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5584,7 +5584,7 @@ fn doAThing(str: []u8) !void { appropriately.

- Finally, you may want to take a different action for every situation. For that, we combine + You may want to take a different action for every situation. For that, we combine the {#link|if#} and {#link|switch#} expression:

{#syntax_block|zig|handle_all_error_scenarios.zig#} @@ -5598,6 +5598,22 @@ fn doAThing(str: []u8) void { // we promise that InvalidChar won't happen (or crash in debug mode if it does) error.InvalidChar => unreachable, } +} + {#end_syntax_block#} +

+ Finally, you may want to handle only some errors. For that, you can capture the unhandled + errors in the {#syntax#}else{#endsyntax#} case, which now contains a narrower error set: +

+ {#syntax_block|zig|handle_some_error_scenarios.zig#} + fn doAnotherThing(str: []u8) error{InvaidChar}!void { + if (parseU64(str, 10)) |number| { + doSomethingWithNumber(number); + } else |err| switch (err) { + error.Overflow => { + // handle overflow... + }, + else => |leftover_err| return leftover_err, + } } {#end_syntax_block#}

diff --git a/lib/build_runner.zig b/lib/build_runner.zig index a5abbebe168e..cdedba90b2d6 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -282,7 +282,7 @@ pub fn main() !void { const ttyconf = get_tty_conf(color, stderr); switch (ttyconf) { .no_color => try builder.env_map.put("NO_COLOR", "1"), - .escape_codes => try builder.env_map.put("ZIG_DEBUG_COLOR", "1"), + .escape_codes => try builder.env_map.put("YES_COLOR", "1"), .windows_api => {}, } diff --git a/lib/std/io/tty.zig b/lib/std/io/tty.zig index 04a84dc375c8..bbae90198ed9 100644 --- a/lib/std/io/tty.zig +++ b/lib/std/io/tty.zig @@ -7,29 +7,34 @@ const native_os = builtin.os.tag; /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr). /// This includes feature checks for ANSI escape codes and the Windows console API, as well as -/// respecting the `NO_COLOR` environment variable. +/// respecting the `NO_COLOR` and `YES_COLOR` environment variables to override the default. pub fn detectConfig(file: File) Config { - if (builtin.os.tag == .wasi) { - // Per https://github.com/WebAssembly/WASI/issues/162 ANSI codes - // aren't currently supported. - return .no_color; - } else if (process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) { - return .escape_codes; - } else if (process.hasEnvVarConstant("NO_COLOR")) { - return .no_color; - } else if (file.supportsAnsiEscapeCodes()) { - return .escape_codes; - } else if (native_os == .windows and file.isTty()) { + const force_color: ?bool = if (builtin.os.tag == .wasi) + null // wasi does not support environment variables + else if (process.hasEnvVarConstant("NO_COLOR")) + false + else if (process.hasEnvVarConstant("YES_COLOR")) + true + else + null; + + if (force_color == false) return .no_color; + + if (native_os == .windows and file.isTty()) { var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) { - // TODO: Should this return an error instead? - return .no_color; + return if (force_color == true) .escape_codes else .no_color; } return .{ .windows_api = .{ .handle = file.handle, .reset_attributes = info.wAttributes, } }; } + + if (force_color == true or file.supportsAnsiEscapeCodes()) { + return .escape_codes; + } + return .no_color; } diff --git a/lib/std/sort.zig b/lib/std/sort.zig index 928503ad40b7..1ae7e1e574e5 100644 --- a/lib/std/sort.zig +++ b/lib/std/sort.zig @@ -36,6 +36,8 @@ pub fn insertion( /// O(1) memory (no allocator required). /// Sorts in ascending order with respect to the given `lessThan` function. pub fn insertionContext(a: usize, b: usize, context: anytype) void { + assert(a <= b); + var i = a + 1; while (i < b) : (i += 1) { var j = i; @@ -73,6 +75,7 @@ pub fn heap( /// O(1) memory (no allocator required). /// Sorts in ascending order with respect to the given `lessThan` function. pub fn heapContext(a: usize, b: usize, context: anytype) void { + assert(a <= b); // build the heap in linear time. var i = a + (b - a) / 2; while (i > a) { @@ -89,22 +92,33 @@ pub fn heapContext(a: usize, b: usize, context: anytype) void { } } -fn siftDown(a: usize, root: usize, n: usize, context: anytype) void { - var node = root; +fn siftDown(a: usize, target: usize, b: usize, context: anytype) void { + var cur = target; while (true) { - var child = a + 2 * (node - a) + 1; - if (child >= n) break; + // When we don't overflow from the multiply below, the following expression equals (2*cur) - (2*a) + a + 1 + // The `+ a + 1` is safe because: + // for `a > 0` then `2a >= a + 1`. + // for `a = 0`, the expression equals `2*cur+1`. `2*cur` is an even number, therefore adding 1 is safe. + var child = (math.mul(usize, cur - a, 2) catch break) + a + 1; + + // stop if we overshot the boundary + if (!(child < b)) break; - // choose the greater child. - child += @intFromBool(child + 1 < n and context.lessThan(child, child + 1)); + // `next_child` is at most `b`, therefore no overflow is possible + const next_child = child + 1; + + // store the greater child in `child` + if (next_child < b and context.lessThan(child, next_child)) { + child = next_child; + } - // stop if the invariant holds at `node`. - if (!context.lessThan(node, child)) break; + // stop if the Heap invariant holds at `cur`. + if (context.lessThan(child, cur)) break; - // swap `node` with the greater child, + // swap `cur` with the greater child, // move one step down, and continue sifting. - context.swap(node, child); - node = child; + context.swap(child, cur); + cur = child; } } diff --git a/src/Sema.zig b/src/Sema.zig index e2a7a81f8cf1..ddc02c1ecefb 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8273,7 +8273,7 @@ fn zirIntFromEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (try sema.resolveMaybeUndefVal(enum_tag)) |enum_tag_val| { const val = try enum_tag_val.intFromEnum(enum_tag_ty, mod); - return sema.addConstant(int_tag_ty, try val.copy(sema.arena)); + return sema.addConstant(int_tag_ty, val); } try sema.requireRuntimeBlock(block, src, operand_src); @@ -28723,14 +28723,11 @@ fn beginComptimePtrMutation( // without making a call to this function. const arena = sema.arena; - const repeated_val = try val_ptr.castTag(.repeated).?.data.copy(arena); + const repeated_val = try val_ptr.castTag(.repeated).?.data.intern(parent.ty.childType(mod), mod); const array_len_including_sentinel = try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel(mod)); const elems = try arena.alloc(Value, array_len_including_sentinel); - if (elems.len > 0) elems[0] = repeated_val; - for (elems[1..]) |*elem| { - elem.* = try repeated_val.copy(arena); - } + @memset(elems, repeated_val.toValue()); val_ptr.* = try Value.Tag.aggregate.create(arena, elems); @@ -36421,7 +36418,7 @@ fn valuesEqual( rhs: Value, ty: Type, ) CompileError!bool { - return Value.eqlAdvanced(lhs, ty, rhs, ty, sema.mod, sema); + return lhs.eql(rhs, ty, sema.mod); } /// Asserts the values are comparable vectors of type `ty`. diff --git a/src/type.zig b/src/type.zig index 59403f9875cd..280c29231426 100644 --- a/src/type.zig +++ b/src/type.zig @@ -120,14 +120,6 @@ pub const Type = struct { return a.toIntern() == b.toIntern(); } - pub fn hash(ty: Type, mod: *const Module) u32 { - _ = mod; // TODO: remove this parameter - // The InternPool data structure hashes based on Key to make interned objects - // unique. An Index can be treated simply as u32 value for the - // purpose of Type/Value hashing and equality. - return std.hash.uint32(@intFromEnum(ty.toIntern())); - } - pub fn format(ty: Type, comptime unused_fmt_string: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { _ = ty; _ = unused_fmt_string; diff --git a/src/value.zig b/src/value.zig index 573f0ca7e2e3..542dfb73ec26 100644 --- a/src/value.zig +++ b/src/value.zig @@ -132,98 +132,6 @@ pub const Value = struct { return null; } - /// It's intentional that this function is not passed a corresponding Type, so that - /// a Value can be copied from a Sema to a Decl prior to resolving struct/union field types. - pub fn copy(self: Value, arena: Allocator) error{OutOfMemory}!Value { - if (self.ip_index != .none) { - return Value{ .ip_index = self.ip_index, .legacy = undefined }; - } - switch (self.legacy.ptr_otherwise.tag) { - .bytes => { - const bytes = self.castTag(.bytes).?.data; - const new_payload = try arena.create(Payload.Bytes); - new_payload.* = .{ - .base = .{ .tag = .bytes }, - .data = try arena.dupe(u8, bytes), - }; - return Value{ - .ip_index = .none, - .legacy = .{ .ptr_otherwise = &new_payload.base }, - }; - }, - .eu_payload, - .opt_payload, - .repeated, - => { - const payload = self.cast(Payload.SubValue).?; - const new_payload = try arena.create(Payload.SubValue); - new_payload.* = .{ - .base = payload.base, - .data = try payload.data.copy(arena), - }; - return Value{ - .ip_index = .none, - .legacy = .{ .ptr_otherwise = &new_payload.base }, - }; - }, - .slice => { - const payload = self.castTag(.slice).?; - const new_payload = try arena.create(Payload.Slice); - new_payload.* = .{ - .base = payload.base, - .data = .{ - .ptr = try payload.data.ptr.copy(arena), - .len = try payload.data.len.copy(arena), - }, - }; - return Value{ - .ip_index = .none, - .legacy = .{ .ptr_otherwise = &new_payload.base }, - }; - }, - .aggregate => { - const payload = self.castTag(.aggregate).?; - const new_payload = try arena.create(Payload.Aggregate); - new_payload.* = .{ - .base = payload.base, - .data = try arena.alloc(Value, payload.data.len), - }; - for (new_payload.data, 0..) |*elem, i| { - elem.* = try payload.data[i].copy(arena); - } - return Value{ - .ip_index = .none, - .legacy = .{ .ptr_otherwise = &new_payload.base }, - }; - }, - .@"union" => { - const tag_and_val = self.castTag(.@"union").?.data; - const new_payload = try arena.create(Payload.Union); - new_payload.* = .{ - .base = .{ .tag = .@"union" }, - .data = .{ - .tag = try tag_and_val.tag.copy(arena), - .val = try tag_and_val.val.copy(arena), - }, - }; - return Value{ - .ip_index = .none, - .legacy = .{ .ptr_otherwise = &new_payload.base }, - }; - }, - } - } - - fn copyPayloadShallow(self: Value, arena: Allocator, comptime T: type) error{OutOfMemory}!Value { - const payload = self.cast(T).?; - const new_payload = try arena.create(T); - new_payload.* = payload.*; - return Value{ - .ip_index = .none, - .legacy = .{ .ptr_otherwise = &new_payload.base }, - }; - } - pub fn format(val: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { _ = val; _ = fmt; @@ -745,7 +653,15 @@ pub const Value = struct { .Extern => for (ty.structFields(mod).values(), 0..) |field, i| { const off = @intCast(usize, ty.structFieldOffset(i, mod)); const field_val = switch (val.ip_index) { - .none => val.castTag(.aggregate).?.data[i], + .none => switch (val.tag()) { + .bytes => { + buffer[off] = val.castTag(.bytes).?.data[i]; + continue; + }, + .aggregate => val.castTag(.aggregate).?.data[i], + .repeated => val.castTag(.repeated).?.data, + else => unreachable, + }, else => switch (mod.intern_pool.indexToKey(val.toIntern()).aggregate.storage) { .bytes => |bytes| { buffer[off] = bytes[i]; @@ -1486,193 +1402,9 @@ pub const Value = struct { } pub fn eql(a: Value, b: Value, ty: Type, mod: *Module) bool { - return eqlAdvanced(a, ty, b, ty, mod, null) catch unreachable; - } - - /// This function is used by hash maps and so treats floating-point NaNs as equal - /// to each other, and not equal to other floating-point values. - /// Similarly, it treats `undef` as a distinct value from all other values. - /// This function has to be able to support implicit coercion of `a` to `ty`. That is, - /// `ty` will be an exactly correct Type for `b` but it may be a post-coerced Type - /// for `a`. This function must act *as if* `a` has been coerced to `ty`. This complication - /// is required in order to make generic function instantiation efficient - specifically - /// the insertion into the monomorphized function table. - /// If `null` is provided for `opt_sema` then it is guaranteed no error will be returned. - pub fn eqlAdvanced( - a: Value, - a_ty: Type, - b: Value, - ty: Type, - mod: *Module, - opt_sema: ?*Sema, - ) Module.CompileError!bool { - if (a.ip_index != .none or b.ip_index != .none) return a.ip_index == b.ip_index; - - const target = mod.getTarget(); - const a_tag = a.tag(); - const b_tag = b.tag(); - if (a_tag == b_tag) switch (a_tag) { - .aggregate => { - const a_field_vals = a.castTag(.aggregate).?.data; - const b_field_vals = b.castTag(.aggregate).?.data; - assert(a_field_vals.len == b_field_vals.len); - - switch (mod.intern_pool.indexToKey(ty.toIntern())) { - .anon_struct_type => |anon_struct| { - assert(anon_struct.types.len == a_field_vals.len); - for (anon_struct.types, 0..) |field_ty, i| { - if (!(try eqlAdvanced(a_field_vals[i], field_ty.toType(), b_field_vals[i], field_ty.toType(), mod, opt_sema))) { - return false; - } - } - return true; - }, - .struct_type => |struct_type| { - const struct_obj = mod.structPtrUnwrap(struct_type.index).?; - const fields = struct_obj.fields.values(); - assert(fields.len == a_field_vals.len); - for (fields, 0..) |field, i| { - if (!(try eqlAdvanced(a_field_vals[i], field.ty, b_field_vals[i], field.ty, mod, opt_sema))) { - return false; - } - } - return true; - }, - else => {}, - } - - const elem_ty = ty.childType(mod); - for (a_field_vals, 0..) |a_elem, i| { - const b_elem = b_field_vals[i]; - - if (!(try eqlAdvanced(a_elem, elem_ty, b_elem, elem_ty, mod, opt_sema))) { - return false; - } - } - return true; - }, - .@"union" => { - const a_union = a.castTag(.@"union").?.data; - const b_union = b.castTag(.@"union").?.data; - switch (ty.containerLayout(mod)) { - .Packed, .Extern => { - const tag_ty = ty.unionTagTypeHypothetical(mod); - if (!(try eqlAdvanced(a_union.tag, tag_ty, b_union.tag, tag_ty, mod, opt_sema))) { - // In this case, we must disregard mismatching tags and compare - // based on the in-memory bytes of the payloads. - @panic("TODO comptime comparison of extern union values with mismatching tags"); - } - }, - .Auto => { - const tag_ty = ty.unionTagTypeHypothetical(mod); - if (!(try eqlAdvanced(a_union.tag, tag_ty, b_union.tag, tag_ty, mod, opt_sema))) { - return false; - } - }, - } - const active_field_ty = ty.unionFieldType(a_union.tag, mod); - return eqlAdvanced(a_union.val, active_field_ty, b_union.val, active_field_ty, mod, opt_sema); - }, - else => {}, - }; - - if (a.pointerDecl(mod)) |a_decl| { - if (b.pointerDecl(mod)) |b_decl| { - return a_decl == b_decl; - } else { - return false; - } - } else if (b.pointerDecl(mod)) |_| { - return false; - } - - switch (ty.zigTypeTag(mod)) { - .Type => { - const a_type = a.toType(); - const b_type = b.toType(); - return a_type.eql(b_type, mod); - }, - .Enum => { - const a_val = try a.intFromEnum(ty, mod); - const b_val = try b.intFromEnum(ty, mod); - const int_ty = ty.intTagType(mod); - return eqlAdvanced(a_val, int_ty, b_val, int_ty, mod, opt_sema); - }, - .Array, .Vector => { - const len = ty.arrayLen(mod); - const elem_ty = ty.childType(mod); - var i: usize = 0; - while (i < len) : (i += 1) { - const a_elem = try elemValue(a, mod, i); - const b_elem = try elemValue(b, mod, i); - if (!(try eqlAdvanced(a_elem, elem_ty, b_elem, elem_ty, mod, opt_sema))) { - return false; - } - } - return true; - }, - .Pointer => switch (ty.ptrSize(mod)) { - .Slice => { - const a_len = switch (a_ty.ptrSize(mod)) { - .Slice => a.sliceLen(mod), - .One => a_ty.childType(mod).arrayLen(mod), - else => unreachable, - }; - if (a_len != b.sliceLen(mod)) { - return false; - } - - const ptr_ty = ty.slicePtrFieldType(mod); - const a_ptr = switch (a_ty.ptrSize(mod)) { - .Slice => a.slicePtr(mod), - .One => a, - else => unreachable, - }; - return try eqlAdvanced(a_ptr, ptr_ty, b.slicePtr(mod), ptr_ty, mod, opt_sema); - }, - .Many, .C, .One => {}, - }, - .Struct => { - // A struct can be represented with one of: - // .the_one_possible_value, - // .aggregate, - // Note that we already checked above for matching tags, e.g. both .aggregate. - return (try ty.onePossibleValue(mod)) != null; - }, - .Union => { - // Here we have to check for value equality, as-if `a` has been coerced to `ty`. - if ((try ty.onePossibleValue(mod)) != null) { - return true; - } - return false; - }, - .Float => { - switch (ty.floatBits(target)) { - 16 => return @bitCast(u16, a.toFloat(f16, mod)) == @bitCast(u16, b.toFloat(f16, mod)), - 32 => return @bitCast(u32, a.toFloat(f32, mod)) == @bitCast(u32, b.toFloat(f32, mod)), - 64 => return @bitCast(u64, a.toFloat(f64, mod)) == @bitCast(u64, b.toFloat(f64, mod)), - 80 => return @bitCast(u80, a.toFloat(f80, mod)) == @bitCast(u80, b.toFloat(f80, mod)), - 128 => return @bitCast(u128, a.toFloat(f128, mod)) == @bitCast(u128, b.toFloat(f128, mod)), - else => unreachable, - } - }, - .ComptimeFloat => { - const a_float = a.toFloat(f128, mod); - const b_float = b.toFloat(f128, mod); - - const a_nan = std.math.isNan(a_float); - const b_nan = std.math.isNan(b_float); - if (a_nan != b_nan) return false; - if (std.math.signbit(a_float) != std.math.signbit(b_float)) return false; - if (a_nan) return true; - return a_float == b_float; - }, - .Optional, - .ErrorUnion, - => unreachable, // handled by InternPool - else => {}, - } - return (try orderAdvanced(a, b, mod, opt_sema)).compare(.eq); + assert(mod.intern_pool.typeOf(a.toIntern()) == ty.toIntern()); + assert(mod.intern_pool.typeOf(b.toIntern()) == ty.toIntern()); + return a.toIntern() == b.toIntern(); } pub fn isComptimeMutablePtr(val: Value, mod: *Module) bool { @@ -1728,15 +1460,6 @@ pub const Value = struct { }; } - fn hashInt(int_val: Value, hasher: *std.hash.Wyhash, mod: *Module) void { - var buffer: BigIntSpace = undefined; - const big = int_val.toBigInt(&buffer, mod); - std.hash.autoHash(hasher, big.positive); - for (big.limbs) |limb| { - std.hash.autoHash(hasher, limb); - } - } - pub const slice_ptr_index = 0; pub const slice_len_index = 1; diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index ade0f0dc4215..d327afb783e1 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -431,3 +431,10 @@ test "dereference undefined pointer to zero-bit type" { const p1: *[0]u32 = undefined; try testing.expect(p1.*.len == 0); } + +test "type pun extern struct" { + const S = extern struct { f: u8 }; + comptime var s = S{ .f = 123 }; + @ptrCast(*u8, &s).* = 72; + try testing.expectEqual(@as(u8, 72), s.f); +} diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig index 0552b419c0d9..71394e00b8ec 100644 --- a/test/src/StackTrace.zig +++ b/test/src/StackTrace.zig @@ -81,7 +81,7 @@ fn addExpect( }); const run = b.addRunArtifact(exe); - run.removeEnvironmentVariable("ZIG_DEBUG_COLOR"); + run.removeEnvironmentVariable("YES_COLOR"); run.setEnvironmentVariable("NO_COLOR", "1"); run.expectExitCode(1); run.expectStdOutEqual("");