`std.zig.fmtId`: conditionally escape primitives/`_` (breaking)
This updates std.zig.fmtId to support conditionally @"" escaping primitives and the reserved _ identifier via format specifiers:
-
{}: escape invalid identifiers, identifiers that shadow primitives and the reserved_identifier. -
{p}: same as{}, but don't escape identifiers that shadow primitives. -
{_}: same as{}, but don't escape the reserved_identifier. -
{p_}or{_p}: only escape invalid identifiers.
In other words, the default empty {} specifier is as conservative as possible and you add p and/or _ to add primitives/_ to the set of identifiers that can be rendered unescaped.
Any other format specifier is a compile error.
Motivation
Correctly formatted and escaped code generation. Status quo fmtId is problematic because it doesn't escape primitives, which means programs that generate Zig code currently need to implement their own formatters that also check std.zig.primitives.isPrimitive for rendering declaration identifiers.
Breaking changes and mitigations
-
print("{}", .{std.zig.fmtId(foo)})did not@""escape primitives before, but now does. Use{p}for the old behavior. -
print("{s}", .{std.zig.fmtId(foo)})(or any other unrecognized specifier) is now a compile error. Again, use{p}for the old behavior. -
isValidIdnow considers_to be a valid identifier (which it is, in certain contexts). If this distinction is important, consider combining existing uses of this function with the newisUnderscorefunction;std.zig.isValidId(foo)should be replaced withstd.zig.isValidId(foo) and !std.zig.isUnderscore(foo).
Additional considerations
fmtId can currently render illegal identifiers like @"" and @"\x00". It could be updated to return an error for these cases, but I would suggest accepting #14534 instead since it would ease the burden of code generators and mean that every possible identifier can be represented in Zig in some form, either escaped or unescaped.