From bd9bc118db1996f7be359de87fc8303d4048f698 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sat, 6 Sep 2025 16:18:33 +0200 Subject: [PATCH] Allow doc comments on individual struct members, faultdefs and enum values #2427. --- releasenotes.md | 1 + src/compiler/parse_global.c | 20 +++++++++- test/test_suite/errors/contracts_in_defs.c3 | 41 +++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 test/test_suite/errors/contracts_in_defs.c3 diff --git a/releasenotes.md b/releasenotes.md index caeca685c..e7c75aab5 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -56,6 +56,7 @@ - `$defined(#hash)` will not check the internal expression, just that `#hash` exists. Use `$defined((void)#hash)` for the old behaviour. - Added optional macro arguments using `macro foo(int x = ...)` which can be checked using `$defined(x)`. - Add compile time ternary `$val ??? : `. +- Allow doc comments on individual struct members, faultdefs and enum values #2427. ### Fixes - List.remove_at would incorrectly trigger ASAN. diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index b65a8ee2c..fd3728b19 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -1728,7 +1728,22 @@ static inline bool parse_fn_parameter_list(ParseContext *c, Signature *signature // --- Parse types - +static bool parse_element_contract(ParseContext *c, const char *error) +{ + AstId contracts = 0; + if (!parse_contracts(c, &contracts)) return false; + if (!contracts) return true; + Ast *ast = astptr(contracts); + while (ast) + { + if (ast->ast_kind != AST_CONTRACT || ast->contract_stmt.kind != CONTRACT_COMMENT) + { + RETURN_PRINT_ERROR_AT(false, ast, "No constraints are allowed on %s.", error); + } + ast = astptrzero(ast->next); + } + return true; +} /** * Expect pointer to after '{' * @@ -1754,6 +1769,7 @@ static bool parse_struct_body(ParseContext *c, Decl *parent) ArrayIndex index = 0; while (!tok_is(c, TOKEN_RBRACE)) { + if (!parse_element_contract(c, "struct/union members")) return decl_poison(parent); TokenType token_type = c->tok; if (token_type == TOKEN_STRUCT || token_type == TOKEN_UNION || token_type == TOKEN_BITSTRUCT) { @@ -2476,6 +2492,7 @@ static inline Decl *parse_macro_declaration(ParseContext *c, AstId docs) static inline Decl *parse_fault(ParseContext *c) { + if (!parse_element_contract(c, "faults")) return poisoned_decl; Decl *decl = decl_new(DECL_FAULT, symstr(c), c->span); if (!consume_const_name(c, "fault")) return poisoned_decl; if (!parse_attributes_for_global(c, decl)) return poisoned_decl; @@ -2556,6 +2573,7 @@ static bool parse_enum_values(ParseContext *c, Decl*** values_ref, Visibility vi Decl **values = NULL; while (!try_consume(c, TOKEN_RBRACE)) { + if (!parse_element_contract(c, "enum values")) return false; Decl *enum_const = decl_new(DECL_ENUM_CONSTANT, symstr(c), c->span); if (is_const_enum) enum_const->enum_constant.is_raw = is_const_enum; enum_const->visibility = visibility; diff --git a/test/test_suite/errors/contracts_in_defs.c3 b/test/test_suite/errors/contracts_in_defs.c3 new file mode 100644 index 000000000..510d0234f --- /dev/null +++ b/test/test_suite/errors/contracts_in_defs.c3 @@ -0,0 +1,41 @@ +module test; +import std; + +struct Foo +{ + <* hello *> + int a; + <* test *> + union + { + <* test2 *> + int b; + } + struct foo + { + <* baz *> + int g; + } +} + +faultdef <* hello *> FOO; +enum Abc +{ + <* test *> + XYZ +} + +faultdef <* @require a > 0 *> FOO2; // #error: No constraints are allowed on faults +enum Abc2 +{ + <* @require a > 0 *> // #error: No constraints are allowed on enum values + XYZ +} +struct Test +{ + <* @require a > 0 *> // #error: No constraints are allowed on struct/union members + int a; +} + +fn void main() +{} \ No newline at end of file