| author | |
| committer | |
| log | 0c70bb4fce8b0460f86ed218f54ba31b291f2bfb |
| tree | 1e0ad8f9ea76b30e52753a2f25f5f1d18d25896d |
| parent | afac5d28951cfd913851094649e8b9f2136694ca |
| parent | 58ee5f4e61cd9b7a9ba65798e2214efa3753a733 |
16 files changed, 677 insertions(+), 110 deletions(-)
doc/langref.html.in+1-1| ... | @@ -9728,7 +9728,7 @@ const c = @cImport({ | ... | @@ -9728,7 +9728,7 @@ const c = @cImport({ |
| 9728 | <li>Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#} | 9728 | <li>Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#} |
| 9729 | please!</li> | 9729 | please!</li> |
| 9730 | </ul> | 9730 | </ul> |
| 9731 | <p>When a C pointer is pointing to a single struct (not an array), deference the C pointer to | 9731 | <p>When a C pointer is pointing to a single struct (not an array), dereference the C pointer to |
| 9732 | access to the struct's fields or member data. That syntax looks like | 9732 | access to the struct's fields or member data. That syntax looks like |
| 9733 | this: </p> | 9733 | this: </p> |
| 9734 | <p>{#syntax#}ptr_to_struct.*.struct_member{#endsyntax#}</p> | 9734 | <p>{#syntax#}ptr_to_struct.*.struct_member{#endsyntax#}</p> |
lib/std/build.zig+2| ... | @@ -1188,6 +1188,7 @@ pub const LibExeObjStep = struct { | ... | @@ -1188,6 +1188,7 @@ pub const LibExeObjStep = struct { |
| 1188 | emit_llvm_ir: bool = false, | 1188 | emit_llvm_ir: bool = false, |
| 1189 | emit_asm: bool = false, | 1189 | emit_asm: bool = false, |
| 1190 | emit_bin: bool = true, | 1190 | emit_bin: bool = true, |
| 1191 | emit_docs: bool = false, | ||
| 1191 | emit_h: bool = false, | 1192 | emit_h: bool = false, |
| 1192 | bundle_compiler_rt: bool, | 1193 | bundle_compiler_rt: bool, |
| 1193 | disable_stack_probing: bool, | 1194 | disable_stack_probing: bool, |
| ... | @@ -2033,6 +2034,7 @@ pub const LibExeObjStep = struct { | ... | @@ -2033,6 +2034,7 @@ pub const LibExeObjStep = struct { |
| 2033 | if (self.emit_llvm_ir) try zig_args.append("-femit-llvm-ir"); | 2034 | if (self.emit_llvm_ir) try zig_args.append("-femit-llvm-ir"); |
| 2034 | if (self.emit_asm) try zig_args.append("-femit-asm"); | 2035 | if (self.emit_asm) try zig_args.append("-femit-asm"); |
| 2035 | if (!self.emit_bin) try zig_args.append("-fno-emit-bin"); | 2036 | if (!self.emit_bin) try zig_args.append("-fno-emit-bin"); |
| 2037 | if (self.emit_docs) try zig_args.append("-femit-docs"); | ||
| 2036 | if (self.emit_h) try zig_args.append("-femit-h"); | 2038 | if (self.emit_h) try zig_args.append("-femit-h"); |
| 2037 | 2039 | ||
| 2038 | if (self.strip) { | 2040 | if (self.strip) { |
lib/std/crypto.zig+24| ... | @@ -35,6 +35,15 @@ pub const onetimeauth = struct { | ... | @@ -35,6 +35,15 @@ pub const onetimeauth = struct { |
| 35 | pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; | 35 | pub const Poly1305 = @import("crypto/poly1305.zig").Poly1305; |
| 36 | }; | 36 | }; |
| 37 | 37 | ||
| 38 | /// A Key Derivation Function (KDF) is intended to turn a weak, human generated password into a | ||
| 39 | /// strong key, suitable for cryptographic uses. It does this by salting and stretching the | ||
| 40 | /// password. Salting injects non-secret random data, so that identical passwords will be converted | ||
| 41 | /// into unique keys. Stretching applies a deliberately slow hashing function to frustrate | ||
| 42 | /// brute-force guessing. | ||
| 43 | pub const kdf = struct { | ||
| 44 | pub const pbkdf2 = @import("crypto/pbkdf2.zig").pbkdf2; | ||
| 45 | }; | ||
| 46 | |||
| 38 | /// Core functions, that should rarely be used directly by applications. | 47 | /// Core functions, that should rarely be used directly by applications. |
| 39 | pub const core = struct { | 48 | pub const core = struct { |
| 40 | pub const aes = @import("crypto/aes.zig"); | 49 | pub const aes = @import("crypto/aes.zig"); |
| ... | @@ -70,6 +79,20 @@ const std = @import("std.zig"); | ... | @@ -70,6 +79,20 @@ const std = @import("std.zig"); |
| 70 | pub const randomBytes = std.os.getrandom; | 79 | pub const randomBytes = std.os.getrandom; |
| 71 | 80 | ||
| 72 | test "crypto" { | 81 | test "crypto" { |
| 82 | inline for (std.meta.declarations(@This())) |decl| { | ||
| 83 | switch (decl.data) { | ||
| 84 | .Type => |t| { | ||
| 85 | std.meta.refAllDecls(t); | ||
| 86 | }, | ||
| 87 | .Var => |v| { | ||
| 88 | _ = v; | ||
| 89 | }, | ||
| 90 | .Fn => |f| { | ||
| 91 | _ = f; | ||
| 92 | }, | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 73 | _ = @import("crypto/aes.zig"); | 96 | _ = @import("crypto/aes.zig"); |
| 74 | _ = @import("crypto/blake2.zig"); | 97 | _ = @import("crypto/blake2.zig"); |
| 75 | _ = @import("crypto/blake3.zig"); | 98 | _ = @import("crypto/blake3.zig"); |
| ... | @@ -77,6 +100,7 @@ test "crypto" { | ... | @@ -77,6 +100,7 @@ test "crypto" { |
| 77 | _ = @import("crypto/gimli.zig"); | 100 | _ = @import("crypto/gimli.zig"); |
| 78 | _ = @import("crypto/hmac.zig"); | 101 | _ = @import("crypto/hmac.zig"); |
| 79 | _ = @import("crypto/md5.zig"); | 102 | _ = @import("crypto/md5.zig"); |
| 103 | _ = @import("crypto/pbkdf2.zig"); | ||
| 80 | _ = @import("crypto/poly1305.zig"); | 104 | _ = @import("crypto/poly1305.zig"); |
| 81 | _ = @import("crypto/sha1.zig"); | 105 | _ = @import("crypto/sha1.zig"); |
| 82 | _ = @import("crypto/sha2.zig"); | 106 | _ = @import("crypto/sha2.zig"); |
lib/std/crypto/pbkdf2.zig created+280| ... | @@ -0,0 +1,280 @@ | ||
| 1 | // SPDX-License-Identifier: MIT | ||
| 2 | // Copyright (c) 2015-2020 Zig Contributors | ||
| 3 | // This file is part of [zig](https://ziglang.org/), which is MIT licensed. | ||
| 4 | // The MIT license requires this copyright notice to be included in all copies | ||
| 5 | // and substantial portions of the software. | ||
| 6 | |||
| 7 | const std = @import("std"); | ||
| 8 | const mem = std.mem; | ||
| 9 | const maxInt = std.math.maxInt; | ||
| 10 | |||
| 11 | // RFC 2898 Section 5.2 | ||
| 12 | // | ||
| 13 | // FromSpec: | ||
| 14 | // | ||
| 15 | // PBKDF2 applies a pseudorandom function (see Appendix B.1 for an | ||
| 16 | // example) to derive keys. The length of the derived key is essentially | ||
| 17 | // unbounded. (However, the maximum effective search space for the | ||
| 18 | // derived key may be limited by the structure of the underlying | ||
| 19 | // pseudorandom function. See Appendix B.1 for further discussion.) | ||
| 20 | // PBKDF2 is recommended for new applications. | ||
| 21 | // | ||
| 22 | // PBKDF2 (P, S, c, dkLen) | ||
| 23 | // | ||
| 24 | // Options: PRF underlying pseudorandom function (hLen | ||
| 25 | // denotes the length in octets of the | ||
| 26 | // pseudorandom function output) | ||
| 27 | // | ||
| 28 | // Input: P password, an octet string | ||
| 29 | // S salt, an octet string | ||
| 30 | // c iteration count, a positive integer | ||
| 31 | // dkLen intended length in octets of the derived | ||
| 32 | // key, a positive integer, at most | ||
| 33 | // (2^32 - 1) * hLen | ||
| 34 | // | ||
| 35 | // Output: DK derived key, a dkLen-octet string | ||
| 36 | |||
| 37 | // Based on Apple's CommonKeyDerivation, based originally on code by Damien Bergamini. | ||
| 38 | |||
| 39 | pub const Pbkdf2Error = error{ | ||
| 40 | /// At least one round is required | ||
| 41 | TooFewRounds, | ||
| 42 | |||
| 43 | /// Maximum length of the derived key is `maxInt(u32) * Prf.mac_length` | ||
| 44 | DerivedKeyTooLong, | ||
| 45 | }; | ||
| 46 | |||
| 47 | /// Apply PBKDF2 to generate a key from a password. | ||
| 48 | /// | ||
| 49 | /// PBKDF2 is defined in RFC 2898, and is a recommendation of NIST SP 800-132. | ||
| 50 | /// | ||
| 51 | /// derivedKey: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length. | ||
| 52 | /// May be uninitialized. All bytes will be overwritten. | ||
| 53 | /// Maximum size is `maxInt(u32) * Hash.digest_length` | ||
| 54 | /// It is a programming error to pass buffer longer than the maximum size. | ||
| 55 | /// | ||
| 56 | /// password: Arbitrary sequence of bytes of any length, including empty. | ||
| 57 | /// | ||
| 58 | /// salt: Arbitrary sequence of bytes of any length, including empty. A common length is 8 bytes. | ||
| 59 | /// | ||
| 60 | /// rounds: Iteration count. Must be greater than 0. Common values range from 1,000 to 100,000. | ||
| 61 | /// Larger iteration counts improve security by increasing the time required to compute | ||
| 62 | /// the derivedKey. It is common to tune this parameter to achieve approximately 100ms. | ||
| 63 | /// | ||
| 64 | /// Prf: Pseudo-random function to use. A common choice is `std.crypto.auth.hmac.HmacSha256`. | ||
| 65 | pub fn pbkdf2(derivedKey: []u8, password: []const u8, salt: []const u8, rounds: u32, comptime Prf: type) Pbkdf2Error!void { | ||
| 66 | if (rounds < 1) return error.TooFewRounds; | ||
| 67 | |||
| 68 | const dkLen = derivedKey.len; | ||
| 69 | const hLen = Prf.mac_length; | ||
| 70 | comptime std.debug.assert(hLen >= 1); | ||
| 71 | |||
| 72 | // FromSpec: | ||
| 73 | // | ||
| 74 | // 1. If dkLen > maxInt(u32) * hLen, output "derived key too long" and | ||
| 75 | // stop. | ||
| 76 | // | ||
| 77 | if (comptime (maxInt(usize) > maxInt(u32) * hLen) and (dkLen > @as(usize, maxInt(u32) * hLen))) { | ||
| 78 | // If maxInt(usize) is less than `maxInt(u32) * hLen` then dkLen is always inbounds | ||
| 79 | return error.DerivedKeyTooLong; | ||
| 80 | } | ||
| 81 | |||
| 82 | // FromSpec: | ||
| 83 | // | ||
| 84 | // 2. Let l be the number of hLen-long blocks of bytes in the derived key, | ||
| 85 | // rounding up, and let r be the number of bytes in the last | ||
| 86 | // block | ||
| 87 | // | ||
| 88 | |||
| 89 | // l will not overflow, proof: | ||
| 90 | // let `L(dkLen, hLen) = (dkLen + hLen - 1) / hLen` | ||
| 91 | // then `L^-1(l, hLen) = l*hLen - hLen + 1` | ||
| 92 | // 1) L^-1(maxInt(u32), hLen) <= maxInt(u32)*hLen | ||
| 93 | // 2) maxInt(u32)*hLen - hLen + 1 <= maxInt(u32)*hLen // subtract maxInt(u32)*hLen + 1 | ||
| 94 | // 3) -hLen <= -1 // multiply by -1 | ||
| 95 | // 4) hLen >= 1 | ||
| 96 | const r_ = dkLen % hLen; | ||
| 97 | const l = @intCast(u32, (dkLen / hLen) + @as(u1, if (r_ == 0) 0 else 1)); // original: (dkLen + hLen - 1) / hLen | ||
| 98 | const r = if (r_ == 0) hLen else r_; | ||
| 99 | |||
| 100 | // FromSpec: | ||
| 101 | // | ||
| 102 | // 3. For each block of the derived key apply the function F defined | ||
| 103 | // below to the password P, the salt S, the iteration count c, and | ||
| 104 | // the block index to compute the block: | ||
| 105 | // | ||
| 106 | // T_1 = F (P, S, c, 1) , | ||
| 107 | // T_2 = F (P, S, c, 2) , | ||
| 108 | // ... | ||
| 109 | // T_l = F (P, S, c, l) , | ||
| 110 | // | ||
| 111 | // where the function F is defined as the exclusive-or sum of the | ||
| 112 | // first c iterates of the underlying pseudorandom function PRF | ||
| 113 | // applied to the password P and the concatenation of the salt S | ||
| 114 | // and the block index i: | ||
| 115 | // | ||
| 116 | // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c | ||
| 117 | // | ||
| 118 | // where | ||
| 119 | // | ||
| 120 | // U_1 = PRF (P, S || INT (i)) , | ||
| 121 | // U_2 = PRF (P, U_1) , | ||
| 122 | // ... | ||
| 123 | // U_c = PRF (P, U_{c-1}) . | ||
| 124 | // | ||
| 125 | // Here, INT (i) is a four-octet encoding of the integer i, most | ||
| 126 | // significant octet first. | ||
| 127 | // | ||
| 128 | // 4. Concatenate the blocks and extract the first dkLen octets to | ||
| 129 | // produce a derived key DK: | ||
| 130 | // | ||
| 131 | // DK = T_1 || T_2 || ... || T_l<0..r-1> | ||
| 132 | var block: u32 = 0; // Spec limits to u32 | ||
| 133 | while (block < l) : (block += 1) { | ||
| 134 | var prevBlock: [hLen]u8 = undefined; | ||
| 135 | var newBlock: [hLen]u8 = undefined; | ||
| 136 | |||
| 137 | // U_1 = PRF (P, S || INT (i)) | ||
| 138 | const blockIndex = mem.toBytes(mem.nativeToBig(u32, block + 1)); // Block index starts at 0001 | ||
| 139 | var ctx = Prf.init(password); | ||
| 140 | ctx.update(salt); | ||
| 141 | ctx.update(blockIndex[0..]); | ||
| 142 | ctx.final(prevBlock[0..]); | ||
| 143 | |||
| 144 | // Choose portion of DK to write into (T_n) and initialize | ||
| 145 | const offset = block * hLen; | ||
| 146 | const blockLen = if (block != l - 1) hLen else r; | ||
| 147 | const dkBlock: []u8 = derivedKey[offset..][0..blockLen]; | ||
| 148 | mem.copy(u8, dkBlock, prevBlock[0..dkBlock.len]); | ||
| 149 | |||
| 150 | var i: u32 = 1; | ||
| 151 | while (i < rounds) : (i += 1) { | ||
| 152 | // U_c = PRF (P, U_{c-1}) | ||
| 153 | Prf.create(&newBlock, prevBlock[0..], password); | ||
| 154 | mem.copy(u8, prevBlock[0..], newBlock[0..]); | ||
| 155 | |||
| 156 | // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c | ||
| 157 | for (dkBlock) |_, j| { | ||
| 158 | dkBlock[j] ^= newBlock[j]; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | const htest = @import("test.zig"); | ||
| 165 | const HmacSha1 = std.crypto.auth.hmac.HmacSha1; | ||
| 166 | |||
| 167 | // RFC 6070 PBKDF2 HMAC-SHA1 Test Vectors | ||
| 168 | test "RFC 6070 one iteration" { | ||
| 169 | const p = "password"; | ||
| 170 | const s = "salt"; | ||
| 171 | const c = 1; | ||
| 172 | const dkLen = 20; | ||
| 173 | |||
| 174 | var derivedKey: [dkLen]u8 = undefined; | ||
| 175 | |||
| 176 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | ||
| 177 | |||
| 178 | const expected = "0c60c80f961f0e71f3a9b524af6012062fe037a6"; | ||
| 179 | |||
| 180 | htest.assertEqual(expected, derivedKey[0..]); | ||
| 181 | } | ||
| 182 | |||
| 183 | test "RFC 6070 two iterations" { | ||
| 184 | const p = "password"; | ||
| 185 | const s = "salt"; | ||
| 186 | const c = 2; | ||
| 187 | const dkLen = 20; | ||
| 188 | |||
| 189 | var derivedKey: [dkLen]u8 = undefined; | ||
| 190 | |||
| 191 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | ||
| 192 | |||
| 193 | const expected = "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"; | ||
| 194 | |||
| 195 | htest.assertEqual(expected, derivedKey[0..]); | ||
| 196 | } | ||
| 197 | |||
| 198 | test "RFC 6070 4096 iterations" { | ||
| 199 | const p = "password"; | ||
| 200 | const s = "salt"; | ||
| 201 | const c = 4096; | ||
| 202 | const dkLen = 20; | ||
| 203 | |||
| 204 | var derivedKey: [dkLen]u8 = undefined; | ||
| 205 | |||
| 206 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | ||
| 207 | |||
| 208 | const expected = "4b007901b765489abead49d926f721d065a429c1"; | ||
| 209 | |||
| 210 | htest.assertEqual(expected, derivedKey[0..]); | ||
| 211 | } | ||
| 212 | |||
| 213 | test "RFC 6070 16,777,216 iterations" { | ||
| 214 | // These iteration tests are slow so we always skip them. Results have been verified. | ||
| 215 | if (true) { | ||
| 216 | return error.SkipZigTest; | ||
| 217 | } | ||
| 218 | |||
| 219 | const p = "password"; | ||
| 220 | const s = "salt"; | ||
| 221 | const c = 16777216; | ||
| 222 | const dkLen = 20; | ||
| 223 | |||
| 224 | var derivedKey = [_]u8{0} ** dkLen; | ||
| 225 | |||
| 226 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | ||
| 227 | |||
| 228 | const expected = "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"; | ||
| 229 | |||
| 230 | htest.assertEqual(expected, derivedKey[0..]); | ||
| 231 | } | ||
| 232 | |||
| 233 | test "RFC 6070 multi-block salt and password" { | ||
| 234 | const p = "passwordPASSWORDpassword"; | ||
| 235 | const s = "saltSALTsaltSALTsaltSALTsaltSALTsalt"; | ||
| 236 | const c = 4096; | ||
| 237 | const dkLen = 25; | ||
| 238 | |||
| 239 | var derivedKey: [dkLen]u8 = undefined; | ||
| 240 | |||
| 241 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | ||
| 242 | |||
| 243 | const expected = "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"; | ||
| 244 | |||
| 245 | htest.assertEqual(expected, derivedKey[0..]); | ||
| 246 | } | ||
| 247 | |||
| 248 | test "RFC 6070 embedded NUL" { | ||
| 249 | const p = "pass\x00word"; | ||
| 250 | const s = "sa\x00lt"; | ||
| 251 | const c = 4096; | ||
| 252 | const dkLen = 16; | ||
| 253 | |||
| 254 | var derivedKey: [dkLen]u8 = undefined; | ||
| 255 | |||
| 256 | try pbkdf2(&derivedKey, p, s, c, HmacSha1); | ||
| 257 | |||
| 258 | const expected = "56fa6aa75548099dcc37d7f03425e0c3"; | ||
| 259 | |||
| 260 | htest.assertEqual(expected, derivedKey[0..]); | ||
| 261 | } | ||
| 262 | |||
| 263 | test "Very large dkLen" { | ||
| 264 | // This test allocates 8GB of memory and is expected to take several hours to run. | ||
| 265 | if (true) { | ||
| 266 | return error.SkipZigTest; | ||
| 267 | } | ||
| 268 | const p = "password"; | ||
| 269 | const s = "salt"; | ||
| 270 | const c = 1; | ||
| 271 | const dkLen = 1 << 33; | ||
| 272 | |||
| 273 | var derivedKey = try std.testing.allocator.alloc(u8, dkLen); | ||
| 274 | defer { | ||
| 275 | std.testing.allocator.free(derivedKey); | ||
| 276 | } | ||
| 277 | |||
| 278 | try pbkdf2(derivedKey, p, s, c, HmacSha1); | ||
| 279 | // Just verify this doesn't crash with an overflow | ||
| 280 | } | ||
lib/std/crypto/siphash.zig+2-1| ... | @@ -218,8 +218,9 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) | ... | @@ -218,8 +218,9 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) |
| 218 | } | 218 | } |
| 219 | 219 | ||
| 220 | /// Return an authentication tag for the current state | 220 | /// Return an authentication tag for the current state |
| 221 | /// Assumes `out` is less than or equal to `mac_length`. | ||
| 221 | pub fn final(self: *Self, out: []u8) void { | 222 | pub fn final(self: *Self, out: []u8) void { |
| 222 | std.debug.assert(out.len >= mac_length); | 223 | std.debug.assert(out.len <= mac_length); |
| 223 | mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len])); | 224 | mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len])); |
| 224 | } | 225 | } |
| 225 | 226 |
lib/std/fmt.zig+56-83| ... | @@ -22,7 +22,7 @@ pub const Alignment = enum { | ... | @@ -22,7 +22,7 @@ pub const Alignment = enum { |
| 22 | pub const FormatOptions = struct { | 22 | pub const FormatOptions = struct { |
| 23 | precision: ?usize = null, | 23 | precision: ?usize = null, |
| 24 | width: ?usize = null, | 24 | width: ?usize = null, |
| 25 | alignment: Alignment = .Left, | 25 | alignment: Alignment = .Right, |
| 26 | fill: u8 = ' ', | 26 | fill: u8 = ' ', |
| 27 | }; | 27 | }; |
| 28 | 28 | ||
| ... | @@ -327,7 +327,7 @@ pub fn formatType( | ... | @@ -327,7 +327,7 @@ pub fn formatType( |
| 327 | max_depth: usize, | 327 | max_depth: usize, |
| 328 | ) @TypeOf(writer).Error!void { | 328 | ) @TypeOf(writer).Error!void { |
| 329 | if (comptime std.mem.eql(u8, fmt, "*")) { | 329 | if (comptime std.mem.eql(u8, fmt, "*")) { |
| 330 | try writer.writeAll(@typeName(@typeInfo(@TypeOf(value)).Pointer.child)); | 330 | try writer.writeAll(@typeName(std.meta.Child(@TypeOf(value)))); |
| 331 | try writer.writeAll("@"); | 331 | try writer.writeAll("@"); |
| 332 | try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); | 332 | try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); |
| 333 | return; | 333 | return; |
| ... | @@ -631,26 +631,22 @@ pub fn formatBuf( | ... | @@ -631,26 +631,22 @@ pub fn formatBuf( |
| 631 | writer: anytype, | 631 | writer: anytype, |
| 632 | ) !void { | 632 | ) !void { |
| 633 | const width = options.width orelse buf.len; | 633 | const width = options.width orelse buf.len; |
| 634 | var padding = if (width > buf.len) (width - buf.len) else 0; | 634 | const padding = if (width > buf.len) (width - buf.len) else 0; |
| 635 | const pad_byte = [1]u8{options.fill}; | 635 | |
| 636 | switch (options.alignment) { | 636 | switch (options.alignment) { |
| 637 | .Left => { | 637 | .Left => { |
| 638 | try writer.writeAll(buf); | 638 | try writer.writeAll(buf); |
| 639 | while (padding > 0) : (padding -= 1) { | 639 | try writer.writeByteNTimes(options.fill, padding); |
| 640 | try writer.writeAll(&pad_byte); | ||
| 641 | } | ||
| 642 | }, | 640 | }, |
| 643 | .Center => { | 641 | .Center => { |
| 644 | const padl = padding / 2; | 642 | const left_padding = padding / 2; |
| 645 | var i: usize = 0; | 643 | const right_padding = (padding + 1) / 2; |
| 646 | while (i < padl) : (i += 1) try writer.writeAll(&pad_byte); | 644 | try writer.writeByteNTimes(options.fill, left_padding); |
| 647 | try writer.writeAll(buf); | 645 | try writer.writeAll(buf); |
| 648 | while (i < padding) : (i += 1) try writer.writeAll(&pad_byte); | 646 | try writer.writeByteNTimes(options.fill, right_padding); |
| 649 | }, | 647 | }, |
| 650 | .Right => { | 648 | .Right => { |
| 651 | while (padding > 0) : (padding -= 1) { | 649 | try writer.writeByteNTimes(options.fill, padding); |
| 652 | try writer.writeAll(&pad_byte); | ||
| 653 | } | ||
| 654 | try writer.writeAll(buf); | 650 | try writer.writeAll(buf); |
| 655 | }, | 651 | }, |
| 656 | } | 652 | } |
| ... | @@ -941,61 +937,27 @@ pub fn formatInt( | ... | @@ -941,61 +937,27 @@ pub fn formatInt( |
| 941 | options: FormatOptions, | 937 | options: FormatOptions, |
| 942 | writer: anytype, | 938 | writer: anytype, |
| 943 | ) !void { | 939 | ) !void { |
| 940 | assert(base >= 2); | ||
| 941 | |||
| 944 | const int_value = if (@TypeOf(value) == comptime_int) blk: { | 942 | const int_value = if (@TypeOf(value) == comptime_int) blk: { |
| 945 | const Int = math.IntFittingRange(value, value); | 943 | const Int = math.IntFittingRange(value, value); |
| 946 | break :blk @as(Int, value); | 944 | break :blk @as(Int, value); |
| 947 | } else | 945 | } else |
| 948 | value; | 946 | value; |
| 949 | 947 | ||
| 950 | if (@typeInfo(@TypeOf(int_value)).Int.is_signed) { | 948 | const value_info = @typeInfo(@TypeOf(int_value)).Int; |
| 951 | return formatIntSigned(int_value, base, uppercase, options, writer); | ||
| 952 | } else { | ||
| 953 | return formatIntUnsigned(int_value, base, uppercase, options, writer); | ||
| 954 | } | ||
| 955 | } | ||
| 956 | 949 | ||
| 957 | fn formatIntSigned( | 950 | // The type must have the same size as `base` or be wider in order for the |
| 958 | value: anytype, | 951 | // division to work |
| 959 | base: u8, | 952 | const min_int_bits = comptime math.max(value_info.bits, 8); |
| 960 | uppercase: bool, | 953 | const MinInt = std.meta.Int(false, min_int_bits); |
| 961 | options: FormatOptions, | ||
| 962 | writer: anytype, | ||
| 963 | ) !void { | ||
| 964 | const new_options = FormatOptions{ | ||
| 965 | .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, | ||
| 966 | .precision = options.precision, | ||
| 967 | .fill = options.fill, | ||
| 968 | }; | ||
| 969 | const bit_count = @typeInfo(@TypeOf(value)).Int.bits; | ||
| 970 | const Uint = std.meta.Int(false, bit_count); | ||
| 971 | if (value < 0) { | ||
| 972 | try writer.writeAll("-"); | ||
| 973 | const new_value = math.absCast(value); | ||
| 974 | return formatIntUnsigned(new_value, base, uppercase, new_options, writer); | ||
| 975 | } else if (options.width == null or options.width.? == 0) { | ||
| 976 | return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, writer); | ||
| 977 | } else { | ||
| 978 | try writer.writeAll("+"); | ||
| 979 | const new_value = @intCast(Uint, value); | ||
| 980 | return formatIntUnsigned(new_value, base, uppercase, new_options, writer); | ||
| 981 | } | ||
| 982 | } | ||
| 983 | 954 | ||
| 984 | fn formatIntUnsigned( | 955 | const abs_value = math.absCast(int_value); |
| 985 | value: anytype, | 956 | // The worst case in terms of space needed is base 2, plus 1 for the sign |
| 986 | base: u8, | 957 | var buf: [1 + math.max(value_info.bits, 1)]u8 = undefined; |
| 987 | uppercase: bool, | ||
| 988 | options: FormatOptions, | ||
| 989 | writer: anytype, | ||
| 990 | ) !void { | ||
| 991 | assert(base >= 2); | ||
| 992 | const value_info = @typeInfo(@TypeOf(value)).Int; | ||
| 993 | var buf: [math.max(value_info.bits, 1)]u8 = undefined; | ||
| 994 | const min_int_bits = comptime math.max(value_info.bits, @typeInfo(@TypeOf(base)).Int.bits); | ||
| 995 | const MinInt = std.meta.Int(value_info.is_signed, min_int_bits); | ||
| 996 | var a: MinInt = value; | ||
| 997 | var index: usize = buf.len; | ||
| 998 | 958 | ||
| 959 | var a: MinInt = abs_value; | ||
| 960 | var index: usize = buf.len; | ||
| 999 | while (true) { | 961 | while (true) { |
| 1000 | const digit = a % base; | 962 | const digit = a % base; |
| 1001 | index -= 1; | 963 | index -= 1; |
| ... | @@ -1004,25 +966,21 @@ fn formatIntUnsigned( | ... | @@ -1004,25 +966,21 @@ fn formatIntUnsigned( |
| 1004 | if (a == 0) break; | 966 | if (a == 0) break; |
| 1005 | } | 967 | } |
| 1006 | 968 | ||
| 1007 | const digits_buf = buf[index..]; | 969 | if (value_info.is_signed) { |
| 1008 | const width = options.width orelse 0; | 970 | if (value < 0) { |
| 1009 | const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0; | 971 | // Negative integer |
| 1010 | 972 | index -= 1; | |
| 1011 | if (padding > index) { | 973 | buf[index] = '-'; |
| 1012 | const zero_byte: u8 = options.fill; | 974 | } else if (options.width == null or options.width.? == 0) { |
| 1013 | var leftover_padding = padding - index; | 975 | // Positive integer, omit the plus sign |
| 1014 | while (true) { | 976 | } else { |
| 1015 | try writer.writeAll(@as(*const [1]u8, &zero_byte)[0..]); | 977 | // Positive integer |
| 1016 | leftover_padding -= 1; | 978 | index -= 1; |
| 1017 | if (leftover_padding == 0) break; | 979 | buf[index] = '+'; |
| 1018 | } | 980 | } |
| 1019 | mem.set(u8, buf[0..index], options.fill); | ||
| 1020 | return writer.writeAll(&buf); | ||
| 1021 | } else { | ||
| 1022 | const padded_buf = buf[index - padding ..]; | ||
| 1023 | mem.set(u8, padded_buf[0..padding], options.fill); | ||
| 1024 | return writer.writeAll(padded_buf); | ||
| 1025 | } | 981 | } |
| 982 | |||
| 983 | return formatBuf(buf[index..], options, writer); | ||
| 1026 | } | 984 | } |
| 1027 | 985 | ||
| 1028 | pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize { | 986 | pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize { |
| ... | @@ -1246,6 +1204,10 @@ test "optional" { | ... | @@ -1246,6 +1204,10 @@ test "optional" { |
| 1246 | const value: ?i32 = null; | 1204 | const value: ?i32 = null; |
| 1247 | try testFmt("optional: null\n", "optional: {}\n", .{value}); | 1205 | try testFmt("optional: null\n", "optional: {}\n", .{value}); |
| 1248 | } | 1206 | } |
| 1207 | { | ||
| 1208 | const value = @intToPtr(?*i32, 0xf000d000); | ||
| 1209 | try testFmt("optional: *i32@f000d000\n", "optional: {*}\n", .{value}); | ||
| 1210 | } | ||
| 1249 | } | 1211 | } |
| 1250 | 1212 | ||
| 1251 | test "error" { | 1213 | test "error" { |
| ... | @@ -1283,7 +1245,17 @@ test "int.specifier" { | ... | @@ -1283,7 +1245,17 @@ test "int.specifier" { |
| 1283 | 1245 | ||
| 1284 | test "int.padded" { | 1246 | test "int.padded" { |
| 1285 | try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); | 1247 | try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); |
| 1286 | try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)}); | 1248 | try testFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)}); |
| 1249 | try testFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)}); | ||
| 1250 | try testFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)}); | ||
| 1251 | try testFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)}); | ||
| 1252 | try testFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)}); | ||
| 1253 | try testFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)}); | ||
| 1254 | try testFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)}); | ||
| 1255 | try testFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)}); | ||
| 1256 | try testFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)}); | ||
| 1257 | try testFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)}); | ||
| 1258 | try testFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)}); | ||
| 1287 | } | 1259 | } |
| 1288 | 1260 | ||
| 1289 | test "buffer" { | 1261 | test "buffer" { |
| ... | @@ -1329,7 +1301,7 @@ test "slice" { | ... | @@ -1329,7 +1301,7 @@ test "slice" { |
| 1329 | try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value}); | 1301 | try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value}); |
| 1330 | } | 1302 | } |
| 1331 | 1303 | ||
| 1332 | try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"}); | 1304 | try testFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"}); |
| 1333 | try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); | 1305 | try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); |
| 1334 | } | 1306 | } |
| 1335 | 1307 | ||
| ... | @@ -1362,7 +1334,7 @@ test "cstr" { | ... | @@ -1362,7 +1334,7 @@ test "cstr" { |
| 1362 | .{@ptrCast([*c]const u8, "Test C")}, | 1334 | .{@ptrCast([*c]const u8, "Test C")}, |
| 1363 | ); | 1335 | ); |
| 1364 | try testFmt( | 1336 | try testFmt( |
| 1365 | "cstr: Test C \n", | 1337 | "cstr: Test C\n", |
| 1366 | "cstr: {s:10}\n", | 1338 | "cstr: {s:10}\n", |
| 1367 | .{@ptrCast([*c]const u8, "Test C")}, | 1339 | .{@ptrCast([*c]const u8, "Test C")}, |
| 1368 | ); | 1340 | ); |
| ... | @@ -1805,7 +1777,7 @@ test "vector" { | ... | @@ -1805,7 +1777,7 @@ test "vector" { |
| 1805 | 1777 | ||
| 1806 | try testFmt("{ true, false, true, false }", "{}", .{vbool}); | 1778 | try testFmt("{ true, false, true, false }", "{}", .{vbool}); |
| 1807 | try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); | 1779 | try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); |
| 1808 | try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64}); | 1780 | try testFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64}); |
| 1809 | try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); | 1781 | try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); |
| 1810 | try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); | 1782 | try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); |
| 1811 | try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64}); | 1783 | try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64}); |
| ... | @@ -1818,15 +1790,16 @@ test "enum-literal" { | ... | @@ -1818,15 +1790,16 @@ test "enum-literal" { |
| 1818 | 1790 | ||
| 1819 | test "padding" { | 1791 | test "padding" { |
| 1820 | try testFmt("Simple", "{}", .{"Simple"}); | 1792 | try testFmt("Simple", "{}", .{"Simple"}); |
| 1821 | try testFmt("true ", "{:10}", .{true}); | 1793 | try testFmt(" true", "{:10}", .{true}); |
| 1822 | try testFmt(" true", "{:>10}", .{true}); | 1794 | try testFmt(" true", "{:>10}", .{true}); |
| 1823 | try testFmt("======true", "{:=>10}", .{true}); | 1795 | try testFmt("======true", "{:=>10}", .{true}); |
| 1824 | try testFmt("true======", "{:=<10}", .{true}); | 1796 | try testFmt("true======", "{:=<10}", .{true}); |
| 1825 | try testFmt(" true ", "{:^10}", .{true}); | 1797 | try testFmt(" true ", "{:^10}", .{true}); |
| 1826 | try testFmt("===true===", "{:=^10}", .{true}); | 1798 | try testFmt("===true===", "{:=^10}", .{true}); |
| 1827 | try testFmt("Minimum width", "{:18} width", .{"Minimum"}); | 1799 | try testFmt(" Minimum width", "{:18} width", .{"Minimum"}); |
| 1828 | try testFmt("==================Filled", "{:=>24}", .{"Filled"}); | 1800 | try testFmt("==================Filled", "{:=>24}", .{"Filled"}); |
| 1829 | try testFmt(" Centered ", "{:^24}", .{"Centered"}); | 1801 | try testFmt(" Centered ", "{:^24}", .{"Centered"}); |
| 1802 | try testFmt("-", "{:-^1}", .{""}); | ||
| 1830 | } | 1803 | } |
| 1831 | 1804 | ||
| 1832 | test "decimal float padding" { | 1805 | test "decimal float padding" { |
lib/std/fs.zig+61-5| ... | @@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig"); | ... | @@ -21,10 +21,6 @@ pub const wasi = @import("fs/wasi.zig"); |
| 21 | 21 | ||
| 22 | // TODO audit these APIs with respect to Dir and absolute paths | 22 | // TODO audit these APIs with respect to Dir and absolute paths |
| 23 | 23 | ||
| 24 | pub const rename = os.rename; | ||
| 25 | pub const renameZ = os.renameZ; | ||
| 26 | pub const renameC = @compileError("deprecated: renamed to renameZ"); | ||
| 27 | pub const renameW = os.renameW; | ||
| 28 | pub const realpath = os.realpath; | 24 | pub const realpath = os.realpath; |
| 29 | pub const realpathZ = os.realpathZ; | 25 | pub const realpathZ = os.realpathZ; |
| 30 | pub const realpathC = @compileError("deprecated: renamed to realpathZ"); | 26 | pub const realpathC = @compileError("deprecated: renamed to realpathZ"); |
| ... | @@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: | ... | @@ -90,7 +86,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: |
| 90 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); | 86 | base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf); |
| 91 | 87 | ||
| 92 | if (cwd().symLink(existing_path, tmp_path, .{})) { | 88 | if (cwd().symLink(existing_path, tmp_path, .{})) { |
| 93 | return rename(tmp_path, new_path); | 89 | return cwd().rename(tmp_path, new_path); |
| 94 | } else |err| switch (err) { | 90 | } else |err| switch (err) { |
| 95 | error.PathAlreadyExists => continue, | 91 | error.PathAlreadyExists => continue, |
| 96 | else => return err, // TODO zig should know this set does not include PathAlreadyExists | 92 | 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 { | ... | @@ -255,6 +251,45 @@ pub fn deleteDirAbsoluteW(dir_path: [*:0]const u16) !void { |
| 255 | return os.rmdirW(dir_path); | 251 | return os.rmdirW(dir_path); |
| 256 | } | 252 | } |
| 257 | 253 | ||
| 254 | pub const renameC = @compileError("deprecated: use renameZ, dir.renameZ, or renameAbsoluteZ"); | ||
| 255 | |||
| 256 | /// Same as `Dir.rename` except the paths are absolute. | ||
| 257 | pub fn renameAbsolute(old_path: []const u8, new_path: []const u8) !void { | ||
| 258 | assert(path.isAbsolute(old_path)); | ||
| 259 | assert(path.isAbsolute(new_path)); | ||
| 260 | return os.rename(old_path, new_path); | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Same as `renameAbsolute` except the path parameters are null-terminated. | ||
| 264 | pub fn renameAbsoluteZ(old_path: [*:0]const u8, new_path: [*:0]const u8) !void { | ||
| 265 | assert(path.isAbsoluteZ(old_path)); | ||
| 266 | assert(path.isAbsoluteZ(new_path)); | ||
| 267 | return os.renameZ(old_path, new_path); | ||
| 268 | } | ||
| 269 | |||
| 270 | /// Same as `renameAbsolute` except the path parameters are WTF-16 and target OS is assumed Windows. | ||
| 271 | pub fn renameAbsoluteW(old_path: [*:0]const u16, new_path: [*:0]const u16) !void { | ||
| 272 | assert(path.isAbsoluteWindowsW(old_path)); | ||
| 273 | assert(path.isAbsoluteWindowsW(new_path)); | ||
| 274 | return os.renameW(old_path, new_path); | ||
| 275 | } | ||
| 276 | |||
| 277 | /// Same as `Dir.rename`, except `new_sub_path` is relative to `new_dir` | ||
| 278 | pub fn rename(old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) !void { | ||
| 279 | return os.renameat(old_dir.fd, old_sub_path, new_dir.fd, new_sub_path); | ||
| 280 | } | ||
| 281 | |||
| 282 | /// Same as `rename` except the parameters are null-terminated. | ||
| 283 | pub fn renameZ(old_dir: Dir, old_sub_path_z: [*:0]const u8, new_dir: Dir, new_sub_path_z: [*:0]const u8) !void { | ||
| 284 | return os.renameatZ(old_dir.fd, old_sub_path_z, new_dir.fd, new_sub_path_z); | ||
| 285 | } | ||
| 286 | |||
| 287 | /// Same as `rename` except the parameters are UTF16LE, NT prefixed. | ||
| 288 | /// This function is Windows-only. | ||
| 289 | pub fn renameW(old_dir: Dir, old_sub_path_w: []const u16, new_dir: Dir, new_sub_path_w: []const u16) !void { | ||
| 290 | return os.renameatW(old_dir.fd, old_sub_path_w, new_dir.fd, new_sub_path_w); | ||
| 291 | } | ||
| 292 | |||
| 258 | pub const Dir = struct { | 293 | pub const Dir = struct { |
| 259 | fd: os.fd_t, | 294 | fd: os.fd_t, |
| 260 | 295 | ||
| ... | @@ -1338,6 +1373,27 @@ pub const Dir = struct { | ... | @@ -1338,6 +1373,27 @@ pub const Dir = struct { |
| 1338 | }; | 1373 | }; |
| 1339 | } | 1374 | } |
| 1340 | 1375 | ||
| 1376 | pub const RenameError = os.RenameError; | ||
| 1377 | |||
| 1378 | /// Change the name or location of a file or directory. | ||
| 1379 | /// If new_sub_path already exists, it will be replaced. | ||
| 1380 | /// Renaming a file over an existing directory or a directory | ||
| 1381 | /// over an existing file will fail with `error.IsDir` or `error.NotDir` | ||
| 1382 | pub fn rename(self: Dir, old_sub_path: []const u8, new_sub_path: []const u8) RenameError!void { | ||
| 1383 | return os.renameat(self.fd, old_sub_path, self.fd, new_sub_path); | ||
| 1384 | } | ||
| 1385 | |||
| 1386 | /// Same as `rename` except the parameters are null-terminated. | ||
| 1387 | pub fn renameZ(self: Dir, old_sub_path_z: [*:0]const u8, new_sub_path_z: [*:0]const u8) RenameError!void { | ||
| 1388 | return os.renameatZ(self.fd, old_sub_path_z, self.fd, new_sub_path_z); | ||
| 1389 | } | ||
| 1390 | |||
| 1391 | /// Same as `rename` except the parameters are UTF16LE, NT prefixed. | ||
| 1392 | /// This function is Windows-only. | ||
| 1393 | pub fn renameW(self: Dir, old_sub_path_w: []const u16, new_sub_path_w: []const u16) RenameError!void { | ||
| 1394 | return os.renameatW(self.fd, old_sub_path_w, self.fd, new_sub_path_w); | ||
| 1395 | } | ||
| 1396 | |||
| 1341 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. | 1397 | /// Creates a symbolic link named `sym_link_path` which contains the string `target_path`. |
| 1342 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent | 1398 | /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent |
| 1343 | /// one; the latter case is known as a dangling link. | 1399 | /// one; the latter case is known as a dangling link. |
lib/std/fs/test.zig+161| ... | @@ -274,6 +274,167 @@ test "file operations on directories" { | ... | @@ -274,6 +274,167 @@ test "file operations on directories" { |
| 274 | dir.close(); | 274 | dir.close(); |
| 275 | } | 275 | } |
| 276 | 276 | ||
| 277 | test "Dir.rename files" { | ||
| 278 | var tmp_dir = tmpDir(.{}); | ||
| 279 | defer tmp_dir.cleanup(); | ||
| 280 | |||
| 281 | testing.expectError(error.FileNotFound, tmp_dir.dir.rename("missing_file_name", "something_else")); | ||
| 282 | |||
| 283 | // Renaming files | ||
| 284 | const test_file_name = "test_file"; | ||
| 285 | const renamed_test_file_name = "test_file_renamed"; | ||
| 286 | var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); | ||
| 287 | file.close(); | ||
| 288 | try tmp_dir.dir.rename(test_file_name, renamed_test_file_name); | ||
| 289 | |||
| 290 | // Ensure the file was renamed | ||
| 291 | testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); | ||
| 292 | file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); | ||
| 293 | file.close(); | ||
| 294 | |||
| 295 | // Rename to self succeeds | ||
| 296 | try tmp_dir.dir.rename(renamed_test_file_name, renamed_test_file_name); | ||
| 297 | |||
| 298 | // Rename to existing file succeeds | ||
| 299 | var existing_file = try tmp_dir.dir.createFile("existing_file", .{ .read = true }); | ||
| 300 | existing_file.close(); | ||
| 301 | try tmp_dir.dir.rename(renamed_test_file_name, "existing_file"); | ||
| 302 | |||
| 303 | testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(renamed_test_file_name, .{})); | ||
| 304 | file = try tmp_dir.dir.openFile("existing_file", .{}); | ||
| 305 | file.close(); | ||
| 306 | } | ||
| 307 | |||
| 308 | test "Dir.rename directories" { | ||
| 309 | // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 | ||
| 310 | if (builtin.os.tag == .windows) return error.SkipZigTest; | ||
| 311 | |||
| 312 | var tmp_dir = tmpDir(.{}); | ||
| 313 | defer tmp_dir.cleanup(); | ||
| 314 | |||
| 315 | // Renaming directories | ||
| 316 | try tmp_dir.dir.makeDir("test_dir"); | ||
| 317 | try tmp_dir.dir.rename("test_dir", "test_dir_renamed"); | ||
| 318 | |||
| 319 | // Ensure the directory was renamed | ||
| 320 | testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir", .{})); | ||
| 321 | var dir = try tmp_dir.dir.openDir("test_dir_renamed", .{}); | ||
| 322 | |||
| 323 | // Put a file in the directory | ||
| 324 | var file = try dir.createFile("test_file", .{ .read = true }); | ||
| 325 | file.close(); | ||
| 326 | dir.close(); | ||
| 327 | |||
| 328 | try tmp_dir.dir.rename("test_dir_renamed", "test_dir_renamed_again"); | ||
| 329 | |||
| 330 | // Ensure the directory was renamed and the file still exists in it | ||
| 331 | testing.expectError(error.FileNotFound, tmp_dir.dir.openDir("test_dir_renamed", .{})); | ||
| 332 | dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); | ||
| 333 | file = try dir.openFile("test_file", .{}); | ||
| 334 | file.close(); | ||
| 335 | dir.close(); | ||
| 336 | |||
| 337 | // Try to rename to a non-empty directory now | ||
| 338 | var target_dir = try tmp_dir.dir.makeOpenPath("non_empty_target_dir", .{}); | ||
| 339 | file = try target_dir.createFile("filler", .{ .read = true }); | ||
| 340 | file.close(); | ||
| 341 | |||
| 342 | testing.expectError(error.PathAlreadyExists, tmp_dir.dir.rename("test_dir_renamed_again", "non_empty_target_dir")); | ||
| 343 | |||
| 344 | // Ensure the directory was not renamed | ||
| 345 | dir = try tmp_dir.dir.openDir("test_dir_renamed_again", .{}); | ||
| 346 | file = try dir.openFile("test_file", .{}); | ||
| 347 | file.close(); | ||
| 348 | dir.close(); | ||
| 349 | } | ||
| 350 | |||
| 351 | test "Dir.rename file <-> dir" { | ||
| 352 | // TODO: Fix on Windows, see https://github.com/ziglang/zig/issues/6364 | ||
| 353 | if (builtin.os.tag == .windows) return error.SkipZigTest; | ||
| 354 | |||
| 355 | var tmp_dir = tmpDir(.{}); | ||
| 356 | defer tmp_dir.cleanup(); | ||
| 357 | |||
| 358 | var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); | ||
| 359 | file.close(); | ||
| 360 | try tmp_dir.dir.makeDir("test_dir"); | ||
| 361 | testing.expectError(error.IsDir, tmp_dir.dir.rename("test_file", "test_dir")); | ||
| 362 | testing.expectError(error.NotDir, tmp_dir.dir.rename("test_dir", "test_file")); | ||
| 363 | } | ||
| 364 | |||
| 365 | test "rename" { | ||
| 366 | var tmp_dir1 = tmpDir(.{}); | ||
| 367 | defer tmp_dir1.cleanup(); | ||
| 368 | |||
| 369 | var tmp_dir2 = tmpDir(.{}); | ||
| 370 | defer tmp_dir2.cleanup(); | ||
| 371 | |||
| 372 | // Renaming files | ||
| 373 | const test_file_name = "test_file"; | ||
| 374 | const renamed_test_file_name = "test_file_renamed"; | ||
| 375 | var file = try tmp_dir1.dir.createFile(test_file_name, .{ .read = true }); | ||
| 376 | file.close(); | ||
| 377 | try fs.rename(tmp_dir1.dir, test_file_name, tmp_dir2.dir, renamed_test_file_name); | ||
| 378 | |||
| 379 | // ensure the file was renamed | ||
| 380 | testing.expectError(error.FileNotFound, tmp_dir1.dir.openFile(test_file_name, .{})); | ||
| 381 | file = try tmp_dir2.dir.openFile(renamed_test_file_name, .{}); | ||
| 382 | file.close(); | ||
| 383 | } | ||
| 384 | |||
| 385 | test "renameAbsolute" { | ||
| 386 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 387 | |||
| 388 | var tmp_dir = tmpDir(.{}); | ||
| 389 | defer tmp_dir.cleanup(); | ||
| 390 | |||
| 391 | // Get base abs path | ||
| 392 | var arena = ArenaAllocator.init(testing.allocator); | ||
| 393 | defer arena.deinit(); | ||
| 394 | const allocator = &arena.allocator; | ||
| 395 | |||
| 396 | const base_path = blk: { | ||
| 397 | const relative_path = try fs.path.join(&arena.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..] }); | ||
| 398 | break :blk try fs.realpathAlloc(&arena.allocator, relative_path); | ||
| 399 | }; | ||
| 400 | |||
| 401 | testing.expectError(error.FileNotFound, fs.renameAbsolute( | ||
| 402 | try fs.path.join(allocator, &[_][]const u8{ base_path, "missing_file_name" }), | ||
| 403 | try fs.path.join(allocator, &[_][]const u8{ base_path, "something_else" }), | ||
| 404 | )); | ||
| 405 | |||
| 406 | // Renaming files | ||
| 407 | const test_file_name = "test_file"; | ||
| 408 | const renamed_test_file_name = "test_file_renamed"; | ||
| 409 | var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); | ||
| 410 | file.close(); | ||
| 411 | try fs.renameAbsolute( | ||
| 412 | try fs.path.join(allocator, &[_][]const u8{ base_path, test_file_name }), | ||
| 413 | try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_file_name }), | ||
| 414 | ); | ||
| 415 | |||
| 416 | // ensure the file was renamed | ||
| 417 | testing.expectError(error.FileNotFound, tmp_dir.dir.openFile(test_file_name, .{})); | ||
| 418 | file = try tmp_dir.dir.openFile(renamed_test_file_name, .{}); | ||
| 419 | const stat = try file.stat(); | ||
| 420 | testing.expect(stat.kind == .File); | ||
| 421 | file.close(); | ||
| 422 | |||
| 423 | // Renaming directories | ||
| 424 | const test_dir_name = "test_dir"; | ||
| 425 | const renamed_test_dir_name = "test_dir_renamed"; | ||
| 426 | try tmp_dir.dir.makeDir(test_dir_name); | ||
| 427 | try fs.renameAbsolute( | ||
| 428 | try fs.path.join(allocator, &[_][]const u8{ base_path, test_dir_name }), | ||
| 429 | try fs.path.join(allocator, &[_][]const u8{ base_path, renamed_test_dir_name }), | ||
| 430 | ); | ||
| 431 | |||
| 432 | // ensure the directory was renamed | ||
| 433 | testing.expectError(error.FileNotFound, tmp_dir.dir.openDir(test_dir_name, .{})); | ||
| 434 | var dir = try tmp_dir.dir.openDir(renamed_test_dir_name, .{}); | ||
| 435 | dir.close(); | ||
| 436 | } | ||
| 437 | |||
| 277 | test "openSelfExe" { | 438 | test "openSelfExe" { |
| 278 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 439 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 279 | 440 |
lib/std/heap.zig+1-1| ... | @@ -489,7 +489,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { | ... | @@ -489,7 +489,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { |
| 489 | const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr); | 489 | const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr); |
| 490 | assert(full_len != std.math.maxInt(usize)); | 490 | assert(full_len != std.math.maxInt(usize)); |
| 491 | assert(full_len >= amt); | 491 | assert(full_len >= amt); |
| 492 | break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr), len_align); | 492 | break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr) - @sizeOf(usize), len_align); |
| 493 | }; | 493 | }; |
| 494 | const buf = @intToPtr([*]u8, aligned_addr)[0..return_len]; | 494 | const buf = @intToPtr([*]u8, aligned_addr)[0..return_len]; |
| 495 | getRecordPtr(buf).* = root_addr; | 495 | getRecordPtr(buf).* = root_addr; |
lib/std/os.zig+2-1| ... | @@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError | ... | @@ -1890,7 +1890,7 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: []const u16, flags: u32) UnlinkatError |
| 1890 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); | 1890 | return windows.DeleteFile(sub_path_w, .{ .dir = dirfd, .remove_dir = remove_dir }); |
| 1891 | } | 1891 | } |
| 1892 | 1892 | ||
| 1893 | const RenameError = error{ | 1893 | pub const RenameError = error{ |
| 1894 | /// In WASI, this error may occur when the file descriptor does | 1894 | /// In WASI, this error may occur when the file descriptor does |
| 1895 | /// not hold the required rights to rename a resource by path relative to it. | 1895 | /// not hold the required rights to rename a resource by path relative to it. |
| 1896 | AccessDenied, | 1896 | AccessDenied, |
| ... | @@ -2107,6 +2107,7 @@ pub fn renameatW( | ... | @@ -2107,6 +2107,7 @@ pub fn renameatW( |
| 2107 | .ACCESS_DENIED => return error.AccessDenied, | 2107 | .ACCESS_DENIED => return error.AccessDenied, |
| 2108 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | 2108 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, |
| 2109 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | 2109 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, |
| 2110 | .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, | ||
| 2110 | else => return windows.unexpectedStatus(rc), | 2111 | else => return windows.unexpectedStatus(rc), |
| 2111 | } | 2112 | } |
| 2112 | } | 2113 | } |
lib/std/os/windows.zig+2-1| ... | @@ -830,7 +830,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil | ... | @@ -830,7 +830,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil |
| 830 | } | 830 | } |
| 831 | } | 831 | } |
| 832 | 832 | ||
| 833 | pub const MoveFileError = error{Unexpected}; | 833 | pub const MoveFileError = error{ FileNotFound, Unexpected }; |
| 834 | 834 | ||
| 835 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { | 835 | pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) MoveFileError!void { |
| 836 | const old_path_w = try sliceToPrefixedFileW(old_path); | 836 | 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 | ... | @@ -841,6 +841,7 @@ pub fn MoveFileEx(old_path: []const u8, new_path: []const u8, flags: DWORD) Move |
| 841 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { | 841 | pub fn MoveFileExW(old_path: [*:0]const u16, new_path: [*:0]const u16, flags: DWORD) MoveFileError!void { |
| 842 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { | 842 | if (kernel32.MoveFileExW(old_path, new_path, flags) == 0) { |
| 843 | switch (kernel32.GetLastError()) { | 843 | switch (kernel32.GetLastError()) { |
| 844 | .FILE_NOT_FOUND => return error.FileNotFound, | ||
| 844 | else => |err| return unexpectedError(err), | 845 | else => |err| return unexpectedError(err), |
| 845 | } | 846 | } |
| 846 | } | 847 | } |
src/stage1/analyze.cpp+14-15| ... | @@ -3161,30 +3161,29 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { | ... | @@ -3161,30 +3161,29 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) { |
| 3161 | tag_type->data.enumeration.fields_by_name.init(field_count); | 3161 | tag_type->data.enumeration.fields_by_name.init(field_count); |
| 3162 | tag_type->data.enumeration.decls_scope = union_type->data.unionation.decls_scope; | 3162 | tag_type->data.enumeration.decls_scope = union_type->data.unionation.decls_scope; |
| 3163 | } else if (enum_type_node != nullptr) { | 3163 | } else if (enum_type_node != nullptr) { |
| 3164 | ZigType *enum_type = analyze_type_expr(g, scope, enum_type_node); | 3164 | tag_type = analyze_type_expr(g, scope, enum_type_node); |
| 3165 | if (type_is_invalid(enum_type)) { | 3165 | } else { |
| 3166 | if (decl_node->type == NodeTypeContainerDecl) { | ||
| 3167 | tag_type = nullptr; | ||
| 3168 | } else { | ||
| 3169 | tag_type = union_type->data.unionation.tag_type; | ||
| 3170 | } | ||
| 3171 | } | ||
| 3172 | if (tag_type != nullptr) { | ||
| 3173 | if (type_is_invalid(tag_type)) { | ||
| 3166 | union_type->data.unionation.resolve_status = ResolveStatusInvalid; | 3174 | union_type->data.unionation.resolve_status = ResolveStatusInvalid; |
| 3167 | return ErrorSemanticAnalyzeFail; | 3175 | return ErrorSemanticAnalyzeFail; |
| 3168 | } | 3176 | } |
| 3169 | if (enum_type->id != ZigTypeIdEnum) { | 3177 | if (tag_type->id != ZigTypeIdEnum) { |
| 3170 | union_type->data.unionation.resolve_status = ResolveStatusInvalid; | 3178 | union_type->data.unionation.resolve_status = ResolveStatusInvalid; |
| 3171 | add_node_error(g, enum_type_node, | 3179 | add_node_error(g, enum_type_node != nullptr ? enum_type_node : decl_node, |
| 3172 | buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&enum_type->name))); | 3180 | buf_sprintf("expected enum tag type, found '%s'", buf_ptr(&tag_type->name))); |
| 3173 | return ErrorSemanticAnalyzeFail; | 3181 | return ErrorSemanticAnalyzeFail; |
| 3174 | } | 3182 | } |
| 3175 | if ((err = type_resolve(g, enum_type, ResolveStatusAlignmentKnown))) { | 3183 | if ((err = type_resolve(g, tag_type, ResolveStatusAlignmentKnown))) { |
| 3176 | assert(g->errors.length != 0); | 3184 | assert(g->errors.length != 0); |
| 3177 | return err; | 3185 | return err; |
| 3178 | } | 3186 | } |
| 3179 | tag_type = enum_type; | ||
| 3180 | } else { | ||
| 3181 | if (decl_node->type == NodeTypeContainerDecl) { | ||
| 3182 | tag_type = nullptr; | ||
| 3183 | } else { | ||
| 3184 | tag_type = union_type->data.unionation.tag_type; | ||
| 3185 | } | ||
| 3186 | } | ||
| 3187 | if (tag_type != nullptr) { | ||
| 3188 | covered_enum_fields = heap::c_allocator.allocate<bool>(tag_type->data.enumeration.src_field_count); | 3187 | covered_enum_fields = heap::c_allocator.allocate<bool>(tag_type->data.enumeration.src_field_count); |
| 3189 | } | 3188 | } |
| 3190 | union_type->data.unionation.tag_type = tag_type; | 3189 | union_type->data.unionation.tag_type = tag_type; |
src/stage1/ir.cpp+23-1| ... | @@ -63,6 +63,7 @@ enum ConstCastResultId { | ... | @@ -63,6 +63,7 @@ enum ConstCastResultId { |
| 63 | ConstCastResultIdPointerChild, | 63 | ConstCastResultIdPointerChild, |
| 64 | ConstCastResultIdSliceChild, | 64 | ConstCastResultIdSliceChild, |
| 65 | ConstCastResultIdOptionalChild, | 65 | ConstCastResultIdOptionalChild, |
| 66 | ConstCastResultIdOptionalShape, | ||
| 66 | ConstCastResultIdErrorUnionPayload, | 67 | ConstCastResultIdErrorUnionPayload, |
| 67 | ConstCastResultIdErrorUnionErrorSet, | 68 | ConstCastResultIdErrorUnionErrorSet, |
| 68 | ConstCastResultIdFnAlign, | 69 | ConstCastResultIdFnAlign, |
| ... | @@ -11946,8 +11947,22 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted | ... | @@ -11946,8 +11947,22 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted |
| 11946 | } | 11947 | } |
| 11947 | } | 11948 | } |
| 11948 | 11949 | ||
| 11949 | // maybe | 11950 | // optional types |
| 11950 | if (wanted_type->id == ZigTypeIdOptional && actual_type->id == ZigTypeIdOptional) { | 11951 | if (wanted_type->id == ZigTypeIdOptional && actual_type->id == ZigTypeIdOptional) { |
| 11952 | // Consider the case where the wanted type is ??[*]T and the actual one | ||
| 11953 | // is ?[*]T, we cannot turn the former into the latter even though the | ||
| 11954 | // child types are compatible (?[*]T and [*]T are both represented as a | ||
| 11955 | // pointer). The extra level of indirection in ??[*]T means it's | ||
| 11956 | // represented as a regular, fat, optional type and, as a consequence, | ||
| 11957 | // has a different shape than the one of ?[*]T. | ||
| 11958 | if ((wanted_ptr_type != nullptr) != (actual_ptr_type != nullptr)) { | ||
| 11959 | // The use of type_mismatch is intentional | ||
| 11960 | result.id = ConstCastResultIdOptionalShape; | ||
| 11961 | result.data.type_mismatch = heap::c_allocator.allocate_nonzero<ConstCastTypeMismatch>(1); | ||
| 11962 | result.data.type_mismatch->wanted_type = wanted_type; | ||
| 11963 | result.data.type_mismatch->actual_type = actual_type; | ||
| 11964 | return result; | ||
| 11965 | } | ||
| 11951 | ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.maybe.child_type, | 11966 | ConstCastOnly child = types_match_const_cast_only(ira, wanted_type->data.maybe.child_type, |
| 11952 | actual_type->data.maybe.child_type, source_node, wanted_is_mutable); | 11967 | actual_type->data.maybe.child_type, source_node, wanted_is_mutable); |
| 11953 | if (child.id == ConstCastResultIdInvalid) | 11968 | if (child.id == ConstCastResultIdInvalid) |
| ... | @@ -14549,6 +14564,13 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa | ... | @@ -14549,6 +14564,13 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa |
| 14549 | report_recursive_error(ira, source_node, &cast_result->data.optional->child, msg); | 14564 | report_recursive_error(ira, source_node, &cast_result->data.optional->child, msg); |
| 14550 | break; | 14565 | break; |
| 14551 | } | 14566 | } |
| 14567 | case ConstCastResultIdOptionalShape: { | ||
| 14568 | add_error_note(ira->codegen, parent_msg, source_node, | ||
| 14569 | buf_sprintf("optional type child '%s' cannot cast into optional type '%s'", | ||
| 14570 | buf_ptr(&cast_result->data.type_mismatch->actual_type->name), | ||
| 14571 | buf_ptr(&cast_result->data.type_mismatch->wanted_type->name))); | ||
| 14572 | break; | ||
| 14573 | } | ||
| 14552 | case ConstCastResultIdErrorUnionErrorSet: { | 14574 | case ConstCastResultIdErrorUnionErrorSet: { |
| 14553 | ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node, | 14575 | ErrorMsg *msg = add_error_note(ira->codegen, parent_msg, source_node, |
| 14554 | buf_sprintf("error set '%s' cannot cast into error set '%s'", | 14576 | buf_sprintf("error set '%s' cannot cast into error set '%s'", |
src/translate_c.zig+1-1| ... | @@ -2032,7 +2032,7 @@ fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 { | ... | @@ -2032,7 +2032,7 @@ fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 { |
| 2032 | // Handle the remaining escapes Zig doesn't support by turning them | 2032 | // Handle the remaining escapes Zig doesn't support by turning them |
| 2033 | // into their respective hex representation | 2033 | // into their respective hex representation |
| 2034 | else => if (std.ascii.isCntrl(c)) | 2034 | else => if (std.ascii.isCntrl(c)) |
| 2035 | std.fmt.bufPrint(char_buf, "\\x{x:0<2}", .{c}) catch unreachable | 2035 | std.fmt.bufPrint(char_buf, "\\x{x:0>2}", .{c}) catch unreachable |
| 2036 | else | 2036 | else |
| 2037 | std.fmt.bufPrint(char_buf, "{c}", .{c}) catch unreachable, | 2037 | std.fmt.bufPrint(char_buf, "{c}", .{c}) catch unreachable, |
| 2038 | }; | 2038 | }; |
test/stage1/behavior/cast.zig+5| ... | @@ -849,3 +849,8 @@ test "comptime float casts" { | ... | @@ -849,3 +849,8 @@ test "comptime float casts" { |
| 849 | expect(b == 2); | 849 | expect(b == 2); |
| 850 | expect(@TypeOf(b) == comptime_int); | 850 | expect(@TypeOf(b) == comptime_int); |
| 851 | } | 851 | } |
| 852 | |||
| 853 | test "cast from ?[*]T to ??[*]T" { | ||
| 854 | const a: ??[*]u8 = @as(?[*]u8, null); | ||
| 855 | expect(a != null and a.? == null); | ||
| 856 | } |
test/stage1/behavior/type.zig+42| ... | @@ -374,3 +374,45 @@ test "Type.Union" { | ... | @@ -374,3 +374,45 @@ test "Type.Union" { |
| 374 | tagged = .{ .unsigned = 1 }; | 374 | tagged = .{ .unsigned = 1 }; |
| 375 | testing.expectEqual(Tag.unsigned, tagged); | 375 | testing.expectEqual(Tag.unsigned, tagged); |
| 376 | } | 376 | } |
| 377 | |||
| 378 | test "Type.Union from Type.Enum" { | ||
| 379 | const Tag = @Type(.{ | ||
| 380 | .Enum = .{ | ||
| 381 | .layout = .Auto, | ||
| 382 | .tag_type = u0, | ||
| 383 | .fields = &[_]TypeInfo.EnumField{ | ||
| 384 | .{ .name = "working_as_expected", .value = 0 }, | ||
| 385 | }, | ||
| 386 | .decls = &[_]TypeInfo.Declaration{}, | ||
| 387 | .is_exhaustive = true, | ||
| 388 | }, | ||
| 389 | }); | ||
| 390 | const T = @Type(.{ | ||
| 391 | .Union = .{ | ||
| 392 | .layout = .Auto, | ||
| 393 | .tag_type = Tag, | ||
| 394 | .fields = &[_]TypeInfo.UnionField{ | ||
| 395 | .{ .name = "working_as_expected", .field_type = u32 }, | ||
| 396 | }, | ||
| 397 | .decls = &[_]TypeInfo.Declaration{}, | ||
| 398 | }, | ||
| 399 | }); | ||
| 400 | _ = T; | ||
| 401 | _ = @typeInfo(T).Union; | ||
| 402 | } | ||
| 403 | |||
| 404 | test "Type.Union from regular enum" { | ||
| 405 | const E = enum { working_as_expected = 0 }; | ||
| 406 | const T = @Type(.{ | ||
| 407 | .Union = .{ | ||
| 408 | .layout = .Auto, | ||
| 409 | .tag_type = E, | ||
| 410 | .fields = &[_]TypeInfo.UnionField{ | ||
| 411 | .{ .name = "working_as_expected", .field_type = u32 }, | ||
| 412 | }, | ||
| 413 | .decls = &[_]TypeInfo.Declaration{}, | ||
| 414 | }, | ||
| 415 | }); | ||
| 416 | _ = T; | ||
| 417 | _ = @typeInfo(T).Union; | ||
| 418 | } |