From 0b621ed9ada9c1562a3f07319676070f038e9b0f Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 6 Nov 2023 13:52:09 +0200 Subject: [PATCH 1/9] Preprocessor: implement `__has_c_attribute` --- src/Preprocessor.zig | 52 ++++++++++++++++++++++++++++++++++ src/Tokenizer.zig | 3 ++ test/cases/__has_c_attribute.c | 11 +++++++ 3 files changed, 66 insertions(+) create mode 100644 test/cases/__has_c_attribute.c diff --git a/src/Preprocessor.zig b/src/Preprocessor.zig index b0e28f9e..94d73dd6 100644 --- a/src/Preprocessor.zig +++ b/src/Preprocessor.zig @@ -123,6 +123,10 @@ const builtin_macros = struct { .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, @@ -189,6 +193,7 @@ 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); @@ -1514,6 +1519,53 @@ fn expandFuncMacro( try pp.comp.generated_buf.writer().print("{}\n", .{@intFromBool(result)}); try buf.append(try pp.makeGeneratedToken(start, .pp_num, tokFromRaw(raw))); }, + .macro_param_has_c_attribute => { + 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.diag.add(.{ .tag = .expected_arguments, .loc = loc, .extra = extra }, &.{}); + break :blk not_found; + } else res: { + var invalid: ?Token = null; + var identifier: ?Token = null; + for (arg) |tok| { + if (tok.id == .macro_ws) continue; + if (tok.id == .comment) continue; + if (!tok.id.isMacroIdentifier()) { + invalid = tok; + break; + } + if (identifier) |_| invalid = tok else identifier = tok; + } + if (identifier == null and invalid == null) invalid = .{ .id = .eof, .loc = loc }; + if (invalid) |some| { + try pp.comp.diag.add( + .{ .tag = .feature_check_requires_identifier, .loc = some.loc }, + some.expansionSlice(), + ); + break :res not_found; + } + if (!pp.comp.langopts.standard.atLeast(.c2x)) break :res not_found; + + const attrs = std.ComptimeStringMap([]const u8, .{ + .{ "deprecated", "201904L\n" }, + .{ "fallthrough", "201904L\n" }, + .{ "maybe_unused", "201904L\n" }, + .{ "nodiscard", "202003L\n" }, + .{ "noreturn", "202202L\n" }, + .{ "_Noreturn", "202202L\n" }, + .{ "unsequenced", "202207L\n" }, + .{ "reproducible", "202207L\n" }, + }); + + const ident_str = pp.expandedSlice(identifier.?); + break :res attrs.get(ident_str) orelse not_found; + }; + const start = pp.comp.generated_buf.items.len; + try pp.comp.generated_buf.appendSlice(result); + 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) diff --git a/src/Tokenizer.zig b/src/Tokenizer.zig index 3c9a85a0..3944e522 100644 --- a/src/Tokenizer.zig +++ b/src/Tokenizer.zig @@ -121,6 +121,8 @@ pub const Token = struct { macro_ws, /// Special token for implementing __has_attribute macro_param_has_attribute, + /// Special token for implementing __has_c_attribute + macro_param_has_c_attribute, /// Special token for implementing __has_declspec_attribute macro_param_has_declspec_attribute, /// Special token for implementing __has_warning @@ -521,6 +523,7 @@ pub const Token = struct { .stringify_param, .stringify_va_args, .macro_param_has_attribute, + .macro_param_has_c_attribute, .macro_param_has_declspec_attribute, .macro_param_has_warning, .macro_param_has_feature, diff --git a/test/cases/__has_c_attribute.c b/test/cases/__has_c_attribute.c new file mode 100644 index 00000000..1f8b364b --- /dev/null +++ b/test/cases/__has_c_attribute.c @@ -0,0 +1,11 @@ +//aro-args -std=c2x +#if defined __has_c_attribute +# if __has_c_attribute(fallthrough) + #error attribute exists +# endif +# if __has_c_attribute(does_not_exist) + #error attribute exists +# endif +#endif + +#define EXPECTED_ERRORS "__has_c_attribute.c:4:8: error: attribute exists" From cecbb5752234941904fc656c795de7caa326df24 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 6 Nov 2023 14:33:13 +0200 Subject: [PATCH 2/9] Parser: deprecate K&R style function definitions --- src/Diagnostics.zig | 20 +++++++++++++++-- src/Parser.zig | 39 +++++++++++++++++++++++++++------- src/Type.zig | 17 ++++++++------- test/cases/_Float16.c | 4 ++-- test/cases/ast/_Float16.c | 12 ----------- test/cases/call.c | 9 ++++++-- test/cases/kr_def_deprecated.c | 16 ++++++++++++++ 7 files changed, 83 insertions(+), 34 deletions(-) create mode 100644 test/cases/kr_def_deprecated.c diff --git a/src/Diagnostics.zig b/src/Diagnostics.zig index 9229d01e..566b9bd9 100644 --- a/src/Diagnostics.zig +++ b/src/Diagnostics.zig @@ -179,6 +179,7 @@ pub const Options = struct { @"four-char-constants": Kind = .default, @"unknown-escape-sequence": Kind = .default, @"invalid-pp-token": Kind = .default, + @"deprecated-non-prototype": Kind = .default, }; const messages = struct { @@ -521,10 +522,10 @@ const messages = struct { const kind = .@"error"; }; pub const implicit_func_decl = struct { - const msg = "implicit declaration of function '{s}' is invalid in C99"; + const msg = "call to undeclared function '{s}'; ISO C99 and later do not support implicit function declarations"; const extra = .str; const opt = "implicit-function-declaration"; - const kind = .warning; + const kind = .@"error"; const all = true; }; pub const unknown_builtin = struct { @@ -2546,6 +2547,21 @@ const messages = struct { const msg = "unterminated comment"; const kind = .@"error"; }; + pub const def_no_proto_deprecated = struct { + const msg = "a function definition without a prototype is deprecated in all versions of C and is not supported in C2x"; + const kind = .warning; + const opt = "deprecated-non-prototype"; + }; + pub const passing_args_to_kr = struct { + const msg = "passing arguments to without a prototype is deprecated in all versions of C and is not supported in C2x"; + const kind = .warning; + const opt = "deprecated-non-prototype"; + }; + pub const unknown_type_name = struct { + const msg = "unknown type name '{s}'"; + const kind = .@"error"; + const extra = .str; + }; }; list: std.ArrayListUnmanaged(Message) = .{}, diff --git a/src/Parser.zig b/src/Parser.zig index cc8ca820..db1057a1 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -2953,11 +2953,14 @@ fn directDeclarator(p: *Parser, base_type: Type, d: *Declarator, kind: Declarato return res_ty; } - if (try p.paramDecls()) |params| { + if (try p.paramDecls(d)) |params| { func_ty.params = params; if (p.eatToken(.ellipsis)) |_| specifier = .var_args_func; } else if (p.tok_ids[p.tok_i] == .r_paren) { - specifier = .var_args_func; + specifier = if (p.comp.langopts.standard.atLeast(.c2x)) + .var_args_func + else + .old_style_func; } else if (p.tok_ids[p.tok_i] == .identifier or p.tok_ids[p.tok_i] == .extended_identifier) { d.old_style_func = p.tok_i; const param_buf_top = p.param_buf.items.len; @@ -3015,7 +3018,7 @@ fn pointer(p: *Parser, base_ty: Type) Error!Type { /// paramDecls : paramDecl (',' paramDecl)* (',' '...') /// paramDecl : declSpec (declarator | abstractDeclarator) -fn paramDecls(p: *Parser) Error!?[]Type.Func.Param { +fn paramDecls(p: *Parser, d: *Declarator) Error!?[]Type.Func.Param { // TODO warn about visibility of types declared here const param_buf_top = p.param_buf.items.len; defer p.param_buf.items.len = param_buf_top; @@ -3027,9 +3030,26 @@ fn paramDecls(p: *Parser) Error!?[]Type.Func.Param { defer p.attr_buf.len = attr_buf_top; const param_decl_spec = if (try p.declSpec()) |some| some - else if (p.param_buf.items.len == param_buf_top) - return null - else blk: { + else if (p.comp.langopts.standard.atLeast(.c2x) and + (p.tok_ids[p.tok_i] == .identifier or p.tok_ids[p.tok_i] == .extended_identifier)) + { + // handle deprecated K&R style parameters + const identifier = try p.expectIdentifier(); + try p.errStr(.unknown_type_name, identifier, p.tokSlice(identifier)); + if (d.old_style_func == null) d.old_style_func = identifier; + + try p.param_buf.append(.{ + .name = try p.comp.intern(p.tokSlice(identifier)), + .name_tok = identifier, + .ty = .{ .specifier = .int }, + }); + + if (p.eatToken(.comma) == null) break; + if (p.tok_ids[p.tok_i] == .ellipsis) break; + continue; + } else if (p.param_buf.items.len == param_buf_top) { + return null; + } else blk: { var spec: Type.Builder = .{}; break :blk DeclSpec{ .ty = try spec.finish(p) }; }; @@ -7209,7 +7229,10 @@ fn callExpr(p: *Parser, lhs: Result) Error!Result { } else if (ty.is(.func) and params.len != arg_count) { try p.errExtra(.expected_arguments, first_after, extra); } else if (ty.is(.old_style_func) and params.len != arg_count) { - try p.errExtra(.expected_arguments_old, first_after, extra); + if (params.len == 0) + try p.errTok(.passing_args_to_kr, first_after) + else + try p.errExtra(.expected_arguments_old, first_after, extra); } else if (ty.is(.var_args_func) and arg_count < params.len) { try p.errExtra(.expected_at_least_arguments, first_after, extra); } @@ -7340,7 +7363,7 @@ fn primaryExpr(p: *Parser) Error!Result { }), }; } - if (p.tok_ids[p.tok_i] == .l_paren) { + if (p.tok_ids[p.tok_i] == .l_paren and !p.comp.langopts.standard.atLeast(.c2x)) { // allow implicitly declaring functions before C99 like `puts("foo")` if (mem.startsWith(u8, name, "__builtin_")) try p.errStr(.unknown_builtin, name_tok, name) diff --git a/src/Type.zig b/src/Type.zig index f844e474..c6f8defe 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -104,22 +104,20 @@ pub const Func = struct { name_tok: TokenIndex, }; - fn eql(a: *const Func, b: *const Func, a_var_args: bool, b_var_args: bool, comp: *const Compilation) bool { + fn eql(a: *const Func, b: *const Func, a_spec: Specifier, b_spec: Specifier, comp: *const Compilation) bool { // return type cannot have qualifiers if (!a.return_type.eql(b.return_type, comp, false)) return false; if (a.params.len != b.params.len) { - const a_no_proto = a_var_args and a.params.len == 0 and !comp.langopts.standard.atLeast(.c2x); - const b_no_proto = b_var_args and b.params.len == 0 and !comp.langopts.standard.atLeast(.c2x); - if (a_no_proto or b_no_proto) { - const maybe_has_params = if (a_no_proto) b else a; + if (a_spec == .old_style_func or b_spec == .old_style_func) { + const maybe_has_params = if (a_spec == .old_style_func) b else a; for (maybe_has_params.params) |param| { if (param.ty.undergoesDefaultArgPromotion(comp)) return false; } return true; } } - if (a_var_args != b_var_args) return false; + if ((a_spec == .func) != (b_spec == .func)) return false; // TODO validate this for (a.params, b.params) |param, b_qual| { var a_unqual = param.ty; @@ -1271,7 +1269,7 @@ pub fn eql(a_param: Type, b_param: Type, comp: *const Compilation, check_qualifi .func, .var_args_func, .old_style_func, - => if (!a.data.func.eql(b.data.func, a.specifier == .var_args_func, b.specifier == .var_args_func, comp)) return false, + => if (!a.data.func.eql(b.data.func, a.specifier, b.specifier, comp)) return false, .array, .static_array, @@ -2606,7 +2604,10 @@ pub fn dump(ty: Type, mapper: StringInterner.TypeMapper, langopts: LangOpts, w: try ty.data.sub_type.dump(mapper, langopts, w); }, .func, .var_args_func, .old_style_func => { - try w.writeAll("fn ("); + if (ty.specifier == .old_style_func) + try w.writeAll("kr (") + else + try w.writeAll("fn ("); for (ty.data.func.params, 0..) |param, i| { if (i != 0) try w.writeAll(", "); if (param.name != .empty) try w.print("{s}: ", .{mapper.lookup(param.name)}); diff --git a/test/cases/_Float16.c b/test/cases/_Float16.c index 600950c9..b9a3df17 100644 --- a/test/cases/_Float16.c +++ b/test/cases/_Float16.c @@ -10,12 +10,10 @@ void bar(int x, ...) { va_start(va, x); va_end(va); } -int baz(); void quux(void) { _Float16 f = 1.0f16; bar(1, f); // _Float16 does not promote to double when used as vararg - baz(1, 2.0F16); // _Float16 does not promote to double when used as untyped arg } void conversions(void) { @@ -25,3 +23,5 @@ void conversions(void) { d = d + f16; (void)(f16 + fp16); // _Float16 + __fp16 promotes both to float } + +#define TESTS_SKIPPED 1 diff --git a/test/cases/ast/_Float16.c b/test/cases/ast/_Float16.c index a8b19e9d..56fe02d2 100644 --- a/test/cases/ast/_Float16.c +++ b/test/cases/ast/_Float16.c @@ -45,9 +45,6 @@ fn_def: 'fn (x: int, ...) void' implicit_return: 'void' -fn_proto: 'fn (...) int' - name: baz - fn_def: 'fn () void' name: quux body: @@ -68,15 +65,6 @@ fn_def: 'fn () void' decl_ref_expr: '_Float16' lvalue name: f - call_expr: 'int' - lhs: - implicit_cast: (function_to_pointer) '*fn (...) int' - decl_ref_expr: 'fn (...) int' lvalue - name: baz - args: - int_literal: 'int' (value: 1) - float16_literal: '_Float16' (value: 2) - implicit_return: 'void' fn_def: 'fn () void' diff --git a/test/cases/call.c b/test/cases/call.c index d3e07bd6..b24cb93f 100644 --- a/test/cases/call.c +++ b/test/cases/call.c @@ -56,6 +56,11 @@ void call_with_unsigned(void) { signed_int(&x); } +void func_no_proto(); +void pass_args_to_no_proto(int a) { + func_no_proto(a); +} + #define EXPECTED_ERRORS "call.c:16:7: error: passing 'void' to parameter of incompatible type '_Bool'" \ "call.c:5:21: note: passing argument to parameter here" \ "call.c:19:7: warning: implicit pointer to integer conversion from 'int *' to 'int' [-Wint-conversion]" \ @@ -69,7 +74,7 @@ void call_with_unsigned(void) { "call.c:28:7: error: passing 'int' to parameter of incompatible type 'struct Foo'" \ "call.c:9:25: note: passing argument to parameter here" \ "call.c:33:17: error: parameter has incomplete type 'enum E'" \ - "call.c:34:5: warning: implicit declaration of function 'baz' is invalid in C99 [-Wimplicit-function-declaration]" \ + "call.c:34:5: error: call to undeclared function 'baz'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]" \ "call.c:41:21: error: passing 'double' to parameter of incompatible type 'void *'" \ "call.c:37:28: note: passing argument to parameter here" \ "call.c:45:21: error: passing 'struct S' to parameter of incompatible type 'void *'" \ @@ -80,4 +85,4 @@ void call_with_unsigned(void) { "call.c:8:20: note: passing argument to parameter here" \ "call.c:56:16: warning: passing 'unsigned int *' to parameter of incompatible type 'int *' converts between pointers to integer types with different sign [-Wpointer-sign]" \ "call.c:52:22: note: passing argument to parameter here" \ - + "call.c:61:19: warning: passing arguments to without a prototype is deprecated in all versions of C and is not supported in C2x [-Wdeprecated-non-prototype]" \ diff --git a/test/cases/kr_def_deprecated.c b/test/cases/kr_def_deprecated.c new file mode 100644 index 00000000..bce2310a --- /dev/null +++ b/test/cases/kr_def_deprecated.c @@ -0,0 +1,16 @@ +//aro-args -std=c2x +int foo(a, int b, char c, d) + int a; short d; +{ + return a; +} + +int baz() { + return bar(1); + // TODO no return-type warning +} + +#define EXPECTED_ERRORS "kr_def_deprecated.c:2:9: error: unknown type name 'a'" \ + "kr_def_deprecated.c:2:27: error: unknown type name 'd'" \ + "kr_def_deprecated.c:9:12: error: use of undeclared identifier 'bar'" \ + "kr_def_deprecated.c:11:1: warning: non-void function 'baz' does not return a value [-Wreturn-type]" \ From 97c3de251ae03fec9cea97d3bcc0605893d5f70d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 6 Nov 2023 14:53:27 +0200 Subject: [PATCH 3/9] Parser: implement typeof_unqual --- src/Parser.zig | 23 +++++++++++++---------- src/Tokenizer.zig | 8 +++++++- test/cases/ast/typeof_unqual.c | 9 +++++++++ test/cases/typeof_unqual.c | 4 ++++ 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 test/cases/ast/typeof_unqual.c create mode 100644 test/cases/typeof_unqual.c diff --git a/src/Parser.zig b/src/Parser.zig index db1057a1..17d3898a 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -828,6 +828,7 @@ fn nextExternDecl(p: *Parser) void { .keyword_typeof, .keyword_typeof1, .keyword_typeof2, + .keyword_typeof_unqual, .keyword_extension, .keyword_bit_int, => if (parens == 0) return, @@ -1332,8 +1333,13 @@ pub const DeclSpec = struct { /// : keyword_typeof '(' typeName ')' /// | keyword_typeof '(' expr ')' fn typeof(p: *Parser) Error!?Type { + var unqual = false; switch (p.tok_ids[p.tok_i]) { .keyword_typeof, .keyword_typeof1, .keyword_typeof2 => p.tok_i += 1, + .keyword_typeof_unqual => { + p.tok_i += 1; + unqual = true; + }, else => return null, } const l_paren = try p.expectToken(.l_paren); @@ -1342,7 +1348,7 @@ fn typeof(p: *Parser) Error!?Type { const typeof_ty = try p.arena.create(Type); typeof_ty.* = .{ .data = ty.data, - .qual = ty.qual.inheritFromTypeof(), + .qual = if (unqual) .{} else ty.qual.inheritFromTypeof(), .specifier = ty.specifier, }; @@ -1356,7 +1362,10 @@ fn typeof(p: *Parser) Error!?Type { try p.expectClosing(l_paren, .r_paren); // Special case nullptr_t since it's defined as typeof(nullptr) if (typeof_expr.ty.is(.nullptr_t)) { - return Type{ .specifier = .nullptr_t, .qual = typeof_expr.ty.qual.inheritFromTypeof() }; + return Type{ + .specifier = .nullptr_t, + .qual = if (unqual) .{} else typeof_expr.ty.qual.inheritFromTypeof(), + }; } const inner = try p.arena.create(Type.Expr); @@ -1364,7 +1373,7 @@ fn typeof(p: *Parser) Error!?Type { .node = typeof_expr.node, .ty = .{ .data = typeof_expr.ty.data, - .qual = typeof_expr.ty.qual.inheritFromTypeof(), + .qual = if (unqual) .{} else typeof_expr.ty.qual.inheritFromTypeof(), .specifier = typeof_expr.ty.specifier, }, }; @@ -4571,6 +4580,7 @@ fn nextStmt(p: *Parser, l_brace: TokenIndex) !void { .keyword_typeof, .keyword_typeof1, .keyword_typeof2, + .keyword_typeof_unqual, .keyword_extension, => if (parens == 0) return, .keyword_pragma => p.skipToPragmaSentinel(), @@ -7150,13 +7160,6 @@ fn checkComplexArg(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, } } -fn checkVariableBuiltinArgument(p: *Parser, builtin_tok: TokenIndex, first_after: TokenIndex, param_tok: TokenIndex, arg: *Result, arg_idx: u32, tag: Builtin.Tag) !void { - switch (tag) { - .__builtin_va_start, .__va_start, .va_start => return p.checkVaStartArg(builtin_tok, first_after, param_tok, arg, arg_idx), - else => {}, - } -} - fn callExpr(p: *Parser, lhs: Result) Error!Result { const l_paren = p.tok_i; p.tok_i += 1; diff --git a/src/Tokenizer.zig b/src/Tokenizer.zig index 3944e522..cf817b98 100644 --- a/src/Tokenizer.zig +++ b/src/Tokenizer.zig @@ -218,6 +218,7 @@ pub const Token = struct { keyword_true, keyword_false, keyword_nullptr, + keyword_typeof_unqual, // Preprocessor directives keyword_include, @@ -445,6 +446,7 @@ pub const Token = struct { .keyword_true, .keyword_false, .keyword_nullptr, + .keyword_typeof_unqual, => return true, else => return false, } @@ -650,6 +652,7 @@ pub const Token = struct { .keyword_true => "true", .keyword_false => "false", .keyword_nullptr => "nullptr", + .keyword_typeof_unqual => "typeof_unqual", .keyword_include => "include", .keyword_include_next => "include_next", .keyword_embed => "embed", @@ -835,6 +838,7 @@ pub const Token = struct { .keyword_true, .keyword_false, .keyword_nullptr, + .keyword_typeof_unqual, .keyword_elifdef, .keyword_elifndef, => if (standard.atLeast(.c2x)) kw else .identifier, @@ -921,6 +925,7 @@ pub const Token = struct { .{ "true", .keyword_true }, .{ "false", .keyword_false }, .{ "nullptr", .keyword_nullptr }, + .{ "typeof_unqual", .keyword_typeof_unqual }, // Preprocessor directives .{ "include", .keyword_include }, @@ -2094,7 +2099,7 @@ test "digraphs" { } test "C23 keywords" { - try expectTokensExtra("true false alignas alignof bool static_assert thread_local nullptr", &.{ + try expectTokensExtra("true false alignas alignof bool static_assert thread_local nullptr typeof_unqual", &.{ .keyword_true, .keyword_false, .keyword_c23_alignas, @@ -2103,6 +2108,7 @@ test "C23 keywords" { .keyword_c23_static_assert, .keyword_c23_thread_local, .keyword_nullptr, + .keyword_typeof_unqual, }, .c2x); } diff --git a/test/cases/ast/typeof_unqual.c b/test/cases/ast/typeof_unqual.c new file mode 100644 index 00000000..b65bc98c --- /dev/null +++ b/test/cases/ast/typeof_unqual.c @@ -0,0 +1,9 @@ +var: 'const int' + name: a + +var: 'typeof(: const int)' + name: b + +var: 'typeof(: int)' + name: c + diff --git a/test/cases/typeof_unqual.c b/test/cases/typeof_unqual.c new file mode 100644 index 00000000..2181aa39 --- /dev/null +++ b/test/cases/typeof_unqual.c @@ -0,0 +1,4 @@ +//aro-args -std=c2x +const int a; +typeof(a) b; +typeof_unqual(a) c; From 08bde2c4c44be847331e71ae7e88a7cc0e1f0dc4 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 6 Nov 2023 21:17:40 +0200 Subject: [PATCH 4/9] Parser: allow labels at end of block in C23 --- src/Diagnostics.zig | 6 ++++++ src/Parser.zig | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Diagnostics.zig b/src/Diagnostics.zig index 566b9bd9..4168f6b8 100644 --- a/src/Diagnostics.zig +++ b/src/Diagnostics.zig @@ -2562,6 +2562,12 @@ const messages = struct { const kind = .@"error"; const extra = .str; }; + pub const label_compound_end = struct { + const msg = "label at end of compound statement is a C2x extension"; + const opt = "c2x-extensions"; + const kind = .warning; + const suppress_version = .c2x; + }; }; list: std.ArrayListUnmanaged(Message) = .{}, diff --git a/src/Parser.zig b/src/Parser.zig index 17d3898a..2f7ffdc2 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -4294,7 +4294,7 @@ fn labeledStmt(p: *Parser) Error!?NodeIndex { var labeled_stmt = Tree.Node{ .tag = .labeled_stmt, - .data = .{ .decl = .{ .name = name_tok, .node = try p.stmt() } }, + .data = .{ .decl = .{ .name = name_tok, .node = try p.labelableStmt() } }, }; labeled_stmt.ty = try Attribute.applyLabelAttributes(p, labeled_stmt.ty, attr_buf_top); return try p.addNode(labeled_stmt); @@ -4341,7 +4341,7 @@ fn labeledStmt(p: *Parser) Error!?NodeIndex { try p.errStr(.case_not_in_switch, case, "case"); } - const s = try p.stmt(); + const s = try p.labelableStmt(); if (second_item) |some| return try p.addNode(.{ .tag = .case_range_stmt, .data = .{ .if3 = .{ .cond = s, .body = (try p.addList(&.{ first_item.node, some.node })).start } }, @@ -4351,7 +4351,7 @@ fn labeledStmt(p: *Parser) Error!?NodeIndex { }); } else if (p.eatToken(.keyword_default)) |default| { _ = try p.expectToken(.colon); - const s = try p.stmt(); + const s = try p.labelableStmt(); const node = try p.addNode(.{ .tag = .default_stmt, .data = .{ .un = s }, @@ -4370,6 +4370,14 @@ fn labeledStmt(p: *Parser) Error!?NodeIndex { } else return null; } +fn labelableStmt(p: *Parser) Error!NodeIndex { + if (p.tok_ids[p.tok_i] == .r_brace) { + try p.err(.label_compound_end); + return p.addNode(.{ .tag = .null_stmt, .data = undefined }); + } + return p.stmt(); +} + const StmtExprState = struct { last_expr_tok: TokenIndex = 0, last_expr_res: Result = .{ .ty = .{ .specifier = .void } }, From a7f0cbd6b659d18c0a1be25b2f06d3d71820be6d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 6 Nov 2023 21:52:21 +0200 Subject: [PATCH 5/9] replace uses of c2x with c23 --- include/stddef.h | 2 +- src/Attribute.zig | 8 +-- src/Attribute/names.def | 16 +++--- src/Diagnostics.zig | 56 +++++++++---------- src/Driver.zig | 4 +- src/LangOpts.zig | 29 +++++----- src/Parser.zig | 28 +++++----- src/Preprocessor.zig | 2 +- src/TextLiteral.zig | 2 +- src/Tokenizer.zig | 10 ++-- src/Type.zig | 6 +- src/Value.zig | 2 +- ...{#elifdefc2x_error.c => #elifndef error.c} | 6 +- test/cases/{#elifdefc2x.c => #elifndef.c} | 2 +- test/cases/_BitInt.c | 6 +- test/cases/__builtin_types_compatible_p.c | 2 +- test/cases/__has_c_attribute.c | 2 +- test/cases/c23 attributes.c | 2 +- ...r8_t disabled.c => c23 char8_t disabled.c} | 4 +- test/cases/{c2x char8_t.c => c23 char8_t.c} | 2 +- test/cases/c23 defines.c | 2 +- ...it separators.c => c23 digit separators.c} | 2 +- test/cases/c23 keywords.c | 2 +- test/cases/c23 true false ast.c | 2 +- test/cases/call.c | 2 +- test/cases/complex numbers clang.c | 2 +- test/cases/complex numbers gcc.c | 2 +- test/cases/constexpr.c | 2 +- test/cases/deprecated vars.c | 2 +- ...{#elifdefc2x_error.c => #elifndef error.c} | 0 .../expanded/{#elifdefc2x.c => #elifndef.c} | 0 ... size integer constants x86_64-linux-gnu.c | 2 +- test/cases/integer conversions 32bit.c | 2 +- test/cases/integer conversions.c | 2 +- test/cases/kr_def_deprecated.c | 2 +- test/cases/limits header.c | 2 +- test/cases/missing type specifier c23.c | 4 ++ test/cases/missing type specifier_c2x.c | 4 -- test/cases/nameless param.c | 6 +- test/cases/nullptr.c | 2 +- test/cases/standard attributes.c | 2 +- ...tatic assert c2x.c => static assert c23.c} | 2 +- test/cases/static assert messages.c | 4 +- test/cases/{stdint c2x.c => stdint c23.c} | 2 +- test/cases/typeof_unqual.c | 2 +- test/cases/unreachable.c | 2 +- test/cases/wide strings.c | 2 +- 47 files changed, 126 insertions(+), 125 deletions(-) rename test/cases/{#elifdefc2x_error.c => #elifndef error.c} (50%) rename test/cases/{#elifdefc2x.c => #elifndef.c} (84%) rename test/cases/{c2x char8_t disabled.c => c23 char8_t disabled.c} (84%) rename test/cases/{c2x char8_t.c => c23 char8_t.c} (97%) rename test/cases/{c2x digit separators.c => c23 digit separators.c} (63%) rename test/cases/expanded/{#elifdefc2x_error.c => #elifndef error.c} (100%) rename test/cases/expanded/{#elifdefc2x.c => #elifndef.c} (100%) create mode 100644 test/cases/missing type specifier c23.c delete mode 100644 test/cases/missing type specifier_c2x.c rename test/cases/{static assert c2x.c => static assert c23.c} (67%) rename test/cases/{stdint c2x.c => stdint c23.c} (99%) diff --git a/include/stddef.h b/include/stddef.h index ab4793e0..bda4c942 100644 --- a/include/stddef.h +++ b/include/stddef.h @@ -19,7 +19,7 @@ typedef struct { #if __STDC_VERSION__ >= 202311L # pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wpre-c2x-compat" +# pragma GCC diagnostic ignored "-Wpre-c23-compat" typedef typeof(nullptr) nullptr_t; # pragma GCC diagnostic pop diff --git a/src/Attribute.zig b/src/Attribute.zig index 23e038d4..b3a2270f 100644 --- a/src/Attribute.zig +++ b/src/Attribute.zig @@ -18,20 +18,20 @@ syntax: Syntax, args: Arguments, pub const Syntax = enum { - c2x, + c23, declspec, gnu, keyword, }; pub const Kind = enum { - c2x, + c23, declspec, gnu, pub fn toSyntax(kind: Kind) Syntax { return switch (kind) { - .c2x => .c2x, + .c23 => .c23, .declspec => .declspec, .gnu => .gnu, }; @@ -684,7 +684,7 @@ pub fn fromString(kind: Kind, namespace: ?[]const u8, name: []const u8) ?Tag { tag: Tag, gnu: bool = false, declspec: bool = false, - c2x: bool = false, + c23: bool = false, }; const attribute_names = @import("Attribute/names.def").with(Properties); diff --git a/src/Attribute/names.def b/src/Attribute/names.def index bed39691..e99f2495 100644 --- a/src/Attribute/names.def +++ b/src/Attribute/names.def @@ -1,18 +1,18 @@ # multiple deprecated .tag = .deprecated - .c2x = true + .c23 = true .gnu = true .declspec = true fallthrough .tag = .fallthrough - .c2x = true + .c23 = true .gnu = true noreturn .tag = .@"noreturn" - .c2x = true + .c23 = true .gnu = true .declspec = true @@ -26,22 +26,22 @@ noinline .gnu = true .declspec = true -# c2x only +# c23 only nodiscard .tag = .nodiscard - .c2x = true + .c23 = true reproducible .tag = .reproducible - .c2x = true + .c23 = true unsequenced .tag = .unsequenced - .c2x = true + .c23 = true maybe_unused .tag = .unused - .c2x = true + .c23 = true # gnu only access diff --git a/src/Diagnostics.zig b/src/Diagnostics.zig index 4168f6b8..5cb2f30c 100644 --- a/src/Diagnostics.zig +++ b/src/Diagnostics.zig @@ -100,7 +100,7 @@ pub const Options = struct { @"array-bounds": Kind = .default, @"int-conversion": Kind = .default, @"pointer-type-mismatch": Kind = .default, - @"c2x-extensions": Kind = .default, + @"c23-extensions": Kind = .default, @"incompatible-pointer-types": Kind = .default, @"excess-initializers": Kind = .default, @"division-by-zero": Kind = .default, @@ -164,7 +164,7 @@ pub const Options = struct { @"keyword-macro": Kind = .default, @"pointer-arith": Kind = .default, @"sizeof-array-argument": Kind = .default, - @"pre-c2x-compat": Kind = .default, + @"pre-c23-compat": Kind = .default, @"pointer-bool-conversion": Kind = .default, @"string-conversion": Kind = .default, @"gnu-auto-type": Kind = .default, @@ -384,7 +384,7 @@ const messages = struct { const kind = .warning; const all = true; }; - pub const missing_type_specifier_c2x = struct { + pub const missing_type_specifier_c23 = struct { const msg = "a type specifier is required for all declarations"; const kind = .@"error"; }; @@ -1137,17 +1137,17 @@ const messages = struct { const kind = .@"error"; }; pub const static_assert_missing_message = struct { - const msg = "static_assert with no message is a C2X extension"; - const opt = "c2x-extensions"; + const msg = "static_assert with no message is a C23 extension"; + const opt = "c23-extensions"; const kind = .warning; - const suppress_version = .c2x; + const suppress_version = .c23; }; - pub const pre_c2x_compat = struct { - const msg = "{s} is incompatible with C standards before C2x"; + pub const pre_c23_compat = struct { + const msg = "{s} is incompatible with C standards before C23"; const extra = .str; const kind = .off; - const suppress_unless_version = .c2x; - const opt = "pre-c2x-compat"; + const suppress_unless_version = .c23; + const opt = "pre-c23-compat"; }; pub const unbound_vla = struct { const msg = "variable length array must be bound in function definition"; @@ -1450,10 +1450,10 @@ const messages = struct { const pedantic = true; }; pub const omitting_parameter_name = struct { - const msg = "omitting the parameter name in a function definition is a C2x extension"; - const opt = "c2x-extensions"; + const msg = "omitting the parameter name in a function definition is a C23 extension"; + const opt = "c23-extensions"; const kind = .warning; - const suppress_version = .c2x; + const suppress_version = .c23; }; pub const non_int_bitfield = struct { const msg = "bit-field has non-integer type '{s}'"; @@ -2227,7 +2227,7 @@ const messages = struct { const kind = .off; const pedantic = true; const opt = "bit-int-extension"; - const suppress_version = .c2x; + const suppress_version = .c23; }; pub const unsigned_bit_int_too_small = struct { const msg = "{s} must have a bit size of at least 1"; @@ -2306,10 +2306,10 @@ const messages = struct { const kind = .@"error"; }; pub const bitint_suffix = struct { - const msg = "'_BitInt' suffix for literals is a C2x extension"; - const opt = "c2x-extensions"; + const msg = "'_BitInt' suffix for literals is a C23 extension"; + const opt = "c23-extensions"; const kind = .warning; - const suppress_version = .c2x; + const suppress_version = .c23; }; pub const auto_type_extension = struct { const msg = "'__auto_type' is a GNU extension"; @@ -2469,21 +2469,21 @@ const messages = struct { const extra = .ascii; }; pub const ucn_basic_char_warning = struct { - const msg = "specifying character '{c}' with a universal character name is incompatible with C standards before C2x"; + const msg = "specifying character '{c}' with a universal character name is incompatible with C standards before C23"; const kind = .off; const extra = .ascii; - const suppress_unless_version = .c2x; - const opt = "pre-c2x-compat"; + const suppress_unless_version = .c23; + const opt = "pre-c23-compat"; }; pub const ucn_control_char_error = struct { const msg = "universal character name refers to a control character"; const kind = .@"error"; }; pub const ucn_control_char_warning = struct { - const msg = "universal character name referring to a control character is incompatible with C standards before C2x"; + const msg = "universal character name referring to a control character is incompatible with C standards before C23"; const kind = .off; - const suppress_unless_version = .c2x; - const opt = "pre-c2x-compat"; + const suppress_unless_version = .c23; + const opt = "pre-c23-compat"; }; pub const c89_ucn_in_literal = struct { const msg = "universal character names are only valid in C99 or later"; @@ -2548,12 +2548,12 @@ const messages = struct { const kind = .@"error"; }; pub const def_no_proto_deprecated = struct { - const msg = "a function definition without a prototype is deprecated in all versions of C and is not supported in C2x"; + const msg = "a function definition without a prototype is deprecated in all versions of C and is not supported in C23"; const kind = .warning; const opt = "deprecated-non-prototype"; }; pub const passing_args_to_kr = struct { - const msg = "passing arguments to without a prototype is deprecated in all versions of C and is not supported in C2x"; + const msg = "passing arguments to a function without a prototype is deprecated in all versions of C and is not supported in C23"; const kind = .warning; const opt = "deprecated-non-prototype"; }; @@ -2563,10 +2563,10 @@ const messages = struct { const extra = .str; }; pub const label_compound_end = struct { - const msg = "label at end of compound statement is a C2x extension"; - const opt = "c2x-extensions"; + const msg = "label at end of compound statement is a C23 extension"; + const opt = "c23-extensions"; const kind = .warning; - const suppress_version = .c2x; + const suppress_version = .c23; }; }; diff --git a/src/Driver.zig b/src/Driver.zig index 6b661fc1..cf6ff2bb 100644 --- a/src/Driver.zig +++ b/src/Driver.zig @@ -87,8 +87,8 @@ pub const usage = \\ -c, --compile Only run preprocess, compile, and assemble steps \\ -D = Define to (defaults to 1) \\ -E Only run the preprocessor - \\ -fchar8_t Enable char8_t (enabled by default in C2X and later) - \\ -fno-char8_t Disable char8_t (disabled by default for pre-C2X) + \\ -fchar8_t Enable char8_t (enabled by default in C23 and later) + \\ -fno-char8_t Disable char8_t (disabled by default for pre-C23) \\ -fcolor-diagnostics Enable colors in diagnostics \\ -fno-color-diagnostics Disable colors in diagnostics \\ -fdeclspec Enable support for __declspec attributes diff --git a/src/LangOpts.zig b/src/LangOpts.zig index 05cd1fba..3e5d7f6a 100644 --- a/src/LangOpts.zig +++ b/src/LangOpts.zig @@ -44,19 +44,21 @@ pub const Standard = enum { default, /// ISO C 2017 with GNU extensions gnu17, - /// Working Draft for ISO C2x - c2x, - /// Working Draft for ISO C2x with GNU extensions - gnu2x, + /// Working Draft for ISO C23 + c23, + /// Working Draft for ISO C23 with GNU extensions + gnu23, const NameMap = std.ComptimeStringMap(Standard, .{ .{ "c89", .c89 }, .{ "c90", .c89 }, .{ "iso9899:1990", .c89 }, .{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 }, .{ "gnu90", .gnu89 }, - .{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "gnu99", .gnu99 }, - .{ "c11", .c11 }, .{ "iso9899:2011", .c11 }, .{ "gnu11", .gnu11 }, - .{ "c17", .c17 }, .{ "iso9899:2017", .c17 }, .{ "c18", .c17 }, - .{ "iso9899:2018", .c17 }, .{ "gnu17", .gnu17 }, .{ "gnu18", .gnu17 }, - .{ "c2x", .c2x }, .{ "gnu2x", .gnu2x }, + .{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 }, + .{ "iso9899:199x", .c99 }, .{ "gnu99", .gnu99 }, .{ "gnu9x", .gnu99 }, + .{ "c11", .c11 }, .{ "iso9899:2011", .c11 }, .{ "c1x", .c11 }, + .{ "iso9899:201x", .c11 }, .{ "gnu11", .gnu11 }, .{ "c17", .c17 }, + .{ "iso9899:2017", .c17 }, .{ "c18", .c17 }, .{ "iso9899:2018", .c17 }, + .{ "gnu17", .gnu17 }, .{ "gnu18", .gnu17 }, .{ "c23", .c23 }, + .{ "gnu23", .gnu23 }, .{ "c2x", .c23 }, .{ "gnu2x", .gnu23 }, }); pub fn atLeast(self: Standard, other: Standard) bool { @@ -65,7 +67,7 @@ pub const Standard = enum { pub fn isGNU(standard: Standard) bool { return switch (standard) { - .gnu89, .gnu99, .gnu11, .default, .gnu17, .gnu2x => true, + .gnu89, .gnu99, .gnu11, .default, .gnu17, .gnu23 => true, else => false, }; } @@ -82,8 +84,7 @@ pub const Standard = enum { .c99, .gnu99 => "199901L", .c11, .gnu11 => "201112L", .default, .c17, .gnu17 => "201710L", - // todo: subject to change, verify once c23 finalized - .c2x, .gnu2x => "202311L", + .c23, .gnu23 => "202311L", }; } @@ -119,7 +120,7 @@ allow_half_args_and_returns: bool = false, fp_eval_method: ?FPEvalMethod = null, /// If set, use specified signedness for `char` instead of the target's default char signedness char_signedness_override: ?std.builtin.Signedness = null, -/// If set, override the default availability of char8_t (by default, enabled in C2X and later; disabled otherwise) +/// If set, override the default availability of char8_t (by default, enabled in C23 and later; disabled otherwise) has_char8_t_override: ?bool = null, /// Whether to allow GNU-style inline assembly @@ -145,7 +146,7 @@ pub fn disableMSExtensions(self: *LangOpts) void { } pub fn hasChar8_T(self: *const LangOpts) bool { - return self.has_char8_t_override orelse self.standard.atLeast(.c2x); + return self.has_char8_t_override orelse self.standard.atLeast(.c23); } pub fn hasDigraphs(self: *const LangOpts) bool { diff --git a/src/Parser.zig b/src/Parser.zig index 2f7ffdc2..87f8abfd 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -1195,7 +1195,7 @@ fn staticAssert(p: *Parser) Error!bool { _ = try p.expectToken(.semicolon); if (str.node == .none) { try p.errTok(.static_assert_missing_message, static_assert); - try p.errStr(.pre_c2x_compat, static_assert, "'_Static_assert' with no message"); + try p.errStr(.pre_c23_compat, static_assert, "'_Static_assert' with no message"); } // Array will never be zero; a value of zero for a pointer is a null pointer constant @@ -1578,7 +1578,7 @@ fn gnuAttributeList(p: *Parser) Error!void { } } -fn c2xAttributeList(p: *Parser) Error!void { +fn c23AttributeList(p: *Parser) Error!void { while (p.tok_ids[p.tok_i] != .r_bracket) { var namespace_tok = try p.expectIdentifier(); var namespace: ?[]const u8 = null; @@ -1587,7 +1587,7 @@ fn c2xAttributeList(p: *Parser) Error!void { } else { p.tok_i -= 1; } - if (try p.attribute(.c2x, namespace)) |attr| try p.attr_buf.append(p.gpa, attr); + if (try p.attribute(.c23, namespace)) |attr| try p.attr_buf.append(p.gpa, attr); _ = p.eatToken(.comma); } } @@ -1599,15 +1599,15 @@ fn msvcAttributeList(p: *Parser) Error!void { } } -fn c2xAttribute(p: *Parser) !bool { - if (!p.comp.langopts.standard.atLeast(.c2x)) return false; +fn c23Attribute(p: *Parser) !bool { + if (!p.comp.langopts.standard.atLeast(.c23)) return false; const bracket1 = p.eatToken(.l_bracket) orelse return false; const bracket2 = p.eatToken(.l_bracket) orelse { p.tok_i -= 1; return false; }; - try p.c2xAttributeList(); + try p.c23AttributeList(); _ = try p.expectClosing(bracket2, .r_bracket); _ = try p.expectClosing(bracket1, .r_bracket); @@ -1647,7 +1647,7 @@ fn attributeSpecifier(p: *Parser) Error!void { fn attributeSpecifierExtra(p: *Parser, declarator_name: ?TokenIndex) Error!void { while (true) { if (try p.gnuAttribute()) continue; - if (try p.c2xAttribute()) continue; + if (try p.c23Attribute()) continue; const maybe_declspec_tok = p.tok_i; const attr_buf_top = p.attr_buf.len; if (try p.msvcAttribute()) { @@ -2834,7 +2834,7 @@ fn directDeclarator(p: *Parser, base_type: Type, d: *Declarator, kind: Declarato if (p.eatToken(.l_bracket)) |l_bracket| { if (p.tok_ids[p.tok_i] == .l_bracket) { switch (kind) { - .normal, .record => if (p.comp.langopts.standard.atLeast(.c2x)) { + .normal, .record => if (p.comp.langopts.standard.atLeast(.c23)) { p.tok_i -= 1; return base_type; }, @@ -2966,7 +2966,7 @@ fn directDeclarator(p: *Parser, base_type: Type, d: *Declarator, kind: Declarato func_ty.params = params; if (p.eatToken(.ellipsis)) |_| specifier = .var_args_func; } else if (p.tok_ids[p.tok_i] == .r_paren) { - specifier = if (p.comp.langopts.standard.atLeast(.c2x)) + specifier = if (p.comp.langopts.standard.atLeast(.c23)) .var_args_func else .old_style_func; @@ -3039,7 +3039,7 @@ fn paramDecls(p: *Parser, d: *Declarator) Error!?[]Type.Func.Param { defer p.attr_buf.len = attr_buf_top; const param_decl_spec = if (try p.declSpec()) |some| some - else if (p.comp.langopts.standard.atLeast(.c2x) and + else if (p.comp.langopts.standard.atLeast(.c23) and (p.tok_ids[p.tok_i] == .identifier or p.tok_ids[p.tok_i] == .extended_identifier)) { // handle deprecated K&R style parameters @@ -7374,7 +7374,7 @@ fn primaryExpr(p: *Parser) Error!Result { }), }; } - if (p.tok_ids[p.tok_i] == .l_paren and !p.comp.langopts.standard.atLeast(.c2x)) { + 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) @@ -7423,7 +7423,7 @@ fn primaryExpr(p: *Parser) Error!Result { }, .keyword_nullptr => { defer p.tok_i += 1; - try p.errStr(.pre_c2x_compat, p.tok_i, "'nullptr'"); + try p.errStr(.pre_c23_compat, p.tok_i, "'nullptr'"); return Result{ .val = .{ .tag = .nullptr_t }, .ty = .{ .specifier = .nullptr_t }, @@ -7788,7 +7788,7 @@ fn parseFloat(p: *Parser, buf: []const u8, suffix: NumberSuffix) !Result { else => unreachable, } }; const d_val = std.fmt.parseFloat(f64, buf) catch |er| switch (er) { - error.InvalidCharacter => return p.todo("c2x digit separators in floats"), + error.InvalidCharacter => return p.todo("c23 digit separators in floats"), else => unreachable, }; const tag: Tree.Tag = switch (suffix) { @@ -7941,7 +7941,7 @@ 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_c2x_compat, tok_i, "'_BitInt' suffix for literals"); + try p.errStr(.pre_c23_compat, tok_i, "'_BitInt' suffix for literals"); try p.errTok(.bitint_suffix, tok_i); var managed = try big.int.Managed.init(p.gpa); diff --git a/src/Preprocessor.zig b/src/Preprocessor.zig index 94d73dd6..5d80c0cf 100644 --- a/src/Preprocessor.zig +++ b/src/Preprocessor.zig @@ -1546,7 +1546,7 @@ fn expandFuncMacro( ); break :res not_found; } - if (!pp.comp.langopts.standard.atLeast(.c2x)) break :res not_found; + if (!pp.comp.langopts.standard.atLeast(.c23)) break :res not_found; const attrs = std.ComptimeStringMap([]const u8, .{ .{ "deprecated", "201904L\n" }, diff --git a/src/TextLiteral.zig b/src/TextLiteral.zig index 4364a1d8..782e399d 100644 --- a/src/TextLiteral.zig +++ b/src/TextLiteral.zig @@ -270,7 +270,7 @@ pub const Parser = struct { } if (val < 0xA0 and (val != '$' and val != '@' and val != '`')) { - const is_error = !self.comp.langopts.standard.atLeast(.c2x); + const is_error = !self.comp.langopts.standard.atLeast(.c23); if (val >= 0x20 and val <= 0x7F) { if (is_error) { self.err(.ucn_basic_char_error, .{ .ascii = @intCast(val) }); diff --git a/src/Tokenizer.zig b/src/Tokenizer.zig index cf817b98..5cccc1fb 100644 --- a/src/Tokenizer.zig +++ b/src/Tokenizer.zig @@ -825,7 +825,7 @@ pub const Token = struct { return switch (kw) { .keyword_inline => if (standard.isGNU() or standard.atLeast(.c99)) kw else .identifier, .keyword_restrict => if (standard.atLeast(.c99)) kw else .identifier, - .keyword_typeof => if (standard.isGNU() or standard.atLeast(.c2x)) kw else .identifier, + .keyword_typeof => if (standard.isGNU() or standard.atLeast(.c23)) kw else .identifier, .keyword_asm => if (standard.isGNU()) kw else .identifier, .keyword_declspec => if (comp.langopts.declspec_attrs) kw else .identifier, @@ -841,7 +841,7 @@ pub const Token = struct { .keyword_typeof_unqual, .keyword_elifdef, .keyword_elifndef, - => if (standard.atLeast(.c2x)) kw else .identifier, + => if (standard.atLeast(.c23)) kw else .identifier, .keyword_int64, .keyword_int64_2, @@ -1353,7 +1353,7 @@ pub fn next(self: *Tokenizer) Token { break; }, ':' => { - if (self.comp.langopts.standard.atLeast(.c2x)) { + if (self.comp.langopts.standard.atLeast(.c23)) { id = .colon_colon; self.index += 1; break; @@ -1659,7 +1659,7 @@ pub fn next(self: *Tokenizer) Token { '.', => {}, 'e', 'E', 'p', 'P' => state = .pp_num_exponent, - '\'' => if (self.comp.langopts.standard.atLeast(.c2x)) { + '\'' => if (self.comp.langopts.standard.atLeast(.c23)) { state = .pp_num_digit_separator; } else { id = .pp_num; @@ -2109,7 +2109,7 @@ test "C23 keywords" { .keyword_c23_thread_local, .keyword_nullptr, .keyword_typeof_unqual, - }, .c2x); + }, .c23); } fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, standard: ?LangOpts.Standard) !void { diff --git a/src/Type.zig b/src/Type.zig index c6f8defe..458a823d 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -1607,7 +1607,7 @@ pub const Builder = struct { .void => "void", .auto_type => "__auto_type", .nullptr_t => "nullptr_t", - .bool => if (langopts.standard.atLeast(.c2x)) "bool" else "_Bool", + .bool => if (langopts.standard.atLeast(.c23)) "bool" else "_Bool", .char => "char", .schar => "signed char", .uchar => "unsigned char", @@ -1736,8 +1736,8 @@ pub const Builder = struct { ty = typeof; } else { ty.specifier = .int; - if (p.comp.langopts.standard.atLeast(.c2x)) { - try p.err(.missing_type_specifier_c2x); + if (p.comp.langopts.standard.atLeast(.c23)) { + try p.err(.missing_type_specifier_c23); } else { try p.err(.missing_type_specifier); } diff --git a/src/Value.zig b/src/Value.zig index 1577db93..f9598492 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -618,7 +618,7 @@ pub fn hash(v: Value) u64 { pub fn dump(v: Value, ty: Type, comp: *Compilation, strings: []const u8, w: anytype) !void { switch (v.tag) { .unavailable => try w.writeAll("unavailable"), - .int => if (ty.is(.bool) and comp.langopts.standard.atLeast(.c2x)) { + .int => if (ty.is(.bool) and comp.langopts.standard.atLeast(.c23)) { try w.print("{s}", .{if (v.isZero()) "false" else "true"}); } else if (ty.isUnsignedInt(comp)) { try w.print("{d}", .{v.data.int}); diff --git a/test/cases/#elifdefc2x_error.c b/test/cases/#elifndef error.c similarity index 50% rename from test/cases/#elifdefc2x_error.c rename to test/cases/#elifndef error.c index 764299de..d3cd4150 100644 --- a/test/cases/#elifdefc2x_error.c +++ b/test/cases/#elifndef error.c @@ -1,7 +1,7 @@ -//aro-args -E -std=c2x -P +//aro-args -E -std=c23 -P #define EXPECTED_ERRORS \ - "#elifdefc2x_error.c:8:9: error: macro name missing" \ - "#elifdefc2x_error.c:17:10: error: macro name missing" + "#elifndef error.c:8:9: error: macro name missing" \ + "#elifndef error.c:17:10: error: macro name missing" #ifdef FOO long long #elifdef diff --git a/test/cases/#elifdefc2x.c b/test/cases/#elifndef.c similarity index 84% rename from test/cases/#elifdefc2x.c rename to test/cases/#elifndef.c index abb76a38..089b00ee 100644 --- a/test/cases/#elifdefc2x.c +++ b/test/cases/#elifndef.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x -E -P +//aro-args -std=c23 -E -P #ifdef FOO long long #elifdef FOO diff --git a/test/cases/_BitInt.c b/test/cases/_BitInt.c index 44d2ccb1..cd2d2030 100644 --- a/test/cases/_BitInt.c +++ b/test/cases/_BitInt.c @@ -19,7 +19,7 @@ unsigned _BitInt(0) d; _BitInt(5) g = 2wb; unsigned _BitInt(5) h = 3Uwb; -#pragma GCC diagnostic ignored "-Wc2x-extensions" +#pragma GCC diagnostic ignored "-Wc23-extensions" int x = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFwb; int y = 0wb; int z = 0uwb; @@ -28,7 +28,7 @@ int z = 0uwb; "_BitInt.c:16:1: error: _BitInt of bit sizes greater than 128 not supported" \ "_BitInt.c:17:8: error: signed _BitInt must have a bit size of at least 2" \ "_BitInt.c:18:10: error: unsigned _BitInt must have a bit size of at least 1" \ - "_BitInt.c:20:16: warning: '_BitInt' suffix for literals is a C2x extension [-Wc2x-extensions]" \ - "_BitInt.c:21:25: warning: '_BitInt' suffix for literals is a C2x extension [-Wc2x-extensions]" \ + "_BitInt.c:20:16: warning: '_BitInt' suffix for literals is a C23 extension [-Wc23-extensions]" \ + "_BitInt.c:21:25: warning: '_BitInt' suffix for literals is a C23 extension [-Wc23-extensions]" \ "_BitInt.c:23:9: error: _BitInt of bit sizes greater than 128 not supported" \ diff --git a/test/cases/__builtin_types_compatible_p.c b/test/cases/__builtin_types_compatible_p.c index 68da2f66..4fd1ca3f 100644 --- a/test/cases/__builtin_types_compatible_p.c +++ b/test/cases/__builtin_types_compatible_p.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 #if !__has_builtin(__builtin_types_compatible_p) #error Missing builtin __builtin_types_compatible_p diff --git a/test/cases/__has_c_attribute.c b/test/cases/__has_c_attribute.c index 1f8b364b..f0d74d02 100644 --- a/test/cases/__has_c_attribute.c +++ b/test/cases/__has_c_attribute.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 #if defined __has_c_attribute # if __has_c_attribute(fallthrough) #error attribute exists diff --git a/test/cases/c23 attributes.c b/test/cases/c23 attributes.c index 879ea340..e7cb0f74 100644 --- a/test/cases/c23 attributes.c +++ b/test/cases/c23 attributes.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 // Example adapted from N2956 - unsequenced functions diff --git a/test/cases/c2x char8_t disabled.c b/test/cases/c23 char8_t disabled.c similarity index 84% rename from test/cases/c2x char8_t disabled.c rename to test/cases/c23 char8_t disabled.c index a093cacb..7ff757a9 100644 --- a/test/cases/c2x char8_t disabled.c +++ b/test/cases/c23 char8_t disabled.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x -fno-char8_t +//aro-args -std=c23 -fno-char8_t void foo(void) { char8_t c = 0; @@ -14,5 +14,5 @@ const signed char scbuf2[] = { u8"text" }; const unsigned char ucbuf1[] = u8"text"; const unsigned char ucbuf2[] = { u8"text" }; -#define EXPECTED_ERRORS "c2x char8_t disabled.c:4:5: error: use of undeclared identifier 'char8_t'" \ +#define EXPECTED_ERRORS "c23 char8_t disabled.c:4:5: error: use of undeclared identifier 'char8_t'" \ diff --git a/test/cases/c2x char8_t.c b/test/cases/c23 char8_t.c similarity index 97% rename from test/cases/c2x char8_t.c rename to test/cases/c23 char8_t.c index fd7d652a..ca159985 100644 --- a/test/cases/c2x char8_t.c +++ b/test/cases/c23 char8_t.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 void foo(void) { char8_t c = 0; diff --git a/test/cases/c23 defines.c b/test/cases/c23 defines.c index 7bd87bfe..e95a7d54 100644 --- a/test/cases/c23 defines.c +++ b/test/cases/c23 defines.c @@ -1,2 +1,2 @@ -//aro-args -std=c2x +//aro-args -std=c23 _Static_assert(__STDC_VERSION__ == 202311L, ""); diff --git a/test/cases/c2x digit separators.c b/test/cases/c23 digit separators.c similarity index 63% rename from test/cases/c2x digit separators.c rename to test/cases/c23 digit separators.c index 7cd68511..ae321591 100644 --- a/test/cases/c2x digit separators.c +++ b/test/cases/c23 digit separators.c @@ -1,2 +1,2 @@ -//aro-args -std=c2x +//aro-args -std=c23 _Static_assert(0b1001'0110 == 150); \ No newline at end of file diff --git a/test/cases/c23 keywords.c b/test/cases/c23 keywords.c index d9c67273..c2447a4c 100644 --- a/test/cases/c23 keywords.c +++ b/test/cases/c23 keywords.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x -pedantic -Wundef +//aro-args -std=c23 -pedantic -Wundef #include static_assert(1 == 1); static_assert(alignof(char) == 1); diff --git a/test/cases/c23 true false ast.c b/test/cases/c23 true false ast.c index 9916bbfe..60933a1a 100644 --- a/test/cases/c23 true false ast.c +++ b/test/cases/c23 true false ast.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 bool a = true; bool b = false; bool c = 0; diff --git a/test/cases/call.c b/test/cases/call.c index b24cb93f..4dbd59df 100644 --- a/test/cases/call.c +++ b/test/cases/call.c @@ -85,4 +85,4 @@ void pass_args_to_no_proto(int a) { "call.c:8:20: note: passing argument to parameter here" \ "call.c:56:16: warning: passing 'unsigned int *' to parameter of incompatible type 'int *' converts between pointers to integer types with different sign [-Wpointer-sign]" \ "call.c:52:22: note: passing argument to parameter here" \ - "call.c:61:19: warning: passing arguments to without a prototype is deprecated in all versions of C and is not supported in C2x [-Wdeprecated-non-prototype]" \ + "call.c:61:19: warning: passing arguments to a function without a prototype is deprecated in all versions of C and is not supported in C23 [-Wdeprecated-non-prototype]" \ diff --git a/test/cases/complex numbers clang.c b/test/cases/complex numbers clang.c index 888ca1eb..523933e5 100644 --- a/test/cases/complex numbers clang.c +++ b/test/cases/complex numbers clang.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x --emulate=clang -pedantic +//aro-args -std=c23 --emulate=clang -pedantic void foo(int x, float y) { _Static_assert(__imag(42) == 0); diff --git a/test/cases/complex numbers gcc.c b/test/cases/complex numbers gcc.c index ada9a641..748afa3f 100644 --- a/test/cases/complex numbers gcc.c +++ b/test/cases/complex numbers gcc.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x --emulate=gcc +//aro-args -std=c23 --emulate=gcc void foo(int x, float y) { _Static_assert(__imag(42) == 0); diff --git a/test/cases/constexpr.c b/test/cases/constexpr.c index b93c3d82..0d6e6d0d 100644 --- a/test/cases/constexpr.c +++ b/test/cases/constexpr.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x -Wpedantic +//aro-args -std=c23 -Wpedantic constexpr int a = 1; static constexpr int b = 2; diff --git a/test/cases/deprecated vars.c b/test/cases/deprecated vars.c index 9934cbf9..49245631 100644 --- a/test/cases/deprecated vars.c +++ b/test/cases/deprecated vars.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x -Wno-unused-value +//aro-args -std=c23 -Wno-unused-value void foo(__attribute__((deprecated)) int arg, int arg2 __attribute__((deprecated))) { __attribute__((deprecated)) int foo = 1; diff --git a/test/cases/expanded/#elifdefc2x_error.c b/test/cases/expanded/#elifndef error.c similarity index 100% rename from test/cases/expanded/#elifdefc2x_error.c rename to test/cases/expanded/#elifndef error.c diff --git a/test/cases/expanded/#elifdefc2x.c b/test/cases/expanded/#elifndef.c similarity index 100% rename from test/cases/expanded/#elifdefc2x.c rename to test/cases/expanded/#elifndef.c diff --git a/test/cases/fixed size integer constants x86_64-linux-gnu.c b/test/cases/fixed size integer constants x86_64-linux-gnu.c index ec32a6f5..cde41688 100644 --- a/test/cases/fixed size integer constants x86_64-linux-gnu.c +++ b/test/cases/fixed size integer constants x86_64-linux-gnu.c @@ -1,4 +1,4 @@ -//aro-args --target=x86_64-linux-gnu -std=c2x +//aro-args --target=x86_64-linux-gnu -std=c23 #include "test_helpers.h" diff --git a/test/cases/integer conversions 32bit.c b/test/cases/integer conversions 32bit.c index 514d6d58..bee80395 100644 --- a/test/cases/integer conversions 32bit.c +++ b/test/cases/integer conversions 32bit.c @@ -1,4 +1,4 @@ -//aro-args --target=x86-linux-gnu -Wno-c2x-extensions +//aro-args --target=x86-linux-gnu -Wno-c23-extensions #include "include/test_helpers.h" void foo(void) { diff --git a/test/cases/integer conversions.c b/test/cases/integer conversions.c index 756dc3f4..0f39412c 100644 --- a/test/cases/integer conversions.c +++ b/test/cases/integer conversions.c @@ -1,4 +1,4 @@ -//aro-args --target=x86_64-linux-gnu -Wno-c2x-extensions +//aro-args --target=x86_64-linux-gnu -Wno-c23-extensions #include "include/test_helpers.h" void foo(void) { diff --git a/test/cases/kr_def_deprecated.c b/test/cases/kr_def_deprecated.c index bce2310a..854bdb4d 100644 --- a/test/cases/kr_def_deprecated.c +++ b/test/cases/kr_def_deprecated.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 int foo(a, int b, char c, d) int a; short d; { diff --git a/test/cases/limits header.c b/test/cases/limits header.c index 0584b8be..6798e8cb 100644 --- a/test/cases/limits header.c +++ b/test/cases/limits header.c @@ -1,4 +1,4 @@ -//aro-args --target=x86_64-linux-gnu -std=c2x +//aro-args --target=x86_64-linux-gnu -std=c23 #include diff --git a/test/cases/missing type specifier c23.c b/test/cases/missing type specifier c23.c new file mode 100644 index 00000000..6187bac4 --- /dev/null +++ b/test/cases/missing type specifier c23.c @@ -0,0 +1,4 @@ +//aro-args -std=c23 +#define EXPECTED_ERRORS \ + "missing type specifier c23.c:4:8: error: a type specifier is required for all declarations" +static x = 5; diff --git a/test/cases/missing type specifier_c2x.c b/test/cases/missing type specifier_c2x.c deleted file mode 100644 index ee137dbe..00000000 --- a/test/cases/missing type specifier_c2x.c +++ /dev/null @@ -1,4 +0,0 @@ -//aro-args -std=c2x -#define EXPECTED_ERRORS \ - "missing type specifier_c2x.c:4:8: error: a type specifier is required for all declarations" -static x = 5; diff --git a/test/cases/nameless param.c b/test/cases/nameless param.c index b4ddd0d5..74095e15 100644 --- a/test/cases/nameless param.c +++ b/test/cases/nameless param.c @@ -8,6 +8,6 @@ void bar(float, char) { return; } -#define EXPECTED_ERRORS "nameless param.c:3:13: warning: omitting the parameter name in a function definition is a C2x extension [-Wc2x-extensions]" \ - "nameless param.c:7:15: warning: omitting the parameter name in a function definition is a C2x extension [-Wc2x-extensions]" \ - "nameless param.c:7:21: warning: omitting the parameter name in a function definition is a C2x extension [-Wc2x-extensions]" \ +#define EXPECTED_ERRORS "nameless param.c:3:13: warning: omitting the parameter name in a function definition is a C23 extension [-Wc23-extensions]" \ + "nameless param.c:7:15: warning: omitting the parameter name in a function definition is a C23 extension [-Wc23-extensions]" \ + "nameless param.c:7:21: warning: omitting the parameter name in a function definition is a C23 extension [-Wc23-extensions]" \ diff --git a/test/cases/nullptr.c b/test/cases/nullptr.c index e1879c77..d9c2e29c 100644 --- a/test/cases/nullptr.c +++ b/test/cases/nullptr.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 #include // Comments below from N3047 working draft — August 4, 2022 § 7.21.2 // Because it is considered to be a scalar type, nullptr_t may appear in any context where (void*)0 would be valid, diff --git a/test/cases/standard attributes.c b/test/cases/standard attributes.c index 7d0f5001..b50514ef 100644 --- a/test/cases/standard attributes.c +++ b/test/cases/standard attributes.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 #pragma GCC diagnostic ignored "-Wgnu-alignof-expression" diff --git a/test/cases/static assert c2x.c b/test/cases/static assert c23.c similarity index 67% rename from test/cases/static assert c2x.c rename to test/cases/static assert c23.c index 5dc24141..47341837 100644 --- a/test/cases/static assert c2x.c +++ b/test/cases/static assert c23.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 void foo(void) { _Static_assert(1); } diff --git a/test/cases/static assert messages.c b/test/cases/static assert messages.c index 5920b341..76906f22 100644 --- a/test/cases/static assert messages.c +++ b/test/cases/static assert messages.c @@ -11,8 +11,8 @@ 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 C2X extension [-Wc2x-extensions]" \ - "static assert messages.c:4:5: warning: static_assert with no message is a C2X extension [-Wc2x-extensions]" \ + "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 '('" \ diff --git a/test/cases/stdint c2x.c b/test/cases/stdint c23.c similarity index 99% rename from test/cases/stdint c2x.c rename to test/cases/stdint c23.c index e99f7347..c5326f20 100644 --- a/test/cases/stdint c2x.c +++ b/test/cases/stdint c23.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 #include #include "test_helpers.h" diff --git a/test/cases/typeof_unqual.c b/test/cases/typeof_unqual.c index 2181aa39..406f3f99 100644 --- a/test/cases/typeof_unqual.c +++ b/test/cases/typeof_unqual.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 const int a; typeof(a) b; typeof_unqual(a) c; diff --git a/test/cases/unreachable.c b/test/cases/unreachable.c index 907af178..8378ce87 100644 --- a/test/cases/unreachable.c +++ b/test/cases/unreachable.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 #include void foo(void) { unreachable(); diff --git a/test/cases/wide strings.c b/test/cases/wide strings.c index 2e37b07d..06753c9b 100644 --- a/test/cases/wide strings.c +++ b/test/cases/wide strings.c @@ -1,4 +1,4 @@ -//aro-args -std=c2x +//aro-args -std=c23 #include typedef __WCHAR_TYPE__ wchar_t; From 1c9e0233e53abd7c36904869f0e40e9665f88351 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 7 Nov 2023 11:34:05 +0200 Subject: [PATCH 6/9] Parser: add warning for using UTF-8 char literal before C23 --- src/Diagnostics.zig | 6 ++++++ src/Parser.zig | 1 + test/cases/u8 character constant.c | 6 ++++-- test/cases/wide character constants.c | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Diagnostics.zig b/src/Diagnostics.zig index 5cb2f30c..f5485c7f 100644 --- a/src/Diagnostics.zig +++ b/src/Diagnostics.zig @@ -2568,6 +2568,12 @@ const messages = struct { const kind = .warning; const suppress_version = .c23; }; + pub const u8_char_lit = struct { + const msg = "UTF-8 character literal is a C23 extension"; + const opt = "c23-extensions"; + const kind = .warning; + const suppress_version = .c23; + }; }; list: std.ArrayListUnmanaged(Message) = .{}, diff --git a/src/Parser.zig b/src/Parser.zig index 87f8abfd..58ba1378 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -7684,6 +7684,7 @@ fn charLiteral(p: *Parser) Error!Result { .node = try p.addNode(.{ .tag = .char_literal, .ty = Type.int, .data = undefined }), }; }; + if (char_kind == .utf_8) try p.err(.u8_char_lit); var val: u32 = 0; const slice = char_kind.contentSlice(p.tokSlice(p.tok_i)); diff --git a/test/cases/u8 character constant.c b/test/cases/u8 character constant.c index 95f02fdb..1cd5ebde 100644 --- a/test/cases/u8 character constant.c +++ b/test/cases/u8 character constant.c @@ -1,6 +1,6 @@ const unsigned char c = u8'A'; _Static_assert(c == 'A', ""); - +#pragma GCC diagnostic ignored "-Wc23-extensions" _Static_assert(u8'\xff' == 0xFF, ""); const unsigned char c2 = u8'™'; @@ -12,7 +12,9 @@ const unsigned char c4 = u8'AA'; #error Character constant should be true in preprocessor #endif -#define EXPECTED_ERRORS "u8 character constant.c:6:26: error: character too large for enclosing character literal type" \ +#define EXPECTED_ERRORS \ + "u8 character constant.c:1:25: warning: UTF-8 character literal is a C23 extension [-Wc23-extensions]" \ + "u8 character constant.c:6:26: error: character too large for enclosing character literal type" \ "u8 character constant.c:7:26: error: character too large for enclosing character literal type" \ "u8 character constant.c:8:26: error: Unicode character literals may not contain multiple characters" \ diff --git a/test/cases/wide character constants.c b/test/cases/wide character constants.c index c174fbef..9b98e60c 100644 --- a/test/cases/wide character constants.c +++ b/test/cases/wide character constants.c @@ -1,4 +1,4 @@ -//aro-args -Wfour-char-constants +//aro-args -Wfour-char-constants -Wno-c23-extensions /* A multiline comment to test that the linenumber is correct. From 52e6e6446c727db3cb53540b300d2951d1a22e44 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 7 Nov 2023 17:53:35 +0200 Subject: [PATCH 7/9] Preprocessor: implement embed parameters --- src/Attribute.zig | 2 +- src/Compilation.zig | 18 ++++- src/Diagnostics.zig | 22 +++++ src/Preprocessor.zig | 188 +++++++++++++++++++++++++++++++++---------- src/Tokenizer.zig | 10 +++ test/cases/#embed.c | 19 ++++- 6 files changed, 212 insertions(+), 47 deletions(-) diff --git a/src/Attribute.zig b/src/Attribute.zig index b3a2270f..d9c6c818 100644 --- a/src/Attribute.zig +++ b/src/Attribute.zig @@ -707,7 +707,7 @@ pub fn fromString(kind: Kind, namespace: ?[]const u8, name: []const u8) ?Tag { return null; } -fn normalize(name: []const u8) []const u8 { +pub fn normalize(name: []const u8) []const u8 { if (name.len >= 4 and mem.startsWith(u8, name, "__") and mem.endsWith(u8, name, "__")) { return name[2 .. name.len - 2]; } diff --git a/src/Compilation.zig b/src/Compilation.zig index f026cbc2..64548a96 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1259,7 +1259,7 @@ pub const IncludeType = enum { angle_brackets, }; -fn getFileContents(comp: *Compilation, path: []const u8) ![]const u8 { +fn getFileContents(comp: *Compilation, path: []const u8, limit: ?u32) ![]const u8 { if (mem.indexOfScalar(u8, path, 0) != null) { return error.FileNotFound; } @@ -1267,7 +1267,16 @@ fn getFileContents(comp: *Compilation, path: []const u8) ![]const u8 { const file = try std.fs.cwd().openFile(path, .{}); defer file.close(); - return file.readToEndAlloc(comp.gpa, std.math.maxInt(u32)); + var buf = std.ArrayList(u8).init(comp.gpa); + defer buf.deinit(); + + const max = limit orelse std.math.maxInt(u32); + file.reader().readAllArrayList(&buf, max) catch |e| switch (e) { + error.StreamTooLong => if (limit == null) return e, + else => return e, + }; + + return buf.toOwnedSlice(); } pub fn findEmbed( @@ -1276,9 +1285,10 @@ pub fn findEmbed( includer_token_source: Source.Id, /// angle bracket vs quotes include_type: IncludeType, + limit: ?u32, ) !?[]const u8 { if (std.fs.path.isAbsolute(filename)) { - return if (comp.getFileContents(filename)) |some| + return if (comp.getFileContents(filename, limit)) |some| some else |err| switch (err) { error.OutOfMemory => |e| return e, @@ -1295,7 +1305,7 @@ pub fn findEmbed( while (try it.nextWithFile(filename, stack_fallback.get())) |found| { defer stack_fallback.get().free(found.path); - if (comp.getFileContents(found.path)) |some| + if (comp.getFileContents(found.path, limit)) |some| return some else |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, diff --git a/src/Diagnostics.zig b/src/Diagnostics.zig index f5485c7f..ad489dea 100644 --- a/src/Diagnostics.zig +++ b/src/Diagnostics.zig @@ -180,6 +180,8 @@ pub const Options = struct { @"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, }; const messages = struct { @@ -2574,6 +2576,26 @@ const messages = struct { const kind = .warning; const suppress_version = .c23; }; + pub const malformed_embed_param = struct { + const msg = "unexpected token in embed parameter"; + const kind = .@"error"; + }; + pub const malformed_embed_limit = struct { + const msg = "the limit parameter expects one non-negative integer as a parameter"; + const kind = .@"error"; + }; + pub const duplicate_embed_param = struct { + const msg = "duplicate embed parameter '{s}'"; + const kind = .warning; + const extra = .str; + const opt = "duplicate-embed-param"; + }; + pub const unsupported_embed_param = struct { + const msg = "unsupported embed parameter '{s}' embed parameter"; + const kind = .warning; + const extra = .str; + const opt = "unsupported-embed-param"; + }; }; list: std.ArrayListUnmanaged(Message) = .{}, diff --git a/src/Preprocessor.zig b/src/Preprocessor.zig index 5d80c0cf..14fd9603 100644 --- a/src/Preprocessor.zig +++ b/src/Preprocessor.zig @@ -694,6 +694,14 @@ fn err(pp: *Preprocessor, raw: RawToken, tag: Diagnostics.Tag) !void { }, &.{}); } +fn errStr(pp: *Preprocessor, tok: Token, tag: Diagnostics.Tag, str: []const u8) !void { + try pp.comp.diag.add(.{ + .tag = tag, + .loc = tok.loc, + .extra = .{ .str = str }, + }, tok.expansionSlice()); +} + fn fatal(pp: *Preprocessor, raw: RawToken, comptime fmt: []const u8, args: anytype) Compilation.Error { const source = pp.comp.getSource(raw.source); const line_col = source.lineCol(.{ .id = raw.source, .line = raw.line, .byte_offset = raw.start }); @@ -834,20 +842,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.comp.diag.add(.{ - .tag = .undefined_macro, - .loc = tok.loc, - .extra = .{ .str = pp.expandedSlice(tok) }, - }, tok.expansionSlice()); + try pp.errStr(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.comp.diag.add(.{ - .tag = .fn_macro_undefined, - .loc = tok.loc, - .extra = .{ .str = pp.expandedSlice(tok) }, - }, tok.expansionSlice()); + try pp.errStr(tok, .fn_macro_undefined, pp.expandedSlice(tok)); return false; } @@ -901,11 +901,7 @@ fn handleKeywordDefined(pp: *Preprocessor, macro_tok: *Token, tokens: []const To .l_paren => {}, else => { if (!first.id.isMacroIdentifier()) { - try pp.comp.diag.add(.{ - .tag = .macro_name_must_be_identifier, - .loc = first.loc, - .extra = .{ .str = pp.expandedSlice(first) }, - }, first.expansionSlice()); + try pp.errStr(first, .macro_name_must_be_identifier, pp.expandedSlice(first)); } macro_tok.id = if (pp.defines.contains(pp.expandedSlice(first))) .one else .zero; return it.i; @@ -1346,21 +1342,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.comp.diag.add(.{ - .tag = .expected_str_literal_in, - .loc = param_toks[0].loc, - .extra = .{ .str = "__has_warning" }, - }, param_toks[0].expansionSlice()); + try pp.errStr(param_toks[0], .expected_str_literal_in, "__has_warning"); return false; }, else => |e| return e, }; if (!mem.startsWith(u8, actual_param, "-W")) { - try pp.comp.diag.add(.{ - .tag = .malformed_warning_check, - .loc = param_toks[0].loc, - .extra = .{ .str = "__has_warning" }, - }, param_toks[0].expansionSlice()); + try pp.errStr(param_toks[0], .malformed_warning_check, "__has_warning"); return false; } const warning_name = actual_param[2..]; @@ -1717,11 +1705,7 @@ fn collectMacroFuncArguments( .l_paren => break, else => { if (is_builtin) { - try pp.comp.diag.add(.{ - .tag = .missing_lparen_after_builtin, - .loc = name_tok.loc, - .extra = .{ .str = pp.expandedSlice(name_tok) }, - }, tok.expansionSlice()); + try pp.errStr(name_tok, .missing_lparen_after_builtin, pp.expandedSlice(name_tok)); } // Not a macro function call, go over normal identifier, rewind tokenizer.* = saved_tokenizer; @@ -2125,14 +2109,11 @@ fn pasteTokens(pp: *Preprocessor, lhs_toks: *ExpandBuf, rhs_toks: []const Token) try lhs_toks.append(try pp.makeGeneratedToken(start, pasted_id, lhs)); if (next.id != .nl and next.id != .eof) { - try pp.comp.diag.add(.{ - .tag = .pasting_formed_invalid, - .loc = lhs.loc, - .extra = .{ .str = try pp.comp.diag.arena.allocator().dupe( - u8, - pp.comp.generated_buf.items[start..end], - ) }, - }, lhs.expansionSlice()); + try pp.errStr( + lhs, + .pasting_formed_invalid, + try pp.comp.diag.arena.allocator().dupe(u8, pp.comp.generated_buf.items[start..end]), + ); try lhs_toks.append(tokFromRaw(next)); } @@ -2438,9 +2419,11 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, macro_name: RawToken, l_pa } /// Handle an #embed directive +/// embedDirective : ("FILENAME" | ) embedParam* +/// embedParam : IDENTIFIER (:: IDENTIFIER)? '(' ')' fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { const first = tokenizer.nextNoWS(); - const filename_tok = pp.findIncludeFilenameToken(first, tokenizer, .expect_nl_eof) catch |er| switch (er) { + const filename_tok = pp.findIncludeFilenameToken(first, tokenizer, .ignore_trailing_tokens) catch |er| switch (er) { error.InvalidInclude => return, else => |e| return e, }; @@ -2458,10 +2441,131 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { else => unreachable, }; - const embed_bytes = (try pp.comp.findEmbed(filename, first.source, include_type)) orelse return pp.fatal(first, "'{s}' not found", .{filename}); + // Index into `token_buf` + const Range = struct { + start: u32, + end: u32, + + fn expand(opt_range: ?@This(), pp_: *Preprocessor, tokenizer_: *Tokenizer) !void { + const range = opt_range orelse return; + const slice = pp_.token_buf.items[range.start..range.end]; + for (slice) |tok| { + try pp_.expandMacro(tokenizer_, tok); + } + } + }; + pp.token_buf.items.len = 0; + + var limit: ?u32 = null; + var prefix: ?Range = null; + var suffix: ?Range = null; + var if_empty: ?Range = null; + while (true) { + const param_first = tokenizer.nextNoWS(); + switch (param_first.id) { + .nl, .eof => break, + .identifier => {}, + else => { + try pp.err(param_first, .malformed_embed_param); + continue; + }, + } + + const char_top = pp.char_buf.items.len; + defer pp.char_buf.items.len = char_top; + + const maybe_colon = tokenizer.colonColon(); + const param = switch (maybe_colon.id) { + .colon_colon => blk: { + // vendor::param + const param = tokenizer.nextNoWS(); + if (param.id != .identifier) { + 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); + continue; + } + try pp.char_buf.appendSlice(Attribute.normalize(pp.tokSlice(param_first))); + try pp.char_buf.appendSlice("::"); + try pp.char_buf.appendSlice(Attribute.normalize(pp.tokSlice(param))); + break :blk pp.char_buf.items; + }, + .l_paren => Attribute.normalize(pp.tokSlice(param_first)), + else => { + try pp.err(maybe_colon, .malformed_embed_param); + continue; + }, + }; + + const start: u32 = @intCast(pp.token_buf.items.len); + while (true) { + const next = tokenizer.nextNoWS(); + if (next.id == .r_paren) break; + try pp.token_buf.append(next); + } + const end: u32 = @intCast(pp.token_buf.items.len); + + if (std.mem.eql(u8, param, "limit")) { + if (limit != null) { + try pp.errStr(tokFromRaw(param_first), .duplicate_embed_param, "limit"); + continue; + } + if (start + 1 != end) { + 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); + continue; + } + limit = 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) { + try pp.errStr(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"); + 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"); + continue; + } + if_empty = .{ .start = start, .end = end }; + } else { + try pp.errStr( + tokFromRaw(param_first), + .unsupported_embed_param, + try pp.comp.diag.arena.allocator().dupe(u8, param), + ); + pp.token_buf.items.len = start; + } + } + + const embed_bytes = (try pp.comp.findEmbed(filename, first.source, include_type, limit)) orelse + return pp.fatal(first, "'{s}' not found", .{filename}); defer pp.comp.gpa.free(embed_bytes); - if (embed_bytes.len == 0) return; + try Range.expand(prefix, pp, tokenizer); + + if (embed_bytes.len == 0) { + try Range.expand(if_empty, pp, tokenizer); + try Range.expand(suffix, pp, tokenizer); + return; + } try pp.tokens.ensureUnusedCapacity(pp.comp.gpa, 2 * embed_bytes.len - 1); // N bytes and N-1 commas @@ -2484,6 +2588,8 @@ fn embed(pp: *Preprocessor, tokenizer: *Tokenizer) MacroError!void { pp.tokens.appendAssumeCapacity(try pp.makeGeneratedToken(start + 1, .embed_byte, filename_tok)); } try pp.comp.generated_buf.append('\n'); + + try Range.expand(suffix, pp, tokenizer); } // Handle a #include directive. diff --git a/src/Tokenizer.zig b/src/Tokenizer.zig index 5cccc1fb..15a008f4 100644 --- a/src/Tokenizer.zig +++ b/src/Tokenizer.zig @@ -1769,6 +1769,16 @@ pub fn nextNoWSComments(self: *Tokenizer) Token { return tok; } +/// Try to tokenize a '::' even if not supported by the current language standard. +pub fn colonColon(self: *Tokenizer) Token { + var tok = self.nextNoWS(); + if (tok.id == .colon and self.buf[self.index] == ':') { + self.index += 1; + tok.id = .colon_colon; + } + return tok; +} + test "operators" { try expectTokens( \\ ! != | || |= = == diff --git a/test/cases/#embed.c b/test/cases/#embed.c index 0df486c6..407e31fc 100644 --- a/test/cases/#embed.c +++ b/test/cases/#embed.c @@ -13,4 +13,21 @@ const int Foo = 1 + #embed "include/embed byte" ; -_Static_assert(Foo == 1 + 'A', ""); \ No newline at end of file +_Static_assert(Foo == 1 + 'A', ""); + +unsigned char third[] = { +#embed "include/embed data" __limit__(3) prefix(0, 1,) vendor::unsupported(some toks) suffix(,3, 4) limit(4) +}; + +_Static_assert(sizeof(third) == 2 + 3 + 2, ""); + +const int bar = +#embed "include/embed data" limit(a) limit(1) 1 +; +_Static_assert(bar == 'H', ""); + +#define EXPECTED_ERRORS \ + "#embed.c:19:56: warning: unsupported embed parameter 'vendor::unsupported' embed parameter [-Wunsupported-embed-param]" \ + "#embed.c:19:101: warning: duplicate embed parameter 'limit' [-Wduplicate-embed-param]" \ + "#embed.c:25:29: error: the limit parameter expects one non-negative integer as a parameter" \ + "#embed.c:25:47: error: unexpected token in embed parameter" \ From effb4843db78f509b779df23141f1ed9944d3d38 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 8 Nov 2023 00:11:42 +0200 Subject: [PATCH 8/9] Parser: allow storage class specifiers on compound literals --- src/CodeGen.zig | 3 + src/Diagnostics.zig | 5 ++ src/Parser.zig | 122 +++++++++++++++++++++++++-------- src/Tree.zig | 18 ++++- test/cases/compound literals.c | 12 +++- 5 files changed, 128 insertions(+), 32 deletions(-) diff --git a/src/CodeGen.zig b/src/CodeGen.zig index 4e6f61f4..e83e1220 100644 --- a/src/CodeGen.zig +++ b/src/CodeGen.zig @@ -1001,6 +1001,9 @@ fn genLval(c: *CodeGen, node: NodeIndex) Error!Ir.Ref { .member_access_expr, .member_access_ptr_expr, .array_access_expr, + .static_compound_literal_expr, + .thread_local_compound_literal_expr, + .static_thread_local_compound_literal_expr, => return c.comp.diag.fatalNoSrc("TODO CodeGen.genLval {}\n", .{c.node_tag[@intFromEnum(node)]}), else => unreachable, // Not an lval expression. } diff --git a/src/Diagnostics.zig b/src/Diagnostics.zig index ad489dea..345793a1 100644 --- a/src/Diagnostics.zig +++ b/src/Diagnostics.zig @@ -2596,6 +2596,11 @@ const messages = struct { const extra = .str; const opt = "unsupported-embed-param"; }; + pub const invalid_compound_literal_storage_class = struct { + const msg = "compound literal cannot have {s} storage class"; + const kind = .@"error"; + const extra = .str; + }; }; list: std.ArrayListUnmanaged(Message) = .{}, diff --git a/src/Parser.zig b/src/Parser.zig index 58ba1378..14292c8a 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -564,6 +564,23 @@ fn getNode(p: *Parser, node: NodeIndex, tag: Tree.Tag) ?NodeIndex { } } +fn nodeIsCompoundLiteral(p: *Parser, node: NodeIndex) bool { + var cur = node; + const tags = p.nodes.items(.tag); + const data = p.nodes.items(.data); + while (true) { + switch (tags[@intFromEnum(cur)]) { + .paren_expr => cur = data[@intFromEnum(cur)].un, + .compound_literal_expr, + .static_compound_literal_expr, + .thread_local_compound_literal_expr, + .static_thread_local_compound_literal_expr, + => return true, + else => return false, + } + } +} + fn pragma(p: *Parser) Compilation.Error!bool { var found_pragma = false; while (p.eatToken(.keyword_pragma)) |_| { @@ -1385,6 +1402,41 @@ fn typeof(p: *Parser) Error!?Type { } /// declSpec: (storageClassSpec | typeSpec | typeQual | funcSpec | alignSpec)+ +/// funcSpec : keyword_inline | keyword_noreturn +fn declSpec(p: *Parser) Error!?DeclSpec { + var d: DeclSpec = .{ .ty = .{ .specifier = undefined } }; + var spec: Type.Builder = .{}; + + const start = p.tok_i; + while (true) { + if (try p.storageClassSpec(&d)) continue; + if (try p.typeSpec(&spec)) continue; + const id = p.tok_ids[p.tok_i]; + switch (id) { + .keyword_inline, .keyword_inline1, .keyword_inline2 => { + if (d.@"inline" != null) { + try p.errStr(.duplicate_decl_spec, p.tok_i, "inline"); + } + d.@"inline" = p.tok_i; + }, + .keyword_noreturn => { + if (d.noreturn != null) { + try p.errStr(.duplicate_decl_spec, p.tok_i, "_Noreturn"); + } + d.noreturn = p.tok_i; + }, + else => break, + } + p.tok_i += 1; + } + + if (p.tok_i == start) return null; + + d.ty = try spec.finish(p); + d.auto_type = spec.auto_type_tok; + return d; +} + /// storageClassSpec: /// : keyword_typedef /// | keyword_extern @@ -1392,14 +1444,9 @@ fn typeof(p: *Parser) Error!?Type { /// | keyword_threadlocal /// | keyword_auto /// | keyword_register -/// funcSpec : keyword_inline | keyword_noreturn -fn declSpec(p: *Parser) Error!?DeclSpec { - var d: DeclSpec = .{ .ty = .{ .specifier = undefined } }; - var spec: Type.Builder = .{}; - +fn storageClassSpec(p: *Parser, d: *DeclSpec) Error!bool { const start = p.tok_i; while (true) { - if (try p.typeSpec(&spec)) continue; const id = p.tok_ids[p.tok_i]; switch (id) { .keyword_typedef, @@ -1459,28 +1506,11 @@ fn declSpec(p: *Parser) Error!?DeclSpec { } d.constexpr = p.tok_i; }, - .keyword_inline, .keyword_inline1, .keyword_inline2 => { - if (d.@"inline" != null) { - try p.errStr(.duplicate_decl_spec, p.tok_i, "inline"); - } - d.@"inline" = p.tok_i; - }, - .keyword_noreturn => { - if (d.noreturn != null) { - try p.errStr(.duplicate_decl_spec, p.tok_i, "_Noreturn"); - } - d.noreturn = p.tok_i; - }, else => break, } p.tok_i += 1; } - - if (p.tok_i == start) return null; - - d.ty = try spec.finish(p); - d.auto_type = spec.auto_type_tok; - return d; + return p.tok_i != start; } const InitDeclarator = struct { d: Declarator, initializer: Result = .{} }; @@ -3528,7 +3558,7 @@ fn coerceArrayInit(p: *Parser, item: *Result, tok: TokenIndex, target: Type) !bo if (!target.isArray()) return false; const is_str_lit = p.nodeIs(item.node, .string_literal_expr); - if (!is_str_lit and !p.nodeIs(item.node, .compound_literal_expr) or !item.ty.isArray()) { + if (!is_str_lit and !p.nodeIsCompoundLiteral(item.node) or !item.ty.isArray()) { try p.errTok(.array_init_str, tok); return true; // do not do further coercion } @@ -6936,14 +6966,45 @@ fn unExpr(p: *Parser) Error!Result { } /// compoundLiteral -/// : '(' type_name ')' '{' initializer_list '}' -/// | '(' type_name ')' '{' initializer_list ',' '}' +/// : '(' 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 Result{}; - const ty = (try p.typeName()) orelse { + + var d: DeclSpec = .{ .ty = .{ .specifier = undefined } }; + const any = if (p.comp.langopts.standard.atLeast(.c23)) + try p.storageClassSpec(&d) + else + false; + + const tag: Tree.Tag = switch (d.storage_class) { + .static => if (d.thread_local != null) + .static_thread_local_compound_literal_expr + else + .static_compound_literal_expr, + .register, .none => if (d.thread_local != null) + .thread_local_compound_literal_expr + else + .compound_literal_expr, + .auto, .@"extern", .typedef => |tok| blk: { + try p.errStr(.invalid_compound_literal_storage_class, tok, @tagName(d.storage_class)); + d.storage_class = .none; + break :blk if (d.thread_local != null) + .thread_local_compound_literal_expr + else + .compound_literal_expr; + }, + }; + + var ty = (try p.typeName()) orelse { p.tok_i = l_paren; + if (any) { + try p.err(.expected_type); + return error.ParsingFailed; + } return Result{}; }; + if (d.storage_class == .register) ty.qual.register = true; try p.expectClosing(l_paren, .r_paren); if (ty.isFunc()) { @@ -6955,7 +7016,10 @@ fn compoundLiteral(p: *Parser) Error!Result { return error.ParsingFailed; } var init_list_expr = try p.initializer(ty); - try init_list_expr.un(p, .compound_literal_expr); + if (d.constexpr) |_| { + // TODO error if not constexpr + } + try init_list_expr.un(p, tag); return init_list_expr; } diff --git a/src/Tree.zig b/src/Tree.zig index f6335014..4f213f69 100644 --- a/src/Tree.zig +++ b/src/Tree.zig @@ -540,6 +540,12 @@ pub const Tag = enum(u8) { union_init_expr, /// (ty){ un } compound_literal_expr, + /// (static ty){ un } + static_compound_literal_expr, + /// (thread_local ty){ un } + thread_local_compound_literal_expr, + /// (static thread_local ty){ un } + static_thread_local_compound_literal_expr, /// Inserted at the end of a function body if no return stmt is found. /// ty is the functions return type @@ -604,7 +610,11 @@ pub fn isLval(nodes: Node.List.Slice, extra: []const NodeIndex, value_map: Value pub fn isLvalExtra(nodes: Node.List.Slice, extra: []const NodeIndex, value_map: ValueMap, node: NodeIndex, is_const: *bool) bool { is_const.* = false; switch (nodes.items(.tag)[@intFromEnum(node)]) { - .compound_literal_expr => { + .compound_literal_expr, + .static_compound_literal_expr, + .thread_local_compound_literal_expr, + .static_thread_local_compound_literal_expr, + => { is_const.* = nodes.items(.ty)[@intFromEnum(node)].isConst(); return true; }, @@ -929,7 +939,11 @@ fn dumpNode(tree: Tree, node: NodeIndex, level: u32, mapper: StringInterner.Type try tree.dumpNode(data.union_init.node, level + delta, mapper, color, w); } }, - .compound_literal_expr => { + .compound_literal_expr, + .static_compound_literal_expr, + .thread_local_compound_literal_expr, + .static_thread_local_compound_literal_expr, + => { try tree.dumpNode(data.un, level + half, mapper, color, w); }, .labeled_stmt => { diff --git a/test/cases/compound literals.c b/test/cases/compound literals.c index 63ce2902..82972066 100644 --- a/test/cases/compound literals.c +++ b/test/cases/compound literals.c @@ -1,3 +1,4 @@ +//aro-args -std=c23 struct A { int x; }; @@ -25,5 +26,14 @@ void bar(void) { const char *str3 = (const char []){"string 3"}; } -#define EXPECTED_ERRORS "compound literals.c:20:32: warning: array index 10 is past the end of the array [-Warray-bounds]" \ +void baz() { + &(register int){0}; + &(static thread_local int){0}; + &(extern int){0}; +} +#define EXPECTED_ERRORS \ + "compound literals.c:21:32: warning: array index 10 is past the end of the array [-Warray-bounds]" \ + "compound literals.c:30:5: error: address of register variable requested" \ + "compound literals.c:31:5: warning: expression result unused [-Wunused-value]" \ + "compound literals.c:32:7: error: compound literal cannot have extern storage class" \ From caa78326e7777243db92a8d9b3070ec551f14393 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 8 Nov 2023 09:40:30 +0200 Subject: [PATCH 9/9] Preprocessor: implement `__VA_OPT__` --- src/Diagnostics.zig | 8 +++++ src/Preprocessor.zig | 62 +++++++++++++++++++++++++++++++- src/Tokenizer.zig | 5 +++ test/cases/__VA_OPT__.c | 17 +++++++++ test/cases/expanded/__VA_OPT__.c | 3 ++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 test/cases/__VA_OPT__.c create mode 100644 test/cases/expanded/__VA_OPT__.c diff --git a/src/Diagnostics.zig b/src/Diagnostics.zig index 345793a1..032c5a21 100644 --- a/src/Diagnostics.zig +++ b/src/Diagnostics.zig @@ -2601,6 +2601,14 @@ const messages = struct { const kind = .@"error"; const extra = .str; }; + pub const va_opt_lparen = struct { + const msg = "missing '(' following __VA_OPT__"; + const kind = .@"error"; + }; + pub const va_opt_rparen = struct { + const msg = "unterminated __VA_OPT__ argument list"; + const kind = .@"error"; + }; }; list: std.ArrayListUnmanaged(Message) = .{}, diff --git a/src/Preprocessor.zig b/src/Preprocessor.zig index 14fd9603..906c0dfd 100644 --- a/src/Preprocessor.zig +++ b/src/Preprocessor.zig @@ -1438,6 +1438,9 @@ fn expandFuncMacro( const raw_next = func_macro.tokens[tok_i + 1]; tok_i += 1; + var va_opt_buf = ExpandBuf.init(pp.gpa); + defer va_opt_buf.deinit(); + const next = switch (raw_next.id) { .macro_ws => continue, .hash_hash => continue, @@ -1450,6 +1453,11 @@ fn expandFuncMacro( else &[1]Token{tokFromRaw(.{ .id = .placemarker, .source = .generated })}, .keyword_va_args => variable_arguments.items, + .keyword_va_opt => blk: { + try pp.expandVaOpt(&va_opt_buf, raw_next, variable_arguments.items.len != 0); + if (va_opt_buf.items.len == 0) break; + break :blk va_opt_buf.items; + }, else => &[1]Token{tokFromRaw(raw_next)}, }; @@ -1473,6 +1481,9 @@ fn expandFuncMacro( const raw_loc = Source.Location{ .id = raw.source, .byte_offset = raw.start, .line = raw.line }; try bufCopyTokens(&buf, expanded_variable_arguments.items, &.{raw_loc}); }, + .keyword_va_opt => { + try pp.expandVaOpt(&buf, raw, variable_arguments.items.len != 0); + }, .stringify_param, .stringify_va_args => { const arg = if (raw.id == .stringify_va_args) variable_arguments.items @@ -1628,6 +1639,28 @@ fn expandFuncMacro( return buf; } +fn expandVaOpt( + pp: *Preprocessor, + buf: *ExpandBuf, + raw: RawToken, + should_expand: bool, +) !void { + if (!should_expand) return; + + const source = pp.comp.getSource(raw.source); + var tokenizer: Tokenizer = .{ + .buf = source.buf, + .index = raw.start, + .source = raw.source, + .comp = pp.comp, + .line = raw.line, + }; + while (tokenizer.index < raw.end) { + const tok = tokenizer.next(); + try buf.append(tokFromRaw(tok)); + } +} + fn shouldExpand(tok: Token, macro: *Macro) bool { // macro.loc.line contains the macros end index if (tok.loc.id == macro.loc.id and @@ -2382,6 +2415,33 @@ fn defineFn(pp: *Preprocessor, tokenizer: *Tokenizer, macro_name: RawToken, l_pa } if (var_args and tok.id == .keyword_va_args) { // do nothing + } 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); + return skipToNl(tokenizer); + } + tok.start = opt_l_paren.end; + + var parens: u32 = 0; + while (true) { + const opt_tok = tokenizer.next(); + switch (opt_tok.id) { + .l_paren => parens += 1, + .r_paren => if (parens == 0) { + break; + } else { + parens -= 1; + }, + .nl, .eof => { + try pp.err(opt_tok, .va_opt_rparen); + try pp.err(opt_l_paren, .to_match_paren); + return skipToNl(tokenizer); + }, + .whitespace => {}, + else => tok.end = opt_tok.end, + } + } } else if (tok.id.isMacroIdentifier()) { tok.id.simplifyMacroKeyword(); const s = pp.tokSlice(tok); @@ -3027,7 +3087,7 @@ test "Include guards" { fn skippable(tok_id: RawToken.Id) bool { return switch (tok_id) { - .keyword_defined, .keyword_va_args, .keyword_endif => true, + .keyword_defined, .keyword_va_args, .keyword_va_opt, .keyword_endif => true, else => false, }; } diff --git a/src/Tokenizer.zig b/src/Tokenizer.zig index 15a008f4..d6bd7079 100644 --- a/src/Tokenizer.zig +++ b/src/Tokenizer.zig @@ -238,6 +238,7 @@ pub const Token = struct { keyword_pragma, keyword_line, keyword_va_args, + keyword_va_opt, // gcc keywords keyword_const1, @@ -338,6 +339,7 @@ pub const Token = struct { .keyword_pragma, .keyword_line, .keyword_va_args, + .keyword_va_opt, .macro_func, .macro_function, .macro_pretty_func, @@ -473,6 +475,7 @@ pub const Token = struct { .keyword_pragma, .keyword_line, .keyword_va_args, + .keyword_va_opt, => id.* = .identifier, .keyword_defined => if (defined_to_identifier) { id.* = .identifier; @@ -670,6 +673,7 @@ pub const Token = struct { .keyword_pragma => "pragma", .keyword_line => "line", .keyword_va_args => "__VA_ARGS__", + .keyword_va_opt => "__VA_OPT__", .keyword_const1 => "__const", .keyword_const2 => "__const__", .keyword_inline1 => "__inline", @@ -945,6 +949,7 @@ pub const Token = struct { .{ "pragma", .keyword_pragma }, .{ "line", .keyword_line }, .{ "__VA_ARGS__", .keyword_va_args }, + .{ "__VA_OPT__", .keyword_va_opt }, .{ "__func__", .macro_func }, .{ "__FUNCTION__", .macro_function }, .{ "__PRETTY_FUNCTION__", .macro_pretty_func }, diff --git a/test/cases/__VA_OPT__.c b/test/cases/__VA_OPT__.c new file mode 100644 index 00000000..eb1a005f --- /dev/null +++ b/test/cases/__VA_OPT__.c @@ -0,0 +1,17 @@ +//aro-args -E -P +#define foo(...) __VA_OPT__(1, 2, ) __VA_OPT__(3, 4) +foo(1) + +#define bar(...) a ## __VA_OPT__(3, 4) + +bar() + +#define baz() __VA_OPT__(1) +baz() + +#define invalid1(...) __VA_OPT__ 1 +#define invalid2(...) __VA_OPT__(1 + +#define EXPECTED_ERRORS \ + "__VA_OPT__.c:11:33: error: missing '(' following __VA_OPT__" \ + "__VA_OPT__.c:13:35: error: unterminated __VA_OPT__ argument list" \ + "__VA_OPT__.c:12:33: note: to match this '('" \ diff --git a/test/cases/expanded/__VA_OPT__.c b/test/cases/expanded/__VA_OPT__.c new file mode 100644 index 00000000..a3f84290 --- /dev/null +++ b/test/cases/expanded/__VA_OPT__.c @@ -0,0 +1,3 @@ +1, 2, 3, 4 +a + +__VA_OPT__(1)