diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 666aacdd7005..f54998e26d1b 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -2216,8 +2216,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn } } if (break_label != 0) { - const label_name = try astgen.identifierTokenString(break_label); - return astgen.failTok(break_label, "label not found: '{s}'", .{label_name}); + const label_name = astgen.fmtIdentifier(break_label); + return astgen.failTok(break_label, "label not found: {}", .{label_name}); } else { return astgen.failNode(node, "break expression outside loop", .{}); } @@ -2289,8 +2289,8 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) } } if (break_label != 0) { - const label_name = try astgen.identifierTokenString(break_label); - return astgen.failTok(break_label, "label not found: '{s}'", .{label_name}); + const label_name = astgen.fmtIdentifier(break_label); + return astgen.failTok(break_label, "label not found: {}", .{label_name}); } else { return astgen.failNode(node, "continue expression outside loop", .{}); } @@ -2400,10 +2400,8 @@ fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: Ast.Toke const gen_zir = scope.cast(GenZir).?; if (gen_zir.label) |prev_label| { if (try astgen.tokenIdentEql(label, prev_label.token)) { - const label_name = try astgen.identifierTokenString(label); - return astgen.failTokNotes(label, "redefinition of label '{s}'", .{ - label_name, - }, &[_]u32{ + const label_name = astgen.fmtIdentifier(label); + return astgen.failTokNotes(label, "redefinition of label {}", .{label_name}, &[_]u32{ try astgen.errNoteTok( prev_label.token, "previous definition here", @@ -4740,8 +4738,8 @@ fn testDecl( .top => break, }; if (found_already == null) { - const ident_name = try astgen.identifierTokenString(test_name_token); - return astgen.failTok(test_name_token, "use of undeclared identifier '{s}'", .{ident_name}); + const ident_name = astgen.fmtIdentifier(test_name_token); + return astgen.failTok(test_name_token, "use of undeclared identifier {}", .{ident_name}); } break :blk .{ .decltest = name_str_index }; @@ -8300,8 +8298,8 @@ fn localVarRef( // Can't close over a runtime variable if (num_namespaces_out != 0 and !local_ptr.maybe_comptime and !gz.is_typeof) { - const ident_name = try astgen.identifierTokenString(ident_token); - return astgen.failNodeNotes(ident, "mutable '{s}' not accessible from here", .{ident_name}, &.{ + const ident_name = astgen.fmtIdentifier(ident_token); + return astgen.failNodeNotes(ident, "mutable {} not accessible from here", .{ident_name}, &.{ try astgen.errNoteTok(local_ptr.token_src, "declared mutable here", .{}), try astgen.errNoteNode(capturing_namespace.node, "crosses namespace boundary here", .{}), }); @@ -8355,10 +8353,8 @@ fn localVarRef( }, .top => break, }; - if (found_already == null) { - const ident_name = try astgen.identifierTokenString(ident_token); - return astgen.failNode(ident, "use of undeclared identifier '{s}'", .{ident_name}); - } + if (found_already == null) + return astgen.failNode(ident, "use of undeclared identifier {}", .{astgen.fmtIdentifier(ident_token)}); // Decl references happen by name rather than ZIR index so that when unrelated // decls are modified, ZIR code containing references to them can be unmodified. @@ -9238,8 +9234,8 @@ fn builtinCall( .top => break, }; if (found_already == null) { - const ident_name = try astgen.identifierTokenString(ident_token); - return astgen.failNode(params[0], "use of undeclared identifier '{s}'", .{ident_name}); + const ident_name = astgen.fmtIdentifier(ident_token); + return astgen.failNode(params[0], "use of undeclared identifier {}", .{ident_name}); } }, .field_access => { @@ -11215,6 +11211,56 @@ fn rvalueInner( } } +fn fmtIdentifier(astgen: *AstGen, ident_token: Ast.TokenIndex) std.fmt.Formatter(formatIdentifier) { + return .{ .data = .{ + .self = astgen, + .token_idx = ident_token, + } }; +} + +const IdentifierFmt = struct { + self: *AstGen, + token_idx: Ast.TokenIndex, +}; + +fn formatIdentifier( + ctx: IdentifierFmt, + comptime unused_format_string: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, +) !void { + comptime assert(unused_format_string.len == 0); + const astgen = ctx.self; + + const token_slice = astgen.tree.tokenSlice(ctx.token_idx); + var buf: ArrayListUnmanaged(u8) = .{}; + defer buf.deinit(astgen.gpa); + + const complete_slice = if (token_slice[0] == '@') blk: { + try astgen.parseStrLit(ctx.token_idx, &buf, token_slice, 1); + break :blk buf.items; + } else token_slice; + + const first = complete_slice[0]; + const needs_escaping = if (!std.ascii.isAlphabetic(first) and first != '_') + true + else for (complete_slice[1..]) |c| { + if (!std.ascii.isAlphanumeric(c) and c != '_') { + break true; + } + } else false; + + if (needs_escaping) { + try writer.writeAll("@\""); + try std.zig.stringEscape(complete_slice, "", .{}, writer); + try writer.writeByte('"'); + } else { + try writer.writeByte('\''); + try writer.writeAll(complete_slice); + try writer.writeByte('\''); + } +} + /// Given an identifier token, obtain the string for it. /// If the token uses @"" syntax, parses as a string, reports errors if applicable, /// and allocates the result within `astgen.arena`. diff --git a/test/cases/compile_errors/error_union_operator_with_non_error_set_LHS.zig b/test/cases/compile_errors/error_union_operator_with_non_error_set_LHS.zig index 6abe861007dc..9a2461e8f08a 100644 --- a/test/cases/compile_errors/error_union_operator_with_non_error_set_LHS.zig +++ b/test/cases/compile_errors/error_union_operator_with_non_error_set_LHS.zig @@ -14,4 +14,4 @@ comptime { // target=native // // :2:15: error: expected error set type, found 'error.Foo' -// :3:15: error: expected error set type, found 'i32' +// :7:15: error: expected error set type, found 'i32' diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 5c5a574caf5b..c61206d7c06b 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -38,6 +38,76 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { }); } + { + const case = ctx.obj("identifier escapes", b.graph.host); + + case.addError( + \\export fn a() void { + \\ return @"Á"; + \\} + \\ + \\export fn b() void { + \\ return @"B"; + \\} + \\ + \\export fn c() void { + \\ return @"a\nb"; + \\} + , &[_][]const u8{ + \\:2:12: error: use of undeclared identifier @"\xc3\x81" + , + \\:6:12: error: use of undeclared identifier 'B' + , + \\:10:12: error: use of undeclared identifier @"a\nb" + }); + } + + + { + const case = ctx.obj("unmatched parentheses", b.graph.host); + + case.addError( + \\export fn a() void { + \\} + \\} + , &[_][]const u8{ + ":3:1: error: unmatched curly brace", + ":2:2: error: expected 'EOF', found '}'", + }); + + } + + { + const case = ctx.obj("unmatched parentheses #2", b.graph.host); + + case.addError( + \\const c = { + \\) + \\}; + , &[_][]const u8{ + ":2:1: error: unmatched parenthesis", + ":2:1: error: expected statement, found ')'", + }); + } + + { + const case = ctx.obj("unmatched parentheses #3", b.graph.host); + + case.addError( + \\pub fn bar() void { + \\ // Oops... + \\ } + \\ + \\ if (true) { + \\ return; + \\ } + \\} + , &[_][]const u8{ + ":8:1: error: unmatched curly brace", + ":5:15: error: expected type expression, found '{'", + }); + } + { const case = ctx.obj("isolated carriage return in multiline string literal", b.graph.host);