Skip to content

Commit 355ccee

Browse files
authored
Merge pull request #18920 from castholm/fmtId
`std.zig.fmtId`: conditionally escape primitives/`_` (breaking)
2 parents 1b27146 + 2465c32 commit 355ccee

13 files changed

+167
-93
lines changed

lib/compiler/aro_translate_c/ast.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ const Context = struct {
828828
fn addIdentifier(c: *Context, bytes: []const u8) Allocator.Error!TokenIndex {
829829
if (std.zig.primitives.isPrimitive(bytes))
830830
return c.addTokenFmt(.identifier, "@\"{s}\"", .{bytes});
831-
return c.addTokenFmt(.identifier, "{s}", .{std.zig.fmtId(bytes)});
831+
return c.addTokenFmt(.identifier, "{p}", .{std.zig.fmtId(bytes)});
832832
}
833833

834834
fn listToSpan(c: *Context, list: []const NodeIndex) Allocator.Error!NodeSubRange {
@@ -2106,7 +2106,7 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {
21062106
members[1] = 0;
21072107

21082108
for (payload.fields, 0..) |field, i| {
2109-
const name_tok = try c.addTokenFmt(.identifier, "{s}", .{std.zig.fmtId(field.name)});
2109+
const name_tok = try c.addTokenFmt(.identifier, "{p}", .{std.zig.fmtId(field.name)});
21102110
_ = try c.addToken(.colon, ":");
21112111
const type_expr = try renderNode(c, field.type);
21122112

@@ -2199,7 +2199,7 @@ fn renderFieldAccess(c: *Context, lhs: NodeIndex, field_name: []const u8) !NodeI
21992199
.main_token = try c.addToken(.period, "."),
22002200
.data = .{
22012201
.lhs = lhs,
2202-
.rhs = try c.addTokenFmt(.identifier, "{s}", .{std.zig.fmtId(field_name)}),
2202+
.rhs = try c.addTokenFmt(.identifier, "{p}", .{std.zig.fmtId(field_name)}),
22032203
},
22042204
});
22052205
}

lib/compiler/test_runner.zig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,10 @@ fn mainServer() !void {
106106
.fail = fail,
107107
.skip = skip,
108108
.leak = leak,
109-
.log_err_count = std.math.lossyCast(std.meta.FieldType(
110-
std.zig.Server.Message.TestResults.Flags,
111-
.log_err_count,
112-
), log_err_count),
109+
.log_err_count = std.math.lossyCast(
110+
@TypeOf(@as(std.zig.Server.Message.TestResults.Flags, undefined).log_err_count),
111+
log_err_count,
112+
),
113113
},
114114
});
115115
},

lib/std/Build/Step/Options.zig

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ fn printType(self: *Options, out: anytype, comptime T: type, value: T, indent: u
234234
try printEnum(self, out, T, info, indent);
235235

236236
if (name) |some| {
237-
try out.print("pub const {}: {s} = .{s};\n", .{
237+
try out.print("pub const {}: {} = .{p_};\n", .{
238238
std.zig.fmtId(some),
239239
std.zig.fmtId(@typeName(T)),
240240
std.zig.fmtId(@tagName(value)),
@@ -246,7 +246,7 @@ fn printType(self: *Options, out: anytype, comptime T: type, value: T, indent: u
246246
try printStruct(self, out, T, info, indent);
247247

248248
if (name) |some| {
249-
try out.print("pub const {}: {s} = ", .{
249+
try out.print("pub const {}: {} = ", .{
250250
std.zig.fmtId(some),
251251
std.zig.fmtId(@typeName(T)),
252252
});
@@ -279,7 +279,7 @@ fn printEnum(self: *Options, out: anytype, comptime T: type, comptime val: std.b
279279

280280
inline for (val.fields) |field| {
281281
try out.writeByteNTimes(' ', indent);
282-
try out.print(" {} = {d},\n", .{ std.zig.fmtId(field.name), field.value });
282+
try out.print(" {p} = {d},\n", .{ std.zig.fmtId(field.name), field.value });
283283
}
284284

285285
if (!val.is_exhaustive) {
@@ -313,9 +313,9 @@ fn printStruct(self: *Options, out: anytype, comptime T: type, comptime val: std
313313

314314
// If the type name doesn't contains a '.' the type is from zig builtins.
315315
if (std.mem.containsAtLeast(u8, type_name, 1, ".")) {
316-
try out.print(" {}: {}", .{ std.zig.fmtId(field.name), std.zig.fmtId(type_name) });
316+
try out.print(" {p_}: {}", .{ std.zig.fmtId(field.name), std.zig.fmtId(type_name) });
317317
} else {
318-
try out.print(" {}: {s}", .{ std.zig.fmtId(field.name), type_name });
318+
try out.print(" {p_}: {s}", .{ std.zig.fmtId(field.name), type_name });
319319
}
320320

321321
if (field.default_value != null) {
@@ -355,7 +355,7 @@ fn printStructValue(self: *Options, out: anytype, comptime struct_val: std.built
355355
} else {
356356
inline for (struct_val.fields) |field| {
357357
try out.writeByteNTimes(' ', indent);
358-
try out.print(" .{} = ", .{std.zig.fmtId(field.name)});
358+
try out.print(" .{p_} = ", .{std.zig.fmtId(field.name)});
359359

360360
const field_name = @field(val, field.name);
361361
switch (@typeInfo(@TypeOf(field_name))) {

lib/std/zig.zig

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub const Tokenizer = tokenizer.Tokenizer;
1010
pub const string_literal = @import("zig/string_literal.zig");
1111
pub const number_literal = @import("zig/number_literal.zig");
1212
pub const primitives = @import("zig/primitives.zig");
13+
pub const isPrimitive = primitives.isPrimitive;
1314
pub const Ast = @import("zig/Ast.zig");
1415
pub const AstGen = @import("zig/AstGen.zig");
1516
pub const Zir = @import("zig/Zir.zig");
@@ -728,20 +729,87 @@ const tokenizer = @import("zig/tokenizer.zig");
728729
const assert = std.debug.assert;
729730
const Allocator = std.mem.Allocator;
730731

731-
/// Return a Formatter for a Zig identifier
732+
/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
733+
///
734+
/// - An empty `{}` format specifier escapes invalid identifiers, identifiers that shadow primitives
735+
/// and the reserved `_` identifier.
736+
/// - Add `p` to the specifier to render identifiers that shadow primitives unescaped.
737+
/// - Add `_` to the specifier to render the reserved `_` identifier unescaped.
738+
/// - `p` and `_` can be combined, e.g. `{p_}`.
739+
///
732740
pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) {
733741
return .{ .data = bytes };
734742
}
735743

736-
/// Print the string as a Zig identifier escaping it with @"" syntax if needed.
744+
test fmtId {
745+
const expectFmt = std.testing.expectFmt;
746+
try expectFmt("@\"while\"", "{}", .{fmtId("while")});
747+
try expectFmt("@\"while\"", "{p}", .{fmtId("while")});
748+
try expectFmt("@\"while\"", "{_}", .{fmtId("while")});
749+
try expectFmt("@\"while\"", "{p_}", .{fmtId("while")});
750+
try expectFmt("@\"while\"", "{_p}", .{fmtId("while")});
751+
752+
try expectFmt("hello", "{}", .{fmtId("hello")});
753+
try expectFmt("hello", "{p}", .{fmtId("hello")});
754+
try expectFmt("hello", "{_}", .{fmtId("hello")});
755+
try expectFmt("hello", "{p_}", .{fmtId("hello")});
756+
try expectFmt("hello", "{_p}", .{fmtId("hello")});
757+
758+
try expectFmt("@\"type\"", "{}", .{fmtId("type")});
759+
try expectFmt("type", "{p}", .{fmtId("type")});
760+
try expectFmt("@\"type\"", "{_}", .{fmtId("type")});
761+
try expectFmt("type", "{p_}", .{fmtId("type")});
762+
try expectFmt("type", "{_p}", .{fmtId("type")});
763+
764+
try expectFmt("@\"_\"", "{}", .{fmtId("_")});
765+
try expectFmt("@\"_\"", "{p}", .{fmtId("_")});
766+
try expectFmt("_", "{_}", .{fmtId("_")});
767+
try expectFmt("_", "{p_}", .{fmtId("_")});
768+
try expectFmt("_", "{_p}", .{fmtId("_")});
769+
770+
try expectFmt("@\"i123\"", "{}", .{fmtId("i123")});
771+
try expectFmt("i123", "{p}", .{fmtId("i123")});
772+
try expectFmt("@\"4four\"", "{}", .{fmtId("4four")});
773+
try expectFmt("_underscore", "{}", .{fmtId("_underscore")});
774+
try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")});
775+
try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")});
776+
777+
// These are technically not currently legal in Zig.
778+
try expectFmt("@\"\"", "{}", .{fmtId("")});
779+
try expectFmt("@\"\\x00\"", "{}", .{fmtId("\x00")});
780+
}
781+
782+
/// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.
737783
fn formatId(
738784
bytes: []const u8,
739-
comptime unused_format_string: []const u8,
785+
comptime fmt: []const u8,
740786
options: std.fmt.FormatOptions,
741787
writer: anytype,
742788
) !void {
743-
_ = unused_format_string;
744-
if (isValidId(bytes)) {
789+
const allow_primitive, const allow_underscore = comptime parse_fmt: {
790+
var allow_primitive = false;
791+
var allow_underscore = false;
792+
for (fmt) |char| {
793+
switch (char) {
794+
'p' => if (!allow_primitive) {
795+
allow_primitive = true;
796+
continue;
797+
},
798+
'_' => if (!allow_underscore) {
799+
allow_underscore = true;
800+
continue;
801+
},
802+
else => {},
803+
}
804+
@compileError("expected {}, {p}, {_}, {p_} or {_p}, found {" ++ fmt ++ "}");
805+
}
806+
break :parse_fmt .{ allow_primitive, allow_underscore };
807+
};
808+
809+
if (isValidId(bytes) and
810+
(allow_primitive or !std.zig.isPrimitive(bytes)) and
811+
(allow_underscore or !isUnderscore(bytes)))
812+
{
745813
return writer.writeAll(bytes);
746814
}
747815
try writer.writeAll("@\"");
@@ -757,12 +825,8 @@ pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(stringEscape) {
757825
return .{ .data = bytes };
758826
}
759827

760-
test "escape invalid identifiers" {
828+
test fmtEscapes {
761829
const expectFmt = std.testing.expectFmt;
762-
try expectFmt("@\"while\"", "{}", .{fmtId("while")});
763-
try expectFmt("hello", "{}", .{fmtId("hello")});
764-
try expectFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")});
765-
try expectFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")});
766830
try expectFmt("\\x0f", "{}", .{fmtEscapes("\x0f")});
767831
try expectFmt(
768832
\\" \\ hi \x07 \x11 " derp \'"
@@ -816,7 +880,6 @@ pub fn stringEscape(
816880

817881
pub fn isValidId(bytes: []const u8) bool {
818882
if (bytes.len == 0) return false;
819-
if (std.mem.eql(u8, bytes, "_")) return false;
820883
for (bytes, 0..) |c, i| {
821884
switch (c) {
822885
'_', 'a'...'z', 'A'...'Z' => {},
@@ -836,6 +899,18 @@ test isValidId {
836899
try std.testing.expect(isValidId("i386"));
837900
}
838901

902+
pub fn isUnderscore(bytes: []const u8) bool {
903+
return bytes.len == 1 and bytes[0] == '_';
904+
}
905+
906+
test isUnderscore {
907+
try std.testing.expect(isUnderscore("_"));
908+
try std.testing.expect(!isUnderscore("__"));
909+
try std.testing.expect(!isUnderscore("_foo"));
910+
try std.testing.expect(isUnderscore("\x5f"));
911+
try std.testing.expect(!isUnderscore("\\x5f"));
912+
}
913+
839914
pub fn readSourceFileToEndAlloc(
840915
allocator: Allocator,
841916
input: std.fs.File,

lib/std/zig/render.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2847,8 +2847,8 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote
28472847
return renderQuotedIdentifier(r, token_index, space, false);
28482848
}
28492849

2850-
// Special case for _ which would incorrectly be rejected by isValidId below.
2851-
if (contents.len == 1 and contents[0] == '_') switch (quote) {
2850+
// Special case for _.
2851+
if (std.zig.isUnderscore(contents)) switch (quote) {
28522852
.eagerly_unquote => return renderQuotedIdentifier(r, token_index, space, true),
28532853
.eagerly_unquote_except_underscore,
28542854
.preserve_when_shadowing,

src/Builtin.zig

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void {
3535
\\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.
3636
\\pub const zig_version = std.SemanticVersion.parse(zig_version_string) catch unreachable;
3737
\\pub const zig_version_string = "{s}";
38-
\\pub const zig_backend = std.builtin.CompilerBackend.{};
38+
\\pub const zig_backend = std.builtin.CompilerBackend.{p_};
3939
\\
40-
\\pub const output_mode = std.builtin.OutputMode.{};
41-
\\pub const link_mode = std.builtin.LinkMode.{};
40+
\\pub const output_mode = std.builtin.OutputMode.{p_};
41+
\\pub const link_mode = std.builtin.LinkMode.{p_};
4242
\\pub const is_test = {};
4343
\\pub const single_threaded = {};
44-
\\pub const abi = std.Target.Abi.{};
44+
\\pub const abi = std.Target.Abi.{p_};
4545
\\pub const cpu: std.Target.Cpu = .{{
46-
\\ .arch = .{},
47-
\\ .model = &std.Target.{}.cpu.{},
48-
\\ .features = std.Target.{}.featureSet(&[_]std.Target.{}.Feature{{
46+
\\ .arch = .{p_},
47+
\\ .model = &std.Target.{p_}.cpu.{p_},
48+
\\ .features = std.Target.{p_}.featureSet(&[_]std.Target.{p_}.Feature{{
4949
\\
5050
, .{
5151
build_options.version,
@@ -66,14 +66,14 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void {
6666
const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
6767
const is_enabled = target.cpu.features.isEnabled(index);
6868
if (is_enabled) {
69-
try buffer.writer().print(" .{},\n", .{std.zig.fmtId(feature.name)});
69+
try buffer.writer().print(" .{p_},\n", .{std.zig.fmtId(feature.name)});
7070
}
7171
}
7272
try buffer.writer().print(
7373
\\ }}),
7474
\\}};
7575
\\pub const os = std.Target.Os{{
76-
\\ .tag = .{},
76+
\\ .tag = .{p_},
7777
\\ .version_range = .{{
7878
,
7979
.{std.zig.fmtId(@tagName(target.os.tag))},
@@ -180,8 +180,8 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void {
180180
const link_libc = opts.link_libc;
181181

182182
try buffer.writer().print(
183-
\\pub const object_format = std.Target.ObjectFormat.{};
184-
\\pub const mode = std.builtin.OptimizeMode.{};
183+
\\pub const object_format = std.Target.ObjectFormat.{p_};
184+
\\pub const mode = std.builtin.OptimizeMode.{p_};
185185
\\pub const link_libc = {};
186186
\\pub const link_libcpp = {};
187187
\\pub const have_error_return_tracing = {};
@@ -190,7 +190,7 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void {
190190
\\pub const position_independent_code = {};
191191
\\pub const position_independent_executable = {};
192192
\\pub const strip_debug_info = {};
193-
\\pub const code_model = std.builtin.CodeModel.{};
193+
\\pub const code_model = std.builtin.CodeModel.{p_};
194194
\\pub const omit_frame_pointer = {};
195195
\\
196196
, .{
@@ -209,11 +209,10 @@ pub fn append(opts: @This(), buffer: *std.ArrayList(u8)) Allocator.Error!void {
209209
});
210210

211211
if (target.os.tag == .wasi) {
212-
const wasi_exec_model_fmt = std.zig.fmtId(@tagName(opts.wasi_exec_model));
213212
try buffer.writer().print(
214-
\\pub const wasi_exec_model = std.builtin.WasiExecModel.{};
213+
\\pub const wasi_exec_model = std.builtin.WasiExecModel.{p_};
215214
\\
216-
, .{wasi_exec_model_fmt});
215+
, .{std.zig.fmtId(@tagName(opts.wasi_exec_model))});
217216
}
218217

219218
if (opts.is_test) {

src/InternPool.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ pub const NullTerminatedString = enum(u32) {
488488
if (comptime std.mem.eql(u8, specifier, "")) {
489489
try writer.writeAll(s);
490490
} else if (comptime std.mem.eql(u8, specifier, "i")) {
491-
try writer.print("{}", .{std.zig.fmtId(s)});
491+
try writer.print("{p}", .{std.zig.fmtId(s)});
492492
} else @compileError("invalid format string '" ++ specifier ++ "' for '" ++ @typeName(NullTerminatedString) ++ "'");
493493
}
494494

src/main.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6943,7 +6943,7 @@ fn cmdFetch(
69436943
std.zig.fmtEscapes(&hex_digest),
69446944
});
69456945

6946-
const new_node_text = try std.fmt.allocPrint(arena, ".{} = {s},\n", .{
6946+
const new_node_text = try std.fmt.allocPrint(arena, ".{p_} = {s},\n", .{
69476947
std.zig.fmtId(name), new_node_init,
69486948
});
69496949

0 commit comments

Comments
 (0)