Skip to content

Commit

Permalink
Parser: deprecate K&R style function definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
Vexu committed Nov 6, 2023
1 parent 0b621ed commit b0a3690
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 34 deletions.
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";
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
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
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]" \

0 comments on commit b0a3690

Please sign in to comment.