std.fmt: breaking API changes

added adapter to AnyWriter and GenericWriter to help bridge the gap
between old and new API

make std.testing.expectFmt work at compile-time

std.fmt no longer has a dependency on std.unicode. Formatted printing
was never properly unicode-aware. Now it no longer pretends to be.

Breakage/deprecations:
* std.fs.File.reader -> std.fs.File.deprecatedReader
* std.fs.File.writer -> std.fs.File.deprecatedWriter
* std.io.GenericReader -> std.io.Reader
* std.io.GenericWriter -> std.io.Writer
* std.io.AnyReader -> std.io.Reader
* std.io.AnyWriter -> std.io.Writer
* std.fmt.format -> std.fmt.deprecatedFormat
* std.fmt.fmtSliceEscapeLower -> std.ascii.hexEscape
* std.fmt.fmtSliceEscapeUpper -> std.ascii.hexEscape
* std.fmt.fmtSliceHexLower -> {x}
* std.fmt.fmtSliceHexUpper -> {X}
* std.fmt.fmtIntSizeDec -> {B}
* std.fmt.fmtIntSizeBin -> {Bi}
* std.fmt.fmtDuration -> {D}
* std.fmt.fmtDurationSigned -> {D}
* {} -> {f} when there is a format method
* format method signature
  - anytype -> *std.io.Writer
  - inferred error set -> error{WriteFailed}
  - options -> (deleted)
* std.fmt.Formatted
  - now takes context type explicitly
  - no fmt string
This commit is contained in:
Andrew Kelley
2025-06-27 20:05:22 -07:00
parent 0b3f0124dc
commit 0e37ff0d59
161 changed files with 4385 additions and 5847 deletions

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const ConfigHeader = @This();
const Step = std.Build.Step;
const Allocator = std.mem.Allocator;
const Writer = std.io.Writer;
pub const Style = union(enum) {
/// A configure format supported by autotools that uses `#undef foo` to
@@ -87,7 +88,7 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path });
config_header.* = .{
.step = Step.init(.{
.step = .init(.{
.id = base_id,
.name = name,
.owner = owner,
@@ -95,7 +96,7 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
.first_ret_addr = options.first_ret_addr orelse @returnAddress(),
}),
.style = options.style,
.values = std.StringArrayHashMap(Value).init(owner.allocator),
.values = .init(owner.allocator),
.max_bytes = options.max_bytes,
.include_path = include_path,
@@ -195,8 +196,9 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
man.hash.addBytes(config_header.include_path);
man.hash.addOptionalBytes(config_header.include_guard_override);
var output = std.ArrayList(u8).init(gpa);
defer output.deinit();
var aw: std.io.Writer.Allocating = .init(gpa);
defer aw.deinit();
const bw = &aw.interface;
const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
const c_generated_line = "/* " ++ header_text ++ " */\n";
@@ -204,7 +206,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
switch (config_header.style) {
.autoconf_undef, .autoconf, .autoconf_at => |file_source| {
try output.appendSlice(c_generated_line);
try bw.writeAll(c_generated_line);
const src_path = file_source.getPath2(b, step);
const contents = std.fs.cwd().readFileAlloc(arena, src_path, config_header.max_bytes) catch |err| {
return step.fail("unable to read autoconf input file '{s}': {s}", .{
@@ -212,32 +214,33 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
});
};
switch (config_header.style) {
.autoconf_undef, .autoconf => try render_autoconf_undef(step, contents, &output, config_header.values, src_path),
.autoconf_at => try render_autoconf_at(step, contents, &output, config_header.values, src_path),
.autoconf_undef, .autoconf => try render_autoconf_undef(step, contents, bw, config_header.values, src_path),
.autoconf_at => try render_autoconf_at(step, contents, &aw, config_header.values, src_path),
else => unreachable,
}
},
.cmake => |file_source| {
try output.appendSlice(c_generated_line);
try bw.writeAll(c_generated_line);
const src_path = file_source.getPath2(b, step);
const contents = std.fs.cwd().readFileAlloc(arena, src_path, config_header.max_bytes) catch |err| {
return step.fail("unable to read cmake input file '{s}': {s}", .{
src_path, @errorName(err),
});
};
try render_cmake(step, contents, &output, config_header.values, src_path);
try render_cmake(step, contents, bw, config_header.values, src_path);
},
.blank => {
try output.appendSlice(c_generated_line);
try render_blank(&output, config_header.values, config_header.include_path, config_header.include_guard_override);
try bw.writeAll(c_generated_line);
try render_blank(gpa, bw, config_header.values, config_header.include_path, config_header.include_guard_override);
},
.nasm => {
try output.appendSlice(asm_generated_line);
try render_nasm(&output, config_header.values);
try bw.writeAll(asm_generated_line);
try render_nasm(bw, config_header.values);
},
}
man.hash.addBytes(output.items);
const output = aw.getWritten();
man.hash.addBytes(output);
if (try step.cacheHit(&man)) {
const digest = man.final();
@@ -256,13 +259,13 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
const sub_path_dirname = std.fs.path.dirname(sub_path).?;
b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
return step.fail("unable to make path '{}{s}': {s}", .{
return step.fail("unable to make path '{f}{s}': {s}", .{
b.cache_root, sub_path_dirname, @errorName(err),
});
};
b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = output.items }) catch |err| {
return step.fail("unable to write file '{}{s}': {s}", .{
b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = output }) catch |err| {
return step.fail("unable to write file '{f}{s}': {s}", .{
b.cache_root, sub_path, @errorName(err),
});
};
@@ -274,7 +277,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
fn render_autoconf_undef(
step: *Step,
contents: []const u8,
output: *std.ArrayList(u8),
bw: *Writer,
values: std.StringArrayHashMap(Value),
src_path: []const u8,
) !void {
@@ -289,15 +292,15 @@ fn render_autoconf_undef(
var line_it = std.mem.splitScalar(u8, contents, '\n');
while (line_it.next()) |line| : (line_index += 1) {
if (!std.mem.startsWith(u8, line, "#")) {
try output.appendSlice(line);
try output.appendSlice("\n");
try bw.writeAll(line);
try bw.writeByte('\n');
continue;
}
var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
const undef = it.next().?;
if (!std.mem.eql(u8, undef, "undef")) {
try output.appendSlice(line);
try output.appendSlice("\n");
try bw.writeAll(line);
try bw.writeByte('\n');
continue;
}
const name = it.next().?;
@@ -309,7 +312,7 @@ fn render_autoconf_undef(
continue;
};
is_used.set(index);
try renderValueC(output, name, values.values()[index]);
try renderValueC(bw, name, values.values()[index]);
}
var unused_value_it = is_used.iterator(.{ .kind = .unset });
@@ -326,12 +329,13 @@ fn render_autoconf_undef(
fn render_autoconf_at(
step: *Step,
contents: []const u8,
output: *std.ArrayList(u8),
aw: *std.io.Writer.Allocating,
values: std.StringArrayHashMap(Value),
src_path: []const u8,
) !void {
const build = step.owner;
const allocator = build.allocator;
const bw = &aw.interface;
const used = allocator.alloc(bool, values.count()) catch @panic("OOM");
for (used) |*u| u.* = false;
@@ -343,11 +347,11 @@ fn render_autoconf_at(
while (line_it.next()) |line| : (line_index += 1) {
const last_line = line_it.index == line_it.buffer.len;
const old_len = output.items.len;
expand_variables_autoconf_at(output, line, values, used) catch |err| switch (err) {
const old_len = aw.getWritten().len;
expand_variables_autoconf_at(bw, line, values, used) catch |err| switch (err) {
error.MissingValue => {
const name = output.items[old_len..];
defer output.shrinkRetainingCapacity(old_len);
const name = aw.getWritten()[old_len..];
defer aw.shrinkRetainingCapacity(old_len);
try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
src_path, line_index + 1, name,
});
@@ -362,9 +366,7 @@ fn render_autoconf_at(
continue;
},
};
if (!last_line) {
try output.append('\n');
}
if (!last_line) try bw.writeByte('\n');
}
for (values.unmanaged.entries.slice().items(.key), used) |name, u| {
@@ -374,15 +376,13 @@ fn render_autoconf_at(
}
}
if (any_errors) {
return error.MakeFailed;
}
if (any_errors) return error.MakeFailed;
}
fn render_cmake(
step: *Step,
contents: []const u8,
output: *std.ArrayList(u8),
bw: *Writer,
values: std.StringArrayHashMap(Value),
src_path: []const u8,
) !void {
@@ -417,10 +417,8 @@ fn render_cmake(
defer allocator.free(line);
if (!std.mem.startsWith(u8, line, "#")) {
try output.appendSlice(line);
if (!last_line) {
try output.appendSlice("\n");
}
try bw.writeAll(line);
if (!last_line) try bw.writeByte('\n');
continue;
}
var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
@@ -428,10 +426,8 @@ fn render_cmake(
if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and
!std.mem.eql(u8, cmakedefine, "cmakedefine01"))
{
try output.appendSlice(line);
if (!last_line) {
try output.appendSlice("\n");
}
try bw.writeAll(line);
if (!last_line) try bw.writeByte('\n');
continue;
}
@@ -502,7 +498,7 @@ fn render_cmake(
value = Value{ .ident = it.rest() };
}
try renderValueC(output, name, value);
try renderValueC(bw, name, value);
}
if (any_errors) {
@@ -511,13 +507,14 @@ fn render_cmake(
}
fn render_blank(
output: *std.ArrayList(u8),
gpa: std.mem.Allocator,
bw: *Writer,
defines: std.StringArrayHashMap(Value),
include_path: []const u8,
include_guard_override: ?[]const u8,
) !void {
const include_guard_name = include_guard_override orelse blk: {
const name = try output.allocator.dupe(u8, include_path);
const name = try gpa.dupe(u8, include_path);
for (name) |*byte| {
switch (byte.*) {
'a'...'z' => byte.* = byte.* - 'a' + 'A',
@@ -527,92 +524,53 @@ fn render_blank(
}
break :blk name;
};
defer if (include_guard_override == null) gpa.free(include_guard_name);
try output.appendSlice("#ifndef ");
try output.appendSlice(include_guard_name);
try output.appendSlice("\n#define ");
try output.appendSlice(include_guard_name);
try output.appendSlice("\n");
try bw.print(
\\#ifndef {[0]s}
\\#define {[0]s}
\\
, .{include_guard_name});
const values = defines.values();
for (defines.keys(), 0..) |name, i| {
try renderValueC(output, name, values[i]);
}
for (defines.keys(), 0..) |name, i| try renderValueC(bw, name, values[i]);
try output.appendSlice("#endif /* ");
try output.appendSlice(include_guard_name);
try output.appendSlice(" */\n");
try bw.print(
\\#endif /* {s} */
\\
, .{include_guard_name});
}
fn render_nasm(output: *std.ArrayList(u8), defines: std.StringArrayHashMap(Value)) !void {
const values = defines.values();
for (defines.keys(), 0..) |name, i| {
try renderValueNasm(output, name, values[i]);
}
fn render_nasm(bw: *Writer, defines: std.StringArrayHashMap(Value)) !void {
for (defines.keys(), defines.values()) |name, value| try renderValueNasm(bw, name, value);
}
fn renderValueC(output: *std.ArrayList(u8), name: []const u8, value: Value) !void {
fn renderValueC(bw: *Writer, name: []const u8, value: Value) !void {
switch (value) {
.undef => {
try output.appendSlice("/* #undef ");
try output.appendSlice(name);
try output.appendSlice(" */\n");
},
.defined => {
try output.appendSlice("#define ");
try output.appendSlice(name);
try output.appendSlice("\n");
},
.boolean => |b| {
try output.appendSlice("#define ");
try output.appendSlice(name);
try output.appendSlice(if (b) " 1\n" else " 0\n");
},
.int => |i| {
try output.writer().print("#define {s} {d}\n", .{ name, i });
},
.ident => |ident| {
try output.writer().print("#define {s} {s}\n", .{ name, ident });
},
.string => |string| {
// TODO: use C-specific escaping instead of zig string literals
try output.writer().print("#define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) });
},
.undef => try bw.print("/* #undef {s} */\n", .{name}),
.defined => try bw.print("#define {s}\n", .{name}),
.boolean => |b| try bw.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
.int => |i| try bw.print("#define {s} {d}\n", .{ name, i }),
.ident => |ident| try bw.print("#define {s} {s}\n", .{ name, ident }),
// TODO: use C-specific escaping instead of zig string literals
.string => |string| try bw.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
}
}
fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !void {
fn renderValueNasm(bw: *Writer, name: []const u8, value: Value) !void {
switch (value) {
.undef => {
try output.appendSlice("; %undef ");
try output.appendSlice(name);
try output.appendSlice("\n");
},
.defined => {
try output.appendSlice("%define ");
try output.appendSlice(name);
try output.appendSlice("\n");
},
.boolean => |b| {
try output.appendSlice("%define ");
try output.appendSlice(name);
try output.appendSlice(if (b) " 1\n" else " 0\n");
},
.int => |i| {
try output.writer().print("%define {s} {d}\n", .{ name, i });
},
.ident => |ident| {
try output.writer().print("%define {s} {s}\n", .{ name, ident });
},
.string => |string| {
// TODO: use nasm-specific escaping instead of zig string literals
try output.writer().print("%define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) });
},
.undef => try bw.print("; %undef {s}\n", .{name}),
.defined => try bw.print("%define {s}\n", .{name}),
.boolean => |b| try bw.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
.int => |i| try bw.print("%define {s} {d}\n", .{ name, i }),
.ident => |ident| try bw.print("%define {s} {s}\n", .{ name, ident }),
// TODO: use nasm-specific escaping instead of zig string literals
.string => |string| try bw.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
}
}
fn expand_variables_autoconf_at(
output: *std.ArrayList(u8),
bw: *Writer,
contents: []const u8,
values: std.StringArrayHashMap(Value),
used: []bool,
@@ -637,23 +595,17 @@ fn expand_variables_autoconf_at(
const key = contents[curr + 1 .. close_pos];
const index = values.getIndex(key) orelse {
// Report the missing key to the caller.
try output.appendSlice(key);
try bw.writeAll(key);
return error.MissingValue;
};
const value = values.unmanaged.entries.slice().items(.value)[index];
used[index] = true;
try output.appendSlice(contents[source_offset..curr]);
try bw.writeAll(contents[source_offset..curr]);
switch (value) {
.undef, .defined => {},
.boolean => |b| {
try output.append(if (b) '1' else '0');
},
.int => |i| {
try output.writer().print("{d}", .{i});
},
.ident, .string => |s| {
try output.appendSlice(s);
},
.boolean => |b| try bw.writeByte(@as(u8, '0') + @intFromBool(b)),
.int => |i| try bw.print("{d}", .{i}),
.ident, .string => |s| try bw.writeAll(s),
}
curr = close_pos;
@@ -661,7 +613,7 @@ fn expand_variables_autoconf_at(
}
}
try output.appendSlice(contents[source_offset..]);
try bw.writeAll(contents[source_offset..]);
}
fn expand_variables_cmake(
@@ -669,7 +621,7 @@ fn expand_variables_cmake(
contents: []const u8,
values: std.StringArrayHashMap(Value),
) ![]const u8 {
var result = std.ArrayList(u8).init(allocator);
var result: std.ArrayList(u8) = .init(allocator);
errdefer result.deinit();
const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
@@ -681,7 +633,7 @@ fn expand_variables_cmake(
source: usize,
target: usize,
};
var var_stack = std.ArrayList(Position).init(allocator);
var var_stack: std.ArrayList(Position) = .init(allocator);
defer var_stack.deinit();
loop: while (curr < contents.len) : (curr += 1) {
switch (contents[curr]) {
@@ -707,7 +659,7 @@ fn expand_variables_cmake(
try result.append(if (b) '1' else '0');
},
.int => |i| {
try result.writer().print("{d}", .{i});
try result.print("{d}", .{i});
},
.ident, .string => |s| {
try result.appendSlice(s);
@@ -764,7 +716,7 @@ fn expand_variables_cmake(
try result.append(if (b) '1' else '0');
},
.int => |i| {
try result.writer().print("{d}", .{i});
try result.print("{d}", .{i});
},
.ident, .string => |s| {
try result.appendSlice(s);
@@ -801,17 +753,17 @@ fn testReplaceVariablesAutoconfAt(
expected: []const u8,
values: std.StringArrayHashMap(Value),
) !void {
var output = std.ArrayList(u8).init(allocator);
var output: std.io.Writer.Allocating = .init(allocator);
defer output.deinit();
const used = try allocator.alloc(bool, values.count());
for (used) |*u| u.* = false;
defer allocator.free(used);
try expand_variables_autoconf_at(&output, contents, values, used);
try expand_variables_autoconf_at(&output.interface, contents, values, used);
for (used) |u| if (!u) return error.UnusedValue;
try std.testing.expectEqualStrings(expected, output.items);
try std.testing.expectEqualStrings(expected, output.getWritten());
}
fn testReplaceVariablesCMake(
@@ -828,7 +780,7 @@ fn testReplaceVariablesCMake(
test "expand_variables_autoconf_at simple cases" {
const allocator = std.testing.allocator;
var values = std.StringArrayHashMap(Value).init(allocator);
var values: std.StringArrayHashMap(Value) = .init(allocator);
defer values.deinit();
// empty strings are preserved
@@ -924,7 +876,7 @@ test "expand_variables_autoconf_at simple cases" {
test "expand_variables_autoconf_at edge cases" {
const allocator = std.testing.allocator;
var values = std.StringArrayHashMap(Value).init(allocator);
var values: std.StringArrayHashMap(Value) = .init(allocator);
defer values.deinit();
// @-vars resolved only when they wrap valid characters, otherwise considered literals
@@ -940,7 +892,7 @@ test "expand_variables_autoconf_at edge cases" {
test "expand_variables_cmake simple cases" {
const allocator = std.testing.allocator;
var values = std.StringArrayHashMap(Value).init(allocator);
var values: std.StringArrayHashMap(Value) = .init(allocator);
defer values.deinit();
try values.putNoClobber("undef", .undef);
@@ -1028,7 +980,7 @@ test "expand_variables_cmake simple cases" {
test "expand_variables_cmake edge cases" {
const allocator = std.testing.allocator;
var values = std.StringArrayHashMap(Value).init(allocator);
var values: std.StringArrayHashMap(Value) = .init(allocator);
defer values.deinit();
// special symbols
@@ -1089,7 +1041,7 @@ test "expand_variables_cmake edge cases" {
test "expand_variables_cmake escaped characters" {
const allocator = std.testing.allocator;
var values = std.StringArrayHashMap(Value).init(allocator);
var values: std.StringArrayHashMap(Value) = .init(allocator);
defer values.deinit();
try values.putNoClobber("string", Value{ .string = "text" });