diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 591e99907..002f519d0 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -100,6 +100,11 @@ const char *decl_safe_name(Decl *decl) return decl_to_name(decl); } +Decl *decl_template_get_generic(Decl *decl) +{ + return decl->is_template ? declptr(decl->generic_id) : NULL; +} + const char *decl_to_name(Decl *decl) { const char *name = decl_to_a_name(decl); @@ -504,6 +509,53 @@ bool ast_supports_continue(Ast *stmt) return stmt->for_stmt.cond || !stmt->flow.skip_first; } +Ast *ast_contract_has_any(AstId contracts) +{ + while (contracts) + { + Ast *current = astptr(contracts); + contracts = current->next; + ASSERT(current->ast_kind == AST_CONTRACT); + switch (current->contract_stmt.kind) + { + case CONTRACT_UNKNOWN: + case CONTRACT_PURE: + case CONTRACT_PARAM: + case CONTRACT_OPTIONALS: + case CONTRACT_ENSURE: + case CONTRACT_REQUIRE: + return current; + case CONTRACT_COMMENT: + continue; + } + } + return NULL; +} + +Ast *ast_contract_has_any_non_require(AstId contracts) +{ + while (contracts) + { + Ast *current = astptr(contracts); + contracts = current->next; + ASSERT(current->ast_kind == AST_CONTRACT); + switch (current->contract_stmt.kind) + { + case CONTRACT_UNKNOWN: + case CONTRACT_PURE: + case CONTRACT_PARAM: + case CONTRACT_OPTIONALS: + case CONTRACT_ENSURE: + return current; + case CONTRACT_REQUIRE: + case CONTRACT_COMMENT: + continue; + } + } + return NULL; +} + + static void scratch_buffer_append_but_mangle_underscore_dot(const char *name, const char *end, const char *suffix) { char c; diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 4e27a2f67..6fe432772 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2172,6 +2172,8 @@ bool ast_is_not_empty(Ast *ast); bool ast_is_compile_time(Ast *ast); bool ast_supports_continue(Ast *stmt); +Ast *ast_contract_has_any(AstId contracts); +Ast *ast_contract_has_any_non_require(AstId contracts); INLINE void ast_append(AstId **succ, Ast *next); INLINE void ast_prepend(AstId *first, Ast *ast); INLINE bool ast_ok(Ast *ast); @@ -2381,6 +2383,7 @@ const char *decl_to_a_name(Decl *decl); int decl_count_elements(Decl *structlike); bool decl_is_defaulted_var(Decl *decl); void decl_append_links_to_global_during_codegen(Decl *decl); +Decl *decl_template_get_generic(Decl *decl); INLINE bool decl_ok(Decl *decl); INLINE bool decl_poison(Decl *decl); diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 9c9b0c611..82d0d0879 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -22,6 +22,7 @@ static bool parse_attributes_for_global(ParseContext *c, Decl *decl); INLINE bool parse_decl_initializer(ParseContext *c, Decl *decl); INLINE Decl *decl_new_var_current(ParseContext *c, TypeInfo *type, VarDeclKind kind); static bool parse_contracts(ParseContext *c, AstId *contracts_ref); +static Ast *contracts_first_real(AstId contracts); INLINE Decl *decl_new_var_current(ParseContext *c, TypeInfo *type, VarDeclKind kind) { @@ -208,51 +209,6 @@ static inline bool parse_optional_module_params(ParseContext *c, Decl **decl_ref } } -static inline Ast *sema_contract_find_first_non_comment(AstId contracts) -{ - while (contracts) - { - Ast *current = astptr(contracts); - contracts = current->next; - ASSERT(current->ast_kind == AST_CONTRACT); - switch (current->contract_stmt.kind) - { - case CONTRACT_UNKNOWN: - case CONTRACT_PURE: - case CONTRACT_PARAM: - case CONTRACT_OPTIONALS: - case CONTRACT_ENSURE: - case CONTRACT_REQUIRE: - return current; - case CONTRACT_COMMENT: - continue; - } - } - return NULL; -} - -static inline Ast *sema_contract_first_non_comment_require(AstId contracts) -{ - while (contracts) - { - Ast *current = astptr(contracts); - contracts = current->next; - ASSERT(current->ast_kind == AST_CONTRACT); - switch (current->contract_stmt.kind) - { - case CONTRACT_UNKNOWN: - case CONTRACT_PURE: - case CONTRACT_PARAM: - case CONTRACT_OPTIONALS: - case CONTRACT_ENSURE: - return current; - case CONTRACT_REQUIRE: - case CONTRACT_COMMENT: - continue; - } - } - return NULL; -} static int generic_id = 1; static inline void unify_generic_decl(CompilationUnit *unit, Decl *decl) @@ -276,6 +232,30 @@ NEXT:; decl->generic_decl.id = generic_id++; } +bool parse_attach_contracts(Decl *generics, AstId contracts) +{ + if (!contracts) return true; + if (!generics) + { + Ast *first_contract = ast_contract_has_any(contracts); + if (first_contract) RETURN_PRINT_ERROR_AT(false, first_contract, "Contracts can only be used with '@generic' declarations and modules."); + return true; + } + Ast *first_invalid_contract = ast_contract_has_any_non_require(contracts); + if (first_invalid_contract) + { + RETURN_PRINT_ERROR_AT(false, first_invalid_contract, "Invalid constraint - only '@require' is valid for '@generic' declarations and modules."); + } + if (generics->generic_decl.contracts) + { + ast_last(astptrzero(generics->generic_decl.contracts))->next = contracts; + } + else + { + generics->generic_decl.contracts = contracts; + } + return true; +} void parse_attach_generics(ParseContext *c, Decl *generic_decl) { vec_add(c->unit->generic_decls, generic_decl); @@ -326,11 +306,6 @@ bool parse_module(ParseContext *c, AstId contracts) { if (!context_set_module(c, path)) return false; recover_top_level(c); - if (contracts) - { - Ast *first_contract = sema_contract_find_first_non_comment(contracts); - if (first_contract) RETURN_PRINT_ERROR_AT(false, first_contract, "Contracts cannot be use with non-generic modules."); - } return true; } if (!context_set_module(c, path)) return false; @@ -351,25 +326,9 @@ bool parse_module(ParseContext *c, AstId contracts) generic_decl = generic_decl_old; } } - if (contracts) - { - if (!generic_decl) - { - Ast *first_contract = sema_contract_find_first_non_comment(contracts); - if (first_contract) RETURN_PRINT_ERROR_AT(false, first_contract, "Contracts can only be used with '@generic' modules."); - } - else - { - Ast *first_invalid_contract = sema_contract_first_non_comment_require(contracts); - if (first_invalid_contract) - { - RETURN_PRINT_ERROR_AT(false, first_invalid_contract, "Invalid constraint - only '@require' is valid for '@generic'."); - } - } - } + if (!parse_attach_contracts(generic_decl, contracts)) return false; if (generic_decl) { - if (contracts) generic_decl->generic_decl.contracts = contracts; parse_attach_generics(c, generic_decl); c->unit->default_generic_section = generic_decl; } @@ -1060,7 +1019,6 @@ Decl *parse_const_declaration(ParseContext *c, bool is_global, bool is_extern) CONSUME_OR_RET(TOKEN_EQ, poisoned_decl); if (!parse_decl_initializer(c, decl)) return poisoned_decl; RANGE_EXTEND_PREV(decl); - return decl; } @@ -2356,7 +2314,7 @@ static inline void decl_add_type(Decl *decl, TypeKind kind) * typedef_type ::= func_typedef | type generic_params? * func_typedef ::= 'fn' optional_type parameter_type_list */ -static inline Decl *parse_alias_type(ParseContext *c, AstId contracts, bool has_real_contracts) +static inline Decl *parse_alias_type(ParseContext *c, AstId contracts) { advance_and_verify(c, TOKEN_ALIAS); @@ -2405,11 +2363,6 @@ static inline Decl *parse_alias_type(ParseContext *c, AstId contracts, bool has_ return decl; } - if (has_real_contracts) - { - RETURN_PRINT_ERROR_AT(poisoned_decl, astptr(contracts), "Contracts are only used for modules, functions and macros."); - } - // 2. Now parse the type which we know is here. ASSIGN_EXPR_OR_RET(Expr *expr, parse_expr(c), poisoned_decl); @@ -2444,7 +2397,7 @@ static inline Decl *parse_alias_type(ParseContext *c, AstId contracts, bool has_ { decl->type_alias_decl.is_redef = true; } - + if (!parse_attach_contracts(decl_template_get_generic(decl), contracts)) return poisoned_decl; RANGE_EXTEND_PREV(decl); CONSUME_EOS_OR_RET(poisoned_decl); return decl; @@ -2491,7 +2444,7 @@ static inline Decl *parse_alias_module(ParseContext *c, Decl *decl, TokenType to * * identifier_alias ::= path? (IDENT | CONST_IDENT | AT_IDENT) */ -static inline Decl *parse_alias_ident(ParseContext *c) +static inline Decl *parse_alias_ident(ParseContext *c, AstId contracts) { // 1. Store the beginning of the "alias". advance_and_verify(c, TOKEN_ALIAS); @@ -2543,6 +2496,7 @@ static inline Decl *parse_alias_ident(ParseContext *c) ASSIGN_EXPR_OR_RET(decl->define_decl.alias_expr, parse_expr(c), poisoned_decl); + if (!parse_attach_contracts(decl_template_get_generic(decl), contracts)) return poisoned_decl; RANGE_EXTEND_PREV(decl); CONSUME_EOS_OR_RET(poisoned_decl); return decl; @@ -2594,18 +2548,14 @@ static inline Decl *parse_attrdef(ParseContext *c) /** * define_decl ::= ALIAS define_type_body */ -static inline Decl *parse_alias(ParseContext *c, AstId contracts, bool has_real_contracts) +static inline Decl *parse_alias(ParseContext *c, AstId contracts) { switch (peek(c)) { case TOKEN_TYPE_IDENT: - return parse_alias_type(c, contracts, has_real_contracts); + return parse_alias_type(c, contracts); default: - if (has_real_contracts) - { - RETURN_PRINT_ERROR_AT(poisoned_decl, astptr(contracts), "Contracts are only used for modules, functions and macros."); - } - return parse_alias_ident(c); + return parse_alias_ident(c, contracts); } } @@ -3435,6 +3385,7 @@ END: return decl; } + /** * top_level_statement ::= struct_declaration | enum_declaration | fault_declaration | const_declaration * | global_declaration | macro_declaration | func_definition | typedef_declaration @@ -3450,15 +3401,6 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) if (!parse_contracts(c, &contracts)) return poisoned_decl; Decl *decl; - bool has_real_contracts = false; - if (contracts) - { - Ast *contract_start = astptr(contracts); - if (contract_start->contract_stmt.kind != CONTRACT_COMMENT || contract_start->next) - { - has_real_contracts = true; - } - } TokenType tok = c->tok; if (tok != TOKEN_MODULE && !c->unit->module) { @@ -3466,7 +3408,7 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) c->unit->module_generated = true; // Pass the docs to the next thing. } - + bool attach_contracts = false; switch (tok) { case TOKEN_EXTERN: @@ -3479,14 +3421,14 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) decl = parse_func_definition(c, contracts, FUNC_PARSE_EXTERN); break; case TOKEN_CONST: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_top_level_const_declaration(c, true); + attach_contracts = true; break; case TOKEN_IDENT: case TOKEN_TLOCAL: case TYPELIKE_TOKENS: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_global_declaration(c); + attach_contracts = true; break; default: PRINT_ERROR_HERE("Expected 'extern' to be followed by a function, constant or global variable."); @@ -3524,7 +3466,7 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) PRINT_ERROR_HERE("There are more than one doc comment in a row, that is not allowed."); return poisoned_decl; case TOKEN_ALIAS: - decl = parse_alias(c, contracts, has_real_contracts); + decl = parse_alias(c, contracts); if (decl->decl_kind == DECL_ALIAS_PATH) { if (!context_out) @@ -3537,8 +3479,8 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) } break; case TOKEN_ATTRDEF: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_attrdef(c); + attach_contracts = true; break; case TOKEN_FN: decl = parse_func_definition(c, contracts, c->unit->is_interface_file ? FUNC_PARSE_C3I : FUNC_PARSE_REGULAR); @@ -3585,40 +3527,40 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) decl = parse_exec(c); break; case TOKEN_BITSTRUCT: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_bitstruct_declaration(c); + attach_contracts = true; break; case TOKEN_INTERFACE: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_interface_declaration(c); + attach_contracts = true; break; case TOKEN_TYPEDEF: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_typedef_declaration(c); + attach_contracts = true; break; case TOKEN_CONST: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_top_level_const_declaration(c, false); + attach_contracts = true; break; case TOKEN_STRUCT: case TOKEN_UNION: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_struct_declaration(c); + attach_contracts = true; break; case TOKEN_MACRO: decl = parse_macro_declaration(c, contracts); break; case TOKEN_ENUM: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_enum_declaration(c); + attach_contracts = true; break; case TOKEN_FAULTDEF: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_faultdef_declaration(c); + attach_contracts = true; break; case TOKEN_IDENT: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_global_declaration(c); + attach_contracts = true; break; case TOKEN_EOF: PRINT_ERROR_LAST("Expected a top level declaration."); @@ -3638,8 +3580,8 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) return poisoned_decl; case TOKEN_TLOCAL: case TYPELIKE_TOKENS: - if (has_real_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_global_declaration(c); + attach_contracts = true; break; case TOKEN_EOS: PRINT_ERROR_HERE("';' wasn't expected here, try removing it."); @@ -3649,6 +3591,7 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) return poisoned_decl; } if (!decl_ok(decl)) return decl; + if (attach_contracts && contracts && !parse_attach_contracts(decl_template_get_generic(decl), contracts)) return poisoned_decl; ASSERT(decl); return decl; CONTRACT_NOT_ALLOWED: diff --git a/test/test_suite/any/casting_voidptr_to_any.c3 b/test/test_suite/any/casting_voidptr_to_any.c3 index 5ee579e78..f3708379b 100644 --- a/test/test_suite/any/casting_voidptr_to_any.c3 +++ b/test/test_suite/any/casting_voidptr_to_any.c3 @@ -1,26 +1,30 @@ module mylib::ifaces; -interface IOp { - fn void op(); +interface IOp +{ + fn void op(); } module mylib{Type}; import std::io; import mylib::ifaces; -struct Op (IOp){ - Type data; +struct Op (IOp) +{ + Type data; } fn void Op.op(&self) @dynamic => io::printn("op"); module myapp; import mylib; -fn void test(void* tptr){ - // this work - IOp iop = (Op{int}*)tptr; - iop.op(); +fn void test(void* tptr) +{ + // this work + IOp iop = (Op{int}*)tptr; + iop.op(); - // this don't work - iop = tptr; // #error: Casting a 'void*' to 'IOp' is not permitted - iop.op(); + // this don't work + iop = tptr; // #error: Casting a 'void*' to 'IOp' is not permitted + iop.op(); } -fn void main(String[] args) { - Op{int}* t = mem::new(Op{int}, {.data = 1}); - test(&t); +fn void main(String[] args) +{ + Op{int}* t = mem::new(Op{int}, {.data = 1}); + test(&t); } diff --git a/test/test_suite/generic/generic_contract_aggregation.c3 b/test/test_suite/generic/generic_contract_aggregation.c3 new file mode 100644 index 000000000..efe1f58b4 --- /dev/null +++ b/test/test_suite/generic/generic_contract_aggregation.c3 @@ -0,0 +1,13 @@ +import std::io; + +<* @require Type.kindof == SIGNED_INT *> +struct Foo @generic(Type) +{ + Type a; +} + +fn int main() +{ + Foo{double} d; // #error: Parameter(s) failed validation: @require "Type.kindof == SIGNED_INT" + return 0; +}