diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index a6be743c2bc0..501591c6188a 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -1,6 +1,8 @@ //! Ingests an AST and produces ZIR code. const AstGen = @This(); +const Parse = @import("Parse.zig"); + const std = @import("std"); const Ast = std.zig.Ast; const mem = std.mem; @@ -13816,21 +13818,65 @@ fn lowerAstErrors(astgen: *AstGen) !void { const gpa = astgen.gpa; const parse_err = tree.errors[0]; - - var msg: std.ArrayListUnmanaged(u8) = .{}; - defer msg.deinit(gpa); + const err_tok = parse_err.token + @intFromBool(parse_err.token_is_prev); const token_starts = tree.tokens.items(.start); const token_tags = tree.tokens.items(.tag); + if (try Parse.findUnmatchedParen(astgen.gpa, token_tags)) |tok| { + const text: []const u8 = switch (token_tags[tok]) { + .l_paren => "unclosed parenthesis", + .l_brace => "unclosed curly brace", + .l_bracket => "unclosed bracket", + .r_paren => "unmatched parenthesis", + .r_brace => "unmatched curly brace", + .r_bracket => "unmatched bracket", + else => unreachable, + }; + try astgen.appendErrorTok(tok, "{s}", .{text}); + // Unmatched parentheses are often an underlying cause of + // otherwise more obscure errors, so we only report the parse + // error if it probably wasn't caused by this. + switch (parse_err.tag) { + .asterisk_after_ptr_deref, + .chained_comparison_operators, + .expected_inlinable, + .expected_labelable, + .expected_prefix_expr, + .expected_return_type, + .extern_fn_body, + .extra_addrspace_qualifier, + .extra_align_qualifier, + .extra_allowzero_qualifier, + .extra_const_qualifier, + .extra_volatile_qualifier, + .ptr_mod_on_array_child_type, + .invalid_bit_range, + .same_line_doc_comment, + .test_doc_comment, + .comptime_doc_comment, + .varargs_nonfinal, + .expected_continue_expr, + .mismatched_binary_op_whitespace, + .invalid_ampersand_ampersand, + .extra_for_capture, + .for_input_not_captured, + => {}, + .expected_token => if (token_tags[err_tok] != .invalid) return, + else => return, + } + } + + var msg: std.ArrayListUnmanaged(u8) = .{}; + defer msg.deinit(gpa); + var notes: std.ArrayListUnmanaged(u32) = .{}; defer notes.deinit(gpa); - const tok = parse_err.token + @intFromBool(parse_err.token_is_prev); - if (token_tags[tok] == .invalid) { - const bad_off: u32 = @intCast(tree.tokenSlice(tok).len); - const byte_abs = token_starts[tok] + bad_off; - try notes.append(gpa, try astgen.errNoteTokOff(tok, bad_off, "invalid byte: '{'}'", .{ + if (token_tags[err_tok] == .invalid) { + const bad_off: u32 = @intCast(tree.tokenSlice(err_tok).len); + const byte_abs = token_starts[err_tok] + bad_off; + try notes.append(gpa, try astgen.errNoteTokOff(err_tok, bad_off, "invalid byte: '{'}'", .{ std.zig.fmtEscapes(tree.source[byte_abs..][0..1]), })); } diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index c43868a7d4c7..b9755b412ba1 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -205,6 +205,40 @@ pub fn parseZon(p: *Parse) !void { }; } +pub fn findUnmatchedParen(gpa: Allocator, token_tags: []const Token.Tag) !?TokenIndex { + var stack = std.ArrayList(struct { + tag: Token.Tag, + idx: TokenIndex, + }).init(gpa); + defer stack.deinit(); + + for (token_tags, 0..) |t, i| { + switch (t) { + .l_paren, .l_brace, .l_bracket => try stack.append(.{ .tag = t, .idx = @intCast(i) }), + .r_paren, .r_brace, .r_bracket => { + if (stack.items.len == 0) + return @intCast(i); + const opening = stack.pop(); + if (t != closingParen(opening.tag)) + return opening.idx; + }, + else => {}, + } + } + if (stack.items.len > 0) + return stack.pop().idx; + return null; +} + +fn closingParen(a: Token.Tag) Token.Tag { + return switch (a) { + .l_paren => .r_paren, + .l_brace => .r_brace, + .l_bracket => .r_bracket, + else => unreachable, + }; +} + /// ContainerMembers <- ContainerDeclaration* (ContainerField COMMA)* (ContainerField / ContainerDeclaration*) /// /// ContainerDeclaration <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl diff --git a/test/cases/compile_errors/invalid_unicode_escape.zig b/test/cases/compile_errors/invalid_unicode_escape.zig index 1555f2be801a..9251022e1f23 100644 --- a/test/cases/compile_errors/invalid_unicode_escape.zig +++ b/test/cases/compile_errors/invalid_unicode_escape.zig @@ -1,5 +1,5 @@ export fn entry() void { - const a = '\u{12z34}'; + const a = '\u{12z34'; } // error diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 5c5a574caf5b..f6e74d7224f8 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -28,13 +28,53 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\ ); \\} , &[_][]const u8{ - \\:2:5: error: + \\:2:5: error: \\ hello! \\ I'm a multiline error message. \\ I hope to be very useful! - \\ + \\ \\ also I will leave this trailing newline here if you don't mind - \\ + \\ + }); + } + + { + const case = ctx.obj("unmatched parentheses", b.graph.host); + + case.addError( + \\export fn a() void { + \\ + , &[_][]const u8{ + ":1:20: error: unclosed curly brace", + }); + } + + { + const case = ctx.obj("unmatched parentheses #2", b.graph.host); + + case.addError( + \\const c = [ + \\) + \\]; + , &[_][]const u8{ + ":1:11: error: unclosed bracket", + }); + } + + { + 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", }); }