From fbd1a648ebd0145034b8229dbc3545244f8f3a9a Mon Sep 17 00:00:00 2001 From: Tau Date: Sat, 13 Jul 2024 18:33:37 +0200 Subject: [PATCH 1/6] For error reporting, assume a struct is not a tuple when ambiguous Fixes #14334: non-tuples are more common, and forgetting a type or name from a struct field is an easier mistake to make than adding a name to a tuple field. Also addresses #18597 (the specific case presented there, but not the general goal of that issue) by checking for mismatched parentheses after a parser error. Unclosed parens can cause otherwise much-more-cryptic errors. --- lib/std/zig/AstGen.zig | 41 +++++++++++++++---- lib/std/zig/Parse.zig | 31 ++++++++++++++ .../compile_errors/tuple_declarations.zig | 3 +- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index aa617144345f..666aacdd7005 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -4935,10 +4935,17 @@ fn structDeclInner( const bodies_start = astgen.scratch.items.len; const node_tags = tree.nodes.items(.tag); - const is_tuple = for (container_decl.ast.members) |member_node| { + var has_any_fields = false; + var first_field_name_token: ?Ast.TokenIndex = null; + for (container_decl.ast.members) |member_node| { const container_field = tree.fullContainerField(member_node) orelse continue; - if (container_field.ast.tuple_like) break true; - } else false; + has_any_fields = true; + if (!container_field.ast.tuple_like) { + first_field_name_token = container_field.ast.main_token; + break; + } + } + const is_tuple = has_any_fields and first_field_name_token == null; if (is_tuple) switch (layout) { .auto => {}, @@ -5003,10 +5010,15 @@ fn structDeclInner( fields_hasher.update(tree.getNodeSource(member_node)); if (!is_tuple) { + if (member.ast.tuple_like) { + return astgen.failTokNotes(member.ast.main_token, "struct field needs a name and a type", .{}, &.{ + try astgen.errNoteTok(first_field_name_token.?, "to make this a tuple type, remove all field names", .{}), + }); + } + const field_name = try astgen.identAsString(member.ast.main_token); member.convertToNonTupleLike(astgen.tree.nodes); - assert(!member.ast.tuple_like); wip_members.appendToField(@intFromEnum(field_name)); @@ -5019,8 +5031,6 @@ fn structDeclInner( gop.value_ptr.* = .{}; try gop.value_ptr.append(sfba_allocator, member.ast.main_token); } - } else if (!member.ast.tuple_like) { - return astgen.failTok(member.ast.main_token, "tuple field has a name", .{}); } const doc_comment_index = try astgen.docCommentAsString(member.firstToken()); @@ -13815,12 +13825,25 @@ 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 token_starts = tree.tokens.items(.start); const token_tags = tree.tokens.items(.tag); + if (try @import("Parse.zig").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}); + } + + var msg: std.ArrayListUnmanaged(u8) = .{}; + defer msg.deinit(gpa); + var notes: std.ArrayListUnmanaged(u32) = .{}; defer notes.deinit(gpa); diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index 369e2ef125b4..73f258028438 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -205,6 +205,37 @@ 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 or !parenMatch(stack.pop().tag, t)) + return @intCast(i); + }, + else => {} + } + } + if (stack.items.len > 0) + return stack.pop().idx; + return null; +} + +fn parenMatch(a: Token.Tag, b: Token.Tag) bool { + return switch (a) { + .l_paren => b == .r_paren, + .l_brace => b == .r_brace, + .l_bracket => b == .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/tuple_declarations.zig b/test/cases/compile_errors/tuple_declarations.zig index 9a181e9769e6..d15637572af3 100644 --- a/test/cases/compile_errors/tuple_declarations.zig +++ b/test/cases/compile_errors/tuple_declarations.zig @@ -21,6 +21,7 @@ const T = struct { // // :2:5: error: enum field missing name // :5:5: error: union field missing name -// :8:5: error: tuple field has a name +// :9:5: error: struct field needs a name and a type +// :8:5: note: to make this a tuple type, remove all field names // :15:5: error: tuple declarations cannot contain declarations // :12:5: note: tuple field here From 774d48081e474109aa63b3ef53086cba63d22582 Mon Sep 17 00:00:00 2001 From: Tau Date: Sat, 13 Jul 2024 19:22:39 +0200 Subject: [PATCH 2/6] Improve some error messages Closes #12547: explain why arrays aren't allowed in packed structs. Closes #13832: improve error message for a common mistake on the LHS of the binary `!` operator. --- src/Sema.zig | 14 +++++++++----- ...error_union_operator_with_non_error_set_LHS.zig | 8 +++++++- ...ked_struct_with_fields_of_not_allowed_types.zig | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 2b43b75132f8..03beb45e422e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8666,16 +8666,20 @@ fn zirErrorUnionType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node }); const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node }); - const error_set = try sema.resolveType(block, lhs_src, extra.lhs); + // Do not resolve as type yet, so that we can give a more useful message + // when someone writes `error.XYZ` instead of `error{XYZ}` as the LHS. + const error_set = try sema.resolveConstDefinedValue(block, lhs_src, try sema.resolveInst(extra.lhs), .{ + .needed_comptime_reason = "types must be comptime-known", + }); const payload = try sema.resolveType(block, rhs_src, extra.rhs); - if (error_set.zigTypeTag(mod) != .ErrorSet) { + if (error_set.typeOf(mod).ip_index != .type_type or error_set.toType().zigTypeTag(mod) != .ErrorSet) { return sema.fail(block, lhs_src, "expected error set type, found '{}'", .{ - error_set.fmt(pt), + error_set.fmtValue(pt, sema), }); } try sema.validateErrorUnionPayloadType(block, payload, rhs_src); - const err_union_ty = try pt.errorUnionType(error_set, payload); + const err_union_ty = try pt.errorUnionType(error_set.toType(), payload); return Air.internedToRef(err_union_ty.toIntern()); } @@ -27071,8 +27075,8 @@ fn explainWhyTypeIsNotPacked( .ErrorSet, .AnyFrame, .Optional, - .Array, => try sema.errNote(src_loc, msg, "type has no guaranteed in-memory representation", .{}), + .Array => try sema.errNote(src_loc, msg, "packed fields are ordered according to machine endianness, array elements are not", .{}), .Pointer => if (ty.isSlice(mod)) { try sema.errNote(src_loc, msg, "slices have no guaranteed in-memory representation", .{}); } else { 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 e20b2861cc25..6abe861007dc 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 @@ -1,3 +1,8 @@ +comptime { + const z = error.Foo!i32; + const x: z = undefined; + _ = x; +} comptime { const z = i32!i32; const x: z = undefined; @@ -8,4 +13,5 @@ comptime { // backend=stage2 // target=native // -// :2:15: error: expected error set type, found 'i32' +// :2:15: error: expected error set type, found 'error.Foo' +// :3:15: error: expected error set type, found 'i32' diff --git a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig index fe86990d31ea..f10a26c5e761 100644 --- a/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig +++ b/test/cases/compile_errors/packed_struct_with_fields_of_not_allowed_types.zig @@ -84,7 +84,7 @@ export fn entry14() void { // :3:12: error: packed structs cannot contain fields of type 'anyerror' // :3:12: note: type has no guaranteed in-memory representation // :8:12: error: packed structs cannot contain fields of type '[2]u24' -// :8:12: note: type has no guaranteed in-memory representation +// :8:12: note: packed fields are ordered according to machine endianness, array elements are not // :13:20: error: packed structs cannot contain fields of type 'anyerror!u32' // :13:20: note: type has no guaranteed in-memory representation // :18:12: error: packed structs cannot contain fields of type 'tmp.S' From 9d8208606fdff56ff714e7dfecd814281d78a3c8 Mon Sep 17 00:00:00 2001 From: Tau Date: Sat, 13 Jul 2024 22:19:50 +0200 Subject: [PATCH 3/6] Fix #16702, add some tests --- lib/std/zig/AstGen.zig | 82 +++++++++++++++---- ..._union_operator_with_non_error_set_LHS.zig | 2 +- test/compile_errors.zig | 70 ++++++++++++++++ 3 files changed, 135 insertions(+), 19 deletions(-) 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); From 5e76ee91dc2e0ba486646c1a1c199ebb052e38b0 Mon Sep 17 00:00:00 2001 From: Tau Date: Sat, 13 Jul 2024 22:51:38 +0200 Subject: [PATCH 4/6] Fix #14238 While we can't cover every I/O function like this, lockStdErr covers std.debug.print and std.log.*, probably the most important places to leave a useful hint for newcomers to Zig. --- lib/std/Progress.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index be4d6d5b3a8f..0036ca215d5c 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -543,6 +543,8 @@ fn windowsApiUpdateThreadRun() void { /// /// The lock is recursive; the same thread may hold the lock multiple times. pub fn lockStdErr() void { + if (@inComptime()) + @compileError("I/O cannot be used from comptime. Consider using @compileError() or @compileLog()."); stderr_mutex.lock(); clearWrittenWithEscapeCodes() catch {}; } From dec2a94997df10f55d68d65249db8d9551c74025 Mon Sep 17 00:00:00 2001 From: Tau Date: Sat, 13 Jul 2024 23:26:36 +0200 Subject: [PATCH 5/6] Fix #12829 --- lib/std/builtin.zig | 2 +- lib/std/zig/Parse.zig | 4 ++-- .../@intFromFloat cannot fit - negative out of range.zig | 2 +- .../@intFromFloat cannot fit - negative to unsigned.zig | 2 +- .../@intFromFloat cannot fit - positive out of range.zig | 2 +- test/compile_errors.zig | 2 -- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 3026911d3f97..9f4ca3edbec7 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -904,7 +904,7 @@ pub const panic_messages = struct { pub const divide_by_zero = "division by zero"; pub const exact_division_remainder = "exact division produced remainder"; pub const inactive_union_field = "access of inactive union field"; - pub const integer_part_out_of_bounds = "integer part of floating point value out of bounds"; + pub const integer_part_out_of_bounds = "integer part of floating point value out of bounds or not finite"; pub const corrupt_switch = "switch on corrupt value"; pub const shift_rhs_too_big = "shift amount is greater than the type size"; pub const invalid_enum_value = "invalid enum value"; diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index 73f258028438..68e891b50b1d 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -214,12 +214,12 @@ pub fn findUnmatchedParen(gpa: Allocator, token_tags: []const Token.Tag) !?Token for (token_tags, 0..) |t, i| { switch (t) { - .l_paren, .l_brace, .l_bracket => try stack.append(.{.tag = t, .idx = @intCast(i)}), + .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 or !parenMatch(stack.pop().tag, t)) return @intCast(i); }, - else => {} + else => {}, } } if (stack.items.len > 0) diff --git a/test/cases/safety/@intFromFloat cannot fit - negative out of range.zig b/test/cases/safety/@intFromFloat cannot fit - negative out of range.zig index a5a8d831b337..ad2489bea09b 100644 --- a/test/cases/safety/@intFromFloat cannot fit - negative out of range.zig +++ b/test/cases/safety/@intFromFloat cannot fit - negative out of range.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) { + if (std.mem.eql(u8, message, "integer part of floating point value out of bounds or not finite")) { std.process.exit(0); } std.process.exit(1); diff --git a/test/cases/safety/@intFromFloat cannot fit - negative to unsigned.zig b/test/cases/safety/@intFromFloat cannot fit - negative to unsigned.zig index 1bf1a667659f..377d32549fae 100644 --- a/test/cases/safety/@intFromFloat cannot fit - negative to unsigned.zig +++ b/test/cases/safety/@intFromFloat cannot fit - negative to unsigned.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) { + if (std.mem.eql(u8, message, "integer part of floating point value out of bounds or not finite")) { std.process.exit(0); } std.process.exit(1); diff --git a/test/cases/safety/@intFromFloat cannot fit - positive out of range.zig b/test/cases/safety/@intFromFloat cannot fit - positive out of range.zig index 15a9fa7ad188..e58cf36e2929 100644 --- a/test/cases/safety/@intFromFloat cannot fit - positive out of range.zig +++ b/test/cases/safety/@intFromFloat cannot fit - positive out of range.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { _ = stack_trace; - if (std.mem.eql(u8, message, "integer part of floating point value out of bounds")) { + if (std.mem.eql(u8, message, "integer part of floating point value out of bounds or not finite")) { std.process.exit(0); } std.process.exit(1); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index c61206d7c06b..055d4262dfe0 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -62,7 +62,6 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { }); } - { const case = ctx.obj("unmatched parentheses", b.graph.host); @@ -74,7 +73,6 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { ":3:1: error: unmatched curly brace", ":2:2: error: expected 'EOF', found '}'", }); - } { From 627ab69f1d86be269f2d7965e3173669262af06b Mon Sep 17 00:00:00 2001 From: Tau Date: Tue, 16 Jul 2024 18:43:12 +0200 Subject: [PATCH 6/6] Suppress other messages when there are mismatched parentheses --- lib/std/zig/AstGen.zig | 45 ++++++++++++++++--- lib/std/zig/Parse.zig | 10 ++--- src/Sema.zig | 2 +- .../compile_errors/invalid_unicode_escape.zig | 2 +- test/compile_errors.zig | 3 -- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index f54998e26d1b..693023481fee 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; @@ -13870,11 +13872,12 @@ fn lowerAstErrors(astgen: *AstGen) !void { const gpa = astgen.gpa; const parse_err = tree.errors[0]; + 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 @import("Parse.zig").findUnmatchedParen(astgen.gpa, token_tags)) |tok| { + 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", @@ -13885,6 +13888,37 @@ fn lowerAstErrors(astgen: *AstGen) !void { 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) = .{}; @@ -13893,11 +13927,10 @@ fn lowerAstErrors(astgen: *AstGen) !void { 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 68e891b50b1d..271b87fedf75 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -216,7 +216,7 @@ pub fn findUnmatchedParen(gpa: Allocator, token_tags: []const Token.Tag) !?Token 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 or !parenMatch(stack.pop().tag, t)) + if (stack.items.len == 0 or t != closingParen(stack.pop().tag)) return @intCast(i); }, else => {}, @@ -227,11 +227,11 @@ pub fn findUnmatchedParen(gpa: Allocator, token_tags: []const Token.Tag) !?Token return null; } -fn parenMatch(a: Token.Tag, b: Token.Tag) bool { +fn closingParen(a: Token.Tag) Token.Tag { return switch (a) { - .l_paren => b == .r_paren, - .l_brace => b == .r_brace, - .l_bracket => b == .r_bracket, + .l_paren => .r_paren, + .l_brace => .r_brace, + .l_bracket => .r_bracket, else => unreachable, }; } diff --git a/src/Sema.zig b/src/Sema.zig index 03beb45e422e..276d57f9ebc5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8675,7 +8675,7 @@ fn zirErrorUnionType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr if (error_set.typeOf(mod).ip_index != .type_type or error_set.toType().zigTypeTag(mod) != .ErrorSet) { return sema.fail(block, lhs_src, "expected error set type, found '{}'", .{ - error_set.fmtValue(pt, sema), + error_set.fmtValue(pt), }); } try sema.validateErrorUnionPayloadType(block, payload, rhs_src); 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 055d4262dfe0..81ce2cbe1cdd 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -71,7 +71,6 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\} , &[_][]const u8{ ":3:1: error: unmatched curly brace", - ":2:2: error: expected 'EOF', found '}'", }); } @@ -84,7 +83,6 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\}; , &[_][]const u8{ ":2:1: error: unmatched parenthesis", - ":2:1: error: expected statement, found ')'", }); } @@ -102,7 +100,6 @@ pub fn addCases(ctx: *Cases, b: *std.Build) !void { \\} , &[_][]const u8{ ":8:1: error: unmatched curly brace", - ":5:15: error: expected type expression, found '{'", }); }