Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement more C23 changes #535

Merged
merged 9 commits into from
Nov 8, 2023
20 changes: 18 additions & 2 deletions src/Diagnostics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a small typo in the error message - s/to without/to a function without/

Copy link
Owner Author

@Vexu Vexu Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I copied Clang's bugs too. Never mind it was my fault for removing the function name from the error. Thanks, fixed.

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) = .{},
Expand Down
39 changes: 31 additions & 8 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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) };
};
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
Expand Down
52 changes: 52 additions & 0 deletions src/Preprocessor.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/Tokenizer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
17 changes: 9 additions & 8 deletions src/Type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)});
Expand Down
4 changes: 2 additions & 2 deletions test/cases/_Float16.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -25,3 +23,5 @@ void conversions(void) {
d = d + f16;
(void)(f16 + fp16); // _Float16 + __fp16 promotes both to float
}

#define TESTS_SKIPPED 1
11 changes: 11 additions & 0 deletions test/cases/__has_c_attribute.c
Original file line number Diff line number Diff line change
@@ -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"
12 changes: 0 additions & 12 deletions test/cases/ast/_Float16.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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'
Expand Down
9 changes: 7 additions & 2 deletions test/cases/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -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]" \
Expand All @@ -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 *'" \
Expand All @@ -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]" \
16 changes: 16 additions & 0 deletions test/cases/kr_def_deprecated.c
Original file line number Diff line number Diff line change
@@ -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]" \