From e21162f1139bc270674f1ef7ab35dff2f4ae976e Mon Sep 17 00:00:00 2001 From: WillLillis Date: Wed, 5 Feb 2025 22:01:14 -0500 Subject: [PATCH] improve error message for unterminated string and character literals --- lib/std/zig/Ast.zig | 11 ++++++++ lib/std/zig/AstGen.zig | 28 ++++++++++++++----- .../character_literal_with_newline.zig | 8 ++++++ .../normal_string_with_newline.zig | 2 +- test/compile_errors.zig | 16 +++++++++++ 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 test/cases/compile_errors/character_literal_with_newline.zig diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index d4503b95ca43..f25e613ef774 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -458,6 +458,16 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { return stream.writeAll("for input is not captured"); }, + .unterminated_literal => { + return stream.print("unterminated '{s} literal'", .{ + switch (tree.source[tree.tokens.items(.start)[parse_error.token]]) { + '\'' => "character", + '"' => "string", + else => unreachable, + }, + }); + }, + .invalid_byte => { const tok_slice = tree.source[tree.tokens.items(.start)[parse_error.token]..]; return stream.print("{s} contains invalid byte: '{'}'", .{ @@ -3003,6 +3013,7 @@ pub const Error = struct { var_const_decl, extra_for_capture, for_input_not_captured, + unterminated_literal, zig_style_container, previous_field, diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index c105a371ef8d..d55885e61ebb 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -14039,15 +14039,29 @@ fn lowerAstErrors(astgen: *AstGen) !void { } break :blk idx - tok_start; }; - - const err: Ast.Error = .{ - .tag = Ast.Error.Tag.invalid_byte, - .token = tok, - .extra = .{ .offset = bad_off }, - }; + const err: Ast.Error = + if ((start_char == '\"' or start_char == '\'') and // If `tok` is a string or character literal and... + (tok_start + bad_off == tree.source.len or // it's directly followed by EOF, or... + tree.source[tok_start + bad_off] == '\n')) // it's terminated by a newline. + .{ + .tag = Ast.Error.Tag.unterminated_literal, + .token = tok, + } + else + .{ + .tag = Ast.Error.Tag.invalid_byte, + .token = tok, + .extra = .{ .offset = bad_off }, + }; msg.clearRetainingCapacity(); try tree.renderError(err, msg.writer(gpa)); - return try astgen.appendErrorTokNotesOff(tok, bad_off, "{s}", .{msg.items}, notes.items); + return try astgen.appendErrorTokNotesOff( + tok, + if (err.tag == .invalid_byte) bad_off else 0, + "{s}", + .{msg.items}, + notes.items, + ); } var cur_err = tree.errors[0]; diff --git a/test/cases/compile_errors/character_literal_with_newline.zig b/test/cases/compile_errors/character_literal_with_newline.zig new file mode 100644 index 000000000000..c94f410eb21d --- /dev/null +++ b/test/cases/compile_errors/character_literal_with_newline.zig @@ -0,0 +1,8 @@ +const foo = 'a +'; + +// error +// backend=stage2 +// target=native +// +// :1:13: error: unterminated 'character literal' diff --git a/test/cases/compile_errors/normal_string_with_newline.zig b/test/cases/compile_errors/normal_string_with_newline.zig index 71fc6352f115..47e3181c8c68 100644 --- a/test/cases/compile_errors/normal_string_with_newline.zig +++ b/test/cases/compile_errors/normal_string_with_newline.zig @@ -5,4 +5,4 @@ b"; // backend=stage2 // target=native // -// :1:15: error: string literal contains invalid byte: '\n' +// :1:13: error: unterminated 'string literal' diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 8472cfbf7ed8..fb37ecde4b61 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -237,6 +237,22 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { }); } + { + const case = ctx.obj("unterminated character literal eof", b.graph.host); + + case.addError("const c = '", &[_][]const u8{ + ":1:11: error: unterminated 'character literal'", + }); + } + + { + const case = ctx.obj("unterminated string literal eof", b.graph.host); + + case.addError("const s = \"hello, ", &[_][]const u8{ + ":1:11: error: unterminated 'string literal'", + }); + } + { const case = ctx.obj("invalid byte at start of token", b.graph.host);