Merge remote-tracking branch 'origin/master' into stage2-zig-cc
This commit is contained in:
commit
0c70bb4fce
@ -9728,7 +9728,7 @@ const c = @cImport({
|
||||
<li>Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#}
|
||||
please!</li>
|
||||
</ul>
|
||||
<p>When a C pointer is pointing to a single struct (not an array), deference the C pointer to
|
||||
<p>When a C pointer is pointing to a single struct (not an array), dereference the C pointer to
|
||||
access to the struct's fields or member data. That syntax looks like
|
||||
this: </p>
|
||||
<p>{#syntax#}ptr_to_struct.*.struct_member{#endsyntax#}</p>
|
||||
|
@ -1188,6 +1188,7 @@ pub const LibExeObjStep = struct {
|
||||
emit_llvm_ir: bool = false,
|
||||
emit_asm: bool = false,
|
||||
emit_bin: bool = true,
|
||||
emit_docs: bool = false,
|
||||
emit_h: bool = false,
|
||||
bundle_compiler_rt: bool,
|
||||
disable_stack_probing: bool,
|
||||
@ -2033,6 +2034,7 @@ pub const LibExeObjStep = struct {
|
||||
if (self.emit_llvm_ir) try zig_args.append("-femit-llvm-ir");
|
||||
if (self.emit_asm) try zig_args.append("-femit-asm");
|
||||
if (!self.emit_bin) try zig_args.append("-fno-emit-bin");
|
||||
if (self.emit_docs) try zig_args.append("-femit-docs");
|
||||
if (self.emit_h) try zig_args.append("-femit-h");
|
||||
|
||||
if (self.strip) {
|
||||
|
@ -35,6 +35,15 @@ pub const onetimeauth = struct {
|
||||
pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305;
|
||||
};
|
||||
|
||||
/// A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a
|
||||
/// strong key, suitable for cryptographic uses. It does this by salting and stretching the
|
||||
/// password. Salting injects non-secret random data, so that identical passwords will be converted
|
||||
/// into unique keys. Stretching applies a deliberately slow hashing function to frustrate
|
||||
/// brute-force guessing.
|
||||
pub const kdf = struct {
|
||||
pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2;
|
||||
};
|
||||
|
||||
/// Core functions, that should rarely be used directly by applications.
|
||||
pub const core = struct {
|
||||
pub const aes = @import("crypto/aes.zig");
|
||||
@ -70,6 +79,20 @@ const std = @import("std.zig");
|
||||
pub const randomBytes = std.os.getrandom;
|
||||
|
||||
test "crypto" {
|
||||
inline for (std.meta.declarations(@This())) |decl| {
|
||||
switch (decl.data) {
|
||||
.Type => |t| {
|
||||
std.meta.refAllDecls(t);
|
||||
},
|
||||
.Var => |v| {
|
||||
_ = v;
|
||||
},
|
||||
.Fn => |f| {
|
||||
_ = f;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
_ = @import("crypto/aes.zig");
|
||||
_ = @import("crypto/blake2.zig");
|
||||
_ = @import("crypto/blake3.zig");
|
||||
@ -77,6 +100,7 @@ test "crypto" {
|
||||
_ = @import("crypto/gimli.zig");
|
||||
_ = @import("crypto/hmac.zig");
|
||||
_ = @import("crypto/md5.zig");
|
||||
_ = @import("crypto/pbkdf2.zig");
|
||||
_ = @import("crypto/poly1305.zig");
|
||||
_ = @import("crypto/sha1.zig");
|
||||
_ = @import("crypto/sha2.zig");
|
||||
|
280
lib/std/crypto/pbkdf2.zig
Normal file
280
lib/std/crypto/pbkdf2.zig
Normal file
@ -0,0 +1,280 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2015-2020 Zig Contributors
|
||||
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||
// The MIT license requires this copyright notice to be included in all copies
|
||||
// and substantial portions of the software.
|
||||
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const maxInt = std.math.maxInt;
|
||||
|
||||
// RFC 2898 Section 5.2
|
||||
//
|
||||
// FromSpec:
|
||||
//
|
||||
// PBKDF2 applies a pseudorandom function (see Appendix B.1 for an
|
||||
// example) to derive keys. The length of the derived key is essentially
|
||||
// unbounded. (However, the maximum effective search space for the
|
||||
// derived key may be limited by the structure of the underlying
|
||||
// pseudorandom function. See Appendix B.1 for further discussion.)
|
||||
// PBKDF2 is recommended for new applications.
|
||||
//
|
||||
// PBKDF2 (P, S, c, dkLen)
|
||||
//
|
||||
// Options: PRF underlying pseudorandom function (hLen
|
||||
// denotes the length in octets of the
|
||||
// pseudorandom function output)
|
||||
//
|
||||
// Input: P password, an octet string
|
||||
// S salt, an octet string
|
||||
// c iteration count, a positive integer
|
||||
// dkLen intended length in octets of the derived
|
||||
// key, a positive integer, at most
|
||||
// (2^32 - 1) * hLen
|
||||
//
|
||||
// Output: DK derived key, a dkLen-octet string
|
||||
|
||||
// Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini.
|
||||
|
||||
pub const Pbkdf2Error = error{
|
||||
/// At least one round is required
|
||||
TooFewRounds,
|
||||
|
||||
/// Maximum length of the derived key is `maxInt(u32) * Prf.mac_length`
|
||||
DerivedKeyTooLong,
|
||||
};
|
||||
|
||||
/// Apply PBKDF2 to generate a key from a password.
|
||||
///
|
||||
/// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132.
|
||||
///
|
||||
/// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length.
|
||||
/// May be uninitialized. All bytes will be overwritten.
|
||||
/// Maximum size is `maxInt(u32) * Hash.digest_length`
|
||||
/// It is a programming error to pass buffer longer than the maximum size.
|
||||
///
|
||||
/// password: Arbitrary sequence of bytes of any length, including empty.
|
||||
///
|
||||
/// salt: Arbitrary sequence of bytes of any length, including empty. A common length is 8 bytes.
|
||||
///
|
||||
/// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000.
|
||||
/// Larger iteration counts improve security by increasing the time required to compute
|
||||
/// the derivedKey. It is common to tune this parameter to achieve approximately 100ms.
|
||||
///
|
||||
/// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`.
|
||||
pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Pbkdf2Error!void {
|
||||
if (rounds < 1) return error.TooFewRounds;
|
||||
|
||||
const dkLen = derivedKey.len;
|
||||
const hLen = Prf.mac_length;
|
||||
comptime std.debug.assert(hLen >= 1);
|
||||
|
||||
// FromSpec:
|
||||
//
|
||||
// 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and
|
||||
// stop.
|
||||
//
|
||||
if (comptime (maxInt(usize) > maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) {
|
||||
// If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds
|
||||
return error.DerivedKeyTooLong;
|
||||
}
|
||||
|
||||
// FromSpec:
|
||||
//
|
||||
// 2. Let l be the number of hLen-long blocks of bytes in the derived key,
|
||||
// rounding up, and let r be the number of bytes in the last
|
||||
// block
|
||||
//
|
||||
|
||||
// l will not overflow, proof:
|
||||
// let `L(dkLen, hLen) = (dkLen + hLen - 1) / hLen`
|
||||
// then `L^-1(l, hLen) = l*hLen - hLen + 1`
|
||||
// 1) L^-1(maxInt(u32), hLen) <= maxInt(u32)*hLen
|
||||
// 2) maxInt(u32)*hLen - hLen + 1 <= maxInt(u32)*hLen // subtract maxInt(u32)*hLen + 1
|
||||
// 3) -hLen <= -1 // multiply by -1
|
||||
// 4) hLen >= 1
|
||||
const r_ = dkLen % hLen;
|
||||
const l = @intCast(u32, (dkLen / hLen) + @as(u1, if (r_ == 0) 0 else 1)); // original: (dkLen + hLen - 1) / hLen
|
||||
const r = if (r_ == 0) hLen else r_;
|
||||
|
||||
// FromSpec:
|
||||
//
|
||||
// 3. For each block of the derived key apply the function F defined
|
||||
// below to the password P, the salt S, the iteration count c, and
|
||||
// the block index to compute the block:
|
||||
//
|
||||
// T_1 = F (P, S, c, 1) ,
|
||||
// T_2 = F (P, S, c, 2) ,
|
||||
// ...
|
||||
// T_l = F (P, S, c, l) ,
|
||||
//
|
||||
// where the function F is defined as the exclusive-or sum of the
|
||||
// first c iterates of the underlying pseudorandom function PRF
|
||||
// applied to the password P and the concatenation of the salt S
|
||||
// and the block index i:
|
||||
//
|
||||
// F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
|
||||
//
|
||||
// where
|
||||
//
|
||||
// U_1 = PRF (P, S || INT (i)) ,
|
||||
// U_2 = PRF (P, U_1) ,
|
||||
// ...
|
||||
// U_c = PRF (P, U_{c-1}) .
|
||||
//
|
||||
// Here, INT (i) is a four-octet encoding of the integer i, most
|
||||
// significant octet first.
|
||||
//
|
||||
// 4. Concatenate the blocks and extract the first dkLen octets to
|
||||
// produce a derived key DK:
|
||||
//
|
||||
// DK = T_1 || T_2 || ... || T_l<0..r-1>
|
||||
var block: u32 = 0; // Spec limits to u32
|
||||
while (block < l) : (block += 1) {
|
||||
var prevBlock: [hLen]u8 = undefined;
|
||||
var newBlock: [hLen]u8 = undefined;
|
||||
|
||||
// U_1 = PRF (P, S || INT (i))
|
||||
const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001
|
||||
var ctx = Prf.init(password);
|
||||
ctx.update(salt);
|
||||
ctx.update(blockIndex[0..]);
|
||||
ctx.final(prevBlock[0..]);
|
||||
|
||||
// Choose portion of DK to write into (T_n) and initialize
|
||||
const offset = block * hLen;
|
||||
const blockLen = if (block != l - 1) hLen else r;
|
||||
const dkBlock: []u8 = derivedKey[offset..][0..blockLen];
|
||||
mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]);
|
||||
|
||||
var i: u32 = 1;
|
||||
while (i < rounds) : (i += 1) {
|
||||
// U_c = PRF (P, U_{c-1})
|
||||
Prf.create(&newBlock, prevBlock[0..], password);
|
||||
mem.copy(u8, prevBlock[0..], newBlock[0..]);
|
||||
|
||||
// F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
|
||||
for (dkBlock) |_, j| {
|
||||
dkBlock[j] ^= newBlock[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const htest = @import("test.zig");
|
||||
const HmacSha1 = std.crypto.auth.hmac.HmacSha1;
|
||||
|
||||
// RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors
|
||||
test "RFC 6070 one iteration" {
|
||||
const p = "password";
|
||||
const s = "salt";
|
||||
const c = 1;
|
||||
const dkLen = 20;
|
||||
|
||||
var derivedKey: [dkLen]u8 = undefined;
|
||||
|
||||
try pbkdf2(&derivedKey, p, s, c, HmacSha1);
|
||||
|
||||
const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6";
|
||||
|
||||
htest.assertEqual(expected, derivedKey[0..]);
|
||||
}
|
||||
|
||||
test "RFC 6070 two iterations" {
|
||||
const p = "password";
|
||||
const s = "salt";
|
||||
const c = 2;
|
||||
const dkLen = 20;
|
||||
|
||||
var derivedKey: [dkLen]u8 = undefined;
|
||||
|
||||
try pbkdf2(&derivedKey, p, s, c, HmacSha1);
|
||||
|
||||
const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957";
|
||||
|
||||
htest.assertEqual(expected, derivedKey[0..]);
|
||||
}
|
||||
|
||||
test "RFC 6070 4096 iterations" {
|
||||
const p = "password";
|
||||
const s = "salt";
|
||||
const c = 4096;
|
||||
const dkLen = 20;
|
||||
|
||||
var derivedKey: [dkLen]u8 = undefined;
|
||||
|
||||
try pbkdf2(&derivedKey, p, s, c, HmacSha1);
|
||||
|
||||
const expected = "4b007901b765489abead49d926f721d065a429c1";
|
||||
|
||||
htest.assertEqual(expected, derivedKey[0..]);
|
||||
}
|
||||
|
||||
test "RFC 6070 16,777,216 iterations" {
|
||||
// These iteration tests are slow so we always skip them. Results have been verified.
|
||||
if (true) {
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
const p = "password";
|
||||
const s = "salt";
|
||||
const c = 16777216;
|
||||
const dkLen = 20;
|
||||
|
||||
var derivedKey = [_]u8{0} ** dkLen;
|
||||
|
||||
try pbkdf2(&derivedKey, p, s, c, HmacSha1);
|
||||
|
||||
const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984";
|
||||
|
||||
htest.assertEqual(expected, derivedKey[0..]);
|
||||
}
|
||||
|
||||
test "RFC 6070 multi-block salt and password" {
|
||||
const p = "passwordPASSWORDpassword";
|
||||
const s = "saltSALTsaltSALTsaltSALTsaltSALTsalt";
|
||||
const c = 4096;
|
||||
const dkLen = 25;
|
||||
|
||||
var derivedKey: [dkLen]u8 = undefined;
|
||||
|
||||
try pbkdf2(&derivedKey, p, s, c, HmacSha1);
|
||||
|
||||
const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038";
|
||||
|
||||
htest.assertEqual(expected, derivedKey[0..]);
|
||||
}
|
||||
|
||||
test "RFC 6070 embedded NUL" {
|
||||
const p = "pass\x00word";
|
||||
const s = "sa\x00lt";
|
||||
const c = 4096;
|
||||
const dkLen = 16;
|
||||
|
||||
var derivedKey: [dkLen]u8 = undefined;
|
||||
|
||||
try pbkdf2(&derivedKey, p, s, c, HmacSha1);
|
||||
|
||||
const expected = "56fa6aa75548099dcc37d7f03425e0c3";
|
||||
|
||||
htest.assertEqual(expected, derivedKey[0..]);
|
||||
}
|
||||
|
||||
test "Very large dkLen" {
|
||||
// This test allocates 8GB of memory and is expected to take several hours to run.
|
||||
if (true) {
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
const p = "password";
|
||||
const s = "salt";
|
||||
const c = 1;
|
||||
const dkLen = 1 << 33;
|
||||
|
||||
var derivedKey = try std.testing.allocator.alloc(u8, dkLen);
|
||||
defer {
|
||||
std.testing.allocator.free(derivedKey);
|
||||
}
|
||||
|
||||
try pbkdf2(derivedKey, p, s, c, HmacSha1);
|
||||
// Just verify this doesn't crash with an overflow
|
||||
}
|
@ -218,8 +218,9 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize)
|
||||
}
|
||||
|
||||
/// Return an authentication tag for the current state
|
||||
/// Assumes `out` is less than or equal to `mac_length`.
|
||||
pub fn final(self: *Self, out: []u8) void {
|
||||
std.debug.assert(out.len >= mac_length);
|
||||
std.debug.assert(out.len <= mac_length);
|
||||
mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len]));
|
||||
}
|
||||
|
||||
|
139
lib/std/fmt.zig
139
lib/std/fmt.zig
@ -22,7 +22,7 @@ pub const Alignment = enum {
|
||||
pub const FormatOptions = struct {
|
||||
precision: ?usize = null,
|
||||
width: ?usize = null,
|
||||
alignment: Alignment = .Left,
|
||||
alignment: Alignment = .Right,
|
||||
fill: u8 = ' ',
|
||||
};
|
||||
|
||||
@ -327,7 +327,7 @@ pub fn formatType(
|
||||
max_depth: usize,
|
||||
) @TypeOf(writer).Error!void {
|
||||
if (comptime std.mem.eql(u8, fmt, "*")) {
|
||||
try writer.writeAll(@typeName(@typeInfo(@TypeOf(value)).Pointer.child));
|
||||
try writer.writeAll(@typeName(std.meta.Child(@TypeOf(value))));
|
||||
try writer.writeAll("@");
|
||||
try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer);
|
||||
return;
|
||||
@ -631,26 +631,22 @@ pub fn formatBuf(
|
||||
writer: anytype,
|
||||
) !void {
|
||||
const width = options.width orelse buf.len;
|
||||
var padding = if (width > buf.len) (width - buf.len) else 0;
|
||||
const pad_byte = [1]u8{options.fill};
|
||||
const padding = if (width > buf.len) (width - buf.len) else 0;
|
||||
|
||||
switch (options.alignment) {
|
||||
.Left => {
|
||||
try writer.writeAll(buf);
|
||||
while (padding > 0) : (padding -= 1) {
|
||||
try writer.writeAll(&pad_byte);
|
||||
}
|
||||
try writer.writeByteNTimes(options.fill, padding);
|
||||
},
|
||||
.Center => {
|
||||
const padl = padding / 2;
|
||||
var i: usize = 0;
|
||||
while (i < padl) : (i += 1) try writer.writeAll(&pad_byte);
|
||||
const left_padding = padding / 2;
|
||||
const right_padding = (padding + 1) / 2;
|
||||
try writer.writeByteNTimes(options.fill, left_padding);
|
||||
try writer.writeAll(buf);
|
||||
while (i < padding) : (i += 1) try writer.writeAll(&pad_byte);
|
||||
try writer.writeByteNTimes(options.fill, right_padding);
|
||||
},
|
||||
.Right => {
|
||||
while (padding > 0) : (padding -= 1) {
|
||||
try writer.writeAll(&pad_byte);
|
||||
}
|
||||
try writer.writeByteNTimes(options.fill, padding);
|
||||
try writer.writeAll(buf);
|
||||
},
|
||||
}
|
||||
@ -941,61 +937,27 @@ pub fn formatInt(
|
||||
options: FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
assert(base >= 2);
|
||||
|
||||
const int_value = if (@TypeOf(value) == comptime_int) blk: {
|
||||
const Int = math.IntFittingRange(value, value);
|
||||
break :blk @as(Int, value);
|
||||
} else
|
||||
value;
|
||||
|
||||
if (@typeInfo(@TypeOf(int_value)).Int.is_signed) {
|
||||
return formatIntSigned(int_value, base, uppercase, options, writer);
|
||||
} else {
|
||||
return formatIntUnsigned(int_value, base, uppercase, options, writer);
|
||||
}
|
||||
}
|
||||
const value_info = @typeInfo(@TypeOf(int_value)).Int;
|
||||
|
||||
fn formatIntSigned(
|
||||
value: anytype,
|
||||
base: u8,
|
||||
uppercase: bool,
|
||||
options: FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
const new_options = FormatOptions{
|
||||
.width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
|
||||
.precision = options.precision,
|
||||
.fill = options.fill,
|
||||
};
|
||||
const bit_count = @typeInfo(@TypeOf(value)).Int.bits;
|
||||
const Uint = std.meta.Int(false, bit_count);
|
||||
if (value < 0) {
|
||||
try writer.writeAll("-");
|
||||
const new_value = math.absCast(value);
|
||||
return formatIntUnsigned(new_value, base, uppercase, new_options, writer);
|
||||
} else if (options.width == null or options.width.? == 0) {
|
||||
return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, writer);
|
||||
} else {
|
||||
try writer.writeAll("+");
|
||||
const new_value = @intCast(Uint, value);
|
||||
return formatIntUnsigned(new_value, base, uppercase, new_options, writer);
|
||||
}
|
||||
}
|
||||
// The type must have the same size as `base` or be wider in order for the
|
||||
// division to work
|
||||
const min_int_bits = comptime math.max(value_info.bits, 8);
|
||||
const MinInt = std.meta.Int(false, min_int_bits);
|
||||
|
||||
fn formatIntUnsigned(
|
||||
value: anytype,
|
||||
base: u8,
|
||||
uppercase: bool,
|
||||
options: FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
assert(base >= 2);
|
||||
const value_info = @typeInfo(@TypeOf(value)).Int;
|
||||
var buf: [math.max(value_info.bits, 1)]u8 = undefined;
|
||||
const min_int_bits = comptime math.max(value_info.bits, @typeInfo(@TypeOf(base)).Int.bits);
|
||||
const MinInt = std.meta.Int(value_info.is_signed, min_int_bits);
|
||||
var a: MinInt = value;
|
||||
const abs_value = math.absCast(int_value);
|
||||
// The worst case in terms of space needed is base 2, plus 1 for the sign
|
||||
var buf: [1 + math.max(value_info.bits, 1)]u8 = undefined;
|
||||
|
||||
var a: MinInt = abs_value;
|
||||
var index: usize = buf.len;
|
||||
|
||||
while (true) {
|
||||
const digit = a % base;
|
||||
index -= 1;
|
||||
@ -1004,25 +966,21 @@ fn formatIntUnsigned(
|
||||
if (a == 0) break;
|
||||
}
|
||||
|
||||
const digits_buf = buf[index..];
|
||||
const width = options.width orelse 0;
|
||||
const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0;
|
||||
|
||||
if (padding > index) {
|
||||
const zero_byte: u8 = options.fill;
|
||||
var leftover_padding = padding - index;
|
||||
while (true) {
|
||||
try writer.writeAll(@as(*const [1]u8, &zero_byte)[0..]);
|
||||
leftover_padding -= 1;
|
||||
if (leftover_padding == 0) break;
|
||||
if (value_info.is_signed) {
|
||||
if (value < 0) {
|
||||
// Negative integer
|
||||
index -= 1;
|
||||
buf[index] = '-';
|
||||
} else if (options.width == null or options.width.? == 0) {
|
||||
// Positive integer, omit the plus sign
|
||||
} else {
|
||||
// Positive integer
|
||||
index -= 1;
|
||||
buf[index] = '+';
|
||||
}
|
||||
mem.set(u8, buf[0..index], options.fill);
|
||||
return writer.writeAll(&buf);
|
||||
} else {
|
||||
const padded_buf = buf[index - padding ..];
|
||||
mem.set(u8, padded_buf[0..padding], options.fill);
|
||||
return writer.writeAll(padded_buf);
|
||||
}
|
||||
|
||||
return formatBuf(buf[index..], options, writer);
|
||||
}
|
||||
|
||||
pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize {
|
||||
@ -1246,6 +1204,10 @@ test "optional" {
|
||||
const value: ?i32 = null;
|
||||
try testFmt("optional: null\n", "optional: {}\n", .{value});
|
||||
}
|
||||
{
|
||||
const value = @intToPtr(?*i32, 0xf000d000);
|
||||
try testFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value});
|
||||
}
|
||||
}
|
||||
|
||||
test "error" {
|
||||
@ -1283,7 +1245,17 @@ test "int.specifier" {
|
||||
|
||||
test "int.padded" {
|
||||
try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)});
|
||||
try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)});
|
||||
try testFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)});
|
||||
try testFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)});
|
||||
try testFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)});
|
||||
try testFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)});
|
||||
try testFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)});
|
||||
try testFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)});
|
||||
try testFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)});
|
||||
try testFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)});
|
||||
try testFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)});
|
||||
try testFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)});
|
||||
try testFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)});
|
||||
}
|
||||
|
||||
test "buffer" {
|
||||
@ -1329,7 +1301,7 @@ test "slice" {
|
||||
try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value});
|
||||
}
|
||||
|
||||
try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"});
|
||||
try testFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"});
|
||||
try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"});
|
||||
}
|
||||
|
||||
@ -1362,7 +1334,7 @@ test "cstr" {
|
||||
.{@ptrCast([*c]const u8, "Test C")},
|
||||
);
|
||||
try testFmt(
|
||||
"cstr: Test C \n",
|
||||
"cstr: Test C\n",
|
||||
"cstr: {s:10}\n",
|
||||
.{@ptrCast([*c]const u8, "Test C")},
|
||||
);
|
||||
@ -1805,7 +1777,7 @@ test "vector" {
|
||||
|
||||
try testFmt("{ true, false, true, false }", "{}", .{vbool});
|
||||
try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64});
|
||||
try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64});
|
||||
try testFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64});
|
||||
try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64});
|
||||
try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64});
|
||||
try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64});
|
||||
@ -1818,15 +1790,16 @@ test "enum-literal" {
|
||||
|
||||
test "padding" {
|
||||
try testFmt("Simple", "{}", .{"Simple"});
|
||||
try testFmt("true ", "{:10}", .{true});
|
||||
try testFmt(" true", "{:10}", .{true});
|
||||
try testFmt(" true", "{:>10}", .{true});
|
||||
try testFmt("======true", "{:=>10}", .{true});
|
||||
try testFmt("true======", "{:=<10}", .{true});
|
||||
try testFmt(" true ", "{:^10}", .{true});
|
||||
try testFmt("===true===", "{:=^10}", .{true});
|
||||
try testFmt("Minimum width", "{:18} width", .{"Minimum"});
|
||||
try testFmt(" Minimum width", "{:18} width", .{"Minimum"});
|
||||
try testFmt("==================Filled", "{:=>24}", .{"Filled"});
|
||||
try testFmt(" Centered ", "{:^24}", .{"Centered"});
|
||||
try testFmt("-", "{:-^1}", .{""});
|
||||
}
|
||||
|
||||
test "decimal float padding" {
|
||||
|
@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig");
|
||||
|
||||
// TODO audit these APIs with respect to Dir and absolute paths
|
||||
|
||||
pub const rename = os.rename;
|
||||
pub const renameZ = os.renameZ;
|
||||
pub const renameC = @compileError("deprecated: renamed to renameZ");
|
||||
pub const renameW = os.renameW;
|
||||
pub const realpath = os.realpath;
|
||||
pub const realpathZ = os.realpathZ;
|
||||
pub const realpathC = @compileError("deprecated: renamed to realpathZ");
|
||||
@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
|
||||
base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
|
||||
|
||||
if (cwd().symLink(existing_path, tmp_path, .{})) {
|
||||
return rename(tmp_path, new_path);
|
||||
return cwd().rename(tmp_path, new_path);
|
||||
} else |err| switch (err) {
|
||||
error.PathAlreadyExists => continue,
|
||||
else => return err, // TODO zig should know this set does not include PathAlreadyExists
|
||||
@ -255,6 +251,45 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void {
|
||||
return os.rmdirW(dir_path);
|
||||
}
|
||||
|
||||
pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ");
|
||||
|
||||
/// Same as `Dir.rename` except the paths are absolute.
|
||||
pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void {
|
||||
assert(path.isAbsolute(old_path));
|
||||
assert(path.isAbsolute(new_path));
|
||||
return os.rename(old_path, new_path);
|
||||
}
|
||||
|
||||
/// Same as `renameAbsolute` except the path parameters are null-terminated.
|
||||
pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void {
|
||||
assert(path.isAbsoluteZ(old_path));
|
||||
assert(path.isAbsoluteZ(new_path));
|
||||
return os.renameZ(old_path, new_path);
|
||||
}
|
||||
|
||||
/// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows.
|
||||
pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void {
|
||||
assert(path.isAbsoluteWindowsW(old_path));
|
||||
assert(path.isAbsoluteWindowsW(new_path));
|
||||
return os.renameW(old_path, new_path);
|
||||
}
|
||||
|
||||
/// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir`
|
||||
pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void {
|
||||
return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path);
|
||||
}
|
||||
|
||||
/// Same as `rename` except the parameters are null-terminated.
|
||||
pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void {
|
||||
return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z);
|
||||
}
|
||||
|
||||
/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
|
||||
/// This function is Windows-only.
|
||||
pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void {
|
||||
return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w);
|
||||
}
|
||||
|
||||
pub const Dir = struct {
|
||||
fd: os.fd_t,
|
||||
|
||||
@ -1338,6 +1373,27 @@ pub const Dir = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub const RenameError = os.RenameError;
|
||||
|
||||
/// Change the name or location of a file or directory.
|
||||
/// If new_sub_path already exists, it will be replaced.
|
||||
/// Renaming a file over an existing directory or a directory
|
||||
/// over an existing file will fail with `error.IsDir` or `error.NotDir`
|
||||
pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void {
|
||||
return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path);
|
||||
}
|
||||
|
||||
/// Same as `rename` except the parameters are null-terminated.
|
||||
pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void {
|
||||
return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z);
|
||||
}
|
||||
|
||||
/// Same as `rename` except the parameters are UTF16LE, NT prefixed.
|
||||
/// This function is Windows-only.
|
||||
pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void {
|
||||
return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w);
|
||||
}
|
||||
|
||||
/// Creates a symbolic link named `sym_link_path` which contains the string `target_path`.
|
||||
/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent
|
||||
/// one; the latter case is known as a dangling link.
|
||||
|
@ -274,6 +274,167 @@ test "file operations on directories" {
|
||||
dir.close();
|
||||
}
|
||||
|
||||
test "Dir.rename files" {
|
||||
var tmp_dir = tmpDir(.{});
|
||||
defer tmp_dir.cleanup();
|
||||
|
||||
testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else"));
|
||||
|
||||
// Renaming files
|
||||
const test_file_name = "test_file";
|
||||
const renamed_test_file_name = "test_file_renamed";
|
||||
var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
|
||||
file.close();
|
||||
try tmp_dir.dir.rename(test_file_name, renamed_test_file_name);
|
||||
|
||||
// Ensure the file was renamed
|
||||
testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
|
||||
file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
|
||||
file.close();
|
||||
|
||||
// Rename to self succeeds
|
||||
try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name);
|
||||
|
||||
// Rename to existing file succeeds
|
||||
var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true });
|
||||
existing_file.close();
|
||||
try tmp_dir.dir.rename(renamed_test_file_name, "existing_file");
|
||||
|
||||
testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{}));
|
||||
file = try tmp_dir.dir.openFile("existing_file", .{});
|
||||
file.close();
|
||||
}
|
||||
|
||||
test "Dir.rename directories" {
|
||||
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
|
||||
if (builtin.os.tag == .windows) return error.SkipZigTest;
|
||||
|
||||
var tmp_dir = tmpDir(.{});
|
||||
defer tmp_dir.cleanup();
|
||||
|
||||
// Renaming directories
|
||||
try tmp_dir.dir.makeDir("test_dir");
|
||||
try tmp_dir.dir.rename("test_dir", "test_dir_renamed");
|
||||
|
||||
// Ensure the directory was renamed
|
||||
testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{}));
|
||||
var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{});
|
||||
|
||||
// Put a file in the directory
|
||||
var file = try dir.createFile("test_file", .{ .read = true });
|
||||
file.close();
|
||||
dir.close();
|
||||
|
||||
try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again");
|
||||
|
||||
// Ensure the directory was renamed and the file still exists in it
|
||||
testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{}));
|
||||
dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
|
||||
file = try dir.openFile("test_file", .{});
|
||||
file.close();
|
||||
dir.close();
|
||||
|
||||
// Try to rename to a non-empty directory now
|
||||
var target_dir = try tmp_dir.dir.makeOpenPath("non_empty_target_dir", .{});
|
||||
file = try target_dir.createFile("filler", .{ .read = true });
|
||||
file.close();
|
||||
|
||||
testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir_renamed_again", "non_empty_target_dir"));
|
||||
|
||||
// Ensure the directory was not renamed
|
||||
dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{});
|
||||
file = try dir.openFile("test_file", .{});
|
||||
file.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
test "Dir.rename file <-> dir" {
|
||||
// TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364
|
||||
if (builtin.os.tag == .windows) return error.SkipZigTest;
|
||||
|
||||
var tmp_dir = tmpDir(.{});
|
||||
defer tmp_dir.cleanup();
|
||||
|
||||
var file = try tmp_dir.dir.createFile("test_file", .{ .read = true });
|
||||
file.close();
|
||||
try tmp_dir.dir.makeDir("test_dir");
|
||||
testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir"));
|
||||
testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file"));
|
||||
}
|
||||
|
||||
test "rename" {
|
||||
var tmp_dir1 = tmpDir(.{});
|
||||
defer tmp_dir1.cleanup();
|
||||
|
||||
var tmp_dir2 = tmpDir(.{});
|
||||
defer tmp_dir2.cleanup();
|
||||
|
||||
// Renaming files
|
||||
const test_file_name = "test_file";
|
||||
const renamed_test_file_name = "test_file_renamed";
|
||||
var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true });
|
||||
file.close();
|
||||
try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name);
|
||||
|
||||
// ensure the file was renamed
|
||||
testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{}));
|
||||
file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{});
|
||||
file.close();
|
||||
}
|
||||
|
||||
test "renameAbsolute" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
var tmp_dir = tmpDir(.{});
|
||||
defer tmp_dir.cleanup();
|
||||
|
||||
// Get base abs path
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const allocator = &arena.allocator;
|
||||
|
||||
const base_path = blk: {
|
||||
const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] });
|
||||
break :blk try fs.realpathAlloc(&arena.allocator, relative_path);
|
||||
};
|
||||
|
||||
testing.expectError(error.FileNotFound, fs.renameAbsolute(
|
||||
try fs.path.join(allocator, &[_][]const u8{ base_path, "missing_file_name" }),
|
||||
try fs.path.join(allocator, &[_][]const u8{ base_path, "something_else" }),
|
||||
));
|
||||
|
||||
// Renaming files
|
||||
const test_file_name = "test_file";
|
||||
const renamed_test_file_name = "test_file_renamed";
|
||||
var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true });
|
||||
file.close();
|
||||
try fs.renameAbsolute(
|
||||
try fs.path.join(allocator, &[_][]const u8{ base_path, test_file_name }),
|
||||
try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_file_name }),
|
||||
);
|
||||
|
||||
// ensure the file was renamed
|
||||
testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{}));
|
||||
file = try tmp_dir.dir.openFile(renamed_test_file_name, .{});
|
||||
const stat = try file.stat();
|
||||
testing.expect(stat.kind == .File);
|
||||
file.close();
|
||||
|
||||
// Renaming directories
|
||||
const test_dir_name = "test_dir";
|
||||
const renamed_test_dir_name = "test_dir_renamed";
|
||||
try tmp_dir.dir.makeDir(test_dir_name);
|
||||
try fs.renameAbsolute(
|
||||
try fs.path.join(allocator, &[_][]const u8{ base_path, test_dir_name }),
|
||||
try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_dir_name }),
|
||||
);
|
||||
|
||||
// ensure the directory was renamed
|
||||
testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{}));
|
||||
var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{});
|
||||
dir.close();
|
||||
}
|
||||
|
||||
test "openSelfExe" {
|
||||
if (builtin.os.tag == .wasi) return error.SkipZigTest;
|
||||
|
||||
|
@ -489,7 +489,7 @@ pub const HeapAllocator = switch (builtin.os.tag) {
|
||||
const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr);
|
||||
assert(full_len != std.math.maxInt(usize));
|
||||
assert(full_len >= amt);
|
||||
break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr), len_align);
|
||||
break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr) - @sizeOf(usize), len_align);
|
||||
};
|
||||
const buf = @intToPtr([*]u8, aligned_addr)[0..return_len];
|
||||
getRecordPtr(buf).* = root_addr;
|
||||
|
@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError
|
||||
return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir });
|
||||
}
|
||||
|
||||
const RenameError = error{
|
||||
pub const RenameError = error{
|
||||
/// In WASI, this error may occur when the file descriptor does
|
||||
/// not hold the required rights to rename a resource by path relative to it.
|
||||
AccessDenied,
|
||||
@ -2107,6 +2107,7 @@ pub fn renameatW(
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
|
||||
.NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
|
||||
else => return windows.unexpectedStatus(rc),
|
||||
}
|
||||
}
|
||||
|
@ -830,7 +830,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
|
||||
}
|
||||
}
|
||||
|
||||
pub const MoveFileError = error{Unexpected};
|
||||
pub const MoveFileError = error{ FileNotFound, Unexpected };
|
||||
|
||||
pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void {
|
||||
const old_path_w = try sliceToPrefixedFileW(old_path);
|
||||
@ -841,6 +841,7 @@ pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) Move
|
||||
pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void {
|
||||
if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
.FILE_NOT_FOUND => return error.FileNotFound,
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
}
|
||||
|
@ -3161,22 +3161,7 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) {
|
||||
tag_type->data.enumeration.fields_by_name.init(field_count);
|
||||
tag_type->data.enumeration.decls_scope = union_type->data.unionation.decls_scope;
|
||||
} else if (enum_type_node != nullptr) {
|
||||
ZigType *enum_type = analyze_type_expr(g, scope, enum_type_node);
|
||||
if (type_is_invalid(enum_type)) {
|
||||
union_type->data.unionation.resolve_status = ResolveStatusInvalid;
|
||||
return ErrorSemanticAnalyzeFail;
|
||||
}
|
||||
if (enum_type->id != ZigTypeIdEnum) {
|
||||
union_type->data.unionation.resolve_status = ResolveStatusInvalid;
|
||||
add_node_error(g, enum_type_node,
|
||||
buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&enum_type->name)));
|
||||
return ErrorSemanticAnalyzeFail;
|
||||
}
|
||||
if ((err = type_resolve(g, enum_type, ResolveStatusAlignmentKnown))) {
|
||||
assert(g->errors.length != 0);
|
||||
return err;
|
||||
}
|
||||
tag_type = enum_type;
|
||||
tag_type = analyze_type_expr(g, scope, enum_type_node);
|
||||
} else {
|
||||
if (decl_node->type == NodeTypeContainerDecl) {
|
||||
tag_type = nullptr;
|
||||
@ -3185,6 +3170,20 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) {
|
||||
}
|
||||
}
|
||||
if (tag_type != nullptr) {
|
||||
if (type_is_invalid(tag_type)) {
|
||||
union_type->data.unionation.resolve_status = ResolveStatusInvalid;
|
||||
return ErrorSemanticAnalyzeFail;
|
||||
}
|
||||
if (tag_type->id != ZigTypeIdEnum) {
|
||||
union_type->data.unionation.resolve_status = ResolveStatusInvalid;
|
||||
add_node_error(g, enum_type_node != nullptr ? enum_type_node : decl_node,
|
||||
buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&tag_type->name)));
|
||||
return ErrorSemanticAnalyzeFail;
|
||||
}
|
||||
if ((err = type_resolve(g, tag_type, ResolveStatusAlignmentKnown))) {
|
||||
assert(g->errors.length != 0);
|
||||
return err;
|
||||
}
|
||||
covered_enum_fields = heap::c_allocator.allocate<bool>(tag_type->data.enumeration.src_field_count);
|
||||
}
|
||||
union_type->data.unionation.tag_type = tag_type;
|
||||
|
@ -63,6 +63,7 @@ enum ConstCastResultId {
|
||||
ConstCastResultIdPointerChild,
|
||||
ConstCastResultIdSliceChild,
|
||||
ConstCastResultIdOptionalChild,
|
||||
ConstCastResultIdOptionalShape,
|
||||
ConstCastResultIdErrorUnionPayload,
|
||||
ConstCastResultIdErrorUnionErrorSet,
|
||||
ConstCastResultIdFnAlign,
|
||||
@ -11946,8 +11947,22 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
|
||||
}
|
||||
}
|
||||
|
||||
// maybe
|
||||
// optional types
|
||||
if (wanted_type->id == ZigTypeIdOptional && actual_type->id == ZigTypeIdOptional) {
|
||||
// Consider the case where the wanted type is ??[*]T and the actual one
|
||||
// is ?[*]T, we cannot turn the former into the latter even though the
|
||||
// child types are compatible (?[*]T and [*]T are both represented as a
|
||||
// pointer). The extra level of indirection in ??[*]T means it's
|
||||
// represented as a regular, fat, optional type and, as a consequence,
|
||||
// has a different shape than the one of ?[*]T.
|
||||
if ((wanted_ptr_type != nullptr) != (actual_ptr_type != nullptr)) {
|
||||
// The use of type_mismatch is intentional
|
||||
result.id = ConstCastResultIdOptionalShape;
|
||||
result.data.type_mismatch = heap::c_allocator.allocate_nonzero<ConstCastTypeMismatch>(1);
|
||||
result.data.type_mismatch->wanted_type = wanted_type;
|
||||
result.data.type_mismatch->actual_type = actual_type;
|
||||
return result;
|
||||
}
|
||||
ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.maybe.child_type,
|
||||
actual_type->data.maybe.child_type, source_node, wanted_is_mutable);
|
||||
if (child.id == ConstCastResultIdInvalid)
|
||||
@ -14549,6 +14564,13 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa
|
||||
report_recursive_error(ira, source_node, &cast_result->data.optional->child, msg);
|
||||
break;
|
||||
}
|
||||
case ConstCastResultIdOptionalShape: {
|
||||
add_error_note(ira->codegen, parent_msg, source_node,
|
||||
buf_sprintf("optional type child '%s' cannot cast into optional type '%s'",
|
||||
buf_ptr(&cast_result->data.type_mismatch->actual_type->name),
|
||||
buf_ptr(&cast_result->data.type_mismatch->wanted_type->name)));
|
||||
break;
|
||||
}
|
||||
case ConstCastResultIdErrorUnionErrorSet: {
|
||||
ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node,
|
||||
buf_sprintf("error set '%s' cannot cast into error set '%s'",
|
||||
|
@ -2032,7 +2032,7 @@ fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 {
|
||||
// Handle the remaining escapes Zig doesn't support by turning them
|
||||
// into their respective hex representation
|
||||
else => if (std.ascii.isCntrl(c))
|
||||
std.fmt.bufPrint(char_buf, "\\x{x:0<2}", .{c}) catch unreachable
|
||||
std.fmt.bufPrint(char_buf, "\\x{x:0>2}", .{c}) catch unreachable
|
||||
else
|
||||
std.fmt.bufPrint(char_buf, "{c}", .{c}) catch unreachable,
|
||||
};
|
||||
|
@ -849,3 +849,8 @@ test "comptime float casts" {
|
||||
expect(b == 2);
|
||||
expect(@TypeOf(b) == comptime_int);
|
||||
}
|
||||
|
||||
test "cast from ?[*]T to ??[*]T" {
|
||||
const a: ??[*]u8 = @as(?[*]u8, null);
|
||||
expect(a != null and a.? == null);
|
||||
}
|
||||
|
@ -374,3 +374,45 @@ test "Type.Union" {
|
||||
tagged = .{ .unsigned = 1 };
|
||||
testing.expectEqual(Tag.unsigned, tagged);
|
||||
}
|
||||
|
||||
test "Type.Union from Type.Enum" {
|
||||
const Tag = @Type(.{
|
||||
.Enum = .{
|
||||
.layout = .Auto,
|
||||
.tag_type = u0,
|
||||
.fields = &[_]TypeInfo.EnumField{
|
||||
.{ .name = "working_as_expected", .value = 0 },
|
||||
},
|
||||
.decls = &[_]TypeInfo.Declaration{},
|
||||
.is_exhaustive = true,
|
||||
},
|
||||
});
|
||||
const T = @Type(.{
|
||||
.Union = .{
|
||||
.layout = .Auto,
|
||||
.tag_type = Tag,
|
||||
.fields = &[_]TypeInfo.UnionField{
|
||||
.{ .name = "working_as_expected", .field_type = u32 },
|
||||
},
|
||||
.decls = &[_]TypeInfo.Declaration{},
|
||||
},
|
||||
});
|
||||
_ = T;
|
||||
_ = @typeInfo(T).Union;
|
||||
}
|
||||
|
||||
test "Type.Union from regular enum" {
|
||||
const E = enum { working_as_expected = 0 };
|
||||
const T = @Type(.{
|
||||
.Union = .{
|
||||
.layout = .Auto,
|
||||
.tag_type = E,
|
||||
.fields = &[_]TypeInfo.UnionField{
|
||||
.{ .name = "working_as_expected", .field_type = u32 },
|
||||
},
|
||||
.decls = &[_]TypeInfo.Declaration{},
|
||||
},
|
||||
});
|
||||
_ = T;
|
||||
_ = @typeInfo(T).Union;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user