diff --git a/releasenotes.md b/releasenotes.md index 2b11f0f80..6492affe9 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -18,7 +18,8 @@ - Improve error message when providing `alias` with a typeid expression where a type was expected. #2944 - Const enums removed. - Constdef declarations introduced. - +- Properly support `@deprecated` as contract. +- Support deprecating enum values. ### Stdlib changes - Summarize sort macros as generic function wrappers to reduce the amount of generated code. #2831 diff --git a/scripts/tools/ci_tests.sh b/scripts/tools/ci_tests.sh index bbd54f49e..9727eb0e7 100755 --- a/scripts/tools/ci_tests.sh +++ b/scripts/tools/ci_tests.sh @@ -64,36 +64,35 @@ cd "$ROOT_DIR/resources" run_examples() { echo "--- Running Standard Examples ---" - "$C3C_BIN" compile-only -vv examples/base64.c3 --target linux-x64 - "$C3C_BIN" compile -vv examples/base64.c3 - "$C3C_BIN" compile -vv examples/binarydigits.c3 - "$C3C_BIN" compile -vv examples/brainfk.c3 - "$C3C_BIN" compile -vv examples/factorial_macro.c3 - "$C3C_BIN" compile -vv examples/fasta.c3 - "$C3C_BIN" compile -vv examples/gameoflife.c3 - "$C3C_BIN" compile -vv examples/hash.c3 - "$C3C_BIN" compile-only -vv examples/levenshtein.c3 - "$C3C_BIN" compile -vv examples/load_world.c3 - "$C3C_BIN" compile-only -vv examples/map.c3 - "$C3C_BIN" compile -vv examples/mandelbrot.c3 - "$C3C_BIN" compile -vv examples/plus_minus.c3 - "$C3C_BIN" compile -vv examples/nbodies.c3 - "$C3C_BIN" compile -vv examples/spectralnorm.c3 - "$C3C_BIN" compile -vv examples/swap.c3 - "$C3C_BIN" compile -vv examples/contextfree/boolerr.c3 - "$C3C_BIN" compile -vv examples/contextfree/dynscope.c3 - "$C3C_BIN" compile -vv examples/contextfree/guess_number.c3 - "$C3C_BIN" compile -vv examples/contextfree/multi.c3 - "$C3C_BIN" compile -vv examples/contextfree/cleanup.c3 + "$C3C_BIN" compile examples/base64.c3 + "$C3C_BIN" compile examples/binarydigits.c3 + "$C3C_BIN" compile examples/brainfk.c3 + "$C3C_BIN" compile examples/factorial_macro.c3 + "$C3C_BIN" compile examples/fasta.c3 + "$C3C_BIN" compile examples/gameoflife.c3 + "$C3C_BIN" compile examples/hash.c3 + "$C3C_BIN" compile-only examples/levenshtein.c3 + "$C3C_BIN" compile examples/load_world.c3 + "$C3C_BIN" compile-only examples/map.c3 + "$C3C_BIN" compile examples/mandelbrot.c3 + "$C3C_BIN" compile examples/plus_minus.c3 + "$C3C_BIN" compile examples/nbodies.c3 + "$C3C_BIN" compile examples/spectralnorm.c3 + "$C3C_BIN" compile examples/swap.c3 + "$C3C_BIN" compile examples/contextfree/boolerr.c3 + "$C3C_BIN" compile examples/contextfree/dynscope.c3 + "$C3C_BIN" compile examples/contextfree/guess_number.c3 + "$C3C_BIN" compile examples/contextfree/multi.c3 + "$C3C_BIN" compile examples/contextfree/cleanup.c3 - "$C3C_BIN" compile-run -vv examples/hello_world_many.c3 - "$C3C_BIN" compile-run -vv examples/time.c3 - "$C3C_BIN" compile-run -vv examples/fannkuch-redux.c3 - "$C3C_BIN" compile-run -vv examples/contextfree/boolerr.c3 - "$C3C_BIN" compile-run -vv examples/load_world.c3 - "$C3C_BIN" compile-run -vv examples/process.c3 - "$C3C_BIN" compile-run -vv examples/ls.c3 - "$C3C_BIN" compile-run -vv examples/args.c3 -- foo -bar "baz baz" + "$C3C_BIN" compile-run examples/hello_world_many.c3 + "$C3C_BIN" compile-run examples/time.c3 + "$C3C_BIN" compile-run examples/fannkuch-redux.c3 + "$C3C_BIN" compile-run examples/contextfree/boolerr.c3 + "$C3C_BIN" compile-run examples/load_world.c3 + "$C3C_BIN" compile-run examples/process.c3 + "$C3C_BIN" compile-run examples/ls.c3 + "$C3C_BIN" compile-run examples/args.c3 -- foo -bar "baz baz" if [[ "$OS_MODE" == "linux" ]]; then "$C3C_BIN" compile-run --linker=builtin linux_stack.c3 || echo "Warning: linux_stack builtin linker skipped" diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 4a5f49f13..7e98a0c08 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -143,6 +143,7 @@ const char *decl_to_a_name(Decl *decl) case DECL_INTERFACE: return "an interface"; case DECL_STRUCT: return "a struct"; case DECL_UNION: return "a union"; + case DECL_CONTRACT: return "a contract"; case DECL_VAR: switch (decl->var.kind) { @@ -347,6 +348,7 @@ bool decl_may_be_generic(Decl *decl) case DECL_IMPORT: case DECL_LABEL: case DECL_CONSTDEF: + case DECL_CONTRACT: return false; case DECL_ATTRIBUTE: case DECL_BITSTRUCT: @@ -548,53 +550,6 @@ 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/c_codegen.c b/src/compiler/c_codegen.c index 0a90f2a9c..3eda9388e 100644 --- a/src/compiler/c_codegen.c +++ b/src/compiler/c_codegen.c @@ -858,10 +858,6 @@ static void c_emit_stmt(GenContext *c, Ast *stmt) break; case AST_NEXTCASE_STMT: break; - case AST_CONTRACT: - break; - case AST_CONTRACT_FAULT: - break; } PRINT("/* TODO */\n"); } diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 4905a2521..4bb836153 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -562,7 +562,7 @@ typedef struct OperatorOverload operator : 6; Signature signature; AstId body; - AstId docs; + DeclId docs; union { struct // Function related @@ -604,7 +604,7 @@ typedef struct typedef struct { Signature signature; - AstId docs; + DeclId docs; } FnTypeDecl; @@ -617,8 +617,8 @@ typedef struct typedef struct { const char **parameters; - AstId contracts; unsigned id; + Expr **requires; Decl **instances; Decl *owner; Decl **decls; @@ -655,6 +655,34 @@ typedef struct }; } DefineDecl; +typedef struct +{ + SourceSpan span; + const char *name; + InOutModifier modifier : 4; + bool by_ref : 1; +} ContractParam; + +typedef struct +{ + Expr *decl_exprs; + const char *comment; + const char *expr_string; +} ExprContract; + +typedef struct +{ + Expr **requires; + Expr **ensures; + ContractParam *params; + bool pure; + union + { + Expr **opt_returns; + Decl **opt_returns_resolved; + }; +} ContractsDecl; + typedef struct { Path *alias_path; @@ -775,6 +803,7 @@ typedef struct Decl_ Ast *ct_echo_decl; Decl** ct_else_decl; Decl** decls; + ContractsDecl contracts_decl; DefineDecl define_decl; ModuleAliasDecl module_alias_decl; EnumConstantDecl enum_constant; @@ -1272,6 +1301,7 @@ struct Expr_ ExprBuiltin builtin_expr; // 16 ExprCall call_expr; // 40 ExprCast cast_expr; // 12 + ExprContract contract_expr; ExprUnresolvedCatch unresolved_catch_expr; // 24 ExprCatch catch_expr; // 24 Expr** cond_expr; // 8 @@ -1565,49 +1595,6 @@ typedef struct } AstAssertStmt; -typedef struct -{ - bool resolved; - bool expanding; - union - { - Expr *expr; - Decl *decl; - }; -} AstDocFault; - -typedef struct AstDocDirective_ -{ - ContractKind kind : 4; - union - { - struct - { - const char *name; - SourceSpan span; - InOutModifier modifier : 4; - bool by_ref : 1; - } param; - Ast **faults; - struct - { - Expr *decl_exprs; - const char *comment; - const char *expr_string; - } contract; - struct - { - const char *directive_name; - const char *rest_of_line; - } generic; - struct - { - const char *string; - size_t strlen; - }; - }; -} AstContractStmt; - typedef struct Ast_ { SourceSpan span; @@ -1625,8 +1612,6 @@ typedef struct Ast_ AstCompoundStmt compound_stmt; // 12 AstId ct_compound_stmt; AstContinueBreakStmt contbreak_stmt;// 24 - AstContractStmt contract_stmt; // 32 - AstDocFault contract_fault; // 24 AstId ct_else_stmt; // 4 AstCtTypeAssignStmt ct_type_assign_stmt; AstCtForeachStmt ct_foreach_stmt; // 40 @@ -2174,8 +2159,6 @@ 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); @@ -2299,6 +2282,7 @@ UNUSED bool i128_get_bit(const Int128 *op, int bit); void copy_begin(void); void copy_end(void); Expr *copy_expr_single(Expr *source_expr); +Expr **copy_exprlist_macro(Expr **source_expr_list); Decl **copy_decl_list_single(Decl **decl_list); Decl **copy_decl_list_single_for_generic(Decl **decl_list, Decl *generic_instance); Attr **copy_attributes_single(Attr** attr_list); @@ -3978,6 +3962,7 @@ static inline void expr_set_span(Expr *expr, SourceSpan loc) case EXPR_RVALUE: case EXPR_CT_SUBSCRIPT: case EXPR_IOTA_DECL: + case EXPR_CONTRACT: break; } } diff --git a/src/compiler/context.c b/src/compiler/context.c index 900b1150b..d7fd812af 100644 --- a/src/compiler/context.c +++ b/src/compiler/context.c @@ -159,6 +159,7 @@ void decl_register(CompilationUnit *unit, Decl *decl) case DECL_IMPORT: case DECL_LABEL: case DECL_POISONED: + case DECL_CONTRACT: UNREACHABLE_VOID case DECL_ATTRIBUTE: case DECL_BITSTRUCT: @@ -293,6 +294,7 @@ void unit_register_global_decl(CompilationUnit *unit, Decl *decl) case DECL_GENERIC_INSTANCE: case DECL_IMPORT: case DECL_LABEL: + case DECL_CONTRACT: UNREACHABLE_VOID case DECL_CT_EXEC: case DECL_CT_INCLUDE: diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 96c4db17f..ad7236c42 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -193,6 +193,13 @@ Expr *copy_expr_single(Expr *source_expr) return expr; } +Expr **copy_exprlist_macro(Expr **source_expr_list) +{ + ASSERT(copy_struct.copy_in_use); + Expr **list = copy_expr_list(©_struct, source_expr_list); + return list; +} + void copy_range(CopyStruct *c, Range *range) { switch (range->range_type) @@ -306,6 +313,9 @@ Expr *copy_expr(CopyStruct *c, Expr *source_expr) MACRO_COPY_EXPR(expr->two_expr.first); MACRO_COPY_EXPR(expr->two_expr.last); return expr; + case EXPR_CONTRACT: + MACRO_COPY_EXPR(expr->contract_expr.decl_exprs); + return expr; case EXPR_TYPECALL: case EXPR_CT_SUBSCRIPT: UNREACHABLE @@ -607,24 +617,6 @@ Expr *copy_expr(CopyStruct *c, Expr *source_expr) UNREACHABLE } -void doc_ast_copy(CopyStruct *c, AstContractStmt *doc) -{ - switch (doc->kind) - { - case CONTRACT_REQUIRE: - case CONTRACT_ENSURE: - MACRO_COPY_EXPR(doc->contract.decl_exprs); - break; - case CONTRACT_OPTIONALS: - MACRO_COPY_AST_LIST(doc->faults); - break; - case CONTRACT_PARAM: - case CONTRACT_PURE: - case CONTRACT_UNKNOWN: - case CONTRACT_COMMENT: - break; - } -} static void copy_expr_asm_arg(CopyStruct *c, ExprAsmArg *arg) { @@ -683,19 +675,6 @@ RETRY: case AST_DECLS_STMT: MACRO_COPY_DECL_LIST(ast->decls_stmt); break; - case AST_CONTRACT_FAULT: - if (ast->contract_fault.resolved) - { - fixup_decl(c, &ast->contract_fault.decl); - } - else - { - MACRO_COPY_EXPR(ast->contract_fault.expr); - } - break; - case AST_CONTRACT: - doc_ast_copy(c, &source->contract_stmt); - break; case AST_ASM_BLOCK_STMT: if (ast->asm_block_stmt.is_string) { @@ -1079,6 +1058,18 @@ Decl *copy_decl(CopyStruct *c, Decl *decl) case DECL_GENERIC: case DECL_GENERIC_INSTANCE: UNREACHABLE; + case DECL_CONTRACT: + MACRO_COPY_EXPR_LIST(copy->contracts_decl.requires); + MACRO_COPY_EXPR_LIST(copy->contracts_decl.ensures); + if (copy->resolve_status == RESOLVE_DONE) + { + MACRO_COPY_DECL_LIST(copy->contracts_decl.opt_returns_resolved); + } + else + { + MACRO_COPY_EXPR_LIST(copy->contracts_decl.opt_returns); + } + break; case DECL_INTERFACE: copy_decl_type(copy); MACRO_COPY_TYPE_LIST(copy->interfaces); @@ -1139,7 +1130,7 @@ Decl *copy_decl(CopyStruct *c, Decl *decl) case DECL_FUNC: copy_decl_type(copy); MACRO_COPY_TYPEID(copy->func_decl.type_parent); - MACRO_COPY_ASTID(copy->func_decl.docs); + MACRO_COPY_DECLID(copy->func_decl.docs); copy_signature_deep(c, ©->func_decl.signature); MACRO_COPY_ASTID(copy->func_decl.body); break; @@ -1204,7 +1195,7 @@ Decl *copy_decl(CopyStruct *c, Decl *decl) case DECL_ALIAS_PATH: break; case DECL_MACRO: - MACRO_COPY_ASTID(copy->func_decl.docs); + MACRO_COPY_DECLID(copy->func_decl.docs); MACRO_COPY_TYPEID(decl->func_decl.type_parent); copy_signature_deep(c, ©->func_decl.signature); MACRO_COPY_ASTID(decl->func_decl.body); diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 97f32d8c2..6d7c2a4fd 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -553,8 +553,6 @@ typedef enum AST_CASE_STMT, AST_COMPOUND_STMT, AST_CONTINUE_STMT, - AST_CONTRACT, - AST_CONTRACT_FAULT, AST_CT_ASSERT, AST_CT_COMPOUND_STMT, AST_CT_ECHO_STMT, @@ -973,17 +971,6 @@ typedef enum CONST_MEMBER, } ConstKind; -typedef enum -{ - CONTRACT_UNKNOWN, - CONTRACT_COMMENT, - CONTRACT_PURE, - CONTRACT_REQUIRE, - CONTRACT_PARAM, - CONTRACT_OPTIONALS, - CONTRACT_ENSURE, -} ContractKind; - typedef enum { CONV_NO = -1, @@ -1034,6 +1021,7 @@ typedef enum DECL_CT_EXEC, DECL_CT_INCLUDE, DECL_DECLARRAY, + DECL_CONTRACT, DECL_ALIAS, DECL_ALIAS_PATH, DECL_TYPEDEF, @@ -1117,6 +1105,7 @@ typedef enum EXPR_COMPOUND_LITERAL, EXPR_COND, EXPR_CONST, + EXPR_CONTRACT, EXPR_CT_ARG, EXPR_CT_ASSIGNABLE, EXPR_CT_CALL, @@ -2141,7 +2130,7 @@ typedef enum case DECL_DECLARRAY: case DECL_ATTRIBUTE: case DECL_LABEL: \ case DECL_ALIAS: case DECL_CT_ASSERT: case DECL_CT_EXEC: case DECL_FAULT: \ case DECL_CT_ECHO: case DECL_CT_INCLUDE: case DECL_GROUP: \ - case DECL_BODYPARAM: case DECL_VAR: case DECL_ENUM_CONSTANT: \ + case DECL_BODYPARAM: case DECL_VAR: case DECL_ENUM_CONSTANT: case DECL_CONTRACT: \ case DECL_POISONED: case DECL_ALIAS_PATH: case DECL_GENERIC: case DECL_GENERIC_INSTANCE // -- Expr helper macros @@ -2149,7 +2138,7 @@ typedef enum case EXPR_CT_DEFINED: \ case EXPR_CT_ASSIGNABLE: case EXPR_CT_IS_CONST: \ case EXPR_CT_ARG: case EXPR_TYPEINFO: case EXPR_HASH_IDENT: \ - case EXPR_COMPILER_CONST: case EXPR_CT_CALL: \ + case EXPR_COMPILER_CONST: case EXPR_CT_CALL: case EXPR_CONTRACT: \ case EXPR_SPLAT: case EXPR_STRINGIFY: case EXPR_TYPECALL: \ case EXPR_CT_EVAL: case EXPR_MAYBE_DEREF diff --git a/src/compiler/expr.c b/src/compiler/expr.c index 55f6d347b..8d36b0c71 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -490,6 +490,7 @@ bool expr_is_runtime_const(Expr *expr) case EXPR_ASM: case EXPR_SUBSCRIPT_ASSIGN: case EXPR_NAMED_ARGUMENT: + case EXPR_CONTRACT: UNREACHABLE case EXPR_NOP: return true; @@ -800,6 +801,7 @@ bool expr_is_pure(Expr *expr) switch (expr->expr_kind) { case UNRESOLVED_EXPRS: + case EXPR_CONTRACT: UNREACHABLE case EXPR_BUILTIN: case EXPR_BENCHMARK_HOOK: diff --git a/src/compiler/json_output.c b/src/compiler/json_output.c index 9565e8124..095e93376 100644 --- a/src/compiler/json_output.c +++ b/src/compiler/json_output.c @@ -17,17 +17,18 @@ #define FOREACH_DECL_END } } } #define INSERT_COMMA do { if (first) { first = false; } else { fputs(",\n", file); } } while(0) -static bool emit_docs(FILE *file, AstId contracts, int tabs) +static bool emit_docs(FILE *file, DeclId contracts, int tabs) { if (!contracts) return false; - Ast *ast = astptr(contracts); - if (ast->contract_stmt.kind != CONTRACT_COMMENT) return false; + return false; + /* + if (!contract->contracts_decl.comment) return false; for (int i = 0; i < tabs; i++) PRINT("\t"); PRINT("\"comment\": \""); bool last_is_whitespace = true; - for (size_t i = 0; i < ast->contract_stmt.strlen; i++) + for (size_t i = 0; i < contract->contracts_decl.comment_len; i++) { - unsigned char c = ast->contract_stmt.string[i]; + unsigned char c = contract->contracts_decl.comment[i]; if (char_is_whitespace(c) || c < 31) { if (last_is_whitespace) continue; @@ -56,7 +57,7 @@ static bool emit_docs(FILE *file, AstId contracts, int tabs) } } PRINT("\""); - return true; + return true;*/ } static inline void emit_modules(FILE *file) { @@ -104,6 +105,7 @@ static inline const char *decl_type_to_string(Decl *type) case DECL_VAR: case DECL_GENERIC: case DECL_GENERIC_INSTANCE: + case DECL_CONTRACT: UNREACHABLE } UNREACHABLE diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index 6a8eff01f..a8a3246f2 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -1407,6 +1407,7 @@ LLVMValueRef llvm_get_ref(GenContext *c, Decl *decl) case DECL_INTERFACE: case DECL_GENERIC: case DECL_GENERIC_INSTANCE: + case DECL_CONTRACT: UNREACHABLE; } UNREACHABLE diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index e796e6dd0..9f6662a34 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -1655,10 +1655,8 @@ void llvm_emit_stmt(GenContext *c, Ast *ast) { case AST_POISONED: case AST_FOREACH_STMT: - case AST_CONTRACT: case AST_ASM_STMT: case AST_ASM_LABEL: - case AST_CONTRACT_FAULT: case AST_CASE_STMT: case AST_DEFAULT_STMT: case CT_AST: diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 4093077f9..daa45fcd4 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -14,7 +14,7 @@ typedef enum FunctionParse_ } FunctionParse; static inline Decl *parse_enum_declaration(ParseContext *c); -static inline Decl *parse_func_definition(ParseContext *c, AstId contracts, FunctionParse parse_kind); +static inline Decl *parse_func_definition(ParseContext *c, ContractDescription *contracts, FunctionParse parse_kind); static inline bool parse_bitstruct_body(ParseContext *c, Decl *decl); static inline bool parse_enum_param_list(ParseContext *c, Decl*** parameters_ref, ArrayIndex *inline_index); static Decl *parse_ct_include(ParseContext *c); @@ -22,7 +22,7 @@ static Decl *parse_exec(ParseContext *c); 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 bool parse_contracts(ParseContext *c, ContractDescription *contracts_ref); INLINE Decl *decl_new_var_current(ParseContext *c, TypeInfo *type, VarDeclKind kind) { @@ -232,28 +232,23 @@ NEXT:; decl->generic_decl.id = generic_id++; } -bool parse_attach_contracts(Decl *generics, AstId contracts) +bool parse_attach_contracts(Decl *generics, ContractDescription *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."); + if (contracts->has_contracts) + { + print_error_at(contracts->first_contract, "Contracts can only be used with '@generic' declarations and modules."); + return false; + } return true; } - Ast *first_invalid_contract = ast_contract_has_any_non_require(contracts); - if (first_invalid_contract) + if (contracts->first_non_require.a) { - 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; + print_error_at(contracts->first_non_require, "Invalid constraint - only '@require' is valid for '@generic' declarations and modules."); + return false; } + FOREACH(Expr *, e, contracts->requires) vec_add(generics->generic_decl.requires, e); return true; } void parse_attach_generics(ParseContext *c, Decl *generic_decl) @@ -266,7 +261,7 @@ void parse_attach_generics(ParseContext *c, Decl *generic_decl) /** * module ::= MODULE module_path ('{' module_params '}')? (@public|@private|@local|@test|@export|@cname) EOS */ -bool parse_module(ParseContext *c, AstId contracts) +bool parse_module(ParseContext *c, ContractDescription *contracts) { if (tok_is(c, TOKEN_STRING)) { @@ -1912,22 +1907,19 @@ static inline bool parse_fn_parameter_list(ParseContext *c, Signature *signature // --- Parse types -static bool parse_element_contract(ParseContext *c, const char *error) +static bool parse_element_contract(ParseContext *c, ContractDescription *contracts, 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; + if (!parse_contracts(c, contracts)) return false; + if (!contracts->has_contracts) return true; + print_error_at(contracts->first_contract, "No constraints are allowed on %s.", error); + return false; } + +INLINE void attach_deprecation_from_contract(ParseContext *c, ContractDescription *contract, Decl *decl) +{ + if (contract->deprecated) vec_add(decl->attributes, contract->deprecated); +} + /** * Expect pointer to after '{' * @@ -1953,7 +1945,8 @@ 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); + ContractDescription contracts = EMPTY_CONTRACT; + if (!parse_element_contract(c, &contracts, "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) { @@ -1996,6 +1989,7 @@ static bool parse_struct_body(ParseContext *c, Decl *parent) member->is_cond = true; if (!parse_struct_body(c, member)) return decl_poison(parent); } + attach_deprecation_from_contract(c, &contracts, member); vec_add(parent->strukt.members, member); index++; if (index > MAX_MEMBERS) @@ -2158,10 +2152,11 @@ static inline bool parse_bitstruct_body(ParseContext *c, Decl *decl) bool is_consecutive = false; while (!try_consume(c, TOKEN_RBRACE)) { - if (!parse_element_contract(c, "bitstruct members")) return decl_poison(decl); + ContractDescription contracts = EMPTY_CONTRACT; + if (!parse_element_contract(c, &contracts, "bitstruct members")) return decl_poison(decl); ASSIGN_TYPE_OR_RET(TypeInfo *type, parse_base_type(c), false); Decl *member_decl = decl_new_var_current(c, type, VARDECL_BITMEMBER); - + attach_deprecation_from_contract(c, &contracts, member_decl); if (!try_consume(c, TOKEN_IDENT)) { if (try_consume(c, TOKEN_CONST_IDENT) || try_consume(c, TOKEN_TYPE_IDENT)) @@ -2223,13 +2218,13 @@ INLINE bool parse_interface_body(ParseContext *c, Decl *interface) Decl **fns = NULL; while (!try_consume(c, TOKEN_RBRACE)) { - AstId contracts = 0; - if (!parse_contracts(c, &contracts)) return poisoned_decl; + ContractDescription contracts_desc = EMPTY_CONTRACT; + if (!parse_contracts(c, &contracts_desc)) return poisoned_decl; if (!tok_is(c, TOKEN_FN)) { RETURN_PRINT_ERROR_HERE("Interfaces can only have function declarations, and they must start with 'fn' as usual."); } - ASSIGN_DECL_OR_RET(Decl *interface_fn, parse_func_definition(c, contracts, FUNC_PARSE_INTERFACE), false); + ASSIGN_DECL_OR_RET(Decl *interface_fn, parse_func_definition(c, &contracts_desc, FUNC_PARSE_INTERFACE), false); vec_add(fns, interface_fn); } interface->interface_methods = fns; @@ -2341,6 +2336,17 @@ static inline void decl_add_type(Decl *decl, TypeKind kind) decl->type = type; } +static DeclId decl_from_contract_description(ContractDescription *description) +{ + if (!description->has_contracts) return 0; + Decl *decl = decl_new(DECL_CONTRACT, "contract", description->first); + decl->contracts_decl.ensures = description->ensures; + decl->contracts_decl.requires = description->requires; + decl->contracts_decl.pure = description->pure; + decl->contracts_decl.params = description->params; + decl->contracts_decl.opt_returns = description->opt_returns; + return declid(decl); +} /** * typedef_declaration ::= ALIAS TYPE_IDENT attributes? '=' typedef_type ';' @@ -2348,7 +2354,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) +static inline Decl *parse_alias_type(ParseContext *c, ContractDescription *contracts) { advance_and_verify(c, TOKEN_ALIAS); @@ -2385,12 +2391,13 @@ static inline Decl *parse_alias_type(ParseContext *c, AstId contracts) decl->type_alias_decl.decl = decl_type; ASSIGN_TYPE_OR_RET(TypeInfo *type_info, parse_optional_type(c), poisoned_decl); decl_type->fntype_decl.signature.rtype = type_infoid(type_info); - decl_type->fntype_decl.docs = contracts; + decl_type->fntype_decl.docs = decl_from_contract_description(contracts); if (!parse_fn_parameter_list(c, &(decl_type->fntype_decl.signature))) { return poisoned_decl; } if (!parse_attributes(c, &decl_type->attributes, NULL, NULL, NULL)) return poisoned_decl; + attach_deprecation_from_contract(c, contracts, decl_type); RANGE_EXTEND_PREV(decl_type); RANGE_EXTEND_PREV(decl); CONSUME_EOS_OR_RET(poisoned_decl); @@ -2482,7 +2489,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, AstId contracts) +static inline Decl *parse_alias_ident(ParseContext *c, ContractDescription *contracts) { // 1. Store the beginning of the "alias". advance_and_verify(c, TOKEN_ALIAS); @@ -2586,7 +2593,7 @@ static inline Decl *parse_attrdef(ParseContext *c) /** * define_decl ::= ALIAS define_type_body */ -static inline Decl *parse_alias(ParseContext *c, AstId contracts) +static inline Decl *parse_alias(ParseContext *c, ContractDescription *contracts) { switch (peek(c)) { @@ -2692,16 +2699,17 @@ static inline bool parse_func_macro_header(ParseContext *c, Decl *decl) * macro ::= MACRO macro_header '(' macro_params ')' opt_attributes macro_body * macro_body ::= IMPLIES expression ';' | compound_statement */ -static inline Decl *parse_macro_declaration(ParseContext *c, AstId docs) +static inline Decl *parse_macro_declaration(ParseContext *c, ContractDescription *contracts) { advance_and_verify(c, TOKEN_MACRO); Decl *decl = decl_calloc(); decl->decl_kind = DECL_MACRO; - decl->func_decl.docs = docs; + decl->func_decl.docs = decl_from_contract_description(contracts); if (!parse_func_macro_header(c, decl)) return poisoned_decl; if (!parse_macro_params(c, decl)) return poisoned_decl; if (!parse_attributes_for_global(c, decl)) return poisoned_decl; + attach_deprecation_from_contract(c, contracts, decl); if (tok_is(c, TOKEN_IMPLIES)) { ASSIGN_ASTID_OR_RET(decl->func_decl.body, @@ -2714,10 +2722,12 @@ 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; + ContractDescription contracts = EMPTY_CONTRACT; + if (!parse_element_contract(c, &contracts, "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; + attach_deprecation_from_contract(c, &contracts, decl); return decl; } @@ -2796,7 +2806,8 @@ static bool parse_enum_values(ParseContext *c, Decl*** values_ref, Visibility vi bool deprecate_warn = true; while (!try_consume(c, TOKEN_RBRACE)) { - if (!parse_element_contract(c, "enum values")) return false; + ContractDescription contracts = EMPTY_CONTRACT; + if (!parse_element_contract(c, &contracts, "enum values")) return false; Decl *enum_const = decl_new(DECL_ENUM_CONSTANT, symstr(c), c->span); if (is_constdef) enum_const->enum_constant.is_raw = is_constdef; enum_const->visibility = visibility; @@ -2812,6 +2823,7 @@ static bool parse_enum_values(ParseContext *c, Decl*** values_ref, Visibility vi } } if (!parse_attributes_for_global(c, enum_const)) return false; + attach_deprecation_from_contract(c, &contracts, enum_const); if (try_consume(c, TOKEN_EQ)) { Expr **args = NULL; @@ -2989,12 +3001,12 @@ static inline Decl *parse_enum_declaration(ParseContext *c) * func_body ::= ('=>' short_body) | compound_stmt * */ -static inline Decl *parse_func_definition(ParseContext *c, AstId contracts, FunctionParse parse_kind) +static inline Decl *parse_func_definition(ParseContext *c, ContractDescription *contracts, FunctionParse parse_kind) { advance_and_verify(c, TOKEN_FN); Decl *func = decl_calloc(); func->decl_kind = DECL_FUNC; - func->func_decl.docs = contracts; + func->func_decl.docs = decl_from_contract_description(contracts); func->func_decl.attr_interface_method = parse_kind == FUNC_PARSE_INTERFACE; if (!parse_func_macro_header(c, func)) return poisoned_decl; if (func->name[0] == '@') @@ -3003,6 +3015,7 @@ static inline Decl *parse_func_definition(ParseContext *c, AstId contracts, Func } if (!parse_fn_parameter_list(c, &(func->func_decl.signature))) return poisoned_decl; if (!parse_attributes_for_global(c, func)) return poisoned_decl; + attach_deprecation_from_contract(c, contracts, func); if (parse_kind != FUNC_PARSE_REGULAR) { if (tok_is(c, TOKEN_LBRACE) || tok_is(c, TOKEN_IMPLIES)) @@ -3117,17 +3130,6 @@ static inline bool parse_import(ParseContext *c) return true; } - -INLINE void append_docs(AstId **next, AstId *first, Ast *new_doc) -{ - if (!*first) - { - *first = astid(new_doc); - } - **next = astid(new_doc); - *next = &new_doc->next; -} - INLINE bool parse_doc_to_eol(ParseContext *c) { if (try_consume(c, TOKEN_DOCS_EOL)) return true; @@ -3181,27 +3183,20 @@ static bool parse_doc_direct_comment(ParseContext *c) /** * contract ::= expression_list (':'? STRING)? */ -static inline bool parse_doc_contract(ParseContext *c, AstId *docs, AstId **docs_next, ContractKind kind) +static inline bool parse_doc_contract(ParseContext *c, Expr ***list_ref, const char *prefix) { - Ast *ast = ast_new_curr(c, AST_CONTRACT); - ast->contract_stmt.kind = kind; + Expr *expr = expr_new(EXPR_CONTRACT, c->span); const char *start = c->lexer.data.lex_start; advance(c); - ASSIGN_EXPR_OR_RET(ast->contract_stmt.contract.decl_exprs, parse_expression_list(c, false), false); + ASSIGN_EXPR_OR_RET(expr->contract_expr.decl_exprs, parse_expression_list(c, false), false); + RANGE_EXTEND_PREV(expr); const char *end = start + 1; while (end[0] != '\n' && end[0] != '\0') end++; if (end > c->data.lex_start) end = c->data.lex_start; while (is_space(end[-1])) end--; scratch_buffer_clear(); - switch (kind) - { - case CONTRACT_ENSURE: - scratch_buffer_append("@ensure \""); - break; - default: - scratch_buffer_append("@require \""); - break; - } + scratch_buffer_append(prefix); + scratch_buffer_append(" \""); scratch_buffer_append_remove_space(start, (int)(end - start)); scratch_buffer_append("\" violated"); bool docs_to_comment = false; @@ -3219,18 +3214,18 @@ static inline bool parse_doc_contract(ParseContext *c, AstId *docs, AstId **docs scratch_buffer_append(": '"); if (!parse_joined_strings(c, NULL, NULL)) return false; scratch_buffer_append("'."); - ast->contract_stmt.contract.comment = scratch_buffer_copy(); + expr->contract_expr.comment = scratch_buffer_copy(); if (!docs_to_comment) { - SEMA_DEPRECATED(ast, "Not using ':' before the description is deprecated"); + SEMA_DEPRECATED(expr, "Not using ':' before the description is deprecated"); } } else { scratch_buffer_append("."); - ast->contract_stmt.contract.expr_string = scratch_buffer_copy(); + expr->contract_expr.expr_string = scratch_buffer_copy(); } - append_docs(docs_next, docs, ast); + vec_add(*list_ref, expr); return true; } @@ -3238,15 +3233,14 @@ static inline bool parse_doc_contract(ParseContext *c, AstId *docs, AstId **docs * param_contract ::= '@param' inout_attribute? any_identifier ( ':' STRING )? * inout_attribute ::= '[' '&'? ('in' | 'inout' | 'out') ']' */ -static inline bool parse_contract_param(ParseContext *c, AstId *docs, AstId **docs_next) +static inline bool parse_contract_param(ParseContext *c, ContractParam **list_ref) { - Ast *ast = ast_new_curr(c, AST_CONTRACT); - ast->contract_stmt.kind = CONTRACT_PARAM; advance(c); // [inout] [in] [out] bool is_ref = false; InOutModifier mod = INOUT_ANY; + SourceSpan span = c->span; if (try_consume(c, TOKEN_LBRACKET)) { is_ref = try_consume(c, TOKEN_AMP); @@ -3271,16 +3265,17 @@ static inline bool parse_contract_param(ParseContext *c, AstId *docs, AstId **do CONSUME_OR_RET(TOKEN_RBRACKET, false); } + ContractParam param = { .span = span }; switch (c->tok) { case TOKEN_IDENT: case TOKEN_CT_IDENT: case TOKEN_CT_TYPE_IDENT: case TOKEN_HASH_IDENT: - ast->contract_stmt.param.name = symstr(c); + param.name = symstr(c); break; case TOKEN_ELLIPSIS: - ast->contract_stmt.param.name = NULL; + param.name = NULL; break; case TOKEN_TYPE_IDENT: case TOKEN_CT_CONST_IDENT: @@ -3289,11 +3284,11 @@ static inline bool parse_contract_param(ParseContext *c, AstId *docs, AstId **do default: RETURN_PRINT_ERROR_HERE("Expected a parameter name here."); } - ast->contract_stmt.param.modifier = mod; - ast->contract_stmt.param.span = c->span; - ast->contract_stmt.param.by_ref = is_ref; - advance(c); + param.modifier = mod; + param.by_ref = is_ref; + advance(c); + RANGE_EXTEND_PREV(¶m); if (parse_docs_to_comment(c)) { if (!parse_doc_check_skip_string_eos(c)) @@ -3321,55 +3316,40 @@ static inline bool parse_contract_param(ParseContext *c, AstId *docs, AstId **do } else { - RANGE_EXTEND_PREV(ast); - SEMA_DEPRECATED(ast, "Not using ':' before the string is deprecated."); + SEMA_DEPRECATED(¶m, "Not using ':' before the string is deprecated."); } } - append_docs(docs_next, docs, ast); + vec_add(*list_ref, param); return true; } -static inline bool parse_doc_optreturn(ParseContext *c, AstId *docs, AstId **docs_next) +static inline bool parse_doc_optreturn(ParseContext *c, Expr ***opt_return_ref) { - Ast **returns = NULL; - Ast *ast = ast_new_curr(c, AST_CONTRACT); - ast->span = c->prev_span; advance_and_verify(c, TOKEN_QUESTION); - ast->contract_stmt.kind = CONTRACT_OPTIONALS; while (1) { - Ast *ret = ast_new_curr(c, AST_CONTRACT_FAULT); - ASSIGN_EXPR_OR_RET(ret->contract_fault.expr, parse_expr(c), false); - vec_add(returns, ret); + ASSIGN_EXPR_OR_RET(Expr *expr, parse_expr(c), false); + vec_add(*opt_return_ref, expr); if (!try_consume(c, TOKEN_COMMA)) break; } - RANGE_EXTEND_PREV(ast); // Just ignore our potential string: if (!parse_doc_discarded_comment(c)) return false; - ast->contract_stmt.faults = returns; - append_docs(docs_next, docs, ast); return true; } -static bool parse_contracts(ParseContext *c, AstId *contracts_ref) +static bool parse_contracts(ParseContext *c, ContractDescription *contracts_ref) { - *contracts_ref = 0; if (!tok_is(c, TOKEN_DOCS_START)) return true; - AstId **next = &contracts_ref; if (c->data.strlen > 0) { - Ast *ast = ast_new_curr(c, AST_CONTRACT); - ast->contract_stmt.kind = CONTRACT_COMMENT; - ast->contract_stmt.string = symstr(c); - ast->contract_stmt.strlen = c->data.strlen; - ast->span = c->span; - append_docs(next, contracts_ref, ast); + contracts_ref->comment = symstr(c); + contracts_ref->comment_span = c->span; } - + contracts_ref->first = c->span; advance_and_verify(c, TOKEN_DOCS_START); - + bool return_comment = false; while (!try_consume(c, TOKEN_DOCS_END)) { if (try_consume(c, TOKEN_DOCS_EOL)) continue; @@ -3377,44 +3357,103 @@ static bool parse_contracts(ParseContext *c, AstId *contracts_ref) { RETURN_PRINT_ERROR_HERE("Expected a directive starting with '@' here, like '@param' or `@require`"); } + if (!contracts_ref->first.a) contracts_ref->first = c->span; const char *name = symstr(c); + if (name == kw_at_require) + { + if (!contracts_ref->has_contracts) + { + contracts_ref->first_contract = c->span; + contracts_ref->has_contracts = true; + } + if (!parse_doc_contract(c, &contracts_ref->requires, "@require")) return false; + goto END; + } + if (contracts_ref->first_non_require.a == 0) + { + contracts_ref->first_non_require = c->span; + } if (name == kw_at_param) { - if (!parse_contract_param(c, contracts_ref, next)) return false; + if (!contracts_ref->has_contracts) + { + contracts_ref->first_contract = c->span; + contracts_ref->has_contracts = true; + } + if (!parse_contract_param(c, &contracts_ref->params)) return false; } else if (name == kw_at_return) { advance(c); if (tok_is(c, TOKEN_QUESTION)) { - if (!parse_doc_optreturn(c, contracts_ref, next)) return false; + if (!contracts_ref->has_contracts) + { + contracts_ref->first_contract = c->span; + contracts_ref->has_contracts = true; + } + if (!parse_doc_optreturn(c, &contracts_ref->opt_returns)) return false; } else { + if (return_comment) + { + RETURN_PRINT_ERROR_HERE("Only one `@return` directive is allowed per contract."); + } + return_comment = true; if (!parse_doc_direct_comment(c)) return false; } } else if (name == kw_at_deprecated) { advance(c); - if (!parse_doc_direct_comment(c)) return false; - REMINDER("Implement @deprecated tracking"); - } - else if (name == kw_at_require) - { - if (!parse_doc_contract(c, contracts_ref, next, CONTRACT_REQUIRE)) return false; + if (contracts_ref->deprecated) + { + RETURN_PRINT_ERROR_HERE("Only one `@deprecated` directive is allowed per contract."); + } + Attr *attr = CALLOCS(Attr); + attr->name = kw_at_deprecated; + attr->span = c->prev_span; + attr->path = NULL; + attr->attr_kind = ATTRIBUTE_DEPRECATED; + if (tok_is(c, TOKEN_DOCS_EOL) && peek(c) == TOKEN_STRING) + { + advance(c); + } + SourceSpan start = c->span; + if (tok_is(c, TOKEN_STRING)) + { + const char *str = NULL; + size_t len; + if (!parse_joined_strings(c, &str, &len)) return false; + Expr *e = expr_new_const_string(extend_span_with_token(start, c->prev_span), str); + vec_add(attr->exprs, e); + } + contracts_ref->deprecated = attr; } else if (name == kw_at_ensure) { - if (!parse_doc_contract(c, contracts_ref, next, CONTRACT_ENSURE)) return false; + if (!contracts_ref->has_contracts) + { + contracts_ref->first_contract = c->span; + contracts_ref->has_contracts = true; + } + if (!parse_doc_contract(c, &contracts_ref->ensures, "@ensure")) return false; } else if (name == kw_at_pure) { - Ast *ast = ast_new_curr(c, AST_CONTRACT); - ast->contract_stmt.kind = CONTRACT_PURE; + if (contracts_ref->pure) + { + RETURN_PRINT_ERROR_HERE("Multiple '@pure' declarations, please remove one."); + } + if (!contracts_ref->has_contracts) + { + contracts_ref->first_contract = c->span; + contracts_ref->has_contracts = true; + } + contracts_ref->pure = true; advance(c); if (!parse_doc_direct_comment(c)) return false; - append_docs(next, contracts_ref, ast); } else { @@ -3423,6 +3462,7 @@ static bool parse_contracts(ParseContext *c, AstId *contracts_ref) if (parse_doc_to_eol(c)) continue; RETURN_PRINT_ERROR_HERE("Expected a string description for the custom contract '%s'.", name); } +END: if (parse_doc_to_eol(c)) continue; PRINT_ERROR_HERE("Expected the end of the contract here."); return false; @@ -3482,7 +3522,7 @@ END: */ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) { - AstId contracts = 0; + ContractDescription contracts = { .first = c->span }; if (!parse_contracts(c, &contracts)) return poisoned_decl; Decl *decl; @@ -3503,7 +3543,7 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) switch (tok) { case TOKEN_FN: - decl = parse_func_definition(c, contracts, FUNC_PARSE_EXTERN); + decl = parse_func_definition(c, &contracts, FUNC_PARSE_EXTERN); break; case TOKEN_CONST: decl = parse_top_level_const_declaration(c, true); @@ -3545,13 +3585,13 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) new_context->unit = unit_create(c->unit->file); *context_out = c = new_context; } - if (!parse_module(c, contracts)) return poisoned_decl; + if (!parse_module(c, &contracts)) return poisoned_decl; return NULL; case TOKEN_DOCS_START: 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); + decl = parse_alias(c, &contracts); if (decl->decl_kind == DECL_ALIAS_PATH) { if (!context_out) @@ -3568,11 +3608,11 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) attach_contracts = true; break; case TOKEN_FN: - decl = parse_func_definition(c, contracts, c->unit->is_interface_file ? FUNC_PARSE_C3I : FUNC_PARSE_REGULAR); + decl = parse_func_definition(c, &contracts, c->unit->is_interface_file ? FUNC_PARSE_C3I : FUNC_PARSE_REGULAR); break; case TOKEN_CT_ASSERT: { - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (contracts.has_contracts) goto CONTRACT_NOT_ALLOWED; ASSIGN_AST_OR_RET(Ast *ast, parse_ct_assert_stmt(c), poisoned_decl); decl = decl_new_ct(DECL_CT_ASSERT, ast->span); decl->ct_assert_decl = ast; @@ -3580,7 +3620,7 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) } case TOKEN_CT_ERROR: { - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (contracts.has_contracts) goto CONTRACT_NOT_ALLOWED; ASSIGN_AST_OR_RET(Ast *ast, parse_ct_error_stmt(c), poisoned_decl); decl = decl_new_ct(DECL_CT_ASSERT, ast->span); decl->ct_assert_decl = ast; @@ -3588,14 +3628,14 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) } case TOKEN_CT_ECHO: { - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (contracts.has_contracts) goto CONTRACT_NOT_ALLOWED; ASSIGN_AST_OR_RET(Ast *ast, parse_ct_echo_stmt(c), poisoned_decl); decl = decl_new_ct(DECL_CT_ECHO, ast->span); decl->ct_echo_decl = ast; break; } case TOKEN_IMPORT: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (contracts.has_contracts) goto CONTRACT_NOT_ALLOWED; if (!context_out) { PRINT_ERROR_HERE("'import' may not appear inside a compile time statement."); @@ -3604,11 +3644,11 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) if (!parse_import(c)) return poisoned_decl; return NULL; case TOKEN_CT_INCLUDE: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (contracts.has_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_ct_include(c); break; case TOKEN_CT_EXEC: - if (contracts) goto CONTRACT_NOT_ALLOWED; + if (contracts.has_contracts) goto CONTRACT_NOT_ALLOWED; decl = parse_exec(c); break; case TOKEN_BITSTRUCT: @@ -3633,7 +3673,7 @@ Decl *parse_top_level_statement(ParseContext *c, ParseContext **context_out) attach_contracts = true; break; case TOKEN_MACRO: - decl = parse_macro_declaration(c, contracts); + decl = parse_macro_declaration(c, &contracts); break; case TOKEN_ENUM: case TOKEN_CONSTDEF: @@ -3677,10 +3717,12 @@ 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; + attach_deprecation_from_contract(c, &contracts, decl); + if (attach_contracts && contracts.has_contracts && !parse_attach_contracts(decl_template_get_generic(decl), &contracts)) return poisoned_decl; ASSERT(decl); return decl; CONTRACT_NOT_ALLOWED: - RETURN_PRINT_ERROR_AT(poisoned_decl, astptr(contracts), "Contracts are only used for modules, functions and macros."); + print_error_at(contracts.first, "Contracts are only used for modules, functions and macros."); + return poisoned_decl; } diff --git a/src/compiler/parser_internal.h b/src/compiler/parser_internal.h index a890490d3..9418cb9a2 100644 --- a/src/compiler/parser_internal.h +++ b/src/compiler/parser_internal.h @@ -15,6 +15,24 @@ typedef enum PARAM_PARSE_ATTR, } ParameterParseKind; +typedef struct +{ + const char *comment; + SourceSpan comment_span; + unsigned comment_len; + Expr **requires; + Expr **ensures; + ContractParam *params; + bool pure; + bool has_contracts; + SourceSpan first; + SourceSpan first_non_require; + SourceSpan first_contract; + Expr **opt_returns; + Attr *deprecated; +} ContractDescription; + +#define EMPTY_CONTRACT ((ContractDescription){ NULL }) #define EXPECT_IDENT_FOR_OR(_name, _res) do { if (!expect_ident(c, _name)) return _res; } while(0) #define EXPECT_OR_RET(_tok, _res) do { if (!expect(c, _tok)) return _res; } while(0) #define CONSUME_OR_RET(_tok, _res) do { if (!expect(c, _tok)) return _res; advance(c); } while(0) @@ -74,7 +92,7 @@ INLINE void add_decl_to_list(Decl ***list, Decl *decl) vec_add(*list, decl); } -bool parse_module(ParseContext *c, AstId contracts); +bool parse_module(ParseContext *c, ContractDescription *contracts); bool try_consume(ParseContext *c, TokenType type); bool consume(ParseContext *c, TokenType type, const char *message, ...); diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 1fe107656..0ad8cd717 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -31,7 +31,7 @@ static inline bool sema_analyse_struct_member(SemaContext *context, Decl *parent static inline bool sema_check_struct_holes(SemaContext *context, Decl *decl, Decl *member); static inline bool sema_analyse_bitstruct_member(SemaContext *context, Decl *parent, Decl *member, unsigned index, bool allow_overlap, bool *erase_decl); -static inline bool sema_analyse_doc_header(SemaContext *context, AstId doc, Decl **params, Decl **extra_params, bool *pure_ref, bool is_raw_vaarg); +static inline bool sema_analyse_doc_header(SemaContext *context, DeclId doc, Decl **params, Decl **extra_params, bool *pure_ref, bool is_raw_vaarg); static const char *attribute_domain_to_string(AttributeDomain domain); static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_data, Decl *decl, Attr *attr, AttributeDomain domain, bool *erase_decl); @@ -721,6 +721,7 @@ static bool sema_analyse_struct_union(SemaContext *context, Decl *decl, bool *er bool is_union = decl->decl_kind == DECL_UNION; AttributeDomain domain = is_union ? ATTR_UNION : ATTR_STRUCT; if (!sema_analyse_attributes(context, decl, decl->attributes, domain, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; // If an @if attribute erases it, end here if (*erase_decl) return true; @@ -949,6 +950,8 @@ static bool sema_analyse_interface(SemaContext *context, Decl *decl, bool *erase // Begin with analysing attributes. if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_INTERFACE, erase_decl)) return false; + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + // If erased using @if, we exit. if (*erase_decl) return true; @@ -1093,7 +1096,11 @@ RETRY: static bool sema_analyse_bitstruct(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_BITSTRUCT, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + if (!sema_resolve_implemented_interfaces(context, decl, false)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + if (*erase_decl) return true; DEBUG_LOG("Beginning analysis of %s.", decl->name ? decl->name : ".anon"); if (!sema_resolve_type_info(context, decl->strukt.container_type, RESOLVE_TYPE_DEFAULT)) return false; @@ -1502,6 +1509,8 @@ bool sema_analyse_function_signature(SemaContext *context, Decl *func_decl, Type static inline bool sema_analyse_fntype(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_FNTYPE, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + if (*erase_decl) return true; Signature *sig = &decl->fntype_decl.signature; if (!sema_analyse_function_signature(context, decl, NULL, sig->abi, sig)) return false; @@ -1514,6 +1523,8 @@ static inline bool sema_analyse_fntype(SemaContext *context, Decl *decl, bool *e static inline bool sema_analyse_type_alias(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_ALIAS, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + if (*erase_decl) return true; bool is_export = decl->is_export; @@ -1559,6 +1570,7 @@ static inline bool sema_analyse_typedef(SemaContext *context, Decl *decl, bool * { // Check the attributes on the distinct type. if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_TYPEDEF, erase_decl)) return false; + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; // Erase it? if (*erase_decl) return true; @@ -1646,6 +1658,8 @@ static inline void sema_print_enum_to_cenum_error(SemaContext *context, Decl *de static inline bool sema_analyse_enum(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_ENUM, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + if (*erase_decl) return true; if (!sema_resolve_implemented_interfaces(context, decl, false)) return decl_poison(decl); @@ -1815,6 +1829,8 @@ static bool sema_analyse_const_enum_constant_val(SemaContext *context, Decl *dec static inline bool sema_analyse_constdef(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_ENUM, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + if (*erase_decl) return true; if (!sema_resolve_implemented_interfaces(context, decl, false)) return decl_poison(decl); @@ -1909,6 +1925,8 @@ static inline bool sema_analyse_fault(SemaContext *context, Decl *decl, bool *er { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_FAULT, erase_decl)) return decl_poison(decl); if (*erase_decl) return true; + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + decl->type = type_fault; decl->alignment = type_abi_alignment(type_string); return true; @@ -3127,7 +3145,7 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_ [ATTRIBUTE_COMPACT] = ATTR_STRUCT | ATTR_UNION, [ATTRIBUTE_CONST] = ATTR_MACRO, [ATTRIBUTE_CONSTINIT] = ATTR_TYPEDEF | ATTR_ENUM, - [ATTRIBUTE_DEPRECATED] = USER_DEFINED_TYPES | CALLABLE_TYPE | ATTR_CONST | ATTR_GLOBAL | ATTR_MEMBER | ATTR_BITSTRUCT_MEMBER | ATTR_INTERFACE | ATTR_ALIAS, + [ATTRIBUTE_DEPRECATED] = (AttributeDomain)~(ATTR_CALL | ATTR_PARAM), [ATTRIBUTE_DYNAMIC] = ATTR_FUNC, [ATTRIBUTE_EXPORT] = ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES | ATTR_ALIAS, [ATTRIBUTE_EXTERN] = ATTR_FUNC | ATTR_GLOBAL | ATTR_CONST | USER_DEFINED_TYPES, @@ -3802,20 +3820,18 @@ bool sema_analyse_attributes(SemaContext *context, Decl *decl, Attr **attrs, Att return true; } -static bool sema_analyse_optional_returns(SemaContext *context, Ast *directive) +bool sema_analyse_optional_returns(SemaContext *context, Decl *contracts) { - FOREACH(Ast *, ret, directive->contract_stmt.faults) + if (contracts->resolve_status != RESOLVE_NOT_DONE) return true; + contracts->resolve_status = RESOLVE_RUNNING; + + Decl **result = NULL; + FOREACH(Expr *, expr, contracts->contracts_decl.opt_returns) { - if (ret->contract_fault.expanding) continue; - if (ret->contract_fault.resolved) - { - continue; - } - Expr *expr = ret->contract_fault.expr; if (expr->expr_kind == EXPR_RETHROW) { Expr *inner = expr->rethrow_expr.inner; - if (!sema_analyse_expr(context, inner)) return false; + if (!sema_analyse_expr(context, inner)) goto FAIL; Decl *decl; switch (inner->expr_kind) { @@ -3837,73 +3853,75 @@ static bool sema_analyse_optional_returns(SemaContext *context, Ast *directive) } decl = decl_flatten(decl); if (decl->decl_kind != DECL_FNTYPE && decl->decl_kind != DECL_FUNC) goto IS_FAULT; - if (!sema_analyse_decl(context, decl)) return false; - AstId docs = decl->decl_kind == DECL_FNTYPE ? decl->fntype_decl.docs : decl->func_decl.docs; - while (docs) + if (!sema_analyse_decl(context, decl)) goto FAIL; + DeclId contract_id = decl->decl_kind == DECL_FNTYPE ? decl->fntype_decl.docs : decl->func_decl.docs; + if (!contract_id) continue; + Decl *sub_contracts = declptr(contract_id); + if (!sub_contracts->contracts_decl.opt_returns) continue; + switch (sub_contracts->resolve_status) { - Ast *doc = astptr(docs); - docs = doc->next; - if (doc->contract_stmt.kind != CONTRACT_OPTIONALS) continue; - ret->contract_fault.expanding = true; - bool success = sema_analyse_optional_returns(context, doc); - ret->contract_fault.expanding = false; - if (!success) false; + case RESOLVE_DONE: + case RESOLVE_RUNNING: + break; + case RESOLVE_NOT_DONE: + if (!sema_analyse_optional_returns(context, sub_contracts)) goto FAIL; } + FOREACH (Decl *, decl, sub_contracts->contracts_decl.opt_returns_resolved) vec_add(result, decl); continue; } IS_FAULT:; - if (!sema_analyse_expr_rvalue(context, expr)) return false; + if (!sema_analyse_expr_rvalue(context, expr)) goto FAIL; if (expr->type->canonical != type_fault) { - RETURN_SEMA_ERROR(expr, "Expected a fault here."); + SEMA_ERROR(expr, "Expected a fault here."); + goto FAIL; + } + if (!expr_is_const_fault(expr)) + { + SEMA_ERROR(expr, "A constant fault is required."); + goto FAIL; } - if (!expr_is_const_fault(expr)) RETURN_SEMA_ERROR(expr, "A constant fault is required."); Decl *decl = expr->const_expr.fault; - if (!decl) RETURN_SEMA_ERROR(expr, "A non-null fault is required."); - ret->contract_fault.decl = decl; - ret->contract_fault.resolved = true; + if (!decl) + { + SEMA_ERROR(expr, "A non-null fault is required."); + goto FAIL; + } + vec_add(result, decl); } + contracts->resolve_status = RESOLVE_DONE; + contracts->contracts_decl.opt_returns_resolved = result; return true; +FAIL: + contracts->resolve_status = RESOLVE_DONE; + contracts->contracts_decl.opt_returns_resolved = NULL; + return false; } -static inline bool sema_analyse_doc_header(SemaContext *context, AstId doc, +static inline bool sema_analyse_doc_header(SemaContext *context, DeclId docs, Decl **params, Decl **extra_params, bool *pure_ref, bool is_raw_vaarg) { bool va_param_found = false; - while (doc) + if (!docs) return true; + Decl *contracts = declptr(docs); + if (!sema_analyse_optional_returns(context, contracts)) return false; + if (contracts->contracts_decl.pure) *pure_ref = true; + + FOREACH_REF(ContractParam, param_contract, contracts->contracts_decl.params) { - Ast *directive = astptr(doc); - doc = directive->next; - ContractKind directive_kind = directive->contract_stmt.kind; - if (directive_kind == CONTRACT_PURE) - { - if (*pure_ref) - { - SEMA_ERROR(directive, "Multiple '@pure' declarations, please remove one."); - return false; - } - *pure_ref = true; - continue; - } - if (directive_kind == CONTRACT_OPTIONALS) - { - if (!sema_analyse_optional_returns(context, directive)) return false; - } - if (directive_kind != CONTRACT_PARAM) continue; - const char *param_name = directive->contract_stmt.param.name; - if (!param_name) + if (!param_contract->name) { if (va_param_found) { - RETURN_SEMA_ERROR_AT(directive->contract_stmt.param.span, "The '...' @param may not be repeated."); + RETURN_SEMA_ERROR_AT(param_contract->span, "The '...' @param may not be repeated."); } - if (directive->contract_stmt.param.modifier != INOUT_ANY) + if (param_contract->modifier != INOUT_ANY) { - RETURN_SEMA_ERROR_AT(directive->contract_stmt.param.span, "'...' @params may not have any in-out modifiers."); + RETURN_SEMA_ERROR(param_contract, "'...' @params may not have any in-out modifiers."); } if (!is_raw_vaarg) { - RETURN_SEMA_ERROR_AT(directive->contract_stmt.param.span, "'...' @params are only allowed macros and functions with a '...' parameter."); + RETURN_SEMA_ERROR(param_contract, "'...' @params are only allowed macros and functions with a '...' parameter."); } va_param_found = true; continue; @@ -3912,27 +3930,27 @@ static inline bool sema_analyse_doc_header(SemaContext *context, AstId doc, FOREACH(Decl *, other_param, params) { param = other_param; - if (param && param->name == param_name) goto NEXT; + if (param && param->name == param_contract->name) goto NEXT; } FOREACH(Decl *, extra, extra_params) { param = extra; - if (param && param->name == param_name) goto NEXT; + if (param && param->name == param_contract->name) goto NEXT; } - RETURN_SEMA_ERROR(&directive->contract_stmt.param, "There is no parameter '%s', did you misspell it?", param_name); + RETURN_SEMA_ERROR(param_contract, "There is no parameter '%s', did you misspell it?", param_contract->name); NEXT:; Type *type = param->type; if (type) type = type_flatten(type); bool may_be_pointer = !type || type_is_pointer(type) || type_is_any_raw(type); - if (directive->contract_stmt.param.by_ref) + if (param_contract->by_ref) { if (!may_be_pointer) { - RETURN_SEMA_ERROR(directive, "'&' can only be added to pointer type parameters."); + RETURN_SEMA_ERROR(param_contract, "'&' can only be added to pointer type parameters."); } param->var.not_null = true; } - switch (directive->contract_stmt.param.modifier) + switch (param_contract->modifier) { case INOUT_ANY: goto ADDED; @@ -3949,7 +3967,7 @@ static inline bool sema_analyse_doc_header(SemaContext *context, AstId doc, } if (!may_be_pointer && type->type_kind != TYPE_SLICE) { - RETURN_SEMA_ERROR(directive, "'in', 'out' and 'inout' may only be added to pointers and slices."); + RETURN_SEMA_ERROR(param_contract, "'in', 'out' and 'inout' may only be added to pointers and slices."); } ADDED:; } @@ -4532,8 +4550,6 @@ static inline bool sema_check_body_const(SemaContext *context, Ast *body) case AST_BLOCK_EXIT_STMT: case AST_SWITCH_STMT: case AST_NEXTCASE_STMT: - case AST_CONTRACT: - case AST_CONTRACT_FAULT: RETURN_SEMA_ERROR(body, "Only 'return' and compile time statements are allowed in an '@const' macro."); } UNREACHABLE @@ -5181,17 +5197,12 @@ static bool sema_generate_parameter_suffix_to_scratch(Expr **params, bool mangle return true; } -static bool sema_analyse_generic_module_contracts(SemaContext *c, Module *module, Decl *instance, AstId contracts, SourceSpan param_span, SourceSpan invocation_span) +static bool sema_analyse_generic_module_contracts(SemaContext *c, Module *module, Decl *instance, Expr **contracts, SourceSpan param_span, SourceSpan invocation_span) { ASSERT(contracts); - while (contracts) + FOREACH(Expr *, contract, contracts) { - Ast *ast = astptr(contracts); - contracts = ast->next; - ASSERT_SPAN(ast, ast->ast_kind == AST_CONTRACT); SemaContext temp_context; - if (ast->contract_stmt.kind == CONTRACT_COMMENT) continue; - ASSERT_SPAN(ast, ast->contract_stmt.kind == CONTRACT_REQUIRE); InliningSpan *old_span = c->inlined_at; InliningSpan new_span = { .prev = old_span, .span = invocation_span }; SemaContext *new_context = context_transform_for_eval(c, &temp_context, module->units[0]); @@ -5200,21 +5211,21 @@ static bool sema_analyse_generic_module_contracts(SemaContext *c, Module *module new_context->generic_instance = instance; new_context->inlined_at = &new_span; - FOREACH(Expr *, expr, ast->contract_stmt.contract.decl_exprs->expression_list) + FOREACH(Expr *, expr, contract->contract_expr.decl_exprs->expression_list) { CondResult res = sema_check_comp_time_bool(new_context, expr); if (res == COND_MISSING) goto FAIL; if (res == COND_TRUE) continue; - if (ast->contract_stmt.contract.comment) + if (contract->contract_expr.comment) { sema_error_at(c, param_span, "Parameter(s) would violate constraint: %s.", - ast->contract_stmt.contract.comment); + contract->contract_expr.comment); } else { sema_error_at(c, param_span, "Parameter(s) failed validation: %s", - ast->contract_stmt.contract.expr_string); + contract->contract_expr.expr_string); } FAIL: new_context->inlined_at = old_inlined_at; @@ -5305,13 +5316,13 @@ FOUND:; { if (decl->generic_decl.id == generic->generic_decl.id) { - AstId contracts = decl->generic_decl.contracts; - if (!contracts) continue; + Expr **requires = decl->generic_decl.requires; + if (!requires) continue; copy_begin(); - contracts = astid(copy_ast_macro(astptr(contracts))); + Expr **contract = copy_exprlist_macro(requires); copy_end(); SourceSpan param_span = extend_span_with_token(params[0]->span, VECLAST(params)->span); // NOLINT - if (!sema_analyse_generic_module_contracts(context, module, instance, contracts, param_span, invocation_span)) + if (!sema_analyse_generic_module_contracts(context, module, instance, contract, param_span, invocation_span)) { decl_poison(instance); decl_poison(alias); @@ -5510,6 +5521,8 @@ Decl *sema_analyse_parameterized_identifier(SemaContext *context, Path *decl_pat static inline bool sema_analyse_attribute_decl(SemaContext *context, SemaContext *c, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(c, decl, decl->attributes, ATTR_ALIAS, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + if (*erase_decl) return true; Decl **params = decl->attr_decl.params; @@ -5536,6 +5549,8 @@ static inline bool sema_analyse_attribute_decl(SemaContext *context, SemaContext static inline bool sema_analyse_alias(SemaContext *context, Decl *decl, bool *erase_decl) { if (!sema_analyse_attributes(context, decl, decl->attributes, ATTR_ALIAS, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; + if (*erase_decl) return true; Expr *expr = decl->define_decl.alias_expr; @@ -5756,6 +5771,7 @@ bool sema_analyse_decl(SemaContext *context, Decl *decl) case DECL_POISONED: case DECL_GENERIC: case DECL_GENERIC_INSTANCE: + case DECL_CONTRACT: UNREACHABLE } if (erase_decl) diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 46ba61279..644f7e5d5 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -773,6 +773,7 @@ static bool sema_binary_is_expr_lvalue(SemaContext *context, Expr *top_expr, Exp case EXPR_EXPRESSION_LIST: case EXPR_TWO: case EXPR_MAYBE_DEREF: + case EXPR_CONTRACT: goto ERR; } UNREACHABLE @@ -819,6 +820,7 @@ static bool expr_may_ref(Expr *expr) case EXPR_DISCARD: case EXPR_ADDR_CONVERSION: case EXPR_MAKE_SLICE: + case EXPR_CONTRACT: return false; case EXPR_SUBSCRIPT_ASSIGN: return true; @@ -981,6 +983,7 @@ static inline bool sema_cast_ident_rvalue(SemaContext *context, Expr *expr) case DECL_TYPE_ALIAS: case DECL_GENERIC: case DECL_GENERIC_INSTANCE: + case DECL_CONTRACT: UNREACHABLE case DECL_POISONED: return expr_poison(expr); @@ -1199,6 +1202,11 @@ static inline bool sema_expr_analyse_enum_constant(SemaContext *context, Expr *e } return expr_poison(expr), true; } + if (!sema_display_deprecated_warning_on_use(context, decl, expr->span)) return expr_poison(expr), true; + if (enum_constant->resolve_status == RESOLVE_DONE) + { + if (!sema_display_deprecated_warning_on_use(context, enum_constant, expr->span)) return expr_poison(expr), true; + } expr->type = decl->type; if (enum_constant->enum_constant.is_raw) { @@ -2564,28 +2572,10 @@ static inline bool sema_call_check_contract_param_match(SemaContext *context, De return true; } -static inline bool sema_has_require(AstId doc_id) +static inline bool sema_has_require(DeclId doc_id) { if (!doc_id) return false; - Ast *docs = astptr(doc_id); - while (docs) - { - switch (docs->contract_stmt.kind) - { - case CONTRACT_UNKNOWN: - case CONTRACT_COMMENT: - case CONTRACT_PURE: - case CONTRACT_PARAM: - case CONTRACT_OPTIONALS: - case CONTRACT_ENSURE: - docs = astptrzero(docs->next); - continue; - case CONTRACT_REQUIRE: - return true; - } - UNREACHABLE - } - return false; + return declptr(doc_id)->contracts_decl.requires != 0; } static inline bool sema_call_analyse_func_invocation(SemaContext *context, Decl *decl, @@ -2619,17 +2609,17 @@ static inline bool sema_call_analyse_func_invocation(SemaContext *context, Decl *any_val = *(any_val->inner_expr); } expr->call_expr.function_contracts = 0; - AstId docs; + DeclId contract_id; if (decl->decl_kind == DECL_FNTYPE) { - docs = decl->fntype_decl.docs; + contract_id = decl->fntype_decl.docs; } else { - docs = decl->func_decl.docs; + contract_id = decl->func_decl.docs; } - if (!sema_has_require(docs)) goto SKIP_CONTRACTS; + if (!sema_has_require(contract_id)) goto SKIP_CONTRACTS; bool is_safe = safe_mode_enabled(); SemaContext temp_context; bool success = false; @@ -2686,8 +2676,12 @@ static inline bool sema_call_analyse_func_invocation(SemaContext *context, Decl } AstId assert_first = 0; AstId* next = &assert_first; - - if (!sema_analyse_contracts(&temp_context, docs, &next, expr->span, NULL)) return false; + Decl *contracts = declptr(contract_id); + copy_begin(); + Expr **requires = copy_exprlist_macro(contracts->contracts_decl.requires); + Expr **ensures = copy_exprlist_macro(contracts->contracts_decl.ensures); + copy_end(); + if (!sema_analyse_contracts(&temp_context, contracts, requires, ensures, &next, expr->span, NULL)) return false; if (!is_safe) goto SKIP_CONTRACTS; @@ -2938,8 +2932,11 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s copy_begin(); Decl **params = copy_decl_list_macro(decl->func_decl.signature.params); Ast *body = copy_ast_macro(astptr(decl->func_decl.body)); - AstId docs = decl->func_decl.docs; - if (docs) docs = astid(copy_ast_macro(astptr(docs))); + Decl *contracts = declptrzero(decl->func_decl.docs); + Expr **requires = contracts ? contracts->contracts_decl.requires : NULL; + Expr **ensures = contracts ? contracts->contracts_decl.ensures : NULL; + if (requires) requires = copy_exprlist_macro(requires); + if (ensures) ensures = copy_exprlist_macro(ensures); Signature *sig = &decl->func_decl.signature; copy_end(); CalledDecl callee = { @@ -3140,7 +3137,7 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s } bool has_ensures = false; - if (!sema_analyse_contracts(¯o_context, docs, &next, call_expr->span, &has_ensures)) return false; + if (!sema_analyse_contracts(¯o_context, contracts, requires, ensures, &next, call_expr->span, &has_ensures)) return false; macro_context.macro_has_ensures = has_ensures; sema_append_contract_asserts(assert_first, body); @@ -10528,6 +10525,7 @@ static inline bool sema_expr_analyse_ct_nameof(SemaContext *context, Expr *expr, case DECL_POISONED: case DECL_GENERIC: case DECL_GENERIC_INSTANCE: + case DECL_CONTRACT: RETURN_SEMA_ERROR(main_var, "'%s' does not have an external name.", decl->name); case DECL_FAULT: goto RETURN_CT; @@ -11317,6 +11315,7 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr case EXPR_ACCESS_RESOLVED: case EXPR_CT_SUBSCRIPT: case EXPR_IOTA_DECL: + case EXPR_CONTRACT: UNREACHABLE case EXPR_DECL: if (!sema_analyse_var_decl(context, main_expr->decl_expr, true, &failed)) @@ -11816,6 +11815,7 @@ static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr) case EXPR_SCALAR_TO_VECTOR: case EXPR_MAKE_SLICE: case EXPR_CT_SUBSCRIPT: + case EXPR_CONTRACT: UNREACHABLE case EXPR_LENGTHOF: return sema_expr_analyse_lenof(context, expr, NULL); @@ -12413,12 +12413,13 @@ IDENT_CHECK:; case EXPR_VASPLAT: case EXPR_TRY_UNRESOLVED: case EXPR_TWO: - case EXPR_MAYBE_DEREF: + case EXPR_MAYBE_DEREF: break; case EXPR_BITACCESS: case EXPR_SUBSCRIPT_ASSIGN: case EXPR_ACCESS_RESOLVED: case EXPR_CT_SUBSCRIPT: + case EXPR_CONTRACT: UNREACHABLE } if (failed_ref) goto FAILED_REF; diff --git a/src/compiler/sema_internal.h b/src/compiler/sema_internal.h index 07fcae40e..4c4d1d7ec 100644 --- a/src/compiler/sema_internal.h +++ b/src/compiler/sema_internal.h @@ -66,7 +66,7 @@ unsigned sema_context_push_ct_stack(SemaContext *context); void sema_context_pop_ct_stack(SemaContext *context, unsigned old_state); bool sema_analyse_function_body(SemaContext *context, Decl *func); -bool sema_analyse_contracts(SemaContext *context, AstId doc, AstId **asserts, SourceSpan call_span, bool *has_ensures); +bool sema_analyse_contracts(SemaContext *context, Decl *contracts, Expr **requires, Expr **ensures, AstId **asserts, SourceSpan call_span, bool *has_ensures); void sema_append_contract_asserts(AstId assert_first, Ast* compound_stmt); Decl *sema_create_runner_main(SemaContext *context, Decl *decl); @@ -135,13 +135,14 @@ bool sema_parameterized_type_is_found(SemaContext *context, Path *decl_path, con Type *sema_resolve_type_get_func(Signature *signature, CallABI abi); INLINE bool sema_set_alignment(SemaContext *context, Type *type, AlignSize *result, bool is_alloca); INLINE bool sema_set_alloca_alignment(SemaContext *context, Type *type, AlignSize *result); -INLINE void sema_display_deprecated_warning_on_use(SemaContext *context, Decl *decl, SourceSpan span); +INLINE bool sema_display_deprecated_warning_on_use(SemaContext *context, Decl *decl, SourceSpan span); bool sema_expr_analyse_ct_concat(SemaContext *context, Expr *concat_expr, Expr *left, Expr *right, bool *failed_ref); bool sema_analyse_attributes(SemaContext *context, Decl *decl, Attr **attrs, AttributeDomain domain, bool *erase_decl); void unit_register_optional_global_decl(CompilationUnit *unit, Decl *decl); bool analyse_func_body(SemaContext *context, Decl *decl); bool sema_check_interfaces(SemaContext *context, Decl *decl); +bool sema_analyse_optional_returns(SemaContext *context, Decl *contract); INLINE bool sema_analyse_stmt_chain(SemaContext *context, Ast *statement) { @@ -166,6 +167,7 @@ INLINE bool sema_analyse_func_macro(SemaContext *context, Decl *decl, AttributeD assert((domain & CALLABLE_TYPE) == domain); if (!sema_analyse_attributes(context, decl, decl->attributes, domain, erase_decl)) return decl_poison(decl); + if (decl_is_deprecated(decl)) context->call_env.ignore_deprecation = true; return true; } @@ -205,11 +207,11 @@ INLINE Attr* attr_find_kind(Attr **attrs, AttributeType attr_type) return NULL; } -INLINE void sema_display_deprecated_warning_on_use(SemaContext *context, Decl *decl, SourceSpan span) +INLINE bool sema_display_deprecated_warning_on_use(SemaContext *context, Decl *decl, SourceSpan span) { ASSERT(decl->resolve_status == RESOLVE_DONE); - if (!decl_is_deprecated(decl)) return; - if (context->call_env.ignore_deprecation) return; + if (!decl_is_deprecated(decl)) return true; + if (context->call_env.ignore_deprecation) return true; const char *msg = decl->attrs_resolved->deprecated; // Prevent multiple reports @@ -218,26 +220,27 @@ INLINE void sema_display_deprecated_warning_on_use(SemaContext *context, Decl *d switch (compiler.build.warnings.deprecation) { case WARNING_NOT_SET: - UNREACHABLE_VOID + UNREACHABLE; case WARNING_SILENT: - return; + return true; case WARNING_WARN: if (msg[0]) { sema_warning_at(span, "'%s' is deprecated: %s.", decl->name, msg); - return; + return true; } sema_warning_at(span, "'%s' is deprecated.", decl->name); - return; + return true; case WARNING_ERROR: if (msg[0]) { print_error_at(span, "'%s' is deprecated: %s.", decl->name, msg); - return; + return false; } print_error_at(span, "'%s' is deprecated.", decl->name); - return; + return false; } + UNREACHABLE } static inline IndexDiff range_const_len(Range *range) diff --git a/src/compiler/sema_liveness.c b/src/compiler/sema_liveness.c index 1b68a9bdc..5d462b413 100644 --- a/src/compiler/sema_liveness.c +++ b/src/compiler/sema_liveness.c @@ -105,9 +105,7 @@ static void sema_trace_stmt_liveness(Ast *ast) { case AST_POISONED: case CT_AST: - case AST_CONTRACT: case AST_FOREACH_STMT: - case AST_CONTRACT_FAULT: assert_print_line(ast->span); error_exit("Unexpected liveness checking of AST node %d.", ast->ast_kind); case AST_ASM_STMT: @@ -640,6 +638,7 @@ RETRY: case DECL_GENERIC: case DECL_GENERIC_INSTANCE: case DECL_DECLARRAY: + case DECL_CONTRACT: assert_print_line(decl->span); error_exit("Unexpected liveness checking of expr decl %d.", decl->decl_kind); case DECL_FNTYPE: diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 2369911ef..016f1e8f9 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -28,7 +28,7 @@ static inline bool sema_defer_has_try_or_catch(AstId defer_top, AstId defer_bott static inline bool sema_analyse_block_exit_stmt(SemaContext *context, Ast *statement); static inline bool sema_analyse_defer_stmt_body(SemaContext *context, Ast *statement); static inline bool sema_analyse_for_cond(SemaContext *context, ExprId *cond_ref, bool *infinite); -static inline bool assert_create_from_contract(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan evaluation_location); +static inline bool assert_create_from_contract(SemaContext *context, Expr *directive, AstId **asserts, SourceSpan evaluation_location); static bool sema_analyse_asm_string_stmt(SemaContext *context, Ast *stmt); static void sema_unwrappable_from_catch_in_else(SemaContext *c, Expr *cond); static inline bool sema_analyse_try_unwrap(SemaContext *context, Expr *expr); @@ -49,9 +49,8 @@ static inline bool sema_check_value_case(SemaContext *context, Type *switch_type static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, CanonicalType *switch_type, Ast **cases); static inline bool sema_analyse_statement_inner(SemaContext *context, Ast *statement); -static bool sema_analyse_require(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan span); -static bool sema_analyse_ensure(SemaContext *context, Ast *directive); -static bool sema_analyse_optional_returns(SemaContext *context, Ast *directive); +static bool sema_analyse_require(SemaContext *context, Expr *directive, AstId **asserts, SourceSpan span); +static bool sema_analyse_ensure(SemaContext *context, Expr *directive); static inline bool sema_analyse_asm_label(SemaContext *context, AsmInlineBlock *block, Ast *label) { @@ -411,10 +410,9 @@ static void sema_unwrappable_from_catch_in_else(SemaContext *c, Expr *cond) /** * Turn a "require" or "ensure" into a contract in the callee. */ -static inline bool assert_create_from_contract(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan evaluation_location) +static inline bool assert_create_from_contract(SemaContext *context, Expr *directive, AstId **asserts, SourceSpan evaluation_location) { - directive = copy_ast_single(directive); - Expr *declexpr = directive->contract_stmt.contract.decl_exprs; + Expr *declexpr = directive->contract_expr.decl_exprs; ASSERT(declexpr->expr_kind == EXPR_EXPRESSION_LIST); FOREACH(Expr *, expr, declexpr->expression_list) @@ -424,8 +422,8 @@ static inline bool assert_create_from_contract(SemaContext *context, Ast *direct if (evaluation_location.a) expr->span = evaluation_location; - const char *comment = directive->contract_stmt.contract.comment; - if (!comment) comment = directive->contract_stmt.contract.expr_string; + const char *comment = directive->contract_expr.comment; + if (!comment) comment = directive->contract_expr.expr_string; if (expr_is_const_bool(expr)) { if (expr->const_expr.b) continue; @@ -530,19 +528,18 @@ static bool sema_analyse_macro_constant_ensures(SemaContext *context, Expr *ret_ // we won't be able to do any constant ensure checks anyway, so skip. if (!sema_cast_const(ret_expr)) return true; - AstId doc_directive = context->current_macro->func_decl.docs; + Decl *contracts = declptrzero(context->current_macro->func_decl.docs); + Expr **ensures = contracts ? contracts->contracts_decl.ensures : NULL; + // We store the old return_expr for retval Expr *return_expr_old = context->return_expr; // And set our new one. context->return_expr = ret_expr; bool success = true; SCOPE_START_WITH_FLAGS(SCOPE_ENSURE_MACRO, ret_expr->span); - while (doc_directive) + FOREACH(Expr *, directive, ensures) { - Ast *directive = astptr(doc_directive); - doc_directive = directive->next; - if (directive->contract_stmt.kind != CONTRACT_ENSURE) continue; - Expr *checks = copy_expr_single(directive->contract_stmt.contract.decl_exprs); + Expr *checks = copy_expr_single(directive->contract_expr.decl_exprs); ASSERT(checks->expr_kind == EXPR_EXPRESSION_LIST); Expr **exprs = checks->expression_list; FOREACH(Expr *, expr, exprs) @@ -562,8 +559,8 @@ static bool sema_analyse_macro_constant_ensures(SemaContext *context, Expr *ret_ // Skipping non-const. if (result == COND_MISSING) continue; if (result == COND_TRUE) continue; - const char *comment = directive->contract_stmt.contract.comment; - if (!comment) comment = directive->contract_stmt.contract.expr_string; + const char *comment = directive->contract_expr.comment; + if (!comment) comment = directive->contract_expr.expr_string; SEMA_ERROR(ret_expr, "%s", comment); success = false; goto END; @@ -753,20 +750,16 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement AstId first = 0; AstId *append_id = &first; // Creating an assign statement - AstId doc_directive = context->call_env.current_function->func_decl.docs; + Decl *contracts = declptrzero(context->call_env.current_function->func_decl.docs); + Expr **ensures = contracts ? contracts->contracts_decl.ensures : NULL; context->return_expr = return_expr; - while (doc_directive) + FOREACH(Expr *, ensure, ensures) { - Ast *directive = astptr(doc_directive); - if (directive->contract_stmt.kind == CONTRACT_ENSURE) - { - bool success; - SCOPE_START_WITH_FLAGS(SCOPE_ENSURE, statement->span); - success = assert_create_from_contract(context, directive, &append_id, statement->span); - SCOPE_END; - if (!success) return false; - } - doc_directive = directive->next; + bool success; + SCOPE_START_WITH_FLAGS(SCOPE_ENSURE, statement->span); + success = assert_create_from_contract(context, ensure, &append_id, statement->span); + SCOPE_END; + if (!success) return false; } if (!first) goto SKIP_ENSURE; if (statement->return_stmt.cleanup) @@ -801,6 +794,7 @@ static inline bool sema_expr_valid_try_expression(Expr *expr) case EXPR_CT_IS_CONST: case EXPR_CT_DEFINED: case EXPR_CT_EVAL: + case EXPR_CONTRACT: case EXPR_NAMED_ARGUMENT: UNREACHABLE case EXPR_BINARY: @@ -3206,10 +3200,8 @@ static inline bool sema_analyse_statement_inner(SemaContext *context, Ast *state switch (statement->ast_kind) { case AST_POISONED: - case AST_CONTRACT: case AST_ASM_STMT: case AST_ASM_LABEL: - case AST_CONTRACT_FAULT: UNREACHABLE case AST_CT_TYPE_ASSIGN_STMT: return sema_analyse_ct_type_assign_stmt(context, statement); @@ -3301,14 +3293,14 @@ bool sema_analyse_statement(SemaContext *context, Ast *statement) } -static bool sema_analyse_require(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan span) +static bool sema_analyse_require(SemaContext *context, Expr *directive, AstId **asserts, SourceSpan span) { return assert_create_from_contract(context, directive, asserts, span); } -static bool sema_analyse_ensure(SemaContext *context, Ast *directive) +static bool sema_analyse_ensure(SemaContext *context, Expr *directive) { - Expr *declexpr = directive->contract_stmt.contract.decl_exprs; + Expr *declexpr = directive->contract_expr.decl_exprs; ASSERT(declexpr->expr_kind == EXPR_EXPRESSION_LIST); FOREACH(Expr *, expr, declexpr->expression_list) @@ -3321,69 +3313,15 @@ static bool sema_analyse_ensure(SemaContext *context, Ast *directive) return true; } -static bool sema_analyse_optional_returns(SemaContext *context, Ast *directive) +static bool sema_analyse_call_optional_returns(SemaContext *context, Decl *contract) { - FOREACH(Ast *, ret, directive->contract_stmt.faults) + if (!contract || !contract->contracts_decl.opt_returns) { - if (ret->contract_fault.expanding) continue; - if (ret->contract_fault.resolved) - { - vec_add(context->call_env.opt_returns, ret->contract_fault.decl); - continue; - } - Expr *expr = ret->contract_fault.expr; - if (expr->expr_kind == EXPR_RETHROW) - { - Expr *inner = expr->rethrow_expr.inner; - if (!sema_analyse_expr(context, inner)) return false; - Decl *decl; - switch (inner->expr_kind) - { - case EXPR_IDENTIFIER: - decl = inner->ident_expr; - break; - case EXPR_TYPEINFO: - { - Type *type = inner->type_expr->type; - if (type->type_kind != TYPE_ALIAS) goto IS_FAULT; - decl = type->decl; - ASSERT(decl->decl_kind == DECL_TYPE_ALIAS); - if (!decl->type_alias_decl.is_func) goto IS_FAULT; - decl = decl->type_alias_decl.decl; - break; - } - default: - goto IS_FAULT;; - } - decl = decl_flatten(decl); - if (decl->decl_kind != DECL_FNTYPE && decl->decl_kind != DECL_FUNC) goto IS_FAULT; - if (!sema_analyse_decl(context, decl)) return false; - AstId docs = decl->decl_kind == DECL_FNTYPE ? decl->fntype_decl.docs : decl->func_decl.docs; - while (docs) - { - Ast *doc = astptr(docs); - docs = doc->next; - if (doc->contract_stmt.kind != CONTRACT_OPTIONALS) continue; - ret->contract_fault.expanding = true; - bool success = sema_analyse_optional_returns(context, doc); - ret->contract_fault.expanding = false; - if (!success) false; - } - continue; - } -IS_FAULT:; - if (!sema_analyse_expr_rvalue(context, expr)) return false; - if (expr->type->canonical != type_fault) - { - RETURN_SEMA_ERROR(expr, "Expected a fault here."); - } - if (!expr_is_const_fault(expr)) RETURN_SEMA_ERROR(expr, "A constant fault is required."); - Decl *decl = expr->const_expr.fault; - if (!decl) RETURN_SEMA_ERROR(expr, "A non-null fault is required."); - ret->contract_fault.decl = decl; - ret->contract_fault.resolved = true; - vec_add(context->call_env.opt_returns, decl); + context->call_env.opt_returns = NULL; + return true; } + if (!sema_analyse_optional_returns(context, contract)) return false; + context->call_env.opt_returns = contract->contracts_decl.opt_returns_resolved; return true; } @@ -3397,34 +3335,23 @@ void sema_append_contract_asserts(AstId assert_first, Ast* compound_stmt) ast_prepend(&compound_stmt->compound_stmt.first_stmt, ast); } -bool sema_analyse_contracts(SemaContext *context, AstId doc, AstId **asserts, SourceSpan call_span, bool *has_ensures) +bool sema_analyse_contracts(SemaContext *context, Decl *contract, Expr **requires, Expr **ensures, AstId **asserts, SourceSpan call_span, bool *has_ensures) { context->call_env.opt_returns = NULL; - while (doc) + if (has_ensures) { - Ast *directive = astptr(doc); - switch (directive->contract_stmt.kind) - { - case CONTRACT_UNKNOWN: - case CONTRACT_PURE: - case CONTRACT_COMMENT: - break; - case CONTRACT_REQUIRE: - if (!sema_analyse_require(context, directive, asserts, call_span)) return false; - break; - case CONTRACT_PARAM: - break; - case CONTRACT_OPTIONALS: - if (!has_ensures) break; - if (!sema_analyse_optional_returns(context, directive)) return false; - break; - case CONTRACT_ENSURE: - if (!has_ensures) break; - if (!sema_analyse_ensure(context, directive)) return false; - *has_ensures = true; - break; - } - doc = directive->next; + if (!sema_analyse_call_optional_returns(context, contract)) return false; + } + + FOREACH(Expr *, require, requires) + { + if (!sema_analyse_require(context, require, asserts, call_span)) return false; + } + if (!has_ensures) return true; + FOREACH(Expr *, ensure, ensures) + { + if (!sema_analyse_ensure(context, ensure)) return false; + *has_ensures = true; } return true; } @@ -3498,7 +3425,15 @@ bool sema_analyse_function_body(SemaContext *context, Decl *func) AstId assert_first = 0; AstId *next = &assert_first; bool has_ensures = false; - if (!sema_analyse_contracts(context, func->func_decl.docs, &next, INVALID_SPAN, &has_ensures)) return false; + Decl *contracts = declptrzero(func->func_decl.docs); + if (contracts) + { + copy_begin(); + Expr **requires = copy_exprlist_macro(contracts->contracts_decl.requires); + Expr **ensures = copy_exprlist_macro(contracts->contracts_decl.ensures); + copy_end(); + if (!sema_analyse_contracts(context, contracts, requires, ensures, &next, INVALID_SPAN, &has_ensures)) return false; + } context->call_env.ensures = has_ensures; bool is_naked = func->func_decl.attr_naked; if (!is_naked) sema_append_contract_asserts(assert_first, body); diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index b39c95416..22d0f05e9 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -299,6 +299,7 @@ static bool sema_resolve_type_identifier(SemaContext *context, TypeInfo *type_in case DECL_GROUP: case DECL_GENERIC: case DECL_GENERIC_INSTANCE: + case DECL_CONTRACT: UNREACHABLE } UNREACHABLE diff --git a/src/compiler/semantic_analyser.c b/src/compiler/semantic_analyser.c index 30df4753f..d0b84d991 100644 --- a/src/compiler/semantic_analyser.c +++ b/src/compiler/semantic_analyser.c @@ -258,6 +258,7 @@ static void register_generic_decls(CompilationUnit *unit, Decl **decls) case DECL_GENERIC: case DECL_GENERIC_INSTANCE: case DECL_LABEL: + case DECL_CONTRACT: UNREACHABLE_VOID case DECL_ALIAS: case DECL_ATTRIBUTE: diff --git a/src/utils/lib.h b/src/utils/lib.h index 7ef2aaa86..16c0a48fd 100644 --- a/src/utils/lib.h +++ b/src/utils/lib.h @@ -441,6 +441,11 @@ type__* CONCAT(foreach_vec_, __LINE__) = (vec__); type__* CONCAT(foreach_vecend_ type__* CONCAT(foreach_it_, __LINE__) = CONCAT(foreach_vec_, __LINE__); \ for (type__ name__ ; CONCAT(foreach_it_, __LINE__) < CONCAT(foreach_vecend_, __LINE__) ? (name__ = *CONCAT(foreach_it_, __LINE__), true) : false; CONCAT(foreach_it_, __LINE__)++) +#define FOREACH_REF(type__, name__, vec__) \ +type__* CONCAT(foreach_vec_, __LINE__) = (vec__); type__* CONCAT(foreach_vecend_, __LINE__) = CONCAT(foreach_vec_, __LINE__) ? CONCAT(foreach_vec_, __LINE__) + vec_size(CONCAT(foreach_vec_, __LINE__)) : NULL; \ +type__* CONCAT(foreach_it_, __LINE__) = CONCAT(foreach_vec_, __LINE__); \ +for (type__* name__ ; CONCAT(foreach_it_, __LINE__) < CONCAT(foreach_vecend_, __LINE__) ? (name__ = CONCAT(foreach_it_, __LINE__), true) : false; CONCAT(foreach_it_, __LINE__)++) + #define FOREACH_IDX(idx__, type__, name__, vec__) \ type__* CONCAT(foreach_vec_, __LINE__) = (vec__); uint32_t CONCAT(foreach_vecsize_, __LINE__) = vec_size(CONCAT(foreach_vec_, __LINE__)); \ uint32_t idx__ = 0; \ diff --git a/test/test_suite/contracts/contract_deprecation.c3 b/test/test_suite/contracts/contract_deprecation.c3 new file mode 100644 index 000000000..a2524cb8f --- /dev/null +++ b/test/test_suite/contracts/contract_deprecation.c3 @@ -0,0 +1,15 @@ +constdef Foo +{ + <* @deprecated *> + ABC +} + +<* @deprecated *> +Foo a; +fn int main() +{ + + Foo f = Foo.ABC; // #warning: 'ABC' is deprecated. + Foo b = a; // #warning: 'a' is deprecated. + return 0; +}