From 60322701dfa155586634c59e6f35125778d68e0e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 9 Apr 2025 18:12:24 +0300 Subject: [PATCH 001/117] Preprocessor: make Macro pub --- src/aro/Preprocessor.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index e4bd42f..728f851 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -60,7 +60,7 @@ const IfContext = struct { const default: IfContext = .{ .kind = @splat(0xFF), .level = 0 }; }; -const Macro = struct { +pub const Macro = struct { /// Parameters of the function type macro params: []const []const u8, @@ -769,7 +769,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans /// Get raw token source string. /// Returned slice is invalidated when comp.generated_buf is updated. -pub fn tokSlice(pp: *Preprocessor, token: anytype) []const u8 { +pub fn tokSlice(pp: *const Preprocessor, token: anytype) []const u8 { if (token.id.lexeme()) |some| return some; const source = pp.comp.getSource(token.source); return source.buf[token.start..token.end]; @@ -2658,7 +2658,7 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! try pp.defineMacro(define_tok, macro_name, .{ .loc = tokFromRaw(macro_name).loc, .tokens = list, - .params = undefined, + .params = &.{}, .is_func = false, .var_args = false, }); -- Gitee From 6d7b44e26e716d962f7ca892c11417b9b4f474fe Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 9 Apr 2025 18:13:31 +0300 Subject: [PATCH 002/117] update to Zig 0.15.0-dev.276+9bbac4288 --- build.zig.zon | 2 +- src/aro/target.zig | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.zig.zon b/build.zig.zon index f6719c4..1a91d7b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ .fingerprint = 0x76501fb842f52025, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.0-dev.27+17b40b1d6", + .minimum_zig_version = "0.15.0-dev.276+9bbac4288", .dependencies = .{}, diff --git a/src/aro/target.zig b/src/aro/target.zig index ed9d2f0..95c4d57 100644 --- a/src/aro/target.zig +++ b/src/aro/target.zig @@ -710,6 +710,8 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { .muslabi64 => "muslabi64", .musleabi => "musleabi", .musleabihf => "musleabihf", + .muslf32 => "muslf32", + .muslsf => "muslsf", .muslx32 => "muslx32", .msvc => "msvc", .itanium => "itanium", -- Gitee From bd554aa9a6a507da2d7cc00e39215c1e0baeb6b7 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 14 Apr 2025 17:32:54 +0300 Subject: [PATCH 003/117] Parser: properly cast compound assignment operands --- build.zig.zon | 2 +- src/aro/CodeGen.zig | 40 ++++++++++++-------- src/aro/Compilation.zig | 2 +- src/aro/Diagnostics/messages.def | 5 +++ src/aro/Parser.zig | 63 +++++++++++++++++++++----------- src/aro/Tree.zig | 53 ++++++++++++++++++--------- test/cases/ast/atomic.c | 9 ++++- test/cases/ast/types.c | 18 +++++++-- test/cases/ast/vectors.c | 11 ++++-- test/cases/casts.c | 2 +- test/cases/nullptr.c | 4 +- 11 files changed, 142 insertions(+), 67 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 1a91d7b..cbcfaea 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ .fingerprint = 0x76501fb842f52025, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.0-dev.276+9bbac4288", + .minimum_zig_version = "0.15.0-dev.345+ec2888858", .dependencies = .{}, diff --git a/src/aro/CodeGen.zig b/src/aro/CodeGen.zig index 49b07a7..632f124 100644 --- a/src/aro/CodeGen.zig +++ b/src/aro/CodeGen.zig @@ -52,6 +52,7 @@ cond_dummy_ref: Ir.Ref = undefined, continue_label: Ir.Ref = undefined, break_label: Ir.Ref = undefined, return_label: Ir.Ref = undefined, +compound_assign_dummy: ?Ir.Ref = null, fn fail(c: *CodeGen, comptime fmt: []const u8, args: anytype) error{ FatalError, OutOfMemory } { try c.comp.diagnostics.list.append(c.comp.gpa, .{ @@ -506,16 +507,16 @@ fn genExpr(c: *CodeGen, node_index: Node.Index) Error!Ir.Ref { try c.builder.addStore(lhs, rhs); return rhs; }, - .mul_assign_expr => |bin| return c.genCompoundAssign(bin, .mul), - .div_assign_expr => |bin| return c.genCompoundAssign(bin, .div), - .mod_assign_expr => |bin| return c.genCompoundAssign(bin, .mod), - .add_assign_expr => |bin| return c.genCompoundAssign(bin, .add), - .sub_assign_expr => |bin| return c.genCompoundAssign(bin, .sub), - .shl_assign_expr => |bin| return c.genCompoundAssign(bin, .bit_shl), - .shr_assign_expr => |bin| return c.genCompoundAssign(bin, .bit_shr), - .bit_and_assign_expr => |bin| return c.genCompoundAssign(bin, .bit_and), - .bit_xor_assign_expr => |bin| return c.genCompoundAssign(bin, .bit_xor), - .bit_or_assign_expr => |bin| return c.genCompoundAssign(bin, .bit_or), + .mul_assign_expr => |bin| return c.genCompoundAssign(bin), + .div_assign_expr => |bin| return c.genCompoundAssign(bin), + .mod_assign_expr => |bin| return c.genCompoundAssign(bin), + .add_assign_expr => |bin| return c.genCompoundAssign(bin), + .sub_assign_expr => |bin| return c.genCompoundAssign(bin), + .shl_assign_expr => |bin| return c.genCompoundAssign(bin), + .shr_assign_expr => |bin| return c.genCompoundAssign(bin), + .bit_and_assign_expr => |bin| return c.genCompoundAssign(bin), + .bit_xor_assign_expr => |bin| return c.genCompoundAssign(bin), + .bit_or_assign_expr => |bin| return c.genCompoundAssign(bin), .bit_or_expr => |bin| return c.genBinOp(bin, .bit_or), .bit_xor_expr => |bin| return c.genBinOp(bin, .bit_xor), .bit_and_expr => |bin| return c.genBinOp(bin, .bit_and), @@ -850,7 +851,7 @@ fn genExpr(c: *CodeGen, node_index: Node.Index) Error!Ir.Ref { const old_sym_len = c.symbols.items.len; c.symbols.items.len = old_sym_len; - for (compound_stmt.body[0 .. compound_stmt.body.len - 1]) |stmt| try c.genStmt(stmt); + for (compound_stmt.body[0..compound_stmt.body.len -| 1]) |stmt| try c.genStmt(stmt); return c.genExpr(compound_stmt.body[compound_stmt.body.len - 1]); }, .builtin_call_expr => |call| { @@ -912,6 +913,9 @@ fn genLval(c: *CodeGen, node_index: Node.Index) Error!Ir.Ref { return c.genLval(conditional.else_expr); } }, + .compound_assign_dummy_expr => { + return c.compound_assign_dummy.?; + }, .member_access_expr, .member_access_ptr_expr, .array_access_expr, @@ -1127,12 +1131,16 @@ fn genCall(c: *CodeGen, call: Node.Call) Error!Ir.Ref { return c.builder.addInst(.call, .{ .call = call_inst }, try c.genType(call.qt)); } -fn genCompoundAssign(c: *CodeGen, bin: Node.Binary, tag: Ir.Inst.Tag) Error!Ir.Ref { - const rhs = try c.genExpr(bin.rhs); +fn genCompoundAssign(c: *CodeGen, bin: Node.Binary) Error!Ir.Ref { const lhs = try c.genLval(bin.lhs); - const res = try c.addBin(tag, lhs, rhs, bin.qt); - try c.builder.addStore(lhs, res); - return res; + + const old_dummy = c.compound_assign_dummy; + defer c.compound_assign_dummy = old_dummy; + c.compound_assign_dummy = lhs; + + const rhs = try c.genExpr(bin.rhs); + try c.builder.addStore(lhs, rhs); + return rhs; } fn genBinOp(c: *CodeGen, bin: Node.Binary, tag: Ir.Inst.Tag) Error!Ir.Ref { diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 0303681..17f5a2b 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -930,7 +930,7 @@ pub fn fixedEnumTagType(comp: *const Compilation) ?QualType { } pub fn getCharSignedness(comp: *const Compilation) std.builtin.Signedness { - return comp.langopts.char_signedness_override orelse comp.target.charSignedness(); + return comp.langopts.char_signedness_override orelse comp.target.cCharSignedness(); } /// Add built-in aro headers directory to system include paths diff --git a/src/aro/Diagnostics/messages.def b/src/aro/Diagnostics/messages.def index 49949fe..e922283 100644 --- a/src/aro/Diagnostics/messages.def +++ b/src/aro/Diagnostics/messages.def @@ -803,6 +803,11 @@ invalid_cast_type .extra = .str .kind = .@"error" +invalid_cast_operand_type + .msg = "operand of type '{s}' where arithmetic or pointer type is required" + .extra = .str + .kind = .@"error" + qual_cast .msg = "cast to type '{s}' will not preserve qualifiers" .extra = .str diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 253cefe..d655431 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -5704,7 +5704,7 @@ pub const Result = struct { inline .addr_of_expr, .deref_expr, .plus_expr, .negate_expr, .bit_not_expr, .bool_not_expr, .pre_inc_expr, .pre_dec_expr, .imag_expr, .real_expr, .post_inc_expr,.post_dec_expr, - .paren_expr, .stmt_expr, .imaginary_literal, + .paren_expr, .stmt_expr, .imaginary_literal, .compound_assign_dummy_expr, // zig fmt: on => |tag| operand.node = try p.addNode(@unionInit(Node, @tagName(tag), un_data)), else => unreachable, @@ -6381,6 +6381,9 @@ pub const Result = struct { // everything can cast to void cast_kind = .to_void; res.val = .{}; + } else if (res.qt.is(p.comp, .void)) { + try p.errStr(.invalid_cast_operand_type, operand_tok, try p.typeStr(res.qt)); + return error.ParsingFailed; } else if (dest_sk == .nullptr_t) { res.val = .{}; if (src_sk == .nullptr_t) { @@ -6493,7 +6496,7 @@ pub const Result = struct { } else if (res.qt.is(p.comp, .func)) { cast_kind = .function_to_pointer; } else { - try p.errStr(.cond_expr_type, operand_tok, try p.typeStr(res.qt)); + try p.errStr(.invalid_cast_operand_type, operand_tok, try p.typeStr(res.qt)); return error.ParsingFailed; } } else if (dest_sk.isFloat()) { @@ -6839,6 +6842,22 @@ fn eatTag(p: *Parser, id: Token.Id) ?std.meta.Tag(Node) { } else return null; } +fn nonAssignExpr(assign_node: std.meta.Tag(Node)) std.meta.Tag(Node) { + return switch (assign_node) { + .mul_assign_expr => .mul_expr, + .div_assign_expr => .div_expr, + .mod_assign_expr => .mod_expr, + .add_assign_expr => .add_expr, + .sub_assign_expr => .sub_expr, + .shl_assign_expr => .shl_expr, + .shr_assign_expr => .shr_expr, + .bit_and_assign_expr => .bit_and_expr, + .bit_xor_assign_expr => .bit_xor_expr, + .bit_or_assign_expr => .bit_or_expr, + else => unreachable, + }; +} + /// assignExpr /// : condExpr /// | unExpr ('=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|=') assignExpr @@ -6866,15 +6885,24 @@ fn assignExpr(p: *Parser) Error!?Result { lhs.qt = .invalid; } - // adjustTypes will do do lvalue conversion but we do not want that - var lhs_copy = lhs; + if (tag == .assign_expr) { + try rhs.coerce(p, lhs.qt, tok, .assign); + + try lhs.bin(p, tag, rhs, tok); + return lhs; + } + + var lhs_dummy = blk: { + var lhs_copy = lhs; + try lhs_copy.un(p, .compound_assign_dummy_expr, tok); + try lhs_copy.lvalConversion(p, tok); + break :blk lhs_copy; + }; switch (tag) { - .assign_expr => {}, // handle plain assignment separately .mul_assign_expr, .div_assign_expr, .mod_assign_expr, => { - try rhs.lvalConversion(p, tok); if (!lhs.qt.isInvalid() and rhs.val.isZero(p.comp) and lhs.qt.isInt(p.comp) and rhs.qt.isInt(p.comp)) { switch (tag) { .div_assign_expr => try p.errStr(.division_by_zero, tok, "division"), @@ -6882,21 +6910,17 @@ fn assignExpr(p: *Parser) Error!?Result { else => {}, } } - _ = try lhs_copy.adjustTypes(tok, &rhs, p, if (tag == .mod_assign_expr) .integer else .arithmetic); - try lhs.bin(p, tag, rhs, tok); - return lhs; + _ = try lhs_dummy.adjustTypes(tok, &rhs, p, if (tag == .mod_assign_expr) .integer else .arithmetic); }, .sub_assign_expr, .add_assign_expr, => { - try rhs.lvalConversion(p, tok); if (!lhs.qt.isInvalid() and lhs.qt.isPointer(p.comp) and rhs.qt.isInt(p.comp)) { - try rhs.castToPointer(p, lhs.qt, tok); + try rhs.lvalConversion(p, tok); + try rhs.castToPointer(p, lhs_dummy.qt, tok); } else { - _ = try lhs_copy.adjustTypes(tok, &rhs, p, .arithmetic); + _ = try lhs_dummy.adjustTypes(tok, &rhs, p, .arithmetic); } - try lhs.bin(p, tag, rhs, tok); - return lhs; }, .shl_assign_expr, .shr_assign_expr, @@ -6904,17 +6928,14 @@ fn assignExpr(p: *Parser) Error!?Result { .bit_xor_assign_expr, .bit_or_assign_expr, => { - try rhs.lvalConversion(p, tok); - _ = try lhs_copy.adjustTypes(tok, &rhs, p, .integer); - try lhs.bin(p, tag, rhs, tok); - return lhs; + _ = try lhs_dummy.adjustTypes(tok, &rhs, p, .integer); }, else => unreachable, } - try rhs.coerce(p, lhs.qt, tok, .assign); - - try lhs.bin(p, tag, rhs, tok); + _ = try lhs_dummy.bin(p, nonAssignExpr(tag), rhs, tok); + try lhs_dummy.coerce(p, lhs.qt, tok, .assign); + try lhs.bin(p, tag, lhs_dummy, tok); return lhs; } diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index 3fbb515..1c4f4c2 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -172,7 +172,6 @@ pub const Node = union(enum) { return_stmt: ReturnStmt, gnu_asm_simple: SimpleAsm, - comma_expr: Binary, assign_expr: Binary, mul_assign_expr: Binary, div_assign_expr: Binary, @@ -184,6 +183,9 @@ pub const Node = union(enum) { bit_and_assign_expr: Binary, bit_xor_assign_expr: Binary, bit_or_assign_expr: Binary, + compound_assign_dummy_expr: Unary, + + comma_expr: Binary, bool_or_expr: Binary, bool_and_expr: Binary, bit_or_expr: Binary, @@ -994,14 +996,6 @@ pub const Node = union(enum) { .asm_str = @enumFromInt(node_data[0]), }, }, - .comma_expr => .{ - .comma_expr = .{ - .op_tok = node_tok, - .qt = @bitCast(node_data[0]), - .lhs = @enumFromInt(node_data[1]), - .rhs = @enumFromInt(node_data[2]), - }, - }, .assign_expr => .{ .assign_expr = .{ .op_tok = node_tok, @@ -1090,6 +1084,21 @@ pub const Node = union(enum) { .rhs = @enumFromInt(node_data[2]), }, }, + .compound_assign_dummy_expr => .{ + .compound_assign_dummy_expr = .{ + .op_tok = node_tok, + .qt = @bitCast(node_data[0]), + .operand = @enumFromInt(node_data[1]), + }, + }, + .comma_expr => .{ + .comma_expr = .{ + .op_tok = node_tok, + .qt = @bitCast(node_data[0]), + .lhs = @enumFromInt(node_data[1]), + .rhs = @enumFromInt(node_data[2]), + }, + }, .bool_or_expr => .{ .bool_or_expr = .{ .op_tok = node_tok, @@ -1763,6 +1772,7 @@ pub const Node = union(enum) { bit_and_assign_expr, bit_xor_assign_expr, bit_or_assign_expr, + compound_assign_dummy_expr, bool_or_expr, bool_and_expr, bit_or_expr, @@ -1842,6 +1852,7 @@ pub const Node = union(enum) { .array_filler_expr, .default_init_expr, .cond_dummy_expr, + .compound_assign_dummy_expr, => true, .return_stmt => |ret| ret.operand == .implicit, .cast => |cast| cast.implicit, @@ -2115,13 +2126,6 @@ pub fn setNode(tree: *Tree, node: Node, index: usize) !void { repr.data[0] = @intFromEnum(gnu_asm_simple.asm_str); repr.tok = gnu_asm_simple.asm_tok; }, - .comma_expr => |bin| { - repr.tag = .comma_expr; - repr.data[0] = @bitCast(bin.qt); - repr.data[1] = @intFromEnum(bin.lhs); - repr.data[2] = @intFromEnum(bin.rhs); - repr.tok = bin.op_tok; - }, .assign_expr => |bin| { repr.tag = .assign_expr; repr.data[0] = @bitCast(bin.qt); @@ -2199,6 +2203,19 @@ pub fn setNode(tree: *Tree, node: Node, index: usize) !void { repr.data[2] = @intFromEnum(bin.rhs); repr.tok = bin.op_tok; }, + .compound_assign_dummy_expr => |un| { + repr.tag = .compound_assign_dummy_expr; + repr.data[0] = @bitCast(un.qt); + repr.data[1] = @intFromEnum(un.operand); + repr.tok = un.op_tok; + }, + .comma_expr => |bin| { + repr.tag = .comma_expr; + repr.data[0] = @bitCast(bin.qt); + repr.data[1] = @intFromEnum(bin.lhs); + repr.data[2] = @intFromEnum(bin.rhs); + repr.tok = bin.op_tok; + }, .bool_or_expr => |bin| { repr.tag = .bool_or_expr; repr.data[0] = @bitCast(bin.qt); @@ -2808,6 +2825,7 @@ pub fn isLvalExtra(tree: *const Tree, node: Node.Index, is_const: *bool) bool { } return false; }, + .compound_assign_dummy_expr => return true, else => return false, } } @@ -3342,7 +3360,6 @@ fn dumpNode( } } }, - .comma_expr, .assign_expr, .mul_assign_expr, .div_assign_expr, @@ -3354,6 +3371,7 @@ fn dumpNode( .bit_and_assign_expr, .bit_xor_assign_expr, .bit_or_assign_expr, + .comma_expr, .bool_or_expr, .bool_and_expr, .bit_or_expr, @@ -3495,6 +3513,7 @@ fn dumpNode( .enum_forward_decl, .default_init_expr, .cond_dummy_expr, + .compound_assign_dummy_expr, => {}, } } diff --git a/test/cases/ast/atomic.c b/test/cases/ast/atomic.c index a12c9c7..64e37db 100644 --- a/test/cases/ast/atomic.c +++ b/test/cases/ast/atomic.c @@ -91,8 +91,13 @@ fn_def: 'fn () void' decl_ref_expr: '_Atomic(int)' lvalue name: a rhs: - implicit cast: (int_cast) '_Atomic(int)' (value: 1) - int_literal: 'int' (value: 1) + add_expr: '_Atomic(int)' + lhs: + implicit cast: (lval_to_rval) '_Atomic(int)' + implicit compound_assign_dummy_expr: '_Atomic(int)' lvalue + rhs: + implicit cast: (int_cast) '_Atomic(int)' (value: 1) + int_literal: 'int' (value: 1) variable: '_Atomic(float)' name: f diff --git a/test/cases/ast/types.c b/test/cases/ast/types.c index 25ada4d..e0ac9ba 100644 --- a/test/cases/ast/types.c +++ b/test/cases/ast/types.c @@ -68,16 +68,26 @@ fn_def: 'fn (a: decayed *const I: [2]int, b: decayed *const I: [2]int) void' decl_ref_expr: 'decayed *const I: [2]int' lvalue name: b rhs: - implicit cast: (int_to_pointer) 'decayed *const I: [2]int' - int_literal: 'int' (value: 1) + add_expr: 'decayed *const I: [2]int' + lhs: + implicit cast: (lval_to_rval) 'decayed *const I: [2]int' + implicit compound_assign_dummy_expr: 'decayed *const I: [2]int' lvalue + rhs: + implicit cast: (int_to_pointer) 'decayed *const I: [2]int' + int_literal: 'int' (value: 1) add_assign_expr: 'decayed *const I: [2]int' lhs: decl_ref_expr: 'decayed *const I: [2]int' lvalue name: a rhs: - implicit cast: (int_to_pointer) 'decayed *const I: [2]int' - int_literal: 'int' (value: 1) + add_expr: 'decayed *const I: [2]int' + lhs: + implicit cast: (lval_to_rval) 'decayed *const I: [2]int' + implicit compound_assign_dummy_expr: 'decayed *const I: [2]int' lvalue + rhs: + implicit cast: (int_to_pointer) 'decayed *const I: [2]int' + int_literal: 'int' (value: 1) implicit return_stmt: 'void' diff --git a/test/cases/ast/vectors.c b/test/cases/ast/vectors.c index 2f9f22f..01da528 100644 --- a/test/cases/ast/vectors.c +++ b/test/cases/ast/vectors.c @@ -46,9 +46,14 @@ fn_def: 'fn () void' decl_ref_expr: 'f2v: vector(2, float)' lvalue name: a rhs: - implicit cast: (vector_splat) 'float' - implicit cast: (int_to_float) 'float' (value: 2) - int_literal: 'int' (value: 2) + mul_expr: 'f2v: vector(2, float)' + lhs: + implicit cast: (lval_to_rval) 'f2v: vector(2, float)' + implicit compound_assign_dummy_expr: 'f2v: vector(2, float)' lvalue + rhs: + implicit cast: (vector_splat) 'float' + implicit cast: (int_to_float) 'float' (value: 2) + int_literal: 'int' (value: 2) implicit return_stmt: 'void' diff --git a/test/cases/casts.c b/test/cases/casts.c index d2c84c5..1e34eb6 100644 --- a/test/cases/casts.c +++ b/test/cases/casts.c @@ -27,6 +27,6 @@ void foo(void) { "casts.c:10:13: warning: cast to smaller integer type 'char' from 'char *' [-Wpointer-to-int-cast]" \ "casts.c:11:13: error: pointer cannot be cast to type 'float'" \ "casts.c:16:5: error: cast to incomplete type 'enum E'" \ - "casts.c:18:18: error: used type 'void' where arithmetic or pointer type is required" \ + "casts.c:18:18: error: operand of type 'void' where arithmetic or pointer type is required" \ "casts.c:19:5: error: cast to incomplete type 'enum DoesNotExist'" \ diff --git a/test/cases/nullptr.c b/test/cases/nullptr.c index d9c2e29..55acae5 100644 --- a/test/cases/nullptr.c +++ b/test/cases/nullptr.c @@ -88,7 +88,7 @@ void baz(void) { void bad_nullptr_use(void) { static_assert(nullptr != 1); static_assert(nullptr != true); - bool b = nullptr; + bool b = nullptr; // TODO clang allows this int x = nullptr; nullptr_t p = 0; float f = (float)nullptr; @@ -106,6 +106,8 @@ void bad_nullptr_use(void) { vp = (nullptr_t)(void *)0; } +#define TESTS_SKIPPED 1 + #define EXPECTED_ERRORS "nullptr.c:89:27: error: invalid operands to binary expression ('nullptr_t' and 'int')" \ "nullptr.c:90:27: error: invalid operands to binary expression ('nullptr_t' and 'bool')" \ "nullptr.c:91:14: error: initializing 'bool' from incompatible type 'nullptr_t'" \ -- Gitee From c4cff955a58df0f9b9fdc56c921ad93926582aa5 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 14 Apr 2025 21:48:09 +0300 Subject: [PATCH 004/117] Parser: correct type adjustment of add/sub compound assignment Always casting to the pointer is too simple and doesn't match the non compound assignment equivalent. --- src/aro/Parser.zig | 14 +++++--------- test/cases/ast/types.c | 6 ++---- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index d655431..39c3027 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -6912,15 +6912,11 @@ fn assignExpr(p: *Parser) Error!?Result { } _ = try lhs_dummy.adjustTypes(tok, &rhs, p, if (tag == .mod_assign_expr) .integer else .arithmetic); }, - .sub_assign_expr, - .add_assign_expr, - => { - if (!lhs.qt.isInvalid() and lhs.qt.isPointer(p.comp) and rhs.qt.isInt(p.comp)) { - try rhs.lvalConversion(p, tok); - try rhs.castToPointer(p, lhs_dummy.qt, tok); - } else { - _ = try lhs_dummy.adjustTypes(tok, &rhs, p, .arithmetic); - } + .sub_assign_expr => { + _ = try lhs_dummy.adjustTypes(tok, &rhs, p, .sub); + }, + .add_assign_expr => { + _ = try lhs_dummy.adjustTypes(tok, &rhs, p, .add); }, .shl_assign_expr, .shr_assign_expr, diff --git a/test/cases/ast/types.c b/test/cases/ast/types.c index e0ac9ba..2caa072 100644 --- a/test/cases/ast/types.c +++ b/test/cases/ast/types.c @@ -73,8 +73,7 @@ fn_def: 'fn (a: decayed *const I: [2]int, b: decayed *const I: [2]int) void' implicit cast: (lval_to_rval) 'decayed *const I: [2]int' implicit compound_assign_dummy_expr: 'decayed *const I: [2]int' lvalue rhs: - implicit cast: (int_to_pointer) 'decayed *const I: [2]int' - int_literal: 'int' (value: 1) + int_literal: 'int' (value: 1) add_assign_expr: 'decayed *const I: [2]int' lhs: @@ -86,8 +85,7 @@ fn_def: 'fn (a: decayed *const I: [2]int, b: decayed *const I: [2]int) void' implicit cast: (lval_to_rval) 'decayed *const I: [2]int' implicit compound_assign_dummy_expr: 'decayed *const I: [2]int' lvalue rhs: - implicit cast: (int_to_pointer) 'decayed *const I: [2]int' - int_literal: 'int' (value: 1) + int_literal: 'int' (value: 1) implicit return_stmt: 'void' -- Gitee From a224f9044e22f38857b5cd752ce0f692a7ed20f9 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk <65823030+wrongnull@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:37:44 +0000 Subject: [PATCH 005/117] update to Zig 0.15.0-dev.355+206bd1ced --- build.zig.zon | 2 +- src/aro/Parser.zig | 2 +- src/aro/Preprocessor.zig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index cbcfaea..2ddcbd2 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ .fingerprint = 0x76501fb842f52025, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.0-dev.345+ec2888858", + .minimum_zig_version = "0.15.0-dev.355+206bd1ced", .dependencies = .{}, diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 39c3027..4b8d305 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -109,7 +109,7 @@ tree: Tree, // buffers used during compilation syms: SymbolStack = .{}, -strings: std.ArrayListAligned(u8, 4), +strings: std.ArrayListAligned(u8, .@"4"), labels: std.ArrayList(Label), list_buf: NodeList, decl_buf: NodeList, diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 728f851..249af00 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -1006,7 +1006,7 @@ fn expr(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!bool { .tok_ids = pp.tokens.items(.id), .tok_i = @intCast(token_state.tokens_len), .in_macro = true, - .strings = std.ArrayListAligned(u8, 4).init(pp.comp.gpa), + .strings = std.ArrayListAligned(u8, .@"4").init(pp.comp.gpa), .tree = undefined, .labels = undefined, -- Gitee From 3daec2b3950ab8aee5b3a8ae3e9cc11fb437e803 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 17 Apr 2025 11:30:38 +0300 Subject: [PATCH 006/117] Preprocessor: preserve order of macro definitions --- src/aro/Preprocessor.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 249af00..426b140 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -17,7 +17,7 @@ const Tree = @import("Tree.zig"); const Token = Tree.Token; const TokenWithExpansionLocs = Tree.TokenWithExpansionLocs; -const DefineMap = std.StringHashMapUnmanaged(Macro); +const DefineMap = std.StringArrayHashMapUnmanaged(Macro); const RawTokenList = std.ArrayList(RawToken); const max_include_depth = 200; @@ -658,7 +658,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans try pp.addToken(tokFromRaw(directive)); } - _ = pp.defines.remove(macro_name); + _ = pp.defines.orderedRemove(macro_name); try pp.expectNl(&tokenizer); }, .keyword_include => { @@ -3361,8 +3361,7 @@ fn prettyPrintMacro(pp: *Preprocessor, w: anytype, loc: Source.Location, parts: } fn prettyPrintMacrosOnly(pp: *Preprocessor, w: anytype) !void { - var it = pp.defines.valueIterator(); - while (it.next()) |macro| { + for (pp.defines.values()) |macro| { if (macro.is_builtin) continue; try w.writeAll("#define "); -- Gitee From cfc7095de7a35ccc7ae930240908ba04e78399fa Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 19 Apr 2025 19:52:01 +0300 Subject: [PATCH 007/117] Parser: implement nullability attribute keywords --- src/aro/Attribute.zig | 21 ++++++++++++++---- src/aro/Diagnostics.zig | 2 ++ src/aro/Diagnostics/messages.def | 22 ++++++++++++++++++ src/aro/Parser.zig | 35 +++++++++++++++++++++++++++++ src/aro/Tokenizer.zig | 20 +++++++++++++++++ src/aro/TypeStore.zig | 20 +++++++++++++++++ test/cases/ast/nullability.c | 38 ++++++++++++++++++++++++++++++++ test/cases/nullability.c | 16 ++++++++++++++ 8 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 test/cases/ast/nullability.c create mode 100644 test/cases/nullability.c diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 996b84d..7585743 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -700,6 +700,18 @@ const attributes = struct { pub const calling_convention = struct { cc: CallingConvention, }; + pub const nullability = struct { + kind: enum { + nonnull, + nullable, + nullable_result, + unspecified, + + const opts = struct { + const enum_kind = .identifier; + }; + }, + }; }; pub const Tag = std.meta.DeclEnum(attributes); @@ -799,7 +811,7 @@ pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, for (attrs, toks) |attr, tok| switch (attr.tag) { // zig fmt: off .alias, .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .weak, .used, - .noinit, .retain, .persistent, .section, .mode, .asm_label, + .noinit, .retain, .persistent, .section, .mode, .asm_label, .nullability, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .common => if (nocommon) { @@ -856,7 +868,8 @@ pub fn applyFieldAttributes(p: *Parser, field_ty: *QualType, attr_buf_start: usi p.attr_application_buf.items.len = 0; for (attrs, toks) |attr, tok| switch (attr.tag) { // zig fmt: off - .@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .mode, .warn_unused_result, .nodiscard, + .@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, + .mode, .warn_unused_result, .nodiscard, .nullability, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .vector_size => try attr.applyVectorSize(p, tok, field_ty), @@ -873,7 +886,7 @@ pub fn applyTypeAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, tag: var base_qt = qt; for (attrs, toks) |attr, tok| switch (attr.tag) { // zig fmt: off - .@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .mode, + .@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .mode, .nullability, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .transparent_union => try attr.applyTransparentUnion(p, tok, base_qt), @@ -908,7 +921,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .noreturn, .unused, .used, .warning, .deprecated, .unavailable, .weak, .pure, .leaf, .@"const", .warn_unused_result, .section, .returns_nonnull, .returns_twice, .@"error", .externally_visible, .retain, .flatten, .gnu_inline, .alias, .asm_label, .nodiscard, - .reproducible, .unsequenced, .nothrow, + .reproducible, .unsequenced, .nothrow, .nullability, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .hot => if (cold) { diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index 31cc4da..f720eca 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -224,6 +224,8 @@ pub const Options = struct { @"atomic-access": Kind = .default, @"gnu-designator": Kind = .default, @"empty-body": Kind = .default, + @"nullability-extension": Kind = .default, + nullability: Kind = .default, }; const Diagnostics = @This(); diff --git a/src/aro/Diagnostics/messages.def b/src/aro/Diagnostics/messages.def index e922283..fd92ff0 100644 --- a/src/aro/Diagnostics/messages.def +++ b/src/aro/Diagnostics/messages.def @@ -2648,3 +2648,25 @@ empty_if_body_note .msg = "put the semicolon on a separate line to silence this warning" .kind = .note .opt = W("empty-body") + +nullability_extension + .msg = "type nullability specifier '{s}' is a Clang extension" + .extra = .str + .kind = .off + .opt = W("nullability-extension") + +duplicate_nullability + .msg = "duplicate nullability specifier '{s}'" + .extra = .str + .kind = .warning + .opt = W("nullability") + +conflicting_nullability + .msg = "nullaibility specifier '{s}' conflicts with existing specifier '{s}'" + .extra = .tok_id + .kind = .@"error" + +invalid_nullability + .msg = "nullability specifier cannot be applied to non-pointer type '{s}'" + .extra = .str + .kind = .@"error" diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 4b8d305..fdd3bc0 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3144,6 +3144,41 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder) Error!bool { else b.atomic = p.tok_i; }, + .keyword_nonnull, .keyword_nullable, .keyword_nullable_result, .keyword_null_unspecified => |tok_id| { + const sym_str = p.tok_ids[p.tok_i].symbol(); + try p.errStr(.nullability_extension, p.tok_i, sym_str); + const new: @FieldType(TypeStore.Builder, "nullability") = switch (tok_id) { + .keyword_nonnull => .{ .nonnull = p.tok_i }, + .keyword_nullable => .{ .nullable = p.tok_i }, + .keyword_nullable_result => .{ .nullable_result = p.tok_i }, + .keyword_null_unspecified => .{ .null_unspecified = p.tok_i }, + else => unreachable, + }; + if (std.meta.activeTag(b.nullability) == new) { + try p.errStr(.duplicate_nullability, p.tok_i, sym_str); + } else switch (b.nullability) { + .none => { + b.nullability = new; + try p.attr_buf.append(p.gpa, .{ + .attr = .{ .tag = .nullability, .args = .{ + .nullability = .{ .kind = switch (tok_id) { + .keyword_nonnull => .nonnull, + .keyword_nullable => .nullable, + .keyword_nullable_result => .nullable_result, + .keyword_null_unspecified => .unspecified, + else => unreachable, + } }, + }, .syntax = .keyword }, + .tok = p.tok_i, + }); + }, + .nonnull, + .nullable, + .nullable_result, + .null_unspecified, + => |prev| try p.errExtra(.conflicting_nullability, p.tok_i, .{ .tok_id = .{ .expected = p.tok_ids[p.tok_i], .actual = p.tok_ids[prev] } }), + } + }, else => break, } p.tok_i += 1; diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 07fcb7a..cbe11b5 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -292,6 +292,12 @@ pub const Token = struct { keyword_vectorcall, keyword_vectorcall2, + // Type nullability + keyword_nonnull, + keyword_nullable, + keyword_nullable_result, + keyword_null_unspecified, + // builtins that require special parsing builtin_choose_expr, builtin_va_arg, @@ -445,6 +451,10 @@ pub const Token = struct { .keyword_thiscall2, .keyword_vectorcall, .keyword_vectorcall2, + .keyword_nonnull, + .keyword_nullable, + .keyword_nullable_result, + .keyword_null_unspecified, .keyword_bit_int, .keyword_c23_alignas, .keyword_c23_alignof, @@ -731,6 +741,10 @@ pub const Token = struct { .keyword_thiscall2 => "_thiscall", .keyword_vectorcall => "__vectorcall", .keyword_vectorcall2 => "_vectorcall", + .keyword_nonnull => "_Nonnull", + .keyword_nullable => "_Nullable", + .keyword_nullable_result => "_Nullable_result", + .keyword_null_unspecified => "_Null_unspecified", }; } @@ -1015,6 +1029,12 @@ pub const Token = struct { .{ "__vectorcall", .keyword_vectorcall }, .{ "_vectorcall", .keyword_vectorcall2 }, + // Type nullability + .{ "_Nonnull", .keyword_nonnull }, + .{ "_Nullable", .keyword_nullable }, + .{ "_Nullable_result", .keyword_nullable_result }, + .{ "_Null_unspecified", .keyword_null_unspecified }, + // builtins that require special parsing .{ "__builtin_choose_expr", .builtin_choose_expr }, .{ "__builtin_va_arg", .builtin_va_arg }, diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 1a4a192..0143d4e 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -2091,6 +2091,14 @@ pub const Builder = struct { @"volatile": ?TokenIndex = null, restrict: ?TokenIndex = null, + nullability: union(enum) { + none, + nonnull: TokenIndex, + nullable: TokenIndex, + nullable_result: TokenIndex, + null_unspecified: TokenIndex, + } = .none, + complex_tok: ?TokenIndex = null, bit_int_tok: ?TokenIndex = null, typedef: bool = false, @@ -2460,6 +2468,18 @@ pub const Builder = struct { } } + // if (b.nullability != .none) { + switch (b.nullability) { + .none => {}, + .nonnull, + .nullable, + .nullable_result, + .null_unspecified, + => |tok| if (!qt.isPointer(b.parser.comp)) { + try b.parser.errStr(.invalid_nullability, tok, try b.parser.typeStr(qt)); + }, + } + if (b.@"const" != null) result_qt.@"const" = true; if (b.@"volatile" != null) result_qt.@"volatile" = true; diff --git a/test/cases/ast/nullability.c b/test/cases/ast/nullability.c new file mode 100644 index 0000000..506f40c --- /dev/null +++ b/test/cases/ast/nullability.c @@ -0,0 +1,38 @@ +implicit typedef: '__int128' + name: __int128_t + +implicit typedef: 'unsigned __int128' + name: __uint128_t + +implicit typedef: '*char' + name: __builtin_ms_va_list + +implicit typedef: '[1]struct __va_list_tag' + name: __builtin_va_list + +implicit typedef: 'struct __NSConstantString_tag' + name: __NSConstantString + +implicit typedef: 'long double' + name: __float80 + +variable: 'attributed(*int)' + attr: nullability kind: nonnull + name: a + +variable: 'attributed(*int)' + attr: nullability kind: nonnull + name: b + +variable: 'attributed(int)' + attr: nullability kind: nonnull + name: c + +fn_proto: 'attributed(fn () int)' + attr: nullability kind: nullable + name: d + +fn_proto: 'attributed(fn () *int)' + attr: nullability kind: unspecified + name: e + diff --git a/test/cases/nullability.c b/test/cases/nullability.c new file mode 100644 index 0000000..ef0fc8b --- /dev/null +++ b/test/cases/nullability.c @@ -0,0 +1,16 @@ +//aro-args --target=x86_64-linux-gnu +int *_Nonnull _Nonnull a; +int _Nonnull *b; +int _Nonnull _Nullable c; +int _Nullable d(void); + +#pragma GCC diagnostic warning "-Wnullability-extension" +int *_Null_unspecified e(void); + +#define EXPECTED_ERRORS \ + "nullability.c:2:15: warning: duplicate nullability specifier '_Nonnull' [-Wnullability]" \ + "nullability.c:3:5: error: nullability specifier cannot be applied to non-pointer type 'int'" \ + "nullability.c:4:14: error: nullaibility specifier '_Nullable' conflicts with existing specifier '_Nonnull'" \ + "nullability.c:4:5: error: nullability specifier cannot be applied to non-pointer type 'int'" \ + "nullability.c:5:5: error: nullability specifier cannot be applied to non-pointer type 'int'" \ + "nullability.c:8:6: warning: type nullability specifier '_Null_unspecified' is a Clang extension [-Wnullability-extension]" \ -- Gitee From 54097ad83b986a399169bb11dd6169a3f728c11b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 19 Apr 2025 20:26:11 +0300 Subject: [PATCH 008/117] Parser: implement some ms keyword attributes --- src/aro/Attribute.zig | 13 ++-- src/aro/Parser.zig | 86 ++++++++++++++++-------- src/aro/Tokenizer.zig | 42 ++++++++++++ src/aro/TypeStore.zig | 9 ++- src/backend.zig | 4 +- test/cases/ast/msvc attribute keywords.c | 32 +++++++++ test/cases/msvc attribute keywords.c | 13 ++++ 7 files changed, 162 insertions(+), 37 deletions(-) create mode 100644 test/cases/ast/msvc attribute keywords.c create mode 100644 test/cases/msvc attribute keywords.c diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 7585743..5a30f35 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -712,6 +712,7 @@ const attributes = struct { }; }, }; + pub const unaligned = struct {}; }; pub const Tag = std.meta.DeclEnum(attributes); @@ -811,7 +812,7 @@ pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, for (attrs, toks) |attr, tok| switch (attr.tag) { // zig fmt: off .alias, .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .weak, .used, - .noinit, .retain, .persistent, .section, .mode, .asm_label, .nullability, + .noinit, .retain, .persistent, .section, .mode, .asm_label, .nullability, .unaligned, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .common => if (nocommon) { @@ -869,7 +870,7 @@ pub fn applyFieldAttributes(p: *Parser, field_ty: *QualType, attr_buf_start: usi for (attrs, toks) |attr, tok| switch (attr.tag) { // zig fmt: off .@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, - .mode, .warn_unused_result, .nodiscard, .nullability, + .mode, .warn_unused_result, .nodiscard, .nullability, .unaligned, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .vector_size => try attr.applyVectorSize(p, tok, field_ty), @@ -886,7 +887,7 @@ pub fn applyTypeAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, tag: var base_qt = qt; for (attrs, toks) |attr, tok| switch (attr.tag) { // zig fmt: off - .@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .mode, .nullability, + .@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .mode, .nullability, .unaligned, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .transparent_union => try attr.applyTransparentUnion(p, tok, base_qt), @@ -921,7 +922,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .noreturn, .unused, .used, .warning, .deprecated, .unavailable, .weak, .pure, .leaf, .@"const", .warn_unused_result, .section, .returns_nonnull, .returns_twice, .@"error", .externally_visible, .retain, .flatten, .gnu_inline, .alias, .asm_label, .nodiscard, - .reproducible, .unsequenced, .nothrow, .nullability, + .reproducible, .unsequenced, .nothrow, .nullability, .unaligned, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .hot => if (cold) { @@ -951,8 +952,8 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .aligned => try attr.applyAligned(p, base_qt, null), .format => try attr.applyFormat(p, base_qt), .calling_convention => switch (attr.args.calling_convention.cc) { - .C => continue, - .stdcall, .thiscall => switch (p.comp.target.cpu.arch) { + .c => continue, + .stdcall, .thiscall, .fastcall, .regcall => switch (p.comp.target.cpu.arch) { .x86 => try p.attr_application_buf.append(p.gpa, attr), else => try p.errStr(.callconv_not_supported, tok, p.tok_ids[tok].lexeme().?), }, diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index fdd3bc0..55805dd 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -1577,6 +1577,14 @@ fn declSpec(p: *Parser) Error!?DeclSpec { p.tok_i += 1; continue; }, + .keyword_forceinline, .keyword_forceinline2 => { + try p.attr_buf.append(p.gpa, .{ + .attr = .{ .tag = .always_inline, .args = .{ .always_inline = .{} }, .syntax = .keyword }, + .tok = p.tok_i, + }); + p.tok_i += 1; + continue; + }, else => {}, } @@ -2049,7 +2057,7 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { try builder.combineFromTypeof(typeof_qt, start); continue; } - if (try p.typeQual(builder)) continue; + if (try p.typeQual(builder, true)) continue; switch (p.tok_ids[p.tok_i]) { .keyword_void => try builder.combine(.void, p.tok_i), .keyword_bool, .keyword_c23_bool => try builder.combine(.bool, p.tok_i), @@ -2133,29 +2141,6 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { try p.expectClosing(l_paren, .r_paren); continue; }, - .keyword_stdcall, - .keyword_stdcall2, - .keyword_thiscall, - .keyword_thiscall2, - .keyword_vectorcall, - .keyword_vectorcall2, - => try p.attr_buf.append(p.gpa, .{ - .attr = .{ .tag = .calling_convention, .args = .{ - .calling_convention = .{ .cc = switch (p.tok_ids[p.tok_i]) { - .keyword_stdcall, - .keyword_stdcall2, - => .stdcall, - .keyword_thiscall, - .keyword_thiscall2, - => .thiscall, - .keyword_vectorcall, - .keyword_vectorcall2, - => .vectorcall, - else => unreachable, - } }, - }, .syntax = .keyword }, - .tok = p.tok_i, - }), .keyword_struct, .keyword_union => { const tag_tok = p.tok_i; const record_ty = try p.recordSpec(); @@ -3114,7 +3099,7 @@ fn enumerator(p: *Parser, e: *Enumerator) Error!?EnumFieldAndNode { } /// typeQual : keyword_const | keyword_restrict | keyword_volatile | keyword_atomic -fn typeQual(p: *Parser, b: *TypeStore.Builder) Error!bool { +fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { var any = false; while (true) { switch (p.tok_ids[p.tok_i]) { @@ -3144,6 +3129,51 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder) Error!bool { else b.atomic = p.tok_i; }, + .keyword_unaligned, .keyword_unaligned2 => { + if (b.unaligned != null) + try p.errStr(.duplicate_decl_spec, p.tok_i, "__unaligned") + else + b.unaligned = p.tok_i; + }, + .keyword_stdcall, + .keyword_stdcall2, + .keyword_thiscall, + .keyword_thiscall2, + .keyword_vectorcall, + .keyword_vectorcall2, + .keyword_fastcall, + .keyword_fastcall2, + .keyword_regcall, + .keyword_cdecl, + .keyword_cdecl2, + => { + if (!allow_cc_attr) break; + try p.attr_buf.append(p.gpa, .{ + .attr = .{ .tag = .calling_convention, .args = .{ + .calling_convention = .{ .cc = switch (p.tok_ids[p.tok_i]) { + .keyword_stdcall, + .keyword_stdcall2, + => .stdcall, + .keyword_thiscall, + .keyword_thiscall2, + => .thiscall, + .keyword_vectorcall, + .keyword_vectorcall2, + => .vectorcall, + .keyword_fastcall, + .keyword_fastcall2, + => .fastcall, + .keyword_regcall, + => .regcall, + .keyword_cdecl, + .keyword_cdecl2, + => .c, + else => unreachable, + } }, + }, .syntax = .keyword }, + .tok = p.tok_i, + }); + }, .keyword_nonnull, .keyword_nullable, .keyword_nullable_result, .keyword_null_unspecified => |tok_id| { const sym_str = p.tok_ids[p.tok_i].symbol(); try p.errStr(.nullability_extension, p.tok_i, sym_str); @@ -3306,7 +3336,7 @@ fn declarator( while (p.eatToken(.asterisk)) |_| { d.declarator_type = .pointer; var builder: TypeStore.Builder = .{ .parser = p }; - _ = try p.typeQual(&builder); + _ = try p.typeQual(&builder, true); const pointer_qt = try p.comp.type_store.put(p.gpa, .{ .pointer = .{ .child = d.qt, @@ -3433,9 +3463,9 @@ fn directDeclarator( var builder: TypeStore.Builder = .{ .parser = p }; - var got_quals = try p.typeQual(&builder); + var got_quals = try p.typeQual(&builder, false); var static = p.eatToken(.keyword_static); - if (static != null and !got_quals) got_quals = try p.typeQual(&builder); + if (static != null and !got_quals) got_quals = try p.typeQual(&builder, false); var star = p.eatToken(.asterisk); const size_tok = p.tok_i; diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index cbe11b5..1250f90 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -291,6 +291,15 @@ pub const Token = struct { keyword_thiscall2, keyword_vectorcall, keyword_vectorcall2, + keyword_fastcall, + keyword_fastcall2, + keyword_regcall, + keyword_cdecl, + keyword_cdecl2, + keyword_forceinline, + keyword_forceinline2, + keyword_unaligned, + keyword_unaligned2, // Type nullability keyword_nonnull, @@ -451,6 +460,15 @@ pub const Token = struct { .keyword_thiscall2, .keyword_vectorcall, .keyword_vectorcall2, + .keyword_fastcall, + .keyword_fastcall2, + .keyword_regcall, + .keyword_cdecl, + .keyword_cdecl2, + .keyword_forceinline, + .keyword_forceinline2, + .keyword_unaligned, + .keyword_unaligned2, .keyword_nonnull, .keyword_nullable, .keyword_nullable_result, @@ -741,6 +759,15 @@ pub const Token = struct { .keyword_thiscall2 => "_thiscall", .keyword_vectorcall => "__vectorcall", .keyword_vectorcall2 => "_vectorcall", + .keyword_fastcall => "__fastcall", + .keyword_fastcall2 => "_fastcall", + .keyword_regcall => "__regcall", + .keyword_cdecl => "__cdecl", + .keyword_cdecl2 => "_cdecl", + .keyword_forceinline => "__forceinline", + .keyword_forceinline2 => "_forceinline", + .keyword_unaligned => "__unaligned", + .keyword_unaligned2 => "_unaligned", .keyword_nonnull => "_Nonnull", .keyword_nullable => "_Nullable", .keyword_nullable_result => "_Nullable_result", @@ -886,6 +913,12 @@ pub const Token = struct { .keyword_stdcall2, .keyword_thiscall2, .keyword_vectorcall2, + .keyword_fastcall2, + .keyword_cdecl2, + .keyword_forceinline, + .keyword_forceinline2, + .keyword_unaligned, + .keyword_unaligned2, => if (langopts.ms_extensions) kw else .identifier, else => kw, }; @@ -1028,6 +1061,15 @@ pub const Token = struct { .{ "_thiscall", .keyword_thiscall2 }, .{ "__vectorcall", .keyword_vectorcall }, .{ "_vectorcall", .keyword_vectorcall2 }, + .{ "__fastcall", .keyword_fastcall }, + .{ "_fastcall", .keyword_fastcall2 }, + .{ "_regcall", .keyword_regcall }, + .{ "__cdecl", .keyword_cdecl }, + .{ "_cdecl", .keyword_cdecl2 }, + .{ "__forceinline", .keyword_forceinline }, + .{ "_forceinline", .keyword_forceinline2 }, + .{ "__unaligned", .keyword_unaligned }, + .{ "_unaligned", .keyword_unaligned2 }, // Type nullability .{ "_Nonnull", .keyword_nonnull }, diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 0143d4e..a43324a 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -2090,7 +2090,7 @@ pub const Builder = struct { atomic: ?TokenIndex = null, @"volatile": ?TokenIndex = null, restrict: ?TokenIndex = null, - + unaligned: ?TokenIndex = null, nullability: union(enum) { none, nonnull: TokenIndex, @@ -2468,7 +2468,12 @@ pub const Builder = struct { } } - // if (b.nullability != .none) { + if (b.unaligned != null and !qt.isPointer(b.parser.comp)) { + result_qt = (try b.parser.comp.type_store.put(b.parser.gpa, .{ .attributed = .{ + .base = result_qt, + .attributes = &.{.{ .tag = .unaligned, .args = .{ .unaligned = .{} }, .syntax = .keyword }}, + } })).withQualifiers(result_qt); + } switch (b.nullability) { .none => {}, .nonnull, diff --git a/src/backend.zig b/src/backend.zig index ba5862b..ebcde31 100644 --- a/src/backend.zig +++ b/src/backend.zig @@ -5,10 +5,12 @@ pub const Ir = @import("backend/Ir.zig"); pub const Object = @import("backend/Object.zig"); pub const CallingConvention = enum { - C, + c, stdcall, thiscall, vectorcall, + fastcall, + regcall, }; pub const version_str = @import("build_options").version_str; diff --git a/test/cases/ast/msvc attribute keywords.c b/test/cases/ast/msvc attribute keywords.c new file mode 100644 index 0000000..2f1eca1 --- /dev/null +++ b/test/cases/ast/msvc attribute keywords.c @@ -0,0 +1,32 @@ +implicit typedef: '__int128' + name: __int128_t + +implicit typedef: 'unsigned __int128' + name: __uint128_t + +implicit typedef: '*char' + name: __builtin_ms_va_list + +implicit typedef: '*char' + name: __builtin_va_list + +implicit typedef: 'struct __NSConstantString_tag' + name: __NSConstantString + +variable: '*attributed(int)' + name: a + +variable: 'attributed(int)' + attr: unaligned + name: b + +fn_proto: 'kr (...) int' + name: foo + +fn_proto: 'attributed(kr (...) *int)' + attr: calling_convention cc: stdcall + name: bar + +fn_proto: 'fn (decayed *[]attributed(int), decayed *attributed([]int)) int' + name: baz + diff --git a/test/cases/msvc attribute keywords.c b/test/cases/msvc attribute keywords.c new file mode 100644 index 0000000..cbc7bd6 --- /dev/null +++ b/test/cases/msvc attribute keywords.c @@ -0,0 +1,13 @@ +//aro-args --target=x86-windows-msvc +int __unaligned * __unaligned a; +int __unaligned b; +int _cdecl foo(); +int *__stdcall bar(); + +int baz(int __unaligned [], int [__unaligned]); +int qux(int __stdcall [], int [__cdecl]); + +#define EXPECTED_ERRORS \ + ".c:8:13: warning: attribute 'calling_convention' ignored on variables [-Wignored-attributes]" \ + ".c:8:32: error: expected ']', found '__cdecl'" \ + ".c:8:31: note: to match this '{'" \ -- Gitee From d1daf86026bbae7ddbf5dd923cc3be593c795e7a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 19 Apr 2025 20:50:33 +0300 Subject: [PATCH 009/117] Attribute: add some arm and riscv calling convention attributes --- src/aro/Attribute.zig | 58 +++++++++++++++++++++++++++++++++++++ src/aro/Attribute/names.def | 16 ++++++++++ src/backend.zig | 5 ++++ 3 files changed, 79 insertions(+) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 5a30f35..87aeeca 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -713,6 +713,19 @@ const attributes = struct { }, }; pub const unaligned = struct {}; + pub const pcs = struct { + kind: enum { + aapcs, + @"aapcs-vfp", + + const opts = struct { + const enum_kind = .string; + }; + }, + }; + pub const riscv_vector_cc = struct {}; + pub const aarch64_sve_pcs = struct {}; + pub const aarch64_vector_pcs = struct {}; }; pub const Tag = std.meta.DeclEnum(attributes); @@ -961,6 +974,51 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .x86, .aarch64, .aarch64_be => try p.attr_application_buf.append(p.gpa, attr), else => try p.errStr(.callconv_not_supported, tok, p.tok_ids[tok].lexeme().?), }, + .riscv_vector, + .aarch64_sve_pcs, + .aarch64_vector_pcs, + .arm_aapcs, + .arm_aapcs_vfp, + => unreachable, // These can't come from keyword syntax + }, + .pcs => if (p.comp.target.cpu.arch.isArm()) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = switch (attr.args.pcs.kind) { + .aapcs => .arm_aapcs, + .@"aapcs-vfp" => .arm_aapcs_vfp, + } } }, + .syntax = attr.syntax, + }); + } else { + try p.errStr(.callconv_not_supported, tok, "pcs"); + }, + .riscv_vector_cc => if (p.comp.target.cpu.arch.isRISCV()) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .riscv_vector } }, + .syntax = attr.syntax, + }); + } else { + try p.errStr(.callconv_not_supported, tok, "pcs"); + }, + .aarch64_sve_pcs => if (p.comp.target.cpu.arch.isAARCH64()) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .aarch64_sve_pcs } }, + .syntax = attr.syntax, + }); + } else { + try p.errStr(.callconv_not_supported, tok, "pcs"); + }, + .aarch64_vector_pcs => if (p.comp.target.cpu.arch.isAARCH64()) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .aarch64_vector_pcs } }, + .syntax = attr.syntax, + }); + } else { + try p.errStr(.callconv_not_supported, tok, "pcs"); }, .malloc => { if (base_qt.get(p.comp, .func).?.return_type.isPointer(p.comp)) { diff --git a/src/aro/Attribute/names.def b/src/aro/Attribute/names.def index 0272264..72e3f91 100644 --- a/src/aro/Attribute/names.def +++ b/src/aro/Attribute/names.def @@ -364,6 +364,22 @@ zero_call_used_regs .tag = .zero_call_used_regs .gnu = true +pcs + .tag = .pcs + .gnu = true + +riscv_vector_cc + .tag = .riscv_vector_cc + .gnu = true + +aarch64_sve_pcs + .tag = .aarch64_sve_pcs + .gnu = true + +aarch64_vector_pcs + .tag = .aarch64_vector_pcs + .gnu = true + # declspec only align .tag = .aligned diff --git a/src/backend.zig b/src/backend.zig index ebcde31..d3dd577 100644 --- a/src/backend.zig +++ b/src/backend.zig @@ -11,6 +11,11 @@ pub const CallingConvention = enum { vectorcall, fastcall, regcall, + riscv_vector, + aarch64_sve_pcs, + aarch64_vector_pcs, + arm_aapcs, + arm_aapcs_vfp, }; pub const version_str = @import("build_options").version_str; -- Gitee From b28b968ee97b05f827275b20c7747717d0881e15 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 19 Apr 2025 20:56:36 +0300 Subject: [PATCH 010/117] Attribute: add gnu syntax equivalents for x86 calling conventions --- src/aro/Attribute.zig | 42 +++++++++++++++++++++++++++++++++++++ src/aro/Attribute/names.def | 21 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 87aeeca..7d92c13 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -726,6 +726,11 @@ const attributes = struct { pub const riscv_vector_cc = struct {}; pub const aarch64_sve_pcs = struct {}; pub const aarch64_vector_pcs = struct {}; + pub const fastcall = struct {}; + pub const stdcall = struct {}; + pub const vectorcall = struct {}; + pub const cdecl = struct {}; + pub const thiscall = struct {}; }; pub const Tag = std.meta.DeclEnum(attributes); @@ -981,6 +986,43 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .arm_aapcs_vfp, => unreachable, // These can't come from keyword syntax }, + .fastcall => if (p.comp.target.cpu.arch == .x86) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .fastcall } }, + .syntax = attr.syntax, + }); + } else { + try p.errStr(.callconv_not_supported, tok, "fastcall"); + }, + .stdcall => if (p.comp.target.cpu.arch == .x86) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .stdcall } }, + .syntax = attr.syntax, + }); + } else { + try p.errStr(.callconv_not_supported, tok, "stdcall"); + }, + .thiscall => if (p.comp.target.cpu.arch == .x86) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .thiscall } }, + .syntax = attr.syntax, + }); + } else { + try p.errStr(.callconv_not_supported, tok, "thiscall"); + }, + .vectorcall => if (p.comp.target.cpu.arch == .x86 or p.comp.target.cpu.arch.isAARCH64()) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .vectorcall } }, + .syntax = attr.syntax, + }); + } else { + try p.errStr(.callconv_not_supported, tok, "vectorcall"); + }, + .cdecl => {}, .pcs => if (p.comp.target.cpu.arch.isArm()) { try p.attr_application_buf.append(p.gpa, .{ .tag = .calling_convention, diff --git a/src/aro/Attribute/names.def b/src/aro/Attribute/names.def index 72e3f91..8aff9ab 100644 --- a/src/aro/Attribute/names.def +++ b/src/aro/Attribute/names.def @@ -380,6 +380,27 @@ aarch64_vector_pcs .tag = .aarch64_vector_pcs .gnu = true +fastcall + .tag = .fastcall + .gnu = true + +stdcall + .tag = .stdcall + .gnu = true + +vectorcall + .tag = .vectorcall + .gnu = true + +cdecl + .tag = .cdecl + .gnu = true + +thiscall + .tag = .thiscall + .gnu = true + + # declspec only align .tag = .aligned -- Gitee From be091cf31bcf1d45a55295b4f629a344999c26e4 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 11 Jan 2025 19:17:30 +0200 Subject: [PATCH 011/117] Diagnostics: add proper support for option groups --- src/aro/Diagnostics.zig | 455 +++++++++++-------- src/aro/Diagnostics/messages.def | 477 ++++++++++---------- src/aro/Driver.zig | 39 +- src/aro/Parser.zig | 23 +- src/aro/Preprocessor.zig | 6 +- src/aro/pragmas/gcc.zig | 20 +- test/cases/_BitInt.c | 2 +- test/cases/complex numbers clang.c | 4 +- test/cases/const decl folding.c | 10 +- test/cases/divide by zero.c | 2 +- test/cases/extension.c | 2 +- test/cases/float builtins.c | 4 +- test/cases/gnu alignof.c | 2 +- test/cases/relocations.c | 10 +- test/cases/signed remainder.c | 2 +- test/cases/sizeof variably modified types.c | 4 +- test/cases/static assert messages.c | 6 +- 17 files changed, 598 insertions(+), 470 deletions(-) diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index f720eca..a7ea5b5 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -76,187 +76,278 @@ const Properties = struct { msg: []const u8, kind: Kind, extra: std.meta.FieldEnum(Message.Extra) = .none, - opt: ?u8 = null, - all: bool = false, - w_extra: bool = false, - pedantic: bool = false, + opt: ?Option = null, + extension: bool = false, + + // TODO look into removing these suppress_version: ?LangOpts.Standard = null, suppress_unless_version: ?LangOpts.Standard = null, - suppress_gnu: bool = false, - suppress_gcc: bool = false, - suppress_clang: bool = false, - suppress_msvc: bool = false, - pub fn makeOpt(comptime str: []const u8) u16 { - return @offsetOf(Options, str); - } - pub fn getKind(prop: Properties, options: *Options) Kind { - const opt = @as([*]Kind, @ptrCast(options))[prop.opt orelse return prop.kind]; - if (opt == .default) return prop.kind; - return opt; - } pub const max_bits = Compilation.bit_int_max_bits; }; pub const Tag = @import("Diagnostics/messages.def").with(Properties).Tag; -pub const Kind = enum { @"fatal error", @"error", note, warning, off, default }; - -pub const Options = struct { - // do not directly use these, instead add `const NAME = true;` - all: Kind = .default, - extra: Kind = .default, - pedantic: Kind = .default, - - @"unsupported-pragma": Kind = .default, - @"c99-extensions": Kind = .default, - @"implicit-int": Kind = .default, - @"duplicate-decl-specifier": Kind = .default, - @"missing-declaration": Kind = .default, - @"extern-initializer": Kind = .default, - @"implicit-function-declaration": Kind = .default, - @"unused-value": Kind = .default, - @"unreachable-code": Kind = .default, - @"unknown-warning-option": Kind = .default, - @"gnu-empty-struct": Kind = .default, - @"gnu-alignof-expression": Kind = .default, - @"macro-redefined": Kind = .default, - @"generic-qual-type": Kind = .default, - multichar: Kind = .default, - @"pointer-integer-compare": Kind = .default, - @"compare-distinct-pointer-types": Kind = .default, - @"literal-conversion": Kind = .default, - @"cast-qualifiers": Kind = .default, - @"array-bounds": Kind = .default, - @"int-conversion": Kind = .default, - @"pointer-type-mismatch": Kind = .default, - @"c23-extensions": Kind = .default, - @"incompatible-pointer-types": Kind = .default, - @"excess-initializers": Kind = .default, - @"division-by-zero": Kind = .default, - @"initializer-overrides": Kind = .default, - @"incompatible-pointer-types-discards-qualifiers": Kind = .default, - @"unknown-attributes": Kind = .default, - @"ignored-attributes": Kind = .default, - @"builtin-macro-redefined": Kind = .default, - @"gnu-label-as-value": Kind = .default, - @"malformed-warning-check": Kind = .default, - @"#pragma-messages": Kind = .default, - @"newline-eof": Kind = .default, - @"empty-translation-unit": Kind = .default, - @"implicitly-unsigned-literal": Kind = .default, - @"c99-compat": Kind = .default, - @"unicode-zero-width": Kind = .default, - @"unicode-homoglyph": Kind = .default, - unicode: Kind = .default, - @"return-type": Kind = .default, - @"dollar-in-identifier-extension": Kind = .default, - @"unknown-pragmas": Kind = .default, - @"predefined-identifier-outside-function": Kind = .default, - @"many-braces-around-scalar-init": Kind = .default, - uninitialized: Kind = .default, - @"gnu-statement-expression": Kind = .default, - @"gnu-imaginary-constant": Kind = .default, - @"gnu-complex-integer": Kind = .default, - @"ignored-qualifiers": Kind = .default, - @"integer-overflow": Kind = .default, - @"extra-semi": Kind = .default, - @"gnu-binary-literal": Kind = .default, - @"variadic-macros": Kind = .default, - varargs: Kind = .default, - @"#warnings": Kind = .default, - @"deprecated-declarations": Kind = .default, - @"backslash-newline-escape": Kind = .default, - @"pointer-to-int-cast": Kind = .default, - @"gnu-case-range": Kind = .default, - @"c++-compat": Kind = .default, - vla: Kind = .default, - @"float-overflow-conversion": Kind = .default, - @"float-zero-conversion": Kind = .default, - @"float-conversion": Kind = .default, - @"gnu-folding-constant": Kind = .default, - undef: Kind = .default, - @"ignored-pragmas": Kind = .default, - @"gnu-include-next": Kind = .default, - @"include-next-outside-header": Kind = .default, - @"include-next-absolute-path": Kind = .default, - @"enum-too-large": Kind = .default, - @"fixed-enum-extension": Kind = .default, - @"designated-init": Kind = .default, - @"attribute-warning": Kind = .default, - @"invalid-noreturn": Kind = .default, - @"zero-length-array": Kind = .default, - @"old-style-flexible-struct": Kind = .default, - @"gnu-zero-variadic-macro-arguments": Kind = .default, - @"main-return-type": Kind = .default, - @"expansion-to-defined": Kind = .default, - @"bit-int-extension": Kind = .default, - @"keyword-macro": Kind = .default, - @"pointer-arith": Kind = .default, - @"sizeof-array-argument": Kind = .default, - @"pre-c23-compat": Kind = .default, - @"pointer-bool-conversion": Kind = .default, - @"string-conversion": Kind = .default, - @"gnu-auto-type": Kind = .default, - @"gnu-pointer-arith": Kind = .default, - @"gnu-union-cast": Kind = .default, - @"pointer-sign": Kind = .default, - @"fuse-ld-path": Kind = .default, - @"language-extension-token": Kind = .default, - @"complex-component-init": Kind = .default, - @"microsoft-include": Kind = .default, - @"microsoft-end-of-file": Kind = .default, - @"invalid-source-encoding": Kind = .default, - @"four-char-constants": Kind = .default, - @"unknown-escape-sequence": Kind = .default, - @"invalid-pp-token": Kind = .default, - @"deprecated-non-prototype": Kind = .default, - @"duplicate-embed-param": Kind = .default, - @"unsupported-embed-param": Kind = .default, - @"unused-result": Kind = .default, - normalized: Kind = .default, - @"shift-count-negative": Kind = .default, - @"shift-count-overflow": Kind = .default, - @"constant-conversion": Kind = .default, - @"sign-conversion": Kind = .default, - @"address-of-packed-member": Kind = .default, - nonnull: Kind = .default, - @"atomic-access": Kind = .default, - @"gnu-designator": Kind = .default, - @"empty-body": Kind = .default, - @"nullability-extension": Kind = .default, - nullability: Kind = .default, +pub const Kind = enum { + off, + note, + warning, + @"error", + @"fatal error", +}; + +pub const Option = enum { + @"unsupported-pragma", + @"c99-extensions", + @"implicit-int", + @"duplicate-decl-specifier", + @"missing-declaration", + @"extern-initializer", + @"implicit-function-declaration", + @"unused-value", + @"unreachable-code", + @"unknown-warning-option", + @"gnu-empty-struct", + @"gnu-alignof-expression", + @"macro-redefined", + @"generic-qual-type", + multichar, + @"pointer-integer-compare", + @"compare-distinct-pointer-types", + @"literal-conversion", + @"cast-qualifiers", + @"array-bounds", + @"int-conversion", + @"pointer-type-mismatch", + @"c23-extensions", + @"incompatible-pointer-types", + @"excess-initializers", + @"division-by-zero", + @"initializer-overrides", + @"incompatible-pointer-types-discards-qualifiers", + @"unknown-attributes", + @"ignored-attributes", + @"builtin-macro-redefined", + @"gnu-label-as-value", + @"malformed-warning-check", + @"#pragma-messages", + @"newline-eof", + @"empty-translation-unit", + @"implicitly-unsigned-literal", + @"c99-compat", + @"unicode-zero-width", + @"unicode-homoglyph", + unicode, + @"return-type", + @"dollar-in-identifier-extension", + @"unknown-pragmas", + @"predefined-identifier-outside-function", + @"many-braces-around-scalar-init", + uninitialized, + @"gnu-statement-expression", + @"gnu-imaginary-constant", + @"gnu-complex-integer", + @"ignored-qualifiers", + @"integer-overflow", + @"extra-semi", + @"gnu-binary-literal", + @"variadic-macros", + varargs, + @"#warnings", + @"deprecated-declarations", + @"backslash-newline-escape", + @"pointer-to-int-cast", + @"gnu-case-range", + @"c++-compat", + vla, + @"float-overflow-conversion", + @"float-zero-conversion", + @"float-conversion", + @"gnu-folding-constant", + undef, + @"ignored-pragmas", + @"gnu-include-next", + @"include-next-outside-header", + @"include-next-absolute-path", + @"enum-too-large", + @"fixed-enum-extension", + @"designated-init", + @"attribute-warning", + @"invalid-noreturn", + @"zero-length-array", + @"old-style-flexible-struct", + @"gnu-zero-variadic-macro-arguments", + @"main-return-type", + @"expansion-to-defined", + @"bit-int-extension", + @"keyword-macro", + @"pointer-arith", + @"sizeof-array-argument", + @"pre-c23-compat", + @"pointer-bool-conversion", + @"string-conversion", + @"gnu-auto-type", + @"gnu-pointer-arith", + @"gnu-union-cast", + @"pointer-sign", + @"fuse-ld-path", + @"language-extension-token", + @"complex-component-init", + @"microsoft-include", + @"microsoft-end-of-file", + @"invalid-source-encoding", + @"four-char-constants", + @"unknown-escape-sequence", + @"invalid-pp-token", + @"deprecated-non-prototype", + @"duplicate-embed-param", + @"unsupported-embed-param", + @"unused-result", + normalized, + @"shift-count-negative", + @"shift-count-overflow", + @"constant-conversion", + @"sign-conversion", + @"address-of-packed-member", + nonnull, + @"atomic-access", + @"gnu-designator", + @"empty-body", + @"nullability-extension", + nullability, + @"microsoft-flexible-array", + + /// GNU extensions + pub const gnu = [_]Option{ + .@"gnu-empty-struct", + .@"gnu-alignof-expression", + .@"gnu-label-as-value", + .@"gnu-statement-expression", + .@"gnu-imaginary-constant", + .@"gnu-complex-integer", + .@"gnu-binary-literal", + .@"gnu-case-range", + .@"gnu-folding-constant", + .@"gnu-include-next", + .@"gnu-zero-variadic-macro-arguments", + .@"gnu-auto-type", + .@"gnu-pointer-arith", + .@"gnu-union-cast", + .@"gnu-designator", + .@"zero-length-array", + }; + + /// Clang extensions + pub const clang = [_]Option{ + .@"fixed-enum-extension", + .@"bit-int-extension", + .@"nullability-extension", + }; + + /// Microsoft extensions + pub const microsoft = [_]Option{ + .@"microsoft-end-of-file", + .@"microsoft-include", + .@"microsoft-flexible-array", + }; + + pub const extra = [_]Option{ + .@"initializer-overrides", + .@"ignored-qualifiers", + .@"initializer-overrides", + .@"expansion-to-defined", + .@"fuse-ld-path", + }; + + pub const implicit = [_]Option{ + .@"implicit-int", + .@"implicit-function-declaration", + }; + + pub const unused = [_]Option{ + .@"unused-value", + .@"unused-result", + }; + + pub const most = implicit ++ unused ++ [_]Option{ + .@"initializer-overrides", + .@"ignored-qualifiers", + .@"initializer-overrides", + .multichar, + .@"return-type", + .@"sizeof-array-argument", + .uninitialized, + .@"unknown-pragmas", + }; + + pub const all = most ++ [_]Option{ + .nonnull, + .@"unreachable-code", + .@"malformed-warning-check", + }; +}; + +pub const State = struct { + // Treat all errors as fatal, set by -Wfatal-errors + fatal_errors: bool = false, + // Treat all warnings as errors, set by -Werror + error_warnings: bool = false, + /// Enable all warnings, set by -Weverything + enable_all_warnings: bool = false, + /// Ignore all warnings, set by -w + ignore_warnings: bool = false, + /// How to treat extension diagnostics, set by -Wpedantic + extensions: Kind = .off, + /// How to treat individual options, set by -W + options: std.EnumMap(Option, Kind) = .{}, }; const Diagnostics = @This(); list: std.ArrayListUnmanaged(Message) = .{}, arena: std.heap.ArenaAllocator, -fatal_errors: bool = false, -options: Options = .{}, +state: State = .{}, errors: u32 = 0, macro_backtrace_limit: u32 = 6, +/// Used by the __has_warning builtin macro. pub fn warningExists(name: []const u8) bool { - inline for (@typeInfo(Options).@"struct".fields) |f| { - if (mem.eql(u8, f.name, name)) return true; + if (std.mem.eql(u8, name, "pedantic")) return true; + inline for (comptime std.meta.declarations(Option)) |group| { + if (std.mem.eql(u8, name, group.name)) return true; } - return false; + return std.meta.stringToEnum(Option, name) != null; } pub fn set(d: *Diagnostics, name: []const u8, to: Kind) !void { - inline for (@typeInfo(Options).@"struct".fields) |f| { - if (mem.eql(u8, f.name, name)) { - @field(d.options, f.name) = to; + if (std.mem.eql(u8, name, "pedantic")) { + d.state.extensions = to; + return; + } + if (std.meta.stringToEnum(Option, name)) |option| { + d.state.options.put(option, to); + return; + } + + inline for (comptime std.meta.declarations(Option)) |group| { + if (std.mem.eql(u8, name, group.name)) { + d.setGroup(&@field(Option, group.name), to); return; } } + try d.addExtra(.{}, .{ .tag = .unknown_warning, .extra = .{ .str = name }, }, &.{}, true); } +fn setGroup(d: *Diagnostics, group: []const Option, to: Kind) void { + for (group) |option| { + d.state.options.put(option, to); + } +} + pub fn init(gpa: Allocator) Diagnostics { return .{ .arena = std.heap.ArenaAllocator.init(gpa), @@ -328,8 +419,7 @@ pub fn addExtra( .loc = msg.loc, }); } - if (kind == .@"fatal error" or (kind == .@"error" and d.fatal_errors)) - return error.FatalError; + if (kind == .@"fatal error") return error.FatalError; } pub fn render(comp: *Compilation, config: std.io.tty.Config) void { @@ -351,7 +441,6 @@ pub fn renderMessages(comp: *Compilation, m: anytype) void { .warning => warnings += 1, .note => {}, .off => continue, // happens if an error is added before it is disabled - .default => unreachable, } renderMessage(comp, m, msg); } @@ -497,9 +586,15 @@ pub fn renderMessage(comp: *Compilation, m: anytype, msg: Message) void { if (prop.opt) |some| { if (msg.kind == .@"error" and prop.kind != .@"error") { - m.print(" [-Werror,-W{s}]", .{optName(some)}); + m.print(" [-Werror,-W{s}]", .{@tagName(some)}); } else if (msg.kind != .note) { - m.print(" [-W{s}]", .{optName(some)}); + m.print(" [-W{s}]", .{@tagName(some)}); + } + } else if (prop.extension) { + if (msg.kind == .@"error") { + m.write(" [-Werror,-Wpedantic]"); + } else { + m.write(" [-Wpedantic]"); } } @@ -517,30 +612,38 @@ fn printRt(m: anytype, str: []const u8, comptime fmts: anytype, args: anytype) v m.write(str[i..]); } -fn optName(offset: u16) []const u8 { - return std.meta.fieldNames(Options)[offset / @sizeOf(Kind)]; -} - fn tagKind(d: *Diagnostics, tag: Tag, langopts: LangOpts) Kind { const prop = tag.property(); - var kind = prop.getKind(&d.options); + var kind = prop.kind; - if (prop.all) { - if (d.options.all != .default) kind = d.options.all; - } - if (prop.w_extra) { - if (d.options.extra != .default) kind = d.options.extra; - } - if (prop.pedantic) { - if (d.options.pedantic != .default) kind = d.options.pedantic; - } if (prop.suppress_version) |some| if (langopts.standard.atLeast(some)) return .off; if (prop.suppress_unless_version) |some| if (!langopts.standard.atLeast(some)) return .off; - if (prop.suppress_gnu and langopts.standard.isExplicitGNU()) return .off; - if (prop.suppress_gcc and langopts.emulate == .gcc) return .off; - if (prop.suppress_clang and langopts.emulate == .clang) return .off; - if (prop.suppress_msvc and langopts.emulate == .msvc) return .off; - if (kind == .@"error" and d.fatal_errors) kind = .@"fatal error"; + + // -w disregards explicit kind set with -W + if (d.state.ignore_warnings and prop.kind == .warning) return .off; + + // Get explicit kind set by -W= + var set_explicit = false; + if (prop.opt) |option| { + if (d.state.options.get(option)) |explicit| { + kind = explicit; + set_explicit = true; + } + } + + // Use extension diagnostic behavior if not set explicitly. + if (prop.extension and !set_explicit) { + kind = @enumFromInt(@max(@intFromEnum(kind), @intFromEnum(d.state.extensions))); + } + + // Make diagnostic a warning if -Weverything is set. + if (kind == .off and d.state.enable_all_warnings) kind = .warning; + + // Upgrade warnigns to errors if -Werror is set + if (kind == .warning and d.state.error_warnings) kind = .@"error"; + + // Upgrade errors to fatal errors if -Wfatal-errors is set + if (kind == .@"error" and d.state.fatal_errors) kind = .@"fatal error"; return kind; } @@ -583,14 +686,14 @@ const MsgWriter = struct { .@"fatal error", .@"error" => m.setColor(.bright_red), .note => m.setColor(.bright_cyan), .warning => m.setColor(.bright_magenta), - .off, .default => unreachable, + .off => unreachable, } m.write(switch (kind) { .@"fatal error" => "fatal error: ", .@"error" => "error: ", .note => "note: ", .warning => "warning: ", - .off, .default => unreachable, + .off => unreachable, }); m.setColor(.white); } diff --git a/src/aro/Diagnostics/messages.def b/src/aro/Diagnostics/messages.def index fd92ff0..ddabf0e 100644 --- a/src/aro/Diagnostics/messages.def +++ b/src/aro/Diagnostics/messages.def @@ -15,7 +15,7 @@ error_directive warning_directive .msg = "{s}" - .opt = W("#warnings") + .opt = .@"#warnings" .extra = .str .kind = .warning @@ -57,9 +57,8 @@ endif_without_if unknown_pragma .msg = "unknown pragma ignored" - .opt = W("unknown-pragmas") + .opt = .@"unknown-pragmas" .kind = .off - .all = true line_simple_digit .msg = "#line directive requires a simple digit sequence" @@ -131,8 +130,9 @@ macro_name_must_be_identifier whitespace_after_macro_name .msg = "ISO C99 requires whitespace after the macro name" - .opt = W("c99-extensions") + .opt = .@"c99-extensions" .kind = .warning + .extension = true hash_hash_at_start .msg = "'##' cannot appear at the start of a macro expansion" @@ -200,9 +200,8 @@ expected_integer_constant_expr missing_type_specifier .msg = "type specifier missing, defaults to 'int'" - .opt = W("implicit-int") + .opt = .@"implicit-int" .kind = .warning - .all = true missing_type_specifier_c23 .msg = "a type specifier is required for all declarations" @@ -210,10 +209,9 @@ missing_type_specifier_c23 param_not_declared .msg = "parameter '{s}' was not declared, defaults to 'int'" - .opt = W("implicit-int") + .opt = .@"implicit-int" .extra = .str .kind = .warning - .all = true multiple_storage_class .msg = "cannot combine with previous '{s}' declaration specifier" @@ -251,9 +249,8 @@ cannot_combine_with_typeof duplicate_decl_spec .msg = "duplicate '{s}' declaration specifier" .extra = .str - .opt = W("duplicate-decl-specifier") + .opt = .@"duplicate-decl-specifier" .kind = .warning - .all = true restrict_non_pointer .msg = "restrict requires a pointer or reference ('{s}' is invalid)" @@ -270,7 +267,7 @@ expected_ident_or_l_paren missing_declaration .msg = "declaration does not declare anything" - .opt = W("missing-declaration") + .opt = .@"missing-declaration" .kind = .warning func_not_in_root @@ -283,7 +280,7 @@ illegal_initializer extern_initializer .msg = "extern variable has initializer" - .opt = W("extern-initializer") + .opt = .@"extern-initializer" .kind = .warning param_before_var_args @@ -357,30 +354,26 @@ static_func_not_global implicit_func_decl .msg = "call to undeclared function '{s}'; ISO C99 and later do not support implicit function declarations" .extra = .str - .opt = W("implicit-function-declaration") + .opt = .@"implicit-function-declaration" .kind = .@"error" - .all = true unknown_builtin .msg = "use of unknown builtin '{s}'" .extra = .str - .opt = W("implicit-function-declaration") + .opt = .@"implicit-function-declaration" .kind = .@"error" - .all = true implicit_builtin .msg = "implicitly declaring library function '{s}'" .extra = .str - .opt = W("implicit-function-declaration") + .opt = .@"implicit-function-declaration" .kind = .@"error" - .all = true implicit_builtin_header_note .msg = "include the header <{s}.h> or explicitly provide a declaration for '{s}'" .extra = .builtin_with_header - .opt = W("implicit-function-declaration") + .opt = .@"implicit-function-declaration" .kind = .note - .all = true expected_param_decl .msg = "expected parameter declaration" @@ -400,9 +393,8 @@ invalid_void_param unused_value .msg = "expression result unused" - .opt = W("unused-value") + .opt = .@"unused-value" .kind = .warning - .all = true continue_not_in_loop .msg = "'continue' statement not in a loop" @@ -414,9 +406,8 @@ break_not_in_loop_or_switch unreachable_code .msg = "unreachable code" - .opt = W("unreachable-code") + .opt = .@"unreachable-code" .kind = .warning - .all = true duplicate_label .msg = "duplicate label '{s}'" @@ -466,12 +457,12 @@ array_argument_too_small .msg = "array argument is too small; contains {d} elements, callee requires at least {d}" .extra = .arguments .kind = .warning - .opt = W("array-bounds") + .opt = .@"array-bounds" non_null_argument .msg = "null passed to a callee that requires a non-null argument" .kind = .warning - .opt = W("nonnull") + .opt = .nonnull expected_arguments_old .msg = expected_arguments @@ -506,7 +497,7 @@ variable_len_array_file_scope useless_static .msg = "'static' useless without a constant size" .kind = .warning - .w_extra = true +# .w_extra = true TODO negative_array_size .msg = "array size must be 0 or greater" @@ -536,19 +527,19 @@ unterminated_macro_arg_list unknown_warning .msg = "unknown warning '{s}'" .extra = .str - .opt = W("unknown-warning-option") + .opt = .@"unknown-warning-option" .kind = .warning array_overflow .msg = "{s}" .extra = .str - .opt = W("array-bounds") + .opt = .@"array-bounds" .kind = .warning overflow .msg = "overflow in expression; result is '{s}'" .extra = .str - .opt = W("integer-overflow") + .opt = .@"integer-overflow" .kind = .warning int_literal_too_big @@ -609,14 +600,14 @@ parameter_missing empty_record .msg = "empty {s} is a GNU extension" .extra = .str - .opt = W("gnu-empty-struct") + .opt = .@"gnu-empty-struct" .kind = .off - .pedantic = true + .extension = true empty_record_size .msg = "empty {s} has size 0 in C, size 1 in C++" .extra = .str - .opt = W("c++-compat") + .opt = .@"c++-compat" .kind = .off wrong_tag @@ -630,9 +621,9 @@ expected_parens_around_typename alignof_expr .msg = "'_Alignof' applied to an expression is a GNU extension" - .opt = W("gnu-alignof-expression") + .opt = .@"gnu-alignof-expression" .kind = .warning - .suppress_gnu = true + .extension = true invalid_alignof .msg = "invalid application of 'alignof' to an incomplete type '{s}'" @@ -647,22 +638,22 @@ invalid_sizeof macro_redefined .msg = "'{s}' macro redefined" .extra = .str - .opt = W("macro-redefined") + .opt = .@"macro-redefined" .kind = .warning generic_qual_type .msg = "generic association with qualifiers cannot be matched with" - .opt = W("generic-qual-type") + .opt = .@"generic-qual-type" .kind = .warning generic_array_type .msg = "generic association array type cannot be matched with" - .opt = W("generic-qual-type") + .opt = .@"generic-qual-type" .kind = .warning generic_func_type .msg = "generic association function type cannot be matched with" - .opt = W("generic-qual-type") + .opt = .@"generic-qual-type" .kind = .warning generic_duplicate @@ -698,9 +689,8 @@ incomplete_universal_character multichar_literal_warning .msg = "multi-character character constant" - .opt = W("multichar") + .opt = .multichar .kind = .warning - .all = true invalid_multichar_literal .msg = "{s} character literals may not contain multiple characters" @@ -714,7 +704,6 @@ wide_multichar_literal char_lit_too_wide .msg = "character constant too long for its type" .kind = .warning - .all = true char_too_large .msg = "character too large for enclosing character literal type" @@ -758,13 +747,13 @@ invalid_bin_types comparison_ptr_int .msg = "comparison between pointer and integer ({s})" .extra = .str - .opt = W("pointer-integer-compare") + .opt = .@"pointer-integer-compare" .kind = .warning comparison_distinct_ptr .msg = "comparison of distinct pointer types ({s})" .extra = .str - .opt = W("compare-distinct-pointer-types") + .opt = .@"compare-distinct-pointer-types" .kind = .warning incompatible_pointers @@ -785,7 +774,7 @@ incompatible_assign implicit_ptr_to_int .msg = "implicit pointer to integer conversion from {s}" .extra = .str - .opt = W("int-conversion") + .opt = .@"int-conversion" .kind = .warning invalid_cast_to_float @@ -811,7 +800,7 @@ invalid_cast_operand_type qual_cast .msg = "cast to type '{s}' will not preserve qualifiers" .extra = .str - .opt = W("cast-qualifiers") + .opt = .@"cast-qualifiers" .kind = .warning invalid_index @@ -825,13 +814,13 @@ invalid_subscript array_after .msg = "array index {s} is past the end of the array" .extra = .str - .opt = W("array-bounds") + .opt = .@"array-bounds" .kind = .warning array_before .msg = "array index {s} is before the beginning of the array" .extra = .str - .opt = W("array-bounds") + .opt = .@"array-bounds" .kind = .warning statement_int @@ -847,9 +836,8 @@ statement_scalar func_should_return .msg = "non-void function '{s}' should return a value" .extra = .str - .opt = W("return-type") + .opt = .@"return-type" .kind = .@"error" - .all = true incompatible_return .msg = "returning {s}" @@ -860,27 +848,25 @@ incompatible_return_sign .msg = "returning {s}" ++ pointer_sign_message .extra = .str .kind = .warning - .opt = W("pointer-sign") + .opt = .@"pointer-sign" implicit_int_to_ptr .msg = "implicit integer to pointer conversion from {s}" .extra = .str - .opt = W("int-conversion") + .opt = .@"int-conversion" .kind = .warning func_does_not_return .msg = "non-void function '{s}' does not return a value" .extra = .str - .opt = W("return-type") + .opt = .@"return-type" .kind = .warning - .all = true void_func_returns_value .msg = "void function '{s}' should not return a value" .extra = .str - .opt = W("return-type") + .opt = .@"return-type" .kind = .@"error" - .all = true incompatible_arg .msg = "passing {s}" @@ -891,13 +877,13 @@ incompatible_ptr_arg .msg = "passing {s}" .extra = .str .kind = .warning - .opt = W("incompatible-pointer-types") + .opt = .@"incompatible-pointer-types" incompatible_ptr_arg_sign .msg = "passing {s}" ++ pointer_sign_message .extra = .str .kind = .warning - .opt = W("pointer-sign") + .opt = .@"pointer-sign" parameter_here .msg = "passing argument to parameter here" @@ -939,7 +925,7 @@ atomic_auto atomic_access .msg = "accessing a member of an atomic structure or union is undefined behavior" - .opt = W("atomic-access") + .opt = .@"atomic-access" .kind = .@"error" addr_of_register @@ -1003,25 +989,26 @@ non_pow2_align pointer_mismatch .msg = "pointer type mismatch ({s})" .extra = .str - .opt = W("pointer-type-mismatch") + .opt = .@"pointer-type-mismatch" .kind = .warning static_assert_not_constant - .msg = "static_assert expression is not an integral constant expression" + .msg = "static assertion expression is not an integral constant expression" .kind = .@"error" static_assert_missing_message - .msg = "static_assert with no message is a C23 extension" - .opt = W("c23-extensions") + .msg = "'_Static_assert' with no message is a C23 extension" + .opt = .@"c23-extensions" .kind = .warning .suppress_version = .c23 + .extension = true pre_c23_compat .msg = "{s} is incompatible with C standards before C23" .extra = .str .kind = .off .suppress_unless_version = .c23 - .opt = W("pre-c23-compat") + .opt = .@"pre-c23-compat" unbound_vla .msg = "variable length array must be bound in function definition" @@ -1039,25 +1026,25 @@ record_too_large incompatible_ptr_init .msg = "incompatible pointer types initializing {s}" .extra = .str - .opt = W("incompatible-pointer-types") + .opt = .@"incompatible-pointer-types" .kind = .warning incompatible_ptr_init_sign .msg = "incompatible pointer types initializing {s}" ++ pointer_sign_message .extra = .str - .opt = W("pointer-sign") + .opt = .@"pointer-sign" .kind = .warning incompatible_ptr_assign .msg = "incompatible pointer types assigning to {s}" .extra = .str - .opt = W("incompatible-pointer-types") + .opt = .@"incompatible-pointer-types" .kind = .warning incompatible_ptr_assign_sign .msg = "incompatible pointer types assigning to {s} " ++ pointer_sign_message .extra = .str - .opt = W("pointer-sign") + .opt = .@"pointer-sign" .kind = .warning vla_init @@ -1079,27 +1066,27 @@ empty_scalar_init excess_scalar_init .msg = "excess elements in scalar initializer" - .opt = W("excess-initializers") + .opt = .@"excess-initializers" .kind = .warning excess_str_init .msg = "excess elements in string initializer" - .opt = W("excess-initializers") + .opt = .@"excess-initializers" .kind = .warning excess_struct_init .msg = "excess elements in struct initializer" - .opt = W("excess-initializers") + .opt = .@"excess-initializers" .kind = .warning excess_array_init .msg = "excess elements in array initializer" - .opt = W("excess-initializers") + .opt = .@"excess-initializers" .kind = .warning str_init_too_long .msg = "initializer-string for char array is too long" - .opt = W("excess-initializers") + .opt = .@"excess-initializers" .kind = .warning arr_init_too_long @@ -1110,7 +1097,7 @@ arr_init_too_long division_by_zero .msg = "{s} by zero is undefined" .extra = .str - .opt = W("division-by-zero") + .opt = .@"division-by-zero" .kind = .warning division_by_zero_macro @@ -1145,9 +1132,8 @@ array_init_str initializer_overrides .msg = "initializer overrides previous initialization" - .opt = W("initializer-overrides") + .opt = .@"initializer-overrides" .kind = .warning - .w_extra = true previous_initializer .msg = "previous initialization" @@ -1185,37 +1171,37 @@ empty_aggregate_init_braces ptr_init_discards_quals .msg = "initializing {s} discards qualifiers" .extra = .str - .opt = W("incompatible-pointer-types-discards-qualifiers") + .opt = .@"incompatible-pointer-types-discards-qualifiers" .kind = .warning ptr_assign_discards_quals .msg = "assigning to {s} discards qualifiers" .extra = .str - .opt = W("incompatible-pointer-types-discards-qualifiers") + .opt = .@"incompatible-pointer-types-discards-qualifiers" .kind = .warning ptr_ret_discards_quals .msg = "returning {s} discards qualifiers" .extra = .str - .opt = W("incompatible-pointer-types-discards-qualifiers") + .opt = .@"incompatible-pointer-types-discards-qualifiers" .kind = .warning ptr_arg_discards_quals .msg = "passing {s} discards qualifiers" .extra = .str - .opt = W("incompatible-pointer-types-discards-qualifiers") + .opt = .@"incompatible-pointer-types-discards-qualifiers" .kind = .warning unknown_attribute .msg = "unknown attribute '{s}' ignored" .extra = .str - .opt = W("unknown-attributes") + .opt = .@"unknown-attributes" .kind = .warning ignored_attribute .msg = "{s}" .extra = .str - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning invalid_fallthrough @@ -1229,7 +1215,7 @@ cannot_apply_attribute_to_statement builtin_macro_redefined .msg = "redefining builtin macro" - .opt = W("builtin-macro-redefined") + .opt = .@"builtin-macro-redefined" .kind = .warning feature_check_requires_identifier @@ -1243,9 +1229,9 @@ missing_tok_builtin gnu_label_as_value .msg = "use of GNU address-of-label extension" - .opt = W("gnu-label-as-value") + .opt = .@"gnu-label-as-value" .kind = .off - .pedantic = true + .extension = true expected_record_ty .msg = "member reference base type '{s}' is not a structure or union" @@ -1275,9 +1261,8 @@ no_such_member malformed_warning_check .msg = "{s} expected option name (e.g. \"-Wundef\")" .extra = .str - .opt = W("malformed-warning-check") + .opt = .@"malformed-warning-check" .kind = .warning - .all = true invalid_computed_goto .msg = "computed goto in function with no address-of-label expressions" @@ -1286,7 +1271,7 @@ invalid_computed_goto pragma_warning_message .msg = "{s}" .extra = .str - .opt = W("#pragma-messages") + .opt = .@"#pragma-messages" .kind = .warning pragma_error_message @@ -1318,21 +1303,20 @@ pragma_poison_macro newline_eof .msg = "no newline at end of file" - .opt = W("newline-eof") + .opt = .@"newline-eof" .kind = .off - .pedantic = true empty_translation_unit .msg = "ISO C requires a translation unit to contain at least one declaration" - .opt = W("empty-translation-unit") + .opt = .@"empty-translation-unit" .kind = .off - .pedantic = true omitting_parameter_name .msg = "omitting the parameter name in a function definition is a C23 extension" - .opt = W("c23-extensions") + .opt = .@"c23-extensions" .kind = .warning .suppress_version = .c23 + .extension = true non_int_bitfield .msg = "bit-field has non-integer type '{s}'" @@ -1358,7 +1342,7 @@ invalid_utf8 implicitly_unsigned_literal .msg = "integer literal is too large to be represented in a signed integer type, interpreting as unsigned" - .opt = W("implicitly-unsigned-literal") + .opt = .@"implicitly-unsigned-literal" .kind = .warning invalid_preproc_operator @@ -1371,7 +1355,7 @@ invalid_preproc_expr_start c99_compat .msg = "using this character in an identifier is incompatible with C99" - .opt = W("c99-compat") + .opt = .@"c99-compat" .kind = .off unexpected_character @@ -1386,14 +1370,14 @@ invalid_identifier_start_char unicode_zero_width .msg = "identifier contains Unicode character 4}> that is invisible in some environments" - .opt = W("unicode-homoglyph") + .opt = .@"unicode-homoglyph" .extra = .actual_codepoint .kind = .warning unicode_homoglyph .msg = "treating Unicode character 4}> as identifier character rather than as '{u}' symbol" .extra = .codepoints - .opt = W("unicode-homoglyph") + .opt = .@"unicode-homoglyph" .kind = .warning meaningless_asm_qual @@ -1413,9 +1397,9 @@ invalid_asm_str dollar_in_identifier_extension .msg = "'$' in identifier" - .opt = W("dollar-in-identifier-extension") + .opt = .@"dollar-in-identifier-extension" .kind = .off - .pedantic = true + .extension = true dollars_in_identifiers .msg = "illegal character '$' in identifier" @@ -1436,19 +1420,17 @@ pragma_operator_string_literal unknown_gcc_pragma .msg = "pragma GCC expected 'error', 'warning', 'diagnostic', 'poison'" - .opt = W("unknown-pragmas") + .opt = .@"unknown-pragmas" .kind = .off - .all = true unknown_gcc_pragma_directive .msg = "pragma GCC diagnostic expected 'error', 'warning', 'ignored', 'fatal', 'push', or 'pop'" - .opt = W("unknown-pragmas") + .opt = .@"unknown-pragmas" .kind = .warning - .all = true predefined_top_level .msg = "predefined identifier is only valid inside function" - .opt = W("predefined-identifier-outside-function") + .opt = .@"predefined-identifier-outside-function" .kind = .warning incompatible_va_arg @@ -1458,22 +1440,20 @@ incompatible_va_arg too_many_scalar_init_braces .msg = "too many braces around scalar initializer" - .opt = W("many-braces-around-scalar-init") + .opt = .@"many-braces-around-scalar-init" .kind = .warning uninitialized_in_own_init .msg = "variable '{s}' is uninitialized when used within its own initialization" .extra = .str - .opt = W("uninitialized") + .opt = .uninitialized .kind = .off - .all = true gnu_statement_expression .msg = "use of GNU statement expression extension" - .opt = W("gnu-statement-expression") + .opt = .@"gnu-statement-expression" .kind = .off - .suppress_gnu = true - .pedantic = true + .extension = true stmt_expr_not_allowed_file_scope .msg = "statement expression not allowed at file scope" @@ -1481,10 +1461,9 @@ stmt_expr_not_allowed_file_scope gnu_imaginary_constant .msg = "imaginary constants are a GNU extension" - .opt = W("gnu-imaginary-constant") + .opt = .@"gnu-imaginary-constant" .kind = .off - .suppress_gnu = true - .pedantic = true + .extension = true plain_complex .msg = "plain '_Complex' requires a type specifier; assuming '_Complex double'" @@ -1492,16 +1471,15 @@ plain_complex complex_int .msg = "complex integer types are a GNU extension" - .opt = W("gnu-complex-integer") - .suppress_gnu = true + .opt = .@"gnu-complex-integer" .kind = .off + .extension = true qual_on_ret_type .msg = "'{s}' type qualifier on return type has no effect" - .opt = W("ignored-qualifiers") + .opt = .@"ignored-qualifiers" .extra = .str .kind = .off - .all = true cli_invalid_standard .msg = "invalid standard '{s}'" @@ -1550,9 +1528,8 @@ cli_unknown_linker extra_semi .msg = "extra ';' outside of a function" - .opt = W("extra-semi") + .opt = .@"extra-semi" .kind = .off - .pedantic = true func_field .msg = "field declared as a function" @@ -1574,7 +1551,12 @@ field_incomplete_ty flexible_in_union .msg = "flexible array member in union is not allowed" .kind = .@"error" - .suppress_msvc = true + +flexible_in_union_msvc + .msg = "flexible array member in union is a Microsoft extension" + .kind = .off + .opt = .@"microsoft-flexible-array" + .extension = true flexible_non_final .msg = "flexible array member is not at the end of struct" @@ -1583,7 +1565,12 @@ flexible_non_final flexible_in_empty .msg = "flexible array member in otherwise empty struct" .kind = .@"error" - .suppress_msvc = true + +flexible_in_empty_msvc + .msg = "flexible array member in otherwise empty struct is a Microsoft extension" + .kind = .off + .opt = .@"microsoft-flexible-array" + .extension = true duplicate_member .msg = "duplicate member '{s}'" @@ -1593,14 +1580,14 @@ duplicate_member binary_integer_literal .msg = "binary integer literals are a GNU extension" .kind = .off - .opt = W("gnu-binary-literal") - .pedantic = true + .opt = .@"gnu-binary-literal" + .extension = true gnu_va_macro .msg = "named variadic macros are a GNU extension" - .opt = W("variadic-macros") + .opt = .@"variadic-macros" .kind = .off - .pedantic = true + .extension = true builtin_must_be_called .msg = "builtin function must be directly called" @@ -1616,7 +1603,7 @@ va_start_fixed_args va_start_not_last_param .msg = "second argument to 'va_start' is not the last named parameter" - .opt = W("varargs") + .opt = .varargs .kind = .warning attribute_not_enough_args @@ -1651,19 +1638,19 @@ declspec_not_enabled declspec_attr_not_supported .msg = "__declspec attribute '{s}' is not supported" .extra = .str - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning deprecated_declarations .msg = "{s}" .extra = .str - .opt = W("deprecated-declarations") + .opt = .@"deprecated-declarations" .kind = .warning deprecated_note .msg = "'{s}' has been explicitly marked deprecated here" .extra = .str - .opt = W("deprecated-declarations") + .opt = .@"deprecated-declarations" .kind = .note unavailable @@ -1680,7 +1667,7 @@ warning_attribute .msg = "{s}" .extra = .str .kind = .warning - .opt = W("attribute-warning") + .opt = .@"attribute-warning" error_attribute .msg = "{s}" @@ -1691,12 +1678,12 @@ ignored_record_attr .msg = "attribute '{s}' is ignored, place it after \"{s}\" to apply attribute to type declaration" .extra = .ignored_record_attr .kind = .warning - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" backslash_newline_escape .msg = "backslash and newline separated by space" .kind = .warning - .opt = W("backslash-newline-escape") + .opt = .@"backslash-newline-escape" array_size_non_int .msg = "size of array has non-integer type '{s}'" @@ -1707,13 +1694,13 @@ cast_to_smaller_int .msg = "cast to smaller integer type {s}" .extra = .str .kind = .warning - .opt = W("pointer-to-int-cast") + .opt = .@"pointer-to-int-cast" gnu_switch_range .msg = "use of GNU case range extension" - .opt = W("gnu-case-range") + .opt = .@"gnu-case-range" .kind = .off - .pedantic = true + .extension = true empty_case_range .msg = "empty case range specified" @@ -1722,7 +1709,7 @@ empty_case_range non_standard_escape_char .msg = "use of non-standard escape character '\\{s}'" .kind = .off - .opt = W("pedantic") + .extension = true .extra = .invalid_escape invalid_pp_stringify_escape @@ -1732,61 +1719,61 @@ invalid_pp_stringify_escape vla .msg = "variable length array used" .kind = .off - .opt = W("vla") + .opt = .vla int_value_changed .msg = "implicit conversion from {s}" .extra = .str .kind = .warning - .opt = W("constant-conversion") + .opt = .@"constant-conversion" sign_conversion .msg = "implicit conversion changes signedness: {s}" .extra = .str .kind = .off - .opt = W("sign-conversion") + .opt = .@"sign-conversion" float_overflow_conversion .msg = "implicit conversion of non-finite value from {s} is undefined" .extra = .str .kind = .off - .opt = W("float-overflow-conversion") + .opt = .@"float-overflow-conversion" float_out_of_range .msg = "implicit conversion of out of range value from {s} is undefined" .extra = .str .kind = .warning - .opt = W("literal-conversion") + .opt = .@"literal-conversion" float_zero_conversion .msg = "implicit conversion from {s}" .extra = .str .kind = .off - .opt = W("float-zero-conversion") + .opt = .@"float-zero-conversion" float_value_changed .msg = "implicit conversion from {s}" .extra = .str .kind = .warning - .opt = W("float-conversion") + .opt = .@"float-conversion" float_to_int .msg = "implicit conversion turns floating-point number into integer: {s}" .extra = .str .kind = .off - .opt = W("literal-conversion") + .opt = .@"literal-conversion" const_decl_folded .msg = "expression is not an integer constant expression; folding it to a constant is a GNU extension" .kind = .off - .opt = W("gnu-folding-constant") - .pedantic = true + .opt = .@"gnu-folding-constant" + .extension = true const_decl_folded_vla .msg = "variable length array folded to constant array as an extension" .kind = .off - .opt = W("gnu-folding-constant") - .pedantic = true + .opt = .@"gnu-folding-constant" + .extension = true redefinition_of_typedef .msg = "typedef redefinition with different types ({s})" @@ -1797,7 +1784,7 @@ undefined_macro .msg = "'{s}' is not defined, evaluates to 0" .extra = .str .kind = .off - .opt = W("undef") + .opt = .undef fn_macro_undefined .msg = "function-like macro '{s}' is not defined" @@ -1832,16 +1819,16 @@ offsetof_array pragma_pack_lparen .msg = "missing '(' after '#pragma pack' - ignoring" .kind = .warning - .opt = W("ignored-pragmas") + .opt = .@"ignored-pragmas" pragma_pack_rparen .msg = "missing ')' after '#pragma pack' - ignoring" .kind = .warning - .opt = W("ignored-pragmas") + .opt = .@"ignored-pragmas" pragma_pack_unknown_action .msg = "unknown action for '#pragma pack' - ignoring" - .opt = W("ignored-pragmas") + .opt = .@"ignored-pragmas" .kind = .warning pragma_pack_show @@ -1851,12 +1838,12 @@ pragma_pack_show pragma_pack_int .msg = "expected #pragma pack parameter to be '1', '2', '4', '8', or '16'" - .opt = W("ignored-pragmas") + .opt = .@"ignored-pragmas" .kind = .warning pragma_pack_int_ident .msg = "expected integer or identifier in '#pragma pack' - ignored" - .opt = W("ignored-pragmas") + .opt = .@"ignored-pragmas" .kind = .warning pragma_pack_undefined_pop @@ -1865,7 +1852,7 @@ pragma_pack_undefined_pop pragma_pack_empty_stack .msg = "#pragma pack(pop, ...) failed: stack empty" - .opt = W("ignored-pragmas") + .opt = .@"ignored-pragmas" .kind = .warning cond_expr_type @@ -1881,24 +1868,24 @@ enumerator_too_small .msg = "ISO C restricts enumerator values to range of 'int' ({s} is too small)" .extra = .str .kind = .off - .opt = W("pedantic") + .extension = true enumerator_too_large .msg = "ISO C restricts enumerator values to range of 'int' ({s} is too large)" .extra = .str .kind = .off - .opt = W("pedantic") + .extension = true include_next .msg = "#include_next is a language extension" .kind = .off - .pedantic = true - .opt = W("gnu-include-next") + .opt = .@"gnu-include-next" + .extension = true include_next_outside_header .msg = "#include_next in primary source file; will search from start of include path" .kind = .warning - .opt = W("include-next-outside-header") + .opt = .@"include-next-outside-header" enumerator_overflow .msg = "overflow in enumeration value" @@ -1907,19 +1894,19 @@ enumerator_overflow enum_not_representable .msg = "incremented enumerator value {s} is not representable in the largest integer type" .kind = .warning - .opt = W("enum-too-large") + .opt = .@"enum-too-large" .extra = .pow_2_as_string enum_too_large .msg = "enumeration values exceed range of largest integer" .kind = .warning - .opt = W("enum-too-large") + .opt = .@"enum-too-large" enum_fixed .msg = "enumeration types with a fixed underlying type are a Clang extension" .kind = .off - .pedantic = true - .opt = W("fixed-enum-extension") + .opt = .@"fixed-enum-extension" + .extension = true enum_prev_nonfixed .msg = "enumeration previously declared with nonfixed underlying type" @@ -1942,18 +1929,18 @@ enum_not_representable_fixed transparent_union_wrong_type .msg = "'transparent_union' attribute only applies to unions" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning transparent_union_one_field .msg = "transparent union definition must contain at least one field; transparent_union attribute ignored" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning transparent_union_size .msg = "size of field {s} bits) does not match the size of the first field in transparent union; transparent_union attribute ignored" .extra = .str - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning transparent_union_size_note @@ -1967,73 +1954,73 @@ designated_init_invalid designated_init_needed .msg = "positional initialization of field in 'struct' declared with 'designated_init' attribute" - .opt = W("designated-init") + .opt = .@"designated-init" .kind = .warning ignore_common .msg = "ignoring attribute 'common' because it conflicts with attribute 'nocommon'" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning ignore_nocommon .msg = "ignoring attribute 'nocommon' because it conflicts with attribute 'common'" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning non_string_ignored .msg = "'nonstring' attribute ignored on objects of type '{s}'" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning local_variable_attribute .msg = "'{s}' attribute only applies to local variables" .extra = .str - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning ignore_cold .msg = "ignoring attribute 'cold' because it conflicts with attribute 'hot'" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning ignore_hot .msg = "ignoring attribute 'hot' because it conflicts with attribute 'cold'" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning ignore_noinline .msg = "ignoring attribute 'noinline' because it conflicts with attribute 'always_inline'" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning ignore_always_inline .msg = "ignoring attribute 'always_inline' because it conflicts with attribute 'noinline'" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning invalid_noreturn .msg = "function '{s}' declared 'noreturn' should not return" .extra = .str .kind = .warning - .opt = W("invalid-noreturn") + .opt = .@"invalid-noreturn" nodiscard_unused .msg = "ignoring return value of '{s}', declared with 'nodiscard' attribute" .extra = .str .kind = .warning - .opt = W("unused-result") + .opt = .@"unused-result" warn_unused_result .msg = "ignoring return value of '{s}', declared with 'warn_unused_result' attribute" .extra = .str .kind = .warning - .opt = W("unused-result") + .opt = .@"unused-result" builtin_unused .msg = "ignoring return value of function declared with {s} attribute" .extra = .str .kind = .warning - .opt = W("unused-value") + .opt = .@"unused-value" invalid_vec_elem_ty .msg = "invalid vector element type '{s}'" @@ -2057,33 +2044,29 @@ invalid_real zero_length_array .msg = "zero size arrays are an extension" .kind = .off - .pedantic = true - .opt = W("zero-length-array") + .opt = .@"zero-length-array" old_style_flexible_struct .msg = "array index {s} is past the end of the array" .extra = .str .kind = .off - .pedantic = true - .opt = W("old-style-flexible-struct") + .opt = .@"old-style-flexible-struct" comma_deletion_va_args .msg = "token pasting of ',' and __VA_ARGS__ is a GNU extension" .kind = .off - .pedantic = true - .opt = W("gnu-zero-variadic-macro-arguments") - .suppress_gcc = true + .opt = .@"gnu-zero-variadic-macro-arguments" + .extension = true main_return_type .msg = "return type of 'main' is not 'int'" .kind = .warning - .opt = W("main-return-type") + .opt = .@"main-return-type" expansion_to_defined .msg = "macro expansion producing 'defined' has undefined behavior" .kind = .off - .pedantic = true - .opt = W("expansion-to-defined") + .opt = .@"expansion-to-defined" invalid_int_suffix .msg = "invalid suffix '{s}' on integer constant" @@ -2116,8 +2099,6 @@ hex_floating_constant_requires_exponent sizeof_returns_zero .msg = "sizeof returns 0" .kind = .warning - .suppress_gcc = true - .suppress_clang = true declspec_not_allowed_after_declarator .msg = "'declspec' attribute not allowed after declarator" @@ -2133,11 +2114,11 @@ type_not_supported_on_target .kind = .@"error" bit_int - .msg = "'_BitInt' in C17 and earlier is a Clang extension'" + .msg = "'_BitInt' in C17 and earlier is a Clang extension" .kind = .off - .pedantic = true - .opt = W("bit-int-extension") + .opt = .@"bit-int-extension" .suppress_version = .c23 + .extension = true unsigned_bit_int_too_small .msg = "{s}unsigned _BitInt must have a bit size of at least 1" @@ -2162,8 +2143,8 @@ signed_bit_int_too_big keyword_macro .msg = "keyword is hidden by macro definition" .kind = .off - .pedantic = true - .opt = W("keyword-macro") + .opt = .@"keyword-macro" + .extension = true ptr_arithmetic_incomplete .msg = "arithmetic on a pointer to an incomplete type '{s}'" @@ -2173,33 +2154,32 @@ ptr_arithmetic_incomplete callconv_not_supported .msg = "'{s}' calling convention is not supported for this target" .extra = .str - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning pointer_arith_void .msg = "invalid application of '{s}' to a void type" .extra = .str .kind = .off - .pedantic = true - .opt = W("pointer-arith") + .opt = .@"pointer-arith" sizeof_array_arg .msg = "sizeof on array function parameter will return size of {s}" .extra = .str .kind = .warning - .opt = W("sizeof-array-argument") + .opt = .@"sizeof-array-argument" array_address_to_bool .msg = "address of array '{s}' will always evaluate to 'true'" .extra = .str .kind = .warning - .opt = W("pointer-bool-conversion") + .opt = .@"pointer-bool-conversion" string_literal_to_bool .msg = "implicit conversion turns string literal into bool: {s}" .extra = .str .kind = .off - .opt = W("string-conversion") + .opt = .@"string-conversion" constant_expression_conversion_not_allowed .msg = "this conversion is not allowed in a constant expression" @@ -2222,21 +2202,21 @@ suggest_pointer_for_invalid_fp16 bitint_suffix .msg = "'_BitInt' suffix for literals is a C23 extension" - .opt = W("c23-extensions") + .opt = .@"c23-extensions" .kind = .warning .suppress_version = .c23 auto_type_extension .msg = "'__auto_type' is a GNU extension" - .opt = W("gnu-auto-type") + .opt = .@"gnu-auto-type" .kind = .off - .pedantic = true + .extension = true gnu_pointer_arith .msg = "arithmetic on pointers to void is a GNU extension" - .opt = W("gnu-pointer-arith") + .opt = .@"gnu-pointer-arith" .kind = .off - .pedantic = true + .extension = true auto_type_not_allowed .msg = "'__auto_type' not allowed in {s}" @@ -2285,9 +2265,9 @@ forward_declaration_here gnu_union_cast .msg = "cast to union type is a GNU extension" - .opt = W("gnu-union-cast") + .opt = .@"gnu-union-cast" .kind = .off - .pedantic = true + .extension = true invalid_union_cast .msg = "cast to union type from type '{s}' not present in union" @@ -2306,7 +2286,7 @@ invalid_source_epoch fuse_ld_path .msg = "'-fuse-ld=' taking a path is deprecated; use '--ld-path=' instead" .kind = .off - .opt = W("fuse-ld-path") + .opt = .@"fuse-ld-path" invalid_rtlib .msg = "invalid runtime library name '{s}'" @@ -2334,20 +2314,20 @@ gnu_asm_disabled extension_token_used .msg = "extension used" .kind = .off - .pedantic = true - .opt = W("language-extension-token") + .opt = .@"language-extension-token" + .extension = true complex_component_init .msg = "complex initialization specifying real and imaginary components is an extension" - .opt = W("complex-component-init") + .opt = .@"complex-component-init" .kind = .off - .pedantic = true + .extension = true complex_prefix_postfix_op .msg = "ISO C does not support '++'/'--' on complex type '{s}'" - .opt = W("pedantic") .extra = .str .kind = .off + .extension = true not_floating_type .msg = "argument type '{s}' is not a real floating point type" @@ -2362,18 +2342,17 @@ argument_types_differ ms_search_rule .msg = "#include resolved using non-portable Microsoft search rules as: {s}" .extra = .str - .opt = W("microsoft-include") + .opt = .@"microsoft-include" .kind = .warning ctrl_z_eof .msg = "treating Ctrl-Z as end-of-file is a Microsoft extension" - .opt = W("microsoft-end-of-file") + .opt = .@"microsoft-end-of-file" .kind = .off - .pedantic = true illegal_char_encoding_warning .msg = "illegal character encoding in character literal" - .opt = W("invalid-source-encoding") + .opt = .@"invalid-source-encoding" .kind = .warning illegal_char_encoding_error @@ -2390,7 +2369,7 @@ ucn_basic_char_warning .kind = .off .extra = .ascii .suppress_unless_version = .c23 - .opt = W("pre-c23-compat") + .opt = .@"pre-c23-compat" ucn_control_char_error .msg = "universal character name refers to a control character" @@ -2400,17 +2379,17 @@ ucn_control_char_warning .msg = "universal character name referring to a control character is incompatible with C standards before C23" .kind = .off .suppress_unless_version = .c23 - .opt = W("pre-c23-compat") + .opt = .@"pre-c23-compat" c89_ucn_in_literal .msg = "universal character names are only valid in C99 or later" .suppress_version = .c99 .kind = .warning - .opt = W("unicode") + .opt = .unicode four_char_char_literal .msg = "multi-character character constant" - .opt = W("four-char-constants") + .opt = .@"four-char-constants" .kind = .off multi_char_char_literal @@ -2425,7 +2404,7 @@ missing_hex_escape unknown_escape_sequence .msg = "unknown escape sequence '\\{s}'" .kind = .warning - .opt = W("unknown-escape-sequence") + .opt = .@"unknown-escape-sequence" .extra = .invalid_escape attribute_requires_string @@ -2436,7 +2415,7 @@ attribute_requires_string unterminated_string_literal_warning .msg = "missing terminating '\"' character" .kind = .warning - .opt = W("invalid-pp-token") + .opt = .@"invalid-pp-token" unterminated_string_literal_error .msg = "missing terminating '\"' character" @@ -2445,7 +2424,7 @@ unterminated_string_literal_error empty_char_literal_warning .msg = "empty character constant" .kind = .warning - .opt = W("invalid-pp-token") + .opt = .@"invalid-pp-token" empty_char_literal_error .msg = "empty character constant" @@ -2454,7 +2433,7 @@ empty_char_literal_error unterminated_char_literal_warning .msg = "missing terminating ' character" .kind = .warning - .opt = W("invalid-pp-token") + .opt = .@"invalid-pp-token" unterminated_char_literal_error .msg = "missing terminating ' character" @@ -2467,12 +2446,12 @@ unterminated_comment def_no_proto_deprecated .msg = "a function definition without a prototype is deprecated in all versions of C and is not supported in C23" .kind = .warning - .opt = W("deprecated-non-prototype") + .opt = .@"deprecated-non-prototype" passing_args_to_kr .msg = "passing arguments to a function without a prototype is deprecated in all versions of C and is not supported in C23" .kind = .warning - .opt = W("deprecated-non-prototype") + .opt = .@"deprecated-non-prototype" unknown_type_name .msg = "unknown type name '{s}'" @@ -2481,15 +2460,17 @@ unknown_type_name label_compound_end .msg = "label at end of compound statement is a C23 extension" - .opt = W("c23-extensions") + .opt = .@"c23-extensions" .kind = .warning .suppress_version = .c23 + .extension = true u8_char_lit .msg = "UTF-8 character literal is a C23 extension" - .opt = W("c23-extensions") + .opt = .@"c23-extensions" .kind = .warning .suppress_version = .c23 + .extension = true malformed_embed_param .msg = "unexpected token in embed parameter" @@ -2503,13 +2484,13 @@ duplicate_embed_param .msg = "duplicate embed parameter '{s}'" .kind = .warning .extra = .str - .opt = W("duplicate-embed-param") + .opt = .@"duplicate-embed-param" unsupported_embed_param .msg = "unsupported embed parameter '{s}' embed parameter" .kind = .warning .extra = .str - .opt = W("unsupported-embed-param") + .opt = .@"unsupported-embed-param" invalid_compound_literal_storage_class .msg = "compound literal cannot have {s} storage class" @@ -2533,7 +2514,7 @@ identifier_not_normalized .msg = "'{s}' is not in NFC" .kind = .warning .extra = .normalized - .opt = W("normalized") + .opt = .normalized c23_auto_single_declarator .msg = "'auto' can only be used with a single declarator" @@ -2559,21 +2540,19 @@ c23_auto_array negative_shift_count .msg = "shift count is negative" - .opt = W("shift-count-negative") + .opt = .@"shift-count-negative" .kind = .warning - .all = true too_big_shift_count .msg = "shift count >= width of type" - .opt = W("shift-count-overflow") + .opt = .@"shift-count-overflow" .kind = .warning - .all = true complex_conj .msg = "ISO C does not support '~' for complex conjugation of '{s}'" - .opt = W("pedantic") .extra = .str .kind = .off + .extension = true overflow_builtin_requires_int .msg = "operand argument to overflow builtin must be an integer ('{s}' invalid)" @@ -2611,18 +2590,18 @@ constexpr_requires_const subtract_pointers_zero_elem_size .msg = "subtraction of pointers to type '{s}' of zero size has undefined behavior" .kind = .warning - .opt = W("pointer-arith") + .opt = .@"pointer-arith" .extra = .str packed_member_address .msg = "taking address of packed member '{s}" - .opt = W("address-of-packed-member") + .opt = .@"address-of-packed-member" .kind = .warning .extra = .str alloc_align_requires_ptr_return .msg = "'alloc_align' attribute only applies to return values that are pointers" - .opt = W("ignored-attributes") + .opt = .@"ignored-attributes" .kind = .warning attribute_param_out_of_bounds @@ -2637,29 +2616,31 @@ alloc_align_required_int_param gnu_missing_eq_designator .msg = "use of GNU 'missing =' extension in designator" .kind = .warning - .opt = W("gnu-designator") + .opt = .@"gnu-designator" + .extension = true empty_if_body .msg = "if statement has empty body" .kind = .warning - .opt = W("empty-body") + .opt = .@"empty-body" empty_if_body_note .msg = "put the semicolon on a separate line to silence this warning" .kind = .note - .opt = W("empty-body") + .opt = .@"empty-body" nullability_extension .msg = "type nullability specifier '{s}' is a Clang extension" .extra = .str .kind = .off - .opt = W("nullability-extension") + .opt = .@"nullability-extension" + .extension = true duplicate_nullability .msg = "duplicate nullability specifier '{s}'" .extra = .str .kind = .warning - .opt = W("nullability") + .opt = .@"nullability" conflicting_nullability .msg = "nullaibility specifier '{s}' conflicts with existing specifier '{s}'" diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index cd9e06f..1089a4e 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -199,6 +199,7 @@ pub const usage = \\ -o Write output to \\ -P, --no-line-commands Disable linemarker output in -E mode \\ -pedantic Warn on language extensions + \\ -pedantic-errors Error on language extensions \\ --rtlib= Compiler runtime library to use (libgcc or compiler-rt) \\ -std= Specify language standard \\ -S, --assemble Only run preprocess and compilation steps @@ -206,6 +207,7 @@ pub const usage = \\ --target= Generate code for the given target \\ -U Undefine \\ -undef Do not predefine any system-specific macros. Standard predefined macros remain defined. + \\ -w Ignore all warnings \\ -Werror Treat all warnings as errors \\ -Werror= Treat warning as error \\ -W Enable the specified warning @@ -402,8 +404,10 @@ pub fn parseArgs( hosted = true; } else if (mem.eql(u8, arg, "-fms-extensions")) { d.comp.langopts.enableMSExtensions(); + try d.comp.diagnostics.set("microsoft", .off); } else if (mem.eql(u8, arg, "-fno-ms-extensions")) { d.comp.langopts.disableMSExtensions(); + try d.comp.diagnostics.set("microsoft", .warning); } else if (mem.startsWith(u8, arg, "-I")) { var path = arg["-I".len..]; if (path.len == 0) { @@ -442,6 +446,11 @@ pub fn parseArgs( continue; }; d.comp.langopts.setEmulatedCompiler(compiler); + switch (d.comp.langopts.emulate) { + .clang => try d.comp.diagnostics.set("clang", .off), + .gcc => try d.comp.diagnostics.set("gnu", .off), + .msvc => try d.comp.diagnostics.set("microsoft", .off), + } } else if (option(arg, "-ffp-eval-method=")) |fp_method_str| { const fp_eval_method = std.meta.stringToEnum(LangOpts.FPEvalMethod, fp_method_str) orelse .indeterminate; if (fp_eval_method == .indeterminate) { @@ -463,21 +472,36 @@ pub fn parseArgs( } else if (option(arg, "--sysroot=")) |sysroot| { d.sysroot = sysroot; } else if (mem.eql(u8, arg, "-pedantic")) { - d.comp.diagnostics.options.pedantic = .warning; + d.comp.diagnostics.state.extensions = .warning; + } else if (mem.eql(u8, arg, "-pedantic-errors")) { + d.comp.diagnostics.state.extensions = .@"error"; + } else if (mem.eql(u8, arg, "-w")) { + d.comp.diagnostics.state.ignore_warnings = true; } else if (option(arg, "--rtlib=")) |rtlib| { if (mem.eql(u8, rtlib, "compiler-rt") or mem.eql(u8, rtlib, "libgcc") or mem.eql(u8, rtlib, "platform")) { d.rtlib = rtlib; } else { try d.comp.addDiagnostic(.{ .tag = .invalid_rtlib, .extra = .{ .str = rtlib } }, &.{}); } + } else if (mem.eql(u8, arg, "-Wno-fatal-errors")) { + d.comp.diagnostics.state.fatal_errors = false; + } else if (mem.eql(u8, arg, "-Wfatal-errors")) { + d.comp.diagnostics.state.fatal_errors = true; + } else if (mem.eql(u8, arg, "-Wno-everything")) { + d.comp.diagnostics.state.enable_all_warnings = false; + } else if (mem.eql(u8, arg, "-Weverything")) { + d.comp.diagnostics.state.enable_all_warnings = true; + } else if (mem.eql(u8, arg, "-Werror")) { + d.comp.diagnostics.state.error_warnings = true; + } else if (mem.eql(u8, arg, "-Wno-error")) { + d.comp.diagnostics.state.error_warnings = false; } else if (option(arg, "-Werror=")) |err_name| { try d.comp.diagnostics.set(err_name, .@"error"); - } else if (mem.eql(u8, arg, "-Wno-fatal-errors")) { - d.comp.diagnostics.fatal_errors = false; + } else if (option(arg, "-Wno-error=")) |err_name| { + // TODO this should not set to warning if the option has not been specified. + try d.comp.diagnostics.set(err_name, .warning); } else if (option(arg, "-Wno-")) |err_name| { try d.comp.diagnostics.set(err_name, .off); - } else if (mem.eql(u8, arg, "-Wfatal-errors")) { - d.comp.diagnostics.fatal_errors = true; } else if (option(arg, "-W")) |err_name| { try d.comp.diagnostics.set(err_name, .warning); } else if (option(arg, "-std=")) |standard| { @@ -495,6 +519,11 @@ pub fn parseArgs( }; d.comp.target = target; d.comp.langopts.setEmulatedCompiler(target_util.systemCompiler(target)); + switch (d.comp.langopts.emulate) { + .clang => try d.comp.diagnostics.set("clang", .off), + .gcc => try d.comp.diagnostics.set("gnu", .off), + .msvc => try d.comp.diagnostics.set("microsoft", .off), + } d.raw_target_triple = triple; } else if (mem.eql(u8, arg, "--verbose-ast")) { d.verbose_ast = true; diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 55805dd..95cfd03 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -432,6 +432,13 @@ pub fn errStr(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex, str: []const pub fn errExtra(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex, extra: Diagnostics.Message.Extra) Compilation.Error!void { @branchHint(.cold); + + // Suppress pedantic diagnostics inside __extension__ blocks. + if (p.extension_suppressed) { + const prop = tag.property(); + if (prop.extension and prop.kind == .off) return; + } + const tok = p.pp.tokens.get(tok_i); var loc = tok.loc; if (tok_i != 0 and tok.id == .eof) { @@ -2352,7 +2359,11 @@ fn recordSpec(p: *Parser) Error!QualType { if (p.record.flexible_field) |some| { if (fields.len == 1 and is_struct) { - try p.errTok(.flexible_in_empty, some); + if (p.comp.langopts.emulate == .msvc) { + try p.errTok(.flexible_in_empty_msvc, some); + } else { + try p.errTok(.flexible_in_empty, some); + } } } @@ -2638,8 +2649,12 @@ fn recordDecl(p: *Parser) Error!bool { .fixed => {}, .incomplete => { if (p.record.kind == .keyword_union) { - try p.errTok(.flexible_in_union, first_tok); - qt = .invalid; + if (p.comp.langopts.emulate == .msvc) { + try p.errTok(.flexible_in_union_msvc, first_tok); + } else { + try p.errTok(.flexible_in_union, first_tok); + qt = .invalid; + } } if (p.record.flexible_field) |some| { if (p.record.kind == .keyword_struct) { @@ -8001,7 +8016,7 @@ fn unExpr(p: *Parser) Error!?Result { } if (base_type.qt.sizeofOrNull(p.comp)) |size| { - if (size == 0) { + if (size == 0 and p.comp.langopts.emulate == .msvc) { try p.errTok(.sizeof_returns_zero, tok); } res.val = try Value.int(size, p.comp); diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 426b140..233ce24 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -821,9 +821,9 @@ fn fatal(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anyty } fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []const u8) Compilation.Error { - const old = pp.comp.diagnostics.fatal_errors; - pp.comp.diagnostics.fatal_errors = true; - defer pp.comp.diagnostics.fatal_errors = old; + const old = pp.comp.diagnostics.state.fatal_errors; + pp.comp.diagnostics.state.fatal_errors = true; + defer pp.comp.diagnostics.state.fatal_errors = old; try pp.comp.diagnostics.addExtra(pp.comp.langopts, .{ .tag = .cli_error, .loc = tok.loc, .extra = .{ .str = try std.fmt.allocPrint(pp.comp.diagnostics.arena.allocator(), "'{s}' not found", .{filename}), diff --git a/src/aro/pragmas/gcc.zig b/src/aro/pragmas/gcc.zig index fe2ca62..1ade61a 100644 --- a/src/aro/pragmas/gcc.zig +++ b/src/aro/pragmas/gcc.zig @@ -19,8 +19,8 @@ pragma: Pragma = .{ .parserHandler = parserHandler, .preserveTokens = preserveTokens, }, -original_options: Diagnostics.Options = .{}, -options_stack: std.ArrayListUnmanaged(Diagnostics.Options) = .{}, +original_state: Diagnostics.State = .{}, +state_stack: std.ArrayListUnmanaged(Diagnostics.State) = .{}, const Directive = enum { warning, @@ -39,19 +39,19 @@ const Directive = enum { fn beforePreprocess(pragma: *Pragma, comp: *Compilation) void { var self: *GCC = @fieldParentPtr("pragma", pragma); - self.original_options = comp.diagnostics.options; + self.original_state = comp.diagnostics.state; } fn beforeParse(pragma: *Pragma, comp: *Compilation) void { var self: *GCC = @fieldParentPtr("pragma", pragma); - comp.diagnostics.options = self.original_options; - self.options_stack.items.len = 0; + comp.diagnostics.state = self.original_state; + self.state_stack.items.len = 0; } fn afterParse(pragma: *Pragma, comp: *Compilation) void { var self: *GCC = @fieldParentPtr("pragma", pragma); - comp.diagnostics.options = self.original_options; - self.options_stack.items.len = 0; + comp.diagnostics.state = self.original_state; + self.state_stack.items.len = 0; } pub fn init(allocator: mem.Allocator) !*Pragma { @@ -62,7 +62,7 @@ pub fn init(allocator: mem.Allocator) !*Pragma { fn deinit(pragma: *Pragma, comp: *Compilation) void { var self: *GCC = @fieldParentPtr("pragma", pragma); - self.options_stack.deinit(comp.gpa); + self.state_stack.deinit(comp.gpa); comp.gpa.destroy(self); } @@ -103,8 +103,8 @@ fn diagnosticHandler(self: *GCC, pp: *Preprocessor, start_idx: TokenIndex) Pragm try pp.comp.diagnostics.set(str[2..], new_kind); }, - .push => try self.options_stack.append(pp.comp.gpa, pp.comp.diagnostics.options), - .pop => pp.comp.diagnostics.options = self.options_stack.pop() orelse self.original_options, + .push => try self.state_stack.append(pp.comp.gpa, pp.comp.diagnostics.state), + .pop => pp.comp.diagnostics.state = self.state_stack.pop() orelse self.original_state, } } diff --git a/test/cases/_BitInt.c b/test/cases/_BitInt.c index c45d42e..7399e63 100644 --- a/test/cases/_BitInt.c +++ b/test/cases/_BitInt.c @@ -28,7 +28,7 @@ enum E: _BitInt(512) { }; _Static_assert(sizeof(_BitInt(65535)) == 8192, ""); -#define EXPECTED_ERRORS "_BitInt.c:3:1: warning: '_BitInt' in C17 and earlier is a Clang extension' [-Wbit-int-extension]" \ +#define EXPECTED_ERRORS "_BitInt.c:3:1: warning: '_BitInt' in C17 and earlier is a Clang extension [-Wbit-int-extension]" \ "_BitInt.c:13:1: error: signed _BitInt of bit sizes greater than 65535 not supported" \ "_BitInt.c:14:8: error: signed _BitInt must have a bit size of at least 2" \ "_BitInt.c:15:10: error: unsigned _BitInt must have a bit size of at least 1" \ diff --git a/test/cases/complex numbers clang.c b/test/cases/complex numbers clang.c index 1e2af15..42253ed 100644 --- a/test/cases/complex numbers clang.c +++ b/test/cases/complex numbers clang.c @@ -15,8 +15,8 @@ void foo(int x, float y) { z = ~z; } -#define EXPECTED_ERRORS "complex numbers clang.c:6:20: error: static_assert expression is not an integral constant expression" \ - "complex numbers clang.c:7:20: error: static_assert expression is not an integral constant expression" \ +#define EXPECTED_ERRORS "complex numbers clang.c:6:20: error: static assertion expression is not an integral constant expression" \ + "complex numbers clang.c:7:20: error: static assertion expression is not an integral constant expression" \ "complex numbers clang.c:9:42: warning: complex initialization specifying real and imaginary components is an extension [-Wcomplex-component-init]" \ "complex numbers clang.c:10:6: warning: ISO C does not support '++'/'--' on complex type '_Complex double' [-Wpedantic]" \ "complex numbers clang.c:12:32: error: argument type 'int' is not a real floating point type" \ diff --git a/test/cases/const decl folding.c b/test/cases/const decl folding.c index 694b71a..67c80ee 100644 --- a/test/cases/const decl folding.c +++ b/test/cases/const decl folding.c @@ -68,13 +68,13 @@ _Static_assert(4.2, ""); "const decl folding.c:34:27: error: '__builtin_choose_expr' requires a constant expression" \ "const decl folding.c:38:15: warning: variable length array folded to constant array as an extension [-Wgnu-folding-constant]" \ "const decl folding.c:43:16: warning: implicit conversion turns string literal into bool: 'char [1]' to '_Bool' [-Wstring-conversion]" \ - "const decl folding.c:43:16: error: static_assert expression is not an integral constant expression" \ + "const decl folding.c:43:16: error: static assertion expression is not an integral constant expression" \ "const decl folding.c:44:1: error: static assertion failed \"\"" \ - "const decl folding.c:46:16: error: static_assert expression is not an integral constant expression" \ - "const decl folding.c:47:16: error: static_assert expression is not an integral constant expression" \ + "const decl folding.c:46:16: error: static assertion expression is not an integral constant expression" \ + "const decl folding.c:47:16: error: static assertion expression is not an integral constant expression" \ "const decl folding.c:50:16: warning: address of array 'arr' will always evaluate to 'true' [-Wpointer-bool-conversion]" \ - "const decl folding.c:50:16: error: static_assert expression is not an integral constant expression" \ + "const decl folding.c:50:16: error: static assertion expression is not an integral constant expression" \ "const decl folding.c:51:1: error: static assertion failed \"\"" \ "const decl folding.c:53:16: warning: implicit conversion from 'double' to '_Bool' changes value from 4.2 to true [-Wfloat-conversion]" \ - "const decl folding.c:53:16: error: static_assert expression is not an integral constant expression" \ + "const decl folding.c:53:16: error: static assertion expression is not an integral constant expression" \ diff --git a/test/cases/divide by zero.c b/test/cases/divide by zero.c index fe7ae27..df6a288 100644 --- a/test/cases/divide by zero.c +++ b/test/cases/divide by zero.c @@ -20,4 +20,4 @@ void foo(void) { "divide by zero.c:7:13: warning: division by zero is undefined [-Wdivision-by-zero]" \ "divide by zero.c:8:13: warning: remainder by zero is undefined [-Wdivision-by-zero]" \ "divide by zero.c:9:21: warning: division by zero is undefined [-Wdivision-by-zero]" \ - "divide by zero.c:9:20: error: static_assert expression is not an integral constant expression" \ + "divide by zero.c:9:20: error: static assertion expression is not an integral constant expression" \ diff --git a/test/cases/extension.c b/test/cases/extension.c index 5ac9bdf..abbdede 100644 --- a/test/cases/extension.c +++ b/test/cases/extension.c @@ -2,7 +2,7 @@ __extension__; __extension__ int a; void foo(void) { - __extension__ int a; + __extension__ __auto_type a = 1; __extension__; __extension__ 1; } diff --git a/test/cases/float builtins.c b/test/cases/float builtins.c index f1f9ec8..892e420 100644 --- a/test/cases/float builtins.c +++ b/test/cases/float builtins.c @@ -15,6 +15,6 @@ _Static_assert(__builtin_isinf(__builtin_infl()), ""); _Static_assert(__builtin_isinf(1.0 / 0.0), ""); _Static_assert(!__builtin_isinf(2.0 + 3.0), ""); -#define EXPECTED_ERRORS "float builtins.c:5:16: error: static_assert expression is not an integral constant expression" \ - "float builtins.c:6:16: error: static_assert expression is not an integral constant expression" \ +#define EXPECTED_ERRORS "float builtins.c:5:16: error: static assertion expression is not an integral constant expression" \ + "float builtins.c:6:16: error: static assertion expression is not an integral constant expression" \ diff --git a/test/cases/gnu alignof.c b/test/cases/gnu alignof.c index 4dd76ba..3b464ec 100644 --- a/test/cases/gnu alignof.c +++ b/test/cases/gnu alignof.c @@ -1,4 +1,4 @@ -//aro-args -std=gnu17 +//aro-args --emulate=gcc void foo(void) { (void) _Alignof 2; (void) _Alignof(2); diff --git a/test/cases/relocations.c b/test/cases/relocations.c index b532ce6..465df79 100644 --- a/test/cases/relocations.c +++ b/test/cases/relocations.c @@ -71,12 +71,12 @@ _Static_assert(&((char *)casted)[4] == (char *)&casted[1], ""); _Static_assert(&((int *)casted)[1] == &casted[1], ""); #define EXPECTED_ERRORS "relocations.c:24:1: error: static assertion failed" \ - "relocations.c:29:16: error: static_assert expression is not an integral constant expression" \ - "relocations.c:30:16: error: static_assert expression is not an integral constant expression" \ + "relocations.c:29:16: error: static assertion expression is not an integral constant expression" \ + "relocations.c:30:16: error: static assertion expression is not an integral constant expression" \ "relocations.c:39:16: warning: taking address of packed member 'x' of class or structure 'Packed' may result in an unaligned pointer value [-Waddress-of-packed-member]" \ "relocations.c:39:28: warning: taking address of packed member 'y' of class or structure 'Packed' may result in an unaligned pointer value [-Waddress-of-packed-member]" \ "relocations.c:50:26: warning: subtraction of pointers to type 'union Empty' of zero size has undefined behavior [-Wpointer-arith]" \ - "relocations.c:50:16: error: static_assert expression is not an integral constant expression" \ - "relocations.c:60:20: error: static_assert expression is not an integral constant expression" \ - "relocations.c:64:20: error: static_assert expression is not an integral constant expression" \ + "relocations.c:50:16: error: static assertion expression is not an integral constant expression" \ + "relocations.c:60:20: error: static assertion expression is not an integral constant expression" \ + "relocations.c:64:20: error: static assertion expression is not an integral constant expression" \ diff --git a/test/cases/signed remainder.c b/test/cases/signed remainder.c index 29d6cdd..e1e2377 100644 --- a/test/cases/signed remainder.c +++ b/test/cases/signed remainder.c @@ -10,4 +10,4 @@ _Static_assert((-9223372036854775807LL - 1LL) % -1 != 0, "failed"); #error Should not get here #endif -#define EXPECTED_ERRORS "signed remainder.c:7:16: error: static_assert expression is not an integral constant expression" \ +#define EXPECTED_ERRORS "signed remainder.c:7:16: error: static assertion expression is not an integral constant expression" \ diff --git a/test/cases/sizeof variably modified types.c b/test/cases/sizeof variably modified types.c index ad63aed..ebb5e4b 100644 --- a/test/cases/sizeof variably modified types.c +++ b/test/cases/sizeof variably modified types.c @@ -7,6 +7,6 @@ void foo(int x) { } #define EXPECTED_ERRORS \ - "sizeof variably modified types.c:3:20: error: static_assert expression is not an integral constant expression" \ - "sizeof variably modified types.c:6:20: error: static_assert expression is not an integral constant expression" \ + "sizeof variably modified types.c:3:20: error: static assertion expression is not an integral constant expression" \ + "sizeof variably modified types.c:6:20: error: static assertion expression is not an integral constant expression" \ diff --git a/test/cases/static assert messages.c b/test/cases/static assert messages.c index 76906f2..e3b2307 100644 --- a/test/cases/static assert messages.c +++ b/test/cases/static assert messages.c @@ -10,9 +10,9 @@ void bar(void) { _Static_assert(1 == 0, "They are not equal!"); -#define EXPECTED_ERRORS "static assert messages.c:2:20: error: static_assert expression is not an integral constant expression" \ - "static assert messages.c:3:5: warning: static_assert with no message is a C23 extension [-Wc23-extensions]" \ - "static assert messages.c:4:5: warning: static_assert with no message is a C23 extension [-Wc23-extensions]" \ +#define EXPECTED_ERRORS "static assert messages.c:2:20: error: static assertion expression is not an integral constant expression" \ + "static assert messages.c:3:5: warning: '_Static_assert' with no message is a C23 extension [-Wc23-extensions]" \ + "static assert messages.c:4:5: warning: '_Static_assert' with no message is a C23 extension [-Wc23-extensions]" \ "static assert messages.c:4:5: error: static assertion failed" \ "static assert messages.c:8:27: error: expected ')', found ','" \ "static assert messages.c:8:19: note: to match this '('" \ -- Gitee From 177cd96ab4941e5a90c8f3e4a0258e045e9653a3 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 20 Apr 2025 22:26:16 +0300 Subject: [PATCH 012/117] rework diagnostics to not store messages before printing With this change the user can select whether the messages are output to a list or to a file. The messages can also be described in separate places enabling aro-pp to be split off in the future. --- build.zig | 1 - src/aro/Attribute.zig | 240 ++- src/aro/CodeGen.zig | 11 +- src/aro/Compilation.zig | 87 +- src/aro/Diagnostics.zig | 626 +++---- src/aro/Diagnostics/messages.def | 2653 --------------------------- src/aro/Driver.zig | 150 +- src/aro/Parser.zig | 1609 ++++++++-------- src/aro/Parser/Diagnostic.zig | 2274 +++++++++++++++++++++++ src/aro/Pragma.zig | 122 ++ src/aro/Preprocessor.zig | 504 ++--- src/aro/Preprocessor/Diagnostic.zig | 407 ++++ src/aro/Source.zig | 19 +- src/aro/SymbolStack.zig | 82 +- src/aro/Toolchain.zig | 11 +- src/aro/Tree.zig | 12 +- src/aro/TypeStore.zig | 59 +- src/aro/char_info.zig | 80 +- src/aro/pragmas/gcc.zig | 59 +- src/aro/pragmas/message.zig | 21 +- src/aro/pragmas/once.zig | 11 +- src/aro/pragmas/pack.zig | 25 +- src/aro/text_literal.zig | 324 +++- src/aro/toolchains/Linux.zig | 6 +- src/assembly_backend/x86_64.zig | 15 +- src/main.zig | 17 +- 26 files changed, 4697 insertions(+), 4728 deletions(-) delete mode 100644 src/aro/Diagnostics/messages.def create mode 100644 src/aro/Parser/Diagnostic.zig create mode 100644 src/aro/Preprocessor/Diagnostic.zig diff --git a/build.zig b/build.zig index 7cbdd7c..4e71a64 100644 --- a/build.zig +++ b/build.zig @@ -154,7 +154,6 @@ pub fn build(b: *Build) !void { }, GenerateDef.create(b, .{ .name = "Builtins/Builtin.def", .needs_large_dafsa_node = true }), GenerateDef.create(b, .{ .name = "Attribute/names.def" }), - GenerateDef.create(b, .{ .name = "Diagnostics/messages.def", .kind = .named }), }, }); const assembly_backend = b.addModule("assembly_backend", .{ diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 7d92c13..b38082b 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -207,21 +207,20 @@ pub fn wantsIdentEnum(attr: Tag) bool { } } -pub fn diagnoseIdent(attr: Tag, arguments: *Arguments, ident: []const u8) ?Diagnostics.Message { +pub fn diagnoseIdent(attr: Tag, arguments: *Arguments, ident: TokenIndex, p: *Parser) !bool { switch (attr) { inline else => |tag| { const fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields; if (fields.len == 0) unreachable; const Unwrapped = UnwrapOptional(fields[0].type); if (@typeInfo(Unwrapped) != .@"enum") unreachable; - if (std.meta.stringToEnum(Unwrapped, normalize(ident))) |enum_val| { + if (std.meta.stringToEnum(Unwrapped, normalize(p.tokSlice(ident)))) |enum_val| { @field(@field(arguments, @tagName(tag)), fields[0].name) = enum_val; - return null; + return false; } - return Diagnostics.Message{ - .tag = .unknown_attr_enum, - .extra = .{ .attr_enum = .{ .tag = attr } }, - }; + + try p.err(ident, .unknown_attr_enum, .{ @tagName(attr), Formatting.choices(attr) }); + return true; }, } } @@ -240,7 +239,7 @@ pub fn wantsAlignment(attr: Tag, idx: usize) bool { } } -pub fn diagnoseAlignment(attr: Tag, arguments: *Arguments, arg_idx: u32, res: Parser.Result, p: *Parser) !?Diagnostics.Message { +pub fn diagnoseAlignment(attr: Tag, arguments: *Arguments, arg_idx: u32, res: Parser.Result, arg_start: TokenIndex, p: *Parser) !bool { switch (attr) { inline else => |tag| { const arg_fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields; @@ -250,17 +249,25 @@ pub fn diagnoseAlignment(attr: Tag, arguments: *Arguments, arg_idx: u32, res: Pa inline 0...arg_fields.len - 1 => |arg_i| { if (UnwrapOptional(arg_fields[arg_i].type) != Alignment) unreachable; - if (!res.val.is(.int, p.comp)) return Diagnostics.Message{ .tag = .alignas_unavailable }; + if (!res.val.is(.int, p.comp)) { + try p.err(arg_start, .alignas_unavailable, .{}); + return true; + } if (res.val.compare(.lt, Value.zero, p.comp)) { - return Diagnostics.Message{ .tag = .negative_alignment, .extra = .{ .str = try res.str(p) } }; + try p.err(arg_start, .negative_alignment, .{res}); + return true; } const requested = res.val.toInt(u29, p.comp) orelse { - return Diagnostics.Message{ .tag = .maximum_alignment, .extra = .{ .str = try res.str(p) } }; + try p.err(arg_start, .maximum_alignment, .{res}); + return true; }; - if (!std.mem.isValidAlign(requested)) return Diagnostics.Message{ .tag = .non_pow2_align }; + if (!std.mem.isValidAlign(requested)) { + try p.err(arg_start, .non_pow2_align, .{}); + return true; + } - @field(@field(arguments, @tagName(tag)), arg_fields[arg_i].name) = Alignment{ .requested = requested }; - return null; + @field(@field(arguments, @tagName(tag)), arg_fields[arg_i].name) = .{ .requested = requested }; + return false; }, else => unreachable, } @@ -274,25 +281,29 @@ fn diagnoseField( comptime Wanted: type, arguments: *Arguments, res: Parser.Result, + arg_start: TokenIndex, node: Tree.Node, p: *Parser, -) !?Diagnostics.Message { +) !bool { if (res.val.opt_ref == .none) { if (Wanted == Identifier and node == .decl_ref_expr) { @field(@field(arguments, decl.name), field.name) = .{ .tok = node.decl_ref_expr.name_tok }; - return null; + return false; } - return invalidArgMsg(Wanted, .expression); + + try p.err(arg_start, .attribute_arg_invalid, .{ expectedArgType(Wanted), "expression" }); + return true; } const key = p.comp.interner.get(res.val.ref()); switch (key) { .int => { if (@typeInfo(Wanted) == .int) { - @field(@field(arguments, decl.name), field.name) = res.val.toInt(Wanted, p.comp) orelse return .{ - .tag = .attribute_int_out_of_range, - .extra = .{ .str = try res.str(p) }, + @field(@field(arguments, decl.name), field.name) = res.val.toInt(Wanted, p.comp) orelse { + try p.err(arg_start, .attribute_int_out_of_range, .{res}); + return true; }; - return null; + + return false; } }, .bytes => |bytes| { @@ -304,78 +315,64 @@ fn diagnoseField( else => break :validate, } @field(@field(arguments, decl.name), field.name) = try p.removeNull(res.val); - return null; + return false; } - return .{ - .tag = .attribute_requires_string, - .extra = .{ .str = decl.name }, - }; + + try p.err(arg_start, .attribute_requires_string, .{decl.name}); + return true; } else if (@typeInfo(Wanted) == .@"enum" and @hasDecl(Wanted, "opts") and Wanted.opts.enum_kind == .string) { const str = bytes[0 .. bytes.len - 1]; if (std.meta.stringToEnum(Wanted, str)) |enum_val| { @field(@field(arguments, decl.name), field.name) = enum_val; - return null; - } else { - return .{ - .tag = .unknown_attr_enum, - .extra = .{ .attr_enum = .{ .tag = std.meta.stringToEnum(Tag, decl.name).? } }, - }; + return false; } + + try p.err(arg_start, .unknown_attr_enum, .{ decl.name, Formatting.choices(@field(Tag, decl.name)) }); + return true; } }, else => {}, } - return invalidArgMsg(Wanted, switch (key) { - .int => .int, - .bytes => .string, - .float => .float, - .complex => .complex_float, - .null => .nullptr_t, - .int_ty, - .float_ty, - .complex_ty, - .ptr_ty, - .noreturn_ty, - .void_ty, - .func_ty, - .array_ty, - .vector_ty, - .record_ty, - .pointer, - => unreachable, - }); + + try p.err(arg_start, .attribute_arg_invalid, .{ expectedArgType(Wanted), switch (key) { + .int => "int", + .bytes => "string", + .float => "float", + .complex => "complex_float", + .null => "nullptr_t", + else => unreachable, + } }); + return true; } -fn invalidArgMsg(comptime Expected: type, actual: ArgumentType) Diagnostics.Message { - return .{ - .tag = .attribute_arg_invalid, - .extra = .{ .attr_arg_type = .{ .expected = switch (Expected) { - Value => .string, - Identifier => .identifier, - u32 => .int, - Alignment => .alignment, - CallingConvention => .identifier, - else => switch (@typeInfo(Expected)) { - .@"enum" => if (Expected.opts.enum_kind == .string) .string else .identifier, - else => unreachable, - }, - }, .actual = actual } }, +fn expectedArgType(comptime Expected: type) []const u8 { + return switch (Expected) { + Value => "string", + Identifier => "identifier", + u32 => "int", + Alignment => "alignment", + CallingConvention => "identifier", + else => switch (@typeInfo(Expected)) { + .@"enum" => if (Expected.opts.enum_kind == .string) "string" else "identifier", + else => unreachable, + }, }; } -pub fn diagnose(attr: Tag, arguments: *Arguments, arg_idx: u32, res: Parser.Result, node: Tree.Node, p: *Parser) !?Diagnostics.Message { +pub fn diagnose(attr: Tag, arguments: *Arguments, arg_idx: u32, res: Parser.Result, arg_start: TokenIndex, node: Tree.Node, p: *Parser) !bool { switch (attr) { inline else => |tag| { const decl = @typeInfo(attributes).@"struct".decls[@intFromEnum(tag)]; const max_arg_count = comptime maxArgCount(tag); - if (arg_idx >= max_arg_count) return Diagnostics.Message{ - .tag = .attribute_too_many_args, - .extra = .{ .attr_arg_count = .{ .attribute = attr, .expected = max_arg_count } }, - }; + if (arg_idx >= max_arg_count) { + try p.err(arg_start, .attribute_too_many_args, .{ @tagName(attr), max_arg_count }); + return true; + } + const arg_fields = @typeInfo(@field(attributes, decl.name)).@"struct".fields; switch (arg_idx) { inline 0...arg_fields.len - 1 => |arg_i| { - return diagnoseField(decl, arg_fields[arg_i], UnwrapOptional(arg_fields[arg_i].type), arguments, res, node, p); + return diagnoseField(decl, arg_fields[arg_i], UnwrapOptional(arg_fields[arg_i].type), arguments, res, arg_start, node, p); }, else => unreachable, } @@ -811,16 +808,11 @@ pub fn normalize(name: []const u8) []const u8 { } fn ignoredAttrErr(p: *Parser, tok: TokenIndex, attr: Attribute.Tag, context: []const u8) !void { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - try p.strings.writer().print("attribute '{s}' ignored on {s}", .{ @tagName(attr), context }); - const str = try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); - try p.errStr(.ignored_attribute, tok, str); + try p.err(tok, .ignored_attribute, .{ @tagName(attr), context }); } pub const applyParameterAttributes = applyVariableAttributes; -pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, tag: ?Diagnostics.Tag) !QualType { +pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, diagnostic: ?Parser.Diagnostic) !QualType { const attrs = p.attr_buf.items(.attr)[attr_buf_start..]; const toks = p.attr_buf.items(.tok)[attr_buf_start..]; p.attr_application_buf.items.len = 0; @@ -834,19 +826,19 @@ pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .common => if (nocommon) { - try p.errTok(.ignore_common, tok); + try p.err(tok, .ignore_common, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); common = true; }, .nocommon => if (common) { - try p.errTok(.ignore_nocommon, tok); + try p.err(tok, .ignore_nocommon, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); nocommon = true; }, .vector_size => try attr.applyVectorSize(p, tok, &base_qt), - .aligned => try attr.applyAligned(p, base_qt, tag), + .aligned => try attr.applyAligned(p, base_qt, diagnostic), .nonstring => { if (base_qt.get(p.comp, .array)) |array_ty| { if (array_ty.elem.get(p.comp, .int)) |int_ty| switch (int_ty) { @@ -857,15 +849,15 @@ pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, else => {}, }; } - try p.errStr(.non_string_ignored, tok, try p.typeStr(qt)); + try p.err(tok, .non_string_ignored, .{qt}); }, .uninitialized => if (p.func.qt == null) { - try p.errStr(.local_variable_attribute, tok, "uninitialized"); + try p.err(tok, .local_variable_attribute, .{"uninitialized"}); } else { try p.attr_application_buf.append(p.gpa, attr); }, .cleanup => if (p.func.qt == null) { - try p.errStr(.local_variable_attribute, tok, "cleanup"); + try p.err(tok, .local_variable_attribute, .{"cleanup"}); } else { try p.attr_application_buf.append(p.gpa, attr); }, @@ -873,7 +865,7 @@ pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, .copy, .tls_model, .visibility, - => |t| try p.errExtra(.attribute_todo, tok, .{ .attribute_todo = .{ .tag = t, .kind = .variables } }), + => |t| try p.err(tok, .attribute_todo, .{ @tagName(t), "variables" }), // There is already an error in Parser for _Noreturn keyword .noreturn => if (attr.syntax != .keyword) try ignoredAttrErr(p, tok, attr.tag, "variables"), else => try ignoredAttrErr(p, tok, attr.tag, "variables"), @@ -898,7 +890,7 @@ pub fn applyFieldAttributes(p: *Parser, field_ty: *QualType, attr_buf_start: usi return p.attr_application_buf.items; } -pub fn applyTypeAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, tag: ?Diagnostics.Tag) !QualType { +pub fn applyTypeAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, diagnostic: ?Parser.Diagnostic) !QualType { const attrs = p.attr_buf.items(.attr)[attr_buf_start..]; const toks = p.attr_buf.items(.tok)[attr_buf_start..]; p.attr_application_buf.items.len = 0; @@ -910,17 +902,17 @@ pub fn applyTypeAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, tag: // zig fmt: on .transparent_union => try attr.applyTransparentUnion(p, tok, base_qt), .vector_size => try attr.applyVectorSize(p, tok, &base_qt), - .aligned => try attr.applyAligned(p, base_qt, tag), + .aligned => try attr.applyAligned(p, base_qt, diagnostic), .designated_init => if (base_qt.is(p.comp, .@"struct")) { try p.attr_application_buf.append(p.gpa, attr); } else { - try p.errTok(.designated_init_invalid, tok); + try p.err(tok, .designated_init_invalid, .{}); }, .alloc_size, .copy, .scalar_storage_order, .nonstring, - => |t| try p.errExtra(.attribute_todo, tok, .{ .attribute_todo = .{ .tag = t, .kind = .types } }), + => |t| try p.err(tok, .attribute_todo, .{ @tagName(t), "types" }), else => try ignoredAttrErr(p, tok, attr.tag, "types"), }; return applySelected(base_qt, p); @@ -944,25 +936,25 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on .hot => if (cold) { - try p.errTok(.ignore_hot, tok); + try p.err(tok, .ignore_hot, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); hot = true; }, .cold => if (hot) { - try p.errTok(.ignore_cold, tok); + try p.err(tok, .ignore_cold, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); cold = true; }, .always_inline => if (@"noinline") { - try p.errTok(.ignore_always_inline, tok); + try p.err(tok, .ignore_always_inline, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); always_inline = true; }, .@"noinline" => if (always_inline) { - try p.errTok(.ignore_noinline, tok); + try p.err(tok, .ignore_noinline, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); @"noinline" = true; @@ -973,11 +965,11 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .c => continue, .stdcall, .thiscall, .fastcall, .regcall => switch (p.comp.target.cpu.arch) { .x86 => try p.attr_application_buf.append(p.gpa, attr), - else => try p.errStr(.callconv_not_supported, tok, p.tok_ids[tok].lexeme().?), + else => try p.err(tok, .callconv_not_supported, .{p.tok_ids[tok].lexeme().?}), }, .vectorcall => switch (p.comp.target.cpu.arch) { .x86, .aarch64, .aarch64_be => try p.attr_application_buf.append(p.gpa, attr), - else => try p.errStr(.callconv_not_supported, tok, p.tok_ids[tok].lexeme().?), + else => try p.err(tok, .callconv_not_supported, .{p.tok_ids[tok].lexeme().?}), }, .riscv_vector, .aarch64_sve_pcs, @@ -993,7 +985,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .syntax = attr.syntax, }); } else { - try p.errStr(.callconv_not_supported, tok, "fastcall"); + try p.err(tok, .callconv_not_supported, .{"fastcall"}); }, .stdcall => if (p.comp.target.cpu.arch == .x86) { try p.attr_application_buf.append(p.gpa, .{ @@ -1002,7 +994,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .syntax = attr.syntax, }); } else { - try p.errStr(.callconv_not_supported, tok, "stdcall"); + try p.err(tok, .callconv_not_supported, .{"stdcall"}); }, .thiscall => if (p.comp.target.cpu.arch == .x86) { try p.attr_application_buf.append(p.gpa, .{ @@ -1011,7 +1003,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .syntax = attr.syntax, }); } else { - try p.errStr(.callconv_not_supported, tok, "thiscall"); + try p.err(tok, .callconv_not_supported, .{"thiscall"}); }, .vectorcall => if (p.comp.target.cpu.arch == .x86 or p.comp.target.cpu.arch.isAARCH64()) { try p.attr_application_buf.append(p.gpa, .{ @@ -1020,7 +1012,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .syntax = attr.syntax, }); } else { - try p.errStr(.callconv_not_supported, tok, "vectorcall"); + try p.err(tok, .callconv_not_supported, .{"vectorcall"}); }, .cdecl => {}, .pcs => if (p.comp.target.cpu.arch.isArm()) { @@ -1033,7 +1025,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .syntax = attr.syntax, }); } else { - try p.errStr(.callconv_not_supported, tok, "pcs"); + try p.err(tok, .callconv_not_supported, .{"pcs"}); }, .riscv_vector_cc => if (p.comp.target.cpu.arch.isRISCV()) { try p.attr_application_buf.append(p.gpa, .{ @@ -1042,7 +1034,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .syntax = attr.syntax, }); } else { - try p.errStr(.callconv_not_supported, tok, "pcs"); + try p.err(tok, .callconv_not_supported, .{"pcs"}); }, .aarch64_sve_pcs => if (p.comp.target.cpu.arch.isAARCH64()) { try p.attr_application_buf.append(p.gpa, .{ @@ -1051,7 +1043,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .syntax = attr.syntax, }); } else { - try p.errStr(.callconv_not_supported, tok, "pcs"); + try p.err(tok, .callconv_not_supported, .{"pcs"}); }, .aarch64_vector_pcs => if (p.comp.target.cpu.arch.isAARCH64()) { try p.attr_application_buf.append(p.gpa, .{ @@ -1060,7 +1052,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .syntax = attr.syntax, }); } else { - try p.errStr(.callconv_not_supported, tok, "pcs"); + try p.err(tok, .callconv_not_supported, .{"pcs"}); }, .malloc => { if (base_qt.get(p.comp, .func).?.return_type.isPointer(p.comp)) { @@ -1073,14 +1065,14 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) const func_ty = base_qt.get(p.comp, .func).?; if (func_ty.return_type.isPointer(p.comp)) { if (attr.args.alloc_align.position == 0 or attr.args.alloc_align.position > func_ty.params.len) { - try p.errExtra(.attribute_param_out_of_bounds, tok, .{ .attr_arg_count = .{ .attribute = .alloc_align, .expected = 1 } }); + try p.err(tok, .attribute_param_out_of_bounds, .{ "alloc_align", 1 }); } else if (!func_ty.params[attr.args.alloc_align.position - 1].qt.isInt(p.comp)) { - try p.errTok(.alloc_align_required_int_param, tok); + try p.err(tok, .alloc_align_required_int_param, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); } } else { - try p.errTok(.alloc_align_requires_ptr_return, tok); + try p.err(tok, .alloc_align_requires_ptr_return, .{}); } }, .access, @@ -1122,7 +1114,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) .visibility, .weakref, .zero_call_used_regs, - => |t| try p.errExtra(.attribute_todo, tok, .{ .attribute_todo = .{ .tag = t, .kind = .functions } }), + => |t| try p.err(tok, .attribute_todo, .{ @tagName(t), "functions" }), else => try ignoredAttrErr(p, tok, attr.tag, "functions"), }; return applySelected(qt, p); @@ -1137,13 +1129,13 @@ pub fn applyLabelAttributes(p: *Parser, attr_buf_start: usize) !QualType { for (attrs, toks) |attr, tok| switch (attr.tag) { .unused => try p.attr_application_buf.append(p.gpa, attr), .hot => if (cold) { - try p.errTok(.ignore_hot, tok); + try p.err(tok, .ignore_hot, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); hot = true; }, .cold => if (hot) { - try p.errTok(.ignore_cold, tok); + try p.err(tok, .ignore_cold, .{}); } else { try p.attr_application_buf.append(p.gpa, attr); cold = true; @@ -1167,13 +1159,13 @@ pub fn applyStatementAttributes(p: *Parser, expr_start: TokenIndex, attr_buf_sta }, .r_brace => {}, else => { - try p.errTok(.invalid_fallthrough, expr_start); + try p.err(expr_start, .invalid_fallthrough, .{}); break; }, } } }, - else => try p.errStr(.cannot_apply_attribute_to_statement, tok, @tagName(attr.tag)), + else => try p.err(tok, .cannot_apply_attribute_to_statement, .{@tagName(attr.tag)}), }; return applySelected(.void, p); } @@ -1189,19 +1181,19 @@ pub fn applyEnumeratorAttributes(p: *Parser, qt: QualType, attr_buf_start: usize return applySelected(qt, p); } -fn applyAligned(attr: Attribute, p: *Parser, qt: QualType, tag: ?Diagnostics.Tag) !void { +fn applyAligned(attr: Attribute, p: *Parser, qt: QualType, diagnostic: ?Parser.Diagnostic) !void { if (attr.args.aligned.alignment) |alignment| alignas: { if (attr.syntax != .keyword) break :alignas; const align_tok = attr.args.aligned.__name_tok; - if (tag) |t| try p.errTok(t, align_tok); + if (diagnostic) |d| try p.err(align_tok, d, .{}); if (qt.isInvalid()) return; const default_align = qt.base(p.comp).qt.alignof(p.comp); if (qt.is(p.comp, .func)) { - try p.errTok(.alignas_on_func, align_tok); + try p.err(align_tok, .alignas_on_func, .{}); } else if (alignment.requested < default_align) { - try p.errExtra(.minimum_alignment, align_tok, .{ .unsigned = default_align }); + try p.err(align_tok, .minimum_alignment, .{default_align}); } } try p.attr_application_buf.append(p.gpa, attr); @@ -1209,24 +1201,20 @@ fn applyAligned(attr: Attribute, p: *Parser, qt: QualType, tag: ?Diagnostics.Tag fn applyTransparentUnion(attr: Attribute, p: *Parser, tok: TokenIndex, qt: QualType) !void { const union_ty = qt.get(p.comp, .@"union") orelse { - return p.errTok(.transparent_union_wrong_type, tok); + return p.err(tok, .transparent_union_wrong_type, .{}); }; // TODO validate union defined at end if (union_ty.layout == null) return; if (union_ty.fields.len == 0) { - return p.errTok(.transparent_union_one_field, tok); + return p.err(tok, .transparent_union_one_field, .{}); } const first_field_size = union_ty.fields[0].qt.bitSizeof(p.comp); for (union_ty.fields[1..]) |field| { const field_size = field.qt.bitSizeof(p.comp); if (field_size == first_field_size) continue; - const str = try std.fmt.allocPrint( - p.comp.diagnostics.arena.allocator(), - "'{s}' ({d}", - .{ field.name.lookup(p.comp), field_size }, - ); - try p.errStr(.transparent_union_size, field.name_tok, str); - return p.errExtra(.transparent_union_size_note, union_ty.fields[0].name_tok, .{ .unsigned = first_field_size }); + + try p.err(field.name_tok, .transparent_union_size, .{ field.name.lookup(p.comp), field_size }); + return p.err(union_ty.fields[0].name_tok, .transparent_union_size_note, .{first_field_size}); } try p.attr_application_buf.append(p.gpa, attr); @@ -1241,14 +1229,14 @@ fn applyVectorSize(attr: Attribute, p: *Parser, tok: TokenIndex, qt: *QualType) return; // Clang silently ignores vector_size on incomplete enums. } } - try p.errStr(.invalid_vec_elem_ty, tok, try p.typeStr(qt.*)); + try p.err(tok, .invalid_vec_elem_ty, .{qt.*}); return error.ParsingFailed; } const vec_bytes = attr.args.vector_size.bytes; const elem_size = qt.sizeof(p.comp); if (vec_bytes % elem_size != 0) { - return p.errTok(.vec_size_not_multiple, tok); + return p.err(tok, .vec_size_not_multiple, .{}); } qt.* = try p.comp.type_store.put(p.gpa, .{ .vector = .{ diff --git a/src/aro/CodeGen.zig b/src/aro/CodeGen.zig index 632f124..b2db935 100644 --- a/src/aro/CodeGen.zig +++ b/src/aro/CodeGen.zig @@ -55,11 +55,12 @@ return_label: Ir.Ref = undefined, compound_assign_dummy: ?Ir.Ref = null, fn fail(c: *CodeGen, comptime fmt: []const u8, args: anytype) error{ FatalError, OutOfMemory } { - try c.comp.diagnostics.list.append(c.comp.gpa, .{ - .tag = .cli_error, - .kind = .@"fatal error", - .extra = .{ .str = try std.fmt.allocPrint(c.comp.diagnostics.arena.allocator(), fmt, args) }, - }); + var sf = std.heap.stackFallback(1024, c.comp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try buf.writer().print(fmt, args); + try c.comp.diagnostics.add(.{ .text = buf.items, .kind = .@"fatal error", .location = null }); return error.FatalError; } diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 17f5a2b..9db30cf 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -96,7 +96,7 @@ pub const Environment = struct { const Compilation = @This(); gpa: Allocator, -diagnostics: Diagnostics, +diagnostics: *Diagnostics, code_gen_options: CodeGenOptions = .default, environment: Environment = .{}, @@ -116,21 +116,21 @@ type_store: TypeStore = .{}, ms_cwd_source_id: ?Source.Id = null, cwd: std.fs.Dir, -pub fn init(gpa: Allocator, cwd: std.fs.Dir) Compilation { +pub fn init(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation { return .{ .gpa = gpa, - .diagnostics = Diagnostics.init(gpa), + .diagnostic = diagnostics, .cwd = cwd, }; } /// Initialize Compilation with default environment, /// pragma handlers and emulation mode set to target. -pub fn initDefault(gpa: Allocator, cwd: std.fs.Dir) !Compilation { +pub fn initDefault(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) !Compilation { var comp: Compilation = .{ .gpa = gpa, + .diagnostics = diagnostics, .environment = try Environment.loadAll(gpa), - .diagnostics = Diagnostics.init(gpa), .cwd = cwd, }; errdefer comp.deinit(); @@ -532,9 +532,9 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { /// Generate builtin macros trying to use mtime as timestamp pub fn generateBuiltinMacrosFromPath(comp: *Compilation, system_defines_mode: SystemDefinesMode, path: []const u8) !Source { - const stat = comp.cwd.statFile(path) catch return try generateBuiltinMacros(comp, system_defines_mode, null); + const stat = comp.cwd.statFile(path) catch return try comp.generateBuiltinMacros(system_defines_mode, null); const timestamp: i64 = @intCast(@divTrunc(stat.mtime, std.time.ns_per_s)); - return try generateBuiltinMacros(comp, system_defines_mode, @intCast(std.math.clamp(timestamp, 0, max_timestamp))); + return try comp.generateBuiltinMacros(system_defines_mode, @intCast(std.math.clamp(timestamp, 0, max_timestamp))); } /// Generate builtin macros that will be available to each source file. @@ -575,10 +575,8 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi } const provided: ?u47 = comp.getSourceEpoch(max_timestamp) catch blk: { - try comp.addDiagnostic(.{ - .tag = .invalid_source_epoch, - .loc = .{ .id = .unused, .byte_offset = 0, .line = 0 }, - }, &.{}); + const diagnostic: Diagnostic = .invalid_source_epoch; + try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null }); break :blk null; }; if (provided) |epoch| { @@ -1022,10 +1020,13 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, i = backslash_loc; try splice_list.append(i); if (state == .trailing_ws) { - try comp.addDiagnostic(.{ - .tag = .backslash_newline_escape, - .loc = .{ .id = source_id, .byte_offset = i, .line = line }, - }, &.{}); + const diagnostic: Diagnostic = .backslash_newline_escape; + try comp.diagnostics.add(.{ + .text = diagnostic.fmt, + .kind = diagnostic.kind, + .opt = diagnostic.opt, + .location = (Source.Location{ .id = source_id, .byte_offset = i, .line = line }).expand(comp), + }); } state = if (state == .back_slash_cr) .cr else .back_slash_cr; }, @@ -1046,10 +1047,13 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, try splice_list.append(i); } if (state == .trailing_ws) { - try comp.addDiagnostic(.{ - .tag = .backslash_newline_escape, - .loc = .{ .id = source_id, .byte_offset = i, .line = line }, - }, &.{}); + const diagnostic: Diagnostic = .backslash_newline_escape; + try comp.diagnostics.add(.{ + .text = diagnostic.fmt, + .kind = diagnostic.kind, + .opt = diagnostic.opt, + .location = (Source.Location{ .id = source_id, .byte_offset = i, .line = line }).expand(comp), + }); } }, .bom1, .bom2 => break, @@ -1359,15 +1363,17 @@ pub fn findInclude( defer sf_allocator.free(found.path); if (comp.addSourceFromPathExtra(found.path, found.kind)) |some| { if (it.tried_ms_cwd) { - try comp.addDiagnostic(.{ - .tag = .ms_search_rule, - .extra = .{ .str = some.path }, - .loc = .{ + const diagnostic: Diagnostic = .ms_search_rule; + try comp.diagnostics.add(.{ + .text = diagnostic.fmt, + .kind = diagnostic.kind, + .opt = diagnostic.opt, + .location = (Source.Location{ .id = includer_token.source, .byte_offset = includer_token.start, .line = includer_token.line, - }, - }, &.{}); + }).expand(comp), + }); } return some; } else |err| switch (err) { @@ -1472,7 +1478,34 @@ pub const CharUnitSize = enum(u32) { } }; -pub const addDiagnostic = Diagnostics.add; +pub const Diagnostic = struct { + fmt: []const u8, + kind: Diagnostics.Message.Kind, + opt: ?Diagnostics.Option = null, + + pub const invalid_source_epoch: Diagnostic = .{ + .fmt = "environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799", + .kind = .@"error", + }; + + pub const backslash_newline_escape: Diagnostic = .{ + .fmt = "backslash and newline separated by space", + .kind = .warning, + .opt = .@"backslash-newline-escape", + }; + + pub const ms_search_rule: Diagnostic = .{ + .fmt = "#include resolved using non-portable Microsoft search rules as: {s}", + .kind = .warning, + .opt = .@"microsoft-include", + }; + + pub const ctrl_z_eof: Diagnostic = .{ + .fmt = "treating Ctrl-Z as end-of-file is a Microsoft extension", + .kind = .off, + .opt = .@"microsoft-end-of-file", + }; +}; test "addSourceFromReader" { const Test = struct { @@ -1484,7 +1517,7 @@ test "addSourceFromReader" { const source = try comp.addSourceFromReader(buf_reader.reader(), "path", .user); try std.testing.expectEqualStrings(expected, source.buf); - try std.testing.expectEqual(warning_count, @as(u32, @intCast(comp.diagnostics.list.items.len))); + try std.testing.expectEqual(warning_count, @as(u32, @intCast(comp.diagnostics.warnings))); try std.testing.expectEqualSlices(u32, splices, source.splice_locs); } diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index a7ea5b5..08e46e1 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -2,98 +2,25 @@ const std = @import("std"); const mem = std.mem; const Allocator = mem.Allocator; -const Attribute = @import("Attribute.zig"); -const Builtins = @import("Builtins.zig"); -const Builtin = Builtins.Builtin; -const Header = @import("Builtins/Properties.zig").Header; const Compilation = @import("Compilation.zig"); const LangOpts = @import("LangOpts.zig"); const Source = @import("Source.zig"); -const Tree = @import("Tree.zig"); - -const is_windows = @import("builtin").os.tag == .windows; pub const Message = struct { - tag: Tag, - kind: Kind = undefined, - loc: Source.Location = .{}, - extra: Extra = .{ .none = {} }, - - pub const Extra = union { - str: []const u8, - tok_id: struct { - expected: Tree.Token.Id, - actual: Tree.Token.Id, - }, - tok_id_expected: Tree.Token.Id, - arguments: struct { - expected: u32, - actual: u32, - }, - codepoints: struct { - actual: u21, - resembles: u21, - }, - attr_arg_count: struct { - attribute: Attribute.Tag, - expected: u32, - }, - attr_arg_type: struct { - expected: Attribute.ArgumentType, - actual: Attribute.ArgumentType, - }, - attr_enum: struct { - tag: Attribute.Tag, - }, - ignored_record_attr: struct { - tag: Attribute.Tag, - tag_kind: enum { @"struct", @"union", @"enum" }, - }, - attribute_todo: struct { - tag: Attribute.Tag, - kind: enum { variables, fields, types, functions }, - }, - builtin_with_header: struct { - builtin: Builtin.Tag, - header: Header, - }, - invalid_escape: struct { - offset: u32, - char: u8, - }, - actual_codepoint: u21, - ascii: u7, - unsigned: u64, - offset: u64, - pow_2_as_string: u8, - signed: i64, - normalized: []const u8, - none: void, - }; -}; - -const Properties = struct { - msg: []const u8, kind: Kind, - extra: std.meta.FieldEnum(Message.Extra) = .none, + text: []const u8, + opt: ?Option = null, extension: bool = false, - - // TODO look into removing these - suppress_version: ?LangOpts.Standard = null, - suppress_unless_version: ?LangOpts.Standard = null, - - pub const max_bits = Compilation.bit_int_max_bits; -}; - -pub const Tag = @import("Diagnostics/messages.def").with(Properties).Tag; - -pub const Kind = enum { - off, - note, - warning, - @"error", - @"fatal error", + location: ?Source.ExpandedLocation, + + pub const Kind = enum { + off, + note, + warning, + @"error", + @"fatal error", + }; }; pub const Option = enum { @@ -297,19 +224,42 @@ pub const State = struct { /// Ignore all warnings, set by -w ignore_warnings: bool = false, /// How to treat extension diagnostics, set by -Wpedantic - extensions: Kind = .off, + extensions: Message.Kind = .off, /// How to treat individual options, set by -W - options: std.EnumMap(Option, Kind) = .{}, + options: std.EnumMap(Option, Message.Kind) = .{}, }; const Diagnostics = @This(); -list: std.ArrayListUnmanaged(Message) = .{}, -arena: std.heap.ArenaAllocator, +output: union(enum) { + to_file: struct { + file: std.fs.File, + config: std.io.tty.Config, + }, + to_list: struct { + messages: std.ArrayListUnmanaged(Message), + arena: std.heap.ArenaAllocator, + }, +}, state: State = .{}, +/// Amount of error or fatal error messages that have been sent to `output`. errors: u32 = 0, +/// Amount of warnings that have been sent to `output`. +warnings: u32 = 0, +// Total amount of diagnostics messages sent to `output`. +total: u32 = 0, macro_backtrace_limit: u32 = 6, +pub fn deinit(d: *Diagnostics) void { + switch (d.output) { + .to_file => {}, + .to_list => |*list| { + list.messages.deinit(list.arena.child_allocator); + list.arena.deinit(); + }, + } +} + /// Used by the __has_warning builtin macro. pub fn warningExists(name: []const u8) bool { if (std.mem.eql(u8, name, "pedantic")) return true; @@ -319,7 +269,7 @@ pub fn warningExists(name: []const u8) bool { return std.meta.stringToEnum(Option, name) != null; } -pub fn set(d: *Diagnostics, name: []const u8, to: Kind) !void { +pub fn set(d: *Diagnostics, name: []const u8, to: Message.Kind) !void { if (std.mem.eql(u8, name, "pedantic")) { d.state.extensions = to; return; @@ -331,385 +281,233 @@ pub fn set(d: *Diagnostics, name: []const u8, to: Kind) !void { inline for (comptime std.meta.declarations(Option)) |group| { if (std.mem.eql(u8, name, group.name)) { - d.setGroup(&@field(Option, group.name), to); + for (@field(Option, group.name)) |option| { + d.state.options.put(option, to); + } return; } } - try d.addExtra(.{}, .{ - .tag = .unknown_warning, - .extra = .{ .str = name }, - }, &.{}, true); + var buf: [256]u8 = undefined; + const slice = std.fmt.bufPrint(&buf, "unknown warning '{s}'", .{name}) catch &buf; + + try d.add(.{ + .text = slice, + .kind = .warning, + .opt = .@"unknown-warning-option", + .location = null, + }); } -fn setGroup(d: *Diagnostics, group: []const Option, to: Kind) void { - for (group) |option| { - d.state.options.put(option, to); +pub fn effectiveKind(d: *Diagnostics, message: anytype) Message.Kind { + var kind = message.kind; + + // -w disregards explicit kind set with -W + if (d.state.ignore_warnings and kind == .warning) return .off; + + // Get explicit kind set by -W= + var set_explicit = false; + if (message.opt) |option| { + if (d.state.options.get(option)) |explicit| { + kind = explicit; + set_explicit = true; + } } -} -pub fn init(gpa: Allocator) Diagnostics { - return .{ - .arena = std.heap.ArenaAllocator.init(gpa), - }; -} + // Use extension diagnostic behavior if not set explicitly. + if (message.extension and !set_explicit) { + kind = @enumFromInt(@max(@intFromEnum(kind), @intFromEnum(d.state.extensions))); + } -pub fn deinit(d: *Diagnostics) void { - d.list.deinit(d.arena.child_allocator); - d.arena.deinit(); + // Make diagnostic a warning if -Weverything is set. + if (kind == .off and d.state.enable_all_warnings) kind = .warning; + + // Upgrade warnigns to errors if -Werror is set + if (kind == .warning and d.state.error_warnings) kind = .@"error"; + + // Upgrade errors to fatal errors if -Wfatal-errors is set + if (kind == .@"error" and d.state.fatal_errors) kind = .@"fatal error"; + return kind; } -pub fn add(comp: *Compilation, msg: Message, expansion_locs: []const Source.Location) Compilation.Error!void { - return comp.diagnostics.addExtra(comp.langopts, msg, expansion_locs, true); +pub fn add(d: *Diagnostics, msg: Message) Compilation.Error!void { + var copy = msg; + copy.kind = d.effectiveKind(msg); + if (copy.kind == .off) return; + try d.addMessage(copy); } -pub fn addExtra( +pub fn addWithLocation( d: *Diagnostics, - langopts: LangOpts, + comp: *const Compilation, msg: Message, expansion_locs: []const Source.Location, note_msg_loc: bool, ) Compilation.Error!void { - const kind = d.tagKind(msg.tag, langopts); - if (kind == .off) return; var copy = msg; - copy.kind = kind; - - if (expansion_locs.len != 0) copy.loc = expansion_locs[expansion_locs.len - 1]; - try d.list.append(d.arena.child_allocator, copy); - if (kind == .@"error" or kind == .@"fatal error") d.errors += 1; + copy.kind = d.effectiveKind(msg); + if (copy.kind == .off) return; + if (copy.kind == .@"error" or copy.kind == .@"fatal error") d.errors += 1; + if (expansion_locs.len != 0) copy.location = expansion_locs[expansion_locs.len - 1].expand(comp); + try d.addMessage(copy); if (expansion_locs.len != 0) { // Add macro backtrace notes in reverse order omitting from the middle if needed. var i = expansion_locs.len - 1; const half = d.macro_backtrace_limit / 2; const limit = if (i < d.macro_backtrace_limit) 0 else i - half; - try d.list.ensureUnusedCapacity( - d.arena.child_allocator, - if (limit == 0) expansion_locs.len else d.macro_backtrace_limit + 1, - ); while (i > limit) { i -= 1; - d.list.appendAssumeCapacity(.{ - .tag = .expanded_from_here, + try d.addMessage(.{ .kind = .note, - .loc = expansion_locs[i], + .text = "expanded from here", + .location = expansion_locs[i].expand(comp), }); } if (limit != 0) { - d.list.appendAssumeCapacity(.{ - .tag = .skipping_macro_backtrace, + var buf: [256]u8 = undefined; + try d.addMessage(.{ .kind = .note, - .extra = .{ .unsigned = expansion_locs.len - d.macro_backtrace_limit }, + .text = std.fmt.bufPrint( + &buf, + "(skipping {d} expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)", + .{expansion_locs.len - d.macro_backtrace_limit}, + ) catch unreachable, + .location = null, }); i = half -| 1; while (i > 0) { i -= 1; - d.list.appendAssumeCapacity(.{ - .tag = .expanded_from_here, + try d.addMessage(.{ .kind = .note, - .loc = expansion_locs[i], + .text = "expanded from here", + .location = expansion_locs[i].expand(comp), }); } } - if (note_msg_loc) d.list.appendAssumeCapacity(.{ - .tag = .expanded_from_here, - .kind = .note, - .loc = msg.loc, - }); - } - if (kind == .@"fatal error") return error.FatalError; -} - -pub fn render(comp: *Compilation, config: std.io.tty.Config) void { - if (comp.diagnostics.list.items.len == 0) return; - var m = defaultMsgWriter(config); - defer m.deinit(); - renderMessages(comp, &m); -} -pub fn defaultMsgWriter(config: std.io.tty.Config) MsgWriter { - return MsgWriter.init(config); -} - -pub fn renderMessages(comp: *Compilation, m: anytype) void { - var errors: u32 = 0; - var warnings: u32 = 0; - for (comp.diagnostics.list.items) |msg| { - switch (msg.kind) { - .@"fatal error", .@"error" => errors += 1, - .warning => warnings += 1, - .note => {}, - .off => continue, // happens if an error is added before it is disabled + if (note_msg_loc) { + try d.add(.{ + .kind = .note, + .text = "expanded from here", + .location = msg.location.?, + }); } - renderMessage(comp, m, msg); } - const w_s: []const u8 = if (warnings == 1) "" else "s"; - const e_s: []const u8 = if (errors == 1) "" else "s"; - if (errors != 0 and warnings != 0) { - m.print("{d} warning{s} and {d} error{s} generated.\n", .{ warnings, w_s, errors, e_s }); - } else if (warnings != 0) { - m.print("{d} warning{s} generated.\n", .{ warnings, w_s }); - } else if (errors != 0) { - m.print("{d} error{s} generated.\n", .{ errors, e_s }); - } - - comp.diagnostics.list.items.len = 0; + if (copy.kind == .@"fatal error") return error.FatalError; } -pub fn renderMessage(comp: *Compilation, m: anytype, msg: Message) void { - var line: ?[]const u8 = null; - var end_with_splice = false; - const width = if (msg.loc.id != .unused) blk: { - var loc = msg.loc; - switch (msg.tag) { - .escape_sequence_overflow, - .invalid_universal_character, - => loc.byte_offset += @truncate(msg.extra.offset), - .non_standard_escape_char, - .unknown_escape_sequence, - => loc.byte_offset += msg.extra.invalid_escape.offset, - else => {}, - } - const source = comp.getSource(loc.id); - var line_col = source.lineCol(loc); - line = line_col.line; - end_with_splice = line_col.end_with_splice; - if (msg.tag == .backslash_newline_escape) { - line = line_col.line[0 .. line_col.col - 1]; - line_col.col += 1; - line_col.width += 1; - } - m.location(source.path, line_col.line_no, line_col.col); - break :blk line_col.width; - } else 0; - - m.start(msg.kind); - const prop = msg.tag.property(); - switch (prop.extra) { - .str => printRt(m, prop.msg, .{"{s}"}, .{msg.extra.str}), - .tok_id => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{ - msg.extra.tok_id.expected.symbol(), - msg.extra.tok_id.actual.symbol(), - }), - .tok_id_expected => printRt(m, prop.msg, .{"{s}"}, .{msg.extra.tok_id_expected.symbol()}), - .arguments => printRt(m, prop.msg, .{ "{d}", "{d}" }, .{ - msg.extra.arguments.expected, - msg.extra.arguments.actual, - }), - .codepoints => printRt(m, prop.msg, .{ "{X:0>4}", "{u}" }, .{ - msg.extra.codepoints.actual, - msg.extra.codepoints.resembles, - }), - .attr_arg_count => printRt(m, prop.msg, .{ "{s}", "{d}" }, .{ - @tagName(msg.extra.attr_arg_count.attribute), - msg.extra.attr_arg_count.expected, - }), - .attr_arg_type => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{ - msg.extra.attr_arg_type.expected.toString(), - msg.extra.attr_arg_type.actual.toString(), - }), - .actual_codepoint => printRt(m, prop.msg, .{"{X:0>4}"}, .{msg.extra.actual_codepoint}), - .ascii => printRt(m, prop.msg, .{"{c}"}, .{msg.extra.ascii}), - .unsigned => printRt(m, prop.msg, .{"{d}"}, .{msg.extra.unsigned}), - .pow_2_as_string => printRt(m, prop.msg, .{"{s}"}, .{switch (msg.extra.pow_2_as_string) { - 63 => "9223372036854775808", - 64 => "18446744073709551616", - 127 => "170141183460469231731687303715884105728", - 128 => "340282366920938463463374607431768211456", - else => unreachable, - }}), - .signed => printRt(m, prop.msg, .{"{d}"}, .{msg.extra.signed}), - .attr_enum => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{ - @tagName(msg.extra.attr_enum.tag), - Attribute.Formatting.choices(msg.extra.attr_enum.tag), - }), - .ignored_record_attr => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{ - @tagName(msg.extra.ignored_record_attr.tag), - @tagName(msg.extra.ignored_record_attr.tag_kind), - }), - .attribute_todo => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{ - @tagName(msg.extra.attribute_todo.tag), - @tagName(msg.extra.attribute_todo.kind), - }), - .builtin_with_header => printRt(m, prop.msg, .{ "{s}", "{s}" }, .{ - @tagName(msg.extra.builtin_with_header.header), - Builtin.nameFromTag(msg.extra.builtin_with_header.builtin).span(), - }), - .invalid_escape => { - if (std.ascii.isPrint(msg.extra.invalid_escape.char)) { - const str: [1]u8 = .{msg.extra.invalid_escape.char}; - printRt(m, prop.msg, .{"{s}"}, .{&str}); - } else { - var buf: [3]u8 = undefined; - const str = std.fmt.bufPrint(&buf, "x{x}", .{std.fmt.fmtSliceHexLower(&.{msg.extra.invalid_escape.char})}) catch unreachable; - printRt(m, prop.msg, .{"{s}"}, .{str}); - } +fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { + switch (msg.kind) { + .off => unreachable, + .@"error", .@"fatal error" => d.errors += 1, + .warning => d.warnings += 1, + .note => {}, + } + d.total += 1; + + switch (d.output) { + .to_file => |to_file| { + _ = to_file; + + // var line: ?[]const u8 = null; + // var end_with_splice = false; + // const width = if (msg.loc.id != .unused) blk: { + // var loc = msg.loc; + // switch (msg.tag) { + // .escape_sequence_overflow, + // .invalid_universal_character, + // => loc.byte_offset += @truncate(msg.extra.offset), + // .non_standard_escape_char, + // .unknown_escape_sequence, + // => loc.byte_offset += msg.extra.invalid_escape.offset, + // else => {}, + // } + // const source = comp.getSource(loc.id); + // var line_col = source.lineCol(loc); + // line = line_col.line; + // end_with_splice = line_col.end_with_splice; + // if (msg.tag == .backslash_newline_escape) { + // line = line_col.line[0 .. line_col.col - 1]; + // line_col.col += 1; + // line_col.width += 1; + // } + // m.location(source.path, line_col.line_no, line_col.col); + // break :blk line_col.width; + // } else 0; + + // if (prop.opt) |some| { + // if (msg.kind == .@"error" and prop.kind != .@"error") { + // m.print(" [-Werror,-W{s}]", .{@tagName(some)}); + // } else if (msg.kind != .note) { + // m.print(" [-W{s}]", .{@tagName(some)}); + // } + // } else if (prop.extension) { + // if (msg.kind == .@"error") { + // m.write(" [-Werror,-Wpedantic]"); + // } else { + // m.write(" [-Wpedantic]"); + // } + // } + + // fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void { + // const line = maybe_line orelse { + // m.write("\n"); + // m.setColor(.reset); + // return; + // }; + // const trailer = if (end_with_splice) "\\ " else ""; + // m.setColor(.reset); + // m.print("\n{s}{s}\n{s: >[3]}", .{ line, trailer, "", col }); + // m.setColor(.bold); + // m.setColor(.bright_green); + // m.write("^\n"); + // m.setColor(.reset); }, - .normalized => { - const f = struct { - pub fn f( - bytes: []const u8, - comptime _: []const u8, - _: std.fmt.FormatOptions, - writer: anytype, - ) !void { - var it: std.unicode.Utf8Iterator = .{ - .bytes = bytes, - .i = 0, - }; - while (it.nextCodepoint()) |codepoint| { - if (codepoint < 0x7F) { - try writer.writeByte(@intCast(codepoint)); - } else if (codepoint < 0xFFFF) { - try writer.writeAll("\\u"); - try std.fmt.formatInt(codepoint, 16, .upper, .{ - .fill = '0', - .width = 4, - }, writer); - } else { - try writer.writeAll("\\U"); - try std.fmt.formatInt(codepoint, 16, .upper, .{ - .fill = '0', - .width = 8, - }, writer); - } - } - } - }.f; - printRt(m, prop.msg, .{"{s}"}, .{ - std.fmt.Formatter(f){ .data = msg.extra.normalized }, + .to_list => |*to_list| { + const arena = to_list.arena.allocator(); + try to_list.messages.append(to_list.arena.child_allocator, .{ + .kind = msg.kind, + .text = try arena.dupe(u8, msg.text), + .location = if (msg.location) |some| .{ + .path = try arena.dupe(u8, some.path), + .line = try arena.dupe(u8, some.line), + .col = some.col, + .line_no = some.line_no, + .width = some.width, + .end_with_splice = some.end_with_splice, + } else null, }); }, - .none, .offset => m.write(prop.msg), } - - if (prop.opt) |some| { - if (msg.kind == .@"error" and prop.kind != .@"error") { - m.print(" [-Werror,-W{s}]", .{@tagName(some)}); - } else if (msg.kind != .note) { - m.print(" [-W{s}]", .{@tagName(some)}); - } - } else if (prop.extension) { - if (msg.kind == .@"error") { - m.write(" [-Werror,-Wpedantic]"); - } else { - m.write(" [-Wpedantic]"); - } - } - - m.end(line, width, end_with_splice); } -fn printRt(m: anytype, str: []const u8, comptime fmts: anytype, args: anytype) void { +pub fn formatArgs(w: anytype, fmt: []const u8, args: anytype) !void { var i: usize = 0; - inline for (fmts, args) |fmt, arg| { - const new = std.mem.indexOfPos(u8, str, i, fmt).?; - m.write(str[i..new]); - i = new + fmt.len; - m.print(fmt, .{arg}); + inline for (std.meta.fields(@TypeOf(args))) |arg_info| { + const arg = @field(args, arg_info.name); + i += switch (@TypeOf(arg)) { + []const u8 => try formatString(w, fmt[i..], arg), + else => switch (@typeInfo(@TypeOf(arg))) { + .int, .comptime_int => try Diagnostics.formatInt(w, fmt[i..], arg), + .pointer => try Diagnostics.formatString(w, fmt[i..], arg), + else => unreachable, + }, + }; } - m.write(str[i..]); + try w.writeAll(fmt[i..]); } -fn tagKind(d: *Diagnostics, tag: Tag, langopts: LangOpts) Kind { - const prop = tag.property(); - var kind = prop.kind; - - if (prop.suppress_version) |some| if (langopts.standard.atLeast(some)) return .off; - if (prop.suppress_unless_version) |some| if (!langopts.standard.atLeast(some)) return .off; - - // -w disregards explicit kind set with -W - if (d.state.ignore_warnings and prop.kind == .warning) return .off; - - // Get explicit kind set by -W= - var set_explicit = false; - if (prop.opt) |option| { - if (d.state.options.get(option)) |explicit| { - kind = explicit; - set_explicit = true; - } - } - - // Use extension diagnostic behavior if not set explicitly. - if (prop.extension and !set_explicit) { - kind = @enumFromInt(@max(@intFromEnum(kind), @intFromEnum(d.state.extensions))); - } - - // Make diagnostic a warning if -Weverything is set. - if (kind == .off and d.state.enable_all_warnings) kind = .warning; - - // Upgrade warnigns to errors if -Werror is set - if (kind == .warning and d.state.error_warnings) kind = .@"error"; - - // Upgrade errors to fatal errors if -Wfatal-errors is set - if (kind == .@"error" and d.state.fatal_errors) kind = .@"fatal error"; - return kind; +pub fn formatString(w: anytype, fmt: []const u8, str: []const u8) !usize { + const i = std.mem.indexOf(u8, fmt, "{s}").?; + try w.writeAll(fmt[0..i]); + try w.writeAll(str); + return i; } -const MsgWriter = struct { - w: std.io.BufferedWriter(4096, std.fs.File.Writer), - config: std.io.tty.Config, - - fn init(config: std.io.tty.Config) MsgWriter { - std.debug.lockStdErr(); - return .{ - .w = std.io.bufferedWriter(std.io.getStdErr().writer()), - .config = config, - }; - } - - pub fn deinit(m: *MsgWriter) void { - m.w.flush() catch {}; - std.debug.unlockStdErr(); - } - - pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void { - m.w.writer().print(fmt, args) catch {}; - } - - fn write(m: *MsgWriter, msg: []const u8) void { - m.w.writer().writeAll(msg) catch {}; - } - - fn setColor(m: *MsgWriter, color: std.io.tty.Color) void { - m.config.setColor(m.w.writer(), color) catch {}; - } - - fn location(m: *MsgWriter, path: []const u8, line: u32, col: u32) void { - m.setColor(.bold); - m.print("{s}:{d}:{d}: ", .{ path, line, col }); - } - - fn start(m: *MsgWriter, kind: Kind) void { - switch (kind) { - .@"fatal error", .@"error" => m.setColor(.bright_red), - .note => m.setColor(.bright_cyan), - .warning => m.setColor(.bright_magenta), - .off => unreachable, - } - m.write(switch (kind) { - .@"fatal error" => "fatal error: ", - .@"error" => "error: ", - .note => "note: ", - .warning => "warning: ", - .off => unreachable, - }); - m.setColor(.white); - } - - fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void { - const line = maybe_line orelse { - m.write("\n"); - m.setColor(.reset); - return; - }; - const trailer = if (end_with_splice) "\\ " else ""; - m.setColor(.reset); - m.print("\n{s}{s}\n{s: >[3]}", .{ line, trailer, "", col }); - m.setColor(.bold); - m.setColor(.bright_green); - m.write("^\n"); - m.setColor(.reset); - } -}; +pub fn formatInt(w: anytype, fmt: []const u8, int: anytype) !usize { + const i = std.mem.indexOf(u8, fmt, "{d}").?; + try w.writeAll(fmt[0..i]); + try std.fmt.formatInt(int, 10, .lower, .{}, w); + return i; +} diff --git a/src/aro/Diagnostics/messages.def b/src/aro/Diagnostics/messages.def deleted file mode 100644 index ddabf0e..0000000 --- a/src/aro/Diagnostics/messages.def +++ /dev/null @@ -1,2653 +0,0 @@ -const W = Properties.makeOpt; - -const pointer_sign_message = " converts between pointers to integer types with different sign"; - -# Maybe someday this will no longer be needed. -todo - .msg = "TODO: {s}" - .extra = .str - .kind = .@"error" - -error_directive - .msg = "{s}" - .extra = .str - .kind = .@"error" - -warning_directive - .msg = "{s}" - .opt = .@"#warnings" - .extra = .str - .kind = .warning - -elif_without_if - .msg = "#elif without #if" - .kind = .@"error" - -elif_after_else - .msg = "#elif after #else" - .kind = .@"error" - -elifdef_without_if - .msg = "#elifdef without #if" - .kind = .@"error" - -elifdef_after_else - .msg = "#elifdef after #else" - .kind = .@"error" - -elifndef_without_if - .msg = "#elifndef without #if" - .kind = .@"error" - -elifndef_after_else - .msg = "#elifndef after #else" - .kind = .@"error" - -else_without_if - .msg = "#else without #if" - .kind = .@"error" - -else_after_else - .msg = "#else after #else" - .kind = .@"error" - -endif_without_if - .msg = "#endif without #if" - .kind = .@"error" - -unknown_pragma - .msg = "unknown pragma ignored" - .opt = .@"unknown-pragmas" - .kind = .off - -line_simple_digit - .msg = "#line directive requires a simple digit sequence" - .kind = .@"error" - -line_invalid_filename - .msg = "invalid filename for #line directive" - .kind = .@"error" - -unterminated_conditional_directive - .msg = "unterminated conditional directive" - .kind = .@"error" - -invalid_preprocessing_directive - .msg = "invalid preprocessing directive" - .kind = .@"error" - -macro_name_missing - .msg = "macro name missing" - .kind = .@"error" - -extra_tokens_directive_end - .msg = "extra tokens at end of macro directive" - .kind = .@"error" - -expected_value_in_expr - .msg = "expected value in expression" - .kind = .@"error" - -closing_paren - .msg = "expected closing ')'" - .kind = .@"error" - -to_match_paren - .msg = "to match this '('" - .kind = .note - -to_match_brace - .msg = "to match this '{'" - .kind = .note - -to_match_bracket - .msg = "to match this '['" - .kind = .note - -header_str_closing - .msg = "expected closing '>'" - .kind = .@"error" - -header_str_match - .msg = "to match this '<'" - .kind = .note - -string_literal_in_pp_expr - .msg = "string literal in preprocessor expression" - .kind = .@"error" - -float_literal_in_pp_expr - .msg = "floating point literal in preprocessor expression" - .kind = .@"error" - -defined_as_macro_name - .msg = "'defined' cannot be used as a macro name" - .kind = .@"error" - -macro_name_must_be_identifier - .msg = "macro name must be an identifier" - .kind = .@"error" - -whitespace_after_macro_name - .msg = "ISO C99 requires whitespace after the macro name" - .opt = .@"c99-extensions" - .kind = .warning - .extension = true - -hash_hash_at_start - .msg = "'##' cannot appear at the start of a macro expansion" - .kind = .@"error" - -hash_hash_at_end - .msg = "'##' cannot appear at the end of a macro expansion" - .kind = .@"error" - -pasting_formed_invalid - .msg = "pasting formed '{s}', an invalid preprocessing token" - .extra = .str - .kind = .@"error" - -missing_paren_param_list - .msg = "missing ')' in macro parameter list" - .kind = .@"error" - -unterminated_macro_param_list - .msg = "unterminated macro param list" - .kind = .@"error" - -invalid_token_param_list - .msg = "invalid token in macro parameter list" - .kind = .@"error" - -expected_comma_param_list - .msg = "expected comma in macro parameter list" - .kind = .@"error" - -hash_not_followed_param - .msg = "'#' is not followed by a macro parameter" - .kind = .@"error" - -expected_filename - .msg = "expected \"FILENAME\" or " - .kind = .@"error" - -empty_filename - .msg = "empty filename" - .kind = .@"error" - -expected_invalid - .msg = "expected '{s}', found invalid bytes" - .extra = .tok_id_expected - .kind = .@"error" - -expected_eof - .msg = "expected '{s}' before end of file" - .extra = .tok_id_expected - .kind = .@"error" - -expected_token - .msg = "expected '{s}', found '{s}'" - .extra = .tok_id - .kind = .@"error" - -expected_expr - .msg = "expected expression" - .kind = .@"error" - -expected_integer_constant_expr - .msg = "expression is not an integer constant expression" - .kind = .@"error" - -missing_type_specifier - .msg = "type specifier missing, defaults to 'int'" - .opt = .@"implicit-int" - .kind = .warning - -missing_type_specifier_c23 - .msg = "a type specifier is required for all declarations" - .kind = .@"error" - -param_not_declared - .msg = "parameter '{s}' was not declared, defaults to 'int'" - .opt = .@"implicit-int" - .extra = .str - .kind = .warning - -multiple_storage_class - .msg = "cannot combine with previous '{s}' declaration specifier" - .extra = .str - .kind = .@"error" - -static_assert_failure - .msg = "static assertion failed" - .kind = .@"error" - -static_assert_failure_message - .msg = "static assertion failed {s}" - .extra = .str - .kind = .@"error" - -expected_type - .msg = "expected a type" - .kind = .@"error" - -cannot_combine_spec - .msg = "cannot combine with previous '{s}' specifier" - .extra = .str - .kind = .@"error" - -cannot_combine_with_typedef - .msg = "'{s} type-name' is invalid" - .extra = .str - .kind = .@"error" - -cannot_combine_with_typeof - .msg = "'{s} typeof' is invalid" - .extra = .str - .kind = .@"error" - -duplicate_decl_spec - .msg = "duplicate '{s}' declaration specifier" - .extra = .str - .opt = .@"duplicate-decl-specifier" - .kind = .warning - -restrict_non_pointer - .msg = "restrict requires a pointer or reference ('{s}' is invalid)" - .extra = .str - .kind = .@"error" - -expected_external_decl - .msg = "expected external declaration" - .kind = .@"error" - -expected_ident_or_l_paren - .msg = "expected identifier or '('" - .kind = .@"error" - -missing_declaration - .msg = "declaration does not declare anything" - .opt = .@"missing-declaration" - .kind = .warning - -func_not_in_root - .msg = "function definition is not allowed here" - .kind = .@"error" - -illegal_initializer - .msg = "illegal initializer (only variables can be initialized)" - .kind = .@"error" - -extern_initializer - .msg = "extern variable has initializer" - .opt = .@"extern-initializer" - .kind = .warning - -param_before_var_args - .msg = "ISO C requires a named parameter before '...'" - .kind = .@"error" - .suppress_version = .c23 - -void_only_param - .msg = "'void' must be the only parameter if specified" - .kind = .@"error" - -void_param_qualified - .msg = "'void' parameter cannot be qualified" - .kind = .@"error" - -void_must_be_first_param - .msg = "'void' must be the first parameter if specified" - .kind = .@"error" - -invalid_storage_on_param - .msg = "invalid storage class on function parameter" - .kind = .@"error" - -threadlocal_non_var - .msg = "_Thread_local only allowed on variables" - .kind = .@"error" - -func_spec_non_func - .msg = "'{s}' can only appear on functions" - .extra = .str - .kind = .@"error" - -illegal_storage_on_func - .msg = "illegal storage class on function" - .kind = .@"error" - -illegal_storage_on_global - .msg = "illegal storage class on global variable" - .kind = .@"error" - -expected_stmt - .msg = "expected statement" - .kind = .@"error" - -func_cannot_return_func - .msg = "function cannot return a function" - .kind = .@"error" - -func_cannot_return_array - .msg = "function cannot return an array" - .kind = .@"error" - -undeclared_identifier - .msg = "use of undeclared identifier '{s}'" - .extra = .str - .kind = .@"error" - -not_callable - .msg = "cannot call non function type '{s}'" - .extra = .str - .kind = .@"error" - -unsupported_str_cat - .msg = "unsupported string literal concatenation" - .kind = .@"error" - -static_func_not_global - .msg = "static functions must be global" - .kind = .@"error" - -implicit_func_decl - .msg = "call to undeclared function '{s}'; ISO C99 and later do not support implicit function declarations" - .extra = .str - .opt = .@"implicit-function-declaration" - .kind = .@"error" - -unknown_builtin - .msg = "use of unknown builtin '{s}'" - .extra = .str - .opt = .@"implicit-function-declaration" - .kind = .@"error" - -implicit_builtin - .msg = "implicitly declaring library function '{s}'" - .extra = .str - .opt = .@"implicit-function-declaration" - .kind = .@"error" - -implicit_builtin_header_note - .msg = "include the header <{s}.h> or explicitly provide a declaration for '{s}'" - .extra = .builtin_with_header - .opt = .@"implicit-function-declaration" - .kind = .note - -expected_param_decl - .msg = "expected parameter declaration" - .kind = .@"error" - -invalid_old_style_params - .msg = "identifier parameter lists are only allowed in function definitions" - .kind = .@"error" - -expected_fn_body - .msg = "expected function body after function declaration" - .kind = .@"error" - -invalid_void_param - .msg = "parameter cannot have void type" - .kind = .@"error" - -unused_value - .msg = "expression result unused" - .opt = .@"unused-value" - .kind = .warning - -continue_not_in_loop - .msg = "'continue' statement not in a loop" - .kind = .@"error" - -break_not_in_loop_or_switch - .msg = "'break' statement not in a loop or a switch" - .kind = .@"error" - -unreachable_code - .msg = "unreachable code" - .opt = .@"unreachable-code" - .kind = .warning - -duplicate_label - .msg = "duplicate label '{s}'" - .extra = .str - .kind = .@"error" - -previous_label - .msg = "previous definition of label '{s}' was here" - .extra = .str - .kind = .note - -undeclared_label - .msg = "use of undeclared label '{s}'" - .extra = .str - .kind = .@"error" - -case_not_in_switch - .msg = "'{s}' statement not in a switch statement" - .extra = .str - .kind = .@"error" - -duplicate_switch_case - .msg = "duplicate case value '{s}'" - .extra = .str - .kind = .@"error" - -multiple_default - .msg = "multiple default cases in the same switch" - .kind = .@"error" - -previous_case - .msg = "previous case defined here" - .kind = .note - -const expected_arguments = "expected {d} argument(s) got {d}"; - -expected_arguments - .msg = expected_arguments - .extra = .arguments - .kind = .@"error" - -callee_with_static_array - .msg = "callee declares array parameter as static here" - .kind = .note - -array_argument_too_small - .msg = "array argument is too small; contains {d} elements, callee requires at least {d}" - .extra = .arguments - .kind = .warning - .opt = .@"array-bounds" - -non_null_argument - .msg = "null passed to a callee that requires a non-null argument" - .kind = .warning - .opt = .nonnull - -expected_arguments_old - .msg = expected_arguments - .extra = .arguments - .kind = .warning - -expected_at_least_arguments - .msg = "expected at least {d} argument(s) got {d}" - .extra = .arguments - .kind = .warning - -invalid_static_star - .msg = "'static' may not be used with an unspecified variable length array size" - .kind = .@"error" - -static_non_param - .msg = "'static' used outside of function parameters" - .kind = .@"error" - -array_qualifiers - .msg = "type qualifier in non parameter array type" - .kind = .@"error" - -star_non_param - .msg = "star modifier used outside of function parameters" - .kind = .@"error" - -variable_len_array_file_scope - .msg = "variable length arrays not allowed at file scope" - .kind = .@"error" - -useless_static - .msg = "'static' useless without a constant size" - .kind = .warning -# .w_extra = true TODO - -negative_array_size - .msg = "array size must be 0 or greater" - .kind = .@"error" - -array_incomplete_elem - .msg = "array has incomplete element type '{s}'" - .extra = .str - .kind = .@"error" - -array_func_elem - .msg = "arrays cannot have functions as their element type" - .kind = .@"error" - -static_non_outermost_array - .msg = "'static' used in non-outermost array type" - .kind = .@"error" - -qualifier_non_outermost_array - .msg = "type qualifier used in non-outermost array type" - .kind = .@"error" - -unterminated_macro_arg_list - .msg = "unterminated function macro argument list" - .kind = .@"error" - -unknown_warning - .msg = "unknown warning '{s}'" - .extra = .str - .opt = .@"unknown-warning-option" - .kind = .warning - -array_overflow - .msg = "{s}" - .extra = .str - .opt = .@"array-bounds" - .kind = .warning - -overflow - .msg = "overflow in expression; result is '{s}'" - .extra = .str - .opt = .@"integer-overflow" - .kind = .warning - -int_literal_too_big - .msg = "integer literal is too large to be represented in any integer type" - .kind = .@"error" - -indirection_ptr - .msg = "indirection requires pointer operand" - .kind = .@"error" - -addr_of_rvalue - .msg = "cannot take the address of an rvalue" - .kind = .@"error" - -addr_of_bitfield - .msg = "address of bit-field requested" - .kind = .@"error" - -not_assignable - .msg = "expression is not assignable" - .kind = .@"error" - -ident_or_l_brace - .msg = "expected identifier or '{'" - .kind = .@"error" - -empty_enum - .msg = "empty enum is invalid" - .kind = .@"error" - -redefinition - .msg = "redefinition of '{s}'" - .extra = .str - .kind = .@"error" - -previous_definition - .msg = "previous definition is here" - .kind = .note - -expected_identifier - .msg = "expected identifier" - .kind = .@"error" - -expected_str_literal - .msg = "expected string literal for diagnostic message in static_assert" - .kind = .@"error" - -expected_str_literal_in - .msg = "expected string literal in '{s}'" - .extra = .str - .kind = .@"error" - -parameter_missing - .msg = "parameter named '{s}' is missing" - .extra = .str - .kind = .@"error" - -empty_record - .msg = "empty {s} is a GNU extension" - .extra = .str - .opt = .@"gnu-empty-struct" - .kind = .off - .extension = true - -empty_record_size - .msg = "empty {s} has size 0 in C, size 1 in C++" - .extra = .str - .opt = .@"c++-compat" - .kind = .off - -wrong_tag - .msg = "use of '{s}' with tag type that does not match previous definition" - .extra = .str - .kind = .@"error" - -expected_parens_around_typename - .msg = "expected parentheses around type name" - .kind = .@"error" - -alignof_expr - .msg = "'_Alignof' applied to an expression is a GNU extension" - .opt = .@"gnu-alignof-expression" - .kind = .warning - .extension = true - -invalid_alignof - .msg = "invalid application of 'alignof' to an incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -invalid_sizeof - .msg = "invalid application of 'sizeof' to an incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -macro_redefined - .msg = "'{s}' macro redefined" - .extra = .str - .opt = .@"macro-redefined" - .kind = .warning - -generic_qual_type - .msg = "generic association with qualifiers cannot be matched with" - .opt = .@"generic-qual-type" - .kind = .warning - -generic_array_type - .msg = "generic association array type cannot be matched with" - .opt = .@"generic-qual-type" - .kind = .warning - -generic_func_type - .msg = "generic association function type cannot be matched with" - .opt = .@"generic-qual-type" - .kind = .warning - -generic_duplicate - .msg = "type '{s}' in generic association compatible with previously specified type" - .extra = .str - .kind = .@"error" - -generic_duplicate_here - .msg = "compatible type '{s}' specified here" - .extra = .str - .kind = .note - -generic_duplicate_default - .msg = "duplicate default generic association" - .kind = .@"error" - -generic_no_match - .msg = "controlling expression type '{s}' not compatible with any generic association type" - .extra = .str - .kind = .@"error" - -escape_sequence_overflow - .msg = "escape sequence out of range" - .kind = .@"error" - -invalid_universal_character - .msg = "invalid universal character" - .kind = .@"error" - -incomplete_universal_character - .msg = "incomplete universal character name" - .kind = .@"error" - -multichar_literal_warning - .msg = "multi-character character constant" - .opt = .multichar - .kind = .warning - -invalid_multichar_literal - .msg = "{s} character literals may not contain multiple characters" - .kind = .@"error" - .extra = .str - -wide_multichar_literal - .msg = "extraneous characters in character constant ignored" - .kind = .warning - -char_lit_too_wide - .msg = "character constant too long for its type" - .kind = .warning - -char_too_large - .msg = "character too large for enclosing character literal type" - .kind = .@"error" - -must_use_struct - .msg = "must use 'struct' tag to refer to type '{s}'" - .extra = .str - .kind = .@"error" - -must_use_union - .msg = "must use 'union' tag to refer to type '{s}'" - .extra = .str - .kind = .@"error" - -must_use_enum - .msg = "must use 'enum' tag to refer to type '{s}'" - .extra = .str - .kind = .@"error" - -redefinition_different_sym - .msg = "redefinition of '{s}' as different kind of symbol" - .extra = .str - .kind = .@"error" - -redefinition_incompatible - .msg = "redefinition of '{s}' with a different type" - .extra = .str - .kind = .@"error" - -redefinition_of_parameter - .msg = "redefinition of parameter '{s}'" - .extra = .str - .kind = .@"error" - -invalid_bin_types - .msg = "invalid operands to binary expression ({s})" - .extra = .str - .kind = .@"error" - -comparison_ptr_int - .msg = "comparison between pointer and integer ({s})" - .extra = .str - .opt = .@"pointer-integer-compare" - .kind = .warning - -comparison_distinct_ptr - .msg = "comparison of distinct pointer types ({s})" - .extra = .str - .opt = .@"compare-distinct-pointer-types" - .kind = .warning - -incompatible_pointers - .msg = "incompatible pointer types ({s})" - .extra = .str - .kind = .@"error" - -invalid_argument_un - .msg = "invalid argument type '{s}' to unary expression" - .extra = .str - .kind = .@"error" - -incompatible_assign - .msg = "assignment to {s}" - .extra = .str - .kind = .@"error" - -implicit_ptr_to_int - .msg = "implicit pointer to integer conversion from {s}" - .extra = .str - .opt = .@"int-conversion" - .kind = .warning - -invalid_cast_to_float - .msg = "pointer cannot be cast to type '{s}'" - .extra = .str - .kind = .@"error" - -invalid_cast_to_pointer - .msg = "operand of type '{s}' cannot be cast to a pointer type" - .extra = .str - .kind = .@"error" - -invalid_cast_type - .msg = "cannot cast to non arithmetic or pointer type '{s}'" - .extra = .str - .kind = .@"error" - -invalid_cast_operand_type - .msg = "operand of type '{s}' where arithmetic or pointer type is required" - .extra = .str - .kind = .@"error" - -qual_cast - .msg = "cast to type '{s}' will not preserve qualifiers" - .extra = .str - .opt = .@"cast-qualifiers" - .kind = .warning - -invalid_index - .msg = "array subscript is not an integer" - .kind = .@"error" - -invalid_subscript - .msg = "subscripted value is not an array or pointer" - .kind = .@"error" - -array_after - .msg = "array index {s} is past the end of the array" - .extra = .str - .opt = .@"array-bounds" - .kind = .warning - -array_before - .msg = "array index {s} is before the beginning of the array" - .extra = .str - .opt = .@"array-bounds" - .kind = .warning - -statement_int - .msg = "statement requires expression with integer type ('{s}' invalid)" - .extra = .str - .kind = .@"error" - -statement_scalar - .msg = "statement requires expression with scalar type ('{s}' invalid)" - .extra = .str - .kind = .@"error" - -func_should_return - .msg = "non-void function '{s}' should return a value" - .extra = .str - .opt = .@"return-type" - .kind = .@"error" - -incompatible_return - .msg = "returning {s}" - .extra = .str - .kind = .@"error" - -incompatible_return_sign - .msg = "returning {s}" ++ pointer_sign_message - .extra = .str - .kind = .warning - .opt = .@"pointer-sign" - -implicit_int_to_ptr - .msg = "implicit integer to pointer conversion from {s}" - .extra = .str - .opt = .@"int-conversion" - .kind = .warning - -func_does_not_return - .msg = "non-void function '{s}' does not return a value" - .extra = .str - .opt = .@"return-type" - .kind = .warning - -void_func_returns_value - .msg = "void function '{s}' should not return a value" - .extra = .str - .opt = .@"return-type" - .kind = .@"error" - -incompatible_arg - .msg = "passing {s}" - .extra = .str - .kind = .@"error" - -incompatible_ptr_arg - .msg = "passing {s}" - .extra = .str - .kind = .warning - .opt = .@"incompatible-pointer-types" - -incompatible_ptr_arg_sign - .msg = "passing {s}" ++ pointer_sign_message - .extra = .str - .kind = .warning - .opt = .@"pointer-sign" - -parameter_here - .msg = "passing argument to parameter here" - .kind = .note - -atomic_array - .msg = "_Atomic cannot be applied to array type '{s}'" - .extra = .str - .kind = .@"error" - -atomic_func - .msg = "_Atomic cannot be applied to function type '{s}'" - .extra = .str - .kind = .@"error" - -atomic_incomplete - .msg = "_Atomic cannot be applied to incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -atomic_atomic - .msg = "_Atomic cannot be applied to atomic type '{s}'" - .extra = .str - .kind = .@"error" - -atomic_complex - .msg = "_Atomic cannot be applied to complex type '{s}'" - .extra = .str - .kind = .@"error" - -atomic_qualified - .msg = "_Atomic cannot be applied to qualified type '{s}'" - .extra = .str - .kind = .@"error" - -atomic_auto - .msg = "_Atomic cannot be applied to type 'auto' in C23" - .kind = .@"error" - -atomic_access - .msg = "accessing a member of an atomic structure or union is undefined behavior" - .opt = .@"atomic-access" - .kind = .@"error" - -addr_of_register - .msg = "address of register variable requested" - .kind = .@"error" - -variable_incomplete_ty - .msg = "variable has incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -parameter_incomplete_ty - .msg = "parameter has incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -tentative_array - .msg = "tentative array definition assumed to have one element" - .kind = .warning - -deref_incomplete_ty_ptr - .msg = "dereferencing pointer to incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -alignas_on_func - .msg = "'_Alignas' attribute only applies to variables and fields" - .kind = .@"error" - -alignas_on_param - .msg = "'_Alignas' attribute cannot be applied to a function parameter" - .kind = .@"error" - -minimum_alignment - .msg = "requested alignment is less than minimum alignment of {d}" - .extra = .unsigned - .kind = .@"error" - -maximum_alignment - .msg = "requested alignment of {s} is too large" - .extra = .str - .kind = .@"error" - -negative_alignment - .msg = "requested negative alignment of {s} is invalid" - .extra = .str - .kind = .@"error" - -align_ignored - .msg = "'_Alignas' attribute is ignored here" - .kind = .warning - -zero_align_ignored - .msg = "requested alignment of zero is ignored" - .kind = .warning - -non_pow2_align - .msg = "requested alignment is not a power of 2" - .kind = .@"error" - -pointer_mismatch - .msg = "pointer type mismatch ({s})" - .extra = .str - .opt = .@"pointer-type-mismatch" - .kind = .warning - -static_assert_not_constant - .msg = "static assertion expression is not an integral constant expression" - .kind = .@"error" - -static_assert_missing_message - .msg = "'_Static_assert' with no message is a C23 extension" - .opt = .@"c23-extensions" - .kind = .warning - .suppress_version = .c23 - .extension = true - -pre_c23_compat - .msg = "{s} is incompatible with C standards before C23" - .extra = .str - .kind = .off - .suppress_unless_version = .c23 - .opt = .@"pre-c23-compat" - -unbound_vla - .msg = "variable length array must be bound in function definition" - .kind = .@"error" - -array_too_large - .msg = "array is too large" - .kind = .@"error" - -record_too_large - .msg = "type '{s}' is too large" - .kind = .@"error" - .extra = .str - -incompatible_ptr_init - .msg = "incompatible pointer types initializing {s}" - .extra = .str - .opt = .@"incompatible-pointer-types" - .kind = .warning - -incompatible_ptr_init_sign - .msg = "incompatible pointer types initializing {s}" ++ pointer_sign_message - .extra = .str - .opt = .@"pointer-sign" - .kind = .warning - -incompatible_ptr_assign - .msg = "incompatible pointer types assigning to {s}" - .extra = .str - .opt = .@"incompatible-pointer-types" - .kind = .warning - -incompatible_ptr_assign_sign - .msg = "incompatible pointer types assigning to {s} " ++ pointer_sign_message - .extra = .str - .opt = .@"pointer-sign" - .kind = .warning - -vla_init - .msg = "variable-sized object may not be initialized" - .kind = .@"error" - -func_init - .msg = "illegal initializer type" - .kind = .@"error" - -incompatible_init - .msg = "initializing {s}" - .extra = .str - .kind = .@"error" - -empty_scalar_init - .msg = "scalar initializer cannot be empty" - .kind = .@"error" - -excess_scalar_init - .msg = "excess elements in scalar initializer" - .opt = .@"excess-initializers" - .kind = .warning - -excess_str_init - .msg = "excess elements in string initializer" - .opt = .@"excess-initializers" - .kind = .warning - -excess_struct_init - .msg = "excess elements in struct initializer" - .opt = .@"excess-initializers" - .kind = .warning - -excess_array_init - .msg = "excess elements in array initializer" - .opt = .@"excess-initializers" - .kind = .warning - -str_init_too_long - .msg = "initializer-string for char array is too long" - .opt = .@"excess-initializers" - .kind = .warning - -arr_init_too_long - .msg = "cannot initialize type ({s})" - .extra = .str - .kind = .@"error" - -division_by_zero - .msg = "{s} by zero is undefined" - .extra = .str - .opt = .@"division-by-zero" - .kind = .warning - -division_by_zero_macro - .msg = "{s} by zero in preprocessor expression" - .extra = .str - .kind = .@"error" - -builtin_choose_cond - .msg = "'__builtin_choose_expr' requires a constant expression" - .kind = .@"error" - -alignas_unavailable - .msg = "'_Alignas' attribute requires integer constant expression" - .kind = .@"error" - -case_val_unavailable - .msg = "case value must be an integer constant expression" - .kind = .@"error" - -enum_val_unavailable - .msg = "enum value must be an integer constant expression" - .kind = .@"error" - -incompatible_array_init - .msg = "cannot initialize array of type {s}" - .extra = .str - .kind = .@"error" - -array_init_str - .msg = "array initializer must be an initializer list or wide string literal" - .kind = .@"error" - -initializer_overrides - .msg = "initializer overrides previous initialization" - .opt = .@"initializer-overrides" - .kind = .warning - -previous_initializer - .msg = "previous initialization" - .kind = .note - -invalid_array_designator - .msg = "array designator used for non-array type '{s}'" - .extra = .str - .kind = .@"error" - -negative_array_designator - .msg = "array designator value {s} is negative" - .extra = .str - .kind = .@"error" - -oob_array_designator - .msg = "array designator index {s} exceeds array bounds" - .extra = .str - .kind = .@"error" - -invalid_field_designator - .msg = "field designator used for non-record type '{s}'" - .extra = .str - .kind = .@"error" - -no_such_field_designator - .msg = "record type has no field named '{s}'" - .extra = .str - .kind = .@"error" - -empty_aggregate_init_braces - .msg = "initializer for aggregate with no elements requires explicit braces" - .kind = .@"error" - -ptr_init_discards_quals - .msg = "initializing {s} discards qualifiers" - .extra = .str - .opt = .@"incompatible-pointer-types-discards-qualifiers" - .kind = .warning - -ptr_assign_discards_quals - .msg = "assigning to {s} discards qualifiers" - .extra = .str - .opt = .@"incompatible-pointer-types-discards-qualifiers" - .kind = .warning - -ptr_ret_discards_quals - .msg = "returning {s} discards qualifiers" - .extra = .str - .opt = .@"incompatible-pointer-types-discards-qualifiers" - .kind = .warning - -ptr_arg_discards_quals - .msg = "passing {s} discards qualifiers" - .extra = .str - .opt = .@"incompatible-pointer-types-discards-qualifiers" - .kind = .warning - -unknown_attribute - .msg = "unknown attribute '{s}' ignored" - .extra = .str - .opt = .@"unknown-attributes" - .kind = .warning - -ignored_attribute - .msg = "{s}" - .extra = .str - .opt = .@"ignored-attributes" - .kind = .warning - -invalid_fallthrough - .msg = "fallthrough annotation does not directly precede switch label" - .kind = .@"error" - -cannot_apply_attribute_to_statement - .msg = "'{s}' attribute cannot be applied to a statement" - .extra = .str - .kind = .@"error" - -builtin_macro_redefined - .msg = "redefining builtin macro" - .opt = .@"builtin-macro-redefined" - .kind = .warning - -feature_check_requires_identifier - .msg = "builtin feature check macro requires a parenthesized identifier" - .kind = .@"error" - -missing_tok_builtin - .msg = "missing '{s}', after builtin feature-check macro" - .extra = .tok_id_expected - .kind = .@"error" - -gnu_label_as_value - .msg = "use of GNU address-of-label extension" - .opt = .@"gnu-label-as-value" - .kind = .off - .extension = true - -expected_record_ty - .msg = "member reference base type '{s}' is not a structure or union" - .extra = .str - .kind = .@"error" - -member_expr_not_ptr - .msg = "member reference type '{s}' is not a pointer; did you mean to use '.'?" - .extra = .str - .kind = .@"error" - -member_expr_ptr - .msg = "member reference type '{s}' is a pointer; did you mean to use '->'?" - .extra = .str - .kind = .@"error" - -member_expr_atomic - .msg = "accessing a member of atomic type '{s}' is undefined behavior" - .extra = .str - .kind = .@"error" - -no_such_member - .msg = "no member named {s}" - .extra = .str - .kind = .@"error" - -malformed_warning_check - .msg = "{s} expected option name (e.g. \"-Wundef\")" - .extra = .str - .opt = .@"malformed-warning-check" - .kind = .warning - -invalid_computed_goto - .msg = "computed goto in function with no address-of-label expressions" - .kind = .@"error" - -pragma_warning_message - .msg = "{s}" - .extra = .str - .opt = .@"#pragma-messages" - .kind = .warning - -pragma_error_message - .msg = "{s}" - .extra = .str - .kind = .@"error" - -pragma_message - .msg = "#pragma message: {s}" - .extra = .str - .kind = .note - -pragma_requires_string_literal - .msg = "pragma {s} requires string literal" - .extra = .str - .kind = .@"error" - -poisoned_identifier - .msg = "attempt to use a poisoned identifier" - .kind = .@"error" - -pragma_poison_identifier - .msg = "can only poison identifier tokens" - .kind = .@"error" - -pragma_poison_macro - .msg = "poisoning existing macro" - .kind = .warning - -newline_eof - .msg = "no newline at end of file" - .opt = .@"newline-eof" - .kind = .off - -empty_translation_unit - .msg = "ISO C requires a translation unit to contain at least one declaration" - .opt = .@"empty-translation-unit" - .kind = .off - -omitting_parameter_name - .msg = "omitting the parameter name in a function definition is a C23 extension" - .opt = .@"c23-extensions" - .kind = .warning - .suppress_version = .c23 - .extension = true - -non_int_bitfield - .msg = "bit-field has non-integer type '{s}'" - .extra = .str - .kind = .@"error" - -negative_bitwidth - .msg = "bit-field has negative width ({s})" - .extra = .str - .kind = .@"error" - -zero_width_named_field - .msg = "named bit-field has zero width" - .kind = .@"error" - -bitfield_too_big - .msg = "width of bit-field exceeds width of its type" - .kind = .@"error" - -invalid_utf8 - .msg = "source file is not valid UTF-8" - .kind = .@"error" - -implicitly_unsigned_literal - .msg = "integer literal is too large to be represented in a signed integer type, interpreting as unsigned" - .opt = .@"implicitly-unsigned-literal" - .kind = .warning - -invalid_preproc_operator - .msg = "token is not a valid binary operator in a preprocessor subexpression" - .kind = .@"error" - -invalid_preproc_expr_start - .msg = "invalid token at start of a preprocessor expression" - .kind = .@"error" - -c99_compat - .msg = "using this character in an identifier is incompatible with C99" - .opt = .@"c99-compat" - .kind = .off - -unexpected_character - .msg = "unexpected character 4}>" - .extra = .actual_codepoint - .kind = .@"error" - -invalid_identifier_start_char - .msg = "character 4}> not allowed at the start of an identifier" - .extra = .actual_codepoint - .kind = .@"error" - -unicode_zero_width - .msg = "identifier contains Unicode character 4}> that is invisible in some environments" - .opt = .@"unicode-homoglyph" - .extra = .actual_codepoint - .kind = .warning - -unicode_homoglyph - .msg = "treating Unicode character 4}> as identifier character rather than as '{u}' symbol" - .extra = .codepoints - .opt = .@"unicode-homoglyph" - .kind = .warning - -meaningless_asm_qual - .msg = "meaningless '{s}' on assembly outside function" - .extra = .str - .kind = .@"error" - -duplicate_asm_qual - .msg = "duplicate asm qualifier '{s}'" - .extra = .str - .kind = .@"error" - -invalid_asm_str - .msg = "cannot use {s} string literal in assembly" - .extra = .str - .kind = .@"error" - -dollar_in_identifier_extension - .msg = "'$' in identifier" - .opt = .@"dollar-in-identifier-extension" - .kind = .off - .extension = true - -dollars_in_identifiers - .msg = "illegal character '$' in identifier" - .kind = .@"error" - -expanded_from_here - .msg = "expanded from here" - .kind = .note - -skipping_macro_backtrace - .msg = "(skipping {d} expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)" - .extra = .unsigned - .kind = .note - -pragma_operator_string_literal - .msg = "_Pragma requires exactly one string literal token" - .kind = .@"error" - -unknown_gcc_pragma - .msg = "pragma GCC expected 'error', 'warning', 'diagnostic', 'poison'" - .opt = .@"unknown-pragmas" - .kind = .off - -unknown_gcc_pragma_directive - .msg = "pragma GCC diagnostic expected 'error', 'warning', 'ignored', 'fatal', 'push', or 'pop'" - .opt = .@"unknown-pragmas" - .kind = .warning - -predefined_top_level - .msg = "predefined identifier is only valid inside function" - .opt = .@"predefined-identifier-outside-function" - .kind = .warning - -incompatible_va_arg - .msg = "first argument to va_arg, is of type '{s}' and not 'va_list'" - .extra = .str - .kind = .@"error" - -too_many_scalar_init_braces - .msg = "too many braces around scalar initializer" - .opt = .@"many-braces-around-scalar-init" - .kind = .warning - -uninitialized_in_own_init - .msg = "variable '{s}' is uninitialized when used within its own initialization" - .extra = .str - .opt = .uninitialized - .kind = .off - -gnu_statement_expression - .msg = "use of GNU statement expression extension" - .opt = .@"gnu-statement-expression" - .kind = .off - .extension = true - -stmt_expr_not_allowed_file_scope - .msg = "statement expression not allowed at file scope" - .kind = .@"error" - -gnu_imaginary_constant - .msg = "imaginary constants are a GNU extension" - .opt = .@"gnu-imaginary-constant" - .kind = .off - .extension = true - -plain_complex - .msg = "plain '_Complex' requires a type specifier; assuming '_Complex double'" - .kind = .warning - -complex_int - .msg = "complex integer types are a GNU extension" - .opt = .@"gnu-complex-integer" - .kind = .off - .extension = true - -qual_on_ret_type - .msg = "'{s}' type qualifier on return type has no effect" - .opt = .@"ignored-qualifiers" - .extra = .str - .kind = .off - -cli_invalid_standard - .msg = "invalid standard '{s}'" - .extra = .str - .kind = .@"error" - -cli_invalid_target - .msg = "invalid target '{s}'" - .extra = .str - .kind = .@"error" - -cli_invalid_emulate - .msg = "invalid compiler '{s}'" - .extra = .str - .kind = .@"error" - -cli_invalid_optimization - .msg = "invalid optimization level '{s}'" - .extra = .str - .kind = .@"error" - -cli_unknown_arg - .msg = "unknown argument '{s}'" - .extra = .str - .kind = .@"error" - -cli_error - .msg = "{s}" - .extra = .str - .kind = .@"error" - -cli_warn - .msg = "{s}" - .extra = .str - .kind = .warning - -cli_unused_link_object - .msg = "{s}: linker input file unused because linking not done" - .extra = .str - .kind = .warning - -cli_unknown_linker - .msg = "unrecognized linker '{s}'" - .extra = .str - .kind = .@"error" - -extra_semi - .msg = "extra ';' outside of a function" - .opt = .@"extra-semi" - .kind = .off - -func_field - .msg = "field declared as a function" - .kind = .@"error" - -expected_member_name - .msg = "expected member name after declarator" - .kind = .@"error" - -vla_field - .msg = "variable length array fields extension is not supported" - .kind = .@"error" - -field_incomplete_ty - .msg = "field has incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -flexible_in_union - .msg = "flexible array member in union is not allowed" - .kind = .@"error" - -flexible_in_union_msvc - .msg = "flexible array member in union is a Microsoft extension" - .kind = .off - .opt = .@"microsoft-flexible-array" - .extension = true - -flexible_non_final - .msg = "flexible array member is not at the end of struct" - .kind = .@"error" - -flexible_in_empty - .msg = "flexible array member in otherwise empty struct" - .kind = .@"error" - -flexible_in_empty_msvc - .msg = "flexible array member in otherwise empty struct is a Microsoft extension" - .kind = .off - .opt = .@"microsoft-flexible-array" - .extension = true - -duplicate_member - .msg = "duplicate member '{s}'" - .extra = .str - .kind = .@"error" - -binary_integer_literal - .msg = "binary integer literals are a GNU extension" - .kind = .off - .opt = .@"gnu-binary-literal" - .extension = true - -gnu_va_macro - .msg = "named variadic macros are a GNU extension" - .opt = .@"variadic-macros" - .kind = .off - .extension = true - -builtin_must_be_called - .msg = "builtin function must be directly called" - .kind = .@"error" - -va_start_not_in_func - .msg = "'va_start' cannot be used outside a function" - .kind = .@"error" - -va_start_fixed_args - .msg = "'va_start' used in a function with fixed args" - .kind = .@"error" - -va_start_not_last_param - .msg = "second argument to 'va_start' is not the last named parameter" - .opt = .varargs - .kind = .warning - -attribute_not_enough_args - .msg = "'{s}' attribute takes at least {d} argument(s)" - .kind = .@"error" - .extra = .attr_arg_count - -attribute_too_many_args - .msg = "'{s}' attribute takes at most {d} argument(s)" - .kind = .@"error" - .extra = .attr_arg_count - -attribute_arg_invalid - .msg = "Attribute argument is invalid, expected {s} but got {s}" - .kind = .@"error" - .extra = .attr_arg_type - -unknown_attr_enum - .msg = "Unknown `{s}` argument. Possible values are: {s}" - .kind = .@"error" - .extra = .attr_enum - -attribute_requires_identifier - .msg = "'{s}' attribute requires an identifier" - .kind = .@"error" - .extra = .str - -declspec_not_enabled - .msg = "'__declspec' attributes are not enabled; use '-fdeclspec' or '-fms-extensions' to enable support for __declspec attributes" - .kind = .@"error" - -declspec_attr_not_supported - .msg = "__declspec attribute '{s}' is not supported" - .extra = .str - .opt = .@"ignored-attributes" - .kind = .warning - -deprecated_declarations - .msg = "{s}" - .extra = .str - .opt = .@"deprecated-declarations" - .kind = .warning - -deprecated_note - .msg = "'{s}' has been explicitly marked deprecated here" - .extra = .str - .opt = .@"deprecated-declarations" - .kind = .note - -unavailable - .msg = "{s}" - .extra = .str - .kind = .@"error" - -unavailable_note - .msg = "'{s}' has been explicitly marked unavailable here" - .extra = .str - .kind = .note - -warning_attribute - .msg = "{s}" - .extra = .str - .kind = .warning - .opt = .@"attribute-warning" - -error_attribute - .msg = "{s}" - .extra = .str - .kind = .@"error" - -ignored_record_attr - .msg = "attribute '{s}' is ignored, place it after \"{s}\" to apply attribute to type declaration" - .extra = .ignored_record_attr - .kind = .warning - .opt = .@"ignored-attributes" - -backslash_newline_escape - .msg = "backslash and newline separated by space" - .kind = .warning - .opt = .@"backslash-newline-escape" - -array_size_non_int - .msg = "size of array has non-integer type '{s}'" - .extra = .str - .kind = .@"error" - -cast_to_smaller_int - .msg = "cast to smaller integer type {s}" - .extra = .str - .kind = .warning - .opt = .@"pointer-to-int-cast" - -gnu_switch_range - .msg = "use of GNU case range extension" - .opt = .@"gnu-case-range" - .kind = .off - .extension = true - -empty_case_range - .msg = "empty case range specified" - .kind = .warning - -non_standard_escape_char - .msg = "use of non-standard escape character '\\{s}'" - .kind = .off - .extension = true - .extra = .invalid_escape - -invalid_pp_stringify_escape - .msg = "invalid string literal, ignoring final '\\'" - .kind = .warning - -vla - .msg = "variable length array used" - .kind = .off - .opt = .vla - -int_value_changed - .msg = "implicit conversion from {s}" - .extra = .str - .kind = .warning - .opt = .@"constant-conversion" - -sign_conversion - .msg = "implicit conversion changes signedness: {s}" - .extra = .str - .kind = .off - .opt = .@"sign-conversion" - -float_overflow_conversion - .msg = "implicit conversion of non-finite value from {s} is undefined" - .extra = .str - .kind = .off - .opt = .@"float-overflow-conversion" - -float_out_of_range - .msg = "implicit conversion of out of range value from {s} is undefined" - .extra = .str - .kind = .warning - .opt = .@"literal-conversion" - -float_zero_conversion - .msg = "implicit conversion from {s}" - .extra = .str - .kind = .off - .opt = .@"float-zero-conversion" - -float_value_changed - .msg = "implicit conversion from {s}" - .extra = .str - .kind = .warning - .opt = .@"float-conversion" - -float_to_int - .msg = "implicit conversion turns floating-point number into integer: {s}" - .extra = .str - .kind = .off - .opt = .@"literal-conversion" - -const_decl_folded - .msg = "expression is not an integer constant expression; folding it to a constant is a GNU extension" - .kind = .off - .opt = .@"gnu-folding-constant" - .extension = true - -const_decl_folded_vla - .msg = "variable length array folded to constant array as an extension" - .kind = .off - .opt = .@"gnu-folding-constant" - .extension = true - -redefinition_of_typedef - .msg = "typedef redefinition with different types ({s})" - .extra = .str - .kind = .@"error" - -undefined_macro - .msg = "'{s}' is not defined, evaluates to 0" - .extra = .str - .kind = .off - .opt = .undef - -fn_macro_undefined - .msg = "function-like macro '{s}' is not defined" - .extra = .str - .kind = .@"error" - -preprocessing_directive_only - .msg = "'{s}' must be used within a preprocessing directive" - .extra = .tok_id_expected - .kind = .@"error" - -missing_lparen_after_builtin - .msg = "Missing '(' after built-in macro '{s}'" - .extra = .str - .kind = .@"error" - -offsetof_ty - .msg = "offsetof requires struct or union type, '{s}' invalid" - .extra = .str - .kind = .@"error" - -offsetof_incomplete - .msg = "offsetof of incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -offsetof_array - .msg = "offsetof requires array type, '{s}' invalid" - .extra = .str - .kind = .@"error" - -pragma_pack_lparen - .msg = "missing '(' after '#pragma pack' - ignoring" - .kind = .warning - .opt = .@"ignored-pragmas" - -pragma_pack_rparen - .msg = "missing ')' after '#pragma pack' - ignoring" - .kind = .warning - .opt = .@"ignored-pragmas" - -pragma_pack_unknown_action - .msg = "unknown action for '#pragma pack' - ignoring" - .opt = .@"ignored-pragmas" - .kind = .warning - -pragma_pack_show - .msg = "value of #pragma pack(show) == {d}" - .extra = .unsigned - .kind = .warning - -pragma_pack_int - .msg = "expected #pragma pack parameter to be '1', '2', '4', '8', or '16'" - .opt = .@"ignored-pragmas" - .kind = .warning - -pragma_pack_int_ident - .msg = "expected integer or identifier in '#pragma pack' - ignored" - .opt = .@"ignored-pragmas" - .kind = .warning - -pragma_pack_undefined_pop - .msg = "specifying both a name and alignment to 'pop' is undefined" - .kind = .warning - -pragma_pack_empty_stack - .msg = "#pragma pack(pop, ...) failed: stack empty" - .opt = .@"ignored-pragmas" - .kind = .warning - -cond_expr_type - .msg = "used type '{s}' where arithmetic or pointer type is required" - .extra = .str - .kind = .@"error" - -too_many_includes - .msg = "#include nested too deeply" - .kind = .@"error" - -enumerator_too_small - .msg = "ISO C restricts enumerator values to range of 'int' ({s} is too small)" - .extra = .str - .kind = .off - .extension = true - -enumerator_too_large - .msg = "ISO C restricts enumerator values to range of 'int' ({s} is too large)" - .extra = .str - .kind = .off - .extension = true - -include_next - .msg = "#include_next is a language extension" - .kind = .off - .opt = .@"gnu-include-next" - .extension = true - -include_next_outside_header - .msg = "#include_next in primary source file; will search from start of include path" - .kind = .warning - .opt = .@"include-next-outside-header" - -enumerator_overflow - .msg = "overflow in enumeration value" - .kind = .warning - -enum_not_representable - .msg = "incremented enumerator value {s} is not representable in the largest integer type" - .kind = .warning - .opt = .@"enum-too-large" - .extra = .pow_2_as_string - -enum_too_large - .msg = "enumeration values exceed range of largest integer" - .kind = .warning - .opt = .@"enum-too-large" - -enum_fixed - .msg = "enumeration types with a fixed underlying type are a Clang extension" - .kind = .off - .opt = .@"fixed-enum-extension" - .extension = true - -enum_prev_nonfixed - .msg = "enumeration previously declared with nonfixed underlying type" - .kind = .@"error" - -enum_prev_fixed - .msg = "enumeration previously declared with fixed underlying type" - .kind = .@"error" - -enum_different_explicit_ty - # str will be like 'new' (was 'old' - .msg = "enumeration redeclared with different underlying type {s})" - .extra = .str - .kind = .@"error" - -enum_not_representable_fixed - .msg = "enumerator value is not representable in the underlying type '{s}'" - .extra = .str - .kind = .@"error" - -transparent_union_wrong_type - .msg = "'transparent_union' attribute only applies to unions" - .opt = .@"ignored-attributes" - .kind = .warning - -transparent_union_one_field - .msg = "transparent union definition must contain at least one field; transparent_union attribute ignored" - .opt = .@"ignored-attributes" - .kind = .warning - -transparent_union_size - .msg = "size of field {s} bits) does not match the size of the first field in transparent union; transparent_union attribute ignored" - .extra = .str - .opt = .@"ignored-attributes" - .kind = .warning - -transparent_union_size_note - .msg = "size of first field is {d}" - .extra = .unsigned - .kind = .note - -designated_init_invalid - .msg = "'designated_init' attribute is only valid on 'struct' type'" - .kind = .@"error" - -designated_init_needed - .msg = "positional initialization of field in 'struct' declared with 'designated_init' attribute" - .opt = .@"designated-init" - .kind = .warning - -ignore_common - .msg = "ignoring attribute 'common' because it conflicts with attribute 'nocommon'" - .opt = .@"ignored-attributes" - .kind = .warning - -ignore_nocommon - .msg = "ignoring attribute 'nocommon' because it conflicts with attribute 'common'" - .opt = .@"ignored-attributes" - .kind = .warning - -non_string_ignored - .msg = "'nonstring' attribute ignored on objects of type '{s}'" - .opt = .@"ignored-attributes" - .kind = .warning - -local_variable_attribute - .msg = "'{s}' attribute only applies to local variables" - .extra = .str - .opt = .@"ignored-attributes" - .kind = .warning - -ignore_cold - .msg = "ignoring attribute 'cold' because it conflicts with attribute 'hot'" - .opt = .@"ignored-attributes" - .kind = .warning - -ignore_hot - .msg = "ignoring attribute 'hot' because it conflicts with attribute 'cold'" - .opt = .@"ignored-attributes" - .kind = .warning - -ignore_noinline - .msg = "ignoring attribute 'noinline' because it conflicts with attribute 'always_inline'" - .opt = .@"ignored-attributes" - .kind = .warning - -ignore_always_inline - .msg = "ignoring attribute 'always_inline' because it conflicts with attribute 'noinline'" - .opt = .@"ignored-attributes" - .kind = .warning - -invalid_noreturn - .msg = "function '{s}' declared 'noreturn' should not return" - .extra = .str - .kind = .warning - .opt = .@"invalid-noreturn" - -nodiscard_unused - .msg = "ignoring return value of '{s}', declared with 'nodiscard' attribute" - .extra = .str - .kind = .warning - .opt = .@"unused-result" - -warn_unused_result - .msg = "ignoring return value of '{s}', declared with 'warn_unused_result' attribute" - .extra = .str - .kind = .warning - .opt = .@"unused-result" - -builtin_unused - .msg = "ignoring return value of function declared with {s} attribute" - .extra = .str - .kind = .warning - .opt = .@"unused-value" - -invalid_vec_elem_ty - .msg = "invalid vector element type '{s}'" - .extra = .str - .kind = .@"error" - -vec_size_not_multiple - .msg = "vector size not an integral multiple of component size" - .kind = .@"error" - -invalid_imag - .msg = "invalid type '{s}' to __imag operator" - .extra = .str - .kind = .@"error" - -invalid_real - .msg = "invalid type '{s}' to __real operator" - .extra = .str - .kind = .@"error" - -zero_length_array - .msg = "zero size arrays are an extension" - .kind = .off - .opt = .@"zero-length-array" - -old_style_flexible_struct - .msg = "array index {s} is past the end of the array" - .extra = .str - .kind = .off - .opt = .@"old-style-flexible-struct" - -comma_deletion_va_args - .msg = "token pasting of ',' and __VA_ARGS__ is a GNU extension" - .kind = .off - .opt = .@"gnu-zero-variadic-macro-arguments" - .extension = true - -main_return_type - .msg = "return type of 'main' is not 'int'" - .kind = .warning - .opt = .@"main-return-type" - -expansion_to_defined - .msg = "macro expansion producing 'defined' has undefined behavior" - .kind = .off - .opt = .@"expansion-to-defined" - -invalid_int_suffix - .msg = "invalid suffix '{s}' on integer constant" - .extra = .str - .kind = .@"error" - -invalid_float_suffix - .msg = "invalid suffix '{s}' on floating constant" - .extra = .str - .kind = .@"error" - -invalid_octal_digit - .msg = "invalid digit '{c}' in octal constant" - .extra = .ascii - .kind = .@"error" - -invalid_binary_digit - .msg = "invalid digit '{c}' in binary constant" - .extra = .ascii - .kind = .@"error" - -exponent_has_no_digits - .msg = "exponent has no digits" - .kind = .@"error" - -hex_floating_constant_requires_exponent - .msg = "hexadecimal floating constant requires an exponent" - .kind = .@"error" - -sizeof_returns_zero - .msg = "sizeof returns 0" - .kind = .warning - -declspec_not_allowed_after_declarator - .msg = "'declspec' attribute not allowed after declarator" - .kind = .@"error" - -declarator_name_tok - .msg = "this declarator" - .kind = .note - -type_not_supported_on_target - .msg = "{s} is not supported on this target" - .extra = .str - .kind = .@"error" - -bit_int - .msg = "'_BitInt' in C17 and earlier is a Clang extension" - .kind = .off - .opt = .@"bit-int-extension" - .suppress_version = .c23 - .extension = true - -unsigned_bit_int_too_small - .msg = "{s}unsigned _BitInt must have a bit size of at least 1" - .extra = .str - .kind = .@"error" - -signed_bit_int_too_small - .msg = "{s}signed _BitInt must have a bit size of at least 2" - .extra = .str - .kind = .@"error" - -unsigned_bit_int_too_big - .msg = "{s}unsigned _BitInt of bit sizes greater than " ++ std.fmt.comptimePrint("{d}", .{Properties.max_bits}) ++ " not supported" - .extra = .str - .kind = .@"error" - -signed_bit_int_too_big - .msg = "{s}signed _BitInt of bit sizes greater than " ++ std.fmt.comptimePrint("{d}", .{Properties.max_bits}) ++ " not supported" - .extra = .str - .kind = .@"error" - -keyword_macro - .msg = "keyword is hidden by macro definition" - .kind = .off - .opt = .@"keyword-macro" - .extension = true - -ptr_arithmetic_incomplete - .msg = "arithmetic on a pointer to an incomplete type '{s}'" - .extra = .str - .kind = .@"error" - -callconv_not_supported - .msg = "'{s}' calling convention is not supported for this target" - .extra = .str - .opt = .@"ignored-attributes" - .kind = .warning - -pointer_arith_void - .msg = "invalid application of '{s}' to a void type" - .extra = .str - .kind = .off - .opt = .@"pointer-arith" - -sizeof_array_arg - .msg = "sizeof on array function parameter will return size of {s}" - .extra = .str - .kind = .warning - .opt = .@"sizeof-array-argument" - -array_address_to_bool - .msg = "address of array '{s}' will always evaluate to 'true'" - .extra = .str - .kind = .warning - .opt = .@"pointer-bool-conversion" - -string_literal_to_bool - .msg = "implicit conversion turns string literal into bool: {s}" - .extra = .str - .kind = .off - .opt = .@"string-conversion" - -constant_expression_conversion_not_allowed - .msg = "this conversion is not allowed in a constant expression" - .kind = .note - -invalid_object_cast - .msg = "cannot cast an object of type {s}" - .extra = .str - .kind = .@"error" - -cli_invalid_fp_eval_method - .msg = "unsupported argument '{s}' to option '-ffp-eval-method='; expected 'source', 'double', or 'extended'" - .extra = .str - .kind = .@"error" - -suggest_pointer_for_invalid_fp16 - .msg = "{s} cannot have __fp16 type; did you forget * ?" - .extra = .str - .kind = .@"error" - -bitint_suffix - .msg = "'_BitInt' suffix for literals is a C23 extension" - .opt = .@"c23-extensions" - .kind = .warning - .suppress_version = .c23 - -auto_type_extension - .msg = "'__auto_type' is a GNU extension" - .opt = .@"gnu-auto-type" - .kind = .off - .extension = true - -gnu_pointer_arith - .msg = "arithmetic on pointers to void is a GNU extension" - .opt = .@"gnu-pointer-arith" - .kind = .off - .extension = true - -auto_type_not_allowed - .msg = "'__auto_type' not allowed in {s}" - .kind = .@"error" - .extra = .str - -auto_type_requires_initializer - .msg = "declaration of variable '{s}' with deduced type requires an initializer" - .kind = .@"error" - .extra = .str - -auto_type_requires_single_declarator - .msg = "'__auto_type' may only be used with a single declarator" - .kind = .@"error" - -auto_type_requires_plain_declarator - .msg = "'__auto_type' requires a plain identifier as declarator" - .kind = .@"error" - -auto_type_from_bitfield - .msg = "cannot use bit-field as '__auto_type' initializer" - .kind = .@"error" - -auto_type_array - .msg = "'{s}' declared as array of '__auto_type'" - .kind = .@"error" - .extra = .str - -auto_type_with_init_list - .msg = "cannot use '__auto_type' with initializer list" - .kind = .@"error" - -missing_semicolon - .msg = "expected ';' at end of declaration list" - .kind = .warning - -tentative_definition_incomplete - .msg = "tentative definition has type '{s}' that is never completed" - .kind = .@"error" - .extra = .str - -forward_declaration_here - .msg = "forward declaration of '{s}'" - .kind = .note - .extra = .str - -gnu_union_cast - .msg = "cast to union type is a GNU extension" - .opt = .@"gnu-union-cast" - .kind = .off - .extension = true - -invalid_union_cast - .msg = "cast to union type from type '{s}' not present in union" - .kind = .@"error" - .extra = .str - -cast_to_incomplete_type - .msg = "cast to incomplete type '{s}'" - .kind = .@"error" - .extra = .str - -invalid_source_epoch - .msg = "environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799" - .kind = .@"error" - -fuse_ld_path - .msg = "'-fuse-ld=' taking a path is deprecated; use '--ld-path=' instead" - .kind = .off - .opt = .@"fuse-ld-path" - -invalid_rtlib - .msg = "invalid runtime library name '{s}'" - .kind = .@"error" - .extra = .str - -unsupported_rtlib_gcc - .msg = "unsupported runtime library 'libgcc' for platform '{s}'" - .kind = .@"error" - .extra = .str - -invalid_unwindlib - .msg = "invalid unwind library name '{s}'" - .kind = .@"error" - .extra = .str - -incompatible_unwindlib - .msg = "--rtlib=libgcc requires --unwindlib=libgcc" - .kind = .@"error" - -gnu_asm_disabled - .msg = "GNU-style inline assembly is disabled" - .kind = .@"error" - -extension_token_used - .msg = "extension used" - .kind = .off - .opt = .@"language-extension-token" - .extension = true - -complex_component_init - .msg = "complex initialization specifying real and imaginary components is an extension" - .opt = .@"complex-component-init" - .kind = .off - .extension = true - -complex_prefix_postfix_op - .msg = "ISO C does not support '++'/'--' on complex type '{s}'" - .extra = .str - .kind = .off - .extension = true - -not_floating_type - .msg = "argument type '{s}' is not a real floating point type" - .extra = .str - .kind = .@"error" - -argument_types_differ - .msg = "arguments are of different types ({s})" - .extra = .str - .kind = .@"error" - -ms_search_rule - .msg = "#include resolved using non-portable Microsoft search rules as: {s}" - .extra = .str - .opt = .@"microsoft-include" - .kind = .warning - -ctrl_z_eof - .msg = "treating Ctrl-Z as end-of-file is a Microsoft extension" - .opt = .@"microsoft-end-of-file" - .kind = .off - -illegal_char_encoding_warning - .msg = "illegal character encoding in character literal" - .opt = .@"invalid-source-encoding" - .kind = .warning - -illegal_char_encoding_error - .msg = "illegal character encoding in character literal" - .kind = .@"error" - -ucn_basic_char_error - .msg = "character '{c}' cannot be specified by a universal character name" - .kind = .@"error" - .extra = .ascii - -ucn_basic_char_warning - .msg = "specifying character '{c}' with a universal character name is incompatible with C standards before C23" - .kind = .off - .extra = .ascii - .suppress_unless_version = .c23 - .opt = .@"pre-c23-compat" - -ucn_control_char_error - .msg = "universal character name refers to a control character" - .kind = .@"error" - -ucn_control_char_warning - .msg = "universal character name referring to a control character is incompatible with C standards before C23" - .kind = .off - .suppress_unless_version = .c23 - .opt = .@"pre-c23-compat" - -c89_ucn_in_literal - .msg = "universal character names are only valid in C99 or later" - .suppress_version = .c99 - .kind = .warning - .opt = .unicode - -four_char_char_literal - .msg = "multi-character character constant" - .opt = .@"four-char-constants" - .kind = .off - -multi_char_char_literal - .msg = "multi-character character constant" - .kind = .off - -missing_hex_escape - .msg = "\\{c} used with no following hex digits" - .kind = .@"error" - .extra = .ascii - -unknown_escape_sequence - .msg = "unknown escape sequence '\\{s}'" - .kind = .warning - .opt = .@"unknown-escape-sequence" - .extra = .invalid_escape - -attribute_requires_string - .msg = "attribute '{s}' requires an ordinary string" - .kind = .@"error" - .extra = .str - -unterminated_string_literal_warning - .msg = "missing terminating '\"' character" - .kind = .warning - .opt = .@"invalid-pp-token" - -unterminated_string_literal_error - .msg = "missing terminating '\"' character" - .kind = .@"error" - -empty_char_literal_warning - .msg = "empty character constant" - .kind = .warning - .opt = .@"invalid-pp-token" - -empty_char_literal_error - .msg = "empty character constant" - .kind = .@"error" - -unterminated_char_literal_warning - .msg = "missing terminating ' character" - .kind = .warning - .opt = .@"invalid-pp-token" - -unterminated_char_literal_error - .msg = "missing terminating ' character" - .kind = .@"error" - -unterminated_comment - .msg = "unterminated comment" - .kind = .@"error" - -def_no_proto_deprecated - .msg = "a function definition without a prototype is deprecated in all versions of C and is not supported in C23" - .kind = .warning - .opt = .@"deprecated-non-prototype" - -passing_args_to_kr - .msg = "passing arguments to a function without a prototype is deprecated in all versions of C and is not supported in C23" - .kind = .warning - .opt = .@"deprecated-non-prototype" - -unknown_type_name - .msg = "unknown type name '{s}'" - .kind = .@"error" - .extra = .str - -label_compound_end - .msg = "label at end of compound statement is a C23 extension" - .opt = .@"c23-extensions" - .kind = .warning - .suppress_version = .c23 - .extension = true - -u8_char_lit - .msg = "UTF-8 character literal is a C23 extension" - .opt = .@"c23-extensions" - .kind = .warning - .suppress_version = .c23 - .extension = true - -malformed_embed_param - .msg = "unexpected token in embed parameter" - .kind = .@"error" - -malformed_embed_limit - .msg = "the limit parameter expects one non-negative integer as a parameter" - .kind = .@"error" - -duplicate_embed_param - .msg = "duplicate embed parameter '{s}'" - .kind = .warning - .extra = .str - .opt = .@"duplicate-embed-param" - -unsupported_embed_param - .msg = "unsupported embed parameter '{s}' embed parameter" - .kind = .warning - .extra = .str - .opt = .@"unsupported-embed-param" - -invalid_compound_literal_storage_class - .msg = "compound literal cannot have {s} storage class" - .kind = .@"error" - .extra = .str - -va_opt_lparen - .msg = "missing '(' following __VA_OPT__" - .kind = .@"error" - -va_opt_rparen - .msg = "unterminated __VA_OPT__ argument list" - .kind = .@"error" - -attribute_int_out_of_range - .msg = "attribute value '{s}' out of range" - .kind = .@"error" - .extra = .str - -identifier_not_normalized - .msg = "'{s}' is not in NFC" - .kind = .warning - .extra = .normalized - .opt = .normalized - -c23_auto_single_declarator - .msg = "'auto' can only be used with a single declarator" - .kind = .@"error" - -c32_auto_requires_initializer - .msg = "'auto' requires an initializer" - .kind = .@"error" - -c23_auto_not_allowed - .msg = "'auto' not allowed in {s}" - .kind = .@"error" - .extra = .str - -c23_auto_with_init_list - .msg = "cannot use 'auto' with array" - .kind = .@"error" - -c23_auto_array - .msg = "'{s}' declared as array of 'auto'" - .kind = .@"error" - .extra = .str - -negative_shift_count - .msg = "shift count is negative" - .opt = .@"shift-count-negative" - .kind = .warning - -too_big_shift_count - .msg = "shift count >= width of type" - .opt = .@"shift-count-overflow" - .kind = .warning - -complex_conj - .msg = "ISO C does not support '~' for complex conjugation of '{s}'" - .extra = .str - .kind = .off - .extension = true - -overflow_builtin_requires_int - .msg = "operand argument to overflow builtin must be an integer ('{s}' invalid)" - .extra = .str - .kind = .@"error" - -overflow_result_requires_ptr - .msg = "result argument to overflow builtin must be a pointer to a non-const integer ('{s}' invalid)" - .extra = .str - .kind = .@"error" - -attribute_todo - .msg = "TODO: implement '{s}' attribute for {s}" - .extra = .attribute_todo - .kind = .@"error" - -invalid_type_underlying_enum - .msg = "non-integral type '{s}' is an invalid underlying type" - .extra = .str - .kind = .@"error" - -auto_type_self_initialized - .msg = "variable '{s}' declared with deduced type '__auto_type' cannot appear in its own initializer" - .extra = .str - .kind = .@"error" - -non_constant_initializer - .msg = "initializer element is not a compile-time constant" - .kind = .@"error" - -constexpr_requires_const - .msg = "constexpr variable must be initialized by a constant expression" - .kind = .@"error" - -subtract_pointers_zero_elem_size - .msg = "subtraction of pointers to type '{s}' of zero size has undefined behavior" - .kind = .warning - .opt = .@"pointer-arith" - .extra = .str - -packed_member_address - .msg = "taking address of packed member '{s}" - .opt = .@"address-of-packed-member" - .kind = .warning - .extra = .str - -alloc_align_requires_ptr_return - .msg = "'alloc_align' attribute only applies to return values that are pointers" - .opt = .@"ignored-attributes" - .kind = .warning - -attribute_param_out_of_bounds - .msg = "'{s}' attribute parameter {d} is out of bounds" - .kind = .@"error" - .extra = .attr_arg_count - -alloc_align_required_int_param - .msg = "'alloc_align' attribute argument may only refer to a function parameter of integer type" - .kind = .@"error" - -gnu_missing_eq_designator - .msg = "use of GNU 'missing =' extension in designator" - .kind = .warning - .opt = .@"gnu-designator" - .extension = true - -empty_if_body - .msg = "if statement has empty body" - .kind = .warning - .opt = .@"empty-body" - -empty_if_body_note - .msg = "put the semicolon on a separate line to silence this warning" - .kind = .note - .opt = .@"empty-body" - -nullability_extension - .msg = "type nullability specifier '{s}' is a Clang extension" - .extra = .str - .kind = .off - .opt = .@"nullability-extension" - .extension = true - -duplicate_nullability - .msg = "duplicate nullability specifier '{s}'" - .extra = .str - .kind = .warning - .opt = .@"nullability" - -conflicting_nullability - .msg = "nullaibility specifier '{s}' conflicts with existing specifier '{s}'" - .extra = .tok_id - .kind = .@"error" - -invalid_nullability - .msg = "nullability specifier cannot be applied to non-pointer type '{s}'" - .extra = .str - .kind = .@"error" diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 1089a4e..4ca2bd8 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -42,6 +42,8 @@ const pic_related_options = std.StaticStringMap(void).initComptime(.{ const Driver = @This(); comp: *Compilation, +diagnostics: *Diagnostics, + inputs: std.ArrayListUnmanaged(Source) = .{}, link_objects: std.ArrayListUnmanaged([]const u8) = .{}, output_name: ?[]const u8 = null, @@ -271,7 +273,7 @@ pub fn parseArgs( if (macro.len == 0) { i += 1; if (i >= args.len) { - try d.err("expected argument after -D"); + try d.err("expected argument after -D", .{}); continue; } macro = args[i]; @@ -287,7 +289,7 @@ pub fn parseArgs( if (macro.len == 0) { i += 1; if (i >= args.len) { - try d.err("expected argument after -U"); + try d.err("expected argument after -U", .{}); continue; } macro = args[i]; @@ -297,7 +299,7 @@ pub fn parseArgs( d.comp.code_gen_options.optimization_level = .@"0"; } else if (mem.startsWith(u8, arg, "-O")) { d.comp.code_gen_options.optimization_level = backend.CodeGenOptions.OptimizationLevel.fromString(arg["-O".len..]) orelse { - try d.comp.addDiagnostic(.{ .tag = .cli_invalid_optimization, .extra = .{ .str = arg } }, &.{}); + try d.err("invalid optimization level '{s}'", .{arg}); continue; }; d.use_assembly_backend = d.comp.code_gen_options.optimization_level == .@"0"; @@ -362,12 +364,12 @@ pub fn parseArgs( d.comp.langopts.digraphs = false; } else if (option(arg, "-fmacro-backtrace-limit=")) |limit_str| { var limit = std.fmt.parseInt(u32, limit_str, 10) catch { - try d.err("-fmacro-backtrace-limit takes a number argument"); + try d.err("-fmacro-backtrace-limit takes a number argument", .{}); continue; }; if (limit == 0) limit = std.math.maxInt(u32); - d.comp.diagnostics.macro_backtrace_limit = limit; + d.diagnostics.macro_backtrace_limit = limit; } else if (mem.eql(u8, arg, "-fnative-half-type")) { d.comp.langopts.use_native_half_type = true; } else if (mem.eql(u8, arg, "-fnative-half-arguments-and-returns")) { @@ -404,16 +406,16 @@ pub fn parseArgs( hosted = true; } else if (mem.eql(u8, arg, "-fms-extensions")) { d.comp.langopts.enableMSExtensions(); - try d.comp.diagnostics.set("microsoft", .off); + try d.diagnostics.set("microsoft", .off); } else if (mem.eql(u8, arg, "-fno-ms-extensions")) { d.comp.langopts.disableMSExtensions(); - try d.comp.diagnostics.set("microsoft", .warning); + try d.diagnostics.set("microsoft", .warning); } else if (mem.startsWith(u8, arg, "-I")) { var path = arg["-I".len..]; if (path.len == 0) { i += 1; if (i >= args.len) { - try d.err("expected argument after -I"); + try d.err("expected argument after -I", .{}); continue; } path = args[i]; @@ -432,7 +434,7 @@ pub fn parseArgs( if (path.len == 0) { i += 1; if (i >= args.len) { - try d.err("expected argument after -isystem"); + try d.err("expected argument after -isystem", .{}); continue; } path = args[i]; @@ -442,19 +444,19 @@ pub fn parseArgs( try d.comp.system_include_dirs.append(d.comp.gpa, duped); } else if (option(arg, "--emulate=")) |compiler_str| { const compiler = std.meta.stringToEnum(LangOpts.Compiler, compiler_str) orelse { - try d.comp.addDiagnostic(.{ .tag = .cli_invalid_emulate, .extra = .{ .str = arg } }, &.{}); + try d.err("invalid compiler '{s}'", .{arg}); continue; }; d.comp.langopts.setEmulatedCompiler(compiler); switch (d.comp.langopts.emulate) { - .clang => try d.comp.diagnostics.set("clang", .off), - .gcc => try d.comp.diagnostics.set("gnu", .off), - .msvc => try d.comp.diagnostics.set("microsoft", .off), + .clang => try d.diagnostics.set("clang", .off), + .gcc => try d.diagnostics.set("gnu", .off), + .msvc => try d.diagnostics.set("microsoft", .off), } } else if (option(arg, "-ffp-eval-method=")) |fp_method_str| { const fp_eval_method = std.meta.stringToEnum(LangOpts.FPEvalMethod, fp_method_str) orelse .indeterminate; if (fp_eval_method == .indeterminate) { - try d.comp.addDiagnostic(.{ .tag = .cli_invalid_fp_eval_method, .extra = .{ .str = fp_method_str } }, &.{}); + try d.err("unsupported argument '{s}' to option '-ffp-eval-method='; expected 'source', 'double', or 'extended'", .{fp_method_str}); continue; } d.comp.langopts.setFpEvalMethod(fp_eval_method); @@ -463,7 +465,7 @@ pub fn parseArgs( if (file.len == 0) { i += 1; if (i >= args.len) { - try d.err("expected argument after -o"); + try d.err("expected argument after -o", .{}); continue; } file = args[i]; @@ -472,46 +474,46 @@ pub fn parseArgs( } else if (option(arg, "--sysroot=")) |sysroot| { d.sysroot = sysroot; } else if (mem.eql(u8, arg, "-pedantic")) { - d.comp.diagnostics.state.extensions = .warning; + d.diagnostics.state.extensions = .warning; } else if (mem.eql(u8, arg, "-pedantic-errors")) { - d.comp.diagnostics.state.extensions = .@"error"; + d.diagnostics.state.extensions = .@"error"; } else if (mem.eql(u8, arg, "-w")) { - d.comp.diagnostics.state.ignore_warnings = true; + d.diagnostics.state.ignore_warnings = true; } else if (option(arg, "--rtlib=")) |rtlib| { if (mem.eql(u8, rtlib, "compiler-rt") or mem.eql(u8, rtlib, "libgcc") or mem.eql(u8, rtlib, "platform")) { d.rtlib = rtlib; } else { - try d.comp.addDiagnostic(.{ .tag = .invalid_rtlib, .extra = .{ .str = rtlib } }, &.{}); + try d.err("invalid runtime library name '{s}'", .{rtlib}); } } else if (mem.eql(u8, arg, "-Wno-fatal-errors")) { - d.comp.diagnostics.state.fatal_errors = false; + d.diagnostics.state.fatal_errors = false; } else if (mem.eql(u8, arg, "-Wfatal-errors")) { - d.comp.diagnostics.state.fatal_errors = true; + d.diagnostics.state.fatal_errors = true; } else if (mem.eql(u8, arg, "-Wno-everything")) { - d.comp.diagnostics.state.enable_all_warnings = false; + d.diagnostics.state.enable_all_warnings = false; } else if (mem.eql(u8, arg, "-Weverything")) { - d.comp.diagnostics.state.enable_all_warnings = true; + d.diagnostics.state.enable_all_warnings = true; } else if (mem.eql(u8, arg, "-Werror")) { - d.comp.diagnostics.state.error_warnings = true; + d.diagnostics.state.error_warnings = true; } else if (mem.eql(u8, arg, "-Wno-error")) { - d.comp.diagnostics.state.error_warnings = false; + d.diagnostics.state.error_warnings = false; } else if (option(arg, "-Werror=")) |err_name| { - try d.comp.diagnostics.set(err_name, .@"error"); + try d.diagnostics.set(err_name, .@"error"); } else if (option(arg, "-Wno-error=")) |err_name| { // TODO this should not set to warning if the option has not been specified. - try d.comp.diagnostics.set(err_name, .warning); + try d.diagnostics.set(err_name, .warning); } else if (option(arg, "-Wno-")) |err_name| { - try d.comp.diagnostics.set(err_name, .off); + try d.diagnostics.set(err_name, .off); } else if (option(arg, "-W")) |err_name| { - try d.comp.diagnostics.set(err_name, .warning); + try d.diagnostics.set(err_name, .warning); } else if (option(arg, "-std=")) |standard| { d.comp.langopts.setStandard(standard) catch - try d.comp.addDiagnostic(.{ .tag = .cli_invalid_standard, .extra = .{ .str = arg } }, &.{}); + try d.err("invalid standard '{s}'", .{arg}); } else if (mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--assemble")) { d.only_preprocess_and_compile = true; } else if (option(arg, "--target=")) |triple| { const query = std.Target.Query.parse(.{ .arch_os_abi = triple }) catch { - try d.comp.addDiagnostic(.{ .tag = .cli_invalid_target, .extra = .{ .str = arg } }, &.{}); + try d.err("invalid target '{s}'", .{arg}); continue; }; const target = std.zig.system.resolveTargetQuery(query) catch |e| { @@ -520,9 +522,9 @@ pub fn parseArgs( d.comp.target = target; d.comp.langopts.setEmulatedCompiler(target_util.systemCompiler(target)); switch (d.comp.langopts.emulate) { - .clang => try d.comp.diagnostics.set("clang", .off), - .gcc => try d.comp.diagnostics.set("gnu", .off), - .msvc => try d.comp.diagnostics.set("microsoft", .off), + .clang => try d.diagnostics.set("clang", .off), + .gcc => try d.diagnostics.set("gnu", .off), + .msvc => try d.diagnostics.set("microsoft", .off), } d.raw_target_triple = triple; } else if (mem.eql(u8, arg, "--verbose-ast")) { @@ -588,10 +590,10 @@ pub fn parseArgs( break; } } else { - try d.comp.addDiagnostic(.{ .tag = .invalid_unwindlib, .extra = .{ .str = unwindlib } }, &.{}); + try d.err("invalid unwind library name '{s}'", .{unwindlib}); } } else { - try d.comp.addDiagnostic(.{ .tag = .cli_unknown_arg, .extra = .{ .str = arg } }, &.{}); + try d.warn("unknown argument '{s}'", .{arg}); } } else if (std.mem.endsWith(u8, arg, ".o") or std.mem.endsWith(u8, arg, ".obj")) { try d.link_objects.append(d.comp.gpa, arg); @@ -642,33 +644,54 @@ fn addSource(d: *Driver, path: []const u8) !Source { return d.comp.addSourceFromPath(path); } -pub fn err(d: *Driver, msg: []const u8) Compilation.Error!void { - try d.comp.addDiagnostic(.{ .tag = .cli_error, .extra = .{ .str = msg } }, &.{}); +pub fn err(d: *Driver, fmt: []const u8, args: anytype) Compilation.Error!void { + var sf = std.heap.stackFallback(1024, d.comp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try Diagnostics.formatArgs(buf.writer(), fmt, args); + try d.diagnostics.add(.{ .kind = .@"error", .text = buf.items, .location = null }); } -pub fn warn(d: *Driver, msg: []const u8) Compilation.Error!void { - try d.comp.addDiagnostic(.{ .tag = .cli_warn, .extra = .{ .str = msg } }, &.{}); +pub fn warn(d: *Driver, fmt: []const u8, args: anytype) Compilation.Error!void { + var sf = std.heap.stackFallback(1024, d.comp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try Diagnostics.formatArgs(buf.writer(), fmt, args); + try d.diagnostics.add(.{ .kind = .warning, .text = buf.items, .location = null }); } pub fn unsupportedOptionForTarget(d: *Driver, target: std.Target, opt: []const u8) Compilation.Error!void { - try d.err(try std.fmt.allocPrint( - d.comp.diagnostics.arena.allocator(), - "unsupported option '{s}' for target '{s}'", - .{ opt, try target.linuxTriple(d.comp.diagnostics.arena.allocator()) }, - )); + try d.err( + "unsupported option '{s}' for target '{s}-{s}-{s}'", + .{ opt, @tagName(target.cpu.arch), @tagName(target.os.tag), @tagName(target.abi) }, + ); } pub fn fatal(d: *Driver, comptime fmt: []const u8, args: anytype) error{ FatalError, OutOfMemory } { - try d.comp.diagnostics.list.append(d.comp.gpa, .{ - .tag = .cli_error, - .kind = .@"fatal error", - .extra = .{ .str = try std.fmt.allocPrint(d.comp.diagnostics.arena.allocator(), fmt, args) }, - }); - return error.FatalError; + var sf = std.heap.stackFallback(1024, d.comp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try Diagnostics.formatArgs(buf.writer(), fmt, args); + try d.diagnostics.add(.{ .kind = .@"fatal error", .text = buf.items, .location = null }); + unreachable; } -pub fn renderErrors(d: *Driver) void { - Diagnostics.render(d.comp, d.detectConfig(std.io.getStdErr())); +pub fn printDiagnosticsStats(d: *Driver) void { + const warnings = d.diagnostics.warnings; + const errors = d.diagnostics.errors; + + const w_s: []const u8 = if (warnings == 1) "" else "s"; + const e_s: []const u8 = if (errors == 1) "" else "s"; + if (errors != 0 and warnings != 0) { + std.debug.print("{d} warning{s} and {d} error{s} generated.\n", .{ warnings, w_s, errors, e_s }); + } else if (warnings != 0) { + std.debug.print("{d} warning{s} generated.\n", .{ warnings, w_s }); + } else if (errors != 0) { + std.debug.print("{d} error{s} generated.\n", .{ errors, e_s }); + } } pub fn detectConfig(d: *Driver, file: std.fs.File) std.io.tty.Config { @@ -730,7 +753,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ } if (!linking) for (d.link_objects.items) |obj| { - try d.comp.addDiagnostic(.{ .tag = .cli_unused_link_object, .extra = .{ .str = obj } }, &.{}); + try d.err("{s}: linker input file unused because linking not done", .{obj}); }; tc.discover() catch |er| switch (er) { @@ -763,7 +786,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ }; try d.processSource(tc, source, builtin, user_macros, fast_exit, asm_gen_fn); } - if (d.comp.diagnostics.errors != 0) { + if (d.diagnostics.errors != 0) { if (fast_exit) d.exitWithCleanup(1); return; } @@ -842,7 +865,7 @@ fn processSource( asm_gen_fn: ?AsmCodeGenFn, ) !void { d.comp.generated_buf.items.len = 0; - const prev_errors = d.comp.diagnostics.errors; + const prev_total = d.diagnostics.total; var pp = try Preprocessor.initDefault(d.comp); defer pp.deinit(); @@ -866,9 +889,9 @@ fn processSource( try pp.preprocessSources(&.{ source, builtin, user_macros }); if (d.only_preprocess) { - d.renderErrors(); + d.printDiagnosticsStats(); - if (d.comp.diagnostics.errors != prev_errors) { + if (d.diagnostics.total != prev_total) { if (fast_exit) std.process.exit(1); // Not linking, no need for cleanup. return; } @@ -901,9 +924,9 @@ fn processSource( buf_writer.flush() catch {}; } - d.renderErrors(); + d.printDiagnosticsStats(); - if (d.comp.diagnostics.errors != prev_errors) { + if (d.diagnostics.errors != prev_total) { if (fast_exit) d.exitWithCleanup(1); return; // do not compile if there were errors } @@ -1167,11 +1190,10 @@ pub fn getPICMode(d: *Driver, lastpic: []const u8) Compilation.Error!struct { ba if (target_util.isPS(target)) { if (d.cmodel != .kernel) { pic = true; - try d.warn(try std.fmt.allocPrint( - d.comp.diagnostics.arena.allocator(), + try d.warn( "option '{s}' was ignored by the {s} toolchain, using '-fPIC'", .{ lastpic, if (target.os.tag == .ps4) "PS4" else "PS5" }, - )); + ); } } } @@ -1207,7 +1229,7 @@ pub fn getPICMode(d: *Driver, lastpic: []const u8) Compilation.Error!struct { ba // ROPI and RWPI are not compatible with PIC or PIE. if ((d.ropi or d.rwpi) and (pic or pie)) { - try d.err("embedded and GOT-based position independence are incompatible"); + try d.err("embedded and GOT-based position independence are incompatible", .{}); } if (target.cpu.arch.isMIPS()) { diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 95cfd03..43785db 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -100,6 +100,7 @@ const Parser = @This(); // values from preprocessor pp: *Preprocessor, comp: *Compilation, +diagnostics: *Diagnostics, gpa: mem.Allocator, tok_ids: []const Token.Id, tok_i: TokenIndex = 0, @@ -171,8 +172,8 @@ record: struct { while (i > r.start) { i -= 1; if (p.record_members.items[i].name == name) { - try p.errStr(.duplicate_member, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, p.record_members.items[i].tok); + try p.err(tok, .duplicate_member, .{p.tokSlice(tok)}); + try p.err(p.record_members.items[i].tok, .previous_definition, .{}); break; } } @@ -207,32 +208,49 @@ string_ids: struct { /// Checks codepoint for various pedantic warnings /// Returns true if diagnostic issued -fn checkIdentifierCodepointWarnings(comp: *Compilation, codepoint: u21, loc: Source.Location) Compilation.Error!bool { +fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Location) Compilation.Error!bool { assert(codepoint >= 0x80); - const err_start = comp.diagnostics.list.items.len; + const prev_total = p.diagnostics.total; + var sf = std.heap.stackFallback(1024, p.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); if (!char_info.isC99IdChar(codepoint)) { - try comp.addDiagnostic(.{ - .tag = .c99_compat, - .loc = loc, - }, &.{}); + const diagnostic: Diagnostic = .c99_compat; + try p.diagnostics.add(.{ + .kind = diagnostic.kind, + .text = diagnostic.fmt, + .extension = diagnostic.extension, + .opt = diagnostic.opt, + .location = loc.expand(p.comp), + }); } if (char_info.isInvisible(codepoint)) { - try comp.addDiagnostic(.{ - .tag = .unicode_zero_width, - .loc = loc, - .extra = .{ .actual_codepoint = codepoint }, - }, &.{}); + const diagnostic: Diagnostic = .unicode_zero_width; + try p.formatArgs(buf.writer(), diagnostic.fmt, .{Codepoint.init(codepoint)}); + + try p.diagnostics.add(.{ + .kind = diagnostic.kind, + .text = buf.items, + .extension = diagnostic.extension, + .opt = diagnostic.opt, + .location = loc.expand(p.comp), + }); } if (char_info.homoglyph(codepoint)) |resembles| { - try comp.addDiagnostic(.{ - .tag = .unicode_homoglyph, - .loc = loc, - .extra = .{ .codepoints = .{ .actual = codepoint, .resembles = resembles } }, - }, &.{}); + const diagnostic: Diagnostic = .unicode_homoglyph; + try p.formatArgs(buf.writer(), diagnostic.fmt, .{ Codepoint.init(codepoint), resembles }); + + try p.diagnostics.add(.{ + .kind = diagnostic.kind, + .text = buf.items, + .extension = diagnostic.extension, + .opt = diagnostic.opt, + .location = loc.expand(p.comp), + }); } - return comp.diagnostics.list.items.len != err_start; + return p.diagnostics.total != prev_total; } /// Issues diagnostics for the current extended identifier token @@ -244,7 +262,7 @@ fn validateExtendedIdentifier(p: *Parser) !bool { const slice = p.tokSlice(p.tok_i); const view = std.unicode.Utf8View.init(slice) catch { - try p.errTok(.invalid_utf8, p.tok_i); + try p.err(p.tok_i, .invalid_utf8, .{}); return error.FatalError; }; var it = view.iterator(); @@ -265,10 +283,16 @@ fn validateExtendedIdentifier(p: *Parser) !bool { } if (codepoint == '$') { warned = true; - if (p.comp.langopts.dollars_in_identifiers) try p.comp.addDiagnostic(.{ - .tag = .dollar_in_identifier_extension, - .loc = loc, - }, &.{}); + if (p.comp.langopts.dollars_in_identifiers) { + const diagnostic: Diagnostic = .dollar_in_identifier_extension; + try p.diagnostics.add(.{ + .kind = diagnostic.kind, + .text = diagnostic.fmt, + .extension = diagnostic.extension, + .opt = diagnostic.opt, + .location = loc.expand(p.comp), + }); + } } if (codepoint <= 0x7F) continue; @@ -282,7 +306,7 @@ fn validateExtendedIdentifier(p: *Parser) !bool { } if (!warned) { - warned = try checkIdentifierCodepointWarnings(p.comp, codepoint, loc); + warned = try p.checkIdentifierCodepointWarnings(codepoint, loc); } // Check NFC normalization. @@ -292,22 +316,22 @@ fn validateExtendedIdentifier(p: *Parser) !bool { canonical_class != .not_reordered) { normalized = false; - try p.errStr(.identifier_not_normalized, p.tok_i, slice); + try p.err(p.tok_i, .identifier_not_normalized, .{slice}); continue; } if (char_info.isNormalized(codepoint) != .yes) { normalized = false; - try p.errExtra(.identifier_not_normalized, p.tok_i, .{ .normalized = slice }); + try p.err(p.tok_i, .identifier_not_normalized, .{Normalized.init(slice)}); } last_canonical_class = canonical_class; } if (!valid_identifier) { if (len == 1) { - try p.errExtra(.unexpected_character, p.tok_i, .{ .actual_codepoint = invalid_char }); + try p.err(p.tok_i, .unexpected_character, .{Codepoint.init(invalid_char)}); return false; } else { - try p.errExtra(.invalid_identifier_start_char, p.tok_i, .{ .actual_codepoint = invalid_char }); + try p.err(p.tok_i, .invalid_identifier_start_char, .{Codepoint.init(invalid_char)}); } } @@ -330,7 +354,7 @@ fn eatIdentifier(p: *Parser) !?TokenIndex { // Handle illegal '$' characters in identifiers if (!p.comp.langopts.dollars_in_identifiers) { if (p.tok_ids[p.tok_i] == .invalid and p.tokSlice(p.tok_i)[0] == '$') { - try p.err(.dollars_in_identifiers); + try p.err(p.tok_i, .dollars_in_identifiers, .{}); p.tok_i += 1; return error.ParsingFailed; } @@ -380,92 +404,150 @@ pub fn tokSlice(p: *Parser, tok: TokenIndex) []const u8 { fn expectClosing(p: *Parser, opening: TokenIndex, id: Token.Id) Error!void { _ = p.expectToken(id) catch |e| { if (e == error.ParsingFailed) { - try p.errTok(switch (id) { + try p.err(opening, switch (id) { .r_paren => .to_match_paren, .r_brace => .to_match_brace, .r_bracket => .to_match_brace, else => unreachable, - }, opening); + }, .{}); } return e; }; } -fn errOverflow(p: *Parser, op_tok: TokenIndex, res: Result) !void { - try p.errStr(.overflow, op_tok, try res.str(p)); -} +pub const Diagnostic = @import("Parser/Diagnostic.zig"); -fn errArrayOverflow(p: *Parser, op_tok: TokenIndex, res: Result) !void { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; +pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) Compilation.Error!void { + if (p.extension_suppressed) { + if (diagnostic.extension and diagnostic.kind == .off) return; + } + if (p.diagnostics.effectiveKind(diagnostic) == .off) return; - const w = p.strings.writer(); - const format = - \\The pointer incremented by {s} refers past the last possible element in {d}-bit address space containing {d}-bit ({d}-byte) elements (max possible {d} elements) - ; - const increment = try res.str(p); - const ptr_bits = p.comp.type_store.intptr.bitSizeof(p.comp); - const element_size = res.qt.childType(p.comp).sizeofOrNull(p.comp) orelse 1; - const max_elems = p.comp.maxArrayBytes() / element_size; + var sf = std.heap.stackFallback(1024, p.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); - try w.print(format, .{ increment, ptr_bits, element_size * 8, element_size, max_elems }); - const duped = try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); - return p.errStr(.array_overflow, op_tok, duped); + try p.formatArgs(buf.writer(), diagnostic.fmt, args); + try p.diagnostics.addWithLocation(p.comp, .{ + .kind = diagnostic.kind, + .text = buf.items, + .opt = diagnostic.opt, + .extension = diagnostic.extension, + .location = p.pp.tokens.items(.loc)[tok_i].expand(p.comp), + }, p.pp.expansionSlice(tok_i), true); +} + +fn formatArgs(p: *Parser, w: anytype, fmt: []const u8, args: anytype) !void { + var i: usize = 0; + inline for (std.meta.fields(@TypeOf(args))) |arg_info| { + const arg = @field(args, arg_info.name); + i += switch (@TypeOf(arg)) { + []const u8 => try Diagnostics.formatString(w, fmt[i..], arg), + Tree.Token.Id => try formatTokenId(w, fmt[i..], arg), + QualType => try p.formatQualType(w, fmt[i..], arg), + text_literal.Ascii => try arg.format(w, fmt[i..]), + Result => try p.formatResult(w, fmt[i..], arg), + *Result => try p.formatResult(w, fmt[i..], arg.*), + Enumerator, *Enumerator => try p.formatResult(w, fmt[i..], .{ + .node = undefined, + .val = arg.val, + .qt = arg.qt, + }), + else => switch (@typeInfo(@TypeOf(arg))) { + .int, .comptime_int => try Diagnostics.formatInt(w, fmt[i..], arg), + .pointer => try Diagnostics.formatString(w, fmt[i..], arg), + else => unreachable, + }, + }; + } + try w.writeAll(fmt[i..]); } -fn errExpectedToken(p: *Parser, expected: Token.Id, actual: Token.Id) Error { - switch (actual) { - .invalid => try p.errExtra(.expected_invalid, p.tok_i, .{ .tok_id_expected = expected }), - .eof => try p.errExtra(.expected_eof, p.tok_i, .{ .tok_id_expected = expected }), - else => try p.errExtra(.expected_token, p.tok_i, .{ .tok_id = .{ - .expected = expected, - .actual = actual, - } }), - } - return error.ParsingFailed; +fn formatTokenId(w: anytype, fmt: []const u8, tok_id: Tree.Token.Id) !usize { + const i = std.mem.indexOf(u8, fmt, "{tok_id}").?; + try w.writeAll(fmt[0..i]); + try w.writeAll(tok_id.symbol()); + return i; } -pub fn errStr(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex, str: []const u8) Compilation.Error!void { - @branchHint(.cold); - return p.errExtra(tag, tok_i, .{ .str = str }); +fn formatQualType(p: *Parser, w: anytype, fmt: []const u8, qt: QualType) !usize { + const i = std.mem.indexOf(u8, fmt, "{qt}").?; + try w.writeAll(fmt[0..i]); + try qt.print(p.comp, w); + return i; } -pub fn errExtra(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex, extra: Diagnostics.Message.Extra) Compilation.Error!void { - @branchHint(.cold); +fn formatResult(p: *Parser, w: anytype, fmt: []const u8, res: Result) !usize { + const i = std.mem.indexOf(u8, fmt, "{value}").?; + try w.writeAll(fmt[0..i]); - // Suppress pedantic diagnostics inside __extension__ blocks. - if (p.extension_suppressed) { - const prop = tag.property(); - if (prop.extension and prop.kind == .off) return; + switch (res.val.opt_ref) { + .none => try w.writeAll("(none)"), + .null => try w.writeAll("nullptr_t"), + else => if (try res.val.print(res.qt, p.comp, w)) |nested| switch (nested) { + .pointer => |ptr| { + const ptr_node: Node.Index = @enumFromInt(ptr.node); + const decl_name = p.tree.tokSlice(ptr_node.tok(&p.tree)); + try ptr.offset.printPointer(decl_name, p.comp, w); + }, + }, } - const tok = p.pp.tokens.get(tok_i); - var loc = tok.loc; - if (tok_i != 0 and tok.id == .eof) { - // if the token is EOF, point at the end of the previous token instead - const prev = p.pp.tokens.get(tok_i - 1); - loc = prev.loc; - loc.byte_offset += @intCast(p.tokSlice(tok_i - 1).len); - } - try p.comp.addDiagnostic(.{ - .tag = tag, - .loc = loc, - .extra = extra, - }, p.pp.expansionSlice(tok_i)); + return i; } -pub fn errTok(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex) Compilation.Error!void { - @branchHint(.cold); - return p.errExtra(tag, tok_i, .{ .none = {} }); -} +const Normalized = struct { + str: []const u8, -pub fn err(p: *Parser, tag: Diagnostics.Tag) Compilation.Error!void { - @branchHint(.cold); - return p.errExtra(tag, p.tok_i, .{ .none = {} }); -} + fn init(str: []const u8) Normalized { + return .{ .str = str }; + } + + pub fn format(w: anytype, fmt_str: []const u8, ctx: Normalized) !usize { + const i = std.mem.indexOf(u8, fmt_str, "{normalized}").?; + try w.writeAll(fmt_str[0..i]); + var it: std.unicode.Utf8Iterator = .{ + .bytes = ctx.str, + .i = 0, + }; + while (it.nextCodepoint()) |codepoint| { + if (codepoint < 0x7F) { + try w.writeByte(@intCast(codepoint)); + } else if (codepoint < 0xFFFF) { + try w.writeAll("\\u"); + try std.fmt.formatInt(codepoint, 16, .upper, .{ + .fill = '0', + .width = 4, + }, w); + } else { + try w.writeAll("\\U"); + try std.fmt.formatInt(codepoint, 16, .upper, .{ + .fill = '0', + .width = 8, + }, w); + } + } + return i; + } +}; + +const Codepoint = struct { + codepoint: u21, + + fn init(codepoint: u21) Codepoint { + return .{ .codepoint = codepoint }; + } + + pub fn write(w: anytype, fmt_str: []const u8, ctx: Codepoint) !usize { + const i = std.mem.indexOf(u8, fmt_str, "{codepoint}").?; + try w.writeAll(fmt_str[0..i]); + try w.print("{X:0>4}", .{ctx.codepoint}); + return i; + } +}; pub fn todo(p: *Parser, msg: []const u8) Error { - try p.errStr(.todo, p.tok_i, msg); + try p.err(p.tok_i, .todo, .{msg}); return error.ParsingFailed; } @@ -479,119 +561,35 @@ pub fn removeNull(p: *Parser, str: Value) !Value { return Value.intern(p.comp, .{ .bytes = p.strings.items[strings_top..] }); } -pub fn typeStr(p: *Parser, qt: QualType) ![]const u8 { - if (@import("builtin").mode != .Debug) { - if (qt.isInvalid()) { - return "Tried to render invalid type - this is an aro bug."; - } - } - if (TypeStore.Builder.fromType(p.comp, qt).str(p.comp.langopts)) |str| return str; - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - try qt.print(p.comp, p.strings.writer()); - return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); -} - -pub fn typePairStr(p: *Parser, a: QualType, b: QualType) ![]const u8 { - return p.typePairStrExtra(a, " and ", b); -} - -pub fn typePairStrExtra(p: *Parser, a: QualType, msg: []const u8, b: QualType) ![]const u8 { - if (@import("builtin").mode != .Debug) { - if (a.isInvalid() or b.isInvalid()) { - return "Tried to render invalid type - this is an aro bug."; - } - } - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - try p.strings.append('\''); - try a.print(p.comp, p.strings.writer()); - try p.strings.append('\''); - try p.strings.appendSlice(msg); - try p.strings.append('\''); - try b.print(p.comp, p.strings.writer()); - try p.strings.append('\''); - return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); -} - -pub fn valueChangedStr(p: *Parser, res: *Result, old_value: Value, int_ty: QualType) ![]const u8 { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - var w = p.strings.writer(); - const type_pair_str = try p.typePairStrExtra(res.qt, " to ", int_ty); - try w.writeAll(type_pair_str); - - try w.writeAll(" changes "); - if (res.val.isZero(p.comp)) try w.writeAll("non-zero "); - try w.writeAll("value from "); - if (try old_value.print(res.qt, p.comp, w)) |nested| switch (nested) { - .pointer => unreachable, - }; - try w.writeAll(" to "); - if (try res.val.print(int_ty, p.comp, w)) |nested| switch (nested) { - .pointer => unreachable, - }; - - return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); +pub fn errValueChanged(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, res: Result, old_res: Result, int_qt: QualType) !void { + const zero_str = if (res.val.isZero(p.comp)) "non-zero " else ""; + try p.err(tok_i, diagnostic, .{ res.qt, int_qt, zero_str, old_res, res }); } fn checkDeprecatedUnavailable(p: *Parser, ty: QualType, usage_tok: TokenIndex, decl_tok: TokenIndex) !void { if (ty.getAttribute(p.comp, .@"error")) |@"error"| { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - const w = p.strings.writer(); const msg_str = p.comp.interner.get(@"error".msg.ref()).bytes; - try w.print("call to '{s}' declared with attribute error: {}", .{ - p.tokSlice(@"error".__name_tok), std.zig.fmtEscapes(msg_str), - }); - const str = try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); - try p.errStr(.error_attribute, usage_tok, str); + try p.err(usage_tok, .error_attribute, .{ p.tokSlice(@"error".__name_tok), std.zig.fmtEscapes(msg_str) }); } if (ty.getAttribute(p.comp, .warning)) |warning| { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - const w = p.strings.writer(); const msg_str = p.comp.interner.get(warning.msg.ref()).bytes; - try w.print("call to '{s}' declared with attribute warning: {}", .{ - p.tokSlice(warning.__name_tok), std.zig.fmtEscapes(msg_str), - }); - const str = try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); - try p.errStr(.warning_attribute, usage_tok, str); + try p.err(usage_tok, .warning_attribute, .{ p.tokSlice(warning.__name_tok), std.zig.fmtEscapes(msg_str) }); } if (ty.getAttribute(p.comp, .unavailable)) |unavailable| { - try p.errDeprecated(.unavailable, usage_tok, unavailable.msg); - try p.errStr(.unavailable_note, unavailable.__name_tok, p.tokSlice(decl_tok)); + try p.errDeprecated(usage_tok, .unavailable, unavailable.msg); + try p.err(unavailable.__name_tok, .unavailable_note, .{p.tokSlice(decl_tok)}); return error.ParsingFailed; } if (ty.getAttribute(p.comp, .deprecated)) |deprecated| { - try p.errDeprecated(.deprecated_declarations, usage_tok, deprecated.msg); - try p.errStr(.deprecated_note, deprecated.__name_tok, p.tokSlice(decl_tok)); + try p.errDeprecated(usage_tok, .deprecated_declarations, deprecated.msg); + try p.err(deprecated.__name_tok, .deprecated_note, .{p.tokSlice(decl_tok)}); } } -fn errDeprecated(p: *Parser, tag: Diagnostics.Tag, tok_i: TokenIndex, msg: ?Value) Compilation.Error!void { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - const w = p.strings.writer(); - try w.print("'{s}' is ", .{p.tokSlice(tok_i)}); - const reason: []const u8 = switch (tag) { - .unavailable => "unavailable", - .deprecated_declarations => "deprecated", - else => unreachable, - }; - try w.writeAll(reason); - if (msg) |m| { - const str = p.comp.interner.get(m.ref()).bytes; - try w.print(": {}", .{std.zig.fmtEscapes(str)}); - } - const str = try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); - return p.errStr(tag, tok_i, str); +fn errDeprecated(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, msg: ?Value) Compilation.Error!void { + const colon_str: []const u8 = if (msg != null) ": " else ""; + const msg_str = std.zig.fmtEscapes(if (msg) |m| p.comp.interner.get(m.ref()).bytes else ""); + return p.err(tok_i, diagnostic, .{ p.tokSlice(tok_i), colon_str, msg_str }); } fn addNode(p: *Parser, node: Tree.Node) Allocator.Error!Node.Index { @@ -599,6 +597,15 @@ fn addNode(p: *Parser, node: Tree.Node) Allocator.Error!Node.Index { return p.tree.addNode(node); } +fn errExpectedToken(p: *Parser, expected: Token.Id, actual: Token.Id) Error { + switch (actual) { + .invalid => try p.err(p.tok_i, .expected_invalid, .{expected}), + .eof => try p.err(p.tok_i, .expected_eof, .{expected}), + else => try p.err(p.tok_i, .expected_token, .{ expected, actual }), + } + return error.ParsingFailed; +} + fn addList(p: *Parser, nodes: []const Node.Index) Allocator.Error!Tree.Node.Range { if (p.in_macro) return Tree.Node.Range{ .start = 0, .end = 0 }; const start: u32 = @intCast(p.data.items.len); @@ -682,9 +689,8 @@ fn diagnoseIncompleteDefinitions(p: *Parser) !void { }; const tentative_def_tok = p.tentative_defs.get(decl_type_name) orelse continue; - const type_str = try p.typeStr(forward.container_qt); - try p.errStr(.tentative_definition_incomplete, tentative_def_tok, type_str); - try p.errStr(.forward_declaration_here, forward.name_or_kind_tok, type_str); + try p.err(tentative_def_tok, .tentative_definition_incomplete, .{forward.container_qt}); + try p.err(forward.name_or_kind_tok, .forward_declaration_here, .{forward.container_qt}); } } @@ -696,6 +702,7 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { var p: Parser = .{ .pp = pp, .comp = pp.comp, + .diagnostics = pp.diagnostics, .gpa = pp.comp.gpa, .tree = .{ .comp = pp.comp, @@ -778,7 +785,7 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { .keyword_asm1, .keyword_asm2, => {}, - else => try p.err(.expected_external_decl), + else => try p.err(p.tok_i, .expected_external_decl, .{}), } continue; } @@ -793,14 +800,14 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { continue; } if (p.eatToken(.semicolon)) |tok| { - try p.errTok(.extra_semi, tok); + try p.err(tok, .extra_semi, .{}); const empty = try p.tree.addNode(.{ .empty_decl = .{ .semicolon = tok, } }); try p.decl_buf.append(empty); continue; } - try p.err(.expected_external_decl); + try p.err(p.tok_i, .expected_external_decl, .{}); p.nextExternDecl(); } if (p.tentative_defs.count() > 0) { @@ -809,7 +816,7 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { p.tree.root_decls = p.decl_buf.moveToUnmanaged(); if (p.tree.root_decls.items.len == implicit_typedef_count) { - try p.errTok(.empty_translation_unit, p.tok_i - 1); + try p.err(p.tok_i - 1, .empty_translation_unit, .{}); } pp.comp.pragmaEvent(.after_parse); @@ -983,14 +990,14 @@ fn decl(p: *Parser) Error!bool { .identifier, .extended_identifier => { // The most likely reason for `identifier identifier` is // an unknown type name. - try p.errStr(.unknown_type_name, p.tok_i, p.tokSlice(p.tok_i)); + try p.err(p.tok_i, .unknown_type_name, .{p.tokSlice(p.tok_i)}); p.tok_i += 1; break :blk DeclSpec{ .qt = .invalid }; }, else => {}, }, else => if (p.tok_i != first_tok) { - try p.err(.expected_ident_or_l_paren); + try p.err(p.tok_i, .expected_ident_or_l_paren, .{}); return error.ParsingFailed; } else return false, } @@ -1011,7 +1018,7 @@ fn decl(p: *Parser) Error!bool { missing_decl: { if (decl_spec.qt.type(p.comp) == .typeof) { // we follow GCC and clang's behavior here - try p.errTok(.missing_declaration, first_tok); + try p.err(first_tok, .missing_declaration, .{}); return true; } switch (decl_spec.qt.base(p.comp).type) { @@ -1020,20 +1027,15 @@ fn decl(p: *Parser) Error!bool { else => {}, } - try p.errTok(.missing_declaration, first_tok); + try p.err(first_tok, .missing_declaration, .{}); return true; } const attrs = p.attr_buf.items(.attr)[attr_buf_top..]; const toks = p.attr_buf.items(.tok)[attr_buf_top..]; for (attrs, toks) |attr, tok| { - try p.errExtra(.ignored_record_attr, tok, .{ - .ignored_record_attr = .{ .tag = attr.tag, .tag_kind = switch (decl_spec.qt.base(p.comp).type) { - .@"enum" => .@"enum", - .@"struct" => .@"struct", - .@"union" => .@"union", - else => unreachable, - } }, + try p.err(tok, .ignored_record_attr, .{ + @tagName(attr.tag), @tagName(decl_spec.qt.base(p.comp).type), }); } return true; @@ -1045,11 +1047,11 @@ fn decl(p: *Parser) Error!bool { .comma, .semicolon => break :fn_def, .l_brace => {}, else => if (init_d.d.old_style_func == null) { - try p.errTok(.expected_fn_body, p.tok_i - 1); + try p.err(p.tok_i - 1, .expected_fn_body, .{}); return true; }, } - if (p.func.qt != null) try p.err(.func_not_in_root); + if (p.func.qt != null) try p.err(p.tok_i, .func_not_in_root, .{}); const interned_declarator_name = try p.comp.internString(p.tokSlice(init_d.d.name)); try p.syms.defineSymbol(p, interned_declarator_name, init_d.d.qt, init_d.d.name, decl_node, .{}, false); @@ -1065,7 +1067,7 @@ fn decl(p: *Parser) Error!bool { const func_ty = init_d.d.qt.get(p.comp, .func).?; const int_ty = func_ty.return_type.get(p.comp, .int); if (int_ty == null or int_ty.? != .int) { - try p.errTok(.main_return_type, init_d.d.name); + try p.err(init_d.d.name, .main_return_type, .{}); } } @@ -1090,7 +1092,7 @@ fn decl(p: *Parser) Error!bool { param_loop: while (true) { const param_decl_spec = (try p.declSpec()) orelse break; if (p.eatToken(.semicolon)) |semi| { - try p.errTok(.missing_declaration, semi); + try p.err(semi, .missing_declaration, .{}); continue :param_loop; } @@ -1099,7 +1101,7 @@ fn decl(p: *Parser) Error!bool { defer p.attr_buf.len = attr_buf_top_declarator; var param_d = (try p.declarator(param_decl_spec.qt, .param)) orelse { - try p.errTok(.missing_declaration, first_tok); + try p.err(first_tok, .missing_declaration, .{}); _ = try p.expectToken(.semicolon); continue :param_loop; }; @@ -1107,9 +1109,9 @@ fn decl(p: *Parser) Error!bool { if (param_d.qt.hasIncompleteSize(p.comp)) { if (param_d.qt.is(p.comp, .void)) { - try p.errTok(.invalid_void_param, param_d.name); + try p.err(param_d.name, .invalid_void_param, .{}); } else { - try p.errStr(.parameter_incomplete_ty, param_d.name, try p.typeStr(param_d.qt)); + try p.err(param_d.name, .parameter_incomplete_ty, .{param_d.qt}); } } else { // Decay params declared as functions or arrays to pointer. @@ -1147,7 +1149,7 @@ fn decl(p: *Parser) Error!bool { break; } } else { - try p.errStr(.parameter_missing, param_d.name, name_str); + try p.err(param_d.name, .parameter_missing, .{name_str}); } if (p.eatToken(.comma) == null) break; @@ -1158,7 +1160,7 @@ fn decl(p: *Parser) Error!bool { const func_ty = func_qt.get(p.comp, .func).?; for (func_ty.params, new_params) |param, *new_param| { if (new_param.name == .empty) { - try p.errStr(.param_not_declared, param.name_tok, param.name.lookup(p.comp)); + try p.err(param.name_tok, .param_not_declared, .{param.name.lookup(p.comp)}); new_param.* = .{ .name = param.name, .name_tok = param.name_tok, @@ -1176,7 +1178,7 @@ fn decl(p: *Parser) Error!bool { } else if (init_d.d.qt.get(p.comp, .func)) |func_ty| { for (func_ty.params) |param| { if (param.name == .empty) { - try p.errTok(.omitting_parameter_name, param.name_tok); + try p.err(param.name_tok, .omitting_parameter_name, .{}); continue; } @@ -1195,20 +1197,20 @@ fn decl(p: *Parser) Error!bool { if (pointer_ty.decayed) |decayed_qt| { if (decayed_qt.get(p.comp, .array)) |array_ty| { if (array_ty.len == .unspecified_variable) { - try p.errTok(.unbound_vla, param.name_tok); + try p.err(param.name_tok, .unbound_vla, .{}); } } } } if (param.qt.hasIncompleteSize(p.comp) and !param.qt.is(p.comp, .void)) { - try p.errStr(.parameter_incomplete_ty, param.name_tok, try p.typeStr(param.qt)); + try p.err(param.name_tok, .parameter_incomplete_ty, .{param.qt}); } } } const body = (try p.compoundStmt(true, null)) orelse { assert(init_d.d.old_style_func != null); - try p.err(.expected_fn_body); + try p.err(p.tok_i, .expected_fn_body, .{}); return true; }; @@ -1227,10 +1229,10 @@ fn decl(p: *Parser) Error!bool { if (func.qt == null) { for (p.labels.items) |item| { if (item == .unresolved_goto) - try p.errStr(.undeclared_label, item.unresolved_goto, p.tokSlice(item.unresolved_goto)); + try p.err(item.unresolved_goto, .undeclared_label, .{p.tokSlice(item.unresolved_goto)}); } if (p.computed_goto_tok) |goto_tok| { - if (!p.contains_address_of_label) try p.errTok(.invalid_computed_goto, goto_tok); + if (!p.contains_address_of_label) try p.err(goto_tok, .invalid_computed_goto, .{}); } p.labels.items.len = 0; p.label_count = 0; @@ -1243,7 +1245,7 @@ fn decl(p: *Parser) Error!bool { // Declare all variable/typedef declarators. var warned_auto = false; while (true) { - if (init_d.d.old_style_func) |tok_i| try p.errTok(.invalid_old_style_params, tok_i); + if (init_d.d.old_style_func) |tok_i| try p.err(tok_i, .invalid_old_style_params, .{}); if (decl_spec.storage_class == .typedef) { try decl_spec.validateDecl(p); @@ -1329,11 +1331,11 @@ fn decl(p: *Parser) Error!bool { if (!warned_auto) { // TODO these are warnings in clang if (decl_spec.auto_type) |tok_i| { - try p.errTok(.auto_type_requires_single_declarator, tok_i); + try p.err(tok_i, .auto_type_requires_single_declarator, .{}); warned_auto = true; } if (decl_spec.c23_auto) |tok_i| { - try p.errTok(.c23_auto_single_declarator, tok_i); + try p.err(tok_i, .c23_auto_single_declarator, .{}); warned_auto = true; } } @@ -1342,7 +1344,7 @@ fn decl(p: *Parser) Error!bool { .semicolon = p.tok_i - 1, } }); init_d = (try p.initDeclarator(&decl_spec, attr_buf_top, decl_node)) orelse { - try p.err(.expected_ident_or_l_paren); + try p.err(p.tok_i, .expected_ident_or_l_paren, .{}); continue; }; } @@ -1352,33 +1354,32 @@ fn decl(p: *Parser) Error!bool { } fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result) !?[]const u8 { - var buf = std.ArrayList(u8).init(p.gpa); - defer buf.deinit(); + const strings_top = p.strings.items.len; const cond = cond_node.get(&p.tree); if (cond == .builtin_types_compatible_p) { - try buf.appendSlice("'__builtin_types_compatible_p("); + try p.strings.appendSlice("'__builtin_types_compatible_p("); const lhs_ty = cond.builtin_types_compatible_p.lhs; - try lhs_ty.print(p.comp, buf.writer()); - try buf.appendSlice(", "); + try lhs_ty.print(p.comp, p.strings.writer()); + try p.strings.appendSlice(", "); const rhs_ty = cond.builtin_types_compatible_p.rhs; - try rhs_ty.print(p.comp, buf.writer()); + try rhs_ty.print(p.comp, p.strings.writer()); - try buf.appendSlice(")'"); + try p.strings.appendSlice(")'"); } else if (maybe_message == null) return null; if (maybe_message) |message| { assert(message.node.get(&p.tree) == .string_literal_expr); - if (buf.items.len > 0) { - try buf.append(' '); + if (p.strings.items.len > 0) { + try p.strings.append(' '); } const bytes = p.comp.interner.get(message.val.ref()).bytes; - try buf.ensureUnusedCapacity(bytes.len); - try Value.printString(bytes, message.qt, p.comp, buf.writer()); + try p.strings.ensureUnusedCapacity(bytes.len); + try Value.printString(bytes, message.qt, p.comp, p.strings.writer()); } - return try p.comp.diagnostics.arena.allocator().dupe(u8, buf.items); + return p.strings.items[strings_top..]; } /// staticAssert @@ -1400,7 +1401,7 @@ fn staticAssert(p: *Parser) Error!bool { .unterminated_string_literal, => try p.stringLiteral(), else => { - try p.err(.expected_str_literal); + try p.err(p.tok_i, .expected_str_literal, .{}); return error.ParsingFailed; }, } @@ -1409,8 +1410,8 @@ fn staticAssert(p: *Parser) Error!bool { try p.expectClosing(l_paren, .r_paren); _ = try p.expectToken(.semicolon); if (str == null) { - try p.errTok(.static_assert_missing_message, static_assert); - try p.errStr(.pre_c23_compat, static_assert, "'_Static_assert' with no message"); + try p.err(static_assert, .static_assert_missing_message, .{}); + try p.err(static_assert, .pre_c23_compat, .{"'_Static_assert' with no message"}); } const is_int_expr = res.qt.isInvalid() or res.qt.isInt(p.comp); @@ -1420,14 +1421,17 @@ fn staticAssert(p: *Parser) Error!bool { } if (res.val.opt_ref == .none) { if (!res.qt.isInvalid()) { - try p.errTok(.static_assert_not_constant, res_token); + try p.err(res_token, .static_assert_not_constant, .{}); } } else { if (!res.val.toBool(p.comp)) { + const strings_top = p.strings.items.len; + defer p.strings.items.len = strings_top; + if (try p.staticAssertMessage(res_node, str)) |message| { - try p.errStr(.static_assert_failure_message, static_assert, message); + try p.err(static_assert, .static_assert_failure_message, .{message}); } else { - try p.errTok(.static_assert_failure, static_assert); + try p.err(static_assert, .static_assert_failure, .{}); } } } @@ -1463,41 +1467,41 @@ pub const DeclSpec = struct { fn validateParam(d: DeclSpec, p: *Parser) Error!void { switch (d.storage_class) { .none, .register => {}, - .auto, .@"extern", .static, .typedef => |tok_i| try p.errTok(.invalid_storage_on_param, tok_i), + .auto, .@"extern", .static, .typedef => |tok_i| try p.err(tok_i, .invalid_storage_on_param, .{}), } - if (d.thread_local) |tok_i| try p.errTok(.threadlocal_non_var, tok_i); - if (d.@"inline") |tok_i| try p.errStr(.func_spec_non_func, tok_i, "inline"); - if (d.noreturn) |tok_i| try p.errStr(.func_spec_non_func, tok_i, "_Noreturn"); - if (d.constexpr) |tok_i| try p.errTok(.invalid_storage_on_param, tok_i); + if (d.thread_local) |tok_i| try p.err(tok_i, .threadlocal_non_var, .{}); + if (d.@"inline") |tok_i| try p.err(tok_i, .func_spec_non_func, .{"inline"}); + if (d.noreturn) |tok_i| try p.err(tok_i, .func_spec_non_func, .{"_Noreturn"}); + if (d.constexpr) |tok_i| try p.err(tok_i, .invalid_storage_on_param, .{}); } fn validateFnDef(d: DeclSpec, p: *Parser) Error!void { switch (d.storage_class) { .none, .@"extern", .static => {}, - .auto, .register, .typedef => |tok_i| try p.errTok(.illegal_storage_on_func, tok_i), + .auto, .register, .typedef => |tok_i| try p.err(tok_i, .illegal_storage_on_func, .{}), } - if (d.thread_local) |tok_i| try p.errTok(.threadlocal_non_var, tok_i); - if (d.constexpr) |tok_i| try p.errTok(.illegal_storage_on_func, tok_i); + if (d.thread_local) |tok_i| try p.err(tok_i, .threadlocal_non_var, .{}); + if (d.constexpr) |tok_i| try p.err(tok_i, .illegal_storage_on_func, .{}); } fn validateFnDecl(d: DeclSpec, p: *Parser) Error!void { switch (d.storage_class) { .none, .@"extern" => {}, - .static => |tok_i| if (p.func.qt != null) try p.errTok(.static_func_not_global, tok_i), + .static => |tok_i| if (p.func.qt != null) try p.err(tok_i, .static_func_not_global, .{}), .typedef => unreachable, - .auto, .register => |tok_i| try p.errTok(.illegal_storage_on_func, tok_i), + .auto, .register => |tok_i| try p.err(tok_i, .illegal_storage_on_func, .{}), } - if (d.thread_local) |tok_i| try p.errTok(.threadlocal_non_var, tok_i); - if (d.constexpr) |tok_i| try p.errTok(.illegal_storage_on_func, tok_i); + if (d.thread_local) |tok_i| try p.err(tok_i, .threadlocal_non_var, .{}); + if (d.constexpr) |tok_i| try p.err(tok_i, .illegal_storage_on_func, .{}); } fn validateDecl(d: DeclSpec, p: *Parser) Error!void { - if (d.@"inline") |tok_i| try p.errStr(.func_spec_non_func, tok_i, "inline"); + if (d.@"inline") |tok_i| try p.err(tok_i, .func_spec_non_func, .{"inline"}); // TODO move to attribute validation - if (d.noreturn) |tok_i| try p.errStr(.func_spec_non_func, tok_i, "_Noreturn"); + if (d.noreturn) |tok_i| try p.err(tok_i, .func_spec_non_func, .{"_Noreturn"}); switch (d.storage_class) { .auto => std.debug.assert(!p.comp.langopts.standard.atLeast(.c23)), - .register => if (p.func.qt == null) try p.err(.illegal_storage_on_global), + .register => if (p.func.qt == null) try p.err(p.tok_i, .illegal_storage_on_global, .{}), else => {}, } } @@ -1557,7 +1561,7 @@ fn declSpec(p: *Parser) Error!?DeclSpec { switch (id) { .keyword_inline, .keyword_inline1, .keyword_inline2 => { if (d.@"inline" != null) { - try p.errStr(.duplicate_decl_spec, p.tok_i, "inline"); + try p.err(p.tok_i, .duplicate_decl_spec, .{"inline"}); } d.@"inline" = p.tok_i; p.tok_i += 1; @@ -1565,14 +1569,14 @@ fn declSpec(p: *Parser) Error!?DeclSpec { }, .keyword_noreturn => { if (d.noreturn != null) { - try p.errStr(.duplicate_decl_spec, p.tok_i, "_Noreturn"); + try p.err(p.tok_i, .duplicate_decl_spec, .{"_Noreturn"}); } d.noreturn = p.tok_i; p.tok_i += 1; continue; }, .keyword_auto_type => { - try p.errTok(.auto_type_extension, p.tok_i); + try p.err(p.tok_i, .auto_type_extension, .{}); try builder.combine(.auto_type, p.tok_i); if (builder.type == .auto_type) d.auto_type = p.tok_i; p.tok_i += 1; @@ -1623,22 +1627,22 @@ fn storageClassSpec(p: *Parser, d: *DeclSpec) Error!bool { .keyword_register, => { if (d.storage_class != .none) { - try p.errStr(.multiple_storage_class, p.tok_i, @tagName(d.storage_class)); + try p.err(p.tok_i, .multiple_storage_class, .{@tagName(d.storage_class)}); return error.ParsingFailed; } if (d.thread_local != null) { switch (id) { .keyword_extern, .keyword_static => {}, - else => try p.errStr(.cannot_combine_spec, p.tok_i, id.lexeme().?), + else => try p.err(p.tok_i, .cannot_combine_spec, .{id.lexeme().?}), } - if (d.constexpr) |tok| try p.errStr(.cannot_combine_spec, p.tok_i, p.tok_ids[tok].lexeme().?); + if (d.constexpr) |tok| try p.err(p.tok_i, .cannot_combine_spec, .{p.tok_ids[tok].lexeme().?}); } if (d.constexpr != null) { switch (id) { .keyword_auto, .keyword_register, .keyword_static => {}, - else => try p.errStr(.cannot_combine_spec, p.tok_i, id.lexeme().?), + else => try p.err(p.tok_i, .cannot_combine_spec, .{id.lexeme().?}), } - if (d.thread_local) |tok| try p.errStr(.cannot_combine_spec, p.tok_i, p.tok_ids[tok].lexeme().?); + if (d.thread_local) |tok| try p.err(p.tok_i, .cannot_combine_spec, .{p.tok_ids[tok].lexeme().?}); } switch (id) { .keyword_typedef => d.storage_class = .{ .typedef = p.tok_i }, @@ -1653,23 +1657,23 @@ fn storageClassSpec(p: *Parser, d: *DeclSpec) Error!bool { .keyword_c23_thread_local, => { if (d.thread_local != null) { - try p.errStr(.duplicate_decl_spec, p.tok_i, id.lexeme().?); + try p.err(p.tok_i, .duplicate_decl_spec, .{id.lexeme().?}); } - if (d.constexpr) |tok| try p.errStr(.cannot_combine_spec, p.tok_i, p.tok_ids[tok].lexeme().?); + if (d.constexpr) |tok| try p.err(p.tok_i, .cannot_combine_spec, .{p.tok_ids[tok].lexeme().?}); switch (d.storage_class) { .@"extern", .none, .static => {}, - else => try p.errStr(.cannot_combine_spec, p.tok_i, @tagName(d.storage_class)), + else => try p.err(p.tok_i, .cannot_combine_spec, .{@tagName(d.storage_class)}), } d.thread_local = p.tok_i; }, .keyword_constexpr => { if (d.constexpr != null) { - try p.errStr(.duplicate_decl_spec, p.tok_i, id.lexeme().?); + try p.err(p.tok_i, .duplicate_decl_spec, .{id.lexeme().?}); } - if (d.thread_local) |tok| try p.errStr(.cannot_combine_spec, p.tok_i, p.tok_ids[tok].lexeme().?); + if (d.thread_local) |tok| try p.err(p.tok_i, .cannot_combine_spec, .{p.tok_ids[tok].lexeme().?}); switch (d.storage_class) { .auto, .register, .none, .static => {}, - else => try p.errStr(.cannot_combine_spec, p.tok_i, @tagName(d.storage_class)), + else => try p.err(p.tok_i, .cannot_combine_spec, .{@tagName(d.storage_class)}), } d.constexpr = p.tok_i; }, @@ -1696,8 +1700,7 @@ fn attribute(p: *Parser, kind: Attribute.Kind, namespace: ?[]const u8) Error!?Te const name = p.tokSlice(name_tok); const attr = Attribute.fromString(kind, namespace, name) orelse { - const tag: Diagnostics.Tag = if (kind == .declspec) .declspec_attr_not_supported else .unknown_attribute; - try p.errStr(tag, name_tok, name); + try p.err(name_tok, if (kind == .declspec) .declspec_attr_not_supported else .unknown_attribute, .{name}); if (p.eatToken(.l_paren)) |_| p.skipTo(.r_paren); return null; }; @@ -1714,20 +1717,18 @@ fn attribute(p: *Parser, kind: Attribute.Kind, namespace: ?[]const u8) Error!?Te if (Attribute.wantsIdentEnum(attr)) { if (try p.eatIdentifier()) |ident| { - if (Attribute.diagnoseIdent(attr, &arguments, p.tokSlice(ident))) |msg| { - try p.errExtra(msg.tag, ident, msg.extra); + if (try Attribute.diagnoseIdent(attr, &arguments, ident, p)) { p.skipTo(.r_paren); return error.ParsingFailed; } } else { - try p.errExtra(.attribute_requires_identifier, name_tok, .{ .str = name }); + try p.err(name_tok, .attribute_requires_identifier, .{name}); return error.ParsingFailed; } } else { const arg_start = p.tok_i; const first_expr = try p.expect(assignExpr); - if (try p.diagnose(attr, &arguments, arg_idx, first_expr)) |msg| { - try p.errExtra(msg.tag, arg_start, msg.extra); + if (try p.diagnose(attr, &arguments, arg_idx, first_expr, arg_start)) { p.skipTo(.r_paren); return error.ParsingFailed; } @@ -1738,8 +1739,7 @@ fn attribute(p: *Parser, kind: Attribute.Kind, namespace: ?[]const u8) Error!?Te const arg_start = p.tok_i; const arg_expr = try p.expect(assignExpr); - if (try p.diagnose(attr, &arguments, arg_idx, arg_expr)) |msg| { - try p.errExtra(msg.tag, arg_start, msg.extra); + if (try p.diagnose(attr, &arguments, arg_idx, arg_expr, arg_start)) { p.skipTo(.r_paren); return error.ParsingFailed; } @@ -1748,17 +1748,19 @@ fn attribute(p: *Parser, kind: Attribute.Kind, namespace: ?[]const u8) Error!?Te else => {}, } if (arg_idx < required_count) { - try p.errExtra(.attribute_not_enough_args, name_tok, .{ .attr_arg_count = .{ .attribute = attr, .expected = required_count } }); + try p.err(name_tok, .attribute_not_enough_args, .{ + @tagName(attr), required_count, + }); return error.ParsingFailed; } return TentativeAttribute{ .attr = .{ .tag = attr, .args = arguments, .syntax = kind.toSyntax() }, .tok = name_tok }; } -fn diagnose(p: *Parser, attr: Attribute.Tag, arguments: *Attribute.Arguments, arg_idx: u32, res: Result) !?Diagnostics.Message { +fn diagnose(p: *Parser, attr: Attribute.Tag, arguments: *Attribute.Arguments, arg_idx: u32, res: Result, arg_start: TokenIndex) !bool { if (Attribute.wantsAlignment(attr, arg_idx)) { - return Attribute.diagnoseAlignment(attr, arguments, arg_idx, res, p); + return Attribute.diagnoseAlignment(attr, arguments, arg_idx, res, arg_start, p); } - return Attribute.diagnose(attr, arguments, arg_idx, res, res.node.get(&p.tree), p); + return Attribute.diagnose(attr, arguments, arg_idx, res, arg_start, res.node.get(&p.tree), p); } /// attributeList : (attribute (',' attribute)*)? @@ -1846,8 +1848,8 @@ fn attributeSpecifierExtra(p: *Parser, declarator_name: ?TokenIndex) Error!void const attr_buf_top = p.attr_buf.len; if (try p.msvcAttribute()) { if (declarator_name) |name_tok| { - try p.errTok(.declspec_not_allowed_after_declarator, maybe_declspec_tok); - try p.errTok(.declarator_name_tok, name_tok); + try p.err(maybe_declspec_tok, .declspec_not_allowed_after_declarator, .{}); + try p.err(name_tok, .declarator_name_tok, .{}); p.attr_buf.len = attr_buf_top; } continue; @@ -1872,35 +1874,35 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize, decl_no switch (init_d.d.declarator_type) { .func => { if (decl_spec.auto_type) |tok_i| { - try p.errStr(.auto_type_not_allowed, tok_i, "function return type"); + try p.err(tok_i, .auto_type_not_allowed, .{"function return type"}); init_d.d.qt = .invalid; } else if (decl_spec.c23_auto) |tok_i| { - try p.errStr(.c23_auto_not_allowed, tok_i, "function return type"); + try p.err(tok_i, .c23_auto_not_allowed, .{"function return type"}); init_d.d.qt = .invalid; } }, .array => { if (decl_spec.auto_type) |tok_i| { - try p.errStr(.auto_type_array, tok_i, p.tokSlice(init_d.d.name)); + try p.err(tok_i, .auto_type_array, .{p.tokSlice(init_d.d.name)}); init_d.d.qt = .invalid; } else if (decl_spec.c23_auto) |tok_i| { - try p.errStr(.c23_auto_array, tok_i, p.tokSlice(init_d.d.name)); + try p.err(tok_i, .c23_auto_array, .{p.tokSlice(init_d.d.name)}); init_d.d.qt = .invalid; } }, .pointer => { if (decl_spec.auto_type != null or decl_spec.c23_auto != null) { // TODO this is not a hard error in clang - try p.errTok(.auto_type_requires_plain_declarator, p.tok_i); + try p.err(p.tok_i, .auto_type_requires_plain_declarator, .{}); init_d.d.qt = .invalid; } }, .other => if (decl_spec.storage_class == .typedef) { if (decl_spec.auto_type) |tok_i| { - try p.errStr(.auto_type_not_allowed, tok_i, "typedef"); + try p.err(tok_i, .auto_type_not_allowed, .{"typedef"}); init_d.d.qt = .invalid; } else if (decl_spec.c23_auto) |tok_i| { - try p.errStr(.c23_auto_not_allowed, tok_i, "typedef"); + try p.err(tok_i, .c23_auto_not_allowed, .{"typedef"}); init_d.d.qt = .invalid; } }, @@ -1919,11 +1921,11 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize, decl_no if (decl_spec.storage_class == .typedef or (init_d.d.declarator_type == .func and init_d.d.qt.is(p.comp, .func))) { - try p.errTok(.illegal_initializer, eq); + try p.err(eq, .illegal_initializer, .{}); } else if (init_d.d.qt.get(p.comp, .array)) |array_ty| { - if (array_ty.len == .variable) try p.errTok(.vla_init, eq); + if (array_ty.len == .variable) try p.err(eq, .vla_init, .{}); } else if (decl_spec.storage_class == .@"extern") { - try p.err(.extern_initializer); + try p.err(p.tok_i, .extern_initializer, .{}); decl_spec.storage_class = .none; } @@ -1935,7 +1937,7 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize, decl_no if (init_d.d.qt.get(p.comp, .array)) |array_ty| { if (array_ty.len == .incomplete) break :incomplete; } - try p.errStr(.variable_incomplete_ty, init_d.d.name, try p.typeStr(init_d.d.qt)); + try p.err(init_d.d.name, .variable_incomplete_ty, .{init_d.d.qt}); init_d.d.qt = .invalid; } @@ -1979,9 +1981,9 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize, decl_no init_d.d.qt = some.qt.withQualifiers(init_d.d.qt); } else { if (init_d.d.qt.isC23Auto()) { - try p.errStr(.c32_auto_requires_initializer, name, p.tokSlice(name)); + try p.err(name, .c23_auto_requires_initializer, .{p.tokSlice(name)}); } else { - try p.errStr(.auto_type_requires_initializer, name, p.tokSlice(name)); + try p.err(name, .auto_type_requires_initializer, .{p.tokSlice(name)}); } init_d.d.qt = .invalid; return init_d; @@ -2009,7 +2011,7 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize, decl_no switch (init_type) { .array => |array_ty| if (array_ty.len == .incomplete) { // TODO properly check this after finishing parsing - try p.errStr(.tentative_array, name, try p.typeStr(init_d.d.qt)); + try p.err(name, .tentative_array, .{init_d.d.qt}); break :incomplete; }, .@"struct", .@"union" => |record_ty| { @@ -2023,7 +2025,7 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize, decl_no else => {}, } } - try p.errStr(.variable_incomplete_ty, name, try p.typeStr(init_d.d.qt)); + try p.err(name, .variable_incomplete_ty, .{init_d.d.qt}); init_d.d.qt = .invalid; } return init_d; @@ -2083,7 +2085,7 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { .keyword_complex => try builder.combine(.complex, p.tok_i), .keyword_float128_1, .keyword_float128_2 => { if (!p.comp.hasFloat128()) { - try p.errStr(.type_not_supported_on_target, p.tok_i, p.tok_ids[p.tok_i].lexeme().?); + try p.err(p.tok_i, .type_not_supported_on_target, .{p.tok_ids[p.tok_i].lexeme().?}); } try builder.combine(.float128, p.tok_i); }, @@ -2096,13 +2098,13 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { break; }; const base_qt = (try p.typeName()) orelse { - try p.err(.expected_type); + try p.err(p.tok_i, .expected_type, .{}); return error.ParsingFailed; }; try p.expectClosing(l_paren, .r_paren); if (base_qt.isQualified() and !base_qt.isInvalid()) { - try p.errStr(.atomic_qualified, atomic_tok, try p.typeStr(base_qt)); + try p.err(atomic_tok, .atomic_qualified, .{base_qt}); builder.type = .{ .other = .invalid }; continue; } @@ -2119,7 +2121,7 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { const typename_start = p.tok_i; if (try p.typeName()) |inner_qt| { if (!inner_qt.alignable(p.comp)) { - try p.errStr(.invalid_alignof, typename_start, try p.typeStr(inner_qt)); + try p.err(typename_start, .invalid_alignof, .{inner_qt}); } const alignment = Attribute.Alignment{ .requested = inner_qt.alignof(p.comp) }; try p.attr_buf.append(p.gpa, .{ @@ -2133,8 +2135,7 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { const res = try p.integerConstExpr(.no_const_decl_folding); if (!res.val.isZero(p.comp)) { var args = Attribute.initArguments(.aligned, align_tok); - if (try p.diagnose(.aligned, &args, 0, res)) |msg| { - try p.errExtra(msg.tag, arg_start, msg.extra); + if (try p.diagnose(.aligned, &args, 0, res, arg_start)) { p.skipTo(.r_paren); return error.ParsingFailed; } @@ -2165,7 +2166,7 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { var declspec_found = false; if (interned_name == p.string_ids.declspec_id) { - try p.errTok(.declspec_not_enabled, p.tok_i); + try p.err(p.tok_i, .declspec_not_enabled, .{}); p.tok_i += 1; if (p.eatToken(.l_paren)) |_| { p.skipTo(.r_paren); @@ -2180,7 +2181,7 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { if (!builder.combineTypedef(typedef.qt)) break; }, .keyword_bit_int => { - try p.err(.bit_int); + try p.err(p.tok_i, .bit_int, .{}); const bit_int_tok = p.tok_i; p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); @@ -2189,7 +2190,7 @@ fn typeSpec(p: *Parser, builder: *TypeStore.Builder) Error!bool { var bits: u64 = undefined; if (res.val.opt_ref == .none) { - try p.errTok(.expected_integer_constant_expr, bit_int_tok); + try p.err(bit_int_tok, .expected_integer_constant_expr, .{}); return error.ParsingFailed; } else if (res.val.compare(.lte, .zero, p.comp)) { bits = 0; @@ -2219,7 +2220,7 @@ fn getAnonymousName(p: *Parser, kind_tok: TokenIndex) !StringId { }; const str = std.fmt.allocPrint( - p.comp.diagnostics.arena.allocator(), // TODO horrible + p.gpa, // TODO horrible "(anonymous {s} at {s}:{d}:{d})", .{ kind_str, source.path, line_col.line_no, line_col.col }, ) catch unreachable; @@ -2243,7 +2244,7 @@ fn recordSpec(p: *Parser) Error!QualType { const maybe_ident = try p.eatIdentifier(); const l_brace = p.eatToken(.l_brace) orelse { const ident = maybe_ident orelse { - try p.err(.ident_or_l_brace); + try p.err(p.tok_i, .ident_or_l_brace, .{}); return error.ParsingFailed; }; // check if this is a reference to a previous type @@ -2298,8 +2299,8 @@ fn recordSpec(p: *Parser) Error!QualType { const record_ty = prev.qt.getRecord(p.comp).?; if (record_ty.layout != null) { // if the record isn't incomplete, this is a redefinition - try p.errStr(.redefinition, ident, ident_str); - try p.errTok(.previous_definition, prev.tok); + try p.err(ident, .redefinition, .{ident_str}); + try p.err(prev.tok, .previous_definition, .{}); } else { break :blk .{ record_ty, prev.qt }; } @@ -2360,16 +2361,16 @@ fn recordSpec(p: *Parser) Error!QualType { if (p.record.flexible_field) |some| { if (fields.len == 1 and is_struct) { if (p.comp.langopts.emulate == .msvc) { - try p.errTok(.flexible_in_empty_msvc, some); + try p.err(some, .flexible_in_empty_msvc, .{}); } else { - try p.errTok(.flexible_in_empty, some); + try p.err(some, .flexible_in_empty, .{}); } } } if (p.record_buf.items.len == record_buf_top) { - try p.errStr(.empty_record, kind_tok, p.tokSlice(kind_tok)); - try p.errStr(.empty_record_size, kind_tok, p.tokSlice(kind_tok)); + try p.err(kind_tok, .empty_record, .{p.tokSlice(kind_tok)}); + try p.err(kind_tok, .empty_record_size, .{p.tokSlice(kind_tok)}); } try p.expectClosing(l_brace, .r_brace); done = true; @@ -2421,7 +2422,7 @@ fn recordSpec(p: *Parser) Error!QualType { record_ty.fields = fields; record_ty.layout = layout; } else |er| switch (er) { - error.Overflow => try p.errStr(.record_too_large, maybe_ident orelse kind_tok, try p.typeStr(qt)), + error.Overflow => try p.err(maybe_ident orelse kind_tok, .record_too_large, .{qt}), } // Override previous incomplete layout and fields. @@ -2471,7 +2472,7 @@ fn recordDecls(p: *Parser) Error!void { p.extension_suppressed = true; if (try p.parseOrNextDecl(recordDecl)) continue; - try p.err(.expected_type); + try p.err(p.tok_i, .expected_type, .{}); p.nextExternDecl(); continue; } @@ -2496,17 +2497,17 @@ fn recordDecl(p: *Parser) Error!bool { .keyword_auto => { if (!p.comp.langopts.standard.atLeast(.c23)) break; - try p.errStr(.c23_auto_not_allowed, p.tok_i, if (p.record.kind == .keyword_struct) "struct member" else "union member"); + try p.err(p.tok_i, .c23_auto_not_allowed, .{if (p.record.kind == .keyword_struct) "struct member" else "union member"}); try builder.combine(.c23_auto, p.tok_i); }, .keyword_auto_type => { - try p.errTok(.auto_type_extension, p.tok_i); - try p.errStr(.auto_type_not_allowed, p.tok_i, if (p.record.kind == .keyword_struct) "struct member" else "union member"); + try p.err(p.tok_i, .auto_type_extension, .{}); + try p.err(p.tok_i, .auto_type_not_allowed, .{if (p.record.kind == .keyword_struct) "struct member" else "union member"}); try builder.combine(.auto_type, p.tok_i); }, .identifier, .extended_identifier => { if (builder.type != .none) break; - try p.errStr(.unknown_type_name, p.tok_i, p.tokSlice(p.tok_i)); + try p.err(p.tok_i, .unknown_type_name, .{p.tokSlice(p.tok_i)}); builder.type = .{ .other = .invalid }; }, else => break, @@ -2545,15 +2546,15 @@ fn recordDecl(p: *Parser) Error!bool { const bits_tok = p.tok_i; const res = try p.integerConstExpr(.gnu_folding_extension); if (!qt.isInt(p.comp)) { - try p.errStr(.non_int_bitfield, first_tok, try p.typeStr(qt)); + try p.err(first_tok, .non_int_bitfield, .{qt}); break :bits; } if (res.val.opt_ref == .none) { - try p.errTok(.expected_integer_constant_expr, bits_tok); + try p.err(bits_tok, .expected_integer_constant_expr, .{}); break :bits; } else if (res.val.compare(.lt, .zero, p.comp)) { - try p.errStr(.negative_bitwidth, first_tok, try res.str(p)); + try p.err(first_tok, .negative_bitwidth, .{res}); break :bits; } @@ -2561,10 +2562,10 @@ fn recordDecl(p: *Parser) Error!bool { const bit_size = qt.bitSizeofOrNull(p.comp) orelse break :bits; const bits_unchecked = res.val.toInt(u32, p.comp) orelse std.math.maxInt(u32); if (bits_unchecked > bit_size) { - try p.errTok(.bitfield_too_big, name_tok); + try p.err(name_tok, .bitfield_too_big, .{}); break :bits; } else if (bits_unchecked == 0 and name_tok != 0) { - try p.errTok(.zero_width_named_field, name_tok); + try p.err(name_tok, .zero_width_named_field, .{}); break :bits; } @@ -2606,9 +2607,9 @@ fn recordDecl(p: *Parser) Error!bool { else => {}, }; if (error_on_unnamed) { - try p.errTok(.expected_member_name, first_tok); + try p.err(first_tok, .expected_member_name, .{}); } else { - try p.err(.missing_declaration); + try p.err(p.tok_i, .missing_declaration, .{}); } if (p.eatToken(.comma) == null) break; continue; @@ -2637,38 +2638,38 @@ fn recordDecl(p: *Parser) Error!bool { const field_type = qt.base(p.comp); switch (field_type.type) { .func => { - try p.errTok(.func_field, first_tok); + try p.err(first_tok, .func_field, .{}); qt = .invalid; }, .array => |array_ty| switch (array_ty.len) { .static, .unspecified_variable => unreachable, .variable => { - try p.errTok(.vla_field, first_tok); + try p.err(first_tok, .vla_field, .{}); qt = .invalid; }, .fixed => {}, .incomplete => { if (p.record.kind == .keyword_union) { if (p.comp.langopts.emulate == .msvc) { - try p.errTok(.flexible_in_union_msvc, first_tok); + try p.err(first_tok, .flexible_in_union_msvc, .{}); } else { - try p.errTok(.flexible_in_union, first_tok); + try p.err(first_tok, .flexible_in_union, .{}); qt = .invalid; } } if (p.record.flexible_field) |some| { if (p.record.kind == .keyword_struct) { - try p.errTok(.flexible_non_final, some); + try p.err(some, .flexible_non_final, .{}); } } p.record.flexible_field = first_tok; }, }, else => if (field_type.qt.hasIncompleteSize(p.comp)) { - try p.errStr(.field_incomplete_ty, first_tok, try p.typeStr(qt)); + try p.err(first_tok, .field_incomplete_ty, .{qt}); } else if (p.record.flexible_field) |some| { std.debug.assert(some != first_tok); - if (p.record.kind == .keyword_struct) try p.errTok(.flexible_non_final, some); + if (p.record.kind == .keyword_struct) try p.err(some, .flexible_non_final, .{}); }, } } @@ -2680,7 +2681,7 @@ fn recordDecl(p: *Parser) Error!bool { if (p.eatToken(.semicolon) == null) { const tok_id = p.tok_ids[p.tok_i]; if (tok_id == .r_brace) { - try p.err(.missing_semicolon); + try p.err(p.tok_i, .missing_semicolon, .{}); } else { return p.errExpectedToken(.semicolon, tok_id); } @@ -2717,18 +2718,18 @@ fn enumSpec(p: *Parser) Error!QualType { p.tok_i -= 1; break :fixed null; } - try p.err(.expected_type); - try p.errTok(.enum_fixed, colon); + try p.err(p.tok_i, .expected_type, .{}); + try p.err(colon, .enum_fixed, .{}); break :fixed null; }; const fixed_sk = fixed.scalarKind(p.comp); if (fixed_sk == .@"enum" or !fixed_sk.isInt()) { - try p.errStr(.invalid_type_underlying_enum, ty_start, try p.typeStr(fixed)); + try p.err(ty_start, .invalid_type_underlying_enum, .{fixed}); break :fixed null; } - try p.errTok(.enum_fixed, colon); + try p.err(colon, .enum_fixed, .{}); break :fixed fixed; } else null; @@ -2736,7 +2737,7 @@ fn enumSpec(p: *Parser) Error!QualType { const l_brace = p.eatToken(.l_brace) orelse { const ident = maybe_ident orelse { - try p.err(.ident_or_l_brace); + try p.err(p.tok_i, .ident_or_l_brace, .{}); return error.ParsingFailed; }; // check if this is a reference to a previous type @@ -2787,8 +2788,8 @@ fn enumSpec(p: *Parser) Error!QualType { const enum_ty = prev.qt.get(p.comp, .@"enum").?; if (!enum_ty.incomplete) { // if the record isn't incomplete, this is a redefinition - try p.errStr(.redefinition, ident, ident_str); - try p.errTok(.previous_definition, prev.tok); + try p.err(ident, .redefinition, .{ident_str}); + try p.err(prev.tok, .previous_definition, .{}); } else { try p.checkEnumFixedTy(fixed_qt, ident, prev); defined = true; @@ -2831,7 +2832,7 @@ fn enumSpec(p: *Parser) Error!QualType { if (p.eatToken(.comma) == null) break; } - if (p.enum_buf.items.len == enum_buf_top) try p.err(.empty_enum); + if (p.enum_buf.items.len == enum_buf_top) try p.err(p.tok_i, .empty_enum, .{}); try p.expectClosing(l_brace, .r_brace); done = true; try p.attributeSpecifier(); @@ -2920,20 +2921,19 @@ fn checkEnumFixedTy(p: *Parser, fixed_qt: ?QualType, ident_tok: TokenIndex, prev const enum_ty = prev.qt.get(p.comp, .@"enum").?; if (fixed_qt) |some| { if (!enum_ty.fixed) { - try p.errTok(.enum_prev_nonfixed, ident_tok); - try p.errTok(.previous_definition, prev.tok); + try p.err(ident_tok, .enum_prev_nonfixed, .{}); + try p.err(prev.tok, .previous_definition, .{}); return error.ParsingFailed; } if (!enum_ty.tag.?.eql(some, p.comp)) { - const str = try p.typePairStrExtra(some, " (was ", enum_ty.tag.?); - try p.errStr(.enum_different_explicit_ty, ident_tok, str); - try p.errTok(.previous_definition, prev.tok); + try p.err(ident_tok, .enum_different_explicit_ty, .{ some, enum_ty.tag.? }); + try p.err(prev.tok, .previous_definition, .{}); return error.ParsingFailed; } } else if (enum_ty.fixed) { - try p.errTok(.enum_prev_fixed, ident_tok); - try p.errTok(.previous_definition, prev.tok); + try p.err(ident_tok, .enum_prev_fixed, .{}); + try p.err(prev.tok, .previous_definition, .{}); return error.ParsingFailed; } } @@ -2962,16 +2962,16 @@ const Enumerator = struct { } if (try e.val.add(e.val, .one, e.qt, p.comp)) { if (e.fixed) { - try p.errStr(.enum_not_representable_fixed, tok, try p.typeStr(e.qt)); + try p.err(tok, .enum_not_representable_fixed, .{e.qt}); return; } if (p.comp.nextLargestIntSameSign(e.qt)) |larger| { - try p.errTok(.enumerator_overflow, tok); + try p.err(tok, .enumerator_overflow, .{}); e.qt = larger; } else { const signed = e.qt.signedness(p.comp) == .signed; const bit_size: u8 = @intCast(e.qt.bitSizeof(p.comp) - @intFromBool(signed)); - try p.errExtra(.enum_not_representable, tok, .{ .pow_2_as_string = bit_size }); + try p.err(tok, .enum_not_representable, .{std.math.pow(usize, bit_size, 2)}); e.qt = .ulong_long; } _ = try e.val.add(old_val, .one, e.qt, p.comp); @@ -2983,7 +2983,7 @@ const Enumerator = struct { if (res.qt.isInvalid()) return; if (e.fixed and !res.qt.eql(e.qt, p.comp)) { if (!try res.intFitsInType(p, e.qt)) { - try p.errStr(.enum_not_representable_fixed, tok, try p.typeStr(e.qt)); + try p.err(tok, .enum_not_representable_fixed, .{e.qt}); return error.ParsingFailed; } res.qt = e.qt; @@ -3016,7 +3016,7 @@ const Enumerator = struct { } const long_long_width = Type.Int.long_long.bits(p.comp); if (e.num_negative_bits > long_long_width or e.num_positive_bits >= long_long_width) { - try p.errTok(.enum_too_large, tok); + try p.err(tok, .enum_too_large, .{}); } return .long_long; } @@ -3031,14 +3031,6 @@ const Enumerator = struct { } return .ulong_long; } - - fn str(e: *const Enumerator, p: *Parser) ![]const u8 { - return (Result{ - .node = undefined, // Result.str does not use the node - .qt = e.qt, - .val = e.val, - }).str(p); - } }; const EnumFieldAndNode = struct { field: Type.Enum.Field, node: Node.Index }; @@ -3048,7 +3040,7 @@ fn enumerator(p: *Parser, e: *Enumerator) Error!?EnumFieldAndNode { _ = try p.pragma(); const name_tok = (try p.eatIdentifier()) orelse { if (p.tok_ids[p.tok_i] == .r_brace) return null; - try p.err(.expected_identifier); + try p.err(p.tok_i, .expected_identifier, .{}); p.skipTo(.r_brace); return error.ParsingFailed; }; @@ -3056,11 +3048,11 @@ fn enumerator(p: *Parser, e: *Enumerator) Error!?EnumFieldAndNode { defer p.attr_buf.len = attr_buf_top; try p.attributeSpecifier(); - const err_start = p.comp.diagnostics.list.items.len; + const prev_total = p.diagnostics.total; const field_init = if (p.eatToken(.equal)) |_| blk: { var specified = try p.integerConstExpr(.gnu_folding_extension); if (specified.val.opt_ref == .none) { - try p.errTok(.enum_val_unavailable, name_tok + 2); + try p.err(name_tok + 2, .enum_val_unavailable, .{}); try e.incr(p, name_tok); break :blk null; } else { @@ -3078,17 +3070,17 @@ fn enumerator(p: *Parser, e: *Enumerator) Error!?EnumFieldAndNode { e.num_negative_bits = @max(e.num_negative_bits, e.val.minSignedBits(p.comp)); } - if (err_start == p.comp.diagnostics.list.items.len) { + if (prev_total == p.diagnostics.total) { // only do these warnings if we didn't already warn about overflow or non-representable values if (e.val.compare(.lt, .zero, p.comp)) { const min_val = try Value.minInt(.int, p.comp); if (e.val.compare(.lt, min_val, p.comp)) { - try p.errStr(.enumerator_too_small, name_tok, try e.str(p)); + try p.err(name_tok, .enumerator_too_small, .{e}); } } else { const max_val = try Value.maxInt(.int, p.comp); if (e.val.compare(.gt, max_val, p.comp)) { - try p.errStr(.enumerator_too_large, name_tok, try e.str(p)); + try p.err(name_tok, .enumerator_too_large, .{e}); } } } @@ -3120,19 +3112,19 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { switch (p.tok_ids[p.tok_i]) { .keyword_restrict, .keyword_restrict1, .keyword_restrict2 => { if (b.restrict != null) - try p.errStr(.duplicate_decl_spec, p.tok_i, "restrict") + try p.err(p.tok_i, .duplicate_decl_spec, .{"restrict"}) else b.restrict = p.tok_i; }, .keyword_const, .keyword_const1, .keyword_const2 => { if (b.@"const" != null) - try p.errStr(.duplicate_decl_spec, p.tok_i, "const") + try p.err(p.tok_i, .duplicate_decl_spec, .{"const"}) else b.@"const" = p.tok_i; }, .keyword_volatile, .keyword_volatile1, .keyword_volatile2 => { if (b.@"volatile" != null) - try p.errStr(.duplicate_decl_spec, p.tok_i, "volatile") + try p.err(p.tok_i, .duplicate_decl_spec, .{"volatile"}) else b.@"volatile" = p.tok_i; }, @@ -3140,13 +3132,13 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { // _Atomic(typeName) instead of just _Atomic if (p.tok_ids[p.tok_i + 1] == .l_paren) break; if (b.atomic != null) - try p.errStr(.duplicate_decl_spec, p.tok_i, "atomic") + try p.err(p.tok_i, .duplicate_decl_spec, .{"atomic"}) else b.atomic = p.tok_i; }, .keyword_unaligned, .keyword_unaligned2 => { if (b.unaligned != null) - try p.errStr(.duplicate_decl_spec, p.tok_i, "__unaligned") + try p.err(p.tok_i, .duplicate_decl_spec, .{"__unaligned"}) else b.unaligned = p.tok_i; }, @@ -3191,7 +3183,7 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { }, .keyword_nonnull, .keyword_nullable, .keyword_nullable_result, .keyword_null_unspecified => |tok_id| { const sym_str = p.tok_ids[p.tok_i].symbol(); - try p.errStr(.nullability_extension, p.tok_i, sym_str); + try p.err(p.tok_i, .nullability_extension, .{sym_str}); const new: @FieldType(TypeStore.Builder, "nullability") = switch (tok_id) { .keyword_nonnull => .{ .nonnull = p.tok_i }, .keyword_nullable => .{ .nullable = p.tok_i }, @@ -3200,7 +3192,7 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { else => unreachable, }; if (std.meta.activeTag(b.nullability) == new) { - try p.errStr(.duplicate_nullability, p.tok_i, sym_str); + try p.err(p.tok_i, .duplicate_nullability, .{sym_str}); } else switch (b.nullability) { .none => { b.nullability = new; @@ -3221,7 +3213,7 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { .nullable, .nullable_result, .null_unspecified, - => |prev| try p.errExtra(.conflicting_nullability, p.tok_i, .{ .tok_id = .{ .expected = p.tok_ids[p.tok_i], .actual = p.tok_ids[prev] } }), + => |prev| try p.err(p.tok_i, .conflicting_nullability, .{ p.tok_ids[p.tok_i], p.tok_ids[prev] }), } }, else => break, @@ -3282,7 +3274,7 @@ const Declarator = struct { if (child_res != .normal) return child_res; if (elem_qt.hasIncompleteSize(p.comp)) { - try p.errStr(.array_incomplete_elem, source_tok, try p.typeStr(elem_qt)); + try p.err(source_tok, .array_incomplete_elem, .{elem_qt}); return .nested_invalid; } switch (array_ty.len) { @@ -3290,7 +3282,7 @@ const Declarator = struct { const elem_size = elem_qt.sizeofOrNull(p.comp) orelse 1; const max_elems = p.comp.maxArrayBytes() / @max(1, elem_size); if (len > max_elems) { - try p.errTok(.array_too_large, source_tok); + try p.err(source_tok, .array_too_large, .{}); return .nested_invalid; } }, @@ -3298,15 +3290,15 @@ const Declarator = struct { } if (elem_qt.is(p.comp, .func)) { - try p.errTok(.array_func_elem, source_tok); + try p.err(source_tok, .array_func_elem, .{}); return .nested_invalid; } if (elem_qt.get(p.comp, .array)) |elem_array_ty| { if (elem_array_ty.len == .static) { - try p.errTok(.static_non_outermost_array, source_tok); + try p.err(source_tok, .static_non_outermost_array, .{}); } if (elem_qt.isQualified()) { - try p.errTok(.qualifier_non_outermost_array, source_tok); + try p.err(source_tok, .qualifier_non_outermost_array, .{}); } } return .normal; @@ -3316,17 +3308,17 @@ const Declarator = struct { const child_res = try validateExtra(p, ret_qt, source_tok); if (child_res != .normal) return child_res; - if (ret_qt.is(p.comp, .array)) try p.errTok(.func_cannot_return_array, source_tok); - if (ret_qt.is(p.comp, .func)) try p.errTok(.func_cannot_return_func, source_tok); + if (ret_qt.is(p.comp, .array)) try p.err(source_tok, .func_cannot_return_array, .{}); + if (ret_qt.is(p.comp, .func)) try p.err(source_tok, .func_cannot_return_func, .{}); if (ret_qt.@"const") { - try p.errStr(.qual_on_ret_type, source_tok, "const"); + try p.err(source_tok, .qual_on_ret_type, .{"const"}); } if (ret_qt.@"volatile") { - try p.errStr(.qual_on_ret_type, source_tok, "volatile"); + try p.err(source_tok, .qual_on_ret_type, .{"volatile"}); } if (ret_qt.get(p.comp, .float)) |float| { if (float == .fp16 and !p.comp.hasHalfPrecisionFloatABI()) { - try p.errStr(.suggest_pointer_for_invalid_fp16, source_tok, "function return value"); + try p.err(source_tok, .suggest_pointer_for_invalid_fp16, .{"function return value"}); } } return .normal; @@ -3432,7 +3424,7 @@ fn declarator( else => |ty| switch (ty) { .@"enum", .@"struct", .@"union" => break, else => { - try p.errTok(.expected_ident_or_l_paren, expected_ident); + try p.err(expected_ident, .expected_ident_or_l_paren, .{}); return error.ParsingFailed; }, }, @@ -3472,7 +3464,7 @@ fn directDeclarator( }, .param, .abstract => {}, } - try p.err(.expected_expr); + try p.err(p.tok_i, .expected_expr, .{}); return error.ParsingFailed; } @@ -3492,15 +3484,15 @@ fn directDeclarator( try p.expectClosing(l_bracket, .r_bracket); if (star != null and static != null) { - try p.errTok(.invalid_static_star, static.?); + try p.err(static.?, .invalid_static_star, .{}); static = null; } if (kind != .param) { if (static != null) - try p.errTok(.static_non_param, l_bracket) + try p.err(l_bracket, .static_non_param, .{}) else if (got_quals) - try p.errTok(.array_qualifiers, l_bracket); - if (star) |some| try p.errTok(.star_non_param, some); + try p.err(l_bracket, .array_qualifiers, .{}); + if (star) |some| try p.err(some, .star_non_param, .{}); static = null; builder = .{ .parser = p }; star = null; @@ -3514,15 +3506,15 @@ fn directDeclarator( base_declarator.declarator_type = .array; if (opt_size != null and !opt_size.?.qt.isInt(p.comp)) { - try p.errStr(.array_size_non_int, size_tok, try p.typeStr(opt_size.?.qt)); + try p.err(size_tok, .array_size_non_int, .{opt_size.?.qt}); return error.ParsingFailed; } if (opt_size) |size| { if (size.val.opt_ref == .none) { - try p.errTok(.vla, size_tok); + try p.err(size_tok, .vla, .{}); if (p.func.qt == null and kind != .param and p.record.kind == .invalid) { - try p.errTok(.variable_len_array_file_scope, base_declarator.name); + try p.err(base_declarator.name, .variable_len_array_file_scope, .{}); } const array_qt = try p.comp.type_store.put(p.gpa, .{ .array = .{ @@ -3530,13 +3522,13 @@ fn directDeclarator( .len = .{ .variable = size.node }, } }); - if (static) |some| try p.errTok(.useless_static, some); + if (static) |some| try p.err(some, .useless_static, .{}); return builder.finishQuals(array_qt); } else { if (size.val.isZero(p.comp)) { - try p.errTok(.zero_length_array, l_bracket); + try p.err(l_bracket, .zero_length_array, .{}); } else if (size.val.compare(.lt, .zero, p.comp)) { - try p.errTok(.negative_array_size, l_bracket); + try p.err(l_bracket, .negative_array_size, .{}); return error.ParsingFailed; } @@ -3571,7 +3563,7 @@ fn directDeclarator( }; if (p.eatToken(.ellipsis)) |_| { - try p.err(.param_before_var_args); + try p.err(p.tok_i, .param_before_var_args, .{}); try p.expectClosing(l_paren, .r_paren); func_ty.kind = .variadic; @@ -3617,7 +3609,7 @@ fn directDeclarator( } func_ty.params = p.param_buf.items[param_buf_top..]; } else { - try p.err(.expected_param_decl); + try p.err(p.tok_i, .expected_param_decl, .{}); } try p.expectClosing(l_paren, .r_paren); @@ -3651,7 +3643,7 @@ fn paramDecls(p: *Parser) Error!?[]Type.Func.Param { { // handle deprecated K&R style parameters const identifier = try p.expectIdentifier(); - try p.errStr(.unknown_type_name, identifier, p.tokSlice(identifier)); + try p.err(identifier, .unknown_type_name, .{p.tokSlice(identifier)}); try p.param_buf.append(.{ .name = try p.comp.internString(p.tokSlice(identifier)), @@ -3666,7 +3658,7 @@ fn paramDecls(p: *Parser) Error!?[]Type.Func.Param { } else if (p.param_buf.items.len == param_buf_top) { return null; } else blk: { - try p.err(.missing_type_specifier); + try p.err(p.tok_i, .missing_type_specifier, .{}); break :blk DeclSpec{ .qt = .int }; }; @@ -3675,16 +3667,16 @@ fn paramDecls(p: *Parser) Error!?[]Type.Func.Param { const first_tok = p.tok_i; var param_qt = param_decl_spec.qt; if (param_decl_spec.auto_type) |tok_i| { - try p.errStr(.auto_type_not_allowed, tok_i, "function prototype"); + try p.err(tok_i, .auto_type_not_allowed, .{"function prototype"}); param_qt = .invalid; } if (param_decl_spec.c23_auto) |tok_i| { - try p.errStr(.c23_auto_not_allowed, tok_i, "function prototype"); + try p.err(tok_i, .c23_auto_not_allowed, .{"function prototype"}); param_qt = .invalid; } if (try p.declarator(param_qt, .param)) |some| { - if (some.old_style_func) |tok_i| try p.errTok(.invalid_old_style_params, tok_i); + if (some.old_style_func) |tok_i| try p.err(tok_i, .invalid_old_style_params, .{}); try p.attributeSpecifier(); name_tok = some.name; param_qt = some.qt; @@ -3694,13 +3686,13 @@ fn paramDecls(p: *Parser) Error!?[]Type.Func.Param { // validate void parameters if (p.param_buf.items.len == param_buf_top) { if (p.tok_ids[p.tok_i] != .r_paren) { - try p.err(.void_only_param); - if (param_qt.isQualified()) try p.err(.void_param_qualified); + try p.err(p.tok_i, .void_only_param, .{}); + if (param_qt.isQualified()) try p.err(p.tok_i, .void_param_qualified, .{}); return error.ParsingFailed; } return &.{}; } - try p.err(.void_must_be_first_param); + try p.err(p.tok_i, .void_must_be_first_param, .{}); return error.ParsingFailed; } else { // Decay params declared as functions or arrays to pointer. @@ -3711,7 +3703,7 @@ fn paramDecls(p: *Parser) Error!?[]Type.Func.Param { if (param_qt.get(p.comp, .float)) |float| { if (float == .fp16 and !p.comp.hasHalfPrecisionFloatABI()) { - try p.errStr(.suggest_pointer_for_invalid_fp16, first_tok, "parameters"); + try p.err(first_tok, .suggest_pointer_for_invalid_fp16, .{"parameters"}); } } @@ -3752,7 +3744,7 @@ fn typeName(p: *Parser) Error!?QualType { defer p.attr_buf.len = attr_buf_top; const ty = (try p.specQual()) orelse return null; if (try p.declarator(ty, .abstract)) |some| { - if (some.old_style_func) |tok_i| try p.errTok(.invalid_old_style_params, tok_i); + if (some.old_style_func) |tok_i| try p.err(tok_i, .invalid_old_style_params, .{}); return try Attribute.applyTypeAttributes(p, some.qt, attr_buf_top, .align_ignored); } return try Attribute.applyTypeAttributes(p, ty, attr_buf_top, .align_ignored); @@ -3768,7 +3760,7 @@ fn complexInitializer(p: *Parser, init_qt: QualType) Error!Result { } const l_brace = p.tok_i; p.tok_i += 1; - try p.errTok(.complex_component_init, l_brace); + try p.err(l_brace, .complex_component_init, .{}); const first_tok = p.tok_i; var first = try p.expect(assignExpr); @@ -3787,14 +3779,14 @@ fn complexInitializer(p: *Parser, init_qt: QualType) Error!Result { if (p.tok_ids[p.tok_i] == .r_brace) break; extra_tok = p.tok_i; if ((try p.assignExpr()) == null) { - try p.errTok(.expected_expr, p.tok_i); + try p.err(p.tok_i, .expected_expr, .{}); p.skipTo(.r_brace); return error.ParsingFailed; } } try p.expectClosing(l_brace, .r_brace); if (extra_tok) |tok| { - try p.errTok(.excess_scalar_init, tok); + try p.err(tok, .excess_scalar_init, .{}); } var res: Result = .{ @@ -3839,10 +3831,10 @@ fn initializer(p: *Parser, init_qt: QualType) Error!Result { // invalidly inferred. var final_init_qt = init_qt; if (init_qt.isAutoType()) { - try p.err(.auto_type_with_init_list); + try p.err(p.tok_i, .auto_type_with_init_list, .{}); final_init_qt = .invalid; } else if (init_qt.isC23Auto()) { - try p.err(.c23_auto_with_init_list); + try p.err(p.tok_i, .c23_auto_with_init_list, .{}); final_init_qt = .invalid; } @@ -3871,8 +3863,8 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { const arr = try p.coerceArrayInit(&res, tok, init_qt); if (!arr) try p.coerceInit(&res, tok, init_qt); if (il.tok != 0 and !init_qt.isInvalid()) { - try p.errTok(.initializer_overrides, tok); - try p.errTok(.previous_initializer, il.tok); + try p.err(tok, .initializer_overrides, .{}); + try p.err(il.tok, .previous_initializer, .{}); } il.node = .pack(res.node); il.tok = tok; @@ -3887,10 +3879,10 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { const scalar_inits_needed: usize = if (is_complex) 2 else 1; if (p.eatToken(.r_brace)) |_| { - if (is_scalar) try p.errTok(.empty_scalar_init, l_brace); + if (is_scalar) try p.err(l_brace, .empty_scalar_init, .{}); if (il.tok != 0 and !init_qt.isInvalid()) { - try p.errTok(.initializer_overrides, l_brace); - try p.errTok(.previous_initializer, il.tok); + try p.err(l_brace, .initializer_overrides, .{}); + try p.err(il.tok, .previous_initializer, .{}); } il.node = .null; il.tok = l_brace; @@ -3912,7 +3904,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { while (true) { if (p.eatToken(.l_bracket)) |l_bracket| { const array_ty = cur_qt.get(p.comp, .array) orelse { - try p.errStr(.invalid_array_designator, l_bracket, try p.typeStr(cur_qt)); + try p.err(l_bracket, .invalid_array_designator, .{cur_qt}); return error.ParsingFailed; }; const expr_tok = p.tok_i; @@ -3922,10 +3914,10 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { if (cur_qt.isInvalid()) continue; if (index_res.val.opt_ref == .none) { - try p.errTok(.expected_integer_constant_expr, expr_tok); + try p.err(expr_tok, .expected_integer_constant_expr, .{}); return error.ParsingFailed; } else if (index_res.val.compare(.lt, .zero, p.comp)) { - try p.errStr(.negative_array_designator, l_bracket + 1, try index_res.str(p)); + try p.err(l_bracket + 1, .negative_array_designator, .{index_res}); return error.ParsingFailed; } @@ -3935,7 +3927,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { }; const index_int = index_res.val.toInt(u64, p.comp) orelse std.math.maxInt(u64); if (index_int >= max_len) { - try p.errStr(.oob_array_designator, l_bracket + 1, try index_res.str(p)); + try p.err(l_bracket + 1, .oob_array_designator, .{index_res}); return error.ParsingFailed; } cur_index_hint = cur_index_hint orelse index_int; @@ -3950,11 +3942,11 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { const field_str = p.tokSlice(field_tok); const target_name = try p.comp.internString(field_str); const record_ty = cur_qt.getRecord(p.comp) orelse { - try p.errStr(.invalid_field_designator, period, try p.typeStr(cur_qt)); + try p.err(period, .invalid_field_designator, .{cur_qt}); return error.ParsingFailed; }; if (!record_ty.hasField(p.comp, target_name)) { - try p.errStr(.no_such_field_designator, period, field_str); + try p.err(period, .no_such_field_designator, .{field_str}); return error.ParsingFailed; } @@ -3982,12 +3974,12 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { if (designation) { if (p.eatToken(.equal) == null) { - try p.err(.gnu_missing_eq_designator); + try p.err(p.tok_i, .gnu_missing_eq_designator, .{}); } } if (!designation and cur_qt.hasAttribute(p.comp, .designated_init)) { - try p.err(.designated_init_needed); + try p.err(p.tok_i, .designated_init_needed, .{}); } var saw = false; @@ -4015,7 +4007,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { var tmp_il: InitList = .{}; defer tmp_il.deinit(p.gpa); saw = try p.initializerItem(&tmp_il, .void); - if (!warned_excess) try p.errTok(if (init_qt.is(p.comp, .array)) .excess_array_init else .excess_struct_init, first_tok); + if (!warned_excess) try p.err(first_tok, if (init_qt.is(p.comp, .array)) .excess_array_init else .excess_struct_init, .{}); warned_excess = true; } } else single_item: { @@ -4032,7 +4024,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { } else if (try p.findScalarInitializer(&cur_il, &cur_qt, &res, first_tok)) break :excess; if (designation) break :excess; - if (!warned_excess) try p.errTok(if (init_qt.is(p.comp, .array)) .excess_array_init else .excess_struct_init, first_tok); + if (!warned_excess) try p.err(first_tok, if (init_qt.is(p.comp, .array)) .excess_array_init else .excess_struct_init, .{}); warned_excess = true; break :single_item; @@ -4041,8 +4033,8 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { const arr = try p.coerceArrayInit(&res, first_tok, cur_qt); if (!arr) try p.coerceInit(&res, first_tok, cur_qt); if (cur_il.tok != 0 and !init_qt.isInvalid()) { - try p.errTok(.initializer_overrides, first_tok); - try p.errTok(.previous_initializer, cur_il.tok); + try p.err(first_tok, .initializer_overrides, .{}); + try p.err(cur_il.tok, .previous_initializer, .{}); } cur_il.node = .pack(res.node); cur_il.tok = first_tok; @@ -4050,15 +4042,15 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { if (!saw) { if (designation) { - try p.err(.expected_expr); + try p.err(p.tok_i, .expected_expr, .{}); return error.ParsingFailed; } break; } else if (count == 1) { - if (is_str_init) try p.errTok(.excess_str_init, first_tok); - if (is_scalar and !is_complex) try p.errTok(.excess_scalar_init, first_tok); + if (is_str_init) try p.err(first_tok, .excess_str_init, .{}); + if (is_scalar and !is_complex) try p.err(first_tok, .excess_scalar_init, .{}); } else if (count == 2) { - if (is_scalar and !is_complex) try p.errTok(.excess_scalar_init, first_tok); + if (is_scalar and !is_complex) try p.err(first_tok, .excess_scalar_init, .{}); } if (p.eatToken(.comma) == null) break; @@ -4066,12 +4058,12 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { try p.expectClosing(l_brace, .r_brace); if (is_scalar and is_complex and count == 1) { // count of 1 means we saw exactly 2 items in the initializer list - try p.errTok(.complex_component_init, l_brace); + try p.err(l_brace, .complex_component_init, .{}); } if (is_scalar or is_str_init) return true; if (il.tok != 0 and !init_qt.isInvalid()) { - try p.errTok(.initializer_overrides, l_brace); - try p.errTok(.previous_initializer, il.tok); + try p.err(l_brace, .initializer_overrides, .{}); + try p.err(il.tok, .previous_initializer, .{}); } il.node = .null; il.tok = l_brace; @@ -4090,7 +4082,7 @@ fn findScalarInitializerAt(p: *Parser, il: **InitList, qt: *QualType, res: *Resu else => std.math.maxInt(u64), }; if (max_len == 0) { - try p.errTok(.empty_aggregate_init_braces, first_tok); + try p.err(first_tok, .empty_aggregate_init_braces, .{}); return error.ParsingFailed; } @@ -4108,7 +4100,7 @@ fn findScalarInitializerAt(p: *Parser, il: **InitList, qt: *QualType, res: *Resu start_index.* += 1; if (struct_ty.fields.len == 0) { - try p.errTok(.empty_aggregate_init_braces, first_tok); + try p.err(first_tok, .empty_aggregate_init_braces, .{}); return error.ParsingFailed; } const struct_il = il.*; @@ -4145,7 +4137,7 @@ fn findScalarInitializer(p: *Parser, il: **InitList, qt: *QualType, res: *Result else => std.math.maxInt(u64), }; if (max_len == 0) { - try p.errTok(.empty_aggregate_init_braces, first_tok); + try p.err(first_tok, .empty_aggregate_init_braces, .{}); return error.ParsingFailed; } @@ -4166,7 +4158,7 @@ fn findScalarInitializer(p: *Parser, il: **InitList, qt: *QualType, res: *Result var index = if (start_index != 0) il.*.list.items[start_index - 1].index + 1 else start_index; if (struct_ty.fields.len == 0) { - try p.errTok(.empty_aggregate_init_braces, first_tok); + try p.err(first_tok, .empty_aggregate_init_braces, .{}); return error.ParsingFailed; } const struct_il = il.*; @@ -4185,7 +4177,7 @@ fn findScalarInitializer(p: *Parser, il: **InitList, qt: *QualType, res: *Result if (il.*.node != .null) return false; if (actual_qt.eql(qt.*, p.comp)) return true; if (union_ty.fields.len == 0) { - try p.errTok(.empty_aggregate_init_braces, first_tok); + try p.err(first_tok, .empty_aggregate_init_braces, .{}); return error.ParsingFailed; } @@ -4255,7 +4247,7 @@ fn findAggregateInitializer(p: *Parser, il: **InitList, qt: *QualType, start_ind return true; }, else => { - try p.err(.too_many_scalar_init_braces); + try p.err(p.tok_i, .too_many_scalar_init_braces, .{}); return il.*.node == .null; }, } @@ -4273,7 +4265,7 @@ fn coerceArrayInitExtra(p: *Parser, item: *Result, tok: TokenIndex, target: Qual const maybe_item_array_ty = item.qt.get(p.comp, .array); if (!is_str_lit and (!p.nodeIs(item.node, .compound_literal_expr) or maybe_item_array_ty == null)) { if (!report_err) return false; - try p.errTok(.array_init_str, tok); + try p.err(tok, .array_init_str, .{}); return true; // do not do further coercion } @@ -4288,8 +4280,7 @@ fn coerceArrayInitExtra(p: *Parser, item: *Result, tok: TokenIndex, target: Qual (is_str_lit and item_int == .uchar and (target_int == .uchar or target_int == .schar or target_int == .char)); if (!compatible) { if (!report_err) return false; - const e_msg = " with array of type "; - try p.errStr(.incompatible_array_init, tok, try p.typePairStrExtra(target, e_msg, item.qt)); + try p.err(tok, .incompatible_array_init, .{ target, item.qt }); return true; // do not do further coercion } @@ -4303,14 +4294,10 @@ fn coerceArrayInitExtra(p: *Parser, item: *Result, tok: TokenIndex, target: Qual if (is_str_lit) { // the null byte of a string can be dropped if (item_len - 1 > target_len and report_err) { - try p.errTok(.str_init_too_long, tok); + try p.err(tok, .str_init_too_long, .{}); } } else if (item_len > target_len and report_err) { - try p.errStr( - .arr_init_too_long, - tok, - try p.typePairStrExtra(target, " with array of type ", item.qt), - ); + try p.err(tok, .arr_init_too_long, .{ target, item.qt }); } } return true; @@ -4322,7 +4309,7 @@ fn coerceInit(p: *Parser, item: *Result, tok: TokenIndex, target: QualType) !voi const node = item.node; if (target.isAutoType() or target.isC23Auto()) { if (p.getNode(node, .member_access_expr) orelse p.getNode(node, .member_access_ptr_expr)) |access| { - if (access.isBitFieldWidth(&p.tree) != null) try p.errTok(.auto_type_from_bitfield, tok); + if (access.isBitFieldWidth(&p.tree) != null) try p.err(tok, .auto_type_from_bitfield, .{}); } try item.lvalConversion(p, tok); return; @@ -4330,13 +4317,13 @@ fn coerceInit(p: *Parser, item: *Result, tok: TokenIndex, target: QualType) !voi try item.coerce(p, target, tok, .init); if (item.val.opt_ref == .none) runtime: { - const tag: Diagnostics.Tag = switch (p.init_context) { + const diagnostic: Diagnostic = switch (p.init_context) { .runtime => break :runtime, .constexpr => .constexpr_requires_const, .static => break :runtime, // TODO: set this to .non_constant_initializer once we are capable of saving all valid values }; p.init_context = .runtime; // Suppress further "non-constant initializer" errors - try p.errTok(tag, tok); + try p.err(tok, diagnostic, .{}); } if (target.@"const" or p.init_context == .constexpr) { var copy = item.*; @@ -4420,7 +4407,7 @@ fn convertInitList(p: *Parser, il: InitList, init_qt: QualType) Error!Node.Index const max_elems = p.comp.maxArrayBytes() / (@max(1, elem_ty.sizeofOrNull(p.comp) orelse 1)); if (start > max_elems) { - try p.errTok(.array_too_large, il.tok); + try p.err(il.tok, .array_too_large, .{}); start = max_elems; } @@ -4518,7 +4505,7 @@ fn msvcAsmStmt(p: *Parser) Error!?Node.Index { fn asmOperand(p: *Parser, names: *std.ArrayList(?TokenIndex), constraints: *NodeList, exprs: *NodeList) Error!void { if (p.eatToken(.l_bracket)) |l_bracket| { const ident = (try p.eatIdentifier()) orelse { - try p.err(.expected_identifier); + try p.err(p.tok_i, .expected_identifier, .{}); return error.ParsingFailed; }; try names.append(ident); @@ -4530,7 +4517,7 @@ fn asmOperand(p: *Parser, names: *std.ArrayList(?TokenIndex), constraints: *Node try constraints.append(constraint.node); const l_paren = p.eatToken(.l_paren) orelse { - try p.errExtra(.expected_token, p.tok_i, .{ .tok_id = .{ .actual = p.tok_ids[p.tok_i], .expected = .l_paren } }); + try p.err(p.tok_i, .expected_token, .{ p.tok_ids[p.tok_i], .l_paren }); return error.ParsingFailed; }; const maybe_res = try p.expr(); @@ -4629,7 +4616,7 @@ fn gnuAsmStmt(p: *Parser, quals: Tree.GNUAssemblyQualifiers, asm_tok: TokenIndex } if (!quals.goto and (p.tok_ids[p.tok_i] != .r_paren or ate_extra_colon)) { - try p.errExtra(.expected_token, p.tok_i, .{ .tok_id = .{ .actual = p.tok_ids[p.tok_i], .expected = .r_paren } }); + try p.err(p.tok_i, .expected_token, .{ p.tok_ids[p.tok_i], Tree.Token.Id.r_paren }); return error.ParsingFailed; } @@ -4641,7 +4628,7 @@ fn gnuAsmStmt(p: *Parser, quals: Tree.GNUAssemblyQualifiers, asm_tok: TokenIndex } while (true) { const ident = (try p.eatIdentifier()) orelse { - try p.err(.expected_identifier); + try p.err(p.tok_i, .expected_identifier, .{}); return error.ParsingFailed; }; const ident_str = p.tokSlice(ident); @@ -4663,7 +4650,7 @@ fn gnuAsmStmt(p: *Parser, quals: Tree.GNUAssemblyQualifiers, asm_tok: TokenIndex if (p.eatToken(.comma) == null) break; } } else if (quals.goto) { - try p.errExtra(.expected_token, p.tok_i, .{ .tok_id = .{ .actual = p.tok_ids[p.tok_i], .expected = .colon } }); + try p.err(p.tok_i, .expected_token, .{ p.tok_ids[p.tok_i], .colon }); return error.ParsingFailed; } @@ -4679,7 +4666,7 @@ fn checkAsmStr(p: *Parser, asm_str: Value, tok: TokenIndex) !void { const str = p.comp.interner.get(asm_str.ref()).bytes; if (str.len > 1) { // Empty string (just a NUL byte) is ok because it does not emit any assembly - try p.errTok(.gnu_asm_disabled, tok); + try p.err(tok, .gnu_asm_disabled, .{}); } } } @@ -4692,7 +4679,7 @@ fn assembly(p: *Parser, kind: enum { global, decl_label, stmt }) Error!?Node.Ind const asm_tok = p.tok_i; switch (p.tok_ids[p.tok_i]) { .keyword_asm => { - try p.err(.extension_token_used); + try p.err(p.tok_i, .extension_token_used, .{}); p.tok_i += 1; }, .keyword_asm1, .keyword_asm2 => p.tok_i += 1, @@ -4706,18 +4693,18 @@ fn assembly(p: *Parser, kind: enum { global, decl_label, stmt }) Error!?Node.Ind var quals: Tree.GNUAssemblyQualifiers = .{}; while (true) : (p.tok_i += 1) switch (p.tok_ids[p.tok_i]) { .keyword_volatile, .keyword_volatile1, .keyword_volatile2 => { - if (kind != .stmt) try p.errStr(.meaningless_asm_qual, p.tok_i, "volatile"); - if (quals.@"volatile") try p.errStr(.duplicate_asm_qual, p.tok_i, "volatile"); + if (kind != .stmt) try p.err(p.tok_i, .meaningless_asm_qual, .{"volatile"}); + if (quals.@"volatile") try p.err(p.tok_i, .duplicate_asm_qual, .{"volatile"}); quals.@"volatile" = true; }, .keyword_inline, .keyword_inline1, .keyword_inline2 => { - if (kind != .stmt) try p.errStr(.meaningless_asm_qual, p.tok_i, "inline"); - if (quals.@"inline") try p.errStr(.duplicate_asm_qual, p.tok_i, "inline"); + if (kind != .stmt) try p.err(p.tok_i, .meaningless_asm_qual, .{"inline"}); + if (quals.@"inline") try p.err(p.tok_i, .duplicate_asm_qual, .{"inline"}); quals.@"inline" = true; }, .keyword_goto => { - if (kind != .stmt) try p.errStr(.meaningless_asm_qual, p.tok_i, "goto"); - if (quals.goto) try p.errStr(.duplicate_asm_qual, p.tok_i, "goto"); + if (kind != .stmt) try p.err(p.tok_i, .meaningless_asm_qual, .{"goto"}); + if (quals.goto) try p.err(p.tok_i, .duplicate_asm_qual, .{"goto"}); quals.goto = true; }, else => break, @@ -4757,16 +4744,16 @@ fn asmStr(p: *Parser) Error!Result { while (true) : (i += 1) switch (p.tok_ids[i]) { .string_literal, .unterminated_string_literal => {}, .string_literal_utf_16, .string_literal_utf_8, .string_literal_utf_32 => { - try p.errStr(.invalid_asm_str, p.tok_i, "unicode"); + try p.err(p.tok_i, .invalid_asm_str, .{"unicode"}); return error.ParsingFailed; }, .string_literal_wide => { - try p.errStr(.invalid_asm_str, p.tok_i, "wide"); + try p.err(p.tok_i, .invalid_asm_str, .{"wide"}); return error.ParsingFailed; }, else => { if (i == p.tok_i) { - try p.errStr(.expected_str_literal_in, p.tok_i, "asm"); + try p.err(p.tok_i, .expected_str_literal_in, .{"asm"}); return error.ParsingFailed; } break; @@ -4802,7 +4789,7 @@ fn stmt(p: *Parser) Error!Node.Index { try cond.lvalConversion(p, cond_tok); try cond.usualUnaryConversion(p, cond_tok); if (!cond.qt.isInvalid() and cond.qt.scalarKind(p.comp) == .none) - try p.errStr(.statement_scalar, l_paren + 1, try p.typeStr(cond.qt)); + try p.err(l_paren + 1, .statement_scalar, .{cond.qt}); try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); @@ -4816,8 +4803,8 @@ fn stmt(p: *Parser) Error!Node.Index { const if_loc = locs[kw_if]; const semicolon_loc = locs[semicolon_tok]; if (if_loc.line == semicolon_loc.line) { - try p.errTok(.empty_if_body, semicolon_tok); - try p.errTok(.empty_if_body_note, semicolon_tok); + try p.err(semicolon_tok, .empty_if_body, .{}); + try p.err(semicolon_tok, .empty_if_body_note, .{}); } } @@ -4836,7 +4823,7 @@ fn stmt(p: *Parser) Error!Node.Index { try cond.usualUnaryConversion(p, cond_tok); if (!cond.qt.isInvalid() and !cond.qt.isInt(p.comp)) - try p.errStr(.statement_int, l_paren + 1, try p.typeStr(cond.qt)); + try p.err(l_paren + 1, .statement_int, .{cond.qt}); try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); @@ -4868,7 +4855,7 @@ fn stmt(p: *Parser) Error!Node.Index { try cond.lvalConversion(p, cond_tok); try cond.usualUnaryConversion(p, cond_tok); if (!cond.qt.isInvalid() and cond.qt.scalarKind(p.comp) == .none) - try p.errStr(.statement_scalar, l_paren + 1, try p.typeStr(cond.qt)); + try p.err(l_paren + 1, .statement_scalar, .{cond.qt}); try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); @@ -4903,7 +4890,7 @@ fn stmt(p: *Parser) Error!Node.Index { try cond.usualUnaryConversion(p, cond_tok); if (!cond.qt.isInvalid() and cond.qt.scalarKind(p.comp) == .none) - try p.errStr(.statement_scalar, l_paren + 1, try p.typeStr(cond.qt)); + try p.err(l_paren + 1, .statement_scalar, .{cond.qt}); try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); @@ -4926,13 +4913,13 @@ fn stmt(p: *Parser) Error!Node.Index { // for (init const init_start = p.tok_i; - var err_start = p.comp.diagnostics.list.items.len; + var prev_total = p.diagnostics.total; const init = init: { if (got_decl) break :init null; var init = (try p.expr()) orelse break :init null; try init.saveValue(p); - try init.maybeWarnUnused(p, init_start, err_start); + try init.maybeWarnUnused(p, init_start, prev_total); break :init init.node; }; if (!got_decl) _ = try p.expectToken(.semicolon); @@ -4945,7 +4932,7 @@ fn stmt(p: *Parser) Error!Node.Index { try cond.lvalConversion(p, cond_tok); try cond.usualUnaryConversion(p, cond_tok); if (!cond.qt.isInvalid() and cond.qt.scalarKind(p.comp) == .none) - try p.errStr(.statement_scalar, l_paren + 1, try p.typeStr(cond.qt)); + try p.err(l_paren + 1, .statement_scalar, .{cond.qt}); try cond.saveValue(p); break :cond cond.node; }; @@ -4953,11 +4940,11 @@ fn stmt(p: *Parser) Error!Node.Index { // for (init; cond; incr const incr_start = p.tok_i; - err_start = p.comp.diagnostics.list.items.len; + prev_total = p.diagnostics.total; const incr = incr: { var incr = (try p.expr()) orelse break :incr null; - try incr.maybeWarnUnused(p, incr_start, err_start); + try incr.maybeWarnUnused(p, incr_start, prev_total); try incr.saveValue(p); break :incr incr.node; }; @@ -4994,13 +4981,13 @@ fn stmt(p: *Parser) Error!Node.Index { .decayed = null, } }); if (!goto_expr.qt.isInt(p.comp)) { - try p.errStr(.incompatible_arg, expr_tok, try p.typePairStrExtra(goto_expr.qt, " to parameter of incompatible type ", result_qt)); + try p.err(expr_tok, .incompatible_arg, .{ goto_expr.qt, result_qt }); return error.ParsingFailed; } if (goto_expr.val.isZero(p.comp)) { try goto_expr.nullToPointer(p, result_qt, expr_tok); } else { - try p.errStr(.implicit_int_to_ptr, expr_tok, try p.typePairStrExtra(goto_expr.qt, " to ", result_qt)); + try p.err(expr_tok, .implicit_int_to_ptr, .{ goto_expr.qt, result_qt }); try goto_expr.castToPointer(p, result_qt, expr_tok); } } @@ -5016,12 +5003,12 @@ fn stmt(p: *Parser) Error!Node.Index { return p.addNode(.{ .goto_stmt = .{ .label_tok = name_tok } }); } if (p.eatToken(.keyword_continue)) |cont| { - if (!p.in_loop) try p.errTok(.continue_not_in_loop, cont); + if (!p.in_loop) try p.err(cont, .continue_not_in_loop, .{}); _ = try p.expectToken(.semicolon); return p.addNode(.{ .continue_stmt = .{ .continue_tok = cont } }); } if (p.eatToken(.keyword_break)) |br| { - if (!p.in_loop and p.@"switch" == null) try p.errTok(.break_not_in_loop_or_switch, br); + if (!p.in_loop and p.@"switch" == null) try p.err(br, .break_not_in_loop_or_switch, .{}); _ = try p.expectToken(.semicolon); return p.addNode(.{ .break_stmt = .{ .break_tok = br } }); } @@ -5029,11 +5016,11 @@ fn stmt(p: *Parser) Error!Node.Index { if (try p.assembly(.stmt)) |some| return some; const expr_start = p.tok_i; - const err_start = p.comp.diagnostics.list.items.len; + const prev_total = p.diagnostics.total; if (try p.expr()) |some| { _ = try p.expectToken(.semicolon); - try some.maybeWarnUnused(p, expr_start, err_start); + try some.maybeWarnUnused(p, expr_start, prev_total); return some.node; } @@ -5048,7 +5035,7 @@ fn stmt(p: *Parser) Error!Node.Index { } }); } - try p.err(.expected_stmt); + try p.err(p.tok_i, .expected_stmt, .{}); return error.ParsingFailed; } @@ -5061,8 +5048,8 @@ fn labeledStmt(p: *Parser) Error!?Node.Index { const name_tok = try p.expectIdentifier(); const str = p.tokSlice(name_tok); if (p.findLabel(str)) |some| { - try p.errStr(.duplicate_label, name_tok, str); - try p.errStr(.previous_label, some, str); + try p.err(name_tok, .duplicate_label, .{str}); + try p.err(some, .previous_label, .{str}); } else { p.label_count += 1; try p.labels.append(.{ .label = name_tok }); @@ -5090,7 +5077,7 @@ fn labeledStmt(p: *Parser) Error!?Node.Index { const first_item = try p.integerConstExpr(.gnu_folding_extension); const ellipsis = p.tok_i; const second_item = if (p.eatToken(.ellipsis) != null) blk: { - try p.errTok(.gnu_switch_range, ellipsis); + try p.err(ellipsis, .gnu_switch_range, .{}); break :blk try p.integerConstExpr(.gnu_folding_extension); } else null; _ = try p.expectToken(.colon); @@ -5101,13 +5088,13 @@ fn labeledStmt(p: *Parser) Error!?Node.Index { const first = first_item.val; const last = if (second_item) |second| second.val else first; if (first.opt_ref == .none) { - try p.errTok(.case_val_unavailable, case + 1); + try p.err(case + 1, .case_val_unavailable, .{}); break :check; } else if (last.opt_ref == .none) { - try p.errTok(.case_val_unavailable, ellipsis + 1); + try p.err(ellipsis + 1, .case_val_unavailable, .{}); break :check; } else if (last.compare(.lt, first, p.comp)) { - try p.errTok(.empty_case_range, case + 1); + try p.err(case + 1, .empty_case_range, .{}); break :check; } @@ -5115,10 +5102,10 @@ fn labeledStmt(p: *Parser) Error!?Node.Index { const prev = (try some.add(first, last, case + 1)) orelse break :check; // TODO check which value was already handled - try p.errStr(.duplicate_switch_case, case + 1, try first_item.str(p)); - try p.errTok(.previous_case, prev.tok); + try p.err(case + 1, .duplicate_switch_case, .{first_item}); + try p.err(prev.tok, .previous_case, .{}); } else { - try p.errStr(.case_not_in_switch, case, "case"); + try p.err(case, .case_not_in_switch, .{"case"}); } return try p.addNode(.{ .case_stmt = .{ @@ -5135,12 +5122,12 @@ fn labeledStmt(p: *Parser) Error!?Node.Index { } }); const @"switch" = p.@"switch" orelse { - try p.errStr(.case_not_in_switch, default, "default"); + try p.err(default, .case_not_in_switch, .{"default"}); return node; }; if (@"switch".default) |previous| { - try p.errTok(.multiple_default, default); - try p.errTok(.previous_case, previous); + try p.err(default, .multiple_default, .{}); + try p.err(previous, .previous_case, .{}); } else { @"switch".default = default; } @@ -5150,7 +5137,7 @@ fn labeledStmt(p: *Parser) Error!?Node.Index { fn labelableStmt(p: *Parser) Error!Node.Index { if (p.tok_ids[p.tok_i] == .r_brace) { - try p.err(.label_compound_end); + try p.err(p.tok_i, .label_compound_end, .{}); return p.addNode(.{ .null_stmt = .{ .semicolon_or_r_brace_tok = p.tok_i, .qt = .void, @@ -5219,7 +5206,7 @@ fn compoundStmt(p: *Parser, is_fn_body: bool, stmt_expr_state: ?*StmtExprState) if (noreturn_index) |some| { // if new labels were defined we cannot be certain that the code is unreachable - if (some != p.tok_i - 1 and noreturn_label_count == p.label_count) try p.errTok(.unreachable_code, some); + if (some != p.tok_i - 1 and noreturn_label_count == p.label_count) try p.err(some, .unreachable_code, .{}); } if (is_fn_body) { const last_noreturn = if (p.decl_buf.items.len == decl_buf_top) @@ -5244,7 +5231,7 @@ fn compoundStmt(p: *Parser, is_fn_body: bool, stmt_expr_state: ?*StmtExprState) } if (!return_zero) { - try p.errStr(.func_does_not_return, p.tok_i - 1, func_name); + try p.err(p.tok_i - 1, .func_does_not_return, .{func_name}); } }, }; @@ -5405,19 +5392,19 @@ fn returnStmt(p: *Parser) Error!?Node.Index { const ret_void = !ret_qt.isInvalid() and ret_qt.is(p.comp, .void); if (func_qt.hasAttribute(p.comp, .noreturn)) { - try p.errStr(.invalid_noreturn, e_tok, p.tokSlice(p.func.name)); + try p.err(e_tok, .invalid_noreturn, .{p.tokSlice(p.func.name)}); } if (ret_expr) |*some| { if (ret_void) { - try p.errStr(.void_func_returns_value, e_tok, p.tokSlice(p.func.name)); + try p.err(e_tok, .void_func_returns_value, .{p.tokSlice(p.func.name)}); } else { try some.coerce(p, ret_qt, e_tok, .ret); try some.saveValue(p); } } else if (!ret_void) { - try p.errStr(.func_should_return, ret_tok, p.tokSlice(p.func.name)); + try p.err(ret_tok, .func_should_return, .{p.tokSlice(p.func.name)}); } return try p.addNode(.{ .return_stmt = .{ @@ -5671,33 +5658,14 @@ pub const Result = struct { qt: QualType = .int, val: Value = .{}, - pub fn str(res: Result, p: *Parser) ![]const u8 { - switch (res.val.opt_ref) { - .none => return "(none)", - .null => return "nullptr_t", - else => {}, - } - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - if (try res.val.print(res.qt, p.comp, p.strings.writer())) |nested| switch (nested) { - .pointer => |ptr| { - const ptr_node: Node.Index = @enumFromInt(ptr.node); - const decl_name = p.tree.tokSlice(ptr_node.tok(&p.tree)); - try ptr.offset.printPointer(decl_name, p.comp, p.strings.writer()); - }, - }; - - return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); - } - - fn maybeWarnUnused(res: Result, p: *Parser, expr_start: TokenIndex, err_start: usize) Error!void { + fn maybeWarnUnused(res: Result, p: *Parser, expr_start: TokenIndex, prev_total: usize) Error!void { if (res.qt.is(p.comp, .void)) return; if (res.qt.isInvalid()) return; - // don't warn about unused result if the expression contained errors besides other unused results - for (p.comp.diagnostics.list.items[err_start..]) |err_item| { - if (err_item.tag != .unused_value) return; - } + // // don't warn about unused result if the expression contained errors besides other unused results + if (p.diagnostics.total != prev_total) return; // TODO improve + // for (p.diagnostics.list.items[err_start..]) |err_item| { + // if (err_item.tag != .unused_value) return; + // } var cur_node = res.node; while (true) switch (cur_node.get(&p.tree)) { .assign_expr, @@ -5718,15 +5686,15 @@ pub const Result = struct { => return, .call_expr => |call| { const call_info = p.tree.callableResultUsage(call.callee) orelse return; - if (call_info.nodiscard) try p.errStr(.nodiscard_unused, expr_start, p.tokSlice(call_info.tok)); - if (call_info.warn_unused_result) try p.errStr(.warn_unused_result, expr_start, p.tokSlice(call_info.tok)); + if (call_info.nodiscard) try p.err(expr_start, .nodiscard_unused, .{p.tokSlice(call_info.tok)}); + if (call_info.warn_unused_result) try p.err(expr_start, .warn_unused_result, .{p.tokSlice(call_info.tok)}); return; }, .builtin_call_expr => |call| { const expanded = p.comp.builtins.lookup(p.tokSlice(call.builtin_tok)); const attributes = expanded.builtin.properties.attributes; - if (attributes.pure) try p.errStr(.builtin_unused, call.builtin_tok, "pure"); - if (attributes.@"const") try p.errStr(.builtin_unused, call.builtin_tok, "const"); + if (attributes.pure) try p.err(call.builtin_tok, .builtin_unused, .{"pure"}); + if (attributes.@"const") try p.err(call.builtin_tok, .builtin_unused, .{"const"}); return; }, .stmt_expr => |stmt_expr| { @@ -5737,7 +5705,7 @@ pub const Result = struct { .paren_expr => |grouped| cur_node = grouped.operand, else => break, }; - try p.errTok(.unused_value, expr_start); + try p.err(expr_start, .unused_value, .{}); } fn boolRes(lhs: *Result, p: *Parser, tag: std.meta.Tag(Node), rhs: Result, tok_i: TokenIndex) !void { @@ -5819,7 +5787,7 @@ pub const Result = struct { var adjusted_elem_qt = a_elem; if (!pointers_compatible or has_void_pointer_branch) { if (!pointers_compatible) { - try p.errStr(.pointer_mismatch, tok, try p.typePairStrExtra(a.qt, " and ", b.qt)); + try p.err(tok, .pointer_mismatch, .{ a.qt, b.qt }); } adjusted_elem_qt = .void; } @@ -5952,13 +5920,13 @@ pub const Result = struct { if (a_sk == .nullptr_t or b_sk == .nullptr_t) return a.invalidBinTy(tok, b, p); if ((a_sk.isInt() or b_sk.isInt()) and !(a.val.isZero(p.comp) or b.val.isZero(p.comp))) { - try p.errStr(.comparison_ptr_int, tok, try p.typePairStr(a.qt, b.qt)); + try p.err(tok, .comparison_ptr_int, .{ a.qt, b.qt }); } else if (a_sk.isPointer() and b_sk.isPointer()) { if (a_sk != .void_pointer and b_sk != .void_pointer) { const a_elem = a.qt.childType(p.comp); const b_elem = b.qt.childType(p.comp); if (!a_elem.eql(b_elem, p.comp)) { - try p.errStr(.comparison_distinct_ptr, tok, try p.typePairStr(a.qt, b.qt)); + try p.err(tok, .comparison_distinct_ptr, .{ a.qt, b.qt }); } } } else if (a_sk.isPointer()) { @@ -5988,7 +5956,7 @@ pub const Result = struct { } const int_ty = if (a_sk.isInt()) a else b; const ptr_ty = if (a_sk.isPointer()) a else b; - try p.errStr(.implicit_int_to_ptr, tok, try p.typePairStrExtra(int_ty.qt, " to ", ptr_ty.qt)); + try p.err(tok, .implicit_int_to_ptr, .{ int_ty.qt, ptr_ty.qt }); try int_ty.castToPointer(p, ptr_ty.qt, tok); return true; @@ -6011,7 +5979,7 @@ pub const Result = struct { if (a_sk.isPointer() == b_sk.isPointer() or a_sk.isInt() == b_sk.isInt()) return a.invalidBinTy(tok, b, p); if (a_sk == .void_pointer or b_sk == .void_pointer) - try p.errTok(.gnu_pointer_arith, tok); + try p.err(tok, .gnu_pointer_arith, .{}); if (a_sk == .nullptr_t) try a.nullToPointer(p, .void_pointer, tok); if (b_sk == .nullptr_t) try b.nullToPointer(p, .void_pointer, tok); @@ -6029,7 +5997,7 @@ pub const Result = struct { if (!a_sk.isPointer() or !(b_sk.isPointer() or b_sk.isInt())) return a.invalidBinTy(tok, b, p); if (a_sk == .void_pointer) - try p.errTok(.gnu_pointer_arith, tok); + try p.err(tok, .gnu_pointer_arith, .{}); if (a_sk == .nullptr_t) try a.nullToPointer(p, .void_pointer, tok); if (b_sk == .nullptr_t) try b.nullToPointer(p, .void_pointer, tok); @@ -6038,8 +6006,8 @@ pub const Result = struct { const a_child_qt = a.qt.get(p.comp, .pointer).?.child; const b_child_qt = b.qt.get(p.comp, .pointer).?.child; - if (!a_child_qt.eql(b_child_qt, p.comp)) try p.errStr(.incompatible_pointers, tok, try p.typePairStr(a.qt, b.qt)); - if (a.qt.childType(p.comp).sizeofOrNull(p.comp) orelse 1 == 0) try p.errStr(.subtract_pointers_zero_elem_size, tok, try p.typeStr(a.qt.childType(p.comp))); + if (!a_child_qt.eql(b_child_qt, p.comp)) try p.err(tok, .incompatible_pointers, .{ a.qt, b.qt }); + if (a.qt.childType(p.comp).sizeofOrNull(p.comp) orelse 1 == 0) try p.err(tok, .subtract_pointers_zero_elem_size, .{a.qt.childType(p.comp)}); a.qt = p.comp.type_store.ptrdiff; } @@ -6075,9 +6043,9 @@ pub const Result = struct { const src_sk = res.qt.scalarKind(p.comp); if (res.qt.is(p.comp, .array)) { if (res.val.is(.bytes, p.comp)) { - try p.errStr(.string_literal_to_bool, tok, try p.typePairStrExtra(res.qt, " to ", bool_qt)); + try p.err(tok, .string_literal_to_bool, .{ res.qt, bool_qt }); } else { - try p.errStr(.array_address_to_bool, tok, p.tokSlice(tok)); + try p.err(tok, .array_address_to_bool, .{p.tokSlice(tok)}); } try res.lvalConversion(p, tok); res.val = .one; @@ -6096,9 +6064,9 @@ pub const Result = struct { res.qt = bool_qt; try res.implicitCast(p, .int_to_bool, tok); } else if (src_sk.isFloat()) { - const old_value = res.val; + const old_res = res.*; const value_change_kind = try res.val.floatToInt(bool_qt, p.comp); - try res.floatToIntWarning(p, bool_qt, old_value, value_change_kind, tok); + try res.floatToIntWarning(p, bool_qt, old_res, value_change_kind, tok); if (!src_sk.isReal()) { res.qt = res.qt.toReal(p.comp); try res.implicitCast(p, .complex_float_to_real, tok); @@ -6134,9 +6102,9 @@ pub const Result = struct { try res.implicitCast(p, .real_to_complex_int, tok); } } else if (res.qt.isFloat(p.comp)) { - const old_value = res.val; + const old_res = res.*; const value_change_kind = try res.val.floatToInt(int_qt, p.comp); - try res.floatToIntWarning(p, int_qt, old_value, value_change_kind, tok); + try res.floatToIntWarning(p, int_qt, old_res, value_change_kind, tok); if (src_sk.isReal() and dest_sk.isReal()) { res.qt = int_qt; try res.implicitCast(p, .float_to_int, tok); @@ -6155,12 +6123,12 @@ pub const Result = struct { try res.implicitCast(p, .complex_float_to_complex_int, tok); } } else if (!res.qt.eql(int_qt, p.comp)) { - const old_val = res.val; + const old_res = res.*; const value_change_kind = try res.val.intCast(int_qt, p.comp); switch (value_change_kind) { .none => {}, - .truncated => try p.errStr(.int_value_changed, tok, try p.valueChangedStr(res, old_val, int_qt)), - .sign_changed => try p.errStr(.sign_conversion, tok, try p.typePairStrExtra(res.qt, " to ", int_qt)), + .truncated => try p.errValueChanged(tok, .int_value_changed, res.*, old_res, int_qt), + .sign_changed => try p.err(tok, .sign_conversion, .{ res.qt, int_qt }), } if (src_sk.isReal() and dest_sk.isReal()) { @@ -6187,19 +6155,19 @@ pub const Result = struct { } fn floatToIntWarning( - res: *Result, + res: Result, p: *Parser, int_qt: QualType, - old_value: Value, + old_res: Result, change_kind: Value.FloatToIntChangeKind, tok: TokenIndex, ) !void { switch (change_kind) { - .none => return p.errStr(.float_to_int, tok, try p.typePairStrExtra(res.qt, " to ", int_qt)), - .out_of_range => return p.errStr(.float_out_of_range, tok, try p.typePairStrExtra(res.qt, " to ", int_qt)), - .overflow => return p.errStr(.float_overflow_conversion, tok, try p.typePairStrExtra(res.qt, " to ", int_qt)), - .nonzero_to_zero => return p.errStr(.float_zero_conversion, tok, try p.valueChangedStr(res, old_value, int_qt)), - .value_changed => return p.errStr(.float_value_changed, tok, try p.valueChangedStr(res, old_value, int_qt)), + .none => return p.err(tok, .float_to_int, .{ res.qt, int_qt }), + .out_of_range => return p.err(tok, .float_out_of_range, .{ res.qt, int_qt }), + .overflow => return p.err(tok, .float_overflow_conversion, .{ res.qt, int_qt }), + .nonzero_to_zero => return p.errValueChanged(tok, .float_zero_conversion, res, old_res, int_qt), + .value_changed => return p.errValueChanged(tok, .float_value_changed, res, old_res, int_qt), } } @@ -6412,7 +6380,7 @@ pub const Result = struct { } fn invalidBinTy(a: *Result, tok: TokenIndex, b: *Result, p: *Parser) Error!bool { - try p.errStr(.invalid_bin_types, tok, try p.typePairStr(a.qt, b.qt)); + try p.err(tok, .invalid_bin_types, .{ a.qt, b.qt }); a.val = .{}; b.val = .{}; a.qt = .invalid; @@ -6462,14 +6430,14 @@ pub const Result = struct { cast_kind = .to_void; res.val = .{}; } else if (res.qt.is(p.comp, .void)) { - try p.errStr(.invalid_cast_operand_type, operand_tok, try p.typeStr(res.qt)); + try p.err(operand_tok, .invalid_cast_operand_type, .{res.qt}); return error.ParsingFailed; } else if (dest_sk == .nullptr_t) { res.val = .{}; if (src_sk == .nullptr_t) { cast_kind = .no_op; } else { - try p.errStr(.invalid_object_cast, l_paren, try p.typePairStrExtra(res.qt, " to ", dest_qt)); + try p.err(l_paren, .invalid_object_cast, .{ res.qt, dest_qt }); return error.ParsingFailed; } } else if (src_sk == .nullptr_t) { @@ -6482,7 +6450,7 @@ pub const Result = struct { } else if (dest_sk.isPointer()) { try res.nullToPointer(p, dest_qt, l_paren); } else { - try p.errStr(.invalid_object_cast, l_paren, try p.typePairStrExtra(res.qt, " to ", dest_qt)); + try p.err(l_paren, .invalid_object_cast, .{ res.qt, dest_qt }); return error.ParsingFailed; } cast_kind = .no_op; @@ -6490,10 +6458,10 @@ pub const Result = struct { cast_kind = .null_to_pointer; } else if (dest_sk != .none) cast: { if (dest_sk.isFloat() and src_sk.isPointer()) { - try p.errStr(.invalid_cast_to_float, l_paren, try p.typeStr(dest_qt)); + try p.err(l_paren, .invalid_cast_to_float, .{dest_qt}); return error.ParsingFailed; } else if (src_sk.isFloat() and dest_sk.isPointer()) { - try p.errStr(.invalid_cast_to_pointer, l_paren, try p.typeStr(res.qt)); + try p.err(l_paren, .invalid_cast_to_pointer, .{res.qt}); return error.ParsingFailed; } @@ -6576,7 +6544,7 @@ pub const Result = struct { } else if (res.qt.is(p.comp, .func)) { cast_kind = .function_to_pointer; } else { - try p.errStr(.invalid_cast_operand_type, operand_tok, try p.typeStr(res.qt)); + try p.err(operand_tok, .invalid_cast_operand_type, .{res.qt}); return error.ParsingFailed; } } else if (dest_sk.isFloat()) { @@ -6624,7 +6592,7 @@ pub const Result = struct { res.val.boolCast(p.comp); } else if (src_sk.isFloat() and dest_int) { if (dest_qt.hasIncompleteSize(p.comp)) { - try p.errStr(.cast_to_incomplete_type, l_paren, try p.typeStr(dest_qt)); + try p.err(l_paren, .cast_to_incomplete_type, .{dest_qt}); return error.ParsingFailed; } // Explicit cast, no conversion warning @@ -6635,35 +6603,35 @@ pub const Result = struct { try res.val.floatCast(dest_qt, p.comp); } else if (src_int and dest_int) { if (dest_qt.hasIncompleteSize(p.comp)) { - try p.errStr(.cast_to_incomplete_type, l_paren, try p.typeStr(dest_qt)); + try p.err(l_paren, .cast_to_incomplete_type, .{dest_qt}); return error.ParsingFailed; } _ = try res.val.intCast(dest_qt, p.comp); } } else if (dest_qt.get(p.comp, .@"union")) |union_ty| { if (union_ty.layout == null) { - try p.errStr(.cast_to_incomplete_type, l_paren, try p.typeStr(dest_qt)); + try p.err(l_paren, .cast_to_incomplete_type, .{dest_qt}); return error.ParsingFailed; } for (union_ty.fields) |field| { if (field.qt.eql(res.qt, p.comp)) { cast_kind = .union_cast; - try p.errTok(.gnu_union_cast, l_paren); + try p.err(l_paren, .gnu_union_cast, .{}); break; } } else { - try p.errStr(.invalid_union_cast, l_paren, try p.typeStr(res.qt)); + try p.err(l_paren, .invalid_union_cast, .{res.qt}); return error.ParsingFailed; } } else { - try p.errStr(.invalid_cast_type, l_paren, try p.typeStr(dest_qt)); + try p.err(l_paren, .invalid_cast_type, .{dest_qt}); return error.ParsingFailed; } - if (dest_qt.isQualified()) try p.errStr(.qual_cast, l_paren, try p.typeStr(dest_qt)); + if (dest_qt.isQualified()) try p.err(l_paren, .qual_cast, .{dest_qt}); if (dest_sk.isInt() and src_sk.isPointer() and dest_qt.sizeCompare(res.qt, p.comp) == .lt) { - try p.errStr(.cast_to_smaller_int, l_paren, try p.typePairStrExtra(dest_qt, " from ", res.qt)); + try p.err(l_paren, .cast_to_smaller_int, .{ dest_qt, res.qt }); } res.qt = dest_qt.unqualified(); @@ -6694,20 +6662,11 @@ pub const Result = struct { fn note(c: CoerceContext, p: *Parser) !void { switch (c) { - .arg => |tok| try p.errTok(.parameter_here, tok), + .arg => |tok| try p.err(tok, .parameter_here, .{}), .test_coerce => unreachable, else => {}, } } - - fn typePairStr(c: CoerceContext, p: *Parser, dest_ty: QualType, src_ty: QualType) ![]const u8 { - switch (c) { - .assign, .init => return p.typePairStrExtra(dest_ty, " from incompatible type ", src_ty), - .ret => return p.typePairStrExtra(src_ty, " from a function with incompatible result type ", dest_ty), - .arg => return p.typePairStrExtra(src_ty, " to parameter of incompatible type ", dest_ty), - .test_coerce => unreachable, - } - } }; /// Perform assignment-like coercion to `dest_ty`. @@ -6754,7 +6713,7 @@ pub const Result = struct { return; } else if (src_sk.isPointer()) { if (c == .test_coerce) return error.CoercionFailed; - try p.errStr(.implicit_ptr_to_int, tok, try p.typePairStrExtra(src_original_qt, " to ", dest_unqual)); + try p.err(tok, .implicit_ptr_to_int, .{ src_original_qt, dest_unqual }); try c.note(p); try res.castToInt(p, dest_unqual, tok); return; @@ -6770,7 +6729,7 @@ pub const Result = struct { return; } else if (src_sk.isInt() and src_sk.isReal()) { if (c == .test_coerce) return error.CoercionFailed; - try p.errStr(.implicit_int_to_ptr, tok, try p.typePairStrExtra(src_original_qt, " to ", dest_unqual)); + try p.err(tok, .implicit_int_to_ptr, .{ src_original_qt, dest_unqual }); try c.note(p); try res.castToPointer(p, dest_unqual, tok); return; @@ -6786,26 +6745,26 @@ pub const Result = struct { (src_child.@"volatile" and !dest_child.@"volatile") or (src_child.restrict and !dest_child.restrict)) { - try p.errStr(switch (c) { + try p.err(tok, switch (c) { .assign => .ptr_assign_discards_quals, .init => .ptr_init_discards_quals, .ret => .ptr_ret_discards_quals, .arg => .ptr_arg_discards_quals, .test_coerce => return error.CoercionFailed, - }, tok, try c.typePairStr(p, dest_qt, src_original_qt)); + }, .{ dest_qt, src_original_qt }); } try res.castToPointer(p, dest_unqual, tok); return; } const different_sign_only = src_child.sameRankDifferentSign(dest_child, p.comp); - try p.errStr(switch (c) { + try p.err(tok, switch (c) { .assign => if (different_sign_only) .incompatible_ptr_assign_sign else .incompatible_ptr_assign, .init => if (different_sign_only) .incompatible_ptr_init_sign else .incompatible_ptr_init, .ret => if (different_sign_only) .incompatible_return_sign else .incompatible_return, .arg => if (different_sign_only) .incompatible_ptr_arg_sign else .incompatible_ptr_arg, .test_coerce => return error.CoercionFailed, - }, tok, try c.typePairStr(p, dest_qt, src_original_qt)); + }, .{ dest_qt, src_original_qt }); try c.note(p); res.qt = dest_unqual; @@ -6838,7 +6797,7 @@ pub const Result = struct { } } else { if (c == .assign and (dest_unqual.is(p.comp, .array) or dest_unqual.is(p.comp, .func))) { - try p.errTok(.not_assignable, tok); + try p.err(tok, .not_assignable, .{}); return; } else if (c == .test_coerce) { return error.CoercionFailed; @@ -6848,13 +6807,13 @@ pub const Result = struct { return error.ParsingFailed; } - try p.errStr(switch (c) { + try p.err(tok, switch (c) { .assign => .incompatible_assign, .init => .incompatible_init, .ret => .incompatible_return, .arg => .incompatible_arg, .test_coerce => return error.CoercionFailed, - }, tok, try c.typePairStr(p, dest_unqual, res.qt)); + }, .{ dest_unqual, res.qt }); try c.note(p); } }; @@ -6865,7 +6824,7 @@ fn expect(p: *Parser, comptime func: fn (*Parser) Error!?Result) Error!Result { fn expectResult(p: *Parser, res: ?Result) Error!Result { return res orelse { - try p.errTok(.expected_expr, p.tok_i); + try p.err(p.tok_i, .expected_expr, .{}); return error.ParsingFailed; }; } @@ -6873,15 +6832,15 @@ fn expectResult(p: *Parser, res: ?Result) Error!Result { /// expr : assignExpr (',' assignExpr)* fn expr(p: *Parser) Error!?Result { var expr_start = p.tok_i; - var err_start = p.comp.diagnostics.list.items.len; + var prev_total = p.diagnostics.total; var lhs = (try p.assignExpr()) orelse { if (p.tok_ids[p.tok_i] == .comma) _ = try p.expectResult(null); return null; }; while (p.eatToken(.comma)) |comma| { - try lhs.maybeWarnUnused(p, expr_start, err_start); + try lhs.maybeWarnUnused(p, expr_start, prev_total); expr_start = p.tok_i; - err_start = p.comp.diagnostics.list.items.len; + prev_total = p.diagnostics.total; var rhs = try p.expect(assignExpr); try rhs.lvalConversion(p, expr_start); @@ -6961,7 +6920,7 @@ fn assignExpr(p: *Parser) Error!?Result { var is_const: bool = undefined; if (!p.tree.isLvalExtra(lhs.node, &is_const) or is_const) { - try p.errTok(.not_assignable, tok); + try p.err(tok, .not_assignable, .{}); lhs.qt = .invalid; } @@ -6985,8 +6944,8 @@ fn assignExpr(p: *Parser) Error!?Result { => { if (!lhs.qt.isInvalid() and rhs.val.isZero(p.comp) and lhs.qt.isInt(p.comp) and rhs.qt.isInt(p.comp)) { switch (tag) { - .div_assign_expr => try p.errStr(.division_by_zero, tok, "division"), - .mod_assign_expr => try p.errStr(.division_by_zero, tok, "remainder"), + .div_assign_expr => try p.err(tok, .division_by_zero, .{"division"}), + .mod_assign_expr => try p.err(tok, .division_by_zero, .{"remainder"}), else => {}, } } @@ -7021,7 +6980,7 @@ fn integerConstExpr(p: *Parser, decl_folding: ConstDeclFoldingMode) Error!Result const start = p.tok_i; const res = try p.constExpr(decl_folding); if (!res.qt.isInvalid() and !res.qt.isInt(p.comp)) { - try p.errTok(.expected_integer_constant_expr, start); + try p.err(start, .expected_integer_constant_expr, .{}); return error.ParsingFailed; } return res; @@ -7053,7 +7012,7 @@ fn condExpr(p: *Parser) Error!?Result { const saved_eval = p.no_eval; if (cond.qt.scalarKind(p.comp) == .none) { - try p.errStr(.cond_expr_type, cond_tok, try p.typeStr(cond.qt)); + try p.err(cond_tok, .cond_expr_type, .{cond.qt}); return error.ParsingFailed; } @@ -7273,14 +7232,14 @@ fn shiftExpr(p: *Parser) Error!?Result { if (try lhs.adjustTypes(tok, &rhs, p, .integer)) { if (rhs.val.compare(.lt, .zero, p.comp)) { - try p.errStr(.negative_shift_count, tok, try rhs.str(p)); + try p.err(tok, .negative_shift_count, .{rhs}); } if (rhs.val.compare(.gte, try Value.int(lhs.qt.bitSizeof(p.comp), p.comp), p.comp)) { - try p.errStr(.too_big_shift_count, tok, try rhs.str(p)); + try p.err(tok, .too_big_shift_count, .{rhs}); } if (tag == .shl_expr) { if (try lhs.val.shl(lhs.val, rhs.val, lhs.qt, p.comp) and - lhs.qt.signedness(p.comp) != .unsigned) try p.errOverflow(tok, lhs); + lhs.qt.signedness(p.comp) != .unsigned) try p.err(tok, .overflow, .{lhs}); } else { lhs.val = try lhs.val.shr(rhs.val, lhs.qt, p.comp); } @@ -7307,9 +7266,14 @@ fn addExpr(p: *Parser) Error!?Result { if (tag == .add_expr) { if (try lhs.val.add(lhs.val, rhs.val, lhs.qt, p.comp)) { if (lhs_sk.isPointer()) { - try p.errArrayOverflow(tok, lhs); + const increment = lhs; + const ptr_bits = p.comp.type_store.intptr.bitSizeof(p.comp); + const element_size = increment.qt.childType(p.comp).sizeofOrNull(p.comp) orelse 1; + const max_elems = p.comp.maxArrayBytes() / element_size; + + try p.err(tok, .array_overflow, .{ increment, ptr_bits, element_size * 8, element_size, max_elems }); } else if (lhs.qt.signedness(p.comp) != .unsigned) { - try p.errOverflow(tok, lhs); + try p.err(tok, .overflow, .{lhs}); } } } else { @@ -7318,14 +7282,17 @@ fn addExpr(p: *Parser) Error!?Result { lhs.val = .{}; } else { if (try lhs.val.sub(lhs.val, rhs.val, lhs.qt, elem_size, p.comp) and - lhs.qt.signedness(p.comp) != .unsigned) try p.errOverflow(tok, lhs); + lhs.qt.signedness(p.comp) != .unsigned) + { + try p.err(tok, .overflow, .{lhs}); + } } } } if (!lhs.qt.isInvalid()) { const lhs_sk = original_lhs_qt.scalarKind(p.comp); if (lhs_sk == .pointer and original_lhs_qt.childType(p.comp).hasIncompleteSize(p.comp)) { - try p.errStr(.ptr_arithmetic_incomplete, tok, try p.typeStr(original_lhs_qt.childType(p.comp))); + try p.err(tok, .ptr_arithmetic_incomplete, .{original_lhs_qt.childType(p.comp)}); lhs.qt = .invalid; } } @@ -7345,22 +7312,17 @@ fn mulExpr(p: *Parser) Error!?Result { var rhs = try p.expect(castExpr); if (rhs.val.isZero(p.comp) and tag != .mul_expr and !p.no_eval and lhs.qt.isInt(p.comp) and rhs.qt.isInt(p.comp)) { - const err_tag: Diagnostics.Tag = if (p.in_macro) .division_by_zero_macro else .division_by_zero; lhs.val = .{}; - if (tag == .div_expr) { - try p.errStr(err_tag, tok, "division"); - } else { - try p.errStr(err_tag, tok, "remainder"); - } + try p.err(tok, if (p.in_macro) .division_by_zero_macro else .division_by_zero, if (tag == .div_expr) .{"division"} else .{"remainder"}); if (p.in_macro) return error.ParsingFailed; } if (try lhs.adjustTypes(tok, &rhs, p, if (tag == .mod_expr) .integer else .arithmetic)) { switch (tag) { .mul_expr => if (try lhs.val.mul(lhs.val, rhs.val, lhs.qt, p.comp) and - lhs.qt.signedness(p.comp) != .unsigned) try p.errOverflow(tok, lhs), + lhs.qt.signedness(p.comp) != .unsigned) try p.err(tok, .overflow, .{lhs}), .div_expr => if (try lhs.val.div(lhs.val, rhs.val, lhs.qt, p.comp) and - lhs.qt.signedness(p.comp) != .unsigned) try p.errOverflow(tok, lhs), + lhs.qt.signedness(p.comp) != .unsigned) try p.err(tok, .overflow, .{lhs}), .mod_expr => { var res = try Value.rem(lhs.val, rhs.val, lhs.qt, p.comp); if (res.opt_ref == .none) { @@ -7383,19 +7345,6 @@ fn mulExpr(p: *Parser) Error!?Result { return lhs; } -/// This will always be the last message, if present -fn removeUnusedWarningForTok(p: *Parser, last_expr_tok: TokenIndex) void { - if (last_expr_tok == 0) return; - if (p.comp.diagnostics.list.items.len == 0) return; - - const last_expr_loc = p.pp.tokens.items(.loc)[last_expr_tok]; - const last_msg = p.comp.diagnostics.list.items[p.comp.diagnostics.list.items.len - 1]; - - if (last_msg.tag == .unused_value and last_msg.loc.eql(last_expr_loc)) { - p.comp.diagnostics.list.items.len = p.comp.diagnostics.list.items.len - 1; - } -} - /// castExpr /// : '(' compoundStmt ')' suffixExpr* /// | '(' typeName ')' castExpr @@ -7409,14 +7358,13 @@ fn castExpr(p: *Parser) Error!?Result { if (p.eatToken(.l_paren)) |l_paren| cast_expr: { if (p.tok_ids[p.tok_i] == .l_brace) { const tok = p.tok_i; - try p.err(.gnu_statement_expression); + try p.err(p.tok_i, .gnu_statement_expression, .{}); if (p.func.qt == null) { - try p.err(.stmt_expr_not_allowed_file_scope); + try p.err(p.tok_i, .stmt_expr_not_allowed_file_scope, .{}); return error.ParsingFailed; } var stmt_expr_state: StmtExprState = .{}; const body_node = (try p.compoundStmt(false, &stmt_expr_state)).?; // compoundStmt only returns null if .l_brace isn't the first token - p.removeUnusedWarningForTok(stmt_expr_state.last_expr_tok); var res = Result{ .node = body_node, @@ -7465,14 +7413,14 @@ fn typesCompatible(p: *Parser) Error!Result { const l_paren = try p.expectToken(.l_paren); const lhs = (try p.typeName()) orelse { - try p.err(.expected_type); + try p.err(p.tok_i, .expected_type, .{}); p.skipTo(.r_paren); return error.ParsingFailed; }; _ = try p.expectToken(.comma); const rhs = (try p.typeName()) orelse { - try p.err(.expected_type); + try p.err(p.tok_i, .expected_type, .{}); p.skipTo(.r_paren); return error.ParsingFailed; }; @@ -7501,7 +7449,7 @@ fn builtinChooseExpr(p: *Parser) Error!Result { const cond_tok = p.tok_i; var cond = try p.integerConstExpr(.no_const_decl_folding); if (cond.val.opt_ref == .none) { - try p.errTok(.builtin_choose_cond, cond_tok); + try p.err(cond_tok, .builtin_choose_cond, .{}); return error.ParsingFailed; } @@ -7552,13 +7500,13 @@ fn builtinVaArg(p: *Parser) Error!Result { _ = try p.expectToken(.comma); const ty = (try p.typeName()) orelse { - try p.err(.expected_type); + try p.err(p.tok_i, .expected_type, .{}); return error.ParsingFailed; }; try p.expectClosing(l_paren, .r_paren); if (!va_list.qt.eql(p.comp.type_store.va_list, p.comp)) { - try p.errStr(.incompatible_va_arg, va_list_tok, try p.typeStr(va_list.qt)); + try p.err(va_list_tok, .incompatible_va_arg, .{va_list.qt}); return error.ParsingFailed; } @@ -7584,19 +7532,19 @@ fn builtinOffsetof(p: *Parser, offset_kind: OffsetKind) Error!Result { const ty_tok = p.tok_i; const operand_qt = (try p.typeName()) orelse { - try p.err(.expected_type); + try p.err(p.tok_i, .expected_type, .{}); p.skipTo(.r_paren); return error.ParsingFailed; }; const record_ty = operand_qt.getRecord(p.comp) orelse { - try p.errStr(.offsetof_ty, ty_tok, try p.typeStr(operand_qt)); + try p.err(ty_tok, .offsetof_ty, .{operand_qt}); p.skipTo(.r_paren); return error.ParsingFailed; }; if (record_ty.layout == null) { - try p.errStr(.offsetof_incomplete, ty_tok, try p.typeStr(operand_qt)); + try p.err(ty_tok, .offsetof_incomplete, .{operand_qt}); p.skipTo(.r_paren); return error.ParsingFailed; } @@ -7649,7 +7597,7 @@ fn offsetofMemberDesignator( const field_name = try p.comp.internString(p.tokSlice(field_name_tok)); const lhs_record_ty = lhs.qt.getRecord(p.comp) orelse { - try p.errStr(.offsetof_ty, field_name_tok, try p.typeStr(lhs.qt)); + try p.err(field_name_tok, .offsetof_ty, .{lhs.qt}); return error.ParsingFailed; }; try p.validateFieldAccess(lhs_record_ty, lhs.qt, field_name_tok, field_name); @@ -7663,7 +7611,7 @@ fn offsetofMemberDesignator( _ = try p.expectClosing(l_bracket_tok, .r_bracket); if (!lhs.qt.is(p.comp, .array)) { - try p.errStr(.offsetof_array, l_bracket_tok, try p.typeStr(lhs.qt)); + try p.err(l_bracket_tok, .offsetof_array, .{lhs.qt}); return error.ParsingFailed; } var ptr = lhs; @@ -7673,7 +7621,7 @@ fn offsetofMemberDesignator( if (index.qt.isInt(p.comp)) { try p.checkArrayBounds(index, lhs, l_bracket_tok); } else { - try p.errTok(.invalid_index, l_bracket_tok); + try p.err(l_bracket_tok, .invalid_index, .{}); } try index.saveValue(p); @@ -7726,19 +7674,6 @@ fn computeOffset(p: *Parser, res: Result) !Value { return p.computeOffsetExtra(res.node, &val); } -fn packedMemberAccessStr(p: *Parser, record: StringId, member: StringId) ![]const u8 { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - var w = p.strings.writer(); - try w.writeAll(member.lookup(p.comp)); - try w.writeAll("' of class or structure '"); - try w.writeAll(record.lookup(p.comp)); - try w.writeAll("' may result in an unaligned pointer value"); - - return try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items[strings_top..]); -} - /// unExpr /// : (compoundLiteral | primaryExpr) suffixExpr* /// | '&&' IDENTIFIER @@ -7754,7 +7689,7 @@ fn unExpr(p: *Parser) Error!?Result { const address_tok = p.tok_i; p.tok_i += 1; const name_tok = try p.expectIdentifier(); - try p.errTok(.gnu_label_as_value, address_tok); + try p.err(address_tok, .gnu_label_as_value, .{}); p.contains_address_of_label = true; const str = p.tokSlice(name_tok); @@ -7774,7 +7709,7 @@ fn unExpr(p: *Parser) Error!?Result { }, .ampersand => { if (p.in_macro) { - try p.err(.invalid_preproc_operator); + try p.err(p.tok_i, .invalid_preproc_operator, .{}); return error.ParsingFailed; } const orig_tok_i = p.tok_i; @@ -7785,16 +7720,19 @@ fn unExpr(p: *Parser) Error!?Result { if (p.getNode(operand.node, .member_access_expr) orelse p.getNode(operand.node, .member_access_ptr_expr)) |access| { - if (access.isBitFieldWidth(&p.tree) != null) try p.errTok(.addr_of_bitfield, tok); + if (access.isBitFieldWidth(&p.tree) != null) try p.err(tok, .addr_of_bitfield, .{}); const lhs_qt = access.base.qt(&p.tree); if (lhs_qt.hasAttribute(p.comp, .@"packed")) { const record_ty = lhs_qt.getRecord(p.comp).?; - try p.errStr(.packed_member_address, orig_tok_i, try p.packedMemberAccessStr(record_ty.name, record_ty.fields[access.member_index].name)); + try p.err(orig_tok_i, .packed_member_address, .{ + record_ty.name.lookup(p.comp), + record_ty.fields[access.member_index].name.lookup(p.comp), + }); } } if (!operand.qt.isInvalid()) { if (!p.tree.isLval(operand.node)) { - try p.errTok(.addr_of_rvalue, tok); + try p.err(tok, .addr_of_rvalue, .{}); } addr_val = try p.computeOffset(operand); @@ -7806,13 +7744,13 @@ fn unExpr(p: *Parser) Error!?Result { if (p.getNode(operand.node, .decl_ref_expr)) |decl_ref| { switch (decl_ref.decl.get(&p.tree)) { .variable => |variable| { - if (variable.storage_class == .register) try p.errTok(.addr_of_register, tok); + if (variable.storage_class == .register) try p.err(tok, .addr_of_register, .{}); }, else => {}, } } else if (p.getNode(operand.node, .compound_literal_expr)) |literal| { switch (literal.storage_class) { - .register => try p.errTok(.addr_of_register, tok), + .register => try p.err(tok, .addr_of_register, .{}), else => {}, } } @@ -7833,12 +7771,12 @@ fn unExpr(p: *Parser) Error!?Result { operand.val = .{}; }, else => { - try p.errTok(.indirection_ptr, tok); + try p.err(tok, .indirection_ptr, .{}); }, } if (operand.qt.hasIncompleteSize(p.comp) and !operand.qt.is(p.comp, .void)) { - try p.errStr(.deref_incomplete_ty_ptr, tok, try p.typeStr(operand.qt)); + try p.err(tok, .deref_incomplete_ty_ptr, .{operand.qt}); } operand.qt = operand.qt.unqualified(); @@ -7851,7 +7789,7 @@ fn unExpr(p: *Parser) Error!?Result { var operand = try p.expect(castExpr); try operand.lvalConversion(p, tok); if (!operand.qt.isInt(p.comp) and !operand.qt.isFloat(p.comp)) - try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.qt)); + try p.err(tok, .invalid_argument_un, .{operand.qt}); try operand.usualUnaryConversion(p, tok); @@ -7863,7 +7801,7 @@ fn unExpr(p: *Parser) Error!?Result { var operand = try p.expect(castExpr); try operand.lvalConversion(p, tok); if (!operand.qt.isInt(p.comp) and !operand.qt.isFloat(p.comp)) - try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.qt)); + try p.err(tok, .invalid_argument_un, .{operand.qt}); try operand.usualUnaryConversion(p, tok); if (operand.val.isArithmetic(p.comp)) { @@ -7880,21 +7818,21 @@ fn unExpr(p: *Parser) Error!?Result { var operand = try p.expect(castExpr); const scalar_kind = operand.qt.scalarKind(p.comp); if (scalar_kind == .void_pointer) - try p.errTok(.gnu_pointer_arith, tok); + try p.err(tok, .gnu_pointer_arith, .{}); if (scalar_kind == .none) - try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.qt)); + try p.err(tok, .invalid_argument_un, .{operand.qt}); if (!scalar_kind.isReal()) - try p.errStr(.complex_prefix_postfix_op, p.tok_i, try p.typeStr(operand.qt)); + try p.err(p.tok_i, .complex_prefix_postfix_op, .{operand.qt}); if (!p.tree.isLval(operand.node) or operand.qt.@"const") { - try p.errTok(.not_assignable, tok); + try p.err(tok, .not_assignable, .{}); return error.ParsingFailed; } try operand.usualUnaryConversion(p, tok); if (operand.val.is(.int, p.comp) or operand.val.is(.int, p.comp)) { if (try operand.val.add(operand.val, .one, operand.qt, p.comp)) - try p.errOverflow(tok, operand); + try p.err(tok, .overflow, .{operand}); } else { operand.val = .{}; } @@ -7908,21 +7846,21 @@ fn unExpr(p: *Parser) Error!?Result { var operand = try p.expect(castExpr); const scalar_kind = operand.qt.scalarKind(p.comp); if (scalar_kind == .void_pointer) - try p.errTok(.gnu_pointer_arith, tok); + try p.err(tok, .gnu_pointer_arith, .{}); if (scalar_kind == .none) - try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.qt)); + try p.err(tok, .invalid_argument_un, .{operand.qt}); if (!scalar_kind.isReal()) - try p.errStr(.complex_prefix_postfix_op, p.tok_i, try p.typeStr(operand.qt)); + try p.err(p.tok_i, .complex_prefix_postfix_op, .{operand.qt}); if (!p.tree.isLval(operand.node) or operand.qt.@"const") { - try p.errTok(.not_assignable, tok); + try p.err(tok, .not_assignable, .{}); return error.ParsingFailed; } try operand.usualUnaryConversion(p, tok); if (operand.val.is(.int, p.comp) or operand.val.is(.int, p.comp)) { if (try operand.val.decrement(operand.val, operand.qt, p.comp)) - try p.errOverflow(tok, operand); + try p.err(tok, .overflow, .{operand}); } else { operand.val = .{}; } @@ -7942,12 +7880,12 @@ fn unExpr(p: *Parser) Error!?Result { operand.val = try operand.val.bitNot(operand.qt, p.comp); } } else if (!scalar_kind.isReal()) { - try p.errStr(.complex_conj, tok, try p.typeStr(operand.qt)); + try p.err(tok, .complex_conj, .{operand.qt}); if (operand.val.is(.complex, p.comp)) { operand.val = try operand.val.complexConj(operand.qt, p.comp); } } else { - try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.qt)); + try p.err(tok, .invalid_argument_un, .{operand.qt}); operand.val = .{}; } try operand.un(p, .bit_not_expr, tok); @@ -7959,7 +7897,7 @@ fn unExpr(p: *Parser) Error!?Result { var operand = try p.expect(castExpr); try operand.lvalConversion(p, tok); if (operand.qt.scalarKind(p.comp) == .none) - try p.errStr(.invalid_argument_un, tok, try p.typeStr(operand.qt)); + try p.err(tok, .invalid_argument_un, .{operand.qt}); try operand.usualUnaryConversion(p, tok); if (operand.val.is(.int, p.comp)) { @@ -7986,7 +7924,7 @@ fn unExpr(p: *Parser) Error!?Result { }; if (try p.typeName()) |qt| { res.qt = qt; - try p.errTok(.expected_parens_around_typename, expected_paren); + try p.err(expected_paren, .expected_parens_around_typename, .{}); } else if (p.eatToken(.l_paren)) |l_paren| { if (try p.typeName()) |ty| { res.qt = ty; @@ -8007,24 +7945,23 @@ fn unExpr(p: *Parser) Error!?Result { } else { const base_type = res.qt.base(p.comp); switch (base_type.type) { - .void => try p.errStr(.pointer_arith_void, tok, "sizeof"), + .void => try p.err(tok, .pointer_arith_void, .{"sizeof"}), .pointer => |pointer_ty| if (pointer_ty.decayed) |decayed_qt| { - const err_str = try p.typePairStrExtra(res.qt, " instead of ", decayed_qt); - try p.errStr(.sizeof_array_arg, tok, err_str); + try p.err(tok, .sizeof_array_arg, .{.{ res.qt, decayed_qt }}); }, else => {}, } if (base_type.qt.sizeofOrNull(p.comp)) |size| { if (size == 0 and p.comp.langopts.emulate == .msvc) { - try p.errTok(.sizeof_returns_zero, tok); + try p.err(tok, .sizeof_returns_zero, .{}); } res.val = try Value.int(size, p.comp); res.qt = p.comp.type_store.size; } else { res.val = .{}; if (res.qt.hasIncompleteSize(p.comp)) { - try p.errStr(.invalid_sizeof, expected_paren - 1, try p.typeStr(res.qt)); + try p.err(expected_paren - 1, .invalid_sizeof, .{res.qt}); res.qt = .invalid; } else { res.qt = p.comp.type_store.size; @@ -8054,7 +7991,7 @@ fn unExpr(p: *Parser) Error!?Result { }; if (try p.typeName()) |qt| { res.qt = qt; - try p.errTok(.expected_parens_around_typename, expected_paren); + try p.err(expected_paren, .expected_parens_around_typename, .{}); } else if (p.eatToken(.l_paren)) |l_paren| { if (try p.typeName()) |qt| { res.qt = qt; @@ -8064,25 +8001,25 @@ fn unExpr(p: *Parser) Error!?Result { res = try p.parseNoEval(unExpr); has_expr = true; - try p.errTok(.alignof_expr, expected_paren); + try p.err(expected_paren, .alignof_expr, .{}); } } else { res = try p.parseNoEval(unExpr); has_expr = true; - try p.errTok(.alignof_expr, expected_paren); + try p.err(expected_paren, .alignof_expr, .{}); } const operand_qt = res.qt; if (res.qt.is(p.comp, .void)) { - try p.errStr(.pointer_arith_void, tok, "alignof"); + try p.err(tok, .pointer_arith_void, .{"alignof"}); } if (res.qt.sizeofOrNull(p.comp) != null) { res.val = try Value.int(res.qt.alignof(p.comp), p.comp); res.qt = p.comp.type_store.size; } else if (!res.qt.isInvalid()) { - try p.errStr(.invalid_alignof, expected_paren, try p.typeStr(res.qt)); + try p.err(expected_paren, .invalid_alignof, .{res.qt}); res.qt = .invalid; } @@ -8112,7 +8049,7 @@ fn unExpr(p: *Parser) Error!?Result { const scalar_kind = operand.qt.scalarKind(p.comp); if (!scalar_kind.isArithmetic()) { - try p.errStr(.invalid_imag, imag_tok, try p.typeStr(operand.qt)); + try p.err(imag_tok, .invalid_imag, .{operand.qt}); } if (!scalar_kind.isReal()) { operand.val = try operand.val.imaginaryPart(p.comp); @@ -8140,7 +8077,7 @@ fn unExpr(p: *Parser) Error!?Result { try operand.lvalConversion(p, tok); if (operand.qt.isInvalid()) return operand; if (!operand.qt.isInt(p.comp) and !operand.qt.isFloat(p.comp)) { - try p.errStr(.invalid_real, real_tok, try p.typeStr(operand.qt)); + try p.err(real_tok, .invalid_real, .{operand.qt}); } // convert _Complex T to T operand.qt = operand.qt.toReal(p.comp); @@ -8175,17 +8112,17 @@ fn compoundLiteral(p: *Parser) Error!?Result { switch (d.storage_class) { .auto, .@"extern", .typedef => |tok| { - try p.errStr(.invalid_compound_literal_storage_class, tok, @tagName(d.storage_class)); + try p.err(tok, .invalid_compound_literal_storage_class, .{@tagName(d.storage_class)}); d.storage_class = .none; }, - .register => if (p.func.qt == null) try p.err(.illegal_storage_on_global), + .register => if (p.func.qt == null) try p.err(p.tok_i, .illegal_storage_on_global, .{}), else => {}, } var qt = (try p.typeName()) orelse { p.tok_i = l_paren; if (any) { - try p.err(.expected_type); + try p.err(p.tok_i, .expected_type, .{}); return error.ParsingFailed; } return null; @@ -8193,12 +8130,12 @@ fn compoundLiteral(p: *Parser) Error!?Result { try p.expectClosing(l_paren, .r_paren); switch (qt.base(p.comp).type) { - .func => try p.err(.func_init), + .func => try p.err(p.tok_i, .func_init, .{}), .array => |array_ty| if (array_ty.len == .variable) { - try p.err(.vla_init); + try p.err(p.tok_i, .vla_init, .{}); }, else => if (qt.hasIncompleteSize(p.comp)) { - try p.errStr(.variable_incomplete_ty, p.tok_i, try p.typeStr(qt)); + try p.err(p.tok_i, .variable_incomplete_ty, .{qt}); return error.ParsingFailed; }, } @@ -8242,14 +8179,14 @@ fn suffixExpr(p: *Parser, lhs: Result) Error!?Result { var operand = lhs; const scalar_kind = operand.qt.scalarKind(p.comp); if (scalar_kind == .void_pointer) - try p.errTok(.gnu_pointer_arith, p.tok_i); + try p.err(p.tok_i, .gnu_pointer_arith, .{}); if (scalar_kind == .none) - try p.errStr(.invalid_argument_un, p.tok_i, try p.typeStr(operand.qt)); + try p.err(p.tok_i, .invalid_argument_un, .{operand.qt}); if (!scalar_kind.isReal()) - try p.errStr(.complex_prefix_postfix_op, p.tok_i, try p.typeStr(operand.qt)); + try p.err(p.tok_i, .complex_prefix_postfix_op, .{operand.qt}); if (!p.tree.isLval(operand.node) or operand.qt.@"const") { - try p.err(.not_assignable); + try p.err(p.tok_i, .not_assignable, .{}); return error.ParsingFailed; } try operand.usualUnaryConversion(p, p.tok_i); @@ -8263,14 +8200,14 @@ fn suffixExpr(p: *Parser, lhs: Result) Error!?Result { var operand = lhs; const scalar_kind = operand.qt.scalarKind(p.comp); if (scalar_kind == .void_pointer) - try p.errTok(.gnu_pointer_arith, p.tok_i); + try p.err(p.tok_i, .gnu_pointer_arith, .{}); if (scalar_kind == .none) - try p.errStr(.invalid_argument_un, p.tok_i, try p.typeStr(operand.qt)); + try p.err(p.tok_i, .invalid_argument_un, .{operand.qt}); if (!scalar_kind.isReal()) - try p.errStr(.complex_prefix_postfix_op, p.tok_i, try p.typeStr(operand.qt)); + try p.err(p.tok_i, .complex_prefix_postfix_op, .{operand.qt}); if (!p.tree.isLval(operand.node) or operand.qt.@"const") { - try p.err(.not_assignable); + try p.err(p.tok_i, .not_assignable, .{}); return error.ParsingFailed; } try operand.usualUnaryConversion(p, p.tok_i); @@ -8294,18 +8231,18 @@ fn suffixExpr(p: *Parser, lhs: Result) Error!?Result { if (index.qt.isInt(p.comp)) { try p.checkArrayBounds(index_before_conversion, array_before_conversion, l_bracket); } else { - try p.errTok(.invalid_index, l_bracket); + try p.err(l_bracket, .invalid_index, .{}); } } else if (index.qt.get(p.comp, .pointer)) |pointer_ty| { index.qt = pointer_ty.child; if (ptr.qt.isInt(p.comp)) { try p.checkArrayBounds(array_before_conversion, index_before_conversion, l_bracket); } else { - try p.errTok(.invalid_index, l_bracket); + try p.err(l_bracket, .invalid_index, .{}); } std.mem.swap(Result, &ptr, &index); } else { - try p.errTok(.invalid_subscript, l_bracket); + try p.err(l_bracket, .invalid_subscript, .{}); } try ptr.saveValue(p); @@ -8368,20 +8305,20 @@ fn fieldAccess( const expr_base_qt = if (is_ptr) expr_qt.childType(p.comp) else expr_qt; const record_qt = if (expr_base_qt.get(p.comp, .atomic)) |atomic| atomic else expr_base_qt; const record_ty = record_qt.getRecord(p.comp) orelse { - try p.errStr(.expected_record_ty, field_name_tok, try p.typeStr(expr_qt)); + try p.err(field_name_tok, .expected_record_ty, .{expr_qt}); return error.ParsingFailed; }; if (record_ty.layout == null) { std.debug.assert(is_ptr); - try p.errStr(.deref_incomplete_ty_ptr, field_name_tok - 2, try p.typeStr(expr_base_qt)); + try p.err(field_name_tok - 2, .deref_incomplete_ty_ptr, .{expr_base_qt}); return error.ParsingFailed; } - if (expr_qt != lhs.qt) try p.errStr(.member_expr_atomic, field_name_tok, try p.typeStr(lhs.qt)); - if (expr_base_qt != record_qt) try p.errStr(.member_expr_atomic, field_name_tok, try p.typeStr(expr_base_qt)); + if (expr_qt != lhs.qt) try p.err(field_name_tok, .member_expr_atomic, .{lhs.qt}); + if (expr_base_qt != record_qt) try p.err(field_name_tok, .member_expr_atomic, .{expr_base_qt}); - if (is_arrow and !is_ptr) try p.errStr(.member_expr_not_ptr, field_name_tok, try p.typeStr(expr_qt)); - if (!is_arrow and is_ptr) try p.errStr(.member_expr_ptr, field_name_tok, try p.typeStr(expr_qt)); + if (is_arrow and !is_ptr) try p.err(field_name_tok, .member_expr_not_ptr, .{expr_qt}); + if (!is_arrow and is_ptr) try p.err(field_name_tok, .member_expr_ptr, .{expr_qt}); const field_name = try p.comp.internString(p.tokSlice(field_name_tok)); try p.validateFieldAccess(record_ty, record_qt, field_name_tok, field_name); @@ -8391,15 +8328,7 @@ fn fieldAccess( fn validateFieldAccess(p: *Parser, record_ty: Type.Record, record_qt: QualType, field_name_tok: TokenIndex, field_name: StringId) Error!void { if (record_ty.hasField(p.comp, field_name)) return; - - p.strings.items.len = 0; - - try p.strings.writer().print("'{s}' in '", .{p.tokSlice(field_name_tok)}); - try record_qt.print(p.comp, p.strings.writer()); - try p.strings.append('\''); - - const duped = try p.comp.diagnostics.arena.allocator().dupe(u8, p.strings.items); - try p.errStr(.no_such_member, field_name_tok, duped); + try p.err(field_name_tok, .no_such_member, .{ p.tokSlice(field_name_tok), record_qt }); return error.ParsingFailed; } @@ -8453,22 +8382,22 @@ fn fieldAccessExtra( fn checkVaStartArg(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, param_tok: TokenIndex, arg: *Result, idx: u32) !void { assert(idx != 0); if (idx > 1) { - try p.errTok(.closing_paren, first_after); + try p.err(first_after, .closing_paren, .{}); return error.ParsingFailed; } const func_qt = p.func.qt orelse { - try p.errTok(.va_start_not_in_func, builtin_tok); + try p.err(builtin_tok, .va_start_not_in_func, .{}); return; }; const func_ty = func_qt.get(p.comp, .func) orelse return; if (func_ty.kind != .variadic or func_ty.params.len == 0) { - return p.errTok(.va_start_fixed_args, builtin_tok); + return p.err(builtin_tok, .va_start_fixed_args, .{}); } const last_param_name = func_ty.params[func_ty.params.len - 1].name; const decl_ref = p.getNode(arg.node, .decl_ref_expr); if (decl_ref == null or last_param_name != try p.comp.internString(p.tokSlice(decl_ref.?.name_tok))) { - try p.errTok(.va_start_not_last_param, param_tok); + try p.err(param_tok, .va_start_not_last_param, .{}); } } @@ -8477,13 +8406,13 @@ fn checkArithOverflowArg(p: *Parser, builtin_tok: TokenIndex, first_after: Token _ = first_after; if (idx <= 1) { if (!arg.qt.isInt(p.comp)) { - return p.errStr(.overflow_builtin_requires_int, param_tok, try p.typeStr(arg.qt)); + return p.err(param_tok, .overflow_builtin_requires_int, .{arg.qt}); } } else if (idx == 2) { - if (!arg.qt.isPointer(p.comp)) return p.errStr(.overflow_result_requires_ptr, param_tok, try p.typeStr(arg.qt)); + if (!arg.qt.isPointer(p.comp)) return p.err(param_tok, .overflow_result_requires_ptr, .{arg.qt}); const child = arg.qt.childType(p.comp); const child_sk = child.scalarKind(p.comp); - if (!child_sk.isInt() or child_sk == .bool or child_sk == .@"enum" or child.@"const") return p.errStr(.overflow_result_requires_ptr, param_tok, try p.typeStr(arg.qt)); + if (!child_sk.isInt() or child_sk == .bool or child_sk == .@"enum" or child.@"const") return p.err(param_tok, .overflow_result_requires_ptr, .{arg.qt}); } } @@ -8491,12 +8420,12 @@ fn checkComplexArg(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, _ = builtin_tok; _ = first_after; if (idx <= 1 and !arg.qt.isFloat(p.comp)) { - try p.errStr(.not_floating_type, param_tok, try p.typeStr(arg.qt)); + try p.err(param_tok, .not_floating_type, .{arg.qt}); } else if (idx == 1) { const prev_idx = p.list_buf.items[p.list_buf.items.len - 1]; const prev_qt = prev_idx.qt(&p.tree); if (!prev_qt.eql(arg.qt, p.comp)) { - try p.errStr(.argument_types_differ, param_tok, try p.typePairStrExtra(prev_qt, " vs ", arg.qt)); + try p.err(param_tok, .argument_types_differ, .{ prev_qt, arg.qt }); } } } @@ -8514,7 +8443,7 @@ fn callExpr(p: *Parser, lhs: Result) Error!Result { const func_type_qt = base_qt.base(p.comp); if (func_type_qt.type != .func) { - try p.errStr(.not_callable, l_paren, try p.typeStr(lhs.qt)); + try p.err(l_paren, .not_callable, .{lhs.qt}); return error.ParsingFailed; } break :blk .{ func_type_qt.qt, func_type_qt.type.func.params.len, func_type_qt.type.func.kind }; @@ -8570,16 +8499,12 @@ fn callExpr(p: *Parser, lhs: Result) Error!Result { const arg_array_len = arg.qt.arrayLen(p.comp); if (arg_array_len != null and arg_array_len.? < param_array_len) { - const extra = Diagnostics.Message.Extra{ .arguments = .{ - .expected = @intCast(arg_array_len.?), - .actual = @intCast(param_array_len), - } }; - try p.errExtra(.array_argument_too_small, param_tok, extra); - try p.errTok(.callee_with_static_array, param.name_tok); + try p.err(param_tok, .array_argument_too_small, .{ arg_array_len.?, param_array_len }); + try p.err(param.name_tok, .callee_with_static_array, .{}); } if (arg.val.isZero(p.comp)) { - try p.errTok(.non_null_argument, param_tok); - try p.errTok(.callee_with_static_array, param.name_tok); + try p.err(param_tok, .non_null_argument, .{}); + try p.err(param.name_tok, .callee_with_static_array, .{}); } } @@ -8601,27 +8526,22 @@ fn callExpr(p: *Parser, lhs: Result) Error!Result { return try call_expr.finish(p, func_qt, list_buf_top, l_paren); } - const actual: u32 = @intCast(arg_count); - const extra = Diagnostics.Message.Extra{ .arguments = .{ - .expected = @intCast(params_len), - .actual = actual, - } }; if (call_expr.paramCountOverride()) |expected| { - if (expected != actual) { - try p.errExtra(.expected_arguments, first_after, .{ .arguments = .{ .expected = expected, .actual = actual } }); + if (expected != arg_count) { + try p.err(first_after, .expected_arguments, .{ params_len, arg_count }); } } else switch (func_kind) { .normal => if (params_len != arg_count) { - try p.errExtra(.expected_arguments, first_after, extra); + try p.err(first_after, .expected_arguments, .{ params_len, arg_count }); }, .variadic => if (arg_count < params_len) { - try p.errExtra(.expected_at_least_arguments, first_after, extra); + try p.err(first_after, .expected_at_least_arguments, .{ params_len, arg_count }); }, .old_style => if (params_len != arg_count) { if (params_len == 0) - try p.errTok(.passing_args_to_kr, first_after) + try p.err(first_after, .passing_args_to_kr, .{}) else - try p.errExtra(.expected_arguments_old, first_after, extra); + try p.err(first_after, .expected_arguments_old, .{ params_len, arg_count }); }, } @@ -8643,7 +8563,7 @@ fn checkArrayBounds(p: *Parser, index: Result, array: Result, tok: TokenIndex) ! if (base_ty.getRecord(p.comp)) |record_ty| { if (access.member_index + 1 == record_ty.fields.len) { if (!index.val.isZero(p.comp)) { - try p.errStr(.old_style_flexible_struct, tok, try index.str(p)); + try p.err(tok, .old_style_flexible_struct, .{index}); } return; } @@ -8653,13 +8573,13 @@ fn checkArrayBounds(p: *Parser, index: Result, array: Result, tok: TokenIndex) ! const index_int = index.val.toInt(u64, p.comp) orelse std.math.maxInt(u64); if (index.qt.signedness(p.comp) == .unsigned) { if (index_int >= array_len) { - try p.errStr(.array_after, tok, try index.str(p)); + try p.err(tok, .array_after, .{index}); } } else { if (index.val.compare(.lt, .zero, p.comp)) { - try p.errStr(.array_before, tok, try index.str(p)); + try p.err(tok, .array_before, .{index}); } else if (index_int >= array_len) { - try p.errStr(.array_after, tok, try index.str(p)); + try p.err(tok, .array_after, .{index}); } } } @@ -8690,7 +8610,7 @@ fn primaryExpr(p: *Parser) Error!?Result { const name = p.tokSlice(name_tok); const interned_name = try p.comp.internString(name); if (interned_name == p.auto_type_decl_name) { - try p.errStr(.auto_type_self_initialized, name_tok, name); + try p.err(name_tok, .auto_type_self_initialized, .{name}); return error.ParsingFailed; } @@ -8711,8 +8631,8 @@ fn primaryExpr(p: *Parser) Error!?Result { } if (sym.val.is(.int, p.comp)) { switch (p.const_decl_folding) { - .gnu_folding_extension => try p.errTok(.const_decl_folded, name_tok), - .gnu_vla_folding_extension => try p.errTok(.const_decl_folded_vla, name_tok), + .gnu_folding_extension => try p.err(name_tok, .const_decl_folded, .{}), + .gnu_vla_folding_extension => try p.err(name_tok, .const_decl_folded_vla, .{}), else => {}, } } @@ -8742,16 +8662,15 @@ fn primaryExpr(p: *Parser) Error!?Result { .r_paren => {}, // closing grouped expr .l_paren => break, // beginning of a call else => { - try p.errTok(.builtin_must_be_called, name_tok); + try p.err(name_tok, .builtin_must_be_called, .{}); return error.ParsingFailed; }, }; if (some.builtin.properties.header != .none) { - try p.errStr(.implicit_builtin, name_tok, name); - try p.errExtra(.implicit_builtin_header_note, name_tok, .{ .builtin_with_header = .{ - .builtin = some.builtin.tag, - .header = some.builtin.properties.header, - } }); + try p.err(name_tok, .implicit_builtin, .{name}); + try p.err(name_tok, .implicit_builtin_header_note, .{ + @tagName(some.builtin.properties.header), Builtin.nameFromTag(some.builtin.tag).span(), + }); } return .{ @@ -8769,9 +8688,9 @@ fn primaryExpr(p: *Parser) Error!?Result { if (p.tok_ids[p.tok_i] == .l_paren and !p.comp.langopts.standard.atLeast(.c23)) { // allow implicitly declaring functions before C99 like `puts("foo")` if (mem.startsWith(u8, name, "__builtin_")) - try p.errStr(.unknown_builtin, name_tok, name) + try p.err(name_tok, .unknown_builtin, .{name}) else - try p.errStr(.implicit_func_decl, name_tok, name); + try p.err(name_tok, .implicit_func_decl, .{name}); const func_qt = try p.comp.type_store.put(p.gpa, .{ .func = .{ .return_type = .int, @@ -8803,7 +8722,7 @@ fn primaryExpr(p: *Parser) Error!?Result { }; } - try p.errStr(.undeclared_identifier, name_tok, p.tokSlice(name_tok)); + try p.err(name_tok, .undeclared_identifier, .{p.tokSlice(name_tok)}); return error.ParsingFailed; }, .keyword_true, .keyword_false => |id| { @@ -8825,7 +8744,7 @@ fn primaryExpr(p: *Parser) Error!?Result { }, .keyword_nullptr => { defer p.tok_i += 1; - try p.errStr(.pre_c23_compat, p.tok_i, "'nullptr'"); + try p.err(p.tok_i, .pre_c23_compat, .{"'nullptr'"}); return Result{ .val = .null, .qt = .nullptr_t, @@ -8864,7 +8783,7 @@ fn primaryExpr(p: *Parser) Error!?Result { p.func.ident = predef; try p.decl_buf.append(predef.node); } - if (p.func.qt == null) try p.err(.predefined_top_level); + if (p.func.qt == null) try p.err(p.tok_i, .predefined_top_level, .{}); return .{ .qt = ty, @@ -8901,7 +8820,7 @@ fn primaryExpr(p: *Parser) Error!?Result { p.func.pretty_ident = predef; try p.decl_buf.append(predef.node); } - if (p.func.qt == null) try p.err(.predefined_top_level); + if (p.func.qt == null) try p.err(p.tok_i, .predefined_top_level, .{}); return .{ .qt = qt, .node = try p.addNode(.{ @@ -9005,12 +8924,12 @@ fn stringLiteral(p: *Parser) Error!Result { var string_kind: text_literal.Kind = .char; while (text_literal.Kind.classify(p.tok_ids[string_end], .string_literal)) |next| : (string_end += 1) { string_kind = string_kind.concat(next) catch { - try p.errTok(.unsupported_str_cat, string_end); + try p.err(string_end, .unsupported_str_cat, .{}); while (p.tok_ids[p.tok_i].isStringLiteral()) : (p.tok_i += 1) {} return error.ParsingFailed; }; if (string_kind == .unterminated) { - try p.errTok(.unterminated_string_literal_error, string_end); + try p.err(string_end, .unterminated_string_literal_error, .{}); p.tok_i = string_end + 1; return error.ParsingFailed; } @@ -9029,10 +8948,17 @@ fn stringLiteral(p: *Parser) Error!Result { while (p.tok_i < string_end) : (p.tok_i += 1) { const this_kind = text_literal.Kind.classify(p.tok_ids[p.tok_i], .string_literal).?; const slice = this_kind.contentSlice(p.tokSlice(p.tok_i)); - var char_literal_parser = text_literal.Parser.init(slice, this_kind, 0x10ffff, p.comp); + var char_literal_parser: text_literal.Parser = .{ + .comp = p.comp, + .literal = slice, + .kind = this_kind, + .max_codepoint = 0x10ffff, + .loc = p.pp.tokens.items(.loc)[p.tok_i], + .expansion_locs = p.pp.expansionSlice(p.tok_i), + }; try p.strings.ensureUnusedCapacity((slice.len + 1) * @intFromEnum(char_width)); // +1 for null terminator - while (char_literal_parser.next()) |item| switch (item) { + while (try char_literal_parser.next()) |item| switch (item) { .value => |v| { switch (char_width) { .@"1" => p.strings.appendAssumeCapacity(@intCast(v)), @@ -9067,7 +8993,7 @@ fn stringLiteral(p: *Parser) Error!Result { }, .improperly_encoded => |bytes| { if (count > 1) { - try p.errTok(.illegal_char_encoding_error, p.tok_i); + try char_literal_parser.err(.illegal_char_encoding_error, .{}); return error.ParsingFailed; } p.strings.appendSliceAssumeCapacity(bytes); @@ -9092,9 +9018,6 @@ fn stringLiteral(p: *Parser) Error!Result { } }, }; - for (char_literal_parser.errors()) |item| { - try p.errExtra(item.tag, p.tok_i, item.extra); - } } p.strings.appendNTimesAssumeCapacity(0, @intFromEnum(char_width)); const slice = p.strings.items[literal_start..]; @@ -9137,9 +9060,9 @@ fn charLiteral(p: *Parser) Error!?Result { const tok_id = p.tok_ids[p.tok_i]; const char_kind = text_literal.Kind.classify(tok_id, .char_literal) orelse { if (tok_id == .empty_char_literal) { - try p.err(.empty_char_literal_error); + try p.err(p.tok_i, .empty_char_literal_error, .{}); } else if (tok_id == .unterminated_char_literal) { - try p.err(.unterminated_char_literal_error); + try p.err(p.tok_i, .unterminated_char_literal_error, .{}); } else unreachable; return .{ .qt = .int, @@ -9147,7 +9070,7 @@ fn charLiteral(p: *Parser) Error!?Result { .node = try p.addNode(.{ .char_literal = .{ .qt = .int, .literal_tok = p.tok_i, .kind = .ascii } }), }; }; - if (char_kind == .utf_8) try p.err(.u8_char_lit); + if (char_kind == .utf_8) try p.err(p.tok_i, .u8_char_lit, .{}); var val: u32 = 0; const slice = char_kind.contentSlice(p.tokSlice(p.tok_i)); @@ -9158,14 +9081,22 @@ fn charLiteral(p: *Parser) Error!?Result { val = slice[0]; } else { const max_codepoint = char_kind.maxCodepoint(p.comp); - var char_literal_parser = text_literal.Parser.init(slice, char_kind, max_codepoint, p.comp); + var char_literal_parser: text_literal.Parser = .{ + .comp = p.comp, + .literal = slice, + .kind = char_kind, + .max_codepoint = max_codepoint, + .loc = p.pp.tokens.items(.loc)[p.tok_i], + .expansion_locs = p.pp.expansionSlice(p.tok_i), + }; + // .init(slice, char_kind, max_codepoint, p.comp); const max_chars_expected = 4; var stack_fallback = std.heap.stackFallback(max_chars_expected * @sizeOf(u32), p.comp.gpa); var chars = std.ArrayList(u32).initCapacity(stack_fallback.get(), max_chars_expected) catch unreachable; // stack allocation already succeeded defer chars.deinit(); - while (char_literal_parser.next()) |item| switch (item) { + while (try char_literal_parser.next()) |item| switch (item) { .value => |v| try chars.append(v), .codepoint => |c| try chars.append(c), .improperly_encoded => |s| { @@ -9181,7 +9112,7 @@ fn charLiteral(p: *Parser) Error!?Result { chars.appendAssumeCapacity(c); } if (max_codepoint_seen > max_codepoint) { - char_literal_parser.err(.char_too_large, .{ .none = {} }); + try char_literal_parser.err(.char_too_large, .{}); } }, }; @@ -9189,16 +9120,16 @@ fn charLiteral(p: *Parser) Error!?Result { is_multichar = chars.items.len > 1; if (is_multichar) { if (char_kind == .char and chars.items.len == 4) { - char_literal_parser.warn(.four_char_char_literal, .{ .none = {} }); + try char_literal_parser.warn(.four_char_char_literal, .{}); } else if (char_kind == .char) { - char_literal_parser.warn(.multichar_literal_warning, .{ .none = {} }); + try char_literal_parser.warn(.multichar_literal_warning, .{}); } else { - const kind = switch (char_kind) { + const kind: []const u8 = switch (char_kind) { .wide => "wide", .utf_8, .utf_16, .utf_32 => "Unicode", else => unreachable, }; - char_literal_parser.err(.invalid_multichar_literal, .{ .str = kind }); + try char_literal_parser.err(.invalid_multichar_literal, .{kind}); } } @@ -9214,11 +9145,7 @@ fn charLiteral(p: *Parser) Error!?Result { } if (multichar_overflow) { - char_literal_parser.err(.char_lit_too_wide, .{ .none = {} }); - } - - for (char_literal_parser.errors()) |item| { - try p.errExtra(item.tag, p.tok_i, item.extra); + try char_literal_parser.err(.char_lit_too_wide, .{}); } } @@ -9294,7 +9221,7 @@ fn parseFloat(p: *Parser, buf: []const u8, suffix: NumberSuffix, tok_i: TokenInd .val = val, }; if (suffix.isImaginary()) { - try p.err(.gnu_imaginary_constant); + try p.err(p.tok_i, .gnu_imaginary_constant, .{}); res.qt = try qt.toComplex(p.comp); res.val = try Value.intern(p.comp, switch (res.qt.bitSizeof(p.comp)) { @@ -9315,9 +9242,9 @@ fn getIntegerPart(p: *Parser, buf: []const u8, prefix: NumberPrefix, tok_i: Toke if (!prefix.digitAllowed(buf[0])) { switch (prefix) { - .binary => try p.errExtra(.invalid_binary_digit, tok_i, .{ .ascii = @intCast(buf[0]) }), - .octal => try p.errExtra(.invalid_octal_digit, tok_i, .{ .ascii = @intCast(buf[0]) }), - .hex => try p.errStr(.invalid_int_suffix, tok_i, buf), + .binary => try p.err(tok_i, .invalid_binary_digit, .{text_literal.Ascii.init(buf[0])}), + .octal => try p.err(tok_i, .invalid_octal_digit, .{text_literal.Ascii.init(buf[0])}), + .hex => try p.err(tok_i, .invalid_int_suffix, .{buf}), .decimal => unreachable, } return error.ParsingFailed; @@ -9328,24 +9255,24 @@ fn getIntegerPart(p: *Parser, buf: []const u8, prefix: NumberPrefix, tok_i: Toke switch (c) { '.' => return buf[0..idx], 'p', 'P' => return if (prefix == .hex) buf[0..idx] else { - try p.errStr(.invalid_int_suffix, tok_i, buf[idx..]); + try p.err(tok_i, .invalid_int_suffix, .{buf[idx..]}); return error.ParsingFailed; }, 'e', 'E' => { switch (prefix) { .hex => continue, .decimal => return buf[0..idx], - .binary => try p.errExtra(.invalid_binary_digit, tok_i, .{ .ascii = @intCast(c) }), - .octal => try p.errExtra(.invalid_octal_digit, tok_i, .{ .ascii = @intCast(c) }), + .binary => try p.err(tok_i, .invalid_binary_digit, .{text_literal.Ascii.init(c)}), + .octal => try p.err(tok_i, .invalid_octal_digit, .{text_literal.Ascii.init(c)}), } return error.ParsingFailed; }, '0'...'9', 'a'...'d', 'A'...'D', 'f', 'F' => { if (!prefix.digitAllowed(c)) { switch (prefix) { - .binary => try p.errExtra(.invalid_binary_digit, tok_i, .{ .ascii = @intCast(c) }), - .octal => try p.errExtra(.invalid_octal_digit, tok_i, .{ .ascii = @intCast(c) }), - .decimal, .hex => try p.errStr(.invalid_int_suffix, tok_i, buf[idx..]), + .binary => try p.err(tok_i, .invalid_binary_digit, .{text_literal.Ascii.init(c)}), + .octal => try p.err(tok_i, .invalid_octal_digit, .{text_literal.Ascii.init(c)}), + .decimal, .hex => try p.err(tok_i, .invalid_int_suffix, .{buf[idx..]}), } return error.ParsingFailed; } @@ -9385,7 +9312,7 @@ fn fixedSizeInt(p: *Parser, base: u8, buf: []const u8, suffix: NumberSuffix, tok .node = undefined, // set later }; if (overflow) { - try p.errTok(.int_literal_too_big, tok_i); + try p.err(tok_i, .int_literal_too_big, .{}); res.qt = .ulong_long; res.node = try p.addNode(.{ .int_literal = .{ .qt = res.qt, .literal_tok = tok_i } }); try res.putValue(p); @@ -9395,7 +9322,7 @@ fn fixedSizeInt(p: *Parser, base: u8, buf: []const u8, suffix: NumberSuffix, tok if (suffix.isSignedInteger() and base == 10) { const max_int = try Value.maxInt(p.comp.type_store.intmax, p.comp); if (interned_val.compare(.gt, max_int, p.comp)) { - try p.errTok(.implicitly_unsigned_literal, tok_i); + try p.err(tok_i, .implicitly_unsigned_literal, .{}); } } @@ -9440,7 +9367,7 @@ fn fixedSizeInt(p: *Parser, base: u8, buf: []const u8, suffix: NumberSuffix, tok fn parseInt(p: *Parser, prefix: NumberPrefix, buf: []const u8, suffix: NumberSuffix, tok_i: TokenIndex) !Result { if (prefix == .binary) { - try p.errTok(.binary_integer_literal, tok_i); + try p.err(tok_i, .binary_integer_literal, .{}); } const base = @intFromEnum(prefix); var res = if (suffix.isBitInt()) @@ -9449,7 +9376,7 @@ fn parseInt(p: *Parser, prefix: NumberPrefix, buf: []const u8, suffix: NumberSuf try p.fixedSizeInt(base, buf, suffix, tok_i); if (suffix.isImaginary()) { - try p.errTok(.gnu_imaginary_constant, tok_i); + try p.err(tok_i, .gnu_imaginary_constant, .{}); res.qt = try res.qt.toComplex(p.comp); res.val = .{}; try res.un(p, .imaginary_literal, tok_i); @@ -9458,8 +9385,8 @@ fn parseInt(p: *Parser, prefix: NumberPrefix, buf: []const u8, suffix: NumberSuf } fn bitInt(p: *Parser, base: u8, buf: []const u8, suffix: NumberSuffix, tok_i: TokenIndex) Error!Result { - try p.errStr(.pre_c23_compat, tok_i, "'_BitInt' suffix for literals"); - try p.errTok(.bitint_suffix, tok_i); + try p.err(tok_i, .pre_c23_compat, .{"'_BitInt' suffix for literals"}); + try p.err(tok_i, .bitint_suffix, .{}); var managed = try big.int.Managed.init(p.gpa); defer managed.deinit(); @@ -9507,7 +9434,7 @@ fn getFracPart(p: *Parser, buf: []const u8, prefix: NumberPrefix, tok_i: TokenIn if (buf.len == 0 or buf[0] != '.') return ""; assert(prefix != .octal); if (prefix == .binary) { - try p.errStr(.invalid_int_suffix, tok_i, buf); + try p.err(tok_i, .invalid_int_suffix, .{buf}); return error.ParsingFailed; } for (buf, 0..) |c, idx| { @@ -9524,7 +9451,7 @@ fn getExponent(p: *Parser, buf: []const u8, prefix: NumberPrefix, tok_i: TokenIn switch (buf[0]) { 'e', 'E' => assert(prefix == .decimal), 'p', 'P' => if (prefix != .hex) { - try p.errStr(.invalid_float_suffix, tok_i, buf); + try p.err(tok_i, .invalid_float_suffix, .{buf}); return error.ParsingFailed; }, else => return "", @@ -9540,7 +9467,7 @@ fn getExponent(p: *Parser, buf: []const u8, prefix: NumberPrefix, tok_i: TokenIn } else buf.len; const exponent = buf[0..end]; if (std.mem.indexOfAny(u8, exponent, "0123456789") == null) { - try p.errTok(.exponent_has_no_digits, tok_i); + try p.err(tok_i, .exponent_has_no_digits, .{}); return error.ParsingFailed; } return exponent; @@ -9565,21 +9492,21 @@ pub fn parseNumberToken(p: *Parser, tok_i: TokenIndex) !Result { const is_float = (exponent.len > 0 or frac.len > 0); const suffix = NumberSuffix.fromString(suffix_str, if (is_float) .float else .int) orelse { if (is_float) { - try p.errStr(.invalid_float_suffix, tok_i, suffix_str); + try p.err(tok_i, .invalid_float_suffix, .{suffix_str}); } else { - try p.errStr(.invalid_int_suffix, tok_i, suffix_str); + try p.err(tok_i, .invalid_int_suffix, .{suffix_str}); } return error.ParsingFailed; }; if (suffix.isFloat80() and p.comp.float80Type() == null) { - try p.errStr(.invalid_float_suffix, tok_i, suffix_str); + try p.err(tok_i, .invalid_float_suffix, .{suffix_str}); return error.ParsingFailed; } if (is_float) { assert(prefix == .hex or prefix == .decimal); if (prefix == .hex and exponent.len == 0) { - try p.errTok(.hex_floating_constant_requires_exponent, tok_i); + try p.err(tok_i, .hex_floating_constant_requires_exponent, .{}); return error.ParsingFailed; } const number = buf[0 .. buf.len - suffix_str.len]; @@ -9595,7 +9522,7 @@ fn ppNum(p: *Parser) Error!Result { if (p.in_macro) { const res_sk = res.qt.scalarKind(p.comp); if (res_sk.isFloat() or !res_sk.isReal()) { - try p.errTok(.float_literal_in_pp_expr, p.tok_i); + try p.err(p.tok_i, .float_literal_in_pp_expr, .{}); return error.ParsingFailed; } res.qt = if (res.qt.signedness(p.comp) == .unsigned) @@ -9651,10 +9578,10 @@ fn genericSelection(p: *Parser) Error!?Result { const start = p.tok_i; if (try p.typeName()) |qt| blk: { switch (qt.base(p.comp).type) { - .array => try p.errTok(.generic_array_type, start), - .func => try p.errTok(.generic_func_type, start), + .array => try p.err(start, .generic_array_type, .{}), + .func => try p.err(start, .generic_func_type, .{}), else => if (qt.isQualified()) { - try p.errTok(.generic_qual_type, start); + try p.err(start, .generic_qual_type, .{}); }, } @@ -9681,8 +9608,8 @@ fn genericSelection(p: *Parser) Error!?Result { const previous_items = p.param_buf.items[0 .. p.param_buf.items.len - 1][param_buf_top..]; for (previous_items) |prev_item| { if (prev_item.qt.eql(qt, p.comp)) { - try p.errStr(.generic_duplicate, start, try p.typeStr(qt)); - try p.errStr(.generic_duplicate_here, prev_item.name_tok, try p.typeStr(qt)); + try p.err(start, .generic_duplicate, .{qt}); + try p.err(prev_item.name_tok, .generic_duplicate_here, .{qt}); } } } else if (p.eatToken(.keyword_default)) |tok| { @@ -9696,14 +9623,14 @@ fn genericSelection(p: *Parser) Error!?Result { }); if (default_tok) |prev| { - try p.errTok(.generic_duplicate_default, tok); - try p.errTok(.previous_case, prev); + try p.err(tok, .generic_duplicate_default, .{}); + try p.err(prev, .previous_case, .{}); } default = res; default_tok = tok; } else { if (p.list_buf.items.len == list_buf_top) { - try p.err(.expected_type); + try p.err(p.tok_i, .expected_type, .{}); return error.ParsingFailed; } break; @@ -9716,7 +9643,7 @@ fn genericSelection(p: *Parser) Error!?Result { if (default_tok != null) { chosen = default; } else { - try p.errStr(.generic_no_match, controlling_tok, try p.typeStr(controlling_qt)); + try p.err(controlling_tok, .generic_no_match, .{controlling_qt}); return error.ParsingFailed; } } else if (default_tok != null) { @@ -9770,7 +9697,7 @@ test "Node locations" { var tree = try Parser.parse(&pp); defer tree.deinit(); - try std.testing.expectEqual(0, comp.diagnostics.list.items.len); + try std.testing.expectEqual(0, comp.diagnostics.total); for (tree.root_decls.items[tree.root_decls.items.len - 3 ..], 0..) |node, i| { const slice = tree.tokSlice(node.tok(&tree)); const expected = switch (i) { diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig new file mode 100644 index 0000000..595afbb --- /dev/null +++ b/src/aro/Parser/Diagnostic.zig @@ -0,0 +1,2274 @@ +const std = @import("std"); + +const Diagnostics = @import("../Diagnostics.zig"); +const LangOpts = @import("../LangOpts.zig"); +const Compilation = @import("../Compilation.zig"); + +const Diagnostic = @This(); + +fmt: []const u8, +kind: Diagnostics.Message.Kind, +opt: ?Diagnostics.Option = null, +extension: bool = false, + +// TODO look into removing these +suppress_version: ?LangOpts.Standard = null, +suppress_unless_version: ?LangOpts.Standard = null, + +const pointer_sign_message = " converts between pointers to integer types with different sign"; + +// Maybe someday this will no longer be needed. +pub const todo: Diagnostic = .{ + .fmt = "TODO: {s}", + .kind = .@"error", +}; + +pub const closing_paren: Diagnostic = .{ + .fmt = "expected closing ')'", + .kind = .@"error", +}; + +pub const to_match_paren: Diagnostic = .{ + .fmt = "to match this '('", + .kind = .note, +}; + +pub const to_match_brace: Diagnostic = .{ + .fmt = "to match this '{'", + .kind = .note, +}; + +pub const to_match_bracket: Diagnostic = .{ + .fmt = "to match this '['", + .kind = .note, +}; + +pub const float_literal_in_pp_expr: Diagnostic = .{ + .fmt = "floating point literal in preprocessor expression", + .kind = .@"error", +}; + +pub const expected_invalid: Diagnostic = .{ + .fmt = "expected '{tok_id}', found invalid bytes", + .kind = .@"error", +}; + +pub const expected_eof: Diagnostic = .{ + .fmt = "expected '{tok_id}' before end of file", + .kind = .@"error", +}; + +pub const expected_token: Diagnostic = .{ + .fmt = "expected '{tok_id}', found '{tok_id}'", + .kind = .@"error", +}; + +pub const expected_expr: Diagnostic = .{ + .fmt = "expected expression", + .kind = .@"error", +}; + +pub const expected_integer_constant_expr: Diagnostic = .{ + .fmt = "expression is not an integer constant expression", + .kind = .@"error", +}; + +pub const missing_type_specifier: Diagnostic = .{ + .fmt = "type specifier missing, defaults to 'int'", + .opt = .@"implicit-int", + .kind = .warning, +}; + +pub const missing_type_specifier_c23: Diagnostic = .{ + .fmt = "a type specifier is required for all declarations", + .kind = .@"error", +}; + +pub const param_not_declared: Diagnostic = .{ + .fmt = "parameter '{s}' was not declared, defaults to 'int'", + .opt = .@"implicit-int", + .kind = .warning, +}; + +pub const multiple_storage_class: Diagnostic = .{ + .fmt = "cannot combine with previous '{s}' declaration specifier", + .kind = .@"error", +}; + +pub const static_assert_failure: Diagnostic = .{ + .fmt = "static assertion failed", + .kind = .@"error", +}; + +pub const static_assert_failure_message: Diagnostic = .{ + .fmt = "static assertion failed {s}", + .kind = .@"error", +}; + +pub const expected_type: Diagnostic = .{ + .fmt = "expected a type", + .kind = .@"error", +}; + +pub const cannot_combine_spec: Diagnostic = .{ + .fmt = "cannot combine with previous '{s}' specifier", + .kind = .@"error", +}; + +pub const cannot_combine_with_typeof: Diagnostic = .{ + .fmt = "'{s} typeof' is invalid", + .kind = .@"error", +}; + +pub const duplicate_decl_spec: Diagnostic = .{ + .fmt = "duplicate '{s}' declaration specifier", + .opt = .@"duplicate-decl-specifier", + .kind = .warning, +}; + +pub const restrict_non_pointer: Diagnostic = .{ + .fmt = "restrict requires a pointer or reference ('{s}' is invalid)", + .kind = .@"error", +}; + +pub const expected_external_decl: Diagnostic = .{ + .fmt = "expected external declaration", + .kind = .@"error", +}; + +pub const expected_ident_or_l_paren: Diagnostic = .{ + .fmt = "expected identifier or '('", + .kind = .@"error", +}; + +pub const missing_declaration: Diagnostic = .{ + .fmt = "declaration does not declare anything", + .opt = .@"missing-declaration", + .kind = .warning, +}; + +pub const func_not_in_root: Diagnostic = .{ + .fmt = "function definition is not allowed here", + .kind = .@"error", +}; + +pub const illegal_initializer: Diagnostic = .{ + .fmt = "illegal initializer (only variables can be initialized)", + .kind = .@"error", +}; + +pub const extern_initializer: Diagnostic = .{ + .fmt = "extern variable has initializer", + .opt = .@"extern-initializer", + .kind = .warning, +}; + +pub const param_before_var_args: Diagnostic = .{ + .fmt = "ISO C requires a named parameter before '...'", + .kind = .@"error", + .suppress_version = .c23, +}; + +pub const void_only_param: Diagnostic = .{ + .fmt = "'void' must be the only parameter if specified", + .kind = .@"error", +}; + +pub const void_param_qualified: Diagnostic = .{ + .fmt = "'void' parameter cannot be qualified", + .kind = .@"error", +}; + +pub const void_must_be_first_param: Diagnostic = .{ + .fmt = "'void' must be the first parameter if specified", + .kind = .@"error", +}; + +pub const invalid_storage_on_param: Diagnostic = .{ + .fmt = "invalid storage class on function parameter", + .kind = .@"error", +}; + +pub const threadlocal_non_var: Diagnostic = .{ + .fmt = "_Thread_local only allowed on variables", + .kind = .@"error", +}; + +pub const func_spec_non_func: Diagnostic = .{ + .fmt = "'{s}' can only appear on functions", + .kind = .@"error", +}; + +pub const illegal_storage_on_func: Diagnostic = .{ + .fmt = "illegal storage class on function", + .kind = .@"error", +}; + +pub const illegal_storage_on_global: Diagnostic = .{ + .fmt = "illegal storage class on global variable", + .kind = .@"error", +}; + +pub const expected_stmt: Diagnostic = .{ + .fmt = "expected statement", + .kind = .@"error", +}; + +pub const func_cannot_return_func: Diagnostic = .{ + .fmt = "function cannot return a function", + .kind = .@"error", +}; + +pub const func_cannot_return_array: Diagnostic = .{ + .fmt = "function cannot return an array", + .kind = .@"error", +}; + +pub const undeclared_identifier: Diagnostic = .{ + .fmt = "use of undeclared identifier '{s}'", + .kind = .@"error", +}; + +pub const not_callable: Diagnostic = .{ + .fmt = "cannot call non function type '{qt}'", + .kind = .@"error", +}; + +pub const unsupported_str_cat: Diagnostic = .{ + .fmt = "unsupported string literal concatenation", + .kind = .@"error", +}; + +pub const static_func_not_global: Diagnostic = .{ + .fmt = "static functions must be global", + .kind = .@"error", +}; + +pub const implicit_func_decl: Diagnostic = .{ + .fmt = "call to undeclared function '{s}'; ISO C99 and later do not support implicit function declarations", + .opt = .@"implicit-function-declaration", + .kind = .@"error", +}; + +pub const unknown_builtin: Diagnostic = .{ + .fmt = "use of unknown builtin '{s}'", + .opt = .@"implicit-function-declaration", + .kind = .@"error", +}; + +pub const implicit_builtin: Diagnostic = .{ + .fmt = "implicitly declaring library function '{s}'", + .kind = .@"error", + .opt = .@"implicit-function-declaration", +}; + +pub const implicit_builtin_header_note: Diagnostic = .{ + .fmt = "include the header <{s}.h> or explicitly provide a declaration for '{s}'", + .kind = .note, + .opt = .@"implicit-function-declaration", +}; + +pub const expected_param_decl: Diagnostic = .{ + .fmt = "expected parameter declaration", + .kind = .@"error", +}; + +pub const invalid_old_style_params: Diagnostic = .{ + .fmt = "identifier parameter lists are only allowed in function definitions", + .kind = .@"error", +}; + +pub const expected_fn_body: Diagnostic = .{ + .fmt = "expected function body after function declaration", + .kind = .@"error", +}; + +pub const invalid_void_param: Diagnostic = .{ + .fmt = "parameter cannot have void type", + .kind = .@"error", +}; + +pub const continue_not_in_loop: Diagnostic = .{ + .fmt = "'continue' statement not in a loop", + .kind = .@"error", +}; + +pub const break_not_in_loop_or_switch: Diagnostic = .{ + .fmt = "'break' statement not in a loop or a switch", + .kind = .@"error", +}; + +pub const unreachable_code: Diagnostic = .{ + .fmt = "unreachable code", + .opt = .@"unreachable-code", + .kind = .warning, +}; + +pub const duplicate_label: Diagnostic = .{ + .fmt = "duplicate label '{s}'", + .kind = .@"error", +}; + +pub const previous_label: Diagnostic = .{ + .fmt = "previous definition of label '{s}' was here", + .kind = .note, +}; + +pub const undeclared_label: Diagnostic = .{ + .fmt = "use of undeclared label '{s}'", + .kind = .@"error", +}; + +pub const case_not_in_switch: Diagnostic = .{ + .fmt = "'{s}' statement not in a switch statement", + .kind = .@"error", +}; + +pub const duplicate_switch_case: Diagnostic = .{ + .fmt = "duplicate case value '{s}'", + .kind = .@"error", +}; + +pub const multiple_default: Diagnostic = .{ + .fmt = "multiple default cases in the same switch", + .kind = .@"error", +}; + +pub const previous_case: Diagnostic = .{ + .fmt = "previous case defined here", + .kind = .note, +}; + +pub const expected_arguments: Diagnostic = .{ + .fmt = "expected {d} argument(s) got {d}", + .kind = .@"error", +}; + +pub const expected_arguments_old: Diagnostic = .{ + .fmt = expected_arguments.fmt, + .kind = .warning, +}; + +pub const callee_with_static_array: Diagnostic = .{ + .fmt = "callee declares array parameter as static here", + .kind = .note, +}; + +pub const array_argument_too_small: Diagnostic = .{ + .fmt = "array argument is too small; contains {d} elements, callee requires at least {d}", + .kind = .warning, + .opt = .@"array-bounds", +}; + +pub const non_null_argument: Diagnostic = .{ + .fmt = "null passed to a callee that requires a non-null argument", + .kind = .warning, + .opt = .nonnull, +}; + +pub const expected_at_least_arguments: Diagnostic = .{ + .fmt = "expected at least {d} argument(s) got {d}", + .kind = .warning, +}; + +pub const invalid_static_star: Diagnostic = .{ + .fmt = "'static' may not be used with an unspecified variable length array size", + .kind = .@"error", +}; + +pub const static_non_param: Diagnostic = .{ + .fmt = "'static' used outside of function parameters", + .kind = .@"error", +}; + +pub const array_qualifiers: Diagnostic = .{ + .fmt = "type qualifier in non parameter array type", + .kind = .@"error", +}; + +pub const star_non_param: Diagnostic = .{ + .fmt = "star modifier used outside of function parameters", + .kind = .@"error", +}; + +pub const variable_len_array_file_scope: Diagnostic = .{ + .fmt = "variable length arrays not allowed at file scope", + .kind = .@"error", +}; + +pub const useless_static: Diagnostic = .{ + .fmt = "'static' useless without a constant size", + .kind = .warning, +}; + +pub const negative_array_size: Diagnostic = .{ + .fmt = "array size must be 0 or greater", + .kind = .@"error", +}; + +pub const array_incomplete_elem: Diagnostic = .{ + .fmt = "array has incomplete element type '{qt}'", + .kind = .@"error", +}; + +pub const array_func_elem: Diagnostic = .{ + .fmt = "arrays cannot have functions as their element type", + .kind = .@"error", +}; + +pub const static_non_outermost_array: Diagnostic = .{ + .fmt = "'static' used in non-outermost array type", + .kind = .@"error", +}; + +pub const qualifier_non_outermost_array: Diagnostic = .{ + .fmt = "type qualifier used in non-outermost array type", + .kind = .@"error", +}; + +pub const array_overflow: Diagnostic = .{ + .fmt = "The pointer incremented by {value} refers past the last possible element in {d}-bit address space containing {d}-bit ({d}-byte) elements (max possible {d} elements)", + .opt = .@"array-bounds", + .kind = .warning, +}; + +pub const overflow: Diagnostic = .{ + .fmt = "overflow in expression; result is '{value}'", + .kind = .warning, + .opt = .@"integer-overflow", +}; + +pub const int_literal_too_big: Diagnostic = .{ + .fmt = "integer literal is too large to be represented in any integer type", + .kind = .@"error", +}; + +pub const indirection_ptr: Diagnostic = .{ + .fmt = "indirection requires pointer operand", + .kind = .@"error", +}; + +pub const addr_of_rvalue: Diagnostic = .{ + .fmt = "cannot take the address of an rvalue", + .kind = .@"error", +}; + +pub const addr_of_bitfield: Diagnostic = .{ + .fmt = "address of bit-field requested", + .kind = .@"error", +}; + +pub const not_assignable: Diagnostic = .{ + .fmt = "expression is not assignable", + .kind = .@"error", +}; + +pub const ident_or_l_brace: Diagnostic = .{ + .fmt = "expected identifier or '{'", + .kind = .@"error", +}; + +pub const empty_enum: Diagnostic = .{ + .fmt = "empty enum is invalid", + .kind = .@"error", +}; + +pub const redefinition: Diagnostic = .{ + .fmt = "redefinition of '{s}'", + .kind = .@"error", +}; + +pub const previous_definition: Diagnostic = .{ + .fmt = "previous definition is here", + .kind = .note, +}; + +pub const expected_identifier: Diagnostic = .{ + .fmt = "expected identifier", + .kind = .@"error", +}; + +pub const expected_str_literal: Diagnostic = .{ + .fmt = "expected string literal for diagnostic message in static_assert", + .kind = .@"error", +}; + +pub const expected_str_literal_in: Diagnostic = .{ + .fmt = "expected string literal in '{s}'", + .kind = .@"error", +}; + +pub const parameter_missing: Diagnostic = .{ + .fmt = "parameter named '{s}' is missing", + .kind = .@"error", +}; + +pub const empty_record: Diagnostic = .{ + .fmt = "empty {s} is a GNU extension", + .opt = .@"gnu-empty-struct", + .kind = .off, + .extension = true, +}; + +pub const empty_record_size: Diagnostic = .{ + .fmt = "empty {s} has size 0 in C, size 1 in C++", + .opt = .@"c++-compat", + .kind = .off, +}; + +pub const wrong_tag: Diagnostic = .{ + .fmt = "use of '{s}' with tag type that does not match previous definition", + .kind = .@"error", +}; + +pub const expected_parens_around_typename: Diagnostic = .{ + .fmt = "expected parentheses around type name", + .kind = .@"error", +}; + +pub const alignof_expr: Diagnostic = .{ + .fmt = "'_Alignof' applied to an expression is a GNU extension", + .opt = .@"gnu-alignof-expression", + .kind = .warning, + .extension = true, +}; + +pub const invalid_alignof: Diagnostic = .{ + .fmt = "invalid application of 'alignof' to an incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const invalid_sizeof: Diagnostic = .{ + .fmt = "invalid application of 'sizeof' to an incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const generic_qual_type: Diagnostic = .{ + .fmt = "generic association with qualifiers cannot be matched with", + .opt = .@"generic-qual-type", + .kind = .warning, +}; + +pub const generic_array_type: Diagnostic = .{ + .fmt = "generic association array type cannot be matched with", + .opt = .@"generic-qual-type", + .kind = .warning, +}; + +pub const generic_func_type: Diagnostic = .{ + .fmt = "generic association function type cannot be matched with", + .opt = .@"generic-qual-type", + .kind = .warning, +}; + +pub const generic_duplicate: Diagnostic = .{ + .fmt = "type '{qt}' in generic association compatible with previously specified type", + .kind = .@"error", +}; + +pub const generic_duplicate_here: Diagnostic = .{ + .fmt = "compatible type '{qt}' specified here", + .kind = .note, +}; + +pub const generic_duplicate_default: Diagnostic = .{ + .fmt = "duplicate default generic association", + .kind = .@"error", +}; + +pub const generic_no_match: Diagnostic = .{ + .fmt = "controlling expression type '{qt}' not compatible with any generic association type", + .kind = .@"error", +}; + +pub const must_use_struct: Diagnostic = .{ + .fmt = "must use 'struct' tag to refer to type '{qt}'", + .kind = .@"error", +}; + +pub const must_use_union: Diagnostic = .{ + .fmt = "must use 'union' tag to refer to type '{qt}'", + .kind = .@"error", +}; + +pub const must_use_enum: Diagnostic = .{ + .fmt = "must use 'enum' tag to refer to type '{qt}'", + .kind = .@"error", +}; + +pub const redefinition_different_sym: Diagnostic = .{ + .fmt = "redefinition of '{s}' as different kind of symbol", + .kind = .@"error", +}; + +pub const redefinition_incompatible: Diagnostic = .{ + .fmt = "redefinition of '{s}' with a different type", + .kind = .@"error", +}; + +pub const redefinition_of_parameter: Diagnostic = .{ + .fmt = "redefinition of parameter '{s}'", + .kind = .@"error", +}; + +pub const invalid_bin_types: Diagnostic = .{ + .fmt = "invalid operands to binary expression ('{qt}' and '{qt}')", + .kind = .@"error", +}; + +pub const comparison_ptr_int: Diagnostic = .{ + .fmt = "comparison between pointer and integer ('{qt}' and '{qt}')", + .kind = .warning, + .opt = .@"pointer-integer-compare", +}; + +pub const comparison_distinct_ptr: Diagnostic = .{ + .fmt = "comparison of distinct pointer types ('{qt}' and '{qt}')", + .kind = .warning, + .opt = .@"compare-distinct-pointer-types", +}; + +pub const incompatible_pointers: Diagnostic = .{ + .fmt = "incompatible pointer types ('{qt}' and '{qt}')", + .kind = .@"error", +}; + +pub const invalid_argument_un: Diagnostic = .{ + .fmt = "invalid argument type '{qt}' to unary expression", + .kind = .@"error", +}; + +pub const incompatible_assign: Diagnostic = .{ + .fmt = "assignment to '{qt}' from incompatible type '{qt}'", + .kind = .@"error", +}; + +pub const implicit_ptr_to_int: Diagnostic = .{ + .fmt = "implicit pointer to integer conversion from '{qt}' to '{qt}'", + .kind = .warning, + .opt = .@"int-conversion", +}; + +pub const invalid_cast_to_float: Diagnostic = .{ + .fmt = "pointer cannot be cast to type '{qt}'", + .kind = .@"error", +}; + +pub const invalid_cast_to_pointer: Diagnostic = .{ + .fmt = "operand of type '{qt}' cannot be cast to a pointer type", + .kind = .@"error", +}; + +pub const invalid_cast_type: Diagnostic = .{ + .fmt = "cannot cast to non arithmetic or pointer type '{qt}'", + .kind = .@"error", +}; + +pub const invalid_cast_operand_type: Diagnostic = .{ + .fmt = "operand of type '{qt}' where arithmetic or pointer type is required", + .kind = .@"error", +}; + +pub const qual_cast: Diagnostic = .{ + .fmt = "cast to type '{qt}' will not preserve qualifiers", + .opt = .@"cast-qualifiers", + .kind = .warning, +}; + +pub const invalid_index: Diagnostic = .{ + .fmt = "array subscript is not an integer", + .kind = .@"error", +}; + +pub const invalid_subscript: Diagnostic = .{ + .fmt = "subscripted value is not an array or pointer", + .kind = .@"error", +}; + +pub const array_after: Diagnostic = .{ + .fmt = "array index {s} is past the end of the array", + .opt = .@"array-bounds", + .kind = .warning, +}; + +pub const array_before: Diagnostic = .{ + .fmt = "array index {s} is before the beginning of the array", + .opt = .@"array-bounds", + .kind = .warning, +}; + +pub const statement_int: Diagnostic = .{ + .fmt = "statement requires expression with integer type ('{qt}' invalid)", + .kind = .@"error", +}; + +pub const statement_scalar: Diagnostic = .{ + .fmt = "statement requires expression with scalar type ('{qt}' invalid)", + .kind = .@"error", +}; + +pub const func_should_return: Diagnostic = .{ + .fmt = "non-void function '{s}' should return a value", + .opt = .@"return-type", + .kind = .@"error", +}; + +pub const incompatible_return: Diagnostic = .{ + .fmt = "returning '{qt}' from a function with incompatible result type '{qt}'", + .kind = .@"error", +}; + +pub const incompatible_return_sign: Diagnostic = .{ + .fmt = incompatible_return.fmt ++ pointer_sign_message, + .kind = .warning, + .opt = .@"pointer-sign", +}; + +pub const implicit_int_to_ptr: Diagnostic = .{ + .fmt = "implicit integer to pointer conversion from '{qt}' to '{qt}'", + .opt = .@"int-conversion", + .kind = .warning, +}; + +pub const func_does_not_return: Diagnostic = .{ + .fmt = "non-void function '{s}' does not return a value", + .opt = .@"return-type", + .kind = .warning, +}; + +pub const void_func_returns_value: Diagnostic = .{ + .fmt = "void function '{s}' should not return a value", + .opt = .@"return-type", + .kind = .@"error", +}; + +pub const incompatible_arg: Diagnostic = .{ + .fmt = "passing '{qt}' to parameter of incompatible type '{qt}'", + .kind = .@"error", +}; + +pub const incompatible_ptr_arg: Diagnostic = .{ + .fmt = "passing '{qt}' to parameter of incompatible type '{qt}'", + .kind = .warning, + .opt = .@"incompatible-pointer-types", +}; + +pub const incompatible_ptr_arg_sign: Diagnostic = .{ + .fmt = incompatible_ptr_arg.fmt ++ pointer_sign_message, + .kind = .warning, + .opt = .@"pointer-sign", +}; + +pub const parameter_here: Diagnostic = .{ + .fmt = "passing argument to parameter here", + .kind = .note, +}; + +pub const atomic_array: Diagnostic = .{ + .fmt = "_Atomic cannot be applied to array type '{qt}'", + .kind = .@"error", +}; + +pub const atomic_func: Diagnostic = .{ + .fmt = "_Atomic cannot be applied to function type '{qt}'", + .kind = .@"error", +}; + +pub const atomic_incomplete: Diagnostic = .{ + .fmt = "_Atomic cannot be applied to incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const atomic_atomic: Diagnostic = .{ + .fmt = "_Atomic cannot be applied to atomic type '{qt}'", + .kind = .@"error", +}; + +pub const atomic_complex: Diagnostic = .{ + .fmt = "_Atomic cannot be applied to complex type '{qt}'", + .kind = .@"error", +}; + +pub const atomic_qualified: Diagnostic = .{ + .fmt = "_Atomic cannot be applied to qualified type '{qt}'", + .kind = .@"error", +}; + +pub const atomic_auto: Diagnostic = .{ + .fmt = "_Atomic cannot be applied to type 'auto' in C23", + .kind = .@"error", +}; + +// pub const atomic_access: Diagnostic = .{ +// .fmt = "accessing a member of an atomic structure or union is undefined behavior", +// .opt = .@"atomic-access", +// .kind = .@"error", +// }; + +pub const addr_of_register: Diagnostic = .{ + .fmt = "address of register variable requested", + .kind = .@"error", +}; + +pub const variable_incomplete_ty: Diagnostic = .{ + .fmt = "variable has incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const parameter_incomplete_ty: Diagnostic = .{ + .fmt = "parameter has incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const tentative_array: Diagnostic = .{ + .fmt = "tentative array definition assumed to have one element", + .kind = .warning, +}; + +pub const deref_incomplete_ty_ptr: Diagnostic = .{ + .fmt = "dereferencing pointer to incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const alignas_on_func: Diagnostic = .{ + .fmt = "'_Alignas' attribute only applies to variables and fields", + .kind = .@"error", +}; + +pub const alignas_on_param: Diagnostic = .{ + .fmt = "'_Alignas' attribute cannot be applied to a function parameter", + .kind = .@"error", +}; + +pub const minimum_alignment: Diagnostic = .{ + .fmt = "requested alignment is less than minimum alignment of {d}", + .kind = .@"error", +}; + +pub const maximum_alignment: Diagnostic = .{ + .fmt = "requested alignment of {s} is too large", + .kind = .@"error", +}; + +pub const negative_alignment: Diagnostic = .{ + .fmt = "requested negative alignment of {s} is invalid", + .kind = .@"error", +}; + +pub const align_ignored: Diagnostic = .{ + .fmt = "'_Alignas' attribute is ignored here", + .kind = .warning, +}; + +// pub const zero_align_ignored: Diagnostic = .{ +// .fmt = "requested alignment of zero is ignored", +// .kind = .warning, +// }; + +pub const non_pow2_align: Diagnostic = .{ + .fmt = "requested alignment is not a power of 2", + .kind = .@"error", +}; + +pub const pointer_mismatch: Diagnostic = .{ + .fmt = "pointer type mismatch ('{qt}' and '{qt}')", + .opt = .@"pointer-type-mismatch", + .kind = .warning, +}; + +pub const static_assert_not_constant: Diagnostic = .{ + .fmt = "static assertion expression is not an integral constant expression", + .kind = .@"error", +}; + +pub const static_assert_missing_message: Diagnostic = .{ + .fmt = "'_Static_assert' with no message is a C23 extension", + .opt = .@"c23-extensions", + .kind = .warning, + .suppress_version = .c23, + .extension = true, +}; + +pub const pre_c23_compat: Diagnostic = .{ + .fmt = "{s} is incompatible with C standards before C23", + .kind = .off, + .suppress_unless_version = .c23, + .opt = .@"pre-c23-compat", +}; + +pub const unbound_vla: Diagnostic = .{ + .fmt = "variable length array must be bound in function definition", + .kind = .@"error", +}; + +pub const array_too_large: Diagnostic = .{ + .fmt = "array is too large", + .kind = .@"error", +}; + +pub const record_too_large: Diagnostic = .{ + .fmt = "type '{qt}' is too large", + .kind = .@"error", +}; + +pub const incompatible_ptr_init: Diagnostic = .{ + .fmt = "incompatible pointer types initializing '{qt}' from incompatible type '{qt}'", + .opt = .@"incompatible-pointer-types", + .kind = .warning, +}; + +pub const incompatible_ptr_init_sign: Diagnostic = .{ + .fmt = incompatible_ptr_init.fmt ++ pointer_sign_message, + .opt = .@"pointer-sign", + .kind = .warning, +}; + +pub const incompatible_ptr_assign: Diagnostic = .{ + .fmt = "incompatible pointer types assigning to '{qt}' from incompatible type '{qt}'", + .opt = .@"incompatible-pointer-types", + .kind = .warning, +}; + +pub const incompatible_ptr_assign_sign: Diagnostic = .{ + .fmt = incompatible_ptr_assign.fmt ++ pointer_sign_message, + .opt = .@"pointer-sign", + .kind = .warning, +}; + +pub const vla_init: Diagnostic = .{ + .fmt = "variable-sized object may not be initialized", + .kind = .@"error", +}; + +pub const func_init: Diagnostic = .{ + .fmt = "illegal initializer type", + .kind = .@"error", +}; + +pub const incompatible_init: Diagnostic = .{ + .fmt = "initializing '{qt}' from incompatible type '{qt}'", + .kind = .@"error", +}; + +pub const empty_scalar_init: Diagnostic = .{ + .fmt = "scalar initializer cannot be empty", + .kind = .@"error", +}; + +pub const excess_scalar_init: Diagnostic = .{ + .fmt = "excess elements in scalar initializer", + .kind = .warning, + .opt = .@"excess-initializers", +}; + +pub const excess_str_init: Diagnostic = .{ + .fmt = "excess elements in string initializer", + .kind = .warning, + .opt = .@"excess-initializers", +}; + +pub const excess_struct_init: Diagnostic = .{ + .fmt = "excess elements in struct initializer", + .kind = .warning, + .opt = .@"excess-initializers", +}; + +pub const excess_array_init: Diagnostic = .{ + .fmt = "excess elements in array initializer", + .kind = .warning, + .opt = .@"excess-initializers", +}; + +pub const str_init_too_long: Diagnostic = .{ + .fmt = "initializer-string for char array is too long", + .opt = .@"excess-initializers", + .kind = .warning, +}; + +pub const arr_init_too_long: Diagnostic = .{ + .fmt = "cannot initialize type '{qt}' with array of type '{qt}'", + .kind = .@"error", +}; + +pub const division_by_zero: Diagnostic = .{ + .fmt = "{s} by zero is undefined", + .kind = .warning, + .opt = .@"division-by-zero", +}; + +pub const division_by_zero_macro: Diagnostic = .{ + .fmt = "{s} by zero in preprocessor expression", + .kind = .@"error", +}; + +pub const builtin_choose_cond: Diagnostic = .{ + .fmt = "'__builtin_choose_expr' requires a constant expression", + .kind = .@"error", +}; + +pub const alignas_unavailable: Diagnostic = .{ + .fmt = "'_Alignas' attribute requires integer constant expression", + .kind = .@"error", +}; + +pub const case_val_unavailable: Diagnostic = .{ + .fmt = "case value must be an integer constant expression", + .kind = .@"error", +}; + +pub const enum_val_unavailable: Diagnostic = .{ + .fmt = "enum value must be an integer constant expression", + .kind = .@"error", +}; + +pub const incompatible_array_init: Diagnostic = .{ + .fmt = "cannot initialize array of type '{qt}' with array of type '{qt}'", + .kind = .@"error", +}; + +pub const array_init_str: Diagnostic = .{ + .fmt = "array initializer must be an initializer list or wide string literal", + .kind = .@"error", +}; + +pub const initializer_overrides: Diagnostic = .{ + .fmt = "initializer overrides previous initialization", + .opt = .@"initializer-overrides", + .kind = .warning, +}; + +pub const previous_initializer: Diagnostic = .{ + .fmt = "previous initialization", + .kind = .note, +}; + +pub const invalid_array_designator: Diagnostic = .{ + .fmt = "array designator used for non-array type '{qt}'", + .kind = .@"error", +}; + +pub const negative_array_designator: Diagnostic = .{ + .fmt = "array designator value {s} is negative", + .kind = .@"error", +}; + +pub const oob_array_designator: Diagnostic = .{ + .fmt = "array designator index {s} exceeds array bounds", + .kind = .@"error", +}; + +pub const invalid_field_designator: Diagnostic = .{ + .fmt = "field designator used for non-record type '{qt}'", + .kind = .@"error", +}; + +pub const no_such_field_designator: Diagnostic = .{ + .fmt = "record type has no field named '{s}'", + .kind = .@"error", +}; + +pub const empty_aggregate_init_braces: Diagnostic = .{ + .fmt = "initializer for aggregate with no elements requires explicit braces", + .kind = .@"error", +}; + +pub const ptr_init_discards_quals: Diagnostic = .{ + .fmt = "initializing '{qt}' from incompatible type '{qt}' discards qualifiers", + .kind = .warning, + .opt = .@"incompatible-pointer-types-discards-qualifiers", +}; + +pub const ptr_assign_discards_quals: Diagnostic = .{ + .fmt = "assigning to '{qt}' from incompatible type '{qt}' discards qualifiers", + .kind = .warning, + .opt = .@"incompatible-pointer-types-discards-qualifiers", +}; + +pub const ptr_ret_discards_quals: Diagnostic = .{ + .fmt = "returning '{qt}' from a function with incompatible result type '{qt}' discards qualifiers", + .kind = .warning, + .opt = .@"incompatible-pointer-types-discards-qualifiers", +}; + +pub const ptr_arg_discards_quals: Diagnostic = .{ + .fmt = "passing '{qt}' to parameter of incompatible type '{qt}' discards qualifiers", + .kind = .warning, + .opt = .@"incompatible-pointer-types-discards-qualifiers", +}; + +pub const unknown_attribute: Diagnostic = .{ + .fmt = "unknown attribute '{s}' ignored", + .kind = .warning, + .opt = .@"unknown-attributes", +}; + +pub const ignored_attribute: Diagnostic = .{ + .fmt = "attribute '{s}' ignored on {s}", + .kind = .warning, + .opt = .@"ignored-attributes", +}; + +pub const invalid_fallthrough: Diagnostic = .{ + .fmt = "fallthrough annotation does not directly precede switch label", + .kind = .@"error", +}; + +pub const cannot_apply_attribute_to_statement: Diagnostic = .{ + .fmt = "'{s}' attribute cannot be applied to a statement", + .kind = .@"error", +}; + +pub const gnu_label_as_value: Diagnostic = .{ + .fmt = "use of GNU address-of-label extension", + .opt = .@"gnu-label-as-value", + .kind = .off, + .extension = true, +}; + +pub const expected_record_ty: Diagnostic = .{ + .fmt = "member reference base type '{qt}' is not a structure or union", + .kind = .@"error", +}; + +pub const member_expr_not_ptr: Diagnostic = .{ + .fmt = "member reference type '{qt}' is not a pointer; did you mean to use '.'?", + .kind = .@"error", +}; + +pub const member_expr_ptr: Diagnostic = .{ + .fmt = "member reference type '{qt}' is a pointer; did you mean to use '->'?", + .kind = .@"error", +}; + +pub const member_expr_atomic: Diagnostic = .{ + .fmt = "accessing a member of atomic type '{qt}' is undefined behavior", + .kind = .@"error", +}; + +pub const no_such_member: Diagnostic = .{ + .fmt = "no member named '{s}' in '{qt}'", + .kind = .@"error", +}; + +pub const invalid_computed_goto: Diagnostic = .{ + .fmt = "computed goto in function with no address-of-label expressions", + .kind = .@"error", +}; + +pub const empty_translation_unit: Diagnostic = .{ + .fmt = "ISO C requires a translation unit to contain at least one declaration", + .opt = .@"empty-translation-unit", + .kind = .off, +}; + +pub const omitting_parameter_name: Diagnostic = .{ + .fmt = "omitting the parameter name in a function definition is a C23 extension", + .opt = .@"c23-extensions", + .kind = .warning, + .suppress_version = .c23, + .extension = true, +}; + +pub const non_int_bitfield: Diagnostic = .{ + .fmt = "bit-field has non-integer type '{qt}'", + .kind = .@"error", +}; + +pub const negative_bitwidth: Diagnostic = .{ + .fmt = "bit-field has negative width ({s})", + .kind = .@"error", +}; + +pub const zero_width_named_field: Diagnostic = .{ + .fmt = "named bit-field has zero width", + .kind = .@"error", +}; + +pub const bitfield_too_big: Diagnostic = .{ + .fmt = "width of bit-field exceeds width of its type", + .kind = .@"error", +}; + +pub const invalid_utf8: Diagnostic = .{ + .fmt = "source file is not valid UTF-8", + .kind = .@"error", +}; + +pub const implicitly_unsigned_literal: Diagnostic = .{ + .fmt = "integer literal is too large to be represented in a signed integer type, interpreting as unsigned", + .opt = .@"implicitly-unsigned-literal", + .kind = .warning, +}; + +pub const invalid_preproc_operator: Diagnostic = .{ + .fmt = "token is not a valid binary operator in a preprocessor subexpression", + .kind = .@"error", +}; + +pub const c99_compat: Diagnostic = .{ + .fmt = "using this character in an identifier is incompatible with C99", + .kind = .off, + .opt = .@"c99-compat", +}; + +pub const unexpected_character: Diagnostic = .{ + .fmt = "unexpected character ", + .kind = .@"error", +}; + +pub const invalid_identifier_start_char: Diagnostic = .{ + .fmt = "character not allowed at the start of an identifier", + .kind = .@"error", +}; + +pub const unicode_zero_width: Diagnostic = .{ + .fmt = "identifier contains Unicode character that is invisible in some environments", + .kind = .warning, + .opt = .@"unicode-homoglyph", +}; + +pub const unicode_homoglyph: Diagnostic = .{ + .fmt = "treating Unicode character as identifier character rather than as '{s}' symbol", + .kind = .warning, + .opt = .@"unicode-homoglyph", +}; + +pub const meaningless_asm_qual: Diagnostic = .{ + .fmt = "meaningless '{s}' on assembly outside function", + .kind = .@"error", +}; + +pub const duplicate_asm_qual: Diagnostic = .{ + .fmt = "duplicate asm qualifier '{s}'", + .kind = .@"error", +}; + +pub const invalid_asm_str: Diagnostic = .{ + .fmt = "cannot use {s} string literal in assembly", + .kind = .@"error", +}; + +pub const dollar_in_identifier_extension: Diagnostic = .{ + .fmt = "'$' in identifier", + .opt = .@"dollar-in-identifier-extension", + .kind = .off, + .extension = true, +}; + +pub const dollars_in_identifiers: Diagnostic = .{ + .fmt = "illegal character '$' in identifier", + .kind = .@"error", +}; + +pub const predefined_top_level: Diagnostic = .{ + .fmt = "predefined identifier is only valid inside function", + .opt = .@"predefined-identifier-outside-function", + .kind = .warning, +}; + +pub const incompatible_va_arg: Diagnostic = .{ + .fmt = "first argument to va_arg, is of type '{qt}' and not 'va_list'", + .kind = .@"error", +}; + +pub const too_many_scalar_init_braces: Diagnostic = .{ + .fmt = "too many braces around scalar initializer", + .opt = .@"many-braces-around-scalar-init", + .kind = .warning, +}; + +// pub const uninitialized_in_own_init: Diagnostic = .{ +// .fmt = "variable '{s}' is uninitialized when used within its own initialization", +// .opt = .uninitialized, +// .kind = .off, +// }; + +pub const gnu_statement_expression: Diagnostic = .{ + .fmt = "use of GNU statement expression extension", + .opt = .@"gnu-statement-expression", + .kind = .off, + .extension = true, +}; + +pub const stmt_expr_not_allowed_file_scope: Diagnostic = .{ + .fmt = "statement expression not allowed at file scope", + .kind = .@"error", +}; + +pub const gnu_imaginary_constant: Diagnostic = .{ + .fmt = "imaginary constants are a GNU extension", + .opt = .@"gnu-imaginary-constant", + .kind = .off, + .extension = true, +}; + +pub const plain_complex: Diagnostic = .{ + .fmt = "plain '_Complex' requires a type specifier; assuming '_Complex double'", + .kind = .warning, +}; + +pub const complex_int: Diagnostic = .{ + .fmt = "complex integer types are a GNU extension", + .opt = .@"gnu-complex-integer", + .kind = .off, + .extension = true, +}; + +pub const qual_on_ret_type: Diagnostic = .{ + .fmt = "'{s}' type qualifier on return type has no effect", + .opt = .@"ignored-qualifiers", + .kind = .off, +}; + +pub const extra_semi: Diagnostic = .{ + .fmt = "extra ';' outside of a function", + .opt = .@"extra-semi", + .kind = .off, +}; + +pub const func_field: Diagnostic = .{ + .fmt = "field declared as a function", + .kind = .@"error", +}; + +pub const expected_member_name: Diagnostic = .{ + .fmt = "expected member name after declarator", + .kind = .@"error", +}; + +pub const vla_field: Diagnostic = .{ + .fmt = "variable length array fields extension is not supported", + .kind = .@"error", +}; + +pub const field_incomplete_ty: Diagnostic = .{ + .fmt = "field has incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const flexible_in_union: Diagnostic = .{ + .fmt = "flexible array member in union is not allowed", + .kind = .@"error", +}; + +pub const flexible_in_union_msvc: Diagnostic = .{ + .fmt = "flexible array member in union is a Microsoft extension", + .kind = .off, + .opt = .@"microsoft-flexible-array", + .extension = true, +}; + +pub const flexible_non_final: Diagnostic = .{ + .fmt = "flexible array member is not at the end of struct", + .kind = .@"error", +}; + +pub const flexible_in_empty: Diagnostic = .{ + .fmt = "flexible array member in otherwise empty struct", + .kind = .@"error", +}; + +pub const flexible_in_empty_msvc: Diagnostic = .{ + .fmt = "flexible array member in otherwise empty struct is a Microsoft extension", + .kind = .off, + .opt = .@"microsoft-flexible-array", + .extension = true, +}; + +pub const duplicate_member: Diagnostic = .{ + .fmt = "duplicate member '{s}'", + .kind = .@"error", +}; + +pub const binary_integer_literal: Diagnostic = .{ + .fmt = "binary integer literals are a GNU extension", + .kind = .off, + .opt = .@"gnu-binary-literal", + .extension = true, +}; + +pub const builtin_must_be_called: Diagnostic = .{ + .fmt = "builtin function must be directly called", + .kind = .@"error", +}; + +pub const va_start_not_in_func: Diagnostic = .{ + .fmt = "'va_start' cannot be used outside a function", + .kind = .@"error", +}; + +pub const va_start_fixed_args: Diagnostic = .{ + .fmt = "'va_start' used in a function with fixed args", + .kind = .@"error", +}; + +pub const va_start_not_last_param: Diagnostic = .{ + .fmt = "second argument to 'va_start' is not the last named parameter", + .opt = .varargs, + .kind = .warning, +}; + +pub const attribute_not_enough_args: Diagnostic = .{ + .fmt = "'{s}' attribute takes at least {d} argument(s)", + .kind = .@"error", +}; + +pub const attribute_too_many_args: Diagnostic = .{ + .fmt = "'{s}' attribute takes at most {d} argument(s)", + .kind = .@"error", +}; + +pub const attribute_arg_invalid: Diagnostic = .{ + .fmt = "Attribute argument is invalid, expected {s} but got {s}", + .kind = .@"error", +}; + +pub const unknown_attr_enum: Diagnostic = .{ + .fmt = "Unknown `{s}` argument. Possible values are: {s}", + .kind = .@"error", +}; + +pub const attribute_requires_identifier: Diagnostic = .{ + .fmt = "'{s}' attribute requires an identifier", + .kind = .@"error", +}; + +pub const attribute_int_out_of_range: Diagnostic = .{ + .fmt = "attribute value '{s}' out of range", + .kind = .@"error", +}; + +pub const declspec_not_enabled: Diagnostic = .{ + .fmt = "'__declspec' attributes are not enabled; use '-fdeclspec' or '-fms-extensions' to enable support for __declspec attributes", + .kind = .@"error", +}; + +pub const declspec_attr_not_supported: Diagnostic = .{ + .fmt = "__declspec attribute '{s}' is not supported", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const deprecated_declarations: Diagnostic = .{ + .fmt = "'{s}' is deprecated{s}{s}", + .opt = .@"deprecated-declarations", + .kind = .warning, +}; + +pub const deprecated_note: Diagnostic = .{ + .fmt = "'{s}' has been explicitly marked deprecated here", + .opt = .@"deprecated-declarations", + .kind = .note, +}; + +pub const unavailable: Diagnostic = .{ + .fmt = "'{s}' is unavailable{s}{s}", + .kind = .@"error", +}; + +pub const unavailable_note: Diagnostic = .{ + .fmt = "'{s}' has been explicitly marked unavailable here", + .kind = .note, +}; + +pub const warning_attribute: Diagnostic = .{ + .fmt = "call to '{s}' declared with attribute warning: {s}", + .kind = .warning, + .opt = .@"attribute-warning", +}; + +pub const error_attribute: Diagnostic = .{ + .fmt = "call to '{s}' declared with attribute error: {s}", + .kind = .@"error", +}; + +pub const ignored_record_attr: Diagnostic = .{ + .fmt = "attribute '{s}' is ignored, place it after \"{s}\" to apply attribute to type declaration", + .kind = .warning, + .opt = .@"ignored-attributes", +}; + +pub const array_size_non_int: Diagnostic = .{ + .fmt = "size of array has non-integer type '{qt}'", + .kind = .@"error", +}; + +pub const cast_to_smaller_int: Diagnostic = .{ + .fmt = "cast to smaller integer type '{qt}' from '{qt}'", + .kind = .warning, + .opt = .@"pointer-to-int-cast", +}; + +pub const gnu_switch_range: Diagnostic = .{ + .fmt = "use of GNU case range extension", + .opt = .@"gnu-case-range", + .kind = .off, + .extension = true, +}; + +pub const empty_case_range: Diagnostic = .{ + .fmt = "empty case range specified", + .kind = .warning, +}; + +pub const vla: Diagnostic = .{ + .fmt = "variable length array used", + .kind = .off, + .opt = .vla, +}; + +pub const int_value_changed: Diagnostic = .{ + .fmt = "implicit conversion from '{qt}' to '{qt}' changes {s}value from {value} to {value}", + .kind = .warning, + .opt = .@"constant-conversion", +}; + +pub const sign_conversion: Diagnostic = .{ + .fmt = "implicit conversion changes signedness: '{qt}' to '{qt}'", + .kind = .off, + .opt = .@"sign-conversion", +}; + +pub const float_overflow_conversion: Diagnostic = .{ + .fmt = "implicit conversion of non-finite value from '{qt}' to '{qt}' is undefined", + .kind = .off, + .opt = .@"float-overflow-conversion", +}; + +pub const float_out_of_range: Diagnostic = .{ + .fmt = "implicit conversion of out of range value from '{qt}' to '{qt}' is undefined", + .kind = .warning, + .opt = .@"literal-conversion", +}; + +pub const float_zero_conversion: Diagnostic = .{ + .fmt = "implicit conversion from '{qt}' to '{qt}' changes {s}value from {value} to {value}", + .kind = .off, + .opt = .@"float-zero-conversion", +}; + +pub const float_value_changed: Diagnostic = .{ + .fmt = "implicit conversion from '{qt}' to '{qt}' changes {s}value from {value} to {value}", + .kind = .warning, + .opt = .@"float-conversion", +}; + +pub const float_to_int: Diagnostic = .{ + .fmt = "implicit conversion turns floating-point number into integer: '{qt}' to '{qt}'", + .kind = .off, + .opt = .@"literal-conversion", +}; + +pub const const_decl_folded: Diagnostic = .{ + .fmt = "expression is not an integer constant expression; folding it to a constant is a GNU extension", + .kind = .off, + .opt = .@"gnu-folding-constant", + .extension = true, +}; + +pub const const_decl_folded_vla: Diagnostic = .{ + .fmt = "variable length array folded to constant array as an extension", + .kind = .off, + .opt = .@"gnu-folding-constant", + .extension = true, +}; + +pub const redefinition_of_typedef: Diagnostic = .{ + .fmt = "typedef redefinition with different types ('{qt}' vs '{qt}')", + .kind = .@"error", +}; + +pub const offsetof_ty: Diagnostic = .{ + .fmt = "offsetof requires struct or union type, '{qt}' invalid", + .kind = .@"error", +}; + +pub const offsetof_incomplete: Diagnostic = .{ + .fmt = "offsetof of incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const offsetof_array: Diagnostic = .{ + .fmt = "offsetof requires array type, '{qt}' invalid", + .kind = .@"error", +}; + +pub const cond_expr_type: Diagnostic = .{ + .fmt = "used type '{qt}' where arithmetic or pointer type is required", + .kind = .@"error", +}; + +pub const enumerator_too_small: Diagnostic = .{ + .fmt = "ISO C restricts enumerator values to range of 'int' ({value} is too small)", + .kind = .off, + .extension = true, +}; + +pub const enumerator_too_large: Diagnostic = .{ + .fmt = "ISO C restricts enumerator values to range of 'int' ({value} is too large)", + .kind = .off, + .extension = true, +}; + +pub const enumerator_overflow: Diagnostic = .{ + .fmt = "overflow in enumeration value", + .kind = .warning, +}; + +pub const enum_not_representable: Diagnostic = .{ + .fmt = "incremented enumerator value {s} is not representable in the largest integer type", + .kind = .warning, + .opt = .@"enum-too-large", +}; + +pub const enum_too_large: Diagnostic = .{ + .fmt = "enumeration values exceed range of largest integer", + .kind = .warning, + .opt = .@"enum-too-large", +}; + +pub const enum_fixed: Diagnostic = .{ + .fmt = "enumeration types with a fixed underlying type are a Clang extension", + .kind = .off, + .opt = .@"fixed-enum-extension", + .extension = true, +}; + +pub const enum_prev_nonfixed: Diagnostic = .{ + .fmt = "enumeration previously declared with nonfixed underlying type", + .kind = .@"error", +}; + +pub const enum_prev_fixed: Diagnostic = .{ + .fmt = "enumeration previously declared with fixed underlying type", + .kind = .@"error", +}; + +pub const enum_different_explicit_ty: Diagnostic = .{ + .fmt = "enumeration redeclared with different underlying type '{qt}' (was '{qt}')", + .kind = .@"error", +}; + +pub const enum_not_representable_fixed: Diagnostic = .{ + .fmt = "enumerator value is not representable in the underlying type '{qt}'", + .kind = .@"error", +}; + +pub const transparent_union_wrong_type: Diagnostic = .{ + .fmt = "'transparent_union' attribute only applies to unions", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const transparent_union_one_field: Diagnostic = .{ + .fmt = "transparent union definition must contain at least one field; transparent_union attribute ignored", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const transparent_union_size: Diagnostic = .{ + .fmt = "size of field '{s}' ({d} bits) does not match the size of the first field in transparent union; transparent_union attribute ignored", + .kind = .warning, + .opt = .@"ignored-attributes", +}; + +pub const transparent_union_size_note: Diagnostic = .{ + .fmt = "size of first field is {d}", + .kind = .note, +}; + +pub const designated_init_invalid: Diagnostic = .{ + .fmt = "'designated_init' attribute is only valid on 'struct' type'", + .kind = .@"error", +}; + +pub const designated_init_needed: Diagnostic = .{ + .fmt = "positional initialization of field in 'struct' declared with 'designated_init' attribute", + .opt = .@"designated-init", + .kind = .warning, +}; + +pub const ignore_common: Diagnostic = .{ + .fmt = "ignoring attribute 'common' because it conflicts with attribute 'nocommon'", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const ignore_nocommon: Diagnostic = .{ + .fmt = "ignoring attribute 'nocommon' because it conflicts with attribute 'common'", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const non_string_ignored: Diagnostic = .{ + .fmt = "'nonstring' attribute ignored on objects of type '{qt}'", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const local_variable_attribute: Diagnostic = .{ + .fmt = "'{s}' attribute only applies to local variables", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const ignore_cold: Diagnostic = .{ + .fmt = "ignoring attribute 'cold' because it conflicts with attribute 'hot'", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const ignore_hot: Diagnostic = .{ + .fmt = "ignoring attribute 'hot' because it conflicts with attribute 'cold'", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const ignore_noinline: Diagnostic = .{ + .fmt = "ignoring attribute 'noinline' because it conflicts with attribute 'always_inline'", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const ignore_always_inline: Diagnostic = .{ + .fmt = "ignoring attribute 'always_inline' because it conflicts with attribute 'noinline'", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const invalid_noreturn: Diagnostic = .{ + .fmt = "function '{s}' declared 'noreturn' should not return", + .kind = .warning, + .opt = .@"invalid-noreturn", +}; + +pub const nodiscard_unused: Diagnostic = .{ + .fmt = "ignoring return value of '{s}', declared with 'nodiscard' attribute", + .kind = .warning, + .opt = .@"unused-result", +}; + +pub const warn_unused_result: Diagnostic = .{ + .fmt = "ignoring return value of '{s}', declared with 'warn_unused_result' attribute", + .kind = .warning, + .opt = .@"unused-result", +}; + +pub const builtin_unused: Diagnostic = .{ + .fmt = "ignoring return value of function declared with {s} attribute", + .kind = .warning, + .opt = .@"unused-value", +}; + +pub const unused_value: Diagnostic = .{ + .fmt = "expression result unused", + .kind = .warning, + .opt = .@"unused-value", +}; + +pub const invalid_vec_elem_ty: Diagnostic = .{ + .fmt = "invalid vector element type '{qt}'", + .kind = .@"error", +}; + +pub const vec_size_not_multiple: Diagnostic = .{ + .fmt = "vector size not an integral multiple of component size", + .kind = .@"error", +}; + +pub const invalid_imag: Diagnostic = .{ + .fmt = "invalid type '{qt}' to __imag operator", + .kind = .@"error", +}; + +pub const invalid_real: Diagnostic = .{ + .fmt = "invalid type '{qt}' to __real operator", + .kind = .@"error", +}; + +pub const zero_length_array: Diagnostic = .{ + .fmt = "zero size arrays are an extension", + .kind = .off, + .opt = .@"zero-length-array", +}; + +pub const old_style_flexible_struct: Diagnostic = .{ + .fmt = "array index {value} is past the end of the array", + .kind = .off, + .opt = .@"old-style-flexible-struct", +}; + +pub const main_return_type: Diagnostic = .{ + .fmt = "return type of 'main' is not 'int'", + .kind = .warning, + .opt = .@"main-return-type", +}; + +pub const invalid_int_suffix: Diagnostic = .{ + .fmt = "invalid suffix '{s}' on integer constant", + .kind = .@"error", +}; + +pub const invalid_float_suffix: Diagnostic = .{ + .fmt = "invalid suffix '{s}' on floating constant", + .kind = .@"error", +}; + +pub const invalid_octal_digit: Diagnostic = .{ + .fmt = "invalid digit '{c}' in octal constant", + .kind = .@"error", +}; + +pub const invalid_binary_digit: Diagnostic = .{ + .fmt = "invalid digit '{c}' in binary constant", + .kind = .@"error", +}; + +pub const exponent_has_no_digits: Diagnostic = .{ + .fmt = "exponent has no digits", + .kind = .@"error", +}; + +pub const hex_floating_constant_requires_exponent: Diagnostic = .{ + .fmt = "hexadecimal floating constant requires an exponent", + .kind = .@"error", +}; + +pub const sizeof_returns_zero: Diagnostic = .{ + .fmt = "sizeof returns 0", + .kind = .warning, +}; + +pub const declspec_not_allowed_after_declarator: Diagnostic = .{ + .fmt = "'declspec' attribute not allowed after declarator", + .kind = .@"error", +}; + +pub const declarator_name_tok: Diagnostic = .{ + .fmt = "this declarator", + .kind = .note, +}; + +pub const type_not_supported_on_target: Diagnostic = .{ + .fmt = "{s} is not supported on this target", + .kind = .@"error", +}; + +pub const bit_int: Diagnostic = .{ + .fmt = "'_BitInt' in C17 and earlier is a Clang extension", + .kind = .off, + .opt = .@"bit-int-extension", + .suppress_version = .c23, + .extension = true, +}; + +pub const unsigned_bit_int_too_small: Diagnostic = .{ + .fmt = "{s}unsigned _BitInt must have a bit size of at least 1", + .kind = .@"error", +}; + +pub const signed_bit_int_too_small: Diagnostic = .{ + .fmt = "{s}signed _BitInt must have a bit size of at least 2", + .kind = .@"error", +}; + +pub const unsigned_bit_int_too_big: Diagnostic = .{ + .fmt = "{s}unsigned _BitInt of bit sizes greater than " ++ std.fmt.comptimePrint("{d}", .{Compilation.bit_int_max_bits}) ++ " not supported", + .kind = .@"error", +}; + +pub const signed_bit_int_too_big: Diagnostic = .{ + .fmt = "{s}signed _BitInt of bit sizes greater than " ++ std.fmt.comptimePrint("{d}", .{Compilation.bit_int_max_bits}) ++ " not supported", + .kind = .@"error", +}; + +pub const ptr_arithmetic_incomplete: Diagnostic = .{ + .fmt = "arithmetic on a pointer to an incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const callconv_not_supported: Diagnostic = .{ + .fmt = "'{s}' calling convention is not supported for this target", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const pointer_arith_void: Diagnostic = .{ + .fmt = "invalid application of '{s}' to a void type", + .kind = .off, + .opt = .@"pointer-arith", +}; + +pub const sizeof_array_arg: Diagnostic = .{ + .fmt = "sizeof on array function parameter will return size of '{qt}' instead of '{qt}'", + .kind = .warning, + .opt = .@"sizeof-array-argument", +}; + +pub const array_address_to_bool: Diagnostic = .{ + .fmt = "address of array '{s}' will always evaluate to 'true'", + .kind = .warning, + .opt = .@"pointer-bool-conversion", +}; + +pub const string_literal_to_bool: Diagnostic = .{ + .fmt = "implicit conversion turns string literal into bool: '{qt}' to '{qt}", + .kind = .off, + .opt = .@"string-conversion", +}; + +// pub const constant_expression_conversion_not_allowed: Diagnostic = .{ +// .fmt = "this conversion is not allowed in a constant expression", +// .kind = .note, +// }; + +pub const invalid_object_cast: Diagnostic = .{ + .fmt = "cannot cast an object of type '{qt}' to '{qt}'", + .kind = .@"error", +}; + +pub const suggest_pointer_for_invalid_fp16: Diagnostic = .{ + .fmt = "{s} cannot have __fp16 type; did you forget * ?", + .kind = .@"error", +}; + +pub const bitint_suffix: Diagnostic = .{ + .fmt = "'_BitInt' suffix for literals is a C23 extension", + .opt = .@"c23-extensions", + .kind = .warning, + .suppress_version = .c23, +}; + +pub const auto_type_extension: Diagnostic = .{ + .fmt = "'__auto_type' is a GNU extension", + .opt = .@"gnu-auto-type", + .kind = .off, + .extension = true, +}; + +pub const gnu_pointer_arith: Diagnostic = .{ + .fmt = "arithmetic on pointers to void is a GNU extension", + .opt = .@"gnu-pointer-arith", + .kind = .off, + .extension = true, +}; + +pub const auto_type_not_allowed: Diagnostic = .{ + .fmt = "'__auto_type' not allowed in {s}", + .kind = .@"error", +}; + +pub const auto_type_requires_initializer: Diagnostic = .{ + .fmt = "declaration of variable '{s}' with deduced type requires an initializer", + .kind = .@"error", +}; + +pub const auto_type_requires_single_declarator: Diagnostic = .{ + .fmt = "'__auto_type' may only be used with a single declarator", + .kind = .@"error", +}; + +pub const auto_type_requires_plain_declarator: Diagnostic = .{ + .fmt = "'__auto_type' requires a plain identifier as declarator", + .kind = .@"error", +}; + +pub const auto_type_from_bitfield: Diagnostic = .{ + .fmt = "cannot use bit-field as '__auto_type' initializer", + .kind = .@"error", +}; + +pub const auto_type_array: Diagnostic = .{ + .fmt = "'{qt}' declared as array of '__auto_type'", + .kind = .@"error", +}; + +pub const auto_type_with_init_list: Diagnostic = .{ + .fmt = "cannot use '__auto_type' with initializer list", + .kind = .@"error", +}; + +pub const missing_semicolon: Diagnostic = .{ + .fmt = "expected ';' at end of declaration list", + .kind = .warning, +}; + +pub const tentative_definition_incomplete: Diagnostic = .{ + .fmt = "tentative definition has type '{qt}' that is never completed", + .kind = .@"error", +}; + +pub const forward_declaration_here: Diagnostic = .{ + .fmt = "forward declaration of '{s}'", + .kind = .note, +}; + +pub const gnu_union_cast: Diagnostic = .{ + .fmt = "cast to union type is a GNU extension", + .opt = .@"gnu-union-cast", + .kind = .off, + .extension = true, +}; + +pub const invalid_union_cast: Diagnostic = .{ + .fmt = "cast to union type from type '{qt}' not present in union", + .kind = .@"error", +}; + +pub const cast_to_incomplete_type: Diagnostic = .{ + .fmt = "cast to incomplete type '{qt}'", + .kind = .@"error", +}; + +pub const gnu_asm_disabled: Diagnostic = .{ + .fmt = "GNU-style inline assembly is disabled", + .kind = .@"error", +}; + +pub const extension_token_used: Diagnostic = .{ + .fmt = "extension used", + .kind = .off, + .opt = .@"language-extension-token", + .extension = true, +}; + +pub const complex_component_init: Diagnostic = .{ + .fmt = "complex initialization specifying real and imaginary components is an extension", + .opt = .@"complex-component-init", + .kind = .off, + .extension = true, +}; + +pub const complex_prefix_postfix_op: Diagnostic = .{ + .fmt = "ISO C does not support '++'/'--' on complex type '{qt}'", + .kind = .off, + .extension = true, +}; + +pub const not_floating_type: Diagnostic = .{ + .fmt = "argument type '{qt}' is not a real floating point type", + .kind = .@"error", +}; + +pub const argument_types_differ: Diagnostic = .{ + .fmt = "arguments are of different types ('{qt}' vs '{qt}')", + .kind = .@"error", +}; + +pub const attribute_requires_string: Diagnostic = .{ + .fmt = "attribute '{s}' requires an ordinary string", + .kind = .@"error", +}; + +pub const unterminated_string_literal_warning: Diagnostic = .{ + .fmt = "missing terminating '\"' character", + .kind = .warning, + .opt = .@"invalid-pp-token", +}; + +pub const unterminated_string_literal_error: Diagnostic = .{ + .fmt = "missing terminating '\"' character", + .kind = .@"error", +}; + +pub const empty_char_literal_error: Diagnostic = .{ + .fmt = "empty character constant", + .kind = .@"error", +}; + +pub const unterminated_char_literal_error: Diagnostic = .{ + .fmt = "missing terminating ' character", + .kind = .@"error", +}; + +// pub const def_no_proto_deprecated: Diagnostic = .{ +// .fmt = "a function definition without a prototype is deprecated in all versions of C and is not supported in C23", +// .kind = .warning, +// .opt = .@"deprecated-non-prototype", +// }; + +pub const passing_args_to_kr: Diagnostic = .{ + .fmt = "passing arguments to a function without a prototype is deprecated in all versions of C and is not supported in C23", + .kind = .warning, + .opt = .@"deprecated-non-prototype", +}; + +pub const unknown_type_name: Diagnostic = .{ + .fmt = "unknown type name '{s}'", + .kind = .@"error", +}; + +pub const label_compound_end: Diagnostic = .{ + .fmt = "label at end of compound statement is a C23 extension", + .opt = .@"c23-extensions", + .kind = .warning, + .suppress_version = .c23, + .extension = true, +}; + +pub const u8_char_lit: Diagnostic = .{ + .fmt = "UTF-8 character literal is a C23 extension", + .opt = .@"c23-extensions", + .kind = .warning, + .suppress_version = .c23, + .extension = true, +}; + +pub const invalid_compound_literal_storage_class: Diagnostic = .{ + .fmt = "compound literal cannot have {s} storage class", + .kind = .@"error", +}; + +pub const identifier_not_normalized: Diagnostic = .{ + .fmt = "'{s}' is not in NFC", + .kind = .warning, + .opt = .normalized, +}; + +pub const c23_auto_single_declarator: Diagnostic = .{ + .fmt = "'auto' can only be used with a single declarator", + .kind = .@"error", +}; + +pub const c23_auto_requires_initializer: Diagnostic = .{ + .fmt = "'auto' requires an initializer", + .kind = .@"error", +}; + +pub const c23_auto_not_allowed: Diagnostic = .{ + .fmt = "'auto' not allowed in {s}", + .kind = .@"error", +}; + +pub const c23_auto_with_init_list: Diagnostic = .{ + .fmt = "cannot use 'auto' with array", + .kind = .@"error", +}; + +pub const c23_auto_array: Diagnostic = .{ + .fmt = "'{qt}' declared as array of 'auto'", + .kind = .@"error", +}; + +pub const negative_shift_count: Diagnostic = .{ + .fmt = "shift count is negative", + .opt = .@"shift-count-negative", + .kind = .warning, +}; + +pub const too_big_shift_count: Diagnostic = .{ + .fmt = "shift count >= width of type", + .opt = .@"shift-count-overflow", + .kind = .warning, +}; + +pub const complex_conj: Diagnostic = .{ + .fmt = "ISO C does not support '~' for complex conjugation of '{qt}'", + .kind = .off, + .extension = true, +}; + +pub const overflow_builtin_requires_int: Diagnostic = .{ + .fmt = "operand argument to overflow builtin must be an integer ('{qt}' invalid)", + .kind = .@"error", +}; + +pub const overflow_result_requires_ptr: Diagnostic = .{ + .fmt = "result argument to overflow builtin must be a pointer to a non-const integer ('{qt}' invalid)", + .kind = .@"error", +}; + +pub const attribute_todo: Diagnostic = .{ + .fmt = "TODO: implement '{s}' attribute for {s}", + .kind = .@"error", +}; + +pub const invalid_type_underlying_enum: Diagnostic = .{ + .fmt = "non-integral type '{qt}' is an invalid underlying type", + .kind = .@"error", +}; + +pub const auto_type_self_initialized: Diagnostic = .{ + .fmt = "variable '{s}' declared with deduced type '__auto_type' cannot appear in its own initializer", + .kind = .@"error", +}; + +// pub const non_constant_initializer: Diagnostic = .{ +// .fmt = "initializer element is not a compile-time constant", +// .kind = .@"error", +// }; + +pub const constexpr_requires_const: Diagnostic = .{ + .fmt = "constexpr variable must be initialized by a constant expression", + .kind = .@"error", +}; + +pub const subtract_pointers_zero_elem_size: Diagnostic = .{ + .fmt = "subtraction of pointers to type '{qt}' of zero size has undefined behavior", + .kind = .warning, + .opt = .@"pointer-arith", +}; + +pub const packed_member_address: Diagnostic = .{ + .fmt = "taking address of packed member '{s}' of class or structure '{s}' may result in an unaligned pointer value", + .kind = .warning, + .opt = .@"address-of-packed-member", +}; + +pub const attribute_param_out_of_bounds: Diagnostic = .{ + .fmt = "'{s}' attribute parameter {d} is out of bounds", + .kind = .@"error", +}; + +pub const alloc_align_requires_ptr_return: Diagnostic = .{ + .fmt = "'alloc_align' attribute only applies to return values that are pointers", + .opt = .@"ignored-attributes", + .kind = .warning, +}; + +pub const alloc_align_required_int_param: Diagnostic = .{ + .fmt = "'alloc_align' attribute argument may only refer to a function parameter of integer type", + .kind = .@"error", +}; + +pub const gnu_missing_eq_designator: Diagnostic = .{ + .fmt = "use of GNU 'missing =' extension in designator", + .kind = .warning, + .opt = .@"gnu-designator", + .extension = true, +}; + +pub const empty_if_body: Diagnostic = .{ + .fmt = "if statement has empty body", + .kind = .warning, + .opt = .@"empty-body", +}; + +pub const empty_if_body_note: Diagnostic = .{ + .fmt = "put the semicolon on a separate line to silence this warning", + .kind = .note, + .opt = .@"empty-body", +}; + +pub const nullability_extension: Diagnostic = .{ + .fmt = "type nullability specifier '{s}' is a Clang extension", + .kind = .off, + .opt = .@"nullability-extension", + .extension = true, +}; + +pub const duplicate_nullability: Diagnostic = .{ + .fmt = "duplicate nullability specifier '{s}'", + .kind = .warning, + .opt = .nullability, +}; + +pub const conflicting_nullability: Diagnostic = .{ + .fmt = "nullaibility specifier '{tok_id}' conflicts with existing specifier '{tok_id}'", + .kind = .@"error", +}; + +pub const invalid_nullability: Diagnostic = .{ + .fmt = "nullability specifier cannot be applied to non-pointer type '{qt}'", + .kind = .@"error", +}; diff --git a/src/aro/Pragma.zig b/src/aro/Pragma.zig index d9bd933..cb275ae 100644 --- a/src/aro/Pragma.zig +++ b/src/aro/Pragma.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Compilation = @import("Compilation.zig"); +const Diagnostics = @import("Diagnostics.zig"); const Parser = @import("Parser.zig"); const Preprocessor = @import("Preprocessor.zig"); const TokenIndex = @import("Tree.zig").TokenIndex; @@ -82,3 +83,124 @@ pub fn parserCB(self: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation.Er defer std.debug.assert(tok_index == p.tok_i); if (self.parserHandler) |func| return func(self, p, start_idx); } + +pub const Diagnostic = struct { + fmt: []const u8, + kind: Diagnostics.Message.Kind, + opt: ?Diagnostics.Option = null, + + pub const pragma_warning_message: Diagnostic = .{ + .fmt = "{s}", + .kind = .warning, + .opt = .@"#pragma-messages", + }; + + pub const pragma_error_message: Diagnostic = .{ + .fmt = "{s}", + .kind = .@"error", + }; + + pub const pragma_message: Diagnostic = .{ + .fmt = "#pragma message: {s}", + .kind = .note, + }; + + pub const pragma_requires_string_literal: Diagnostic = .{ + .fmt = "pragma {s} requires string literal", + .kind = .@"error", + }; + + pub const poisoned_identifier: Diagnostic = .{ + .fmt = "attempt to use a poisoned identifier", + .kind = .@"error", + }; + + pub const pragma_poison_identifier: Diagnostic = .{ + .fmt = "can only poison identifier tokens", + .kind = .@"error", + }; + + pub const pragma_poison_macro: Diagnostic = .{ + .fmt = "poisoning existing macro", + .kind = .warning, + }; + + pub const unknown_gcc_pragma: Diagnostic = .{ + .fmt = "pragma GCC expected 'error', 'warning', 'diagnostic', 'poison'", + .kind = .off, + .opt = .@"unknown-pragmas", + }; + + pub const unknown_gcc_pragma_directive: Diagnostic = .{ + .fmt = "pragma GCC diagnostic expected 'error', 'warning', 'ignored', 'fatal', 'push', or 'pop'", + .kind = .warning, + .opt = .@"unknown-pragmas", + }; + + pub const malformed_warning_check: Diagnostic = .{ + .fmt = "{s} expected option name (e.g. \"-Wundef\")", + .opt = .@"malformed-warning-check", + .kind = .warning, + }; + + pub const pragma_pack_lparen: Diagnostic = .{ + .fmt = "missing '(' after '#pragma pack' - ignoring", + .kind = .warning, + .opt = .@"ignored-pragmas", + }; + + pub const pragma_pack_rparen: Diagnostic = .{ + .fmt = "missing ')' after '#pragma pack' - ignoring", + .kind = .warning, + .opt = .@"ignored-pragmas", + }; + + pub const pragma_pack_unknown_action: Diagnostic = .{ + .fmt = "unknown action for '#pragma pack' - ignoring", + .kind = .warning, + .opt = .@"ignored-pragmas", + }; + + pub const pragma_pack_show: Diagnostic = .{ + .fmt = "value of #pragma pack(show) == {d}", + .kind = .warning, + }; + + pub const pragma_pack_int_ident: Diagnostic = .{ + .fmt = "expected integer or identifier in '#pragma pack' - ignored", + .kind = .warning, + .opt = .@"ignored-pragmas", + }; + + pub const pragma_pack_int: Diagnostic = .{ + .fmt = "expected #pragma pack parameter to be '1', '2', '4', '8', or '16'", + .opt = .@"ignored-pragmas", + .kind = .warning, + }; + + pub const pragma_pack_undefined_pop: Diagnostic = .{ + .fmt = "specifying both a name and alignment to 'pop' is undefined", + .kind = .warning, + }; + + pub const pragma_pack_empty_stack: Diagnostic = .{ + .fmt = "#pragma pack(pop, ...) failed: stack empty", + .opt = .@"ignored-pragmas", + .kind = .warning, + }; +}; + +pub fn err(pp: *Preprocessor, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) Compilation.Error!void { + var sf = std.heap.stackFallback(1024, pp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try Diagnostics.formatArgs(buf.writer(), diagnostic.fmt, args); + + try pp.diagnostics.addWithLocation(pp.comp, .{ + .kind = diagnostic.kind, + .opt = diagnostic.opt, + .text = buf.items, + .location = pp.tokens.items(.loc)[tok_i].expand(pp.comp), + }, pp.expansionSlice(tok_i), true); +} diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 233ce24..fb03517 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -111,7 +111,9 @@ const TokenState = struct { }; comp: *Compilation, +diagnostics: *Diagnostics, gpa: mem.Allocator, + arena: std.heap.ArenaAllocator, defines: DefineMap = .{}, /// Do not directly mutate this; use addToken / addTokenAssumeCapacity / ensureTotalTokenCapacity / ensureUnusedTokenCapacity @@ -163,6 +165,7 @@ pub const Linemarkers = enum { pub fn init(comp: *Compilation) Preprocessor { const pp = Preprocessor{ .comp = comp, + .diagnostics = comp.diagnostics, .gpa = comp.gpa, .arena = std.heap.ArenaAllocator.init(comp.gpa), .token_buf = RawTokenList.init(comp.gpa), @@ -383,7 +386,7 @@ pub fn addIncludeResume(pp: *Preprocessor, source: Source.Id, offset: u32, line: } }); } -fn invalidTokenDiagnostic(tok_id: Token.Id) Diagnostics.Tag { +fn invalidTokenDiagnostic(tok_id: Token.Id) Diagnostic { return switch (tok_id) { .unterminated_string_literal => .unterminated_string_literal_warning, .empty_char_literal => .empty_char_literal_warning, @@ -431,6 +434,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans switch (tok.id) { .hash => if (!start_of_line) try pp.addToken(tokFromRaw(tok)) else { const directive = tokenizer.nextNoWS(); + const directive_loc: Source.Location = .{ .id = tok.source, .byte_offset = directive.start, .line = directive.line }; switch (directive.id) { .keyword_error, .keyword_warning => { // #error tokens.. @@ -446,13 +450,12 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans } try pp.stringify(pp.top_expansion_buf.items); const slice = pp.char_buf.items[char_top + 1 .. pp.char_buf.items.len - 2]; - const duped = try pp.comp.diagnostics.arena.allocator().dupe(u8, slice); - try pp.comp.addDiagnostic(.{ - .tag = if (directive.id == .keyword_error) .error_directive else .warning_directive, - .loc = .{ .id = tok.source, .byte_offset = directive.start, .line = directive.line }, - .extra = .{ .str = duped }, - }, &.{}); + try pp.err( + directive_loc, + if (directive.id == .keyword_error) .error_directive else .warning_directive, + .{slice}, + ); }, .keyword_if => { const overflowed = if_context.increment(); @@ -508,7 +511,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans }, .keyword_elif => { if (if_context.level == 0) { - try pp.err(directive, .elif_without_if); + try pp.err(directive, .elif_without_if, .{}); _ = if_context.increment(); if_context.set(.until_else); } else if (if_context.level == 1) { @@ -528,14 +531,14 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans }, .until_endif => try pp.skip(&tokenizer, .until_endif), .until_endif_seen_else => { - try pp.err(directive, .elif_after_else); + try pp.err(directive, .elif_after_else, .{}); skipToNl(&tokenizer); }, } }, .keyword_elifdef => { if (if_context.level == 0) { - try pp.err(directive, .elifdef_without_if); + try pp.err(directive, .elifdef_without_if, .{}); _ = if_context.increment(); if_context.set(.until_else); } else if (if_context.level == 1) { @@ -568,14 +571,14 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans }, .until_endif => try pp.skip(&tokenizer, .until_endif), .until_endif_seen_else => { - try pp.err(directive, .elifdef_after_else); + try pp.err(directive, .elifdef_after_else, .{}); skipToNl(&tokenizer); }, } }, .keyword_elifndef => { if (if_context.level == 0) { - try pp.err(directive, .elifdef_without_if); + try pp.err(directive, .elifndef_without_if, .{}); _ = if_context.increment(); if_context.set(.until_else); } else if (if_context.level == 1) { @@ -608,7 +611,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans }, .until_endif => try pp.skip(&tokenizer, .until_endif), .until_endif_seen_else => { - try pp.err(directive, .elifdef_after_else); + try pp.err(directive, .elifdef_after_else, .{}); skipToNl(&tokenizer); }, } @@ -616,7 +619,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans .keyword_else => { try pp.expectNl(&tokenizer); if (if_context.level == 0) { - try pp.err(directive, .else_without_if); + try pp.err(directive, .else_without_if, .{}); continue; } else if (if_context.level == 1) { guard_name = null; @@ -630,7 +633,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans }, .until_endif => try pp.skip(&tokenizer, .until_endif_seen_else), .until_endif_seen_else => { - try pp.err(directive, .else_after_else); + try pp.err(directive, .else_after_else, .{}); skipToNl(&tokenizer); }, } @@ -639,7 +642,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans try pp.expectNl(&tokenizer); if (if_context.level == 0) { guard_name = null; - try pp.err(directive, .endif_without_if); + try pp.err(directive, .endif_without_if, .{}); continue; } else if (if_context.level == 1) { const saved_tokenizer = tokenizer; @@ -666,15 +669,10 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans continue; }, .keyword_include_next => { - try pp.comp.addDiagnostic(.{ - .tag = .include_next, - .loc = .{ .id = tok.source, .byte_offset = directive.start, .line = directive.line }, - }, &.{}); + try pp.err(directive_loc, .include_next, .{}); + if (pp.include_depth == 0) { - try pp.comp.addDiagnostic(.{ - .tag = .include_next_outside_header, - .loc = .{ .id = tok.source, .byte_offset = directive.start, .line = directive.line }, - }, &.{}); + try pp.err(directive_loc, .include_next_outside_header, .{}); try pp.include(&tokenizer, .first); } else { try pp.include(&tokenizer, .next); @@ -688,13 +686,13 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans .keyword_line => { // #line number "file" const digits = tokenizer.nextNoWS(); - if (digits.id != .pp_num) try pp.err(digits, .line_simple_digit); + if (digits.id != .pp_num) try pp.err(digits, .line_simple_digit, .{}); // TODO: validate that the pp_num token is solely digits if (digits.id == .eof or digits.id == .nl) continue; const name = tokenizer.nextNoWS(); if (name.id == .eof or name.id == .nl) continue; - if (name.id != .string_literal) try pp.err(name, .line_invalid_filename); + if (name.id != .string_literal) try pp.err(name, .line_invalid_filename, .{}); try pp.expectNl(&tokenizer); }, .pp_num => { @@ -703,7 +701,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans // if not, emit `GNU line marker directive requires a simple digit sequence` const name = tokenizer.nextNoWS(); if (name.id == .eof or name.id == .nl) continue; - if (name.id != .string_literal) try pp.err(name, .line_invalid_filename); + if (name.id != .string_literal) try pp.err(name, .line_invalid_filename, .{}); const flag_1 = tokenizer.nextNoWS(); if (flag_1.id == .eof or flag_1.id == .nl) continue; @@ -717,11 +715,11 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans }, .nl => {}, .eof => { - if (if_context.level != 0) try pp.err(tok, .unterminated_conditional_directive); + if (if_context.level != 0) try pp.err(tok, .unterminated_conditional_directive, .{}); return tokFromRaw(directive); }, else => { - try pp.err(tok, .invalid_preprocessing_directive); + try pp.err(tok, .invalid_preprocessing_directive, .{}); skipToNl(&tokenizer); }, } @@ -736,11 +734,11 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if (pp.preserve_whitespace) try pp.addToken(tokFromRaw(tok)); }, .eof => { - if (if_context.level != 0) try pp.err(tok, .unterminated_conditional_directive); + if (if_context.level != 0) try pp.err(tok, .unterminated_conditional_directive, .{}); // The following check needs to occur here and not at the top of the function // because a pragma may change the level during preprocessing if (source.buf.len > 0 and source.buf[source.buf.len - 1] != '\n') { - try pp.err(tok, .newline_eof); + try pp.err(tok, .newline_eof, .{}); } if (guard_name) |name| { if (try pp.include_guards.fetchPut(pp.gpa, source.id, name)) |prev| { @@ -751,13 +749,13 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans }, .unterminated_string_literal, .unterminated_char_literal, .empty_char_literal => |tag| { start_of_line = false; - try pp.err(tok, invalidTokenDiagnostic(tag)); + try pp.err(tok, invalidTokenDiagnostic(tag), .{}); try pp.expandMacro(&tokenizer, tok); }, - .unterminated_comment => try pp.err(tok, .unterminated_comment), + .unterminated_comment => try pp.err(tok, .unterminated_comment, .{}), else => { if (tok.id.isMacroIdentifier() and pp.poisoned_identifiers.get(pp.tokSlice(tok)) != null) { - try pp.err(tok, .poisoned_identifier); + try pp.err(tok, .poisoned_identifier, .{}); } // Add the token to the buffer doing any necessary expansions. start_of_line = false; @@ -787,48 +785,74 @@ fn tokFromRaw(raw: RawToken) TokenWithExpansionLocs { }; } -fn err(pp: *Preprocessor, raw: RawToken, tag: Diagnostics.Tag) !void { - try pp.comp.addDiagnostic(.{ - .tag = tag, - .loc = .{ - .id = raw.source, - .byte_offset = raw.start, - .line = raw.line, - }, - }, &.{}); -} +pub const Diagnostic = @import("Preprocessor/Diagnostic.zig"); + +fn err(pp: *Preprocessor, loc: anytype, diagnostic: Diagnostic, args: anytype) !void { + if (pp.diagnostics.effectiveKind(diagnostic) == .off) return; -fn errStr(pp: *Preprocessor, tok: TokenWithExpansionLocs, tag: Diagnostics.Tag, str: []const u8) !void { - try pp.comp.addDiagnostic(.{ - .tag = tag, - .loc = tok.loc, - .extra = .{ .str = str }, - }, tok.expansionSlice()); + var sf = std.heap.stackFallback(1024, pp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try Diagnostics.formatArgs(buf.writer(), diagnostic.fmt, args); + try pp.diagnostics.addWithLocation(pp.comp, .{ + .kind = .@"fatal error", + .text = buf.items, + .opt = diagnostic.opt, + .extension = diagnostic.extension, + .location = switch (@TypeOf(loc)) { + RawToken => (Source.Location{ + .id = loc.source, + .byte_offset = loc.start, + .line = loc.line, + }).expand(pp.comp), + TokenWithExpansionLocs, *TokenWithExpansionLocs => loc.loc.expand(pp.comp), + Source.Location => loc.expand(pp.comp), + else => @compileError("invalid token type " ++ @typeName(@TypeOf(loc))), + }, + }, switch (@TypeOf(loc)) { + RawToken => &.{}, + TokenWithExpansionLocs, *TokenWithExpansionLocs => loc.expansionSlice(), + Source.Location => &.{}, + else => @compileError("invalid token type"), + }, true); + unreachable; } fn fatal(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) Compilation.Error { - try pp.comp.diagnostics.list.append(pp.gpa, .{ - .tag = .cli_error, + var sf = std.heap.stackFallback(1024, pp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try Diagnostics.formatArgs(buf.writer(), fmt, args); + try pp.diagnostics.add(.{ .kind = .@"fatal error", - .extra = .{ .str = try std.fmt.allocPrint(pp.comp.diagnostics.arena.allocator(), fmt, args) }, - .loc = .{ + .text = buf.items, + .location = (Source.Location{ .id = raw.source, .byte_offset = raw.start, .line = raw.line, - }, + }).expand(pp.comp), }); - return error.FatalError; + unreachable; } fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []const u8) Compilation.Error { - const old = pp.comp.diagnostics.state.fatal_errors; - pp.comp.diagnostics.state.fatal_errors = true; - defer pp.comp.diagnostics.state.fatal_errors = old; + const old = pp.diagnostics.state.fatal_errors; + pp.diagnostics.state.fatal_errors = true; + defer pp.diagnostics.state.fatal_errors = old; + + var sf = std.heap.stackFallback(1024, pp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); - try pp.comp.diagnostics.addExtra(pp.comp.langopts, .{ .tag = .cli_error, .loc = tok.loc, .extra = .{ - .str = try std.fmt.allocPrint(pp.comp.diagnostics.arena.allocator(), "'{s}' not found", .{filename}), - } }, tok.expansionSlice(), false); - unreachable; // addExtra should've returned FatalError + try Diagnostics.formatArgs(buf.writer(), "'{s}' not found", .{filename}); + try pp.diagnostics.addWithLocation(pp.comp, .{ + .kind = .@"fatal error", + .text = buf.items, + .location = tok.loc.expand(pp.comp), + }, tok.expansionSlice(), true); + unreachable; // should've returned FatalError } fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) void { @@ -850,7 +874,7 @@ fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: fn expectMacroName(pp: *Preprocessor, tokenizer: *Tokenizer) Error!?[]const u8 { const macro_name = tokenizer.nextNoWS(); if (!macro_name.id.isMacroIdentifier()) { - try pp.err(macro_name, .macro_name_missing); + try pp.err(macro_name, .macro_name_missing, .{}); skipToNl(tokenizer); return null; } @@ -866,7 +890,7 @@ fn expectNl(pp: *Preprocessor, tokenizer: *Tokenizer) Error!void { if (tok.id == .whitespace or tok.id == .comment) continue; if (!sent_err) { sent_err = true; - try pp.err(tok, .extra_tokens_directive_end); + try pp.err(tok, .extra_tokens_directive_end, .{}); } } } @@ -909,15 +933,12 @@ fn expr(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!bool { for (pp.top_expansion_buf.items) |tok| { if (tok.id == .macro_ws) continue; if (!tok.id.validPreprocessorExprStart()) { - try pp.comp.addDiagnostic(.{ - .tag = .invalid_preproc_expr_start, - .loc = tok.loc, - }, tok.expansionSlice()); + try pp.err(tok, .invalid_preproc_expr_start, .{}); return false; } break; } else { - try pp.err(eof, .expected_value_in_expr); + try pp.err(eof, .expected_value_in_expr, .{}); return false; } @@ -934,10 +955,7 @@ fn expr(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!bool { .string_literal_utf_32, .string_literal_wide, => { - try pp.comp.addDiagnostic(.{ - .tag = .string_literal_in_pp_expr, - .loc = tok.loc, - }, tok.expansionSlice()); + try pp.err(tok, .string_literal_in_pp_expr, .{}); return false; }, .plus_plus, @@ -964,10 +982,7 @@ fn expr(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!bool { .arrow, .period, => { - try pp.comp.addDiagnostic(.{ - .tag = .invalid_preproc_operator, - .loc = tok.loc, - }, tok.expansionSlice()); + try pp.err(tok, .invalid_preproc_operator, .{}); return false; }, .macro_ws, .whitespace => continue, @@ -978,12 +993,12 @@ fn expr(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!bool { const tokens_consumed = try pp.handleKeywordDefined(&tok, items[i + 1 ..], eof); i += tokens_consumed; } else { - try pp.errStr(tok, .undefined_macro, pp.expandedSlice(tok)); + try pp.err(tok, .undefined_macro, .{pp.expandedSlice(tok)}); if (i + 1 < pp.top_expansion_buf.items.len and pp.top_expansion_buf.items[i + 1].id == .l_paren) { - try pp.errStr(tok, .fn_macro_undefined, pp.expandedSlice(tok)); + try pp.err(tok, .fn_macro_undefined, .{pp.expandedSlice(tok)}); return false; } @@ -1002,6 +1017,7 @@ fn expr(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!bool { var parser: Parser = .{ .pp = pp, .comp = pp.comp, + .diagnostics = pp.diagnostics, .gpa = pp.gpa, .tok_ids = pp.tokens.items(.id), .tok_i = @intCast(token_state.tokens_len), @@ -1028,28 +1044,25 @@ fn handleKeywordDefined(pp: *Preprocessor, macro_tok: *TokenWithExpansionLocs, t std.debug.assert(macro_tok.id == .keyword_defined); var it = TokenIterator.init(tokens); const first = it.nextNoWS() orelse { - try pp.err(eof, .macro_name_missing); + try pp.err(eof, .macro_name_missing, .{}); return it.i; }; switch (first.id) { .l_paren => {}, else => { if (!first.id.isMacroIdentifier()) { - try pp.errStr(first, .macro_name_must_be_identifier, pp.expandedSlice(first)); + try pp.err(first, .macro_name_must_be_identifier, .{}); } macro_tok.id = if (pp.defines.contains(pp.expandedSlice(first))) .one else .zero; return it.i; }, } const second = it.nextNoWS() orelse { - try pp.err(eof, .macro_name_missing); + try pp.err(eof, .macro_name_missing, .{}); return it.i; }; if (!second.id.isMacroIdentifier()) { - try pp.comp.addDiagnostic(.{ - .tag = .macro_name_must_be_identifier, - .loc = second.loc, - }, second.expansionSlice()); + try pp.err(second, .macro_name_must_be_identifier, .{}); return it.i; } macro_tok.id = if (pp.defines.contains(pp.expandedSlice(second))) .one else .zero; @@ -1057,14 +1070,8 @@ fn handleKeywordDefined(pp: *Preprocessor, macro_tok: *TokenWithExpansionLocs, t const last = it.nextNoWS(); if (last == null or last.?.id != .r_paren) { const tok = last orelse tokFromRaw(eof); - try pp.comp.addDiagnostic(.{ - .tag = .closing_paren, - .loc = tok.loc, - }, tok.expansionSlice()); - try pp.comp.addDiagnostic(.{ - .tag = .to_match_paren, - .loc = first.loc, - }, first.expansionSlice()); + try pp.err(tok, .closing_paren, .{}); + try pp.err(first, .to_match_paren, .{}); } return it.i; @@ -1091,7 +1098,7 @@ fn skip( .keyword_else => { if (ifs_seen != 0) continue; if (cont == .until_endif_seen_else) { - try pp.err(directive, .else_after_else); + try pp.err(directive, .else_after_else, .{}); continue; } tokenizer.* = saved_tokenizer; @@ -1100,7 +1107,7 @@ fn skip( .keyword_elif => { if (ifs_seen != 0 or cont == .until_endif) continue; if (cont == .until_endif_seen_else) { - try pp.err(directive, .elif_after_else); + try pp.err(directive, .elif_after_else, .{}); continue; } tokenizer.* = saved_tokenizer; @@ -1109,7 +1116,7 @@ fn skip( .keyword_elifdef => { if (ifs_seen != 0 or cont == .until_endif) continue; if (cont == .until_endif_seen_else) { - try pp.err(directive, .elifdef_after_else); + try pp.err(directive, .elifdef_after_else, .{}); continue; } tokenizer.* = saved_tokenizer; @@ -1118,7 +1125,7 @@ fn skip( .keyword_elifndef => { if (ifs_seen != 0 or cont == .until_endif) continue; if (cont == .until_endif_seen_else) { - try pp.err(directive, .elifndef_after_else); + try pp.err(directive, .elifndef_after_else, .{}); continue; } tokenizer.* = saved_tokenizer; @@ -1150,7 +1157,7 @@ fn skip( } } else { const eof = tokenizer.next(); - return pp.err(eof, .unterminated_conditional_directive); + return pp.err(eof, .unterminated_conditional_directive, .{}); } } @@ -1373,10 +1380,7 @@ fn stringify(pp: *Preprocessor, tokens: []const TokenWithExpansionLocs) !void { const item = tokenizer.next(); if (item.id == .unterminated_string_literal) { const tok = tokens[tokens.len - 1]; - try pp.comp.addDiagnostic(.{ - .tag = .invalid_pp_stringify_escape, - .loc = tok.loc, - }, tok.expansionSlice()); + try pp.err(tok, .invalid_pp_stringify_escape, .{}); pp.char_buf.items.len -= 2; // erase unpaired backslash and appended end quote pp.char_buf.appendAssumeCapacity('"'); } @@ -1385,10 +1389,7 @@ fn stringify(pp: *Preprocessor, tokens: []const TokenWithExpansionLocs) !void { fn reconstructIncludeString(pp: *Preprocessor, param_toks: []const TokenWithExpansionLocs, embed_args: ?*[]const TokenWithExpansionLocs, first: TokenWithExpansionLocs) !?[]const u8 { if (param_toks.len == 0) { - try pp.comp.addDiagnostic(.{ - .tag = .expected_filename, - .loc = first.loc, - }, first.expansionSlice()); + try pp.err(first, .expected_filename, .{}); return null; } @@ -1403,18 +1404,12 @@ fn reconstructIncludeString(pp: *Preprocessor, param_toks: []const TokenWithExpa const params = param_toks[begin..end]; if (params.len == 0) { - try pp.comp.addDiagnostic(.{ - .tag = .expected_filename, - .loc = first.loc, - }, first.expansionSlice()); + try pp.err(first, .expected_filename, .{}); return null; } // no string pasting if (embed_args == null and params[0].id == .string_literal and params.len > 1) { - try pp.comp.addDiagnostic(.{ - .tag = .closing_paren, - .loc = params[1].loc, - }, params[1].expansionSlice()); + try pp.err(params[1], .closing_paren, .{}); return null; } @@ -1432,16 +1427,10 @@ fn reconstructIncludeString(pp: *Preprocessor, param_toks: []const TokenWithExpa const include_str = pp.char_buf.items[char_top..]; if (include_str.len < 3) { if (include_str.len == 0) { - try pp.comp.addDiagnostic(.{ - .tag = .expected_filename, - .loc = first.loc, - }, first.expansionSlice()); + try pp.err(first, .expected_filename, .{}); return null; } - try pp.comp.addDiagnostic(.{ - .tag = .empty_filename, - .loc = params[0].loc, - }, params[0].expansionSlice()); + try pp.err(params[0], .empty_filename, .{}); return null; } @@ -1449,25 +1438,18 @@ fn reconstructIncludeString(pp: *Preprocessor, param_toks: []const TokenWithExpa '<' => { if (include_str[include_str.len - 1] != '>') { // Ugly hack to find out where the '>' should go, since we don't have the closing ')' location - const start = params[0].loc; - try pp.comp.addDiagnostic(.{ - .tag = .header_str_closing, - .loc = .{ .id = start.id, .byte_offset = start.byte_offset + @as(u32, @intCast(include_str.len)) + 1, .line = start.line }, - }, params[0].expansionSlice()); - try pp.comp.addDiagnostic(.{ - .tag = .header_str_match, - .loc = params[0].loc, - }, params[0].expansionSlice()); + var closing = params[0]; + closing.loc.byte_offset += @as(u32, @intCast(include_str.len)) + 1; + try pp.err(closing, .header_str_closing, .{}); + + try pp.err(params[0], .header_str_match, .{}); return null; } return include_str; }, '"' => return include_str, else => { - try pp.comp.addDiagnostic(.{ - .tag = .expected_filename, - .loc = params[0].loc, - }, params[0].expansionSlice()); + try pp.err(params[0], .expected_filename, .{}); return null; }, } @@ -1494,10 +1476,7 @@ fn handleBuiltinMacro(pp: *Preprocessor, builtin: RawToken.Id, param_toks: []con } if (identifier == null and invalid == null) invalid = .{ .id = .eof, .loc = src_loc }; if (invalid) |some| { - try pp.comp.addDiagnostic( - .{ .tag = .feature_check_requires_identifier, .loc = some.loc }, - some.expansionSlice(), - ); + try pp.err(some, .feature_check_requires_identifier, .{}); return false; } @@ -1519,13 +1498,13 @@ fn handleBuiltinMacro(pp: *Preprocessor, builtin: RawToken.Id, param_toks: []con .macro_param_has_warning => { const actual_param = pp.pasteStringsUnsafe(param_toks) catch |er| switch (er) { error.ExpectedStringLiteral => { - try pp.errStr(param_toks[0], .expected_str_literal_in, "__has_warning"); + try pp.err(param_toks[0], .expected_str_literal_in, .{"__has_warning"}); return false; }, else => |e| return e, }; if (!mem.startsWith(u8, actual_param, "-W")) { - try pp.errStr(param_toks[0], .malformed_warning_check, "__has_warning"); + try pp.err(param_toks[0], .malformed_warning_check, .{"__has_warning"}); return false; } const warning_name = actual_param[2..]; @@ -1543,11 +1522,7 @@ fn handleBuiltinMacro(pp: *Preprocessor, builtin: RawToken.Id, param_toks: []con }; if (identifier == null and invalid == null) invalid = .{ .id = .eof, .loc = src_loc }; if (invalid) |some| { - try pp.comp.addDiagnostic(.{ - .tag = .missing_tok_builtin, - .loc = some.loc, - .extra = .{ .tok_id_expected = .r_paren }, - }, some.expansionSlice()); + try pp.err(some, .builtin_missing_r_paren, .{}); return false; } @@ -1564,10 +1539,7 @@ fn handleBuiltinMacro(pp: *Preprocessor, builtin: RawToken.Id, param_toks: []con const filename = include_str[1 .. include_str.len - 1]; if (builtin == .macro_param_has_include or pp.include_depth == 0) { if (builtin == .macro_param_has_include_next) { - try pp.comp.addDiagnostic(.{ - .tag = .include_next_outside_header, - .loc = src_loc, - }, &.{}); + try pp.err(src_loc, .include_next_outside_header, .{}); } return pp.comp.hasInclude(filename, src_loc.id, include_type, .first); } @@ -1699,8 +1671,7 @@ fn expandFuncMacro( => { const arg = expanded_args.items[0]; const result = if (arg.len == 0) blk: { - const extra = Diagnostics.Message.Extra{ .arguments = .{ .expected = 1, .actual = 0 } }; - try pp.comp.addDiagnostic(.{ .tag = .expected_arguments, .loc = macro_tok.loc, .extra = extra }, &.{}); + try pp.err(macro_tok, .expected_arguments, .{ 1, 0 }); break :blk false; } else try pp.handleBuiltinMacro(raw.id, arg, macro_tok.loc); const start = pp.comp.generated_buf.items.len; @@ -1712,8 +1683,7 @@ fn expandFuncMacro( const arg = expanded_args.items[0]; const not_found = "0\n"; const result = if (arg.len == 0) blk: { - const extra = Diagnostics.Message.Extra{ .arguments = .{ .expected = 1, .actual = 0 } }; - try pp.comp.addDiagnostic(.{ .tag = .expected_arguments, .loc = macro_tok.loc, .extra = extra }, &.{}); + try pp.err(macro_tok, .expected_arguments, .{ 1, 0 }); break :blk not_found; } else res: { var invalid: ?TokenWithExpansionLocs = null; @@ -1748,10 +1718,7 @@ fn expandFuncMacro( invalid = .{ .id = .eof, .loc = macro_tok.loc }; } if (invalid) |some| { - try pp.comp.addDiagnostic( - .{ .tag = .feature_check_requires_identifier, .loc = some.loc }, - some.expansionSlice(), - ); + try pp.err(some, .feature_check_requires_identifier, .{}); break :res not_found; } if (vendor_ident) |some| { @@ -1788,8 +1755,7 @@ fn expandFuncMacro( const arg = expanded_args.items[0]; const not_found = "0\n"; const result = if (arg.len == 0) blk: { - const extra = Diagnostics.Message.Extra{ .arguments = .{ .expected = 1, .actual = 0 } }; - try pp.comp.addDiagnostic(.{ .tag = .expected_arguments, .loc = macro_tok.loc, .extra = extra }, &.{}); + try pp.err(macro_tok, .expected_arguments, .{ 1, 0 }); break :blk not_found; } else res: { var embed_args: []const TokenWithExpansionLocs = &.{}; @@ -1818,10 +1784,7 @@ fn expandFuncMacro( const param_first = it.next(); if (param_first.id == .eof) break; if (param_first.id != .identifier) { - try pp.comp.addDiagnostic( - .{ .tag = .malformed_embed_param, .loc = param_first.loc }, - param_first.expansionSlice(), - ); + try pp.err(param_first, .malformed_embed_param, .{}); continue; } @@ -1834,28 +1797,19 @@ fn expandFuncMacro( // vendor::param const param = it.next(); if (param.id != .identifier) { - try pp.comp.addDiagnostic( - .{ .tag = .malformed_embed_param, .loc = param.loc }, - param.expansionSlice(), - ); + try pp.err(param, .malformed_embed_param, .{}); continue; } const l_paren = it.next(); if (l_paren.id != .l_paren) { - try pp.comp.addDiagnostic( - .{ .tag = .malformed_embed_param, .loc = l_paren.loc }, - l_paren.expansionSlice(), - ); + try pp.err(l_paren, .malformed_embed_param, .{}); continue; } break :blk "doesn't exist"; }, .l_paren => Attribute.normalize(pp.expandedSlice(param_first)), else => { - try pp.comp.addDiagnostic( - .{ .tag = .malformed_embed_param, .loc = maybe_colon.loc }, - maybe_colon.expansionSlice(), - ); + try pp.err(maybe_colon, .malformed_embed_param, .{}); continue; }, }; @@ -1865,10 +1819,7 @@ fn expandFuncMacro( while (true) { const next = it.next(); if (next.id == .eof) { - try pp.comp.addDiagnostic( - .{ .tag = .malformed_embed_limit, .loc = param_first.loc }, - param_first.expansionSlice(), - ); + try pp.err(param_first, .malformed_embed_limit, .{}); break; } if (next.id == .r_paren) break; @@ -1878,17 +1829,11 @@ fn expandFuncMacro( if (std.mem.eql(u8, param, "limit")) { if (arg_count != 1) { - try pp.comp.addDiagnostic( - .{ .tag = .malformed_embed_limit, .loc = param_first.loc }, - param_first.expansionSlice(), - ); + try pp.err(param_first, .malformed_embed_limit, .{}); continue; } if (first_arg.id != .pp_num) { - try pp.comp.addDiagnostic( - .{ .tag = .malformed_embed_limit, .loc = param_first.loc }, - param_first.expansionSlice(), - ); + try pp.err(param_first, .malformed_embed_limit, .{}); continue; } _ = std.fmt.parseInt(u32, pp.expandedSlice(first_arg), 10) catch { @@ -1936,10 +1881,10 @@ fn expandFuncMacro( }, }; if (string == null and invalid == null) invalid = .{ .loc = macro_tok.loc, .id = .eof }; - if (invalid) |some| try pp.comp.addDiagnostic( - .{ .tag = .pragma_operator_string_literal, .loc = some.loc }, - some.expansionSlice(), - ) else try pp.pragmaOperator(string.?, macro_tok.loc); + if (invalid) |some| + try pp.err(some, .pragma_operator_string_literal, .{}) + else + try pp.pragmaOperator(string.?, macro_tok.loc); }, .comma => { if (tok_i + 2 < func_macro.tokens.len and func_macro.tokens[tok_i + 1].id == .hash_hash) { @@ -1955,12 +1900,12 @@ fn expandFuncMacro( tok_i += consumed; if (func_macro.params.len == expanded_args.items.len) { // Empty __VA_ARGS__, drop the comma - try pp.err(hash_hash, .comma_deletion_va_args); + try pp.err(hash_hash, .comma_deletion_va_args, .{}); } else if (func_macro.params.len == 0 and expanded_args.items.len == 1 and expanded_args.items[0].len == 0) { // Ambiguous whether this is "empty __VA_ARGS__" or "__VA_ARGS__ omitted" if (pp.comp.langopts.standard.isGNU()) { // GNU standard, drop the comma - try pp.err(hash_hash, .comma_deletion_va_args); + try pp.err(hash_hash, .comma_deletion_va_args, .{}); } else { // C standard, retain the comma try buf.append(tokFromRaw(raw)); @@ -1968,7 +1913,7 @@ fn expandFuncMacro( } else { try buf.append(tokFromRaw(raw)); if (expanded_variable_arguments.items.len > 0 or variable_arguments.items.len == func_macro.params.len) { - try pp.err(hash_hash, .comma_deletion_va_args); + try pp.err(hash_hash, .comma_deletion_va_args, .{}); } const raw_loc = Source.Location{ .id = maybe_va_args.source, @@ -2046,7 +1991,7 @@ fn nextBufToken( const raw_tok = tokenizer.next(); if (raw_tok.id.isMacroIdentifier() and pp.poisoned_identifiers.get(pp.tokSlice(raw_tok)) != null) - try pp.err(raw_tok, .poisoned_identifier); + try pp.err(raw_tok, .poisoned_identifier, .{}); if (raw_tok.id == .nl) pp.add_expansion_nl += 1; @@ -2083,7 +2028,7 @@ fn collectMacroFuncArguments( .l_paren => break, else => { if (is_builtin) { - try pp.errStr(name_tok, .missing_lparen_after_builtin, pp.expandedSlice(name_tok)); + try pp.err(name_tok, .missing_lparen_after_builtin, .{pp.expandedSlice(name_tok)}); } // Not a macro function call, go over normal identifier, rewind tokenizer.* = saved_tokenizer; @@ -2141,10 +2086,7 @@ fn collectMacroFuncArguments( try args.append(owned); } tokenizer.* = saved_tokenizer; - try pp.comp.addDiagnostic( - .{ .tag = .unterminated_macro_arg_list, .loc = name_tok.loc }, - name_tok.expansionSlice(), - ); + try pp.err(name_tok, .unterminated_macro_arg_list, .{}); return error.Unterminated; }, .nl, .whitespace => { @@ -2299,25 +2241,16 @@ fn expandMacroExhaustive( } // Validate argument count. - const extra = Diagnostics.Message.Extra{ - .arguments = .{ .expected = @intCast(macro.params.len), .actual = args_count }, - }; if (macro.var_args and args_count < macro.params.len) { free_arg_expansion_locs = true; - try pp.comp.addDiagnostic( - .{ .tag = .expected_at_least_arguments, .loc = buf.items[idx].loc, .extra = extra }, - buf.items[idx].expansionSlice(), - ); + try pp.err(buf.items[idx], .expected_at_least_arguments, .{ macro.params.len, args_count }); idx += 1; try pp.removeExpandedTokens(buf, idx, macro_scan_idx - idx + 1, &moving_end_idx); continue; } if (!macro.var_args and args_count != macro.params.len) { free_arg_expansion_locs = true; - try pp.comp.addDiagnostic( - .{ .tag = .expected_arguments, .loc = buf.items[idx].loc, .extra = extra }, - buf.items[idx].expansionSlice(), - ); + try pp.err(buf.items[idx], .expected_arguments, .{ macro.params.len, args_count }); idx += 1; try pp.removeExpandedTokens(buf, idx, macro_scan_idx - idx + 1, &moving_end_idx); continue; @@ -2366,10 +2299,7 @@ fn expandMacroExhaustive( try pp.hideset.put(tok.loc, new_hidelist); if (tok.id == .keyword_defined and eval_ctx == .expr) { - try pp.comp.addDiagnostic(.{ - .tag = .expansion_to_defined, - .loc = tok.loc, - }, tok.expansionSlice()); + try pp.err(tok, .expansion_to_defined, .{}); } if (i < increment_idx_by and (tok.id == .keyword_defined or pp.defines.contains(pp.expandedSlice(tok.*)))) { @@ -2513,11 +2443,7 @@ fn pasteTokens(pp: *Preprocessor, lhs_toks: *ExpandBuf, rhs_toks: []const TokenW try lhs_toks.append(try pp.makeGeneratedToken(start, pasted_id, lhs)); if (next.id != .nl and next.id != .eof) { - try pp.errStr( - lhs, - .pasting_formed_invalid, - try pp.comp.diagnostics.arena.allocator().dupe(u8, pp.comp.generated_buf.items[start..end]), - ); + try pp.err(lhs, .pasting_formed_invalid, .{pp.comp.generated_buf.items[start..end]}); try lhs_toks.append(tokFromRaw(next)); } @@ -2541,18 +2467,12 @@ fn defineMacro(pp: *Preprocessor, define_tok: RawToken, name_tok: RawToken, macr const name_str = pp.tokSlice(name_tok); const gop = try pp.defines.getOrPut(pp.gpa, name_str); if (gop.found_existing and !gop.value_ptr.eql(macro, pp)) { - const tag: Diagnostics.Tag = if (gop.value_ptr.is_builtin) .builtin_macro_redefined else .macro_redefined; - const start = pp.comp.diagnostics.list.items.len; - try pp.comp.addDiagnostic(.{ - .tag = tag, - .loc = .{ .id = name_tok.source, .byte_offset = name_tok.start, .line = name_tok.line }, - .extra = .{ .str = name_str }, - }, &.{}); - if (!gop.value_ptr.is_builtin and pp.comp.diagnostics.list.items.len != start) { - try pp.comp.addDiagnostic(.{ - .tag = .previous_definition, - .loc = gop.value_ptr.loc, - }, &.{}); + const loc: Source.Location = .{ .id = name_tok.source, .byte_offset = name_tok.start, .line = name_tok.line }; + const prev_total = pp.diagnostics.total; + try pp.err(loc, if (gop.value_ptr.is_builtin) .builtin_macro_redefined else .macro_redefined, .{name_str}); + + if (!gop.value_ptr.is_builtin and pp.diagnostics.total != prev_total) { + try pp.err(gop.value_ptr.loc, .previous_definition, .{}); } } if (pp.verbose) { @@ -2569,11 +2489,11 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! // Get macro name and validate it. const macro_name = tokenizer.nextNoWS(); if (macro_name.id == .keyword_defined) { - try pp.err(macro_name, .defined_as_macro_name); + try pp.err(macro_name, .defined_as_macro_name, .{}); return skipToNl(tokenizer); } if (!macro_name.id.isMacroIdentifier()) { - try pp.err(macro_name, .macro_name_must_be_identifier); + try pp.err(macro_name, .macro_name_must_be_identifier, .{}); return skipToNl(tokenizer); } var macro_name_token_id = macro_name.id; @@ -2581,7 +2501,7 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! switch (macro_name_token_id) { .identifier, .extended_identifier => {}, else => if (macro_name_token_id.isMacroIdentifier()) { - try pp.err(macro_name, .keyword_macro); + try pp.err(macro_name, .keyword_macro, .{}); }, } @@ -2597,10 +2517,10 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! }), .whitespace => first = tokenizer.next(), .l_paren => return pp.defineFn(tokenizer, define_tok, macro_name, first), - else => try pp.err(first, .whitespace_after_macro_name), + else => try pp.err(first, .whitespace_after_macro_name, .{}), } if (first.id == .hash_hash) { - try pp.err(first, .hash_hash_at_start); + try pp.err(first, .hash_hash_at_start, .{}); return skipToNl(tokenizer); } first.id.simplifyMacroKeyword(); @@ -2617,11 +2537,11 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! const next = tokenizer.nextNoWSComments(); switch (next.id) { .nl, .eof => { - try pp.err(tok, .hash_hash_at_end); + try pp.err(tok, .hash_hash_at_end, .{}); return; }, .hash_hash => { - try pp.err(next, .hash_hash_at_end); + try pp.err(next, .hash_hash_at_end, .{}); return; }, else => {}, @@ -2639,10 +2559,10 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! }, .whitespace => need_ws = true, .unterminated_string_literal, .unterminated_char_literal, .empty_char_literal => |tag| { - try pp.err(tok, invalidTokenDiagnostic(tag)); + try pp.err(tok, invalidTokenDiagnostic(tag), .{}); try pp.token_buf.append(tok); }, - .unterminated_comment => try pp.err(tok, .unterminated_comment), + .unterminated_comment => try pp.err(tok, .unterminated_comment, .{}), else => { if (tok.id != .whitespace and need_ws) { need_ws = false; @@ -2676,19 +2596,19 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macr while (true) { var tok = tokenizer.nextNoWS(); if (tok.id == .r_paren) break; - if (tok.id == .eof) return pp.err(tok, .unterminated_macro_param_list); + if (tok.id == .eof) return pp.err(tok, .unterminated_macro_param_list, .{}); if (tok.id == .ellipsis) { var_args = true; const r_paren = tokenizer.nextNoWS(); if (r_paren.id != .r_paren) { - try pp.err(r_paren, .missing_paren_param_list); - try pp.err(l_paren, .to_match_paren); + try pp.err(r_paren, .missing_paren_param_list, .{}); + try pp.err(l_paren, .to_match_paren, .{}); return skipToNl(tokenizer); } break; } if (!tok.id.isMacroIdentifier()) { - try pp.err(tok, .invalid_token_param_list); + try pp.err(tok, .invalid_token_param_list, .{}); return skipToNl(tokenizer); } @@ -2696,19 +2616,19 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macr tok = tokenizer.nextNoWS(); if (tok.id == .ellipsis) { - try pp.err(tok, .gnu_va_macro); + try pp.err(tok, .gnu_va_macro, .{}); gnu_var_args = params.pop().?; const r_paren = tokenizer.nextNoWS(); if (r_paren.id != .r_paren) { - try pp.err(r_paren, .missing_paren_param_list); - try pp.err(l_paren, .to_match_paren); + try pp.err(r_paren, .missing_paren_param_list, .{}); + try pp.err(l_paren, .to_match_paren, .{}); return skipToNl(tokenizer); } break; } else if (tok.id == .r_paren) { break; } else if (tok.id != .comma) { - try pp.err(tok, .expected_comma_param_list); + try pp.err(tok, .expected_comma_param_list, .{}); return skipToNl(tokenizer); } } @@ -2756,7 +2676,7 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macr } } } - try pp.err(param, .hash_not_followed_param); + try pp.err(param, .hash_not_followed_param, .{}); return skipToNl(tokenizer); }, .hash_hash => { @@ -2764,13 +2684,13 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macr // if ## appears at the beginning, the token buf is still empty // in this case, error out if (pp.token_buf.items.len == 0) { - try pp.err(tok, .hash_hash_at_start); + try pp.err(tok, .hash_hash_at_start, .{}); return skipToNl(tokenizer); } const saved_tokenizer = tokenizer.*; const next = tokenizer.nextNoWSComments(); if (next.id == .nl or next.id == .eof) { - try pp.err(tok, .hash_hash_at_end); + try pp.err(tok, .hash_hash_at_end, .{}); return; } tokenizer.* = saved_tokenizer; @@ -2781,10 +2701,10 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macr try pp.token_buf.append(tok); }, .unterminated_string_literal, .unterminated_char_literal, .empty_char_literal => |tag| { - try pp.err(tok, invalidTokenDiagnostic(tag)); + try pp.err(tok, invalidTokenDiagnostic(tag), .{}); try pp.token_buf.append(tok); }, - .unterminated_comment => try pp.err(tok, .unterminated_comment), + .unterminated_comment => try pp.err(tok, .unterminated_comment, .{}), else => { if (tok.id != .whitespace and need_ws) { need_ws = false; @@ -2795,7 +2715,7 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macr } else if (var_args and tok.id == .keyword_va_opt) { const opt_l_paren = tokenizer.next(); if (opt_l_paren.id != .l_paren) { - try pp.err(opt_l_paren, .va_opt_lparen); + try pp.err(opt_l_paren, .va_opt_lparen, .{}); return skipToNl(tokenizer); } tok.start = opt_l_paren.end; @@ -2811,8 +2731,8 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macr parens -= 1; }, .nl, .eof => { - try pp.err(opt_tok, .va_opt_rparen); - try pp.err(opt_l_paren, .to_match_paren); + try pp.err(opt_tok, .va_opt_rparen, .{}); + try pp.err(opt_l_paren, .to_match_paren, .{}); return skipToNl(tokenizer); }, .whitespace => {}, @@ -2865,7 +2785,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { // Check for empty filename. const tok_slice = pp.expandedSliceExtra(filename_tok, .single_macro_ws); if (tok_slice.len < 3) { - try pp.err(first, .empty_filename); + try pp.err(first, .empty_filename, .{}); return; } const filename = tok_slice[1 .. tok_slice.len - 1]; @@ -2900,7 +2820,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { .nl, .eof => break, .identifier => {}, else => { - try pp.err(param_first, .malformed_embed_param); + try pp.err(param_first, .malformed_embed_param, .{}); continue; }, } @@ -2914,12 +2834,12 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { // vendor::param const param = tokenizer.nextNoWS(); if (param.id != .identifier) { - try pp.err(param, .malformed_embed_param); + try pp.err(param, .malformed_embed_param, .{}); continue; } const l_paren = tokenizer.nextNoWS(); if (l_paren.id != .l_paren) { - try pp.err(l_paren, .malformed_embed_param); + try pp.err(l_paren, .malformed_embed_param, .{}); continue; } try pp.char_buf.appendSlice(Attribute.normalize(pp.tokSlice(param_first))); @@ -2929,7 +2849,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { }, .l_paren => Attribute.normalize(pp.tokSlice(param_first)), else => { - try pp.err(maybe_colon, .malformed_embed_param); + try pp.err(maybe_colon, .malformed_embed_param, .{}); continue; }, }; @@ -2939,7 +2859,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { const next = tokenizer.nextNoWS(); if (next.id == .r_paren) break; if (next.id == .eof) { - try pp.err(maybe_colon, .malformed_embed_param); + try pp.err(maybe_colon, .malformed_embed_param, .{}); break; } try pp.token_buf.append(next); @@ -2948,47 +2868,43 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { if (std.mem.eql(u8, param, "limit")) { if (limit != null) { - try pp.errStr(tokFromRaw(param_first), .duplicate_embed_param, "limit"); + try pp.err(tokFromRaw(param_first), .duplicate_embed_param, .{"limit"}); continue; } if (start + 1 != end) { - try pp.err(param_first, .malformed_embed_limit); + try pp.err(param_first, .malformed_embed_limit, .{}); continue; } const limit_tok = pp.token_buf.items[start]; if (limit_tok.id != .pp_num) { - try pp.err(param_first, .malformed_embed_limit); + try pp.err(param_first, .malformed_embed_limit, .{}); continue; } limit = std.fmt.parseInt(u32, pp.tokSlice(limit_tok), 10) catch { - try pp.err(limit_tok, .malformed_embed_limit); + try pp.err(limit_tok, .malformed_embed_limit, .{}); continue; }; pp.token_buf.items.len = start; } else if (std.mem.eql(u8, param, "prefix")) { if (prefix != null) { - try pp.errStr(tokFromRaw(param_first), .duplicate_embed_param, "prefix"); + try pp.err(tokFromRaw(param_first), .duplicate_embed_param, .{"prefix"}); continue; } prefix = .{ .start = start, .end = end }; } else if (std.mem.eql(u8, param, "suffix")) { if (suffix != null) { - try pp.errStr(tokFromRaw(param_first), .duplicate_embed_param, "suffix"); + try pp.err(tokFromRaw(param_first), .duplicate_embed_param, .{"suffix"}); continue; } suffix = .{ .start = start, .end = end }; } else if (std.mem.eql(u8, param, "if_empty")) { if (if_empty != null) { - try pp.errStr(tokFromRaw(param_first), .duplicate_embed_param, "if_empty"); + try pp.err(tokFromRaw(param_first), .duplicate_embed_param, .{"if_empty"}); continue; } if_empty = .{ .start = start, .end = end }; } else { - try pp.errStr( - tokFromRaw(param_first), - .unsupported_embed_param, - try pp.comp.diagnostics.arena.allocator().dupe(u8, param), - ); + try pp.err(tokFromRaw(param_first), .unsupported_embed_param, .{param}); pp.token_buf.items.len = start; } } @@ -3042,10 +2958,8 @@ fn include(pp: *Preprocessor, tokenizer: *Tokenizer, which: Compilation.WhichInc pp.include_depth += 1; defer pp.include_depth -= 1; if (pp.include_depth > max_include_depth) { - try pp.comp.addDiagnostic(.{ - .tag = .too_many_includes, - .loc = .{ .id = first.source, .byte_offset = first.start, .line = first.line }, - }, &.{}); + const loc: Source.Location = .{ .id = first.source, .byte_offset = first.start, .line = first.line }; + try pp.err(loc, .too_many_includes, .{}); return error.StopPreprocessing; } @@ -3154,10 +3068,8 @@ fn pragma(pp: *Preprocessor, tokenizer: *Tokenizer, pragma_tok: RawToken, operat else => |e| return e, }; } - return pp.comp.addDiagnostic(.{ - .tag = .unknown_pragma, - .loc = pragma_name_tok.loc, - }, pragma_name_tok.expansionSlice()); + + try pp.err(pragma_name_tok, .unknown_pragma, .{}); } fn findIncludeFilenameToken( @@ -3182,11 +3094,9 @@ fn findIncludeFilenameToken( else => {}, } } - try pp.comp.addDiagnostic(.{ - .tag = .header_str_closing, - .loc = .{ .id = first.source, .byte_offset = tokenizer.index, .line = first.line }, - }, &.{}); - try pp.err(first, .header_str_match); + const loc: Source.Location = .{ .id = first.source, .byte_offset = tokenizer.index, .line = first.line }; + try pp.err(loc, .header_str_closing, .{}); + try pp.err(first, .header_str_match, .{}); } const source_tok = tokFromRaw(first); @@ -3222,17 +3132,11 @@ fn findIncludeFilenameToken( const nl = tokenizer.nextNoWS(); if ((nl.id != .nl and nl.id != .eof) or expanded_trailing) { skipToNl(tokenizer); - try pp.comp.diagnostics.addExtra(pp.comp.langopts, .{ - .tag = .extra_tokens_directive_end, - .loc = filename_tok.loc, - }, filename_tok.expansionSlice(), false); + try pp.err(filename_tok, .extra_tokens_directive_end, .{}); } }, .ignore_trailing_tokens => if (expanded_trailing) { - try pp.comp.diagnostics.addExtra(pp.comp.langopts, .{ - .tag = .extra_tokens_directive_end, - .loc = filename_tok.loc, - }, filename_tok.expansionSlice(), false); + try pp.err(filename_tok, .extra_tokens_directive_end, .{}); }, } return filename_tok; @@ -3245,7 +3149,7 @@ fn findIncludeSource(pp: *Preprocessor, tokenizer: *Tokenizer, first: RawToken, // Check for empty filename. const tok_slice = pp.expandedSliceExtra(filename_tok, .single_macro_ws); if (tok_slice.len < 3) { - try pp.err(first, .empty_filename); + try pp.err(first, .empty_filename, .{}); return error.InvalidInclude; } diff --git a/src/aro/Preprocessor/Diagnostic.zig b/src/aro/Preprocessor/Diagnostic.zig new file mode 100644 index 0000000..8068c6d --- /dev/null +++ b/src/aro/Preprocessor/Diagnostic.zig @@ -0,0 +1,407 @@ +const std = @import("std"); + +const Diagnostics = @import("../Diagnostics.zig"); +const LangOpts = @import("../LangOpts.zig"); +const Compilation = @import("../Compilation.zig"); + +const Diagnostic = @This(); + +fmt: []const u8, +kind: Diagnostics.Message.Kind, +opt: ?Diagnostics.Option = null, +extension: bool = false, + +pub const elif_without_if: Diagnostic = .{ + .fmt = "#elif without #if", + .kind = .@"error", +}; + +pub const elif_after_else: Diagnostic = .{ + .fmt = "#elif after #else", + .kind = .@"error", +}; + +pub const elifdef_without_if: Diagnostic = .{ + .fmt = "#elifdef without #if", + .kind = .@"error", +}; + +pub const elifdef_after_else: Diagnostic = .{ + .fmt = "#elifdef after #else", + .kind = .@"error", +}; + +pub const elifndef_without_if: Diagnostic = .{ + .fmt = "#elifndef without #if", + .kind = .@"error", +}; + +pub const elifndef_after_else: Diagnostic = .{ + .fmt = "#elifndef after #else", + .kind = .@"error", +}; + +pub const else_without_if: Diagnostic = .{ + .fmt = "#else without #if", + .kind = .@"error", +}; + +pub const else_after_else: Diagnostic = .{ + .fmt = "#else after #else", + .kind = .@"error", +}; + +pub const endif_without_if: Diagnostic = .{ + .fmt = "#endif without #if", + .kind = .@"error", +}; + +pub const unknown_pragma: Diagnostic = .{ + .fmt = "unknown pragma ignored", + .opt = .@"unknown-pragmas", + .kind = .off, +}; + +pub const line_simple_digit: Diagnostic = .{ + .fmt = "#line directive requires a simple digit sequence", + .kind = .@"error", +}; + +pub const line_invalid_filename: Diagnostic = .{ + .fmt = "invalid filename for #line directive", + .kind = .@"error", +}; + +pub const unterminated_conditional_directive: Diagnostic = .{ + .fmt = "unterminated conditional directive", + .kind = .@"error", +}; + +pub const invalid_preprocessing_directive: Diagnostic = .{ + .fmt = "invalid preprocessing directive", + .kind = .@"error", +}; + +pub const error_directive: Diagnostic = .{ + .fmt = "{s}", + .kind = .@"error", +}; + +pub const warning_directive: Diagnostic = .{ + .fmt = "{s}", + .opt = .@"#warnings", + .kind = .warning, +}; + +pub const macro_name_missing: Diagnostic = .{ + .fmt = "macro name missing", + .kind = .@"error", +}; + +pub const extra_tokens_directive_end: Diagnostic = .{ + .fmt = "extra tokens at end of macro directive", + .kind = .@"error", +}; + +pub const expected_value_in_expr: Diagnostic = .{ + .fmt = "expected value in expression", + .kind = .@"error", +}; + +pub const defined_as_macro_name: Diagnostic = .{ + .fmt = "'defined' cannot be used as a macro name", + .kind = .@"error", +}; + +pub const macro_name_must_be_identifier: Diagnostic = .{ + .fmt = "macro name must be an identifier", + .kind = .@"error", +}; + +pub const whitespace_after_macro_name: Diagnostic = .{ + .fmt = "ISO C99 requires whitespace after the macro name", + .opt = .@"c99-extensions", + .kind = .warning, + .extension = true, +}; + +pub const hash_hash_at_start: Diagnostic = .{ + .fmt = "'##' cannot appear at the start of a macro expansion", + .kind = .@"error", +}; + +pub const hash_hash_at_end: Diagnostic = .{ + .fmt = "'##' cannot appear at the end of a macro expansion", + .kind = .@"error", +}; + +pub const pasting_formed_invalid: Diagnostic = .{ + .fmt = "pasting formed '{s}', an invalid preprocessing token", + .kind = .@"error", +}; + +pub const missing_paren_param_list: Diagnostic = .{ + .fmt = "missing ')' in macro parameter list", + .kind = .@"error", +}; + +pub const unterminated_macro_param_list: Diagnostic = .{ + .fmt = "unterminated macro param list", + .kind = .@"error", +}; + +pub const invalid_token_param_list: Diagnostic = .{ + .fmt = "invalid token in macro parameter list", + .kind = .@"error", +}; + +pub const expected_comma_param_list: Diagnostic = .{ + .fmt = "expected comma in macro parameter list", + .kind = .@"error", +}; + +pub const hash_not_followed_param: Diagnostic = .{ + .fmt = "'#' is not followed by a macro parameter", + .kind = .@"error", +}; + +pub const expected_filename: Diagnostic = .{ + .fmt = "expected \"FILENAME\" or ", + .kind = .@"error", +}; + +pub const empty_filename: Diagnostic = .{ + .fmt = "empty filename", + .kind = .@"error", +}; + +pub const header_str_closing: Diagnostic = .{ + .fmt = "expected closing '>'", + .kind = .@"error", +}; + +pub const header_str_match: Diagnostic = .{ + .fmt = "to match this '<'", + .kind = .note, +}; + +pub const string_literal_in_pp_expr: Diagnostic = .{ + .fmt = "string literal in preprocessor expression", + .kind = .@"error", +}; + +pub const empty_char_literal_warning: Diagnostic = .{ + .fmt = "empty character constant", + .kind = .warning, + .opt = .@"invalid-pp-token", +}; + +pub const unterminated_char_literal_warning: Diagnostic = .{ + .fmt = "missing terminating ' character", + .kind = .warning, + .opt = .@"invalid-pp-token", +}; + +pub const unterminated_comment: Diagnostic = .{ + .fmt = "unterminated comment", + .kind = .@"error", +}; + +pub const malformed_embed_param: Diagnostic = .{ + .fmt = "unexpected token in embed parameter", + .kind = .@"error", +}; + +pub const malformed_embed_limit: Diagnostic = .{ + .fmt = "the limit parameter expects one non-negative integer as a parameter", + .kind = .@"error", +}; + +pub const duplicate_embed_param: Diagnostic = .{ + .fmt = "duplicate embed parameter '{s}'", + .kind = .warning, + .opt = .@"duplicate-embed-param", +}; + +pub const unsupported_embed_param: Diagnostic = .{ + .fmt = "unsupported embed parameter '{s}' embed parameter", + .kind = .warning, + .opt = .@"unsupported-embed-param", +}; + +pub const va_opt_lparen: Diagnostic = .{ + .fmt = "missing '(' following __VA_OPT__", + .kind = .@"error", +}; + +pub const va_opt_rparen: Diagnostic = .{ + .fmt = "unterminated __VA_OPT__ argument list", + .kind = .@"error", +}; + +pub const keyword_macro: Diagnostic = .{ + .fmt = "keyword is hidden by macro definition", + .kind = .off, + .opt = .@"keyword-macro", + .extension = true, +}; + +pub const undefined_macro: Diagnostic = .{ + .fmt = "'{s}' is not defined, evaluates to 0", + .kind = .off, + .opt = .undef, +}; + +pub const fn_macro_undefined: Diagnostic = .{ + .fmt = "function-like macro '{s}' is not defined", + .kind = .@"error", +}; + +// pub const preprocessing_directive_only: Diagnostic = .{ +// .fmt = "'{s}' must be used within a preprocessing directive", +// .extra = .tok_id_expected, +// .kind = .@"error", +// }; + +pub const missing_lparen_after_builtin: Diagnostic = .{ + .fmt = "Missing '(' after built-in macro '{s}'", + .kind = .@"error", +}; + +pub const too_many_includes: Diagnostic = .{ + .fmt = "#include nested too deeply", + .kind = .@"error", +}; + +pub const include_next: Diagnostic = .{ + .fmt = "#include_next is a language extension", + .kind = .off, + .opt = .@"gnu-include-next", + .extension = true, +}; + +pub const include_next_outside_header: Diagnostic = .{ + .fmt = "#include_next in primary source file; will search from start of include path", + .kind = .warning, + .opt = .@"include-next-outside-header", +}; + +pub const comma_deletion_va_args: Diagnostic = .{ + .fmt = "token pasting of ',' and __VA_ARGS__ is a GNU extension", + .kind = .off, + .opt = .@"gnu-zero-variadic-macro-arguments", + .extension = true, +}; + +pub const expansion_to_defined: Diagnostic = .{ + .fmt = "macro expansion producing 'defined' has undefined behavior", + .kind = .off, + .opt = .@"expansion-to-defined", +}; + +pub const invalid_pp_stringify_escape: Diagnostic = .{ + .fmt = "invalid string literal, ignoring final '\\'", + .kind = .warning, +}; + +pub const gnu_va_macro: Diagnostic = .{ + .fmt = "named variadic macros are a GNU extension", + .opt = .@"variadic-macros", + .kind = .off, + .extension = true, +}; + +pub const pragma_operator_string_literal: Diagnostic = .{ + .fmt = "_Pragma requires exactly one string literal token", + .kind = .@"error", +}; + +pub const invalid_preproc_expr_start: Diagnostic = .{ + .fmt = "invalid token at start of a preprocessor expression", + .kind = .@"error", +}; + +pub const newline_eof: Diagnostic = .{ + .fmt = "no newline at end of file", + .opt = .@"newline-eof", + .kind = .off, +}; + +pub const malformed_warning_check: Diagnostic = .{ + .fmt = "{s} expected option name (e.g. \"-Wundef\")", + .opt = .@"malformed-warning-check", + .kind = .warning, +}; + +pub const feature_check_requires_identifier: Diagnostic = .{ + .fmt = "builtin feature check macro requires a parenthesized identifier", + .kind = .@"error", +}; + +pub const builtin_macro_redefined: Diagnostic = .{ + .fmt = "redefining builtin macro", + .opt = .@"builtin-macro-redefined", + .kind = .warning, +}; + +pub const macro_redefined: Diagnostic = .{ + .fmt = "'{s}' macro redefined", + .opt = .@"macro-redefined", + .kind = .warning, +}; + +pub const previous_definition: Diagnostic = .{ + .fmt = "previous definition is here", + .kind = .note, +}; + +pub const unterminated_macro_arg_list: Diagnostic = .{ + .fmt = "unterminated function macro argument list", + .kind = .@"error", +}; + +pub const to_match_paren: Diagnostic = .{ + .fmt = "to match this '('", + .kind = .note, +}; + +pub const closing_paren: Diagnostic = .{ + .fmt = "expected closing ')'", + .kind = .@"error", +}; + +pub const poisoned_identifier: Diagnostic = .{ + .fmt = "attempt to use a poisoned identifier", + .kind = .@"error", +}; + +pub const expected_arguments: Diagnostic = .{ + .fmt = "expected {d} argument(s) got {d}", + .kind = .@"error", +}; + +pub const expected_at_least_arguments: Diagnostic = .{ + .fmt = "expected at least {d} argument(s) got {d}", + .kind = .warning, +}; + +pub const invalid_preproc_operator: Diagnostic = .{ + .fmt = "token is not a valid binary operator in a preprocessor subexpression", + .kind = .@"error", +}; + +pub const expected_str_literal_in: Diagnostic = .{ + .fmt = "expected string literal in '{s}'", + .kind = .@"error", +}; + +pub const builtin_missing_r_paren: Diagnostic = .{ + .fmt = "missing ')', after builtin feature-check macro", + .kind = .@"error", +}; + +pub const unterminated_string_literal_warning: Diagnostic = .{ + .fmt = "missing terminating '\"' character", + .kind = .warning, + .opt = .@"invalid-pp-token", +}; diff --git a/src/aro/Source.zig b/src/aro/Source.zig index 20788af..09eec72 100644 --- a/src/aro/Source.zig +++ b/src/aro/Source.zig @@ -24,6 +24,20 @@ pub const Location = struct { pub fn eql(a: Location, b: Location) bool { return a.id == b.id and a.byte_offset == b.byte_offset and a.line == b.line; } + + pub fn expand(loc: Location, comp: *const @import("Compilation.zig")) ExpandedLocation { + const source = comp.getSource(loc.id); + return source.lineCol(loc); + } +}; + +pub const ExpandedLocation = struct { + path: []const u8, + line: []const u8, + line_no: u32, + col: u32, + width: u32, + end_with_splice: bool, }; const Source = @This(); @@ -51,9 +65,7 @@ pub fn physicalLine(source: Source, loc: Location) u32 { return loc.line + source.numSplicesBefore(loc.byte_offset); } -const LineCol = struct { line: []const u8, line_no: u32, col: u32, width: u32, end_with_splice: bool }; - -pub fn lineCol(source: Source, loc: Location) LineCol { +pub fn lineCol(source: Source, loc: Location) ExpandedLocation { var start: usize = 0; // find the start of the line which is either a newline or a splice if (std.mem.lastIndexOfScalar(u8, source.buf[0..loc.byte_offset], '\n')) |some| start = some + 1; @@ -102,6 +114,7 @@ pub fn lineCol(source: Source, loc: Location) LineCol { nl = source.splice_locs[splice_index]; } return .{ + .path = source.path, .line = source.buf[start..nl], .line_no = loc.line + splice_index, .col = col, diff --git a/src/aro/SymbolStack.zig b/src/aro/SymbolStack.zig index 8c8f19d..33e8708 100644 --- a/src/aro/SymbolStack.zig +++ b/src/aro/SymbolStack.zig @@ -83,17 +83,17 @@ pub fn findTypedef(s: *SymbolStack, p: *Parser, name: StringId, name_tok: TokenI .typedef => return prev, .@"struct" => { if (no_type_yet) return null; - try p.errStr(.must_use_struct, name_tok, p.tokSlice(name_tok)); + try p.err(name_tok, .must_use_struct, .{p.tokSlice(name_tok)}); return prev; }, .@"union" => { if (no_type_yet) return null; - try p.errStr(.must_use_union, name_tok, p.tokSlice(name_tok)); + try p.err(name_tok, .must_use_union, .{p.tokSlice(name_tok)}); return prev; }, .@"enum" => { if (no_type_yet) return null; - try p.errStr(.must_use_enum, name_tok, p.tokSlice(name_tok)); + try p.err(name_tok, .must_use_enum, .{p.tokSlice(name_tok)}); return prev; }, else => return null, @@ -121,8 +121,8 @@ pub fn findTag( else => unreachable, } if (s.get(name, .tags) == null) return null; - try p.errStr(.wrong_tag, name_tok, p.tokSlice(name_tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(name_tok, .wrong_tag, .{p.tokSlice(name_tok)}); + try p.err(prev.tok, .previous_definition, .{}); return null; } @@ -183,13 +183,13 @@ pub fn defineTypedef( if (qt.isInvalid()) return; const non_typedef_qt = qt.type(p.comp).typedef.base; const non_typedef_prev_qt = prev.qt.type(p.comp).typedef.base; - try p.errStr(.redefinition_of_typedef, tok, try p.typePairStrExtra(non_typedef_qt, " vs ", non_typedef_prev_qt)); - if (prev.tok != 0) try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_of_typedef, .{ non_typedef_qt, non_typedef_prev_qt }); + if (prev.tok != 0) try p.err(prev.tok, .previous_definition, .{}); } }, .enumeration, .decl, .def, .constexpr => { - try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, else => unreachable, } @@ -218,25 +218,25 @@ pub fn defineSymbol( switch (prev.kind) { .enumeration => { if (qt.isInvalid()) return; - try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, .decl => { if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) { if (qt.isInvalid()) return; - try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); } }, .def, .constexpr => if (!prev.qt.isInvalid()) { if (qt.isInvalid()) return; - try p.errStr(.redefinition, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, .typedef => { if (qt.isInvalid()) return; - try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, else => unreachable, } @@ -273,29 +273,29 @@ pub fn declareSymbol( switch (prev.kind) { .enumeration => { if (qt.isInvalid()) return; - try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, .decl => { if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) { if (qt.isInvalid()) return; - try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); } }, .def, .constexpr => { if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) { if (qt.isInvalid()) return; - try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); } else { return; } }, .typedef => { if (qt.isInvalid()) return; - try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, else => unreachable, } @@ -322,13 +322,13 @@ pub fn defineParam( switch (prev.kind) { .enumeration, .decl, .def, .constexpr => if (!prev.qt.isInvalid()) { if (qt.isInvalid()) return; - try p.errStr(.redefinition_of_parameter, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_of_parameter, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, .typedef => { if (qt.isInvalid()) return; - try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, else => unreachable, } @@ -354,20 +354,20 @@ pub fn defineTag( switch (prev.kind) { .@"enum" => { if (kind == .keyword_enum) return prev; - try p.errStr(.wrong_tag, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .wrong_tag, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); return null; }, .@"struct" => { if (kind == .keyword_struct) return prev; - try p.errStr(.wrong_tag, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .wrong_tag, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); return null; }, .@"union" => { if (kind == .keyword_union) return prev; - try p.errStr(.wrong_tag, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .wrong_tag, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); return null; }, else => unreachable, @@ -387,20 +387,20 @@ pub fn defineEnumeration( switch (prev.kind) { .enumeration => if (!prev.qt.isInvalid()) { if (qt.isInvalid()) return; - try p.errStr(.redefinition, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); return; }, .decl, .def, .constexpr => { if (qt.isInvalid()) return; - try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); return; }, .typedef => { if (qt.isInvalid()) return; - try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok)); - try p.errTok(.previous_definition, prev.tok); + try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)}); + try p.err(prev.tok, .previous_definition, .{}); }, else => unreachable, } diff --git a/src/aro/Toolchain.zig b/src/aro/Toolchain.zig index 3981571..d7733c6 100644 --- a/src/aro/Toolchain.zig +++ b/src/aro/Toolchain.zig @@ -158,7 +158,12 @@ pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 { // to a relative path is surprising. This is more complex due to priorities // among -B, COMPILER_PATH and PATH. --ld-path= should be used instead. if (mem.indexOfScalar(u8, use_linker, '/') != null) { - try tc.driver.comp.addDiagnostic(.{ .tag = .fuse_ld_path }, &.{}); + try tc.driver.comp.diagnostics.add(.{ + .text = "'-fuse-ld=' taking a path is deprecated; use '--ld-path=' instead", + .kind = .off, + .opt = .@"fuse-ld-path", + .location = null, + }); } if (std.fs.path.isAbsolute(use_linker)) { @@ -405,7 +410,7 @@ fn getUnwindLibKind(tc: *const Toolchain) !UnwindLibKind { return .libgcc; } else if (mem.eql(u8, libname, "libunwind")) { if (tc.getRuntimeLibKind() == .libgcc) { - try tc.driver.comp.addDiagnostic(.{ .tag = .incompatible_unwindlib }, &.{}); + try tc.driver.err("--rtlib=libgcc requires --unwindlib=libgcc", .{}); } return .compiler_rt; } else { @@ -482,7 +487,7 @@ pub fn addRuntimeLibs(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !v if (target_util.isKnownWindowsMSVCEnvironment(target)) { const rtlib_str = tc.driver.rtlib orelse system_defaults.rtlib; if (!mem.eql(u8, rtlib_str, "platform")) { - try tc.driver.comp.addDiagnostic(.{ .tag = .unsupported_rtlib_gcc, .extra = .{ .str = "MSVC" } }, &.{}); + try tc.driver.err("unsupported runtime library 'libgcc' for platform 'MSVC'", .{}); } } else { try tc.addLibGCC(argv); diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index 1c4f4c2..2a0a9e4 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -91,14 +91,16 @@ pub const TokenWithExpansionLocs = struct { pub fn checkMsEof(tok: TokenWithExpansionLocs, source: Source, comp: *Compilation) !void { std.debug.assert(tok.id == .eof); if (source.buf.len > tok.loc.byte_offset and source.buf[tok.loc.byte_offset] == 0x1A) { - try comp.addDiagnostic(.{ - .tag = .ctrl_z_eof, - .loc = .{ + const diagnostic: Compilation.Diagnostic = .ctrl_z_eof; + try comp.diagnostics.add(.{ + .text = diagnostic.fmt, + .kind = diagnostic.kind, + .location = source.lineCol(.{ .id = source.id, .byte_offset = tok.loc.byte_offset, .line = tok.loc.line, - }, - }, &.{}); + }), + }); } } }; diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index a43324a..0630669 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -2291,9 +2291,9 @@ pub const Builder = struct { const qt: QualType = switch (b.type) { .none => blk: { if (b.parser.comp.langopts.standard.atLeast(.c23)) { - try b.parser.err(.missing_type_specifier_c23); + try b.parser.err(b.parser.tok_i, .missing_type_specifier_c23, .{}); } else { - try b.parser.err(.missing_type_specifier); + try b.parser.err(b.parser.tok_i, .missing_type_specifier, .{}); } break :blk .int; }, @@ -2367,7 +2367,7 @@ pub const Builder = struct { .complex_uint128 => .uint128, else => unreachable, }; - if (b.complex_tok) |tok| try b.parser.errTok(.complex_int, tok); + if (b.complex_tok) |tok| try b.parser.err(tok, .complex_int, .{}); break :blk try base_qt.toComplex(b.parser.comp); }, @@ -2378,20 +2378,20 @@ pub const Builder = struct { if (unsigned) { if (bits < 1) { - try b.parser.errStr(.unsigned_bit_int_too_small, b.bit_int_tok.?, complex_str); + try b.parser.err(b.bit_int_tok.?, .unsigned_bit_int_too_small, .{complex_str}); return .invalid; } } else { if (bits < 2) { - try b.parser.errStr(.signed_bit_int_too_small, b.bit_int_tok.?, complex_str); + try b.parser.err(b.bit_int_tok.?, .signed_bit_int_too_small, .{complex_str}); return .invalid; } } if (bits > Compilation.bit_int_max_bits) { - try b.parser.errStr(if (unsigned) .unsigned_bit_int_too_big else .signed_bit_int_too_big, b.bit_int_tok.?, complex_str); + try b.parser.err(b.bit_int_tok.?, if (unsigned) .unsigned_bit_int_too_big else .signed_bit_int_too_big, .{complex_str}); return .invalid; } - if (b.complex_tok) |tok| try b.parser.errTok(.complex_int, tok); + if (b.complex_tok) |tok| try b.parser.err(tok, .complex_int, .{}); const qt = try b.parser.comp.type_store.put(b.parser.gpa, .{ .bit_int = .{ .signedness = if (unsigned) .unsigned else .signed, @@ -2423,7 +2423,7 @@ pub const Builder = struct { .complex => .double, else => unreachable, }; - if (b.type == .complex) try b.parser.errTok(.plain_complex, b.parser.tok_i - 1); + if (b.type == .complex) try b.parser.err(b.parser.tok_i - 1, .plain_complex, .{}); break :blk try base_qt.toComplex(b.parser.comp); }, @@ -2438,28 +2438,28 @@ pub const Builder = struct { if (b.atomic_type orelse b.atomic) |atomic_tok| { if (result_qt.isAutoType()) return b.parser.todo("_Atomic __auto_type"); if (result_qt.isC23Auto()) { - try b.parser.errTok(.atomic_auto, atomic_tok); + try b.parser.err(atomic_tok, .atomic_auto, .{}); return .invalid; } if (result_qt.hasIncompleteSize(b.parser.comp)) { - try b.parser.errStr(.atomic_incomplete, atomic_tok, try b.parser.typeStr(qt)); + try b.parser.err(atomic_tok, .atomic_incomplete, .{qt}); return .invalid; } switch (result_qt.base(b.parser.comp).type) { .array => { - try b.parser.errStr(.atomic_array, atomic_tok, try b.parser.typeStr(qt)); + try b.parser.err(atomic_tok, .atomic_array, .{qt}); return .invalid; }, .func => { - try b.parser.errStr(.atomic_func, atomic_tok, try b.parser.typeStr(qt)); + try b.parser.err(atomic_tok, .atomic_func, .{qt}); return .invalid; }, .atomic => { - try b.parser.errStr(.atomic_atomic, atomic_tok, try b.parser.typeStr(qt)); + try b.parser.err(atomic_tok, .atomic_atomic, .{qt}); return .invalid; }, .complex => { - try b.parser.errStr(.atomic_complex, atomic_tok, try b.parser.typeStr(qt)); + try b.parser.err(atomic_tok, .atomic_complex, .{qt}); return .invalid; }, else => { @@ -2481,7 +2481,7 @@ pub const Builder = struct { .nullable_result, .null_unspecified, => |tok| if (!qt.isPointer(b.parser.comp)) { - try b.parser.errStr(.invalid_nullability, tok, try b.parser.typeStr(qt)); + try b.parser.err(tok, .invalid_nullability, .{qt}); }, } @@ -2492,7 +2492,7 @@ pub const Builder = struct { switch (qt.base(b.parser.comp).type) { .array, .pointer => result_qt.restrict = true, else => { - try b.parser.errStr(.restrict_non_pointer, restrict_tok, try b.parser.typeStr(qt)); + try b.parser.err(restrict_tok, .restrict_non_pointer, .{qt}); }, } } @@ -2500,28 +2500,27 @@ pub const Builder = struct { } fn cannotCombine(b: Builder, source_tok: TokenIndex) !void { - const ty_str = b.type.str(b.parser.comp.langopts) orelse try b.parser.typeStr(try b.finish()); - try b.parser.errExtra(.cannot_combine_spec, source_tok, .{ .str = ty_str }); + try b.parser.err(source_tok, .cannot_combine_spec, .{try b.finish()}); } fn duplicateSpec(b: *Builder, source_tok: TokenIndex, spec: []const u8) !void { if (b.parser.comp.langopts.emulate != .clang) return b.cannotCombine(source_tok); - try b.parser.errStr(.duplicate_decl_spec, b.parser.tok_i, spec); + try b.parser.err(b.parser.tok_i, .duplicate_decl_spec, .{spec}); } pub fn combineFromTypeof(b: *Builder, new: QualType, source_tok: TokenIndex) Compilation.Error!void { - if (b.atomic_type != null) return b.parser.errStr(.cannot_combine_spec, source_tok, "_Atomic"); - if (b.typedef) return b.parser.errStr(.cannot_combine_spec, source_tok, "type-name"); - if (b.typeof) return b.parser.errStr(.cannot_combine_spec, source_tok, "typeof"); - if (b.type != .none) return b.parser.errStr(.cannot_combine_with_typeof, source_tok, @tagName(b.type)); + if (b.atomic_type != null) return b.parser.err(source_tok, .cannot_combine_spec, .{"_Atomic"}); + if (b.typedef) return b.parser.err(source_tok, .cannot_combine_spec, .{"type-name"}); + if (b.typeof) return b.parser.err(source_tok, .cannot_combine_spec, .{"typeof"}); + if (b.type != .none) return b.parser.err(source_tok, .cannot_combine_with_typeof, .{@tagName(b.type)}); b.typeof = true; b.type = .{ .other = new }; } pub fn combineAtomic(b: *Builder, base_qt: QualType, source_tok: TokenIndex) !void { - if (b.atomic_type != null) return b.parser.errStr(.cannot_combine_spec, source_tok, "_Atomic"); - if (b.typedef) return b.parser.errStr(.cannot_combine_spec, source_tok, "type-name"); - if (b.typeof) return b.parser.errStr(.cannot_combine_spec, source_tok, "typeof"); + if (b.atomic_type != null) return b.parser.err(source_tok, .cannot_combine_spec, .{"_Atomic"}); + if (b.typedef) return b.parser.err(source_tok, .cannot_combine_spec, .{"type-name"}); + if (b.typeof) return b.parser.err(source_tok, .cannot_combine_spec, .{"typeof"}); const new_spec = TypeStore.Builder.fromType(b.parser.comp, base_qt); try b.combine(new_spec, source_tok); @@ -2540,13 +2539,13 @@ pub const Builder = struct { pub fn combine(b: *Builder, new: Builder.Specifier, source_tok: TokenIndex) !void { if (b.typeof) { - return b.parser.errStr(.cannot_combine_with_typeof, source_tok, @tagName(new)); + return b.parser.err(source_tok, .cannot_combine_with_typeof, .{@tagName(new)}); } if (b.atomic_type != null) { - return b.parser.errStr(.cannot_combine_spec, source_tok, "_Atomic"); + return b.parser.err(source_tok, .cannot_combine_spec, .{"_Atomic"}); } if (b.typedef) { - return b.parser.errStr(.cannot_combine_spec, source_tok, "type-name"); + return b.parser.err(source_tok, .cannot_combine_spec, .{"type-name"}); } if (b.type == .other and b.type.other.isInvalid()) return; @@ -2557,7 +2556,7 @@ pub const Builder = struct { } if (new == .int128 and !target_util.hasInt128(b.parser.comp.target)) { - try b.parser.errStr(.type_not_supported_on_target, source_tok, "__int128"); + try b.parser.err(source_tok, .type_not_supported_on_target, .{"__int128"}); } b.type = switch (new) { diff --git a/src/aro/char_info.zig b/src/aro/char_info.zig index c2134ef..a4d5857 100644 --- a/src/aro/char_info.zig +++ b/src/aro/char_info.zig @@ -442,48 +442,48 @@ pub fn isInvisible(codepoint: u21) bool { } /// Checks for identifier characters which resemble non-identifier characters -pub fn homoglyph(codepoint: u21) ?u21 { +pub fn homoglyph(codepoint: u21) ?[]const u8 { assert(codepoint > 0x7F); return switch (codepoint) { - 0x01c3 => '!', // LATIN LETTER RETROFLEX CLICK - 0x037e => ';', // GREEK QUESTION MARK - 0x2212 => '-', // MINUS SIGN - 0x2215 => '/', // DIVISION SLASH - 0x2216 => '\\', // SET MINUS - 0x2217 => '*', // ASTERISK OPERATOR - 0x2223 => '|', // DIVIDES - 0x2227 => '^', // LOGICAL AND - 0x2236 => ':', // RATIO - 0x223c => '~', // TILDE OPERATOR - 0xa789 => ':', // MODIFIER LETTER COLON - 0xff01 => '!', // FULLWIDTH EXCLAMATION MARK - 0xff03 => '#', // FULLWIDTH NUMBER SIGN - 0xff04 => '$', // FULLWIDTH DOLLAR SIGN - 0xff05 => '%', // FULLWIDTH PERCENT SIGN - 0xff06 => '&', // FULLWIDTH AMPERSAND - 0xff08 => '(', // FULLWIDTH LEFT PARENTHESIS - 0xff09 => ')', // FULLWIDTH RIGHT PARENTHESIS - 0xff0a => '*', // FULLWIDTH ASTERISK - 0xff0b => '+', // FULLWIDTH ASTERISK - 0xff0c => ',', // FULLWIDTH COMMA - 0xff0d => '-', // FULLWIDTH HYPHEN-MINUS - 0xff0e => '.', // FULLWIDTH FULL STOP - 0xff0f => '/', // FULLWIDTH SOLIDUS - 0xff1a => ':', // FULLWIDTH COLON - 0xff1b => ';', // FULLWIDTH SEMICOLON - 0xff1c => '<', // FULLWIDTH LESS-THAN SIGN - 0xff1d => '=', // FULLWIDTH EQUALS SIGN - 0xff1e => '>', // FULLWIDTH GREATER-THAN SIGN - 0xff1f => '?', // FULLWIDTH QUESTION MARK - 0xff20 => '@', // FULLWIDTH COMMERCIAL AT - 0xff3b => '[', // FULLWIDTH LEFT SQUARE BRACKET - 0xff3c => '\\', // FULLWIDTH REVERSE SOLIDUS - 0xff3d => ']', // FULLWIDTH RIGHT SQUARE BRACKET - 0xff3e => '^', // FULLWIDTH CIRCUMFLEX ACCENT - 0xff5b => '{', // FULLWIDTH LEFT CURLY BRACKET - 0xff5c => '|', // FULLWIDTH VERTICAL LINE - 0xff5d => '}', // FULLWIDTH RIGHT CURLY BRACKET - 0xff5e => '~', // FULLWIDTH TILDE + 0x01c3 => "!", // LATIN LETTER RETROFLEX CLICK + 0x037e => ";", // GREEK QUESTION MARK + 0x2212 => "-", // MINUS SIGN + 0x2215 => "/", // DIVISION SLASH + 0x2216 => "\\", // SET MINUS + 0x2217 => "*", // ASTERISK OPERATOR + 0x2223 => "|", // DIVIDES + 0x2227 => "^", // LOGICAL AND + 0x2236 => ":", // RATIO + 0x223c => "~", // TILDE OPERATOR + 0xa789 => ":", // MODIFIER LETTER COLON + 0xff01 => "!", // FULLWIDTH EXCLAMATION MARK + 0xff03 => "#", // FULLWIDTH NUMBER SIGN + 0xff04 => "$", // FULLWIDTH DOLLAR SIGN + 0xff05 => "%", // FULLWIDTH PERCENT SIGN + 0xff06 => "&", // FULLWIDTH AMPERSAND + 0xff08 => "(", // FULLWIDTH LEFT PARENTHESIS + 0xff09 => ")", // FULLWIDTH RIGHT PARENTHESIS + 0xff0a => "*", // FULLWIDTH ASTERISK + 0xff0b => "+", // FULLWIDTH ASTERISK + 0xff0c => ",", // FULLWIDTH COMMA + 0xff0d => "-", // FULLWIDTH HYPHEN-MINUS + 0xff0e => ".", // FULLWIDTH FULL STOP + 0xff0f => "/", // FULLWIDTH SOLIDUS + 0xff1a => ":", // FULLWIDTH COLON + 0xff1b => ";", // FULLWIDTH SEMICOLON + 0xff1c => "<", // FULLWIDTH LESS-THAN SIGN + 0xff1d => "=", // FULLWIDTH EQUALS SIGN + 0xff1e => ">", // FULLWIDTH GREATER-THAN SIGN + 0xff1f => "?", // FULLWIDTH QUESTION MARK + 0xff20 => "@", // FULLWIDTH COMMERCIAL AT + 0xff3b => "[", // FULLWIDTH LEFT SQUARE BRACKET + 0xff3c => "\\", // FULLWIDTH REVERSE SOLIDUS + 0xff3d => "]", // FULLWIDTH RIGHT SQUARE BRACKET + 0xff3e => "^", // FULLWIDTH CIRCUMFLEX ACCENT + 0xff5b => "{", // FULLWIDTH LEFT CURLY BRACKET + 0xff5c => "|", // FULLWIDTH VERTICAL LINE + 0xff5d => "}", // FULLWIDTH RIGHT CURLY BRACKET + 0xff5e => "~", // FULLWIDTH TILDE else => null, }; } diff --git a/src/aro/pragmas/gcc.zig b/src/aro/pragmas/gcc.zig index 1ade61a..5165413 100644 --- a/src/aro/pragmas/gcc.zig +++ b/src/aro/pragmas/gcc.zig @@ -77,23 +77,14 @@ fn diagnosticHandler(self: *GCC, pp: *Preprocessor, start_idx: TokenIndex) Pragm .ignored, .warning, .@"error", .fatal => { const str = Pragma.pasteTokens(pp, start_idx + 1) catch |err| switch (err) { error.ExpectedStringLiteral => { - return pp.comp.addDiagnostic(.{ - .tag = .pragma_requires_string_literal, - .loc = diagnostic_tok.loc, - .extra = .{ .str = "GCC diagnostic" }, - }, pp.expansionSlice(start_idx)); + return Pragma.err(pp, start_idx, .pragma_requires_string_literal, .{"GCC diagnostic"}); }, else => |e| return e, }; if (!mem.startsWith(u8, str, "-W")) { - const next = pp.tokens.get(start_idx + 1); - return pp.comp.addDiagnostic(.{ - .tag = .malformed_warning_check, - .loc = next.loc, - .extra = .{ .str = "GCC diagnostic" }, - }, pp.expansionSlice(start_idx + 1)); + return Pragma.err(pp, start_idx + 1, .malformed_warning_check, .{"GCC diagnostic"}); } - const new_kind: Diagnostics.Kind = switch (diagnostic) { + const new_kind: Diagnostics.Message.Kind = switch (diagnostic) { .ignored => .off, .warning => .warning, .@"error" => .@"error", @@ -101,10 +92,10 @@ fn diagnosticHandler(self: *GCC, pp: *Preprocessor, start_idx: TokenIndex) Pragm else => unreachable, }; - try pp.comp.diagnostics.set(str[2..], new_kind); + try pp.diagnostics.set(str[2..], new_kind); }, - .push => try self.state_stack.append(pp.comp.gpa, pp.comp.diagnostics.state), - .pop => pp.comp.diagnostics.state = self.state_stack.pop() orelse self.original_state, + .push => try self.state_stack.append(pp.comp.gpa, pp.diagnostics.state), + .pop => pp.diagnostics.state = self.state_stack.pop() orelse self.original_state, } } @@ -113,38 +104,24 @@ fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex const directive_tok = pp.tokens.get(start_idx + 1); if (directive_tok.id == .nl) return; - const gcc_pragma = std.meta.stringToEnum(Directive, pp.expandedSlice(directive_tok)) orelse - return pp.comp.addDiagnostic(.{ - .tag = .unknown_gcc_pragma, - .loc = directive_tok.loc, - }, pp.expansionSlice(start_idx + 1)); + const gcc_pragma = std.meta.stringToEnum(Directive, pp.expandedSlice(directive_tok)) orelse { + return Pragma.err(pp, start_idx + 1, .unknown_gcc_pragma, .{}); + }; switch (gcc_pragma) { .warning, .@"error" => { const text = Pragma.pasteTokens(pp, start_idx + 2) catch |err| switch (err) { error.ExpectedStringLiteral => { - return pp.comp.addDiagnostic(.{ - .tag = .pragma_requires_string_literal, - .loc = directive_tok.loc, - .extra = .{ .str = @tagName(gcc_pragma) }, - }, pp.expansionSlice(start_idx + 1)); + return Pragma.err(pp, start_idx + 1, .pragma_requires_string_literal, .{@tagName(gcc_pragma)}); }, else => |e| return e, }; - const extra = Diagnostics.Message.Extra{ .str = try pp.comp.diagnostics.arena.allocator().dupe(u8, text) }; - const diagnostic_tag: Diagnostics.Tag = if (gcc_pragma == .warning) .pragma_warning_message else .pragma_error_message; - return pp.comp.addDiagnostic( - .{ .tag = diagnostic_tag, .loc = directive_tok.loc, .extra = extra }, - pp.expansionSlice(start_idx + 1), - ); + + return Pragma.err(pp, start_idx + 1, if (gcc_pragma == .warning) .pragma_warning_message else .pragma_error_message, .{text}); }, .diagnostic => return self.diagnosticHandler(pp, start_idx + 2) catch |err| switch (err) { error.UnknownPragma => { - const tok = pp.tokens.get(start_idx + 2); - return pp.comp.addDiagnostic(.{ - .tag = .unknown_gcc_pragma_directive, - .loc = tok.loc, - }, pp.expansionSlice(start_idx + 2)); + return Pragma.err(pp, start_idx + 2, .unknown_gcc_pragma_directive, .{}); }, else => |e| return e, }, @@ -155,17 +132,11 @@ fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex if (tok.id == .nl) break; if (!tok.id.isMacroIdentifier()) { - return pp.comp.addDiagnostic(.{ - .tag = .pragma_poison_identifier, - .loc = tok.loc, - }, pp.expansionSlice(start_idx + i)); + return Pragma.err(pp, start_idx + i, .pragma_poison_identifier, .{}); } const str = pp.expandedSlice(tok); if (pp.defines.get(str) != null) { - try pp.comp.addDiagnostic(.{ - .tag = .pragma_poison_macro, - .loc = tok.loc, - }, pp.expansionSlice(start_idx + i)); + try Pragma.err(pp, start_idx + i, .pragma_poison_macro, .{}); } try pp.poisoned_identifiers.put(str, {}); } diff --git a/src/aro/pragmas/message.zig b/src/aro/pragmas/message.zig index f981a63..025f97f 100644 --- a/src/aro/pragmas/message.zig +++ b/src/aro/pragmas/message.zig @@ -28,24 +28,25 @@ fn deinit(pragma: *Pragma, comp: *Compilation) void { } fn preprocessorHandler(_: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Pragma.Error!void { - const message_tok = pp.tokens.get(start_idx); - const message_expansion_locs = pp.expansionSlice(start_idx); - const str = Pragma.pasteTokens(pp, start_idx + 1) catch |err| switch (err) { error.ExpectedStringLiteral => { - return pp.comp.addDiagnostic(.{ - .tag = .pragma_requires_string_literal, - .loc = message_tok.loc, - .extra = .{ .str = "message" }, - }, message_expansion_locs); + return Pragma.err(pp, start_idx, .pragma_requires_string_literal, .{}); }, else => |e| return e, }; + const message_tok = pp.tokens.get(start_idx); + const message_expansion_locs = pp.expansionSlice(start_idx); const loc = if (message_expansion_locs.len != 0) message_expansion_locs[message_expansion_locs.len - 1] else message_tok.loc; - const extra = Diagnostics.Message.Extra{ .str = try pp.comp.diagnostics.arena.allocator().dupe(u8, str) }; - return pp.comp.addDiagnostic(.{ .tag = .pragma_message, .loc = loc, .extra = extra }, &.{}); + + const diagnostic: Pragma.Diagnostic = .pragma_message; + try pp.diagnostics.add(.{ + .text = str, + .kind = diagnostic.kind, + .opt = diagnostic.opt, + .location = loc.expand(pp.comp), + }); } diff --git a/src/aro/pragmas/once.zig b/src/aro/pragmas/once.zig index 6ae2521..1a2f989 100644 --- a/src/aro/pragmas/once.zig +++ b/src/aro/pragmas/once.zig @@ -43,10 +43,13 @@ fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex const name_tok = pp.tokens.get(start_idx); const next = pp.tokens.get(start_idx + 1); if (next.id != .nl) { - try pp.comp.addDiagnostic(.{ - .tag = .extra_tokens_directive_end, - .loc = name_tok.loc, - }, pp.expansionSlice(start_idx + 1)); + const diagnostic: Preprocessor.Diagnostic = .extra_tokens_directive_end; + return pp.diagnostics.addWithLocation(pp.comp, .{ + .text = diagnostic.fmt, + .kind = diagnostic.kind, + .opt = diagnostic.opt, + .location = name_tok.loc.expand(pp.comp), + }, pp.expansionSlice(start_idx + 1), true); } const seen = self.preprocess_count == pp.preprocess_count; const prev = try self.pragma_once.fetchPut(name_tok.loc.id, {}); diff --git a/src/aro/pragmas/pack.zig b/src/aro/pragmas/pack.zig index 96cfd6b..ef805b4 100644 --- a/src/aro/pragmas/pack.zig +++ b/src/aro/pragmas/pack.zig @@ -35,10 +35,7 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation var idx = start_idx + 1; const l_paren = p.pp.tokens.get(idx); if (l_paren.id != .l_paren) { - return p.comp.addDiagnostic(.{ - .tag = .pragma_pack_lparen, - .loc = l_paren.loc, - }, p.pp.expansionSlice(idx)); + return Pragma.err(p.pp, idx, .pragma_pack_lparen, .{}); } idx += 1; @@ -55,11 +52,11 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation pop, }; const action = std.meta.stringToEnum(Action, p.tokSlice(arg)) orelse { - return p.errTok(.pragma_pack_unknown_action, arg); + return Pragma.err(p.pp, arg, .pragma_pack_unknown_action, .{}); }; switch (action) { .show => { - try p.errExtra(.pragma_pack_show, arg, .{ .unsigned = p.pragma_pack orelse 8 }); + return Pragma.err(p.pp, arg, .pragma_pack_show, .{p.pragma_pack orelse 8}); }, .push, .pop => { var new_val: ?u8 = null; @@ -76,11 +73,13 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation idx += 1; const int = idx; idx += 1; - if (tok_ids[int] != .pp_num) return p.errTok(.pragma_pack_int_ident, int); + if (tok_ids[int] != .pp_num) { + return Pragma.err(p.pp, int, .pragma_pack_int_ident, .{}); + } new_val = (try packInt(p, int)) orelse return; } }, - else => return p.errTok(.pragma_pack_int_ident, next), + else => return Pragma.err(p.pp, next, .pragma_pack_int_ident, .{}), } } if (action == .push) { @@ -88,9 +87,9 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation } else { pack.pop(p, label); if (new_val != null) { - try p.errTok(.pragma_pack_undefined_pop, arg); + try Pragma.err(p.pp, arg, .pragma_pack_undefined_pop, .{}); } else if (pack.stack.items.len == 0) { - try p.errTok(.pragma_pack_empty_stack, arg); + try Pragma.err(p.pp, arg, .pragma_pack_empty_stack, .{}); } } if (new_val) |some| { @@ -116,14 +115,14 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation } if (tok_ids[idx] != .r_paren) { - return p.errTok(.pragma_pack_rparen, idx); + return Pragma.err(p.pp, idx, .pragma_pack_rparen, .{}); } } fn packInt(p: *Parser, tok_i: TokenIndex) Compilation.Error!?u8 { const res = p.parseNumberToken(tok_i) catch |err| switch (err) { error.ParsingFailed => { - try p.errTok(.pragma_pack_int, tok_i); + try Pragma.err(p.pp, tok_i, .pragma_pack_int, .{}); return null; }, else => |e| return e, @@ -132,7 +131,7 @@ fn packInt(p: *Parser, tok_i: TokenIndex) Compilation.Error!?u8 { switch (int) { 1, 2, 4, 8, 16 => return @intCast(int), else => { - try p.errTok(.pragma_pack_int, tok_i); + try Pragma.err(p.pp, tok_i, .pragma_pack_int, .{}); return null; }, } diff --git a/src/aro/text_literal.zig b/src/aro/text_literal.zig index a23cfc2..e2891cb 100644 --- a/src/aro/text_literal.zig +++ b/src/aro/text_literal.zig @@ -7,6 +7,7 @@ const Compilation = @import("Compilation.zig"); const Diagnostics = @import("Diagnostics.zig"); const Tokenizer = @import("Tokenizer.zig"); const QualType = @import("TypeStore.zig").QualType; +const Source = @import("Source.zig"); pub const Item = union(enum) { /// decoded hex or character escape @@ -19,11 +20,6 @@ pub const Item = union(enum) { utf8_text: std.unicode.Utf8View, }; -const CharDiagnostic = struct { - tag: Diagnostics.Tag, - extra: Diagnostics.Message.Extra, -}; - pub const Kind = enum { char, wide, @@ -151,27 +147,38 @@ pub const Kind = enum { } }; +pub const Ascii = struct { + val: u7, + + pub fn init(val: anytype) Ascii { + return .{ .val = @intCast(val) }; + } + + pub fn format(ctx: Ascii, w: anytype, fmt_str: []const u8) !usize { + const i = std.mem.indexOf(u8, fmt_str, "{c}").?; + try w.writeAll(fmt_str[0..i]); + + if (std.ascii.isPrint(ctx.val)) { + try w.writeByte(ctx.val); + } else { + var buf: [3]u8 = undefined; + const str = std.fmt.bufPrint(&buf, "x{x}", .{std.fmt.fmtSliceHexLower(&.{ctx.val})}) catch unreachable; + try w.writeAll(str); + } + return i; + } +}; + pub const Parser = struct { + comp: *const Compilation, literal: []const u8, i: usize = 0, kind: Kind, max_codepoint: u21, + loc: Source.Location, + expansion_locs: []const Source.Location, /// We only want to issue a max of 1 error per char literal errored: bool = false, - errors_buffer: [4]CharDiagnostic, - errors_len: usize, - comp: *const Compilation, - - pub fn init(literal: []const u8, kind: Kind, max_codepoint: u21, comp: *const Compilation) Parser { - return .{ - .literal = literal, - .comp = comp, - .kind = kind, - .max_codepoint = max_codepoint, - .errors_buffer = undefined, - .errors_len = 0, - }; - } fn prefixLen(self: *const Parser) usize { return switch (self.kind) { @@ -182,65 +189,193 @@ pub const Parser = struct { }; } - pub fn errors(p: *Parser) []CharDiagnostic { - return p.errors_buffer[0..p.errors_len]; + const Diagnostic = struct { + fmt: []const u8, + kind: Diagnostics.Message.Kind, + opt: ?Diagnostics.Option = null, + extension: bool = false, + + pub const illegal_char_encoding_error: Diagnostic = .{ + .fmt = "illegal character encoding in character literal", + .kind = .@"error", + }; + + pub const illegal_char_encoding_warning: Diagnostic = .{ + .fmt = "illegal character encoding in character literal", + .kind = .warning, + .opt = .@"invalid-source-encoding", + }; + + pub const missing_hex_escape: Diagnostic = .{ + .fmt = "\\{c} used with no following hex digits", + .kind = .@"error", + }; + + pub const escape_sequence_overflow: Diagnostic = .{ + .fmt = "escape sequence out of range", + .kind = .@"error", + }; + + pub const incomplete_universal_character: Diagnostic = .{ + .fmt = "incomplete universal character name", + .kind = .@"error", + }; + + pub const invalid_universal_character: Diagnostic = .{ + .fmt = "invalid universal character", + .kind = .@"error", + }; + + pub const char_too_large: Diagnostic = .{ + .fmt = "character too large for enclosing character literal type", + .kind = .@"error", + }; + + pub const ucn_basic_char_error: Diagnostic = .{ + .fmt = "character '{c}' cannot be specified by a universal character name", + .kind = .@"error", + }; + + pub const ucn_basic_char_warning: Diagnostic = .{ + .fmt = "specifying character '{c}' with a universal character name is incompatible with C standards before C23", + .kind = .off, + .opt = .@"pre-c23-compat", + }; + + pub const ucn_control_char_error: Diagnostic = .{ + .fmt = "universal character name refers to a control character", + .kind = .@"error", + }; + + pub const ucn_control_char_warning: Diagnostic = .{ + .fmt = "universal character name referring to a control character is incompatible with C standards before C23", + .kind = .off, + .opt = .@"pre-c23-compat", + }; + + pub const c89_ucn_in_literal: Diagnostic = .{ + .fmt = "universal character names are only valid in C99 or later", + .kind = .warning, + .opt = .unicode, + }; + + const non_standard_escape_char: Diagnostic = .{ + .fmt = "use of non-standard escape character '\\{c}'", + .kind = .off, + .extension = true, + }; + + pub const unknown_escape_sequence: Diagnostic = .{ + .fmt = "unknown escape sequence '\\{c}'", + .kind = .warning, + .opt = .@"unknown-escape-sequence", + }; + + pub const four_char_char_literal: Diagnostic = .{ + .fmt = "multi-character character constant", + .opt = .@"four-char-constants", + .kind = .off, + }; + + pub const multichar_literal_warning: Diagnostic = .{ + .fmt = "multi-character character constant", + .kind = .warning, + .opt = .multichar, + }; + + pub const invalid_multichar_literal: Diagnostic = .{ + .fmt = "{s} character literals may not contain multiple characters", + .kind = .@"error", + }; + + pub const char_lit_too_wide: Diagnostic = .{ + .fmt = "character constant too long for its type", + .kind = .warning, + }; + + // pub const wide_multichar_literal: Diagnostic = .{ + // .fmt = "extraneous characters in character constant ignored", + // .kind = .warning, + // }; + }; + + pub fn err(p: *Parser, diagnostic: Diagnostic, args: anytype) !void { + if (p.errored) return; + defer p.errored = true; + try p.warn(diagnostic, args); } - pub fn err(self: *Parser, tag: Diagnostics.Tag, extra: Diagnostics.Message.Extra) void { - if (self.errored) return; - self.errored = true; - const diagnostic: CharDiagnostic = .{ .tag = tag, .extra = extra }; - if (self.errors_len == self.errors_buffer.len) { - self.errors_buffer[self.errors_buffer.len - 1] = diagnostic; - } else { - self.errors_buffer[self.errors_len] = diagnostic; - self.errors_len += 1; - } + pub fn warn(p: *Parser, diagnostic: Diagnostic, args: anytype) !void { + if (p.errored) return; + if (p.comp.diagnostics.effectiveKind(diagnostic) == .off) return; + + var sf = std.heap.stackFallback(1024, p.comp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try formatArgs(buf.writer(), diagnostic.fmt, args); + + try p.comp.diagnostics.addWithLocation(p.comp, .{ + .kind = diagnostic.kind, + .text = buf.items, + .opt = diagnostic.opt, + .extension = diagnostic.extension, + .location = p.loc.expand(p.comp), + }, p.expansion_locs, true); } - pub fn warn(self: *Parser, tag: Diagnostics.Tag, extra: Diagnostics.Message.Extra) void { - if (self.errored) return; - if (self.errors_len < self.errors_buffer.len) { - self.errors_buffer[self.errors_len] = .{ .tag = tag, .extra = extra }; - self.errors_len += 1; + fn formatArgs(w: anytype, fmt: []const u8, args: anytype) !void { + var i: usize = 0; + inline for (std.meta.fields(@TypeOf(args))) |arg_info| { + const arg = @field(args, arg_info.name); + i += switch (@TypeOf(arg)) { + []const u8 => try Diagnostics.formatString(w, fmt[i..], arg), + Ascii => try arg.format(w, fmt[i..]), + else => switch (@typeInfo(@TypeOf(arg))) { + .int, .comptime_int => try Diagnostics.formatInt(w, fmt[i..], arg), + .pointer => try Diagnostics.formatString(w, fmt[i..], arg), + else => unreachable, + }, + }; } + try w.writeAll(fmt[i..]); } - pub fn next(self: *Parser) ?Item { - if (self.i >= self.literal.len) return null; + pub fn next(p: *Parser) !?Item { + if (p.i >= p.literal.len) return null; - const start = self.i; - if (self.literal[start] != '\\') { - self.i = mem.indexOfScalarPos(u8, self.literal, start + 1, '\\') orelse self.literal.len; - const unescaped_slice = self.literal[start..self.i]; + const start = p.i; + if (p.literal[start] != '\\') { + p.i = mem.indexOfScalarPos(u8, p.literal, start + 1, '\\') orelse p.literal.len; + const unescaped_slice = p.literal[start..p.i]; const view = std.unicode.Utf8View.init(unescaped_slice) catch { - if (self.kind != .char) { - self.err(.illegal_char_encoding_error, .{ .none = {} }); + if (p.kind != .char) { + try p.err(.illegal_char_encoding_error, .{}); return null; } - self.warn(.illegal_char_encoding_warning, .{ .none = {} }); - return .{ .improperly_encoded = self.literal[start..self.i] }; + try p.warn(.illegal_char_encoding_warning, .{}); + return .{ .improperly_encoded = p.literal[start..p.i] }; }; return .{ .utf8_text = view }; } - switch (self.literal[start + 1]) { - 'u', 'U' => return self.parseUnicodeEscape(), - else => return self.parseEscapedChar(), + switch (p.literal[start + 1]) { + 'u', 'U' => return try p.parseUnicodeEscape(), + else => return try p.parseEscapedChar(), } } - fn parseUnicodeEscape(self: *Parser) ?Item { - const start = self.i; + fn parseUnicodeEscape(p: *Parser) !?Item { + const start = p.i; - std.debug.assert(self.literal[self.i] == '\\'); + std.debug.assert(p.literal[p.i] == '\\'); - const kind = self.literal[self.i + 1]; + const kind = p.literal[p.i + 1]; std.debug.assert(kind == 'u' or kind == 'U'); - self.i += 2; - if (self.i >= self.literal.len or !std.ascii.isHex(self.literal[self.i])) { - self.err(.missing_hex_escape, .{ .ascii = @intCast(kind) }); + p.i += 2; + if (p.i >= p.literal.len or !std.ascii.isHex(p.literal[p.i])) { + try p.err(.missing_hex_escape, .{Ascii.init(kind)}); return null; } const expected_len: usize = if (kind == 'u') 4 else 8; @@ -248,66 +383,66 @@ pub const Parser = struct { var count: usize = 0; var val: u32 = 0; - for (self.literal[self.i..], 0..) |c, i| { + for (p.literal[p.i..], 0..) |c, i| { if (i == expected_len) break; - const char = std.fmt.charToDigit(c, 16) catch { - break; - }; + const char = std.fmt.charToDigit(c, 16) catch break; val, const overflow = @shlWithOverflow(val, 4); overflowed = overflowed or overflow != 0; val |= char; count += 1; } - self.i += expected_len; + p.i += expected_len; if (overflowed) { - self.err(.escape_sequence_overflow, .{ .offset = start + self.prefixLen() }); + p.loc.byte_offset += @intCast(start + p.prefixLen()); + try p.err(.escape_sequence_overflow, .{}); return null; } if (count != expected_len) { - self.err(.incomplete_universal_character, .{ .none = {} }); + try p.err(.incomplete_universal_character, .{}); return null; } if (val > std.math.maxInt(u21) or !std.unicode.utf8ValidCodepoint(@intCast(val))) { - self.err(.invalid_universal_character, .{ .offset = start + self.prefixLen() }); + p.loc.byte_offset += @intCast(start + p.prefixLen()); + try p.err(.invalid_universal_character, .{}); return null; } - if (val > self.max_codepoint) { - self.err(.char_too_large, .{ .none = {} }); + if (val > p.max_codepoint) { + try p.err(.char_too_large, .{}); return null; } if (val < 0xA0 and (val != '$' and val != '@' and val != '`')) { - const is_error = !self.comp.langopts.standard.atLeast(.c23); + const is_error = !p.comp.langopts.standard.atLeast(.c23); if (val >= 0x20 and val <= 0x7F) { if (is_error) { - self.err(.ucn_basic_char_error, .{ .ascii = @intCast(val) }); - } else { - self.warn(.ucn_basic_char_warning, .{ .ascii = @intCast(val) }); + try p.err(.ucn_basic_char_error, .{Ascii.init(val)}); + } else if (!p.comp.langopts.standard.atLeast(.c23)) { + try p.warn(.ucn_basic_char_warning, .{Ascii.init(val)}); } } else { if (is_error) { - self.err(.ucn_control_char_error, .{ .none = {} }); - } else { - self.warn(.ucn_control_char_warning, .{ .none = {} }); + try p.err(.ucn_control_char_error, .{}); + } else if (!p.comp.langopts.standard.atLeast(.c23)) { + try p.warn(.ucn_control_char_warning, .{}); } } } - self.warn(.c89_ucn_in_literal, .{ .none = {} }); + if (!p.comp.langopts.standard.atLeast(.c99)) try p.warn(.c89_ucn_in_literal, .{}); return .{ .codepoint = @intCast(val) }; } - fn parseEscapedChar(self: *Parser) Item { - self.i += 1; - const c = self.literal[self.i]; + fn parseEscapedChar(p: *Parser) !Item { + p.i += 1; + const c = p.literal[p.i]; defer if (c != 'x' and (c < '0' or c > '7')) { - self.i += 1; + p.i += 1; }; switch (c) { @@ -320,36 +455,40 @@ pub const Parser = struct { 'a' => return .{ .value = 0x07 }, 'b' => return .{ .value = 0x08 }, 'e', 'E' => { - self.warn(.non_standard_escape_char, .{ .invalid_escape = .{ .char = c, .offset = @intCast(self.i) } }); + p.loc.byte_offset += @intCast(p.i); + try p.warn(.non_standard_escape_char, .{Ascii.init(c)}); return .{ .value = 0x1B }; }, '(', '{', '[', '%' => { - self.warn(.non_standard_escape_char, .{ .invalid_escape = .{ .char = c, .offset = @intCast(self.i) } }); + p.loc.byte_offset += @intCast(p.i); + try p.warn(.non_standard_escape_char, .{Ascii.init(c)}); return .{ .value = c }; }, 'f' => return .{ .value = 0x0C }, 'v' => return .{ .value = 0x0B }, - 'x' => return .{ .value = self.parseNumberEscape(.hex) }, - '0'...'7' => return .{ .value = self.parseNumberEscape(.octal) }, + 'x' => return .{ .value = try p.parseNumberEscape(.hex) }, + '0'...'7' => return .{ .value = try p.parseNumberEscape(.octal) }, 'u', 'U' => unreachable, // handled by parseUnicodeEscape else => { - self.warn(.unknown_escape_sequence, .{ .invalid_escape = .{ .char = c, .offset = @intCast(self.i) } }); + p.loc.byte_offset += @intCast(p.i); + try p.warn(.unknown_escape_sequence, .{Ascii.init(c)}); return .{ .value = c }; }, } } - fn parseNumberEscape(self: *Parser, base: EscapeBase) u32 { + fn parseNumberEscape(p: *Parser, base: EscapeBase) !u32 { var val: u32 = 0; var count: usize = 0; var overflowed = false; - const start = self.i; - defer self.i += count; + const start = p.i; + defer p.i += count; + const slice = switch (base) { - .octal => self.literal[self.i..@min(self.literal.len, self.i + 3)], // max 3 chars + .octal => p.literal[p.i..@min(p.literal.len, p.i + 3)], // max 3 chars .hex => blk: { - self.i += 1; - break :blk self.literal[self.i..]; // skip over 'x'; could have an arbitrary number of chars + p.i += 1; + break :blk p.literal[p.i..]; // skip over 'x'; could have an arbitrary number of chars }, }; for (slice) |c| { @@ -359,13 +498,14 @@ pub const Parser = struct { val += char; count += 1; } - if (overflowed or val > self.kind.maxInt(self.comp)) { - self.err(.escape_sequence_overflow, .{ .offset = start + self.prefixLen() }); + if (overflowed or val > p.kind.maxInt(p.comp)) { + p.loc.byte_offset += @intCast(start + p.prefixLen()); + try p.err(.escape_sequence_overflow, .{}); return 0; } if (count == 0) { std.debug.assert(base == .hex); - self.err(.missing_hex_escape, .{ .ascii = 'x' }); + try p.err(.missing_hex_escape, .{Ascii.init('c')}); } return val; } diff --git a/src/aro/toolchains/Linux.zig b/src/aro/toolchains/Linux.zig index ed4dc02..2c45d80 100644 --- a/src/aro/toolchains/Linux.zig +++ b/src/aro/toolchains/Linux.zig @@ -146,7 +146,7 @@ fn getPIE(self: *const Linux, d: *const Driver) bool { fn getStaticPIE(self: *const Linux, d: *Driver) !bool { _ = self; if (d.static_pie and d.pie != null) { - try d.err("cannot specify 'nopie' along with 'static-pie'"); + try d.err("cannot specify 'nopie' along with 'static-pie'", .{}); } return d.static_pie; } @@ -198,7 +198,7 @@ pub fn buildLinkerArgs(self: *const Linux, tc: *const Toolchain, argv: *std.Arra if (target_util.ldEmulationOption(d.comp.target, null)) |emulation| { try argv.appendSlice(&.{ "-m", emulation }); } else { - try d.err("Unknown target triple"); + try d.err("Unknown target triple", .{}); return; } if (d.comp.target.cpu.arch.isRISCV()) { @@ -219,7 +219,7 @@ pub fn buildLinkerArgs(self: *const Linux, tc: *const Toolchain, argv: *std.Arra if (dynamic_linker.get()) |path| { try argv.appendSlice(&.{ "-dynamic-linker", try tc.arena.dupe(u8, path) }); } else { - try d.err("Could not find dynamic linker path"); + try d.err("Could not find dynamic linker path", .{}); } } } diff --git a/src/assembly_backend/x86_64.zig b/src/assembly_backend/x86_64.zig index 7f192e9..d0a5ef2 100644 --- a/src/assembly_backend/x86_64.zig +++ b/src/assembly_backend/x86_64.zig @@ -70,11 +70,16 @@ fn serializeFloat(comptime T: type, value: T, w: Writer) !void { pub fn todo(c: *AsmCodeGen, msg: []const u8, tok: Tree.TokenIndex) Error { const loc: Source.Location = c.tree.tokens.items(.loc)[tok]; - try c.comp.addDiagnostic(.{ - .tag = .todo, - .loc = loc, - .extra = .{ .str = msg }, - }, &.{}); + var sf = std.heap.stackFallback(1024, c.comp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try buf.writer().print("TODO: {s}", .{msg}); + try c.comp.diagnostics.add(.{ + .text = buf.items, + .kind = .@"error", + .location = loc.expand(c.comp), + }); return error.FatalError; } diff --git a/src/main.zig b/src/main.zig index 19935e6..bc4cb5c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,6 +4,7 @@ const mem = std.mem; const process = std.process; const aro = @import("aro"); const Compilation = aro.Compilation; +const Diagnostics = aro.Diagnostics; const Driver = aro.Driver; const Toolchain = aro.Toolchain; const assembly_backend = @import("assembly_backend"); @@ -38,7 +39,15 @@ pub fn main() u8 { }; defer gpa.free(aro_name); - var comp = Compilation.initDefault(gpa, std.fs.cwd()) catch |er| switch (er) { + const stderr_file = std.io.getStdErr(); + var diagnostics: Diagnostics = .{ + .output = .{ .to_file = .{ + .config = std.io.tty.detectConfig(stderr_file), + .file = stderr_file, + } }, + }; + + var comp = Compilation.initDefault(gpa, &diagnostics, std.fs.cwd()) catch |er| switch (er) { error.OutOfMemory => { std.debug.print("out of memory\n", .{}); if (fast_exit) process.exit(1); @@ -47,7 +56,7 @@ pub fn main() u8 { }; defer comp.deinit(); - var driver: Driver = .{ .comp = &comp, .aro_name = aro_name }; + var driver: Driver = .{ .comp = &comp, .aro_name = aro_name, .diagnostics = &diagnostics }; defer driver.deinit(); var toolchain: Toolchain = .{ .driver = &driver, .arena = arena, .filesystem = .{ .real = comp.cwd } }; @@ -60,11 +69,11 @@ pub fn main() u8 { return 1; }, error.FatalError => { - driver.renderErrors(); + driver.printDiagnosticsStats(); if (fast_exit) process.exit(1); return 1; }, }; if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0)); - return @intFromBool(comp.diagnostics.errors != 0); + return @intFromBool(diagnostics.errors != 0); } -- Gitee From 2c71d2c7911a0d110a7f454f8b9be1a72b52490b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 21 Apr 2025 00:38:53 +0300 Subject: [PATCH 013/117] Diagnostics: implement writing messages to file --- src/aro/Diagnostics.zig | 159 ++++++++++++++++++--------------------- src/aro/Preprocessor.zig | 3 +- 2 files changed, 75 insertions(+), 87 deletions(-) diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index 08e46e1..eaaa1ef 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -335,6 +335,7 @@ pub fn add(d: *Diagnostics, msg: Message) Compilation.Error!void { copy.kind = d.effectiveKind(msg); if (copy.kind == .off) return; try d.addMessage(copy); + if (copy.kind == .@"fatal error") return error.FatalError; } pub fn addWithLocation( @@ -397,91 +398,6 @@ pub fn addWithLocation( if (copy.kind == .@"fatal error") return error.FatalError; } -fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { - switch (msg.kind) { - .off => unreachable, - .@"error", .@"fatal error" => d.errors += 1, - .warning => d.warnings += 1, - .note => {}, - } - d.total += 1; - - switch (d.output) { - .to_file => |to_file| { - _ = to_file; - - // var line: ?[]const u8 = null; - // var end_with_splice = false; - // const width = if (msg.loc.id != .unused) blk: { - // var loc = msg.loc; - // switch (msg.tag) { - // .escape_sequence_overflow, - // .invalid_universal_character, - // => loc.byte_offset += @truncate(msg.extra.offset), - // .non_standard_escape_char, - // .unknown_escape_sequence, - // => loc.byte_offset += msg.extra.invalid_escape.offset, - // else => {}, - // } - // const source = comp.getSource(loc.id); - // var line_col = source.lineCol(loc); - // line = line_col.line; - // end_with_splice = line_col.end_with_splice; - // if (msg.tag == .backslash_newline_escape) { - // line = line_col.line[0 .. line_col.col - 1]; - // line_col.col += 1; - // line_col.width += 1; - // } - // m.location(source.path, line_col.line_no, line_col.col); - // break :blk line_col.width; - // } else 0; - - // if (prop.opt) |some| { - // if (msg.kind == .@"error" and prop.kind != .@"error") { - // m.print(" [-Werror,-W{s}]", .{@tagName(some)}); - // } else if (msg.kind != .note) { - // m.print(" [-W{s}]", .{@tagName(some)}); - // } - // } else if (prop.extension) { - // if (msg.kind == .@"error") { - // m.write(" [-Werror,-Wpedantic]"); - // } else { - // m.write(" [-Wpedantic]"); - // } - // } - - // fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void { - // const line = maybe_line orelse { - // m.write("\n"); - // m.setColor(.reset); - // return; - // }; - // const trailer = if (end_with_splice) "\\ " else ""; - // m.setColor(.reset); - // m.print("\n{s}{s}\n{s: >[3]}", .{ line, trailer, "", col }); - // m.setColor(.bold); - // m.setColor(.bright_green); - // m.write("^\n"); - // m.setColor(.reset); - }, - .to_list => |*to_list| { - const arena = to_list.arena.allocator(); - try to_list.messages.append(to_list.arena.child_allocator, .{ - .kind = msg.kind, - .text = try arena.dupe(u8, msg.text), - .location = if (msg.location) |some| .{ - .path = try arena.dupe(u8, some.path), - .line = try arena.dupe(u8, some.line), - .col = some.col, - .line_no = some.line_no, - .width = some.width, - .end_with_splice = some.end_with_splice, - } else null, - }); - }, - } -} - pub fn formatArgs(w: anytype, fmt: []const u8, args: anytype) !void { var i: usize = 0; inline for (std.meta.fields(@TypeOf(args))) |arg_info| { @@ -511,3 +427,76 @@ pub fn formatInt(w: anytype, fmt: []const u8, int: anytype) !usize { try std.fmt.formatInt(int, 10, .lower, .{}, w); return i; } + +fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { + switch (msg.kind) { + .off => unreachable, + .@"error", .@"fatal error" => d.errors += 1, + .warning => d.warnings += 1, + .note => {}, + } + d.total += 1; + + switch (d.output) { + .to_file => |to_file| { + d.writeToFile(msg, to_file.file, to_file.config) catch { + return error.FatalError; + }; + }, + .to_list => |*to_list| { + const arena = to_list.arena.allocator(); + try to_list.messages.append(to_list.arena.child_allocator, .{ + .kind = msg.kind, + .text = try arena.dupe(u8, msg.text), + .opt = msg.opt, + .extension = msg.extension, + .location = msg.location, + }); + }, + } +} + +fn writeToFile(d: *Diagnostics, msg: Message, file: std.fs.File, config: std.io.tty.Config) !void { + const w = file.writer(); + + try config.setColor(w, .bold); + if (msg.location) |loc| { + try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col }); + } + switch (msg.kind) { + .@"fatal error", .@"error" => try config.setColor(w, .bright_red), + .note => try config.setColor(w, .bright_cyan), + .warning => try config.setColor(w, .bright_magenta), + .off => unreachable, + } + try w.print("{s}: ", .{@tagName(msg.kind)}); + + try config.setColor(w, .white); + try w.writeAll(msg.text); + if (msg.opt) |some| { + if (msg.kind == .@"error" and d.state.options.get(some) == .@"error") { + try w.print(" [-Werror,-W{s}]", .{@tagName(some)}); + } else if (msg.kind != .note) { + try w.print(" [-W{s}]", .{@tagName(some)}); + } + } else if (msg.extension) { + if (msg.kind == .@"error") { + try w.writeAll(" [-Werror,-Wpedantic]"); + } else { + try w.writeAll(" [-Wpedantic]"); + } + } + + if (msg.location) |loc| { + const trailer = if (loc.end_with_splice) "\\ " else ""; + try config.setColor(w, .reset); + try w.print("\n{s}{s}\n{s: >[3]}", .{ loc.line, trailer, "", loc.col }); + try config.setColor(w, .bold); + try config.setColor(w, .bright_green); + try w.writeAll("^\n"); + try config.setColor(w, .reset); + } else { + try w.writeAll("\n"); + try config.setColor(w, .reset); + } +} diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index fb03517..0dc1ebb 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -796,7 +796,7 @@ fn err(pp: *Preprocessor, loc: anytype, diagnostic: Diagnostic, args: anytype) ! try Diagnostics.formatArgs(buf.writer(), diagnostic.fmt, args); try pp.diagnostics.addWithLocation(pp.comp, .{ - .kind = .@"fatal error", + .kind = diagnostic.kind, .text = buf.items, .opt = diagnostic.opt, .extension = diagnostic.extension, @@ -816,7 +816,6 @@ fn err(pp: *Preprocessor, loc: anytype, diagnostic: Diagnostic, args: anytype) ! Source.Location => &.{}, else => @compileError("invalid token type"), }, true); - unreachable; } fn fatal(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) Compilation.Error { -- Gitee From 0a234a7a8f3cc2a6482e102911b646a59ab2aeac Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 21 Apr 2025 00:57:58 +0300 Subject: [PATCH 014/117] update unit tests to diagnostics changes --- src/aro/Builtins.zig | 5 +++-- src/aro/Compilation.zig | 32 +++++++++++++++++++++++--------- src/aro/Diagnostics.zig | 5 ++++- src/aro/Parser.zig | 3 ++- src/aro/Preprocessor.zig | 9 ++++++--- src/aro/Tokenizer.zig | 4 ++-- src/aro/Value.zig | 4 ++-- src/aro/toolchains/Linux.zig | 4 ++-- 8 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/aro/Builtins.zig b/src/aro/Builtins.zig index d6d4594..01651c1 100644 --- a/src/aro/Builtins.zig +++ b/src/aro/Builtins.zig @@ -342,8 +342,9 @@ test Iterator { } test "All builtins" { - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); + try comp.type_store.initNamedTypes(&comp); comp.type_store.va_list = try comp.type_store.va_list.decay(&comp); @@ -367,7 +368,7 @@ test "All builtins" { test "Allocation failures" { const Test = struct { fn testOne(allocator: std.mem.Allocator) !void { - var comp = Compilation.init(allocator, std.fs.cwd()); + var comp = Compilation.init(allocator, undefined, std.fs.cwd()); defer comp.deinit(); _ = try comp.generateBuiltinMacros(.include_system_defines, null); diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 9db30cf..de5fa02 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -119,7 +119,7 @@ cwd: std.fs.Dir, pub fn init(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation { return .{ .gpa = gpa, - .diagnostic = diagnostics, + .diagnostics = diagnostics, .cwd = cwd, }; } @@ -149,7 +149,6 @@ pub fn deinit(comp: *Compilation) void { comp.gpa.free(source.splice_locs); } comp.sources.deinit(comp.gpa); - comp.diagnostics.deinit(); comp.include_dirs.deinit(comp.gpa); for (comp.system_include_dirs.items) |path| comp.gpa.free(path); comp.system_include_dirs.deinit(comp.gpa); @@ -1003,6 +1002,15 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, } = .beginning_of_file; var line: u32 = 1; + // Temporary source for getting the location for errors. + var tmp_source: Source = .{ + .path = path, + .buf = buf, + .id = source_id, + .kind = kind, + .splice_locs = undefined, + }; + for (contents) |byte| { contents[i] = byte; @@ -1020,12 +1028,13 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, i = backslash_loc; try splice_list.append(i); if (state == .trailing_ws) { + tmp_source.splice_locs = splice_list.items; const diagnostic: Diagnostic = .backslash_newline_escape; try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, - .location = (Source.Location{ .id = source_id, .byte_offset = i, .line = line }).expand(comp), + .location = tmp_source.lineCol(.{ .id = source_id, .byte_offset = i, .line = line }), }); } state = if (state == .back_slash_cr) .cr else .back_slash_cr; @@ -1047,12 +1056,13 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, try splice_list.append(i); } if (state == .trailing_ws) { + tmp_source.splice_locs = splice_list.items; const diagnostic: Diagnostic = .backslash_newline_escape; try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, - .location = (Source.Location{ .id = source_id, .byte_offset = i, .line = line }).expand(comp), + .location = tmp_source.lineCol(.{ .id = source_id, .byte_offset = i, .line = line }), }); } }, @@ -1510,19 +1520,21 @@ pub const Diagnostic = struct { test "addSourceFromReader" { const Test = struct { fn addSourceFromReader(str: []const u8, expected: []const u8, warning_count: u32, splices: []const u32) !void { - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var diagnostics: Diagnostics = .{ .output = .ignore }; + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); var buf_reader = std.io.fixedBufferStream(str); const source = try comp.addSourceFromReader(buf_reader.reader(), "path", .user); try std.testing.expectEqualStrings(expected, source.buf); - try std.testing.expectEqual(warning_count, @as(u32, @intCast(comp.diagnostics.warnings))); + try std.testing.expectEqual(warning_count, @as(u32, @intCast(diagnostics.warnings))); try std.testing.expectEqualSlices(u32, splices, source.splice_locs); } fn withAllocationFailures(allocator: std.mem.Allocator) !void { - var comp = Compilation.init(allocator, std.fs.cwd()); + var diagnostics: Diagnostics = .{ .output = .ignore }; + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n"); @@ -1564,7 +1576,8 @@ test "addSourceFromReader - exhaustive check for carriage return elimination" { const alen = alphabet.len; var buf: [alphabet.len]u8 = [1]u8{alphabet[0]} ** alen; - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var diagnostics: Diagnostics = .{ .output = .ignore }; + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); var source_count: u32 = 0; @@ -1592,7 +1605,8 @@ test "ignore BOM at beginning of file" { const Test = struct { fn run(buf: []const u8) !void { - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var diagnostics: Diagnostics = .{ .output = .ignore }; + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); var buf_reader = std.io.fixedBufferStream(buf); diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index eaaa1ef..f78dce6 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -237,9 +237,10 @@ output: union(enum) { config: std.io.tty.Config, }, to_list: struct { - messages: std.ArrayListUnmanaged(Message), + messages: std.ArrayListUnmanaged(Message) = .empty, arena: std.heap.ArenaAllocator, }, + ignore, }, state: State = .{}, /// Amount of error or fatal error messages that have been sent to `output`. @@ -252,6 +253,7 @@ macro_backtrace_limit: u32 = 6, pub fn deinit(d: *Diagnostics) void { switch (d.output) { + .ignore => {}, .to_file => {}, .to_list => |*list| { list.messages.deinit(list.arena.child_allocator); @@ -438,6 +440,7 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { d.total += 1; switch (d.output) { + .ignore => {}, .to_file => |to_file| { d.writeToFile(msg, to_file.file, to_file.config) catch { return error.FatalError; diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 43785db..b9410e6 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -9673,7 +9673,8 @@ fn genericSelection(p: *Parser) Error!?Result { } test "Node locations" { - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var diagnostics: Diagnostics = .{ .output = .ignore }; + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); const file = try comp.addSourceFromBuffer("file.c", diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 0dc1ebb..bcf764c 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -3397,7 +3397,8 @@ test "Preserve pragma tokens sometimes" { var buf = std.ArrayList(u8).init(allocator); defer buf.deinit(); - var comp = Compilation.init(allocator, std.fs.cwd()); + var diagnostics: Diagnostics = .{ .output = .ignore }; + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -3457,7 +3458,8 @@ test "destringify" { try std.testing.expectEqualStrings(destringified, pp.char_buf.items); } }; - var comp = Compilation.init(allocator, std.fs.cwd()); + var diagnostics: Diagnostics = .{ .output = .ignore }; + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); var pp = Preprocessor.init(&comp); defer pp.deinit(); @@ -3515,7 +3517,8 @@ test "Include guards" { } fn testIncludeGuard(allocator: std.mem.Allocator, comptime template: []const u8, tok_id: RawToken.Id, expected_guards: u32) !void { - var comp = Compilation.init(allocator, std.fs.cwd()); + var diagnostics: Diagnostics = .{ .output = .ignore }; + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); var pp = Preprocessor.init(&comp); defer pp.deinit(); diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 1250f90..2122f59 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -2216,7 +2216,7 @@ test "C23 keywords" { test "Tokenizer fuzz test" { const Context = struct { fn testOne(_: @This(), input_bytes: []const u8) anyerror!void { - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes); @@ -2238,7 +2238,7 @@ test "Tokenizer fuzz test" { } fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, standard: ?LangOpts.Standard) !void { - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); if (standard) |provided| { comp.langopts.standard = provided; diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 2f0cc78..e1494a2 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -76,7 +76,7 @@ test "minUnsignedBits" { } }; - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = try std.zig.system.resolveTargetQuery(target_query); @@ -111,7 +111,7 @@ test "minSignedBits" { } }; - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = try std.zig.system.resolveTargetQuery(target_query); diff --git a/src/aro/toolchains/Linux.zig b/src/aro/toolchains/Linux.zig index 2c45d80..2777f02 100644 --- a/src/aro/toolchains/Linux.zig +++ b/src/aro/toolchains/Linux.zig @@ -425,7 +425,7 @@ test Linux { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); comp.environment = .{ .path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", @@ -437,7 +437,7 @@ test Linux { comp.target = try std.zig.system.resolveTargetQuery(target_query); comp.langopts.setEmulatedCompiler(.gcc); - var driver: Driver = .{ .comp = &comp }; + var driver: Driver = .{ .comp = &comp, .diagnostics = undefined }; defer driver.deinit(); driver.raw_target_triple = raw_triple; -- Gitee From 46ba5266f0b205e5ee66fc2693b3b041e9372dc6 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 21 Apr 2025 14:53:39 +0300 Subject: [PATCH 015/117] update integration tests to diagnostics changes --- src/aro/Attribute.zig | 72 +++++-------- src/aro/Compilation.zig | 51 ++++----- src/aro/Diagnostics.zig | 27 +++-- src/aro/Parser.zig | 160 +++++++++++++++++++---------- src/aro/Parser/Diagnostic.zig | 39 ++++--- src/aro/Preprocessor.zig | 6 +- src/aro/TypeStore.zig | 7 +- src/aro/pragmas/message.zig | 11 +- src/aro/text_literal.zig | 34 ++++-- test/cases/assignment.c | 2 +- test/cases/atomic.c | 2 +- test/cases/casts.c | 2 +- test/cases/initializers.c | 2 +- test/cases/statement expressions.c | 10 +- test/runner.zig | 104 +++++++------------ 15 files changed, 289 insertions(+), 240 deletions(-) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index b38082b..7f1a4e6 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -85,29 +85,6 @@ pub const Iterator = struct { } }; -pub const ArgumentType = enum { - string, - identifier, - int, - alignment, - float, - complex_float, - expression, - nullptr_t, - - pub fn toString(self: ArgumentType) []const u8 { - return switch (self) { - .string => "a string", - .identifier => "an identifier", - .int, .alignment => "an integer constant", - .nullptr_t => "nullptr", - .float => "a floating point number", - .complex_float => "a complex floating point number", - .expression => "an expression", - }; - } -}; - /// number of required arguments pub fn requiredArgCount(attr: Tag) u32 { switch (attr) { @@ -285,13 +262,34 @@ fn diagnoseField( node: Tree.Node, p: *Parser, ) !bool { + const string = "a string"; + const identifier = "an identifier"; + const int = "an integer constant"; + const alignment = "an integer constant"; + const nullptr_t = "nullptr"; + const float = "a floating point number"; + const complex_float = "a complex floating point number"; + const expression = "an expression"; + + const expected: []const u8 = switch (Wanted) { + Value => string, + Identifier => identifier, + u32 => int, + Alignment => alignment, + CallingConvention => identifier, + else => switch (@typeInfo(Wanted)) { + .@"enum" => if (Wanted.opts.enum_kind == .string) string else identifier, + else => unreachable, + }, + }; + if (res.val.opt_ref == .none) { if (Wanted == Identifier and node == .decl_ref_expr) { @field(@field(arguments, decl.name), field.name) = .{ .tok = node.decl_ref_expr.name_tok }; return false; } - try p.err(arg_start, .attribute_arg_invalid, .{ expectedArgType(Wanted), "expression" }); + try p.err(arg_start, .attribute_arg_invalid, .{ expected, expression }); return true; } const key = p.comp.interner.get(res.val.ref()); @@ -334,31 +332,17 @@ fn diagnoseField( else => {}, } - try p.err(arg_start, .attribute_arg_invalid, .{ expectedArgType(Wanted), switch (key) { - .int => "int", - .bytes => "string", - .float => "float", - .complex => "complex_float", - .null => "nullptr_t", + try p.err(arg_start, .attribute_arg_invalid, .{ expected, switch (key) { + .int => int, + .bytes => string, + .float => float, + .complex => complex_float, + .null => nullptr_t, else => unreachable, } }); return true; } -fn expectedArgType(comptime Expected: type) []const u8 { - return switch (Expected) { - Value => "string", - Identifier => "identifier", - u32 => "int", - Alignment => "alignment", - CallingConvention => "identifier", - else => switch (@typeInfo(Expected)) { - .@"enum" => if (Expected.opts.enum_kind == .string) "string" else "identifier", - else => unreachable, - }, - }; -} - pub fn diagnose(attr: Tag, arguments: *Arguments, arg_idx: u32, res: Parser.Result, arg_start: TokenIndex, node: Tree.Node, p: *Parser) !bool { switch (attr) { inline else => |tag| { diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index de5fa02..8bd7a5c 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -1002,15 +1002,6 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, } = .beginning_of_file; var line: u32 = 1; - // Temporary source for getting the location for errors. - var tmp_source: Source = .{ - .path = path, - .buf = buf, - .id = source_id, - .kind = kind, - .splice_locs = undefined, - }; - for (contents) |byte| { contents[i] = byte; @@ -1028,14 +1019,7 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, i = backslash_loc; try splice_list.append(i); if (state == .trailing_ws) { - tmp_source.splice_locs = splice_list.items; - const diagnostic: Diagnostic = .backslash_newline_escape; - try comp.diagnostics.add(.{ - .text = diagnostic.fmt, - .kind = diagnostic.kind, - .opt = diagnostic.opt, - .location = tmp_source.lineCol(.{ .id = source_id, .byte_offset = i, .line = line }), - }); + try comp.addNewlineEscapeError(path, buf, splice_list.items, i, line); } state = if (state == .back_slash_cr) .cr else .back_slash_cr; }, @@ -1056,14 +1040,7 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, try splice_list.append(i); } if (state == .trailing_ws) { - tmp_source.splice_locs = splice_list.items; - const diagnostic: Diagnostic = .backslash_newline_escape; - try comp.diagnostics.add(.{ - .text = diagnostic.fmt, - .kind = diagnostic.kind, - .opt = diagnostic.opt, - .location = tmp_source.lineCol(.{ .id = source_id, .byte_offset = i, .line = line }), - }); + try comp.addNewlineEscapeError(path, buf, splice_list.items, i, line); } }, .bom1, .bom2 => break, @@ -1131,6 +1108,30 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, return source; } +fn addNewlineEscapeError(comp: *Compilation, path: []const u8, buf: []const u8, splice_locs: []const u32, byte_offset: u32, line: u32) !void { + // Temporary source for getting the location for errors. + var tmp_source: Source = .{ + .path = path, + .buf = buf, + .id = undefined, + .kind = undefined, + .splice_locs = splice_locs, + }; + + const diagnostic: Diagnostic = .backslash_newline_escape; + var loc = tmp_source.lineCol(.{ .id = undefined, .byte_offset = byte_offset, .line = line }); + loc.line = loc.line[0 .. loc.line.len - 1]; + loc.width += 1; + loc.col += 1; + + try comp.diagnostics.add(.{ + .text = diagnostic.fmt, + .kind = diagnostic.kind, + .opt = diagnostic.opt, + .location = loc, + }); +} + /// Caller retains ownership of `path` and `buf`. /// Dupes the source buffer; if it is acceptable to modify the source buffer and possibly resize /// the allocation, please use `addSourceFromOwnedBuffer` diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index f78dce6..e6d7694 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -240,6 +240,7 @@ output: union(enum) { messages: std.ArrayListUnmanaged(Message) = .empty, arena: std.heap.ArenaAllocator, }, + to_buffer: std.ArrayList(u8), ignore, }, state: State = .{}, @@ -259,6 +260,7 @@ pub fn deinit(d: *Diagnostics) void { list.messages.deinit(list.arena.child_allocator); list.arena.deinit(); }, + .to_buffer => |*buf| buf.deinit(), } } @@ -417,17 +419,19 @@ pub fn formatArgs(w: anytype, fmt: []const u8, args: anytype) !void { } pub fn formatString(w: anytype, fmt: []const u8, str: []const u8) !usize { - const i = std.mem.indexOf(u8, fmt, "{s}").?; + const template = "{s}"; + const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); try w.writeAll(str); - return i; + return i + template.len; } pub fn formatInt(w: anytype, fmt: []const u8, int: anytype) !usize { - const i = std.mem.indexOf(u8, fmt, "{d}").?; + const template = "{d}"; + const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); try std.fmt.formatInt(int, 10, .lower, .{}, w); - return i; + return i + template.len; } fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { @@ -442,13 +446,13 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { switch (d.output) { .ignore => {}, .to_file => |to_file| { - d.writeToFile(msg, to_file.file, to_file.config) catch { + d.writeToWriter(msg, to_file.file.writer(), to_file.config) catch { return error.FatalError; }; }, - .to_list => |*to_list| { - const arena = to_list.arena.allocator(); - try to_list.messages.append(to_list.arena.child_allocator, .{ + .to_list => |*list| { + const arena = list.arena.allocator(); + try list.messages.append(list.arena.child_allocator, .{ .kind = msg.kind, .text = try arena.dupe(u8, msg.text), .opt = msg.opt, @@ -456,12 +460,13 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { .location = msg.location, }); }, + .to_buffer => |*buf| { + d.writeToWriter(msg, buf.writer(), .no_color) catch return error.OutOfMemory; + }, } } -fn writeToFile(d: *Diagnostics, msg: Message, file: std.fs.File, config: std.io.tty.Config) !void { - const w = file.writer(); - +fn writeToWriter(d: *Diagnostics, msg: Message, w: anytype, config: std.io.tty.Config) !void { try config.setColor(w, .bold); if (msg.location) |loc| { try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col }); diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index b9410e6..ce0e9e3 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -421,6 +421,8 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) if (p.extension_suppressed) { if (diagnostic.extension and diagnostic.kind == .off) return; } + if (diagnostic.suppress_version) |some| if (p.comp.langopts.standard.atLeast(some)) return; + if (diagnostic.suppress_unless_version) |some| if (!p.comp.langopts.standard.atLeast(some)) return; if (p.diagnostics.effectiveKind(diagnostic) == .off) return; var sf = std.heap.stackFallback(1024, p.gpa); @@ -428,12 +430,21 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) defer buf.deinit(); try p.formatArgs(buf.writer(), diagnostic.fmt, args); + + const tok = p.pp.tokens.get(tok_i); + var loc = tok.loc; + if (tok_i != 0 and tok.id == .eof) { + // if the token is EOF, point at the end of the previous token instead + const prev = p.pp.tokens.get(tok_i - 1); + loc = prev.loc; + loc.byte_offset += @intCast(p.tokSlice(tok_i - 1).len); + } try p.diagnostics.addWithLocation(p.comp, .{ .kind = diagnostic.kind, .text = buf.items, .opt = diagnostic.opt, .extension = diagnostic.extension, - .location = p.pp.tokens.items(.loc)[tok_i].expand(p.comp), + .location = loc.expand(p.comp), }, p.pp.expansionSlice(tok_i), true); } @@ -453,6 +464,9 @@ fn formatArgs(p: *Parser, w: anytype, fmt: []const u8, args: anytype) !void { .val = arg.val, .qt = arg.qt, }), + Codepoint => try arg.format(w, fmt[i..]), + Normalized => try arg.format(w, fmt[i..]), + Escaped => try arg.format(w, fmt[i..]), else => switch (@typeInfo(@TypeOf(arg))) { .int, .comptime_int => try Diagnostics.formatInt(w, fmt[i..], arg), .pointer => try Diagnostics.formatString(w, fmt[i..], arg), @@ -464,21 +478,24 @@ fn formatArgs(p: *Parser, w: anytype, fmt: []const u8, args: anytype) !void { } fn formatTokenId(w: anytype, fmt: []const u8, tok_id: Tree.Token.Id) !usize { - const i = std.mem.indexOf(u8, fmt, "{tok_id}").?; + const template = "{tok_id}"; + const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); try w.writeAll(tok_id.symbol()); - return i; + return i + template.len; } fn formatQualType(p: *Parser, w: anytype, fmt: []const u8, qt: QualType) !usize { - const i = std.mem.indexOf(u8, fmt, "{qt}").?; + const template = "{qt}"; + const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); try qt.print(p.comp, w); - return i; + return i + template.len; } fn formatResult(p: *Parser, w: anytype, fmt: []const u8, res: Result) !usize { - const i = std.mem.indexOf(u8, fmt, "{value}").?; + const template = "{value}"; + const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); switch (res.val.opt_ref) { @@ -493,7 +510,7 @@ fn formatResult(p: *Parser, w: anytype, fmt: []const u8, res: Result) !usize { }, } - return i; + return i + template.len; } const Normalized = struct { @@ -503,8 +520,9 @@ const Normalized = struct { return .{ .str = str }; } - pub fn format(w: anytype, fmt_str: []const u8, ctx: Normalized) !usize { - const i = std.mem.indexOf(u8, fmt_str, "{normalized}").?; + pub fn format(ctx: Normalized, w: anytype, fmt_str: []const u8) !usize { + const template = "{normalized}"; + const i = std.mem.indexOf(u8, fmt_str, template).?; try w.writeAll(fmt_str[0..i]); var it: std.unicode.Utf8Iterator = .{ .bytes = ctx.str, @@ -527,7 +545,7 @@ const Normalized = struct { }, w); } } - return i; + return i + template.len; } }; @@ -538,11 +556,28 @@ const Codepoint = struct { return .{ .codepoint = codepoint }; } - pub fn write(w: anytype, fmt_str: []const u8, ctx: Codepoint) !usize { - const i = std.mem.indexOf(u8, fmt_str, "{codepoint}").?; + pub fn format(ctx: Codepoint, w: anytype, fmt_str: []const u8) !usize { + const template = "{codepoint}"; + const i = std.mem.indexOf(u8, fmt_str, template).?; try w.writeAll(fmt_str[0..i]); try w.print("{X:0>4}", .{ctx.codepoint}); - return i; + return i + template.len; + } +}; + +const Escaped = struct { + str: []const u8, + + fn init(str: []const u8) Escaped { + return .{ .str = str }; + } + + pub fn format(ctx: Escaped, w: anytype, fmt_str: []const u8) !usize { + const template = "{s}"; + const i = std.mem.indexOf(u8, fmt_str, template).?; + try w.writeAll(fmt_str[0..i]); + try w.print("{}", .{std.zig.fmtEscapes(ctx.str)}); + return i + template.len; } }; @@ -561,9 +596,19 @@ pub fn removeNull(p: *Parser, str: Value) !Value { return Value.intern(p.comp, .{ .bytes = p.strings.items[strings_top..] }); } -pub fn errValueChanged(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, res: Result, old_res: Result, int_qt: QualType) !void { +pub fn errValueChanged(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, res: Result, old_val: Value, int_qt: QualType) !void { const zero_str = if (res.val.isZero(p.comp)) "non-zero " else ""; - try p.err(tok_i, diagnostic, .{ res.qt, int_qt, zero_str, old_res, res }); + const old_res: Result = .{ + .node = undefined, + .val = old_val, + .qt = res.qt, + }; + const new_res: Result = .{ + .node = undefined, + .val = res.val, + .qt = int_qt, + }; + try p.err(tok_i, diagnostic, .{ res.qt, int_qt, zero_str, old_res, new_res }); } fn checkDeprecatedUnavailable(p: *Parser, ty: QualType, usage_tok: TokenIndex, decl_tok: TokenIndex) !void { @@ -588,8 +633,8 @@ fn checkDeprecatedUnavailable(p: *Parser, ty: QualType, usage_tok: TokenIndex, d fn errDeprecated(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, msg: ?Value) Compilation.Error!void { const colon_str: []const u8 = if (msg != null) ": " else ""; - const msg_str = std.zig.fmtEscapes(if (msg) |m| p.comp.interner.get(m.ref()).bytes else ""); - return p.err(tok_i, diagnostic, .{ p.tokSlice(tok_i), colon_str, msg_str }); + const msg_str: []const u8 = if (msg) |m| p.comp.interner.get(m.ref()).bytes else ""; + return p.err(tok_i, diagnostic, .{ p.tokSlice(tok_i), colon_str, Escaped.init(msg_str) }); } fn addNode(p: *Parser, node: Tree.Node) Allocator.Error!Node.Index { @@ -1981,7 +2026,7 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize, decl_no init_d.d.qt = some.qt.withQualifiers(init_d.d.qt); } else { if (init_d.d.qt.isC23Auto()) { - try p.err(name, .c23_auto_requires_initializer, .{p.tokSlice(name)}); + try p.err(name, .c23_auto_requires_initializer, .{}); } else { try p.err(name, .auto_type_requires_initializer, .{p.tokSlice(name)}); } @@ -2011,7 +2056,7 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize, decl_no switch (init_type) { .array => |array_ty| if (array_ty.len == .incomplete) { // TODO properly check this after finishing parsing - try p.err(name, .tentative_array, .{init_d.d.qt}); + try p.err(name, .tentative_array, .{}); break :incomplete; }, .@"struct", .@"union" => |record_ty| { @@ -2219,11 +2264,13 @@ fn getAnonymousName(p: *Parser, kind_tok: TokenIndex) !StringId { else => "record field", }; - const str = std.fmt.allocPrint( - p.gpa, // TODO horrible + var arena = p.comp.type_store.anon_name_arena.promote(p.gpa); + defer p.comp.type_store.anon_name_arena = arena.state; + const str = try std.fmt.allocPrint( + arena.allocator(), "(anonymous {s} at {s}:{d}:{d})", .{ kind_str, source.path, line_col.line_no, line_col.col }, - ) catch unreachable; + ); return p.comp.internString(str); } @@ -2970,8 +3017,14 @@ const Enumerator = struct { e.qt = larger; } else { const signed = e.qt.signedness(p.comp) == .signed; - const bit_size: u8 = @intCast(e.qt.bitSizeof(p.comp) - @intFromBool(signed)); - try p.err(tok, .enum_not_representable, .{std.math.pow(usize, bit_size, 2)}); + const bit_size = e.qt.bitSizeof(p.comp) - @intFromBool(signed); + try p.err(tok, .enum_not_representable, .{switch (bit_size) { + 63 => "9223372036854775808", + 64 => "18446744073709551616", + 127 => "170141183460469231731687303715884105728", + 128 => "340282366920938463463374607431768211456", + else => unreachable, + }}); e.qt = .ulong_long; } _ = try e.val.add(old_val, .one, e.qt, p.comp); @@ -4616,7 +4669,7 @@ fn gnuAsmStmt(p: *Parser, quals: Tree.GNUAssemblyQualifiers, asm_tok: TokenIndex } if (!quals.goto and (p.tok_ids[p.tok_i] != .r_paren or ate_extra_colon)) { - try p.err(p.tok_i, .expected_token, .{ p.tok_ids[p.tok_i], Tree.Token.Id.r_paren }); + try p.err(p.tok_i, .expected_token, .{ Tree.Token.Id.r_paren, p.tok_ids[p.tok_i] }); return error.ParsingFailed; } @@ -4650,7 +4703,7 @@ fn gnuAsmStmt(p: *Parser, quals: Tree.GNUAssemblyQualifiers, asm_tok: TokenIndex if (p.eatToken(.comma) == null) break; } } else if (quals.goto) { - try p.err(p.tok_i, .expected_token, .{ p.tok_ids[p.tok_i], .colon }); + try p.err(p.tok_i, .expected_token, .{ Token.Id.colon, p.tok_ids[p.tok_i] }); return error.ParsingFailed; } @@ -6064,9 +6117,9 @@ pub const Result = struct { res.qt = bool_qt; try res.implicitCast(p, .int_to_bool, tok); } else if (src_sk.isFloat()) { - const old_res = res.*; + const old_val = res.val; const value_change_kind = try res.val.floatToInt(bool_qt, p.comp); - try res.floatToIntWarning(p, bool_qt, old_res, value_change_kind, tok); + try res.floatToIntWarning(p, bool_qt, old_val, value_change_kind, tok); if (!src_sk.isReal()) { res.qt = res.qt.toReal(p.comp); try res.implicitCast(p, .complex_float_to_real, tok); @@ -6102,9 +6155,9 @@ pub const Result = struct { try res.implicitCast(p, .real_to_complex_int, tok); } } else if (res.qt.isFloat(p.comp)) { - const old_res = res.*; + const old_val = res.val; const value_change_kind = try res.val.floatToInt(int_qt, p.comp); - try res.floatToIntWarning(p, int_qt, old_res, value_change_kind, tok); + try res.floatToIntWarning(p, int_qt, old_val, value_change_kind, tok); if (src_sk.isReal() and dest_sk.isReal()) { res.qt = int_qt; try res.implicitCast(p, .float_to_int, tok); @@ -6123,11 +6176,11 @@ pub const Result = struct { try res.implicitCast(p, .complex_float_to_complex_int, tok); } } else if (!res.qt.eql(int_qt, p.comp)) { - const old_res = res.*; + const old_val = res.val; const value_change_kind = try res.val.intCast(int_qt, p.comp); switch (value_change_kind) { .none => {}, - .truncated => try p.errValueChanged(tok, .int_value_changed, res.*, old_res, int_qt), + .truncated => try p.errValueChanged(tok, .int_value_changed, res.*, old_val, int_qt), .sign_changed => try p.err(tok, .sign_conversion, .{ res.qt, int_qt }), } @@ -6158,7 +6211,7 @@ pub const Result = struct { res: Result, p: *Parser, int_qt: QualType, - old_res: Result, + old_val: Value, change_kind: Value.FloatToIntChangeKind, tok: TokenIndex, ) !void { @@ -6166,8 +6219,8 @@ pub const Result = struct { .none => return p.err(tok, .float_to_int, .{ res.qt, int_qt }), .out_of_range => return p.err(tok, .float_out_of_range, .{ res.qt, int_qt }), .overflow => return p.err(tok, .float_overflow_conversion, .{ res.qt, int_qt }), - .nonzero_to_zero => return p.errValueChanged(tok, .float_zero_conversion, res, old_res, int_qt), - .value_changed => return p.errValueChanged(tok, .float_value_changed, res, old_res, int_qt), + .nonzero_to_zero => return p.errValueChanged(tok, .float_zero_conversion, res, old_val, int_qt), + .value_changed => return p.errValueChanged(tok, .float_value_changed, res, old_val, int_qt), } } @@ -6758,13 +6811,13 @@ pub const Result = struct { } const different_sign_only = src_child.sameRankDifferentSign(dest_child, p.comp); - try p.err(tok, switch (c) { - .assign => if (different_sign_only) .incompatible_ptr_assign_sign else .incompatible_ptr_assign, - .init => if (different_sign_only) .incompatible_ptr_init_sign else .incompatible_ptr_init, - .ret => if (different_sign_only) .incompatible_return_sign else .incompatible_return, - .arg => if (different_sign_only) .incompatible_ptr_arg_sign else .incompatible_ptr_arg, + switch (c) { + .assign => try p.err(tok, if (different_sign_only) .incompatible_ptr_assign_sign else .incompatible_ptr_assign, .{ dest_qt, src_original_qt }), + .init => try p.err(tok, if (different_sign_only) .incompatible_ptr_init_sign else .incompatible_ptr_init, .{ dest_qt, src_original_qt }), + .ret => try p.err(tok, if (different_sign_only) .incompatible_return_sign else .incompatible_return, .{ src_original_qt, dest_qt }), + .arg => try p.err(tok, if (different_sign_only) .incompatible_ptr_arg_sign else .incompatible_ptr_arg, .{ src_original_qt, dest_qt }), .test_coerce => return error.CoercionFailed, - }, .{ dest_qt, src_original_qt }); + } try c.note(p); res.qt = dest_unqual; @@ -6807,13 +6860,13 @@ pub const Result = struct { return error.ParsingFailed; } - try p.err(tok, switch (c) { - .assign => .incompatible_assign, - .init => .incompatible_init, - .ret => .incompatible_return, - .arg => .incompatible_arg, + switch (c) { + .assign => try p.err(tok, .incompatible_assign, .{ dest_unqual, res.qt }), + .init => try p.err(tok, .incompatible_init, .{ dest_unqual, res.qt }), + .ret => try p.err(tok, .incompatible_return, .{ res.qt, dest_unqual }), + .arg => try p.err(tok, .incompatible_arg, .{ res.qt, dest_unqual }), .test_coerce => return error.CoercionFailed, - }, .{ dest_unqual, res.qt }); + } try c.note(p); } }; @@ -7232,10 +7285,10 @@ fn shiftExpr(p: *Parser) Error!?Result { if (try lhs.adjustTypes(tok, &rhs, p, .integer)) { if (rhs.val.compare(.lt, .zero, p.comp)) { - try p.err(tok, .negative_shift_count, .{rhs}); + try p.err(tok, .negative_shift_count, .{}); } if (rhs.val.compare(.gte, try Value.int(lhs.qt.bitSizeof(p.comp), p.comp), p.comp)) { - try p.err(tok, .too_big_shift_count, .{rhs}); + try p.err(tok, .too_big_shift_count, .{}); } if (tag == .shl_expr) { if (try lhs.val.shl(lhs.val, rhs.val, lhs.qt, p.comp) and @@ -7725,8 +7778,8 @@ fn unExpr(p: *Parser) Error!?Result { if (lhs_qt.hasAttribute(p.comp, .@"packed")) { const record_ty = lhs_qt.getRecord(p.comp).?; try p.err(orig_tok_i, .packed_member_address, .{ - record_ty.name.lookup(p.comp), record_ty.fields[access.member_index].name.lookup(p.comp), + record_ty.name.lookup(p.comp), }); } } @@ -7947,7 +8000,7 @@ fn unExpr(p: *Parser) Error!?Result { switch (base_type.type) { .void => try p.err(tok, .pointer_arith_void, .{"sizeof"}), .pointer => |pointer_ty| if (pointer_ty.decayed) |decayed_qt| { - try p.err(tok, .sizeof_array_arg, .{.{ res.qt, decayed_qt }}); + try p.err(tok, .sizeof_array_arg, .{ res.qt, decayed_qt }); }, else => {}, } @@ -8528,7 +8581,7 @@ fn callExpr(p: *Parser, lhs: Result) Error!Result { if (call_expr.paramCountOverride()) |expected| { if (expected != arg_count) { - try p.err(first_after, .expected_arguments, .{ params_len, arg_count }); + try p.err(first_after, .expected_arguments, .{ expected, arg_count }); } } else switch (func_kind) { .normal => if (params_len != arg_count) { @@ -8955,6 +9008,7 @@ fn stringLiteral(p: *Parser) Error!Result { .max_codepoint = 0x10ffff, .loc = p.pp.tokens.items(.loc)[p.tok_i], .expansion_locs = p.pp.expansionSlice(p.tok_i), + .incorrect_encoding_is_error = count > 1, }; try p.strings.ensureUnusedCapacity((slice.len + 1) * @intFromEnum(char_width)); // +1 for null terminator @@ -8993,7 +9047,6 @@ fn stringLiteral(p: *Parser) Error!Result { }, .improperly_encoded => |bytes| { if (count > 1) { - try char_literal_parser.err(.illegal_char_encoding_error, .{}); return error.ParsingFailed; } p.strings.appendSliceAssumeCapacity(bytes); @@ -9089,7 +9142,6 @@ fn charLiteral(p: *Parser) Error!?Result { .loc = p.pp.tokens.items(.loc)[p.tok_i], .expansion_locs = p.pp.expansionSlice(p.tok_i), }; - // .init(slice, char_kind, max_codepoint, p.comp); const max_chars_expected = 4; var stack_fallback = std.heap.stackFallback(max_chars_expected * @sizeOf(u32), p.comp.gpa); diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 595afbb..c443c50 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -115,6 +115,11 @@ pub const cannot_combine_spec: Diagnostic = .{ .kind = .@"error", }; +pub const cannot_combine_spec_qt: Diagnostic = .{ + .fmt = "cannot combine with previous '{qt}' specifier", + .kind = .@"error", +}; + pub const cannot_combine_with_typeof: Diagnostic = .{ .fmt = "'{s} typeof' is invalid", .kind = .@"error", @@ -325,7 +330,7 @@ pub const case_not_in_switch: Diagnostic = .{ }; pub const duplicate_switch_case: Diagnostic = .{ - .fmt = "duplicate case value '{s}'", + .fmt = "duplicate case value '{value}'", .kind = .@"error", }; @@ -582,17 +587,17 @@ pub const generic_no_match: Diagnostic = .{ }; pub const must_use_struct: Diagnostic = .{ - .fmt = "must use 'struct' tag to refer to type '{qt}'", + .fmt = "must use 'struct' tag to refer to type '{s}'", .kind = .@"error", }; pub const must_use_union: Diagnostic = .{ - .fmt = "must use 'union' tag to refer to type '{qt}'", + .fmt = "must use 'union' tag to refer to type '{s}'", .kind = .@"error", }; pub const must_use_enum: Diagnostic = .{ - .fmt = "must use 'enum' tag to refer to type '{qt}'", + .fmt = "must use 'enum' tag to refer to type '{s}'", .kind = .@"error", }; @@ -686,13 +691,13 @@ pub const invalid_subscript: Diagnostic = .{ }; pub const array_after: Diagnostic = .{ - .fmt = "array index {s} is past the end of the array", + .fmt = "array index {value} is past the end of the array", .opt = .@"array-bounds", .kind = .warning, }; pub const array_before: Diagnostic = .{ - .fmt = "array index {s} is before the beginning of the array", + .fmt = "array index {value} is before the beginning of the array", .opt = .@"array-bounds", .kind = .warning, }; @@ -846,12 +851,12 @@ pub const minimum_alignment: Diagnostic = .{ }; pub const maximum_alignment: Diagnostic = .{ - .fmt = "requested alignment of {s} is too large", + .fmt = "requested alignment of {value} is too large", .kind = .@"error", }; pub const negative_alignment: Diagnostic = .{ - .fmt = "requested negative alignment of {s} is invalid", + .fmt = "requested negative alignment of {value} is invalid", .kind = .@"error", }; @@ -1048,12 +1053,12 @@ pub const invalid_array_designator: Diagnostic = .{ }; pub const negative_array_designator: Diagnostic = .{ - .fmt = "array designator value {s} is negative", + .fmt = "array designator value {value} is negative", .kind = .@"error", }; pub const oob_array_designator: Diagnostic = .{ - .fmt = "array designator index {s} exceeds array bounds", + .fmt = "array designator index {value} exceeds array bounds", .kind = .@"error", }; @@ -1175,7 +1180,7 @@ pub const non_int_bitfield: Diagnostic = .{ }; pub const negative_bitwidth: Diagnostic = .{ - .fmt = "bit-field has negative width ({s})", + .fmt = "bit-field has negative width ({value})", .kind = .@"error", }; @@ -1434,7 +1439,7 @@ pub const attribute_requires_identifier: Diagnostic = .{ }; pub const attribute_int_out_of_range: Diagnostic = .{ - .fmt = "attribute value '{s}' out of range", + .fmt = "attribute value '{value}' out of range", .kind = .@"error", }; @@ -1912,7 +1917,7 @@ pub const array_address_to_bool: Diagnostic = .{ }; pub const string_literal_to_bool: Diagnostic = .{ - .fmt = "implicit conversion turns string literal into bool: '{qt}' to '{qt}", + .fmt = "implicit conversion turns string literal into bool: '{qt}' to '{qt}'", .kind = .off, .opt = .@"string-conversion", }; @@ -1979,7 +1984,7 @@ pub const auto_type_from_bitfield: Diagnostic = .{ }; pub const auto_type_array: Diagnostic = .{ - .fmt = "'{qt}' declared as array of '__auto_type'", + .fmt = "'{s}' declared as array of '__auto_type'", .kind = .@"error", }; @@ -1999,7 +2004,7 @@ pub const tentative_definition_incomplete: Diagnostic = .{ }; pub const forward_declaration_here: Diagnostic = .{ - .fmt = "forward declaration of '{s}'", + .fmt = "forward declaration of '{qt}'", .kind = .note, }; @@ -2120,7 +2125,7 @@ pub const invalid_compound_literal_storage_class: Diagnostic = .{ }; pub const identifier_not_normalized: Diagnostic = .{ - .fmt = "'{s}' is not in NFC", + .fmt = "'{normalized}' is not in NFC", .kind = .warning, .opt = .normalized, }; @@ -2146,7 +2151,7 @@ pub const c23_auto_with_init_list: Diagnostic = .{ }; pub const c23_auto_array: Diagnostic = .{ - .fmt = "'{qt}' declared as array of 'auto'", + .fmt = "'{s}' declared as array of 'auto'", .kind = .@"error", }; diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index bcf764c..cb3f7ac 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -2468,7 +2468,11 @@ fn defineMacro(pp: *Preprocessor, define_tok: RawToken, name_tok: RawToken, macr if (gop.found_existing and !gop.value_ptr.eql(macro, pp)) { const loc: Source.Location = .{ .id = name_tok.source, .byte_offset = name_tok.start, .line = name_tok.line }; const prev_total = pp.diagnostics.total; - try pp.err(loc, if (gop.value_ptr.is_builtin) .builtin_macro_redefined else .macro_redefined, .{name_str}); + if (gop.value_ptr.is_builtin) { + try pp.err(loc, .builtin_macro_redefined, .{}); + } else { + try pp.err(loc, .macro_redefined, .{name_str}); + } if (!gop.value_ptr.is_builtin and pp.diagnostics.total != prev_total) { try pp.err(gop.value_ptr.loc, .previous_definition, .{}); diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 0630669..52b00c6 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -1660,6 +1660,7 @@ pub const Type = union(enum) { types: std.MultiArrayList(Repr) = .empty, extra: std.ArrayListUnmanaged(u32) = .empty, attributes: std.ArrayListUnmanaged(Attribute) = .empty, +anon_name_arena: std.heap.ArenaAllocator.State = .{}, wchar: QualType = .invalid, uint_least16_t: QualType = .invalid, @@ -1682,6 +1683,7 @@ pub fn deinit(ts: *TypeStore, gpa: std.mem.Allocator) void { ts.types.deinit(gpa); ts.extra.deinit(gpa); ts.attributes.deinit(gpa); + ts.anon_name_arena.promote(gpa).deinit(); ts.* = undefined; } @@ -2500,7 +2502,10 @@ pub const Builder = struct { } fn cannotCombine(b: Builder, source_tok: TokenIndex) !void { - try b.parser.err(source_tok, .cannot_combine_spec, .{try b.finish()}); + if (b.type.str(b.parser.comp.langopts)) |some| { + return b.parser.err(source_tok, .cannot_combine_spec, .{some}); + } + try b.parser.err(source_tok, .cannot_combine_spec_qt, .{try b.finish()}); } fn duplicateSpec(b: *Builder, source_tok: TokenIndex, spec: []const u8) !void { diff --git a/src/aro/pragmas/message.zig b/src/aro/pragmas/message.zig index 025f97f..d24defc 100644 --- a/src/aro/pragmas/message.zig +++ b/src/aro/pragmas/message.zig @@ -30,7 +30,7 @@ fn deinit(pragma: *Pragma, comp: *Compilation) void { fn preprocessorHandler(_: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Pragma.Error!void { const str = Pragma.pasteTokens(pp, start_idx + 1) catch |err| switch (err) { error.ExpectedStringLiteral => { - return Pragma.err(pp, start_idx, .pragma_requires_string_literal, .{}); + return Pragma.err(pp, start_idx, .pragma_requires_string_literal, .{"message"}); }, else => |e| return e, }; @@ -43,8 +43,15 @@ fn preprocessorHandler(_: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Pra message_tok.loc; const diagnostic: Pragma.Diagnostic = .pragma_message; + + var sf = std.heap.stackFallback(1024, pp.gpa); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); + + try Diagnostics.formatArgs(buf.writer(), diagnostic.fmt, .{str}); + try pp.diagnostics.add(.{ - .text = str, + .text = buf.items, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = loc.expand(pp.comp), diff --git a/src/aro/text_literal.zig b/src/aro/text_literal.zig index e2891cb..fae8c73 100644 --- a/src/aro/text_literal.zig +++ b/src/aro/text_literal.zig @@ -155,7 +155,8 @@ pub const Ascii = struct { } pub fn format(ctx: Ascii, w: anytype, fmt_str: []const u8) !usize { - const i = std.mem.indexOf(u8, fmt_str, "{c}").?; + const template = "{c}"; + const i = std.mem.indexOf(u8, fmt_str, template).?; try w.writeAll(fmt_str[0..i]); if (std.ascii.isPrint(ctx.val)) { @@ -165,7 +166,7 @@ pub const Ascii = struct { const str = std.fmt.bufPrint(&buf, "x{x}", .{std.fmt.fmtSliceHexLower(&.{ctx.val})}) catch unreachable; try w.writeAll(str); } - return i; + return i + template.len; } }; @@ -176,9 +177,14 @@ pub const Parser = struct { kind: Kind, max_codepoint: u21, loc: Source.Location, + /// Offset added to `loc.byte_offset` when emitting an error. + offset: u32 = 0, expansion_locs: []const Source.Location, /// We only want to issue a max of 1 error per char literal errored: bool = false, + /// Makes incorrect encoding always an error. + /// Used when concatenating string literals. + incorrect_encoding_is_error: bool = false, fn prefixLen(self: *const Parser) usize { return switch (self.kind) { @@ -300,12 +306,14 @@ pub const Parser = struct { }; pub fn err(p: *Parser, diagnostic: Diagnostic, args: anytype) !void { + defer p.offset = 0; if (p.errored) return; defer p.errored = true; try p.warn(diagnostic, args); } pub fn warn(p: *Parser, diagnostic: Diagnostic, args: anytype) !void { + defer p.offset = 0; if (p.errored) return; if (p.comp.diagnostics.effectiveKind(diagnostic) == .off) return; @@ -315,12 +323,14 @@ pub const Parser = struct { try formatArgs(buf.writer(), diagnostic.fmt, args); + var offset_location = p.loc; + offset_location.byte_offset += p.offset; try p.comp.diagnostics.addWithLocation(p.comp, .{ .kind = diagnostic.kind, .text = buf.items, .opt = diagnostic.opt, .extension = diagnostic.extension, - .location = p.loc.expand(p.comp), + .location = offset_location.expand(p.comp), }, p.expansion_locs, true); } @@ -350,6 +360,10 @@ pub const Parser = struct { const unescaped_slice = p.literal[start..p.i]; const view = std.unicode.Utf8View.init(unescaped_slice) catch { + if (p.incorrect_encoding_is_error) { + try p.warn(.illegal_char_encoding_error, .{}); + return .{ .improperly_encoded = p.literal[start..p.i] }; + } if (p.kind != .char) { try p.err(.illegal_char_encoding_error, .{}); return null; @@ -396,7 +410,7 @@ pub const Parser = struct { p.i += expected_len; if (overflowed) { - p.loc.byte_offset += @intCast(start + p.prefixLen()); + p.offset += @intCast(start + p.prefixLen()); try p.err(.escape_sequence_overflow, .{}); return null; } @@ -407,7 +421,7 @@ pub const Parser = struct { } if (val > std.math.maxInt(u21) or !std.unicode.utf8ValidCodepoint(@intCast(val))) { - p.loc.byte_offset += @intCast(start + p.prefixLen()); + p.offset += @intCast(start + p.prefixLen()); try p.err(.invalid_universal_character, .{}); return null; } @@ -455,12 +469,12 @@ pub const Parser = struct { 'a' => return .{ .value = 0x07 }, 'b' => return .{ .value = 0x08 }, 'e', 'E' => { - p.loc.byte_offset += @intCast(p.i); + p.offset += @intCast(p.i); try p.warn(.non_standard_escape_char, .{Ascii.init(c)}); return .{ .value = 0x1B }; }, '(', '{', '[', '%' => { - p.loc.byte_offset += @intCast(p.i); + p.offset += @intCast(p.i); try p.warn(.non_standard_escape_char, .{Ascii.init(c)}); return .{ .value = c }; }, @@ -470,7 +484,7 @@ pub const Parser = struct { '0'...'7' => return .{ .value = try p.parseNumberEscape(.octal) }, 'u', 'U' => unreachable, // handled by parseUnicodeEscape else => { - p.loc.byte_offset += @intCast(p.i); + p.offset += @intCast(p.i); try p.warn(.unknown_escape_sequence, .{Ascii.init(c)}); return .{ .value = c }; }, @@ -499,13 +513,13 @@ pub const Parser = struct { count += 1; } if (overflowed or val > p.kind.maxInt(p.comp)) { - p.loc.byte_offset += @intCast(start + p.prefixLen()); + p.offset += @intCast(start + p.prefixLen()); try p.err(.escape_sequence_overflow, .{}); return 0; } if (count == 0) { std.debug.assert(base == .hex); - try p.err(.missing_hex_escape, .{Ascii.init('c')}); + try p.err(.missing_hex_escape, .{Ascii.init('x')}); } return val; } diff --git a/test/cases/assignment.c b/test/cases/assignment.c index 7b5fc0c..340da11 100644 --- a/test/cases/assignment.c +++ b/test/cases/assignment.c @@ -118,7 +118,7 @@ void constant_sign_conversion(void) { "assignment.c:61:9: error: expression is not assignable" \ "assignment.c:72:12: error: variable has incomplete type 'enum E'" \ "assignment.c:79:7: error: expression is not assignable" \ - "assignment.c:86:7: warning: incompatible pointer types assigning to 'unsigned int *' from incompatible type 'int *' converts between pointers to integer types with different sign [-Wpointer-sign]" \ + "assignment.c:86:7: warning: incompatible pointer types assigning to 'unsigned int *' from incompatible type 'int *' converts between pointers to integer types with different sign [-Wpointer-sign]" \ "assignment.c:90:23: warning: implicit conversion from 'int' to 'unsigned char' changes value from 1000 to 232 [-Wconstant-conversion]" \ "assignment.c:94:22: warning: implicit conversion changes signedness: 'int' to 'unsigned int' [-Wsign-conversion]" \ diff --git a/test/cases/atomic.c b/test/cases/atomic.c index c6bedea..68fcfa9 100644 --- a/test/cases/atomic.c +++ b/test/cases/atomic.c @@ -42,7 +42,7 @@ void test_member_access() { #define TESTS_SKIPPED 6 #define EXPECTED_ERRORS \ - "atomic.c:2:1: error: _Atomic cannot be applied to qualified type 'int'" \ + "atomic.c:2:1: error: _Atomic cannot be applied to qualified type 'const int'" \ "atomic.c:3:1: error: _Atomic cannot be applied to array type 'int [2]'" \ "atomic.c:4:1: error: _Atomic cannot be applied to function type 'int (int)'" \ "atomic.c:5:1: error: _Atomic cannot be applied to incomplete type 'struct A'" \ diff --git a/test/cases/casts.c b/test/cases/casts.c index 1e34eb6..16f953a 100644 --- a/test/cases/casts.c +++ b/test/cases/casts.c @@ -22,7 +22,7 @@ void foo(void) { #define EXPECTED_ERRORS "casts.c:5:5: error: cannot cast to non arithmetic or pointer type 'struct Foo'" \ "casts.c:6:5: error: pointer cannot be cast to type 'float'" \ "casts.c:7:5: error: operand of type 'float' cannot be cast to a pointer type" \ - "casts.c:8:5: warning: cast to type 'int' will not preserve qualifiers [-Wcast-qualifiers]" \ + "casts.c:8:5: warning: cast to type 'const int' will not preserve qualifiers [-Wcast-qualifiers]" \ "casts.c:9:23: error: cannot cast to non arithmetic or pointer type 'typeof(const int [])'" \ "casts.c:10:13: warning: cast to smaller integer type 'char' from 'char *' [-Wpointer-to-int-cast]" \ "casts.c:11:13: error: pointer cannot be cast to type 'float'" \ diff --git a/test/cases/initializers.c b/test/cases/initializers.c index e81c1ca..c93c1de 100644 --- a/test/cases/initializers.c +++ b/test/cases/initializers.c @@ -159,7 +159,7 @@ void array_members(void) { "initializers.c:6:17: warning: excess elements in scalar initializer [-Wexcess-initializers]" \ "initializers.c:7:30: warning: excess elements in string initializer [-Wexcess-initializers]" \ "initializers.c:8:23: warning: initializer-string for char array is too long [-Wexcess-initializers]" \ - "initializers.c:9:16: error: cannot initialize type ('int [2]' with array of type 'int [3]')" \ + "initializers.c:9:16: error: cannot initialize type 'int [2]' with array of type 'int [3]'" \ "initializers.c:10:15: error: cannot initialize array of type 'int []' with array of type 'char [4]'" \ "initializers.c:11:15: error: array designator used for non-array type 'int'" \ "initializers.c:12:19: error: array designator value -1 is negative" \ diff --git a/test/cases/statement expressions.c b/test/cases/statement expressions.c index 072a63a..304aa9f 100644 --- a/test/cases/statement expressions.c +++ b/test/cases/statement expressions.c @@ -25,12 +25,16 @@ void self_referential_initializer(void) { }); } -#define TESTS_SKIPPED 1 +#define TESTS_SKIPPED 5 #define EXPECTED_ERRORS "statement expressions.c:3:10: warning: use of GNU statement expression extension [-Wgnu-statement-expression]" \ "statement expressions.c:3:10: error: statement expression not allowed at file scope" \ "statement expressions.c:10:13: error: initializing 'int' from incompatible type 'void'" \ - "statement expressions.c:11:5: warning: expression result unused [-Wunused-value]" \ + /* "statement expressions.c:11:5: warning: expression result unused [-Wunused-value]" */ \ + "statement expressions.c:11:8: warning: expression result unused [-Wunused-value]" /* TODO wrong */ \ "statement expressions.c:12:8: warning: expression result unused [-Wunused-value]" \ - "statement expressions.c:12:5: warning: expression result unused [-Wunused-value]" \ + "statement expressions.c:12:11: warning: expression result unused [-Wunused-value]"/* TODO should be the one below */ \ + /* "statement expressions.c:12:5: warning: expression result unused [-Wunused-value]" */ \ + "statement expressions.c:16:9: warning: expression result unused [-Wunused-value]" /* TODO wrong */ \ "statement expressions.c:18:5: error: use of undeclared identifier 'z'" \ + "statement expressions.c:19:7: warning: expression result unused [-Wunused-value]" /* TODO wrong */\ diff --git a/test/runner.zig b/test/runner.zig index 7d02ee8..9779266 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -31,7 +31,7 @@ fn addCommandLineArgs(comp: *aro.Compilation, file: aro.Source, macro_buf: anyty var it = std.mem.tokenizeScalar(u8, file.buf[0..nl], ' '); while (it.next()) |some| try test_args.append(some); - var driver: aro.Driver = .{ .comp = comp }; + var driver: aro.Driver = .{ .comp = comp, .diagnostics = comp.diagnostics }; defer driver.deinit(); _ = try driver.parseArgs(std.io.null_writer, macro_buf, test_args.items); only_preprocess = driver.only_preprocess; @@ -167,8 +167,13 @@ pub fn main() !void { .estimated_total_items = cases.items.len, }); + var diagnostics: aro.Diagnostics = .{ + .output = .{ .to_buffer = .init(gpa) }, + }; + defer diagnostics.deinit(); + // prepare compiler - var initial_comp = aro.Compilation.init(gpa, std.fs.cwd()); + var initial_comp = aro.Compilation.init(gpa, &diagnostics, std.fs.cwd()); defer initial_comp.deinit(); const cases_include_dir = try std.fs.path.join(gpa, &.{ args[1], "include" }); @@ -196,6 +201,12 @@ pub fn main() !void { var fail_count: u32 = 0; var skip_count: u32 = 0; next_test: for (cases.items) |path| { + const diag_buf = diagnostics.output.to_buffer; + diagnostics = .{ + .output = .{ .to_buffer = diag_buf }, + }; + diagnostics.output.to_buffer.items.len = 0; + var comp = initial_comp; defer { // preserve some values @@ -225,7 +236,6 @@ pub fn main() !void { const builtin_macros = try comp.generateBuiltinMacrosFromPath(system_defines, file.path); - comp.diagnostics.errors = 0; var pp = aro.Preprocessor.init(&comp); defer pp.deinit(); if (only_preprocess) { @@ -271,7 +281,9 @@ pub fn main() !void { continue; } } else { - aro.Diagnostics.render(&comp, std.io.tty.detectConfig(std.io.getStdErr())); + const stderr = std.io.getStdErr(); + try stderr.writeAll(pp.diagnostics.output.to_buffer.items); + if (comp.diagnostics.errors != 0) { std.debug.print("in case {s}\n", .{case}); fail_count += 1; @@ -364,7 +376,7 @@ pub fn main() !void { var actual = StmtTypeDumper.init(gpa); defer actual.deinit(gpa); - try actual.dump(&tree, test_fn.body, gpa); + try actual.dump(&tree, test_fn.body); var i: usize = 0; for (types.tokens) |str| { @@ -409,13 +421,9 @@ pub fn main() !void { continue; } - if (pp.defines.contains("NO_ERROR_VALIDATION")) { - var m = MsgWriter.init(pp.comp.gpa); - defer m.deinit(); - aro.Diagnostics.renderMessages(pp.comp, &m); - continue; - } - aro.Diagnostics.render(&comp, std.io.tty.detectConfig(std.io.getStdErr())); + if (pp.defines.contains("NO_ERROR_VALIDATION")) continue; + const stderr = std.io.getStdErr(); + try stderr.writeAll(pp.diagnostics.output.to_buffer.items); if (pp.defines.get("EXPECTED_OUTPUT")) |macro| blk: { if (comp.diagnostics.errors != 0) break :blk; @@ -508,10 +516,8 @@ pub fn main() !void { fn checkExpectedErrors(pp: *aro.Preprocessor, buf: *std.ArrayList(u8)) !?bool { const macro = pp.defines.get("EXPECTED_ERRORS") orelse return null; - const expected_count = pp.comp.diagnostics.list.items.len; - var m = MsgWriter.init(pp.comp.gpa); - defer m.deinit(); - aro.Diagnostics.renderMessages(pp.comp, &m); + const expected_count = pp.diagnostics.total; + const errors = pp.diagnostics.output.to_buffer.items; if (macro.is_func) { std.debug.print("invalid EXPECTED_ERRORS {}\n", .{macro}); @@ -535,7 +541,7 @@ fn checkExpectedErrors(pp: *aro.Preprocessor, buf: *std.ArrayList(u8)) !?bool { try buf.append('\n'); const expected_error = buf.items[start..]; - const index = std.mem.indexOf(u8, m.buf.items, expected_error); + const index = std.mem.indexOf(u8, errors, expected_error); if (index == null) { std.debug.print( \\ @@ -546,7 +552,7 @@ fn checkExpectedErrors(pp: *aro.Preprocessor, buf: *std.ArrayList(u8)) !?bool { \\{s} \\ \\ - , .{ expected_error, m.buf.items }); + , .{ expected_error, errors }); return false; } } @@ -556,7 +562,7 @@ fn checkExpectedErrors(pp: *aro.Preprocessor, buf: *std.ArrayList(u8)) !?bool { \\EXPECTED_ERRORS missing errors, expected {d} found {d}, \\ , .{ count, expected_count }); - var it = std.mem.tokenizeScalar(u8, m.buf.items, '\n'); + var it = std.mem.tokenizeScalar(u8, errors, '\n'); while (it.next()) |msg| { const start = std.mem.indexOf(u8, msg, ".c:") orelse continue; const index = std.mem.indexOf(u8, buf.items, msg[start..]); @@ -577,46 +583,6 @@ fn checkExpectedErrors(pp: *aro.Preprocessor, buf: *std.ArrayList(u8)) !?bool { return true; } -const MsgWriter = struct { - buf: std.ArrayList(u8), - - fn init(gpa: std.mem.Allocator) MsgWriter { - return .{ - .buf = std.ArrayList(u8).init(gpa), - }; - } - - fn deinit(m: *MsgWriter) void { - m.buf.deinit(); - } - - pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void { - m.buf.writer().print(fmt, args) catch {}; - } - - pub fn write(m: *MsgWriter, msg: []const u8) void { - m.buf.writer().writeAll(msg) catch {}; - } - - pub fn location(m: *MsgWriter, path: []const u8, line: u32, col: u32) void { - m.print("{s}:{d}:{d}: ", .{ path, line, col }); - } - - pub fn start(m: *MsgWriter, kind: aro.Diagnostics.Kind) void { - m.print("{s}: ", .{@tagName(kind)}); - } - - pub fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void { - const line = maybe_line orelse { - m.write("\n"); - return; - }; - const trailer = if (end_with_splice) "\\ " else ""; - m.print("\n{s}{s}\n", .{ line, trailer }); - m.print("{s: >[1]}^\n", .{ "", col }); - } -}; - const StmtTypeDumper = struct { types: std.ArrayList([]const u8), @@ -633,22 +599,24 @@ const StmtTypeDumper = struct { }; } - fn dumpNode(self: *StmtTypeDumper, tree: *const aro.Tree, node: Node.Index, m: *MsgWriter) AllocatorError!void { + fn dumpNode(self: *StmtTypeDumper, tree: *const aro.Tree, node: Node.Index) AllocatorError!void { const maybe_ret = node.get(tree); if (maybe_ret == .return_stmt and maybe_ret.return_stmt.operand == .implicit) return; - node.qt(tree).dump(tree.comp, m.buf.writer()) catch {}; - const owned = try m.buf.toOwnedSlice(); - errdefer m.buf.allocator.free(owned); + + var buf = std.ArrayList(u8).init(self.types.allocator); + defer buf.deinit(); + + node.qt(tree).dump(tree.comp, buf.writer()) catch {}; + const owned = try buf.toOwnedSlice(); + errdefer buf.allocator.free(owned); + try self.types.append(owned); } - fn dump(self: *StmtTypeDumper, tree: *const aro.Tree, body: Node.Index, allocator: std.mem.Allocator) AllocatorError!void { - var m = MsgWriter.init(allocator); - defer m.deinit(); - + fn dump(self: *StmtTypeDumper, tree: *const aro.Tree, body: Node.Index) AllocatorError!void { const compound = body.get(tree).compound_stmt; for (compound.body) |stmt| { - try self.dumpNode(tree, stmt, &m); + try self.dumpNode(tree, stmt); } } }; -- Gitee From 405255beb5b0f88d4ad4c0dde3dc08b60ed6fd0e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 21 Apr 2025 15:02:49 +0300 Subject: [PATCH 016/117] update record tests to diagnostics changes --- test/record_runner.zig | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/test/record_runner.zig b/test/record_runner.zig index 73c8fb1..c681707 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -224,7 +224,14 @@ fn runTestCases(allocator: std.mem.Allocator, test_dir: []const u8, wg: *std.Thr fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, stats: *Stats) !void { const path = test_case.path; - var comp = aro.Compilation.init(alloc, std.fs.cwd()); + var diagnostics: aro.Diagnostics = .{ + .output = .{ .to_list = .{ + .arena = .init(alloc), + } }, + }; + defer diagnostics.deinit(); + + var comp = aro.Compilation.init(alloc, &diagnostics, std.fs.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -300,7 +307,7 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase tree.dump(.no_color, std.io.null_writer) catch {}; if (test_single_target) { - aro.Diagnostics.render(&comp, std.io.tty.detectConfig(std.io.getStdErr())); + printDiagnostics(&diagnostics); return; } @@ -315,19 +322,17 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase const expected = compErr.get(buf[0..buf_strm.pos]) orelse ExpectedFailure{}; - if (comp.diagnostics.list.items.len == 0 and expected.any()) { + if (diagnostics.total == 0 and expected.any()) { std.debug.print("\nTest Passed when failures expected:\n\texpected:{any}\n", .{expected}); + stats.recordResult(.fail); } else { - var m = aro.Diagnostics.defaultMsgWriter(std.io.tty.detectConfig(std.io.getStdErr())); - defer m.deinit(); var actual = ExpectedFailure{}; - for (comp.diagnostics.list.items) |msg| { + for (diagnostics.output.to_list.messages.items) |msg| { switch (msg.kind) { .@"fatal error", .@"error" => {}, else => continue, } - const src = comp.getSource(msg.loc.id); - const line = src.lineCol(msg.loc).line; + const line = msg.location.?.line; if (std.ascii.indexOfIgnoreCase(line, "_Static_assert") != null) { if (std.ascii.indexOfIgnoreCase(line, "_extra_") != null) { actual.extra = true; @@ -343,10 +348,8 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase } } if (!expected.eql(actual)) { - m.print("\nexp:{any}\nact:{any}\n", .{ expected, actual }); - for (comp.diagnostics.list.items) |msg| { - aro.Diagnostics.renderMessage(&comp, &m, msg); - } + std.debug.print("\nexp:{any}\nact:{any}\n", .{ expected, actual }); + printDiagnostics(&diagnostics); stats.recordResult(.fail); } else if (actual.any()) { stats.recordResult(.skip); @@ -356,6 +359,18 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase } } +fn printDiagnostics(diagnostics: *aro.Diagnostics) void { + for (diagnostics.output.to_list.messages.items) |msg| { + if (msg.location) |loc| { + std.debug.print("{s}:{d}:{d}: {s}: {s}\n{s}\n", .{ + loc.path, loc.line_no, loc.col, @tagName(msg.kind), msg.text, loc.line, + }); + } else { + std.debug.print("{s}: {s}\n", .{ @tagName(msg.kind), msg.text }); + } + } +} + /// Get Zig std.Target from string in the arch-cpu-os-abi format. fn getTarget(zig_target_string: []const u8) !std.Target { var buf: [128]u8 = undefined; -- Gitee From 6211f260b924c33b566367fe505021a2887489f3 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 21 Apr 2025 18:36:19 +0300 Subject: [PATCH 017/117] Toolchain: fix path join with sysroot leading to a relative path --- src/aro/toolchains/Linux.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aro/toolchains/Linux.zig b/src/aro/toolchains/Linux.zig index 2777f02..8007239 100644 --- a/src/aro/toolchains/Linux.zig +++ b/src/aro/toolchains/Linux.zig @@ -389,7 +389,7 @@ pub fn defineSystemIncludes(self: *const Linux, tc: *const Toolchain) !void { if (tc.driver.nostdlibinc) return; const sysroot = tc.getSysroot(); - const local_include = try std.fmt.allocPrint(comp.gpa, "{s}{s}", .{ sysroot, "/usr/local/include" }); + const local_include = try std.fs.path.join(comp.gpa, &.{ sysroot, "/usr/local/include" }); defer comp.gpa.free(local_include); try comp.addSystemIncludeDir(local_include); @@ -400,7 +400,7 @@ pub fn defineSystemIncludes(self: *const Linux, tc: *const Toolchain) !void { } if (getMultiarchTriple(target)) |triple| { - const joined = try std.fs.path.join(comp.gpa, &.{ sysroot, "usr", "include", triple }); + const joined = try std.fs.path.join(comp.gpa, &.{ sysroot, "/usr/include", triple }); defer comp.gpa.free(joined); if (tc.filesystem.exists(joined)) { try comp.addSystemIncludeDir(joined); -- Gitee From b5d0c619ad38094eabd1c31f1935a1ecf0e76ea6 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 23 Apr 2025 12:39:46 +0300 Subject: [PATCH 018/117] mark some diagnostics as extensions --- src/aro/Compilation.zig | 4 ++++ src/aro/Parser.zig | 2 +- src/aro/Parser/Diagnostic.zig | 30 ++++++++++++++---------- src/aro/Pragma.zig | 4 ++++ src/aro/Preprocessor.zig | 6 ++++- src/aro/Preprocessor/Diagnostic.zig | 16 ++++++++++++- src/aro/Tree.zig | 2 ++ test/cases/containers.c | 2 +- test/cases/invalid types.c | 2 +- test/cases/unterminated string literal.c | 9 +++---- 10 files changed, 54 insertions(+), 23 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 8bd7a5c..ffbaae0 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -1379,6 +1379,7 @@ pub fn findInclude( .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, + .extension = diagnostic.extension, .location = (Source.Location{ .id = includer_token.source, .byte_offset = includer_token.start, @@ -1493,6 +1494,7 @@ pub const Diagnostic = struct { fmt: []const u8, kind: Diagnostics.Message.Kind, opt: ?Diagnostics.Option = null, + extension: bool = false, pub const invalid_source_epoch: Diagnostic = .{ .fmt = "environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799", @@ -1509,12 +1511,14 @@ pub const Diagnostic = struct { .fmt = "#include resolved using non-portable Microsoft search rules as: {s}", .kind = .warning, .opt = .@"microsoft-include", + .extension = true, }; pub const ctrl_z_eof: Diagnostic = .{ .fmt = "treating Ctrl-Z as end-of-file is a Microsoft extension", .kind = .off, .opt = .@"microsoft-end-of-file", + .extension = true, }; }; diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index ce0e9e3..71ff26a 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -8982,7 +8982,7 @@ fn stringLiteral(p: *Parser) Error!Result { return error.ParsingFailed; }; if (string_kind == .unterminated) { - try p.err(string_end, .unterminated_string_literal_error, .{}); + // Diagnostic issued in preprocessor. p.tok_i = string_end + 1; return error.ParsingFailed; } diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index c443c50..3d11f41 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -88,6 +88,7 @@ pub const param_not_declared: Diagnostic = .{ .fmt = "parameter '{s}' was not declared, defaults to 'int'", .opt = .@"implicit-int", .kind = .warning, + .extension = true, }; pub const multiple_storage_class: Diagnostic = .{ @@ -150,6 +151,7 @@ pub const missing_declaration: Diagnostic = .{ .fmt = "declaration does not declare anything", .opt = .@"missing-declaration", .kind = .warning, + .extension = true, }; pub const func_not_in_root: Diagnostic = .{ @@ -432,7 +434,7 @@ pub const qualifier_non_outermost_array: Diagnostic = .{ }; pub const array_overflow: Diagnostic = .{ - .fmt = "The pointer incremented by {value} refers past the last possible element in {d}-bit address space containing {d}-bit ({d}-byte) elements (max possible {d} elements)", + .fmt = "the pointer incremented by {value} refers past the last possible element in {d}-bit address space containing {d}-bit ({d}-byte) elements (max possible {d} elements)", .opt = .@"array-bounds", .kind = .warning, }; @@ -625,12 +627,14 @@ pub const comparison_ptr_int: Diagnostic = .{ .fmt = "comparison between pointer and integer ('{qt}' and '{qt}')", .kind = .warning, .opt = .@"pointer-integer-compare", + .extension = true, }; pub const comparison_distinct_ptr: Diagnostic = .{ .fmt = "comparison of distinct pointer types ('{qt}' and '{qt}')", .kind = .warning, .opt = .@"compare-distinct-pointer-types", + .extension = true, }; pub const incompatible_pointers: Diagnostic = .{ @@ -879,6 +883,7 @@ pub const pointer_mismatch: Diagnostic = .{ .fmt = "pointer type mismatch ('{qt}' and '{qt}')", .opt = .@"pointer-type-mismatch", .kind = .warning, + .extension = true, }; pub const static_assert_not_constant: Diagnostic = .{ @@ -988,6 +993,7 @@ pub const str_init_too_long: Diagnostic = .{ .fmt = "initializer-string for char array is too long", .opt = .@"excess-initializers", .kind = .warning, + .extension = true, }; pub const arr_init_too_long: Diagnostic = .{ @@ -1164,6 +1170,7 @@ pub const empty_translation_unit: Diagnostic = .{ .fmt = "ISO C requires a translation unit to contain at least one declaration", .opt = .@"empty-translation-unit", .kind = .off, + .extension = true, }; pub const omitting_parameter_name: Diagnostic = .{ @@ -1203,6 +1210,7 @@ pub const implicitly_unsigned_literal: Diagnostic = .{ .fmt = "integer literal is too large to be represented in a signed integer type, interpreting as unsigned", .opt = .@"implicitly-unsigned-literal", .kind = .warning, + .extension = true, }; pub const invalid_preproc_operator: Diagnostic = .{ @@ -1280,6 +1288,7 @@ pub const too_many_scalar_init_braces: Diagnostic = .{ .fmt = "too many braces around scalar initializer", .opt = .@"many-braces-around-scalar-init", .kind = .warning, + .extension = true, }; // pub const uninitialized_in_own_init: Diagnostic = .{ @@ -1310,6 +1319,7 @@ pub const gnu_imaginary_constant: Diagnostic = .{ pub const plain_complex: Diagnostic = .{ .fmt = "plain '_Complex' requires a type specifier; assuming '_Complex double'", .kind = .warning, + .extension = true, }; pub const complex_int: Diagnostic = .{ @@ -1624,12 +1634,14 @@ pub const enum_not_representable: Diagnostic = .{ .fmt = "incremented enumerator value {s} is not representable in the largest integer type", .kind = .warning, .opt = .@"enum-too-large", + .extension = true, }; pub const enum_too_large: Diagnostic = .{ .fmt = "enumeration values exceed range of largest integer", .kind = .warning, .opt = .@"enum-too-large", + .extension = true, }; pub const enum_fixed: Diagnostic = .{ @@ -1795,6 +1807,7 @@ pub const zero_length_array: Diagnostic = .{ .fmt = "zero size arrays are an extension", .kind = .off, .opt = .@"zero-length-array", + .extension = true, }; pub const old_style_flexible_struct: Diagnostic = .{ @@ -1807,6 +1820,7 @@ pub const main_return_type: Diagnostic = .{ .fmt = "return type of 'main' is not 'int'", .kind = .warning, .opt = .@"main-return-type", + .extension = true, }; pub const invalid_int_suffix: Diagnostic = .{ @@ -1902,6 +1916,7 @@ pub const pointer_arith_void: Diagnostic = .{ .fmt = "invalid application of '{s}' to a void type", .kind = .off, .opt = .@"pointer-arith", + .extension = true, }; pub const sizeof_array_arg: Diagnostic = .{ @@ -1942,6 +1957,7 @@ pub const bitint_suffix: Diagnostic = .{ .opt = .@"c23-extensions", .kind = .warning, .suppress_version = .c23, + .extension = true, }; pub const auto_type_extension: Diagnostic = .{ @@ -1996,6 +2012,7 @@ pub const auto_type_with_init_list: Diagnostic = .{ pub const missing_semicolon: Diagnostic = .{ .fmt = "expected ';' at end of declaration list", .kind = .warning, + .extension = true, }; pub const tentative_definition_incomplete: Diagnostic = .{ @@ -2065,17 +2082,6 @@ pub const attribute_requires_string: Diagnostic = .{ .kind = .@"error", }; -pub const unterminated_string_literal_warning: Diagnostic = .{ - .fmt = "missing terminating '\"' character", - .kind = .warning, - .opt = .@"invalid-pp-token", -}; - -pub const unterminated_string_literal_error: Diagnostic = .{ - .fmt = "missing terminating '\"' character", - .kind = .@"error", -}; - pub const empty_char_literal_error: Diagnostic = .{ .fmt = "empty character constant", .kind = .@"error", diff --git a/src/aro/Pragma.zig b/src/aro/Pragma.zig index cb275ae..c42e1e7 100644 --- a/src/aro/Pragma.zig +++ b/src/aro/Pragma.zig @@ -88,6 +88,7 @@ pub const Diagnostic = struct { fmt: []const u8, kind: Diagnostics.Message.Kind, opt: ?Diagnostics.Option = null, + extension: bool = false, pub const pragma_warning_message: Diagnostic = .{ .fmt = "{s}", @@ -135,12 +136,14 @@ pub const Diagnostic = struct { .fmt = "pragma GCC diagnostic expected 'error', 'warning', 'ignored', 'fatal', 'push', or 'pop'", .kind = .warning, .opt = .@"unknown-pragmas", + .extension = true, }; pub const malformed_warning_check: Diagnostic = .{ .fmt = "{s} expected option name (e.g. \"-Wundef\")", .opt = .@"malformed-warning-check", .kind = .warning, + .extension = true, }; pub const pragma_pack_lparen: Diagnostic = .{ @@ -202,5 +205,6 @@ pub fn err(pp: *Preprocessor, tok_i: TokenIndex, diagnostic: Diagnostic, args: a .opt = diagnostic.opt, .text = buf.items, .location = pp.tokens.items(.loc)[tok_i].expand(pp.comp), + .extension = diagnostic.extension, }, pp.expansionSlice(tok_i), true); } diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index cb3f7ac..cbf1cd8 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -2298,7 +2298,11 @@ fn expandMacroExhaustive( try pp.hideset.put(tok.loc, new_hidelist); if (tok.id == .keyword_defined and eval_ctx == .expr) { - try pp.err(tok, .expansion_to_defined, .{}); + if (macro.is_func) { + try pp.err(tok, .expansion_to_defined_func, .{}); + } else { + try pp.err(tok, .expansion_to_defined_obj, .{}); + } } if (i < increment_idx_by and (tok.id == .keyword_defined or pp.defines.contains(pp.expandedSlice(tok.*)))) { diff --git a/src/aro/Preprocessor/Diagnostic.zig b/src/aro/Preprocessor/Diagnostic.zig index 8068c6d..d676140 100644 --- a/src/aro/Preprocessor/Diagnostic.zig +++ b/src/aro/Preprocessor/Diagnostic.zig @@ -194,12 +194,14 @@ pub const empty_char_literal_warning: Diagnostic = .{ .fmt = "empty character constant", .kind = .warning, .opt = .@"invalid-pp-token", + .extension = true, }; pub const unterminated_char_literal_warning: Diagnostic = .{ .fmt = "missing terminating ' character", .kind = .warning, .opt = .@"invalid-pp-token", + .extension = true, }; pub const unterminated_comment: Diagnostic = .{ @@ -293,12 +295,19 @@ pub const comma_deletion_va_args: Diagnostic = .{ .extension = true, }; -pub const expansion_to_defined: Diagnostic = .{ +pub const expansion_to_defined_obj: Diagnostic = .{ .fmt = "macro expansion producing 'defined' has undefined behavior", .kind = .off, .opt = .@"expansion-to-defined", }; +pub const expansion_to_defined_func: Diagnostic = .{ + .fmt = expansion_to_defined_obj.fmt, + .kind = .off, + .opt = .@"expansion-to-defined", + .extension = true, +}; + pub const invalid_pp_stringify_escape: Diagnostic = .{ .fmt = "invalid string literal, ignoring final '\\'", .kind = .warning, @@ -325,12 +334,14 @@ pub const newline_eof: Diagnostic = .{ .fmt = "no newline at end of file", .opt = .@"newline-eof", .kind = .off, + .extension = true, }; pub const malformed_warning_check: Diagnostic = .{ .fmt = "{s} expected option name (e.g. \"-Wundef\")", .opt = .@"malformed-warning-check", .kind = .warning, + .extension = true, }; pub const feature_check_requires_identifier: Diagnostic = .{ @@ -342,12 +353,14 @@ pub const builtin_macro_redefined: Diagnostic = .{ .fmt = "redefining builtin macro", .opt = .@"builtin-macro-redefined", .kind = .warning, + .extension = true, }; pub const macro_redefined: Diagnostic = .{ .fmt = "'{s}' macro redefined", .opt = .@"macro-redefined", .kind = .warning, + .extension = true, }; pub const previous_definition: Diagnostic = .{ @@ -404,4 +417,5 @@ pub const unterminated_string_literal_warning: Diagnostic = .{ .fmt = "missing terminating '\"' character", .kind = .warning, .opt = .@"invalid-pp-token", + .extension = true, }; diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index 2a0a9e4..0b687cf 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -95,6 +95,8 @@ pub const TokenWithExpansionLocs = struct { try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, + .opt = diagnostic.opt, + .extension = diagnostic.extension, .location = source.lineCol(.{ .id = source.id, .byte_offset = tok.loc.byte_offset, diff --git a/test/cases/containers.c b/test/cases/containers.c index e4ae503..1f9928c 100644 --- a/test/cases/containers.c +++ b/test/cases/containers.c @@ -165,5 +165,5 @@ struct NoTrailingSemicolon { "containers.c:132:12: error: use of 'A' with tag type that does not match previous definition" \ "containers.c:131:10: note: previous definition is here" \ "containers.c:132:14: error: variable has incomplete type 'struct A'" \ - "containers.c:140:1: warning: expected ';' at end of declaration list" \ + "containers.c:140:1: warning: expected ';' at end of declaration list [-Wpedantic]" \ diff --git a/test/cases/invalid types.c b/test/cases/invalid types.c index 28ae289..b68dee8 100644 --- a/test/cases/invalid types.c +++ b/test/cases/invalid types.c @@ -42,7 +42,7 @@ ArrayOfIncomplete a; "invalid types.c:8:12: error: tentative definition has type 'struct Bar' that is never completed" \ "invalid types.c:8:8: note: forward declaration of 'struct Bar'" \ "invalid types.c:9:6: error: array is too large" \ - "invalid types.c:11:1: warning: plain '_Complex' requires a type specifier; assuming '_Complex double'" \ + "invalid types.c:11:1: warning: plain '_Complex' requires a type specifier; assuming '_Complex double' [-Wpedantic]" \ "invalid types.c:14:6: note: previous definition is here" \ "invalid types.c:16:15: error: expected identifier or '('" \ "invalid types.c:17:17: error: array has incomplete element type 'struct bar'" \ diff --git a/test/cases/unterminated string literal.c b/test/cases/unterminated string literal.c index d5bbcc9..7849db0 100644 --- a/test/cases/unterminated string literal.c +++ b/test/cases/unterminated string literal.c @@ -1,9 +1,6 @@ -#define EXPECTED_ERRORS "unterminated string literal.c:9:12: warning: missing terminating '\"' character [-Winvalid-pp-token]" \ - "unterminated string literal.c:10:20: warning: missing terminating '\"' character [-Winvalid-pp-token]" \ - "unterminated string literal.c:11:12: warning: missing terminating '\"' character [-Winvalid-pp-token]" \ - "unterminated string literal.c:9:12: error: missing terminating '\"' character" \ - "unterminated string literal.c:10:20: error: missing terminating '\"' character" \ - "unterminated string literal.c:11:12: error: missing terminating '\"' character" \ +#define EXPECTED_ERRORS "unterminated string literal.c:6:12: warning: missing terminating '\"' character [-Winvalid-pp-token]" \ + "unterminated string literal.c:7:20: warning: missing terminating '\"' character [-Winvalid-pp-token]" \ + "unterminated string literal.c:8:12: warning: missing terminating '\"' character [-Winvalid-pp-token]" \ char A[] = "hello -- Gitee From 9faca93fd6a4ad992f5f7da4b56fc858e6ca870f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 23 Apr 2025 12:47:49 +0300 Subject: [PATCH 019/117] Diagnostics: do not print -Wpedantic for warnings that are enabled by default --- src/aro/Diagnostics.zig | 42 +++++++++++++++++++++++--------------- test/cases/containers.c | 2 +- test/cases/invalid types.c | 2 +- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index e6d7694..9cc2361 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -14,6 +14,8 @@ pub const Message = struct { extension: bool = false, location: ?Source.ExpandedLocation, + effective_kind: Kind = .off, + pub const Kind = enum { off, note, @@ -336,10 +338,10 @@ pub fn effectiveKind(d: *Diagnostics, message: anytype) Message.Kind { pub fn add(d: *Diagnostics, msg: Message) Compilation.Error!void { var copy = msg; - copy.kind = d.effectiveKind(msg); - if (copy.kind == .off) return; + copy.effective_kind = d.effectiveKind(msg); + if (copy.effective_kind == .off) return; try d.addMessage(copy); - if (copy.kind == .@"fatal error") return error.FatalError; + if (copy.effective_kind == .@"fatal error") return error.FatalError; } pub fn addWithLocation( @@ -350,9 +352,9 @@ pub fn addWithLocation( note_msg_loc: bool, ) Compilation.Error!void { var copy = msg; - copy.kind = d.effectiveKind(msg); - if (copy.kind == .off) return; - if (copy.kind == .@"error" or copy.kind == .@"fatal error") d.errors += 1; + copy.effective_kind = d.effectiveKind(msg); + if (copy.effective_kind == .off) return; + if (copy.effective_kind == .@"error" or copy.effective_kind == .@"fatal error") d.errors += 1; if (expansion_locs.len != 0) copy.location = expansion_locs[expansion_locs.len - 1].expand(comp); try d.addMessage(copy); @@ -365,6 +367,7 @@ pub fn addWithLocation( i -= 1; try d.addMessage(.{ .kind = .note, + .effective_kind = .note, .text = "expanded from here", .location = expansion_locs[i].expand(comp), }); @@ -373,6 +376,7 @@ pub fn addWithLocation( var buf: [256]u8 = undefined; try d.addMessage(.{ .kind = .note, + .effective_kind = .note, .text = std.fmt.bufPrint( &buf, "(skipping {d} expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)", @@ -385,6 +389,7 @@ pub fn addWithLocation( i -= 1; try d.addMessage(.{ .kind = .note, + .effective_kind = .note, .text = "expanded from here", .location = expansion_locs[i].expand(comp), }); @@ -392,8 +397,9 @@ pub fn addWithLocation( } if (note_msg_loc) { - try d.add(.{ + try d.addMessage(.{ .kind = .note, + .effective_kind = .note, .text = "expanded from here", .location = msg.location.?, }); @@ -435,7 +441,8 @@ pub fn formatInt(w: anytype, fmt: []const u8, int: anytype) !usize { } fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { - switch (msg.kind) { + std.debug.assert(msg.effective_kind != .off); + switch (msg.effective_kind) { .off => unreachable, .@"error", .@"fatal error" => d.errors += 1, .warning => d.warnings += 1, @@ -446,7 +453,7 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { switch (d.output) { .ignore => {}, .to_file => |to_file| { - d.writeToWriter(msg, to_file.file.writer(), to_file.config) catch { + writeToWriter(msg, to_file.file.writer(), to_file.config) catch { return error.FatalError; }; }, @@ -454,6 +461,7 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { const arena = list.arena.allocator(); try list.messages.append(list.arena.child_allocator, .{ .kind = msg.kind, + .effective_kind = msg.effective_kind, .text = try arena.dupe(u8, msg.text), .opt = msg.opt, .extension = msg.extension, @@ -461,36 +469,36 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { }); }, .to_buffer => |*buf| { - d.writeToWriter(msg, buf.writer(), .no_color) catch return error.OutOfMemory; + writeToWriter(msg, buf.writer(), .no_color) catch return error.OutOfMemory; }, } } -fn writeToWriter(d: *Diagnostics, msg: Message, w: anytype, config: std.io.tty.Config) !void { +fn writeToWriter(msg: Message, w: anytype, config: std.io.tty.Config) !void { try config.setColor(w, .bold); if (msg.location) |loc| { try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col }); } - switch (msg.kind) { + switch (msg.effective_kind) { .@"fatal error", .@"error" => try config.setColor(w, .bright_red), .note => try config.setColor(w, .bright_cyan), .warning => try config.setColor(w, .bright_magenta), .off => unreachable, } - try w.print("{s}: ", .{@tagName(msg.kind)}); + try w.print("{s}: ", .{@tagName(msg.effective_kind)}); try config.setColor(w, .white); try w.writeAll(msg.text); if (msg.opt) |some| { - if (msg.kind == .@"error" and d.state.options.get(some) == .@"error") { + if (msg.effective_kind == .@"error" and msg.kind != .@"error") { try w.print(" [-Werror,-W{s}]", .{@tagName(some)}); - } else if (msg.kind != .note) { + } else if (msg.effective_kind != .note) { try w.print(" [-W{s}]", .{@tagName(some)}); } } else if (msg.extension) { - if (msg.kind == .@"error") { + if (msg.effective_kind == .@"error") { try w.writeAll(" [-Werror,-Wpedantic]"); - } else { + } else if (msg.effective_kind != msg.kind) { try w.writeAll(" [-Wpedantic]"); } } diff --git a/test/cases/containers.c b/test/cases/containers.c index 1f9928c..e4ae503 100644 --- a/test/cases/containers.c +++ b/test/cases/containers.c @@ -165,5 +165,5 @@ struct NoTrailingSemicolon { "containers.c:132:12: error: use of 'A' with tag type that does not match previous definition" \ "containers.c:131:10: note: previous definition is here" \ "containers.c:132:14: error: variable has incomplete type 'struct A'" \ - "containers.c:140:1: warning: expected ';' at end of declaration list [-Wpedantic]" \ + "containers.c:140:1: warning: expected ';' at end of declaration list" \ diff --git a/test/cases/invalid types.c b/test/cases/invalid types.c index b68dee8..28ae289 100644 --- a/test/cases/invalid types.c +++ b/test/cases/invalid types.c @@ -42,7 +42,7 @@ ArrayOfIncomplete a; "invalid types.c:8:12: error: tentative definition has type 'struct Bar' that is never completed" \ "invalid types.c:8:8: note: forward declaration of 'struct Bar'" \ "invalid types.c:9:6: error: array is too large" \ - "invalid types.c:11:1: warning: plain '_Complex' requires a type specifier; assuming '_Complex double' [-Wpedantic]" \ + "invalid types.c:11:1: warning: plain '_Complex' requires a type specifier; assuming '_Complex double'" \ "invalid types.c:14:6: note: previous definition is here" \ "invalid types.c:16:15: error: expected identifier or '('" \ "invalid types.c:17:17: error: array has incomplete element type 'struct bar'" \ -- Gitee From 82dddfbaee15c6aa2bbab9f61595a6767bf9e48c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 23 Apr 2025 13:14:32 +0300 Subject: [PATCH 020/117] Parser: improve handling of __cdecl It is now parsed in parenthesized declarators but is still not correctly applied due to bugs in attribute application. --- src/aro/Attribute.zig | 50 +++++++------ src/aro/Parser.zig | 91 ++++++++++++++---------- src/aro/Parser/Diagnostic.zig | 6 ++ test/cases/ast/msvc attribute keywords.c | 3 + test/cases/msvc attribute keywords.c | 5 +- 5 files changed, 96 insertions(+), 59 deletions(-) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 7f1a4e6..526df5c 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -845,6 +845,7 @@ pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, } else { try p.attr_application_buf.append(p.gpa, attr); }, + .calling_convention => try applyCallingConvention(attr, p, tok, base_qt), .alloc_size, .copy, .tls_model, @@ -857,7 +858,7 @@ pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, return applySelected(base_qt, p); } -pub fn applyFieldAttributes(p: *Parser, field_ty: *QualType, attr_buf_start: usize) ![]const Attribute { +pub fn applyFieldAttributes(p: *Parser, field_qt: *QualType, attr_buf_start: usize) ![]const Attribute { const attrs = p.attr_buf.items(.attr)[attr_buf_start..]; const toks = p.attr_buf.items(.tok)[attr_buf_start..]; p.attr_application_buf.items.len = 0; @@ -867,8 +868,9 @@ pub fn applyFieldAttributes(p: *Parser, field_ty: *QualType, attr_buf_start: usi .mode, .warn_unused_result, .nodiscard, .nullability, .unaligned, => try p.attr_application_buf.append(p.gpa, attr), // zig fmt: on - .vector_size => try attr.applyVectorSize(p, tok, field_ty), - .aligned => try attr.applyAligned(p, field_ty.*, null), + .vector_size => try attr.applyVectorSize(p, tok, field_qt), + .aligned => try attr.applyAligned(p, field_qt.*, null), + .calling_convention => try applyCallingConvention(attr, p, tok, field_qt.*), else => try ignoredAttrErr(p, tok, attr.tag, "fields"), }; return p.attr_application_buf.items; @@ -892,6 +894,7 @@ pub fn applyTypeAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, diag } else { try p.err(tok, .designated_init_invalid, .{}); }, + .calling_convention => try applyCallingConvention(attr, p, tok, base_qt), .alloc_size, .copy, .scalar_storage_order, @@ -945,23 +948,7 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) }, .aligned => try attr.applyAligned(p, base_qt, null), .format => try attr.applyFormat(p, base_qt), - .calling_convention => switch (attr.args.calling_convention.cc) { - .c => continue, - .stdcall, .thiscall, .fastcall, .regcall => switch (p.comp.target.cpu.arch) { - .x86 => try p.attr_application_buf.append(p.gpa, attr), - else => try p.err(tok, .callconv_not_supported, .{p.tok_ids[tok].lexeme().?}), - }, - .vectorcall => switch (p.comp.target.cpu.arch) { - .x86, .aarch64, .aarch64_be => try p.attr_application_buf.append(p.gpa, attr), - else => try p.err(tok, .callconv_not_supported, .{p.tok_ids[tok].lexeme().?}), - }, - .riscv_vector, - .aarch64_sve_pcs, - .aarch64_vector_pcs, - .arm_aapcs, - .arm_aapcs_vfp, - => unreachable, // These can't come from keyword syntax - }, + .calling_convention => try applyCallingConvention(attr, p, tok, base_qt), .fastcall => if (p.comp.target.cpu.arch == .x86) { try p.attr_application_buf.append(p.gpa, .{ .tag = .calling_convention, @@ -1235,6 +1222,29 @@ fn applyFormat(attr: Attribute, p: *Parser, qt: QualType) !void { try p.attr_application_buf.append(p.gpa, attr); } +fn applyCallingConvention(attr: Attribute, p: *Parser, tok: TokenIndex, qt: QualType) !void { + if (!qt.is(p.comp, .func)) { + return p.err(tok, .callconv_non_func, .{ p.tok_ids[tok].symbol(), qt }); + } + switch (attr.args.calling_convention.cc) { + .c => {}, + .stdcall, .thiscall, .fastcall, .regcall => switch (p.comp.target.cpu.arch) { + .x86 => try p.attr_application_buf.append(p.gpa, attr), + else => try p.err(tok, .callconv_not_supported, .{p.tok_ids[tok].symbol()}), + }, + .vectorcall => switch (p.comp.target.cpu.arch) { + .x86, .aarch64, .aarch64_be => try p.attr_application_buf.append(p.gpa, attr), + else => try p.err(tok, .callconv_not_supported, .{p.tok_ids[tok].symbol()}), + }, + .riscv_vector, + .aarch64_sve_pcs, + .aarch64_vector_pcs, + .arm_aapcs, + .arm_aapcs_vfp, + => unreachable, // These can't come from keyword syntax + } +} + fn applySelected(qt: QualType, p: *Parser) !QualType { if (p.attr_application_buf.items.len == 0) return qt; if (qt.isInvalid()) return qt; diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 71ff26a..0a16716 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3162,6 +3162,7 @@ fn enumerator(p: *Parser, e: *Enumerator) Error!?EnumFieldAndNode { fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { var any = false; while (true) { + if (allow_cc_attr and try p.msTypeAttribute()) continue; switch (p.tok_ids[p.tok_i]) { .keyword_restrict, .keyword_restrict1, .keyword_restrict2 => { if (b.restrict != null) @@ -3195,6 +3196,53 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { else b.unaligned = p.tok_i; }, + .keyword_nonnull, .keyword_nullable, .keyword_nullable_result, .keyword_null_unspecified => |tok_id| { + const sym_str = p.tok_ids[p.tok_i].symbol(); + try p.err(p.tok_i, .nullability_extension, .{sym_str}); + const new: @FieldType(TypeStore.Builder, "nullability") = switch (tok_id) { + .keyword_nonnull => .{ .nonnull = p.tok_i }, + .keyword_nullable => .{ .nullable = p.tok_i }, + .keyword_nullable_result => .{ .nullable_result = p.tok_i }, + .keyword_null_unspecified => .{ .null_unspecified = p.tok_i }, + else => unreachable, + }; + if (std.meta.activeTag(b.nullability) == new) { + try p.err(p.tok_i, .duplicate_nullability, .{sym_str}); + } else switch (b.nullability) { + .none => { + b.nullability = new; + try p.attr_buf.append(p.gpa, .{ + .attr = .{ .tag = .nullability, .args = .{ + .nullability = .{ .kind = switch (tok_id) { + .keyword_nonnull => .nonnull, + .keyword_nullable => .nullable, + .keyword_nullable_result => .nullable_result, + .keyword_null_unspecified => .unspecified, + else => unreachable, + } }, + }, .syntax = .keyword }, + .tok = p.tok_i, + }); + }, + .nonnull, + .nullable, + .nullable_result, + .null_unspecified, + => |prev| try p.err(p.tok_i, .conflicting_nullability, .{ p.tok_ids[p.tok_i], p.tok_ids[prev] }), + } + }, + else => break, + } + p.tok_i += 1; + any = true; + } + return any; +} + +fn msTypeAttribute(p: *Parser) !bool { + var any = false; + while (true) { + switch (p.tok_ids[p.tok_i]) { .keyword_stdcall, .keyword_stdcall2, .keyword_thiscall, @@ -3207,7 +3255,6 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { .keyword_cdecl, .keyword_cdecl2, => { - if (!allow_cc_attr) break; try p.attr_buf.append(p.gpa, .{ .attr = .{ .tag = .calling_convention, .args = .{ .calling_convention = .{ .cc = switch (p.tok_ids[p.tok_i]) { @@ -3233,46 +3280,11 @@ fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { }, .syntax = .keyword }, .tok = p.tok_i, }); - }, - .keyword_nonnull, .keyword_nullable, .keyword_nullable_result, .keyword_null_unspecified => |tok_id| { - const sym_str = p.tok_ids[p.tok_i].symbol(); - try p.err(p.tok_i, .nullability_extension, .{sym_str}); - const new: @FieldType(TypeStore.Builder, "nullability") = switch (tok_id) { - .keyword_nonnull => .{ .nonnull = p.tok_i }, - .keyword_nullable => .{ .nullable = p.tok_i }, - .keyword_nullable_result => .{ .nullable_result = p.tok_i }, - .keyword_null_unspecified => .{ .null_unspecified = p.tok_i }, - else => unreachable, - }; - if (std.meta.activeTag(b.nullability) == new) { - try p.err(p.tok_i, .duplicate_nullability, .{sym_str}); - } else switch (b.nullability) { - .none => { - b.nullability = new; - try p.attr_buf.append(p.gpa, .{ - .attr = .{ .tag = .nullability, .args = .{ - .nullability = .{ .kind = switch (tok_id) { - .keyword_nonnull => .nonnull, - .keyword_nullable => .nullable, - .keyword_nullable_result => .nullable_result, - .keyword_null_unspecified => .unspecified, - else => unreachable, - } }, - }, .syntax = .keyword }, - .tok = p.tok_i, - }); - }, - .nonnull, - .nullable, - .nullable_result, - .null_unspecified, - => |prev| try p.err(p.tok_i, .conflicting_nullability, .{ p.tok_ids[p.tok_i], p.tok_ids[prev] }), - } + any = true; + p.tok_i += 1; }, else => break, } - p.tok_i += 1; - any = true; } return any; } @@ -3413,6 +3425,9 @@ fn declarator( try d.validate(p, combine_tok); return d; } else if (p.eatToken(.l_paren)) |l_paren| blk: { + // Parse Microsoft keyword type attributes. + _ = try p.msTypeAttribute(); + const special_marker: QualType = .{ ._index = .declarator_combine }; var res = (try p.declarator(special_marker, kind)) orelse { p.tok_i = l_paren; diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 3d11f41..b8d2e5f 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -1908,8 +1908,14 @@ pub const ptr_arithmetic_incomplete: Diagnostic = .{ pub const callconv_not_supported: Diagnostic = .{ .fmt = "'{s}' calling convention is not supported for this target", + .kind = .warning, .opt = .@"ignored-attributes", +}; + +pub const callconv_non_func: Diagnostic = .{ + .fmt = "'{s}' only applies to function types; type here is '{qt}'", .kind = .warning, + .opt = .@"ignored-attributes", }; pub const pointer_arith_void: Diagnostic = .{ diff --git a/test/cases/ast/msvc attribute keywords.c b/test/cases/ast/msvc attribute keywords.c index 2f1eca1..6220d6e 100644 --- a/test/cases/ast/msvc attribute keywords.c +++ b/test/cases/ast/msvc attribute keywords.c @@ -30,3 +30,6 @@ fn_proto: 'attributed(kr (...) *int)' fn_proto: 'fn (decayed *[]attributed(int), decayed *attributed([]int)) int' name: baz +fn_proto: 'fn (fn_ptr: *fn () void) void' + name: quux + diff --git a/test/cases/msvc attribute keywords.c b/test/cases/msvc attribute keywords.c index cbc7bd6..cbcb60c 100644 --- a/test/cases/msvc attribute keywords.c +++ b/test/cases/msvc attribute keywords.c @@ -7,7 +7,10 @@ int *__stdcall bar(); int baz(int __unaligned [], int [__unaligned]); int qux(int __stdcall [], int [__cdecl]); +void quux(void (__cdecl *fn_ptr)(void)); + #define EXPECTED_ERRORS \ - ".c:8:13: warning: attribute 'calling_convention' ignored on variables [-Wignored-attributes]" \ + ".c:8:13: warning: '__stdcall' only applies to function types; type here is 'int *' [-Wignored-attributes]" \ ".c:8:32: error: expected ']', found '__cdecl'" \ ".c:8:31: note: to match this '{'" \ + ".c:10:17: warning: '__cdecl' only applies to function types; type here is 'void (*)(void)' [-Wignored-attributes]" \ -- Gitee From 25765aa788e117e1d757190039f29974070743e8 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 23 Apr 2025 23:21:04 +0300 Subject: [PATCH 021/117] Compilation: add some misssing OS specific defines --- src/aro/Compilation.zig | 270 ++++++++++++++++++++++------------------ 1 file changed, 151 insertions(+), 119 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index ffbaae0..04700ab 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -226,7 +226,25 @@ pub const SystemDefinesMode = enum { }; fn generateSystemDefines(comp: *Compilation, w: anytype) !void { + const define = struct { + fn define(_w: anytype, name: []const u8) !void { + try _w.print("#define {s} 1\n", .{name}); + } + }.define; + const defineStd = struct { + fn defineStd(_w: anytype, name: []const u8, is_gnu: bool) !void { + if (is_gnu) { + try _w.print("#define {s} 1\n", .{name}); + } + try _w.print( + \\#define __{s} 1 + \\#define __{s}__ 1 + \\ + , .{ name, name }); + } + }.defineStd; const ptr_width = comp.target.ptrBitWidth(); + const is_gnu = comp.langopts.standard.isGNU(); if (comp.langopts.gnuc_version > 0) { try w.print("#define __GNUC__ {d}\n", .{comp.langopts.gnuc_version / 10_000}); @@ -236,43 +254,38 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { // os macros switch (comp.target.os.tag) { - .linux => try w.writeAll( - \\#define linux 1 - \\#define __linux 1 - \\#define __linux__ 1 - \\ - ), - .windows => if (ptr_width == 32) try w.writeAll( - \\#define WIN32 1 - \\#define _WIN32 1 - \\#define __WIN32 1 - \\#define __WIN32__ 1 - \\ - ) else try w.writeAll( - \\#define WIN32 1 - \\#define WIN64 1 - \\#define _WIN32 1 - \\#define _WIN64 1 - \\#define __WIN32 1 - \\#define __WIN64 1 - \\#define __WIN32__ 1 - \\#define __WIN64__ 1 - \\ - ), + .linux => try defineStd(w, "linux", is_gnu), + .windows => { + try define(w, "_WIN32"); + if (ptr_width == 64) { + try define(w, "_WIN64"); + } + + if (comp.target.isMinGW()) { + try defineStd(w, "WIN32", is_gnu); + try defineStd(w, "WINNT", is_gnu); + if (ptr_width == 64) { + try defineStd(w, "WIN64", is_gnu); + } + try define(w, "__MSVCRT__"); + try define(w, "__MINGW32__"); + } + }, + .uefi => try define(w, "__UEFI__"), .freebsd => try w.print("#define __FreeBSD__ {d}\n", .{comp.target.os.version_range.semver.min.major}), - .netbsd => try w.writeAll("#define __NetBSD__ 1\n"), - .openbsd => try w.writeAll("#define __OpenBSD__ 1\n"), - .dragonfly => try w.writeAll("#define __DragonFly__ 1\n"), - .solaris => try w.writeAll( - \\#define sun 1 - \\#define __sun 1 - \\ - ), - .macos => try w.writeAll( - \\#define __APPLE__ 1 - \\#define __MACH__ 1 - \\ - ), + .netbsd => try define(w, "__NetBSD__"), + .openbsd => try define(w, "__OpenBSD__"), + .dragonfly => try define(w, "__DragonFly__"), + .solaris => try defineStd(w, "sun", is_gnu), + .macos, + .tvos, + .ios, + .driverkit, + .visionos, + .watchos, + => try define(w, "__APPLE__"), + .wasi => try define(w, "__wasi__"), + .emscripten => try define(w, "__EMSCRIPTEN__"), else => {}, } @@ -283,107 +296,111 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { .openbsd, .dragonfly, .linux, - => try w.writeAll( - \\#define unix 1 - \\#define __unix 1 - \\#define __unix__ 1 - \\ - ), + .haiku, + .hurd, + .solaris, + .aix, + .emscripten, + => try defineStd(w, "unix", is_gnu), else => {}, } if (comp.target.abi.isAndroid()) { - try w.writeAll("#define __ANDROID__ 1\n"); + try define(w, "__ANDROID__"); } // architecture macros switch (comp.target.cpu.arch) { - .x86_64 => try w.writeAll( - \\#define __amd64__ 1 - \\#define __amd64 1 - \\#define __x86_64 1 - \\#define __x86_64__ 1 - \\ - ), - .x86 => try w.writeAll( - \\#define i386 1 - \\#define __i386 1 - \\#define __i386__ 1 - \\ - ), + .x86_64 => { + try define(w, "__amd64__"); + try define(w, "__amd64"); + try define(w, "__x86_64__"); + try define(w, "__x86_64"); + }, + .x86 => try defineStd(w, "i386", is_gnu), .mips, .mipsel, .mips64, .mips64el, - => try w.writeAll( - \\#define __mips__ 1 - \\#define mips 1 - \\ - ), + => { + try define(w, "__mips__"); + try define(w, "_mips"); + }, .powerpc, .powerpcle, - => try w.writeAll( - \\#define __powerpc__ 1 - \\#define __POWERPC__ 1 - \\#define __ppc__ 1 - \\#define __PPC__ 1 - \\#define _ARCH_PPC 1 - \\ - ), + => { + try define(w, "__powerpc__"); + try define(w, "__POWERPC__"); + try define(w, "__ppc__"); + try define(w, "__PPC__"); + try define(w, "_ARCH_PPC"); + }, .powerpc64, .powerpc64le, - => try w.writeAll( - \\#define __powerpc 1 - \\#define __powerpc__ 1 - \\#define __powerpc64__ 1 - \\#define __POWERPC__ 1 - \\#define __ppc__ 1 - \\#define __ppc64__ 1 - \\#define __PPC__ 1 - \\#define __PPC64__ 1 - \\#define _ARCH_PPC 1 - \\#define _ARCH_PPC64 1 - \\ - ), - .sparc64 => try w.writeAll( - \\#define __sparc__ 1 - \\#define __sparc 1 - \\#define __sparc_v9__ 1 - \\ - ), - .sparc => try w.writeAll( - \\#define __sparc__ 1 - \\#define __sparc 1 - \\ - ), - .arm, .armeb => try w.writeAll( - \\#define __arm__ 1 - \\#define __arm 1 - \\ - ), - .thumb, .thumbeb => try w.writeAll( - \\#define __arm__ 1 - \\#define __arm 1 - \\#define __thumb__ 1 - \\ - ), - .aarch64, .aarch64_be => try w.writeAll("#define __aarch64__ 1\n"), - .msp430 => try w.writeAll( - \\#define MSP430 1 - \\#define __MSP430__ 1 - \\ - ), + => { + try define(w, "__powerpc"); + try define(w, "__powerpc__"); + try define(w, "__powerpc64__"); + try define(w, "__POWERPC__"); + try define(w, "__ppc__"); + try define(w, "__ppc64__"); + try define(w, "__PPC__"); + try define(w, "__PPC64__"); + try define(w, "_ARCH_PPC"); + try define(w, "_ARCH_PPC64"); + }, + .sparc64 => { + try defineStd(w, "sparc", is_gnu); + try define(w, "__sparc_v9__"); + try define(w, "__arch64__"); + if (comp.target.os.tag != .solaris) { + try define(w, "__sparc64__"); + try define(w, "__sparc_v9__"); + try define(w, "__sparcv9__"); + } + }, + .sparc => { + try defineStd(w, "sparc", is_gnu); + if (comp.target.os.tag == .solaris) { + try define(w, "__sparcv8"); + } + }, + .arm, .armeb, .thumb, .thumbeb => { + try define(w, "__arm__"); + try define(w, "__arm"); + if (comp.target.cpu.arch.isThumb()) { + try define(w, "__thumb__"); + } + }, + .aarch64, .aarch64_be => { + try define(w, "__aarch64__"); + if (comp.target.os.tag == .macos) { + try define(w, "__AARCH64_SIMD__"); + if (ptr_width == 32) { + try define(w, "__ARM64_ARCH_8_32__"); + } else { + try define(w, "__ARM64_ARCH_8__"); + } + try define(w, "__ARM_NEON__"); + try define(w, "__arm64"); + try define(w, "__arm64__"); + } + }, + .msp430 => { + try define(w, "MSP430"); + try define(w, "__MSP430__"); + }, else => {}, } - if (comp.target.os.tag != .windows) switch (ptr_width) { - 64 => try w.writeAll( - \\#define _LP64 1 - \\#define __LP64__ 1 - \\ - ), - 32 => try w.writeAll("#define _ILP32 1\n"), - else => {}, - }; + if (ptr_width == 64 and comp.target.cTypeBitSize(.long) == 32) { + try define(w, "_LP64"); + try define(w, "__LP64__"); + } else if (ptr_width == 32 and comp.target.cTypeBitSize(.long) == 32 and + comp.target.cTypeBitSize(.int) == 32) + { + try define(w, "_ILP32"); + try define(w, "__ILP32__"); + } try w.writeAll( \\#define __ORDER_LITTLE_ENDIAN__ 1234 @@ -401,6 +418,21 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { \\ ); + switch (comp.target.ofmt) { + .elf => try define(w, "__ELF__"), + .macho => try define(w, "__MACH__"), + else => {}, + } + + if (comp.target.os.tag.isDarwin()) { + try w.writeAll( + \\#define __nonnull _Nonnull + \\#define __null_unspecified _Null_unspecified + \\#define __nullable _Nullable + \\ + ); + } + // atomics try w.writeAll( \\#define __ATOMIC_RELAXED 0 -- Gitee From cb3f55b38fb9f267a1002d965cd53c70f9c6a61e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 24 Apr 2025 16:08:06 +0300 Subject: [PATCH 022/117] TypeStore: fix combining with __int64 --- src/aro/Builtins.zig | 5 +---- src/aro/Parser/Diagnostic.zig | 2 +- src/aro/TypeStore.zig | 26 +++++++++++++++++++++++- test/cases/ast/msvc attribute keywords.c | 6 ++++++ test/cases/msvc attribute keywords.c | 3 +++ 5 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/aro/Builtins.zig b/src/aro/Builtins.zig index 01651c1..319a5d7 100644 --- a/src/aro/Builtins.zig +++ b/src/aro/Builtins.zig @@ -53,10 +53,7 @@ fn createType(desc: TypeDescription, it: *TypeDescription.TypeIterator, comp: *C for (desc.prefix) |prefix| { switch (prefix) { .L => builder.combine(.long, 0) catch unreachable, - .LL => { - builder.combine(.long, 0) catch unreachable; - builder.combine(.long, 0) catch unreachable; - }, + .LL => builder.combine(.long_long, 0) catch unreachable, .LLL => { switch (builder.type) { .none => builder.type = .int128, diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index b8d2e5f..429a566 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -2197,7 +2197,7 @@ pub const overflow_result_requires_ptr: Diagnostic = .{ pub const attribute_todo: Diagnostic = .{ .fmt = "TODO: implement '{s}' attribute for {s}", - .kind = .@"error", + .kind = .warning, }; pub const invalid_type_underlying_enum: Diagnostic = .{ diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 52b00c6..8f34625 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -2716,7 +2716,7 @@ pub const Builder = struct { .double => .long_double, .long => .long_long, .unsigned => .ulong, - .signed => .long, + .signed => .slong, .int => .long_int, .sint => .slong_int, .ulong => .ulong_long, @@ -2729,6 +2729,30 @@ pub const Builder = struct { .complex_double => .complex_long_double, else => return b.cannotCombine(source_tok), }, + .long_long => switch (b.type) { + .none => .long_long, + .unsigned => .ulong_long, + .signed => .slong_long, + .int => .long_long_int, + .sint => .slong_long_int, + .long => .long_long, + .slong => .slong_long, + .ulong => .ulong_long, + .complex => .complex_long, + .complex_signed => .complex_slong_long, + .complex_unsigned => .complex_ulong_long, + .complex_long => .complex_long_long, + .complex_slong => .complex_slong_long, + .complex_ulong => .complex_ulong_long, + .long_long, + .ulong_long, + .ulong_long_int, + .complex_long_long, + .complex_ulong_long, + .complex_ulong_long_int, + => return b.duplicateSpec(source_tok, "long"), + else => return b.cannotCombine(source_tok), + }, .int128 => switch (b.type) { .none => .int128, .unsigned => .uint128, diff --git a/test/cases/ast/msvc attribute keywords.c b/test/cases/ast/msvc attribute keywords.c index 6220d6e..49fd0af 100644 --- a/test/cases/ast/msvc attribute keywords.c +++ b/test/cases/ast/msvc attribute keywords.c @@ -33,3 +33,9 @@ fn_proto: 'fn (decayed *[]attributed(int), decayed *attributed([]int)) int' fn_proto: 'fn (fn_ptr: *fn () void) void' name: quux +variable: 'unsigned long long' + name: l + +variable: 'unsigned long long' + name: l + diff --git a/test/cases/msvc attribute keywords.c b/test/cases/msvc attribute keywords.c index cbcb60c..85d0960 100644 --- a/test/cases/msvc attribute keywords.c +++ b/test/cases/msvc attribute keywords.c @@ -9,6 +9,9 @@ int qux(int __stdcall [], int [__cdecl]); void quux(void (__cdecl *fn_ptr)(void)); +unsigned __int64 l; +unsigned long __int64 l; + #define EXPECTED_ERRORS \ ".c:8:13: warning: '__stdcall' only applies to function types; type here is 'int *' [-Wignored-attributes]" \ ".c:8:32: error: expected ']', found '__cdecl'" \ -- Gitee From b3db1c46d2feac54f99ec54aff3f0498ffe1b4a3 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 24 Apr 2025 16:10:36 +0300 Subject: [PATCH 023/117] Preprocessor: implement MS __identifier and __pragma --- src/aro/Diagnostics.zig | 4 +- src/aro/Driver.zig | 4 +- src/aro/Preprocessor.zig | 212 +++++++++++++++------------- src/aro/Preprocessor/Diagnostic.zig | 22 ++- src/aro/Tokenizer.zig | 8 +- test/cases/msvc macros.c | 17 +++ 6 files changed, 154 insertions(+), 113 deletions(-) create mode 100644 test/cases/msvc macros.c diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index 9cc2361..add9173 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -354,7 +354,6 @@ pub fn addWithLocation( var copy = msg; copy.effective_kind = d.effectiveKind(msg); if (copy.effective_kind == .off) return; - if (copy.effective_kind == .@"error" or copy.effective_kind == .@"fatal error") d.errors += 1; if (expansion_locs.len != 0) copy.location = expansion_locs[expansion_locs.len - 1].expand(comp); try d.addMessage(copy); @@ -506,7 +505,8 @@ fn writeToWriter(msg: Message, w: anytype, config: std.io.tty.Config) !void { if (msg.location) |loc| { const trailer = if (loc.end_with_splice) "\\ " else ""; try config.setColor(w, .reset); - try w.print("\n{s}{s}\n{s: >[3]}", .{ loc.line, trailer, "", loc.col }); + try w.print("\n{s}{s}\n", .{ loc.line, trailer }); + try w.writeByteNTimes(' ', loc.width); try config.setColor(w, .bold); try config.setColor(w, .bright_green); try w.writeAll("^\n"); diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 4ca2bd8..c4dbc2d 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -865,7 +865,7 @@ fn processSource( asm_gen_fn: ?AsmCodeGenFn, ) !void { d.comp.generated_buf.items.len = 0; - const prev_total = d.diagnostics.total; + const prev_total = d.diagnostics.errors; var pp = try Preprocessor.initDefault(d.comp); defer pp.deinit(); @@ -891,7 +891,7 @@ fn processSource( if (d.only_preprocess) { d.printDiagnosticsStats(); - if (d.diagnostics.total != prev_total) { + if (d.diagnostics.errors != prev_total) { if (fast_exit) std.process.exit(1); // Not linking, no need for cleanup. return; } diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index cbf1cd8..2fe9259 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -186,78 +186,14 @@ pub fn initDefault(comp: *Compilation) !Preprocessor { return pp; } -const builtin_macros = struct { - const args = [1][]const u8{"X"}; - - const has_attribute = [1]RawToken{.{ - .id = .macro_param_has_attribute, - .source = .generated, - }}; - const has_c_attribute = [1]RawToken{.{ - .id = .macro_param_has_c_attribute, - .source = .generated, - }}; - const has_declspec_attribute = [1]RawToken{.{ - .id = .macro_param_has_declspec_attribute, - .source = .generated, - }}; - const has_warning = [1]RawToken{.{ - .id = .macro_param_has_warning, - .source = .generated, - }}; - const has_feature = [1]RawToken{.{ - .id = .macro_param_has_feature, - .source = .generated, - }}; - const has_extension = [1]RawToken{.{ - .id = .macro_param_has_extension, - .source = .generated, - }}; - const has_builtin = [1]RawToken{.{ - .id = .macro_param_has_builtin, - .source = .generated, - }}; - const has_include = [1]RawToken{.{ - .id = .macro_param_has_include, - .source = .generated, - }}; - const has_include_next = [1]RawToken{.{ - .id = .macro_param_has_include_next, - .source = .generated, - }}; - const has_embed = [1]RawToken{.{ - .id = .macro_param_has_embed, - .source = .generated, - }}; - - const is_identifier = [1]RawToken{.{ - .id = .macro_param_is_identifier, - .source = .generated, - }}; - - const pragma_operator = [1]RawToken{.{ - .id = .macro_param_pragma_operator, - .source = .generated, - }}; - - const file = [1]RawToken{.{ - .id = .macro_file, - .source = .generated, - }}; - const line = [1]RawToken{.{ - .id = .macro_line, - .source = .generated, - }}; - const counter = [1]RawToken{.{ - .id = .macro_counter, - .source = .generated, - }}; -}; - -fn addBuiltinMacro(pp: *Preprocessor, name: []const u8, is_func: bool, tokens: []const RawToken) !void { +// `param_tok_id` is comptime so that the generated `tokens` list is unique for every macro. +fn addBuiltinMacro(pp: *Preprocessor, name: []const u8, is_func: bool, comptime param_tok_id: Token.Id) !void { try pp.defines.putNoClobber(pp.gpa, name, .{ - .params = &builtin_macros.args, - .tokens = tokens, + .params = &[1][]const u8{"X"}, + .tokens = &[1]RawToken{.{ + .id = param_tok_id, + .source = .generated, + }}, .var_args = false, .is_func = is_func, .loc = .{ .id = .generated }, @@ -266,22 +202,27 @@ fn addBuiltinMacro(pp: *Preprocessor, name: []const u8, is_func: bool, tokens: [ } pub fn addBuiltinMacros(pp: *Preprocessor) !void { - try pp.addBuiltinMacro("__has_attribute", true, &builtin_macros.has_attribute); - try pp.addBuiltinMacro("__has_c_attribute", true, &builtin_macros.has_c_attribute); - try pp.addBuiltinMacro("__has_declspec_attribute", true, &builtin_macros.has_declspec_attribute); - try pp.addBuiltinMacro("__has_warning", true, &builtin_macros.has_warning); - try pp.addBuiltinMacro("__has_feature", true, &builtin_macros.has_feature); - try pp.addBuiltinMacro("__has_extension", true, &builtin_macros.has_extension); - try pp.addBuiltinMacro("__has_builtin", true, &builtin_macros.has_builtin); - try pp.addBuiltinMacro("__has_include", true, &builtin_macros.has_include); - try pp.addBuiltinMacro("__has_include_next", true, &builtin_macros.has_include_next); - try pp.addBuiltinMacro("__has_embed", true, &builtin_macros.has_embed); - try pp.addBuiltinMacro("__is_identifier", true, &builtin_macros.is_identifier); - try pp.addBuiltinMacro("_Pragma", true, &builtin_macros.pragma_operator); - - try pp.addBuiltinMacro("__FILE__", false, &builtin_macros.file); - try pp.addBuiltinMacro("__LINE__", false, &builtin_macros.line); - try pp.addBuiltinMacro("__COUNTER__", false, &builtin_macros.counter); + try pp.addBuiltinMacro("__has_attribute", true, .macro_param_has_attribute); + try pp.addBuiltinMacro("__has_c_attribute", true, .macro_param_has_c_attribute); + try pp.addBuiltinMacro("__has_declspec_attribute", true, .macro_param_has_declspec_attribute); + try pp.addBuiltinMacro("__has_warning", true, .macro_param_has_warning); + try pp.addBuiltinMacro("__has_feature", true, .macro_param_has_feature); + try pp.addBuiltinMacro("__has_extension", true, .macro_param_has_extension); + try pp.addBuiltinMacro("__has_builtin", true, .macro_param_has_builtin); + try pp.addBuiltinMacro("__has_include", true, .macro_param_has_include); + try pp.addBuiltinMacro("__has_include_next", true, .macro_param_has_include_next); + try pp.addBuiltinMacro("__has_embed", true, .macro_param_has_embed); + try pp.addBuiltinMacro("__is_identifier", true, .macro_param_is_identifier); + try pp.addBuiltinMacro("_Pragma", true, .macro_param_pragma_operator); + + if (pp.comp.langopts.ms_extensions) { + try pp.addBuiltinMacro("__identifier", true, .macro_param_ms_identifier); + try pp.addBuiltinMacro("__pragma", true, .macro_param_ms_pragma); + } + + try pp.addBuiltinMacro("__FILE__", false, .macro_file); + try pp.addBuiltinMacro("__LINE__", false, .macro_line); + try pp.addBuiltinMacro("__COUNTER__", false, .macro_counter); } pub fn deinit(pp: *Preprocessor) void { @@ -1302,6 +1243,39 @@ fn pragmaOperator(pp: *Preprocessor, arg_tok: TokenWithExpansionLocs, operator_l try pp.pragma(&tmp_tokenizer, pragma_tok, operator_loc, arg_tok.expansionSlice()); } +/// Handle Microsoft __pragma operator +fn msPragmaOperator(pp: *Preprocessor, pragma_tok: TokenWithExpansionLocs, args: []const TokenWithExpansionLocs) !void { + if (args.len == 0) { + try pp.err(pragma_tok, .unknown_pragma, .{}); + return; + } + + { + var copy = try pragma_tok.dupe(pp.gpa); + copy.id = .keyword_pragma; + try pp.addToken(copy); + } + + const pragma_start: u32 = @intCast(pp.tokens.len); + for (args) |tok| { + switch (tok.id) { + .macro_ws, .comment => continue, + else => try pp.addToken(try tok.dupe(pp.gpa)), + } + } + try pp.addToken(.{ .id = .nl, .loc = .{ .id = .generated } }); + + const name = pp.expandedSlice(pp.tokens.get(pragma_start)); + if (pp.comp.getPragma(name)) |prag| unknown: { + return prag.preprocessorCB(pp, pragma_start) catch |er| switch (er) { + error.UnknownPragma => break :unknown, + else => |e| return e, + }; + } + + try pp.err(args[0], .unknown_pragma, .{}); +} + /// Inverts the output of the preprocessor stringify (#) operation /// (except all whitespace is condensed to a single space) /// writes output to pp.char_buf; assumes capacity is sufficient @@ -1521,7 +1495,7 @@ fn handleBuiltinMacro(pp: *Preprocessor, builtin: RawToken.Id, param_toks: []con }; if (identifier == null and invalid == null) invalid = .{ .id = .eof, .loc = src_loc }; if (invalid) |some| { - try pp.err(some, .builtin_missing_r_paren, .{}); + try pp.err(some, .builtin_missing_r_paren, .{"builtin feature-check macro"}); return false; } @@ -1862,29 +1836,63 @@ fn expandFuncMacro( try buf.append(try pp.makeGeneratedToken(start, .pp_num, tokFromRaw(raw))); }, .macro_param_pragma_operator => { - const param_toks = expanded_args.items[0]; // Clang and GCC require exactly one token (so, no parentheses or string pasting) // even though their error messages indicate otherwise. Ours is slightly more // descriptive. var invalid: ?TokenWithExpansionLocs = null; var string: ?TokenWithExpansionLocs = null; - for (param_toks) |tok| switch (tok.id) { - .string_literal => { - if (string) |_| invalid = tok else string = tok; - }, - .macro_ws => continue, - .comment => continue, - else => { - invalid = tok; - break; - }, - }; - if (string == null and invalid == null) invalid = .{ .loc = macro_tok.loc, .id = .eof }; + for (expanded_args.items[0]) |tok| { + switch (tok.id) { + .string_literal => { + if (string) |_| { + invalid = tok; + break; + } + string = tok; + }, + .macro_ws => continue, + .comment => continue, + else => { + invalid = tok; + break; + }, + } + } + if (string == null and invalid == null) invalid = macro_tok; if (invalid) |some| try pp.err(some, .pragma_operator_string_literal, .{}) else try pp.pragmaOperator(string.?, macro_tok.loc); }, + .macro_param_ms_identifier => blk: { + // Expect '__identifier' '(' macro-identifier ')' + var ident: ?TokenWithExpansionLocs = null; + for (expanded_args.items[0]) |tok| { + switch (tok.id) { + .macro_ws => continue, + .comment => continue, + else => {}, + } + if (ident) |_| { + try pp.err(tok, .builtin_missing_r_paren, .{"identifier"}); + break :blk; + } else if (tok.id.isMacroIdentifier()) { + ident = tok; + } else { + try pp.err(tok, .cannot_convert_to_identifier, .{tok.id.symbol()}); + break :blk; + } + } + if (ident) |*some| { + some.id = .identifier; + try buf.append(some.*); + } else { + try pp.err(macro_tok, .expected_identifier, .{}); + } + }, + .macro_param_ms_pragma => { + try pp.msPragmaOperator(macro_tok, expanded_args.items[0]); + }, .comma => { if (tok_i + 2 < func_macro.tokens.len and func_macro.tokens[tok_i + 1].id == .hash_hash) { const hash_hash = func_macro.tokens[tok_i + 1]; @@ -3050,10 +3058,10 @@ fn pragma(pp: *Preprocessor, tokenizer: *Tokenizer, pragma_tok: RawToken, operat const name_tok = tokenizer.nextNoWS(); if (name_tok.id == .nl or name_tok.id == .eof) return; - const name = pp.tokSlice(name_tok); try pp.addToken(try pp.makePragmaToken(pragma_tok, operator_loc, arg_locs)); const pragma_start: u32 = @intCast(pp.tokens.len); + const name = pp.tokSlice(name_tok); const pragma_name_tok = try pp.makePragmaToken(name_tok, operator_loc, arg_locs); try pp.addToken(pragma_name_tok); while (true) { diff --git a/src/aro/Preprocessor/Diagnostic.zig b/src/aro/Preprocessor/Diagnostic.zig index d676140..eb18566 100644 --- a/src/aro/Preprocessor/Diagnostic.zig +++ b/src/aro/Preprocessor/Diagnostic.zig @@ -204,6 +204,13 @@ pub const unterminated_char_literal_warning: Diagnostic = .{ .extension = true, }; +pub const unterminated_string_literal_warning: Diagnostic = .{ + .fmt = "missing terminating '\"' character", + .kind = .warning, + .opt = .@"invalid-pp-token", + .extension = true, +}; + pub const unterminated_comment: Diagnostic = .{ .fmt = "unterminated comment", .kind = .@"error", @@ -409,13 +416,16 @@ pub const expected_str_literal_in: Diagnostic = .{ }; pub const builtin_missing_r_paren: Diagnostic = .{ - .fmt = "missing ')', after builtin feature-check macro", + .fmt = "missing ')', after {s}", .kind = .@"error", }; -pub const unterminated_string_literal_warning: Diagnostic = .{ - .fmt = "missing terminating '\"' character", - .kind = .warning, - .opt = .@"invalid-pp-token", - .extension = true, +pub const cannot_convert_to_identifier: Diagnostic = .{ + .fmt = "cannot convert {s} to an identifier", + .kind = .@"error", +}; + +pub const expected_identifier: Diagnostic = .{ + .fmt = "expected identifier argument", + .kind = .@"error", }; diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 2122f59..11a67cd 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -148,6 +148,10 @@ pub const Token = struct { macro_counter, /// Special token for implementing _Pragma macro_param_pragma_operator, + /// Special token for implementing __identifier (MS extension) + macro_param_ms_identifier, + /// Special token for implementing __pragma (MS extension) + macro_param_ms_pragma, /// Special identifier for implementing __func__ macro_func, @@ -577,6 +581,8 @@ pub const Token = struct { .macro_line, .macro_counter, .macro_param_pragma_operator, + .macro_param_ms_identifier, + .macro_param_ms_pragma, .placemarker, => "", .macro_ws => " ", @@ -805,7 +811,7 @@ pub const Token = struct { .unterminated_char_literal, .empty_char_literal, => "a character literal", - .pp_num, .embed_byte => "A number", + .pp_num, .embed_byte => "a number", else => id.lexeme().?, }; } diff --git a/test/cases/msvc macros.c b/test/cases/msvc macros.c new file mode 100644 index 0000000..257cf20 --- /dev/null +++ b/test/cases/msvc macros.c @@ -0,0 +1,17 @@ +//aro-args --target=x86_64-windows-msvc +__pragma(once) +#define PRAGMA(x) __pragma(x) + +PRAGMA(message "foo") + +int __identifier(int); + +__identifier() +__identifier(1) +__identifier(a b) + +#define EXPECTED_ERRORS \ + "msvc macros.c:5:1: note: #pragma message: foo" \ + "msvc macros.c:9:1: error: expected identifier argument" \ + "msvc macros.c:10:14: error: cannot convert a number to an identifier" \ + "msvc macros.c:11:16: error: missing ')', after identifier" \ -- Gitee From fda783f97db178a0cdb5bfdfb41375b3d9db23ef Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 24 Apr 2025 16:26:00 +0300 Subject: [PATCH 024/117] build: add test filter option --- build.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 4e71a64..f344e6c 100644 --- a/build.zig +++ b/build.zig @@ -58,6 +58,7 @@ pub fn build(b: *Build) !void { const tracy_allocation = b.option(bool, "tracy-allocation", "Include allocation information with Tracy data. Does nothing if -Dtracy is not provided") orelse false; const use_llvm = b.option(bool, "llvm", "Use LLVM backend to generate aro executable"); const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false; + const test_filter = b.option([]const []const u8, "test-filter", "Test filter for unit tests") orelse &.{}; const system_defaults = b.addOptions(); system_defaults.addOption(bool, "enable_linker_build_id", enable_linker_build_id); @@ -218,7 +219,10 @@ pub fn build(b: *Build) !void { } const unit_tests_step = step: { - var unit_tests = b.addTest(.{ .root_source_file = b.path("src/aro.zig") }); + var unit_tests = b.addTest(.{ + .root_source_file = b.path("src/aro.zig"), + .filters = test_filter, + }); for (aro_module.import_table.keys(), aro_module.import_table.values()) |name, module| { unit_tests.root_module.addImport(name, module); } -- Gitee From 8ac527b8b0036dcc98ed5443c9bc50946cdf073e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 24 Apr 2025 22:02:17 +0300 Subject: [PATCH 025/117] Pragma: preserve tokens by default --- src/aro/Pragma.zig | 2 +- src/aro/pragmas/once.zig | 5 +++++ src/aro/pragmas/pack.zig | 7 ------- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/aro/Pragma.zig b/src/aro/Pragma.zig index c42e1e7..a54e139 100644 --- a/src/aro/Pragma.zig +++ b/src/aro/Pragma.zig @@ -71,7 +71,7 @@ pub fn pasteTokens(pp: *Preprocessor, start_idx: TokenIndex) ![]const u8 { pub fn shouldPreserveTokens(self: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) bool { if (self.preserveTokens) |func| return func(self, pp, start_idx); - return false; + return true; } pub fn preprocessorCB(self: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Error!void { diff --git a/src/aro/pragmas/once.zig b/src/aro/pragmas/once.zig index 1a2f989..da99c78 100644 --- a/src/aro/pragmas/once.zig +++ b/src/aro/pragmas/once.zig @@ -15,6 +15,7 @@ pragma: Pragma = .{ .afterParse = afterParse, .deinit = deinit, .preprocessorHandler = preprocessorHandler, + .preserveTokens = preserveTokens, }, pragma_once: std.AutoHashMap(Source.Id, void), preprocess_count: u32 = 0, @@ -58,3 +59,7 @@ fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex } self.preprocess_count = pp.preprocess_count; } + +fn preserveTokens(_: *Pragma, _: *Preprocessor, _: TokenIndex) bool { + return false; +} diff --git a/src/aro/pragmas/pack.zig b/src/aro/pragmas/pack.zig index ef805b4..6f96372 100644 --- a/src/aro/pragmas/pack.zig +++ b/src/aro/pragmas/pack.zig @@ -14,7 +14,6 @@ const Pack = @This(); pragma: Pragma = .{ .deinit = deinit, .parserHandler = parserHandler, - .preserveTokens = preserveTokens, }, stack: std.ArrayListUnmanaged(struct { label: []const u8, val: u8 }) = .{}, @@ -156,9 +155,3 @@ fn pop(pack: *Pack, p: *Parser, maybe_label: ?[]const u8) void { p.pragma_pack = prev.val; } } - -fn preserveTokens(_: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) bool { - _ = pp; - _ = start_idx; - return true; -} -- Gitee From 257128cca805f15b3eb3832e70366053a5de1d2a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 25 Apr 2025 21:55:39 +0300 Subject: [PATCH 026/117] Parser: update value stored for enum_field node if needed --- src/aro/Parser.zig | 6 +- test/cases/ast/enum sizes linux.c | 213 ++++++++++++++++++++++++++++++ test/cases/enum sizes linux.c | 6 + 3 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 test/cases/ast/enum sizes linux.c diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 0a16716..a92f16a 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -2912,6 +2912,7 @@ fn enumSpec(p: *Parser) Error!QualType { const symbol = p.syms.getPtr(field.name, .vars); _ = try symbol.val.intCast(dest_ty, p.comp); + try p.tree.value_map.put(p.gpa, field_node, symbol.val); symbol.qt = dest_ty; field.qt = dest_ty; @@ -8717,11 +8718,14 @@ fn primaryExpr(p: *Parser) Error!?Result { .qt = sym.qt, .decl = sym.node.unpack().?, } }); - return .{ + + const res: Result = .{ .val = if (p.const_decl_folding == .no_const_decl_folding and sym.kind != .enumeration) Value{} else sym.val, .qt = sym.qt, .node = node, }; + try res.putValue(p); + return res; } // Check if this is a builtin call. diff --git a/test/cases/ast/enum sizes linux.c b/test/cases/ast/enum sizes linux.c new file mode 100644 index 0000000..319b50f --- /dev/null +++ b/test/cases/ast/enum sizes linux.c @@ -0,0 +1,213 @@ +implicit typedef: '__int128' + name: __int128_t + +implicit typedef: 'unsigned __int128' + name: __uint128_t + +implicit typedef: '*char' + name: __builtin_ms_va_list + +implicit typedef: '[1]struct __va_list_tag' + name: __builtin_va_list + +implicit typedef: 'struct __NSConstantString_tag' + name: __NSConstantString + +implicit typedef: 'long double' + name: __float80 + +enum_decl: 'attributed(enum Small: unsigned char)' + attr: packed + enum_field: 'int' (value: 0) + name: A + +static_assert + condition: + implicit cast: (int_to_bool) '_Bool' + equal_expr: 'int' (value: 1) + lhs: + sizeof_expr: 'unsigned long' + operand type: attributed(enum Small: unsigned char) + rhs: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'int' (value: 1) + diagnostic: + string_literal_expr: '[6]char' lvalue (value: "Small") + +enum_decl: 'attributed(enum StillSmall: unsigned char)' + attr: packed + enum_field: 'int' (value: 255) + name: B + init: + int_literal: 'int' (value: 255) + +static_assert + condition: + implicit cast: (int_to_bool) '_Bool' + equal_expr: 'int' (value: 1) + lhs: + sizeof_expr: 'unsigned long' + operand type: attributed(enum StillSmall: unsigned char) + rhs: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'int' (value: 1) + diagnostic: + string_literal_expr: '[11]char' lvalue (value: "StillSmall") + +enum_decl: 'attributed(enum Medium: unsigned short)' + attr: packed + enum_field: 'int' (value: 255) + name: C + init: + int_literal: 'int' (value: 255) + + enum_field: 'int' (value: 256) + name: D + +static_assert + condition: + implicit cast: (int_to_bool) '_Bool' + equal_expr: 'int' (value: 1) + lhs: + sizeof_expr: 'unsigned long' + operand type: attributed(enum Medium: unsigned short) + rhs: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'int' (value: 2) + diagnostic: + string_literal_expr: '[7]char' lvalue (value: "Medium") + +enum_decl: 'attributed(enum StillMedium: short)' + attr: packed + enum_field: 'int' (value: -32768) + name: E + init: + negate_expr: 'int' (value: -32768) + operand: + int_literal: 'int' (value: 32768) + + enum_field: 'int' (value: 32767) + name: F + init: + int_literal: 'int' (value: 32767) + +static_assert + condition: + implicit cast: (int_to_bool) '_Bool' + equal_expr: 'int' (value: 1) + lhs: + sizeof_expr: 'unsigned long' + operand type: attributed(enum StillMedium: short) + rhs: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'int' (value: 2) + diagnostic: + string_literal_expr: '[12]char' lvalue (value: "StillMedium") + +enum_decl: 'enum Normal: int' + enum_field: 'int' (value: -2147483648) + name: G + init: + implicit cast: (int_cast) 'int' + negate_expr: 'long' (value: -2147483648) + operand: + int_literal: 'long' (value: 2147483648) + + enum_field: 'int' (value: 2147483647) + name: H + init: + int_literal: 'int' (value: 2147483647) + +static_assert + condition: + implicit cast: (int_to_bool) '_Bool' + equal_expr: 'int' (value: 1) + lhs: + sizeof_expr: 'unsigned long' + operand type: enum Normal: int + rhs: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'int' (value: 4) + diagnostic: + string_literal_expr: '[7]char' lvalue (value: "Normal") + +enum_decl: 'enum Unsigned: unsigned int' + enum_field: 'unsigned int' (value: 4294967295) + name: I + init: + implicit cast: (int_cast) 'unsigned int' + int_literal: 'long' (value: 4294967295) + +static_assert + condition: + implicit cast: (int_to_bool) '_Bool' + equal_expr: 'int' (value: 1) + lhs: + sizeof_expr: 'unsigned long' + operand type: enum Unsigned: unsigned int + rhs: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'int' (value: 4) + diagnostic: + string_literal_expr: '[9]char' lvalue (value: "Unsigned") + +enum_decl: 'enum Large: long' + enum_field: 'int' (value: -1) + name: J + init: + negate_expr: 'int' (value: -1) + operand: + int_literal: 'int' (value: 1) + + enum_field: 'long' (value: 4294967295) + name: K + init: + int_literal: 'long' (value: 4294967295) + +static_assert + condition: + implicit cast: (int_to_bool) '_Bool' + equal_expr: 'int' (value: 1) + lhs: + sizeof_expr: 'unsigned long' + operand type: enum Large: long + rhs: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'int' (value: 8) + diagnostic: + string_literal_expr: '[6]char' lvalue (value: "Large") + +enum_decl: 'enum Huge: unsigned long' + enum_field: 'unsigned long' (value: 18446744073709551615) + name: L + init: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'unsigned long long' (value: 18446744073709551615) + +static_assert + condition: + implicit cast: (int_to_bool) '_Bool' + equal_expr: 'int' (value: 1) + lhs: + sizeof_expr: 'unsigned long' + operand type: enum Huge: unsigned long + rhs: + implicit cast: (int_cast) 'unsigned long' + int_literal: 'int' (value: 8) + diagnostic: + string_literal_expr: '[5]char' lvalue (value: "Huge") + +enum_decl: 'enum EnumWithInits: long long' + enum_field: 'int' (value: -2) + name: Negative + init: + negate_expr: 'int' (value: -2) + operand: + int_literal: 'int' (value: 2) + + enum_field: 'long long' (value: -1) + name: Positive + init: + implicit cast: (int_cast) 'long long' + int_literal: 'unsigned long' (value: 18446744073709551615) + diff --git a/test/cases/enum sizes linux.c b/test/cases/enum sizes linux.c index 51875d8..e660b01 100644 --- a/test/cases/enum sizes linux.c +++ b/test/cases/enum sizes linux.c @@ -47,3 +47,9 @@ enum Huge { L = 18446744073709551615ULL }; _Static_assert(sizeof(enum Huge) == 8, "Huge"); + +#pragma GCC diagnostic ignored "-Wenum-too-large" +enum EnumWithInits { + Negative = -2, + Positive = 0xFFFFFFFFFFFFFFFF, +}; -- Gitee From 79d66eeec6e926cd2406b995a1a863e3e9388e8f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 25 Apr 2025 23:34:10 +0300 Subject: [PATCH 027/117] TypeStore: check qt more carefully in `finishQuals` --- src/aro/Parser/Diagnostic.zig | 2 +- src/aro/TypeStore.zig | 21 +++++++++++++++++++-- test/cases/ast/c23 auto.c | 5 +++++ test/cases/ast/nullability.c | 8 ++++++++ test/cases/c23 auto.c | 2 ++ test/cases/nullability.c | 5 +++++ 6 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 429a566..9ec2303 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -133,7 +133,7 @@ pub const duplicate_decl_spec: Diagnostic = .{ }; pub const restrict_non_pointer: Diagnostic = .{ - .fmt = "restrict requires a pointer or reference ('{s}' is invalid)", + .fmt = "restrict requires a pointer or reference ('{qt}' is invalid)", .kind = .@"error", }; diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 8f34625..92ddb9e 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -1144,11 +1144,19 @@ pub const QualType = packed struct(u32) { } pub fn print(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { + if (qt.isC23Auto()) { + try w.writeAll("auto"); + return; + } _ = try qt.printPrologue(comp, w); try qt.printEpilogue(comp, w); } pub fn printNamed(qt: QualType, name: []const u8, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { + if (qt.isC23Auto()) { + try w.print("auto {s}", .{name}); + return; + } const simple = try qt.printPrologue(comp, w); if (simple) try w.writeByte(' '); try w.writeAll(name); @@ -2470,7 +2478,10 @@ pub const Builder = struct { } } - if (b.unaligned != null and !qt.isPointer(b.parser.comp)) { + // We can't use `qt.isPointer()` because `qt` might contain a `.declarator_combine`. + const is_pointer = qt.isAutoType() or qt.isC23Auto() or qt.base(b.parser.comp).type == .pointer; + + if (b.unaligned != null and !is_pointer) { result_qt = (try b.parser.comp.type_store.put(b.parser.gpa, .{ .attributed = .{ .base = result_qt, .attributes = &.{.{ .tag = .unaligned, .args = .{ .unaligned = .{} }, .syntax = .keyword }}, @@ -2482,7 +2493,8 @@ pub const Builder = struct { .nullable, .nullable_result, .null_unspecified, - => |tok| if (!qt.isPointer(b.parser.comp)) { + => |tok| if (!is_pointer) { + // TODO this should be checked later so that auto types can be properly validated. try b.parser.err(tok, .invalid_nullability, .{qt}); }, } @@ -2491,6 +2503,11 @@ pub const Builder = struct { if (b.@"volatile" != null) result_qt.@"volatile" = true; if (b.restrict) |restrict_tok| { + if (result_qt.isAutoType()) return b.parser.todo("restrict __auto_type"); + if (result_qt.isC23Auto()) { + try b.parser.err(restrict_tok, .restrict_non_pointer, .{qt}); + return result_qt; + } switch (qt.base(b.parser.comp).type) { .array, .pointer => result_qt.restrict = true, else => { diff --git a/test/cases/ast/c23 auto.c b/test/cases/ast/c23 auto.c index d384c20..9afd7d8 100644 --- a/test/cases/ast/c23 auto.c +++ b/test/cases/ast/c23 auto.c @@ -54,6 +54,11 @@ fn_def: 'fn () void' init: implicit default_init_expr: 'invalid' + variable: 'int' + name: g + init: + int_literal: 'int' (value: 1) + implicit return_stmt: 'void' fn_def: 'fn () void' diff --git a/test/cases/ast/nullability.c b/test/cases/ast/nullability.c index 506f40c..aaa0a6e 100644 --- a/test/cases/ast/nullability.c +++ b/test/cases/ast/nullability.c @@ -36,3 +36,11 @@ fn_proto: 'attributed(fn () *int)' attr: nullability kind: unspecified name: e +struct_decl: 'struct __sFILE' + record_field: '*fn (*void) int' + name: _close + field attr: nullability kind: nullable + +typedef: 'struct __sFILE' + name: FILE + diff --git a/test/cases/c23 auto.c b/test/cases/c23 auto.c index 63dd51e..df299ea 100644 --- a/test/cases/c23 auto.c +++ b/test/cases/c23 auto.c @@ -6,6 +6,7 @@ void bad() { auto b = 1, c = 2, d = 3; auto e[] = ""; auto f = {1}; + restrict auto g = 1; } void good() { @@ -20,3 +21,4 @@ void good() { "c23 auto.c:6:5: error: 'auto' can only be used with a single declarator" \ "c23 auto.c:7:5: error: 'e' declared as array of 'auto'" \ "c23 auto.c:8:14: error: cannot use 'auto' with array" \ + "c23 auto.c:9:5: error: restrict requires a pointer or reference ('auto' is invalid)" \ diff --git a/test/cases/nullability.c b/test/cases/nullability.c index ef0fc8b..05e08c3 100644 --- a/test/cases/nullability.c +++ b/test/cases/nullability.c @@ -6,6 +6,11 @@ int _Nullable d(void); #pragma GCC diagnostic warning "-Wnullability-extension" int *_Null_unspecified e(void); +#pragma GCC diagnostic pop + +typedef struct __sFILE { + int (* _Nullable _close)(void *); +} FILE; #define EXPECTED_ERRORS \ "nullability.c:2:15: warning: duplicate nullability specifier '_Nonnull' [-Wnullability]" \ -- Gitee From 160be9f1473c06b239d8b597cae95d9ba53744eb Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Apr 2025 00:23:27 +0300 Subject: [PATCH 028/117] Parser: accept any macro identifier as attribute name token --- src/aro/Parser.zig | 8 +++++--- test/cases/attributes.c | 5 ++++- test/cases/declspec.c | 6 +++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index a92f16a..9cd8b99 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -1738,10 +1738,12 @@ const InitDeclarator = struct { d: Declarator, initializer: ?Result = null }; /// | attrIdentifier '(' (expr (',' expr)*)? ')' fn attribute(p: *Parser, kind: Attribute.Kind, namespace: ?[]const u8) Error!?TentativeAttribute { const name_tok = p.tok_i; - switch (p.tok_ids[p.tok_i]) { - .keyword_const, .keyword_const1, .keyword_const2 => p.tok_i += 1, - else => _ = try p.expectIdentifier(), + if (!p.tok_ids[p.tok_i].isMacroIdentifier()) { + return p.errExpectedToken(.identifier, p.tok_ids[p.tok_i]); } + _ = (try p.eatIdentifier()) orelse { + p.tok_i += 1; + }; const name = p.tokSlice(name_tok); const attr = Attribute.fromString(kind, namespace, name) orelse { diff --git a/test/cases/attributes.c b/test/cases/attributes.c index 2363b8e..fb533c8 100644 --- a/test/cases/attributes.c +++ b/test/cases/attributes.c @@ -109,6 +109,8 @@ _Static_assert(sizeof(aligned_arr) == 3, ""); __attribute__((section(1))) int Z; +__attribute__((void)) int a; + __attribute__(()) // test attribute at eof #define TESTS_SKIPPED 1 @@ -122,4 +124,5 @@ __attribute__(()) // test attribute at eof "attributes.c:40:20: error: 'noreturn' attribute cannot be applied to a statement" \ "attributes.c:76:6: error: cannot call non function type 'int'" \ "attributes.c:110:24: error: Attribute argument is invalid, expected a string but got an integer constant" \ - "attributes.c:112:18: error: expected identifier or '('" \ + "attributes.c:112:16: warning: unknown attribute 'void' ignored [-Wunknown-attributes]" \ + "attributes.c:114:18: error: expected identifier or '('" \ diff --git a/test/cases/declspec.c b/test/cases/declspec.c index 9e1a08a..bd6cbff 100644 --- a/test/cases/declspec.c +++ b/test/cases/declspec.c @@ -21,6 +21,10 @@ typedef __declspec(align(8)) int Int2; _Static_assert(_Alignof(Int2) == 8, ""); +__declspec(restrict) int *qux(void); // TODO should be allowed + +#define TESTS_SKIPPED 1 #define EXPECTED_ERRORS "declspec.c:7:12: warning: __declspec attribute 'aligned' is not supported [-Wignored-attributes]" \ "declspec.c:19:18: error: 'declspec' attribute not allowed after declarator" \ - "declspec.c:19:13: note: this declarator" + "declspec.c:19:13: note: this declarator" \ + "declspec.c:24:12: warning: attribute 'restrict' ignored on functions [-Wignored-attributes]" \ -- Gitee From aec84bf3327905c223c19da1cf915cf4eb61aca7 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Apr 2025 00:41:23 +0300 Subject: [PATCH 029/117] Compilation: add some MSVC specific defines --- src/aro/Compilation.zig | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 04700ab..adc062a 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -315,8 +315,27 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { try define(w, "__amd64"); try define(w, "__x86_64__"); try define(w, "__x86_64"); + + if (comp.target.os.tag == .windows and comp.target.abi == .msvc) { + try w.writeAll( + \\#define _M_X64 100 + \\#define _M_AMD64 100 + \\ + ); + } + }, + .x86 => { + try defineStd(w, "i386", is_gnu); + + if (comp.target.os.tag == .windows and comp.target.abi == .msvc) { + try w.print("#define _M_IX86 {d}\n", .{blk: { + if (comp.target.cpu.model == &std.Target.x86.cpu.i386) break :blk 300; + if (comp.target.cpu.model == &std.Target.x86.cpu.i486) break :blk 400; + if (comp.target.cpu.model == &std.Target.x86.cpu.i586) break :blk 500; + break :blk @as(u32, 600); + }}); + } }, - .x86 => try defineStd(w, "i386", is_gnu), .mips, .mipsel, .mips64, @@ -384,6 +403,9 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { try define(w, "__arm64"); try define(w, "__arm64__"); } + if (comp.target.os.tag == .windows and comp.target.abi == .msvc) { + try w.writeAll("#define _M_ARM64 100\n"); + } }, .msp430 => { try define(w, "MSP430"); -- Gitee From 281422249d4880f1f35f0c18eac06052a0790f1d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Apr 2025 17:03:23 +0300 Subject: [PATCH 030/117] Compilation: add some more mingw/cygwin defines --- src/aro/Compilation.zig | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index adc062a..71d6f3e 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -261,14 +261,42 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { try define(w, "_WIN64"); } - if (comp.target.isMinGW()) { + if (comp.target.abi.isGnu()) { try defineStd(w, "WIN32", is_gnu); try defineStd(w, "WINNT", is_gnu); if (ptr_width == 64) { try defineStd(w, "WIN64", is_gnu); + try define(w, "__MINGW64__"); } try define(w, "__MSVCRT__"); try define(w, "__MINGW32__"); + } else if (comp.target.abi == .cygnus) { + try define(w, "__CYGWIN__"); + if (ptr_width == 64) { + try define(w, "__CYGWIN64__"); + } else { + try define(w, "__CYGWIN32__"); + } + } + + if (comp.target.abi.isGnu() or comp.target.abi == .cygnus) { + // MinGW and Cygwin define __declspec(a) to __attribute((a)). + // Like Clang we make the define no op if -fdeclspec is enabled. + if (comp.langopts.declspec_attrs) { + try w.writeAll("#define __declspec __declspec\n"); + } else { + try w.writeAll("#define __declspec(a) __attribute__((a))\n"); + } + if (!comp.langopts.ms_extensions) { + // Provide aliases for the calling convention keywords. + for ([_][]const u8{ "cdecl", "stdcall", "fastcall", "thiscall" }) |keyword| { + try w.print( + \\#define _{[0]s} __attribute__((__{[0]s}__)) + \\#define __{[0]s} __attribute__((__{[0]s}__)) + \\ + , .{keyword}); + } + } } }, .uefi => try define(w, "__UEFI__"), @@ -302,6 +330,9 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { .aix, .emscripten, => try defineStd(w, "unix", is_gnu), + .windows => if (comp.target.abi.isGnu() or comp.target.abi == .cygnus) { + try defineStd(w, "unix", is_gnu); + }, else => {}, } if (comp.target.abi.isAndroid()) { -- Gitee From 213269d0fb2c119777e911c071e44db1583adac0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Apr 2025 17:04:12 +0300 Subject: [PATCH 031/117] Parser: allow attribute specifiers in typeQual --- src/aro/Parser.zig | 5 +++-- src/aro/Parser/Diagnostic.zig | 7 ++++--- test/cases/attribute errors.c | 8 ++++---- test/cases/attributes.c | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 9cd8b99..e43bbc6 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3162,10 +3162,11 @@ fn enumerator(p: *Parser, e: *Enumerator) Error!?EnumFieldAndNode { } /// typeQual : keyword_const | keyword_restrict | keyword_volatile | keyword_atomic -fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_cc_attr: bool) Error!bool { +fn typeQual(p: *Parser, b: *TypeStore.Builder, allow_attr: bool) Error!bool { var any = false; while (true) { - if (allow_cc_attr and try p.msTypeAttribute()) continue; + if (allow_attr and try p.msTypeAttribute()) continue; + if (allow_attr) try p.attributeSpecifier(); switch (p.tok_ids[p.tok_i]) { .keyword_restrict, .keyword_restrict1, .keyword_restrict2 => { if (b.restrict != null) diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 9ec2303..bef0af9 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -1434,13 +1434,14 @@ pub const attribute_too_many_args: Diagnostic = .{ }; pub const attribute_arg_invalid: Diagnostic = .{ - .fmt = "Attribute argument is invalid, expected {s} but got {s}", + .fmt = "attribute argument is invalid, expected {s} but got {s}", .kind = .@"error", }; pub const unknown_attr_enum: Diagnostic = .{ - .fmt = "Unknown `{s}` argument. Possible values are: {s}", - .kind = .@"error", + .fmt = "unknown `{s}` argument. Possible values are: {s}", + .kind = .warning, + .opt = .@"ignored-attributes", }; pub const attribute_requires_identifier: Diagnostic = .{ diff --git a/test/cases/attribute errors.c b/test/cases/attribute errors.c index 1b7cdab..ec510f5 100644 --- a/test/cases/attribute errors.c +++ b/test/cases/attribute errors.c @@ -33,7 +33,7 @@ int big __attribute__((vector_size(4294967296))); typedef float f2v __attribute__((vector_size(8.0i))); #define EXPECTED_ERRORS "attribute errors.c:4:24: error: 'access' attribute takes at least 2 argument(s)" \ - "attribute errors.c:5:31: error: Unknown `access` argument. Possible values are: 'read_only', 'read_write', 'write_only', 'none'" \ + "attribute errors.c:5:31: warning: unknown `access` argument. Possible values are: 'read_only', 'read_write', 'write_only', 'none' [-Wignored-attributes]" \ "attribute errors.c:6:24: warning: attribute 'access' ignored on variables [-Wignored-attributes]" \ "attribute errors.c:7:30: error: use of undeclared identifier 'bar'" \ "attribute errors.c:11:35: error: 'aligned' attribute takes at most 1 argument(s)" \ @@ -43,15 +43,15 @@ typedef float f2v __attribute__((vector_size(8.0i))); "attribute errors.c:15:24: warning: attribute 'assume_aligned' ignored on variables [-Wignored-attributes]" \ "attribute errors.c:16:24: warning: attribute 'hot' ignored on variables [-Wignored-attributes]" \ "attribute errors.c:16:29: warning: attribute 'pure' ignored on variables [-Wignored-attributes]" \ - "attribute errors.c:18:32: error: Attribute argument is invalid, expected an identifier but got a string" \ + "attribute errors.c:18:32: error: attribute argument is invalid, expected an identifier but got a string" \ "attribute errors.c:19:24: warning: attribute 'simd' ignored on variables [-Wignored-attributes]" \ "attribute errors.c:20:24: warning: attribute 'simd' ignored on variables [-Wignored-attributes]" \ - "attribute errors.c:21:29: error: Unknown `simd` argument. Possible values are: \"notinbranch\", \"inbranch\"" \ + "attribute errors.c:21:29: warning: unknown `simd` argument. Possible values are: \"notinbranch\", \"inbranch\" [-Wignored-attributes]" \ "attribute errors.c:22:24: warning: unknown attribute 'invalid_attribute' ignored [-Wunknown-attributes]" \ "attribute errors.c:23:24: warning: unknown attribute 'invalid_attribute' ignored [-Wunknown-attributes]" \ "attribute errors.c:24:49: error: 'deprecated' attribute takes at most 1 argument(s)" \ "attribute errors.c:28:24: warning: attribute 'cold' ignored on fields [-Wignored-attributes]" \ "attribute errors.c:31:5: warning: '__thiscall' calling convention is not supported for this target [-Wignored-attributes]" \ "attribute errors.c:32:36: error: attribute value '4294967296' out of range" \ - "attribute errors.c:33:46: error: Attribute argument is invalid, expected an integer constant but got a complex floating point number" \ + "attribute errors.c:33:46: error: attribute argument is invalid, expected an integer constant but got a complex floating point number" \ diff --git a/test/cases/attributes.c b/test/cases/attributes.c index fb533c8..e8a9487 100644 --- a/test/cases/attributes.c +++ b/test/cases/attributes.c @@ -123,6 +123,6 @@ __attribute__(()) // test attribute at eof "attributes.c:36:5: error: fallthrough annotation does not directly precede switch label" \ "attributes.c:40:20: error: 'noreturn' attribute cannot be applied to a statement" \ "attributes.c:76:6: error: cannot call non function type 'int'" \ - "attributes.c:110:24: error: Attribute argument is invalid, expected a string but got an integer constant" \ + "attributes.c:110:24: error: attribute argument is invalid, expected a string but got an integer constant" \ "attributes.c:112:16: warning: unknown attribute 'void' ignored [-Wunknown-attributes]" \ "attributes.c:114:18: error: expected identifier or '('" \ -- Gitee From 19542054c6b22dfb876deb1881b0be97624be379 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Apr 2025 17:59:20 +0300 Subject: [PATCH 032/117] Preprocessor: do not warn about macro in hiding a keyword --- src/aro/Preprocessor.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 2fe9259..d2701f5 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -2515,7 +2515,10 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! macro_name_token_id.simplifyMacroKeyword(); switch (macro_name_token_id) { .identifier, .extended_identifier => {}, - else => if (macro_name_token_id.isMacroIdentifier()) { + // TODO allow #define and #define extern|inline|static|const + else => if (macro_name_token_id.isMacroIdentifier() and + !mem.eql(u8, pp.comp.getSource(tokenizer.source).path, "")) + { try pp.err(macro_name, .keyword_macro, .{}); }, } -- Gitee From 30b59c63ce99350d464f715deeedb78a2789b7e9 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Apr 2025 18:10:56 +0300 Subject: [PATCH 033/117] Parser: allow GNU attributes at the start of a paren declarator --- src/aro/Parser.zig | 3 +++ test/cases/attributes.c | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index e43bbc6..df74373 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3429,6 +3429,9 @@ fn declarator( try d.validate(p, combine_tok); return d; } else if (p.eatToken(.l_paren)) |l_paren| blk: { + // C23 and declspec attributes are not allowed here + while (try p.gnuAttribute()) {} + // Parse Microsoft keyword type attributes. _ = try p.msTypeAttribute(); diff --git a/test/cases/attributes.c b/test/cases/attributes.c index e8a9487..c4bccce 100644 --- a/test/cases/attributes.c +++ b/test/cases/attributes.c @@ -111,6 +111,8 @@ __attribute__((section(1))) int Z; __attribute__((void)) int a; +int (__attribute__((aligned)) a); + __attribute__(()) // test attribute at eof #define TESTS_SKIPPED 1 @@ -125,4 +127,4 @@ __attribute__(()) // test attribute at eof "attributes.c:76:6: error: cannot call non function type 'int'" \ "attributes.c:110:24: error: attribute argument is invalid, expected a string but got an integer constant" \ "attributes.c:112:16: warning: unknown attribute 'void' ignored [-Wunknown-attributes]" \ - "attributes.c:114:18: error: expected identifier or '('" \ + "attributes.c:116:18: error: expected identifier or '('" \ -- Gitee From 84031788034196ab2be4857f99c7eeaf478c149a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 26 Apr 2025 18:15:13 +0300 Subject: [PATCH 034/117] add target to declspec tests Follow up to aec84bf3327905c223c19da1cf915cf4eb61aca7 --- test/cases/declspec.c | 2 +- test/cases/no declspec.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/cases/declspec.c b/test/cases/declspec.c index bd6cbff..95f1c2c 100644 --- a/test/cases/declspec.c +++ b/test/cases/declspec.c @@ -1,4 +1,4 @@ -//aro-args -fdeclspec +//aro-args -fdeclspec --target=x86_64-linux #pragma GCC diagnostic ignored "-Wgnu-alignof-expression" diff --git a/test/cases/no declspec.c b/test/cases/no declspec.c index 720a2c6..47e8c56 100644 --- a/test/cases/no declspec.c +++ b/test/cases/no declspec.c @@ -1,3 +1,4 @@ +//aro-args -fno-declspec --target=x86_64-linux __declspec(align(4)) int foo; #if __has_declspec_attribute(noreturn) @@ -9,4 +10,4 @@ __declspec(align(4)) int foo; #endif #define EXPECTED_ERRORS \ - "no declspec.c:1:1: error: '__declspec' attributes are not enabled; use '-fdeclspec' or '-fms-extensions' to enable support for __declspec attributes" \ + "no declspec.c:2:1: error: '__declspec' attributes are not enabled; use '-fdeclspec' or '-fms-extensions' to enable support for __declspec attributes" \ -- Gitee From d749c2c0dd86b0c1527059d747c53eee70fc9412 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sun, 12 Jan 2025 15:01:58 -0800 Subject: [PATCH 035/117] Preprocessor: add support for UCN identifiers Closes #823 --- src/aro/Preprocessor.zig | 115 +++++++++++++++++++++++++++- src/aro/Preprocessor/Diagnostic.zig | 16 ++++ src/aro/Tokenizer.zig | 111 ++++++++++++++++++++++++++- src/aro/ucn.zig | 97 +++++++++++++++++++++++ test/cases/ucn identifiers.c | 28 +++++++ 5 files changed, 363 insertions(+), 4 deletions(-) create mode 100644 src/aro/ucn.zig create mode 100644 test/cases/ucn identifiers.c diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index d2701f5..eaa292e 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -11,6 +11,7 @@ const features = @import("features.zig"); const Hideset = @import("Hideset.zig"); const Parser = @import("Parser.zig"); const Source = @import("Source.zig"); +const text_literal = @import("text_literal.zig"); const Tokenizer = @import("Tokenizer.zig"); const RawToken = Tokenizer.Token; const Tree = @import("Tree.zig"); @@ -946,7 +947,7 @@ fn expr(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!bool { } }, } - pp.addTokenAssumeCapacity(tok); + pp.addTokenAssumeCapacity(try pp.unescapeUcn(tok)); } try pp.addToken(.{ .id = .eof, @@ -2339,6 +2340,113 @@ fn expandMacroExhaustive( buf.items.len = moving_end_idx; } +// fn writeUnescapedChar(pp: *Preprocessor, decoded: ucn.DecodedUniversalChar, loc: Source.Location, offset: u32) !void { +// pp.comp.generated_buf.appendSliceAssumeCapacity(decoded.buf); +// _ = offset; +// switch (decoded.kind) { +// .normal => {}, +// .ucn => { +// // TODO: UCN not allowed before C99 +// }, +// .control => { +// try pp.err(loc, .ucn_control_char_error, .{}); +// }, +// .not_allowed => { +// // try pp.err(loc, .ucn_basic_char_error, .{Ascii.init(val)}); // todo offset +// // try pp.comp.addDiagnostic(.{ +// // .tag = .ucn_basic_char_error, +// // .loc = .{ +// // .id = loc.id, +// // .byte_offset = loc.byte_offset + offset, +// // .line = loc.line, +// // }, +// // .extra = .{ .ascii = @intCast(decoded.buf[0]) }, +// // }, &.{}); +// }, +// .incomplete_ucn => { +// // try pp.comp.addDiagnostic(.{ +// // .tag = .incomplete_universal_character, +// // .loc = .{ +// // .id = loc.id, +// // .byte_offset = loc.byte_offset + offset, +// // .line = loc.line, +// // }, +// // }, &.{}); +// }, +// .invalid_utf_8 => { +// // try pp.comp.addDiagnostic(.{ +// // .tag = .invalid_utf8, +// // .loc = .{ +// // .id = loc.id, +// // .byte_offset = loc.byte_offset + offset, +// // .line = loc.line, +// // }, +// // }, &.{}); +// }, +// .invalid_codepoint => { +// // try pp.comp.addDiagnostic(.{ +// // .tag = .invalid_universal_character, +// // .loc = loc, +// // .extra = .{ .offset = offset }, +// // }, &.{}); +// }, +// } +// } + +fn unescapeUcn(pp: *Preprocessor, tok: TokenWithExpansionLocs) !TokenWithExpansionLocs { + switch (tok.id) { + .incomplete_ucn => { + @branchHint(.cold); + try pp.err(tok, .incomplete_ucn, .{}); + }, + .extended_identifier => { + @branchHint(.cold); + const identifier = pp.expandedSlice(tok); + if (mem.indexOfScalar(u8, identifier, '\\') != null) { + @branchHint(.cold); + const start = pp.comp.generated_buf.items.len; + try pp.comp.generated_buf.ensureUnusedCapacity(pp.gpa, identifier.len + 1); + var identifier_parser: text_literal.Parser = .{ + .comp = pp.comp, + .literal = pp.expandedSlice(tok), // re-expand since previous line may have caused a reallocation, invalidating `identifier` + .kind = .utf_8, + .max_codepoint = 0x10ffff, + .loc = tok.loc, + .expansion_locs = tok.expansionSlice(), + }; + while (try identifier_parser.next()) |decoded| { + switch (decoded) { + .value => unreachable, // validated by tokenizer + .codepoint => |c| { + var buf: [4]u8 = undefined; + const written = std.unicode.utf8Encode(c, &buf) catch unreachable; + pp.comp.generated_buf.appendSliceAssumeCapacity(buf[0..written]); + }, + .improperly_encoded => |b| { + std.debug.print("got improperly encoded {s}\n", .{b}); + }, + .utf8_text => |view| { + pp.comp.generated_buf.appendSliceAssumeCapacity(view.bytes); + }, + } + } + + // var offset: u32 = 0; + // while (it.next()) |decoded| { + // try pp.writeUnescapedChar(decoded, tok.loc, offset); + // std.debug.assert(decoded.consumed <= 10); + // offset += @truncate(decoded.consumed); + // } + pp.comp.generated_buf.appendAssumeCapacity('\n'); + defer TokenWithExpansionLocs.free(tok.expansion_locs, pp.gpa); + return pp.makeGeneratedToken(start, .extended_identifier, tok); + } + }, + else => {}, + } + return tok; +} + /// Try to expand a macro after a possible candidate has been read from the `tokenizer` /// into the `raw` token passed as argument fn expandMacro(pp: *Preprocessor, tokenizer: *Tokenizer, raw: RawToken) MacroError!void { @@ -2368,7 +2476,7 @@ fn expandMacro(pp: *Preprocessor, tokenizer: *Tokenizer, raw: RawToken) MacroErr continue; } tok.id.simplifyMacroKeywordExtra(true); - pp.addTokenAssumeCapacity(tok.*); + pp.addTokenAssumeCapacity(try pp.unescapeUcn(tok.*)); } if (pp.preserve_whitespace) { try pp.ensureUnusedTokenCapacity(pp.add_expansion_nl); @@ -3032,7 +3140,8 @@ fn makePragmaToken(pp: *Preprocessor, raw: RawToken, operator_loc: ?Source.Locat return tok; } -pub fn addToken(pp: *Preprocessor, tok: TokenWithExpansionLocs) !void { +pub fn addToken(pp: *Preprocessor, tok_arg: TokenWithExpansionLocs) !void { + const tok = try pp.unescapeUcn(tok_arg); if (tok.expansion_locs) |expansion_locs| { try pp.expansion_entries.append(pp.gpa, .{ .idx = @intCast(pp.tokens.len), .locs = expansion_locs }); } diff --git a/src/aro/Preprocessor/Diagnostic.zig b/src/aro/Preprocessor/Diagnostic.zig index eb18566..26fe6f0 100644 --- a/src/aro/Preprocessor/Diagnostic.zig +++ b/src/aro/Preprocessor/Diagnostic.zig @@ -429,3 +429,19 @@ pub const expected_identifier: Diagnostic = .{ .fmt = "expected identifier argument", .kind = .@"error", }; + +pub const incomplete_ucn: Diagnostic = .{ + .fmt = "incomplete universal character name; treating as '\\' followed by identifier", + .kind = .warning, + .opt = .unicode, +}; + +pub const ucn_control_char_error: Diagnostic = .{ + .fmt = "universal character name refers to a control character", + .kind = .@"error", +}; + +pub const ucn_basic_char_error: Diagnostic = .{ + .fmt = "character '{c}' cannot be specified by a universal character name", + .kind = .@"error", +}; diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 11a67cd..d9f6e00 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -5,6 +5,42 @@ const Compilation = @import("Compilation.zig"); const LangOpts = @import("LangOpts.zig"); const Source = @import("Source.zig"); +/// Value for valid escapes indicates how many characters to consume, not counting leading backslash +const UCNKind = enum(u8) { + /// Just `\` + none, + /// \u or \U + incomplete, + /// `\uxxxx` + hex4 = 5, + /// `\Uxxxxxxxx` + hex8 = 9, + + /// In the classification phase we do not care if the escape represents a valid universal character name + /// e.g. \UFFFFFFFF is acceptable. + fn classify(buf: []const u8) UCNKind { + assert(buf[0] == '\\'); + if (buf.len == 1) return .none; + switch (buf[1]) { + 'u' => { + if (buf.len < 6) return .incomplete; + for (buf[2..6]) |c| { + if (!std.ascii.isHex(c)) return .incomplete; + } + return .hex4; + }, + 'U' => { + if (buf.len < 10) return .incomplete; + for (buf[2..10]) |c| { + if (!std.ascii.isHex(c)) return .incomplete; + } + return .hex8; + }, + else => return .none, + } + } +}; + pub const Token = struct { id: Id, source: Source.Id, @@ -19,7 +55,7 @@ pub const Token = struct { eof, /// identifier containing solely basic character set characters identifier, - /// identifier with at least one extended character + /// identifier with at least one extended character or UCN escape sequence extended_identifier, // string literals with prefixes @@ -343,6 +379,9 @@ pub const Token = struct { /// A comment token if asked to preserve comments. comment, + /// Incomplete universal character name + incomplete_ucn, + /// Return true if token is identifier or keyword. pub fn isMacroIdentifier(id: Id) bool { switch (id) { @@ -587,6 +626,8 @@ pub const Token = struct { => "", .macro_ws => " ", + .incomplete_ucn => "\\", + .macro_func => "__func__", .macro_function => "__FUNCTION__", .macro_pretty_func => "__PRETTY_FUNCTION__", @@ -1168,6 +1209,26 @@ pub fn next(self: *Tokenizer) Token { 'u' => state = .u, 'U' => state = .U, 'L' => state = .L, + '\\' => { + const ucn_kind = UCNKind.classify(self.buf[self.index..]); + switch (ucn_kind) { + .none => { + self.index += 1; + id = .invalid; + break; + }, + .incomplete => { + self.index += 1; + id = .incomplete_ucn; + break; + }, + .hex4, .hex8 => { + self.index += @intFromEnum(ucn_kind); + id = .extended_identifier; + state = .extended_identifier; + }, + } + }, 'a'...'t', 'v'...'z', 'A'...'K', 'M'...'T', 'V'...'Z', '_' => state = .identifier, '=' => state = .equal, '!' => state = .bang, @@ -1393,6 +1454,20 @@ pub fn next(self: *Tokenizer) Token { break; }, 0x80...0xFF => state = .extended_identifier, + '\\' => { + const ucn_kind = UCNKind.classify(self.buf[self.index..]); + switch (ucn_kind) { + .none, .incomplete => { + id = if (state == .identifier) Token.getTokenId(self.langopts, self.buf[start..self.index]) else .extended_identifier; + break; + }, + .hex4, .hex8 => { + state = .extended_identifier; + self.index += @intFromEnum(ucn_kind); + }, + } + }, + else => { id = if (state == .identifier) Token.getTokenId(self.langopts, self.buf[start..self.index]) else .extended_identifier; break; @@ -2219,6 +2294,40 @@ test "C23 keywords" { }, .c23); } +test "Universal character names" { + try expectTokens("\\", &.{.invalid}); + try expectTokens("\\g", &.{ .invalid, .identifier }); + try expectTokens("\\u", &.{ .incomplete_ucn, .identifier }); + try expectTokens("\\ua", &.{ .incomplete_ucn, .identifier }); + try expectTokens("\\U9", &.{ .incomplete_ucn, .identifier }); + try expectTokens("\\ug", &.{ .incomplete_ucn, .identifier }); + try expectTokens("\\uag", &.{ .incomplete_ucn, .identifier }); + + try expectTokens("\\ ", &.{ .invalid, .eof }); + try expectTokens("\\g ", &.{ .invalid, .identifier, .eof }); + try expectTokens("\\u ", &.{ .incomplete_ucn, .identifier, .eof }); + try expectTokens("\\ua ", &.{ .incomplete_ucn, .identifier, .eof }); + try expectTokens("\\U9 ", &.{ .incomplete_ucn, .identifier, .eof }); + try expectTokens("\\ug ", &.{ .incomplete_ucn, .identifier, .eof }); + try expectTokens("\\uag ", &.{ .incomplete_ucn, .identifier, .eof }); + + try expectTokens("a\\", &.{ .identifier, .invalid }); + try expectTokens("a\\g", &.{ .identifier, .invalid, .identifier }); + try expectTokens("a\\u", &.{ .identifier, .incomplete_ucn, .identifier }); + try expectTokens("a\\ua", &.{ .identifier, .incomplete_ucn, .identifier }); + try expectTokens("a\\U9", &.{ .identifier, .incomplete_ucn, .identifier }); + try expectTokens("a\\ug", &.{ .identifier, .incomplete_ucn, .identifier }); + try expectTokens("a\\uag", &.{ .identifier, .incomplete_ucn, .identifier }); + + try expectTokens("a\\ ", &.{ .identifier, .invalid, .eof }); + try expectTokens("a\\g ", &.{ .identifier, .invalid, .identifier, .eof }); + try expectTokens("a\\u ", &.{ .identifier, .incomplete_ucn, .identifier, .eof }); + try expectTokens("a\\ua ", &.{ .identifier, .incomplete_ucn, .identifier, .eof }); + try expectTokens("a\\U9 ", &.{ .identifier, .incomplete_ucn, .identifier, .eof }); + try expectTokens("a\\ug ", &.{ .identifier, .incomplete_ucn, .identifier, .eof }); + try expectTokens("a\\uag ", &.{ .identifier, .incomplete_ucn, .identifier, .eof }); +} + test "Tokenizer fuzz test" { const Context = struct { fn testOne(_: @This(), input_bytes: []const u8) anyerror!void { diff --git a/src/aro/ucn.zig b/src/aro/ucn.zig new file mode 100644 index 0000000..1d294be --- /dev/null +++ b/src/aro/ucn.zig @@ -0,0 +1,97 @@ +//! Universal Character Name support + +const std = @import("std"); + +const Kind = enum { + /// Valid not escaped char + normal, + /// Not escaped, but not valid UTF-8 + invalid_utf_8, + /// Valid UCN char + ucn, + /// Incomplete UCN escape sequence + incomplete_ucn, + /// UCN escape sequence does not specify a unicode code point + invalid_codepoint, + /// UCN names a control character + control, + /// UCN names a basic character set character + not_allowed, +}; + +pub const DecodedUniversalChar = struct { + codepoint: u32, + consumed: usize, + buf: []const u8, + kind: Kind, +}; + +/// Decodes a C99-style universal character name (e.g., \uXXXX or \UXXXXXXXX) +/// into a unicode codepoint. Returns the decoded character and the number of +/// bytes consumed from the input string. +fn decodeUniversalChar(input: []const u8, output: []u8) DecodedUniversalChar { + std.debug.assert(input.len >= 2 and input[0] == '\\' and (input[1] == 'u' or input[1] == 'U')); + const is_long = input[1] == 'U'; + const required: usize = if (is_long) 10 else 6; + + if (input.len < required) { + return .{ + .codepoint = 0, + .consumed = input.len, + .buf = input, + .kind = .incomplete_ucn, + }; + } + + const hex_part = input[2..required]; + var codepoint: u32 = 0; + for (hex_part, 0..) |c, i| { + codepoint *= 16; + const value = switch (c) { + '0'...'9' => c - '0', + 'a'...'f' => 10 + (c - 'a'), + 'A'...'F' => 10 + (c - 'A'), + else => return .{ .codepoint = 0, .consumed = i, .buf = input[0..i], .kind = .incomplete_ucn }, + }; + codepoint += value; + } + if (codepoint > std.math.maxInt(u21)) { + return .{ .codepoint = 0, .consumed = required, .buf = input, .kind = .invalid_codepoint }; + } + + const len = std.unicode.utf8Encode(@as(u21, @intCast(codepoint)), output) catch { + return .{ .codepoint = codepoint, .consumed = required, .buf = input, .kind = .invalid_codepoint }; + }; + const kind: Kind = switch (codepoint) { + 0...0x1F, 0x7F...0x9F => .control, + 0x20...0x7E => .not_allowed, + else => .ucn, + }; + return .{ .codepoint = codepoint, .consumed = required, .buf = output[0..len], .kind = kind }; +} + +pub const CharIterator = struct { + str: []const u8, + i: usize, + buf: [10]u8, + + pub fn init(str: []const u8) CharIterator { + return .{ .str = str, .i = 0, .buf = undefined }; + } + + pub fn next(self: *@This()) ?DecodedUniversalChar { + if (self.i >= self.str.len) return null; + if (self.str[self.i] == '\\' and self.i + 1 < self.str.len and (self.str[self.i + 1] == 'u' or self.str[self.i + 1] == 'U')) { + const decoded = decodeUniversalChar(self.str[self.i..], self.buf[0..]); + self.i += decoded.consumed; + return decoded; + } else { + const len = std.unicode.utf8ByteSequenceLength(self.str[self.i]) catch { + defer self.i += 1; + return .{ .codepoint = self.str[self.i], .consumed = 1, .buf = self.str[self.i..][0..1], .kind = .invalid_utf_8 }; + }; + defer self.i += len; + return .{ .codepoint = 0, .consumed = len, .buf = self.str[self.i..][0..len], .kind = .normal }; + } + } +}; diff --git a/test/cases/ucn identifiers.c b/test/cases/ucn identifiers.c new file mode 100644 index 0000000..de56fbb --- /dev/null +++ b/test/cases/ucn identifiers.c @@ -0,0 +1,28 @@ +#define FOO \u4F60 ## \u597D + +int foo(void) { + int FOO = 0; + \u4F60\u597D = 5; + int \u0061 = 0x61; + int ABC\U00000001 = 0x01; + return 你好; +} + +struct S { + int 你好; + int a\u4F60\u597D; +}; + +int bar(int x) { + struct S s; + s.\u4F60\u597D = x; + s.a\u4F60\u597D = x; + return s.你好; +} + +int \UFFFFFFFF = 42; + +#define EXPECTED_ERRORS "ucn identifiers.c:6:9: error: character 'a' cannot be specified by a universal character name" \ + "ucn identifiers.c:7:12: error: universal character name refers to a control character" \ + "ucn identifiers.c:23:5: error: invalid universal character" \ + -- Gitee From e2628272a46518fee92837058dfe6bdbf858e731 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 10:36:54 -0700 Subject: [PATCH 036/117] Preprocessor: unescape macro names --- src/aro/Preprocessor.zig | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index eaa292e..e21518b 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -2582,11 +2582,11 @@ fn makeGeneratedToken(pp: *Preprocessor, start: usize, id: Token.Id, source: Tok } /// Defines a new macro and warns if it is a duplicate -fn defineMacro(pp: *Preprocessor, define_tok: RawToken, name_tok: RawToken, macro: Macro) Error!void { - const name_str = pp.tokSlice(name_tok); +fn defineMacro(pp: *Preprocessor, define_tok: RawToken, name_tok: TokenWithExpansionLocs, macro: Macro) Error!void { + const name_str = pp.expandedSlice(name_tok); const gop = try pp.defines.getOrPut(pp.gpa, name_str); if (gop.found_existing and !gop.value_ptr.eql(macro, pp)) { - const loc: Source.Location = .{ .id = name_tok.source, .byte_offset = name_tok.start, .line = name_tok.line }; + const loc = name_tok.loc; const prev_total = pp.diagnostics.total; if (gop.value_ptr.is_builtin) { try pp.err(loc, .builtin_macro_redefined, .{}); @@ -2599,7 +2599,7 @@ fn defineMacro(pp: *Preprocessor, define_tok: RawToken, name_tok: RawToken, macr } } if (pp.verbose) { - pp.verboseLog(name_tok, "macro {s} defined", .{name_str}); + // pp.verboseLog(name_tok, "macro {s} defined", .{name_str}); } if (pp.store_macro_tokens) { try pp.addToken(tokFromRaw(define_tok)); @@ -2610,15 +2610,17 @@ fn defineMacro(pp: *Preprocessor, define_tok: RawToken, name_tok: RawToken, macr /// Handle a #define directive. fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error!void { // Get macro name and validate it. - const macro_name = tokenizer.nextNoWS(); - if (macro_name.id == .keyword_defined) { - try pp.err(macro_name, .defined_as_macro_name, .{}); + const escaped_macro_name = tokenizer.nextNoWS(); + if (escaped_macro_name.id == .keyword_defined) { + try pp.err(escaped_macro_name, .defined_as_macro_name, .{}); return skipToNl(tokenizer); } - if (!macro_name.id.isMacroIdentifier()) { - try pp.err(macro_name, .macro_name_must_be_identifier, .{}); + if (!escaped_macro_name.id.isMacroIdentifier()) { + try pp.err(escaped_macro_name, .macro_name_must_be_identifier, .{}); return skipToNl(tokenizer); } + const macro_name = try pp.unescapeUcn(tokFromRaw(escaped_macro_name)); + var macro_name_token_id = macro_name.id; macro_name_token_id.simplifyMacroKeyword(); switch (macro_name_token_id) { @@ -2638,7 +2640,7 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! .params = &.{}, .tokens = &.{}, .var_args = false, - .loc = tokFromRaw(macro_name).loc, + .loc = macro_name.loc, .is_func = false, }), .whitespace => first = tokenizer.next(), @@ -2702,7 +2704,7 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! const list = try pp.arena.allocator().dupe(RawToken, pp.token_buf.items); try pp.defineMacro(define_tok, macro_name, .{ - .loc = tokFromRaw(macro_name).loc, + .loc = macro_name.loc, .tokens = list, .params = &.{}, .is_func = false, @@ -2711,7 +2713,7 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! } /// Handle a function like #define directive. -fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macro_name: RawToken, l_paren: RawToken) Error!void { +fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macro_name: TokenWithExpansionLocs, l_paren: RawToken) Error!void { assert(macro_name.id.isMacroIdentifier()); var params = std.ArrayList([]const u8).init(pp.gpa); defer params.deinit(); @@ -2893,7 +2895,7 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken, macr .params = param_list, .var_args = var_args or gnu_var_args.len != 0, .tokens = token_list, - .loc = tokFromRaw(macro_name).loc, + .loc = macro_name.loc, }); } -- Gitee From 273a6556881e5da7efe2a72da2a610a9d5406da7 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 10:45:46 -0700 Subject: [PATCH 037/117] Preprocessor: update verboseLog to use TokenWithExpansionLocs --- src/aro/Preprocessor.zig | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index e21518b..e35651d 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -407,13 +407,13 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if (try pp.expr(&tokenizer)) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(directive, "entering then branch of #if", .{}); + pp.verboseLog(tokFromRaw(directive), "entering then branch of #if", .{}); } } else { if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(directive, "entering else branch of #if", .{}); + pp.verboseLog(tokFromRaw(directive), "entering else branch of #if", .{}); } } }, @@ -427,13 +427,13 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if (pp.defines.get(macro_name) != null) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(directive, "entering then branch of #ifdef", .{}); + pp.verboseLog(tokFromRaw(directive), "entering then branch of #ifdef", .{}); } } else { if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(directive, "entering else branch of #ifdef", .{}); + pp.verboseLog(tokFromRaw(directive), "entering else branch of #ifdef", .{}); } } }, @@ -463,12 +463,12 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans .until_else => if (try pp.expr(&tokenizer)) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(directive, "entering then branch of #elif", .{}); + pp.verboseLog(tokFromRaw(directive), "entering then branch of #elif", .{}); } } else { try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(directive, "entering else branch of #elif", .{}); + pp.verboseLog(tokFromRaw(directive), "entering else branch of #elif", .{}); } }, .until_endif => try pp.skip(&tokenizer, .until_endif), @@ -493,20 +493,20 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(directive, "entering else branch of #elifdef", .{}); + pp.verboseLog(tokFromRaw(directive), "entering else branch of #elifdef", .{}); } } else { try pp.expectNl(&tokenizer); if (pp.defines.get(macro_name.?) != null) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(directive, "entering then branch of #elifdef", .{}); + pp.verboseLog(tokFromRaw(directive), "entering then branch of #elifdef", .{}); } } else { if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(directive, "entering else branch of #elifdef", .{}); + pp.verboseLog(tokFromRaw(directive), "entering else branch of #elifdef", .{}); } } } @@ -533,20 +533,20 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(directive, "entering else branch of #elifndef", .{}); + pp.verboseLog(tokFromRaw(directive), "entering else branch of #elifndef", .{}); } } else { try pp.expectNl(&tokenizer); if (pp.defines.get(macro_name.?) == null) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(directive, "entering then branch of #elifndef", .{}); + pp.verboseLog(tokFromRaw(directive), "entering then branch of #elifndef", .{}); } } else { if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(directive, "entering else branch of #elifndef", .{}); + pp.verboseLog(tokFromRaw(directive), "entering else branch of #elifndef", .{}); } } } @@ -570,7 +570,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans .until_else => { if_context.set(.until_endif_seen_else); if (pp.verbose) { - pp.verboseLog(directive, "#else branch here", .{}); + pp.verboseLog(tokFromRaw(directive), "#else branch here", .{}); } }, .until_endif => try pp.skip(&tokenizer, .until_endif_seen_else), @@ -796,9 +796,10 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con unreachable; // should've returned FatalError } -fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) void { - const source = pp.comp.getSource(raw.source); - const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); +fn verboseLog(pp: *Preprocessor, tok: TokenWithExpansionLocs, comptime fmt: []const u8, args: anytype) void { + @branchHint(.cold); + const source = pp.comp.getSource(tok.loc.id); + const line_col = source.lineCol(tok.loc); const stderr = std.io.getStdErr().writer(); var buf_writer = std.io.bufferedWriter(stderr); @@ -2599,7 +2600,7 @@ fn defineMacro(pp: *Preprocessor, define_tok: RawToken, name_tok: TokenWithExpan } } if (pp.verbose) { - // pp.verboseLog(name_tok, "macro {s} defined", .{name_str}); + pp.verboseLog(name_tok, "macro {s} defined", .{name_str}); } if (pp.store_macro_tokens) { try pp.addToken(tokFromRaw(define_tok)); @@ -3096,7 +3097,7 @@ fn include(pp: *Preprocessor, tokenizer: *Tokenizer, which: Compilation.WhichInc } if (pp.verbose) { - pp.verboseLog(first, "include file {s}", .{new_source.path}); + pp.verboseLog(tokFromRaw(first), "include file {s}", .{new_source.path}); } const token_state = pp.getTokenState(); -- Gitee From 7968d0c2a946eb5d4c6e16ed3b0d76bfdb600799 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 11:13:16 -0700 Subject: [PATCH 038/117] Do not diagnose improper encoding in the preprocessor --- src/aro/Preprocessor.zig | 5 +++-- src/aro/text_literal.zig | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index e35651d..5b17fff 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -2414,6 +2414,7 @@ fn unescapeUcn(pp: *Preprocessor, tok: TokenWithExpansionLocs) !TokenWithExpansi .max_codepoint = 0x10ffff, .loc = tok.loc, .expansion_locs = tok.expansionSlice(), + .diagnose_incorrect_encoding = false, }; while (try identifier_parser.next()) |decoded| { switch (decoded) { @@ -2423,8 +2424,8 @@ fn unescapeUcn(pp: *Preprocessor, tok: TokenWithExpansionLocs) !TokenWithExpansi const written = std.unicode.utf8Encode(c, &buf) catch unreachable; pp.comp.generated_buf.appendSliceAssumeCapacity(buf[0..written]); }, - .improperly_encoded => |b| { - std.debug.print("got improperly encoded {s}\n", .{b}); + .improperly_encoded => |bytes| { + pp.comp.generated_buf.appendSliceAssumeCapacity(bytes); }, .utf8_text => |view| { pp.comp.generated_buf.appendSliceAssumeCapacity(view.bytes); diff --git a/src/aro/text_literal.zig b/src/aro/text_literal.zig index fae8c73..817230f 100644 --- a/src/aro/text_literal.zig +++ b/src/aro/text_literal.zig @@ -185,6 +185,9 @@ pub const Parser = struct { /// Makes incorrect encoding always an error. /// Used when concatenating string literals. incorrect_encoding_is_error: bool = false, + /// If this is false, do not issue any diagnostics for incorrect character encoding + /// Incorrect encoding is allowed if we are unescaping an identifier in the preprocessor + diagnose_incorrect_encoding: bool = true, fn prefixLen(self: *const Parser) usize { return switch (self.kind) { @@ -360,6 +363,9 @@ pub const Parser = struct { const unescaped_slice = p.literal[start..p.i]; const view = std.unicode.Utf8View.init(unescaped_slice) catch { + if (!p.diagnose_incorrect_encoding) { + return .{ .improperly_encoded = p.literal[start..p.i] }; + } if (p.incorrect_encoding_is_error) { try p.warn(.illegal_char_encoding_error, .{}); return .{ .improperly_encoded = p.literal[start..p.i] }; -- Gitee From 00c8e46f6be4a40ee80e12c9ced2ab76becfbec2 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 11:13:54 -0700 Subject: [PATCH 039/117] Preprocessor: remove old unused code --- src/aro/Preprocessor.zig | 60 ------------------------- src/aro/ucn.zig | 97 ---------------------------------------- 2 files changed, 157 deletions(-) delete mode 100644 src/aro/ucn.zig diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 5b17fff..95deb77 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -2341,59 +2341,6 @@ fn expandMacroExhaustive( buf.items.len = moving_end_idx; } -// fn writeUnescapedChar(pp: *Preprocessor, decoded: ucn.DecodedUniversalChar, loc: Source.Location, offset: u32) !void { -// pp.comp.generated_buf.appendSliceAssumeCapacity(decoded.buf); -// _ = offset; -// switch (decoded.kind) { -// .normal => {}, -// .ucn => { -// // TODO: UCN not allowed before C99 -// }, -// .control => { -// try pp.err(loc, .ucn_control_char_error, .{}); -// }, -// .not_allowed => { -// // try pp.err(loc, .ucn_basic_char_error, .{Ascii.init(val)}); // todo offset -// // try pp.comp.addDiagnostic(.{ -// // .tag = .ucn_basic_char_error, -// // .loc = .{ -// // .id = loc.id, -// // .byte_offset = loc.byte_offset + offset, -// // .line = loc.line, -// // }, -// // .extra = .{ .ascii = @intCast(decoded.buf[0]) }, -// // }, &.{}); -// }, -// .incomplete_ucn => { -// // try pp.comp.addDiagnostic(.{ -// // .tag = .incomplete_universal_character, -// // .loc = .{ -// // .id = loc.id, -// // .byte_offset = loc.byte_offset + offset, -// // .line = loc.line, -// // }, -// // }, &.{}); -// }, -// .invalid_utf_8 => { -// // try pp.comp.addDiagnostic(.{ -// // .tag = .invalid_utf8, -// // .loc = .{ -// // .id = loc.id, -// // .byte_offset = loc.byte_offset + offset, -// // .line = loc.line, -// // }, -// // }, &.{}); -// }, -// .invalid_codepoint => { -// // try pp.comp.addDiagnostic(.{ -// // .tag = .invalid_universal_character, -// // .loc = loc, -// // .extra = .{ .offset = offset }, -// // }, &.{}); -// }, -// } -// } - fn unescapeUcn(pp: *Preprocessor, tok: TokenWithExpansionLocs) !TokenWithExpansionLocs { switch (tok.id) { .incomplete_ucn => { @@ -2432,13 +2379,6 @@ fn unescapeUcn(pp: *Preprocessor, tok: TokenWithExpansionLocs) !TokenWithExpansi }, } } - - // var offset: u32 = 0; - // while (it.next()) |decoded| { - // try pp.writeUnescapedChar(decoded, tok.loc, offset); - // std.debug.assert(decoded.consumed <= 10); - // offset += @truncate(decoded.consumed); - // } pp.comp.generated_buf.appendAssumeCapacity('\n'); defer TokenWithExpansionLocs.free(tok.expansion_locs, pp.gpa); return pp.makeGeneratedToken(start, .extended_identifier, tok); diff --git a/src/aro/ucn.zig b/src/aro/ucn.zig deleted file mode 100644 index 1d294be..0000000 --- a/src/aro/ucn.zig +++ /dev/null @@ -1,97 +0,0 @@ -//! Universal Character Name support - -const std = @import("std"); - -const Kind = enum { - /// Valid not escaped char - normal, - /// Not escaped, but not valid UTF-8 - invalid_utf_8, - /// Valid UCN char - ucn, - /// Incomplete UCN escape sequence - incomplete_ucn, - /// UCN escape sequence does not specify a unicode code point - invalid_codepoint, - /// UCN names a control character - control, - /// UCN names a basic character set character - not_allowed, -}; - -pub const DecodedUniversalChar = struct { - codepoint: u32, - consumed: usize, - buf: []const u8, - kind: Kind, -}; - -/// Decodes a C99-style universal character name (e.g., \uXXXX or \UXXXXXXXX) -/// into a unicode codepoint. Returns the decoded character and the number of -/// bytes consumed from the input string. -fn decodeUniversalChar(input: []const u8, output: []u8) DecodedUniversalChar { - std.debug.assert(input.len >= 2 and input[0] == '\\' and (input[1] == 'u' or input[1] == 'U')); - const is_long = input[1] == 'U'; - const required: usize = if (is_long) 10 else 6; - - if (input.len < required) { - return .{ - .codepoint = 0, - .consumed = input.len, - .buf = input, - .kind = .incomplete_ucn, - }; - } - - const hex_part = input[2..required]; - var codepoint: u32 = 0; - for (hex_part, 0..) |c, i| { - codepoint *= 16; - const value = switch (c) { - '0'...'9' => c - '0', - 'a'...'f' => 10 + (c - 'a'), - 'A'...'F' => 10 + (c - 'A'), - else => return .{ .codepoint = 0, .consumed = i, .buf = input[0..i], .kind = .incomplete_ucn }, - }; - codepoint += value; - } - if (codepoint > std.math.maxInt(u21)) { - return .{ .codepoint = 0, .consumed = required, .buf = input, .kind = .invalid_codepoint }; - } - - const len = std.unicode.utf8Encode(@as(u21, @intCast(codepoint)), output) catch { - return .{ .codepoint = codepoint, .consumed = required, .buf = input, .kind = .invalid_codepoint }; - }; - const kind: Kind = switch (codepoint) { - 0...0x1F, 0x7F...0x9F => .control, - 0x20...0x7E => .not_allowed, - else => .ucn, - }; - return .{ .codepoint = codepoint, .consumed = required, .buf = output[0..len], .kind = kind }; -} - -pub const CharIterator = struct { - str: []const u8, - i: usize, - buf: [10]u8, - - pub fn init(str: []const u8) CharIterator { - return .{ .str = str, .i = 0, .buf = undefined }; - } - - pub fn next(self: *@This()) ?DecodedUniversalChar { - if (self.i >= self.str.len) return null; - if (self.str[self.i] == '\\' and self.i + 1 < self.str.len and (self.str[self.i + 1] == 'u' or self.str[self.i + 1] == 'U')) { - const decoded = decodeUniversalChar(self.str[self.i..], self.buf[0..]); - self.i += decoded.consumed; - return decoded; - } else { - const len = std.unicode.utf8ByteSequenceLength(self.str[self.i]) catch { - defer self.i += 1; - return .{ .codepoint = self.str[self.i], .consumed = 1, .buf = self.str[self.i..][0..1], .kind = .invalid_utf_8 }; - }; - defer self.i += len; - return .{ .codepoint = 0, .consumed = len, .buf = self.str[self.i..][0..len], .kind = .normal }; - } - } -}; -- Gitee From 95a7779005ad8aee660a48d7610ac3b331f9650d Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 11:29:55 -0700 Subject: [PATCH 040/117] Preprocessor: fix leak of expansion location of escaped identifiers --- src/aro/Preprocessor.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 95deb77..7958e83 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -2562,6 +2562,7 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! return skipToNl(tokenizer); } const macro_name = try pp.unescapeUcn(tokFromRaw(escaped_macro_name)); + defer TokenWithExpansionLocs.free(macro_name.expansion_locs, pp.gpa); var macro_name_token_id = macro_name.id; macro_name_token_id.simplifyMacroKeyword(); -- Gitee From d4c6e980434a7511e085520802e5923dcaee6934 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 11:30:04 -0700 Subject: [PATCH 041/117] tests: update UCN test --- test/cases/ucn identifiers.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/cases/ucn identifiers.c b/test/cases/ucn identifiers.c index de56fbb..d1ff880 100644 --- a/test/cases/ucn identifiers.c +++ b/test/cases/ucn identifiers.c @@ -1,3 +1,6 @@ +#define \U0001F525 42 +_Static_assert(🔥 == 42, ""); + #define FOO \u4F60 ## \u597D int foo(void) { @@ -22,7 +25,7 @@ int bar(int x) { int \UFFFFFFFF = 42; -#define EXPECTED_ERRORS "ucn identifiers.c:6:9: error: character 'a' cannot be specified by a universal character name" \ - "ucn identifiers.c:7:12: error: universal character name refers to a control character" \ - "ucn identifiers.c:23:5: error: invalid universal character" \ +#define EXPECTED_ERRORS "ucn identifiers.c:9:9: error: character 'a' cannot be specified by a universal character name" \ + "ucn identifiers.c:10:9: error: universal character name refers to a control character" \ + "ucn identifiers.c:26:7: error: invalid universal character" \ -- Gitee From fcdfe1214862a8b68c4a1fbca18091e2ad29749a Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 11:36:11 -0700 Subject: [PATCH 042/117] Preprocessor: diagnose incomplete UCN in macro definition --- src/aro/Preprocessor.zig | 4 ++++ test/cases/ucn identifiers.c | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 7958e83..908562e 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -2635,6 +2635,10 @@ fn define(pp: *Preprocessor, tokenizer: *Tokenizer, define_tok: RawToken) Error! }, .unterminated_comment => try pp.err(tok, .unterminated_comment, .{}), else => { + if (tok.id == .incomplete_ucn) { + @branchHint(.cold); + try pp.err(tok, .incomplete_ucn, .{}); + } if (tok.id != .whitespace and need_ws) { need_ws = false; try pp.token_buf.append(.{ .id = .macro_ws, .source = .generated }); diff --git a/test/cases/ucn identifiers.c b/test/cases/ucn identifiers.c index d1ff880..4790cf8 100644 --- a/test/cases/ucn identifiers.c +++ b/test/cases/ucn identifiers.c @@ -3,6 +3,8 @@ _Static_assert(🔥 == 42, ""); #define FOO \u4F60 ## \u597D +#define INCOMPLETE_UCN \u4F ## 60 + int foo(void) { int FOO = 0; \u4F60\u597D = 5; @@ -25,7 +27,8 @@ int bar(int x) { int \UFFFFFFFF = 42; -#define EXPECTED_ERRORS "ucn identifiers.c:9:9: error: character 'a' cannot be specified by a universal character name" \ - "ucn identifiers.c:10:9: error: universal character name refers to a control character" \ - "ucn identifiers.c:26:7: error: invalid universal character" \ +#define EXPECTED_ERRORS "ucn identifiers.c:6:24: warning: incomplete universal character name; treating as '\\' followed by identifier [-Wunicode]" \ + "ucn identifiers.c:11:9: error: character 'a' cannot be specified by a universal character name" \ + "ucn identifiers.c:12:9: error: universal character name refers to a control character" \ + "ucn identifiers.c:28:7: error: invalid universal character" \ -- Gitee From cb3dc7773f6d45495f93faf443e0098bf85e3b7c Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 11:42:02 -0700 Subject: [PATCH 043/117] Preprocessor: clarify a comment and remove duplicate diagnostics --- src/aro/Preprocessor/Diagnostic.zig | 10 ---------- src/aro/Tokenizer.zig | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/aro/Preprocessor/Diagnostic.zig b/src/aro/Preprocessor/Diagnostic.zig index 26fe6f0..d53a661 100644 --- a/src/aro/Preprocessor/Diagnostic.zig +++ b/src/aro/Preprocessor/Diagnostic.zig @@ -435,13 +435,3 @@ pub const incomplete_ucn: Diagnostic = .{ .kind = .warning, .opt = .unicode, }; - -pub const ucn_control_char_error: Diagnostic = .{ - .fmt = "universal character name refers to a control character", - .kind = .@"error", -}; - -pub const ucn_basic_char_error: Diagnostic = .{ - .fmt = "character '{c}' cannot be specified by a universal character name", - .kind = .@"error", -}; diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index d9f6e00..b803f3c 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -9,7 +9,7 @@ const Source = @import("Source.zig"); const UCNKind = enum(u8) { /// Just `\` none, - /// \u or \U + /// \u or \U followed by an insufficient number of hex digits incomplete, /// `\uxxxx` hex4 = 5, -- Gitee From 877931b0fb8611b5383d0cb3f251618b02f53982 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 11:44:29 -0700 Subject: [PATCH 044/117] Preprocessor: more clarifying comments --- src/aro/Tokenizer.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index b803f3c..41b2da5 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -380,6 +380,9 @@ pub const Token = struct { comment, /// Incomplete universal character name + /// This happens if the source text contains `\u` or `\U` followed by an insufficient number of hex + /// digits. This token id represents just the backslash; the subsequent `u` or `U` will be treated as the + /// leading character of the following identifier token. incomplete_ucn, /// Return true if token is identifier or keyword. -- Gitee From 311606e83a9f16aec06e40475987b9f59466ed1c Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sat, 26 Apr 2025 11:55:37 -0700 Subject: [PATCH 045/117] Preprocessor: revert verboseLog changes --- src/aro/Preprocessor.zig | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 908562e..59fa035 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -407,13 +407,13 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if (try pp.expr(&tokenizer)) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering then branch of #if", .{}); + pp.verboseLog(directive, "entering then branch of #if", .{}); } } else { if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering else branch of #if", .{}); + pp.verboseLog(directive, "entering else branch of #if", .{}); } } }, @@ -427,13 +427,13 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if (pp.defines.get(macro_name) != null) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering then branch of #ifdef", .{}); + pp.verboseLog(directive, "entering then branch of #ifdef", .{}); } } else { if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering else branch of #ifdef", .{}); + pp.verboseLog(directive, "entering else branch of #ifdef", .{}); } } }, @@ -463,12 +463,12 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans .until_else => if (try pp.expr(&tokenizer)) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering then branch of #elif", .{}); + pp.verboseLog(directive, "entering then branch of #elif", .{}); } } else { try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering else branch of #elif", .{}); + pp.verboseLog(directive, "entering else branch of #elif", .{}); } }, .until_endif => try pp.skip(&tokenizer, .until_endif), @@ -493,20 +493,20 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering else branch of #elifdef", .{}); + pp.verboseLog(directive, "entering else branch of #elifdef", .{}); } } else { try pp.expectNl(&tokenizer); if (pp.defines.get(macro_name.?) != null) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering then branch of #elifdef", .{}); + pp.verboseLog(directive, "entering then branch of #elifdef", .{}); } } else { if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering else branch of #elifdef", .{}); + pp.verboseLog(directive, "entering else branch of #elifdef", .{}); } } } @@ -533,20 +533,20 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering else branch of #elifndef", .{}); + pp.verboseLog(directive, "entering else branch of #elifndef", .{}); } } else { try pp.expectNl(&tokenizer); if (pp.defines.get(macro_name.?) == null) { if_context.set(.until_endif); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering then branch of #elifndef", .{}); + pp.verboseLog(directive, "entering then branch of #elifndef", .{}); } } else { if_context.set(.until_else); try pp.skip(&tokenizer, .until_else); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "entering else branch of #elifndef", .{}); + pp.verboseLog(directive, "entering else branch of #elifndef", .{}); } } } @@ -570,7 +570,7 @@ fn preprocessExtra(pp: *Preprocessor, source: Source) MacroError!TokenWithExpans .until_else => { if_context.set(.until_endif_seen_else); if (pp.verbose) { - pp.verboseLog(tokFromRaw(directive), "#else branch here", .{}); + pp.verboseLog(directive, "#else branch here", .{}); } }, .until_endif => try pp.skip(&tokenizer, .until_endif_seen_else), @@ -796,10 +796,10 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con unreachable; // should've returned FatalError } -fn verboseLog(pp: *Preprocessor, tok: TokenWithExpansionLocs, comptime fmt: []const u8, args: anytype) void { +fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) void { @branchHint(.cold); - const source = pp.comp.getSource(tok.loc.id); - const line_col = source.lineCol(tok.loc); + const source = pp.comp.getSource(raw.source); + const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); const stderr = std.io.getStdErr().writer(); var buf_writer = std.io.bufferedWriter(stderr); @@ -2541,7 +2541,8 @@ fn defineMacro(pp: *Preprocessor, define_tok: RawToken, name_tok: TokenWithExpan } } if (pp.verbose) { - pp.verboseLog(name_tok, "macro {s} defined", .{name_str}); + const raw: RawToken = .{ .id = name_tok.id, .source = name_tok.loc.id, .start = name_tok.loc.byte_offset, .line = name_tok.loc.line }; + pp.verboseLog(raw, "macro {s} defined", .{name_str}); } if (pp.store_macro_tokens) { try pp.addToken(tokFromRaw(define_tok)); @@ -3043,7 +3044,7 @@ fn include(pp: *Preprocessor, tokenizer: *Tokenizer, which: Compilation.WhichInc } if (pp.verbose) { - pp.verboseLog(tokFromRaw(first), "include file {s}", .{new_source.path}); + pp.verboseLog(first, "include file {s}", .{new_source.path}); } const token_state = pp.getTokenState(); -- Gitee From ce0c331d8cc96b4ae7eaefe81a8b2460fe659353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B7=A6=E5=85=83?= Date: Tue, 29 Apr 2025 17:43:53 +0800 Subject: [PATCH 046/117] Tree: fix typo, `CompoutedGotoStmt` ==> `ComputedGotoStmt` --- src/aro/Tree.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index 0b687cf..ad9702c 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -169,7 +169,7 @@ pub const Node = union(enum) { do_while_stmt: DoWhileStmt, for_stmt: ForStmt, goto_stmt: GotoStmt, - computed_goto_stmt: CompoutedGotoStmt, + computed_goto_stmt: ComputedGotoStmt, continue_stmt: ContinueStmt, break_stmt: BreakStmt, null_stmt: NullStmt, @@ -430,7 +430,7 @@ pub const Node = union(enum) { label_tok: TokenIndex, }; - pub const CompoutedGotoStmt = struct { + pub const ComputedGotoStmt = struct { goto_tok: TokenIndex, expr: Node.Index, }; -- Gitee From 86aa2712d2503f6ab5bc936aabb7609ab68fdd68 Mon Sep 17 00:00:00 2001 From: Bogdan Romanyuk <65823030+wrongnull@users.noreply.github.com> Date: Sat, 3 May 2025 01:53:35 +0300 Subject: [PATCH 047/117] Update Zig version to 0.15.0-dev.451+a843be44a --- build.zig.zon | 2 +- src/aro/Driver/GCCDetector.zig | 11 +---------- src/aro/Toolchain.zig | 2 -- src/aro/target.zig | 13 +++---------- src/aro/toolchains/Linux.zig | 15 +++------------ 5 files changed, 8 insertions(+), 35 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 2ddcbd2..1756682 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ .fingerprint = 0x76501fb842f52025, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.0-dev.355+206bd1ced", + .minimum_zig_version = "0.15.0-dev.451+a843be44a", .dependencies = .{}, diff --git a/src/aro/Driver/GCCDetector.zig b/src/aro/Driver/GCCDetector.zig index c437750..38168dd 100644 --- a/src/aro/Driver/GCCDetector.zig +++ b/src/aro/Driver/GCCDetector.zig @@ -286,15 +286,6 @@ fn collectLibDirsAndTriples( }, .x86 => { lib_dirs.appendSliceAssumeCapacity(&X86LibDirs); - // MCU toolchain is 32 bit only and its triple alias is TargetTriple - // itself, which will be appended below. - if (target.os.tag != .elfiamcu) { - triple_aliases.appendSliceAssumeCapacity(&X86Triples); - biarch_libdirs.appendSliceAssumeCapacity(&X86_64LibDirs); - biarch_triple_aliases.appendSliceAssumeCapacity(&X86_64Triples); - biarch_libdirs.appendSliceAssumeCapacity(&X32LibDirs); - biarch_triple_aliases.appendSliceAssumeCapacity(&X32Triples); - } }, .loongarch64 => { lib_dirs.appendSliceAssumeCapacity(&LoongArch64LibDirs); @@ -513,7 +504,7 @@ fn findBiarchMultilibs( const multilib_filter = Multilib.Filter{ .base = path, - .file = if (target.os.tag == .elfiamcu) "libgcc.a" else "crtbegin.o", + .file = "crtbegin.o", }; const Want = enum { diff --git a/src/aro/Toolchain.zig b/src/aro/Toolchain.zig index d7733c6..0ff79b5 100644 --- a/src/aro/Toolchain.zig +++ b/src/aro/Toolchain.zig @@ -85,7 +85,6 @@ pub fn discover(tc: *Toolchain) !void { const target = tc.getTarget(); tc.inner = switch (target.os.tag) { - .elfiamcu, .linux, => if (target.cpu.arch == .hexagon) .{ .unknown = {} } // TODO @@ -430,7 +429,6 @@ fn addUnwindLibrary(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !voi const unw = try tc.getUnwindLibKind(); const target = tc.getTarget(); if ((target.abi.isAndroid() and unw == .libgcc) or - target.os.tag == .elfiamcu or target.ofmt == .wasm or target_util.isWindowsMSVCEnvironment(target) or unw == .none) return; diff --git a/src/aro/target.zig b/src/aro/target.zig index 95c4d57..ef3e628 100644 --- a/src/aro/target.zig +++ b/src/aro/target.zig @@ -420,7 +420,7 @@ pub fn defaultFpEvalMethod(target: std.Target) LangOpts.FPEvalMethod { /// Value of the `-m` flag for `ld` for this target pub fn ldEmulationOption(target: std.Target, arm_endianness: ?std.builtin.Endian) ?[]const u8 { return switch (target.cpu.arch) { - .x86 => if (target.os.tag == .elfiamcu) "elf_iamcu" else "elf_i386", + .x86 => "elf_i386", .arm, .armeb, .thumb, @@ -654,7 +654,6 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { .amdhsa => "amdhsa", .ps4 => "ps4", .ps5 => "ps5", - .elfiamcu => "elfiamcu", .mesa3d => "mesa3d", .contiki => "contiki", .amdpal => "amdpal", @@ -699,7 +698,6 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { .gnuf32 => "gnuf32", .gnusf => "gnusf", .gnux32 => "gnux32", - .gnuilp32 => "gnu_ilp32", .code16 => "code16", .eabi => "eabi", .eabihf => "eabihf", @@ -760,9 +758,7 @@ pub fn isPIEDefault(target: std.Target) DefaultPIStatus { .fuchsia, => .yes, - .linux, - .elfiamcu, - => { + .linux => { if (target.abi == .ohos) return .yes; @@ -836,9 +832,7 @@ pub fn isPICdefault(target: std.Target) DefaultPIStatus { }; }, - .linux, - .elfiamcu, - => { + .linux => { if (target.abi == .ohos) return .no; @@ -894,7 +888,6 @@ pub fn isPICDefaultForced(target: std.Target) DefaultPIStatus { .ps5, .hurd, .linux, - .elfiamcu, .fuchsia, .zos, => .no, diff --git a/src/aro/toolchains/Linux.zig b/src/aro/toolchains/Linux.zig index 8007239..fafddd0 100644 --- a/src/aro/toolchains/Linux.zig +++ b/src/aro/toolchains/Linux.zig @@ -172,7 +172,6 @@ pub fn buildLinkerArgs(self: *const Linux, tc: *const Toolchain, argv: *std.Arra const is_static_pie = try self.getStaticPIE(d); const is_static = self.getStatic(d); const is_android = target.abi.isAndroid(); - const is_iamcu = target.os.tag == .elfiamcu; const is_ve = target.cpu.arch == .ve; const has_crt_begin_end_files = target.abi != .none; // TODO: clang checks for MIPS vendor @@ -227,7 +226,7 @@ pub fn buildLinkerArgs(self: *const Linux, tc: *const Toolchain, argv: *std.Arra try argv.appendSlice(&.{ "-o", d.output_name orelse "a.out" }); if (!d.nostdlib and !d.nostartfiles and !d.relocatable) { - if (!is_android and !is_iamcu) { + if (!is_android) { if (!d.shared) { const crt1 = if (is_pie) "Scrt1.o" @@ -243,9 +242,7 @@ pub fn buildLinkerArgs(self: *const Linux, tc: *const Toolchain, argv: *std.Arra try argv.appendSlice(&.{ "-z", "max-page-size=0x4000000" }); } - if (is_iamcu) { - try argv.append(try tc.getFilePath("crt0.o")); - } else if (has_crt_begin_end_files) { + if (has_crt_begin_end_files) { var path: []const u8 = ""; if (tc.getRuntimeLibKind() == .compiler_rt and !is_android) { const crt_begin = try tc.getCompilerRt("crtbegin", .object); @@ -287,19 +284,13 @@ pub fn buildLinkerArgs(self: *const Linux, tc: *const Toolchain, argv: *std.Arra if (!d.nolibc) { try argv.append("-lc"); } - if (is_iamcu) { - try argv.append("-lgloss"); - } if (is_static or is_static_pie) { try argv.append("--end-group"); } else { try tc.addRuntimeLibs(argv); } - if (is_iamcu) { - try argv.appendSlice(&.{ "--as-needed", "-lsoftfp", "--no-as-needed" }); - } } - if (!d.nostartfiles and !is_iamcu) { + if (!d.nostartfiles) { if (has_crt_begin_end_files) { var path: []const u8 = ""; if (tc.getRuntimeLibKind() == .compiler_rt and !is_android) { -- Gitee From f954ddd40b43ec6fd4d349a13ba7ceff3c4e1055 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 4 May 2025 19:00:50 +0300 Subject: [PATCH 048/117] update to Zig 0.15.0-dev.460+f4e9846bc --- build.zig.zon | 2 +- src/aro/target.zig | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 1756682..d768c5f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ .fingerprint = 0x76501fb842f52025, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.0-dev.451+a843be44a", + .minimum_zig_version = "0.15.0-dev.460+f4e9846bc", .dependencies = .{}, diff --git a/src/aro/target.zig b/src/aro/target.zig index ef3e628..c0c63ec 100644 --- a/src/aro/target.zig +++ b/src/aro/target.zig @@ -499,6 +499,7 @@ pub fn get32BitArchVariant(target: std.Target) ?std.Target { .loongarch32, .xtensa, .propeller, + .or1k, => {}, // Already 32 bit .aarch64 => copy.cpu.arch = .arm, @@ -532,6 +533,7 @@ pub fn get64BitArchVariant(target: std.Target) ?std.Target { .xcore, .xtensa, .propeller, + .or1k, => return null, .aarch64, @@ -626,9 +628,10 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { .wasm32 => "wasm32", .wasm64 => "wasm64", .ve => "ve", - // Note: propeller1 and kalimba are not supported in LLVM; this is the Zig arch name + // Note: propeller1, kalimba and or1k are not supported in LLVM; this is the Zig arch name .kalimba => "kalimba", .propeller => "propeller", + .or1k => "or1k", }; writer.writeAll(llvm_arch) catch unreachable; writer.writeByte('-') catch unreachable; -- Gitee From 2b40e9158a3f79e853383325cdc91528ca936445 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 14 May 2025 21:49:16 +0300 Subject: [PATCH 049/117] Attribute: add sysv_abi --- src/aro/Attribute.zig | 1 + src/aro/Attribute/names.def | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 526df5c..5ac63ea 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -712,6 +712,7 @@ const attributes = struct { pub const vectorcall = struct {}; pub const cdecl = struct {}; pub const thiscall = struct {}; + pub const sysv_abi = struct {}; }; pub const Tag = std.meta.DeclEnum(attributes); diff --git a/src/aro/Attribute/names.def b/src/aro/Attribute/names.def index 8aff9ab..971fdf5 100644 --- a/src/aro/Attribute/names.def +++ b/src/aro/Attribute/names.def @@ -400,6 +400,10 @@ thiscall .tag = .thiscall .gnu = true +sysv_abi + .tag = .sysv_abi + .gnu = true + # declspec only align -- Gitee From d34fd53d75f3988c26deb1defad2377ffae18f73 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 14 May 2025 21:49:47 +0300 Subject: [PATCH 050/117] Parser: fix pretty_func decl_ref decl being undefined --- src/aro/Parser.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index df74373..d1432e4 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -8905,7 +8905,7 @@ fn primaryExpr(p: *Parser) Error!?Result { .decl_ref_expr = .{ .name_tok = p.tok_i, .qt = qt, - .decl = undefined, // TODO + .decl = p.func.pretty_ident.?.node, }, }), }; -- Gitee From 8acfd0c304927fe6a507e59d38e01a3d6f4ea222 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 14 May 2025 22:31:04 +0300 Subject: [PATCH 051/117] Attribute: add ms_abi --- src/aro/Attribute.zig | 17 +++++++++++++++++ src/aro/Attribute/names.def | 4 ++++ src/backend.zig | 2 ++ 3 files changed, 23 insertions(+) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 5ac63ea..16fb850 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -713,6 +713,7 @@ const attributes = struct { pub const cdecl = struct {}; pub const thiscall = struct {}; pub const sysv_abi = struct {}; + pub const ms_abi = struct {}; }; pub const Tag = std.meta.DeclEnum(attributes); @@ -1026,6 +1027,20 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) } else { try p.err(tok, .callconv_not_supported, .{"pcs"}); }, + .sysv_abi => if (p.comp.target.cpu.arch == .x86_64 and p.comp.target.os.tag == .windows) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .x86_64_sysv } }, + .syntax = attr.syntax, + }); + }, + .ms_abi => if (p.comp.target.cpu.arch == .x86_64 and p.comp.target.os.tag != .windows) { + try p.attr_application_buf.append(p.gpa, .{ + .tag = .calling_convention, + .args = .{ .calling_convention = .{ .cc = .x86_64_win } }, + .syntax = attr.syntax, + }); + }, .malloc => { if (base_qt.get(p.comp, .func).?.return_type.isPointer(p.comp)) { try p.attr_application_buf.append(p.gpa, attr); @@ -1242,6 +1257,8 @@ fn applyCallingConvention(attr: Attribute, p: *Parser, tok: TokenIndex, qt: Qual .aarch64_vector_pcs, .arm_aapcs, .arm_aapcs_vfp, + .x86_64_sysv, + .x86_64_win, => unreachable, // These can't come from keyword syntax } } diff --git a/src/aro/Attribute/names.def b/src/aro/Attribute/names.def index 971fdf5..fead095 100644 --- a/src/aro/Attribute/names.def +++ b/src/aro/Attribute/names.def @@ -404,6 +404,10 @@ sysv_abi .tag = .sysv_abi .gnu = true +ms_abi + .tag = .ms_abi + .gnu = true + # declspec only align diff --git a/src/backend.zig b/src/backend.zig index d3dd577..cb96a06 100644 --- a/src/backend.zig +++ b/src/backend.zig @@ -16,6 +16,8 @@ pub const CallingConvention = enum { aarch64_vector_pcs, arm_aapcs, arm_aapcs_vfp, + x86_64_sysv, + x86_64_win, }; pub const version_str = @import("build_options").version_str; -- Gitee From a513239539a826fdd3a5df5a2707a099c4c5a897 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 15 May 2025 11:40:58 +0300 Subject: [PATCH 052/117] Parser: add bitcasts when casting to/from void pointer --- src/aro/Parser.zig | 23 ++++++++++++++++------- test/cases/ast/cast kinds.c | 23 +++++++++++++++++++++++ test/cases/cast kinds.c | 5 +++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index d1432e4..0bb3422 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -674,14 +674,15 @@ fn nodeIs(p: *Parser, node: Node.Index, comptime tag: std.meta.Tag(Tree.Node)) b } pub fn getDecayedStringLiteral(p: *Parser, node: Node.Index) ?Value { - const cast = p.getNode(node, .cast) orelse return null; - if (cast.kind != .array_to_pointer) return null; - - var cur = cast.operand; + var cur = node; while (true) { switch (cur.get(&p.tree)) { .paren_expr => |un| cur = un.operand, .string_literal_expr => return p.tree.value_map.get(cur), + .cast => |cast| switch (cast.kind) { + .bitcast, .array_to_pointer => cur = cast.operand, + else => return null, + }, else => return null, } } @@ -6002,7 +6003,12 @@ pub const Result = struct { const b_elem = b.qt.childType(p.comp); if (!a_elem.eql(b_elem, p.comp)) { try p.err(tok, .comparison_distinct_ptr, .{ a.qt, b.qt }); + try b.castToPointer(p, a.qt, tok); } + } else if (a_sk == .void_pointer) { + try b.castToPointer(p, a.qt, tok); + } else if (b_sk == .void_pointer) { + try a.castToPointer(p, b.qt, tok); } } else if (a_sk.isPointer()) { try b.castToPointer(p, a.qt, tok); @@ -6313,6 +6319,9 @@ pub const Result = struct { _ = try res.val.intCast(ptr_qt, p.comp); res.qt = ptr_qt; try res.implicitCast(p, .int_to_pointer, tok); + } else if (src_sk.isPointer() and !res.qt.eql(ptr_qt, p.comp)) { + res.qt = ptr_qt; + try res.implicitCast(p, .bitcast, tok); } } @@ -6809,9 +6818,9 @@ pub const Result = struct { try res.castToPointer(p, dest_unqual, tok); return; } else if (src_sk == .void_pointer or dest_unqual.eql(res.qt, p.comp)) { - return; // ok - } else if (dest_sk == .void_pointer and src_sk.isPointer() or (res.qt.isInt(p.comp) and src_sk.isReal())) { - return; // ok + return res.castToPointer(p, dest_unqual, tok); + } else if (dest_sk == .void_pointer and src_sk.isPointer()) { + return res.castToPointer(p, dest_unqual, tok); } else if (src_sk.isPointer()) { const src_child = res.qt.childType(p.comp); const dest_child = dest_unqual.childType(p.comp); diff --git a/test/cases/ast/cast kinds.c b/test/cases/ast/cast kinds.c index 977dfb7..4789299 100644 --- a/test/cases/ast/cast kinds.c +++ b/test/cases/ast/cast kinds.c @@ -243,5 +243,28 @@ fn_def: 'fn () void' decl_ref_expr: 'float' lvalue name: f + variable: '*void' + name: vp + + assign_expr: '*void' + lhs: + decl_ref_expr: '*void' lvalue + name: vp + rhs: + implicit cast: (bitcast) '*void' + implicit cast: (lval_to_rval) '*int' + decl_ref_expr: '*int' lvalue + name: p + + assign_expr: '*int' + lhs: + decl_ref_expr: '*int' lvalue + name: p + rhs: + implicit cast: (bitcast) '*int' + implicit cast: (lval_to_rval) '*void' + decl_ref_expr: '*void' lvalue + name: vp + implicit return_stmt: 'void' diff --git a/test/cases/cast kinds.c b/test/cases/cast kinds.c index 724f15e..99ebecb 100644 --- a/test/cases/cast kinds.c +++ b/test/cases/cast kinds.c @@ -52,4 +52,9 @@ void foo(void) { u = (union U)x; // to_union u = (union U)f; // to_union + + + void *vp; + vp = p; // bitcast + p = vp; // bitcast } -- Gitee From e626d0db917ad60c7f45a15ad879c78a45812c4c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 15 May 2025 12:46:36 +0300 Subject: [PATCH 053/117] Parser: make pointer cast gaining qualifiers a no-op --- src/aro/Parser.zig | 22 ++++++++++++++++-- test/cases/ast/cast kinds.c | 46 +++++++++++++++++++++++++++++++++++++ test/cases/cast kinds.c | 10 +++++++- 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 0bb3422..78e7d37 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -680,7 +680,7 @@ pub fn getDecayedStringLiteral(p: *Parser, node: Node.Index) ?Value { .paren_expr => |un| cur = un.operand, .string_literal_expr => return p.tree.value_map.get(cur), .cast => |cast| switch (cast.kind) { - .bitcast, .array_to_pointer => cur = cast.operand, + .no_op, .bitcast, .array_to_pointer => cur = cast.operand, else => return null, }, else => return null, @@ -6319,9 +6319,27 @@ pub const Result = struct { _ = try res.val.intCast(ptr_qt, p.comp); res.qt = ptr_qt; try res.implicitCast(p, .int_to_pointer, tok); + } else if (src_sk == .nullptr_t) { + try res.nullToPointer(p, ptr_qt, tok); } else if (src_sk.isPointer() and !res.qt.eql(ptr_qt, p.comp)) { + if (ptr_qt.is(p.comp, .nullptr_t)) { + res.qt = .invalid; + return; + } + + const src_elem = res.qt.childType(p.comp); + const dest_elem = ptr_qt.childType(p.comp); res.qt = ptr_qt; - try res.implicitCast(p, .bitcast, tok); + + if (dest_elem.eql(src_elem, p.comp) and + (dest_elem.@"const" == src_elem.@"const" or dest_elem.@"const") and + (dest_elem.@"volatile" == src_elem.@"volatile" or dest_elem.@"volatile")) + { + // Gaining qualifiers is a no-op. + try res.implicitCast(p, .no_op, tok); + } else { + try res.implicitCast(p, .bitcast, tok); + } } } diff --git a/test/cases/ast/cast kinds.c b/test/cases/ast/cast kinds.c index 4789299..f51f128 100644 --- a/test/cases/ast/cast kinds.c +++ b/test/cases/ast/cast kinds.c @@ -266,5 +266,51 @@ fn_def: 'fn () void' decl_ref_expr: '*void' lvalue name: vp + variable: '*const int' + name: const_p + + assign_expr: '*const int' + lhs: + decl_ref_expr: '*const int' lvalue + name: const_p + rhs: + implicit cast: (no_op) '*const int' + implicit cast: (lval_to_rval) '*int' + decl_ref_expr: '*int' lvalue + name: p + + assign_expr: '*int' + lhs: + decl_ref_expr: '*int' lvalue + name: p + rhs: + implicit cast: (bitcast) '*int' + implicit cast: (lval_to_rval) '*const int' + decl_ref_expr: '*const int' lvalue + name: const_p + + variable: '*volatile int' + name: volatile_p + + assign_expr: '*volatile int' + lhs: + decl_ref_expr: '*volatile int' lvalue + name: volatile_p + rhs: + implicit cast: (no_op) '*volatile int' + implicit cast: (lval_to_rval) '*int' + decl_ref_expr: '*int' lvalue + name: p + + assign_expr: '*int' + lhs: + decl_ref_expr: '*int' lvalue + name: p + rhs: + implicit cast: (bitcast) '*int' + implicit cast: (lval_to_rval) '*volatile int' + decl_ref_expr: '*volatile int' lvalue + name: volatile_p + implicit return_stmt: 'void' diff --git a/test/cases/cast kinds.c b/test/cases/cast kinds.c index 99ebecb..74bd209 100644 --- a/test/cases/cast kinds.c +++ b/test/cases/cast kinds.c @@ -53,8 +53,16 @@ void foo(void) { u = (union U)x; // to_union u = (union U)f; // to_union - void *vp; vp = p; // bitcast p = vp; // bitcast + + #pragma GCC diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers" + const int *const_p; + const_p = p; // no_op + p = const_p; // bitcast + + volatile int *volatile_p; + volatile_p = p; // no_op + p = volatile_p; // bitcast } -- Gitee From ed0c2c316f84a25a136be3df97dcd7e8bbb3f326 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 19 May 2025 15:12:49 +0300 Subject: [PATCH 054/117] TypeStore: fix combining signed long long Closes #857 --- src/aro/TypeStore.zig | 4 +++- test/cases/avr sizeof long.c | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 92ddb9e..1ad044f 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -2731,11 +2731,13 @@ pub const Builder = struct { .long => switch (b.type) { .none => .long, .double => .long_double, - .long => .long_long, .unsigned => .ulong, .signed => .slong, .int => .long_int, + .uint => .ulong_int, .sint => .slong_int, + .long => .long_long, + .slong => .slong_long, .ulong => .ulong_long, .complex => .complex_long, .complex_signed => .complex_slong, diff --git a/test/cases/avr sizeof long.c b/test/cases/avr sizeof long.c index f2c1a96..6b00f4d 100644 --- a/test/cases/avr sizeof long.c +++ b/test/cases/avr sizeof long.c @@ -2,10 +2,14 @@ _Static_assert(sizeof(int) == 2, "wrong sizeof int"); _Static_assert(sizeof(unsigned) == 2, "wrong sizeof unsigned"); +_Static_assert(sizeof(signed) == 2, "wrong sizeof signed"); _Static_assert(sizeof(short) == 2, "wrong sizeof short"); _Static_assert(sizeof(unsigned short) == 2, "wrong sizeof unsigned short"); +_Static_assert(sizeof(signed short) == 2, "wrong sizeof signed short"); _Static_assert(sizeof(long) == 4, "wrong sizeof long"); _Static_assert(sizeof(unsigned long) == 4, "wrong sizeof unsigned long"); +_Static_assert(sizeof(signed long) == 4, "wrong sizeof signed long"); _Static_assert(sizeof(long double) == 4, "wrong sizeof long double"); _Static_assert(sizeof(long long) == 8, "wrong sizeof long long"); _Static_assert(sizeof(unsigned long long) == 8, "wrong sizeof unsigned long long"); +_Static_assert(sizeof(signed long long) == 8, "wrong sizeof signed long long"); -- Gitee From 348624998092bf4173f365c21fc8c0f43851ce97 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 19 May 2025 15:22:15 +0300 Subject: [PATCH 055/117] Parser: fix anonymous record being added to AST twice by compound literal Closes #847 --- src/aro/Parser.zig | 60 +++++++++++++++++++--------------- test/cases/compound literals.c | 6 +++- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 78e7d37..72ea214 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -7486,9 +7486,11 @@ fn castExpr(p: *Parser) Error!?Result { try p.expectClosing(l_paren, .r_paren); if (p.tok_ids[p.tok_i] == .l_brace) { - // Compound literal; handled in unExpr - p.tok_i = l_paren; - break :cast_expr; + var lhs = (try p.compoundLiteral(ty, l_paren)).?; + while (try p.suffixExpr(lhs)) |suffix| { + lhs = suffix; + } + return lhs; } const operand_tok = p.tok_i; @@ -8188,7 +8190,7 @@ fn unExpr(p: *Parser) Error!?Result { return operand; }, else => { - var lhs = (try p.compoundLiteral()) orelse + var lhs = (try p.compoundLiteral(null, null)) orelse (try p.primaryExpr()) orelse return null; @@ -8203,33 +8205,37 @@ fn unExpr(p: *Parser) Error!?Result { /// compoundLiteral /// : '(' storageClassSpec* type_name ')' '{' initializer_list '}' /// | '(' storageClassSpec* type_name ')' '{' initializer_list ',' '}' -fn compoundLiteral(p: *Parser) Error!?Result { - const l_paren = p.eatToken(.l_paren) orelse return null; +fn compoundLiteral(p: *Parser, qt_opt: ?QualType, opt_l_paren: ?TokenIndex) Error!?Result { + const l_paren, const d = if (qt_opt) |some| .{ opt_l_paren.?, DeclSpec{ .qt = some } } else blk: { + const l_paren = p.eatToken(.l_paren) orelse return null; - var d: DeclSpec = .{ .qt = .invalid }; - const any = if (p.comp.langopts.standard.atLeast(.c23)) - try p.storageClassSpec(&d) - else - false; - - switch (d.storage_class) { - .auto, .@"extern", .typedef => |tok| { - try p.err(tok, .invalid_compound_literal_storage_class, .{@tagName(d.storage_class)}); - d.storage_class = .none; - }, - .register => if (p.func.qt == null) try p.err(p.tok_i, .illegal_storage_on_global, .{}), - else => {}, - } + var d: DeclSpec = .{ .qt = .invalid }; + const any = if (p.comp.langopts.standard.atLeast(.c23)) + try p.storageClassSpec(&d) + else + false; - var qt = (try p.typeName()) orelse { - p.tok_i = l_paren; - if (any) { - try p.err(p.tok_i, .expected_type, .{}); - return error.ParsingFailed; + switch (d.storage_class) { + .auto, .@"extern", .typedef => |tok| { + try p.err(tok, .invalid_compound_literal_storage_class, .{@tagName(d.storage_class)}); + d.storage_class = .none; + }, + .register => if (p.func.qt == null) try p.err(p.tok_i, .illegal_storage_on_global, .{}), + else => {}, } - return null; + + d.qt = (try p.typeName()) orelse { + p.tok_i = l_paren; + if (any) { + try p.err(p.tok_i, .expected_type, .{}); + return error.ParsingFailed; + } + return null; + }; + try p.expectClosing(l_paren, .r_paren); + break :blk .{ l_paren, d }; }; - try p.expectClosing(l_paren, .r_paren); + var qt = d.qt; switch (qt.base(p.comp).type) { .func => try p.err(p.tok_i, .func_init, .{}), diff --git a/test/cases/compound literals.c b/test/cases/compound literals.c index 8297206..4699205 100644 --- a/test/cases/compound literals.c +++ b/test/cases/compound literals.c @@ -30,7 +30,11 @@ void baz() { &(register int){0}; &(static thread_local int){0}; &(extern int){0}; -} +} + +unsigned long qux(unsigned long x) { + return ((union{unsigned long _x;}){x})._x; +} #define EXPECTED_ERRORS \ "compound literals.c:21:32: warning: array index 10 is past the end of the array [-Warray-bounds]" \ -- Gitee From 0ba11fd46816886829277f88213302011d4c8c9d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 20 May 2025 20:12:41 +0300 Subject: [PATCH 056/117] Parser: do not treat extern incomplete arrays as tentative definitions --- src/aro/Parser.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 72ea214..1c41ca2 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -1312,7 +1312,7 @@ fn decl(p: *Parser) Error!bool { } else { try decl_spec.validateDecl(p); var node_qt = init_d.d.qt; - if (p.func.qt == null) { + if (p.func.qt == null and decl_spec.storage_class != .@"extern") { if (node_qt.get(p.comp, .array)) |array_ty| { if (array_ty.len == .incomplete) { // Create tentative array node with fixed type. -- Gitee From 7649da892e1b4d184af185fa6258e024eb30bedd Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 21 May 2025 22:17:33 +0300 Subject: [PATCH 057/117] Parser: fix parsing of complex initializers --- src/aro/InitList.zig | 46 +-- src/aro/Parser.zig | 561 +++++++++++++++++++--------------- src/aro/Parser/Diagnostic.zig | 6 + 3 files changed, 314 insertions(+), 299 deletions(-) diff --git a/src/aro/InitList.zig b/src/aro/InitList.zig index b2edc4f..8c39209 100644 --- a/src/aro/InitList.zig +++ b/src/aro/InitList.zig @@ -22,7 +22,7 @@ const Item = struct { const InitList = @This(); -list: std.ArrayListUnmanaged(Item) = .{}, +list: std.ArrayListUnmanaged(Item) = .empty, node: Node.OptIndex = .null, tok: TokenIndex = 0, @@ -33,50 +33,6 @@ pub fn deinit(il: *InitList, gpa: Allocator) void { il.* = undefined; } -/// Insert initializer at index, returning previous entry if one exists. -pub fn put(il: *InitList, gpa: Allocator, index: usize, node: Node.Index, tok: TokenIndex) !?TokenIndex { - const items = il.list.items; - var left: usize = 0; - var right: usize = items.len; - - // Append new value to empty list - if (left == right) { - const item = try il.list.addOne(gpa); - item.* = .{ - .list = .{ .node = .pack(node), .tok = tok }, - .index = index, - }; - return null; - } - - while (left < right) { - // Avoid overflowing in the midpoint calculation - const mid = left + (right - left) / 2; - // Compare the key with the midpoint element - switch (std.math.order(index, items[mid].index)) { - .eq => { - // Replace previous entry. - const prev = items[mid].list.tok; - items[mid].list.deinit(gpa); - items[mid] = .{ - .list = .{ .node = .pack(node), .tok = tok }, - .index = index, - }; - return prev; - }, - .gt => left = mid + 1, - .lt => right = mid, - } - } - - // Insert a new value into a sorted position. - try il.list.insert(gpa, left, .{ - .list = .{ .node = .pack(node), .tok = tok }, - .index = index, - }); - return null; -} - /// Find item at index, create new if one does not exist. pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList { const items = il.list.items; diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 1c41ca2..beb2519 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -122,16 +122,18 @@ enum_buf: std.ArrayList(Type.Enum.Field), /// Record type fields. record_buf: std.ArrayList(Type.Record.Field), /// Attributes that have been parsed but not yet validated or applied. -attr_buf: std.MultiArrayList(TentativeAttribute) = .{}, +attr_buf: std.MultiArrayList(TentativeAttribute) = .empty, /// Used to store validated attributes before they are applied to types. -attr_application_buf: std.ArrayListUnmanaged(Attribute) = .{}, +attr_application_buf: std.ArrayListUnmanaged(Attribute) = .empty, /// type name -> variable name location for tentative definitions (top-level defs with thus-far-incomplete types) /// e.g. `struct Foo bar;` where `struct Foo` is not defined yet. /// The key is the StringId of `Foo` and the value is the TokenIndex of `bar` /// Items are removed if the type is subsequently completed with a definition. /// We only store the first tentative definition that uses a given type because this map is only used /// for issuing an error message, and correcting the first error for a type will fix all of them for that type. -tentative_defs: std.AutoHashMapUnmanaged(StringId, TokenIndex) = .{}, +tentative_defs: std.AutoHashMapUnmanaged(StringId, TokenIndex) = .empty, +// TODO InitList pointers aren't stable +init_list_index: std.AutoHashMapUnmanaged(*InitList, u64) = .empty, // configuration and miscellaneous info no_eval: bool = false, @@ -785,6 +787,7 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { p.attr_buf.deinit(pp.comp.gpa); p.attr_application_buf.deinit(pp.comp.gpa); p.tentative_defs.deinit(pp.comp.gpa); + p.init_list_index.deinit(pp.comp.gpa); } try p.syms.pushScope(&p); @@ -3898,7 +3901,7 @@ fn initializer(p: *Parser, init_qt: QualType) Error!Result { if (p.tok_ids[p.tok_i] != .l_brace) { const tok = p.tok_i; var res = try p.expect(assignExpr); - if (try p.coerceArrayInit(&res, tok, init_qt)) return res; + if (try p.coerceArrayInit(res, tok, init_qt)) return res; try p.coerceInit(&res, tok, init_qt); return res; } @@ -3914,12 +3917,14 @@ fn initializer(p: *Parser, init_qt: QualType) Error!Result { final_init_qt = .invalid; } + // TODO get rid of if (final_init_qt.is(p.comp, .complex)) { return p.complexInitializer(final_init_qt); } var il: InitList = .{}; defer il.deinit(p.gpa); + p.init_list_index.clearRetainingCapacity(); _ = try p.initializerItem(&il, final_init_qt); const res = try p.convertInitList(il, final_init_qt); @@ -3927,23 +3932,12 @@ fn initializer(p: *Parser, init_qt: QualType) Error!Result { } /// initializerItems : designation? initializer (',' designation? initializer)* ','? -/// designation : designator+ '=' -/// designator -/// : '[' integerConstExpr ']' -/// | '.' identifier fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { const l_brace = p.eatToken(.l_brace) orelse { const tok = p.tok_i; - var res = (try p.assignExpr()) orelse return false; + const res = (try p.assignExpr()) orelse return false; - const arr = try p.coerceArrayInit(&res, tok, init_qt); - if (!arr) try p.coerceInit(&res, tok, init_qt); - if (il.tok != 0 and !init_qt.isInvalid()) { - try p.err(tok, .initializer_overrides, .{}); - try p.err(il.tok, .previous_initializer, .{}); - } - il.node = .pack(res.node); - il.tok = tok; + try p.setInitializer(il, init_qt, tok, res); return true; }; @@ -3968,98 +3962,20 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { var count: u64 = 0; var warned_excess = init_qt.isInvalid(); var is_str_init = false; - var index_hint: ?u64 = null; - while (true) : (count += 1) { + while (true) { errdefer p.skipTo(.r_brace); var first_tok = p.tok_i; - var cur_qt = init_qt; - var cur_il = il; - var designation = false; - var cur_index_hint: ?u64 = null; - while (true) { - if (p.eatToken(.l_bracket)) |l_bracket| { - const array_ty = cur_qt.get(p.comp, .array) orelse { - try p.err(l_bracket, .invalid_array_designator, .{cur_qt}); - return error.ParsingFailed; - }; - const expr_tok = p.tok_i; - const index_res = try p.integerConstExpr(.gnu_folding_extension); - try p.expectClosing(l_bracket, .r_bracket); - designation = true; - if (cur_qt.isInvalid()) continue; - - if (index_res.val.opt_ref == .none) { - try p.err(expr_tok, .expected_integer_constant_expr, .{}); - return error.ParsingFailed; - } else if (index_res.val.compare(.lt, .zero, p.comp)) { - try p.err(l_bracket + 1, .negative_array_designator, .{index_res}); - return error.ParsingFailed; - } - - const max_len = switch (array_ty.len) { - .fixed, .static => |len| len, - else => std.math.maxInt(u64), - }; - const index_int = index_res.val.toInt(u64, p.comp) orelse std.math.maxInt(u64); - if (index_int >= max_len) { - try p.err(l_bracket + 1, .oob_array_designator, .{index_res}); - return error.ParsingFailed; - } - cur_index_hint = cur_index_hint orelse index_int; - - cur_il = try cur_il.find(p.gpa, index_int); - cur_qt = array_ty.elem; - } else if (p.eatToken(.period)) |period| { - const field_tok = try p.expectIdentifier(); - designation = true; - if (cur_qt.isInvalid()) continue; - - const field_str = p.tokSlice(field_tok); - const target_name = try p.comp.internString(field_str); - const record_ty = cur_qt.getRecord(p.comp) orelse { - try p.err(period, .invalid_field_designator, .{cur_qt}); - return error.ParsingFailed; - }; - if (!record_ty.hasField(p.comp, target_name)) { - try p.err(period, .no_such_field_designator, .{field_str}); - return error.ParsingFailed; - } - - // TODO check if union already has field set - for (record_ty.fields, 0..) |field, field_index| { - if (field.name_tok == 0) if (field.qt.getRecord(p.comp)) |field_record_ty| { - // Recurse into anonymous field if it has a field by the name. - if (!field_record_ty.hasField(p.comp, target_name)) continue; - cur_qt = field.qt; - cur_il = try il.find(p.gpa, field_index); - cur_index_hint = cur_index_hint orelse field_index; - break; - }; - if (field.name == target_name) { - cur_il = try cur_il.find(p.gpa, field_index); - cur_qt = field.qt; - cur_index_hint = cur_index_hint orelse field_index; - break; - } - } - } else break; - } - if (designation) index_hint = null; - defer index_hint = cur_index_hint orelse null; - - if (designation) { - if (p.eatToken(.equal) == null) { - try p.err(p.tok_i, .gnu_missing_eq_designator, .{}); - } - } - - if (!designation and cur_qt.hasAttribute(p.comp, .designated_init)) { + const designated = try p.designation(il, init_qt); + if (!designated and init_qt.hasAttribute(p.comp, .designated_init)) { try p.err(p.tok_i, .designated_init_needed, .{}); } var saw = false; if (is_str_init and p.isStringInit(init_qt)) { + if (!warned_excess) try p.err(first_tok, .excess_str_init, .{}); + warned_excess = true; + // discard further strings var tmp_il: InitList = .{}; defer tmp_il.deinit(p.gpa); @@ -4068,272 +3984,366 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { is_str_init = true; saw = try p.initializerItem(il, init_qt); } else if (is_scalar and count >= scalar_inits_needed) { + if (!warned_excess) try p.err(first_tok, .excess_scalar_init, .{}); + warned_excess = true; + // discard further scalars var tmp_il: InitList = .{}; defer tmp_il.deinit(p.gpa); saw = try p.initializerItem(&tmp_il, .void); } else if (p.tok_ids[p.tok_i] == .l_brace) { - if (designation) { - // designation overrides previous value, let existing mechanism handle it - saw = try p.initializerItem(cur_il, cur_qt); - } else if (try p.findAggregateInitializer(&cur_il, &cur_qt, &index_hint)) { - saw = try p.initializerItem(cur_il, cur_qt); + if (try p.findAggregateInitializer(il, init_qt)) |item| { + saw = try p.initializerItem(item.il, item.qt); } else { // discard further values var tmp_il: InitList = .{}; defer tmp_il.deinit(p.gpa); saw = try p.initializerItem(&tmp_il, .void); - if (!warned_excess) try p.err(first_tok, if (init_qt.is(p.comp, .array)) .excess_array_init else .excess_struct_init, .{}); + if (!warned_excess) try p.err(first_tok, switch (init_qt.base(p.comp).type) { + .array => .excess_array_init, + .@"struct" => .excess_struct_init, + .vector => .excess_vector_init, + else => .excess_scalar_init, + }, .{}); warned_excess = true; } } else single_item: { first_tok = p.tok_i; - var res = (try p.assignExpr()) orelse { + const res = (try p.assignExpr()) orelse { saw = false; break :single_item; }; saw = true; - excess: { - if (index_hint) |*hint| { - if (try p.findScalarInitializerAt(&cur_il, &cur_qt, &res, first_tok, hint)) break :excess; - } else if (try p.findScalarInitializer(&cur_il, &cur_qt, &res, first_tok)) break :excess; - - if (designation) break :excess; - if (!warned_excess) try p.err(first_tok, if (init_qt.is(p.comp, .array)) .excess_array_init else .excess_struct_init, .{}); - warned_excess = true; - - break :single_item; - } - - const arr = try p.coerceArrayInit(&res, first_tok, cur_qt); - if (!arr) try p.coerceInit(&res, first_tok, cur_qt); - if (cur_il.tok != 0 and !init_qt.isInvalid()) { - try p.err(first_tok, .initializer_overrides, .{}); - try p.err(cur_il.tok, .previous_initializer, .{}); - } - cur_il.node = .pack(res.node); - cur_il.tok = first_tok; + _ = try p.findScalarInitializer(il, init_qt, res, first_tok, &warned_excess); } if (!saw) { - if (designation) { + if (designated) { try p.err(p.tok_i, .expected_expr, .{}); return error.ParsingFailed; } break; - } else if (count == 1) { - if (is_str_init) try p.err(first_tok, .excess_str_init, .{}); - if (is_scalar and !is_complex) try p.err(first_tok, .excess_scalar_init, .{}); - } else if (count == 2) { - if (is_scalar and !is_complex) try p.err(first_tok, .excess_scalar_init, .{}); } + count += 1; if (p.eatToken(.comma) == null) break; } try p.expectClosing(l_brace, .r_brace); - if (is_scalar and is_complex and count == 1) { // count of 1 means we saw exactly 2 items in the initializer list + if (is_scalar and is_complex and count == 2) { + // TODO move to convertInitList try p.err(l_brace, .complex_component_init, .{}); } - if (is_scalar or is_str_init) return true; + return true; +} + +fn setInitializer(p: *Parser, il: *InitList, init_qt: QualType, tok: TokenIndex, res: Result) !void { + var copy = res; + + const arr = try p.coerceArrayInit(copy, tok, init_qt); + if (!arr) try p.coerceInit(©, tok, init_qt); if (il.tok != 0 and !init_qt.isInvalid()) { - try p.err(l_brace, .initializer_overrides, .{}); + try p.err(tok, .initializer_overrides, .{}); try p.err(il.tok, .previous_initializer, .{}); } - il.node = .null; - il.tok = l_brace; - return true; + il.node = .pack(copy.node); + il.tok = tok; } -/// Returns true if the value is unused. -fn findScalarInitializerAt(p: *Parser, il: **InitList, qt: *QualType, res: *Result, first_tok: TokenIndex, start_index: *u64) Error!bool { - switch (qt.base(p.comp).type) { - .array => |array_ty| { - if (il.*.node != .null) return false; - start_index.* += 1; +/// designation : designator+ '='? +/// designator +/// : '[' integerConstExpr ']' +/// | '.' identifier +fn designation(p: *Parser, il: *InitList, init_qt: QualType) !bool { + switch (p.tok_ids[p.tok_i]) { + .l_bracket, .period => p.init_list_index.clearRetainingCapacity(), + else => return false, + } + + var cur_qt = init_qt; + var cur_il = il; + while (true) { + if (p.eatToken(.l_bracket)) |l_bracket| { + const array_ty = cur_qt.get(p.comp, .array) orelse { + try p.err(l_bracket, .invalid_array_designator, .{cur_qt}); + return error.ParsingFailed; + }; + const expr_tok = p.tok_i; + const index_res = try p.integerConstExpr(.gnu_folding_extension); + try p.expectClosing(l_bracket, .r_bracket); + if (cur_qt.isInvalid()) continue; + + if (index_res.val.opt_ref == .none) { + try p.err(expr_tok, .expected_integer_constant_expr, .{}); + return error.ParsingFailed; + } else if (index_res.val.compare(.lt, .zero, p.comp)) { + try p.err(l_bracket + 1, .negative_array_designator, .{index_res}); + return error.ParsingFailed; + } const max_len = switch (array_ty.len) { .fixed, .static => |len| len, else => std.math.maxInt(u64), }; - if (max_len == 0) { - try p.err(first_tok, .empty_aggregate_init_braces, .{}); + const index_int = index_res.val.toInt(u64, p.comp) orelse std.math.maxInt(u64); + if (index_int >= max_len) { + try p.err(l_bracket + 1, .oob_array_designator, .{index_res}); return error.ParsingFailed; } - const arr_il = il.*; - if (start_index.* < max_len) { - qt.* = array_ty.elem; - il.* = try arr_il.find(p.gpa, start_index.*); - _ = try p.findScalarInitializer(il, qt, res, first_tok); - return true; - } - return false; - }, - .@"struct" => |struct_ty| { - if (il.*.node != .null) return false; - start_index.* += 1; + try p.init_list_index.put(p.gpa, cur_il, index_int); + cur_il = try cur_il.find(p.gpa, index_int); + cur_qt = array_ty.elem; + } else if (p.eatToken(.period)) |period| { + const field_tok = try p.expectIdentifier(); + if (cur_qt.isInvalid()) continue; - if (struct_ty.fields.len == 0) { - try p.err(first_tok, .empty_aggregate_init_braces, .{}); + const field_str = p.tokSlice(field_tok); + const target_name = try p.comp.internString(field_str); + const record_ty = cur_qt.getRecord(p.comp) orelse { + try p.err(period, .invalid_field_designator, .{cur_qt}); + return error.ParsingFailed; + }; + if (!record_ty.hasField(p.comp, target_name)) { + try p.err(period, .no_such_field_designator, .{field_str}); return error.ParsingFailed; } - const struct_il = il.*; - if (start_index.* < struct_ty.fields.len) { - qt.* = struct_ty.fields[@intCast(start_index.*)].qt; - il.* = try struct_il.find(p.gpa, start_index.*); - _ = try p.findScalarInitializer(il, qt, res, first_tok); - return true; + + // TODO check if union already has field set + for (record_ty.fields, 0..) |field, field_index| { + if (field.name_tok == 0) if (field.qt.getRecord(p.comp)) |field_record_ty| { + // Recurse into anonymous field if it has a field by the name. + if (!field_record_ty.hasField(p.comp, target_name)) continue; + cur_qt = field.qt; + try p.init_list_index.put(p.gpa, cur_il, field_index); + cur_il = try il.find(p.gpa, field_index); + break; + }; + if (field.name == target_name) { + cur_qt = field.qt; + try p.init_list_index.put(p.gpa, cur_il, field_index); + cur_il = try cur_il.find(p.gpa, field_index); + break; + } } - return false; - }, - .@"union" => { - return false; - }, - else => return il.*.node == .null, + } else break; + } + + if (p.eatToken(.equal) == null) { + try p.err(p.tok_i, .gnu_missing_eq_designator, .{}); } + return true; } -/// Returns true if the value is unused. -fn findScalarInitializer(p: *Parser, il: **InitList, qt: *QualType, res: *Result, first_tok: TokenIndex) Error!bool { - const actual_qt = res.qt; - if (qt.isInvalid()) return il.*.node == .null; +/// Returns true if the item was filled. +fn findScalarInitializer( + p: *Parser, + il: *InitList, + qt: QualType, + res: Result, + first_tok: TokenIndex, + warned_excess: *bool, +) Error!bool { + if (qt.isInvalid()) return false; + switch (qt.base(p.comp).type) { - // .complex TODO - .array => |array_ty| { - if (il.*.node != .null) return false; - if (try p.coerceArrayInitExtra(res, first_tok, qt.*, false)) return true; + .complex => |complex_ty| { + const index = p.init_list_index.get(il) orelse 0; + if (il.node != .null or index >= 2) { + if (!warned_excess.*) try p.err(first_tok, .excess_scalar_init, .{}); + warned_excess.* = true; + return true; + } + if (res.qt.eql(qt, p.comp)) { + try p.setInitializer(il, qt, first_tok, res); + return true; + } - const start_index = il.*.list.items.len; - var index = if (start_index != 0) il.*.list.items[start_index - 1].index else start_index; + const elem_il = try il.find(p.gpa, index); + if (try p.findScalarInitializer(elem_il, complex_ty, res, first_tok, warned_excess)) { + const new_index = index + 1; + try p.init_list_index.put(p.gpa, il, new_index); + return new_index >= 2; + } + return false; + }, + .vector => |vector_ty| { + const index = p.init_list_index.get(il) orelse 0; + if (il.node != .null or index >= vector_ty.len) { + if (!warned_excess.*) try p.err(first_tok, .excess_vector_init, .{}); + warned_excess.* = true; + return true; + } + if (res.qt.eql(qt, p.comp)) { + try p.setInitializer(il, qt, first_tok, res); + return true; + } + + const elem_il = try il.find(p.gpa, index); + if (try p.findScalarInitializer(elem_il, vector_ty.elem, res, first_tok, warned_excess)) { + const new_index = index + 1; + try p.init_list_index.put(p.gpa, il, new_index); + return new_index >= vector_ty.len; + } + + return false; + }, + .array => |array_ty| { const max_len = switch (array_ty.len) { .fixed, .static => |len| len, else => std.math.maxInt(u64), }; if (max_len == 0) { try p.err(first_tok, .empty_aggregate_init_braces, .{}); - return error.ParsingFailed; + return true; } - const arr_il = il.*; - while (index < max_len) : (index += 1) { - qt.* = array_ty.elem; - il.* = try arr_il.find(p.gpa, index); + const index = p.init_list_index.get(il) orelse 0; + if (il.node != .null or index >= max_len) { + if (!warned_excess.*) try p.err(first_tok, .excess_array_init, .{}); + warned_excess.* = true; + return true; + } + if (try p.coerceArrayInitExtra(res, first_tok, qt, false)) { + try p.setInitializer(il, qt, first_tok, res); + return true; + } - if (il.*.node == .null and actual_qt.eql(array_ty.elem, p.comp)) return true; - if (try p.findScalarInitializer(il, qt, res, first_tok)) return true; + const elem_il = try il.find(p.gpa, index); + if (try p.findScalarInitializer(elem_il, array_ty.elem, res, first_tok, warned_excess)) { + const new_index = index + 1; + try p.init_list_index.put(p.gpa, il, new_index); + return new_index >= max_len; } + return false; }, .@"struct" => |struct_ty| { - if (il.*.node != .null) return false; - if (actual_qt.eql(qt.*, p.comp)) return true; - const start_index = il.*.list.items.len; - var index = if (start_index != 0) il.*.list.items[start_index - 1].index + 1 else start_index; - if (struct_ty.fields.len == 0) { try p.err(first_tok, .empty_aggregate_init_braces, .{}); - return error.ParsingFailed; + return true; + } + + const index = p.init_list_index.get(il) orelse 0; + if (il.node != .null or index >= struct_ty.fields.len) { + if (!warned_excess.*) try p.err(first_tok, .excess_struct_init, .{}); + warned_excess.* = true; + return true; + } + if (res.qt.eql(qt, p.comp)) { + try p.setInitializer(il, qt, first_tok, res); + return true; } - const struct_il = il.*; - while (index < struct_ty.fields.len) : (index += 1) { - const field = struct_ty.fields[@intCast(index)]; - qt.* = field.qt; - il.* = try struct_il.find(p.gpa, index); - if (il.*.node == .null and actual_qt.eql(field.qt, p.comp)) return true; - if (il.*.node == .null and try p.coerceArrayInitExtra(res, first_tok, qt.*, false)) return true; - if (try p.findScalarInitializer(il, qt, res, first_tok)) return true; + const field = struct_ty.fields[@intCast(index)]; + const field_il = try il.find(p.gpa, index); + if (try p.findScalarInitializer(field_il, field.qt, res, first_tok, warned_excess)) { + const new_index = index + 1; + try p.init_list_index.put(p.gpa, il, new_index); + return new_index >= struct_ty.fields.len; } + return false; }, .@"union" => |union_ty| { - if (il.*.node != .null) return false; - if (actual_qt.eql(qt.*, p.comp)) return true; if (union_ty.fields.len == 0) { try p.err(first_tok, .empty_aggregate_init_braces, .{}); - return error.ParsingFailed; + return true; + } + + const index = p.init_list_index.get(il) orelse 0; + if (il.node != .null or index != 0 or res.qt.eql(qt, p.comp)) { + try p.setInitializer(il, qt, first_tok, res); + return true; } - qt.* = union_ty.fields[0].qt; - il.* = try il.*.find(p.gpa, 0); + const field = union_ty.fields[0]; + const field_il = try il.find(p.gpa, index); + if (try p.findScalarInitializer(field_il, field.qt, res, first_tok, warned_excess)) { + const new_index = index + 1; + try p.init_list_index.put(p.gpa, il, new_index); + } - // if (il.*.node == null and actual_ty.eql(ty, p.comp, false)) return true; - if (try p.coerceArrayInitExtra(res, first_tok, qt.*, false)) return true; - if (try p.findScalarInitializer(il, qt, res, first_tok)) return true; - return false; + return true; + }, + else => { + try p.setInitializer(il, qt, first_tok, res); + return true; }, - else => return il.*.node == .null, } } -fn findAggregateInitializer(p: *Parser, il: **InitList, qt: *QualType, start_index: *?u64) Error!bool { - if (qt.isInvalid()) return il.*.node == .null; +const InitItem = struct { il: *InitList, qt: QualType }; + +fn findAggregateInitializer(p: *Parser, il: *InitList, qt: QualType) Error!?InitItem { + if (qt.isInvalid()) { + if (il.node != .null) return .{ .il = il, .qt = qt }; + return null; + } switch (qt.base(p.comp).type) { + .complex => |complex_ty| { + if (il.node != .null) return null; + const index = p.init_list_index.get(il) orelse 0; + + if (index < 2) { + try p.init_list_index.put(p.gpa, il, index + 1); + return .{ .il = try il.find(p.gpa, index), .qt = complex_ty }; + } + }, + .vector => |vector_ty| { + if (il.node != .null) return null; + const index = p.init_list_index.get(il) orelse 0; + + if (index < vector_ty.len) { + try p.init_list_index.put(p.gpa, il, index + 1); + return .{ .il = try il.find(p.gpa, index), .qt = vector_ty.elem }; + } + }, .array => |array_ty| { - if (il.*.node != .null) return false; - const list_index = il.*.list.items.len; - const index = if (start_index.*) |*some| blk: { - some.* += 1; - break :blk some.*; - } else if (list_index != 0) - il.*.list.items[list_index - 1].index + 1 - else - list_index; + if (il.node != .null) return null; + const index = p.init_list_index.get(il) orelse 0; const max_len = switch (array_ty.len) { .fixed, .static => |len| len, else => std.math.maxInt(u64), }; if (index < max_len) { - qt.* = array_ty.elem; - il.* = try il.*.find(p.gpa, index); - return true; + try p.init_list_index.put(p.gpa, il, index + 1); + return .{ .il = try il.find(p.gpa, index), .qt = array_ty.elem }; } - return false; }, .@"struct" => |struct_ty| { - if (il.*.node != .null) return false; - const list_index = il.*.list.items.len; - const index = if (start_index.*) |*some| blk: { - some.* += 1; - break :blk some.*; - } else if (list_index != 0) - il.*.list.items[list_index - 1].index + 1 - else - list_index; + if (il.node != .null) return null; + const index = p.init_list_index.get(il) orelse 0; const field_count = struct_ty.fields.len; if (index < field_count) { - qt.* = struct_ty.fields[@intCast(index)].qt; - il.* = try il.*.find(p.gpa, index); - return true; + try p.init_list_index.put(p.gpa, il, index + 1); + const field_qt = struct_ty.fields[@intCast(index)].qt; + return .{ .il = try il.find(p.gpa, index), .qt = field_qt }; } - return false; }, .@"union" => |union_ty| { - if (il.*.node != .null) return false; - if (start_index.*) |_| return false; // overrides - if (union_ty.fields.len == 0) return false; + if (il.node != .null) return null; + if (union_ty.fields.len == 0) return null; - qt.* = union_ty.fields[0].qt; - il.* = try il.*.find(p.gpa, 0); - return true; + return .{ .il = try il.find(p.gpa, 0), .qt = union_ty.fields[0].qt }; }, else => { try p.err(p.tok_i, .too_many_scalar_init_braces, .{}); - return il.*.node == .null; + if (il.node != .null) return .{ .il = il, .qt = qt }; }, } + return null; } -fn coerceArrayInit(p: *Parser, item: *Result, tok: TokenIndex, target: QualType) !bool { +fn coerceArrayInit(p: *Parser, item: Result, tok: TokenIndex, target: QualType) !bool { return p.coerceArrayInitExtra(item, tok, target, true); } -fn coerceArrayInitExtra(p: *Parser, item: *Result, tok: TokenIndex, target: QualType, report_err: bool) !bool { +fn coerceArrayInitExtra(p: *Parser, item: Result, tok: TokenIndex, target: QualType, report_err: bool) !bool { if (target.isInvalid()) return false; const target_array_ty = target.get(p.comp, .array) orelse return false; @@ -4402,8 +4412,7 @@ fn coerceInit(p: *Parser, item: *Result, tok: TokenIndex, target: QualType) !voi try p.err(tok, diagnostic, .{}); } if (target.@"const" or p.init_context == .constexpr) { - var copy = item.*; - return copy.saveValue(p); + return item.putValue(p); } return item.saveValue(p); } @@ -4446,6 +4455,50 @@ fn convertInitList(p: *Parser, il: InitList, init_qt: QualType) Error!Node.Index switch (init_qt.base(p.comp).type) { // .complex => TODO + .vector => |vector_ty| { + if (il.node.unpack()) |some| return some; + + const list_buf_top = p.list_buf.items.len; + defer p.list_buf.items.len = list_buf_top; + + const elem_ty = init_qt.childType(p.comp); + + const max_len = vector_ty.len; + var start: u64 = 0; + for (il.list.items) |*init| { + if (init.index > start) { + const elem = try p.addNode(.{ + .array_filler_expr = .{ + .last_tok = p.tok_i - 1, + .count = init.index - start, + .qt = elem_ty, + }, + }); + try p.list_buf.append(elem); + } + start = init.index + 1; + + const elem = try p.convertInitList(init.list, elem_ty); + try p.list_buf.append(elem); + } + + if (start < max_len) { + const elem = try p.addNode(.{ + .array_filler_expr = .{ + .last_tok = p.tok_i - 1, + .count = max_len - start, + .qt = elem_ty, + }, + }); + try p.list_buf.append(elem); + } + + return p.addNode(.{ .array_init_expr = .{ + .l_brace_tok = il.tok, + .container_qt = init_qt, + .items = p.list_buf.items[list_buf_top..], + } }); + }, .array => |array_ty| { if (il.node.unpack()) |some| return some; diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index bef0af9..e0ee18e 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -989,6 +989,12 @@ pub const excess_array_init: Diagnostic = .{ .opt = .@"excess-initializers", }; +pub const excess_vector_init: Diagnostic = .{ + .fmt = "excess elements in vector initializer", + .kind = .warning, + .opt = .@"excess-initializers", +}; + pub const str_init_too_long: Diagnostic = .{ .fmt = "initializer-string for char array is too long", .opt = .@"excess-initializers", -- Gitee From 8932f98daf0485c0d8cbd87c6ae43045aad55486 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 22 May 2025 14:52:05 +0300 Subject: [PATCH 058/117] Parser: use list instead of map for init list indexes --- src/aro/InitList.zig | 16 --- src/aro/Parser.zig | 131 +++++++++++++------------ test/cases/initializers.c | 18 ++-- test/cases/parser using typeof types.c | 5 +- 4 files changed, 80 insertions(+), 90 deletions(-) diff --git a/src/aro/InitList.zig b/src/aro/InitList.zig index 8c39209..b6b4efd 100644 --- a/src/aro/InitList.zig +++ b/src/aro/InitList.zig @@ -73,22 +73,6 @@ test "basic usage" { var il: InitList = .{}; defer il.deinit(gpa); - { - var i: usize = 0; - while (i < 5) : (i += 1) { - const prev = try il.put(gpa, i, undefined, 0); - try testing.expect(prev == null); - } - } - - { - const failing = testing.failing_allocator; - var i: usize = 0; - while (i < 5) : (i += 1) { - _ = try il.find(failing, i); - } - } - { var item = try il.find(gpa, 0); var i: usize = 1; diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index beb2519..0956826 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -132,8 +132,6 @@ attr_application_buf: std.ArrayListUnmanaged(Attribute) = .empty, /// We only store the first tentative definition that uses a given type because this map is only used /// for issuing an error message, and correcting the first error for a type will fix all of them for that type. tentative_defs: std.AutoHashMapUnmanaged(StringId, TokenIndex) = .empty, -// TODO InitList pointers aren't stable -init_list_index: std.AutoHashMapUnmanaged(*InitList, u64) = .empty, // configuration and miscellaneous info no_eval: bool = false, @@ -787,7 +785,6 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { p.attr_buf.deinit(pp.comp.gpa); p.attr_application_buf.deinit(pp.comp.gpa); p.tentative_defs.deinit(pp.comp.gpa); - p.init_list_index.deinit(pp.comp.gpa); } try p.syms.pushScope(&p); @@ -3924,13 +3921,14 @@ fn initializer(p: *Parser, init_qt: QualType) Error!Result { var il: InitList = .{}; defer il.deinit(p.gpa); - p.init_list_index.clearRetainingCapacity(); _ = try p.initializerItem(&il, final_init_qt); const res = try p.convertInitList(il, final_init_qt); return .{ .qt = res.qt(&p.tree).withQualifiers(final_init_qt), .node = res }; } +const IndexList = std.ArrayListUnmanaged(u64); + /// initializerItems : designation? initializer (',' designation? initializer)* ','? fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { const l_brace = p.eatToken(.l_brace) orelse { @@ -3959,6 +3957,9 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { return true; } + var index_list: IndexList = .empty; + defer index_list.deinit(p.gpa); + var count: u64 = 0; var warned_excess = init_qt.isInvalid(); var is_str_init = false; @@ -3966,7 +3967,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { errdefer p.skipTo(.r_brace); var first_tok = p.tok_i; - const designated = try p.designation(il, init_qt); + const designated = try p.designation(il, init_qt, &index_list); if (!designated and init_qt.hasAttribute(p.comp, .designated_init)) { try p.err(p.tok_i, .designated_init_needed, .{}); } @@ -3992,7 +3993,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { defer tmp_il.deinit(p.gpa); saw = try p.initializerItem(&tmp_il, .void); } else if (p.tok_ids[p.tok_i] == .l_brace) { - if (try p.findAggregateInitializer(il, init_qt)) |item| { + if (try p.findAggregateInitializer(il, init_qt, &index_list)) |item| { saw = try p.initializerItem(item.il, item.qt); } else { // discard further values @@ -4015,7 +4016,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { }; saw = true; - _ = try p.findScalarInitializer(il, init_qt, res, first_tok, &warned_excess); + _ = try p.findScalarInitializer(il, init_qt, res, first_tok, &warned_excess, &index_list, 0); } if (!saw) { @@ -4035,6 +4036,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { // TODO move to convertInitList try p.err(l_brace, .complex_component_init, .{}); } + if (il.tok == 0) il.tok = l_brace; return true; } @@ -4055,9 +4057,9 @@ fn setInitializer(p: *Parser, il: *InitList, init_qt: QualType, tok: TokenIndex, /// designator /// : '[' integerConstExpr ']' /// | '.' identifier -fn designation(p: *Parser, il: *InitList, init_qt: QualType) !bool { +fn designation(p: *Parser, il: *InitList, init_qt: QualType, index_list: *IndexList) !bool { switch (p.tok_ids[p.tok_i]) { - .l_bracket, .period => p.init_list_index.clearRetainingCapacity(), + .l_bracket, .period => index_list.items.len = 0, else => return false, } @@ -4092,7 +4094,7 @@ fn designation(p: *Parser, il: *InitList, init_qt: QualType) !bool { return error.ParsingFailed; } - try p.init_list_index.put(p.gpa, cur_il, index_int); + try index_list.append(p.gpa, index_int); cur_il = try cur_il.find(p.gpa, index_int); cur_qt = array_ty.elem; } else if (p.eatToken(.period)) |period| { @@ -4101,31 +4103,33 @@ fn designation(p: *Parser, il: *InitList, init_qt: QualType) !bool { const field_str = p.tokSlice(field_tok); const target_name = try p.comp.internString(field_str); - const record_ty = cur_qt.getRecord(p.comp) orelse { + var record_ty = cur_qt.getRecord(p.comp) orelse { try p.err(period, .invalid_field_designator, .{cur_qt}); return error.ParsingFailed; }; - if (!record_ty.hasField(p.comp, target_name)) { - try p.err(period, .no_such_field_designator, .{field_str}); - return error.ParsingFailed; - } - // TODO check if union already has field set - for (record_ty.fields, 0..) |field, field_index| { + var field_index: u32 = 0; + while (field_index < record_ty.fields.len) { + const field = record_ty.fields[field_index]; if (field.name_tok == 0) if (field.qt.getRecord(p.comp)) |field_record_ty| { // Recurse into anonymous field if it has a field by the name. if (!field_record_ty.hasField(p.comp, target_name)) continue; - cur_qt = field.qt; - try p.init_list_index.put(p.gpa, cur_il, field_index); + try index_list.append(p.gpa, field_index); cur_il = try il.find(p.gpa, field_index); - break; + record_ty = field_record_ty; + field_index = 0; + continue; }; if (field.name == target_name) { cur_qt = field.qt; - try p.init_list_index.put(p.gpa, cur_il, field_index); + try index_list.append(p.gpa, field_index); cur_il = try cur_il.find(p.gpa, field_index); break; } + field_index += 1; + } else { + try p.err(period, .no_such_field_designator, .{field_str}); + return error.ParsingFailed; } } else break; } @@ -4144,12 +4148,16 @@ fn findScalarInitializer( res: Result, first_tok: TokenIndex, warned_excess: *bool, + index_list: *IndexList, + index_list_top: u32, ) Error!bool { if (qt.isInvalid()) return false; + if (index_list.items.len <= index_list_top) try index_list.append(p.gpa, 0); + const index = index_list.items[index_list_top]; switch (qt.base(p.comp).type) { + .void => return true, .complex => |complex_ty| { - const index = p.init_list_index.get(il) orelse 0; if (il.node != .null or index >= 2) { if (!warned_excess.*) try p.err(first_tok, .excess_scalar_init, .{}); warned_excess.* = true; @@ -4161,16 +4169,16 @@ fn findScalarInitializer( } const elem_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(elem_il, complex_ty, res, first_tok, warned_excess)) { + if (try p.findScalarInitializer(elem_il, complex_ty, res, first_tok, warned_excess, index_list, index_list_top + 1)) { const new_index = index + 1; - try p.init_list_index.put(p.gpa, il, new_index); + index_list.items[index_list_top] = new_index; + index_list.items.len = index_list_top + 1; return new_index >= 2; } return false; }, .vector => |vector_ty| { - const index = p.init_list_index.get(il) orelse 0; if (il.node != .null or index >= vector_ty.len) { if (!warned_excess.*) try p.err(first_tok, .excess_vector_init, .{}); warned_excess.* = true; @@ -4182,9 +4190,10 @@ fn findScalarInitializer( } const elem_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(elem_il, vector_ty.elem, res, first_tok, warned_excess)) { + if (try p.findScalarInitializer(elem_il, vector_ty.elem, res, first_tok, warned_excess, index_list, index_list_top + 1)) { const new_index = index + 1; - try p.init_list_index.put(p.gpa, il, new_index); + index_list.items[index_list_top] = new_index; + index_list.items.len = index_list_top + 1; return new_index >= vector_ty.len; } @@ -4200,7 +4209,6 @@ fn findScalarInitializer( return true; } - const index = p.init_list_index.get(il) orelse 0; if (il.node != .null or index >= max_len) { if (!warned_excess.*) try p.err(first_tok, .excess_array_init, .{}); warned_excess.* = true; @@ -4212,9 +4220,10 @@ fn findScalarInitializer( } const elem_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(elem_il, array_ty.elem, res, first_tok, warned_excess)) { + if (try p.findScalarInitializer(elem_il, array_ty.elem, res, first_tok, warned_excess, index_list, index_list_top + 1)) { const new_index = index + 1; - try p.init_list_index.put(p.gpa, il, new_index); + index_list.items[index_list_top] = new_index; + index_list.items.len = index_list_top + 1; return new_index >= max_len; } @@ -4226,7 +4235,6 @@ fn findScalarInitializer( return true; } - const index = p.init_list_index.get(il) orelse 0; if (il.node != .null or index >= struct_ty.fields.len) { if (!warned_excess.*) try p.err(first_tok, .excess_struct_init, .{}); warned_excess.* = true; @@ -4239,9 +4247,10 @@ fn findScalarInitializer( const field = struct_ty.fields[@intCast(index)]; const field_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(field_il, field.qt, res, first_tok, warned_excess)) { + if (try p.findScalarInitializer(field_il, field.qt, res, first_tok, warned_excess, index_list, index_list_top + 1)) { const new_index = index + 1; - try p.init_list_index.put(p.gpa, il, new_index); + index_list.items[index_list_top] = new_index; + index_list.items.len = index_list_top + 1; return new_index >= struct_ty.fields.len; } @@ -4253,17 +4262,21 @@ fn findScalarInitializer( return true; } - const index = p.init_list_index.get(il) orelse 0; - if (il.node != .null or index != 0 or res.qt.eql(qt, p.comp)) { + // TODO warn excess initializers + if (il.node != .null or index >= union_ty.fields.len) { + return true; + } + if (res.qt.eql(qt, p.comp)) { try p.setInitializer(il, qt, first_tok, res); return true; } - const field = union_ty.fields[0]; + const field = union_ty.fields[index]; const field_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(field_il, field.qt, res, first_tok, warned_excess)) { + if (try p.findScalarInitializer(field_il, field.qt, res, first_tok, warned_excess, index_list, index_list_top + 1)) { const new_index = index + 1; - try p.init_list_index.put(p.gpa, il, new_index); + index_list.items[index_list_top] = new_index; + index_list.items.len = index_list_top + 1; } return true; @@ -4277,50 +4290,53 @@ fn findScalarInitializer( const InitItem = struct { il: *InitList, qt: QualType }; -fn findAggregateInitializer(p: *Parser, il: *InitList, qt: QualType) Error!?InitItem { +fn findAggregateInitializer(p: *Parser, il: *InitList, qt: QualType, index_list: *IndexList) Error!?InitItem { if (qt.isInvalid()) { if (il.node != .null) return .{ .il = il, .qt = qt }; return null; } + if (index_list.items.len == 0) try index_list.append(p.gpa, 0); + const index = index_list.items[0]; + switch (qt.base(p.comp).type) { .complex => |complex_ty| { if (il.node != .null) return null; - const index = p.init_list_index.get(il) orelse 0; if (index < 2) { - try p.init_list_index.put(p.gpa, il, index + 1); + index_list.items[0] = index + 1; + index_list.items.len = 1; return .{ .il = try il.find(p.gpa, index), .qt = complex_ty }; } }, .vector => |vector_ty| { if (il.node != .null) return null; - const index = p.init_list_index.get(il) orelse 0; if (index < vector_ty.len) { - try p.init_list_index.put(p.gpa, il, index + 1); + index_list.items[0] = index + 1; + index_list.items.len = 1; return .{ .il = try il.find(p.gpa, index), .qt = vector_ty.elem }; } }, .array => |array_ty| { if (il.node != .null) return null; - const index = p.init_list_index.get(il) orelse 0; const max_len = switch (array_ty.len) { .fixed, .static => |len| len, else => std.math.maxInt(u64), }; if (index < max_len) { - try p.init_list_index.put(p.gpa, il, index + 1); + index_list.items[0] = index + 1; + index_list.items.len = 1; return .{ .il = try il.find(p.gpa, index), .qt = array_ty.elem }; } }, .@"struct" => |struct_ty| { if (il.node != .null) return null; - const index = p.init_list_index.get(il) orelse 0; const field_count = struct_ty.fields.len; if (index < field_count) { - try p.init_list_index.put(p.gpa, il, index + 1); + index_list.items[0] = index + 1; + index_list.items.len = 1; const field_qt = struct_ty.fields[@intCast(index)].qt; return .{ .il = try il.find(p.gpa, index), .qt = field_qt }; } @@ -4329,11 +4345,13 @@ fn findAggregateInitializer(p: *Parser, il: *InitList, qt: QualType) Error!?Init if (il.node != .null) return null; if (union_ty.fields.len == 0) return null; + index_list.items[0] = index + 1; + index_list.items.len = 1; return .{ .il = try il.find(p.gpa, 0), .qt = union_ty.fields[0].qt }; }, else => { try p.err(p.tok_i, .too_many_scalar_init_braces, .{}); - if (il.node != .null) return .{ .il = il, .qt = qt }; + if (il.node == .null) return .{ .il = il, .qt = qt }; }, } return null; @@ -4444,20 +4462,11 @@ fn convertInitList(p: *Parser, il: InitList, init_qt: QualType) Error!Node.Index } }); } - const scalar_kind = init_qt.scalarKind(p.comp); - if (scalar_kind != .none and scalar_kind.isReal()) { - return il.node.unpack() orelse - try p.addNode(.{ .default_init_expr = .{ - .last_tok = il.tok, - .qt = init_qt, - } }); - } + if (il.node.unpack()) |some| return some; switch (init_qt.base(p.comp).type) { // .complex => TODO .vector => |vector_ty| { - if (il.node.unpack()) |some| return some; - const list_buf_top = p.list_buf.items.len; defer p.list_buf.items.len = list_buf_top; @@ -4500,8 +4509,6 @@ fn convertInitList(p: *Parser, il: InitList, init_qt: QualType) Error!Node.Index } }); }, .array => |array_ty| { - if (il.node.unpack()) |some| return some; - const list_buf_top = p.list_buf.items.len; defer p.list_buf.items.len = list_buf_top; @@ -4565,8 +4572,6 @@ fn convertInitList(p: *Parser, il: InitList, init_qt: QualType) Error!Node.Index }, .@"struct" => |struct_ty| { assert(struct_ty.layout != null); - if (il.node.unpack()) |some| return some; - const list_buf_top = p.list_buf.items.len; defer p.list_buf.items.len = list_buf_top; @@ -4594,8 +4599,6 @@ fn convertInitList(p: *Parser, il: InitList, init_qt: QualType) Error!Node.Index } }); }, .@"union" => |union_ty| { - if (il.node.unpack()) |some| return some; - const init_node, const index = if (union_ty.fields.len == 0) // do nothing for empty unions .{ null, 0 } diff --git a/test/cases/initializers.c b/test/cases/initializers.c index c93c1de..062babf 100644 --- a/test/cases/initializers.c +++ b/test/cases/initializers.c @@ -115,7 +115,7 @@ union { } empty = {{'a', 'b'}}; int invalid_init[] = (int){1}; -int array_2d[3][2] = { 1, 2, [2] = 3, [1][1] = 1, 4}; // TODO 4 overrides 3 +int array_2d[3][2] = { 1, 2, [2] = 3, [1][1] = 1, 4}; void quux(void) { struct Foo { @@ -151,7 +151,11 @@ void array_members(void) { char b[32] = (char[32]){"ABC"}; } -#define TESTS_SKIPPED 3 +struct { + int sec, min, hour, day; +} s1 = {.day = 3, .sec = 0, 1, 2}; + +#define TESTS_SKIPPED 1 #define EXPECTED_ERRORS "initializers.c:2:17: error: variable-sized object may not be initialized" \ "initializers.c:3:15: error: illegal initializer type" \ "initializers.c:4:14: error: initializing 'int *' from incompatible type 'float'" \ @@ -174,8 +178,7 @@ void array_members(void) { "initializers.c:20:62: warning: excess elements in struct initializer [-Wexcess-initializers]" \ "initializers.c:21:23: warning: excess elements in array initializer [-Wexcess-initializers]" \ "initializers.c:21:44: warning: excess elements in array initializer [-Wexcess-initializers]" \ - /* "initializers.c:23:37: warning: excess elements in array initializer [-Wexcess-initializers]" */ \ - "initializers.c:23:34: warning: excess elements in array initializer [-Wexcess-initializers]" \ + "initializers.c:23:37: warning: excess elements in array initializer [-Wexcess-initializers]" \ "initializers.c:30:43: warning: excess elements in array initializer [-Wexcess-initializers]" \ "initializers.c:31:27: error: initializer for aggregate with no elements requires explicit braces" \ "initializers.c:32:15: error: array initializer must be an initializer list or wide string literal" \ @@ -195,11 +198,10 @@ void array_members(void) { /* "initializers.c:79:31: warning: variable 's2' is uninitialized when used within its own initialization" */ \ /* "initializers.c:80:38: warning: variable 's3' is uninitialized when used within its own initialization" */ \ "initializers.c:104:32: warning: excess elements in array initializer [-Wexcess-initializers]" \ - "initializers.c:115:18: warning: excess elements in struct initializer [-Wexcess-initializers]" \ - "initializers.c:115:12: warning: initializer overrides previous initialization [-Winitializer-overrides]" \ - "initializers.c:115:13: note: previous initialization" \ - "initializers.c:115:12: warning: excess elements in struct initializer [-Wexcess-initializers]" \ + "initializers.c:115:12: warning: excess elements in scalar initializer [-Wexcess-initializers]" \ "initializers.c:117:22: error: array initializer must be an initializer list or wide string literal" \ + "initializers.c:118:51: warning: initializer overrides previous initialization [-Winitializer-overrides]" \ + "initializers.c:118:36: note: previous initialization" \ "initializers.c:128:17: error: initializing 'void *' from incompatible type 'double'" \ "initializers.c:129:17: error: initializing 'void *' from incompatible type 'struct Foo'" \ "initializers.c:131:15: warning: incompatible pointer types initializing 'long *' from incompatible type 'int *' [-Wincompatible-pointer-types]" \ diff --git a/test/cases/parser using typeof types.c b/test/cases/parser using typeof types.c index bb450d0..a9a92f0 100644 --- a/test/cases/parser using typeof types.c +++ b/test/cases/parser using typeof types.c @@ -68,13 +68,14 @@ void bool_init(void) { typeof(b) b2 = s; } +#define TESTS_SKIPPED 1 #define EXPECTED_ERRORS "parser using typeof types.c:26:14: warning: array index 5 is past the end of the array [-Warray-bounds]" \ "parser using typeof types.c:27:15: warning: array index 5 is past the end of the array [-Warray-bounds]" \ "parser using typeof types.c:28:22: warning: array index 5 is past the end of the array [-Warray-bounds]" \ "parser using typeof types.c:33:31: warning: excess elements in struct initializer [-Wexcess-initializers]" \ "parser using typeof types.c:34:25: warning: excess elements in struct initializer [-Wexcess-initializers]" \ - "parser using typeof types.c:37:30: warning: excess elements in struct initializer [-Wexcess-initializers]" \ - "parser using typeof types.c:38:26: warning: excess elements in struct initializer [-Wexcess-initializers]" \ + /* "parser using typeof types.c:37:30: warning: excess elements in union initializer [-Wexcess-initializers]" */ \ + /* "parser using typeof types.c:38:26: warning: excess elements in union initializer [-Wexcess-initializers]" */ \ "parser using typeof types.c:46:35: error: 'void' must be the only parameter if specified" \ "parser using typeof types.c:46:35: error: 'void' parameter cannot be qualified" \ "parser using typeof types.c:49:28: warning: initializer-string for char array is too long [-Wexcess-initializers]" \ -- Gitee From dff019279294ea56b2202c136cfbe1e6e6a1906c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 22 May 2025 22:51:38 +0300 Subject: [PATCH 059/117] Parser: add error for excess union initializers --- src/aro/Parser.zig | 234 +++++++++++++------------ src/aro/Parser/Diagnostic.zig | 6 + test/cases/initializers.c | 44 ++++- test/cases/parser using typeof types.c | 5 +- 4 files changed, 173 insertions(+), 116 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 0956826..0543c79 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3826,16 +3826,13 @@ fn typeName(p: *Parser) Error!?QualType { return try Attribute.applyTypeAttributes(p, ty, attr_buf_top, .align_ignored); } -fn complexInitializer(p: *Parser, init_qt: QualType) Error!Result { - assert(p.tok_ids[p.tok_i] == .l_brace); +fn complexInitializer(p: *Parser, init_qt: QualType, l_brace: TokenIndex) Error!Result { assert(init_qt.is(p.comp, .complex)); const real_ty = init_qt.toReal(p.comp); if (real_ty.isInt(p.comp)) { return p.todo("Complex integer initializers"); } - const l_brace = p.tok_i; - p.tok_i += 1; try p.err(l_brace, .complex_component_init, .{}); const first_tok = p.tok_i; @@ -3894,34 +3891,34 @@ fn complexInitializer(p: *Parser, init_qt: QualType) Error!Result { /// : assignExpr /// | '{' initializerItems '}' fn initializer(p: *Parser, init_qt: QualType) Error!Result { - // fast path for non-braced initializers - if (p.tok_ids[p.tok_i] != .l_brace) { + const l_brace = p.eatToken(.l_brace) orelse { + // fast path for non-braced initializers const tok = p.tok_i; var res = try p.expect(assignExpr); if (try p.coerceArrayInit(res, tok, init_qt)) return res; try p.coerceInit(&res, tok, init_qt); return res; - } + }; // We want to parse the initializer even if the target is // invalidly inferred. var final_init_qt = init_qt; if (init_qt.isAutoType()) { - try p.err(p.tok_i, .auto_type_with_init_list, .{}); + try p.err(l_brace, .auto_type_with_init_list, .{}); final_init_qt = .invalid; } else if (init_qt.isC23Auto()) { - try p.err(p.tok_i, .c23_auto_with_init_list, .{}); + try p.err(l_brace, .c23_auto_with_init_list, .{}); final_init_qt = .invalid; } // TODO get rid of if (final_init_qt.is(p.comp, .complex)) { - return p.complexInitializer(final_init_qt); + return p.complexInitializer(final_init_qt, l_brace); } var il: InitList = .{}; defer il.deinit(p.gpa); - _ = try p.initializerItem(&il, final_init_qt); + try p.initializerItem(&il, final_init_qt, l_brace); const res = try p.convertInitList(il, final_init_qt); return .{ .qt = res.qt(&p.tree).withQualifiers(final_init_qt), .node = res }; @@ -3930,22 +3927,13 @@ fn initializer(p: *Parser, init_qt: QualType) Error!Result { const IndexList = std.ArrayListUnmanaged(u64); /// initializerItems : designation? initializer (',' designation? initializer)* ','? -fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { - const l_brace = p.eatToken(.l_brace) orelse { - const tok = p.tok_i; - const res = (try p.assignExpr()) orelse return false; - - try p.setInitializer(il, init_qt, tok, res); - return true; - }; - +fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType, l_brace: TokenIndex) Error!void { const is_scalar, const is_complex = blk: { if (init_qt.isInvalid()) break :blk .{ false, false }; const scalar_kind = init_qt.scalarKind(p.comp); break :blk .{ scalar_kind != .none, !scalar_kind.isReal() }; }; - const scalar_inits_needed: usize = if (is_complex) 2 else 1; if (p.eatToken(.r_brace)) |_| { if (is_scalar) try p.err(l_brace, .empty_scalar_init, .{}); if (il.tok != 0 and !init_qt.isInvalid()) { @@ -3954,7 +3942,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { } il.node = .null; il.tok = l_brace; - return true; + return; } var index_list: IndexList = .empty; @@ -3962,70 +3950,47 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { var count: u64 = 0; var warned_excess = init_qt.isInvalid(); - var is_str_init = false; while (true) { errdefer p.skipTo(.r_brace); - var first_tok = p.tok_i; const designated = try p.designation(il, init_qt, &index_list); if (!designated and init_qt.hasAttribute(p.comp, .designated_init)) { try p.err(p.tok_i, .designated_init_needed, .{}); } - var saw = false; - if (is_str_init and p.isStringInit(init_qt)) { - if (!warned_excess) try p.err(first_tok, .excess_str_init, .{}); - warned_excess = true; - - // discard further strings - var tmp_il: InitList = .{}; - defer tmp_il.deinit(p.gpa); - saw = try p.initializerItem(&tmp_il, .void); - } else if (count == 0 and p.isStringInit(init_qt)) { - is_str_init = true; - saw = try p.initializerItem(il, init_qt); - } else if (is_scalar and count >= scalar_inits_needed) { - if (!warned_excess) try p.err(first_tok, .excess_scalar_init, .{}); - warned_excess = true; - - // discard further scalars - var tmp_il: InitList = .{}; - defer tmp_il.deinit(p.gpa); - saw = try p.initializerItem(&tmp_il, .void); - } else if (p.tok_ids[p.tok_i] == .l_brace) { - if (try p.findAggregateInitializer(il, init_qt, &index_list)) |item| { - saw = try p.initializerItem(item.il, item.qt); + const first_tok = p.tok_i; + if (p.eatToken(.l_brace)) |inner_l_brace| { + if (try p.findAggregateInitializer(il, init_qt, first_tok, &index_list)) |item| { + try p.initializerItem(item.il, item.qt, inner_l_brace); } else { // discard further values var tmp_il: InitList = .{}; defer tmp_il.deinit(p.gpa); - saw = try p.initializerItem(&tmp_il, .void); + try p.initializerItem(&tmp_il, .void, inner_l_brace); if (!warned_excess) try p.err(first_tok, switch (init_qt.base(p.comp).type) { - .array => .excess_array_init, + .array => if (il.node != .null and p.isStringInit(init_qt, il.node.unpack().?)) + .excess_str_init + else + .excess_array_init, .@"struct" => .excess_struct_init, + .@"union" => .excess_union_init, .vector => .excess_vector_init, else => .excess_scalar_init, }, .{}); + warned_excess = true; } - } else single_item: { - first_tok = p.tok_i; - const res = (try p.assignExpr()) orelse { - saw = false; - break :single_item; - }; - saw = true; - - _ = try p.findScalarInitializer(il, init_qt, res, first_tok, &warned_excess, &index_list, 0); - } - - if (!saw) { - if (designated) { - try p.err(p.tok_i, .expected_expr, .{}); - return error.ParsingFailed; + } else if (try p.assignExpr()) |res| { + if (is_scalar and il.node != .null) { + if (!warned_excess) try p.err(first_tok, .excess_scalar_init, .{}); + warned_excess = true; + } else { + _ = try p.findScalarInitializer(il, init_qt, res, first_tok, &warned_excess, &index_list, 0); } - break; - } + } else if (designated) { + try p.err(p.tok_i, .expected_expr, .{}); + return error.ParsingFailed; + } else break; count += 1; if (p.eatToken(.comma) == null) break; @@ -4037,7 +4002,6 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType) Error!bool { try p.err(l_brace, .complex_component_init, .{}); } if (il.tok == 0) il.tok = l_brace; - return true; } fn setInitializer(p: *Parser, il: *InitList, init_qt: QualType, tok: TokenIndex, res: Result) !void { @@ -4163,13 +4127,23 @@ fn findScalarInitializer( warned_excess.* = true; return true; } - if (res.qt.eql(qt, p.comp)) { + if (res.qt.eql(qt, p.comp) and il.list.items.len == 0) { try p.setInitializer(il, qt, first_tok, res); return true; } const elem_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(elem_il, complex_ty, res, first_tok, warned_excess, index_list, index_list_top + 1)) { + if (try p.setInitializerIfEqual(elem_il, complex_ty, first_tok, res) or + try p.findScalarInitializer( + elem_il, + complex_ty, + res, + first_tok, + warned_excess, + index_list, + index_list_top + 1, + )) + { const new_index = index + 1; index_list.items[index_list_top] = new_index; index_list.items.len = index_list_top + 1; @@ -4184,13 +4158,23 @@ fn findScalarInitializer( warned_excess.* = true; return true; } - if (res.qt.eql(qt, p.comp)) { + if (res.qt.eql(qt, p.comp) and il.list.items.len == 0) { try p.setInitializer(il, qt, first_tok, res); return true; } const elem_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(elem_il, vector_ty.elem, res, first_tok, warned_excess, index_list, index_list_top + 1)) { + if (try p.setInitializerIfEqual(elem_il, vector_ty.elem, first_tok, res) or + try p.findScalarInitializer( + elem_il, + vector_ty.elem, + res, + first_tok, + warned_excess, + index_list, + index_list_top + 1, + )) + { const new_index = index + 1; index_list.items[index_list_top] = new_index; index_list.items.len = index_list_top + 1; @@ -4210,17 +4194,36 @@ fn findScalarInitializer( } if (il.node != .null or index >= max_len) { - if (!warned_excess.*) try p.err(first_tok, .excess_array_init, .{}); + if (!warned_excess.*) { + if (il.node.unpack()) |some| if (p.isStringInit(qt, some)) { + try p.err(first_tok, .excess_str_init, .{}); + warned_excess.* = true; + return true; + }; + try p.err(first_tok, .excess_array_init, .{}); + } warned_excess.* = true; return true; } - if (try p.coerceArrayInitExtra(res, first_tok, qt, false)) { + if (il.list.items.len == 0 and p.isStringInit(qt, res.node) and + try p.coerceArrayInit(res, first_tok, qt)) + { try p.setInitializer(il, qt, first_tok, res); return true; } const elem_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(elem_il, array_ty.elem, res, first_tok, warned_excess, index_list, index_list_top + 1)) { + if (try p.setInitializerIfEqual(elem_il, array_ty.elem, first_tok, res) or + try p.findScalarInitializer( + elem_il, + array_ty.elem, + res, + first_tok, + warned_excess, + index_list, + index_list_top + 1, + )) + { const new_index = index + 1; index_list.items[index_list_top] = new_index; index_list.items.len = index_list_top + 1; @@ -4240,14 +4243,20 @@ fn findScalarInitializer( warned_excess.* = true; return true; } - if (res.qt.eql(qt, p.comp)) { - try p.setInitializer(il, qt, first_tok, res); - return true; - } const field = struct_ty.fields[@intCast(index)]; const field_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(field_il, field.qt, res, first_tok, warned_excess, index_list, index_list_top + 1)) { + if (try p.setInitializerIfEqual(field_il, field.qt, first_tok, res) or + try p.findScalarInitializer( + field_il, + field.qt, + res, + first_tok, + warned_excess, + index_list, + index_list_top + 1, + )) + { const new_index = index + 1; index_list.items[index_list_top] = new_index; index_list.items.len = index_list_top + 1; @@ -4262,18 +4271,27 @@ fn findScalarInitializer( return true; } - // TODO warn excess initializers - if (il.node != .null or index >= union_ty.fields.len) { - return true; - } - if (res.qt.eql(qt, p.comp)) { - try p.setInitializer(il, qt, first_tok, res); + if (il.node != .null or il.list.items.len > 1 or + (il.list.items.len == 1 and il.list.items[0].index != index)) + { + if (!warned_excess.*) try p.err(first_tok, .excess_union_init, .{}); + warned_excess.* = true; return true; } - const field = union_ty.fields[index]; + const field = union_ty.fields[@intCast(index)]; const field_il = try il.find(p.gpa, index); - if (try p.findScalarInitializer(field_il, field.qt, res, first_tok, warned_excess, index_list, index_list_top + 1)) { + if (try p.setInitializerIfEqual(field_il, field.qt, first_tok, res) or + try p.findScalarInitializer( + field_il, + field.qt, + res, + first_tok, + warned_excess, + index_list, + index_list_top + 1, + )) + { const new_index = index + 1; index_list.items[index_list_top] = new_index; index_list.items.len = index_list_top + 1; @@ -4288,9 +4306,21 @@ fn findScalarInitializer( } } +fn setInitializerIfEqual(p: *Parser, il: *InitList, init_qt: QualType, tok: TokenIndex, res: Result) !bool { + if (!res.qt.eql(init_qt, p.comp)) return false; + try p.setInitializer(il, init_qt, tok, res); + return true; +} + const InitItem = struct { il: *InitList, qt: QualType }; -fn findAggregateInitializer(p: *Parser, il: *InitList, qt: QualType, index_list: *IndexList) Error!?InitItem { +fn findAggregateInitializer( + p: *Parser, + il: *InitList, + qt: QualType, + first_tok: TokenIndex, + index_list: *IndexList, +) Error!?InitItem { if (qt.isInvalid()) { if (il.node != .null) return .{ .il = il, .qt = qt }; return null; @@ -4350,7 +4380,7 @@ fn findAggregateInitializer(p: *Parser, il: *InitList, qt: QualType, index_list: return .{ .il = try il.find(p.gpa, 0), .qt = union_ty.fields[0].qt }; }, else => { - try p.err(p.tok_i, .too_many_scalar_init_braces, .{}); + try p.err(first_tok, .too_many_scalar_init_braces, .{}); if (il.node == .null) return .{ .il = il, .qt = qt }; }, } @@ -4358,17 +4388,12 @@ fn findAggregateInitializer(p: *Parser, il: *InitList, qt: QualType, index_list: } fn coerceArrayInit(p: *Parser, item: Result, tok: TokenIndex, target: QualType) !bool { - return p.coerceArrayInitExtra(item, tok, target, true); -} - -fn coerceArrayInitExtra(p: *Parser, item: Result, tok: TokenIndex, target: QualType, report_err: bool) !bool { if (target.isInvalid()) return false; const target_array_ty = target.get(p.comp, .array) orelse return false; const is_str_lit = p.nodeIs(item.node, .string_literal_expr); const maybe_item_array_ty = item.qt.get(p.comp, .array); if (!is_str_lit and (!p.nodeIs(item.node, .compound_literal_expr) or maybe_item_array_ty == null)) { - if (!report_err) return false; try p.err(tok, .array_init_str, .{}); return true; // do not do further coercion } @@ -4383,7 +4408,6 @@ fn coerceArrayInitExtra(p: *Parser, item: Result, tok: TokenIndex, target: QualT (is_str_lit and item_int == .char and (target_int == .uchar or target_int == .schar)) or (is_str_lit and item_int == .uchar and (target_int == .uchar or target_int == .schar or target_int == .char)); if (!compatible) { - if (!report_err) return false; try p.err(tok, .incompatible_array_init, .{ target, item.qt }); return true; // do not do further coercion } @@ -4397,10 +4421,10 @@ fn coerceArrayInitExtra(p: *Parser, item: Result, tok: TokenIndex, target: QualT if (is_str_lit) { // the null byte of a string can be dropped - if (item_len - 1 > target_len and report_err) { + if (item_len - 1 > target_len) { try p.err(tok, .str_init_too_long, .{}); } - } else if (item_len > target_len and report_err) { + } else if (item_len > target_len) { try p.err(tok, .arr_init_too_long, .{ target, item.qt }); } } @@ -4435,22 +4459,10 @@ fn coerceInit(p: *Parser, item: *Result, tok: TokenIndex, target: QualType) !voi return item.saveValue(p); } -fn isStringInit(p: *Parser, init_qt: QualType) bool { +fn isStringInit(p: *Parser, init_qt: QualType, node: Node.Index) bool { const init_array_ty = init_qt.get(p.comp, .array) orelse return false; if (!init_array_ty.elem.is(p.comp, .int)) return false; - var i = p.tok_i; - while (true) : (i += 1) { - switch (p.tok_ids[i]) { - .l_paren => {}, - .string_literal, - .string_literal_utf_16, - .string_literal_utf_8, - .string_literal_utf_32, - .string_literal_wide, - => return true, - else => return false, - } - } + return p.nodeIs(node, .string_literal_expr); } /// Convert InitList into an AST diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index e0ee18e..0153de3 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -983,6 +983,12 @@ pub const excess_struct_init: Diagnostic = .{ .opt = .@"excess-initializers", }; +pub const excess_union_init: Diagnostic = .{ + .fmt = "excess elements in union initializer", + .kind = .warning, + .opt = .@"excess-initializers", +}; + pub const excess_array_init: Diagnostic = .{ .fmt = "excess elements in array initializer", .kind = .warning, diff --git a/test/cases/initializers.c b/test/cases/initializers.c index 062babf..a047bcb 100644 --- a/test/cases/initializers.c +++ b/test/cases/initializers.c @@ -120,7 +120,7 @@ int array_2d[3][2] = { 1, 2, [2] = 3, [1][1] = 1, 4}; void quux(void) { struct Foo { int a; - } a; + } a, aa[2] = { a, a }; struct Bar { struct Foo a; } b = {a}; @@ -155,6 +155,33 @@ struct { int sec, min, hour, day; } s1 = {.day = 3, .sec = 0, 1, 2}; +void string_initializers(void) { + char str1[] = {"fo", "ba"}; + char str2[] = {"fo", 1}; + char str3[] = {1, "fo"}; + char str4[1] = {1, 2}; + char str5[4] = { (char [4]){"foo"} }; + char str6[] = {"foo", {1}, {3}}; + int arr2[2] = { (int [2]){ 1, 2 } }; +} +void union_excess(void) { + union U2 u1 = { 1, 2 }; + union U2 u2 = { .a = 1, .a = 2 }; +} +void struct_excess(void) { + struct A { + int a; + int b; + } a, b = { 1, a }; +} +void vector_excess(void) { + typedef int vec __attribute__((vector_size(4 * 4))); + vec v1 = { 1, 2, 3, 4, 5 }; + vec v2 = v1; + vec v3 = { v2 }; + vec v4 = { 1, v3 }; +} + #define TESTS_SKIPPED 1 #define EXPECTED_ERRORS "initializers.c:2:17: error: variable-sized object may not be initialized" \ "initializers.c:3:15: error: illegal initializer type" \ @@ -198,7 +225,7 @@ struct { /* "initializers.c:79:31: warning: variable 's2' is uninitialized when used within its own initialization" */ \ /* "initializers.c:80:38: warning: variable 's3' is uninitialized when used within its own initialization" */ \ "initializers.c:104:32: warning: excess elements in array initializer [-Wexcess-initializers]" \ - "initializers.c:115:12: warning: excess elements in scalar initializer [-Wexcess-initializers]" \ + "initializers.c:115:12: warning: excess elements in union initializer [-Wexcess-initializers]" \ "initializers.c:117:22: error: array initializer must be an initializer list or wide string literal" \ "initializers.c:118:51: warning: initializer overrides previous initialization [-Winitializer-overrides]" \ "initializers.c:118:36: note: previous initialization" \ @@ -208,3 +235,16 @@ struct { "initializers.c:132:23: warning: incompatible pointer types initializing 'unsigned int *' from incompatible type 'int *' converts between pointers to integer types with different sign [-Wpointer-sign]" \ "initializers.c:148:35: error: array designator used for non-array type 'struct S'" \ "initializers.c:150:30: warning: implicit pointer to integer conversion from 'char [4]' to 'int' [-Wint-conversion]" \ + "initializers.c:159:26: warning: excess elements in string initializer [-Wexcess-initializers]" \ + "initializers.c:160:26: warning: excess elements in string initializer [-Wexcess-initializers]" \ + "initializers.c:161:23: warning: implicit pointer to integer conversion from 'char [3]' to 'char' [-Wint-conversion]" \ + "initializers.c:162:24: warning: excess elements in array initializer [-Wexcess-initializers]" \ + "initializers.c:163:22: warning: implicit pointer to integer conversion from 'char [4]' to 'char' [-Wint-conversion]" \ + "initializers.c:164:27: warning: excess elements in string initializer [-Wexcess-initializers]" \ + "initializers.c:165:21: warning: implicit pointer to integer conversion from 'int [2]' to 'int' [-Wint-conversion]" \ + "initializers.c:168:24: warning: excess elements in union initializer [-Wexcess-initializers]" \ + "initializers.c:169:34: warning: initializer overrides previous initialization [-Winitializer-overrides]" \ + "initializers.c:169:26: note: previous initialization" \ + "initializers.c:175:19: error: initializing 'int' from incompatible type 'struct A'" \ + "initializers.c:179:28: warning: excess elements in vector initializer [-Wexcess-initializers]" \ + "initializers.c:182:19: error: initializing 'int' from incompatible type 'vec'" \ diff --git a/test/cases/parser using typeof types.c b/test/cases/parser using typeof types.c index a9a92f0..647ee85 100644 --- a/test/cases/parser using typeof types.c +++ b/test/cases/parser using typeof types.c @@ -68,14 +68,13 @@ void bool_init(void) { typeof(b) b2 = s; } -#define TESTS_SKIPPED 1 #define EXPECTED_ERRORS "parser using typeof types.c:26:14: warning: array index 5 is past the end of the array [-Warray-bounds]" \ "parser using typeof types.c:27:15: warning: array index 5 is past the end of the array [-Warray-bounds]" \ "parser using typeof types.c:28:22: warning: array index 5 is past the end of the array [-Warray-bounds]" \ "parser using typeof types.c:33:31: warning: excess elements in struct initializer [-Wexcess-initializers]" \ "parser using typeof types.c:34:25: warning: excess elements in struct initializer [-Wexcess-initializers]" \ - /* "parser using typeof types.c:37:30: warning: excess elements in union initializer [-Wexcess-initializers]" */ \ - /* "parser using typeof types.c:38:26: warning: excess elements in union initializer [-Wexcess-initializers]" */ \ + "parser using typeof types.c:37:30: warning: excess elements in union initializer [-Wexcess-initializers]" \ + "parser using typeof types.c:38:26: warning: excess elements in union initializer [-Wexcess-initializers]" \ "parser using typeof types.c:46:35: error: 'void' must be the only parameter if specified" \ "parser using typeof types.c:46:35: error: 'void' parameter cannot be qualified" \ "parser using typeof types.c:49:28: warning: initializer-string for char array is too long [-Wexcess-initializers]" \ -- Gitee From 985688c605f1225f4c8cc049d6c4d8b5ff70d8bd Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 22 May 2025 23:15:48 +0300 Subject: [PATCH 060/117] Parser: implement convertInitList for complex initializers --- src/aro/Parser.zig | 134 ++++++++++++++---------------------- test/cases/complex values.c | 6 +- 2 files changed, 57 insertions(+), 83 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 0543c79..e637de8 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3826,67 +3826,6 @@ fn typeName(p: *Parser) Error!?QualType { return try Attribute.applyTypeAttributes(p, ty, attr_buf_top, .align_ignored); } -fn complexInitializer(p: *Parser, init_qt: QualType, l_brace: TokenIndex) Error!Result { - assert(init_qt.is(p.comp, .complex)); - - const real_ty = init_qt.toReal(p.comp); - if (real_ty.isInt(p.comp)) { - return p.todo("Complex integer initializers"); - } - try p.err(l_brace, .complex_component_init, .{}); - - const first_tok = p.tok_i; - var first = try p.expect(assignExpr); - try p.coerceInit(&first, first_tok, real_ty); - - const second = if (p.eatToken(.comma)) |_| second: { - const second_tok = p.tok_i; - var second = (try p.assignExpr()) orelse break :second null; - try p.coerceInit(&second, second_tok, real_ty); - break :second second; - } else null; - - // Eat excess initializers - var extra_tok: ?TokenIndex = null; - while (p.eatToken(.comma)) |_| { - if (p.tok_ids[p.tok_i] == .r_brace) break; - extra_tok = p.tok_i; - if ((try p.assignExpr()) == null) { - try p.err(p.tok_i, .expected_expr, .{}); - p.skipTo(.r_brace); - return error.ParsingFailed; - } - } - try p.expectClosing(l_brace, .r_brace); - if (extra_tok) |tok| { - try p.err(tok, .excess_scalar_init, .{}); - } - - var res: Result = .{ - .node = try p.addNode(.{ .array_init_expr = .{ - .container_qt = init_qt, - .items = if (second) |some| - &.{ first.node, some.node } - else - &.{first.node}, - .l_brace_tok = l_brace, - } }), - .qt = init_qt, - }; - - const first_val = p.tree.value_map.get(first.node) orelse return res; - const second_val = if (second) |some| p.tree.value_map.get(some.node) orelse return res else Value.zero; - res.val = try Value.intern(p.comp, switch (real_ty.bitSizeof(p.comp)) { - 32 => .{ .complex = .{ .cf32 = .{ first_val.toFloat(f32, p.comp), second_val.toFloat(f32, p.comp) } } }, - 64 => .{ .complex = .{ .cf64 = .{ first_val.toFloat(f64, p.comp), second_val.toFloat(f64, p.comp) } } }, - 80 => .{ .complex = .{ .cf80 = .{ first_val.toFloat(f80, p.comp), second_val.toFloat(f80, p.comp) } } }, - 128 => .{ .complex = .{ .cf128 = .{ first_val.toFloat(f128, p.comp), second_val.toFloat(f128, p.comp) } } }, - else => unreachable, - }); - try res.putValue(p); - return res; -} - /// initializer /// : assignExpr /// | '{' initializerItems '}' @@ -3911,28 +3850,24 @@ fn initializer(p: *Parser, init_qt: QualType) Error!Result { final_init_qt = .invalid; } - // TODO get rid of - if (final_init_qt.is(p.comp, .complex)) { - return p.complexInitializer(final_init_qt, l_brace); - } var il: InitList = .{}; defer il.deinit(p.gpa); try p.initializerItem(&il, final_init_qt, l_brace); - const res = try p.convertInitList(il, final_init_qt); - return .{ .qt = res.qt(&p.tree).withQualifiers(final_init_qt), .node = res }; + const list_node = try p.convertInitList(il, final_init_qt); + return .{ + .qt = list_node.qt(&p.tree).withQualifiers(final_init_qt), + .node = list_node, + .val = p.tree.value_map.get(list_node) orelse .{}, + }; } const IndexList = std.ArrayListUnmanaged(u64); /// initializerItems : designation? initializer (',' designation? initializer)* ','? fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType, l_brace: TokenIndex) Error!void { - const is_scalar, const is_complex = blk: { - if (init_qt.isInvalid()) break :blk .{ false, false }; - const scalar_kind = init_qt.scalarKind(p.comp); - break :blk .{ scalar_kind != .none, !scalar_kind.isReal() }; - }; + const is_scalar = !init_qt.isInvalid() and init_qt.scalarKind(p.comp) != .none; if (p.eatToken(.r_brace)) |_| { if (is_scalar) try p.err(l_brace, .empty_scalar_init, .{}); @@ -3948,9 +3883,9 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType, l_brace: TokenI var index_list: IndexList = .empty; defer index_list.deinit(p.gpa); - var count: u64 = 0; + var seen_any = false; var warned_excess = init_qt.isInvalid(); - while (true) { + while (true) : (seen_any = true) { errdefer p.skipTo(.r_brace); const designated = try p.designation(il, init_qt, &index_list); @@ -3987,20 +3922,14 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType, l_brace: TokenI } else { _ = try p.findScalarInitializer(il, init_qt, res, first_tok, &warned_excess, &index_list, 0); } - } else if (designated) { + } else if (designated or (seen_any and p.tok_ids[p.tok_i] != .r_brace)) { try p.err(p.tok_i, .expected_expr, .{}); - return error.ParsingFailed; } else break; - count += 1; if (p.eatToken(.comma) == null) break; } try p.expectClosing(l_brace, .r_brace); - if (is_scalar and is_complex and count == 2) { - // TODO move to convertInitList - try p.err(l_brace, .complex_component_init, .{}); - } if (il.tok == 0) il.tok = l_brace; } @@ -4477,7 +4406,48 @@ fn convertInitList(p: *Parser, il: InitList, init_qt: QualType) Error!Node.Index if (il.node.unpack()) |some| return some; switch (init_qt.base(p.comp).type) { - // .complex => TODO + .complex => |complex_ty| { + if (il.list.items.len == 0) { + return p.addNode(.{ .default_init_expr = .{ + .last_tok = p.tok_i - 1, + .qt = init_qt, + } }); + } + const first = try p.convertInitList(il.list.items[0].list, complex_ty); + const second = if (il.list.items.len > 1) + try p.convertInitList(il.list.items[1].list, complex_ty) + else + null; + + if (il.list.items.len == 2) { + try p.err(il.tok, .complex_component_init, .{}); + } + + const node = try p.addNode(.{ .array_init_expr = .{ + .container_qt = init_qt, + .items = if (second) |some| + &.{ first, some } + else + &.{first}, + .l_brace_tok = il.tok, + } }); + if (!complex_ty.isFloat(p.comp)) return node; + + const first_node = il.list.items[0].list.node.unpack() orelse return node; + const second_node = if (il.list.items.len > 1) il.list.items[1].list.node else .null; + + const first_val = p.tree.value_map.get(first_node) orelse return node; + const second_val = if (second_node.unpack()) |some| p.tree.value_map.get(some) orelse return node else Value.zero; + const complex_val = try Value.intern(p.comp, switch (complex_ty.bitSizeof(p.comp)) { + 32 => .{ .complex = .{ .cf32 = .{ first_val.toFloat(f32, p.comp), second_val.toFloat(f32, p.comp) } } }, + 64 => .{ .complex = .{ .cf64 = .{ first_val.toFloat(f64, p.comp), second_val.toFloat(f64, p.comp) } } }, + 80 => .{ .complex = .{ .cf80 = .{ first_val.toFloat(f80, p.comp), second_val.toFloat(f80, p.comp) } } }, + 128 => .{ .complex = .{ .cf128 = .{ first_val.toFloat(f128, p.comp), second_val.toFloat(f128, p.comp) } } }, + else => unreachable, + }); + try p.tree.value_map.put(p.gpa, node, complex_val); + return node; + }, .vector => |vector_ty| { const list_buf_top = p.list_buf.items.len; defer p.list_buf.items.len = list_buf_top; diff --git a/test/cases/complex values.c b/test/cases/complex values.c index b03adf8..91a361a 100644 --- a/test/cases/complex values.c +++ b/test/cases/complex values.c @@ -34,8 +34,12 @@ _Complex double c = (_Complex double) {1.0, 2.0,3.0}; _Static_assert(3 + 4.0il == 3 + 4.0il, ""); _Static_assert(5ll + 4.0il == 5ll + 4.0il, ""); unsigned long complex_integer = 2.0i; -bool b = 3 != 2.0i; +bool d = 3 != 2.0i; #define EXPECTED_ERRORS "complex values.c:31:49: error: expected expression" \ + "complex values.c:31:50: error: expected expression" \ + "complex values.c:31:51: error: expected expression" \ + "complex values.c:31:52: error: expected expression" \ + "complex values.c:31:53: error: expected expression" \ "complex values.c:32:49: warning: excess elements in scalar initializer [-Wexcess-initializers]" \ -- Gitee From a078ffa911963fea0ea692beb70bd5d6bd867846 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 22 May 2025 23:20:35 +0300 Subject: [PATCH 061/117] Parser: add warning for use of empty initializer pre-c23 --- src/aro/Parser.zig | 1 + src/aro/Parser/Diagnostic.zig | 8 ++++++++ test/cases/initializers.c | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index e637de8..70211a6 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3870,6 +3870,7 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType, l_brace: TokenI const is_scalar = !init_qt.isInvalid() and init_qt.scalarKind(p.comp) != .none; if (p.eatToken(.r_brace)) |_| { + try p.err(l_brace, .empty_initializer, .{}); if (is_scalar) try p.err(l_brace, .empty_scalar_init, .{}); if (il.tok != 0 and !init_qt.isInvalid()) { try p.err(l_brace, .initializer_overrides, .{}); diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 0153de3..2c644b8 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -1013,6 +1013,14 @@ pub const arr_init_too_long: Diagnostic = .{ .kind = .@"error", }; +pub const empty_initializer: Diagnostic = .{ + .fmt = "use of an empty initializer is a C23 extension", + .opt = .@"c23-extensions", + .kind = .off, + .suppress_version = .c23, + .extension = true, +}; + pub const division_by_zero: Diagnostic = .{ .fmt = "{s} by zero is undefined", .kind = .warning, diff --git a/test/cases/initializers.c b/test/cases/initializers.c index a047bcb..ccb27d1 100644 --- a/test/cases/initializers.c +++ b/test/cases/initializers.c @@ -182,6 +182,9 @@ void vector_excess(void) { vec v4 = { 1, v3 }; } +#pragma GCC diagnostic warning "-Wc23-extensions" +int empty_initializer[2] = {}; + #define TESTS_SKIPPED 1 #define EXPECTED_ERRORS "initializers.c:2:17: error: variable-sized object may not be initialized" \ "initializers.c:3:15: error: illegal initializer type" \ @@ -248,3 +251,4 @@ void vector_excess(void) { "initializers.c:175:19: error: initializing 'int' from incompatible type 'struct A'" \ "initializers.c:179:28: warning: excess elements in vector initializer [-Wexcess-initializers]" \ "initializers.c:182:19: error: initializing 'int' from incompatible type 'vec'" \ + "initializers.c:186:28: warning: use of an empty initializer is a C23 extension [-Wc23-extensions]" \ -- Gitee From 8cc06cfec17ee5e9cda26c3c5c9bf25aa7606bd1 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 22 May 2025 23:30:57 +0300 Subject: [PATCH 062/117] Parser: remove empty scalar initializer error This was an error in C++ until C++03 but never in C --- src/aro/Parser.zig | 1 - src/aro/Parser/Diagnostic.zig | 5 ----- test/cases/initializers.c | 1 - 3 files changed, 7 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 70211a6..4eff1a0 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3871,7 +3871,6 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType, l_brace: TokenI if (p.eatToken(.r_brace)) |_| { try p.err(l_brace, .empty_initializer, .{}); - if (is_scalar) try p.err(l_brace, .empty_scalar_init, .{}); if (il.tok != 0 and !init_qt.isInvalid()) { try p.err(l_brace, .initializer_overrides, .{}); try p.err(il.tok, .previous_initializer, .{}); diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 2c644b8..89f4a24 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -960,11 +960,6 @@ pub const incompatible_init: Diagnostic = .{ .kind = .@"error", }; -pub const empty_scalar_init: Diagnostic = .{ - .fmt = "scalar initializer cannot be empty", - .kind = .@"error", -}; - pub const excess_scalar_init: Diagnostic = .{ .fmt = "excess elements in scalar initializer", .kind = .warning, diff --git a/test/cases/initializers.c b/test/cases/initializers.c index ccb27d1..ce94a78 100644 --- a/test/cases/initializers.c +++ b/test/cases/initializers.c @@ -189,7 +189,6 @@ int empty_initializer[2] = {}; #define EXPECTED_ERRORS "initializers.c:2:17: error: variable-sized object may not be initialized" \ "initializers.c:3:15: error: illegal initializer type" \ "initializers.c:4:14: error: initializing 'int *' from incompatible type 'float'" \ - "initializers.c:5:13: error: scalar initializer cannot be empty" \ "initializers.c:6:17: warning: excess elements in scalar initializer [-Wexcess-initializers]" \ "initializers.c:7:30: warning: excess elements in string initializer [-Wexcess-initializers]" \ "initializers.c:8:23: warning: initializer-string for char array is too long [-Wexcess-initializers]" \ -- Gitee From b8cff73a92d3a2d43c7decf11b00ea47f768274c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 00:09:05 +0300 Subject: [PATCH 063/117] Parser: fix braced initializers not being able to override previous initializations --- src/aro/InitList.zig | 14 +++++++++++--- src/aro/Parser.zig | 15 ++++++++++----- test/cases/initializers.c | 10 ++++++++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/aro/InitList.zig b/src/aro/InitList.zig index b6b4efd..23b1497 100644 --- a/src/aro/InitList.zig +++ b/src/aro/InitList.zig @@ -40,13 +40,21 @@ pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList { var right: usize = items.len; // Append new value to empty list - if (left == right) { + if (il.list.items.len == 0) { const item = try il.list.addOne(gpa); item.* = .{ - .list = .{ .node = .null, .tok = 0 }, + .list = .{}, .index = index, }; return &item.list; + } else if (il.list.items[il.list.items.len - 1].index < index) { + // Append a new value to the end of the list. + const new = try il.list.addOne(gpa); + new.* = .{ + .list = .{}, + .index = index, + }; + return &new.list; } while (left < right) { @@ -62,7 +70,7 @@ pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList { // Insert a new value into a sorted position. try il.list.insert(gpa, left, .{ - .list = .{ .node = .null, .tok = 0 }, + .list = .{}, .index = index, }); return &il.list.items[left].list; diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 4eff1a0..b351bc9 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -3895,13 +3895,19 @@ fn initializerItem(p: *Parser, il: *InitList, init_qt: QualType, l_brace: TokenI const first_tok = p.tok_i; if (p.eatToken(.l_brace)) |inner_l_brace| { - if (try p.findAggregateInitializer(il, init_qt, first_tok, &index_list)) |item| { + if (try p.findBracedInitializer(il, init_qt, first_tok, &index_list)) |item| { + if (item.il.tok != 0 and !init_qt.isInvalid()) { + try p.err(first_tok, .initializer_overrides, .{}); + try p.err(item.il.tok, .previous_initializer, .{}); + item.il.deinit(p.gpa); + item.il.* = .{}; + } try p.initializerItem(item.il, item.qt, inner_l_brace); } else { // discard further values var tmp_il: InitList = .{}; defer tmp_il.deinit(p.gpa); - try p.initializerItem(&tmp_il, .void, inner_l_brace); + try p.initializerItem(&tmp_il, .invalid, inner_l_brace); if (!warned_excess) try p.err(first_tok, switch (init_qt.base(p.comp).type) { .array => if (il.node != .null and p.isStringInit(init_qt, il.node.unpack().?)) .excess_str_init @@ -4049,7 +4055,6 @@ fn findScalarInitializer( const index = index_list.items[index_list_top]; switch (qt.base(p.comp).type) { - .void => return true, .complex => |complex_ty| { if (il.node != .null or index >= 2) { if (!warned_excess.*) try p.err(first_tok, .excess_scalar_init, .{}); @@ -4243,7 +4248,7 @@ fn setInitializerIfEqual(p: *Parser, il: *InitList, init_qt: QualType, tok: Toke const InitItem = struct { il: *InitList, qt: QualType }; -fn findAggregateInitializer( +fn findBracedInitializer( p: *Parser, il: *InitList, qt: QualType, @@ -4361,7 +4366,7 @@ fn coerceArrayInit(p: *Parser, item: Result, tok: TokenIndex, target: QualType) } fn coerceInit(p: *Parser, item: *Result, tok: TokenIndex, target: QualType) !void { - if (target.is(p.comp, .void)) return; // Do not do type coercion on excess items + if (target.isInvalid()) return; const node = item.node; if (target.isAutoType() or target.isC23Auto()) { diff --git a/test/cases/initializers.c b/test/cases/initializers.c index ce94a78..9500fb7 100644 --- a/test/cases/initializers.c +++ b/test/cases/initializers.c @@ -185,6 +185,12 @@ void vector_excess(void) { #pragma GCC diagnostic warning "-Wc23-extensions" int empty_initializer[2] = {}; +void braced_init_overrides(void) { + struct { + int a; + } aa, a[2] = {aa, aa, [0] = {2}, {3}}; +} + #define TESTS_SKIPPED 1 #define EXPECTED_ERRORS "initializers.c:2:17: error: variable-sized object may not be initialized" \ "initializers.c:3:15: error: illegal initializer type" \ @@ -251,3 +257,7 @@ int empty_initializer[2] = {}; "initializers.c:179:28: warning: excess elements in vector initializer [-Wexcess-initializers]" \ "initializers.c:182:19: error: initializing 'int' from incompatible type 'vec'" \ "initializers.c:186:28: warning: use of an empty initializer is a C23 extension [-Wc23-extensions]" \ + "initializers.c:191:33: warning: initializer overrides previous initialization [-Winitializer-overrides]" \ + "initializers.c:191:19: note: previous initialization" \ + "initializers.c:191:38: warning: initializer overrides previous initialization [-Winitializer-overrides]" \ + "initializers.c:191:23: note: previous initialization" \ -- Gitee From 6c917e561267161074b18a4c1b739bfe3b3d26a0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 00:58:22 +0300 Subject: [PATCH 064/117] Parser: handle msvc anonymous structs Closes #845 --- src/aro/Diagnostics.zig | 2 ++ src/aro/Driver.zig | 2 -- src/aro/LangOpts.zig | 2 +- src/aro/Parser.zig | 16 ++++++++++++++-- src/aro/Parser/Diagnostic.zig | 7 +++++++ test/cases/anonymous_struct_ms.c | 16 ++++++++++++++++ test/cases/anonymous_struct_no-ms.c | 16 ++++++++++++++++ 7 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 test/cases/anonymous_struct_ms.c create mode 100644 test/cases/anonymous_struct_no-ms.c diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index add9173..47e5dcb 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -145,6 +145,7 @@ pub const Option = enum { @"nullability-extension", nullability, @"microsoft-flexible-array", + @"microsoft-anon-tag", /// GNU extensions pub const gnu = [_]Option{ @@ -178,6 +179,7 @@ pub const Option = enum { .@"microsoft-end-of-file", .@"microsoft-include", .@"microsoft-flexible-array", + .@"microsoft-anon-tag", }; pub const extra = [_]Option{ diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index c4dbc2d..4fd7052 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -406,10 +406,8 @@ pub fn parseArgs( hosted = true; } else if (mem.eql(u8, arg, "-fms-extensions")) { d.comp.langopts.enableMSExtensions(); - try d.diagnostics.set("microsoft", .off); } else if (mem.eql(u8, arg, "-fno-ms-extensions")) { d.comp.langopts.disableMSExtensions(); - try d.diagnostics.set("microsoft", .warning); } else if (mem.startsWith(u8, arg, "-I")) { var path = arg["-I".len..]; if (path.len == 0) { diff --git a/src/aro/LangOpts.zig b/src/aro/LangOpts.zig index 2fb84be..e03099b 100644 --- a/src/aro/LangOpts.zig +++ b/src/aro/LangOpts.zig @@ -152,7 +152,7 @@ pub fn enableMSExtensions(self: *LangOpts) void { pub fn disableMSExtensions(self: *LangOpts) void { self.declspec_attrs = false; - self.ms_extensions = true; + self.ms_extensions = false; } pub fn hasChar8_T(self: *const LangOpts) bool { diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index b351bc9..9c07c7b 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -2632,9 +2632,21 @@ fn recordDecl(p: *Parser) Error!bool { try p.comp.type_store.attributes.appendSlice(p.gpa, to_append); if (name_tok == 0 and bits == null) unnamed: { - if (!qt.isInvalid()) switch (qt.base(p.comp).type) { + var is_typedef = false; + if (!qt.isInvalid()) loop: switch (qt.type(p.comp)) { + .attributed => |attributed_ty| continue :loop attributed_ty.base.type(p.comp), + .typedef => |typedef_ty| { + is_typedef = true; + continue :loop typedef_ty.base.type(p.comp); + }, + // typeof intentionally ignored here .@"enum" => break :unnamed, - .@"struct", .@"union" => |record_ty| if (record_ty.isAnonymous(p.comp)) { + .@"struct", .@"union" => |record_ty| if ((record_ty.isAnonymous(p.comp) and !is_typedef) or + (p.comp.langopts.ms_extensions and is_typedef)) + { + if (!(record_ty.isAnonymous(p.comp) and !is_typedef)) { + try p.err(first_tok, .anonymous_struct, .{}); + } // An anonymous record appears as indirect fields on the parent try p.record_buf.append(.{ .name = try p.getAnonymousName(first_tok), diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 89f4a24..647477d 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -1405,6 +1405,13 @@ pub const flexible_in_empty_msvc: Diagnostic = .{ .extension = true, }; +pub const anonymous_struct: Diagnostic = .{ + .fmt = "anonymous structs are a Microsoft extension", + .kind = .warning, + .opt = .@"microsoft-anon-tag", + .extension = true, +}; + pub const duplicate_member: Diagnostic = .{ .fmt = "duplicate member '{s}'", .kind = .@"error", diff --git a/test/cases/anonymous_struct_ms.c b/test/cases/anonymous_struct_ms.c new file mode 100644 index 0000000..200f013 --- /dev/null +++ b/test/cases/anonymous_struct_ms.c @@ -0,0 +1,16 @@ +//aro-args -fms-extensions +typedef struct { + int a; +} unnamed; +typedef struct named { + int b; +} named; + +struct S { + unnamed; + named; +}; + +#define EXPECTED_ERRORS \ + "anonymous_struct_ms.c:10:12: warning: anonymous structs are a Microsoft extension [-Wmicrosoft-anon-tag]" \ + "anonymous_struct_ms.c:11:10: warning: anonymous structs are a Microsoft extension [-Wmicrosoft-anon-tag]" \ diff --git a/test/cases/anonymous_struct_no-ms.c b/test/cases/anonymous_struct_no-ms.c new file mode 100644 index 0000000..cadb02e --- /dev/null +++ b/test/cases/anonymous_struct_no-ms.c @@ -0,0 +1,16 @@ +//aro-args -fno-ms-extensions +typedef struct { + int a; +} unnamed; +typedef struct named { + int b; +} named; + +struct S { + unnamed; + named; +}; + +#define EXPECTED_ERRORS \ + "anonymous_struct_no-ms.c:10:12: warning: declaration does not declare anything [-Wmissing-declaration]" \ + "anonymous_struct_no-ms.c:11:10: warning: declaration does not declare anything [-Wmissing-declaration]" \ -- Gitee From 04ac8025ffe60510e91f701faf4a58f60f42a86e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 01:09:44 +0300 Subject: [PATCH 065/117] Parser: fix braced union init not using designated index --- src/aro/Parser.zig | 12 +++++++----- test/cases/initializers.c | 4 ++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 9c07c7b..2bae361 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -4309,8 +4309,7 @@ fn findBracedInitializer( .@"struct" => |struct_ty| { if (il.node != .null) return null; - const field_count = struct_ty.fields.len; - if (index < field_count) { + if (index < struct_ty.fields.len) { index_list.items[0] = index + 1; index_list.items.len = 1; const field_qt = struct_ty.fields[@intCast(index)].qt; @@ -4321,9 +4320,12 @@ fn findBracedInitializer( if (il.node != .null) return null; if (union_ty.fields.len == 0) return null; - index_list.items[0] = index + 1; - index_list.items.len = 1; - return .{ .il = try il.find(p.gpa, 0), .qt = union_ty.fields[0].qt }; + if (index < union_ty.fields.len) { + index_list.items[0] = index + 1; + index_list.items.len = 1; + const field_qt = union_ty.fields[@intCast(index)].qt; + return .{ .il = try il.find(p.gpa, index), .qt = field_qt }; + } }, else => { try p.err(first_tok, .too_many_scalar_init_braces, .{}); diff --git a/test/cases/initializers.c b/test/cases/initializers.c index 9500fb7..54986b6 100644 --- a/test/cases/initializers.c +++ b/test/cases/initializers.c @@ -191,6 +191,10 @@ void braced_init_overrides(void) { } aa, a[2] = {aa, aa, [0] = {2}, {3}}; } +union { int x; char c[4]; } + ua = {1}, + ub = {.c={'a','b','b','a'}}; + #define TESTS_SKIPPED 1 #define EXPECTED_ERRORS "initializers.c:2:17: error: variable-sized object may not be initialized" \ "initializers.c:3:15: error: illegal initializer type" \ -- Gitee From f3406a92570b018e069b88854db660df06ee4943 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Thu, 15 May 2025 23:56:41 -0700 Subject: [PATCH 066/117] Preprocessor: make __DATE__, __TIME__, and __TIMESTAMP__ builtin macros Also makes __DATE__ and __TIME__ behave closer to the way they do in clang/gcc: They represent system time, not the current file's time. GCC and clang use local time; we currently use UTC time. Closes #852 --- src/aro/Builtins.zig | 6 +- src/aro/Compilation.zig | 126 +++++++++++++++-------------------- src/aro/Driver.zig | 17 ++--- src/aro/Parser.zig | 4 +- src/aro/Preprocessor.zig | 87 +++++++++++++++++++++++- src/aro/Tokenizer.zig | 16 ++++- src/aro/Value.zig | 4 +- src/aro/toolchains/Linux.zig | 2 +- src/main.zig | 4 ++ test/cases/invalid epoch.c | 2 +- test/record_runner.zig | 4 +- test/runner.zig | 7 +- 12 files changed, 177 insertions(+), 102 deletions(-) diff --git a/src/aro/Builtins.zig b/src/aro/Builtins.zig index 319a5d7..0704e04 100644 --- a/src/aro/Builtins.zig +++ b/src/aro/Builtins.zig @@ -339,7 +339,7 @@ test Iterator { } test "All builtins" { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); try comp.type_store.initNamedTypes(&comp); @@ -365,9 +365,9 @@ test "All builtins" { test "Allocation failures" { const Test = struct { fn testOne(allocator: std.mem.Allocator) !void { - var comp = Compilation.init(allocator, undefined, std.fs.cwd()); + var comp = Compilation.init(allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); - _ = try comp.generateBuiltinMacros(.include_system_defines, null); + _ = try comp.generateBuiltinMacros(.include_system_defines); const num_builtins = 40; var builtin_it = Iterator{}; diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 71d6f3e..0cac277 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -58,7 +58,7 @@ pub const Environment = struct { /// TODO: not implemented yet c_include_path: ?[]const u8 = null, - /// UNIX timestamp to be used instead of the current date and time in the __DATE__ and __TIME__ macros + /// UNIX timestamp to be used instead of the current date and time in the __DATE__, __TIME__, and __TIMESTAMP__ macros source_date_epoch: ?[]const u8 = null, /// Load all of the environment variables using the std.process API. Do not use if using Aro as a shared library on Linux without libc @@ -91,6 +91,19 @@ pub const Environment = struct { } self.* = undefined; } + + fn sourceEpoch(self: *const Environment) !SourceEpoch { + const max_timestamp = 253402300799; // Dec 31 9999 23:59:59 + + if (self.source_date_epoch) |epoch| { + const parsed = std.fmt.parseInt(u64, epoch, 10) catch return error.InvalidEpoch; + if (parsed > max_timestamp) return error.InvalidEpoch; + return .{ .provided = parsed }; + } else { + const timestamp = std.math.cast(u64, std.time.timestamp()) orelse return error.InvalidEpoch; + return .{ .system = std.math.clamp(timestamp, 0, max_timestamp) }; + } + } }; const Compilation = @This(); @@ -115,12 +128,15 @@ type_store: TypeStore = .{}, /// Used by MS extensions which allow searching for includes relative to the directory of the main source file. ms_cwd_source_id: ?Source.Id = null, cwd: std.fs.Dir, +source_epoch: SourceEpoch, +m_times: std.AutoHashMapUnmanaged(Source.Id, u64) = .{}, -pub fn init(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation { +pub fn init(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir, source_epoch: SourceEpoch) Compilation { return .{ .gpa = gpa, .diagnostics = diagnostics, .cwd = cwd, + .source_epoch = source_epoch, }; } @@ -132,8 +148,10 @@ pub fn initDefault(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) ! .diagnostics = diagnostics, .environment = try Environment.loadAll(gpa), .cwd = cwd, + .source_epoch = undefined, }; errdefer comp.deinit(); + try comp.loadSourceEpochFromEnvironment(); try comp.addDefaultPragmaHandlers(); comp.langopts.setEmulatedCompiler(target_util.systemCompiler(comp.target)); return comp; @@ -159,6 +177,7 @@ pub fn deinit(comp: *Compilation) void { comp.interner.deinit(comp.gpa); comp.environment.deinit(comp.gpa); comp.type_store.deinit(comp.gpa); + comp.m_times.deinit(comp.gpa); comp.* = undefined; } @@ -166,56 +185,27 @@ pub fn internString(comp: *Compilation, str: []const u8) !StringInterner.StringI return comp.string_interner.intern(comp.gpa, str); } -pub fn getSourceEpoch(self: *const Compilation, max: i64) !?u47 { - const provided = self.environment.source_date_epoch orelse return null; - const parsed = std.fmt.parseInt(i64, provided, 10) catch return error.InvalidEpoch; - if (parsed < 0 or parsed > max) return error.InvalidEpoch; - return @intCast(std.math.clamp(parsed, 0, max_timestamp)); +pub fn mTime(comp: *Compilation, source_id: Source.Id) !u64 { + const gop = try comp.m_times.getOrPut(comp.gpa, source_id); + if (!gop.found_existing) { + const source = comp.getSource(source_id); + if (comp.cwd.statFile(source.path)) |stat| { + const mtime = @divTrunc(stat.mtime, std.time.ns_per_s); + gop.value_ptr.* = std.math.cast(u64, mtime) orelse 0; + } else |_| { + gop.value_ptr.* = 0; + } + } + return gop.value_ptr.*; } -/// Dec 31 9999 23:59:59 -const max_timestamp = 253402300799; - -fn generateDateAndTime(w: anytype, opt_timestamp: ?u47) !void { - const timestamp = opt_timestamp orelse { - try w.print("#define __DATE__ \"??? ?? ????\"\n", .{}); - try w.print("#define __TIME__ \"??:??:??\"\n", .{}); - try w.print("#define __TIMESTAMP__ \"??? ??? ?? ??:??:?? ????\"\n", .{}); - return; - }; - const epoch_seconds = EpochSeconds{ .secs = timestamp }; - const epoch_day = epoch_seconds.getEpochDay(); - const day_seconds = epoch_seconds.getDaySeconds(); - const year_day = epoch_day.calculateYearDay(); - const month_day = year_day.calculateMonthDay(); - - const month_names = [_][]const u8{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - std.debug.assert(std.time.epoch.Month.jan.numeric() == 1); - - const month_name = month_names[month_day.month.numeric() - 1]; - try w.print("#define __DATE__ \"{s} {d: >2} {d}\"\n", .{ - month_name, - month_day.day_index + 1, - year_day.year, - }); - try w.print("#define __TIME__ \"{d:0>2}:{d:0>2}:{d:0>2}\"\n", .{ - day_seconds.getHoursIntoDay(), - day_seconds.getMinutesIntoHour(), - day_seconds.getSecondsIntoMinute(), - }); - - const day_names = [_][]const u8{ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; - const day_name = day_names[@intCast((epoch_day.day + 3) % 7)]; - try w.print("#define __TIMESTAMP__ \"{s} {s} {d: >2} {d:0>2}:{d:0>2}:{d:0>2} {d}\"\n", .{ - day_name, - month_name, - month_day.day_index + 1, - day_seconds.getHoursIntoDay(), - day_seconds.getMinutesIntoHour(), - day_seconds.getSecondsIntoMinute(), - year_day.year, - }); -} +pub const SourceEpoch = union(enum) { + /// Represents system time when aro is invoked; used for __DATE__ and __TIME__ macros + system: u64, + /// Represents a user-provided time (typically via the SOURCE_DATE_EPOCH environment variable) + /// used for __DATE__, __TIME__, and __TIMESTAMP__ + provided: u64, +}; /// Which set of system defines to generate via generateBuiltinMacros pub const SystemDefinesMode = enum { @@ -614,15 +604,18 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { } } -/// Generate builtin macros trying to use mtime as timestamp -pub fn generateBuiltinMacrosFromPath(comp: *Compilation, system_defines_mode: SystemDefinesMode, path: []const u8) !Source { - const stat = comp.cwd.statFile(path) catch return try comp.generateBuiltinMacros(system_defines_mode, null); - const timestamp: i64 = @intCast(@divTrunc(stat.mtime, std.time.ns_per_s)); - return try comp.generateBuiltinMacros(system_defines_mode, @intCast(std.math.clamp(timestamp, 0, max_timestamp))); +pub fn loadSourceEpochFromEnvironment(comp: *Compilation) !void { + comp.source_epoch = comp.environment.sourceEpoch() catch |err| switch (err) { + error.InvalidEpoch => blk: { + const diagnostic: Diagnostic = .invalid_source_epoch; + try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null }); + break :blk .{ .provided = 0 }; + }, + }; } /// Generate builtin macros that will be available to each source file. -pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode, timestamp: ?u47) !Source { +pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) !Source { try comp.type_store.initNamedTypes(comp); var buf = std.ArrayList(u8).init(comp.gpa); @@ -658,17 +651,6 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi try buf.append('\n'); } - const provided: ?u47 = comp.getSourceEpoch(max_timestamp) catch blk: { - const diagnostic: Diagnostic = .invalid_source_epoch; - try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null }); - break :blk null; - }; - if (provided) |epoch| { - try generateDateAndTime(buf.writer(), epoch); - } else { - try generateDateAndTime(buf.writer(), timestamp); - } - if (system_defines_mode == .include_system_defines) { try comp.generateSystemDefines(buf.writer()); } @@ -1583,7 +1565,7 @@ pub const Diagnostic = struct { pub const invalid_source_epoch: Diagnostic = .{ .fmt = "environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799", - .kind = .@"error", + .kind = .@"fatal error", }; pub const backslash_newline_escape: Diagnostic = .{ @@ -1611,7 +1593,7 @@ test "addSourceFromReader" { const Test = struct { fn addSourceFromReader(str: []const u8, expected: []const u8, warning_count: u32, splices: []const u32) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); var buf_reader = std.io.fixedBufferStream(str); @@ -1624,7 +1606,7 @@ test "addSourceFromReader" { fn withAllocationFailures(allocator: std.mem.Allocator) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n"); @@ -1667,7 +1649,7 @@ test "addSourceFromReader - exhaustive check for carriage return elimination" { var buf: [alphabet.len]u8 = [1]u8{alphabet[0]} ** alen; var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); var source_count: u32 = 0; @@ -1696,7 +1678,7 @@ test "ignore BOM at beginning of file" { const Test = struct { fn run(buf: []const u8) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); var buf_reader = std.io.fixedBufferStream(buf); diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 4fd7052..173db76 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -767,22 +767,17 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ error.StreamTooLong => return d.fatal("user provided macro source exceeded max size", .{}), else => |e| return e, }; - + const builtin_macros = d.comp.generateBuiltinMacros(d.system_defines) catch |er| switch (er) { + error.StreamTooLong => return d.fatal("builtin macro source exceeded max size", .{}), + else => |e| return e, + }; if (fast_exit and d.inputs.items.len == 1) { - const builtin = d.comp.generateBuiltinMacrosFromPath(d.system_defines, d.inputs.items[0].path) catch |er| switch (er) { - error.StreamTooLong => return d.fatal("builtin macro source exceeded max size", .{}), - else => |e| return e, - }; - try d.processSource(tc, d.inputs.items[0], builtin, user_macros, fast_exit, asm_gen_fn); + try d.processSource(tc, d.inputs.items[0], builtin_macros, user_macros, fast_exit, asm_gen_fn); unreachable; } for (d.inputs.items) |source| { - const builtin = d.comp.generateBuiltinMacrosFromPath(d.system_defines, source.path) catch |er| switch (er) { - error.StreamTooLong => return d.fatal("builtin macro source exceeded max size", .{}), - else => |e| return e, - }; - try d.processSource(tc, source, builtin, user_macros, fast_exit, asm_gen_fn); + try d.processSource(tc, source, builtin_macros, user_macros, fast_exit, asm_gen_fn); } if (d.diagnostics.errors != 0) { if (fast_exit) d.exitWithCleanup(1); diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 2bae361..c574f22 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -9841,7 +9841,7 @@ fn genericSelection(p: *Parser) Error!?Result { test "Node locations" { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); const file = try comp.addSourceFromBuffer("file.c", @@ -9851,7 +9851,7 @@ test "Node locations" { \\ ); - const builtin_macros = try comp.generateBuiltinMacros(.no_system_defines, null); + const builtin_macros = try comp.generateBuiltinMacros(.no_system_defines); var pp = Preprocessor.init(&comp); defer pp.deinit(); diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 59fa035..f78e3a8 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -224,6 +224,9 @@ pub fn addBuiltinMacros(pp: *Preprocessor) !void { try pp.addBuiltinMacro("__FILE__", false, .macro_file); try pp.addBuiltinMacro("__LINE__", false, .macro_line); try pp.addBuiltinMacro("__COUNTER__", false, .macro_counter); + try pp.addBuiltinMacro("__DATE__", false, .macro_date); + try pp.addBuiltinMacro("__TIME__", false, .macro_time); + try pp.addBuiltinMacro("__TIMESTAMP__", false, .macro_timestamp); } pub fn deinit(pp: *Preprocessor) void { @@ -1185,6 +1188,24 @@ fn expandObjMacro(pp: *Preprocessor, simple_macro: *const Macro) Error!ExpandBuf buf.appendAssumeCapacity(try pp.makeGeneratedToken(start, .pp_num, tok)); }, + .macro_date, .macro_time => { + const start = pp.comp.generated_buf.items.len; + const timestamp = switch (pp.comp.source_epoch) { + .system, .provided => |ts| ts, + }; + try pp.writeDateTimeStamp(.fromTokId(raw.id), timestamp); + buf.appendAssumeCapacity(try pp.makeGeneratedToken(start, .string_literal, tok)); + }, + .macro_timestamp => { + const start = pp.comp.generated_buf.items.len; + const timestamp = switch (pp.comp.source_epoch) { + .provided => |ts| ts, + .system => try pp.comp.mTime(pp.expansion_source_loc.id), + }; + + try pp.writeDateTimeStamp(.fromTokId(raw.id), timestamp); + buf.appendAssumeCapacity(try pp.makeGeneratedToken(start, .string_literal, tok)); + }, else => buf.appendAssumeCapacity(tok), } } @@ -1192,6 +1213,66 @@ fn expandObjMacro(pp: *Preprocessor, simple_macro: *const Macro) Error!ExpandBuf return buf; } +const DateTimeStampKind = enum { + date, + time, + timestamp, + + fn fromTokId(tok_id: RawToken.Id) DateTimeStampKind { + return switch (tok_id) { + .macro_date => .date, + .macro_time => .time, + .macro_timestamp => .timestamp, + else => unreachable, + }; + } +}; + +fn writeDateTimeStamp(pp: *Preprocessor, kind: DateTimeStampKind, timestamp: u64) !void { + std.debug.assert(std.time.epoch.Month.jan.numeric() == 1); + + const w = pp.comp.generated_buf.writer(pp.gpa); + + const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = timestamp }; + const epoch_day = epoch_seconds.getEpochDay(); + const day_seconds = epoch_seconds.getDaySeconds(); + const year_day = epoch_day.calculateYearDay(); + const month_day = year_day.calculateMonthDay(); + + const day_names = [_][]const u8{ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; + const month_names = [_][]const u8{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + const day_name = day_names[@intCast((epoch_day.day + 3) % 7)]; + const month_name = month_names[month_day.month.numeric() - 1]; + + switch (kind) { + .date => { + try w.print("\"{s} {d: >2} {d}\"", .{ + month_name, + month_day.day_index + 1, + year_day.year, + }); + }, + .time => { + try w.print("\"{d:0>2}:{d:0>2}:{d:0>2}\"", .{ + day_seconds.getHoursIntoDay(), + day_seconds.getMinutesIntoHour(), + day_seconds.getSecondsIntoMinute(), + }); + }, + .timestamp => { + try w.print("\"{s} {s} {d: >2} {d:0>2}:{d:0>2}:{d:0>2} {d}\"", .{ + day_name, + month_name, + month_day.day_index + 1, + day_seconds.getHoursIntoDay(), + day_seconds.getMinutesIntoHour(), + day_seconds.getSecondsIntoMinute(), + year_day.year, + }); + }, + } +} + /// Join a possibly-parenthesized series of string literal tokens into a single string without /// leading or trailing quotes. The returned slice is invalidated if pp.char_buf changes. /// Returns error.ExpectedStringLiteral if parentheses are not balanced, a non-string-literal @@ -3476,7 +3557,7 @@ test "Preserve pragma tokens sometimes" { defer buf.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -3537,7 +3618,7 @@ test "destringify" { } }; var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); var pp = Preprocessor.init(&comp); defer pp.deinit(); @@ -3596,7 +3677,7 @@ test "Include guards" { fn testIncludeGuard(allocator: std.mem.Allocator, comptime template: []const u8, tok_id: RawToken.Id, expected_guards: u32) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); var pp = Preprocessor.init(&comp); defer pp.deinit(); diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 41b2da5..4b2ef40 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -195,6 +195,12 @@ pub const Token = struct { macro_function, /// Special identifier for implementing __PRETTY_FUNCTION__ macro_pretty_func, + /// Special identifier for implementing __DATE__ + macro_date, + /// Special identifier for implementing __TIME__ + macro_time, + /// Special identifier for implementing __TIMESTAMP__ + macro_timestamp, keyword_auto, keyword_auto_type, @@ -409,6 +415,9 @@ pub const Token = struct { .macro_func, .macro_function, .macro_pretty_func, + .macro_date, + .macro_time, + .macro_timestamp, .keyword_auto, .keyword_auto_type, .keyword_break, @@ -622,6 +631,9 @@ pub const Token = struct { .macro_file, .macro_line, .macro_counter, + .macro_time, + .macro_date, + .macro_timestamp, .macro_param_pragma_operator, .macro_param_ms_identifier, .macro_param_ms_pragma, @@ -2334,7 +2346,7 @@ test "Universal character names" { test "Tokenizer fuzz test" { const Context = struct { fn testOne(_: @This(), input_bytes: []const u8) anyerror!void { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes); @@ -2356,7 +2368,7 @@ test "Tokenizer fuzz test" { } fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, standard: ?LangOpts.Standard) !void { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); if (standard) |provided| { comp.langopts.standard = provided; diff --git a/src/aro/Value.zig b/src/aro/Value.zig index e1494a2..39fb1c5 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -76,7 +76,7 @@ test "minUnsignedBits" { } }; - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = try std.zig.system.resolveTargetQuery(target_query); @@ -111,7 +111,7 @@ test "minSignedBits" { } }; - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = try std.zig.system.resolveTargetQuery(target_query); diff --git a/src/aro/toolchains/Linux.zig b/src/aro/toolchains/Linux.zig index fafddd0..8f6647e 100644 --- a/src/aro/toolchains/Linux.zig +++ b/src/aro/toolchains/Linux.zig @@ -416,7 +416,7 @@ test Linux { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); comp.environment = .{ .path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", diff --git a/src/main.zig b/src/main.zig index bc4cb5c..0909527 100644 --- a/src/main.zig +++ b/src/main.zig @@ -53,6 +53,10 @@ pub fn main() u8 { if (fast_exit) process.exit(1); return 1; }, + error.FatalError => { + if (fast_exit) process.exit(1); + return 1; + }, }; defer comp.deinit(); diff --git a/test/cases/invalid epoch.c b/test/cases/invalid epoch.c index 32d046b..596b74d 100644 --- a/test/cases/invalid epoch.c +++ b/test/cases/invalid epoch.c @@ -1,4 +1,4 @@ //aro-env SOURCE_DATE_EPOCH=abc -#define EXPECTED_ERRORS "error: environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799" \ +#define EXPECTED_ERRORS "fatal error: environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799" \ diff --git a/test/record_runner.zig b/test/record_runner.zig index c681707..2e96cd2 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -231,7 +231,7 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase }; defer diagnostics.deinit(); - var comp = aro.Compilation.init(alloc, &diagnostics, std.fs.cwd()); + var comp = aro.Compilation.init(alloc, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -287,7 +287,7 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase } const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); - const builtin_macros = try comp.generateBuiltinMacrosFromPath(.include_system_defines, file.path); + const builtin_macros = try comp.generateBuiltinMacros(.include_system_defines); var pp = aro.Preprocessor.init(&comp); defer pp.deinit(); diff --git a/test/runner.zig b/test/runner.zig index 9779266..0ac7169 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -76,7 +76,7 @@ fn testOne(allocator: std.mem.Allocator, path: []const u8, test_dir: []const u8) _, _, const system_defines, _ = try addCommandLineArgs(&comp, file, macro_buf.writer()); const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); - const builtin_macros = try comp.generateBuiltinMacrosFromPath(system_defines, path); + const builtin_macros = try comp.generateBuiltinMacros(system_defines); var pp = aro.Preprocessor.init(&comp); defer pp.deinit(); @@ -173,7 +173,7 @@ pub fn main() !void { defer diagnostics.deinit(); // prepare compiler - var initial_comp = aro.Compilation.init(gpa, &diagnostics, std.fs.cwd()); + var initial_comp = aro.Compilation.init(gpa, &diagnostics, std.fs.cwd(), .{ .system = 0 }); defer initial_comp.deinit(); const cases_include_dir = try std.fs.path.join(gpa, &.{ args[1], "include" }); @@ -232,9 +232,10 @@ pub fn main() !void { defer macro_buf.deinit(); const only_preprocess, const linemarkers, const system_defines, const dump_mode = try addCommandLineArgs(&comp, file, macro_buf.writer()); + try comp.loadSourceEpochFromEnvironment(); const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); - const builtin_macros = try comp.generateBuiltinMacrosFromPath(system_defines, file.path); + const builtin_macros = try comp.generateBuiltinMacros(system_defines); var pp = aro.Preprocessor.init(&comp); defer pp.deinit(); -- Gitee From 73374e05381f906b7f2858a2cf15e3764c3b364b Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Thu, 22 May 2025 23:03:48 -0700 Subject: [PATCH 067/117] Preprocessor: move source_epoch handling into Preprocessor from Compilation --- src/aro/Builtins.zig | 4 +- src/aro/Compilation.zig | 75 ++++++++++------------------- src/aro/Parser.zig | 4 +- src/aro/Preprocessor.zig | 45 ++++++++++++----- src/aro/Preprocessor/Diagnostic.zig | 5 ++ src/aro/Tokenizer.zig | 4 +- src/aro/Value.zig | 4 +- src/aro/toolchains/Linux.zig | 2 +- src/main.zig | 4 -- test/cases/invalid epoch.c | 2 +- test/record_runner.zig | 5 +- test/runner.zig | 6 +-- 12 files changed, 79 insertions(+), 81 deletions(-) diff --git a/src/aro/Builtins.zig b/src/aro/Builtins.zig index 0704e04..e76a8a8 100644 --- a/src/aro/Builtins.zig +++ b/src/aro/Builtins.zig @@ -339,7 +339,7 @@ test Iterator { } test "All builtins" { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); try comp.type_store.initNamedTypes(&comp); @@ -365,7 +365,7 @@ test "All builtins" { test "Allocation failures" { const Test = struct { fn testOne(allocator: std.mem.Allocator) !void { - var comp = Compilation.init(allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(allocator, undefined, std.fs.cwd()); defer comp.deinit(); _ = try comp.generateBuiltinMacros(.include_system_defines); diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 0cac277..d915a70 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -61,6 +61,16 @@ pub const Environment = struct { /// UNIX timestamp to be used instead of the current date and time in the __DATE__, __TIME__, and __TIMESTAMP__ macros source_date_epoch: ?[]const u8 = null, + pub const SourceEpoch = union(enum) { + /// Represents system time when aro is invoked; used for __DATE__ and __TIME__ macros + system: u64, + /// Represents a user-provided time (typically via the SOURCE_DATE_EPOCH environment variable) + /// used for __DATE__, __TIME__, and __TIMESTAMP__ + provided: u64, + + pub const default: @This() = .{ .provided = 0 }; + }; + /// Load all of the environment variables using the std.process API. Do not use if using Aro as a shared library on Linux without libc /// See https://github.com/ziglang/zig/issues/4524 pub fn loadAll(allocator: std.mem.Allocator) !Environment { @@ -92,7 +102,7 @@ pub const Environment = struct { self.* = undefined; } - fn sourceEpoch(self: *const Environment) !SourceEpoch { + pub fn sourceEpoch(self: *const Environment) !SourceEpoch { const max_timestamp = 253402300799; // Dec 31 9999 23:59:59 if (self.source_date_epoch) |epoch| { @@ -128,15 +138,12 @@ type_store: TypeStore = .{}, /// Used by MS extensions which allow searching for includes relative to the directory of the main source file. ms_cwd_source_id: ?Source.Id = null, cwd: std.fs.Dir, -source_epoch: SourceEpoch, -m_times: std.AutoHashMapUnmanaged(Source.Id, u64) = .{}, -pub fn init(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir, source_epoch: SourceEpoch) Compilation { +pub fn init(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation { return .{ .gpa = gpa, .diagnostics = diagnostics, .cwd = cwd, - .source_epoch = source_epoch, }; } @@ -148,10 +155,8 @@ pub fn initDefault(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) ! .diagnostics = diagnostics, .environment = try Environment.loadAll(gpa), .cwd = cwd, - .source_epoch = undefined, }; errdefer comp.deinit(); - try comp.loadSourceEpochFromEnvironment(); try comp.addDefaultPragmaHandlers(); comp.langopts.setEmulatedCompiler(target_util.systemCompiler(comp.target)); return comp; @@ -177,7 +182,6 @@ pub fn deinit(comp: *Compilation) void { comp.interner.deinit(comp.gpa); comp.environment.deinit(comp.gpa); comp.type_store.deinit(comp.gpa); - comp.m_times.deinit(comp.gpa); comp.* = undefined; } @@ -185,28 +189,6 @@ pub fn internString(comp: *Compilation, str: []const u8) !StringInterner.StringI return comp.string_interner.intern(comp.gpa, str); } -pub fn mTime(comp: *Compilation, source_id: Source.Id) !u64 { - const gop = try comp.m_times.getOrPut(comp.gpa, source_id); - if (!gop.found_existing) { - const source = comp.getSource(source_id); - if (comp.cwd.statFile(source.path)) |stat| { - const mtime = @divTrunc(stat.mtime, std.time.ns_per_s); - gop.value_ptr.* = std.math.cast(u64, mtime) orelse 0; - } else |_| { - gop.value_ptr.* = 0; - } - } - return gop.value_ptr.*; -} - -pub const SourceEpoch = union(enum) { - /// Represents system time when aro is invoked; used for __DATE__ and __TIME__ macros - system: u64, - /// Represents a user-provided time (typically via the SOURCE_DATE_EPOCH environment variable) - /// used for __DATE__, __TIME__, and __TIMESTAMP__ - provided: u64, -}; - /// Which set of system defines to generate via generateBuiltinMacros pub const SystemDefinesMode = enum { /// Only define macros required by the C standard (date/time macros and those beginning with `__STDC`) @@ -604,16 +586,6 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { } } -pub fn loadSourceEpochFromEnvironment(comp: *Compilation) !void { - comp.source_epoch = comp.environment.sourceEpoch() catch |err| switch (err) { - error.InvalidEpoch => blk: { - const diagnostic: Diagnostic = .invalid_source_epoch; - try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null }); - break :blk .{ .provided = 0 }; - }, - }; -} - /// Generate builtin macros that will be available to each source file. pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) !Source { try comp.type_store.initNamedTypes(comp); @@ -1543,6 +1515,16 @@ pub fn locSlice(comp: *const Compilation, loc: Source.Location) []const u8 { return tmp_tokenizer.buf[tok.start..tok.end]; } +pub fn getSourceMTimeUncached(comp: *const Compilation, source_id: Source.Id) ?u64 { + const source = comp.getSource(source_id); + if (comp.cwd.statFile(source.path)) |stat| { + const mtime = @divTrunc(stat.mtime, std.time.ns_per_s); + return std.math.cast(u64, mtime); + } else |_| { + return null; + } +} + pub const CharUnitSize = enum(u32) { @"1" = 1, @"2" = 2, @@ -1563,11 +1545,6 @@ pub const Diagnostic = struct { opt: ?Diagnostics.Option = null, extension: bool = false, - pub const invalid_source_epoch: Diagnostic = .{ - .fmt = "environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799", - .kind = .@"fatal error", - }; - pub const backslash_newline_escape: Diagnostic = .{ .fmt = "backslash and newline separated by space", .kind = .warning, @@ -1593,7 +1570,7 @@ test "addSourceFromReader" { const Test = struct { fn addSourceFromReader(str: []const u8, expected: []const u8, warning_count: u32, splices: []const u32) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); var buf_reader = std.io.fixedBufferStream(str); @@ -1606,7 +1583,7 @@ test "addSourceFromReader" { fn withAllocationFailures(allocator: std.mem.Allocator) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n"); @@ -1649,7 +1626,7 @@ test "addSourceFromReader - exhaustive check for carriage return elimination" { var buf: [alphabet.len]u8 = [1]u8{alphabet[0]} ** alen; var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); var source_count: u32 = 0; @@ -1678,7 +1655,7 @@ test "ignore BOM at beginning of file" { const Test = struct { fn run(buf: []const u8) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); var buf_reader = std.io.fixedBufferStream(buf); diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index c574f22..d7b4295 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -9841,7 +9841,7 @@ fn genericSelection(p: *Parser) Error!?Result { test "Node locations" { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); const file = try comp.addSourceFromBuffer("file.c", @@ -9853,7 +9853,7 @@ test "Node locations" { const builtin_macros = try comp.generateBuiltinMacros(.no_system_defines); - var pp = Preprocessor.init(&comp); + var pp = Preprocessor.init(&comp, .default); defer pp.deinit(); try pp.addBuiltinMacros(); diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index f78e3a8..5dda462 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -14,6 +14,7 @@ const Source = @import("Source.zig"); const text_literal = @import("text_literal.zig"); const Tokenizer = @import("Tokenizer.zig"); const RawToken = Tokenizer.Token; +const SourceEpoch = Compilation.Environment.SourceEpoch; const Tree = @import("Tree.zig"); const Token = Tree.Token; const TokenWithExpansionLocs = Tree.TokenWithExpansionLocs; @@ -152,6 +153,10 @@ linemarkers: Linemarkers = .none, hideset: Hideset, +/// Epoch used for __DATE__, __TIME__, and possibly __TIMESTAMP__ +source_epoch: SourceEpoch, +m_times: std.AutoHashMapUnmanaged(Source.Id, u64) = .{}, + pub const parse = Parser.parse; pub const Linemarkers = enum { @@ -163,7 +168,7 @@ pub const Linemarkers = enum { numeric_directives, }; -pub fn init(comp: *Compilation) Preprocessor { +pub fn init(comp: *Compilation, source_epoch: SourceEpoch) Preprocessor { const pp = Preprocessor{ .comp = comp, .diagnostics = comp.diagnostics, @@ -174,6 +179,7 @@ pub fn init(comp: *Compilation) Preprocessor { .poisoned_identifiers = std.StringHashMap(void).init(comp.gpa), .top_expansion_buf = ExpandBuf.init(comp.gpa), .hideset = .{ .comp = comp }, + .source_epoch = source_epoch, }; comp.pragmaEvent(.before_preprocess); return pp; @@ -181,7 +187,15 @@ pub fn init(comp: *Compilation) Preprocessor { /// Initialize Preprocessor with builtin macros. pub fn initDefault(comp: *Compilation) !Preprocessor { - var pp = init(comp); + const source_epoch: SourceEpoch = comp.environment.sourceEpoch() catch |er| switch (er) { + error.InvalidEpoch => blk: { + const diagnostic: Diagnostic = .invalid_source_epoch; + try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null }); + break :blk .default; + }, + }; + + var pp = init(comp, source_epoch); errdefer pp.deinit(); try pp.addBuiltinMacros(); return pp; @@ -241,6 +255,7 @@ pub fn deinit(pp: *Preprocessor) void { pp.hideset.deinit(); for (pp.expansion_entries.items(.locs)) |locs| TokenWithExpansionLocs.free(locs, pp.gpa); pp.expansion_entries.deinit(pp.gpa); + pp.m_times.deinit(pp.gpa); } /// Free buffers that are not needed after preprocessing @@ -251,6 +266,14 @@ fn clearBuffers(pp: *Preprocessor) void { pp.hideset.clearAndFree(); } +fn mTime(pp: *Preprocessor, source_id: Source.Id) !u64 { + const gop = try pp.m_times.getOrPut(pp.gpa, source_id); + if (!gop.found_existing) { + gop.value_ptr.* = pp.comp.getSourceMTimeUncached(source_id) orelse 0; + } + return gop.value_ptr.*; +} + pub fn expansionSlice(pp: *Preprocessor, tok: Tree.TokenIndex) []Source.Location { const S = struct { fn orderTokenIndex(context: Tree.TokenIndex, item: Tree.TokenIndex) std.math.Order { @@ -1190,7 +1213,7 @@ fn expandObjMacro(pp: *Preprocessor, simple_macro: *const Macro) Error!ExpandBuf }, .macro_date, .macro_time => { const start = pp.comp.generated_buf.items.len; - const timestamp = switch (pp.comp.source_epoch) { + const timestamp = switch (pp.source_epoch) { .system, .provided => |ts| ts, }; try pp.writeDateTimeStamp(.fromTokId(raw.id), timestamp); @@ -1198,9 +1221,9 @@ fn expandObjMacro(pp: *Preprocessor, simple_macro: *const Macro) Error!ExpandBuf }, .macro_timestamp => { const start = pp.comp.generated_buf.items.len; - const timestamp = switch (pp.comp.source_epoch) { + const timestamp = switch (pp.source_epoch) { .provided => |ts| ts, - .system => try pp.comp.mTime(pp.expansion_source_loc.id), + .system => try pp.mTime(pp.expansion_source_loc.id), }; try pp.writeDateTimeStamp(.fromTokId(raw.id), timestamp); @@ -3557,12 +3580,12 @@ test "Preserve pragma tokens sometimes" { defer buf.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); - var pp = Preprocessor.init(&comp); + var pp = Preprocessor.init(&comp, .default); defer pp.deinit(); pp.preserve_whitespace = true; @@ -3618,9 +3641,9 @@ test "destringify" { } }; var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); - var pp = Preprocessor.init(&comp); + var pp = Preprocessor.init(&comp, .default); defer pp.deinit(); try Test.testDestringify(&pp, "hello\tworld\n", "hello\tworld\n"); @@ -3677,9 +3700,9 @@ test "Include guards" { fn testIncludeGuard(allocator: std.mem.Allocator, comptime template: []const u8, tok_id: RawToken.Id, expected_guards: u32) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); defer comp.deinit(); - var pp = Preprocessor.init(&comp); + var pp = Preprocessor.init(&comp, .default); defer pp.deinit(); const path = try std.fs.path.join(allocator, &.{ ".", "bar.h" }); diff --git a/src/aro/Preprocessor/Diagnostic.zig b/src/aro/Preprocessor/Diagnostic.zig index d53a661..7060edf 100644 --- a/src/aro/Preprocessor/Diagnostic.zig +++ b/src/aro/Preprocessor/Diagnostic.zig @@ -435,3 +435,8 @@ pub const incomplete_ucn: Diagnostic = .{ .kind = .warning, .opt = .unicode, }; + +pub const invalid_source_epoch: Diagnostic = .{ + .fmt = "environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799", + .kind = .@"error", +}; diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 4b2ef40..ba6fc00 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -2346,7 +2346,7 @@ test "Universal character names" { test "Tokenizer fuzz test" { const Context = struct { fn testOne(_: @This(), input_bytes: []const u8) anyerror!void { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes); @@ -2368,7 +2368,7 @@ test "Tokenizer fuzz test" { } fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, standard: ?LangOpts.Standard) !void { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); if (standard) |provided| { comp.langopts.standard = provided; diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 39fb1c5..e1494a2 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -76,7 +76,7 @@ test "minUnsignedBits" { } }; - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = try std.zig.system.resolveTargetQuery(target_query); @@ -111,7 +111,7 @@ test "minSignedBits" { } }; - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = try std.zig.system.resolveTargetQuery(target_query); diff --git a/src/aro/toolchains/Linux.zig b/src/aro/toolchains/Linux.zig index 8f6647e..fafddd0 100644 --- a/src/aro/toolchains/Linux.zig +++ b/src/aro/toolchains/Linux.zig @@ -416,7 +416,7 @@ test Linux { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd(), .{ .provided = 0 }); + var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); defer comp.deinit(); comp.environment = .{ .path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", diff --git a/src/main.zig b/src/main.zig index 0909527..bc4cb5c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -53,10 +53,6 @@ pub fn main() u8 { if (fast_exit) process.exit(1); return 1; }, - error.FatalError => { - if (fast_exit) process.exit(1); - return 1; - }, }; defer comp.deinit(); diff --git a/test/cases/invalid epoch.c b/test/cases/invalid epoch.c index 596b74d..32d046b 100644 --- a/test/cases/invalid epoch.c +++ b/test/cases/invalid epoch.c @@ -1,4 +1,4 @@ //aro-env SOURCE_DATE_EPOCH=abc -#define EXPECTED_ERRORS "fatal error: environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799" \ +#define EXPECTED_ERRORS "error: environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799" \ diff --git a/test/record_runner.zig b/test/record_runner.zig index 2e96cd2..5a4ea99 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -231,7 +231,7 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase }; defer diagnostics.deinit(); - var comp = aro.Compilation.init(alloc, &diagnostics, std.fs.cwd(), .{ .provided = 0 }); + var comp = aro.Compilation.init(alloc, &diagnostics, std.fs.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -289,9 +289,8 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); const builtin_macros = try comp.generateBuiltinMacros(.include_system_defines); - var pp = aro.Preprocessor.init(&comp); + var pp = try aro.Preprocessor.initDefault(&comp); defer pp.deinit(); - try pp.addBuiltinMacros(); _ = try pp.preprocess(builtin_macros); _ = try pp.preprocess(user_macros); diff --git a/test/runner.zig b/test/runner.zig index 0ac7169..fdf28df 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -173,7 +173,7 @@ pub fn main() !void { defer diagnostics.deinit(); // prepare compiler - var initial_comp = aro.Compilation.init(gpa, &diagnostics, std.fs.cwd(), .{ .system = 0 }); + var initial_comp = aro.Compilation.init(gpa, &diagnostics, std.fs.cwd()); defer initial_comp.deinit(); const cases_include_dir = try std.fs.path.join(gpa, &.{ args[1], "include" }); @@ -232,12 +232,11 @@ pub fn main() !void { defer macro_buf.deinit(); const only_preprocess, const linemarkers, const system_defines, const dump_mode = try addCommandLineArgs(&comp, file, macro_buf.writer()); - try comp.loadSourceEpochFromEnvironment(); const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); const builtin_macros = try comp.generateBuiltinMacros(system_defines); - var pp = aro.Preprocessor.init(&comp); + var pp = try aro.Preprocessor.initDefault(&comp); defer pp.deinit(); if (only_preprocess) { pp.preserve_whitespace = true; @@ -246,7 +245,6 @@ pub fn main() !void { pp.store_macro_tokens = true; } } - try pp.addBuiltinMacros(); if (comp.langopts.ms_extensions) { comp.ms_cwd_source_id = file.id; -- Gitee From badb7d2e589f9d3eade6c4cb067db1502a145631 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Thu, 22 May 2025 23:05:40 -0700 Subject: [PATCH 068/117] Compilation: clarify comment about SOURCE_DATE_EPOCH --- src/aro/Compilation.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index d915a70..954417f 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -58,7 +58,8 @@ pub const Environment = struct { /// TODO: not implemented yet c_include_path: ?[]const u8 = null, - /// UNIX timestamp to be used instead of the current date and time in the __DATE__, __TIME__, and __TIMESTAMP__ macros + /// UNIX timestamp to be used instead of the current date and time in the __DATE__ and __TIME__ macros, and instead of the + /// file modification time in the __TIMESTAMP__ macro source_date_epoch: ?[]const u8 = null, pub const SourceEpoch = union(enum) { -- Gitee From db1df038bd5f1fc1fa01a8fff7932025f374fd01 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 11:21:46 +0300 Subject: [PATCH 069/117] LangOpts: disable ms extensions if not emulating msvc --- src/aro/LangOpts.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aro/LangOpts.zig b/src/aro/LangOpts.zig index e03099b..a932c5b 100644 --- a/src/aro/LangOpts.zig +++ b/src/aro/LangOpts.zig @@ -165,7 +165,10 @@ pub fn hasDigraphs(self: *const LangOpts) bool { pub fn setEmulatedCompiler(self: *LangOpts, compiler: Compiler) void { self.emulate = compiler; - if (compiler == .msvc) self.enableMSExtensions(); + if (compiler == .msvc) + self.enableMSExtensions() + else + self.disableMSExtensions(); } pub fn setFpEvalMethod(self: *LangOpts, fp_eval_method: FPEvalMethod) void { -- Gitee From 0e522b0162eef572a889359ae0f81e93c0ad7e85 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 11:47:48 +0300 Subject: [PATCH 070/117] Driver: don't override -fdeclspec and -fms-extensions with target --- src/aro/Driver.zig | 52 +++++++++++++++++++++++++++--------------- src/aro/LangOpts.zig | 16 ++++--------- test/record_runner.zig | 2 +- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 173db76..901e7d2 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -255,6 +255,8 @@ pub fn parseArgs( var hosted: ?bool = null; var gnuc_version: []const u8 = "4.2.1"; // default value set by clang var pic_arg: []const u8 = ""; + var declspec_attrs: ?bool = null; + var ms_extensions: ?bool = null; while (i < args.len) : (i += 1) { const arg = args[i]; if (mem.startsWith(u8, arg, "-") and arg.len > 1) { @@ -356,12 +358,12 @@ pub fn parseArgs( d.comp.code_gen_options.debug = false; } else if (mem.eql(u8, arg, "-fdigraphs")) { d.comp.langopts.digraphs = true; + } else if (mem.eql(u8, arg, "-fno-digraphs")) { + d.comp.langopts.digraphs = false; } else if (mem.eql(u8, arg, "-fgnu-inline-asm")) { d.comp.langopts.gnu_asm = true; } else if (mem.eql(u8, arg, "-fno-gnu-inline-asm")) { d.comp.langopts.gnu_asm = false; - } else if (mem.eql(u8, arg, "-fno-digraphs")) { - d.comp.langopts.digraphs = false; } else if (option(arg, "-fmacro-backtrace-limit=")) |limit_str| { var limit = std.fmt.parseInt(u32, limit_str, 10) catch { try d.err("-fmacro-backtrace-limit takes a number argument", .{}); @@ -397,17 +399,17 @@ pub fn parseArgs( } else if (mem.eql(u8, arg, "-fno-unsigned-char")) { d.comp.langopts.setCharSignedness(.signed); } else if (mem.eql(u8, arg, "-fdeclspec")) { - d.comp.langopts.declspec_attrs = true; + declspec_attrs = true; } else if (mem.eql(u8, arg, "-fno-declspec")) { - d.comp.langopts.declspec_attrs = false; + declspec_attrs = false; } else if (mem.eql(u8, arg, "-ffreestanding")) { hosted = false; } else if (mem.eql(u8, arg, "-fhosted")) { hosted = true; } else if (mem.eql(u8, arg, "-fms-extensions")) { - d.comp.langopts.enableMSExtensions(); + ms_extensions = true; } else if (mem.eql(u8, arg, "-fno-ms-extensions")) { - d.comp.langopts.disableMSExtensions(); + ms_extensions = false; } else if (mem.startsWith(u8, arg, "-I")) { var path = arg["-I".len..]; if (path.len == 0) { @@ -509,21 +511,14 @@ pub fn parseArgs( try d.err("invalid standard '{s}'", .{arg}); } else if (mem.eql(u8, arg, "-S") or mem.eql(u8, arg, "--assemble")) { d.only_preprocess_and_compile = true; - } else if (option(arg, "--target=")) |triple| { - const query = std.Target.Query.parse(.{ .arch_os_abi = triple }) catch { - try d.err("invalid target '{s}'", .{arg}); + } else if (mem.eql(u8, arg, "-target")) { + i += 1; + if (i >= args.len) { + try d.err("expected argument after -target", .{}); continue; - }; - const target = std.zig.system.resolveTargetQuery(query) catch |e| { - return d.fatal("unable to resolve target: {s}", .{errorDescription(e)}); - }; - d.comp.target = target; - d.comp.langopts.setEmulatedCompiler(target_util.systemCompiler(target)); - switch (d.comp.langopts.emulate) { - .clang => try d.diagnostics.set("clang", .off), - .gcc => try d.diagnostics.set("gnu", .off), - .msvc => try d.diagnostics.set("microsoft", .off), } + d.raw_target_triple = args[i]; + } else if (option(arg, "--target=")) |triple| { d.raw_target_triple = triple; } else if (mem.eql(u8, arg, "--verbose-ast")) { d.verbose_ast = true; @@ -602,6 +597,23 @@ pub fn parseArgs( try d.inputs.append(d.comp.gpa, source); } } + if (d.raw_target_triple) |triple| triple: { + const query = std.Target.Query.parse(.{ .arch_os_abi = triple }) catch { + try d.err("invalid target '{s}'", .{triple}); + d.raw_target_triple = null; + break :triple; + }; + const target = std.zig.system.resolveTargetQuery(query) catch |e| { + return d.fatal("unable to resolve target: {s}", .{errorDescription(e)}); + }; + d.comp.target = target; + d.comp.langopts.setEmulatedCompiler(target_util.systemCompiler(target)); + switch (d.comp.langopts.emulate) { + .clang => try d.diagnostics.set("clang", .off), + .gcc => try d.diagnostics.set("gnu", .off), + .msvc => try d.diagnostics.set("microsoft", .off), + } + } if (d.comp.langopts.preserve_comments and !d.only_preprocess) { return d.fatal("invalid argument '{s}' only allowed with '-E'", .{comment_arg}); } @@ -622,6 +634,8 @@ pub fn parseArgs( const pic_level, const is_pie = try d.getPICMode(pic_arg); d.comp.code_gen_options.pic_level = pic_level; d.comp.code_gen_options.is_pie = is_pie; + if (declspec_attrs) |some| d.comp.langopts.declspec_attrs = some; + if (ms_extensions) |some| d.comp.langopts.setMSExtensions(some); return false; } diff --git a/src/aro/LangOpts.zig b/src/aro/LangOpts.zig index a932c5b..811b6bf 100644 --- a/src/aro/LangOpts.zig +++ b/src/aro/LangOpts.zig @@ -145,14 +145,9 @@ pub fn setStandard(self: *LangOpts, name: []const u8) error{InvalidStandard}!voi self.standard = Standard.NameMap.get(name) orelse return error.InvalidStandard; } -pub fn enableMSExtensions(self: *LangOpts) void { - self.declspec_attrs = true; - self.ms_extensions = true; -} - -pub fn disableMSExtensions(self: *LangOpts) void { - self.declspec_attrs = false; - self.ms_extensions = false; +pub fn setMSExtensions(self: *LangOpts, enabled: bool) void { + self.declspec_attrs = enabled; + self.ms_extensions = enabled; } pub fn hasChar8_T(self: *const LangOpts) bool { @@ -165,10 +160,7 @@ pub fn hasDigraphs(self: *const LangOpts) bool { pub fn setEmulatedCompiler(self: *LangOpts, compiler: Compiler) void { self.emulate = compiler; - if (compiler == .msvc) - self.enableMSExtensions() - else - self.disableMSExtensions(); + self.setMSExtensions(compiler == .msvc); } pub fn setFpEvalMethod(self: *LangOpts, fp_eval_method: FPEvalMethod) void { diff --git a/test/record_runner.zig b/test/record_runner.zig index 5a4ea99..9dc6b56 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -282,7 +282,7 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase const mac_writer = macro_buf.writer(); try mac_writer.print("#define {s}\n", .{test_case.c_define}); if (comp.langopts.emulate == .msvc) { - comp.langopts.enableMSExtensions(); + comp.langopts.setMSExtensions(true); try mac_writer.writeAll("#define MSVC\n"); } -- Gitee From 9d8cdb40f30fa4186b8a940cad70d6c78b2bc00e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 12:29:37 +0300 Subject: [PATCH 071/117] Attribute: validate _BitInt vector size --- src/aro/Attribute.zig | 9 +++++++++ src/aro/Parser/Diagnostic.zig | 10 ++++++++++ test/cases/vectors.c | 5 +++++ 3 files changed, 24 insertions(+) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 16fb850..797e87b 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -1219,6 +1219,15 @@ fn applyVectorSize(attr: Attribute, p: *Parser, tok: TokenIndex, qt: *QualType) try p.err(tok, .invalid_vec_elem_ty, .{qt.*}); return error.ParsingFailed; } + if (qt.get(p.comp, .bit_int)) |bit_int| { + if (bit_int.bits < 8) { + try p.err(tok, .bit_int_vec_too_small, .{}); + return error.ParsingFailed; + } else if (!std.math.isPowerOfTwo(bit_int.bits)) { + try p.err(tok, .bit_int_vec_not_pow2, .{}); + return error.ParsingFailed; + } + } const vec_bytes = attr.args.vector_size.bytes; const elem_size = qt.sizeof(p.comp); diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 647477d..db57e0a 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -1811,6 +1811,16 @@ pub const invalid_vec_elem_ty: Diagnostic = .{ .kind = .@"error", }; +pub const bit_int_vec_too_small: Diagnostic = .{ + .fmt = "'_BitInt' vector element width must be at least as wide as 'CHAR_BIT'", + .kind = .@"error", +}; + +pub const bit_int_vec_not_pow2: Diagnostic = .{ + .fmt = "'_BitInt' vector element width must be a power of 2", + .kind = .@"error", +}; + pub const vec_size_not_multiple: Diagnostic = .{ .fmt = "vector size not an integral multiple of component size", .kind = .@"error", diff --git a/test/cases/vectors.c b/test/cases/vectors.c index 98d7912..d79383a 100644 --- a/test/cases/vectors.c +++ b/test/cases/vectors.c @@ -10,6 +10,11 @@ void foo(void) { (f2v)1; } +typedef _BitInt(4) invalid3 __attribute__((vector_size(4 * 2))); +typedef _BitInt(11) invalid4 __attribute__((vector_size(11 * 2))); + #define EXPECTED_ERRORS "vectors.c:2:40: error: invalid vector element type 'float *'" \ "vectors.c:3:39: error: vector size not an integral multiple of component size" \ "vectors.c:10:5: error: cannot cast to non arithmetic or pointer type 'f2v'" \ + "vectors.c:13:44: error: '_BitInt' vector element width must be at least as wide as 'CHAR_BIT'" \ + "vectors.c:14:45: error: '_BitInt' vector element width must be a power of 2" \ -- Gitee From b4a52b88d1e441d3bf414e6e0f339367e91fa3be Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 12:47:06 +0300 Subject: [PATCH 072/117] Parser: allow subscript on vector types --- src/aro/Parser.zig | 6 ++++++ src/aro/Parser/Diagnostic.zig | 2 +- test/cases/ast/vectors.c | 30 ++++++++++++++++++++++++++++++ test/cases/subscript.c | 2 +- test/cases/vectors.c | 5 +++++ 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index d7b4295..76d4621 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -8406,6 +8406,12 @@ fn suffixExpr(p: *Parser, lhs: Result) Error!?Result { try p.err(l_bracket, .invalid_index, .{}); } std.mem.swap(Result, &ptr, &index); + } else if (ptr.qt.get(p.comp, .vector)) |vector_ty| { + ptr = array_before_conversion; + ptr.qt = vector_ty.elem; + if (!index.qt.isInt(p.comp)) { + try p.err(l_bracket, .invalid_index, .{}); + } } else { try p.err(l_bracket, .invalid_subscript, .{}); } diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index db57e0a..d5a2b79 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -690,7 +690,7 @@ pub const invalid_index: Diagnostic = .{ }; pub const invalid_subscript: Diagnostic = .{ - .fmt = "subscripted value is not an array or pointer", + .fmt = "subscripted value is not an array, pointer or vector", .kind = .@"error", }; diff --git a/test/cases/ast/vectors.c b/test/cases/ast/vectors.c index 01da528..0bdc54c 100644 --- a/test/cases/ast/vectors.c +++ b/test/cases/ast/vectors.c @@ -57,3 +57,33 @@ fn_def: 'fn () void' implicit return_stmt: 'void' +fn_def: 'fn (vec: f2v: vector(2, float), index: int) float' + name: subscript + body: + compound_stmt + assign_expr: 'float' + lhs: + array_access_expr: 'float' lvalue + base: + decl_ref_expr: 'f2v: vector(2, float)' lvalue + name: vec + index: + implicit cast: (lval_to_rval) 'int' + decl_ref_expr: 'int' lvalue + name: index + rhs: + implicit cast: (int_to_float) 'float' + int_literal: 'int' (value: 1) + + return_stmt: 'float' + expr: + implicit cast: (lval_to_rval) 'float' + array_access_expr: 'float' lvalue + base: + decl_ref_expr: 'f2v: vector(2, float)' lvalue + name: vec + index: + implicit cast: (lval_to_rval) 'int' + decl_ref_expr: 'int' lvalue + name: index + diff --git a/test/cases/subscript.c b/test/cases/subscript.c index 0f56f09..099e5f8 100644 --- a/test/cases/subscript.c +++ b/test/cases/subscript.c @@ -8,6 +8,6 @@ int foo(int foo[2]) { #define EXPECTED_ERRORS "subscript.c:3:8: warning: array index -4 is before the beginning of the array [-Warray-bounds]" \ "subscript.c:3:16: warning: array index 4 is past the end of the array [-Warray-bounds]" \ - "subscript.c:4:6: error: subscripted value is not an array or pointer" \ + "subscript.c:4:6: error: subscripted value is not an array, pointer or vector" \ "subscript.c:5:8: error: array subscript is not an integer" \ "subscript.c:6:15: warning: array index 2 is past the end of the array [-Warray-bounds]" \ diff --git a/test/cases/vectors.c b/test/cases/vectors.c index d79383a..2428a3c 100644 --- a/test/cases/vectors.c +++ b/test/cases/vectors.c @@ -13,6 +13,11 @@ void foo(void) { typedef _BitInt(4) invalid3 __attribute__((vector_size(4 * 2))); typedef _BitInt(11) invalid4 __attribute__((vector_size(11 * 2))); +float subscript(f2v vec, int index) { + vec[index] = 1; + return vec[index]; +} + #define EXPECTED_ERRORS "vectors.c:2:40: error: invalid vector element type 'float *'" \ "vectors.c:3:39: error: vector size not an integral multiple of component size" \ "vectors.c:10:5: error: cannot cast to non arithmetic or pointer type 'f2v'" \ -- Gitee From 4356f3a0106f5c5bdc2e69e22b2a0a29373e1d01 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 13:05:28 +0300 Subject: [PATCH 073/117] Parser: bitcast equally sized vectors --- src/aro/Parser.zig | 13 ++++++- src/aro/Parser/Diagnostic.zig | 5 +++ test/cases/ast/vectors.c | 65 ++++++++++++++++++++++++++++++++++- test/cases/vectors.c | 13 ++++++- 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 76d4621..ca6e49e 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -5976,10 +5976,20 @@ pub const Result = struct { if (a.qt.eql(b.qt, p.comp)) { return a.shouldEval(b, p); } - return a.invalidBinTy(tok, b, p); + if (a.qt.sizeCompare(b.qt, p.comp) == .eq) { + b.qt = a.qt; + try b.implicitCast(p, .bitcast, tok); + return a.shouldEval(b, p); + } + try p.err(tok, .incompatible_vec_types, .{ a.qt, b.qt }); + a.val = .{}; + b.val = .{}; + a.qt = .invalid; + return false; } else if (a_vec) { if (b.coerceExtra(p, a.qt.childType(p.comp), tok, .test_coerce)) { try b.saveValue(p); + b.qt = a.qt; try b.implicitCast(p, .vector_splat, tok); return a.shouldEval(b, p); } else |er| switch (er) { @@ -5989,6 +5999,7 @@ pub const Result = struct { } else if (b_vec) { if (a.coerceExtra(p, b.qt.childType(p.comp), tok, .test_coerce)) { try a.saveValue(p); + a.qt = b.qt; try a.implicitCast(p, .vector_splat, tok); return a.shouldEval(b, p); } else |er| switch (er) { diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index d5a2b79..766bdc6 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -623,6 +623,11 @@ pub const invalid_bin_types: Diagnostic = .{ .kind = .@"error", }; +pub const incompatible_vec_types: Diagnostic = .{ + .fmt = "cannot convert between vector type '{qt}' and vector type '{qt}' as implicit conversion would cause truncation", + .kind = .@"error", +}; + pub const comparison_ptr_int: Diagnostic = .{ .fmt = "comparison between pointer and integer ('{qt}' and '{qt}')", .kind = .warning, diff --git a/test/cases/ast/vectors.c b/test/cases/ast/vectors.c index 0bdc54c..dd61864 100644 --- a/test/cases/ast/vectors.c +++ b/test/cases/ast/vectors.c @@ -51,7 +51,7 @@ fn_def: 'fn () void' implicit cast: (lval_to_rval) 'f2v: vector(2, float)' implicit compound_assign_dummy_expr: 'f2v: vector(2, float)' lvalue rhs: - implicit cast: (vector_splat) 'float' + implicit cast: (vector_splat) 'f2v: vector(2, float)' implicit cast: (int_to_float) 'float' (value: 2) int_literal: 'int' (value: 2) @@ -87,3 +87,66 @@ fn_def: 'fn (vec: f2v: vector(2, float), index: int) float' decl_ref_expr: 'int' lvalue name: index +typedef: 'vector(2, int)' + name: i2v + +typedef: 'vector(3, int)' + name: i3v + +fn_def: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, int)) void' + name: vector_conversions + body: + compound_stmt + add_expr: 'f2v: vector(2, float)' + lhs: + implicit cast: (lval_to_rval) 'f2v: vector(2, float)' + decl_ref_expr: 'f2v: vector(2, float)' lvalue + name: a + rhs: + implicit cast: (bitcast) 'f2v: vector(2, float)' + implicit cast: (lval_to_rval) 'i2v: vector(2, int)' + decl_ref_expr: 'i2v: vector(2, int)' lvalue + name: b + + add_expr: 'i2v: vector(2, int)' + lhs: + implicit cast: (lval_to_rval) 'i2v: vector(2, int)' + decl_ref_expr: 'i2v: vector(2, int)' lvalue + name: b + rhs: + implicit cast: (bitcast) 'i2v: vector(2, int)' + implicit cast: (lval_to_rval) 'f2v: vector(2, float)' + decl_ref_expr: 'f2v: vector(2, float)' lvalue + name: a + + add_expr: 'f2v: vector(2, float)' + lhs: + implicit cast: (lval_to_rval) 'f2v: vector(2, float)' + decl_ref_expr: 'f2v: vector(2, float)' lvalue + name: a + rhs: + implicit cast: (vector_splat) 'f2v: vector(2, float)' + implicit cast: (int_to_float) 'float' (value: 1) + int_literal: 'int' (value: 1) + + add_expr: 'i2v: vector(2, int)' + lhs: + implicit cast: (lval_to_rval) 'i2v: vector(2, int)' + decl_ref_expr: 'i2v: vector(2, int)' lvalue + name: b + rhs: + implicit cast: (vector_splat) 'i2v: vector(2, int)' + int_literal: 'int' (value: 1) + + add_expr: 'invalid' + lhs: + implicit cast: (lval_to_rval) 'f2v: vector(2, float)' + decl_ref_expr: 'f2v: vector(2, float)' lvalue + name: a + rhs: + implicit cast: (lval_to_rval) 'i3v: vector(3, int)' + decl_ref_expr: 'i3v: vector(3, int)' lvalue + name: c + + implicit return_stmt: 'void' + diff --git a/test/cases/vectors.c b/test/cases/vectors.c index 2428a3c..2a65d73 100644 --- a/test/cases/vectors.c +++ b/test/cases/vectors.c @@ -1,4 +1,4 @@ -//aro-args --target=x86_64-linux-gnu +//aro-args --target=x86_64-linux-gnu -Wno-unused typedef float *invalid1 __attribute__((vector_size(8))); typedef float invalid2 __attribute__((vector_size(9))); typedef float f2v __attribute__((vector_size(8))); @@ -18,8 +18,19 @@ float subscript(f2v vec, int index) { return vec[index]; } +typedef int i2v __attribute__((vector_size(8))); +typedef int i3v __attribute__((vector_size(12))); +void vector_conversions(f2v a, i2v b, i3v c) { + a + b; + b + a; + a + 1; + b + 1; + a + c; +} + #define EXPECTED_ERRORS "vectors.c:2:40: error: invalid vector element type 'float *'" \ "vectors.c:3:39: error: vector size not an integral multiple of component size" \ "vectors.c:10:5: error: cannot cast to non arithmetic or pointer type 'f2v'" \ "vectors.c:13:44: error: '_BitInt' vector element width must be at least as wide as 'CHAR_BIT'" \ "vectors.c:14:45: error: '_BitInt' vector element width must be a power of 2" \ + "vectors.c:28:7: error: cannot convert between vector type 'f2v' and vector type 'i3v' as implicit conversion would cause truncation" \ -- Gitee From 692f9055ba2bb777106e04374e0824daf854fd4a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 13:50:13 +0300 Subject: [PATCH 074/117] Parser: desugar types in diagnostics --- src/aro/Parser.zig | 31 ++++ src/aro/Parser/Diagnostic.zig | 192 ++++++++++++------------- src/aro/TypeStore.zig | 38 ++--- test/cases/casts.c | 2 +- test/cases/enum fixed.c | 4 +- test/cases/functions.c | 4 +- test/cases/initializers.c | 2 +- test/cases/parser using typeof types.c | 4 +- test/cases/redefinitions.c | 2 +- test/cases/typeof.c | 6 +- test/cases/vectors.c | 4 +- 11 files changed, 163 insertions(+), 126 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index ca6e49e..85f6942 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -489,7 +489,38 @@ fn formatQualType(p: *Parser, w: anytype, fmt: []const u8, qt: QualType) !usize const template = "{qt}"; const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); + try w.writeByte('\''); try qt.print(p.comp, w); + try w.writeByte('\''); + if (qt.isInvalid() or qt.isC23Auto() or qt.isAutoType()) return i + template.len; + + var desugar = false; + loop: switch (qt.type(p.comp)) { + .vector => |vector_ty| { + try w.print(" (vector of {d} '", .{vector_ty.len}); + try vector_ty.elem.printDesugared(p.comp, w); + try w.writeAll("' values)"); + return i + template.len; + }, + .attributed => |attributed_ty| continue :loop attributed_ty.base.type(p.comp), + .pointer => |pointer_ty| continue :loop pointer_ty.child.type(p.comp), + // TODO .func + .typeof => |typeof_ty| { + desugar = true; + continue :loop typeof_ty.base.type(p.comp); + }, + .typedef => |typedef_ty| { + desugar = true; + continue :loop typedef_ty.base.type(p.comp); + }, + .nullptr_t => desugar = false, + else => {}, + } + if (desugar) { + try w.writeAll(" (aka '"); + try qt.printDesugared(p.comp, w); + try w.writeAll("')"); + } return i + template.len; } diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 766bdc6..25ead6e 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -117,7 +117,7 @@ pub const cannot_combine_spec: Diagnostic = .{ }; pub const cannot_combine_spec_qt: Diagnostic = .{ - .fmt = "cannot combine with previous '{qt}' specifier", + .fmt = "cannot combine with previous {qt} specifier", .kind = .@"error", }; @@ -133,7 +133,7 @@ pub const duplicate_decl_spec: Diagnostic = .{ }; pub const restrict_non_pointer: Diagnostic = .{ - .fmt = "restrict requires a pointer or reference ('{qt}' is invalid)", + .fmt = "restrict requires a pointer or reference ({qt} is invalid)", .kind = .@"error", }; @@ -237,7 +237,7 @@ pub const undeclared_identifier: Diagnostic = .{ }; pub const not_callable: Diagnostic = .{ - .fmt = "cannot call non function type '{qt}'", + .fmt = "cannot call non function type {qt}", .kind = .@"error", }; @@ -414,7 +414,7 @@ pub const negative_array_size: Diagnostic = .{ }; pub const array_incomplete_elem: Diagnostic = .{ - .fmt = "array has incomplete element type '{qt}'", + .fmt = "array has incomplete element type {qt}", .kind = .@"error", }; @@ -541,12 +541,12 @@ pub const alignof_expr: Diagnostic = .{ }; pub const invalid_alignof: Diagnostic = .{ - .fmt = "invalid application of 'alignof' to an incomplete type '{qt}'", + .fmt = "invalid application of 'alignof' to an incomplete type {qt}", .kind = .@"error", }; pub const invalid_sizeof: Diagnostic = .{ - .fmt = "invalid application of 'sizeof' to an incomplete type '{qt}'", + .fmt = "invalid application of 'sizeof' to an incomplete type {qt}", .kind = .@"error", }; @@ -569,12 +569,12 @@ pub const generic_func_type: Diagnostic = .{ }; pub const generic_duplicate: Diagnostic = .{ - .fmt = "type '{qt}' in generic association compatible with previously specified type", + .fmt = "type {qt} in generic association compatible with previously specified type", .kind = .@"error", }; pub const generic_duplicate_here: Diagnostic = .{ - .fmt = "compatible type '{qt}' specified here", + .fmt = "compatible type {qt} specified here", .kind = .note, }; @@ -584,7 +584,7 @@ pub const generic_duplicate_default: Diagnostic = .{ }; pub const generic_no_match: Diagnostic = .{ - .fmt = "controlling expression type '{qt}' not compatible with any generic association type", + .fmt = "controlling expression type {qt} not compatible with any generic association type", .kind = .@"error", }; @@ -619,72 +619,72 @@ pub const redefinition_of_parameter: Diagnostic = .{ }; pub const invalid_bin_types: Diagnostic = .{ - .fmt = "invalid operands to binary expression ('{qt}' and '{qt}')", + .fmt = "invalid operands to binary expression ({qt} and {qt})", .kind = .@"error", }; pub const incompatible_vec_types: Diagnostic = .{ - .fmt = "cannot convert between vector type '{qt}' and vector type '{qt}' as implicit conversion would cause truncation", + .fmt = "cannot convert between vector type {qt} and vector type {qt} as implicit conversion would cause truncation", .kind = .@"error", }; pub const comparison_ptr_int: Diagnostic = .{ - .fmt = "comparison between pointer and integer ('{qt}' and '{qt}')", + .fmt = "comparison between pointer and integer ({qt} and {qt})", .kind = .warning, .opt = .@"pointer-integer-compare", .extension = true, }; pub const comparison_distinct_ptr: Diagnostic = .{ - .fmt = "comparison of distinct pointer types ('{qt}' and '{qt}')", + .fmt = "comparison of distinct pointer types ({qt} and {qt})", .kind = .warning, .opt = .@"compare-distinct-pointer-types", .extension = true, }; pub const incompatible_pointers: Diagnostic = .{ - .fmt = "incompatible pointer types ('{qt}' and '{qt}')", + .fmt = "incompatible pointer types ({qt} and {qt})", .kind = .@"error", }; pub const invalid_argument_un: Diagnostic = .{ - .fmt = "invalid argument type '{qt}' to unary expression", + .fmt = "invalid argument type {qt} to unary expression", .kind = .@"error", }; pub const incompatible_assign: Diagnostic = .{ - .fmt = "assignment to '{qt}' from incompatible type '{qt}'", + .fmt = "assignment to {qt} from incompatible type {qt}", .kind = .@"error", }; pub const implicit_ptr_to_int: Diagnostic = .{ - .fmt = "implicit pointer to integer conversion from '{qt}' to '{qt}'", + .fmt = "implicit pointer to integer conversion from {qt} to {qt}", .kind = .warning, .opt = .@"int-conversion", }; pub const invalid_cast_to_float: Diagnostic = .{ - .fmt = "pointer cannot be cast to type '{qt}'", + .fmt = "pointer cannot be cast to type {qt}", .kind = .@"error", }; pub const invalid_cast_to_pointer: Diagnostic = .{ - .fmt = "operand of type '{qt}' cannot be cast to a pointer type", + .fmt = "operand of type {qt} cannot be cast to a pointer type", .kind = .@"error", }; pub const invalid_cast_type: Diagnostic = .{ - .fmt = "cannot cast to non arithmetic or pointer type '{qt}'", + .fmt = "cannot cast to non arithmetic or pointer type {qt}", .kind = .@"error", }; pub const invalid_cast_operand_type: Diagnostic = .{ - .fmt = "operand of type '{qt}' where arithmetic or pointer type is required", + .fmt = "operand of type {qt} where arithmetic or pointer type is required", .kind = .@"error", }; pub const qual_cast: Diagnostic = .{ - .fmt = "cast to type '{qt}' will not preserve qualifiers", + .fmt = "cast to type {qt} will not preserve qualifiers", .opt = .@"cast-qualifiers", .kind = .warning, }; @@ -712,12 +712,12 @@ pub const array_before: Diagnostic = .{ }; pub const statement_int: Diagnostic = .{ - .fmt = "statement requires expression with integer type ('{qt}' invalid)", + .fmt = "statement requires expression with integer type ({qt} invalid)", .kind = .@"error", }; pub const statement_scalar: Diagnostic = .{ - .fmt = "statement requires expression with scalar type ('{qt}' invalid)", + .fmt = "statement requires expression with scalar type ({qt} invalid)", .kind = .@"error", }; @@ -728,7 +728,7 @@ pub const func_should_return: Diagnostic = .{ }; pub const incompatible_return: Diagnostic = .{ - .fmt = "returning '{qt}' from a function with incompatible result type '{qt}'", + .fmt = "returning {qt} from a function with incompatible result type {qt}", .kind = .@"error", }; @@ -739,7 +739,7 @@ pub const incompatible_return_sign: Diagnostic = .{ }; pub const implicit_int_to_ptr: Diagnostic = .{ - .fmt = "implicit integer to pointer conversion from '{qt}' to '{qt}'", + .fmt = "implicit integer to pointer conversion from {qt} to {qt}", .opt = .@"int-conversion", .kind = .warning, }; @@ -757,12 +757,12 @@ pub const void_func_returns_value: Diagnostic = .{ }; pub const incompatible_arg: Diagnostic = .{ - .fmt = "passing '{qt}' to parameter of incompatible type '{qt}'", + .fmt = "passing {qt} to parameter of incompatible type {qt}", .kind = .@"error", }; pub const incompatible_ptr_arg: Diagnostic = .{ - .fmt = "passing '{qt}' to parameter of incompatible type '{qt}'", + .fmt = "passing {qt} to parameter of incompatible type {qt}", .kind = .warning, .opt = .@"incompatible-pointer-types", }; @@ -779,32 +779,32 @@ pub const parameter_here: Diagnostic = .{ }; pub const atomic_array: Diagnostic = .{ - .fmt = "_Atomic cannot be applied to array type '{qt}'", + .fmt = "_Atomic cannot be applied to array type {qt}", .kind = .@"error", }; pub const atomic_func: Diagnostic = .{ - .fmt = "_Atomic cannot be applied to function type '{qt}'", + .fmt = "_Atomic cannot be applied to function type {qt}", .kind = .@"error", }; pub const atomic_incomplete: Diagnostic = .{ - .fmt = "_Atomic cannot be applied to incomplete type '{qt}'", + .fmt = "_Atomic cannot be applied to incomplete type {qt}", .kind = .@"error", }; pub const atomic_atomic: Diagnostic = .{ - .fmt = "_Atomic cannot be applied to atomic type '{qt}'", + .fmt = "_Atomic cannot be applied to atomic type {qt}", .kind = .@"error", }; pub const atomic_complex: Diagnostic = .{ - .fmt = "_Atomic cannot be applied to complex type '{qt}'", + .fmt = "_Atomic cannot be applied to complex type {qt}", .kind = .@"error", }; pub const atomic_qualified: Diagnostic = .{ - .fmt = "_Atomic cannot be applied to qualified type '{qt}'", + .fmt = "_Atomic cannot be applied to qualified type {qt}", .kind = .@"error", }; @@ -825,12 +825,12 @@ pub const addr_of_register: Diagnostic = .{ }; pub const variable_incomplete_ty: Diagnostic = .{ - .fmt = "variable has incomplete type '{qt}'", + .fmt = "variable has incomplete type {qt}", .kind = .@"error", }; pub const parameter_incomplete_ty: Diagnostic = .{ - .fmt = "parameter has incomplete type '{qt}'", + .fmt = "parameter has incomplete type {qt}", .kind = .@"error", }; @@ -840,7 +840,7 @@ pub const tentative_array: Diagnostic = .{ }; pub const deref_incomplete_ty_ptr: Diagnostic = .{ - .fmt = "dereferencing pointer to incomplete type '{qt}'", + .fmt = "dereferencing pointer to incomplete type {qt}", .kind = .@"error", }; @@ -885,7 +885,7 @@ pub const non_pow2_align: Diagnostic = .{ }; pub const pointer_mismatch: Diagnostic = .{ - .fmt = "pointer type mismatch ('{qt}' and '{qt}')", + .fmt = "pointer type mismatch ({qt} and {qt})", .opt = .@"pointer-type-mismatch", .kind = .warning, .extension = true, @@ -922,12 +922,12 @@ pub const array_too_large: Diagnostic = .{ }; pub const record_too_large: Diagnostic = .{ - .fmt = "type '{qt}' is too large", + .fmt = "type {qt} is too large", .kind = .@"error", }; pub const incompatible_ptr_init: Diagnostic = .{ - .fmt = "incompatible pointer types initializing '{qt}' from incompatible type '{qt}'", + .fmt = "incompatible pointer types initializing {qt} from incompatible type {qt}", .opt = .@"incompatible-pointer-types", .kind = .warning, }; @@ -939,7 +939,7 @@ pub const incompatible_ptr_init_sign: Diagnostic = .{ }; pub const incompatible_ptr_assign: Diagnostic = .{ - .fmt = "incompatible pointer types assigning to '{qt}' from incompatible type '{qt}'", + .fmt = "incompatible pointer types assigning to {qt} from incompatible type {qt}", .opt = .@"incompatible-pointer-types", .kind = .warning, }; @@ -961,7 +961,7 @@ pub const func_init: Diagnostic = .{ }; pub const incompatible_init: Diagnostic = .{ - .fmt = "initializing '{qt}' from incompatible type '{qt}'", + .fmt = "initializing {qt} from incompatible type {qt}", .kind = .@"error", }; @@ -1009,7 +1009,7 @@ pub const str_init_too_long: Diagnostic = .{ }; pub const arr_init_too_long: Diagnostic = .{ - .fmt = "cannot initialize type '{qt}' with array of type '{qt}'", + .fmt = "cannot initialize type {qt} with array of type {qt}", .kind = .@"error", }; @@ -1053,7 +1053,7 @@ pub const enum_val_unavailable: Diagnostic = .{ }; pub const incompatible_array_init: Diagnostic = .{ - .fmt = "cannot initialize array of type '{qt}' with array of type '{qt}'", + .fmt = "cannot initialize array of type {qt} with array of type {qt}", .kind = .@"error", }; @@ -1074,7 +1074,7 @@ pub const previous_initializer: Diagnostic = .{ }; pub const invalid_array_designator: Diagnostic = .{ - .fmt = "array designator used for non-array type '{qt}'", + .fmt = "array designator used for non-array type {qt}", .kind = .@"error", }; @@ -1089,7 +1089,7 @@ pub const oob_array_designator: Diagnostic = .{ }; pub const invalid_field_designator: Diagnostic = .{ - .fmt = "field designator used for non-record type '{qt}'", + .fmt = "field designator used for non-record type {qt}", .kind = .@"error", }; @@ -1104,25 +1104,25 @@ pub const empty_aggregate_init_braces: Diagnostic = .{ }; pub const ptr_init_discards_quals: Diagnostic = .{ - .fmt = "initializing '{qt}' from incompatible type '{qt}' discards qualifiers", + .fmt = "initializing {qt} from incompatible type {qt} discards qualifiers", .kind = .warning, .opt = .@"incompatible-pointer-types-discards-qualifiers", }; pub const ptr_assign_discards_quals: Diagnostic = .{ - .fmt = "assigning to '{qt}' from incompatible type '{qt}' discards qualifiers", + .fmt = "assigning to {qt} from incompatible type {qt} discards qualifiers", .kind = .warning, .opt = .@"incompatible-pointer-types-discards-qualifiers", }; pub const ptr_ret_discards_quals: Diagnostic = .{ - .fmt = "returning '{qt}' from a function with incompatible result type '{qt}' discards qualifiers", + .fmt = "returning {qt} from a function with incompatible result type {qt} discards qualifiers", .kind = .warning, .opt = .@"incompatible-pointer-types-discards-qualifiers", }; pub const ptr_arg_discards_quals: Diagnostic = .{ - .fmt = "passing '{qt}' to parameter of incompatible type '{qt}' discards qualifiers", + .fmt = "passing {qt} to parameter of incompatible type {qt} discards qualifiers", .kind = .warning, .opt = .@"incompatible-pointer-types-discards-qualifiers", }; @@ -1157,27 +1157,27 @@ pub const gnu_label_as_value: Diagnostic = .{ }; pub const expected_record_ty: Diagnostic = .{ - .fmt = "member reference base type '{qt}' is not a structure or union", + .fmt = "member reference base type {qt} is not a structure or union", .kind = .@"error", }; pub const member_expr_not_ptr: Diagnostic = .{ - .fmt = "member reference type '{qt}' is not a pointer; did you mean to use '.'?", + .fmt = "member reference type {qt} is not a pointer; did you mean to use '.'?", .kind = .@"error", }; pub const member_expr_ptr: Diagnostic = .{ - .fmt = "member reference type '{qt}' is a pointer; did you mean to use '->'?", + .fmt = "member reference type {qt} is a pointer; did you mean to use '->'?", .kind = .@"error", }; pub const member_expr_atomic: Diagnostic = .{ - .fmt = "accessing a member of atomic type '{qt}' is undefined behavior", + .fmt = "accessing a member of atomic type {qt} is undefined behavior", .kind = .@"error", }; pub const no_such_member: Diagnostic = .{ - .fmt = "no member named '{s}' in '{qt}'", + .fmt = "no member named '{s}' in {qt}", .kind = .@"error", }; @@ -1202,7 +1202,7 @@ pub const omitting_parameter_name: Diagnostic = .{ }; pub const non_int_bitfield: Diagnostic = .{ - .fmt = "bit-field has non-integer type '{qt}'", + .fmt = "bit-field has non-integer type {qt}", .kind = .@"error", }; @@ -1300,7 +1300,7 @@ pub const predefined_top_level: Diagnostic = .{ }; pub const incompatible_va_arg: Diagnostic = .{ - .fmt = "first argument to va_arg, is of type '{qt}' and not 'va_list'", + .fmt = "first argument to va_arg, is of type {qt} and not 'va_list'", .kind = .@"error", }; @@ -1377,7 +1377,7 @@ pub const vla_field: Diagnostic = .{ }; pub const field_incomplete_ty: Diagnostic = .{ - .fmt = "field has incomplete type '{qt}'", + .fmt = "field has incomplete type {qt}", .kind = .@"error", }; @@ -1532,12 +1532,12 @@ pub const ignored_record_attr: Diagnostic = .{ }; pub const array_size_non_int: Diagnostic = .{ - .fmt = "size of array has non-integer type '{qt}'", + .fmt = "size of array has non-integer type {qt}", .kind = .@"error", }; pub const cast_to_smaller_int: Diagnostic = .{ - .fmt = "cast to smaller integer type '{qt}' from '{qt}'", + .fmt = "cast to smaller integer type {qt} from {qt}", .kind = .warning, .opt = .@"pointer-to-int-cast", }; @@ -1561,43 +1561,43 @@ pub const vla: Diagnostic = .{ }; pub const int_value_changed: Diagnostic = .{ - .fmt = "implicit conversion from '{qt}' to '{qt}' changes {s}value from {value} to {value}", + .fmt = "implicit conversion from {qt} to {qt} changes {s}value from {value} to {value}", .kind = .warning, .opt = .@"constant-conversion", }; pub const sign_conversion: Diagnostic = .{ - .fmt = "implicit conversion changes signedness: '{qt}' to '{qt}'", + .fmt = "implicit conversion changes signedness: {qt} to {qt}", .kind = .off, .opt = .@"sign-conversion", }; pub const float_overflow_conversion: Diagnostic = .{ - .fmt = "implicit conversion of non-finite value from '{qt}' to '{qt}' is undefined", + .fmt = "implicit conversion of non-finite value from {qt} to {qt} is undefined", .kind = .off, .opt = .@"float-overflow-conversion", }; pub const float_out_of_range: Diagnostic = .{ - .fmt = "implicit conversion of out of range value from '{qt}' to '{qt}' is undefined", + .fmt = "implicit conversion of out of range value from {qt} to {qt} is undefined", .kind = .warning, .opt = .@"literal-conversion", }; pub const float_zero_conversion: Diagnostic = .{ - .fmt = "implicit conversion from '{qt}' to '{qt}' changes {s}value from {value} to {value}", + .fmt = "implicit conversion from {qt} to {qt} changes {s}value from {value} to {value}", .kind = .off, .opt = .@"float-zero-conversion", }; pub const float_value_changed: Diagnostic = .{ - .fmt = "implicit conversion from '{qt}' to '{qt}' changes {s}value from {value} to {value}", + .fmt = "implicit conversion from {qt} to {qt} changes {s}value from {value} to {value}", .kind = .warning, .opt = .@"float-conversion", }; pub const float_to_int: Diagnostic = .{ - .fmt = "implicit conversion turns floating-point number into integer: '{qt}' to '{qt}'", + .fmt = "implicit conversion turns floating-point number into integer: {qt} to {qt}", .kind = .off, .opt = .@"literal-conversion", }; @@ -1617,27 +1617,27 @@ pub const const_decl_folded_vla: Diagnostic = .{ }; pub const redefinition_of_typedef: Diagnostic = .{ - .fmt = "typedef redefinition with different types ('{qt}' vs '{qt}')", + .fmt = "typedef redefinition with different types ({qt} vs {qt})", .kind = .@"error", }; pub const offsetof_ty: Diagnostic = .{ - .fmt = "offsetof requires struct or union type, '{qt}' invalid", + .fmt = "offsetof requires struct or union type, {qt} invalid", .kind = .@"error", }; pub const offsetof_incomplete: Diagnostic = .{ - .fmt = "offsetof of incomplete type '{qt}'", + .fmt = "offsetof of incomplete type {qt}", .kind = .@"error", }; pub const offsetof_array: Diagnostic = .{ - .fmt = "offsetof requires array type, '{qt}' invalid", + .fmt = "offsetof requires array type, {qt} invalid", .kind = .@"error", }; pub const cond_expr_type: Diagnostic = .{ - .fmt = "used type '{qt}' where arithmetic or pointer type is required", + .fmt = "used type {qt} where arithmetic or pointer type is required", .kind = .@"error", }; @@ -1690,12 +1690,12 @@ pub const enum_prev_fixed: Diagnostic = .{ }; pub const enum_different_explicit_ty: Diagnostic = .{ - .fmt = "enumeration redeclared with different underlying type '{qt}' (was '{qt}')", + .fmt = "enumeration redeclared with different underlying type {qt} (was {qt})", .kind = .@"error", }; pub const enum_not_representable_fixed: Diagnostic = .{ - .fmt = "enumerator value is not representable in the underlying type '{qt}'", + .fmt = "enumerator value is not representable in the underlying type {qt}", .kind = .@"error", }; @@ -1746,7 +1746,7 @@ pub const ignore_nocommon: Diagnostic = .{ }; pub const non_string_ignored: Diagnostic = .{ - .fmt = "'nonstring' attribute ignored on objects of type '{qt}'", + .fmt = "'nonstring' attribute ignored on objects of type {qt}", .opt = .@"ignored-attributes", .kind = .warning, }; @@ -1812,7 +1812,7 @@ pub const unused_value: Diagnostic = .{ }; pub const invalid_vec_elem_ty: Diagnostic = .{ - .fmt = "invalid vector element type '{qt}'", + .fmt = "invalid vector element type {qt}", .kind = .@"error", }; @@ -1832,12 +1832,12 @@ pub const vec_size_not_multiple: Diagnostic = .{ }; pub const invalid_imag: Diagnostic = .{ - .fmt = "invalid type '{qt}' to __imag operator", + .fmt = "invalid type {qt} to __imag operator", .kind = .@"error", }; pub const invalid_real: Diagnostic = .{ - .fmt = "invalid type '{qt}' to __real operator", + .fmt = "invalid type {qt} to __real operator", .kind = .@"error", }; @@ -1940,7 +1940,7 @@ pub const signed_bit_int_too_big: Diagnostic = .{ }; pub const ptr_arithmetic_incomplete: Diagnostic = .{ - .fmt = "arithmetic on a pointer to an incomplete type '{qt}'", + .fmt = "arithmetic on a pointer to an incomplete type {qt}", .kind = .@"error", }; @@ -1951,7 +1951,7 @@ pub const callconv_not_supported: Diagnostic = .{ }; pub const callconv_non_func: Diagnostic = .{ - .fmt = "'{s}' only applies to function types; type here is '{qt}'", + .fmt = "'{s}' only applies to function types; type here is {qt}", .kind = .warning, .opt = .@"ignored-attributes", }; @@ -1964,7 +1964,7 @@ pub const pointer_arith_void: Diagnostic = .{ }; pub const sizeof_array_arg: Diagnostic = .{ - .fmt = "sizeof on array function parameter will return size of '{qt}' instead of '{qt}'", + .fmt = "sizeof on array function parameter will return size of {qt} instead of {qt}", .kind = .warning, .opt = .@"sizeof-array-argument", }; @@ -1976,7 +1976,7 @@ pub const array_address_to_bool: Diagnostic = .{ }; pub const string_literal_to_bool: Diagnostic = .{ - .fmt = "implicit conversion turns string literal into bool: '{qt}' to '{qt}'", + .fmt = "implicit conversion turns string literal into bool: {qt} to {qt}", .kind = .off, .opt = .@"string-conversion", }; @@ -1987,7 +1987,7 @@ pub const string_literal_to_bool: Diagnostic = .{ // }; pub const invalid_object_cast: Diagnostic = .{ - .fmt = "cannot cast an object of type '{qt}' to '{qt}'", + .fmt = "cannot cast an object of type {qt} to {qt}", .kind = .@"error", }; @@ -2060,12 +2060,12 @@ pub const missing_semicolon: Diagnostic = .{ }; pub const tentative_definition_incomplete: Diagnostic = .{ - .fmt = "tentative definition has type '{qt}' that is never completed", + .fmt = "tentative definition has type {qt} that is never completed", .kind = .@"error", }; pub const forward_declaration_here: Diagnostic = .{ - .fmt = "forward declaration of '{qt}'", + .fmt = "forward declaration of {qt}", .kind = .note, }; @@ -2077,12 +2077,12 @@ pub const gnu_union_cast: Diagnostic = .{ }; pub const invalid_union_cast: Diagnostic = .{ - .fmt = "cast to union type from type '{qt}' not present in union", + .fmt = "cast to union type from type {qt} not present in union", .kind = .@"error", }; pub const cast_to_incomplete_type: Diagnostic = .{ - .fmt = "cast to incomplete type '{qt}'", + .fmt = "cast to incomplete type {qt}", .kind = .@"error", }; @@ -2106,18 +2106,18 @@ pub const complex_component_init: Diagnostic = .{ }; pub const complex_prefix_postfix_op: Diagnostic = .{ - .fmt = "ISO C does not support '++'/'--' on complex type '{qt}'", + .fmt = "ISO C does not support '++'/'--' on complex type {qt}", .kind = .off, .extension = true, }; pub const not_floating_type: Diagnostic = .{ - .fmt = "argument type '{qt}' is not a real floating point type", + .fmt = "argument type {qt} is not a real floating point type", .kind = .@"error", }; pub const argument_types_differ: Diagnostic = .{ - .fmt = "arguments are of different types ('{qt}' vs '{qt}')", + .fmt = "arguments are of different types ({qt} vs {qt})", .kind = .@"error", }; @@ -2218,18 +2218,18 @@ pub const too_big_shift_count: Diagnostic = .{ }; pub const complex_conj: Diagnostic = .{ - .fmt = "ISO C does not support '~' for complex conjugation of '{qt}'", + .fmt = "ISO C does not support '~' for complex conjugation of {qt}", .kind = .off, .extension = true, }; pub const overflow_builtin_requires_int: Diagnostic = .{ - .fmt = "operand argument to overflow builtin must be an integer ('{qt}' invalid)", + .fmt = "operand argument to overflow builtin must be an integer ({qt} invalid)", .kind = .@"error", }; pub const overflow_result_requires_ptr: Diagnostic = .{ - .fmt = "result argument to overflow builtin must be a pointer to a non-const integer ('{qt}' invalid)", + .fmt = "result argument to overflow builtin must be a pointer to a non-const integer ({qt} invalid)", .kind = .@"error", }; @@ -2239,7 +2239,7 @@ pub const attribute_todo: Diagnostic = .{ }; pub const invalid_type_underlying_enum: Diagnostic = .{ - .fmt = "non-integral type '{qt}' is an invalid underlying type", + .fmt = "non-integral type {qt} is an invalid underlying type", .kind = .@"error", }; @@ -2259,7 +2259,7 @@ pub const constexpr_requires_const: Diagnostic = .{ }; pub const subtract_pointers_zero_elem_size: Diagnostic = .{ - .fmt = "subtraction of pointers to type '{qt}' of zero size has undefined behavior", + .fmt = "subtraction of pointers to type {qt} of zero size has undefined behavior", .kind = .warning, .opt = .@"pointer-arith", }; @@ -2324,6 +2324,6 @@ pub const conflicting_nullability: Diagnostic = .{ }; pub const invalid_nullability: Diagnostic = .{ - .fmt = "nullability specifier cannot be applied to non-pointer type '{qt}'", + .fmt = "nullability specifier cannot be applied to non-pointer type {qt}", .kind = .@"error", }; diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 1ad044f..3d198b3 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -1148,7 +1148,7 @@ pub const QualType = packed struct(u32) { try w.writeAll("auto"); return; } - _ = try qt.printPrologue(comp, w); + _ = try qt.printPrologue(comp, false, w); try qt.printEpilogue(comp, w); } @@ -1157,16 +1157,21 @@ pub const QualType = packed struct(u32) { try w.print("auto {s}", .{name}); return; } - const simple = try qt.printPrologue(comp, w); + const simple = try qt.printPrologue(comp, false, w); if (simple) try w.writeByte(' '); try w.writeAll(name); try qt.printEpilogue(comp, w); } - fn printPrologue(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!bool { + pub fn printDesugared(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { + _ = try qt.printPrologue(comp, true, w); + try qt.printEpilogue(comp, w); + } + + fn printPrologue(qt: QualType, comp: *const Compilation, desugar: bool, w: anytype) @TypeOf(w).Error!bool { loop: switch (qt.type(comp)) { .pointer => |pointer| { - const simple = try pointer.child.printPrologue(comp, w); + const simple = try pointer.child.printPrologue(comp, desugar, w); if (simple) try w.writeByte(' '); switch (pointer.child.base(comp).type) { .func, .array => try w.writeByte('('), @@ -1185,24 +1190,28 @@ pub const QualType = packed struct(u32) { return false; }, .func => |func| { - const simple = try func.return_type.printPrologue(comp, w); + const simple = try func.return_type.printPrologue(comp, desugar, w); if (simple) try w.writeByte(' '); return false; }, .array => |array| { - const simple = try array.elem.printPrologue(comp, w); + const simple = try array.elem.printPrologue(comp, desugar, w); if (simple) try w.writeByte(' '); return false; }, - .typeof => |typeof| { + .typeof => |typeof| if (!desugar) { try w.writeAll("typeof("); try typeof.base.print(comp, w); try w.writeAll(")"); return true; + } else { + continue :loop typeof.base.type(comp); }, - .typedef => |typedef| { + .typedef => |typedef| if (!desugar) { try w.writeAll(typedef.name.lookup(comp)); return true; + } else { + continue :loop typedef.base.type(comp); }, .attributed => |attributed| continue :loop attributed.base.type(comp), else => {}, @@ -1247,30 +1256,27 @@ pub const QualType = packed struct(u32) { }, .complex => |complex| { try w.writeAll("_Complex "); - _ = try complex.printPrologue(comp, w); + _ = try complex.printPrologue(comp, desugar, w); }, .atomic => |atomic| { try w.writeAll("_Atomic("); - _ = try atomic.printPrologue(comp, w); + _ = try atomic.printPrologue(comp, desugar, w); try atomic.printEpilogue(comp, w); try w.writeAll(")"); }, .vector => |vector| { try w.print("__attribute__((__vector_size__({d} * sizeof(", .{vector.len}); - _ = try vector.elem.printPrologue(comp, w); + _ = try vector.elem.printPrologue(comp, desugar, w); try w.writeAll(")))) "); - _ = try vector.elem.printPrologue(comp, w); - try w.print(" (vector of {d} '", .{vector.len}); - _ = try vector.elem.printPrologue(comp, w); - try w.writeAll("' values)"); + _ = try vector.elem.printPrologue(comp, desugar, w); }, .@"struct" => |struct_ty| try w.print("struct {s}", .{struct_ty.name.lookup(comp)}), .@"union" => |union_ty| try w.print("union {s}", .{union_ty.name.lookup(comp)}), .@"enum" => |enum_ty| if (enum_ty.fixed) { try w.print("enum {s}: ", .{enum_ty.name.lookup(comp)}); - _ = try enum_ty.tag.?.printPrologue(comp, w); + _ = try enum_ty.tag.?.printPrologue(comp, desugar, w); } else { try w.print("enum {s}", .{enum_ty.name.lookup(comp)}); }, diff --git a/test/cases/casts.c b/test/cases/casts.c index 16f953a..d2f5a60 100644 --- a/test/cases/casts.c +++ b/test/cases/casts.c @@ -23,7 +23,7 @@ void foo(void) { "casts.c:6:5: error: pointer cannot be cast to type 'float'" \ "casts.c:7:5: error: operand of type 'float' cannot be cast to a pointer type" \ "casts.c:8:5: warning: cast to type 'const int' will not preserve qualifiers [-Wcast-qualifiers]" \ - "casts.c:9:23: error: cannot cast to non arithmetic or pointer type 'typeof(const int [])'" \ + "casts.c:9:23: error: cannot cast to non arithmetic or pointer type 'typeof(const int [])' (aka 'const int ')" \ "casts.c:10:13: warning: cast to smaller integer type 'char' from 'char *' [-Wpointer-to-int-cast]" \ "casts.c:11:13: error: pointer cannot be cast to type 'float'" \ "casts.c:16:5: error: cast to incomplete type 'enum E'" \ diff --git a/test/cases/enum fixed.c b/test/cases/enum fixed.c index 2000855..638664a 100644 --- a/test/cases/enum fixed.c +++ b/test/cases/enum fixed.c @@ -112,8 +112,8 @@ void more_pointers(void) { "enum fixed.c:67:25: warning: incompatible pointer types initializing 'enum Unsigned: unsigned int *' from incompatible type 'int *' [-Wincompatible-pointer-types]" \ "enum fixed.c:70:27: warning: incompatible pointer types initializing 'enum Incomplete *' from incompatible type 'int *' [-Wincompatible-pointer-types]" \ "enum fixed.c:71:27: warning: incompatible pointer types initializing 'enum Incomplete *' from incompatible type 'unsigned int *' [-Wincompatible-pointer-types]" \ - "enum fixed.c:82:12: error: non-integral type 'BackingStruct' is an invalid underlying type" \ - "enum fixed.c:86:12: error: non-integral type 'BackingEnum' is an invalid underlying type" \ + "enum fixed.c:82:12: error: non-integral type 'BackingStruct' (aka 'struct BackingStruct') is an invalid underlying type" \ + "enum fixed.c:86:12: error: non-integral type 'BackingEnum' (aka 'enum BackingEnum: int') is an invalid underlying type" \ "enum fixed.c:90:23: error: expected identifier or '('" \ "enum fixed.c:97:31: warning: incompatible pointer types initializing 'enum SignedEnum: int *' from incompatible type 'unsigned int *' converts between pointers to integer types with different sign [-Wpointer-sign]" \ "enum fixed.c:99:38: warning: incompatible pointer types initializing 'enum CharEnum: signed char *' from incompatible type 'unsigned int *' [-Wincompatible-pointer-types]" \ diff --git a/test/cases/functions.c b/test/cases/functions.c index 14790ca..cf5be35 100644 --- a/test/cases/functions.c +++ b/test/cases/functions.c @@ -87,8 +87,8 @@ int invalid_int = invalid_func; "functions.c:22:13: error: variable length array must be bound in function definition" \ "functions.c:42:35: error: parameter has incomplete type 'struct S'" \ "functions.c:44:10: error: parameter has incomplete type 'struct S'" \ - "functions.c:48:30: error: parameter has incomplete type 'U'" \ - "functions.c:50:3: error: parameter has incomplete type 'U'" \ + "functions.c:48:30: error: parameter has incomplete type 'U' (aka 'union Union')" \ + "functions.c:50:3: error: parameter has incomplete type 'U' (aka 'union Union')" \ "functions.c:53:30: error: parameter has incomplete type 'enum E'" \ "functions.c:55:9: error: parameter has incomplete type 'enum EE'" \ "functions.c:79:6: error: redefinition of 'no_params' with a different type" \ diff --git a/test/cases/initializers.c b/test/cases/initializers.c index 54986b6..bd29d48 100644 --- a/test/cases/initializers.c +++ b/test/cases/initializers.c @@ -259,7 +259,7 @@ union { int x; char c[4]; } "initializers.c:169:26: note: previous initialization" \ "initializers.c:175:19: error: initializing 'int' from incompatible type 'struct A'" \ "initializers.c:179:28: warning: excess elements in vector initializer [-Wexcess-initializers]" \ - "initializers.c:182:19: error: initializing 'int' from incompatible type 'vec'" \ + "initializers.c:182:19: error: initializing 'int' from incompatible type 'vec' (vector of 4 'int' values)" \ "initializers.c:186:28: warning: use of an empty initializer is a C23 extension [-Wc23-extensions]" \ "initializers.c:191:33: warning: initializer overrides previous initialization [-Winitializer-overrides]" \ "initializers.c:191:19: note: previous initialization" \ diff --git a/test/cases/parser using typeof types.c b/test/cases/parser using typeof types.c index 647ee85..58a117f 100644 --- a/test/cases/parser using typeof types.c +++ b/test/cases/parser using typeof types.c @@ -83,5 +83,5 @@ void bool_init(void) { "parser using typeof types.c:55:15: error: parameter cannot have void type" \ "parser using typeof types.c:60:31: warning: excess elements in array initializer [-Wexcess-initializers]" \ "parser using typeof types.c:61:29: warning: excess elements in array initializer [-Wexcess-initializers]" \ - "parser using typeof types.c:67:23: error: initializing 'typeof(_Bool)' from incompatible type 'struct S'" \ - "parser using typeof types.c:68:20: error: initializing 'typeof(typeof(_Bool))' from incompatible type 'struct S'" \ + "parser using typeof types.c:67:23: error: initializing 'typeof(_Bool)' (aka '_Bool') from incompatible type 'struct S'" \ + "parser using typeof types.c:68:20: error: initializing 'typeof(typeof(_Bool))' (aka '_Bool') from incompatible type 'struct S'" \ diff --git a/test/cases/redefinitions.c b/test/cases/redefinitions.c index 3b2dab5..ff2325e 100644 --- a/test/cases/redefinitions.c +++ b/test/cases/redefinitions.c @@ -127,7 +127,7 @@ enum E { "redefinitions.c:53:9: note: previous definition is here" \ "redefinitions.c:57:17: error: duplicate member 'a'" \ "redefinitions.c:54:11: note: previous definition is here" \ - "redefinitions.c:64:17: error: typedef redefinition with different types ('MyFloat' vs 'int')" \ + "redefinitions.c:64:17: error: typedef redefinition with different types ('MyFloat' (aka 'float') vs 'int')" \ "redefinitions.c:62:13: note: previous definition is here" \ "redefinitions.c:67:19: error: typedef redefinition with different types ('const int' vs 'int')" \ "redefinitions.c:66:13: note: previous definition is here" \ diff --git a/test/cases/typeof.c b/test/cases/typeof.c index 756679a..13c4726 100644 --- a/test/cases/typeof.c +++ b/test/cases/typeof.c @@ -131,10 +131,10 @@ void initializers(void) { #define TESTS_SKIPPED 1 #define EXPECTED_ERRORS \ - "typeof.c:24:9: warning: incompatible pointer types assigning to 'typeof(typeof(int)) *' from incompatible type 'typeof(float) *' [-Wincompatible-pointer-types]" \ + "typeof.c:24:9: warning: incompatible pointer types assigning to 'typeof(typeof(int)) *' (aka 'int *') from incompatible type 'typeof(float) *' (aka 'float *') [-Wincompatible-pointer-types]" \ "typeof.c:28:7: error: expression is not assignable" \ "typeof.c:30:7: error: expression is not assignable" \ - "typeof.c:34:30: error: initializing 'typeof(int *)' from incompatible type 'float'" \ + "typeof.c:34:30: error: initializing 'typeof(int *)' (aka 'int *') from incompatible type 'float'" \ "typeof.c:35:8: error: expected expression" \ "typeof.c:59:13: error: expression is not assignable" \ "typeof.c:61:13: error: expression is not assignable" \ @@ -144,7 +144,7 @@ void initializers(void) { "typeof.c:71:13: error: expression is not assignable" \ "typeof.c:74:13: error: expression is not assignable" \ "typeof.c:77:13: error: expression is not assignable" \ - "typeof.c:98:29: warning: initializing 'typeof(int *)' from incompatible type 'const int [2]' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]" \ + "typeof.c:98:29: warning: initializing 'typeof(int *)' (aka 'int *const') from incompatible type 'const int [2]' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]" \ "typeof.c:113:5: error: invalid argument type 'char *' to unary expression" \ "typeof.c:119:5: warning: declaration does not declare anything [-Wmissing-declaration]" \ "typeof.c:128:26: error: array initializer must be an initializer list or wide string literal" \ diff --git a/test/cases/vectors.c b/test/cases/vectors.c index 2a65d73..d512496 100644 --- a/test/cases/vectors.c +++ b/test/cases/vectors.c @@ -30,7 +30,7 @@ void vector_conversions(f2v a, i2v b, i3v c) { #define EXPECTED_ERRORS "vectors.c:2:40: error: invalid vector element type 'float *'" \ "vectors.c:3:39: error: vector size not an integral multiple of component size" \ - "vectors.c:10:5: error: cannot cast to non arithmetic or pointer type 'f2v'" \ + "vectors.c:10:5: error: cannot cast to non arithmetic or pointer type 'f2v' (vector of 2 'float' values)" \ "vectors.c:13:44: error: '_BitInt' vector element width must be at least as wide as 'CHAR_BIT'" \ "vectors.c:14:45: error: '_BitInt' vector element width must be a power of 2" \ - "vectors.c:28:7: error: cannot convert between vector type 'f2v' and vector type 'i3v' as implicit conversion would cause truncation" \ + "vectors.c:28:7: error: cannot convert between vector type 'f2v' (vector of 2 'float' values) and vector type 'i3v' (vector of 3 'int' values) as implicit conversion would cause truncation" \ -- Gitee From 4070ebe53e89857438894bb4fecd8dca38ee2662 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 17:31:34 +0300 Subject: [PATCH 075/117] Parser: also desugar function types --- src/aro/Parser.zig | 30 ++++++------------------------ src/aro/TypeStore.zig | 41 +++++++++++++++++++++++++++++------------ test/cases/functions.c | 8 +++++++- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 85f6942..8cfdbde 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -492,31 +492,13 @@ fn formatQualType(p: *Parser, w: anytype, fmt: []const u8, qt: QualType) !usize try w.writeByte('\''); try qt.print(p.comp, w); try w.writeByte('\''); - if (qt.isInvalid() or qt.isC23Auto() or qt.isAutoType()) return i + template.len; - var desugar = false; - loop: switch (qt.type(p.comp)) { - .vector => |vector_ty| { - try w.print(" (vector of {d} '", .{vector_ty.len}); - try vector_ty.elem.printDesugared(p.comp, w); - try w.writeAll("' values)"); - return i + template.len; - }, - .attributed => |attributed_ty| continue :loop attributed_ty.base.type(p.comp), - .pointer => |pointer_ty| continue :loop pointer_ty.child.type(p.comp), - // TODO .func - .typeof => |typeof_ty| { - desugar = true; - continue :loop typeof_ty.base.type(p.comp); - }, - .typedef => |typedef_ty| { - desugar = true; - continue :loop typedef_ty.base.type(p.comp); - }, - .nullptr_t => desugar = false, - else => {}, - } - if (desugar) { + if (qt.isC23Auto()) return i + template.len; + if (qt.get(p.comp, .vector)) |vector_ty| { + try w.print(" (vector of {d} '", .{vector_ty.len}); + try vector_ty.elem.printDesugared(p.comp, w); + try w.writeAll("' values)"); + } else if (qt.shouldDesugar(p.comp)) { try w.writeAll(" (aka '"); try qt.printDesugared(p.comp, w); try w.writeAll("')"); diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 3d198b3..6806172 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -1143,13 +1143,29 @@ pub const QualType = packed struct(u32) { return comp.langopts.short_enums or target_util.packAllEnums(comp.target) or qt.hasAttribute(comp, .@"packed"); } + pub fn shouldDesugar(qt: QualType, comp: *const Compilation) bool { + loop: switch (qt.type(comp)) { + .attributed => |attributed| continue :loop attributed.base.type(comp), + .pointer => |pointer| continue :loop pointer.child.type(comp), + .func => |func| { + for (func.params) |param| { + if (param.qt.shouldDesugar(comp)) return true; + } + continue :loop func.return_type.type(comp); + }, + .typeof => return true, + .typedef => |typedef| return !typedef.base.is(comp, .nullptr_t), + else => return false, + } + } + pub fn print(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { if (qt.isC23Auto()) { try w.writeAll("auto"); return; } _ = try qt.printPrologue(comp, false, w); - try qt.printEpilogue(comp, w); + try qt.printEpilogue(comp, false, w); } pub fn printNamed(qt: QualType, name: []const u8, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { @@ -1160,12 +1176,12 @@ pub const QualType = packed struct(u32) { const simple = try qt.printPrologue(comp, false, w); if (simple) try w.writeByte(' '); try w.writeAll(name); - try qt.printEpilogue(comp, w); + try qt.printEpilogue(comp, false, w); } pub fn printDesugared(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { _ = try qt.printPrologue(comp, true, w); - try qt.printEpilogue(comp, w); + try qt.printEpilogue(comp, true, w); } fn printPrologue(qt: QualType, comp: *const Compilation, desugar: bool, w: anytype) @TypeOf(w).Error!bool { @@ -1199,19 +1215,19 @@ pub const QualType = packed struct(u32) { if (simple) try w.writeByte(' '); return false; }, - .typeof => |typeof| if (!desugar) { + .typeof => |typeof| if (desugar) { + continue :loop typeof.base.type(comp); + } else { try w.writeAll("typeof("); try typeof.base.print(comp, w); try w.writeAll(")"); return true; - } else { - continue :loop typeof.base.type(comp); }, - .typedef => |typedef| if (!desugar) { + .typedef => |typedef| if (desugar) { + continue :loop typedef.base.type(comp); + } else { try w.writeAll(typedef.name.lookup(comp)); return true; - } else { - continue :loop typedef.base.type(comp); }, .attributed => |attributed| continue :loop attributed.base.type(comp), else => {}, @@ -1261,7 +1277,7 @@ pub const QualType = packed struct(u32) { .atomic => |atomic| { try w.writeAll("_Atomic("); _ = try atomic.printPrologue(comp, desugar, w); - try atomic.printEpilogue(comp, w); + try atomic.printEpilogue(comp, desugar, w); try w.writeAll(")"); }, @@ -1284,7 +1300,7 @@ pub const QualType = packed struct(u32) { return true; } - fn printEpilogue(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { + fn printEpilogue(qt: QualType, comp: *const Compilation, desugar: bool, w: anytype) @TypeOf(w).Error!void { loop: switch (qt.type(comp)) { .pointer => |pointer| { switch (pointer.child.base(comp).type) { @@ -1297,7 +1313,8 @@ pub const QualType = packed struct(u32) { try w.writeByte('('); for (func.params, 0..) |param, i| { if (i != 0) try w.writeAll(", "); - try param.qt.print(comp, w); + _ = try param.qt.printPrologue(comp, desugar, w); + try param.qt.printEpilogue(comp, desugar, w); } if (func.kind != .normal) { if (func.params.len != 0) try w.writeAll(", "); diff --git a/test/cases/functions.c b/test/cases/functions.c index cf5be35..65d3a74 100644 --- a/test/cases/functions.c +++ b/test/cases/functions.c @@ -81,6 +81,12 @@ void no_params(int x){} void invalid_func(__auto_type); int invalid_int = invalid_func; +void desugar_func_ty(void) { + typedef int foo; + foo bar(foo); + int *ptr = bar; +} + #define EXPECTED_ERRORS "functions.c:10:12: error: parameter named 'quux' is missing" \ "functions.c:20:14: error: illegal initializer (only variables can be initialized)" \ "functions.c:18:2: warning: non-void function 'foooo' does not return a value [-Wreturn-type]" \ @@ -94,4 +100,4 @@ int invalid_int = invalid_func; "functions.c:79:6: error: redefinition of 'no_params' with a different type" \ "functions.c:78:6: note: previous definition is here" \ "functions.c:81:19: error: '__auto_type' not allowed in function prototype" \ - + "functions.c:87:16: warning: incompatible pointer types initializing 'int *' from incompatible type 'foo (foo)' (aka 'int (int)') [-Wincompatible-pointer-types]" \ -- Gitee From 9b8088e5705d48c3eeb70423734b03b9984de150 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 18:24:26 +0300 Subject: [PATCH 076/117] Parser: implement explicit vector casts --- src/aro/Parser.zig | 29 +++++++++++++++++++++++++++++ src/aro/Parser/Diagnostic.zig | 15 +++++++++++++++ test/cases/ast/vectors.c | 28 ++++++++++++++++++++++++++++ test/cases/vectors.c | 23 ++++++++++++++++++++++- 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 8cfdbde..aa248e9 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -6608,6 +6608,9 @@ pub const Result = struct { const dest_sk = dest_qt.scalarKind(p.comp); const src_sk = res.qt.scalarKind(p.comp); + const dest_vec = dest_qt.is(p.comp, .vector); + const src_vec = res.qt.is(p.comp, .vector); + if (dest_qt.is(p.comp, .void)) { // everything can cast to void cast_kind = .to_void; @@ -6615,6 +6618,32 @@ pub const Result = struct { } else if (res.qt.is(p.comp, .void)) { try p.err(operand_tok, .invalid_cast_operand_type, .{res.qt}); return error.ParsingFailed; + } else if (dest_vec and src_vec) { + if (dest_qt.eql(res.qt, p.comp)) { + cast_kind = .no_op; + } else if (dest_qt.sizeCompare(res.qt, p.comp) == .eq) { + cast_kind = .bitcast; + } else { + try p.err(l_paren, .invalid_vec_conversion, .{ dest_qt, res.qt }); + return error.ParsingFailed; + } + } else if (dest_vec or src_vec) { + const non_vec_sk = if (dest_vec) src_sk else dest_sk; + const vec_qt = if (dest_vec) dest_qt else res.qt; + const non_vec_qt = if (dest_vec) res.qt else dest_qt; + const non_vec_tok = if (dest_vec) operand_tok else l_paren; + if (non_vec_sk == .none) { + try p.err(non_vec_tok, .invalid_cast_operand_type, .{non_vec_qt}); + return error.ParsingFailed; + } else if (!non_vec_sk.isInt()) { + try p.err(non_vec_tok, .invalid_vec_conversion_scalar, .{ vec_qt, non_vec_qt }); + return error.ParsingFailed; + } else if (dest_qt.sizeCompare(res.qt, p.comp) != .eq) { + try p.err(non_vec_tok, .invalid_vec_conversion_int, .{ vec_qt, non_vec_qt }); + return error.ParsingFailed; + } else { + cast_kind = .bitcast; + } } else if (dest_sk == .nullptr_t) { res.val = .{}; if (src_sk == .nullptr_t) { diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 25ead6e..d0f182d 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -689,6 +689,21 @@ pub const qual_cast: Diagnostic = .{ .kind = .warning, }; +pub const invalid_vec_conversion: Diagnostic = .{ + .fmt = "invalid conversion between vector type {qt} and {qt} of different size", + .kind = .@"error", +}; + +pub const invalid_vec_conversion_scalar: Diagnostic = .{ + .fmt = "invalid conversion between vector type {qt} and scalar type {qt}", + .kind = .@"error", +}; + +pub const invalid_vec_conversion_int: Diagnostic = .{ + .fmt = "invalid conversion between vector type {qt} and integer type {qt} of different size", + .kind = .@"error", +}; + pub const invalid_index: Diagnostic = .{ .fmt = "array subscript is not an integer", .kind = .@"error", diff --git a/test/cases/ast/vectors.c b/test/cases/ast/vectors.c index dd61864..d7fd793 100644 --- a/test/cases/ast/vectors.c +++ b/test/cases/ast/vectors.c @@ -150,3 +150,31 @@ fn_def: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, implicit return_stmt: 'void' +fn_def: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, int)) void' + name: explicit_casts + body: + compound_stmt + cast: (bitcast) 'f2v: vector(2, float)' + implicit cast: (lval_to_rval) 'i2v: vector(2, int)' + decl_ref_expr: 'i2v: vector(2, int)' lvalue + name: b + + cast: (no_op) 'i2v: vector(2, int)' + implicit cast: (lval_to_rval) 'i2v: vector(2, int)' + decl_ref_expr: 'i2v: vector(2, int)' lvalue + name: b + + cast: (bitcast) 'long' + implicit cast: (lval_to_rval) 'i2v: vector(2, int)' + decl_ref_expr: 'i2v: vector(2, int)' lvalue + name: b + + struct_decl: 'struct S' + record_field: 'long' + name: a + + cast: (bitcast) 'f2v: vector(2, float)' + int_literal: 'long' (value: 1) + + implicit return_stmt: 'void' + diff --git a/test/cases/vectors.c b/test/cases/vectors.c index d512496..5b386d6 100644 --- a/test/cases/vectors.c +++ b/test/cases/vectors.c @@ -28,9 +28,30 @@ void vector_conversions(f2v a, i2v b, i3v c) { a + c; } +void explicit_casts(f2v a, i2v b, i3v c) { + (f2v)b; + (i2v)b; + (i3v)b; + (long)b; + (int)b; + (double)b; + (struct S { long a; })b; + (f2v)1L; + (i3v)1L; + (f2v)1.2; + (f2v)(struct S){1}; +} + #define EXPECTED_ERRORS "vectors.c:2:40: error: invalid vector element type 'float *'" \ "vectors.c:3:39: error: vector size not an integral multiple of component size" \ - "vectors.c:10:5: error: cannot cast to non arithmetic or pointer type 'f2v' (vector of 2 'float' values)" \ + "vectors.c:10:10: error: invalid conversion between vector type 'f2v' (vector of 2 'float' values) and integer type 'int' of different size" \ "vectors.c:13:44: error: '_BitInt' vector element width must be at least as wide as 'CHAR_BIT'" \ "vectors.c:14:45: error: '_BitInt' vector element width must be a power of 2" \ "vectors.c:28:7: error: cannot convert between vector type 'f2v' (vector of 2 'float' values) and vector type 'i3v' (vector of 3 'int' values) as implicit conversion would cause truncation" \ + "vectors.c:34:5: error: invalid conversion between vector type 'i3v' (vector of 3 'int' values) and 'i2v' (vector of 2 'int' values) of different size" \ + "vectors.c:36:5: error: invalid conversion between vector type 'i2v' (vector of 2 'int' values) and integer type 'int' of different size" \ + "vectors.c:37:5: error: invalid conversion between vector type 'i2v' (vector of 2 'int' values) and scalar type 'double'" \ + "vectors.c:38:5: error: operand of type 'struct S' where arithmetic or pointer type is required" \ + "vectors.c:40:10: error: invalid conversion between vector type 'i3v' (vector of 3 'int' values) and integer type 'long' of different size" \ + "vectors.c:41:10: error: invalid conversion between vector type 'f2v' (vector of 2 'float' values) and scalar type 'double'" \ + "vectors.c:42:10: error: operand of type 'struct S' where arithmetic or pointer type is required" \ -- Gitee From 1830401a61464cf2ae20d127ece3cf8cde61bd20 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 18:39:08 +0300 Subject: [PATCH 077/117] Parser: add error for using type name in expression --- src/aro/Parser.zig | 4 ++++ src/aro/Parser/Diagnostic.zig | 5 +++++ test/cases/unexpected_type_name.c | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100644 test/cases/unexpected_type_name.c diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index aa248e9..c03c6fb 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -8839,6 +8839,10 @@ fn primaryExpr(p: *Parser) Error!?Result { } if (p.syms.findSymbol(interned_name)) |sym| { + if (sym.kind == .typedef) { + try p.err(name_tok, .unexpected_type_name, .{name}); + return error.ParsingFailed; + } try p.checkDeprecatedUnavailable(sym.qt, name_tok, sym.tok); if (sym.kind == .constexpr) { return .{ diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index d0f182d..c0cc052 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -68,6 +68,11 @@ pub const expected_expr: Diagnostic = .{ .kind = .@"error", }; +pub const unexpected_type_name: Diagnostic = .{ + .fmt = "unexpected type name '{s}': expected expression", + .kind = .@"error", +}; + pub const expected_integer_constant_expr: Diagnostic = .{ .fmt = "expression is not an integer constant expression", .kind = .@"error", diff --git a/test/cases/unexpected_type_name.c b/test/cases/unexpected_type_name.c new file mode 100644 index 0000000..e8d761f --- /dev/null +++ b/test/cases/unexpected_type_name.c @@ -0,0 +1,7 @@ +typedef int foo; +void bar(void) { + int a = foo; +} + +#define EXPECTED_ERRORS "unexpected_type_name.c:3:13: error: unexpected type name 'foo': expected expression" \ + -- Gitee From 4e12bdbe34bf41e8905bdfb587dabf1e26989397 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 19:15:18 +0300 Subject: [PATCH 078/117] Parser: implement __builtin_convertvector --- src/aro/Builtins/Builtin.def | 4 --- src/aro/Compilation.zig | 3 ++- src/aro/Parser.zig | 48 +++++++++++++++++++++++++++++++++++ src/aro/Parser/Diagnostic.zig | 10 ++++++++ src/aro/Tokenizer.zig | 5 ++++ src/aro/Tree.zig | 26 +++++++++++++++++++ test/cases/convertvector.c | 20 +++++++++++++++ 7 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 test/cases/convertvector.c diff --git a/src/aro/Builtins/Builtin.def b/src/aro/Builtins/Builtin.def index 2ae201a..3cb84f2 100644 --- a/src/aro/Builtins/Builtin.def +++ b/src/aro/Builtins/Builtin.def @@ -2025,10 +2025,6 @@ __builtin_constant_p .param_str = "i." .attributes = .{ .@"const" = true, .custom_typecheck = true, .eval_args = false, .const_evaluable = true } -__builtin_convertvector - .param_str = "v." - .attributes = .{ .@"const" = true, .custom_typecheck = true } - __builtin_copysign .param_str = "ddd" .attributes = .{ .@"const" = true, .lib_function_with_builtin_prefix = true, .const_evaluable = true } diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 954417f..02789b5 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -1489,7 +1489,8 @@ pub fn hasBuiltin(comp: *const Compilation, name: []const u8) bool { std.mem.eql(u8, name, "__builtin_choose_expr") or std.mem.eql(u8, name, "__builtin_bitoffsetof") or std.mem.eql(u8, name, "__builtin_offsetof") or - std.mem.eql(u8, name, "__builtin_types_compatible_p")) return true; + std.mem.eql(u8, name, "__builtin_types_compatible_p") or + std.mem.eql(u8, name, "__builtin_convertvector")) return true; const builtin = Builtin.fromName(name) orelse return false; return comp.hasBuiltinFunction(builtin); diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index c03c6fb..21b0195 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -7615,12 +7615,60 @@ fn castExpr(p: *Parser) Error!?Result { .builtin_offsetof => return try p.builtinOffsetof(.bytes), .builtin_bitoffsetof => return try p.builtinOffsetof(.bits), .builtin_types_compatible_p => return try p.typesCompatible(), + .builtin_convertvector => return try p.convertvector(), // TODO: other special-cased builtins else => {}, } return p.unExpr(); } +fn convertvector(p: *Parser) Error!Result { + const builtin_tok = p.tok_i; + p.tok_i += 1; + const l_paren = try p.expectToken(.l_paren); + + const operand = try p.expect(assignExpr); + _ = try p.expectToken(.comma); + + var dest_qt = (try p.typeName()) orelse { + try p.err(p.tok_i, .expected_type, .{}); + p.skipTo(.r_paren); + return error.ParsingFailed; + }; + + try p.expectClosing(l_paren, .r_paren); + + if (operand.qt.isInvalid() or operand.qt.isInvalid()) { + dest_qt = .invalid; + } else check: { + const operand_vec = operand.qt.get(p.comp, .vector) orelse { + try p.err(builtin_tok, .convertvector_arg, .{"first"}); + dest_qt = .invalid; + break :check; + }; + const dest_vec = dest_qt.get(p.comp, .vector) orelse { + try p.err(builtin_tok, .convertvector_arg, .{"second"}); + dest_qt = .invalid; + break :check; + }; + if (operand_vec.len != dest_vec.len or operand_vec.elem.sizeCompare(dest_vec.elem, p.comp) != .eq) { + try p.err(builtin_tok, .convertvector_size, .{}); + dest_qt = .invalid; + } + } + + return .{ + .qt = dest_qt, + .node = try p.addNode(.{ + .builtin_convertvector = .{ + .builtin_tok = builtin_tok, + .dest_qt = dest_qt, + .operand = operand.node, + }, + }), + }; +} + fn typesCompatible(p: *Parser) Error!Result { const builtin_tok = p.tok_i; p.tok_i += 1; diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index c0cc052..1743b2a 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -1057,6 +1057,16 @@ pub const builtin_choose_cond: Diagnostic = .{ .kind = .@"error", }; +pub const convertvector_arg: Diagnostic = .{ + .fmt = "{s} argument to __builtin_convertvector must be a vector type", + .kind = .@"error", +}; + +pub const convertvector_size: Diagnostic = .{ + .fmt = "first two arguments to __builtin_convertvector must have the same number of elements", + .kind = .@"error", +}; + pub const alignas_unavailable: Diagnostic = .{ .fmt = "'_Alignas' attribute requires integer constant expression", .kind = .@"error", diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index ba6fc00..34854fe 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -359,6 +359,7 @@ pub const Token = struct { builtin_offsetof, builtin_bitoffsetof, builtin_types_compatible_p, + builtin_convertvector, /// Generated by #embed directive /// Decimal value with no prefix or suffix @@ -485,6 +486,7 @@ pub const Token = struct { .builtin_offsetof, .builtin_bitoffsetof, .builtin_types_compatible_p, + .builtin_convertvector, .keyword_attribute1, .keyword_attribute2, .keyword_extension, @@ -791,6 +793,7 @@ pub const Token = struct { .builtin_offsetof => "__builtin_offsetof", .builtin_bitoffsetof => "__builtin_bitoffsetof", .builtin_types_compatible_p => "__builtin_types_compatible_p", + .builtin_convertvector => "__builtin_convertvector", .keyword_attribute1 => "__attribute", .keyword_attribute2 => "__attribute__", .keyword_extension => "__extension__", @@ -851,6 +854,7 @@ pub const Token = struct { .builtin_offsetof, .builtin_bitoffsetof, .builtin_types_compatible_p, + .builtin_convertvector, => "an identifier", .string_literal, .string_literal_utf_16, @@ -1145,6 +1149,7 @@ pub const Token = struct { .{ "__builtin_offsetof", .builtin_offsetof }, .{ "__builtin_bitoffsetof", .builtin_bitoffsetof }, .{ "__builtin_types_compatible_p", .builtin_types_compatible_p }, + .{ "__builtin_convertvector", .builtin_convertvector }, }); }; diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index ad9702c..c587de5 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -241,6 +241,7 @@ pub const Node = union(enum) { builtin_ref: BuiltinRef, builtin_types_compatible_p: TypesCompatible, builtin_choose_expr: Conditional, + builtin_convertvector: Convertvector, /// C23 bool literal `true` / `false` bool_literal: Literal, @@ -606,6 +607,12 @@ pub const Node = union(enum) { rhs: QualType, }; + pub const Convertvector = struct { + builtin_tok: TokenIndex, + dest_qt: QualType, + operand: Node.Index, + }; + pub const Literal = struct { literal_tok: TokenIndex, qt: QualType, @@ -1577,6 +1584,13 @@ pub const Node = union(enum) { .rhs = @bitCast(node_data[1]), }, }, + .builtin_convertvector => .{ + .builtin_convertvector = .{ + .builtin_tok = node_tok, + .dest_qt = @bitCast(node_data[0]), + .operand = @enumFromInt(node_data[1]), + }, + }, .array_init_expr_two => .{ .array_init_expr = .{ .l_brace_tok = node_tok, @@ -1840,6 +1854,7 @@ pub const Node = union(enum) { cond_expr, builtin_choose_expr, builtin_types_compatible_p, + builtin_convertvector, array_init_expr, array_init_expr_two, struct_init_expr, @@ -2623,6 +2638,12 @@ pub fn setNode(tree: *Tree, node: Node, index: usize) !void { repr.data[1] = @bitCast(builtin.rhs); repr.tok = builtin.builtin_tok; }, + .builtin_convertvector => |builtin| { + repr.tag = .builtin_convertvector; + repr.data[0] = @bitCast(builtin.dest_qt); + repr.data[1] = @intFromEnum(builtin.operand); + repr.tok = builtin.builtin_tok; + }, .array_init_expr => |init| { repr.data[0] = @bitCast(init.container_qt); if (init.items.len > 2) { @@ -3233,6 +3254,11 @@ fn dumpNode( try w.writeByte('\n'); try config.setColor(w, .reset); }, + .builtin_convertvector => |convert| { + try w.writeByteNTimes(' ', level + half); + try w.writeAll("operand: "); + try tree.dumpNode(convert.operand, level + delta, config, w); + }, .if_stmt => |@"if"| { try w.writeByteNTimes(' ', level + half); try w.writeAll("cond:\n"); diff --git a/test/cases/convertvector.c b/test/cases/convertvector.c new file mode 100644 index 0000000..16d57ab --- /dev/null +++ b/test/cases/convertvector.c @@ -0,0 +1,20 @@ +//aro-args --target=x86_64-linux-gnu -Wno-unused +typedef int i2v __attribute__((__vector_size__(4 * 2))); +typedef float f2v __attribute__((__vector_size__(4 * 2))); +typedef short s4v __attribute__((__vector_size__(2 * 4))); +typedef int i4v __attribute__((__vector_size__(4 * 4))); + +void foo(i2v a, f2v b, i4v c) { + __builtin_convertvector(a, f2v); + __builtin_convertvector(a, i4v); + __builtin_convertvector(a, s4v); + __builtin_convertvector(a, int); + __builtin_convertvector(a, 1); + __builtin_convertvector(1, f2v); +} + +#define EXPECTED_ERRORS "convertvector.c:9:5: error: first two arguments to __builtin_convertvector must have the same number of elements" \ + "convertvector.c:10:5: error: first two arguments to __builtin_convertvector must have the same number of elements" \ + "convertvector.c:11:5: error: second argument to __builtin_convertvector must be a vector type" \ + "convertvector.c:12:32: error: expected a type" \ + "convertvector.c:13:5: error: first argument to __builtin_convertvector must be a vector type" \ -- Gitee From 4cbc74d505df7c0bebb88696096774654921f540 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 23 May 2025 20:08:07 +0300 Subject: [PATCH 079/117] Parser: don't use custom tokens for special cased builtins --- src/aro/Builtins/Builtin.def | 26 +++++++++++++++ src/aro/Compilation.zig | 7 ----- src/aro/Parser.zig | 61 +++++++++++++++++------------------- src/aro/Tokenizer.zig | 34 -------------------- 4 files changed, 55 insertions(+), 73 deletions(-) diff --git a/src/aro/Builtins/Builtin.def b/src/aro/Builtins/Builtin.def index 3cb84f2..094d864 100644 --- a/src/aro/Builtins/Builtin.def +++ b/src/aro/Builtins/Builtin.def @@ -5,6 +5,32 @@ const TargetSet = Properties.TargetSet; pub const max_param_count = 12; +# Special cased builtins + +__builtin_choose_expr + .param_str = "v." + .attributes = .{ .custom_typecheck = true } + +__builtin_va_arg + .param_str = "v." + .attributes = .{ .custom_typecheck = true } + +__builtin_offsetof + .param_str = "z." + .attributes = .{ .custom_typecheck = true } + +__builtin_bitoffsetof + .param_str = "z." + .attributes = .{ .custom_typecheck = true } + +__builtin_types_compatible_p + .param_str = "i." + .attributes = .{ .custom_typecheck = true } + +__builtin_convertvector + .param_str = "v." + .attributes = .{ .@"const" = true, .custom_typecheck = true } + _Block_object_assign .param_str = "vv*vC*iC" .header = .blocks diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 02789b5..d23e5a0 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -1485,13 +1485,6 @@ pub fn pragmaEvent(comp: *Compilation, event: PragmaEvent) void { } pub fn hasBuiltin(comp: *const Compilation, name: []const u8) bool { - if (std.mem.eql(u8, name, "__builtin_va_arg") or - std.mem.eql(u8, name, "__builtin_choose_expr") or - std.mem.eql(u8, name, "__builtin_bitoffsetof") or - std.mem.eql(u8, name, "__builtin_offsetof") or - std.mem.eql(u8, name, "__builtin_types_compatible_p") or - std.mem.eql(u8, name, "__builtin_convertvector")) return true; - const builtin = Builtin.fromName(name) orelse return false; return comp.hasBuiltinFunction(builtin); } diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 21b0195..8227cc0 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -7561,10 +7561,6 @@ fn mulExpr(p: *Parser) Error!?Result { /// : '(' compoundStmt ')' suffixExpr* /// | '(' typeName ')' castExpr /// | '(' typeName ')' '{' initializerItems '}' -/// | __builtin_choose_expr '(' integerConstExpr ',' assignExpr ',' assignExpr ')' -/// | __builtin_va_arg '(' assignExpr ',' typeName ')' -/// | __builtin_offsetof '(' typeName ',' offsetofMemberDesignator ')' -/// | __builtin_bitoffsetof '(' typeName ',' offsetofMemberDesignator ')' /// | unExpr fn castExpr(p: *Parser) Error!?Result { if (p.eatToken(.l_paren)) |l_paren| cast_expr: { @@ -7578,7 +7574,7 @@ fn castExpr(p: *Parser) Error!?Result { var stmt_expr_state: StmtExprState = .{}; const body_node = (try p.compoundStmt(false, &stmt_expr_state)).?; // compoundStmt only returns null if .l_brace isn't the first token - var res = Result{ + var res: Result = .{ .node = body_node, .qt = stmt_expr_state.last_expr_qt, }; @@ -7609,22 +7605,11 @@ fn castExpr(p: *Parser) Error!?Result { try operand.castType(p, ty, operand_tok, l_paren); return operand; } - switch (p.tok_ids[p.tok_i]) { - .builtin_choose_expr => return try p.builtinChooseExpr(), - .builtin_va_arg => return try p.builtinVaArg(), - .builtin_offsetof => return try p.builtinOffsetof(.bytes), - .builtin_bitoffsetof => return try p.builtinOffsetof(.bits), - .builtin_types_compatible_p => return try p.typesCompatible(), - .builtin_convertvector => return try p.convertvector(), - // TODO: other special-cased builtins - else => {}, - } return p.unExpr(); } -fn convertvector(p: *Parser) Error!Result { - const builtin_tok = p.tok_i; - p.tok_i += 1; +/// convertvector : __builtin_convertvector '(' assignExpr ',' typeName ')' +fn convertvector(p: *Parser, builtin_tok: TokenIndex) Error!Result { const l_paren = try p.expectToken(.l_paren); const operand = try p.expect(assignExpr); @@ -7669,9 +7654,8 @@ fn convertvector(p: *Parser) Error!Result { }; } -fn typesCompatible(p: *Parser) Error!Result { - const builtin_tok = p.tok_i; - p.tok_i += 1; +/// typesCompatible : __builtin_types_compatible_p '(' typeName ',' typeName ')' +fn typesCompatible(p: *Parser, builtin_tok: TokenIndex) Error!Result { const l_paren = try p.expectToken(.l_paren); const lhs = (try p.typeName()) orelse { @@ -7705,8 +7689,8 @@ fn typesCompatible(p: *Parser) Error!Result { return res; } +/// chooseExpr : __builtin_choose_expr '(' integerConstExpr ',' assignExpr ',' assignExpr ')' fn builtinChooseExpr(p: *Parser) Error!Result { - p.tok_i += 1; const l_paren = try p.expectToken(.l_paren); const cond_tok = p.tok_i; var cond = try p.integerConstExpr(.no_const_decl_folding); @@ -7750,10 +7734,8 @@ fn builtinChooseExpr(p: *Parser) Error!Result { return cond; } -fn builtinVaArg(p: *Parser) Error!Result { - const builtin_tok = p.tok_i; - p.tok_i += 1; - +/// vaStart : __builtin_va_arg '(' assignExpr ',' typeName ')' +fn builtinVaArg(p: *Parser, builtin_tok: TokenIndex) Error!Result { const l_paren = try p.expectToken(.l_paren); const va_list_tok = p.tok_i; var va_list = try p.expect(assignExpr); @@ -7786,10 +7768,10 @@ fn builtinVaArg(p: *Parser) Error!Result { const OffsetKind = enum { bits, bytes }; -fn builtinOffsetof(p: *Parser, offset_kind: OffsetKind) Error!Result { - const builtin_tok = p.tok_i; - p.tok_i += 1; - +/// offsetof +/// : __builtin_offsetof '(' typeName ',' offsetofMemberDesignator ')' +/// | __builtin_bitoffsetof '(' typeName ',' offsetofMemberDesignator ')' +fn builtinOffsetof(p: *Parser, builtin_tok: TokenIndex, offset_kind: OffsetKind) Error!Result { const l_paren = try p.expectToken(.l_paren); const ty_tok = p.tok_i; @@ -7830,7 +7812,7 @@ fn builtinOffsetof(p: *Parser, offset_kind: OffsetKind) Error!Result { }; } -/// offsetofMemberDesignator: IDENTIFIER ('.' IDENTIFIER | '[' expr ']' )* +/// offsetofMemberDesignator : IDENTIFIER ('.' IDENTIFIER | '[' expr ']' )* fn offsetofMemberDesignator( p: *Parser, base_record_ty: Type.Record, @@ -8868,6 +8850,11 @@ fn checkArrayBounds(p: *Parser, index: Result, array: Result, tok: TokenIndex) ! /// | STRING_LITERAL /// | '(' expr ')' /// | genericSelection +/// | convertvector +/// | typesCompatible +/// | chooseExpr +/// | vaStart +/// | offsetof fn primaryExpr(p: *Parser) Error!?Result { if (p.eatToken(.l_paren)) |l_paren| { var grouped_expr = try p.expect(expr); @@ -8952,6 +8939,16 @@ fn primaryExpr(p: *Parser) Error!?Result { }); } + switch (some.builtin.tag) { + .__builtin_choose_expr => return try p.builtinChooseExpr(), + .__builtin_va_arg => return try p.builtinVaArg(name_tok), + .__builtin_offsetof => return try p.builtinOffsetof(name_tok, .bytes), + .__builtin_bitoffsetof => return try p.builtinOffsetof(name_tok, .bits), + .__builtin_types_compatible_p => return try p.typesCompatible(name_tok), + .__builtin_convertvector => return try p.convertvector(name_tok), + else => {}, + } + return .{ .qt = some.qt, .node = try p.addNode(.{ @@ -9024,7 +9021,7 @@ fn primaryExpr(p: *Parser) Error!?Result { .keyword_nullptr => { defer p.tok_i += 1; try p.err(p.tok_i, .pre_c23_compat, .{"'nullptr'"}); - return Result{ + return .{ .val = .null, .qt = .nullptr_t, .node = try p.addNode(.{ diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 34854fe..13c1d88 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -353,14 +353,6 @@ pub const Token = struct { keyword_nullable_result, keyword_null_unspecified, - // builtins that require special parsing - builtin_choose_expr, - builtin_va_arg, - builtin_offsetof, - builtin_bitoffsetof, - builtin_types_compatible_p, - builtin_convertvector, - /// Generated by #embed directive /// Decimal value with no prefix or suffix embed_byte, @@ -481,12 +473,6 @@ pub const Token = struct { .keyword_restrict2, .keyword_alignof1, .keyword_alignof2, - .builtin_choose_expr, - .builtin_va_arg, - .builtin_offsetof, - .builtin_bitoffsetof, - .builtin_types_compatible_p, - .builtin_convertvector, .keyword_attribute1, .keyword_attribute2, .keyword_extension, @@ -788,12 +774,6 @@ pub const Token = struct { .keyword_alignof2 => "__alignof__", .keyword_typeof1 => "__typeof", .keyword_typeof2 => "__typeof__", - .builtin_choose_expr => "__builtin_choose_expr", - .builtin_va_arg => "__builtin_va_arg", - .builtin_offsetof => "__builtin_offsetof", - .builtin_bitoffsetof => "__builtin_bitoffsetof", - .builtin_types_compatible_p => "__builtin_types_compatible_p", - .builtin_convertvector => "__builtin_convertvector", .keyword_attribute1 => "__attribute", .keyword_attribute2 => "__attribute__", .keyword_extension => "__extension__", @@ -849,12 +829,6 @@ pub const Token = struct { .macro_func, .macro_function, .macro_pretty_func, - .builtin_choose_expr, - .builtin_va_arg, - .builtin_offsetof, - .builtin_bitoffsetof, - .builtin_types_compatible_p, - .builtin_convertvector, => "an identifier", .string_literal, .string_literal_utf_16, @@ -1142,14 +1116,6 @@ pub const Token = struct { .{ "_Nullable", .keyword_nullable }, .{ "_Nullable_result", .keyword_nullable_result }, .{ "_Null_unspecified", .keyword_null_unspecified }, - - // builtins that require special parsing - .{ "__builtin_choose_expr", .builtin_choose_expr }, - .{ "__builtin_va_arg", .builtin_va_arg }, - .{ "__builtin_offsetof", .builtin_offsetof }, - .{ "__builtin_bitoffsetof", .builtin_bitoffsetof }, - .{ "__builtin_types_compatible_p", .builtin_types_compatible_p }, - .{ "__builtin_convertvector", .builtin_convertvector }, }); }; -- Gitee From 861d612fc330866636fb6239b55f268940369bed Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 24 May 2025 15:06:56 +0300 Subject: [PATCH 080/117] Parser: implement __builtin_shufflevector --- src/aro/Builtins/Builtin.def | 9 ++--- src/aro/Parser.zig | 66 +++++++++++++++++++++++++++++++++++ src/aro/Parser/Diagnostic.zig | 20 +++++++++++ src/aro/Tree.zig | 49 +++++++++++++++++++++++++- test/cases/shufflevector.c | 20 +++++++++++ 5 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 test/cases/shufflevector.c diff --git a/src/aro/Builtins/Builtin.def b/src/aro/Builtins/Builtin.def index 094d864..c8b2fe7 100644 --- a/src/aro/Builtins/Builtin.def +++ b/src/aro/Builtins/Builtin.def @@ -31,6 +31,11 @@ __builtin_convertvector .param_str = "v." .attributes = .{ .@"const" = true, .custom_typecheck = true } +__builtin_shufflevector + .param_str = "v." + .attributes = .{ .@"const" = true, .custom_typecheck = true } + + _Block_object_assign .param_str = "vv*vC*iC" .header = .blocks @@ -7351,10 +7356,6 @@ __builtin_setrnd .param_str = "di" .target_set = TargetSet.initOne(.ppc) -__builtin_shufflevector - .param_str = "v." - .attributes = .{ .@"const" = true, .custom_typecheck = true } - __builtin_signbit .param_str = "i." .attributes = .{ .@"const" = true, .custom_typecheck = true, .lib_function_with_builtin_prefix = true } diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 8227cc0..2f0dea2 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -7608,6 +7608,70 @@ fn castExpr(p: *Parser) Error!?Result { return p.unExpr(); } +/// shufflevector : __builtin_shufflevector '(' assignExpr ',' assignExpr (',' integerConstExpr)* ')' +fn shufflevector(p: *Parser, builtin_tok: TokenIndex) Error!Result { + const l_paren = try p.expectToken(.l_paren); + + const first_tok = p.tok_i; + const lhs = try p.expect(assignExpr); + _ = try p.expectToken(.comma); + const second_tok = p.tok_i; + const rhs = try p.expect(assignExpr); + + const max_index: ?Value = blk: { + if (lhs.qt.isInvalid() or rhs.qt.isInvalid()) break :blk null; + const lhs_vec = lhs.qt.get(p.comp, .vector) orelse break :blk null; + const rhs_vec = rhs.qt.get(p.comp, .vector) orelse break :blk null; + + break :blk try Value.int(lhs_vec.len + rhs_vec.len, p.comp); + }; + const negative_one = try Value.intern(p.comp, .{ .int = .{ .i64 = -1 } }); + + const list_buf_top = p.list_buf.items.len; + defer p.list_buf.items.len = list_buf_top; + while (p.eatToken(.comma)) |_| { + const index_tok = p.tok_i; + const index = try p.integerConstExpr(.gnu_folding_extension); + try p.list_buf.append(index.node); + if (index.val.compare(.lt, negative_one, p.comp)) { + try p.err(index_tok, .shufflevector_negative_index, .{}); + } else if (max_index != null and index.val.compare(.gte, max_index.?, p.comp)) { + try p.err(index_tok, .shufflevector_index_too_big, .{}); + } + } + + try p.expectClosing(l_paren, .r_paren); + + var res_qt: QualType = .invalid; + if (!lhs.qt.isInvalid() and !lhs.qt.is(p.comp, .vector)) { + try p.err(first_tok, .shufflevector_arg, .{"first"}); + } else if (!rhs.qt.isInvalid() and !rhs.qt.is(p.comp, .vector)) { + try p.err(second_tok, .shufflevector_arg, .{"second"}); + } else if (!lhs.qt.eql(rhs.qt, p.comp)) { + try p.err(builtin_tok, .shufflevector_same_type, .{}); + } else if (p.list_buf.items.len == list_buf_top) { + res_qt = lhs.qt; + } else { + res_qt = try p.comp.type_store.put(p.gpa, .{ .vector = .{ + .elem = lhs.qt.childType(p.comp), + .len = @intCast(p.list_buf.items.len - list_buf_top), + } }); + } + + return .{ + .qt = res_qt, + .node = try p.addNode(.{ + .builtin_shufflevector = .{ + .builtin_tok = builtin_tok, + .qt = res_qt, + .lhs = lhs.node, + .rhs = rhs.node, + .indexes = p.list_buf.items[list_buf_top..], + }, + }), + }; +} + /// convertvector : __builtin_convertvector '(' assignExpr ',' typeName ')' fn convertvector(p: *Parser, builtin_tok: TokenIndex) Error!Result { const l_paren = try p.expectToken(.l_paren); @@ -8850,6 +8914,7 @@ fn checkArrayBounds(p: *Parser, index: Result, array: Result, tok: TokenIndex) ! /// | STRING_LITERAL /// | '(' expr ')' /// | genericSelection +/// | shufflevector /// | convertvector /// | typesCompatible /// | chooseExpr @@ -8946,6 +9011,7 @@ fn primaryExpr(p: *Parser) Error!?Result { .__builtin_bitoffsetof => return try p.builtinOffsetof(name_tok, .bits), .__builtin_types_compatible_p => return try p.typesCompatible(name_tok), .__builtin_convertvector => return try p.convertvector(name_tok), + .__builtin_shufflevector => return try p.shufflevector(name_tok), else => {}, } diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 1743b2a..49224d7 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -1067,6 +1067,26 @@ pub const convertvector_size: Diagnostic = .{ .kind = .@"error", }; +pub const shufflevector_arg: Diagnostic = .{ + .fmt = "{s} argument to __builtin_shufflevector must be a vector type", + .kind = .@"error", +}; + +pub const shufflevector_same_type: Diagnostic = .{ + .fmt = "first two arguments to '__builtin_shufflevector' must have the same type", + .kind = .@"error", +}; + +pub const shufflevector_negative_index: Diagnostic = .{ + .fmt = "index for __builtin_shufflevector must be positive or -1", + .kind = .@"error", +}; + +pub const shufflevector_index_too_big: Diagnostic = .{ + .fmt = "index for __builtin_shufflevector must be less than the total number of vector elements", + .kind = .@"error", +}; + pub const alignas_unavailable: Diagnostic = .{ .fmt = "'_Alignas' attribute requires integer constant expression", .kind = .@"error", diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index c587de5..b8eeadb 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -242,6 +242,7 @@ pub const Node = union(enum) { builtin_types_compatible_p: TypesCompatible, builtin_choose_expr: Conditional, builtin_convertvector: Convertvector, + builtin_shufflevector: Shufflevector, /// C23 bool literal `true` / `false` bool_literal: Literal, @@ -613,6 +614,14 @@ pub const Node = union(enum) { operand: Node.Index, }; + pub const Shufflevector = struct { + builtin_tok: TokenIndex, + qt: QualType, + lhs: Node.Index, + rhs: Node.Index, + indexes: []const Node.Index, + }; + pub const Literal = struct { literal_tok: TokenIndex, qt: QualType, @@ -1591,6 +1600,15 @@ pub const Node = union(enum) { .operand = @enumFromInt(node_data[1]), }, }, + .builtin_shufflevector => .{ + .builtin_shufflevector = .{ + .builtin_tok = node_tok, + .qt = @bitCast(node_data[0]), + .lhs = @enumFromInt(tree.extra.items[node_data[1]]), + .rhs = @enumFromInt(tree.extra.items[node_data[1] + 1]), + .indexes = @ptrCast(tree.extra.items[node_data[1] + 2 ..][0..node_data[2]]), + }, + }, .array_init_expr_two => .{ .array_init_expr = .{ .l_brace_tok = node_tok, @@ -1855,6 +1873,7 @@ pub const Node = union(enum) { builtin_choose_expr, builtin_types_compatible_p, builtin_convertvector, + builtin_shufflevector, array_init_expr, array_init_expr_two, struct_init_expr, @@ -2644,6 +2663,17 @@ pub fn setNode(tree: *Tree, node: Node, index: usize) !void { repr.data[1] = @intFromEnum(builtin.operand); repr.tok = builtin.builtin_tok; }, + .builtin_shufflevector => |builtin| { + repr.tag = .builtin_shufflevector; + repr.data[0] = @bitCast(builtin.qt); + repr.data[1] = @intCast(tree.extra.items.len); + repr.data[2] = @intCast(builtin.indexes.len); + repr.tok = builtin.builtin_tok; + try tree.extra.ensureUnusedCapacity(tree.comp.gpa, builtin.indexes.len + 2); + tree.extra.appendAssumeCapacity(@intFromEnum(builtin.lhs)); + tree.extra.appendAssumeCapacity(@intFromEnum(builtin.rhs)); + tree.extra.appendSliceAssumeCapacity(@ptrCast(builtin.indexes)); + }, .array_init_expr => |init| { repr.data[0] = @bitCast(init.container_qt); if (init.items.len > 2) { @@ -3256,9 +3286,26 @@ fn dumpNode( }, .builtin_convertvector => |convert| { try w.writeByteNTimes(' ', level + half); - try w.writeAll("operand: "); + try w.writeAll("operand:\n"); try tree.dumpNode(convert.operand, level + delta, config, w); }, + .builtin_shufflevector => |shuffle| { + try w.writeByteNTimes(' ', level + half); + try w.writeAll("lhs:\n"); + try tree.dumpNode(shuffle.lhs, level + delta, config, w); + + try w.writeByteNTimes(' ', level + half); + try w.writeAll("rhs:\n"); + try tree.dumpNode(shuffle.rhs, level + delta, config, w); + + if (shuffle.indexes.len > 0) { + try w.writeByteNTimes(' ', level + half); + try w.writeAll("indexes:\n"); + for (shuffle.indexes) |index| { + try tree.dumpNode(index, level + delta, config, w); + } + } + }, .if_stmt => |@"if"| { try w.writeByteNTimes(' ', level + half); try w.writeAll("cond:\n"); diff --git a/test/cases/shufflevector.c b/test/cases/shufflevector.c new file mode 100644 index 0000000..0dc4e4c --- /dev/null +++ b/test/cases/shufflevector.c @@ -0,0 +1,20 @@ +//aro-args --target=x86_64-linux-gnu -Wno-unused +typedef int i2v __attribute__((__vector_size__(4 * 2))); +typedef int i4v __attribute__((__vector_size__(4 * 4))); + +void foo(i2v a, i2v b, i4v c) { + i4v d = __builtin_shufflevector(a, b, 0, 1, 2, 3); + i2v e = __builtin_shufflevector(a, b, -1, 3); + i4v f = __builtin_shufflevector(a, b, 1, 1); + __builtin_shufflevector(a, c); + __builtin_shufflevector(1, b); + __builtin_shufflevector(a, 1); + __builtin_shufflevector(a, b, -2, 5); +} + +#define EXPECTED_ERRORS "shufflevector.c:8:13: error: initializing 'i4v' (vector of 4 'int' values) from incompatible type '__attribute__((__vector_size__(2 * sizeof(int)))) int' (vector of 2 'int' values)" \ + "shufflevector.c:9:5: error: first two arguments to '__builtin_shufflevector' must have the same type" \ + "shufflevector.c:10:29: error: first argument to __builtin_shufflevector must be a vector type" \ + "shufflevector.c:11:32: error: second argument to __builtin_shufflevector must be a vector type" \ + "shufflevector.c:12:35: error: index for __builtin_shufflevector must be positive or -1" \ + "shufflevector.c:12:39: error: index for __builtin_shufflevector must be less than the total number of vector elements" \ -- Gitee From 90e53d93b3f0fc251f4c2d2a910eea200f46a7a5 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 24 May 2025 19:27:23 +0300 Subject: [PATCH 081/117] CI: update to setup-zig v2 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03ce532..4714f59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v3 with: submodules: true - - uses: mlugg/setup-zig@v1 + - uses: mlugg/setup-zig@v2 with: version: master -- Gitee From 713e7ebd06362ed47ae728893690135d4c35495f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 24 May 2025 23:42:11 +0300 Subject: [PATCH 082/117] CI: skip running tests in release mode For some reason the "complex values" test case fails on x86-64 Linux in ReleaseFast and is fixed by a no-op in a specific place. Since the test case only fails when running in the test-runner and not in the main executable compiled in ReleaseFast I'll disable it for now until #543 is implemented. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4714f59..e82e994 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,5 +43,5 @@ jobs: - name: Run Tests run: zig build test - - name: Run Tests in release mode - run: zig build test -Doptimize=ReleaseFast + # - name: Run Tests in release mode + # run: zig build test -Doptimize=ReleaseFast -- Gitee From 7c9ce74c3bbf4395b856c1550bac61a0ec968e9a Mon Sep 17 00:00:00 2001 From: "Feng.YJ" <32027253+huiyifyj@users.noreply.github.com> Date: Thu, 29 May 2025 16:12:20 +0800 Subject: [PATCH 083/117] fix typo in readme C1/ => C17 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 159e4fc..55a88f4 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ The project intends to support standard C and all common extensions: | Version | status | | ---------------- | ---------------------------------------------------------------------------------------------------------- | | C23 | Complete excluding [Add IEEE 754 interchange and extended types](https://github.com/Vexu/arocc/issues/552) | -| C17 | Complete excluding warnings [Ensure C1/ compatibility](https://github.com/Vexu/arocc/issues/820) | +| C17 | Complete excluding warnings [Ensure C17 compatibility](https://github.com/Vexu/arocc/issues/820) | | C11 | Complete excluding warnings [Ensure C11 compatibility](https://github.com/Vexu/arocc/issues/821) | | C99 | Complete excluding warnings [Ensure C99 compatibility](https://github.com/Vexu/arocc/issues/822) | | C95 | Complete | | C89 | Complete | | GNU extensions | [Ensure GNU C extension compatibility](https://github.com/Vexu/arocc/issues/824) | -| Clang extensions | [ Ensure Clang C extension compatibility](https://github.com/Vexu/arocc/issues/825) | +| Clang extensions | [Ensure Clang C extension compatibility](https://github.com/Vexu/arocc/issues/825) | Aro will be used as the C frontend for [C to Zig translation](https://github.com/ziglang/translate-c/) in the Zig toolchain. -- Gitee From a9b2639a8ae2dcd2a000a65d40491df6bdb5166f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 29 May 2025 13:05:59 +0300 Subject: [PATCH 084/117] Parser: allow convert vector between different element sizes --- src/aro/Parser.zig | 2 +- test/cases/convertvector.c | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 2f0dea2..031df06 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -7700,7 +7700,7 @@ fn convertvector(p: *Parser, builtin_tok: TokenIndex) Error!Result { dest_qt = .invalid; break :check; }; - if (operand_vec.len != dest_vec.len or operand_vec.elem.sizeCompare(dest_vec.elem, p.comp) != .eq) { + if (operand_vec.len != dest_vec.len) { try p.err(builtin_tok, .convertvector_size, .{}); dest_qt = .invalid; } diff --git a/test/cases/convertvector.c b/test/cases/convertvector.c index 16d57ab..be9e7fc 100644 --- a/test/cases/convertvector.c +++ b/test/cases/convertvector.c @@ -1,20 +1,25 @@ //aro-args --target=x86_64-linux-gnu -Wno-unused -typedef int i2v __attribute__((__vector_size__(4 * 2))); -typedef float f2v __attribute__((__vector_size__(4 * 2))); -typedef short s4v __attribute__((__vector_size__(2 * 4))); -typedef int i4v __attribute__((__vector_size__(4 * 4))); +typedef int i2v __attribute__((__vector_size__(sizeof(float) * 2))); +typedef float f2v __attribute__((__vector_size__(sizeof(float) * 2))); +typedef short s4v __attribute__((__vector_size__(sizeof(short) * 4))); +typedef short s2v __attribute__((__vector_size__(sizeof(short) * 2))); +typedef int i4v __attribute__((__vector_size__(sizeof(int) * 4))); void foo(i2v a, f2v b, i4v c) { __builtin_convertvector(a, f2v); __builtin_convertvector(a, i4v); __builtin_convertvector(a, s4v); + __builtin_convertvector(a, s2v); + __builtin_convertvector(b, s2v); + __builtin_convertvector(c, s2v); __builtin_convertvector(a, int); __builtin_convertvector(a, 1); __builtin_convertvector(1, f2v); } -#define EXPECTED_ERRORS "convertvector.c:9:5: error: first two arguments to __builtin_convertvector must have the same number of elements" \ - "convertvector.c:10:5: error: first two arguments to __builtin_convertvector must have the same number of elements" \ - "convertvector.c:11:5: error: second argument to __builtin_convertvector must be a vector type" \ - "convertvector.c:12:32: error: expected a type" \ - "convertvector.c:13:5: error: first argument to __builtin_convertvector must be a vector type" \ +#define EXPECTED_ERRORS "convertvector.c:10:5: error: first two arguments to __builtin_convertvector must have the same number of elements" \ + "convertvector.c:11:5: error: first two arguments to __builtin_convertvector must have the same number of elements" \ + "convertvector.c:14:5: error: first two arguments to __builtin_convertvector must have the same number of elements" \ + "convertvector.c:15:5: error: second argument to __builtin_convertvector must be a vector type" \ + "convertvector.c:16:32: error: expected a type" \ + "convertvector.c:17:5: error: first argument to __builtin_convertvector must be a vector type" \ -- Gitee From 3432872ca2bd38262d7bd93d931a1cf12d965344 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 29 May 2025 13:08:15 +0300 Subject: [PATCH 085/117] Attribute: disallow bool vectors --- src/aro/Attribute.zig | 2 +- test/cases/vectors.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 797e87b..9feda33 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -1210,7 +1210,7 @@ fn applyTransparentUnion(attr: Attribute, p: *Parser, tok: TokenIndex, qt: QualT fn applyVectorSize(attr: Attribute, p: *Parser, tok: TokenIndex, qt: *QualType) !void { if (qt.isInvalid()) return; const scalar_kind = qt.scalarKind(p.comp); - if (!scalar_kind.isArithmetic() or !scalar_kind.isReal() or scalar_kind == .@"enum") { + if (scalar_kind != .int and scalar_kind != .float) { if (qt.get(p.comp, .@"enum")) |enum_ty| { if (p.comp.langopts.emulate == .clang and enum_ty.incomplete) { return; // Clang silently ignores vector_size on incomplete enums. diff --git a/test/cases/vectors.c b/test/cases/vectors.c index 5b386d6..4174500 100644 --- a/test/cases/vectors.c +++ b/test/cases/vectors.c @@ -42,6 +42,8 @@ void explicit_casts(f2v a, i2v b, i3v c) { (f2v)(struct S){1}; } +typedef _Bool invalid5 __attribute__((vector_size(sizeof(_Bool) * 2))); + #define EXPECTED_ERRORS "vectors.c:2:40: error: invalid vector element type 'float *'" \ "vectors.c:3:39: error: vector size not an integral multiple of component size" \ "vectors.c:10:10: error: invalid conversion between vector type 'f2v' (vector of 2 'float' values) and integer type 'int' of different size" \ @@ -55,3 +57,4 @@ void explicit_casts(f2v a, i2v b, i3v c) { "vectors.c:40:10: error: invalid conversion between vector type 'i3v' (vector of 3 'int' values) and integer type 'long' of different size" \ "vectors.c:41:10: error: invalid conversion between vector type 'f2v' (vector of 2 'float' values) and scalar type 'double'" \ "vectors.c:42:10: error: operand of type 'struct S' where arithmetic or pointer type is required" \ + "vectors.c:45:39: error: invalid vector element type '_Bool'" \ -- Gitee From 16f2967de910f41982007e3cf27ec0ed1e1be8cb Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 29 May 2025 14:52:25 +0300 Subject: [PATCH 086/117] Parser: allow coercing to same sized vectors --- src/aro/Parser.zig | 12 +++++-- test/cases/ast/vectors.c | 68 ++++++++++++++++++++++++++++++++++++++++ test/cases/vectors.c | 14 +++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 031df06..32aaf77 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -4117,7 +4117,9 @@ fn findScalarInitializer( warned_excess.* = true; return true; } - if (res.qt.eql(qt, p.comp) and il.list.items.len == 0) { + if (il.list.items.len == 0 and (res.qt.eql(qt, p.comp) or + (res.qt.is(p.comp, .vector) and res.qt.sizeCompare(qt, p.comp) == .eq))) + { try p.setInitializer(il, qt, first_tok, res); return true; } @@ -6911,7 +6913,13 @@ pub const Result = struct { const dest_sk = dest_unqual.scalarKind(p.comp); const src_sk = res.qt.scalarKind(p.comp); - if (dest_sk == .nullptr_t) { + if (dest_qt.is(p.comp, .vector) and res.qt.is(p.comp, .vector)) { + if (dest_unqual.eql(res.qt, p.comp)) return; + if (dest_unqual.sizeCompare(res.qt, p.comp) == .eq) { + res.qt = dest_unqual; + return res.implicitCast(p, .bitcast, tok); + } + } else if (dest_sk == .nullptr_t) { if (src_sk == .nullptr_t) return; } else if (dest_sk == .bool) { if (src_sk != .none and src_sk != .nullptr_t) { diff --git a/test/cases/ast/vectors.c b/test/cases/ast/vectors.c index d7fd793..a11e71e 100644 --- a/test/cases/ast/vectors.c +++ b/test/cases/ast/vectors.c @@ -178,3 +178,71 @@ fn_def: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, implicit return_stmt: 'void' +typedef: 'vector(8, char)' + name: vec_a + +typedef: 'vector(2, float)' + name: vec_b + +fn_def: 'fn (a: vec_a: vector(8, char)) vec_b: vector(2, float)' + name: bitcast_vector + body: + compound_stmt + return_stmt: 'vec_b: vector(2, float)' + expr: + implicit cast: (bitcast) 'vec_b: vector(2, float)' + implicit cast: (lval_to_rval) 'vec_a: vector(8, char)' + decl_ref_expr: 'vec_a: vector(8, char)' lvalue + name: a + +fn_def: 'fn () int' + name: main + body: + compound_stmt + variable: 'vec_b: vector(2, float)' + name: b + init: + array_init_expr: 'vec_b: vector(2, float)' + float_literal: 'float' (value: 1.4) + + float_literal: 'float' (value: 2.4) + + variable: 'vec_a: vector(8, char)' + name: a + init: + implicit cast: (bitcast) 'vec_a: vector(8, char)' + implicit cast: (lval_to_rval) 'vec_b: vector(2, float)' + decl_ref_expr: 'vec_b: vector(2, float)' lvalue + name: b + + variable: 'vec_a: vector(8, char)' + name: a2 + init: + implicit cast: (bitcast) 'vec_a: vector(8, char)' + implicit cast: (lval_to_rval) 'vec_b: vector(2, float)' + decl_ref_expr: 'vec_b: vector(2, float)' lvalue + name: b + + assign_expr: 'vec_a: vector(8, char)' + lhs: + decl_ref_expr: 'vec_a: vector(8, char)' lvalue + name: a + rhs: + implicit cast: (bitcast) 'vec_a: vector(8, char)' + implicit cast: (lval_to_rval) 'vec_b: vector(2, float)' + decl_ref_expr: 'vec_b: vector(2, float)' lvalue + name: b + + call_expr: 'vec_b: vector(2, float)' + callee: + implicit cast: (function_to_pointer) '*fn (a: vec_a: vector(8, char)) vec_b: vector(2, float)' + decl_ref_expr: 'fn (a: vec_a: vector(8, char)) vec_b: vector(2, float)' lvalue + name: bitcast_vector + args: + implicit cast: (bitcast) 'vec_a: vector(8, char)' + implicit cast: (lval_to_rval) 'vec_b: vector(2, float)' + decl_ref_expr: 'vec_b: vector(2, float)' lvalue + name: b + + implicit return_stmt: 'int' (value: 0) + diff --git a/test/cases/vectors.c b/test/cases/vectors.c index 4174500..748dc8a 100644 --- a/test/cases/vectors.c +++ b/test/cases/vectors.c @@ -44,6 +44,20 @@ void explicit_casts(f2v a, i2v b, i3v c) { typedef _Bool invalid5 __attribute__((vector_size(sizeof(_Bool) * 2))); +typedef char vec_a __attribute__((vector_size(8))); +typedef float vec_b __attribute__((vector_size(8))); + +vec_b bitcast_vector(vec_a a) { + return a; +} +int main(void) { + vec_b b = { 1.4f, 2.4f }; + vec_a a = b; + vec_a a2 = {b}; + a = b; + bitcast_vector(b); +} + #define EXPECTED_ERRORS "vectors.c:2:40: error: invalid vector element type 'float *'" \ "vectors.c:3:39: error: vector size not an integral multiple of component size" \ "vectors.c:10:10: error: invalid conversion between vector type 'f2v' (vector of 2 'float' values) and integer type 'int' of different size" \ -- Gitee From 4da0716d90c00c9f4ec706bdedce5fb8047aca5c Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Tue, 27 May 2025 08:15:12 -0700 Subject: [PATCH 087/117] Parser: Do not attempt vararg promotion on invalid types Closes #855 --- src/aro/Parser.zig | 2 +- .../call undeclared function with invalid type.c | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 test/cases/call undeclared function with invalid type.c diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 32aaf77..d776052 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -8793,7 +8793,7 @@ fn callExpr(p: *Parser, lhs: Result) Error!Result { if (call_expr.shouldPerformLvalConversion(arg_count)) { try arg.lvalConversion(p, param_tok); } - if (arg.qt.hasIncompleteSize(p.comp) and !arg.qt.is(p.comp, .void)) return error.ParsingFailed; + if ((arg.qt.hasIncompleteSize(p.comp) and !arg.qt.is(p.comp, .void)) or arg.qt.isInvalid()) return error.ParsingFailed; if (arg_count >= params_len) { if (call_expr.shouldPromoteVarArg(arg_count)) switch (arg.qt.base(p.comp).type) { diff --git a/test/cases/call undeclared function with invalid type.c b/test/cases/call undeclared function with invalid type.c new file mode 100644 index 0000000..50c3c3e --- /dev/null +++ b/test/cases/call undeclared function with invalid type.c @@ -0,0 +1,12 @@ +struct S { + does_not_exist x; +}; + +void foo(void) { + struct S s = {0}; + bar(s.x); +} + +#define EXPECTED_ERRORS "call undeclared function with invalid type.c:2:5: error: unknown type name 'does_not_exist'" \ + "call undeclared function with invalid type.c:7:5: error: call to undeclared function 'bar'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]" + -- Gitee From 7339f1b1f1450a769be2aaa2ff9d2a17a728f135 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 31 May 2025 00:11:05 +0300 Subject: [PATCH 088/117] Parser: cast case values to switch condition type --- src/aro/Parser.zig | 25 ++++++---- test/cases/ast/switch unsigned int.c | 74 ++++++++++++++++++++++++++++ test/cases/switch unsigned int.c | 9 ++++ 3 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 test/cases/ast/switch unsigned int.c create mode 100644 test/cases/switch unsigned int.c diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index d776052..7a5c364 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -5222,16 +5222,24 @@ fn labeledStmt(p: *Parser) Error!?Node.Index { .label_tok = name_tok, } }); } else if (p.eatToken(.keyword_case)) |case| { - const first_item = try p.integerConstExpr(.gnu_folding_extension); + var first_item = try p.integerConstExpr(.gnu_folding_extension); const ellipsis = p.tok_i; - const second_item = if (p.eatToken(.ellipsis) != null) blk: { + var second_item = if (p.eatToken(.ellipsis) != null) blk: { try p.err(ellipsis, .gnu_switch_range, .{}); break :blk try p.integerConstExpr(.gnu_folding_extension); } else null; _ = try p.expectToken(.colon); - if (p.@"switch") |some| check: { - if (some.qt.hasIncompleteSize(p.comp)) break :check; // error already reported for incomplete size + if (p.@"switch") |@"switch"| check: { + if (@"switch".qt.hasIncompleteSize(p.comp)) break :check; // error already reported for incomplete size + + // Coerce to switch condition type + try first_item.coerce(p, @"switch".qt, case + 1, .assign); + try first_item.putValue(p); + if (second_item) |*item| { + try item.coerce(p, @"switch".qt, ellipsis + 1, .assign); + try item.putValue(p); + } const first = first_item.val; const last = if (second_item) |second| second.val else first; @@ -5247,7 +5255,7 @@ fn labeledStmt(p: *Parser) Error!?Node.Index { } // TODO cast to target type - const prev = (try some.add(first, last, case + 1)) orelse break :check; + const prev = (try @"switch".add(first, last, case + 1)) orelse break :check; // TODO check which value was already handled try p.err(case + 1, .duplicate_switch_case, .{first_item}); @@ -6585,8 +6593,7 @@ pub const Result = struct { /// Saves value and replaces it with `.unavailable`. fn saveValue(res: *Result, p: *Parser) !void { assert(!p.in_macro); - if (res.val.opt_ref == .none or res.val.opt_ref == .null) return; - if (!p.in_macro) try p.tree.value_map.put(p.gpa, res.node, res.val); + try res.putValue(p); res.val = .{}; } @@ -7217,9 +7224,7 @@ fn constExpr(p: *Parser, decl_folding: ConstDeclFoldingMode) Error!Result { if (res.qt.isInvalid() or res.val.opt_ref == .none) return res; - // saveValue sets val to unavailable - var copy = res; - try copy.saveValue(p); + try res.putValue(p); return res; } diff --git a/test/cases/ast/switch unsigned int.c b/test/cases/ast/switch unsigned int.c new file mode 100644 index 0000000..1941ca8 --- /dev/null +++ b/test/cases/ast/switch unsigned int.c @@ -0,0 +1,74 @@ +implicit typedef: '__int128' + name: __int128_t + +implicit typedef: 'unsigned __int128' + name: __uint128_t + +implicit typedef: '*char' + name: __builtin_ms_va_list + +implicit typedef: '[1]struct __va_list_tag' + name: __builtin_va_list + +implicit typedef: 'struct __NSConstantString_tag' + name: __NSConstantString + +implicit typedef: 'long double' + name: __float80 + +fn_def: 'fn (x: unsigned int) int' + name: lottery + body: + compound_stmt + switch_stmt + cond: + implicit cast: (lval_to_rval) 'unsigned int' + decl_ref_expr: 'unsigned int' lvalue + name: x + body: + compound_stmt + case_stmt + value: + implicit cast: (int_cast) 'unsigned int' (value: 3) + int_literal: 'int' (value: 3) + stmt: + return_stmt: 'int' + expr: + int_literal: 'int' (value: 0) + + case_stmt + value: + implicit cast: (int_cast) 'unsigned int' (value: 4294967295) + negate_expr: 'int' (value: -1) + operand: + int_literal: 'int' (value: 1) + stmt: + return_stmt: 'int' + expr: + int_literal: 'int' (value: 3) + + case_stmt + range start: + implicit cast: (int_cast) 'unsigned int' (value: 8) + int_literal: 'int' (value: 8) + range end: + implicit cast: (int_cast) 'unsigned int' (value: 10) + int_literal: 'int' (value: 10) + stmt: + return_stmt: 'int' + expr: + implicit cast: (int_cast) 'int' + implicit cast: (lval_to_rval) 'unsigned int' + decl_ref_expr: 'unsigned int' lvalue + name: x + + default_stmt + stmt: + return_stmt: 'int' + expr: + negate_expr: 'int' (value: -1) + operand: + int_literal: 'int' (value: 1) + + implicit return_stmt: 'int' + diff --git a/test/cases/switch unsigned int.c b/test/cases/switch unsigned int.c new file mode 100644 index 0000000..c7ddd81 --- /dev/null +++ b/test/cases/switch unsigned int.c @@ -0,0 +1,9 @@ +//aro-args --target=x86_64-linux-gnu +int lottery(unsigned int x) { + switch (x) { + case 3: return 0; + case -1: return 3; + case 8 ... 10: return x; + default: return -1; + } +} -- Gitee From eb08ad61c370ac3bf5ca8a1ca96b437a23a7289b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 30 May 2025 11:14:41 +0300 Subject: [PATCH 089/117] add reference to non-tentative definition of declarations --- src/aro/Diagnostics.zig | 1 + src/aro/Parser.zig | 55 ++++++++++++++ src/aro/Parser/Diagnostic.zig | 11 +++ src/aro/SymbolStack.zig | 19 +++++ src/aro/Tree.zig | 54 +++++++++++-- test/cases/ast/tentative decls defined.c | 97 ++++++++++++++++++++++++ test/cases/tentative decls defined.c | 22 ++++++ 7 files changed, 253 insertions(+), 6 deletions(-) create mode 100644 test/cases/ast/tentative decls defined.c create mode 100644 test/cases/tentative decls defined.c diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index 47e5dcb..f392b89 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -146,6 +146,7 @@ pub const Option = enum { nullability, @"microsoft-flexible-array", @"microsoft-anon-tag", + @"out-of-scope-function", /// GNU extensions pub const gnu = [_]Option{ diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 7a5c364..9bf4d42 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -672,6 +672,51 @@ fn addList(p: *Parser, nodes: []const Node.Index) Allocator.Error!Tree.Node.Rang return Tree.Node.Range{ .start = start, .end = end }; } +/// Recursively sets the defintion field of `tentative_decl` to `definition`. +pub fn setTentativeDeclDefinition(p: *Parser, tentative_decl: Node.Index, definition: Node.Index) void { + const node_data = &p.tree.nodes.items(.data)[@intFromEnum(tentative_decl)]; + switch (p.tree.nodes.items(.tag)[@intFromEnum(tentative_decl)]) { + .fn_proto => {}, + .variable => {}, + else => return, + } + + const prev: Node.OptIndex = @enumFromInt(node_data[2]); + + node_data[2] = @intFromEnum(definition); + if (prev.unpack()) |some| { + p.setTentativeDeclDefinition(some, definition); + } +} + +/// Clears the defintion field of declarations that were not defined so that +/// the field always contains a _def if present. +fn clearNonTentativeDefinitions(p: *Parser) void { + const tags = p.tree.nodes.items(.tag); + const data = p.tree.nodes.items(.data); + for (p.tree.root_decls.items) |root_decl| { + switch (tags[@intFromEnum(root_decl)]) { + .fn_proto => { + const node_data = &data[@intFromEnum(root_decl)]; + if (node_data[2] != @intFromEnum(Node.OptIndex.null)) { + if (tags[node_data[2]] != .fn_def) { + node_data[2] = @intFromEnum(Node.OptIndex.null); + } + } + }, + .variable => { + const node_data = &data[@intFromEnum(root_decl)]; + if (node_data[2] != @intFromEnum(Node.OptIndex.null)) { + if (tags[node_data[2]] != .variable_def) { + node_data[2] = @intFromEnum(Node.OptIndex.null); + } + } + }, + else => {}, + } + } +} + fn findLabel(p: *Parser, name: []const u8) ?TokenIndex { for (p.labels.items) |item| { switch (item) { @@ -879,6 +924,8 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { } pp.comp.pragmaEvent(.after_parse); + p.clearNonTentativeDefinitions(); + return p.tree; } @@ -1351,6 +1398,7 @@ fn decl(p: *Parser) Error!bool { else => .auto, // Error reported in `validate` }, .initializer = if (init_d.initializer) |some| some.node else null, + .definition = null, }, }, @intFromEnum(decl_node)); } @@ -1379,6 +1427,8 @@ fn decl(p: *Parser) Error!bool { if (init_d.d.qt.@"const" or decl_spec.constexpr != null) init.val else .{}, decl_spec.constexpr != null, ); + } else if (init_d.d.qt.is(p.comp, .func)) { + try p.syms.declareSymbol(p, interned_name, init_d.d.qt, init_d.d.name, decl_node); } else if (p.func.qt != null and decl_spec.storage_class != .@"extern") { try p.syms.defineSymbol(p, interned_name, init_d.d.qt, init_d.d.name, decl_node, .{}, false); } else { @@ -8956,6 +9006,10 @@ fn primaryExpr(p: *Parser) Error!?Result { try p.err(name_tok, .unexpected_type_name, .{name}); return error.ParsingFailed; } + if (sym.out_of_scope) { + try p.err(name_tok, .out_of_scope_use, .{name}); + try p.err(sym.tok, .previous_definition, .{}); + } try p.checkDeprecatedUnavailable(sym.qt, name_tok, sym.tok); if (sym.kind == .constexpr) { return .{ @@ -9269,6 +9323,7 @@ fn makePredefinedIdentifier(p: *Parser, strings_top: usize) !Result { .thread_local = false, .implicit = true, .initializer = str_lit, + .definition = null, }, }) }; } diff --git a/src/aro/Parser/Diagnostic.zig b/src/aro/Parser/Diagnostic.zig index 49224d7..a4c068c 100644 --- a/src/aro/Parser/Diagnostic.zig +++ b/src/aro/Parser/Diagnostic.zig @@ -495,6 +495,17 @@ pub const previous_definition: Diagnostic = .{ .kind = .note, }; +pub const previous_declaration: Diagnostic = .{ + .fmt = "previous declaration is here", + .kind = .note, +}; + +pub const out_of_scope_use: Diagnostic = .{ + .fmt = "use of out-of-scope declaration of '{s}'", + .kind = .warning, + .opt = .@"out-of-scope-function", +}; + pub const expected_identifier: Diagnostic = .{ .fmt = "expected identifier", .kind = .@"error", diff --git a/src/aro/SymbolStack.zig b/src/aro/SymbolStack.zig index 33e8708..5566b99 100644 --- a/src/aro/SymbolStack.zig +++ b/src/aro/SymbolStack.zig @@ -19,6 +19,7 @@ pub const Symbol = struct { qt: QualType, tok: TokenIndex, node: Node.OptIndex = .null, + out_of_scope: bool = false, kind: Kind, val: Value, }; @@ -226,6 +227,8 @@ pub fn defineSymbol( if (qt.isInvalid()) return; try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)}); try p.err(prev.tok, .previous_definition, .{}); + } else { + if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(some, node); } }, .def, .constexpr => if (!prev.qt.isInvalid()) { @@ -281,6 +284,8 @@ pub fn declareSymbol( if (qt.isInvalid()) return; try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)}); try p.err(prev.tok, .previous_definition, .{}); + } else { + if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(node, some); } }, .def, .constexpr => { @@ -289,6 +294,7 @@ pub fn declareSymbol( try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)}); try p.err(prev.tok, .previous_definition, .{}); } else { + if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(node, some); return; } }, @@ -308,6 +314,19 @@ pub fn declareSymbol( .node = .pack(node), .val = .{}, }); + + // Declare out of scope symbol for functions declared in functions. + if (s.active_len > 1 and !p.comp.langopts.standard.atLeast(.c23) and qt.is(p.comp, .func)) { + try s.scopes.items[0].vars.put(p.gpa, name, .{ + .kind = .decl, + .name = name, + .tok = tok, + .qt = qt, + .node = .pack(node), + .val = .{}, + .out_of_scope = true, + }); + } } pub fn defineParam( diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index b8eeadb..fa217f5 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -331,6 +331,8 @@ pub const Node = union(enum) { /// Implies `static == true`. implicit: bool, initializer: ?Node.Index, + /// Actual, non-tentative definition of this variable. + definition: ?Node.Index, }; pub const Typedef = struct { @@ -738,8 +740,7 @@ pub const Node = union(enum) { .qt = @bitCast(node_data[0]), .static = attr.static, .@"inline" = attr.@"inline", - // TODO decide how to handle definition - .definition = null, + .definition = unpackOptIndex(node_data[2]), }, }; }, @@ -769,6 +770,27 @@ pub const Node = union(enum) { }; }, .variable => { + const attr: Node.Repr.DeclAttr = @bitCast(node_data[1]); + return .{ + .variable = .{ + .name_tok = node_tok, + .qt = @bitCast(node_data[0]), + .storage_class = if (attr.static) + .static + else if (attr.@"extern") + .@"extern" + else if (attr.register) + .register + else + .auto, + .thread_local = attr.thread_local, + .implicit = attr.implicit, + .initializer = null, + .definition = unpackOptIndex(node_data[2]), + }, + }; + }, + .variable_def => { const attr: Node.Repr.DeclAttr = @bitCast(node_data[1]); return .{ .variable = .{ @@ -785,6 +807,7 @@ pub const Node = union(enum) { .thread_local = attr.thread_local, .implicit = attr.implicit, .initializer = unpackOptIndex(node_data[2]), + .definition = null, }, }; }, @@ -1763,6 +1786,7 @@ pub const Node = union(enum) { fn_def, param, variable, + variable_def, typedef, global_asm, struct_decl, @@ -1927,8 +1951,7 @@ pub fn setNode(tree: *Tree, node: Node, index: usize) !void { .static = proto.static, .@"inline" = proto.@"inline", }); - // TODO decide how to handle definition - // repr.data[2] = proto.definition; + repr.data[2] = packOptIndex(proto.definition); repr.tok = proto.name_tok; }, .fn_def => |def| { @@ -1950,7 +1973,7 @@ pub fn setNode(tree: *Tree, node: Node, index: usize) !void { repr.tok = param.name_tok; }, .variable => |variable| { - repr.tag = .variable; + repr.tag = if (variable.initializer != null) .variable_def else .variable; repr.data[0] = @bitCast(variable.qt); repr.data[1] = @bitCast(Node.Repr.DeclAttr{ .@"extern" = variable.storage_class == .@"extern", @@ -1959,7 +1982,11 @@ pub fn setNode(tree: *Tree, node: Node, index: usize) !void { .implicit = variable.implicit, .register = variable.storage_class == .register, }); - repr.data[2] = packOptIndex(variable.initializer); + if (variable.initializer) |some| { + repr.data[2] = @intFromEnum(some); + } else { + repr.data[2] = packOptIndex(variable.definition); + } repr.tok = variable.name_tok; }, .typedef => |typedef| { @@ -3058,6 +3085,14 @@ fn dumpNode( try config.setColor(w, NAME); try w.print("{s}\n", .{tree.tokSlice(proto.name_tok)}); try config.setColor(w, .reset); + + if (proto.definition) |definition| { + try w.writeByteNTimes(' ', level + half); + try w.writeAll("definition: "); + try config.setColor(w, NAME); + try w.print("0x{X}\n", .{@intFromEnum(definition)}); + try config.setColor(w, .reset); + } }, .fn_def => |def| { try w.writeByteNTimes(' ', level + half); @@ -3124,6 +3159,13 @@ fn dumpNode( try w.writeAll("init:\n"); try tree.dumpNode(some, level + delta, config, w); } + if (variable.definition) |definition| { + try w.writeByteNTimes(' ', level + half); + try w.writeAll("definition: "); + try config.setColor(w, NAME); + try w.print("0x{X}\n", .{@intFromEnum(definition)}); + try config.setColor(w, .reset); + } }, .enum_field => |field| { try w.writeByteNTimes(' ', level + half); diff --git a/test/cases/ast/tentative decls defined.c b/test/cases/ast/tentative decls defined.c new file mode 100644 index 0000000..18c3c51 --- /dev/null +++ b/test/cases/ast/tentative decls defined.c @@ -0,0 +1,97 @@ +implicit typedef: '__int128' + name: __int128_t + +implicit typedef: 'unsigned __int128' + name: __uint128_t + +implicit typedef: '*char' + name: __builtin_ms_va_list + +implicit typedef: '[1]struct __va_list_tag' + name: __builtin_va_list + +implicit typedef: 'struct __NSConstantString_tag' + name: __NSConstantString + +implicit typedef: 'long double' + name: __float80 + +fn_proto: 'fn (int) int' + name: foo + definition: 0x9 + +fn_proto: 'fn (int) int' + name: foo + definition: 0x9 + +fn_proto: 'fn (int) int' + name: foo + definition: 0x9 + +fn_def: 'fn (a: int) int' + name: foo + body: + compound_stmt + return_stmt: 'int' + expr: + implicit cast: (lval_to_rval) 'int' + decl_ref_expr: 'int' lvalue + name: a + +fn_proto: 'fn (int) int' + name: foo + definition: 0x9 + +fn_proto: 'fn (int) int' + name: foo + definition: 0x9 + +fn_proto: 'fn (int) int' + name: foo + definition: 0x9 + +variable: 'int' + extern name: a + definition: 0x14 + +variable: 'int' + name: a + definition: 0x14 + +variable: 'int' + name: a + init: + int_literal: 'int' (value: 1) + +variable: 'int' + extern name: a + definition: 0x14 + +variable: 'int' + name: a + definition: 0x14 + +fn_def: 'fn () int' + name: bar + body: + compound_stmt + fn_proto: 'fn () int' + name: baz + + fn_proto: 'fn () int' + name: baz + definition: 0x19 + + variable: 'int' + extern name: b + + variable: 'int' + extern name: b + definition: 0x1B + + return_stmt: 'int' + expr: + implicit cast: (lval_to_rval) 'int' + decl_ref_expr: 'int' lvalue + name: b + diff --git a/test/cases/tentative decls defined.c b/test/cases/tentative decls defined.c new file mode 100644 index 0000000..db5c44b --- /dev/null +++ b/test/cases/tentative decls defined.c @@ -0,0 +1,22 @@ +//aro-args --target=x86_64-linux-gnu +int foo(int); +extern int foo(int); +int foo(int); +int foo(int a) { return a; } +int foo(int); +extern int foo(int); +int foo(int); + +extern int a; +int a; +int a = 1; +extern int a; +int a; + +int bar(void) { + int baz(void); + int baz(void); + extern int b; + extern int b; + return b; +} -- Gitee From b5c50ec19b346d11990c6880a501eacff7c1f82a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 30 May 2025 11:15:24 +0300 Subject: [PATCH 090/117] Tree: combine fn_proto and fn_def --- src/aro/CodeGen.zig | 23 +++---- src/aro/Parser.zig | 9 ++- src/aro/Tree.zig | 82 ++++++++--------------- src/assembly_backend/x86_64.zig | 11 +-- test/cases/ast/_Float16.c | 8 +-- test/cases/ast/__float80.c | 2 +- test/cases/ast/atomic.c | 4 +- test/cases/ast/c23 auto.c | 6 +- test/cases/ast/cast kinds.c | 4 +- test/cases/ast/complex init.c | 2 +- test/cases/ast/decayed attributed array.c | 2 +- test/cases/ast/float eval method.c | 2 +- test/cases/ast/for decl stmt.c | 2 +- test/cases/ast/forever stmt.c | 2 +- test/cases/ast/msvc attribute keywords.c | 8 +-- test/cases/ast/native half type.c | 2 +- test/cases/ast/nullability.c | 4 +- test/cases/ast/promotion edge cases.c | 2 +- test/cases/ast/stdckdint_ast.c | 2 +- test/cases/ast/switch unsigned int.c | 2 +- test/cases/ast/tentative decls defined.c | 20 +++--- test/cases/ast/types.c | 8 +-- test/cases/ast/vectors.c | 12 ++-- test/runner.zig | 4 +- 24 files changed, 101 insertions(+), 122 deletions(-) diff --git a/src/aro/CodeGen.zig b/src/aro/CodeGen.zig index b2db935..eb9869a 100644 --- a/src/aro/CodeGen.zig +++ b/src/aro/CodeGen.zig @@ -98,11 +98,12 @@ pub fn genIr(tree: *const Tree) Compilation.Error!Ir { .enum_forward_decl, => {}, - .fn_proto => {}, - - .fn_def => |def| c.genFn(def) catch |err| switch (err) { - error.FatalError => return error.FatalError, - error.OutOfMemory => return error.OutOfMemory, + .function => |function| { + if (function.body == null) continue; + c.genFn(function) catch |err| switch (err) { + error.FatalError => return error.FatalError, + error.OutOfMemory => return error.OutOfMemory, + }; }, .variable => |variable| c.genVar(variable) catch |err| switch (err) { @@ -174,9 +175,9 @@ fn genType(c: *CodeGen, qt: QualType) !Interner.Ref { return c.builder.interner.put(c.builder.gpa, key); } -fn genFn(c: *CodeGen, def: Node.FnDef) Error!void { - const name = c.tree.tokSlice(def.name_tok); - const func_ty = def.qt.base(c.comp).type.func; +fn genFn(c: *CodeGen, function: Node.Function) Error!void { + const name = c.tree.tokSlice(function.name_tok); + const func_ty = function.qt.base(c.comp).type.func; c.ret_nodes.items.len = 0; try c.builder.startFn(); @@ -194,7 +195,7 @@ fn genFn(c: *CodeGen, def: Node.FnDef) Error!void { // Generate body c.return_label = try c.builder.makeLabel("return"); - try c.genStmt(def.body); + try c.genStmt(function.body.?); // Relocate returns if (c.ret_nodes.items.len == 0) { @@ -257,10 +258,8 @@ fn genExpr(c: *CodeGen, node_index: Node.Index) Error!Ir.Ref { .string_literal_expr, .alignof_expr, => unreachable, // These should have an entry in value_map. - .fn_def, - => unreachable, .static_assert, - .fn_proto, + .function, .typedef, .struct_decl, .union_decl, diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 9bf4d42..c6d6004 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -1321,12 +1321,13 @@ fn decl(p: *Parser) Error!bool { }; try decl_spec.validateFnDef(p); - try p.tree.setNode(.{ .fn_def = .{ + try p.tree.setNode(.{ .function = .{ .name_tok = init_d.d.name, .@"inline" = decl_spec.@"inline" != null, .static = decl_spec.storage_class == .static, .qt = p.func.qt.?, .body = body, + .definition = null, } }, @intFromEnum(decl_node)); try p.decl_buf.append(decl_node); @@ -1362,11 +1363,12 @@ fn decl(p: *Parser) Error!bool { } }, @intFromEnum(decl_node)); } else if (init_d.d.declarator_type == .func or init_d.d.qt.is(p.comp, .func)) { try decl_spec.validateFnDecl(p); - try p.tree.setNode(.{ .fn_proto = .{ + try p.tree.setNode(.{ .function = .{ .name_tok = init_d.d.name, .qt = init_d.d.qt, .static = decl_spec.storage_class == .static, .@"inline" = decl_spec.@"inline" != null, + .body = null, .definition = null, } }, @intFromEnum(decl_node)); } else { @@ -9107,12 +9109,13 @@ fn primaryExpr(p: *Parser) Error!?Result { .params = &.{}, } }); const node = try p.addNode(.{ - .fn_proto = .{ + .function = .{ .name_tok = name_tok, .qt = func_qt, .static = false, .@"inline" = false, .definition = null, + .body = null, }, }); diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index fa217f5..0b72e56 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -142,8 +142,7 @@ pub const GNUAssemblyQualifiers = struct { pub const Node = union(enum) { empty_decl: EmptyDecl, static_assert: StaticAssert, - fn_proto: FnProto, - fn_def: FnDef, + function: Function, param: Param, variable: Variable, typedef: Typedef, @@ -291,23 +290,16 @@ pub const Node = union(enum) { message: ?Node.Index, }; - pub const FnProto = struct { + pub const Function = struct { name_tok: TokenIndex, qt: QualType, static: bool, @"inline": bool, - /// The definition for this prototype if one exists. + body: ?Node.Index, + /// Actual, non-tentative definition of this function. definition: ?Node.Index, }; - pub const FnDef = struct { - name_tok: TokenIndex, - qt: QualType, - static: bool, - @"inline": bool, - body: Node.Index, - }; - pub const Param = struct { name_tok: TokenIndex, qt: QualType, @@ -735,11 +727,12 @@ pub const Node = union(enum) { .fn_proto => { const attr: Node.Repr.DeclAttr = @bitCast(node_data[1]); return .{ - .fn_proto = .{ + .function = .{ .name_tok = node_tok, .qt = @bitCast(node_data[0]), .static = attr.static, .@"inline" = attr.@"inline", + .body = null, .definition = unpackOptIndex(node_data[2]), }, }; @@ -747,12 +740,13 @@ pub const Node = union(enum) { .fn_def => { const attr: Node.Repr.DeclAttr = @bitCast(node_data[1]); return .{ - .fn_def = .{ + .function = .{ .name_tok = node_tok, .qt = @bitCast(node_data[0]), .static = attr.static, .@"inline" = attr.@"inline", .body = @enumFromInt(node_data[2]), + .definition = null, }, }; }, @@ -1944,25 +1938,19 @@ pub fn setNode(tree: *Tree, node: Node, index: usize) !void { repr.data[1] = packOptIndex(assert.message); repr.tok = assert.assert_tok; }, - .fn_proto => |proto| { - repr.tag = .fn_proto; - repr.data[0] = @bitCast(proto.qt); - repr.data[1] = @bitCast(Node.Repr.DeclAttr{ - .static = proto.static, - .@"inline" = proto.@"inline", - }); - repr.data[2] = packOptIndex(proto.definition); - repr.tok = proto.name_tok; - }, - .fn_def => |def| { - repr.tag = .fn_def; - repr.data[0] = @bitCast(def.qt); + .function => |function| { + repr.tag = if (function.body != null) .fn_def else .fn_proto; + repr.data[0] = @bitCast(function.qt); repr.data[1] = @bitCast(Node.Repr.DeclAttr{ - .static = def.static, - .@"inline" = def.@"inline", + .static = function.static, + .@"inline" = function.@"inline", }); - repr.data[2] = @intFromEnum(def.body); - repr.tok = def.name_tok; + if (function.body) |some| { + repr.data[2] = @intFromEnum(some); + } else { + repr.data[2] = packOptIndex(function.definition); + } + repr.tok = function.name_tok; }, .param => |param| { repr.tag = .param; @@ -3073,20 +3061,25 @@ fn dumpNode( try tree.dumpNode(some, level + delta, config, w); } }, - .fn_proto => |proto| { + .function => |function| { try w.writeByteNTimes(' ', level + half); try config.setColor(w, ATTRIBUTE); - if (proto.static) try w.writeAll("static "); - if (proto.@"inline") try w.writeAll("inline "); + if (function.static) try w.writeAll("static "); + if (function.@"inline") try w.writeAll("inline "); try config.setColor(w, .reset); try w.writeAll("name: "); try config.setColor(w, NAME); - try w.print("{s}\n", .{tree.tokSlice(proto.name_tok)}); + try w.print("{s}\n", .{tree.tokSlice(function.name_tok)}); try config.setColor(w, .reset); - if (proto.definition) |definition| { + if (function.body) |body| { + try w.writeByteNTimes(' ', level + half); + try w.writeAll("body:\n"); + try tree.dumpNode(body, level + delta, config, w); + } + if (function.definition) |definition| { try w.writeByteNTimes(' ', level + half); try w.writeAll("definition: "); try config.setColor(w, NAME); @@ -3094,23 +3087,6 @@ fn dumpNode( try config.setColor(w, .reset); } }, - .fn_def => |def| { - try w.writeByteNTimes(' ', level + half); - - try config.setColor(w, ATTRIBUTE); - if (def.static) try w.writeAll("static "); - if (def.@"inline") try w.writeAll("inline "); - - try config.setColor(w, .reset); - try w.writeAll("name: "); - try config.setColor(w, NAME); - try w.print("{s}\n", .{tree.tokSlice(def.name_tok)}); - - try config.setColor(w, .reset); - try w.writeByteNTimes(' ', level + half); - try w.writeAll("body:\n"); - try tree.dumpNode(def.body, level + delta, config, w); - }, .typedef => |typedef| { try w.writeByteNTimes(' ', level + half); try w.writeAll("name: "); diff --git a/src/assembly_backend/x86_64.zig b/src/assembly_backend/x86_64.zig index d0a5ef2..eb84cf9 100644 --- a/src/assembly_backend/x86_64.zig +++ b/src/assembly_backend/x86_64.zig @@ -164,9 +164,10 @@ pub fn genAsm(tree: *const Tree) Error!Assembly { .enum_decl, => {}, - .fn_proto => {}, - - .fn_def => |def| try codegen.genFn(def), + .function => |function| { + if (function.body == null) continue; + try codegen.genFn(function); + }, .variable => |variable| try codegen.genVar(variable), @@ -183,8 +184,8 @@ pub fn genAsm(tree: *const Tree) Error!Assembly { }; } -fn genFn(c: *AsmCodeGen, def: Node.FnDef) !void { - return c.todo("Codegen functions", def.name_tok); +fn genFn(c: *AsmCodeGen, function: Node.Function) !void { + return c.todo("Codegen functions", function.name_tok); } fn genVar(c: *AsmCodeGen, variable: Node.Variable) !void { diff --git a/test/cases/ast/_Float16.c b/test/cases/ast/_Float16.c index 04a78f2..60b8520 100644 --- a/test/cases/ast/_Float16.c +++ b/test/cases/ast/_Float16.c @@ -22,7 +22,7 @@ typedef: '__builtin_va_list: [1]struct __va_list_tag' typedef: '__builtin_va_list: [1]struct __va_list_tag' name: __gnuc_va_list -fn_def: 'fn (x: _Float16, y: _Float16) _Float16' +function: 'fn (x: _Float16, y: _Float16) _Float16' name: foo body: compound_stmt @@ -38,7 +38,7 @@ fn_def: 'fn (x: _Float16, y: _Float16) _Float16' decl_ref_expr: '_Float16' lvalue name: y -fn_def: 'fn (x: int, ...) void' +function: 'fn (x: int, ...) void' name: bar body: compound_stmt @@ -63,7 +63,7 @@ fn_def: 'fn (x: int, ...) void' implicit return_stmt: 'void' -fn_def: 'fn () void' +function: 'fn () void' name: quux body: compound_stmt @@ -85,7 +85,7 @@ fn_def: 'fn () void' implicit return_stmt: 'void' -fn_def: 'fn () void' +function: 'fn () void' name: conversions body: compound_stmt diff --git a/test/cases/ast/__float80.c b/test/cases/ast/__float80.c index 86ade36..180dcd3 100644 --- a/test/cases/ast/__float80.c +++ b/test/cases/ast/__float80.c @@ -16,7 +16,7 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt diff --git a/test/cases/ast/atomic.c b/test/cases/ast/atomic.c index 64e37db..28e37a3 100644 --- a/test/cases/ast/atomic.c +++ b/test/cases/ast/atomic.c @@ -56,7 +56,7 @@ variable: '_Atomic(int)' variable: 'invalid' name: l -fn_def: 'fn () void' +function: 'fn () void' name: test_coerce body: compound_stmt @@ -124,7 +124,7 @@ fn_def: 'fn () void' implicit return_stmt: 'void' -fn_def: 'fn () void' +function: 'fn () void' name: test_member_access body: compound_stmt diff --git a/test/cases/ast/c23 auto.c b/test/cases/ast/c23 auto.c index 9afd7d8..eeeb822 100644 --- a/test/cases/ast/c23 auto.c +++ b/test/cases/ast/c23 auto.c @@ -19,10 +19,10 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_proto: 'invalid' +function: 'invalid' name: a -fn_def: 'fn () void' +function: 'fn () void' name: bad body: compound_stmt @@ -61,7 +61,7 @@ fn_def: 'fn () void' implicit return_stmt: 'void' -fn_def: 'fn () void' +function: 'fn () void' name: good body: compound_stmt diff --git a/test/cases/ast/cast kinds.c b/test/cases/ast/cast kinds.c index f51f128..91d9468 100644 --- a/test/cases/ast/cast kinds.c +++ b/test/cases/ast/cast kinds.c @@ -23,7 +23,7 @@ union_decl: 'union U' record_field: 'float' name: y -fn_def: 'fn () int' +function: 'fn () int' name: bar body: compound_stmt @@ -31,7 +31,7 @@ fn_def: 'fn () int' expr: int_literal: 'int' (value: 42) -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt diff --git a/test/cases/ast/complex init.c b/test/cases/ast/complex init.c index 04f139e..4242afd 100644 --- a/test/cases/ast/complex init.c +++ b/test/cases/ast/complex init.c @@ -13,7 +13,7 @@ implicit typedef: '*char' implicit typedef: 'struct __NSConstantString_tag' name: __NSConstantString -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt diff --git a/test/cases/ast/decayed attributed array.c b/test/cases/ast/decayed attributed array.c index 68c36e4..9a04b6e 100644 --- a/test/cases/ast/decayed attributed array.c +++ b/test/cases/ast/decayed attributed array.c @@ -31,7 +31,7 @@ variable: '*int' attr: aligned alignment: null name: arr -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt diff --git a/test/cases/ast/float eval method.c b/test/cases/ast/float eval method.c index 8c6dbd7..8aa9b15 100644 --- a/test/cases/ast/float eval method.c +++ b/test/cases/ast/float eval method.c @@ -16,7 +16,7 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt diff --git a/test/cases/ast/for decl stmt.c b/test/cases/ast/for decl stmt.c index 850184b..71ebbac 100644 --- a/test/cases/ast/for decl stmt.c +++ b/test/cases/ast/for decl stmt.c @@ -16,7 +16,7 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_def: 'fn () int' +function: 'fn () int' name: main body: compound_stmt diff --git a/test/cases/ast/forever stmt.c b/test/cases/ast/forever stmt.c index 72eaf60..ef861a7 100644 --- a/test/cases/ast/forever stmt.c +++ b/test/cases/ast/forever stmt.c @@ -16,7 +16,7 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_def: 'fn () int' +function: 'fn () int' name: main body: compound_stmt diff --git a/test/cases/ast/msvc attribute keywords.c b/test/cases/ast/msvc attribute keywords.c index 49fd0af..82befe2 100644 --- a/test/cases/ast/msvc attribute keywords.c +++ b/test/cases/ast/msvc attribute keywords.c @@ -20,17 +20,17 @@ variable: 'attributed(int)' attr: unaligned name: b -fn_proto: 'kr (...) int' +function: 'kr (...) int' name: foo -fn_proto: 'attributed(kr (...) *int)' +function: 'attributed(kr (...) *int)' attr: calling_convention cc: stdcall name: bar -fn_proto: 'fn (decayed *[]attributed(int), decayed *attributed([]int)) int' +function: 'fn (decayed *[]attributed(int), decayed *attributed([]int)) int' name: baz -fn_proto: 'fn (fn_ptr: *fn () void) void' +function: 'fn (fn_ptr: *fn () void) void' name: quux variable: 'unsigned long long' diff --git a/test/cases/ast/native half type.c b/test/cases/ast/native half type.c index 1811a51..2b23e98 100644 --- a/test/cases/ast/native half type.c +++ b/test/cases/ast/native half type.c @@ -16,7 +16,7 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt diff --git a/test/cases/ast/nullability.c b/test/cases/ast/nullability.c index aaa0a6e..e8467c3 100644 --- a/test/cases/ast/nullability.c +++ b/test/cases/ast/nullability.c @@ -28,11 +28,11 @@ variable: 'attributed(int)' attr: nullability kind: nonnull name: c -fn_proto: 'attributed(fn () int)' +function: 'attributed(fn () int)' attr: nullability kind: nullable name: d -fn_proto: 'attributed(fn () *int)' +function: 'attributed(fn () *int)' attr: nullability kind: unspecified name: e diff --git a/test/cases/ast/promotion edge cases.c b/test/cases/ast/promotion edge cases.c index 0b050b3..4ddbba9 100644 --- a/test/cases/ast/promotion edge cases.c +++ b/test/cases/ast/promotion edge cases.c @@ -27,7 +27,7 @@ struct_decl: 'struct S' bits: int_literal: 'int' (value: 5) -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt diff --git a/test/cases/ast/stdckdint_ast.c b/test/cases/ast/stdckdint_ast.c index 7c51c2b..c9d4d47 100644 --- a/test/cases/ast/stdckdint_ast.c +++ b/test/cases/ast/stdckdint_ast.c @@ -16,7 +16,7 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt diff --git a/test/cases/ast/switch unsigned int.c b/test/cases/ast/switch unsigned int.c index 1941ca8..108bc0f 100644 --- a/test/cases/ast/switch unsigned int.c +++ b/test/cases/ast/switch unsigned int.c @@ -16,7 +16,7 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_def: 'fn (x: unsigned int) int' +function: 'fn (x: unsigned int) int' name: lottery body: compound_stmt diff --git a/test/cases/ast/tentative decls defined.c b/test/cases/ast/tentative decls defined.c index 18c3c51..b30806e 100644 --- a/test/cases/ast/tentative decls defined.c +++ b/test/cases/ast/tentative decls defined.c @@ -16,19 +16,19 @@ implicit typedef: 'struct __NSConstantString_tag' implicit typedef: 'long double' name: __float80 -fn_proto: 'fn (int) int' +function: 'fn (int) int' name: foo definition: 0x9 -fn_proto: 'fn (int) int' +function: 'fn (int) int' name: foo definition: 0x9 -fn_proto: 'fn (int) int' +function: 'fn (int) int' name: foo definition: 0x9 -fn_def: 'fn (a: int) int' +function: 'fn (a: int) int' name: foo body: compound_stmt @@ -38,15 +38,15 @@ fn_def: 'fn (a: int) int' decl_ref_expr: 'int' lvalue name: a -fn_proto: 'fn (int) int' +function: 'fn (int) int' name: foo definition: 0x9 -fn_proto: 'fn (int) int' +function: 'fn (int) int' name: foo definition: 0x9 -fn_proto: 'fn (int) int' +function: 'fn (int) int' name: foo definition: 0x9 @@ -71,14 +71,14 @@ variable: 'int' name: a definition: 0x14 -fn_def: 'fn () int' +function: 'fn () int' name: bar body: compound_stmt - fn_proto: 'fn () int' + function: 'fn () int' name: baz - fn_proto: 'fn () int' + function: 'fn () int' name: baz definition: 0x19 diff --git a/test/cases/ast/types.c b/test/cases/ast/types.c index 2caa072..35cdbe4 100644 --- a/test/cases/ast/types.c +++ b/test/cases/ast/types.c @@ -31,16 +31,16 @@ variable: 'const volatile int' variable: 'const volatile int' name: d -fn_proto: 'fn (a: restrict *int, b: restrict *int, c: restrict *int) int' +function: 'fn (a: restrict *int, b: restrict *int, c: restrict *int) int' name: foo -fn_proto: 'fn (n: int, bar: decayed *[]int) int' +function: 'fn (n: int, bar: decayed *[]int) int' name: bar typedef: 'void' name: baz -fn_proto: 'attributed(fn () void)' +function: 'attributed(fn () void)' attr: noreturn name: abort @@ -59,7 +59,7 @@ typedef: 'C: A: int' typedef: '[2]int' name: I -fn_def: 'fn (a: decayed *const I: [2]int, b: decayed *const I: [2]int) void' +function: 'fn (a: decayed *const I: [2]int, b: decayed *const I: [2]int) void' name: qux body: compound_stmt diff --git a/test/cases/ast/vectors.c b/test/cases/ast/vectors.c index a11e71e..41231f6 100644 --- a/test/cases/ast/vectors.c +++ b/test/cases/ast/vectors.c @@ -22,7 +22,7 @@ typedef: 'float' typedef: 'vector(2, float)' name: f2v -fn_def: 'fn () void' +function: 'fn () void' name: foo body: compound_stmt @@ -57,7 +57,7 @@ fn_def: 'fn () void' implicit return_stmt: 'void' -fn_def: 'fn (vec: f2v: vector(2, float), index: int) float' +function: 'fn (vec: f2v: vector(2, float), index: int) float' name: subscript body: compound_stmt @@ -93,7 +93,7 @@ typedef: 'vector(2, int)' typedef: 'vector(3, int)' name: i3v -fn_def: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, int)) void' +function: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, int)) void' name: vector_conversions body: compound_stmt @@ -150,7 +150,7 @@ fn_def: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, implicit return_stmt: 'void' -fn_def: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, int)) void' +function: 'fn (a: f2v: vector(2, float), b: i2v: vector(2, int), c: i3v: vector(3, int)) void' name: explicit_casts body: compound_stmt @@ -184,7 +184,7 @@ typedef: 'vector(8, char)' typedef: 'vector(2, float)' name: vec_b -fn_def: 'fn (a: vec_a: vector(8, char)) vec_b: vector(2, float)' +function: 'fn (a: vec_a: vector(8, char)) vec_b: vector(2, float)' name: bitcast_vector body: compound_stmt @@ -195,7 +195,7 @@ fn_def: 'fn (a: vec_a: vector(8, char)) vec_b: vector(2, float)' decl_ref_expr: 'vec_a: vector(8, char)' lvalue name: a -fn_def: 'fn () int' +function: 'fn () int' name: main body: compound_stmt diff --git a/test/runner.zig b/test/runner.zig index fdf28df..143b261 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -364,7 +364,7 @@ pub fn main() !void { if (expected_types) |types| { const test_fn = for (tree.root_decls.items) |decl| { const node = decl.get(&tree); - if (node == .fn_def) break node.fn_def; + if (node == .function and node.function.body != null) break node.function; } else { fail_count += 1; std.debug.print("{s}:\n", .{case}); @@ -375,7 +375,7 @@ pub fn main() !void { var actual = StmtTypeDumper.init(gpa); defer actual.deinit(gpa); - try actual.dump(&tree, test_fn.body); + try actual.dump(&tree, test_fn.body.?); var i: usize = 0; for (types.tokens) |str| { -- Gitee From 0a1bafae3f12a29c65b0cfd91f138c038cc04d34 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 14 Jun 2025 19:12:14 +0300 Subject: [PATCH 091/117] Parser: fix use-after-free from implicit typedefs when using -undef --- src/aro/Parser.zig | 14 +++++++++----- test/cases/undef.c | 13 +++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index c6d6004..3c813fb 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -803,6 +803,9 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { assert(pp.linemarkers == .none); pp.comp.pragmaEvent(.before_parse); + const expected_implicit_typedef_max = 7; + try pp.tokens.ensureUnusedCapacity(pp.gpa, expected_implicit_typedef_max); + var p: Parser = .{ .pp = pp, .comp = pp.comp, @@ -810,7 +813,7 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { .gpa = pp.comp.gpa, .tree = .{ .comp = pp.comp, - .tokens = pp.tokens.slice(), + .tokens = undefined, // Set after implicit typedefs }, .tok_ids = pp.tokens.items(.id), .strings = .init(pp.comp.gpa), @@ -866,8 +869,12 @@ pub fn parse(pp: *Preprocessor) Compilation.Error!Tree { if (p.comp.float80Type()) |float80_ty| { try p.addImplicitTypedef("__float80", float80_ty); } + + // Set here so that the newly generated tokens are included. + p.tree.tokens = p.pp.tokens.slice(); } const implicit_typedef_count = p.decl_buf.items.len; + assert(implicit_typedef_count <= expected_implicit_typedef_max); while (p.eatToken(.eof) == null) { if (try p.pragma()) continue; @@ -935,16 +942,13 @@ fn addImplicitTypedef(p: *Parser, name: []const u8, qt: QualType) !void { try p.comp.generated_buf.append(p.comp.gpa, '\n'); const name_tok: u32 = @intCast(p.pp.tokens.len); - try p.pp.tokens.append(p.gpa, .{ .id = .identifier, .loc = .{ + p.pp.tokens.appendAssumeCapacity(.{ .id = .identifier, .loc = .{ .id = .generated, .byte_offset = @intCast(start), .line = p.pp.generated_line, } }); p.pp.generated_line += 1; - // Reset in case there was an allocation. - p.tree.tokens = p.pp.tokens.slice(); - const node = try p.addNode(.{ .typedef = .{ .name_tok = name_tok, diff --git a/test/cases/undef.c b/test/cases/undef.c index d45a5d0..5eb8c36 100644 --- a/test/cases/undef.c +++ b/test/cases/undef.c @@ -3,3 +3,16 @@ #if defined(linux) || defined(__linux) || defined(__linux__) #error Should not be defined #endif + +int foo = __CHAR_BIT__; +int bar = 100; + +int *someFunc(int x, int y) { return &bar; } + +struct Foo { + int x: 3; + int y; + int z; +}; + +#define EXPECTED_ERRORS "undef.c:7:11: error: use of undeclared identifier '__CHAR_BIT__'" -- Gitee From 0668998bc6bb9fe78455a5df09a81d7d59557840 Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 16 Jun 2025 19:06:37 +0100 Subject: [PATCH 092/117] Diagnostics: hide notes of hidden warnings --- src/aro/Diagnostics.zig | 19 +++++++++++++++++-- test/cases/hide warnings.c | 5 +++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 test/cases/hide warnings.c diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index f392b89..4f8182f 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -256,6 +256,9 @@ warnings: u32 = 0, // Total amount of diagnostics messages sent to `output`. total: u32 = 0, macro_backtrace_limit: u32 = 6, +/// If `effectiveKind` causes us to skip a diagnostic, this is temporarily set to +/// `true` to signal that associated notes should also be skipped. +hide_notes: bool = false, pub fn deinit(d: *Diagnostics) void { switch (d.output) { @@ -308,11 +311,20 @@ pub fn set(d: *Diagnostics, name: []const u8, to: Message.Kind) !void { }); } +/// This mutates the `Diagnostics`, so may only be called when `message` is being added. +/// If `.off` is returned, `message` will not be included, so the caller should give up. pub fn effectiveKind(d: *Diagnostics, message: anytype) Message.Kind { - var kind = message.kind; + if (d.hide_notes and message.kind == .note) { + return .off; + } // -w disregards explicit kind set with -W - if (d.state.ignore_warnings and kind == .warning) return .off; + if (d.state.ignore_warnings and message.kind == .warning) { + d.hide_notes = true; + return .off; + } + + var kind = message.kind; // Get explicit kind set by -W= var set_explicit = false; @@ -336,6 +348,8 @@ pub fn effectiveKind(d: *Diagnostics, message: anytype) Message.Kind { // Upgrade errors to fatal errors if -Wfatal-errors is set if (kind == .@"error" and d.state.fatal_errors) kind = .@"fatal error"; + + if (kind == .off) d.hide_notes = true; return kind; } @@ -451,6 +465,7 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { .note => {}, } d.total += 1; + d.hide_notes = false; switch (d.output) { .ignore => {}, diff --git a/test/cases/hide warnings.c b/test/cases/hide warnings.c new file mode 100644 index 0000000..5afd035 --- /dev/null +++ b/test/cases/hide warnings.c @@ -0,0 +1,5 @@ +//aro-args -w + +int arr[2] = { 0, [0] = 10 }; + +#define EXPECTED_ERRORS -- Gitee From 600bac2238581f88f521fb21cb6533356c30a68c Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 16 Jun 2025 19:21:56 +0100 Subject: [PATCH 093/117] add `-resource-dir ` CLI option This allows overriding the path to the "resource directory", which is the *parent* of Aro's builtin 'include' directory. This is useful when invoking Aro programmatically from the Zig build system since it avoids needing to copy everything into an installation prefix. The option mimics Clang's `-resource-dir` option in form and function. --- src/aro/Compilation.zig | 12 ++++++++---- src/aro/Driver.zig | 9 +++++++++ src/aro/Toolchain.zig | 2 +- src/aro/toolchains/Linux.zig | 4 ++-- test/record_runner.zig | 2 +- test/runner.zig | 4 ++-- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index d23e5a0..a0ac8ac 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -971,16 +971,20 @@ pub fn getCharSignedness(comp: *const Compilation) std.builtin.Signedness { } /// Add built-in aro headers directory to system include paths -pub fn addBuiltinIncludeDir(comp: *Compilation, aro_dir: []const u8) !void { +pub fn addBuiltinIncludeDir(comp: *Compilation, aro_dir: []const u8, override_resource_dir: ?[]const u8) !void { + const gpa = comp.gpa; + try comp.system_include_dirs.ensureUnusedCapacity(gpa, 1); + if (override_resource_dir) |resource_dir| { + comp.system_include_dirs.appendAssumeCapacity(try std.fs.path.join(gpa, &.{ resource_dir, "include" })); + return; + } var search_path = aro_dir; while (std.fs.path.dirname(search_path)) |dirname| : (search_path = dirname) { var base_dir = comp.cwd.openDir(dirname, .{}) catch continue; defer base_dir.close(); base_dir.access("include/stddef.h", .{}) catch continue; - const path = try std.fs.path.join(comp.gpa, &.{ dirname, "include" }); - errdefer comp.gpa.free(path); - try comp.system_include_dirs.append(comp.gpa, path); + comp.system_include_dirs.appendAssumeCapacity(try std.fs.path.join(gpa, &.{ dirname, "include" })); break; } else return error.AroIncludeNotFound; } diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 901e7d2..9f23868 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -48,6 +48,7 @@ inputs: std.ArrayListUnmanaged(Source) = .{}, link_objects: std.ArrayListUnmanaged([]const u8) = .{}, output_name: ?[]const u8 = null, sysroot: ?[]const u8 = null, +resource_dir: ?[]const u8 = null, system_defines: Compilation.SystemDefinesMode = .include_system_defines, temp_file_count: u32 = 0, /// If false, do not emit line directives in -E mode @@ -195,6 +196,7 @@ pub const usage = \\ -mcmodel= Generate code for the given code model \\ -mkernel Enable kernel development mode \\ -nobuiltininc Do not search the compiler's builtin directory for include files + \\ -resource-dir Override the path to the compiler's builtin resource directory \\ -nostdinc, --no-standard-includes \\ Do not search the standard system directories or compiler builtin directories for include files. \\ -nostdlibinc Do not search the standard system directories for include files, but do search compiler builtin include directories @@ -567,6 +569,13 @@ pub fn parseArgs( d.nolibc = true; } else if (mem.eql(u8, arg, "-nobuiltininc")) { d.nobuiltininc = true; + } else if (mem.eql(u8, arg, "-resource-dir")) { + i += 1; + if (i >= args.len) { + try d.err("expected argument after -resource-dir", .{}); + continue; + } + d.resource_dir = args[i]; } else if (mem.eql(u8, arg, "-nostdinc") or mem.eql(u8, arg, "--no-standard-includes")) { d.nostdinc = true; } else if (mem.eql(u8, arg, "-nostdlibinc")) { diff --git a/src/aro/Toolchain.zig b/src/aro/Toolchain.zig index 0ff79b5..e6754a6 100644 --- a/src/aro/Toolchain.zig +++ b/src/aro/Toolchain.zig @@ -507,7 +507,7 @@ pub fn defineSystemIncludes(tc: *Toolchain) !void { const comp = tc.driver.comp; if (!tc.driver.nobuiltininc) { - try comp.addBuiltinIncludeDir(tc.driver.aro_name); + try comp.addBuiltinIncludeDir(tc.driver.aro_name, tc.driver.resource_dir); } if (!tc.driver.nostdlibinc) { diff --git a/src/aro/toolchains/Linux.zig b/src/aro/toolchains/Linux.zig index fafddd0..826e30b 100644 --- a/src/aro/toolchains/Linux.zig +++ b/src/aro/toolchains/Linux.zig @@ -374,7 +374,7 @@ pub fn defineSystemIncludes(self: *const Linux, tc: *const Toolchain) !void { // musl prefers /usr/include before builtin includes, so musl targets will add builtins // at the end of this function (unless disabled with nostdlibinc) if (!tc.driver.nobuiltininc and (!target.abi.isMusl() or tc.driver.nostdlibinc)) { - try comp.addBuiltinIncludeDir(tc.driver.aro_name); + try comp.addBuiltinIncludeDir(tc.driver.aro_name, tc.driver.resource_dir); } if (tc.driver.nostdlibinc) return; @@ -405,7 +405,7 @@ pub fn defineSystemIncludes(self: *const Linux, tc: *const Toolchain) !void { std.debug.assert(!tc.driver.nostdlibinc); if (!tc.driver.nobuiltininc and target.abi.isMusl()) { - try comp.addBuiltinIncludeDir(tc.driver.aro_name); + try comp.addBuiltinIncludeDir(tc.driver.aro_name, tc.driver.resource_dir); } } diff --git a/test/record_runner.zig b/test/record_runner.zig index 9dc6b56..7143d07 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -235,7 +235,7 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase defer comp.deinit(); try comp.addDefaultPragmaHandlers(); - try comp.addBuiltinIncludeDir(test_dir); + try comp.addBuiltinIncludeDir(test_dir, null); try setTarget(&comp, test_case.target); switch (comp.target.os.tag) { diff --git a/test/runner.zig b/test/runner.zig index 143b261..0253301 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -67,7 +67,7 @@ fn testOne(allocator: std.mem.Allocator, path: []const u8, test_dir: []const u8) defer comp.deinit(); try comp.addDefaultPragmaHandlers(); - try comp.addBuiltinIncludeDir(test_dir); + try comp.addBuiltinIncludeDir(test_dir, null); const file = try comp.addSourceFromPath(path); var macro_buf = std.ArrayList(u8).init(comp.gpa); @@ -187,7 +187,7 @@ pub fn main() !void { try initial_comp.include_dirs.append(gpa, cases_next_include_dir); try initial_comp.addDefaultPragmaHandlers(); - try initial_comp.addBuiltinIncludeDir(test_dir); + try initial_comp.addBuiltinIncludeDir(test_dir, null); // apparently we can't use setAstCwd without libc on windows yet const win = @import("builtin").os.tag == .windows; -- Gitee From 6ac189d3f4e39f4a456ad2798a4dc97f0db7f1ef Mon Sep 17 00:00:00 2001 From: mlugg Date: Mon, 16 Jun 2025 19:40:35 +0100 Subject: [PATCH 094/117] add `--embed-dir=` CLI option Clang has this option, and it's very easy for us to implement. It's like `-I`, but for `#embed` instead; normal include paths should not apply to `#embed` at all. --- src/aro/Compilation.zig | 42 +++++++++++++++++++++++++++++------------ src/aro/Driver.zig | 5 ++++- test/runner.zig | 3 +++ 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index a0ac8ac..f0179e9 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -127,6 +127,7 @@ environment: Environment = .{}, sources: std.StringArrayHashMapUnmanaged(Source) = .{}, include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, system_include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +embed_dirs: std.ArrayListUnmanaged([]const u8) = .{}, target: std.Target = @import("builtin").target, pragma_handlers: std.StringArrayHashMapUnmanaged(*Pragma) = .{}, langopts: LangOpts = .{}, @@ -176,6 +177,7 @@ pub fn deinit(comp: *Compilation) void { comp.include_dirs.deinit(comp.gpa); for (comp.system_include_dirs.items) |path| comp.gpa.free(path); comp.system_include_dirs.deinit(comp.gpa); + comp.embed_dirs.deinit(comp.gpa); comp.pragma_handlers.deinit(comp.gpa); comp.generated_buf.deinit(comp.gpa); comp.builtins.deinit(comp.gpa); @@ -1297,7 +1299,7 @@ pub fn hasInclude( }, .angle_brackets => null, }; - var it = IncludeDirIterator{ .comp = comp, .cwd_source_id = cwd_source_id }; + var it: IncludeDirIterator = .{ .comp = comp, .cwd_source_id = cwd_source_id }; if (which == .next) { it.skipUntilDirMatch(includer_token_source); } @@ -1359,19 +1361,35 @@ pub fn findEmbed( }; } - const cwd_source_id = switch (include_type) { - .quotes => includer_token_source, - .angle_brackets => null, - }; - var it = IncludeDirIterator{ .comp = comp, .cwd_source_id = cwd_source_id }; var stack_fallback = std.heap.stackFallback(path_buf_stack_limit, comp.gpa); const sf_allocator = stack_fallback.get(); - while (try it.nextWithFile(filename, sf_allocator)) |found| { - defer sf_allocator.free(found.path); - if (comp.getFileContents(found.path, limit)) |some| - return some - else |err| switch (err) { + switch (include_type) { + .quotes => { + const dir = std.fs.path.dirname(comp.getSource(includer_token_source).path) orelse "."; + const path = try std.fs.path.join(sf_allocator, &.{ dir, filename }); + defer sf_allocator.free(path); + if (comp.langopts.ms_extensions) { + std.mem.replaceScalar(u8, path, '\\', '/'); + } + if (comp.getFileContents(path, limit)) |some| { + return some; + } else |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => {}, + } + }, + .angle_brackets => {}, + } + for (comp.embed_dirs.items) |embed_dir| { + const path = try std.fs.path.join(sf_allocator, &.{ embed_dir, filename }); + defer sf_allocator.free(path); + if (comp.langopts.ms_extensions) { + std.mem.replaceScalar(u8, path, '\\', '/'); + } + if (comp.getFileContents(path, limit)) |some| { + return some; + } else |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => {}, } @@ -1405,7 +1423,7 @@ pub fn findInclude( }, .angle_brackets => null, }; - var it = IncludeDirIterator{ .comp = comp, .cwd_source_id = cwd_source_id }; + var it: IncludeDirIterator = .{ .comp = comp, .cwd_source_id = cwd_source_id }; if (which == .next) { it.skipUntilDirMatch(includer_token.source); diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 9f23868..14039ca 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -188,7 +188,8 @@ pub const usage = \\ -fno-use-line-directives \\ Use `# ` linemarkers in preprocessed output \\ -I Add directory to include search path - \\ -isystem Add directory to SYSTEM include search path + \\ -isystem Add directory to SYSTEM include search path + \\ --embed-dir= Add directory to `#embed` search path \\ --emulate=[clang|gcc|msvc] \\ Select which C compiler to emulate (default clang) \\ -mabicalls Enable SVR4-style position-independent code (Mips only) @@ -444,6 +445,8 @@ pub fn parseArgs( const duped = try d.comp.gpa.dupe(u8, path); errdefer d.comp.gpa.free(duped); try d.comp.system_include_dirs.append(d.comp.gpa, duped); + } else if (option(arg, "--embed-dir=")) |path| { + try d.comp.embed_dirs.append(d.comp.gpa, path); } else if (option(arg, "--emulate=")) |compiler_str| { const compiler = std.meta.stringToEnum(LangOpts.Compiler, compiler_str) orelse { try d.err("invalid compiler '{s}'", .{arg}); diff --git a/test/runner.zig b/test/runner.zig index 0253301..45101e9 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -180,11 +180,13 @@ pub fn main() !void { defer gpa.free(cases_include_dir); try initial_comp.include_dirs.append(gpa, cases_include_dir); + try initial_comp.embed_dirs.append(gpa, cases_include_dir); const cases_next_include_dir = try std.fs.path.join(gpa, &.{ args[1], "include", "next" }); defer gpa.free(cases_next_include_dir); try initial_comp.include_dirs.append(gpa, cases_next_include_dir); + try initial_comp.embed_dirs.append(gpa, cases_next_include_dir); try initial_comp.addDefaultPragmaHandlers(); try initial_comp.addBuiltinIncludeDir(test_dir, null); @@ -212,6 +214,7 @@ pub fn main() !void { // preserve some values comp.include_dirs = .{}; comp.system_include_dirs = .{}; + comp.embed_dirs = .{}; comp.pragma_handlers = .{}; comp.environment = .{}; // reset everything else -- Gitee From a80492e85b9433293cd5ee8453826037cd19665c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 16 Jun 2025 23:41:16 +0300 Subject: [PATCH 095/117] Parser: fix array designators in offsetof builtins --- src/aro/Parser.zig | 36 ++++++++++++++++++++++++++++-------- test/cases/offsetof.c | 10 ++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 3c813fb..36e7043 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -7932,7 +7932,7 @@ fn builtinOffsetof(p: *Parser, builtin_tok: TokenIndex, offset_kind: OffsetKind) try p.expectClosing(l_paren, .r_paren); - return .{ + const res: Result = .{ .qt = p.comp.type_store.size, .val = offsetof_expr.val, .node = try p.addNode(.{ @@ -7943,6 +7943,8 @@ fn builtinOffsetof(p: *Parser, builtin_tok: TokenIndex, offset_kind: OffsetKind) }, }), }; + try res.putValue(p); + return res; } /// offsetofMemberDesignator : IDENTIFIER ('.' IDENTIFIER | '[' expr ']' )* @@ -7966,7 +7968,8 @@ fn offsetofMemberDesignator( var cur_offset: u64 = 0; var lhs = try p.fieldAccessExtra(base_node, base_record_ty, base_field_name, false, access_tok, &cur_offset); - var total_offset = cur_offset; + var total_offset: i64 = @intCast(cur_offset); + var runtime_offset = false; while (true) switch (p.tok_ids[p.tok_i]) { .period => { p.tok_i += 1; @@ -7979,7 +7982,7 @@ fn offsetofMemberDesignator( }; try p.validateFieldAccess(lhs_record_ty, lhs.qt, field_name_tok, field_name); lhs = try p.fieldAccessExtra(lhs.node, lhs_record_ty, field_name, false, access_tok, &cur_offset); - total_offset += cur_offset; + total_offset += @intCast(cur_offset); }, .l_bracket => { const l_bracket_tok = p.tok_i; @@ -7987,10 +7990,10 @@ fn offsetofMemberDesignator( var index = try p.expect(expr); _ = try p.expectClosing(l_bracket_tok, .r_bracket); - if (!lhs.qt.is(p.comp, .array)) { + const array_ty = lhs.qt.get(p.comp, .array) orelse { try p.err(l_bracket_tok, .offsetof_array, .{lhs.qt}); return error.ParsingFailed; - } + }; var ptr = lhs; try ptr.lvalConversion(p, l_bracket_tok); try index.lvalConversion(p, l_bracket_tok); @@ -8001,14 +8004,31 @@ fn offsetofMemberDesignator( try p.err(l_bracket_tok, .invalid_index, .{}); } + if (index.val.toInt(i64, p.comp)) |index_int| { + total_offset += @as(i64, @intCast(array_ty.elem.bitSizeof(p.comp))) * index_int; + } else { + runtime_offset = true; + } + try index.saveValue(p); - try ptr.bin(p, .array_access_expr, index, l_bracket_tok); + ptr.node = try p.addNode(.{ .array_access_expr = .{ + .l_bracket_tok = l_bracket_tok, + .base = ptr.node, + .index = index.node, + .qt = ptr.qt, + } }); lhs = ptr; }, else => break, }; - const val = try Value.int(if (offset_kind == .bits) total_offset else total_offset / 8, p.comp); - return .{ .qt = base_qt, .val = val, .node = lhs.node }; + return .{ + .qt = base_qt, + .val = if (runtime_offset) + .{} + else + try Value.int(if (offset_kind == .bits) total_offset else @divExact(total_offset, 8), p.comp), + .node = lhs.node, + }; } fn computeOffsetExtra(p: *Parser, node: Node.Index, offset_so_far: *Value) !Value { diff --git a/test/cases/offsetof.c b/test/cases/offsetof.c index d4f41af..f61668b 100644 --- a/test/cases/offsetof.c +++ b/test/cases/offsetof.c @@ -13,7 +13,17 @@ struct Foo { _Static_assert(__builtin_offsetof(struct Foo, a) == 0, "field Foo.a wrong bit offset"); _Static_assert(__builtin_bitoffsetof(struct Foo, a) == 0, "field Foo.a wrong bit offset"); +struct B { + int a[4]; +}; +void foo(void) { + int a = 2; + _Static_assert(__builtin_offsetof(struct B, a[a]) == 8ul, ""); +} +_Static_assert(__builtin_offsetof(struct B, a[2]) == 8ul, ""); + #define EXPECTED_ERRORS "offsetof.c:1:28: error: offsetof requires struct or union type, 'int' invalid" \ "offsetof.c:3:28: error: offsetof of incomplete type 'struct A'" \ "offsetof.c:7:38: error: no member named 'b' in 'struct A'" \ "offsetof.c:8:39: error: offsetof requires array type, 'int *' invalid" \ + "offsetof.c:21:20: error: static assertion expression is not an integral constant expression" \ -- Gitee From 51457183e62dd6cab016bc00da410b65e794e07f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 16 Jun 2025 23:42:08 +0300 Subject: [PATCH 096/117] Parser: audit isInt checks with complex types --- src/aro/Attribute.zig | 11 +++++-- src/aro/Parser.zig | 69 +++++++++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig index 9feda33..5628374 100644 --- a/src/aro/Attribute.zig +++ b/src/aro/Attribute.zig @@ -1053,10 +1053,15 @@ pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) if (func_ty.return_type.isPointer(p.comp)) { if (attr.args.alloc_align.position == 0 or attr.args.alloc_align.position > func_ty.params.len) { try p.err(tok, .attribute_param_out_of_bounds, .{ "alloc_align", 1 }); - } else if (!func_ty.params[attr.args.alloc_align.position - 1].qt.isInt(p.comp)) { - try p.err(tok, .alloc_align_required_int_param, .{}); } else { - try p.attr_application_buf.append(p.gpa, attr); + const arg_qt = func_ty.params[attr.args.alloc_align.position - 1].qt; + if (arg_qt.isInvalid()) continue; + const arg_sk = arg_qt.scalarKind(p.comp); + if (!arg_sk.isInt() or !arg_sk.isReal()) { + try p.err(tok, .alloc_align_required_int_param, .{}); + } else { + try p.attr_application_buf.append(p.gpa, attr); + } } } else { try p.err(tok, .alloc_align_requires_ptr_return, .{}); diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 36e7043..cd5bba9 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -2664,7 +2664,9 @@ fn recordDecl(p: *Parser) Error!bool { if (p.eatToken(.colon)) |_| bits: { const bits_tok = p.tok_i; const res = try p.integerConstExpr(.gnu_folding_extension); - if (!qt.isInt(p.comp)) { + + const field_sk = qt.scalarKind(p.comp); + if (!field_sk.isInt() or !field_sk.isReal()) { try p.err(first_tok, .non_int_bitfield, .{qt}); break :bits; } @@ -2855,7 +2857,7 @@ fn enumSpec(p: *Parser) Error!QualType { }; const fixed_sk = fixed.scalarKind(p.comp); - if (fixed_sk == .@"enum" or !fixed_sk.isInt()) { + if (fixed_sk == .@"enum" or !fixed_sk.isInt() or !fixed_sk.isReal()) { try p.err(ty_start, .invalid_type_underlying_enum, .{fixed}); break :fixed null; } @@ -3662,9 +3664,12 @@ fn directDeclarator( // array type from here. base_declarator.declarator_type = .array; - if (opt_size != null and !opt_size.?.qt.isInt(p.comp)) { - try p.err(size_tok, .array_size_non_int, .{opt_size.?.qt}); - return error.ParsingFailed; + if (opt_size) |size| { + const size_sk = size.qt.scalarKind(p.comp); + if (!size_sk.isInt() or !size_sk.isReal()) { + try p.err(size_tok, .array_size_non_int, .{opt_size.?.qt}); + return error.ParsingFailed; + } } if (opt_size) |size| { @@ -5026,8 +5031,13 @@ fn stmt(p: *Parser) Error!Node.Index { try cond.lvalConversion(p, cond_tok); try cond.usualUnaryConversion(p, cond_tok); - if (!cond.qt.isInvalid() and !cond.qt.isInt(p.comp)) - try p.err(l_paren + 1, .statement_int, .{cond.qt}); + // Switch condition can't be complex. + if (!cond.qt.isInvalid()) { + const sk = cond.qt.scalarKind(p.comp); + if (!sk.isInt() or !sk.isReal()) + try p.err(l_paren + 1, .statement_int, .{cond.qt}); + } + try cond.saveValue(p); try p.expectClosing(l_paren, .r_paren); @@ -5184,7 +5194,8 @@ fn stmt(p: *Parser) Error!Node.Index { .child = .{ .@"const" = true, ._index = .void }, .decayed = null, } }); - if (!goto_expr.qt.isInt(p.comp)) { + const expr_sk = goto_expr.qt.scalarKind(p.comp); + if (!expr_sk.isInt() or !expr_sk.isReal()) { try p.err(expr_tok, .incompatible_arg, .{ goto_expr.qt, result_qt }); return error.ParsingFailed; } @@ -6737,7 +6748,7 @@ pub const Result = struct { if (dest_sk.isFloat() and src_sk.isPointer()) { try p.err(l_paren, .invalid_cast_to_float, .{dest_qt}); return error.ParsingFailed; - } else if (src_sk.isFloat() and dest_sk.isPointer()) { + } else if ((src_sk.isFloat() or !src_sk.isReal()) and dest_sk.isPointer()) { try p.err(l_paren, .invalid_cast_to_pointer, .{res.qt}); return error.ParsingFailed; } @@ -6991,7 +7002,7 @@ pub const Result = struct { return; } } else if (dest_sk.isInt()) { - if (res.qt.isInt(p.comp) or res.qt.isFloat(p.comp)) { + if (src_sk.isInt() or src_sk.isFloat()) { try res.castToInt(p, dest_unqual, tok); return; } else if (src_sk.isPointer()) { @@ -7002,7 +7013,7 @@ pub const Result = struct { return; } } else if (dest_sk.isFloat()) { - if (res.qt.isInt(p.comp) or res.qt.isFloat(p.comp)) { + if (src_sk.isInt() or src_sk.isFloat()) { try res.castToFloat(p, dest_unqual, tok); return; } @@ -7262,9 +7273,12 @@ fn assignExpr(p: *Parser) Error!?Result { fn integerConstExpr(p: *Parser, decl_folding: ConstDeclFoldingMode) Error!Result { const start = p.tok_i; const res = try p.constExpr(decl_folding); - if (!res.qt.isInvalid() and !res.qt.isInt(p.comp)) { - try p.err(start, .expected_integer_constant_expr, .{}); - return error.ParsingFailed; + if (!res.qt.isInvalid()) { + const res_sk = res.qt.scalarKind(p.comp); + if (!res_sk.isInt() or !res_sk.isReal()) { + try p.err(start, .expected_integer_constant_expr, .{}); + return error.ParsingFailed; + } } return res; } @@ -7998,7 +8012,8 @@ fn offsetofMemberDesignator( try ptr.lvalConversion(p, l_bracket_tok); try index.lvalConversion(p, l_bracket_tok); - if (index.qt.isInt(p.comp)) { + const index_sk = index.qt.scalarKind(p.comp); + if (index_sk.isInt() and index_sk.isReal()) { try p.checkArrayBounds(index, lhs, l_bracket_tok); } else { try p.err(l_bracket_tok, .invalid_index, .{}); @@ -8272,15 +8287,15 @@ fn unExpr(p: *Parser) Error!?Result { try operand.lvalConversion(p, tok); try operand.usualUnaryConversion(p, tok); const scalar_kind = operand.qt.scalarKind(p.comp); - if (scalar_kind.isInt()) { - if (operand.val.is(.int, p.comp)) { - operand.val = try operand.val.bitNot(operand.qt, p.comp); - } - } else if (!scalar_kind.isReal()) { + if (!scalar_kind.isReal()) { try p.err(tok, .complex_conj, .{operand.qt}); if (operand.val.is(.complex, p.comp)) { operand.val = try operand.val.complexConj(operand.qt, p.comp); } + } else if (scalar_kind.isInt()) { + if (operand.val.is(.int, p.comp)) { + operand.val = try operand.val.bitNot(operand.qt, p.comp); + } } else { try p.err(tok, .invalid_argument_un, .{operand.qt}); operand.val = .{}; @@ -8629,14 +8644,16 @@ fn suffixExpr(p: *Parser, lhs: Result) Error!?Result { try index.lvalConversion(p, l_bracket); if (ptr.qt.get(p.comp, .pointer)) |pointer_ty| { ptr.qt = pointer_ty.child; - if (index.qt.isInt(p.comp)) { + const index_sk = index.qt.scalarKind(p.comp); + if (index_sk.isInt() and index_sk.isReal()) { try p.checkArrayBounds(index_before_conversion, array_before_conversion, l_bracket); } else { try p.err(l_bracket, .invalid_index, .{}); } } else if (index.qt.get(p.comp, .pointer)) |pointer_ty| { index.qt = pointer_ty.child; - if (ptr.qt.isInt(p.comp)) { + const index_sk = ptr.qt.scalarKind(p.comp); + if (index_sk.isInt() and index_sk.isReal()) { try p.checkArrayBounds(array_before_conversion, index_before_conversion, l_bracket); } else { try p.err(l_bracket, .invalid_index, .{}); @@ -8645,7 +8662,8 @@ fn suffixExpr(p: *Parser, lhs: Result) Error!?Result { } else if (ptr.qt.get(p.comp, .vector)) |vector_ty| { ptr = array_before_conversion; ptr.qt = vector_ty.elem; - if (!index.qt.isInt(p.comp)) { + const index_sk = index.qt.scalarKind(p.comp); + if (!index_sk.isInt() or !index_sk.isReal()) { try p.err(l_bracket, .invalid_index, .{}); } } else { @@ -8812,14 +8830,15 @@ fn checkArithOverflowArg(p: *Parser, builtin_tok: TokenIndex, first_after: Token _ = builtin_tok; _ = first_after; if (idx <= 1) { - if (!arg.qt.isInt(p.comp)) { + const arg_sk = arg.qt.scalarKind(p.comp); + if (!arg_sk.isInt() or !arg_sk.isReal()) { return p.err(param_tok, .overflow_builtin_requires_int, .{arg.qt}); } } else if (idx == 2) { if (!arg.qt.isPointer(p.comp)) return p.err(param_tok, .overflow_result_requires_ptr, .{arg.qt}); const child = arg.qt.childType(p.comp); const child_sk = child.scalarKind(p.comp); - if (!child_sk.isInt() or child_sk == .bool or child_sk == .@"enum" or child.@"const") return p.err(param_tok, .overflow_result_requires_ptr, .{arg.qt}); + if (child_sk != .int or child.@"const") return p.err(param_tok, .overflow_result_requires_ptr, .{arg.qt}); } } -- Gitee From 685e3d8d75b0ea51fa9eb0b5831bbdf9d91ceff4 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 18 Jun 2025 19:37:11 +0300 Subject: [PATCH 097/117] QualType: add isRealInt helper function Follow up to 51457183e62dd6cab016bc00da410b65e794e07f --- src/aro/Parser.zig | 53 +++++++++++++++---------------------------- src/aro/TypeStore.zig | 5 ++++ 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index cd5bba9..ece73c4 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -2664,9 +2664,7 @@ fn recordDecl(p: *Parser) Error!bool { if (p.eatToken(.colon)) |_| bits: { const bits_tok = p.tok_i; const res = try p.integerConstExpr(.gnu_folding_extension); - - const field_sk = qt.scalarKind(p.comp); - if (!field_sk.isInt() or !field_sk.isReal()) { + if (!qt.isInvalid() and !qt.isRealInt(p.comp)) { try p.err(first_tok, .non_int_bitfield, .{qt}); break :bits; } @@ -3664,12 +3662,9 @@ fn directDeclarator( // array type from here. base_declarator.declarator_type = .array; - if (opt_size) |size| { - const size_sk = size.qt.scalarKind(p.comp); - if (!size_sk.isInt() or !size_sk.isReal()) { - try p.err(size_tok, .array_size_non_int, .{opt_size.?.qt}); - return error.ParsingFailed; - } + if (opt_size != null and !opt_size.?.qt.isInvalid() and !opt_size.?.qt.isRealInt(p.comp)) { + try p.err(size_tok, .array_size_non_int, .{opt_size.?.qt}); + return error.ParsingFailed; } if (opt_size) |size| { @@ -5032,10 +5027,8 @@ fn stmt(p: *Parser) Error!Node.Index { try cond.usualUnaryConversion(p, cond_tok); // Switch condition can't be complex. - if (!cond.qt.isInvalid()) { - const sk = cond.qt.scalarKind(p.comp); - if (!sk.isInt() or !sk.isReal()) - try p.err(l_paren + 1, .statement_int, .{cond.qt}); + if (!cond.qt.isInvalid() and !cond.qt.isRealInt(p.comp)) { + try p.err(l_paren + 1, .statement_int, .{cond.qt}); } try cond.saveValue(p); @@ -5194,8 +5187,7 @@ fn stmt(p: *Parser) Error!Node.Index { .child = .{ .@"const" = true, ._index = .void }, .decayed = null, } }); - const expr_sk = goto_expr.qt.scalarKind(p.comp); - if (!expr_sk.isInt() or !expr_sk.isReal()) { + if (!goto_expr.qt.isRealInt(p.comp)) { try p.err(expr_tok, .incompatible_arg, .{ goto_expr.qt, result_qt }); return error.ParsingFailed; } @@ -7273,12 +7265,9 @@ fn assignExpr(p: *Parser) Error!?Result { fn integerConstExpr(p: *Parser, decl_folding: ConstDeclFoldingMode) Error!Result { const start = p.tok_i; const res = try p.constExpr(decl_folding); - if (!res.qt.isInvalid()) { - const res_sk = res.qt.scalarKind(p.comp); - if (!res_sk.isInt() or !res_sk.isReal()) { - try p.err(start, .expected_integer_constant_expr, .{}); - return error.ParsingFailed; - } + if (!res.qt.isInvalid() and !res.qt.isRealInt(p.comp)) { + try p.err(start, .expected_integer_constant_expr, .{}); + return error.ParsingFailed; } return res; } @@ -8012,10 +8001,9 @@ fn offsetofMemberDesignator( try ptr.lvalConversion(p, l_bracket_tok); try index.lvalConversion(p, l_bracket_tok); - const index_sk = index.qt.scalarKind(p.comp); - if (index_sk.isInt() and index_sk.isReal()) { + if (!index.qt.isInvalid() and index.qt.isRealInt(p.comp)) { try p.checkArrayBounds(index, lhs, l_bracket_tok); - } else { + } else if (!index.qt.isInvalid()) { try p.err(l_bracket_tok, .invalid_index, .{}); } @@ -8644,16 +8632,14 @@ fn suffixExpr(p: *Parser, lhs: Result) Error!?Result { try index.lvalConversion(p, l_bracket); if (ptr.qt.get(p.comp, .pointer)) |pointer_ty| { ptr.qt = pointer_ty.child; - const index_sk = index.qt.scalarKind(p.comp); - if (index_sk.isInt() and index_sk.isReal()) { + if (index.qt.isRealInt(p.comp)) { try p.checkArrayBounds(index_before_conversion, array_before_conversion, l_bracket); } else { try p.err(l_bracket, .invalid_index, .{}); } } else if (index.qt.get(p.comp, .pointer)) |pointer_ty| { index.qt = pointer_ty.child; - const index_sk = ptr.qt.scalarKind(p.comp); - if (index_sk.isInt() and index_sk.isReal()) { + if (ptr.qt.isRealInt(p.comp)) { try p.checkArrayBounds(array_before_conversion, index_before_conversion, l_bracket); } else { try p.err(l_bracket, .invalid_index, .{}); @@ -8662,11 +8648,10 @@ fn suffixExpr(p: *Parser, lhs: Result) Error!?Result { } else if (ptr.qt.get(p.comp, .vector)) |vector_ty| { ptr = array_before_conversion; ptr.qt = vector_ty.elem; - const index_sk = index.qt.scalarKind(p.comp); - if (!index_sk.isInt() or !index_sk.isReal()) { + if (!index.qt.isRealInt(p.comp)) { try p.err(l_bracket, .invalid_index, .{}); } - } else { + } else if (!index.qt.isInvalid() and !ptr.qt.isInvalid()) { try p.err(l_bracket, .invalid_subscript, .{}); } @@ -8830,15 +8815,13 @@ fn checkArithOverflowArg(p: *Parser, builtin_tok: TokenIndex, first_after: Token _ = builtin_tok; _ = first_after; if (idx <= 1) { - const arg_sk = arg.qt.scalarKind(p.comp); - if (!arg_sk.isInt() or !arg_sk.isReal()) { + if (!arg.qt.isRealInt(p.comp)) { return p.err(param_tok, .overflow_builtin_requires_int, .{arg.qt}); } } else if (idx == 2) { if (!arg.qt.isPointer(p.comp)) return p.err(param_tok, .overflow_result_requires_ptr, .{arg.qt}); const child = arg.qt.childType(p.comp); - const child_sk = child.scalarKind(p.comp); - if (child_sk != .int or child.@"const") return p.err(param_tok, .overflow_result_requires_ptr, .{arg.qt}); + if (child.scalarKind(p.comp) != .int or child.@"const") return p.err(param_tok, .overflow_result_requires_ptr, .{arg.qt}); } } diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 6806172..039f885 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -949,6 +949,11 @@ pub const QualType = packed struct(u32) { return qt.scalarKind(comp).isInt(); } + pub fn isRealInt(qt: QualType, comp: *const Compilation) bool { + const sk = qt.scalarKind(comp); + return sk.isInt() and sk.isReal(); + } + // Prefer calling scalarKind directly if checking multiple kinds. pub fn isFloat(qt: QualType, comp: *const Compilation) bool { return qt.scalarKind(comp).isFloat(); -- Gitee From bd6c4641ec1a95d7f112931c32e25a7f7d50f18b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 19 Jun 2025 13:01:51 +0300 Subject: [PATCH 098/117] update to Zig 0.15.0-dev.847+850655f06 --- build.zig.zon | 2 +- src/aro/Value.zig | 46 ++++++++++++++++++++-------------------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index d768c5f..5ae9aa3 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ .fingerprint = 0x76501fb842f52025, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.0-dev.460+f4e9846bc", + .minimum_zig_version = "0.15.0-dev.847+850655f06", .dependencies = .{}, diff --git a/src/aro/Value.zig b/src/aro/Value.zig index e1494a2..911e001 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -155,37 +155,31 @@ pub fn floatToInt(v: *Value, dest_ty: QualType, comp: *Compilation) !FloatToIntC } else if (dest_ty.signedness(comp) == .unsigned and float_val < 0) { v.* = zero; return .out_of_range; - } - - const had_fraction = @rem(float_val, 1) != 0; - const is_negative = std.math.signbit(float_val); - const floored = @floor(@abs(float_val)); - - var rational = try std.math.big.Rational.init(comp.gpa); - defer rational.deinit(); - rational.setFloat(f128, floored) catch |err| switch (err) { - error.NonFiniteFloat => { - v.* = .{}; - return .overflow; - }, - error.OutOfMemory => return error.OutOfMemory, - }; - - // The float is reduced in rational.setFloat, so we assert that denominator is equal to one - const big_one = BigIntConst{ .limbs = &.{1}, .positive = true }; - assert(rational.q.toConst().eqlAbs(big_one)); - - if (is_negative) { - rational.negate(); + } else if (!std.math.isFinite(float_val)) { + v.* = .{}; + return .overflow; } const signedness = dest_ty.signedness(comp); const bits: usize = @intCast(dest_ty.bitSizeof(comp)); - // rational.p.truncate(rational.p.toConst(), signedness: Signedness, bit_count: usize) - const fits = rational.p.fitsInTwosComp(signedness, bits); - v.* = try intern(comp, .{ .int = .{ .big_int = rational.p.toConst() } }); - try rational.p.truncate(&rational.p, signedness, bits); + var big_int: std.math.big.int.Mutable = .{ + .limbs = try comp.gpa.alloc(std.math.big.Limb, @max( + std.math.big.int.calcLimbLen(float_val), + std.math.big.int.calcTwosCompLimbCount(bits), + )), + .len = undefined, + .positive = undefined, + }; + defer comp.gpa.free(big_int.limbs); + const had_fraction = switch (big_int.setFloat(float_val, .trunc)) { + .inexact => true, + .exact => false, + }; + + const fits = big_int.toConst().fitsInTwosComp(signedness, bits); + v.* = try intern(comp, .{ .int = .{ .big_int = big_int.toConst() } }); + big_int.truncate(big_int.toConst(), signedness, bits); if (!was_zero and v.isZero(comp)) return .nonzero_to_zero; if (!fits) return .out_of_range; -- Gitee From b2d78d76f5e034ecd34811290c186896257bc48d Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 19 Jun 2025 13:14:06 +0100 Subject: [PATCH 099/117] Compilation: add an arena This is very helpful to have for things like include directories; it avoids needing to manage that memory manually, which is more difficult and bug-prone, and (negligibly) slower. This arena replaces the toolchain arena. System include paths are now stored in this arena instead of `gpa`. --- src/aro/Builtins.zig | 19 +++++----- src/aro/Compilation.zig | 65 +++++++++++++++++++++------------- src/aro/Driver.zig | 4 +-- src/aro/Driver/GCCDetector.zig | 9 ++--- src/aro/Parser.zig | 6 +++- src/aro/Preprocessor.zig | 32 ++++++++++------- src/aro/Tokenizer.zig | 8 +++-- src/aro/Toolchain.zig | 20 +++++------ src/aro/Value.zig | 12 +++++-- src/aro/toolchains/Linux.zig | 6 ++-- src/main.zig | 4 +-- test/fuzz/fuzz_lib.zig | 7 ++-- test/record_runner.zig | 11 +++--- test/runner.zig | 22 ++++++------ 14 files changed, 136 insertions(+), 89 deletions(-) diff --git a/src/aro/Builtins.zig b/src/aro/Builtins.zig index e76a8a8..0a2eed4 100644 --- a/src/aro/Builtins.zig +++ b/src/aro/Builtins.zig @@ -339,20 +339,19 @@ test Iterator { } test "All builtins" { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + var comp = Compilation.init(std.testing.allocator, arena, undefined, std.fs.cwd()); defer comp.deinit(); try comp.type_store.initNamedTypes(&comp); comp.type_store.va_list = try comp.type_store.va_list.decay(&comp); - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - - const name_arena = arena.allocator(); - var builtin_it = Iterator{}; while (builtin_it.next()) |entry| { - const name = try name_arena.dupe(u8, entry.name); + const name = try arena.dupe(u8, entry.name); if (try comp.builtins.getOrCreate(&comp, name)) |func_ty| { const get_again = (try comp.builtins.getOrCreate(&comp, name)).?; const found_by_lookup = comp.builtins.lookup(name); @@ -365,7 +364,11 @@ test "All builtins" { test "Allocation failures" { const Test = struct { fn testOne(allocator: std.mem.Allocator) !void { - var comp = Compilation.init(allocator, undefined, std.fs.cwd()); + var arena_state: std.heap.ArenaAllocator = .init(allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + var comp = Compilation.init(allocator, arena, undefined, std.fs.cwd()); defer comp.deinit(); _ = try comp.generateBuiltinMacros(.include_system_defines); diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index f0179e9..f3d8235 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -120,13 +120,18 @@ pub const Environment = struct { const Compilation = @This(); gpa: Allocator, +/// Allocations in this arena live all the way until `Compilation.deinit`. +arena: Allocator, diagnostics: *Diagnostics, code_gen_options: CodeGenOptions = .default, environment: Environment = .{}, sources: std.StringArrayHashMapUnmanaged(Source) = .{}, +/// Allocated into `gpa`, but keys are externally managed. include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +/// Allocated into `gpa`, but keys are externally managed. system_include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +/// Allocated into `gpa`, but keys are externally managed. embed_dirs: std.ArrayListUnmanaged([]const u8) = .{}, target: std.Target = @import("builtin").target, pragma_handlers: std.StringArrayHashMapUnmanaged(*Pragma) = .{}, @@ -141,9 +146,10 @@ type_store: TypeStore = .{}, ms_cwd_source_id: ?Source.Id = null, cwd: std.fs.Dir, -pub fn init(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation { +pub fn init(gpa: Allocator, arena: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilation { return .{ .gpa = gpa, + .arena = arena, .diagnostics = diagnostics, .cwd = cwd, }; @@ -151,9 +157,10 @@ pub fn init(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) Compilat /// Initialize Compilation with default environment, /// pragma handlers and emulation mode set to target. -pub fn initDefault(gpa: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) !Compilation { +pub fn initDefault(gpa: Allocator, arena: Allocator, diagnostics: *Diagnostics, cwd: std.fs.Dir) !Compilation { var comp: Compilation = .{ .gpa = gpa, + .arena = arena, .diagnostics = diagnostics, .environment = try Environment.loadAll(gpa), .cwd = cwd, @@ -175,7 +182,6 @@ pub fn deinit(comp: *Compilation) void { } comp.sources.deinit(comp.gpa); comp.include_dirs.deinit(comp.gpa); - for (comp.system_include_dirs.items) |path| comp.gpa.free(path); comp.system_include_dirs.deinit(comp.gpa); comp.embed_dirs.deinit(comp.gpa); comp.pragma_handlers.deinit(comp.gpa); @@ -975,9 +981,10 @@ pub fn getCharSignedness(comp: *const Compilation) std.builtin.Signedness { /// Add built-in aro headers directory to system include paths pub fn addBuiltinIncludeDir(comp: *Compilation, aro_dir: []const u8, override_resource_dir: ?[]const u8) !void { const gpa = comp.gpa; + const arena = comp.arena; try comp.system_include_dirs.ensureUnusedCapacity(gpa, 1); if (override_resource_dir) |resource_dir| { - comp.system_include_dirs.appendAssumeCapacity(try std.fs.path.join(gpa, &.{ resource_dir, "include" })); + comp.system_include_dirs.appendAssumeCapacity(try std.fs.path.join(arena, &.{ resource_dir, "include" })); return; } var search_path = aro_dir; @@ -986,15 +993,13 @@ pub fn addBuiltinIncludeDir(comp: *Compilation, aro_dir: []const u8, override_re defer base_dir.close(); base_dir.access("include/stddef.h", .{}) catch continue; - comp.system_include_dirs.appendAssumeCapacity(try std.fs.path.join(gpa, &.{ dirname, "include" })); + comp.system_include_dirs.appendAssumeCapacity(try std.fs.path.join(arena, &.{ dirname, "include" })); break; } else return error.AroIncludeNotFound; } pub fn addSystemIncludeDir(comp: *Compilation, path: []const u8) !void { - const duped = try comp.gpa.dupe(u8, path); - errdefer comp.gpa.free(duped); - try comp.system_include_dirs.append(comp.gpa, duped); + try comp.system_include_dirs.append(comp.gpa, try comp.arena.dupe(u8, path)); } pub fn getSource(comp: *const Compilation, id: Source.Id) Source { @@ -1586,8 +1591,10 @@ pub const Diagnostic = struct { test "addSourceFromReader" { const Test = struct { fn addSourceFromReader(str: []const u8, expected: []const u8, warning_count: u32, splices: []const u32) !void { + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); var buf_reader = std.io.fixedBufferStream(str); @@ -1599,8 +1606,10 @@ test "addSourceFromReader" { } fn withAllocationFailures(allocator: std.mem.Allocator) !void { + var arena: std.heap.ArenaAllocator = .init(allocator); + defer arena.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(allocator, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n"); @@ -1638,12 +1647,15 @@ test "addSourceFromReader" { } test "addSourceFromReader - exhaustive check for carriage return elimination" { + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const alphabet = [_]u8{ '\r', '\n', ' ', '\\', 'a' }; const alen = alphabet.len; var buf: [alphabet.len]u8 = [1]u8{alphabet[0]} ** alen; var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); var source_count: u32 = 0; @@ -1668,11 +1680,10 @@ test "addSourceFromReader - exhaustive check for carriage return elimination" { test "ignore BOM at beginning of file" { const BOM = "\xEF\xBB\xBF"; - const Test = struct { - fn run(buf: []const u8) !void { + fn run(arena: Allocator, buf: []const u8) !void { var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena, &diagnostics, std.fs.cwd()); defer comp.deinit(); var buf_reader = std.io.fixedBufferStream(buf); @@ -1682,15 +1693,19 @@ test "ignore BOM at beginning of file" { } }; - try Test.run(BOM); - try Test.run(BOM ++ "x"); - try Test.run("x" ++ BOM); - try Test.run(BOM ++ " "); - try Test.run(BOM ++ "\n"); - try Test.run(BOM ++ "\\"); - - try Test.run(BOM[0..1] ++ "x"); - try Test.run(BOM[0..2] ++ "x"); - try Test.run(BOM[1..] ++ "x"); - try Test.run(BOM[2..] ++ "x"); + var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + try Test.run(arena, BOM); + try Test.run(arena, BOM ++ "x"); + try Test.run(arena, "x" ++ BOM); + try Test.run(arena, BOM ++ " "); + try Test.run(arena, BOM ++ "\n"); + try Test.run(arena, BOM ++ "\\"); + + try Test.run(arena, BOM[0..1] ++ "x"); + try Test.run(arena, BOM[0..2] ++ "x"); + try Test.run(arena, BOM[1..] ++ "x"); + try Test.run(arena, BOM[2..] ++ "x"); } diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 14039ca..5d9436e 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -442,9 +442,7 @@ pub fn parseArgs( } path = args[i]; } - const duped = try d.comp.gpa.dupe(u8, path); - errdefer d.comp.gpa.free(duped); - try d.comp.system_include_dirs.append(d.comp.gpa, duped); + try d.comp.system_include_dirs.append(d.comp.gpa, path); } else if (option(arg, "--embed-dir=")) |path| { try d.comp.embed_dirs.append(d.comp.gpa, path); } else if (option(arg, "--emulate=")) |compiler_str| { diff --git a/src/aro/Driver/GCCDetector.zig b/src/aro/Driver/GCCDetector.zig index 38168dd..b1f187b 100644 --- a/src/aro/Driver/GCCDetector.zig +++ b/src/aro/Driver/GCCDetector.zig @@ -52,7 +52,7 @@ fn addDefaultGCCPrefixes(prefixes: *std.ArrayListUnmanaged([]const u8), tc: *con if (sysroot.len == 0) { prefixes.appendAssumeCapacity("/usr"); } else { - var usr_path = try tc.arena.alloc(u8, 4 + sysroot.len); + var usr_path = try tc.driver.comp.arena.alloc(u8, 4 + sysroot.len); @memcpy(usr_path[0..4], "/usr"); @memcpy(usr_path[4..], sysroot); prefixes.appendAssumeCapacity(usr_path); @@ -584,6 +584,7 @@ fn scanLibDirForGCCTriple( ) !void { var path_buf: [std.fs.max_path_bytes]u8 = undefined; var fib = std.heap.FixedBufferAllocator.init(&path_buf); + const arena = tc.driver.comp.arena; for (0..2) |i| { if (i == 0 and !gcc_dir_exists) continue; if (i == 1 and !gcc_cross_dir_exists) continue; @@ -616,9 +617,9 @@ fn scanLibDirForGCCTriple( if (!try self.scanGCCForMultilibs(tc, target, .{ dir_name, version_text }, needs_biarch_suffix)) continue; self.version = candidate_version; - self.gcc_triple = try tc.arena.dupe(u8, candidate_triple); - self.install_path = try std.fs.path.join(tc.arena, &.{ lib_dir, lib_suffix, version_text }); - self.parent_lib_path = try std.fs.path.join(tc.arena, &.{ self.install_path, "..", "..", ".." }); + self.gcc_triple = try arena.dupe(u8, candidate_triple); + self.install_path = try std.fs.path.join(arena, &.{ lib_dir, lib_suffix, version_text }); + self.parent_lib_path = try std.fs.path.join(arena, &.{ self.install_path, "..", "..", ".." }); self.is_valid = true; } } diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index ece73c4..0d4c7a1 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -10111,8 +10111,12 @@ fn genericSelection(p: *Parser) Error!?Result { } test "Node locations" { + var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(std.testing.allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena, &diagnostics, std.fs.cwd()); defer comp.deinit(); const file = try comp.addSourceFromBuffer("file.c", diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 5dda462..682e720 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -3573,14 +3573,17 @@ pub fn prettyPrintTokens(pp: *Preprocessor, w: anytype, macro_dump_mode: DumpMod } test "Preserve pragma tokens sometimes" { - const allocator = std.testing.allocator; + const gpa = std.testing.allocator; const Test = struct { fn runPreprocessor(source_text: []const u8) ![]const u8 { - var buf = std.ArrayList(u8).init(allocator); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + + var buf = std.ArrayList(u8).init(gpa); defer buf.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(gpa, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -3595,12 +3598,12 @@ test "Preserve pragma tokens sometimes" { const eof = try pp.preprocess(test_runner_macros); try pp.addToken(eof); try pp.prettyPrintTokens(buf.writer(), .result_only); - return allocator.dupe(u8, buf.items); + return gpa.dupe(u8, buf.items); } fn check(source_text: []const u8, expected: []const u8) !void { const output = try runPreprocessor(source_text); - defer allocator.free(output); + defer gpa.free(output); try std.testing.expectEqualStrings(expected, output); } @@ -3631,7 +3634,7 @@ test "Preserve pragma tokens sometimes" { } test "destringify" { - const allocator = std.testing.allocator; + const gpa = std.testing.allocator; const Test = struct { fn testDestringify(pp: *Preprocessor, stringified: []const u8, destringified: []const u8) !void { pp.char_buf.clearRetainingCapacity(); @@ -3640,8 +3643,10 @@ test "destringify" { try std.testing.expectEqualStrings(destringified, pp.char_buf.items); } }; + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(gpa, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); var pp = Preprocessor.init(&comp, .default); defer pp.deinit(); @@ -3698,19 +3703,22 @@ test "Include guards" { }; } - fn testIncludeGuard(allocator: std.mem.Allocator, comptime template: []const u8, tok_id: RawToken.Id, expected_guards: u32) !void { + fn testIncludeGuard(gpa: std.mem.Allocator, comptime template: []const u8, tok_id: RawToken.Id, expected_guards: u32) !void { + var arena_state: std.heap.ArenaAllocator = .init(gpa); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + var diagnostics: Diagnostics = .{ .output = .ignore }; - var comp = Compilation.init(allocator, &diagnostics, std.fs.cwd()); + var comp = Compilation.init(gpa, arena, &diagnostics, std.fs.cwd()); defer comp.deinit(); var pp = Preprocessor.init(&comp, .default); defer pp.deinit(); - const path = try std.fs.path.join(allocator, &.{ ".", "bar.h" }); - defer allocator.free(path); + const path = try std.fs.path.join(arena, &.{ ".", "bar.h" }); _ = try comp.addSourceFromBuffer(path, "int bar = 5;\n"); - var buf = std.ArrayList(u8).init(allocator); + var buf = std.ArrayList(u8).init(gpa); defer buf.deinit(); var writer = buf.writer(); diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 13c1d88..7fa3aba 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -2317,7 +2317,9 @@ test "Universal character names" { test "Tokenizer fuzz test" { const Context = struct { fn testOne(_: @This(), input_bytes: []const u8) anyerror!void { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + var comp = Compilation.init(std.testing.allocator, arena.allocator(), undefined, std.fs.cwd()); defer comp.deinit(); const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes); @@ -2339,7 +2341,9 @@ test "Tokenizer fuzz test" { } fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, standard: ?LangOpts.Standard) !void { - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + var comp = Compilation.init(std.testing.allocator, arena.allocator(), undefined, std.fs.cwd()); defer comp.deinit(); if (standard) |provided| { comp.langopts.standard = provided; diff --git a/src/aro/Toolchain.zig b/src/aro/Toolchain.zig index e6754a6..a011ea6 100644 --- a/src/aro/Toolchain.zig +++ b/src/aro/Toolchain.zig @@ -52,7 +52,6 @@ const Toolchain = @This(); filesystem: Filesystem, driver: *Driver, -arena: mem.Allocator, /// The list of toolchain specific path prefixes to search for libraries. library_paths: PathList = .{}, @@ -218,7 +217,7 @@ pub fn addFilePathLibArgs(tc: *const Toolchain, argv: *std.ArrayList([]const u8) for (tc.file_paths.items) |path| { bytes_needed += path.len + 2; // +2 for `-L` } - var bytes = try tc.arena.alloc(u8, bytes_needed); + var bytes = try tc.driver.comp.arena.alloc(u8, bytes_needed); var index: usize = 0; for (tc.file_paths.items) |path| { @memcpy(bytes[index..][0..2], "-L"); @@ -265,6 +264,7 @@ pub fn getFilePath(tc: *const Toolchain, name: []const u8) ![]const u8 { var path_buf: [std.fs.max_path_bytes]u8 = undefined; var fib = std.heap.FixedBufferAllocator.init(&path_buf); const allocator = fib.allocator(); + const arena = tc.driver.comp.arena; const sysroot = tc.getSysroot(); @@ -273,15 +273,15 @@ pub fn getFilePath(tc: *const Toolchain, name: []const u8) ![]const u8 { const aro_dir = std.fs.path.dirname(tc.driver.aro_name) orelse ""; const candidate = try std.fs.path.join(allocator, &.{ aro_dir, "..", name }); if (tc.filesystem.exists(candidate)) { - return tc.arena.dupe(u8, candidate); + return arena.dupe(u8, candidate); } if (tc.searchPaths(&fib, sysroot, tc.library_paths.items, name)) |path| { - return tc.arena.dupe(u8, path); + return arena.dupe(u8, path); } if (tc.searchPaths(&fib, sysroot, tc.file_paths.items, name)) |path| { - return try tc.arena.dupe(u8, path); + return try arena.dupe(u8, path); } return name; @@ -312,7 +312,7 @@ const PathKind = enum { program, }; -/// Join `components` into a path. If the path exists, dupe it into the toolchain arena and +/// Join `components` into a path. If the path exists, dupe it into the Compilation arena and /// add it to the specified path list. pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void { var path_buf: [std.fs.max_path_bytes]u8 = undefined; @@ -321,7 +321,7 @@ pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind const candidate = try std.fs.path.join(fib.allocator(), components); if (tc.filesystem.exists(candidate)) { - const duped = try tc.arena.dupe(u8, candidate); + const duped = try tc.driver.comp.arena.dupe(u8, candidate); const dest = switch (dest_kind) { .library => &tc.library_paths, .file => &tc.file_paths, @@ -331,10 +331,10 @@ pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind } } -/// Join `components` using the toolchain arena and add the resulting path to `dest_kind`. Does not check +/// Join `components` using the Compilation arena and add the resulting path to `dest_kind`. Does not check /// whether the path actually exists pub fn addPathFromComponents(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void { - const full_path = try std.fs.path.join(tc.arena, components); + const full_path = try std.fs.path.join(tc.driver.comp.arena, components); const dest = switch (dest_kind) { .library => &tc.library_paths, .file => &tc.file_paths, @@ -344,7 +344,7 @@ pub fn addPathFromComponents(tc: *Toolchain, components: []const []const u8, des } /// Add linker args to `argv`. Does not add path to linker executable as first item; that must be handled separately -/// Items added to `argv` will be string literals or owned by `tc.arena` so they must not be individually freed +/// Items added to `argv` will be string literals or owned by `tc.driver.comp.arena` so they must not be individually freed pub fn buildLinkerArgs(tc: *Toolchain, argv: *std.ArrayList([]const u8)) !void { return switch (tc.inner) { .uninitialized => unreachable, diff --git a/src/aro/Value.zig b/src/aro/Value.zig index 911e001..cc49862 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -76,7 +76,11 @@ test "minUnsignedBits" { } }; - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + var comp = Compilation.init(std.testing.allocator, arena, undefined, std.fs.cwd()); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = try std.zig.system.resolveTargetQuery(target_query); @@ -111,7 +115,11 @@ test "minSignedBits" { } }; - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + var comp = Compilation.init(std.testing.allocator, arena, undefined, std.fs.cwd()); defer comp.deinit(); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); comp.target = try std.zig.system.resolveTargetQuery(target_query); diff --git a/src/aro/toolchains/Linux.zig b/src/aro/toolchains/Linux.zig index 826e30b..78d8218 100644 --- a/src/aro/toolchains/Linux.zig +++ b/src/aro/toolchains/Linux.zig @@ -216,7 +216,7 @@ pub fn buildLinkerArgs(self: *const Linux, tc: *const Toolchain, argv: *std.Arra const dynamic_linker = d.comp.target.standardDynamicLinkerPath(); // todo: check for --dyld-prefix if (dynamic_linker.get()) |path| { - try argv.appendSlice(&.{ "-dynamic-linker", try tc.arena.dupe(u8, path) }); + try argv.appendSlice(&.{ "-dynamic-linker", try d.comp.arena.dupe(u8, path) }); } else { try d.err("Could not find dynamic linker path", .{}); } @@ -416,7 +416,7 @@ test Linux { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - var comp = Compilation.init(std.testing.allocator, undefined, std.fs.cwd()); + var comp = Compilation.init(std.testing.allocator, arena, undefined, std.fs.cwd()); defer comp.deinit(); comp.environment = .{ .path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", @@ -436,7 +436,7 @@ test Linux { try driver.link_objects.append(driver.comp.gpa, link_obj); driver.temp_file_count += 1; - var toolchain: Toolchain = .{ .driver = &driver, .arena = arena, .filesystem = .{ .fake = &.{ + var toolchain: Toolchain = .{ .driver = &driver, .filesystem = .{ .fake = &.{ .{ .path = "/tmp" }, .{ .path = "/usr" }, .{ .path = "/usr/lib64" }, diff --git a/src/main.zig b/src/main.zig index bc4cb5c..dcf6a1a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -47,7 +47,7 @@ pub fn main() u8 { } }, }; - var comp = Compilation.initDefault(gpa, &diagnostics, std.fs.cwd()) catch |er| switch (er) { + var comp = Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |er| switch (er) { error.OutOfMemory => { std.debug.print("out of memory\n", .{}); if (fast_exit) process.exit(1); @@ -59,7 +59,7 @@ pub fn main() u8 { var driver: Driver = .{ .comp = &comp, .aro_name = aro_name, .diagnostics = &diagnostics }; defer driver.deinit(); - var toolchain: Toolchain = .{ .driver = &driver, .arena = arena, .filesystem = .{ .real = comp.cwd } }; + var toolchain: Toolchain = .{ .driver = &driver, .filesystem = .{ .real = comp.cwd } }; defer toolchain.deinit(); driver.main(&toolchain, args, fast_exit, assembly_backend.genAsm) catch |er| switch (er) { diff --git a/test/fuzz/fuzz_lib.zig b/test/fuzz/fuzz_lib.zig index 33c4cc0..ba361fd 100644 --- a/test/fuzz/fuzz_lib.zig +++ b/test/fuzz/fuzz_lib.zig @@ -21,20 +21,21 @@ export fn compile_c_buf(buf: [*]const u8, len: c_int) void { } fn compileSlice(buf: []const u8) !void { - var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + var fixed_allocator = std.heap.FixedBufferAllocator.init(&fixed_buffer_mem); const allocator = fixed_allocator.allocator(); const aro_dir = try std.fs.selfExePathAlloc(allocator); defer allocator.free(aro_dir); - var comp = Compilation.init(allocator, std.fs.cwd()); + // FBA is a valid "arena" because leaks are OK and FBA doesn't tend to reuse memory anyway. + var comp = Compilation.init(allocator, allocator, std.fs.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); try comp.addSystemIncludeDir(aro_dir); - const builtin = try comp.generateBuiltinMacrosFromPath(.include_system_defines, user_source.path); const user_source = try comp.addSourceFromBuffer("", buf); + const builtin = try comp.generateBuiltinMacrosFromPath(.include_system_defines, user_source.path); try processSource(&comp, builtin, user_source); _ = comp.sources.swapRemove(user_source.path); diff --git a/test/record_runner.zig b/test/record_runner.zig index 7143d07..c7fb70f 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -221,17 +221,20 @@ fn runTestCases(allocator: std.mem.Allocator, test_dir: []const u8, wg: *std.Thr } } -fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, stats: *Stats) !void { +fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, stats: *Stats) !void { const path = test_case.path; + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var diagnostics: aro.Diagnostics = .{ .output = .{ .to_list = .{ - .arena = .init(alloc), + .arena = .init(gpa), } }, }; defer diagnostics.deinit(); - var comp = aro.Compilation.init(alloc, &diagnostics, std.fs.cwd()); + var comp = aro.Compilation.init(gpa, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -255,7 +258,7 @@ fn singleRun(alloc: std.mem.Allocator, test_dir: []const u8, test_case: TestCase else => {}, } - var case_name = std.ArrayList(u8).init(alloc); + var case_name = std.ArrayList(u8).init(gpa); defer case_name.deinit(); const test_name = std.mem.sliceTo(std.fs.path.basename(path), '_'); diff --git a/test/runner.zig b/test/runner.zig index 45101e9..9e65d7e 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -62,8 +62,11 @@ fn addCommandLineArgs(comp: *aro.Compilation, file: aro.Source, macro_buf: anyty return .{ only_preprocess, line_markers, system_defines, dump_mode }; } -fn testOne(allocator: std.mem.Allocator, path: []const u8, test_dir: []const u8) !void { - var comp = aro.Compilation.init(allocator, std.fs.cwd()); +fn testOne(gpa: std.mem.Allocator, path: []const u8, test_dir: []const u8) !void { + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + + var comp = aro.Compilation.init(gpa, arena.allocator(), std.fs.cwd()); defer comp.deinit(); try comp.addDefaultPragmaHandlers(); @@ -121,8 +124,11 @@ pub fn main() !void { const gpa = general_purpose_allocator.allocator(); defer if (general_purpose_allocator.deinit() == .leak) std.process.exit(1); - const args = try std.process.argsAlloc(gpa); - defer std.process.argsFree(gpa, args); + var arena_state: std.heap.ArenaAllocator = .init(gpa); + defer arena_state.deinit(); + const arena = arena_state.allocator(); + + const args = try std.process.argsAlloc(arena); if (args.len != 3) { print("expected test case directory and zig executable as only arguments\n", .{}); @@ -134,7 +140,6 @@ pub fn main() !void { var buf = std.ArrayList(u8).init(gpa); var cases = std.ArrayList([]const u8).init(gpa); defer { - for (cases.items) |path| gpa.free(path); cases.deinit(); buf.deinit(); } @@ -151,10 +156,7 @@ pub fn main() !void { print("skipping non file entry '{s}'\n", .{entry.name}); continue; } - - defer buf.items.len = 0; - try buf.writer().print("{s}{c}{s}", .{ args[1], std.fs.path.sep, entry.name }); - try cases.append(try gpa.dupe(u8, buf.items)); + try cases.append(try std.fmt.allocPrint(arena, "{s}{c}{s}", .{ args[1], std.fs.path.sep, entry.name })); } } if (build_options.test_all_allocation_failures) { @@ -173,7 +175,7 @@ pub fn main() !void { defer diagnostics.deinit(); // prepare compiler - var initial_comp = aro.Compilation.init(gpa, &diagnostics, std.fs.cwd()); + var initial_comp = aro.Compilation.init(gpa, arena, &diagnostics, std.fs.cwd()); defer initial_comp.deinit(); const cases_include_dir = try std.fs.path.join(gpa, &.{ args[1], "include" }); -- Gitee From a4cfc18de3a6286da5d7a07918864c3ccbea20c8 Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 19 Jun 2025 15:37:55 +0100 Subject: [PATCH 100/117] implement macOS framework paths Three new command line options are implementated: * `-F` to pass a framework directory * `-iframework` to pass a SYSTEM framework directory * `-idirafter` to pass an AFTER include directory (this isn't related to frameworks but it was a desirable and easy change) macOS framework directories are kind of just... weirder include directories. If you pass `-I path`, then `#include ` looks for `path/Foo/Bar.h`. On the other hand, if you pass `-F path`, then `#include ` looks for `path/Foo.framework/Headers/Bar.h`. This is a relatively easy expansion to do. `-idirafter` are just include directories but considered after system includes. I checked the full search order by running `clang empty.c -v` while passing a bunch of different include dir args, and the order is: * include dirs (`-I`) * framework dirs (`-F`) * SYSTEM include dirs (`-isystem`) * SYSTEM framework dirs (`-iframework`) * AFTER include dirs (`-idirafter`) Clang does have a few other flags like this; I've not implemented those for now. Notably, we now support all of the include-like Clang flags which the Zig build system has specific support for. Of course, in terms of Aro as a standalone C compiler, `-F` and `-iframework` also have implications in terms of library lookup, but my current focus is on the frontend, for ziglang/translate-c. --- src/aro/Compilation.zig | 344 ++++++++++-------- src/aro/Driver.zig | 52 ++- .../SimpleFramework.framework/Headers/Foo.h | 1 + test/cases/include framework.c | 2 + test/runner.zig | 14 +- 5 files changed, 254 insertions(+), 159 deletions(-) create mode 100644 test/cases/frameworks/SimpleFramework.framework/Headers/Foo.h create mode 100644 test/cases/include framework.c diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index f3d8235..3eeca37 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -128,11 +128,17 @@ code_gen_options: CodeGenOptions = .default, environment: Environment = .{}, sources: std.StringArrayHashMapUnmanaged(Source) = .{}, /// Allocated into `gpa`, but keys are externally managed. -include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +include_dirs: std.ArrayListUnmanaged([]const u8) = .empty, /// Allocated into `gpa`, but keys are externally managed. -system_include_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +system_include_dirs: std.ArrayListUnmanaged([]const u8) = .empty, /// Allocated into `gpa`, but keys are externally managed. -embed_dirs: std.ArrayListUnmanaged([]const u8) = .{}, +after_include_dirs: std.ArrayListUnmanaged([]const u8) = .empty, +/// Allocated into `gpa`, but keys are externally managed. +framework_dirs: std.ArrayListUnmanaged([]const u8) = .empty, +/// Allocated into `gpa`, but keys are externally managed. +system_framework_dirs: std.ArrayListUnmanaged([]const u8) = .empty, +/// Allocated into `gpa`, but keys are externally managed. +embed_dirs: std.ArrayListUnmanaged([]const u8) = .empty, target: std.Target = @import("builtin").target, pragma_handlers: std.StringArrayHashMapUnmanaged(*Pragma) = .{}, langopts: LangOpts = .{}, @@ -172,25 +178,29 @@ pub fn initDefault(gpa: Allocator, arena: Allocator, diagnostics: *Diagnostics, } pub fn deinit(comp: *Compilation) void { + const gpa = comp.gpa; for (comp.pragma_handlers.values()) |pragma| { pragma.deinit(pragma, comp); } for (comp.sources.values()) |source| { - comp.gpa.free(source.path); - comp.gpa.free(source.buf); - comp.gpa.free(source.splice_locs); + gpa.free(source.path); + gpa.free(source.buf); + gpa.free(source.splice_locs); } - comp.sources.deinit(comp.gpa); - comp.include_dirs.deinit(comp.gpa); - comp.system_include_dirs.deinit(comp.gpa); - comp.embed_dirs.deinit(comp.gpa); - comp.pragma_handlers.deinit(comp.gpa); - comp.generated_buf.deinit(comp.gpa); - comp.builtins.deinit(comp.gpa); - comp.string_interner.deinit(comp.gpa); - comp.interner.deinit(comp.gpa); - comp.environment.deinit(comp.gpa); - comp.type_store.deinit(comp.gpa); + comp.sources.deinit(gpa); + comp.include_dirs.deinit(gpa); + comp.system_include_dirs.deinit(gpa); + comp.after_include_dirs.deinit(gpa); + comp.framework_dirs.deinit(gpa); + comp.system_framework_dirs.deinit(gpa); + comp.embed_dirs.deinit(gpa); + comp.pragma_handlers.deinit(gpa); + comp.generated_buf.deinit(gpa); + comp.builtins.deinit(gpa); + comp.string_interner.deinit(gpa); + comp.interner.deinit(gpa); + comp.environment.deinit(gpa); + comp.type_store.deinit(gpa); comp.* = undefined; } @@ -1221,103 +1231,175 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin return comp.addSourceFromOwnedBuffer(contents, path, kind); } -pub const IncludeDirIterator = struct { - comp: *const Compilation, - cwd_source_id: ?Source.Id, - include_dirs_idx: usize = 0, - sys_include_dirs_idx: usize = 0, - tried_ms_cwd: bool = false, +pub fn hasInclude( + comp: *Compilation, + filename: []const u8, + includer_token_source: Source.Id, + /// angle bracket vs quotes + include_type: IncludeType, + /// __has_include vs __has_include_next + which: WhichInclude, +) Compilation.Error!bool { + if (try FindInclude.run(comp, filename, switch (which) { + .next => .{ .only_search_after_dir = comp.getSource(includer_token_source).path }, + .first => switch (include_type) { + .quotes => .{ .allow_same_dir = comp.getSource(includer_token_source).path }, + .angle_brackets => .only_search, + }, + })) |_| { + return true; + } else { + return false; + } +} + +const FindInclude = struct { + comp: *Compilation, + include_path: []const u8, + /// We won't actually consider any include directories until after this directory. + wait_for: ?[]const u8, - const FoundSource = struct { - path: []const u8, + const Result = struct { + source: Source.Id, kind: Source.Kind, + used_ms_search_rule: bool, }; - fn next(self: *IncludeDirIterator) ?FoundSource { - if (self.cwd_source_id) |source_id| { - self.cwd_source_id = null; - const path = self.comp.getSource(source_id).path; - return .{ .path = std.fs.path.dirname(path) orelse ".", .kind = .user }; + fn run( + comp: *Compilation, + include_path: []const u8, + search_strat: union(enum) { + allow_same_dir: []const u8, + only_search, + only_search_after_dir: []const u8, + }, + ) Allocator.Error!?Result { + var find: FindInclude = .{ + .comp = comp, + .include_path = include_path, + .wait_for = null, + }; + + if (std.fs.path.isAbsolute(include_path)) { + switch (search_strat) { + .allow_same_dir, .only_search => {}, + .only_search_after_dir => return null, + } + return find.check("{s}", .{include_path}, .user, false); } - if (self.include_dirs_idx < self.comp.include_dirs.items.len) { - defer self.include_dirs_idx += 1; - return .{ .path = self.comp.include_dirs.items[self.include_dirs_idx], .kind = .user }; + + switch (search_strat) { + .allow_same_dir => |other_file| { + const dir = std.fs.path.dirname(other_file) orelse "."; + if (try find.checkIncludeDir(dir, .user)) |res| return res; + }, + .only_search => {}, + .only_search_after_dir => |other_file| { + // TODO: this is not the correct interpretation of `#include_next` and friends, + // because a file might not be directly inside of an include directory. To implement + // this correctly, we will need to track which include directory a file has been + // included from. + find.wait_for = std.fs.path.dirname(other_file); + }, } - if (self.sys_include_dirs_idx < self.comp.system_include_dirs.items.len) { - defer self.sys_include_dirs_idx += 1; - return .{ .path = self.comp.system_include_dirs.items[self.sys_include_dirs_idx], .kind = .system }; + + for (comp.include_dirs.items) |dir| { + if (try find.checkIncludeDir(dir, .user)) |res| return res; } - if (self.comp.ms_cwd_source_id) |source_id| { - if (self.tried_ms_cwd) return null; - self.tried_ms_cwd = true; - const path = self.comp.getSource(source_id).path; - return .{ .path = std.fs.path.dirname(path) orelse ".", .kind = .user }; + for (comp.framework_dirs.items) |dir| { + if (try find.checkFrameworkDir(dir, .user)) |res| return res; } - return null; - } - - /// Returned value's path field must be freed by allocator - fn nextWithFile(self: *IncludeDirIterator, filename: []const u8, allocator: Allocator) !?FoundSource { - while (self.next()) |found| { - const path = try std.fs.path.join(allocator, &.{ found.path, filename }); - if (self.comp.langopts.ms_extensions) { - std.mem.replaceScalar(u8, path, '\\', '/'); - } - return .{ .path = path, .kind = found.kind }; + for (comp.system_include_dirs.items) |dir| { + if (try find.checkIncludeDir(dir, .system)) |res| return res; + } + for (comp.system_framework_dirs.items) |dir| { + if (try find.checkFrameworkDir(dir, .system)) |res| return res; + } + for (comp.after_include_dirs.items) |dir| { + if (try find.checkIncludeDir(dir, .user)) |res| return res; + } + if (comp.ms_cwd_source_id) |source_id| { + if (try find.checkMsCwdIncludeDir(source_id)) |res| return res; } return null; } - - /// Advance the iterator until it finds an include directory that matches - /// the directory which contains `source`. - fn skipUntilDirMatch(self: *IncludeDirIterator, source: Source.Id) void { - const path = self.comp.getSource(source).path; - const includer_path = std.fs.path.dirname(path) orelse "."; - while (self.next()) |found| { - if (mem.eql(u8, includer_path, found.path)) break; + fn checkIncludeDir(find: *FindInclude, include_dir: []const u8, kind: Source.Kind) Allocator.Error!?Result { + if (find.wait_for) |wait_for| { + if (std.mem.eql(u8, include_dir, wait_for)) find.wait_for = null; + return null; } + return find.check("{s}{c}{s}", .{ + include_dir, + std.fs.path.sep, + find.include_path, + }, kind, false); } -}; - -pub fn hasInclude( - comp: *const Compilation, - filename: []const u8, - includer_token_source: Source.Id, - /// angle bracket vs quotes - include_type: IncludeType, - /// __has_include vs __has_include_next - which: WhichInclude, -) !bool { - if (mem.indexOfScalar(u8, filename, 0) != null) { - return false; - } - - if (std.fs.path.isAbsolute(filename)) { - if (which == .next) return false; - return !std.meta.isError(comp.cwd.access(filename, .{})); + fn checkMsCwdIncludeDir(find: *FindInclude, source_id: Source.Id) Allocator.Error!?Result { + const path = find.comp.getSource(source_id).path; + const dir = std.fs.path.dirname(path) orelse "."; + if (find.wait_for) |wait_for| { + if (std.mem.eql(u8, dir, wait_for)) find.wait_for = null; + return null; + } + return find.check("{s}{c}{s}", .{ + dir, + std.fs.path.sep, + find.include_path, + }, .user, true); } - - const cwd_source_id = switch (include_type) { - .quotes => switch (which) { - .first => includer_token_source, - .next => null, - }, - .angle_brackets => null, - }; - var it: IncludeDirIterator = .{ .comp = comp, .cwd_source_id = cwd_source_id }; - if (which == .next) { - it.skipUntilDirMatch(includer_token_source); + fn checkFrameworkDir(find: *FindInclude, framework_dir: []const u8, kind: Source.Kind) Allocator.Error!?Result { + if (find.wait_for) |wait_for| { + match: { + // If this is a match, then `wait_for` looks like '.../Foo.framework/Headers'. + const wait_framework = std.fs.path.dirname(wait_for) orelse break :match; + const wait_framework_dir = std.fs.path.dirname(wait_framework) orelse break :match; + if (!std.mem.eql(u8, framework_dir, wait_framework_dir)) break :match; + find.wait_for = null; + } + return null; + } + // For an include like 'Foo/Bar.h', search in '/Foo.framework/Headers/Bar.h'. + const framework_name: []const u8, const header_sub_path: []const u8 = f: { + const i = std.mem.indexOfScalar(u8, find.include_path, '/') orelse return null; + break :f .{ find.include_path[0..i], find.include_path[i + 1 ..] }; + }; + return find.check("{s}{c}{s}.framework{c}Headers{c}{s}", .{ + framework_dir, + std.fs.path.sep, + framework_name, + std.fs.path.sep, + std.fs.path.sep, + header_sub_path, + }, kind, false); } + fn check( + find: *FindInclude, + comptime format: []const u8, + args: anytype, + kind: Source.Kind, + used_ms_search_rule: bool, + ) Allocator.Error!?Result { + const comp = find.comp; - var stack_fallback = std.heap.stackFallback(path_buf_stack_limit, comp.gpa); - const sf_allocator = stack_fallback.get(); + var stack_fallback = std.heap.stackFallback(path_buf_stack_limit, comp.gpa); + const sfa = stack_fallback.get(); + const header_path = try std.fmt.allocPrint(sfa, format, args); + defer sfa.free(header_path); - while (try it.nextWithFile(filename, sf_allocator)) |found| { - defer sf_allocator.free(found.path); - if (!std.meta.isError(comp.cwd.access(found.path, .{}))) return true; + if (find.comp.langopts.ms_extensions) { + std.mem.replaceScalar(u8, header_path, '\\', '/'); + } + const source = comp.addSourceFromPathExtra(header_path, kind) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => return null, + }; + return .{ + .source = source.id, + .kind = kind, + .used_ms_search_rule = used_ms_search_rule, + }; } - return false; -} +}; pub const WhichInclude = enum { first, @@ -1410,57 +1492,29 @@ pub fn findInclude( include_type: IncludeType, /// include vs include_next which: WhichInclude, -) !?Source { - if (std.fs.path.isAbsolute(filename)) { - if (which == .next) return null; - // TODO: classify absolute file as belonging to system includes or not? - return if (comp.addSourceFromPath(filename)) |some| - some - else |err| switch (err) { - error.OutOfMemory => |e| return e, - else => null, - }; - } - const cwd_source_id = switch (include_type) { - .quotes => switch (which) { - .first => includer_token.source, - .next => null, +) Compilation.Error!?Source { + const found = try FindInclude.run(comp, filename, switch (which) { + .next => .{ .only_search_after_dir = comp.getSource(includer_token.source).path }, + .first => switch (include_type) { + .quotes => .{ .allow_same_dir = comp.getSource(includer_token.source).path }, + .angle_brackets => .only_search, }, - .angle_brackets => null, - }; - var it: IncludeDirIterator = .{ .comp = comp, .cwd_source_id = cwd_source_id }; - - if (which == .next) { - it.skipUntilDirMatch(includer_token.source); + }) orelse return null; + if (found.used_ms_search_rule) { + const diagnostic: Diagnostic = .ms_search_rule; + try comp.diagnostics.add(.{ + .text = diagnostic.fmt, + .kind = diagnostic.kind, + .opt = diagnostic.opt, + .extension = diagnostic.extension, + .location = (Source.Location{ + .id = includer_token.source, + .byte_offset = includer_token.start, + .line = includer_token.line, + }).expand(comp), + }); } - - var stack_fallback = std.heap.stackFallback(path_buf_stack_limit, comp.gpa); - const sf_allocator = stack_fallback.get(); - - while (try it.nextWithFile(filename, sf_allocator)) |found| { - defer sf_allocator.free(found.path); - if (comp.addSourceFromPathExtra(found.path, found.kind)) |some| { - if (it.tried_ms_cwd) { - const diagnostic: Diagnostic = .ms_search_rule; - try comp.diagnostics.add(.{ - .text = diagnostic.fmt, - .kind = diagnostic.kind, - .opt = diagnostic.opt, - .extension = diagnostic.extension, - .location = (Source.Location{ - .id = includer_token.source, - .byte_offset = includer_token.start, - .line = includer_token.line, - }).expand(comp), - }); - } - return some; - } else |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => {}, - } - } - return null; + return comp.getSource(found.source); } pub fn addPragmaHandler(comp: *Compilation, name: []const u8, handler: *Pragma) Allocator.Error!void { diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 5d9436e..5875420 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -188,7 +188,10 @@ pub const usage = \\ -fno-use-line-directives \\ Use `# ` linemarkers in preprocessed output \\ -I Add directory to include search path + \\ -idirafter Add directory to AFTER include search path \\ -isystem Add directory to SYSTEM include search path + \\ -F Add directory to macOS framework search path + \\ -iframework Add directory to SYSTEM macOS framework search path \\ --embed-dir= Add directory to `#embed` search path \\ --emulate=[clang|gcc|msvc] \\ Select which C compiler to emulate (default clang) @@ -413,6 +416,14 @@ pub fn parseArgs( ms_extensions = true; } else if (mem.eql(u8, arg, "-fno-ms-extensions")) { ms_extensions = false; + } else if (mem.startsWith(u8, arg, "-fsyntax-only")) { + d.only_syntax = true; + } else if (mem.startsWith(u8, arg, "-fno-syntax-only")) { + d.only_syntax = false; + } else if (mem.eql(u8, arg, "-fgnuc-version=")) { + gnuc_version = "0"; + } else if (option(arg, "-fgnuc-version=")) |version| { + gnuc_version = version; } else if (mem.startsWith(u8, arg, "-I")) { var path = arg["-I".len..]; if (path.len == 0) { @@ -424,14 +435,17 @@ pub fn parseArgs( path = args[i]; } try d.comp.include_dirs.append(d.comp.gpa, path); - } else if (mem.startsWith(u8, arg, "-fsyntax-only")) { - d.only_syntax = true; - } else if (mem.startsWith(u8, arg, "-fno-syntax-only")) { - d.only_syntax = false; - } else if (mem.eql(u8, arg, "-fgnuc-version=")) { - gnuc_version = "0"; - } else if (option(arg, "-fgnuc-version=")) |version| { - gnuc_version = version; + } else if (mem.startsWith(u8, arg, "-idirafter")) { + var path = arg["-idirafter".len..]; + if (path.len == 0) { + i += 1; + if (i >= args.len) { + try d.err("expected argument after -idirafter", .{}); + continue; + } + path = args[i]; + } + try d.comp.after_include_dirs.append(d.comp.gpa, path); } else if (mem.startsWith(u8, arg, "-isystem")) { var path = arg["-isystem".len..]; if (path.len == 0) { @@ -443,6 +457,28 @@ pub fn parseArgs( path = args[i]; } try d.comp.system_include_dirs.append(d.comp.gpa, path); + } else if (mem.startsWith(u8, arg, "-F")) { + var path = arg["-F".len..]; + if (path.len == 0) { + i += 1; + if (i >= args.len) { + try d.err("expected argument after -F", .{}); + continue; + } + path = args[i]; + } + try d.comp.framework_dirs.append(d.comp.gpa, path); + } else if (mem.startsWith(u8, arg, "-iframework")) { + var path = arg["-iframework".len..]; + if (path.len == 0) { + i += 1; + if (i >= args.len) { + try d.err("expected argument after -iframework", .{}); + continue; + } + path = args[i]; + } + try d.comp.system_framework_dirs.append(d.comp.gpa, path); } else if (option(arg, "--embed-dir=")) |path| { try d.comp.embed_dirs.append(d.comp.gpa, path); } else if (option(arg, "--emulate=")) |compiler_str| { diff --git a/test/cases/frameworks/SimpleFramework.framework/Headers/Foo.h b/test/cases/frameworks/SimpleFramework.framework/Headers/Foo.h new file mode 100644 index 0000000..a5d2ba8 --- /dev/null +++ b/test/cases/frameworks/SimpleFramework.framework/Headers/Foo.h @@ -0,0 +1 @@ +#define SIMPLE_FRAMEWORK_FOO 123 diff --git a/test/cases/include framework.c b/test/cases/include framework.c new file mode 100644 index 0000000..2133aec --- /dev/null +++ b/test/cases/include framework.c @@ -0,0 +1,2 @@ +#include +_Static_assert(SIMPLE_FRAMEWORK_FOO == 123, "macro from framework not accessible"); diff --git a/test/runner.zig b/test/runner.zig index 9e65d7e..99fe838 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -178,18 +178,17 @@ pub fn main() !void { var initial_comp = aro.Compilation.init(gpa, arena, &diagnostics, std.fs.cwd()); defer initial_comp.deinit(); - const cases_include_dir = try std.fs.path.join(gpa, &.{ args[1], "include" }); - defer gpa.free(cases_include_dir); - + const cases_include_dir = try std.fs.path.join(arena, &.{ args[1], "include" }); try initial_comp.include_dirs.append(gpa, cases_include_dir); try initial_comp.embed_dirs.append(gpa, cases_include_dir); - const cases_next_include_dir = try std.fs.path.join(gpa, &.{ args[1], "include", "next" }); - defer gpa.free(cases_next_include_dir); - + const cases_next_include_dir = try std.fs.path.join(arena, &.{ args[1], "include", "next" }); try initial_comp.include_dirs.append(gpa, cases_next_include_dir); try initial_comp.embed_dirs.append(gpa, cases_next_include_dir); + const cases_frameworks_dir = try std.fs.path.join(arena, &.{ args[1], "frameworks" }); + try initial_comp.framework_dirs.append(gpa, cases_frameworks_dir); + try initial_comp.addDefaultPragmaHandlers(); try initial_comp.addBuiltinIncludeDir(test_dir, null); @@ -216,6 +215,9 @@ pub fn main() !void { // preserve some values comp.include_dirs = .{}; comp.system_include_dirs = .{}; + comp.after_include_dirs = .{}; + comp.framework_dirs = .{}; + comp.system_framework_dirs = .{}; comp.embed_dirs = .{}; comp.pragma_handlers = .{}; comp.environment = .{}; -- Gitee From da7abcfbe5f04366845aa64dc8efd7484d455244 Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 19 Jun 2025 18:22:47 +0100 Subject: [PATCH 101/117] TypeStore: add VaList for ARM The source is Zig's `std.builtin.VaList`, which uses exactly this logic for ARM. --- src/aro/TypeStore.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index 039f885..ba6ba54 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -2049,11 +2049,15 @@ fn generateNsConstantStringType(ts: *TypeStore, comp: *Compilation) !QualType { fn generateVaListType(ts: *TypeStore, comp: *Compilation) !QualType { const Kind = enum { aarch64_va_list, x86_64_va_list }; const kind: Kind = switch (comp.target.cpu.arch) { - .aarch64 => switch (comp.target.os.tag) { + .aarch64, .aarch64_be => switch (comp.target.os.tag) { .windows => return .char_pointer, .ios, .macos, .tvos, .watchos => return .char_pointer, else => .aarch64_va_list, }, + .arm, .armeb, .thumb, .thumbeb => switch (comp.target.os.tag) { + .ios, .macos, .tvos, .watchos, .visionos => return .char_pointer, + else => return .void_pointer, + }, .sparc, .wasm32, .wasm64, .bpfel, .bpfeb, .riscv32, .riscv64, .avr, .spirv32, .spirv64 => return .void_pointer, .powerpc => switch (comp.target.os.tag) { .ios, .macos, .tvos, .watchos, .aix => return .char_pointer, -- Gitee From 382c756f63a0dacfcd93c5f9ad56b10a009d24bd Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 19 Jun 2025 19:29:33 +0100 Subject: [PATCH 102/117] Preprocessor: escape `__FILE__` properly These backslashes weren't escaped on Windows, causing CI failures on translate-c. More generally, file paths can contain essentially any byte on e.g. POSIX systems, so this string *must* be escaped. --- src/aro/Preprocessor.zig | 45 ++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 682e720..ff344c2 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -1191,7 +1191,7 @@ fn expandObjMacro(pp: *Preprocessor, simple_macro: *const Macro) Error!ExpandBuf const start = pp.comp.generated_buf.items.len; const source = pp.comp.getSource(pp.expansion_source_loc.id); const w = pp.comp.generated_buf.writer(pp.gpa); - try w.print("\"{s}\"\n", .{source.path}); + try w.print("\"{}\"\n", .{fmtEscapes(source.path)}); buf.appendAssumeCapacity(try pp.makeGeneratedToken(start, .string_literal, tok)); }, @@ -3355,23 +3355,7 @@ fn printLinemarker( ) !void { try w.writeByte('#'); if (pp.linemarkers == .line_directives) try w.writeAll("line"); - try w.print(" {d} \"", .{line_no}); - for (source.path) |byte| switch (byte) { - '\n' => try w.writeAll("\\n"), - '\r' => try w.writeAll("\\r"), - '\t' => try w.writeAll("\\t"), - '\\' => try w.writeAll("\\\\"), - '"' => try w.writeAll("\\\""), - ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte), - // Use hex escapes for any non-ASCII/unprintable characters. - // This ensures that the parsed version of this string will end up - // containing the same bytes as the input regardless of encoding. - else => { - try w.writeAll("\\x"); - try std.fmt.formatInt(byte, 16, .lower, .{ .width = 2, .fill = '0' }, w); - }, - }; - try w.writeByte('"'); + try w.print(" {d} \"{}\"", .{ line_no, fmtEscapes(source.path) }); if (pp.linemarkers == .numeric_directives) { switch (start_resume) { .none => {}, @@ -3572,6 +3556,31 @@ pub fn prettyPrintTokens(pp: *Preprocessor, w: anytype, macro_dump_mode: DumpMod } } +/// Like `std.zig.fmtEscapes`, but for C strings. Hex escapes are used for any +/// non-ASCII/unprintable bytes to ensure that the string bytes do not change if +/// the encoding of the file is not UTF-8. +fn fmtEscapes(bytes: []const u8) FmtEscapes { + return .{ .bytes = bytes }; +} +const FmtEscapes = struct { + bytes: []const u8, + pub fn format(ctx: FmtEscapes, comptime fmt: []const u8, _: std.fmt.FormatOptions, w: anytype) !void { + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, ctx); + for (ctx.bytes) |byte| switch (byte) { + '\n' => try w.writeAll("\\n"), + '\r' => try w.writeAll("\\r"), + '\t' => try w.writeAll("\\t"), + '\\' => try w.writeAll("\\\\"), + '"' => try w.writeAll("\\\""), + ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte), + // Use hex escapes for any non-ASCII/unprintable characters. + // This ensures that the parsed version of this string will end up + // containing the same bytes as the input regardless of encoding. + else => try w.print("\\x{x:0>2}", .{byte}), + }; + } +}; + test "Preserve pragma tokens sometimes" { const gpa = std.testing.allocator; const Test = struct { -- Gitee From dc5d3c41ae31d8ac24982571e7fc1e267516cc68 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 25 Jun 2025 14:18:04 +0300 Subject: [PATCH 103/117] Compilation: update some feature detection macros --- src/aro/Compilation.zig | 50 ++++++++++++++++++++++++++++++++++++---- src/aro/Driver.zig | 8 +++---- src/aro/Preprocessor.zig | 6 ++++- src/aro/features.zig | 4 ++-- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 3eeca37..06d766b 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -291,7 +291,22 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { } }, .uefi => try define(w, "__UEFI__"), - .freebsd => try w.print("#define __FreeBSD__ {d}\n", .{comp.target.os.version_range.semver.min.major}), + .freebsd => { + const release = comp.target.os.version_range.semver.min.major; + const cc_version = release * 10_000 + 1; + try w.print( + \\#define __FreeBSD__ {d} + \\#define __FreeBSD_cc_version {d} + \\ + , .{ release, cc_version }); + }, + .ps4, .ps5 => { + try w.writeAll( + \\#define __FreeBSD__ 9 + \\#define __FreeBSD_cc_version 900001 + \\ + ); + }, .netbsd => try define(w, "__NetBSD__"), .openbsd => try define(w, "__OpenBSD__"), .dragonfly => try define(w, "__DragonFly__"), @@ -320,6 +335,8 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { .solaris, .aix, .emscripten, + .ps4, + .ps5, => try defineStd(w, "unix", is_gnu), .windows => if (comp.target.abi.isGnu() or comp.target.abi == .cygnus) { try defineStd(w, "unix", is_gnu); @@ -446,6 +463,10 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { try define(w, "__ILP32__"); } + if (comp.hasFloat128()) { + try define(w, "__FLOAT128__"); + } + try w.writeAll( \\#define __ORDER_LITTLE_ENDIAN__ 1234 \\#define __ORDER_BIG_ENDIAN__ 4321 @@ -621,14 +642,13 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi ); } - try buf.appendSlice("#define __STDC__ 1\n"); + if (comp.langopts.emulate != .msvc) { + try buf.appendSlice("#define __STDC__ 1\n"); + } try buf.writer().print("#define __STDC_HOSTED__ {d}\n", .{@intFromBool(comp.target.os.tag != .freestanding)}); // standard macros try buf.appendSlice( - \\#define __STDC_NO_COMPLEX__ 1 - \\#define __STDC_NO_THREADS__ 1 - \\#define __STDC_NO_VLA__ 1 \\#define __STDC_UTF_16__ 1 \\#define __STDC_UTF_32__ 1 \\#define __STDC_EMBED_NOT_FOUND__ 0 @@ -636,6 +656,26 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi \\#define __STDC_EMBED_EMPTY__ 2 \\ ); + if (comp.langopts.standard.atLeast(.c11)) switch (comp.target.os.tag) { + .openbsd, .driverkit, .ios, .macos, .tvos, .visionos, .watchos => { + try buf.appendSlice("#define __STDC_NO_THREADS__ 1\n"); + }, + .ps4, .ps5 => { + try buf.appendSlice( + \\#define __STDC_NO_THREADS__ 1 + \\#define __STDC_NO_COMPLEX__ 1 + \\ + ); + }, + .aix => { + try buf.appendSlice( + \\#define __STDC_NO_THREADS__ 1 + \\#define __STDC_NO_ATOMICS__ 1 + \\ + ); + }, + else => {}, + }; if (comp.langopts.standard.StdCVersionMacro()) |stdc_version| { try buf.appendSlice("#define __STDC_VERSION__ "); try buf.appendSlice(stdc_version); diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 5875420..05ecf3c 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -131,8 +131,8 @@ pub const usage = \\Usage {s}: [options] file.. \\ \\General options: - \\ -h, --help Print this message. - \\ -v, --version Print aro version. + \\ --help Print this message + \\ --version Print aro version \\ \\Compile options: \\ -c, --compile Only run preprocess, compile, and assemble steps @@ -266,12 +266,12 @@ pub fn parseArgs( while (i < args.len) : (i += 1) { const arg = args[i]; if (mem.startsWith(u8, arg, "-") and arg.len > 1) { - if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { + if (mem.eql(u8, arg, "--help")) { std_out.print(usage, .{args[0]}) catch |er| { return d.fatal("unable to print usage: {s}", .{errorDescription(er)}); }; return true; - } else if (mem.eql(u8, arg, "-v") or mem.eql(u8, arg, "--version")) { + } else if (mem.eql(u8, arg, "--version")) { std_out.writeAll(@import("backend").version_str ++ "\n") catch |er| { return d.fatal("unable to print version: {s}", .{errorDescription(er)}); }; diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index ff344c2..006866e 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -1569,7 +1569,11 @@ fn handleBuiltinMacro(pp: *Preprocessor, builtin: RawToken.Id, param_toks: []con false; }, .macro_param_has_feature => features.hasFeature(pp.comp, ident_str), - .macro_param_has_extension => features.hasExtension(pp.comp, ident_str), + // If -pedantic-errors is given __has_extension is equivalent to __has_feature + .macro_param_has_extension => if (pp.comp.diagnostics.state.extensions == .@"error") + features.hasFeature(pp.comp, ident_str) + else + features.hasExtension(pp.comp, ident_str), .macro_param_has_builtin => pp.comp.hasBuiltin(ident_str), else => unreachable, }; diff --git a/src/aro/features.zig b/src/aro/features.zig index fdc49b7..94d02ca 100644 --- a/src/aro/features.zig +++ b/src/aro/features.zig @@ -57,13 +57,13 @@ pub fn hasExtension(comp: *Compilation, ext: []const u8) bool { // C11 features .c_alignas = true, .c_alignof = true, - .c_atomic = false, // TODO + .c_atomic = true, .c_generic_selections = true, .c_static_assert = true, .c_thread_local = target_util.isTlsSupported(comp.target), // misc .overloadable_unmarked = false, // TODO - .statement_attributes_with_gnu_syntax = false, // TODO + .statement_attributes_with_gnu_syntax = true, .gnu_asm = true, .gnu_asm_goto_with_outputs = true, .matrix_types = false, // TODO -- Gitee From 1916d00fe055af0a33783378d76571cfc9561761 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 25 Jun 2025 14:20:12 +0300 Subject: [PATCH 104/117] update to Zig 0.15.0-dev.870+710632b45 --- build.zig.zon | 2 +- src/aro/target.zig | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 5ae9aa3..69fce25 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ .fingerprint = 0x76501fb842f52025, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.0-dev.847+850655f06", + .minimum_zig_version = "0.15.0-dev.870+710632b45", .dependencies = .{}, diff --git a/src/aro/target.zig b/src/aro/target.zig index c0c63ec..a38ca6c 100644 --- a/src/aro/target.zig +++ b/src/aro/target.zig @@ -494,7 +494,6 @@ pub fn get32BitArchVariant(target: std.Target) ?std.Target { .kalimba, .lanai, .wasm32, - .spirv, .spirv32, .loongarch32, .xtensa, @@ -566,7 +565,6 @@ pub fn get64BitArchVariant(target: std.Target) ?std.Target { .powerpcle => copy.cpu.arch = .powerpc64le, .riscv32 => copy.cpu.arch = .riscv64, .sparc => copy.cpu.arch = .sparc64, - .spirv => copy.cpu.arch = .spirv64, .spirv32 => copy.cpu.arch = .spirv64, .thumb => copy.cpu.arch = .aarch64, .thumbeb => copy.cpu.arch = .aarch64_be, @@ -621,7 +619,6 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { .xtensa => "xtensa", .nvptx => "nvptx", .nvptx64 => "nvptx64", - .spirv => "spirv", .spirv32 => "spirv32", .spirv64 => "spirv64", .lanai => "lanai", -- Gitee From 26f8bca5fd28747dd087719801474f3b14a5a300 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 26 Jun 2025 22:05:08 +0300 Subject: [PATCH 105/117] Tokenizer: preserve line comment at EOF --- src/aro/Tokenizer.zig | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 7fa3aba..1a06dcb 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -1861,7 +1861,10 @@ pub fn next(self: *Tokenizer) Token { } } else if (self.index == self.buf.len) { switch (state) { - .start, .line_comment => {}, + .start => {}, + .line_comment => if (self.langopts.preserve_comments) { + id = .comment; + }, .u, .u8, .U, .L, .identifier => id = Token.getTokenId(self.langopts, self.buf[start..self.index]), .extended_identifier => id = .extended_identifier, @@ -2235,6 +2238,15 @@ test "comments" { .hash, .identifier, }); + try expectTokensExtra( + \\//foo + \\void + \\//bar + , &.{ + .comment, .nl, + .keyword_void, .nl, + .comment, + }, .{ .preserve_comments = true }); } test "extended identifiers" { @@ -2277,7 +2289,7 @@ test "C23 keywords" { .keyword_c23_thread_local, .keyword_nullptr, .keyword_typeof_unqual, - }, .c23); + }, .{ .standard = .c23 }); } test "Universal character names" { @@ -2340,13 +2352,13 @@ test "Tokenizer fuzz test" { return std.testing.fuzz(Context{}, Context.testOne, .{}); } -fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, standard: ?LangOpts.Standard) !void { +fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, langopts: ?LangOpts) !void { var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); var comp = Compilation.init(std.testing.allocator, arena.allocator(), undefined, std.fs.cwd()); defer comp.deinit(); - if (standard) |provided| { - comp.langopts.standard = provided; + if (langopts) |provided| { + comp.langopts = provided; } const source = try comp.addSourceFromBuffer("path", contents); var tokenizer = Tokenizer{ -- Gitee From 965edf2a27eebfd6c33685be316df22982826b02 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 12 Jul 2025 01:43:55 +0300 Subject: [PATCH 106/117] update to writergate changes --- build.zig | 2 +- build.zig.zon | 2 +- build/GenerateDef.zig | 10 ++-- src/aro/Compilation.zig | 82 ++++++++++++++++--------------- src/aro/Diagnostics.zig | 16 +++--- src/aro/Driver.zig | 55 ++++++++++----------- src/aro/Parser.zig | 97 +++++++++++++++++-------------------- src/aro/Pragma.zig | 8 +-- src/aro/Preprocessor.zig | 69 +++++++++++++------------- src/aro/Tree.zig | 2 +- src/aro/TypeStore.zig | 12 ++--- src/aro/Value.zig | 8 +-- src/aro/pragmas/message.zig | 8 +-- src/aro/text_literal.zig | 16 +++--- src/backend/Ir.zig | 14 +++--- src/main.zig | 4 +- 16 files changed, 203 insertions(+), 202 deletions(-) diff --git a/build.zig b/build.zig index f344e6c..4bec254 100644 --- a/build.zig +++ b/build.zig @@ -100,7 +100,7 @@ pub fn build(b: *Build) !void { const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor); if (!aro_version.order(ancestor_ver).compare(.gte)) { - std.debug.print("Aro version '{}' must be greater than tagged ancestor '{}'\n", .{ aro_version, ancestor_ver }); + std.debug.print("Aro version '{f}' must be greater than tagged ancestor '{f}'\n", .{ aro_version, ancestor_ver }); std.process.exit(1); } diff --git a/build.zig.zon b/build.zig.zon index 69fce25..817aa07 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,7 @@ .fingerprint = 0x76501fb842f52025, // Changing this has security and trust implications. - .minimum_zig_version = "0.15.0-dev.870+710632b45", + .minimum_zig_version = "0.15.0-dev.1021+43fba5ea8", .dependencies = .{}, diff --git a/build/GenerateDef.zig b/build/GenerateDef.zig index 984f656..5dc9b2d 100644 --- a/build/GenerateDef.zig +++ b/build/GenerateDef.zig @@ -82,14 +82,14 @@ fn make(step: *Step, options: std.Build.Step.MakeOptions) !void { const sub_path_dirname = std.fs.path.dirname(sub_path).?; b.cache_root.handle.makePath(sub_path_dirname) catch |err| { - return step.fail("unable to make path '{}{s}': {s}", .{ + return step.fail("unable to make path '{f}{s}': {s}", .{ b.cache_root, sub_path_dirname, @errorName(err), }); }; const output = try self.generate(contents); b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = output }) catch |err| { - return step.fail("unable to write file '{}{s}': {s}", .{ + return step.fail("unable to write file '{f}{s}': {s}", .{ b.cache_root, sub_path, @errorName(err), }); }; @@ -198,7 +198,7 @@ fn generate(self: *GenerateDef, input: []const u8) ![]const u8 { if (self.kind == .named) { try writer.writeAll("pub const Tag = enum {\n"); for (values.keys()) |property| { - try writer.print(" {p},\n", .{std.zig.fmtId(property)}); + try writer.print(" {f},\n", .{std.zig.fmtIdFlags(property, .{ .allow_primitive = true })}); } try writer.writeAll( \\ @@ -248,7 +248,7 @@ fn generate(self: *GenerateDef, input: []const u8) ![]const u8 { \\pub const Tag = enum(u16) { ); for (values_array) |value| { - try writer.print(" {},\n", .{std.zig.fmtId(value.name)}); + try writer.print(" {f},\n", .{std.zig.fmtId(value.name)}); } try writer.writeAll( \\}; @@ -466,7 +466,7 @@ fn writeData(writer: anytype, values: []const Value) !void { try writer.print(" @setEvalBranchQuota({d});\n", .{values.len * 7}); try writer.writeAll(" break :blk [_]@This(){\n"); for (values) |value| { - try writer.print(" .{{ .tag = .{}, .properties = .{{", .{std.zig.fmtId(value.name)}); + try writer.print(" .{{ .tag = .{f}, .properties = .{{", .{std.zig.fmtId(value.name)}); for (value.properties, 0..) |property, j| { if (j != 0) try writer.writeByte(','); try writer.writeByte(' '); diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 06d766b..d5ab0ca 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -216,14 +216,14 @@ pub const SystemDefinesMode = enum { include_system_defines, }; -fn generateSystemDefines(comp: *Compilation, w: anytype) !void { +fn generateSystemDefines(comp: *Compilation, w: *std.io.Writer) !void { const define = struct { - fn define(_w: anytype, name: []const u8) !void { + fn define(_w: *std.io.Writer, name: []const u8) !void { try _w.print("#define {s} 1\n", .{name}); } }.define; const defineStd = struct { - fn defineStd(_w: anytype, name: []const u8, is_gnu: bool) !void { + fn defineStd(_w: *std.io.Writer, name: []const u8, is_gnu: bool) !void { if (is_gnu) { try _w.print("#define {s} 1\n", .{name}); } @@ -630,11 +630,19 @@ fn generateSystemDefines(comp: *Compilation, w: anytype) !void { pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) !Source { try comp.type_store.initNamedTypes(comp); - var buf = std.ArrayList(u8).init(comp.gpa); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(comp.gpa); + defer allocating.deinit(); + comp.writeBuiltinMacros(system_defines_mode, &allocating.writer) catch |err| switch (err) { + error.WriteFailed, error.OutOfMemory => return error.OutOfMemory, + }; + + return comp.addSourceFromBuffer("", allocating.getWritten()); +} + +fn writeBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode, w: *std.io.Writer) !void { if (system_defines_mode == .include_system_defines) { - try buf.appendSlice( + try w.writeAll( \\#define __VERSION__ "Aro ++ " " ++ @import("backend").version_str ++ "\"\n" ++ \\#define __Aro__ @@ -643,12 +651,12 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi } if (comp.langopts.emulate != .msvc) { - try buf.appendSlice("#define __STDC__ 1\n"); + try w.writeAll("#define __STDC__ 1\n"); } - try buf.writer().print("#define __STDC_HOSTED__ {d}\n", .{@intFromBool(comp.target.os.tag != .freestanding)}); + try w.print("#define __STDC_HOSTED__ {d}\n", .{@intFromBool(comp.target.os.tag != .freestanding)}); // standard macros - try buf.appendSlice( + try w.writeAll( \\#define __STDC_UTF_16__ 1 \\#define __STDC_UTF_32__ 1 \\#define __STDC_EMBED_NOT_FOUND__ 0 @@ -658,17 +666,17 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi ); if (comp.langopts.standard.atLeast(.c11)) switch (comp.target.os.tag) { .openbsd, .driverkit, .ios, .macos, .tvos, .visionos, .watchos => { - try buf.appendSlice("#define __STDC_NO_THREADS__ 1\n"); + try w.writeAll("#define __STDC_NO_THREADS__ 1\n"); }, .ps4, .ps5 => { - try buf.appendSlice( + try w.writeAll( \\#define __STDC_NO_THREADS__ 1 \\#define __STDC_NO_COMPLEX__ 1 \\ ); }, .aix => { - try buf.appendSlice( + try w.writeAll( \\#define __STDC_NO_THREADS__ 1 \\#define __STDC_NO_ATOMICS__ 1 \\ @@ -677,19 +685,17 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi else => {}, }; if (comp.langopts.standard.StdCVersionMacro()) |stdc_version| { - try buf.appendSlice("#define __STDC_VERSION__ "); - try buf.appendSlice(stdc_version); - try buf.append('\n'); + try w.writeAll("#define __STDC_VERSION__ "); + try w.writeAll(stdc_version); + try w.writeByte('\n'); } if (system_defines_mode == .include_system_defines) { - try comp.generateSystemDefines(buf.writer()); + try comp.generateSystemDefines(w); } - - return comp.addSourceFromBuffer("", buf.items); } -fn generateFloatMacros(w: anytype, prefix: []const u8, semantics: target_util.FPSemantics, ext: []const u8) !void { +fn generateFloatMacros(w: *std.io.Writer, prefix: []const u8, semantics: target_util.FPSemantics, ext: []const u8) !void { const denormMin = semantics.chooseValue( []const u8, .{ @@ -764,7 +770,7 @@ fn generateFloatMacros(w: anytype, prefix: []const u8, semantics: target_util.FP try w.print("#define __{s}_MIN__ {s}{s}\n", .{ prefix, min, ext }); } -fn generateTypeMacro(comp: *const Compilation, w: anytype, name: []const u8, qt: QualType) !void { +fn generateTypeMacro(comp: *const Compilation, w: *std.io.Writer, name: []const u8, qt: QualType) !void { try w.print("#define {s} ", .{name}); try qt.print(comp, w); try w.writeByte('\n'); @@ -799,7 +805,7 @@ fn generateFastOrLeastType( bits: usize, kind: enum { least, fast }, signedness: std.builtin.Signedness, - w: anytype, + w: *std.io.Writer, ) !void { const ty = comp.intLeastN(bits, signedness); // defining the fast types as the least types is permitted @@ -829,7 +835,7 @@ fn generateFastOrLeastType( try comp.generateFmt(prefix, w, ty); } -fn generateFastAndLeastWidthTypes(comp: *Compilation, w: anytype) !void { +fn generateFastAndLeastWidthTypes(comp: *Compilation, w: *std.io.Writer) !void { const sizes = [_]usize{ 8, 16, 32, 64 }; for (sizes) |size| { try comp.generateFastOrLeastType(size, .least, .signed, w); @@ -839,7 +845,7 @@ fn generateFastAndLeastWidthTypes(comp: *Compilation, w: anytype) !void { } } -fn generateExactWidthTypes(comp: *Compilation, w: anytype) !void { +fn generateExactWidthTypes(comp: *Compilation, w: *std.io.Writer) !void { try comp.generateExactWidthType(w, .schar); if (QualType.short.sizeof(comp) > QualType.char.sizeof(comp)) { @@ -887,7 +893,7 @@ fn generateExactWidthTypes(comp: *Compilation, w: anytype) !void { } } -fn generateFmt(comp: *const Compilation, prefix: []const u8, w: anytype, qt: QualType) !void { +fn generateFmt(comp: *const Compilation, prefix: []const u8, w: *std.io.Writer, qt: QualType) !void { const unsigned = qt.signedness(comp) == .unsigned; const modifier = qt.formatModifier(comp); const formats = if (unsigned) "ouxX" else "di"; @@ -896,7 +902,7 @@ fn generateFmt(comp: *const Compilation, prefix: []const u8, w: anytype, qt: Qua } } -fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: anytype, qt: QualType) !void { +fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: *std.io.Writer, qt: QualType) !void { return w.print("#define {s}_C_SUFFIX__ {s}\n", .{ prefix, qt.intValueSuffix(comp) }); } @@ -904,7 +910,7 @@ fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: anytype, /// Name macro (e.g. #define __UINT32_TYPE__ unsigned int) /// Format strings (e.g. #define __UINT32_FMTu__ "u") /// Suffix macro (e.g. #define __UINT32_C_SUFFIX__ U) -fn generateExactWidthType(comp: *Compilation, w: anytype, original_qt: QualType) !void { +fn generateExactWidthType(comp: *Compilation, w: *std.io.Writer, original_qt: QualType) !void { var qt = original_qt; const width = qt.sizeof(comp) * 8; const unsigned = qt.signedness(comp) == .unsigned; @@ -937,7 +943,7 @@ pub fn hasHalfPrecisionFloatABI(comp: *const Compilation) bool { return comp.langopts.allow_half_args_and_returns or target_util.hasHalfPrecisionFloatABI(comp.target); } -fn generateIntMax(comp: *const Compilation, w: anytype, name: []const u8, qt: QualType) !void { +fn generateIntMax(comp: *const Compilation, w: *std.io.Writer, name: []const u8, qt: QualType) !void { const unsigned = qt.signedness(comp) == .unsigned; const max: u128 = switch (qt.bitSizeof(comp)) { 8 => if (unsigned) std.math.maxInt(u8) else std.math.maxInt(i8), @@ -961,7 +967,7 @@ pub fn wcharMax(comp: *const Compilation) u32 { }; } -fn generateExactWidthIntMax(comp: *Compilation, w: anytype, original_qt: QualType) !void { +fn generateExactWidthIntMax(comp: *Compilation, w: *std.io.Writer, original_qt: QualType) !void { var qt = original_qt; const bit_count: u8 = @intCast(qt.sizeof(comp) * 8); const unsigned = qt.signedness(comp) == .unsigned; @@ -978,16 +984,16 @@ fn generateExactWidthIntMax(comp: *Compilation, w: anytype, original_qt: QualTyp return comp.generateIntMax(w, name, qt); } -fn generateIntWidth(comp: *Compilation, w: anytype, name: []const u8, qt: QualType) !void { +fn generateIntWidth(comp: *Compilation, w: *std.io.Writer, name: []const u8, qt: QualType) !void { try w.print("#define __{s}_WIDTH__ {d}\n", .{ name, qt.sizeof(comp) * 8 }); } -fn generateIntMaxAndWidth(comp: *Compilation, w: anytype, name: []const u8, qt: QualType) !void { +fn generateIntMaxAndWidth(comp: *Compilation, w: *std.io.Writer, name: []const u8, qt: QualType) !void { try comp.generateIntMax(w, name, qt); try comp.generateIntWidth(w, name, qt); } -fn generateSizeofType(comp: *Compilation, w: anytype, name: []const u8, qt: QualType) !void { +fn generateSizeofType(comp: *Compilation, w: *std.io.Writer, name: []const u8, qt: QualType) !void { try w.print("#define {s} {d}\n", .{ name, qt.sizeof(comp) }); } @@ -1451,24 +1457,24 @@ pub const IncludeType = enum { angle_brackets, }; -fn getFileContents(comp: *Compilation, path: []const u8, limit: ?u32) ![]const u8 { +fn getFileContents(comp: *Compilation, path: []const u8, limit: std.io.Limit) ![]const u8 { if (mem.indexOfScalar(u8, path, 0) != null) { return error.FileNotFound; } const file = try comp.cwd.openFile(path, .{}); defer file.close(); + var reader = file.reader(""); - var buf = std.ArrayList(u8).init(comp.gpa); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(comp.gpa); + defer allocating.deinit(); - const max = limit orelse std.math.maxInt(u32); - file.reader().readAllArrayList(&buf, max) catch |e| switch (e) { + _ = allocating.writer.sendFileAll(&reader, limit) catch |e| switch (e) { error.StreamTooLong => if (limit == null) return e, else => return e, }; - return buf.toOwnedSlice(); + return allocating.toOwnedSlice(); } pub fn findEmbed( @@ -1477,7 +1483,7 @@ pub fn findEmbed( includer_token_source: Source.Id, /// angle bracket vs quotes include_type: IncludeType, - limit: ?u32, + limit: std.io.Limit, ) !?[]const u8 { if (std.fs.path.isAbsolute(filename)) { return if (comp.getFileContents(filename, limit)) |some| diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index 4f8182f..3836d0e 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -424,7 +424,7 @@ pub fn addWithLocation( if (copy.kind == .@"fatal error") return error.FatalError; } -pub fn formatArgs(w: anytype, fmt: []const u8, args: anytype) !void { +pub fn formatArgs(w: *std.io.Writer, fmt: []const u8, args: anytype) !void { var i: usize = 0; inline for (std.meta.fields(@TypeOf(args))) |arg_info| { const arg = @field(args, arg_info.name); @@ -440,7 +440,7 @@ pub fn formatArgs(w: anytype, fmt: []const u8, args: anytype) !void { try w.writeAll(fmt[i..]); } -pub fn formatString(w: anytype, fmt: []const u8, str: []const u8) !usize { +pub fn formatString(w: *std.io.Writer, fmt: []const u8, str: []const u8) std.io.Writer.Error!usize { const template = "{s}"; const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); @@ -448,11 +448,11 @@ pub fn formatString(w: anytype, fmt: []const u8, str: []const u8) !usize { return i + template.len; } -pub fn formatInt(w: anytype, fmt: []const u8, int: anytype) !usize { +pub fn formatInt(w: *std.io.Writer, fmt: []const u8, int: anytype) std.io.Writer.Error!usize { const template = "{d}"; const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); - try std.fmt.formatInt(int, 10, .lower, .{}, w); + try w.printInt(int, 10, .lower, .{}); return i + template.len; } @@ -470,7 +470,9 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { switch (d.output) { .ignore => {}, .to_file => |to_file| { - writeToWriter(msg, to_file.file.writer(), to_file.config) catch { + var buf: [1024]u8 = undefined; + var w = to_file.file.writer(&buf); + writeToWriter(msg, &w.interface, to_file.config) catch { return error.FatalError; }; }, @@ -491,7 +493,7 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { } } -fn writeToWriter(msg: Message, w: anytype, config: std.io.tty.Config) !void { +fn writeToWriter(msg: Message, w: *std.io.Writer, config: std.io.tty.Config) !void { try config.setColor(w, .bold); if (msg.location) |loc| { try w.print("{s}:{d}:{d}: ", .{ loc.path, loc.line_no, loc.col }); @@ -524,7 +526,7 @@ fn writeToWriter(msg: Message, w: anytype, config: std.io.tty.Config) !void { const trailer = if (loc.end_with_splice) "\\ " else ""; try config.setColor(w, .reset); try w.print("\n{s}{s}\n", .{ loc.line, trailer }); - try w.writeByteNTimes(' ', loc.width); + try w.splatByteAll(' ', loc.width); try config.setColor(w, .bold); try config.setColor(w, .bright_green); try w.writeAll("^\n"); diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 05ecf3c..a0ba56e 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -252,8 +252,8 @@ pub const usage = /// Process command line arguments, returns true if something was written to std_out. pub fn parseArgs( d: *Driver, - std_out: anytype, - macro_buf: anytype, + std_out: *std.io.Writer, + macro_buf: *std.io.Writer, args: []const []const u8, ) Compilation.Error!bool { var i: usize = 1; @@ -694,8 +694,8 @@ fn option(arg: []const u8, name: []const u8) ?[]const u8 { fn addSource(d: *Driver, path: []const u8) !Source { if (mem.eql(u8, "-", path)) { - const stdin = std.io.getStdIn().reader(); - const input = try stdin.readAllAlloc(d.comp.gpa, std.math.maxInt(u32)); + var stdin = std.fs.File.stdin().reader(""); + const input = try stdin.interface.stream(d.comp.gpa, std.math.maxInt(u32)); defer d.comp.gpa.free(input); return d.comp.addSourceFromBuffer("", input); } @@ -704,20 +704,20 @@ fn addSource(d: *Driver, path: []const u8) !Source { pub fn err(d: *Driver, fmt: []const u8, args: anytype) Compilation.Error!void { var sf = std.heap.stackFallback(1024, d.comp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try Diagnostics.formatArgs(buf.writer(), fmt, args); - try d.diagnostics.add(.{ .kind = .@"error", .text = buf.items, .location = null }); + Diagnostics.formatArgs(&allocating.writer, fmt, args) catch return error.OutOfMemory; + try d.diagnostics.add(.{ .kind = .@"error", .text = allocating.getWritten(), .location = null }); } pub fn warn(d: *Driver, fmt: []const u8, args: anytype) Compilation.Error!void { var sf = std.heap.stackFallback(1024, d.comp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try Diagnostics.formatArgs(buf.writer(), fmt, args); - try d.diagnostics.add(.{ .kind = .warning, .text = buf.items, .location = null }); + Diagnostics.formatArgs(&allocating.writer, fmt, args) catch return error.OutOfMemory; + try d.diagnostics.add(.{ .kind = .warning, .text = allocating.getWritten(), .location = null }); } pub fn unsupportedOptionForTarget(d: *Driver, target: std.Target, opt: []const u8) Compilation.Error!void { @@ -729,11 +729,11 @@ pub fn unsupportedOptionForTarget(d: *Driver, target: std.Target, opt: []const u pub fn fatal(d: *Driver, comptime fmt: []const u8, args: anytype) error{ FatalError, OutOfMemory } { var sf = std.heap.stackFallback(1024, d.comp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try Diagnostics.formatArgs(buf.writer(), fmt, args); - try d.diagnostics.add(.{ .kind = .@"fatal error", .text = buf.items, .location = null }); + Diagnostics.formatArgs(&allocating.writer, fmt, args) catch return error.OutOfMemory; + try d.diagnostics.add(.{ .kind = .@"fatal error", .text = allocating.getWritten(), .location = null }); unreachable; } @@ -796,11 +796,11 @@ pub fn errorDescription(e: anyerror) []const u8 { /// The entry point of the Aro compiler. /// **MAY call `exit` if `fast_exit` is set.** pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_exit: bool, asm_gen_fn: ?AsmCodeGenFn) Compilation.Error!void { - var macro_buf = std.ArrayList(u8).init(d.comp.gpa); - defer macro_buf.deinit(); + var macro_writer: std.io.Writer.Allocating = .init(d.comp.gpa); + defer macro_writer.deinit(); - const std_out = std.io.getStdOut().writer(); - if (try parseArgs(d, std_out, macro_buf.writer(), args)) return; + var std_out = std.fs.File.stderr().writer(""); + if (try parseArgs(d, &std_out.interface, ¯o_writer.writer, args)) return; const linking = !(d.only_preprocess or d.only_syntax or d.only_compile or d.only_preprocess_and_compile); @@ -823,7 +823,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ error.AroIncludeNotFound => return d.fatal("unable to find Aro builtin headers", .{}), }; - const user_macros = d.comp.addSourceFromBuffer("", macro_buf.items) catch |er| switch (er) { + const user_macros = d.comp.addSourceFromBuffer("", macro_writer.getWritten()) catch |er| switch (er) { error.StreamTooLong => return d.fatal("user provided macro source exceeded max size", .{}), else => |e| return e, }; @@ -953,16 +953,17 @@ fn processSource( d.comp.cwd.createFile(some, .{}) catch |er| return d.fatal("unable to create output file '{s}': {s}", .{ some, errorDescription(er) }) else - std.io.getStdOut(); + std.fs.File.stdout(); defer if (d.output_name != null) file.close(); - var buf_w = std.io.bufferedWriter(file.writer()); + var buf: [1024]u8 = undefined; + var writer = file.writer(&buf); - pp.prettyPrintTokens(buf_w.writer(), dump_mode) catch |er| - return d.fatal("unable to write result: {s}", .{errorDescription(er)}); + pp.prettyPrintTokens(&writer.interface, dump_mode) catch + return d.fatal("unable to write result: {s}", .{errorDescription(writer.err.?)}); - buf_w.flush() catch |er| - return d.fatal("unable to write result: {s}", .{errorDescription(er)}); + writer.interface.flush() catch + return d.fatal("unable to write result: {s}", .{errorDescription(writer.err.?)}); if (fast_exit) std.process.exit(0); // Not linking, no need for cleanup. return; } diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 0d4c7a1..c65812c 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -426,10 +426,10 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) if (p.diagnostics.effectiveKind(diagnostic) == .off) return; var sf = std.heap.stackFallback(1024, p.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try p.formatArgs(buf.writer(), diagnostic.fmt, args); + p.formatArgs(&allocating.writer, diagnostic.fmt, args) catch return error.OutOfMemory; const tok = p.pp.tokens.get(tok_i); var loc = tok.loc; @@ -441,14 +441,14 @@ pub fn err(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) } try p.diagnostics.addWithLocation(p.comp, .{ .kind = diagnostic.kind, - .text = buf.items, + .text = allocating.getWritten(), .opt = diagnostic.opt, .extension = diagnostic.extension, .location = loc.expand(p.comp), }, p.pp.expansionSlice(tok_i), true); } -fn formatArgs(p: *Parser, w: anytype, fmt: []const u8, args: anytype) !void { +fn formatArgs(p: *Parser, w: *std.io.Writer, fmt: []const u8, args: anytype) !void { var i: usize = 0; inline for (std.meta.fields(@TypeOf(args))) |arg_info| { const arg = @field(args, arg_info.name); @@ -477,7 +477,7 @@ fn formatArgs(p: *Parser, w: anytype, fmt: []const u8, args: anytype) !void { try w.writeAll(fmt[i..]); } -fn formatTokenId(w: anytype, fmt: []const u8, tok_id: Tree.Token.Id) !usize { +fn formatTokenId(w: *std.io.Writer, fmt: []const u8, tok_id: Tree.Token.Id) !usize { const template = "{tok_id}"; const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); @@ -485,7 +485,7 @@ fn formatTokenId(w: anytype, fmt: []const u8, tok_id: Tree.Token.Id) !usize { return i + template.len; } -fn formatQualType(p: *Parser, w: anytype, fmt: []const u8, qt: QualType) !usize { +fn formatQualType(p: *Parser, w: *std.io.Writer, fmt: []const u8, qt: QualType) !usize { const template = "{qt}"; const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); @@ -506,7 +506,7 @@ fn formatQualType(p: *Parser, w: anytype, fmt: []const u8, qt: QualType) !usize return i + template.len; } -fn formatResult(p: *Parser, w: anytype, fmt: []const u8, res: Result) !usize { +fn formatResult(p: *Parser, w: *std.io.Writer, fmt: []const u8, res: Result) !usize { const template = "{value}"; const i = std.mem.indexOf(u8, fmt, template).?; try w.writeAll(fmt[0..i]); @@ -533,7 +533,7 @@ const Normalized = struct { return .{ .str = str }; } - pub fn format(ctx: Normalized, w: anytype, fmt_str: []const u8) !usize { + pub fn format(ctx: Normalized, w: *std.io.Writer, fmt_str: []const u8) !usize { const template = "{normalized}"; const i = std.mem.indexOf(u8, fmt_str, template).?; try w.writeAll(fmt_str[0..i]); @@ -546,16 +546,16 @@ const Normalized = struct { try w.writeByte(@intCast(codepoint)); } else if (codepoint < 0xFFFF) { try w.writeAll("\\u"); - try std.fmt.formatInt(codepoint, 16, .upper, .{ + try w.printInt(codepoint, 16, .upper, .{ .fill = '0', .width = 4, - }, w); + }); } else { try w.writeAll("\\U"); - try std.fmt.formatInt(codepoint, 16, .upper, .{ + try w.printInt(codepoint, 16, .upper, .{ .fill = '0', .width = 8, - }, w); + }); } } return i + template.len; @@ -569,7 +569,7 @@ const Codepoint = struct { return .{ .codepoint = codepoint }; } - pub fn format(ctx: Codepoint, w: anytype, fmt_str: []const u8) !usize { + pub fn format(ctx: Codepoint, w: *std.io.Writer, fmt_str: []const u8) !usize { const template = "{codepoint}"; const i = std.mem.indexOf(u8, fmt_str, template).?; try w.writeAll(fmt_str[0..i]); @@ -585,11 +585,11 @@ const Escaped = struct { return .{ .str = str }; } - pub fn format(ctx: Escaped, w: anytype, fmt_str: []const u8) !usize { + pub fn format(ctx: Escaped, w: *std.io.Writer, fmt_str: []const u8) !usize { const template = "{s}"; const i = std.mem.indexOf(u8, fmt_str, template).?; try w.writeAll(fmt_str[0..i]); - try w.print("{}", .{std.zig.fmtEscapes(ctx.str)}); + try std.zig.stringEscape(ctx.str, w); return i + template.len; } }; @@ -627,11 +627,11 @@ pub fn errValueChanged(p: *Parser, tok_i: TokenIndex, diagnostic: Diagnostic, re fn checkDeprecatedUnavailable(p: *Parser, ty: QualType, usage_tok: TokenIndex, decl_tok: TokenIndex) !void { if (ty.getAttribute(p.comp, .@"error")) |@"error"| { const msg_str = p.comp.interner.get(@"error".msg.ref()).bytes; - try p.err(usage_tok, .error_attribute, .{ p.tokSlice(@"error".__name_tok), std.zig.fmtEscapes(msg_str) }); + try p.err(usage_tok, .error_attribute, .{ p.tokSlice(@"error".__name_tok), std.zig.fmtString(msg_str) }); } if (ty.getAttribute(p.comp, .warning)) |warning| { const msg_str = p.comp.interner.get(warning.msg.ref()).bytes; - try p.err(usage_tok, .warning_attribute, .{ p.tokSlice(warning.__name_tok), std.zig.fmtEscapes(msg_str) }); + try p.err(usage_tok, .warning_attribute, .{ p.tokSlice(warning.__name_tok), std.zig.fmtString(msg_str) }); } if (ty.getAttribute(p.comp, .unavailable)) |unavailable| { try p.errDeprecated(usage_tok, .unavailable, unavailable.msg); @@ -1468,33 +1468,32 @@ fn decl(p: *Parser) Error!bool { return true; } -fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result) !?[]const u8 { - const strings_top = p.strings.items.len; +fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result, allocating: *std.io.Writer.Allocating) !?[]const u8 { + const w = &allocating.writer; const cond = cond_node.get(&p.tree); if (cond == .builtin_types_compatible_p) { - try p.strings.appendSlice("'__builtin_types_compatible_p("); + try w.writeAll("'__builtin_types_compatible_p("); const lhs_ty = cond.builtin_types_compatible_p.lhs; - try lhs_ty.print(p.comp, p.strings.writer()); - try p.strings.appendSlice(", "); + try lhs_ty.print(p.comp, w); + try w.writeAll(", "); const rhs_ty = cond.builtin_types_compatible_p.rhs; - try rhs_ty.print(p.comp, p.strings.writer()); + try rhs_ty.print(p.comp, w); - try p.strings.appendSlice(")'"); + try w.writeAll(")'"); } else if (maybe_message == null) return null; if (maybe_message) |message| { assert(message.node.get(&p.tree) == .string_literal_expr); - if (p.strings.items.len > 0) { - try p.strings.append(' '); + if (allocating.writer.end > 0) { + try w.writeByte(' '); } const bytes = p.comp.interner.get(message.val.ref()).bytes; - try p.strings.ensureUnusedCapacity(bytes.len); - try Value.printString(bytes, message.qt, p.comp, p.strings.writer()); + try Value.printString(bytes, message.qt, p.comp, w); } - return p.strings.items[strings_top..]; + return allocating.getWritten(); } /// staticAssert @@ -1540,10 +1539,11 @@ fn staticAssert(p: *Parser) Error!bool { } } else { if (!res.val.toBool(p.comp)) { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; + var sf = std.heap.stackFallback(1024, p.gpa); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - if (try p.staticAssertMessage(res_node, str)) |message| { + if (p.staticAssertMessage(res_node, str, &allocating) catch return error.OutOfMemory) |message| { try p.err(static_assert, .static_assert_failure_message, .{message}); } else { try p.err(static_assert, .static_assert_failure, .{}); @@ -9208,15 +9208,11 @@ fn primaryExpr(p: *Parser) Error!?Result { try p.strings.appendSlice(p.tokSlice(p.func.name)); try p.strings.append(0); - const predef = try p.makePredefinedIdentifier(strings_top); + const predef = try p.makePredefinedIdentifier(p.strings.items[strings_top..]); ty = predef.qt; p.func.ident = predef; } else { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - try p.strings.append(0); - const predef = try p.makePredefinedIdentifier(strings_top); + const predef = try p.makePredefinedIdentifier("\x00"); ty = predef.qt; p.func.ident = predef; try p.decl_buf.append(predef.node); @@ -9240,20 +9236,18 @@ fn primaryExpr(p: *Parser) Error!?Result { if (p.func.pretty_ident) |some| { qt = some.qt; } else if (p.func.qt) |func_qt| { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; + var sf = std.heap.stackFallback(1024, p.gpa); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try func_qt.printNamed(p.tokSlice(p.func.name), p.comp, p.strings.writer()); - try p.strings.append(0); - const predef = try p.makePredefinedIdentifier(strings_top); + func_qt.printNamed(p.tokSlice(p.func.name), p.comp, &allocating.writer) catch return error.OutOfMemory; + allocating.writer.writeByte(0) catch return error.OutOfMemory; + + const predef = try p.makePredefinedIdentifier(allocating.getWritten()); qt = predef.qt; p.func.pretty_ident = predef; } else { - const strings_top = p.strings.items.len; - defer p.strings.items.len = strings_top; - - try p.strings.appendSlice("top level\x00"); - const predef = try p.makePredefinedIdentifier(strings_top); + const predef = try p.makePredefinedIdentifier("top level\x00"); qt = predef.qt; p.func.pretty_ident = predef; try p.decl_buf.append(predef.node); @@ -9332,13 +9326,12 @@ fn primaryExpr(p: *Parser) Error!?Result { } } -fn makePredefinedIdentifier(p: *Parser, strings_top: usize) !Result { +fn makePredefinedIdentifier(p: *Parser, slice: []const u8) !Result { const array_qt = try p.comp.type_store.put(p.gpa, .{ .array = .{ .elem = .{ .@"const" = true, ._index = .int_char }, - .len = .{ .fixed = p.strings.items.len - strings_top }, + .len = .{ .fixed = slice.len }, } }); - const slice = p.strings.items[strings_top..]; const val = try Value.intern(p.comp, .{ .bytes = slice }); const str_lit = try p.addNode(.{ .string_literal_expr = .{ .qt = array_qt, .literal_tok = p.tok_i, .kind = .ascii } }); diff --git a/src/aro/Pragma.zig b/src/aro/Pragma.zig index a54e139..b1ec117 100644 --- a/src/aro/Pragma.zig +++ b/src/aro/Pragma.zig @@ -195,15 +195,15 @@ pub const Diagnostic = struct { pub fn err(pp: *Preprocessor, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) Compilation.Error!void { var sf = std.heap.stackFallback(1024, pp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try Diagnostics.formatArgs(buf.writer(), diagnostic.fmt, args); + Diagnostics.formatArgs(&allocating.writer, diagnostic.fmt, args) catch return error.OutOfMemory; try pp.diagnostics.addWithLocation(pp.comp, .{ .kind = diagnostic.kind, .opt = diagnostic.opt, - .text = buf.items, + .text = allocating.getWritten(), .location = pp.tokens.items(.loc)[tok_i].expand(pp.comp), .extension = diagnostic.extension, }, pp.expansionSlice(tok_i), true); diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 006866e..5ca482f 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -755,17 +755,17 @@ fn tokFromRaw(raw: RawToken) TokenWithExpansionLocs { pub const Diagnostic = @import("Preprocessor/Diagnostic.zig"); -fn err(pp: *Preprocessor, loc: anytype, diagnostic: Diagnostic, args: anytype) !void { +fn err(pp: *Preprocessor, loc: anytype, diagnostic: Diagnostic, args: anytype) Compilation.Error!void { if (pp.diagnostics.effectiveKind(diagnostic) == .off) return; var sf = std.heap.stackFallback(1024, pp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try Diagnostics.formatArgs(buf.writer(), diagnostic.fmt, args); + Diagnostics.formatArgs(&allocating.writer, diagnostic.fmt, args) catch return error.OutOfMemory; try pp.diagnostics.addWithLocation(pp.comp, .{ .kind = diagnostic.kind, - .text = buf.items, + .text = allocating.getWritten(), .opt = diagnostic.opt, .extension = diagnostic.extension, .location = switch (@TypeOf(loc)) { @@ -788,13 +788,13 @@ fn err(pp: *Preprocessor, loc: anytype, diagnostic: Diagnostic, args: anytype) ! fn fatal(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) Compilation.Error { var sf = std.heap.stackFallback(1024, pp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try Diagnostics.formatArgs(buf.writer(), fmt, args); + Diagnostics.formatArgs(&allocating.writer, fmt, args) catch return error.OutOfMemory; try pp.diagnostics.add(.{ .kind = .@"fatal error", - .text = buf.items, + .text = allocating.getWritten(), .location = (Source.Location{ .id = raw.source, .byte_offset = raw.start, @@ -810,13 +810,13 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con defer pp.diagnostics.state.fatal_errors = old; var sf = std.heap.stackFallback(1024, pp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try Diagnostics.formatArgs(buf.writer(), "'{s}' not found", .{filename}); + Diagnostics.formatArgs(&allocating.writer, "'{s}' not found", .{filename}) catch return error.OutOfMemory; try pp.diagnostics.addWithLocation(pp.comp, .{ .kind = .@"fatal error", - .text = buf.items, + .text = allocating.getWritten(), .location = tok.loc.expand(pp.comp), }, tok.expansionSlice(), true); unreachable; // should've returned FatalError @@ -827,15 +827,15 @@ fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: const source = pp.comp.getSource(raw.source); const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); - const stderr = std.io.getStdErr().writer(); - var buf_writer = std.io.bufferedWriter(stderr); - const writer = buf_writer.writer(); - defer buf_writer.flush() catch {}; - writer.print("{s}:{d}:{d}: ", .{ source.path, line_col.line_no, line_col.col }) catch return; - writer.print(fmt, args) catch return; - writer.writeByte('\n') catch return; - writer.writeAll(line_col.line) catch return; - writer.writeByte('\n') catch return; + var buf: [1024]u8 = undefined; + var stderr = std.fs.File.stderr().writer(&buf); + const w = &stderr.interface; + defer w.flush() catch {}; + w.print("{s}:{d}:{d}: ", .{ source.path, line_col.line_no, line_col.col }) catch return; + w.print(fmt, args) catch return; + w.writeByte('\n') catch return; + w.writeAll(line_col.line) catch return; + w.writeByte('\n') catch return; } /// Consume next token, error if it is not an identifier. @@ -1191,7 +1191,7 @@ fn expandObjMacro(pp: *Preprocessor, simple_macro: *const Macro) Error!ExpandBuf const start = pp.comp.generated_buf.items.len; const source = pp.comp.getSource(pp.expansion_source_loc.id); const w = pp.comp.generated_buf.writer(pp.gpa); - try w.print("\"{}\"\n", .{fmtEscapes(source.path)}); + try w.print("\"{f}\"\n", .{fmtEscapes(source.path)}); buf.appendAssumeCapacity(try pp.makeGeneratedToken(start, .string_literal, tok)); }, @@ -1935,7 +1935,7 @@ fn expandFuncMacro( else => unreachable, }; const filename = include_str[1 .. include_str.len - 1]; - const contents = (try pp.comp.findEmbed(filename, arg[0].loc.id, include_type, 1)) orelse + const contents = (try pp.comp.findEmbed(filename, arg[0].loc.id, include_type, .limited(1))) orelse break :res not_found; defer pp.comp.gpa.free(contents); @@ -2994,7 +2994,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { }; pp.token_buf.items.len = 0; - var limit: ?u32 = null; + var limit: std.io.Limit = .unlimited; var prefix: ?Range = null; var suffix: ?Range = null; var if_empty: ?Range = null; @@ -3051,7 +3051,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { const end: u32 = @intCast(pp.token_buf.items.len); if (std.mem.eql(u8, param, "limit")) { - if (limit != null) { + if (limit != .unlimited) { try pp.err(tokFromRaw(param_first), .duplicate_embed_param, .{"limit"}); continue; } @@ -3064,10 +3064,10 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { try pp.err(param_first, .malformed_embed_limit, .{}); continue; } - limit = std.fmt.parseInt(u32, pp.tokSlice(limit_tok), 10) catch { + limit = .limited(std.fmt.parseInt(u32, pp.tokSlice(limit_tok), 10) catch { try pp.err(limit_tok, .malformed_embed_limit, .{}); continue; - }; + }); pp.token_buf.items.len = start; } else if (std.mem.eql(u8, param, "prefix")) { if (prefix != null) { @@ -3352,14 +3352,14 @@ fn findIncludeSource(pp: *Preprocessor, tokenizer: *Tokenizer, first: RawToken, fn printLinemarker( pp: *Preprocessor, - w: anytype, + w: *std.io.Writer, line_no: u32, source: Source, start_resume: enum(u8) { start, @"resume", none }, ) !void { try w.writeByte('#'); if (pp.linemarkers == .line_directives) try w.writeAll("line"); - try w.print(" {d} \"{}\"", .{ line_no, fmtEscapes(source.path) }); + try w.print(" {d} \"{f}\"", .{ line_no, fmtEscapes(source.path) }); if (pp.linemarkers == .numeric_directives) { switch (start_resume) { .none => {}, @@ -3395,7 +3395,7 @@ pub const DumpMode = enum { /// Pretty-print the macro define or undef at location `loc`. /// We re-tokenize the directive because we are printing a macro that may have the same name as one in /// `pp.defines` but a different definition (due to being #undef'ed and then redefined) -fn prettyPrintMacro(pp: *Preprocessor, w: anytype, loc: Source.Location, parts: enum { name_only, name_and_body }) !void { +fn prettyPrintMacro(pp: *Preprocessor, w: *std.io.Writer, loc: Source.Location, parts: enum { name_only, name_and_body }) !void { const source = pp.comp.getSource(loc.id); var tokenizer: Tokenizer = .{ .buf = source.buf, @@ -3433,7 +3433,7 @@ fn prettyPrintMacro(pp: *Preprocessor, w: anytype, loc: Source.Location, parts: } } -fn prettyPrintMacrosOnly(pp: *Preprocessor, w: anytype) !void { +fn prettyPrintMacrosOnly(pp: *Preprocessor, w: *std.io.Writer) !void { for (pp.defines.values()) |macro| { if (macro.is_builtin) continue; @@ -3444,7 +3444,7 @@ fn prettyPrintMacrosOnly(pp: *Preprocessor, w: anytype) !void { } /// Pretty print tokens and try to preserve whitespace. -pub fn prettyPrintTokens(pp: *Preprocessor, w: anytype, macro_dump_mode: DumpMode) !void { +pub fn prettyPrintTokens(pp: *Preprocessor, w: *std.io.Writer, macro_dump_mode: DumpMode) !void { if (macro_dump_mode == .macros_only) { return pp.prettyPrintMacrosOnly(w); } @@ -3568,8 +3568,7 @@ fn fmtEscapes(bytes: []const u8) FmtEscapes { } const FmtEscapes = struct { bytes: []const u8, - pub fn format(ctx: FmtEscapes, comptime fmt: []const u8, _: std.fmt.FormatOptions, w: anytype) !void { - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, ctx); + pub fn format(ctx: FmtEscapes, w: *std.io.Writer) !void { for (ctx.bytes) |byte| switch (byte) { '\n' => try w.writeAll("\\n"), '\r' => try w.writeAll("\\r"), diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index 0b72e56..ccf2e17 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -2958,7 +2958,7 @@ fn dumpNode( node_index: Node.Index, level: u32, config: std.io.tty.Config, - w: anytype, + w: *std.io.Writer, ) !void { const delta = 2; const half = delta / 2; diff --git a/src/aro/TypeStore.zig b/src/aro/TypeStore.zig index ba6ba54..193c496 100644 --- a/src/aro/TypeStore.zig +++ b/src/aro/TypeStore.zig @@ -1164,7 +1164,7 @@ pub const QualType = packed struct(u32) { } } - pub fn print(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { + pub fn print(qt: QualType, comp: *const Compilation, w: *std.io.Writer) std.io.Writer.Error!void { if (qt.isC23Auto()) { try w.writeAll("auto"); return; @@ -1173,7 +1173,7 @@ pub const QualType = packed struct(u32) { try qt.printEpilogue(comp, false, w); } - pub fn printNamed(qt: QualType, name: []const u8, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { + pub fn printNamed(qt: QualType, name: []const u8, comp: *const Compilation, w: *std.io.Writer) std.io.Writer.Error!void { if (qt.isC23Auto()) { try w.print("auto {s}", .{name}); return; @@ -1184,12 +1184,12 @@ pub const QualType = packed struct(u32) { try qt.printEpilogue(comp, false, w); } - pub fn printDesugared(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { + pub fn printDesugared(qt: QualType, comp: *const Compilation, w: *std.io.Writer) std.io.Writer.Error!void { _ = try qt.printPrologue(comp, true, w); try qt.printEpilogue(comp, true, w); } - fn printPrologue(qt: QualType, comp: *const Compilation, desugar: bool, w: anytype) @TypeOf(w).Error!bool { + fn printPrologue(qt: QualType, comp: *const Compilation, desugar: bool, w: *std.io.Writer) std.io.Writer.Error!bool { loop: switch (qt.type(comp)) { .pointer => |pointer| { const simple = try pointer.child.printPrologue(comp, desugar, w); @@ -1305,7 +1305,7 @@ pub const QualType = packed struct(u32) { return true; } - fn printEpilogue(qt: QualType, comp: *const Compilation, desugar: bool, w: anytype) @TypeOf(w).Error!void { + fn printEpilogue(qt: QualType, comp: *const Compilation, desugar: bool, w: *std.io.Writer) std.io.Writer.Error!void { loop: switch (qt.type(comp)) { .pointer => |pointer| { switch (pointer.child.base(comp).type) { @@ -1362,7 +1362,7 @@ pub const QualType = packed struct(u32) { } } - pub fn dump(qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { + pub fn dump(qt: QualType, comp: *const Compilation, w: *std.io.Writer) std.io.Writer.Error!void { if (qt.@"const") try w.writeAll("const "); if (qt.@"volatile") try w.writeAll("volatile "); if (qt.restrict) try w.writeAll("restrict "); diff --git a/src/aro/Value.zig b/src/aro/Value.zig index cc49862..c65bd0e 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -1080,7 +1080,7 @@ const NestedPrint = union(enum) { }, }; -pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { +pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: *std.io.Writer) std.io.Writer.Error!void { try w.writeByte('&'); try w.writeAll(base); if (!offset.isZero(comp)) { @@ -1089,7 +1089,7 @@ pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w } } -pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!?NestedPrint { +pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.io.Writer) std.io.Writer.Error!?NestedPrint { if (qt.is(comp, .bool)) { try w.writeAll(if (v.isZero(comp)) "false" else "true"); return null; @@ -1116,12 +1116,12 @@ pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: anytype) @Type return null; } -pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: anytype) @TypeOf(w).Error!void { +pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: *std.io.Writer) std.io.Writer.Error!void { const size: Compilation.CharUnitSize = @enumFromInt(qt.childType(comp).sizeof(comp)); const without_null = bytes[0 .. bytes.len - @intFromEnum(size)]; try w.writeByte('"'); switch (size) { - .@"1" => try w.print("{}", .{std.zig.fmtEscapes(without_null)}), + .@"1" => try w.print("{f}", .{std.zig.fmtString(without_null)}), .@"2" => { var items: [2]u16 = undefined; var i: usize = 0; diff --git a/src/aro/pragmas/message.zig b/src/aro/pragmas/message.zig index d24defc..6753693 100644 --- a/src/aro/pragmas/message.zig +++ b/src/aro/pragmas/message.zig @@ -45,13 +45,13 @@ fn preprocessorHandler(_: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Pra const diagnostic: Pragma.Diagnostic = .pragma_message; var sf = std.heap.stackFallback(1024, pp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try Diagnostics.formatArgs(buf.writer(), diagnostic.fmt, .{str}); + Diagnostics.formatArgs(&allocating.writer, diagnostic.fmt, .{str}) catch return error.OutOfMemory; try pp.diagnostics.add(.{ - .text = buf.items, + .text = allocating.getWritten(), .kind = diagnostic.kind, .opt = diagnostic.opt, .location = loc.expand(pp.comp), diff --git a/src/aro/text_literal.zig b/src/aro/text_literal.zig index 817230f..92487fd 100644 --- a/src/aro/text_literal.zig +++ b/src/aro/text_literal.zig @@ -154,7 +154,7 @@ pub const Ascii = struct { return .{ .val = @intCast(val) }; } - pub fn format(ctx: Ascii, w: anytype, fmt_str: []const u8) !usize { + pub fn format(ctx: Ascii, w: *std.io.Writer, fmt_str: []const u8) !usize { const template = "{c}"; const i = std.mem.indexOf(u8, fmt_str, template).?; try w.writeAll(fmt_str[0..i]); @@ -163,7 +163,7 @@ pub const Ascii = struct { try w.writeByte(ctx.val); } else { var buf: [3]u8 = undefined; - const str = std.fmt.bufPrint(&buf, "x{x}", .{std.fmt.fmtSliceHexLower(&.{ctx.val})}) catch unreachable; + const str = std.fmt.bufPrint(&buf, "x{x}", .{ctx.val}) catch unreachable; try w.writeAll(str); } return i + template.len; @@ -315,29 +315,29 @@ pub const Parser = struct { try p.warn(diagnostic, args); } - pub fn warn(p: *Parser, diagnostic: Diagnostic, args: anytype) !void { + pub fn warn(p: *Parser, diagnostic: Diagnostic, args: anytype) Compilation.Error!void { defer p.offset = 0; if (p.errored) return; if (p.comp.diagnostics.effectiveKind(diagnostic) == .off) return; var sf = std.heap.stackFallback(1024, p.comp.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); - try formatArgs(buf.writer(), diagnostic.fmt, args); + formatArgs(&allocating.writer, diagnostic.fmt, args) catch return error.OutOfMemory; var offset_location = p.loc; offset_location.byte_offset += p.offset; try p.comp.diagnostics.addWithLocation(p.comp, .{ .kind = diagnostic.kind, - .text = buf.items, + .text = allocating.getWritten(), .opt = diagnostic.opt, .extension = diagnostic.extension, .location = offset_location.expand(p.comp), }, p.expansion_locs, true); } - fn formatArgs(w: anytype, fmt: []const u8, args: anytype) !void { + fn formatArgs(w: *std.io.Writer, fmt: []const u8, args: anytype) !void { var i: usize = 0; inline for (std.meta.fields(@TypeOf(args))) |arg_info| { const arg = @field(args, arg_info.name); diff --git a/src/backend/Ir.zig b/src/backend/Ir.zig index e694a23..3a1aac3 100644 --- a/src/backend/Ir.zig +++ b/src/backend/Ir.zig @@ -382,13 +382,13 @@ const ATTRIBUTE = std.io.tty.Color.bright_yellow; const RefMap = std.AutoArrayHashMap(Ref, void); -pub fn dump(ir: *const Ir, gpa: Allocator, config: std.io.tty.Config, w: anytype) !void { +pub fn dump(ir: *const Ir, gpa: Allocator, config: std.io.tty.Config, w: *std.io.Writer) !void { for (ir.decls.keys(), ir.decls.values()) |name, *decl| { try ir.dumpDecl(decl, gpa, name, config, w); } } -fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, config: std.io.tty.Config, w: anytype) !void { +fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, config: std.io.tty.Config, w: *std.io.Writer) !void { const tags = decl.instructions.items(.tag); const data = decl.instructions.items(.data); @@ -609,7 +609,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, try w.writeAll("}\n\n"); } -fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.io.tty.Config, w: anytype) !void { +fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.io.tty.Config, w: *std.io.Writer) !void { const ty = ir.interner.get(ty_ref); try config.setColor(w, TYPE); switch (ty) { @@ -639,7 +639,7 @@ fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.io.tty.Config, w: anytype } } -fn writeValue(ir: Ir, val: Interner.Ref, config: std.io.tty.Config, w: anytype) !void { +fn writeValue(ir: Ir, val: Interner.Ref, config: std.io.tty.Config, w: *std.io.Writer) !void { try config.setColor(w, LITERAL); const key = ir.interner.get(val); switch (key) { @@ -655,7 +655,7 @@ fn writeValue(ir: Ir, val: Interner.Ref, config: std.io.tty.Config, w: anytype) } } -fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void { +fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: *std.io.Writer) !void { assert(ref != .none); const index = @intFromEnum(ref); const ty_ref = decl.instructions.items(.ty)[index]; @@ -678,7 +678,7 @@ fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.i try w.print(" %{d}", .{ref_index}); } -fn writeNewRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void { +fn writeNewRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: *std.io.Writer) !void { try ref_map.put(ref, {}); try w.writeAll(" "); try ir.writeRef(decl, ref_map, ref, config, w); @@ -687,7 +687,7 @@ fn writeNewRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: st try config.setColor(w, INST); } -fn writeLabel(decl: *const Decl, label_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void { +fn writeLabel(decl: *const Decl, label_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: *std.io.Writer) !void { assert(ref != .none); const index = @intFromEnum(ref); const label = decl.instructions.items(.data)[index].label; diff --git a/src/main.zig b/src/main.zig index dcf6a1a..8113c6e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -39,10 +39,10 @@ pub fn main() u8 { }; defer gpa.free(aro_name); - const stderr_file = std.io.getStdErr(); + const stderr_file: std.fs.File = .stderr(); var diagnostics: Diagnostics = .{ .output = .{ .to_file = .{ - .config = std.io.tty.detectConfig(stderr_file), + .config = .detect(stderr_file), .file = stderr_file, } }, }; -- Gitee From 49627aaa65a41af448c4d899a5e8c289c92d778c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 12 Jul 2025 13:44:41 +0300 Subject: [PATCH 107/117] progress --- src/aro/Compilation.zig | 108 +++++++++---------- src/aro/Diagnostics.zig | 17 +-- src/aro/Driver.zig | 90 ++++++++-------- src/aro/Parser.zig | 16 +-- src/aro/Preprocessor.zig | 24 +++-- src/aro/Tokenizer.zig | 4 +- src/aro/Tree.zig | 179 ++++++++++++++++---------------- src/assembly_backend/x86_64.zig | 60 ++++++----- src/backend/Ir.zig | 3 +- src/backend/Object.zig | 4 +- src/backend/Object/Elf.zig | 11 +- src/main.zig | 9 +- 12 files changed, 269 insertions(+), 256 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index d5ab0ca..6bab964 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -637,7 +637,11 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi error.WriteFailed, error.OutOfMemory => return error.OutOfMemory, }; - return comp.addSourceFromBuffer("", allocating.getWritten()); + if (allocating.getWritten().len > std.math.maxInt(u32)) return error.StreamTooLong; + + const contents = try allocating.toOwnedSlice(); + errdefer comp.gpa.free(contents); + return comp.addSourceFromOwnedBuffer(contents, "", .user); } fn writeBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode, w: *std.io.Writer) !void { @@ -1069,21 +1073,14 @@ pub fn getSource(comp: *const Compilation, id: Source.Id) Source { return comp.sources.values()[@intFromEnum(id) - 2]; } -/// Creates a Source from the contents of `reader` and adds it to the Compilation -pub fn addSourceFromReader(comp: *Compilation, reader: anytype, path: []const u8, kind: Source.Kind) !Source { - const contents = try reader.readAllAlloc(comp.gpa, std.math.maxInt(u32)); - errdefer comp.gpa.free(contents); - return comp.addSourceFromOwnedBuffer(contents, path, kind); -} - /// Creates a Source from `buf` and adds it to the Compilation /// Performs newline splicing and line-ending normalization to '\n' /// `buf` will be modified and the allocation will be resized if newline splicing /// or line-ending changes happen. /// caller retains ownership of `path` -/// To add the contents of an arbitrary reader as a Source, see addSourceFromReader /// To add a file's contents given its path, see addSourceFromPath pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, kind: Source.Kind) !Source { + assert(buf.len <= std.math.maxInt(u32)); try comp.sources.ensureUnusedCapacity(comp.gpa, 1); var contents = buf; @@ -1242,9 +1239,9 @@ fn addNewlineEscapeError(comp: *Compilation, path: []const u8, buf: []const u8, /// Caller retains ownership of `path` and `buf`. /// Dupes the source buffer; if it is acceptable to modify the source buffer and possibly resize /// the allocation, please use `addSourceFromOwnedBuffer` -pub fn addSourceFromBuffer(comp: *Compilation, path: []const u8, buf: []const u8) !Source { +pub fn addSourceFromBuffer(comp: *Compilation, buf: []const u8, path: []const u8) !Source { if (comp.sources.get(path)) |some| return some; - if (@as(u64, buf.len) > std.math.maxInt(u32)) return error.StreamTooLong; + if (buf.len > std.math.maxInt(u32)) return error.StreamTooLong; const contents = try comp.gpa.dupe(u8, buf); errdefer comp.gpa.free(contents); @@ -1267,13 +1264,21 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin const file = try comp.cwd.openFile(path, .{}); defer file.close(); + return comp.addSourceFromFile(file, path, kind); +} + +pub fn addSourceFromFile(comp: *Compilation, file: std.fs.File, path: []const u8, kind: Source.Kind) !Source { + var buf: [4096]u8 = undefined; + var reader = file.reader(&buf); - const contents = file.readToEndAlloc(comp.gpa, std.math.maxInt(u32)) catch |err| switch (err) { - error.FileTooBig => return error.StreamTooLong, - else => |e| return e, + var allocating: std.io.Writer.Allocating = .init(comp.gpa); + _ = allocating.writer.sendFileAll(&reader, .limited(std.math.maxInt(u32))) catch |e| switch (e) { + error.WriteFailed => return error.OutOfMemory, + error.ReadFailed => return reader.err.?, }; + + const contents = try allocating.toOwnedSlice(); errdefer comp.gpa.free(contents); - return comp.addSourceFromOwnedBuffer(contents, path, kind); } @@ -1464,14 +1469,15 @@ fn getFileContents(comp: *Compilation, path: []const u8, limit: std.io.Limit) ![ const file = try comp.cwd.openFile(path, .{}); defer file.close(); - var reader = file.reader(""); var allocating: std.io.Writer.Allocating = .init(comp.gpa); defer allocating.deinit(); - _ = allocating.writer.sendFileAll(&reader, limit) catch |e| switch (e) { - error.StreamTooLong => if (limit == null) return e, - else => return e, + var buf: [4096]u8 = undefined; + var reader = file.reader(&buf); + _ = allocating.writer.sendFileAll(&reader, limit) catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + error.ReadFailed => return reader.err.?, }; return allocating.toOwnedSlice(); @@ -1688,17 +1694,16 @@ pub const Diagnostic = struct { }; }; -test "addSourceFromReader" { +test "addSourceFromBuffer" { const Test = struct { - fn addSourceFromReader(str: []const u8, expected: []const u8, warning_count: u32, splices: []const u32) !void { + fn addSourceFromBuffer(str: []const u8, expected: []const u8, warning_count: u32, splices: []const u32) !void { var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); var diagnostics: Diagnostics = .{ .output = .ignore }; var comp = Compilation.init(std.testing.allocator, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); - var buf_reader = std.io.fixedBufferStream(str); - const source = try comp.addSourceFromReader(buf_reader.reader(), "path", .user); + const source = try comp.addSourceFromBuffer(str, "path"); try std.testing.expectEqualStrings(expected, source.buf); try std.testing.expectEqual(warning_count, @as(u32, @intCast(diagnostics.warnings))); @@ -1712,41 +1717,41 @@ test "addSourceFromReader" { var comp = Compilation.init(allocator, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); - _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n"); - _ = try comp.addSourceFromBuffer("path", "non-spliced buffer\n"); + _ = try comp.addSourceFromBuffer("spliced\\\nbuffer\n", "path", ); + _ = try comp.addSourceFromBuffer("non-spliced buffer\n", "path", ); } }; - try Test.addSourceFromReader("ab\\\nc", "abc", 0, &.{2}); - try Test.addSourceFromReader("ab\\\rc", "abc", 0, &.{2}); - try Test.addSourceFromReader("ab\\\r\nc", "abc", 0, &.{2}); - try Test.addSourceFromReader("ab\\ \nc", "abc", 1, &.{2}); - try Test.addSourceFromReader("ab\\\t\nc", "abc", 1, &.{2}); - try Test.addSourceFromReader("ab\\ \t\nc", "abc", 1, &.{2}); - try Test.addSourceFromReader("ab\\\r \nc", "ab \nc", 0, &.{2}); - try Test.addSourceFromReader("ab\\\\\nc", "ab\\c", 0, &.{3}); - try Test.addSourceFromReader("ab\\ \r\nc", "abc", 1, &.{2}); - try Test.addSourceFromReader("ab\\ \\\nc", "ab\\ c", 0, &.{4}); - try Test.addSourceFromReader("ab\\\r\\\nc", "abc", 0, &.{ 2, 2 }); - try Test.addSourceFromReader("ab\\ \rc", "abc", 1, &.{2}); - try Test.addSourceFromReader("ab\\", "ab\\", 0, &.{}); - try Test.addSourceFromReader("ab\\\\", "ab\\\\", 0, &.{}); - try Test.addSourceFromReader("ab\\ ", "ab\\ ", 0, &.{}); - try Test.addSourceFromReader("ab\\\n", "ab", 0, &.{2}); - try Test.addSourceFromReader("ab\\\r\n", "ab", 0, &.{2}); - try Test.addSourceFromReader("ab\\\r", "ab", 0, &.{2}); + try Test.addSourceFromBuffer("ab\\\nc", "abc", 0, &.{2}); + try Test.addSourceFromBuffer("ab\\\rc", "abc", 0, &.{2}); + try Test.addSourceFromBuffer("ab\\\r\nc", "abc", 0, &.{2}); + try Test.addSourceFromBuffer("ab\\ \nc", "abc", 1, &.{2}); + try Test.addSourceFromBuffer("ab\\\t\nc", "abc", 1, &.{2}); + try Test.addSourceFromBuffer("ab\\ \t\nc", "abc", 1, &.{2}); + try Test.addSourceFromBuffer("ab\\\r \nc", "ab \nc", 0, &.{2}); + try Test.addSourceFromBuffer("ab\\\\\nc", "ab\\c", 0, &.{3}); + try Test.addSourceFromBuffer("ab\\ \r\nc", "abc", 1, &.{2}); + try Test.addSourceFromBuffer("ab\\ \\\nc", "ab\\ c", 0, &.{4}); + try Test.addSourceFromBuffer("ab\\\r\\\nc", "abc", 0, &.{ 2, 2 }); + try Test.addSourceFromBuffer("ab\\ \rc", "abc", 1, &.{2}); + try Test.addSourceFromBuffer("ab\\", "ab\\", 0, &.{}); + try Test.addSourceFromBuffer("ab\\\\", "ab\\\\", 0, &.{}); + try Test.addSourceFromBuffer("ab\\ ", "ab\\ ", 0, &.{}); + try Test.addSourceFromBuffer("ab\\\n", "ab", 0, &.{2}); + try Test.addSourceFromBuffer("ab\\\r\n", "ab", 0, &.{2}); + try Test.addSourceFromBuffer("ab\\\r", "ab", 0, &.{2}); // carriage return normalization - try Test.addSourceFromReader("ab\r", "ab\n", 0, &.{}); - try Test.addSourceFromReader("ab\r\r", "ab\n\n", 0, &.{}); - try Test.addSourceFromReader("ab\r\r\n", "ab\n\n", 0, &.{}); - try Test.addSourceFromReader("ab\r\r\n\r", "ab\n\n\n", 0, &.{}); - try Test.addSourceFromReader("\r\\", "\n\\", 0, &.{}); - try Test.addSourceFromReader("\\\r\\", "\\", 0, &.{0}); + try Test.addSourceFromBuffer("ab\r", "ab\n", 0, &.{}); + try Test.addSourceFromBuffer("ab\r\r", "ab\n\n", 0, &.{}); + try Test.addSourceFromBuffer("ab\r\r\n", "ab\n\n", 0, &.{}); + try Test.addSourceFromBuffer("ab\r\r\n\r", "ab\n\n\n", 0, &.{}); + try Test.addSourceFromBuffer("\r\\", "\n\\", 0, &.{}); + try Test.addSourceFromBuffer("\\\r\\", "\\", 0, &.{0}); try std.testing.checkAllAllocationFailures(std.testing.allocator, Test.withAllocationFailures, .{}); } -test "addSourceFromReader - exhaustive check for carriage return elimination" { +test "addSourceFromBuffer - exhaustive check for carriage return elimination" { var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); @@ -1786,8 +1791,7 @@ test "ignore BOM at beginning of file" { var comp = Compilation.init(std.testing.allocator, arena, &diagnostics, std.fs.cwd()); defer comp.deinit(); - var buf_reader = std.io.fixedBufferStream(buf); - const source = try comp.addSourceFromReader(buf_reader.reader(), "file.c", .user); + const source = try comp.addSourceFromBuffer(buf, "file.c"); const expected_output = if (mem.startsWith(u8, buf, BOM)) buf[BOM.len..] else buf; try std.testing.expectEqualStrings(expected_output, source.buf); } diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index 3836d0e..2098608 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -237,15 +237,14 @@ pub const State = struct { const Diagnostics = @This(); output: union(enum) { - to_file: struct { - file: std.fs.File, + to_writer: struct { + writer: *std.io.Writer, config: std.io.tty.Config, }, to_list: struct { messages: std.ArrayListUnmanaged(Message) = .empty, arena: std.heap.ArenaAllocator, }, - to_buffer: std.ArrayList(u8), ignore, }, state: State = .{}, @@ -263,12 +262,11 @@ hide_notes: bool = false, pub fn deinit(d: *Diagnostics) void { switch (d.output) { .ignore => {}, - .to_file => {}, + .to_writer => {}, .to_list => |*list| { list.messages.deinit(list.arena.child_allocator); list.arena.deinit(); }, - .to_buffer => |*buf| buf.deinit(), } } @@ -469,10 +467,8 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { switch (d.output) { .ignore => {}, - .to_file => |to_file| { - var buf: [1024]u8 = undefined; - var w = to_file.file.writer(&buf); - writeToWriter(msg, &w.interface, to_file.config) catch { + .to_writer => |writer| { + writeToWriter(msg, writer.writer, writer.config) catch { return error.FatalError; }; }, @@ -487,9 +483,6 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { .location = msg.location, }); }, - .to_buffer => |*buf| { - writeToWriter(msg, buf.writer(), .no_color) catch return error.OutOfMemory; - }, } } diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index a0ba56e..6f69047 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -252,10 +252,10 @@ pub const usage = /// Process command line arguments, returns true if something was written to std_out. pub fn parseArgs( d: *Driver, - std_out: *std.io.Writer, - macro_buf: *std.io.Writer, + stdout: *std.io.Writer, + macro_writer: *std.io.Writer.Allocating, args: []const []const u8, -) Compilation.Error!bool { +) (Compilation.Error || std.io.Writer.Error)!bool { var i: usize = 1; var comment_arg: []const u8 = ""; var hosted: ?bool = null; @@ -267,14 +267,12 @@ pub fn parseArgs( const arg = args[i]; if (mem.startsWith(u8, arg, "-") and arg.len > 1) { if (mem.eql(u8, arg, "--help")) { - std_out.print(usage, .{args[0]}) catch |er| { - return d.fatal("unable to print usage: {s}", .{errorDescription(er)}); - }; + try stdout.print(usage, .{args[0]}); + try stdout.flush(); return true; } else if (mem.eql(u8, arg, "--version")) { - std_out.writeAll(@import("backend").version_str ++ "\n") catch |er| { - return d.fatal("unable to print version: {s}", .{errorDescription(er)}); - }; + try stdout.writeAll(@import("backend").version_str ++ "\n"); + try stdout.flush(); return true; } else if (mem.startsWith(u8, arg, "-D")) { var macro = arg["-D".len..]; @@ -291,7 +289,7 @@ pub fn parseArgs( value = macro[some + 1 ..]; macro = macro[0..some]; } - try macro_buf.print("#define {s} {s}\n", .{ macro, value }); + macro_writer.writer.print("#define {s} {s}\n", .{ macro, value }) catch return error.OutOfMemory; } else if (mem.startsWith(u8, arg, "-U")) { var macro = arg["-U".len..]; if (macro.len == 0) { @@ -302,7 +300,7 @@ pub fn parseArgs( } macro = args[i]; } - try macro_buf.print("#undef {s}\n", .{macro}); + macro_writer.writer.print("#undef {s}\n", .{macro}) catch return error.OutOfMemory; } else if (mem.eql(u8, arg, "-O")) { d.comp.code_gen_options.optimization_level = .@"0"; } else if (mem.startsWith(u8, arg, "-O")) { @@ -694,10 +692,7 @@ fn option(arg: []const u8, name: []const u8) ?[]const u8 { fn addSource(d: *Driver, path: []const u8) !Source { if (mem.eql(u8, "-", path)) { - var stdin = std.fs.File.stdin().reader(""); - const input = try stdin.interface.stream(d.comp.gpa, std.math.maxInt(u32)); - defer d.comp.gpa.free(input); - return d.comp.addSourceFromBuffer("", input); + return d.comp.addSourceFromFile(.stdin(), "", .user); } return d.comp.addSourceFromPath(path); } @@ -796,11 +791,24 @@ pub fn errorDescription(e: anyerror) []const u8 { /// The entry point of the Aro compiler. /// **MAY call `exit` if `fast_exit` is set.** pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_exit: bool, asm_gen_fn: ?AsmCodeGenFn) Compilation.Error!void { - var macro_writer: std.io.Writer.Allocating = .init(d.comp.gpa); - defer macro_writer.deinit(); + const user_macros = macros: { + var macro_writer: std.io.Writer.Allocating = .init(d.comp.gpa); + defer macro_writer.deinit(); - var std_out = std.fs.File.stderr().writer(""); - if (try parseArgs(d, &std_out.interface, ¯o_writer.writer, args)) return; + var stdout = std.fs.File.stdout().writer(""); + if (parseArgs(d, &stdout.interface, ¯o_writer, args) catch |er| switch (er) { + error.WriteFailed => return d.fatal("failed to write to stdout: {s}", .{errorDescription(er)}), + error.OutOfMemory => return error.OutOfMemory, + error.FatalError => return error.FatalError, + }) return; + if (macro_writer.getWritten().len > std.math.maxInt(u32)) { + return d.fatal("user provided macro source exceeded max size", .{}); + } + const contents = try macro_writer.toOwnedSlice(); + errdefer d.comp.gpa.free(contents); + + break :macros try d.comp.addSourceFromOwnedBuffer(contents, "", .user); + }; const linking = !(d.only_preprocess or d.only_syntax or d.only_compile or d.only_preprocess_and_compile); @@ -823,10 +831,6 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ error.AroIncludeNotFound => return d.fatal("unable to find Aro builtin headers", .{}), }; - const user_macros = d.comp.addSourceFromBuffer("", macro_writer.getWritten()) catch |er| switch (er) { - error.StreamTooLong => return d.fatal("user provided macro source exceeded max size", .{}), - else => |e| return e, - }; const builtin_macros = d.comp.generateBuiltinMacros(d.system_defines) catch |er| switch (er) { error.StreamTooLong => return d.fatal("builtin macro source exceeded max size", .{}), else => |e| return e, @@ -956,14 +960,12 @@ fn processSource( std.fs.File.stdout(); defer if (d.output_name != null) file.close(); - var buf: [1024]u8 = undefined; + var buf: [4096]u8 = undefined; var writer = file.writer(&buf); pp.prettyPrintTokens(&writer.interface, dump_mode) catch return d.fatal("unable to write result: {s}", .{errorDescription(writer.err.?)}); - writer.interface.flush() catch - return d.fatal("unable to write result: {s}", .{errorDescription(writer.err.?)}); if (fast_exit) std.process.exit(0); // Not linking, no need for cleanup. return; } @@ -972,10 +974,9 @@ fn processSource( defer tree.deinit(); if (d.verbose_ast) { - const stdout = std.io.getStdOut(); - var buf_writer = std.io.bufferedWriter(stdout.writer()); - tree.dump(d.detectConfig(stdout), buf_writer.writer()) catch {}; - buf_writer.flush() catch {}; + var buf: [4096]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&buf); + tree.dump(d.detectConfig(stdout.file), &stdout.interface) catch {}; } d.printDiagnosticsStats(); @@ -1039,10 +1040,9 @@ fn processSource( defer ir.deinit(d.comp.gpa); if (d.verbose_ir) { - const stdout = std.io.getStdOut(); - var buf_writer = std.io.bufferedWriter(stdout.writer()); - ir.dump(d.comp.gpa, d.detectConfig(stdout), buf_writer.writer()) catch {}; - buf_writer.flush() catch {}; + var buf: [4096]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&buf); + ir.dump(d.comp.gpa, d.detectConfig(stdout.file), &stdout.interface) catch {}; } var render_errors: Ir.Renderer.ErrorList = .{}; @@ -1066,8 +1066,10 @@ fn processSource( return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); defer out_file.close(); - obj.finish(out_file) catch |er| - return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(er) }); + var buf: [4096]u8 = undefined; + var writer = out_file.writer(&buf); + obj.finish(&writer.interface) catch + return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(writer.err.?) }); } if (d.only_compile or d.only_preprocess_and_compile) { @@ -1082,13 +1084,13 @@ fn processSource( } } -fn dumpLinkerArgs(items: []const []const u8) !void { - const stdout = std.io.getStdOut().writer(); +fn dumpLinkerArgs(w: *std.io.Writer, items: []const []const u8) !void { for (items, 0..) |item, i| { - if (i > 0) try stdout.writeByte(' '); - try stdout.print("\"{}\"", .{std.zig.fmtEscapes(item)}); + if (i > 0) try w.writeByte(' '); + try w.print("\"{f}\"", .{std.zig.fmtString(item)}); } - try stdout.writeByte('\n'); + try w.writeByte('\n'); + try w.flush(); } /// The entry point of the Aro compiler. @@ -1104,8 +1106,10 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil try tc.buildLinkerArgs(&argv); if (d.verbose_linker_args) { - dumpLinkerArgs(argv.items) catch |er| { - return d.fatal("unable to dump linker args: {s}", .{errorDescription(er)}); + var buf: [4096]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&buf); + dumpLinkerArgs(&stdout.interface, argv.items) catch { + return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)}); }; } var child = std.process.Child.init(argv.items, d.comp.gpa); diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index c65812c..3738496 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -213,8 +213,8 @@ fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Loca const prev_total = p.diagnostics.total; var sf = std.heap.stackFallback(1024, p.gpa); - var buf = std.ArrayList(u8).init(sf.get()); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(sf.get()); + defer allocating.deinit(); if (!char_info.isC99IdChar(codepoint)) { const diagnostic: Diagnostic = .c99_compat; @@ -228,11 +228,11 @@ fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Loca } if (char_info.isInvisible(codepoint)) { const diagnostic: Diagnostic = .unicode_zero_width; - try p.formatArgs(buf.writer(), diagnostic.fmt, .{Codepoint.init(codepoint)}); + p.formatArgs(&allocating.writer, diagnostic.fmt, .{Codepoint.init(codepoint)}) catch return error.OutOfMemory; try p.diagnostics.add(.{ .kind = diagnostic.kind, - .text = buf.items, + .text = allocating.getWritten(), .extension = diagnostic.extension, .opt = diagnostic.opt, .location = loc.expand(p.comp), @@ -240,11 +240,11 @@ fn checkIdentifierCodepointWarnings(p: *Parser, codepoint: u21, loc: Source.Loca } if (char_info.homoglyph(codepoint)) |resembles| { const diagnostic: Diagnostic = .unicode_homoglyph; - try p.formatArgs(buf.writer(), diagnostic.fmt, .{ Codepoint.init(codepoint), resembles }); + p.formatArgs(&allocating.writer, diagnostic.fmt, .{ Codepoint.init(codepoint), resembles }) catch return error.OutOfMemory; try p.diagnostics.add(.{ .kind = diagnostic.kind, - .text = buf.items, + .text = allocating.getWritten(), .extension = diagnostic.extension, .opt = diagnostic.opt, .location = loc.expand(p.comp), @@ -10112,12 +10112,12 @@ test "Node locations" { var comp = Compilation.init(std.testing.allocator, arena, &diagnostics, std.fs.cwd()); defer comp.deinit(); - const file = try comp.addSourceFromBuffer("file.c", + const file = try comp.addSourceFromBuffer( \\int foo = 5; \\int bar = 10; \\int main(void) {} \\ - ); + , "file.c"); const builtin_macros = try comp.generateBuiltinMacros(.no_system_defines); diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 5ca482f..6a7265f 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -827,15 +827,16 @@ fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: const source = pp.comp.getSource(raw.source); const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); - var buf: [1024]u8 = undefined; + var buf: [4096]u8 = undefined; var stderr = std.fs.File.stderr().writer(&buf); const w = &stderr.interface; - defer w.flush() catch {}; + w.print("{s}:{d}:{d}: ", .{ source.path, line_col.line_no, line_col.col }) catch return; w.print(fmt, args) catch return; w.writeByte('\n') catch return; w.writeAll(line_col.line) catch return; w.writeByte('\n') catch return; + w.flush() catch {}; } /// Consume next token, error if it is not an identifier. @@ -3458,6 +3459,7 @@ pub fn prettyPrintTokens(pp: *Preprocessor, w: *std.io.Writer, macro_dump_mode: switch (cur.id) { .eof => { if (!last_nl) try w.writeByte('\n'); + try w.flush(); return; }, .nl => { @@ -3467,6 +3469,7 @@ pub fn prettyPrintTokens(pp: *Preprocessor, w: *std.io.Writer, macro_dump_mode: newlines += 1; } else if (id == .eof) { if (!last_nl) try w.writeByte('\n'); + try w.flush(); return; } else if (id != .whitespace) { if (pp.linemarkers == .none) { @@ -3591,9 +3594,6 @@ test "Preserve pragma tokens sometimes" { var arena: std.heap.ArenaAllocator = .init(gpa); defer arena.deinit(); - var buf = std.ArrayList(u8).init(gpa); - defer buf.deinit(); - var diagnostics: Diagnostics = .{ .output = .ignore }; var comp = Compilation.init(gpa, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); @@ -3606,11 +3606,15 @@ test "Preserve pragma tokens sometimes" { pp.preserve_whitespace = true; assert(pp.linemarkers == .none); - const test_runner_macros = try comp.addSourceFromBuffer("", source_text); + const test_runner_macros = try comp.addSourceFromBuffer(source_text, ""); const eof = try pp.preprocess(test_runner_macros); try pp.addToken(eof); - try pp.prettyPrintTokens(buf.writer(), .result_only); - return gpa.dupe(u8, buf.items); + + var allocating: std.io.Writer.Allocating = .init(gpa); + defer allocating.deinit(); + + try pp.prettyPrintTokens(&allocating.writer, .result_only); + return allocating.toOwnedSlice(); } fn check(source_text: []const u8, expected: []const u8) !void { @@ -3728,7 +3732,7 @@ test "Include guards" { const path = try std.fs.path.join(arena, &.{ ".", "bar.h" }); - _ = try comp.addSourceFromBuffer(path, "int bar = 5;\n"); + _ = try comp.addSourceFromBuffer("int bar = 5;\n", path); var buf = std.ArrayList(u8).init(gpa); defer buf.deinit(); @@ -3744,7 +3748,7 @@ test "Include guards" { => try writer.print(template, .{ tok_id.lexeme().?, " BAR\n#endif" }), else => try writer.print(template, .{ tok_id.lexeme().?, "" }), } - const source = try comp.addSourceFromBuffer("test.h", buf.items); + const source = try comp.addSourceFromBuffer(buf.items, "test.h", ); _ = try pp.preprocess(source); try std.testing.expectEqual(expected_guards, pp.include_guards.count()); diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 1a06dcb..9fbdbe8 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -2334,7 +2334,7 @@ test "Tokenizer fuzz test" { var comp = Compilation.init(std.testing.allocator, arena.allocator(), undefined, std.fs.cwd()); defer comp.deinit(); - const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes); + const source = try comp.addSourceFromBuffer(input_bytes, "fuzz.c", ); var tokenizer: Tokenizer = .{ .buf = source.buf, @@ -2360,7 +2360,7 @@ fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, la if (langopts) |provided| { comp.langopts = provided; } - const source = try comp.addSourceFromBuffer("path", contents); + const source = try comp.addSourceFromBuffer(contents, "path", ); var tokenizer = Tokenizer{ .buf = source.buf, .source = source.id, diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index ccf2e17..4d9632d 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -2906,48 +2906,49 @@ pub fn tokSlice(tree: *const Tree, tok_i: TokenIndex) []const u8 { return tree.comp.locSlice(loc); } -pub fn dump(tree: *const Tree, config: std.io.tty.Config, writer: anytype) !void { +pub fn dump(tree: *const Tree, config: std.io.tty.Config, w: *std.io.Writer) !void { for (tree.root_decls.items) |i| { - try tree.dumpNode(i, 0, config, writer); - try writer.writeByte('\n'); + try tree.dumpNode(i, 0, config, w); + try w.writeByte('\n'); } + try w.flush(); } -fn dumpFieldAttributes(tree: *const Tree, attributes: []const Attribute, level: u32, writer: anytype) !void { +fn dumpFieldAttributes(tree: *const Tree, attributes: []const Attribute, level: u32, w: *std.io.Writer) !void { for (attributes) |attr| { - try writer.writeByteNTimes(' ', level); - try writer.print("field attr: {s}", .{@tagName(attr.tag)}); - try tree.dumpAttribute(attr, writer); + try w.splatByteAll(' ', level); + try w.print("field attr: {s}", .{@tagName(attr.tag)}); + try tree.dumpAttribute(attr, w); } } -fn dumpAttribute(tree: *const Tree, attr: Attribute, writer: anytype) !void { +fn dumpAttribute(tree: *const Tree, attr: Attribute, w: *std.io.Writer) !void { switch (attr.tag) { inline else => |tag| { const args = @field(attr.args, @tagName(tag)); const fields = @typeInfo(@TypeOf(args)).@"struct".fields; if (fields.len == 0) { - try writer.writeByte('\n'); + try w.writeByte('\n'); return; } - try writer.writeByte(' '); + try w.writeByte(' '); inline for (fields, 0..) |f, i| { if (comptime std.mem.eql(u8, f.name, "__name_tok")) continue; if (i != 0) { - try writer.writeAll(", "); + try w.writeAll(", "); } - try writer.writeAll(f.name); - try writer.writeAll(": "); + try w.writeAll(f.name); + try w.writeAll(": "); switch (f.type) { - Interner.Ref => try writer.print("\"{s}\"", .{tree.interner.get(@field(args, f.name)).bytes}), - ?Interner.Ref => try writer.print("\"{?s}\"", .{if (@field(args, f.name)) |str| tree.interner.get(str).bytes else null}), + Interner.Ref => try w.print("\"{s}\"", .{tree.interner.get(@field(args, f.name)).bytes}), + ?Interner.Ref => try w.print("\"{?s}\"", .{if (@field(args, f.name)) |str| tree.interner.get(str).bytes else null}), else => switch (@typeInfo(f.type)) { - .@"enum" => try writer.writeAll(@tagName(@field(args, f.name))), - else => try writer.print("{any}", .{@field(args, f.name)}), + .@"enum" => try w.writeAll(@tagName(@field(args, f.name))), + else => try w.print("{any}", .{@field(args, f.name)}), }, } } - try writer.writeByte('\n'); + try w.writeByte('\n'); return; }, } @@ -2970,7 +2971,7 @@ fn dumpNode( const ATTRIBUTE = std.io.tty.Color.bright_yellow; const node = node_index.get(tree); - try w.writeByteNTimes(' ', level); + try w.splatByteAll(' ', level); if (config == .no_color) { if (node.isImplicit()) try w.writeAll("implicit "); @@ -3038,7 +3039,7 @@ fn dumpNode( var it = Attribute.Iterator.initType(qt, tree.comp); while (it.next()) |item| { const attr, _ = item; - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.print("attr: {s}", .{@tagName(attr.tag)}); try tree.dumpAttribute(attr, w); } @@ -3048,21 +3049,21 @@ fn dumpNode( switch (node) { .empty_decl => {}, .global_asm, .gnu_asm_simple => |@"asm"| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try tree.dumpNode(@"asm".asm_str, level + delta, config, w); }, .static_assert => |assert| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("condition:\n"); try tree.dumpNode(assert.cond, level + delta, config, w); if (assert.message) |some| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("diagnostic:\n"); try tree.dumpNode(some, level + delta, config, w); } }, .function => |function| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try config.setColor(w, ATTRIBUTE); if (function.static) try w.writeAll("static "); @@ -3075,12 +3076,12 @@ fn dumpNode( try config.setColor(w, .reset); if (function.body) |body| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("body:\n"); try tree.dumpNode(body, level + delta, config, w); } if (function.definition) |definition| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("definition: "); try config.setColor(w, NAME); try w.print("0x{X}\n", .{@intFromEnum(definition)}); @@ -3088,14 +3089,14 @@ fn dumpNode( } }, .typedef => |typedef| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("name: "); try config.setColor(w, NAME); try w.print("{s}\n", .{tree.tokSlice(typedef.name_tok)}); try config.setColor(w, .reset); }, .param => |param| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); switch (param.storage_class) { .auto => {}, @@ -3112,7 +3113,7 @@ fn dumpNode( try config.setColor(w, .reset); }, .variable => |variable| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try config.setColor(w, ATTRIBUTE); switch (variable.storage_class) { @@ -3131,12 +3132,12 @@ fn dumpNode( if (variable.initializer) |some| { try config.setColor(w, .reset); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("init:\n"); try tree.dumpNode(some, level + delta, config, w); } if (variable.definition) |definition| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("definition: "); try config.setColor(w, NAME); try w.print("0x{X}\n", .{@intFromEnum(definition)}); @@ -3144,13 +3145,13 @@ fn dumpNode( } }, .enum_field => |field| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("name: "); try config.setColor(w, NAME); try w.print("{s}\n", .{tree.tokSlice(field.name_tok)}); try config.setColor(w, .reset); if (field.init) |some| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("init:\n"); try tree.dumpNode(some, level + delta, config, w); } @@ -3158,14 +3159,14 @@ fn dumpNode( .record_field => |field| { const name_tok_id = tree.tokens.items(.id)[field.name_or_first_tok]; if (name_tok_id == .identifier or name_tok_id == .extended_identifier) { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("name: "); try config.setColor(w, NAME); try w.print("{s}\n", .{tree.tokSlice(field.name_or_first_tok)}); try config.setColor(w, .reset); } if (field.bit_width) |some| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("bits:\n"); try tree.dumpNode(some, level + delta, config, w); } @@ -3213,7 +3214,7 @@ fn dumpNode( } }, .union_init_expr => |init| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("field index: "); try config.setColor(w, LITERAL); try w.print("{d}\n", .{init.field_index}); @@ -3224,7 +3225,7 @@ fn dumpNode( }, .compound_literal_expr => |literal| { if (literal.storage_class != .auto or literal.thread_local) { - try w.writeByteNTimes(' ', level + half - 1); + try w.splatByteAll(' ', level + half - 1); try config.setColor(w, ATTRIBUTE); switch (literal.storage_class) { @@ -3240,24 +3241,24 @@ fn dumpNode( try tree.dumpNode(literal.initializer, level + half, config, w); }, .labeled_stmt => |labeled| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("label: "); try config.setColor(w, LITERAL); try w.print("{s}\n", .{tree.tokSlice(labeled.label_tok)}); try config.setColor(w, .reset); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("stmt:\n"); try tree.dumpNode(labeled.body, level + delta, config, w); }, .case_stmt => |case| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); if (case.end) |some| { try w.writeAll("range start:\n"); try tree.dumpNode(case.start, level + delta, config, w); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("range end:\n"); try tree.dumpNode(some, level + delta, config, w); } else { @@ -3265,37 +3266,37 @@ fn dumpNode( try tree.dumpNode(case.start, level + delta, config, w); } - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("stmt:\n"); try tree.dumpNode(case.body, level + delta, config, w); }, .default_stmt => |default| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("stmt:\n"); try tree.dumpNode(default.body, level + delta, config, w); }, .binary_cond_expr, .cond_expr, .builtin_choose_expr => |conditional| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("cond:\n"); try tree.dumpNode(conditional.cond, level + delta, config, w); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("then:\n"); try tree.dumpNode(conditional.then_expr, level + delta, config, w); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("else:\n"); try tree.dumpNode(conditional.else_expr, level + delta, config, w); }, .builtin_types_compatible_p => |call| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("lhs: "); try config.setColor(w, TYPE); try call.lhs.dump(tree.comp, w); try w.writeByte('\n'); try config.setColor(w, .reset); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("rhs: "); try config.setColor(w, TYPE); try call.rhs.dump(tree.comp, w); @@ -3303,21 +3304,21 @@ fn dumpNode( try config.setColor(w, .reset); }, .builtin_convertvector => |convert| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("operand:\n"); try tree.dumpNode(convert.operand, level + delta, config, w); }, .builtin_shufflevector => |shuffle| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("lhs:\n"); try tree.dumpNode(shuffle.lhs, level + delta, config, w); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("rhs:\n"); try tree.dumpNode(shuffle.rhs, level + delta, config, w); if (shuffle.indexes.len > 0) { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("indexes:\n"); for (shuffle.indexes) |index| { try tree.dumpNode(index, level + delta, config, w); @@ -3325,51 +3326,51 @@ fn dumpNode( } }, .if_stmt => |@"if"| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("cond:\n"); try tree.dumpNode(@"if".cond, level + delta, config, w); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("then:\n"); try tree.dumpNode(@"if".then_body, level + delta, config, w); if (@"if".else_body) |some| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("else:\n"); try tree.dumpNode(some, level + delta, config, w); } }, .switch_stmt => |@"switch"| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("cond:\n"); try tree.dumpNode(@"switch".cond, level + delta, config, w); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("body:\n"); try tree.dumpNode(@"switch".body, level + delta, config, w); }, .while_stmt => |@"while"| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("cond:\n"); try tree.dumpNode(@"while".cond, level + delta, config, w); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("body:\n"); try tree.dumpNode(@"while".body, level + delta, config, w); }, .do_while_stmt => |do| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("cond:\n"); try tree.dumpNode(do.cond, level + delta, config, w); - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("body:\n"); try tree.dumpNode(do.body, level + delta, config, w); }, .for_stmt => |@"for"| { switch (@"for".init) { .decls => |decls| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("decl:\n"); for (decls) |decl| { try tree.dumpNode(decl, level + delta, config, w); @@ -3377,41 +3378,41 @@ fn dumpNode( } }, .expr => |expr| if (expr) |some| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("init:\n"); try tree.dumpNode(some, level + delta, config, w); }, } if (@"for".cond) |some| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("cond:\n"); try tree.dumpNode(some, level + delta, config, w); } if (@"for".incr) |some| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("incr:\n"); try tree.dumpNode(some, level + delta, config, w); } - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("body:\n"); try tree.dumpNode(@"for".body, level + delta, config, w); }, .addr_of_label => |addr| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("label: "); try config.setColor(w, LITERAL); try w.print("{s}\n", .{tree.tokSlice(addr.label_tok)}); try config.setColor(w, .reset); }, .goto_stmt => |goto| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("label: "); try config.setColor(w, LITERAL); try w.print("{s}\n", .{tree.tokSlice(goto.label_tok)}); try config.setColor(w, .reset); }, .computed_goto_stmt => |goto| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("expr:\n"); try tree.dumpNode(goto.expr, level + delta, config, w); }, @@ -3419,7 +3420,7 @@ fn dumpNode( .return_stmt => |ret| { switch (ret.operand) { .expr => |expr| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("expr:\n"); try tree.dumpNode(expr, level + delta, config, w); }, @@ -3428,12 +3429,12 @@ fn dumpNode( } }, .call_expr => |call| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("callee:\n"); try tree.dumpNode(call.callee, level + delta, config, w); if (call.args.len > 0) { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("args:\n"); for (call.args) |arg| { try tree.dumpNode(arg, level + delta, config, w); @@ -3441,14 +3442,14 @@ fn dumpNode( } }, .builtin_call_expr => |call| { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("name: "); try config.setColor(w, NAME); try w.print("{s}\n", .{tree.tokSlice(call.builtin_tok)}); try config.setColor(w, .reset); if (call.args.len > 0) { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("args:\n"); for (call.args) |arg| { try tree.dumpNode(arg, level + delta, config, w); @@ -3486,11 +3487,11 @@ fn dumpNode( .div_expr, .mod_expr, => |bin| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("lhs:\n"); try tree.dumpNode(bin.lhs, level + delta, config, w); - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("rhs:\n"); try tree.dumpNode(bin.rhs, level + delta, config, w); }, @@ -3511,19 +3512,19 @@ fn dumpNode( .stmt_expr, .imaginary_literal, => |un| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("operand:\n"); try tree.dumpNode(un.operand, level + delta, config, w); }, .decl_ref_expr, .enumeration_ref => |dr| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("name: "); try config.setColor(w, NAME); try w.print("{s}\n", .{tree.tokSlice(dr.name_tok)}); try config.setColor(w, .reset); }, .builtin_ref => |dr| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("name: "); try config.setColor(w, NAME); try w.print("{s}\n", .{tree.tokSlice(dr.name_tok)}); @@ -3537,7 +3538,7 @@ fn dumpNode( .string_literal_expr, => {}, .member_access_expr, .member_access_ptr_expr => |access| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("lhs:\n"); try tree.dumpNode(access.base, level + delta, config, w); @@ -3545,28 +3546,28 @@ fn dumpNode( if (base_qt.get(tree.comp, .pointer)) |some| base_qt = some.child; const fields = (base_qt.getRecord(tree.comp) orelse return).fields; - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("name: "); try config.setColor(w, NAME); try w.print("{s}\n", .{fields[access.member_index].name.lookup(tree.comp)}); try config.setColor(w, .reset); }, .array_access_expr => |access| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("base:\n"); try tree.dumpNode(access.base, level + delta, config, w); - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("index:\n"); try tree.dumpNode(access.index, level + delta, config, w); }, .sizeof_expr, .alignof_expr => |type_info| { if (type_info.expr) |some| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("expr:\n"); try tree.dumpNode(some, level + delta, config, w); } else { - try w.writeByteNTimes(' ', level + half); + try w.splatByteAll(' ', level + half); try w.writeAll("operand type: "); try config.setColor(w, TYPE); try type_info.operand_qt.dump(tree.comp, w); @@ -3575,15 +3576,15 @@ fn dumpNode( } }, .generic_expr => |generic| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("controlling:\n"); try tree.dumpNode(generic.controlling, level + delta, config, w); - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("chosen:\n"); try tree.dumpNode(generic.chosen, level + delta, config, w); if (generic.rest.len > 0) { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("rest:\n"); for (generic.rest) |expr| { try tree.dumpNode(expr, level + delta, config, w); @@ -3597,7 +3598,7 @@ fn dumpNode( try tree.dumpNode(default.expr, level + delta, config, w); }, .array_filler_expr => |filler| { - try w.writeByteNTimes(' ', level + 1); + try w.splatByteAll(' ', level + 1); try w.writeAll("count: "); try config.setColor(w, LITERAL); try w.print("{d}\n", .{filler.count}); diff --git a/src/assembly_backend/x86_64.zig b/src/assembly_backend/x86_64.zig index eb84cf9..1032799 100644 --- a/src/assembly_backend/x86_64.zig +++ b/src/assembly_backend/x86_64.zig @@ -13,12 +13,11 @@ const Value = aro.Value; const AsmCodeGen = @This(); const Error = aro.Compilation.Error; -const Writer = std.ArrayListUnmanaged(u8).Writer; tree: *const Tree, comp: *Compilation, -text: Writer, -data: Writer, +text: *std.io.Writer, +data: *std.io.Writer, const StorageUnit = enum(u8) { byte = 8, @@ -36,11 +35,11 @@ const StorageUnit = enum(u8) { } }; -fn serializeInt(value: u64, storage_unit: StorageUnit, w: Writer) !void { +fn serializeInt(value: u64, storage_unit: StorageUnit, w: *std.io.Writer) !void { try w.print(" .{s} 0x{x}\n", .{ @tagName(storage_unit), storage_unit.trunc(value) }); } -fn serializeFloat(comptime T: type, value: T, w: Writer) !void { +fn serializeFloat(comptime T: type, value: T, w: *std.io.Writer) !void { switch (T) { f128 => { const bytes = std.mem.asBytes(&value); @@ -135,28 +134,44 @@ fn emitValue(c: *AsmCodeGen, qt: QualType, node: Node.Index) !void { } pub fn genAsm(tree: *const Tree) Error!Assembly { - var data: std.ArrayListUnmanaged(u8) = .empty; - defer data.deinit(tree.comp.gpa); + var data: std.io.Writer.Allocating = .init(tree.comp.gpa); + defer data.deinit(); - var text: std.ArrayListUnmanaged(u8) = .empty; - defer text.deinit(tree.comp.gpa); + var text: std.io.Writer.Allocating = .init(tree.comp.gpa); + defer text.deinit(); var codegen: AsmCodeGen = .{ .tree = tree, .comp = tree.comp, - .text = text.writer(tree.comp.gpa), - .data = data.writer(tree.comp.gpa), + .text = &text.writer, + .data = &data.writer, }; - if (tree.comp.code_gen_options.debug) { - const sources = tree.comp.sources.values(); + codegen.genDecls() catch |err| switch (err) { + error.WriteFailed => return error.OutOfMemory, + error.OutOfMemory => return error.OutOfMemory, + error.FatalError => return error.FatalError, + }; + + const text_slice = try text.toOwnedSlice(); + errdefer tree.comp.gpa.free(text_slice); + const data_slice = try data.toOwnedSlice(); + return .{ + .text = text_slice, + .data = data_slice, + }; +} + +fn genDecls(c: *AsmCodeGen) !void { + if (c.tree.comp.code_gen_options.debug) { + const sources = c.tree.comp.sources.values(); for (sources) |source| { - try codegen.data.print(" .file {d} \"{s}\"\n", .{ @intFromEnum(source.id) - 1, source.path }); + try c.data.print(" .file {d} \"{s}\"\n", .{ @intFromEnum(source.id) - 1, source.path }); } } - for (codegen.tree.root_decls.items) |decl| { - switch (decl.get(codegen.tree)) { + for (c.tree.root_decls.items) |decl| { + switch (decl.get(c.tree)) { .static_assert, .typedef, .struct_decl, @@ -166,22 +181,15 @@ pub fn genAsm(tree: *const Tree) Error!Assembly { .function => |function| { if (function.body == null) continue; - try codegen.genFn(function); + try c.genFn(function); }, - .variable => |variable| try codegen.genVar(variable), + .variable => |variable| try c.genVar(variable), else => unreachable, } } - try codegen.text.writeAll(" .section .note.GNU-stack,\"\",@progbits\n"); - const text_slice = try text.toOwnedSlice(tree.comp.gpa); - errdefer tree.comp.gpa.free(text_slice); - const data_slice = try data.toOwnedSlice(tree.comp.gpa); - return .{ - .text = text_slice, - .data = data_slice, - }; + try c.text.writeAll(" .section .note.GNU-stack,\"\",@progbits\n"); } fn genFn(c: *AsmCodeGen, function: Node.Function) !void { diff --git a/src/backend/Ir.zig b/src/backend/Ir.zig index 3a1aac3..a60916f 100644 --- a/src/backend/Ir.zig +++ b/src/backend/Ir.zig @@ -386,6 +386,7 @@ pub fn dump(ir: *const Ir, gpa: Allocator, config: std.io.tty.Config, w: *std.io for (ir.decls.keys(), ir.decls.values()) |name, *decl| { try ir.dumpDecl(decl, gpa, name, config, w); } + try w.flush(); } fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, config: std.io.tty.Config, w: *std.io.Writer) !void { @@ -650,7 +651,7 @@ fn writeValue(ir: Ir, val: Interner.Ref, config: std.io.tty.Config, w: *std.io.W .float => |repr| switch (repr) { inline else => |x| return w.print("{d}", .{@as(f64, @floatCast(x))}), }, - .bytes => |b| return std.zig.stringEscape(b, "", .{}, w), + .bytes => |b| return std.zig.stringEscape(b, w), else => unreachable, // not a value } } diff --git a/src/backend/Object.zig b/src/backend/Object.zig index 98355e8..90d5339 100644 --- a/src/backend/Object.zig +++ b/src/backend/Object.zig @@ -65,9 +65,9 @@ pub fn addRelocation(obj: *Object, name: []const u8, section: Section, address: } } -pub fn finish(obj: *Object, file: std.fs.File) !void { +pub fn finish(obj: *Object, w: *std.io.Writer) !void { switch (obj.format) { - .elf => return @as(*Elf, @alignCast(@fieldParentPtr("obj", obj))).finish(file), + .elf => return @as(*Elf, @alignCast(@fieldParentPtr("obj", obj))).finish(w), else => unreachable, } } diff --git a/src/backend/Object/Elf.zig b/src/backend/Object/Elf.zig index 105d075..49bf6f3 100644 --- a/src/backend/Object/Elf.zig +++ b/src/backend/Object/Elf.zig @@ -170,10 +170,7 @@ pub fn addRelocation(elf: *Elf, name: []const u8, section_kind: Object.Section, /// relocations /// strtab /// section headers -pub fn finish(elf: *Elf, file: std.fs.File) !void { - var buf_writer = std.io.bufferedWriter(file.writer()); - const w = buf_writer.writer(); - +pub fn finish(elf: *Elf, w: *std.io.Writer) !void { var num_sections: std.elf.Half = additional_sections; var relocations_len: std.elf.Elf64_Off = 0; var sections_len: std.elf.Elf64_Off = 0; @@ -221,7 +218,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void { } // pad to 8 bytes - try w.writeByteNTimes(0, @intCast(symtab_offset_aligned - symtab_offset)); + try w.splatByteAll(0, @intCast(symtab_offset_aligned - symtab_offset)); var name_offset: u32 = strtab_default.len; // write symbols @@ -293,7 +290,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void { } // pad to 16 bytes - try w.writeByteNTimes(0, @intCast(sh_offset_aligned - sh_offset)); + try w.splatByteAll(0, @intCast(sh_offset_aligned - sh_offset)); // mandatory null header try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Shdr)); @@ -374,5 +371,5 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void { name_offset += @as(u32, @intCast(entry.key_ptr.len + ".\x00".len)) + rela_name_offset; } } - try buf_writer.flush(); + try w.flush(); } diff --git a/src/main.zig b/src/main.zig index 8113c6e..8db0f88 100644 --- a/src/main.zig +++ b/src/main.zig @@ -39,11 +39,12 @@ pub fn main() u8 { }; defer gpa.free(aro_name); - const stderr_file: std.fs.File = .stderr(); + var buf: [1024]u8 = undefined; + var stderr = std.fs.File.stderr().writer(&buf); var diagnostics: Diagnostics = .{ - .output = .{ .to_file = .{ - .config = .detect(stderr_file), - .file = stderr_file, + .output = .{ .to_writer = .{ + .config = .detect(stderr.file), + .writer = &stderr.interface, } }, }; -- Gitee From 89f72f471af12994046316e1483782b2f39ae696 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 12 Jul 2025 14:16:04 +0300 Subject: [PATCH 108/117] runners --- test/record_runner.zig | 42 +++++++++++---------- test/runner.zig | 84 ++++++++++++++++++++++++------------------ 2 files changed, 70 insertions(+), 56 deletions(-) diff --git a/test/record_runner.zig b/test/record_runner.zig index c7fb70f..0a598ce 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -122,7 +122,7 @@ pub fn main() !void { var cases_dir = try std.fs.cwd().openDir(args[1], .{ .iterate = true }); defer cases_dir.close(); var buf: [1024]u8 = undefined; - var buf_strm = std.io.fixedBufferStream(&buf); + var writer: std.io.Writer = .fixed(&buf); var it = cases_dir.iterate(); while (try it.next()) |entry| { @@ -133,9 +133,9 @@ pub fn main() !void { } if (std.ascii.indexOfIgnoreCase(entry.name, "_test.c") != null) { - buf_strm.reset(); - try buf_strm.writer().print("{s}{c}{s}", .{ args[1], std.fs.path.sep, entry.name }); - try cases.append(try gpa.dupe(u8, buf[0..buf_strm.pos])); + _ = writer.consumeAll(); + try writer.print("{s}{c}{s}", .{ args[1], std.fs.path.sep, entry.name }); + try cases.append(try gpa.dupe(u8, writer.buffered())); } } } @@ -182,7 +182,7 @@ pub fn main() !void { thread_pool.waitAndWork(&wait_group); root_node.end(); - std.debug.print("max mem used = {:.2}\n", .{std.fmt.fmtIntSizeBin(stats.max_alloc)}); + std.debug.print("max mem used = {Bi:.2}\n", .{stats.max_alloc}); if (stats.ok_count == cases.items.len and stats.skip_count == 0) { print("All {d} tests passed ({d} invalid targets)\n", .{ stats.ok_count, stats.invalid_target_count }); } else if (stats.fail_count == 0) { @@ -258,17 +258,17 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, else => {}, } - var case_name = std.ArrayList(u8).init(gpa); - defer case_name.deinit(); + var name_buf: [1024]u8 = undefined; + var name_writer: std.io.Writer = .fixed(&name_buf); const test_name = std.mem.sliceTo(std.fs.path.basename(path), '_'); - try case_name.writer().print("{s} | {s} | {s}", .{ + try name_writer.print("{s} | {s} | {s}", .{ test_name, test_case.target, test_case.c_define, }); - var case_node = stats.root_node.start(case_name.items, 0); + var case_node = stats.root_node.start(name_writer.buffered(), 0); defer case_node.end(); const file = comp.addSourceFromBuffer(path, test_case.source) catch |err| { @@ -277,19 +277,17 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, return; }; - var macro_buf = std.ArrayList(u8).init(comp.gpa); - defer macro_buf.deinit(); - comp.langopts.setEmulatedCompiler(aro.target_util.systemCompiler(comp.target)); - const mac_writer = macro_buf.writer(); - try mac_writer.print("#define {s}\n", .{test_case.c_define}); + var macro_buf: [1024]u8 = undefined; + var macro_writer: std.io.Writer = .fixed(¯o_buf); + try macro_writer.print("#define {s}\n", .{test_case.c_define}); if (comp.langopts.emulate == .msvc) { comp.langopts.setMSExtensions(true); - try mac_writer.writeAll("#define MSVC\n"); + try macro_writer.writeAll("#define MSVC\n"); } - const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); + const user_macros = try comp.addSourceFromBuffer("", macro_writer.buffered()); const builtin_macros = try comp.generateBuiltinMacros(.include_system_defines); var pp = try aro.Preprocessor.initDefault(&comp); @@ -306,7 +304,11 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, var tree = try aro.Parser.parse(&pp); defer tree.deinit(); - tree.dump(.no_color, std.io.null_writer) catch {}; + { + var discard_buf: [256]u8 = undefined; + var discarding: std.io.Writer.Discarding = .init(&discard_buf); + tree.dump(.no_color, &discarding.writer) catch {}; + } if (test_single_target) { printDiagnostics(&diagnostics); @@ -319,10 +321,10 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, } var buf: [128]u8 = undefined; - var buf_strm = std.io.fixedBufferStream(&buf); - try buf_strm.writer().print("{s}|{s}", .{ test_case.target, test_name }); + var writer: std.io.Writer = .fixed(&buf); + try writer.print("{s}|{s}", .{ test_case.target, test_name }); - const expected = compErr.get(buf[0..buf_strm.pos]) orelse ExpectedFailure{}; + const expected = compErr.get(writer.buffered()) orelse ExpectedFailure{}; if (diagnostics.total == 0 and expected.any()) { std.debug.print("\nTest Passed when failures expected:\n\texpected:{any}\n", .{expected}); diff --git a/test/runner.zig b/test/runner.zig index 99fe838..c44822e 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -18,7 +18,7 @@ const AddCommandLineArgsResult = struct { }; /// Returns only_preprocess and line_markers settings if saw -E -fn addCommandLineArgs(comp: *aro.Compilation, file: aro.Source, macro_buf: anytype) !AddCommandLineArgsResult { +fn addCommandLineArgs(comp: *aro.Compilation, file: aro.Source, macro_writer: *std.io.Writer.Allocating) !AddCommandLineArgsResult { var only_preprocess = false; var line_markers: aro.Preprocessor.Linemarkers = .none; var system_defines: aro.Compilation.SystemDefinesMode = .include_system_defines; @@ -33,7 +33,10 @@ fn addCommandLineArgs(comp: *aro.Compilation, file: aro.Source, macro_buf: anyty var driver: aro.Driver = .{ .comp = comp, .diagnostics = comp.diagnostics }; defer driver.deinit(); - _ = try driver.parseArgs(std.io.null_writer, macro_buf, test_args.items); + + var discard_buf: [256]u8 = undefined; + var discarding: std.io.Writer.Discarding = .init(&discard_buf); + _ = try driver.parseArgs(&discarding.writer, macro_writer, test_args.items); only_preprocess = driver.only_preprocess; system_defines = driver.system_defines; dump_mode = driver.debug_dump_letters.getPreprocessorDumpMode(); @@ -169,8 +172,14 @@ pub fn main() !void { .estimated_total_items = cases.items.len, }); + var diag_buf: std.io.Writer.Allocating = .init(gpa); + defer diag_buf.deinit(); + var diagnostics: aro.Diagnostics = .{ - .output = .{ .to_buffer = .init(gpa) }, + .output = .{ .to_writer = .{ + .writer = &diag_buf.writer, + .config = .no_color, + } }, }; defer diagnostics.deinit(); @@ -204,11 +213,7 @@ pub fn main() !void { var fail_count: u32 = 0; var skip_count: u32 = 0; next_test: for (cases.items) |path| { - const diag_buf = diagnostics.output.to_buffer; - diagnostics = .{ - .output = .{ .to_buffer = diag_buf }, - }; - diagnostics.output.to_buffer.items.len = 0; + diag_buf.shrinkRetainingCapacity(0); var comp = initial_comp; defer { @@ -235,11 +240,11 @@ pub fn main() !void { continue; }; - var macro_buf = std.ArrayList(u8).init(comp.gpa); - defer macro_buf.deinit(); + var macro_writer: std.io.Writer.Allocating = .init(comp.gpa); + defer macro_writer.deinit(); - const only_preprocess, const linemarkers, const system_defines, const dump_mode = try addCommandLineArgs(&comp, file, macro_buf.writer()); - const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); + const only_preprocess, const linemarkers, const system_defines, const dump_mode = try addCommandLineArgs(&comp, file, ¯o_writer); + const user_macros = try comp.addSourceFromBuffer("", macro_writer.getWritten()); const builtin_macros = try comp.generateBuiltinMacros(system_defines); @@ -280,15 +285,17 @@ pub fn main() !void { } if (only_preprocess) { - if (try checkExpectedErrors(&pp, &buf)) |some| { + if (try checkExpectedErrors(&pp, &buf, diag_buf.getWritten())) |some| { if (!some) { std.debug.print("in case {s}\n", .{case}); fail_count += 1; continue; } } else { - const stderr = std.io.getStdErr(); - try stderr.writeAll(pp.diagnostics.output.to_buffer.items); + var stderr_buf: [4096]u8 = undefined; + var stderr = std.fs.File.stderr().writer(&stderr_buf); + try stderr.interface.writeAll(diag_buf.getWritten()); + try stderr.interface.flush(); if (comp.diagnostics.errors != 0) { std.debug.print("in case {s}\n", .{case}); @@ -309,13 +316,13 @@ pub fn main() !void { }; defer gpa.free(expected_output); - var output = std.ArrayList(u8).init(gpa); + var output: std.io.Writer.Allocating = .init(gpa); defer output.deinit(); - try pp.prettyPrintTokens(output.writer(), dump_mode); + try pp.prettyPrintTokens(&output.writer, dump_mode); if (pp.defines.contains("CHECK_PARTIAL_MATCH")) { - const index = std.mem.indexOf(u8, output.items, expected_output); + const index = std.mem.indexOf(u8, output.getWritten(), expected_output); if (index != null) { ok_count += 1; } else { @@ -324,11 +331,11 @@ pub fn main() !void { std.debug.print("\n====== expected to find: =========\n", .{}); std.debug.print("{s}", .{expected_output}); std.debug.print("\n======== but did not find it in this: =========\n", .{}); - std.debug.print("{s}", .{output.items}); + std.debug.print("{s}", .{output.getWritten()}); std.debug.print("\n======================================\n", .{}); } } else { - if (std.testing.expectEqualStrings(expected_output, output.items)) + if (std.testing.expectEqualStrings(expected_output, output.getWritten())) ok_count += 1 else |_| fail_count += 1; @@ -340,7 +347,7 @@ pub fn main() !void { var tree = aro.Parser.parse(&pp) catch |err| switch (err) { error.FatalError => { - if (try checkExpectedErrors(&pp, &buf)) |some| { + if (try checkExpectedErrors(&pp, &buf, diag_buf.getWritten())) |some| { if (some) ok_count += 1 else { std.debug.print("in case {s}\n", .{case}); fail_count += 1; @@ -357,16 +364,20 @@ pub fn main() !void { const maybe_ast = std.fs.cwd().readFileAlloc(gpa, ast_path, std.math.maxInt(u32)) catch null; if (maybe_ast) |expected_ast| { defer gpa.free(expected_ast); - var actual_ast = std.ArrayList(u8).init(gpa); + var actual_ast: std.io.Writer.Allocating = .init(gpa); defer actual_ast.deinit(); - try tree.dump(.no_color, actual_ast.writer()); - std.testing.expectEqualStrings(expected_ast, actual_ast.items) catch { + try tree.dump(.no_color, &actual_ast.writer); + std.testing.expectEqualStrings(expected_ast, actual_ast.getWritten()) catch { std.debug.print("in case {s}\n", .{case}); fail_count += 1; break; }; - } else tree.dump(.no_color, std.io.null_writer) catch {}; + } else { + var discard_buf: [256]u8 = undefined; + var discarding: std.io.Writer.Discarding = .init(&discard_buf); + tree.dump(.no_color, &discarding.writer) catch {}; + } if (expected_types) |types| { const test_fn = for (tree.root_decls.items) |decl| { @@ -419,7 +430,7 @@ pub fn main() !void { } } - if (try checkExpectedErrors(&pp, &buf)) |some| { + if (try checkExpectedErrors(&pp, &buf, diag_buf.getWritten())) |some| { if (some) ok_count += 1 else { std.debug.print("in case {s}\n", .{case}); fail_count += 1; @@ -428,8 +439,11 @@ pub fn main() !void { } if (pp.defines.contains("NO_ERROR_VALIDATION")) continue; - const stderr = std.io.getStdErr(); - try stderr.writeAll(pp.diagnostics.output.to_buffer.items); + { + var stderr_buf: [4096]u8 = undefined; + var stderr = std.fs.File.stderr().writer(&stderr_buf); + try stderr.interface.writeAll(diag_buf.getWritten()); + } if (pp.defines.get("EXPECTED_OUTPUT")) |macro| blk: { if (comp.diagnostics.errors != 0) break :blk; @@ -519,12 +533,10 @@ pub fn main() !void { } // returns true if passed -fn checkExpectedErrors(pp: *aro.Preprocessor, buf: *std.ArrayList(u8)) !?bool { +fn checkExpectedErrors(pp: *aro.Preprocessor, buf: *std.ArrayList(u8), errors: []const u8) !?bool { const macro = pp.defines.get("EXPECTED_ERRORS") orelse return null; const expected_count = pp.diagnostics.total; - const errors = pp.diagnostics.output.to_buffer.items; - if (macro.is_func) { std.debug.print("invalid EXPECTED_ERRORS {}\n", .{macro}); return false; @@ -609,12 +621,12 @@ const StmtTypeDumper = struct { const maybe_ret = node.get(tree); if (maybe_ret == .return_stmt and maybe_ret.return_stmt.operand == .implicit) return; - var buf = std.ArrayList(u8).init(self.types.allocator); - defer buf.deinit(); + var allocating: std.io.Writer.Allocating = .init(self.types.allocator); + defer allocating.deinit(); - node.qt(tree).dump(tree.comp, buf.writer()) catch {}; - const owned = try buf.toOwnedSlice(); - errdefer buf.allocator.free(owned); + node.qt(tree).dump(tree.comp, &allocating.writer) catch {}; + const owned = try allocating.toOwnedSlice(); + errdefer allocating.allocator.free(owned); try self.types.append(owned); } -- Gitee From f1c53e524d127b7ead9a50a5917c0d08f9b2ae71 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 12 Jul 2025 14:16:39 +0300 Subject: [PATCH 109/117] fmt --- src/aro/Compilation.zig | 6 +++--- src/aro/Preprocessor.zig | 2 +- src/aro/Tokenizer.zig | 7 +++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 6bab964..2a9e256 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -1276,7 +1276,7 @@ pub fn addSourceFromFile(comp: *Compilation, file: std.fs.File, path: []const u8 error.WriteFailed => return error.OutOfMemory, error.ReadFailed => return reader.err.?, }; - + const contents = try allocating.toOwnedSlice(); errdefer comp.gpa.free(contents); return comp.addSourceFromOwnedBuffer(contents, path, kind); @@ -1717,8 +1717,8 @@ test "addSourceFromBuffer" { var comp = Compilation.init(allocator, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); - _ = try comp.addSourceFromBuffer("spliced\\\nbuffer\n", "path", ); - _ = try comp.addSourceFromBuffer("non-spliced buffer\n", "path", ); + _ = try comp.addSourceFromBuffer("spliced\\\nbuffer\n", "path"); + _ = try comp.addSourceFromBuffer("non-spliced buffer\n", "path"); } }; try Test.addSourceFromBuffer("ab\\\nc", "abc", 0, &.{2}); diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 6a7265f..5872220 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -3748,7 +3748,7 @@ test "Include guards" { => try writer.print(template, .{ tok_id.lexeme().?, " BAR\n#endif" }), else => try writer.print(template, .{ tok_id.lexeme().?, "" }), } - const source = try comp.addSourceFromBuffer(buf.items, "test.h", ); + const source = try comp.addSourceFromBuffer(buf.items, "test.h"); _ = try pp.preprocess(source); try std.testing.expectEqual(expected_guards, pp.include_guards.count()); diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 9fbdbe8..142d15a 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -2334,7 +2334,7 @@ test "Tokenizer fuzz test" { var comp = Compilation.init(std.testing.allocator, arena.allocator(), undefined, std.fs.cwd()); defer comp.deinit(); - const source = try comp.addSourceFromBuffer(input_bytes, "fuzz.c", ); + const source = try comp.addSourceFromBuffer(input_bytes, "fuzz.c"); var tokenizer: Tokenizer = .{ .buf = source.buf, @@ -2360,7 +2360,10 @@ fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, la if (langopts) |provided| { comp.langopts = provided; } - const source = try comp.addSourceFromBuffer(contents, "path", ); + const source = try comp.addSourceFromBuffer( + contents, + "path", + ); var tokenizer = Tokenizer{ .buf = source.buf, .source = source.id, -- Gitee From 287e152720745a0d2227ac5d8d6da7fb0aaffff3 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 12 Jul 2025 22:06:08 +0300 Subject: [PATCH 110/117] diag flush --- src/aro/Diagnostics.zig | 5 +++-- src/aro/Driver.zig | 3 ++- src/main.zig | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index 2098608..46b7b38 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -239,7 +239,7 @@ const Diagnostics = @This(); output: union(enum) { to_writer: struct { writer: *std.io.Writer, - config: std.io.tty.Config, + color: std.io.tty.Config, }, to_list: struct { messages: std.ArrayListUnmanaged(Message) = .empty, @@ -468,7 +468,7 @@ fn addMessage(d: *Diagnostics, msg: Message) Compilation.Error!void { switch (d.output) { .ignore => {}, .to_writer => |writer| { - writeToWriter(msg, writer.writer, writer.config) catch { + writeToWriter(msg, writer.writer, writer.color) catch { return error.FatalError; }; }, @@ -528,4 +528,5 @@ fn writeToWriter(msg: Message, w: *std.io.Writer, config: std.io.tty.Config) !vo try w.writeAll("\n"); try config.setColor(w, .reset); } + try w.flush(); } diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 6f69047..68e34f6 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -795,7 +795,8 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ var macro_writer: std.io.Writer.Allocating = .init(d.comp.gpa); defer macro_writer.deinit(); - var stdout = std.fs.File.stdout().writer(""); + var stdout_buf: [256]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&stdout_buf); if (parseArgs(d, &stdout.interface, ¯o_writer, args) catch |er| switch (er) { error.WriteFailed => return d.fatal("failed to write to stdout: {s}", .{errorDescription(er)}), error.OutOfMemory => return error.OutOfMemory, diff --git a/src/main.zig b/src/main.zig index 8db0f88..3b5a2fa 100644 --- a/src/main.zig +++ b/src/main.zig @@ -43,7 +43,7 @@ pub fn main() u8 { var stderr = std.fs.File.stderr().writer(&buf); var diagnostics: Diagnostics = .{ .output = .{ .to_writer = .{ - .config = .detect(stderr.file), + .color = .detect(stderr.file), .writer = &stderr.interface, } }, }; -- Gitee From 3a05246378d1c99f664d22f10ea935e22def4c24 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 12 Jul 2025 23:23:18 +0300 Subject: [PATCH 111/117] tweaks --- src/aro/CodeGen.zig | 2 +- src/aro/Compilation.zig | 24 +++++++------- src/aro/Diagnostics.zig | 4 +-- src/aro/Driver.zig | 44 +++++++++++++------------- src/aro/Parser.zig | 2 +- src/aro/Preprocessor.zig | 56 ++++++++++++++------------------- src/aro/Tokenizer.zig | 5 +-- src/aro/Value.zig | 2 +- src/aro/target.zig | 5 ++- src/aro/text_literal.zig | 4 +-- src/assembly_backend/x86_64.zig | 2 +- src/main.zig | 4 +-- test/record_runner.zig | 20 ++++++------ test/runner.zig | 17 +++++----- 14 files changed, 89 insertions(+), 102 deletions(-) diff --git a/src/aro/CodeGen.zig b/src/aro/CodeGen.zig index eb9869a..96ed0a8 100644 --- a/src/aro/CodeGen.zig +++ b/src/aro/CodeGen.zig @@ -59,7 +59,7 @@ fn fail(c: *CodeGen, comptime fmt: []const u8, args: anytype) error{ FatalError, var buf = std.ArrayList(u8).init(sf.get()); defer buf.deinit(); - try buf.writer().print(fmt, args); + try buf.print(fmt, args); try c.comp.diagnostics.add(.{ .text = buf.items, .kind = .@"fatal error", .location = null }); return error.FatalError; } diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 2a9e256..83f506d 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -630,7 +630,7 @@ fn generateSystemDefines(comp: *Compilation, w: *std.io.Writer) !void { pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) !Source { try comp.type_store.initNamedTypes(comp); - var allocating: std.io.Writer.Allocating = .init(comp.gpa); + var allocating: std.io.Writer.Allocating = try .initCapacity(comp.gpa, 2 << 14); defer allocating.deinit(); comp.writeBuiltinMacros(system_defines_mode, &allocating.writer) catch |err| switch (err) { @@ -826,7 +826,7 @@ fn generateFastOrLeastType( const full = std.fmt.bufPrint(&buf, "{s}{s}{d}{s}", .{ base_name, kind_str, bits, suffix, - }) catch return error.OutOfMemory; + }) catch unreachable; try comp.generateTypeMacro(w, full, ty); @@ -929,7 +929,7 @@ fn generateExactWidthType(comp: *Compilation, w: *std.io.Writer, original_qt: Qu const suffix = "_TYPE__"; const full = std.fmt.bufPrint(&buffer, "{s}{d}{s}", .{ if (unsigned) "__UINT" else "__INT", width, suffix, - }) catch return error.OutOfMemory; + }) catch unreachable; try comp.generateTypeMacro(w, full, qt); @@ -983,7 +983,7 @@ fn generateExactWidthIntMax(comp: *Compilation, w: *std.io.Writer, original_qt: var name_buffer: [6]u8 = undefined; const name = std.fmt.bufPrint(&name_buffer, "{s}{d}", .{ if (unsigned) "UINT" else "INT", bit_count, - }) catch return error.OutOfMemory; + }) catch unreachable; return comp.generateIntMax(w, name, qt); } @@ -1268,13 +1268,13 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin } pub fn addSourceFromFile(comp: *Compilation, file: std.fs.File, path: []const u8, kind: Source.Kind) !Source { - var buf: [4096]u8 = undefined; - var reader = file.reader(&buf); + var file_buf: [4096]u8 = undefined; + var file_reader = file.reader(&file_buf); var allocating: std.io.Writer.Allocating = .init(comp.gpa); - _ = allocating.writer.sendFileAll(&reader, .limited(std.math.maxInt(u32))) catch |e| switch (e) { + _ = allocating.writer.sendFileAll(&file_reader, .limited(std.math.maxInt(u32))) catch |e| switch (e) { error.WriteFailed => return error.OutOfMemory, - error.ReadFailed => return reader.err.?, + error.ReadFailed => return file_reader.err.?, }; const contents = try allocating.toOwnedSlice(); @@ -1473,11 +1473,11 @@ fn getFileContents(comp: *Compilation, path: []const u8, limit: std.io.Limit) ![ var allocating: std.io.Writer.Allocating = .init(comp.gpa); defer allocating.deinit(); - var buf: [4096]u8 = undefined; - var reader = file.reader(&buf); - _ = allocating.writer.sendFileAll(&reader, limit) catch |err| switch (err) { + var file_buf: [4096]u8 = undefined; + var file_reader = file.reader(&file_buf); + _ = allocating.writer.sendFileAll(&file_reader, limit) catch |err| switch (err) { error.WriteFailed => return error.OutOfMemory, - error.ReadFailed => return reader.err.?, + error.ReadFailed => return file_reader.err.?, }; return allocating.toOwnedSlice(); diff --git a/src/aro/Diagnostics.zig b/src/aro/Diagnostics.zig index 46b7b38..1e32017 100644 --- a/src/aro/Diagnostics.zig +++ b/src/aro/Diagnostics.zig @@ -279,7 +279,7 @@ pub fn warningExists(name: []const u8) bool { return std.meta.stringToEnum(Option, name) != null; } -pub fn set(d: *Diagnostics, name: []const u8, to: Message.Kind) !void { +pub fn set(d: *Diagnostics, name: []const u8, to: Message.Kind) Compilation.Error!void { if (std.mem.eql(u8, name, "pedantic")) { d.state.extensions = to; return; @@ -422,7 +422,7 @@ pub fn addWithLocation( if (copy.kind == .@"fatal error") return error.FatalError; } -pub fn formatArgs(w: *std.io.Writer, fmt: []const u8, args: anytype) !void { +pub fn formatArgs(w: *std.io.Writer, fmt: []const u8, args: anytype) std.io.Writer.Error!void { var i: usize = 0; inline for (std.meta.fields(@TypeOf(args))) |arg_info| { const arg = @field(args, arg_info.name); diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 68e34f6..681a4ef 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -253,7 +253,7 @@ pub const usage = pub fn parseArgs( d: *Driver, stdout: *std.io.Writer, - macro_writer: *std.io.Writer.Allocating, + macro_buf: *std.ArrayListUnmanaged(u8), args: []const []const u8, ) (Compilation.Error || std.io.Writer.Error)!bool { var i: usize = 1; @@ -289,7 +289,7 @@ pub fn parseArgs( value = macro[some + 1 ..]; macro = macro[0..some]; } - macro_writer.writer.print("#define {s} {s}\n", .{ macro, value }) catch return error.OutOfMemory; + try macro_buf.print(d.comp.gpa, "#define {s} {s}\n", .{ macro, value }); } else if (mem.startsWith(u8, arg, "-U")) { var macro = arg["-U".len..]; if (macro.len == 0) { @@ -300,7 +300,7 @@ pub fn parseArgs( } macro = args[i]; } - macro_writer.writer.print("#undef {s}\n", .{macro}) catch return error.OutOfMemory; + try macro_buf.print(d.comp.gpa, "#undef {s}\n", .{macro}); } else if (mem.eql(u8, arg, "-O")) { d.comp.code_gen_options.optimization_level = .@"0"; } else if (mem.startsWith(u8, arg, "-O")) { @@ -792,20 +792,20 @@ pub fn errorDescription(e: anyerror) []const u8 { /// **MAY call `exit` if `fast_exit` is set.** pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_exit: bool, asm_gen_fn: ?AsmCodeGenFn) Compilation.Error!void { const user_macros = macros: { - var macro_writer: std.io.Writer.Allocating = .init(d.comp.gpa); - defer macro_writer.deinit(); + var macro_buf: std.ArrayListUnmanaged(u8) = .empty; + defer macro_buf.deinit(d.comp.gpa); var stdout_buf: [256]u8 = undefined; var stdout = std.fs.File.stdout().writer(&stdout_buf); - if (parseArgs(d, &stdout.interface, ¯o_writer, args) catch |er| switch (er) { + if (parseArgs(d, &stdout.interface, ¯o_buf, args) catch |er| switch (er) { error.WriteFailed => return d.fatal("failed to write to stdout: {s}", .{errorDescription(er)}), error.OutOfMemory => return error.OutOfMemory, error.FatalError => return error.FatalError, }) return; - if (macro_writer.getWritten().len > std.math.maxInt(u32)) { + if (macro_buf.items.len > std.math.maxInt(u32)) { return d.fatal("user provided macro source exceeded max size", .{}); } - const contents = try macro_writer.toOwnedSlice(); + const contents = try macro_buf.toOwnedSlice(d.comp.gpa); errdefer d.comp.gpa.free(contents); break :macros try d.comp.addSourceFromOwnedBuffer(contents, "", .user); @@ -961,11 +961,11 @@ fn processSource( std.fs.File.stdout(); defer if (d.output_name != null) file.close(); - var buf: [4096]u8 = undefined; - var writer = file.writer(&buf); + var file_buf: [4096]u8 = undefined; + var file_writer = file.writer(&file_buf); - pp.prettyPrintTokens(&writer.interface, dump_mode) catch - return d.fatal("unable to write result: {s}", .{errorDescription(writer.err.?)}); + pp.prettyPrintTokens(&file_writer.interface, dump_mode) catch + return d.fatal("unable to write result: {s}", .{errorDescription(file_writer.err.?)}); if (fast_exit) std.process.exit(0); // Not linking, no need for cleanup. return; @@ -975,8 +975,8 @@ fn processSource( defer tree.deinit(); if (d.verbose_ast) { - var buf: [4096]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buf); + var stdout_buf: [4096]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&stdout_buf); tree.dump(d.detectConfig(stdout.file), &stdout.interface) catch {}; } @@ -1041,8 +1041,8 @@ fn processSource( defer ir.deinit(d.comp.gpa); if (d.verbose_ir) { - var buf: [4096]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buf); + var stdout_buf: [4096]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&stdout_buf); ir.dump(d.comp.gpa, d.detectConfig(stdout.file), &stdout.interface) catch {}; } @@ -1067,10 +1067,10 @@ fn processSource( return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) }); defer out_file.close(); - var buf: [4096]u8 = undefined; - var writer = out_file.writer(&buf); - obj.finish(&writer.interface) catch - return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(writer.err.?) }); + var file_buf: [4096]u8 = undefined; + var file_writer = out_file.writer(&file_buf); + obj.finish(&file_writer.interface) catch + return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(file_writer.err.?) }); } if (d.only_compile or d.only_preprocess_and_compile) { @@ -1107,8 +1107,8 @@ pub fn invokeLinker(d: *Driver, tc: *Toolchain, comptime fast_exit: bool) Compil try tc.buildLinkerArgs(&argv); if (d.verbose_linker_args) { - var buf: [4096]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buf); + var stdout_buf: [4096]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&stdout_buf); dumpLinkerArgs(&stdout.interface, argv.items) catch { return d.fatal("unable to dump linker args: {s}", .{errorDescription(stdout.err.?)}); }; diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 3738496..6a9067b 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -1487,7 +1487,7 @@ fn staticAssertMessage(p: *Parser, cond_node: Node.Index, maybe_message: ?Result if (maybe_message) |message| { assert(message.node.get(&p.tree) == .string_literal_expr); - if (allocating.writer.end > 0) { + if (allocating.getWritten().len > 0) { try w.writeByte(' '); } const bytes = p.comp.interner.get(message.val.ref()).bytes; diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 5872220..2e0c355 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -810,13 +810,13 @@ fn fatalNotFound(pp: *Preprocessor, tok: TokenWithExpansionLocs, filename: []con defer pp.diagnostics.state.fatal_errors = old; var sf = std.heap.stackFallback(1024, pp.gpa); - var allocating: std.io.Writer.Allocating = .init(sf.get()); - defer allocating.deinit(); + var buf = std.ArrayList(u8).init(sf.get()); + defer buf.deinit(); - Diagnostics.formatArgs(&allocating.writer, "'{s}' not found", .{filename}) catch return error.OutOfMemory; + try buf.print("'{s}' not found", .{filename}); try pp.diagnostics.addWithLocation(pp.comp, .{ .kind = .@"fatal error", - .text = allocating.getWritten(), + .text = buf.items, .location = tok.loc.expand(pp.comp), }, tok.expansionSlice(), true); unreachable; // should've returned FatalError @@ -827,8 +827,8 @@ fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: const source = pp.comp.getSource(raw.source); const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); - var buf: [4096]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); + var stderr_buf: [4096]u8 = undefined; + var stderr = std.fs.File.stderr().writer(&stderr_buf); const w = &stderr.interface; w.print("{s}:{d}:{d}: ", .{ source.path, line_col.line_no, line_col.col }) catch return; @@ -836,7 +836,7 @@ fn verboseLog(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: w.writeByte('\n') catch return; w.writeAll(line_col.line) catch return; w.writeByte('\n') catch return; - w.flush() catch {}; + w.flush() catch return; } /// Consume next token, error if it is not an identifier. @@ -1191,24 +1191,21 @@ fn expandObjMacro(pp: *Preprocessor, simple_macro: *const Macro) Error!ExpandBuf .macro_file => { const start = pp.comp.generated_buf.items.len; const source = pp.comp.getSource(pp.expansion_source_loc.id); - const w = pp.comp.generated_buf.writer(pp.gpa); - try w.print("\"{f}\"\n", .{fmtEscapes(source.path)}); + try pp.comp.generated_buf.print(pp.gpa, "\"{f}\"\n", .{fmtEscapes(source.path)}); buf.appendAssumeCapacity(try pp.makeGeneratedToken(start, .string_literal, tok)); }, .macro_line => { const start = pp.comp.generated_buf.items.len; const source = pp.comp.getSource(pp.expansion_source_loc.id); - const w = pp.comp.generated_buf.writer(pp.gpa); - try w.print("{d}\n", .{source.physicalLine(pp.expansion_source_loc)}); + try pp.comp.generated_buf.print(pp.gpa, "{d}\n", .{source.physicalLine(pp.expansion_source_loc)}); buf.appendAssumeCapacity(try pp.makeGeneratedToken(start, .pp_num, tok)); }, .macro_counter => { defer pp.counter += 1; const start = pp.comp.generated_buf.items.len; - const w = pp.comp.generated_buf.writer(pp.gpa); - try w.print("{d}\n", .{pp.counter}); + try pp.comp.generated_buf.print(pp.gpa, "{d}\n", .{pp.counter}); buf.appendAssumeCapacity(try pp.makeGeneratedToken(start, .pp_num, tok)); }, @@ -1255,8 +1252,6 @@ const DateTimeStampKind = enum { fn writeDateTimeStamp(pp: *Preprocessor, kind: DateTimeStampKind, timestamp: u64) !void { std.debug.assert(std.time.epoch.Month.jan.numeric() == 1); - const w = pp.comp.generated_buf.writer(pp.gpa); - const epoch_seconds = std.time.epoch.EpochSeconds{ .secs = timestamp }; const epoch_day = epoch_seconds.getEpochDay(); const day_seconds = epoch_seconds.getDaySeconds(); @@ -1270,21 +1265,21 @@ fn writeDateTimeStamp(pp: *Preprocessor, kind: DateTimeStampKind, timestamp: u64 switch (kind) { .date => { - try w.print("\"{s} {d: >2} {d}\"", .{ + try pp.comp.generated_buf.print(pp.gpa, "\"{s} {d: >2} {d}\"", .{ month_name, month_day.day_index + 1, year_day.year, }); }, .time => { - try w.print("\"{d:0>2}:{d:0>2}:{d:0>2}\"", .{ + try pp.comp.generated_buf.print(pp.gpa, "\"{d:0>2}:{d:0>2}:{d:0>2}\"", .{ day_seconds.getHoursIntoDay(), day_seconds.getMinutesIntoHour(), day_seconds.getSecondsIntoMinute(), }); }, .timestamp => { - try w.print("\"{s} {s} {d: >2} {d:0>2}:{d:0>2}:{d:0>2} {d}\"", .{ + try pp.comp.generated_buf.print(pp.gpa, "\"{s} {s} {d: >2} {d:0>2}:{d:0>2}:{d:0>2} {d}\"", .{ day_name, month_name, month_day.day_index + 1, @@ -1759,8 +1754,8 @@ fn expandFuncMacro( break :blk false; } else try pp.handleBuiltinMacro(raw.id, arg, macro_tok.loc); const start = pp.comp.generated_buf.items.len; - const w = pp.comp.generated_buf.writer(pp.gpa); - try w.print("{}\n", .{@intFromBool(result)}); + + try pp.comp.generated_buf.print(pp.gpa, "{}\n", .{@intFromBool(result)}); try buf.append(try pp.makeGeneratedToken(start, .pp_num, tokFromRaw(raw))); }, .macro_param_has_c_attribute => { @@ -2995,7 +2990,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { }; pp.token_buf.items.len = 0; - var limit: std.io.Limit = .unlimited; + var limit: ?std.io.Limit = null; var prefix: ?Range = null; var suffix: ?Range = null; var if_empty: ?Range = null; @@ -3052,7 +3047,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { const end: u32 = @intCast(pp.token_buf.items.len); if (std.mem.eql(u8, param, "limit")) { - if (limit != .unlimited) { + if (limit != null) { try pp.err(tokFromRaw(param_first), .duplicate_embed_param, .{"limit"}); continue; } @@ -3094,7 +3089,7 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { } } - const embed_bytes = (try pp.comp.findEmbed(filename, first.source, include_type, limit)) orelse + const embed_bytes = (try pp.comp.findEmbed(filename, first.source, include_type, limit orelse .unlimited)) orelse return pp.fatalNotFound(filename_tok, filename); defer pp.comp.gpa.free(embed_bytes); @@ -3111,18 +3106,16 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { // TODO: We currently only support systems with CHAR_BIT == 8 // If the target's CHAR_BIT is not 8, we need to write out correctly-sized embed_bytes // and correctly account for the target's endianness - const writer = pp.comp.generated_buf.writer(pp.gpa); - { const byte = embed_bytes[0]; const start = pp.comp.generated_buf.items.len; - try writer.print("{d}", .{byte}); + try pp.comp.generated_buf.print(pp.gpa, "{d}", .{byte}); pp.addTokenAssumeCapacity(try pp.makeGeneratedToken(start, .embed_byte, filename_tok)); } for (embed_bytes[1..]) |byte| { const start = pp.comp.generated_buf.items.len; - try writer.print(",{d}", .{byte}); + try pp.comp.generated_buf.print(pp.gpa, ",{d}", .{byte}); pp.addTokenAssumeCapacity(.{ .id = .comma, .loc = .{ .id = .generated, .byte_offset = @intCast(start) } }); pp.addTokenAssumeCapacity(try pp.makeGeneratedToken(start + 1, .embed_byte, filename_tok)); } @@ -3737,16 +3730,15 @@ test "Include guards" { var buf = std.ArrayList(u8).init(gpa); defer buf.deinit(); - var writer = buf.writer(); switch (tok_id) { - .keyword_include, .keyword_include_next => try writer.print(template, .{ tok_id.lexeme().?, " \"bar.h\"" }), - .keyword_define, .keyword_undef => try writer.print(template, .{ tok_id.lexeme().?, " BAR" }), + .keyword_include, .keyword_include_next => try buf.print(template, .{ tok_id.lexeme().?, " \"bar.h\"" }), + .keyword_define, .keyword_undef => try buf.print(template, .{ tok_id.lexeme().?, " BAR" }), .keyword_ifndef, .keyword_ifdef, .keyword_elifdef, .keyword_elifndef, - => try writer.print(template, .{ tok_id.lexeme().?, " BAR\n#endif" }), - else => try writer.print(template, .{ tok_id.lexeme().?, "" }), + => try buf.print(template, .{ tok_id.lexeme().?, " BAR\n#endif" }), + else => try buf.print(template, .{ tok_id.lexeme().?, "" }), } const source = try comp.addSourceFromBuffer(buf.items, "test.h"); _ = try pp.preprocess(source); diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 142d15a..67d67d9 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -2360,10 +2360,7 @@ fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, la if (langopts) |provided| { comp.langopts = provided; } - const source = try comp.addSourceFromBuffer( - contents, - "path", - ); + const source = try comp.addSourceFromBuffer(contents, "path"); var tokenizer = Tokenizer{ .buf = source.buf, .source = source.id, diff --git a/src/aro/Value.zig b/src/aro/Value.zig index c65bd0e..4807ab7 100644 --- a/src/aro/Value.zig +++ b/src/aro/Value.zig @@ -1121,7 +1121,7 @@ pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: const without_null = bytes[0 .. bytes.len - @intFromEnum(size)]; try w.writeByte('"'); switch (size) { - .@"1" => try w.print("{f}", .{std.zig.fmtString(without_null)}), + .@"1" => try std.zig.stringEscape(without_null, w), .@"2" => { var items: [2]u16 = undefined; var i: usize = 0; diff --git a/src/aro/target.zig b/src/aro/target.zig index a38ca6c..d4222db 100644 --- a/src/aro/target.zig +++ b/src/aro/target.zig @@ -579,8 +579,7 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { // 64 bytes is assumed to be large enough to hold any target triple; increase if necessary std.debug.assert(buf.len >= 64); - var stream = std.io.fixedBufferStream(buf); - const writer = stream.writer(); + var writer: std.io.Writer = .fixed(buf); const llvm_arch = switch (target.cpu.arch) { .arm => "arm", @@ -720,7 +719,7 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 { .ohoseabi => "ohoseabi", }; writer.writeAll(llvm_abi) catch unreachable; - return stream.getWritten(); + return writer.buffered(); } pub const DefaultPIStatus = enum { yes, no, depends_on_linker }; diff --git a/src/aro/text_literal.zig b/src/aro/text_literal.zig index 92487fd..47c1293 100644 --- a/src/aro/text_literal.zig +++ b/src/aro/text_literal.zig @@ -162,9 +162,7 @@ pub const Ascii = struct { if (std.ascii.isPrint(ctx.val)) { try w.writeByte(ctx.val); } else { - var buf: [3]u8 = undefined; - const str = std.fmt.bufPrint(&buf, "x{x}", .{ctx.val}) catch unreachable; - try w.writeAll(str); + try w.print("x{x}", .{ctx.val}); } return i + template.len; } diff --git a/src/assembly_backend/x86_64.zig b/src/assembly_backend/x86_64.zig index 1032799..abd8eb0 100644 --- a/src/assembly_backend/x86_64.zig +++ b/src/assembly_backend/x86_64.zig @@ -73,7 +73,7 @@ pub fn todo(c: *AsmCodeGen, msg: []const u8, tok: Tree.TokenIndex) Error { var buf = std.ArrayList(u8).init(sf.get()); defer buf.deinit(); - try buf.writer().print("TODO: {s}", .{msg}); + try buf.print("TODO: {s}", .{msg}); try c.comp.diagnostics.add(.{ .text = buf.items, .kind = .@"error", diff --git a/src/main.zig b/src/main.zig index 3b5a2fa..3a293f4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -39,8 +39,8 @@ pub fn main() u8 { }; defer gpa.free(aro_name); - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); + var stderr_buf: [1024]u8 = undefined; + var stderr = std.fs.File.stderr().writer(&stderr_buf); var diagnostics: Diagnostics = .{ .output = .{ .to_writer = .{ .color = .detect(stderr.file), diff --git a/test/record_runner.zig b/test/record_runner.zig index 0a598ce..c675243 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -121,8 +121,8 @@ pub fn main() !void { { var cases_dir = try std.fs.cwd().openDir(args[1], .{ .iterate = true }); defer cases_dir.close(); - var buf: [1024]u8 = undefined; - var writer: std.io.Writer = .fixed(&buf); + var name_buf: [1024]u8 = undefined; + var name_writer: std.io.Writer = .fixed(&name_buf); var it = cases_dir.iterate(); while (try it.next()) |entry| { @@ -133,9 +133,9 @@ pub fn main() !void { } if (std.ascii.indexOfIgnoreCase(entry.name, "_test.c") != null) { - _ = writer.consumeAll(); - try writer.print("{s}{c}{s}", .{ args[1], std.fs.path.sep, entry.name }); - try cases.append(try gpa.dupe(u8, writer.buffered())); + _ = name_writer.consumeAll(); + try name_writer.print("{s}{c}{s}", .{ args[1], std.fs.path.sep, entry.name }); + try cases.append(try gpa.dupe(u8, name_writer.buffered())); } } } @@ -287,7 +287,7 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, try macro_writer.writeAll("#define MSVC\n"); } - const user_macros = try comp.addSourceFromBuffer("", macro_writer.buffered()); + const user_macros = try comp.addSourceFromBuffer(macro_writer.buffered(), ""); const builtin_macros = try comp.generateBuiltinMacros(.include_system_defines); var pp = try aro.Preprocessor.initDefault(&comp); @@ -320,11 +320,11 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, return; } - var buf: [128]u8 = undefined; - var writer: std.io.Writer = .fixed(&buf); - try writer.print("{s}|{s}", .{ test_case.target, test_name }); + var expected_buf: [128]u8 = undefined; + var expected_writer: std.io.Writer = .fixed(&expected_buf); + try expected_writer.print("{s}|{s}", .{ test_case.target, test_name }); - const expected = compErr.get(writer.buffered()) orelse ExpectedFailure{}; + const expected = compErr.get(expected_writer.buffered()) orelse ExpectedFailure{}; if (diagnostics.total == 0 and expected.any()) { std.debug.print("\nTest Passed when failures expected:\n\texpected:{any}\n", .{expected}); diff --git a/test/runner.zig b/test/runner.zig index c44822e..fbc683a 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -18,7 +18,7 @@ const AddCommandLineArgsResult = struct { }; /// Returns only_preprocess and line_markers settings if saw -E -fn addCommandLineArgs(comp: *aro.Compilation, file: aro.Source, macro_writer: *std.io.Writer.Allocating) !AddCommandLineArgsResult { +fn addCommandLineArgs(comp: *aro.Compilation, file: aro.Source, macro_buf: *std.ArrayListUnmanaged(u8)) !AddCommandLineArgsResult { var only_preprocess = false; var line_markers: aro.Preprocessor.Linemarkers = .none; var system_defines: aro.Compilation.SystemDefinesMode = .include_system_defines; @@ -36,7 +36,7 @@ fn addCommandLineArgs(comp: *aro.Compilation, file: aro.Source, macro_writer: *s var discard_buf: [256]u8 = undefined; var discarding: std.io.Writer.Discarding = .init(&discard_buf); - _ = try driver.parseArgs(&discarding.writer, macro_writer, test_args.items); + _ = try driver.parseArgs(&discarding.writer, macro_buf, test_args.items); only_preprocess = driver.only_preprocess; system_defines = driver.system_defines; dump_mode = driver.debug_dump_letters.getPreprocessorDumpMode(); @@ -80,7 +80,7 @@ fn testOne(gpa: std.mem.Allocator, path: []const u8, test_dir: []const u8) !void defer macro_buf.deinit(); _, _, const system_defines, _ = try addCommandLineArgs(&comp, file, macro_buf.writer()); - const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); + const user_macros = try comp.addSourceFromBuffer(macro_buf.items, ""); const builtin_macros = try comp.generateBuiltinMacros(system_defines); @@ -178,7 +178,7 @@ pub fn main() !void { var diagnostics: aro.Diagnostics = .{ .output = .{ .to_writer = .{ .writer = &diag_buf.writer, - .config = .no_color, + .color = .no_color, } }, }; defer diagnostics.deinit(); @@ -240,11 +240,11 @@ pub fn main() !void { continue; }; - var macro_writer: std.io.Writer.Allocating = .init(comp.gpa); - defer macro_writer.deinit(); + var macro_buf: std.ArrayListUnmanaged(u8) = .empty; + defer macro_buf.deinit(comp.gpa); - const only_preprocess, const linemarkers, const system_defines, const dump_mode = try addCommandLineArgs(&comp, file, ¯o_writer); - const user_macros = try comp.addSourceFromBuffer("", macro_writer.getWritten()); + const only_preprocess, const linemarkers, const system_defines, const dump_mode = try addCommandLineArgs(&comp, file, ¯o_buf); + const user_macros = try comp.addSourceFromBuffer(macro_buf.items, ""); const builtin_macros = try comp.generateBuiltinMacros(system_defines); @@ -443,6 +443,7 @@ pub fn main() !void { var stderr_buf: [4096]u8 = undefined; var stderr = std.fs.File.stderr().writer(&stderr_buf); try stderr.interface.writeAll(diag_buf.getWritten()); + try stderr.interface.flush(); } if (pp.defines.get("EXPECTED_OUTPUT")) |macro| blk: { -- Gitee From f5eaa0abbe149c6dba90e123c3cb7657d43fd80f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 12 Jul 2025 23:40:48 +0300 Subject: [PATCH 112/117] test fixes --- src/aro/text_literal.zig | 2 +- test/cases/ast/attributed record fields.c | 6 +++--- test/cases/ast/decayed attributed array.c | 6 +++--- test/cases/ast/types.c | 6 +++--- test/runner.zig | 6 ++++++ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/aro/text_literal.zig b/src/aro/text_literal.zig index 47c1293..db51885 100644 --- a/src/aro/text_literal.zig +++ b/src/aro/text_literal.zig @@ -162,7 +162,7 @@ pub const Ascii = struct { if (std.ascii.isPrint(ctx.val)) { try w.writeByte(ctx.val); } else { - try w.print("x{x}", .{ctx.val}); + try w.print("x{x:0>2}", .{ctx.val}); } return i + template.len; } diff --git a/test/cases/ast/attributed record fields.c b/test/cases/ast/attributed record fields.c index bdd4887..90d6371 100644 --- a/test/cases/ast/attributed record fields.c +++ b/test/cases/ast/attributed record fields.c @@ -76,8 +76,8 @@ struct_decl: 'struct S9' record_field: 'long' name: l field attr: packed - field attr: aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex.null, .requested = 16 } - field attr: warn_if_not_aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex.null, .requested = 16 } + field attr: aligned alignment: .{ .node = .null, .requested = 16 } + field attr: warn_if_not_aligned alignment: .{ .node = .null, .requested = 16 } union_decl: 'union U1' record_field: 'long' @@ -85,7 +85,7 @@ union_decl: 'union U1' record_field: 'int' name: y - field attr: aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex.null, .requested = 32 } + field attr: aligned alignment: .{ .node = .null, .requested = 32 } record_field: 'unsigned int' name: z diff --git a/test/cases/ast/decayed attributed array.c b/test/cases/ast/decayed attributed array.c index 9a04b6e..62e9393 100644 --- a/test/cases/ast/decayed attributed array.c +++ b/test/cases/ast/decayed attributed array.c @@ -36,7 +36,7 @@ function: 'fn () void' body: compound_stmt variable: 'attributed([64]char)' - attr: aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex(14), .requested = 8 } + attr: aligned alignment: .{ .node = @enumFromInt(14), .requested = 8 } name: x variable: '*char' @@ -48,10 +48,10 @@ function: 'fn () void' base: implicit cast: (array_to_pointer) 'decayed *attributed([64]char)' paren_expr: 'attributed([64]char)' lvalue - attr: aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex(14), .requested = 8 } + attr: aligned alignment: .{ .node = @enumFromInt(14), .requested = 8 } operand: decl_ref_expr: 'attributed([64]char)' lvalue - attr: aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex(14), .requested = 8 } + attr: aligned alignment: .{ .node = @enumFromInt(14), .requested = 8 } name: x index: int_literal: 'int' (value: 0) diff --git a/test/cases/ast/types.c b/test/cases/ast/types.c index 35cdbe4..4f2d40e 100644 --- a/test/cases/ast/types.c +++ b/test/cases/ast/types.c @@ -17,9 +17,9 @@ implicit typedef: 'long double' name: __float80 variable: 'attributed(int)' - attr: aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex.null, .requested = 4 } - attr: aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex.null, .requested = 4 } - attr: aligned alignment: aro.Attribute.Alignment{ .node = aro.Tree.Node.OptIndex(6), .requested = 16 } + attr: aligned alignment: .{ .node = .null, .requested = 4 } + attr: aligned alignment: .{ .node = .null, .requested = 4 } + attr: aligned alignment: .{ .node = @enumFromInt(6), .requested = 16 } name: a variable: 'const volatile int' diff --git a/test/runner.zig b/test/runner.zig index fbc683a..a956675 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -214,6 +214,12 @@ pub fn main() !void { var skip_count: u32 = 0; next_test: for (cases.items) |path| { diag_buf.shrinkRetainingCapacity(0); + diagnostics = .{ + .output = .{ .to_writer = .{ + .writer = &diag_buf.writer, + .color = .no_color, + } }, + }; var comp = initial_comp; defer { -- Gitee From 1d4fd12d326afa7a37c04979ffa892656807c8b7 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 13 Jul 2025 00:53:40 +0300 Subject: [PATCH 113/117] 32 bit --- src/aro/Compilation.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 83f506d..fb102d5 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -627,7 +627,7 @@ fn generateSystemDefines(comp: *Compilation, w: *std.io.Writer) !void { } /// Generate builtin macros that will be available to each source file. -pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) !Source { +pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) (Compilation.Error || error{StreamTooLong})!Source { try comp.type_store.initNamedTypes(comp); var allocating: std.io.Writer.Allocating = try .initCapacity(comp.gpa, 2 << 14); -- Gitee From ba8b206a644416c985edd2f96d8485e916d5c46b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 13 Jul 2025 01:16:45 +0300 Subject: [PATCH 114/117] record test fix --- src/aro/Tree.zig | 2 +- test/record_runner.zig | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig index 4d9632d..b3e6ad8 100644 --- a/src/aro/Tree.zig +++ b/src/aro/Tree.zig @@ -2906,7 +2906,7 @@ pub fn tokSlice(tree: *const Tree, tok_i: TokenIndex) []const u8 { return tree.comp.locSlice(loc); } -pub fn dump(tree: *const Tree, config: std.io.tty.Config, w: *std.io.Writer) !void { +pub fn dump(tree: *const Tree, config: std.io.tty.Config, w: *std.io.Writer) std.io.tty.Config.SetColorError!void { for (tree.root_decls.items) |i| { try tree.dumpNode(i, 0, config, w); try w.writeByte('\n'); diff --git a/test/record_runner.zig b/test/record_runner.zig index c675243..275c916 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -122,7 +122,6 @@ pub fn main() !void { var cases_dir = try std.fs.cwd().openDir(args[1], .{ .iterate = true }); defer cases_dir.close(); var name_buf: [1024]u8 = undefined; - var name_writer: std.io.Writer = .fixed(&name_buf); var it = cases_dir.iterate(); while (try it.next()) |entry| { @@ -133,7 +132,7 @@ pub fn main() !void { } if (std.ascii.indexOfIgnoreCase(entry.name, "_test.c") != null) { - _ = name_writer.consumeAll(); + var name_writer: std.io.Writer = .fixed(&name_buf); try name_writer.print("{s}{c}{s}", .{ args[1], std.fs.path.sep, entry.name }); try cases.append(try gpa.dupe(u8, name_writer.buffered())); } @@ -271,7 +270,7 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, var case_node = stats.root_node.start(name_writer.buffered(), 0); defer case_node.end(); - const file = comp.addSourceFromBuffer(path, test_case.source) catch |err| { + const file = comp.addSourceFromBuffer(test_case.source, path) catch |err| { stats.recordResult(.fail); std.debug.print("could not add source '{s}': {s}\n", .{ path, @errorName(err) }); return; -- Gitee From 48a43152d503c03122b432b2185357d87b9648c0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 13 Jul 2025 15:27:15 +0300 Subject: [PATCH 115/117] unit test fixes --- src/aro/Compilation.zig | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index fb102d5..e785ff8 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -27,6 +27,7 @@ pub const Error = error{ /// A fatal error has ocurred and compilation has stopped. FatalError, } || Allocator.Error; +pub const AddSourceError = Error || error{StreamTooLong}; pub const bit_int_max_bits = std.math.maxInt(u16); const path_buf_stack_limit = 1024; @@ -627,10 +628,12 @@ fn generateSystemDefines(comp: *Compilation, w: *std.io.Writer) !void { } /// Generate builtin macros that will be available to each source file. -pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) (Compilation.Error || error{StreamTooLong})!Source { +pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode) AddSourceError!Source { try comp.type_store.initNamedTypes(comp); - var allocating: std.io.Writer.Allocating = try .initCapacity(comp.gpa, 2 << 14); + // TODO fails testAllocationFailures? + // var allocating: std.io.Writer.Allocating = try .initCapacity(comp.gpa, 2 << 14); + var allocating: std.io.Writer.Allocating = .init(comp.gpa); defer allocating.deinit(); comp.writeBuiltinMacros(system_defines_mode, &allocating.writer) catch |err| switch (err) { @@ -1197,10 +1200,16 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, const splice_locs = try splice_list.toOwnedSlice(); errdefer comp.gpa.free(splice_locs); - if (i != contents.len) contents = try comp.gpa.realloc(contents, i); + if (i != contents.len){ + var list: std.ArrayListUnmanaged(u8) = .{ + .items = contents[0..i], + .capacity = contents.len, + }; + contents = try list.toOwnedSlice(comp.gpa); + } errdefer @compileError("errdefers in callers would possibly free the realloced slice using the original len"); - const source = Source{ + const source: Source = .{ .id = source_id, .path = duped_path, .buf = contents, @@ -1239,7 +1248,7 @@ fn addNewlineEscapeError(comp: *Compilation, path: []const u8, buf: []const u8, /// Caller retains ownership of `path` and `buf`. /// Dupes the source buffer; if it is acceptable to modify the source buffer and possibly resize /// the allocation, please use `addSourceFromOwnedBuffer` -pub fn addSourceFromBuffer(comp: *Compilation, buf: []const u8, path: []const u8) !Source { +pub fn addSourceFromBuffer(comp: *Compilation, buf: []const u8, path: []const u8) AddSourceError!Source { if (comp.sources.get(path)) |some| return some; if (buf.len > std.math.maxInt(u32)) return error.StreamTooLong; -- Gitee From 989371d4df7b94266d9646c024c4068bc294adad Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 13 Jul 2025 21:09:41 +0300 Subject: [PATCH 116/117] swap path and contents in addSourceFromBuffer --- src/aro/Compilation.zig | 20 ++++++++++---------- src/aro/Driver.zig | 2 +- src/aro/Parser.zig | 4 ++-- src/aro/Preprocessor.zig | 6 +++--- src/aro/Tokenizer.zig | 4 ++-- test/record_runner.zig | 4 ++-- test/runner.zig | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index e785ff8..0d7a839 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -644,7 +644,7 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi const contents = try allocating.toOwnedSlice(); errdefer comp.gpa.free(contents); - return comp.addSourceFromOwnedBuffer(contents, "", .user); + return comp.addSourceFromOwnedBuffer("", contents, .user); } fn writeBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefinesMode, w: *std.io.Writer) !void { @@ -1082,7 +1082,7 @@ pub fn getSource(comp: *const Compilation, id: Source.Id) Source { /// or line-ending changes happen. /// caller retains ownership of `path` /// To add a file's contents given its path, see addSourceFromPath -pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, kind: Source.Kind) !Source { +pub fn addSourceFromOwnedBuffer(comp: *Compilation, path: []const u8, buf: []u8, kind: Source.Kind) !Source { assert(buf.len <= std.math.maxInt(u32)); try comp.sources.ensureUnusedCapacity(comp.gpa, 1); @@ -1200,7 +1200,7 @@ pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, const splice_locs = try splice_list.toOwnedSlice(); errdefer comp.gpa.free(splice_locs); - if (i != contents.len){ + if (i != contents.len) { var list: std.ArrayListUnmanaged(u8) = .{ .items = contents[0..i], .capacity = contents.len, @@ -1248,14 +1248,14 @@ fn addNewlineEscapeError(comp: *Compilation, path: []const u8, buf: []const u8, /// Caller retains ownership of `path` and `buf`. /// Dupes the source buffer; if it is acceptable to modify the source buffer and possibly resize /// the allocation, please use `addSourceFromOwnedBuffer` -pub fn addSourceFromBuffer(comp: *Compilation, buf: []const u8, path: []const u8) AddSourceError!Source { +pub fn addSourceFromBuffer(comp: *Compilation, path: []const u8, buf: []const u8) AddSourceError!Source { if (comp.sources.get(path)) |some| return some; if (buf.len > std.math.maxInt(u32)) return error.StreamTooLong; const contents = try comp.gpa.dupe(u8, buf); errdefer comp.gpa.free(contents); - return comp.addSourceFromOwnedBuffer(contents, path, .user); + return comp.addSourceFromOwnedBuffer(path, contents, .user); } /// Caller retains ownership of `path`. @@ -1288,7 +1288,7 @@ pub fn addSourceFromFile(comp: *Compilation, file: std.fs.File, path: []const u8 const contents = try allocating.toOwnedSlice(); errdefer comp.gpa.free(contents); - return comp.addSourceFromOwnedBuffer(contents, path, kind); + return comp.addSourceFromOwnedBuffer(path, contents, kind); } pub fn hasInclude( @@ -1712,7 +1712,7 @@ test "addSourceFromBuffer" { var comp = Compilation.init(std.testing.allocator, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); - const source = try comp.addSourceFromBuffer(str, "path"); + const source = try comp.addSourceFromBuffer("path", str); try std.testing.expectEqualStrings(expected, source.buf); try std.testing.expectEqual(warning_count, @as(u32, @intCast(diagnostics.warnings))); @@ -1726,8 +1726,8 @@ test "addSourceFromBuffer" { var comp = Compilation.init(allocator, arena.allocator(), &diagnostics, std.fs.cwd()); defer comp.deinit(); - _ = try comp.addSourceFromBuffer("spliced\\\nbuffer\n", "path"); - _ = try comp.addSourceFromBuffer("non-spliced buffer\n", "path"); + _ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n"); + _ = try comp.addSourceFromBuffer("path", "non-spliced buffer\n"); } }; try Test.addSourceFromBuffer("ab\\\nc", "abc", 0, &.{2}); @@ -1800,7 +1800,7 @@ test "ignore BOM at beginning of file" { var comp = Compilation.init(std.testing.allocator, arena, &diagnostics, std.fs.cwd()); defer comp.deinit(); - const source = try comp.addSourceFromBuffer(buf, "file.c"); + const source = try comp.addSourceFromBuffer("file.c", buf); const expected_output = if (mem.startsWith(u8, buf, BOM)) buf[BOM.len..] else buf; try std.testing.expectEqualStrings(expected_output, source.buf); } diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index 681a4ef..d19c5cd 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -808,7 +808,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ const contents = try macro_buf.toOwnedSlice(d.comp.gpa); errdefer d.comp.gpa.free(contents); - break :macros try d.comp.addSourceFromOwnedBuffer(contents, "", .user); + break :macros try d.comp.addSourceFromOwnedBuffer("", contents, .user); }; const linking = !(d.only_preprocess or d.only_syntax or d.only_compile or d.only_preprocess_and_compile); diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig index 6a9067b..6486821 100644 --- a/src/aro/Parser.zig +++ b/src/aro/Parser.zig @@ -10112,12 +10112,12 @@ test "Node locations" { var comp = Compilation.init(std.testing.allocator, arena, &diagnostics, std.fs.cwd()); defer comp.deinit(); - const file = try comp.addSourceFromBuffer( + const file = try comp.addSourceFromBuffer("file.c", \\int foo = 5; \\int bar = 10; \\int main(void) {} \\ - , "file.c"); + ); const builtin_macros = try comp.generateBuiltinMacros(.no_system_defines); diff --git a/src/aro/Preprocessor.zig b/src/aro/Preprocessor.zig index 2e0c355..df63646 100644 --- a/src/aro/Preprocessor.zig +++ b/src/aro/Preprocessor.zig @@ -3599,7 +3599,7 @@ test "Preserve pragma tokens sometimes" { pp.preserve_whitespace = true; assert(pp.linemarkers == .none); - const test_runner_macros = try comp.addSourceFromBuffer(source_text, ""); + const test_runner_macros = try comp.addSourceFromBuffer("", source_text); const eof = try pp.preprocess(test_runner_macros); try pp.addToken(eof); @@ -3725,7 +3725,7 @@ test "Include guards" { const path = try std.fs.path.join(arena, &.{ ".", "bar.h" }); - _ = try comp.addSourceFromBuffer("int bar = 5;\n", path); + _ = try comp.addSourceFromBuffer(path, "int bar = 5;\n"); var buf = std.ArrayList(u8).init(gpa); defer buf.deinit(); @@ -3740,7 +3740,7 @@ test "Include guards" { => try buf.print(template, .{ tok_id.lexeme().?, " BAR\n#endif" }), else => try buf.print(template, .{ tok_id.lexeme().?, "" }), } - const source = try comp.addSourceFromBuffer(buf.items, "test.h"); + const source = try comp.addSourceFromBuffer("test.h", buf.items); _ = try pp.preprocess(source); try std.testing.expectEqual(expected_guards, pp.include_guards.count()); diff --git a/src/aro/Tokenizer.zig b/src/aro/Tokenizer.zig index 67d67d9..1a06dcb 100644 --- a/src/aro/Tokenizer.zig +++ b/src/aro/Tokenizer.zig @@ -2334,7 +2334,7 @@ test "Tokenizer fuzz test" { var comp = Compilation.init(std.testing.allocator, arena.allocator(), undefined, std.fs.cwd()); defer comp.deinit(); - const source = try comp.addSourceFromBuffer(input_bytes, "fuzz.c"); + const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes); var tokenizer: Tokenizer = .{ .buf = source.buf, @@ -2360,7 +2360,7 @@ fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, la if (langopts) |provided| { comp.langopts = provided; } - const source = try comp.addSourceFromBuffer(contents, "path"); + const source = try comp.addSourceFromBuffer("path", contents); var tokenizer = Tokenizer{ .buf = source.buf, .source = source.id, diff --git a/test/record_runner.zig b/test/record_runner.zig index 275c916..ecc31d9 100644 --- a/test/record_runner.zig +++ b/test/record_runner.zig @@ -270,7 +270,7 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, var case_node = stats.root_node.start(name_writer.buffered(), 0); defer case_node.end(); - const file = comp.addSourceFromBuffer(test_case.source, path) catch |err| { + const file = comp.addSourceFromBuffer(path, test_case.source) catch |err| { stats.recordResult(.fail); std.debug.print("could not add source '{s}': {s}\n", .{ path, @errorName(err) }); return; @@ -286,7 +286,7 @@ fn singleRun(gpa: std.mem.Allocator, test_dir: []const u8, test_case: TestCase, try macro_writer.writeAll("#define MSVC\n"); } - const user_macros = try comp.addSourceFromBuffer(macro_writer.buffered(), ""); + const user_macros = try comp.addSourceFromBuffer("", macro_writer.buffered()); const builtin_macros = try comp.generateBuiltinMacros(.include_system_defines); var pp = try aro.Preprocessor.initDefault(&comp); diff --git a/test/runner.zig b/test/runner.zig index a956675..5e95407 100644 --- a/test/runner.zig +++ b/test/runner.zig @@ -80,7 +80,7 @@ fn testOne(gpa: std.mem.Allocator, path: []const u8, test_dir: []const u8) !void defer macro_buf.deinit(); _, _, const system_defines, _ = try addCommandLineArgs(&comp, file, macro_buf.writer()); - const user_macros = try comp.addSourceFromBuffer(macro_buf.items, ""); + const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); const builtin_macros = try comp.generateBuiltinMacros(system_defines); @@ -250,7 +250,7 @@ pub fn main() !void { defer macro_buf.deinit(comp.gpa); const only_preprocess, const linemarkers, const system_defines, const dump_mode = try addCommandLineArgs(&comp, file, ¯o_buf); - const user_macros = try comp.addSourceFromBuffer(macro_buf.items, ""); + const user_macros = try comp.addSourceFromBuffer("", macro_buf.items); const builtin_macros = try comp.generateBuiltinMacros(system_defines); -- Gitee From 0ea27bbaf360997d505362451947a052e45c2b0e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 14 Jul 2025 12:20:21 +0300 Subject: [PATCH 117/117] check file size --- src/aro/Compilation.zig | 9 ++++++--- src/aro/Driver.zig | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/aro/Compilation.zig b/src/aro/Compilation.zig index 0d7a839..666fb72 100644 --- a/src/aro/Compilation.zig +++ b/src/aro/Compilation.zig @@ -27,7 +27,7 @@ pub const Error = error{ /// A fatal error has ocurred and compilation has stopped. FatalError, } || Allocator.Error; -pub const AddSourceError = Error || error{StreamTooLong}; +pub const AddSourceError = Error || error{FileTooBig}; pub const bit_int_max_bits = std.math.maxInt(u16); const path_buf_stack_limit = 1024; @@ -640,7 +640,7 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi error.WriteFailed, error.OutOfMemory => return error.OutOfMemory, }; - if (allocating.getWritten().len > std.math.maxInt(u32)) return error.StreamTooLong; + if (allocating.getWritten().len > std.math.maxInt(u32)) return error.FileTooBig; const contents = try allocating.toOwnedSlice(); errdefer comp.gpa.free(contents); @@ -1250,7 +1250,7 @@ fn addNewlineEscapeError(comp: *Compilation, path: []const u8, buf: []const u8, /// the allocation, please use `addSourceFromOwnedBuffer` pub fn addSourceFromBuffer(comp: *Compilation, path: []const u8, buf: []const u8) AddSourceError!Source { if (comp.sources.get(path)) |some| return some; - if (buf.len > std.math.maxInt(u32)) return error.StreamTooLong; + if (buf.len > std.math.maxInt(u32)) return error.FileTooBig; const contents = try comp.gpa.dupe(u8, buf); errdefer comp.gpa.free(contents); @@ -1279,6 +1279,7 @@ fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kin pub fn addSourceFromFile(comp: *Compilation, file: std.fs.File, path: []const u8, kind: Source.Kind) !Source { var file_buf: [4096]u8 = undefined; var file_reader = file.reader(&file_buf); + if (try file_reader.getSize() > std.math.maxInt(u32)) return error.FileTooBig; var allocating: std.io.Writer.Allocating = .init(comp.gpa); _ = allocating.writer.sendFileAll(&file_reader, .limited(std.math.maxInt(u32))) catch |e| switch (e) { @@ -1484,6 +1485,8 @@ fn getFileContents(comp: *Compilation, path: []const u8, limit: std.io.Limit) ![ var file_buf: [4096]u8 = undefined; var file_reader = file.reader(&file_buf); + if (try file_reader.getSize() > limit.minInt(std.math.maxInt(u32))) return error.FileTooBig; + _ = allocating.writer.sendFileAll(&file_reader, limit) catch |err| switch (err) { error.WriteFailed => return error.OutOfMemory, error.ReadFailed => return file_reader.err.?, diff --git a/src/aro/Driver.zig b/src/aro/Driver.zig index d19c5cd..2cd40d8 100644 --- a/src/aro/Driver.zig +++ b/src/aro/Driver.zig @@ -833,7 +833,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_ }; const builtin_macros = d.comp.generateBuiltinMacros(d.system_defines) catch |er| switch (er) { - error.StreamTooLong => return d.fatal("builtin macro source exceeded max size", .{}), + error.FileTooBig => return d.fatal("builtin macro source exceeded max size", .{}), else => |e| return e, }; if (fast_exit and d.inputs.items.len == 1) { -- Gitee