From 59ff94c005bd6858ccdf0c3d08f124725e3efe97 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Tue, 3 Sep 2024 23:25:47 +0200 Subject: [PATCH] Issue where a lambda wasn't correctly registered as external. #1408 --- releasenotes.md | 2 +- src/compiler/sema_expr.c | 2 +- src/compiler/sema_stmts.c | 81 +++++++++++++++++++++++---------------- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/releasenotes.md b/releasenotes.md index a1fee3b77..4da027703 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -6,7 +6,7 @@ *None yet* ### Fixes -*None yet* +- Issue where a lambda wasn't correctly registered as external. #1408 ### Stdlib changes *None yet* diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index c1e09af98..f65e51705 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -8772,7 +8772,7 @@ static inline bool sema_expr_analyse_lambda(SemaContext *context, Type *target_t decl->alignment = type_alloca_alignment(decl->type); // We will actually compile this into any module using it (from a macro) by necessity, // so we'll declare it as weak and externally visible. - if (context->compilation_unit != decl->unit) decl->is_external_visible = true; + unit_register_external_symbol(context, decl); // Before function analysis, lambda evaluation is deferred if (unit->module->stage < ANALYSIS_FUNCTIONS) diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 7171fec2d..c7544fb5f 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -24,7 +24,7 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement static inline bool sema_analyse_switch_stmt(SemaContext *context, Ast *statement); static inline bool sema_return_optional_check_is_valid_in_scope(SemaContext *context, Expr *ret_expr); -static inline bool sema_defer_by_result(AstId defer_top, AstId defer_bottom); +static inline bool sema_defer_has_try_or_catch(AstId defer_top, AstId defer_bottom); 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); @@ -157,13 +157,9 @@ static inline bool sema_analyse_break_stmt(SemaContext *context, Ast *statement) { if (context_labels_exist_in_scope(context)) { - SEMA_ERROR(statement, "Unlabelled 'break' is not allowed here."); + RETURN_SEMA_ERROR(statement, "Unlabelled 'break' is not allowed here."); } - else - { - SEMA_ERROR(statement, "There is no valid target for 'break', did you make a mistake?"); - } - return false; + RETURN_SEMA_ERROR(statement, "There is no valid target for 'break', did you make a mistake?"); } // Is jump, and set it as resolved. @@ -221,8 +217,7 @@ static inline bool sema_analyse_continue_stmt(SemaContext *context, Ast *stateme // If we have a plain continue and no continue label, we just failed. if (!context->continue_target && !statement->contbreak_stmt.label.name) { - SEMA_ERROR(statement, "'continue' is not allowed here."); - return false; + RETURN_SEMA_ERROR(statement, "'continue' is not allowed here."); } AstId defer_id; @@ -233,11 +228,11 @@ static inline bool sema_analyse_continue_stmt(SemaContext *context, Ast *stateme ASSIGN_DECL_OR_RET(Decl *target, sema_analyse_label(context, statement), false); defer_id = target->label.defer; parent = astptr(target->label.parent); + // Continue can only be used with "for" statements, skipping the "do { };" statement if (!ast_supports_continue(parent)) { - SEMA_ERROR(statement, "'continue' may only be used with 'for', 'while' and 'do-while' statements."); - return false; + RETURN_SEMA_ERROR(statement, "'continue' may only be used with 'for', 'while' and 'do-while' statements."); } } else @@ -256,24 +251,37 @@ static inline bool sema_analyse_continue_stmt(SemaContext *context, Ast *stateme return true; } +/** + * If we have "if (catch x)", then we want to unwrap x in the else clause. + **/ static void sema_unwrappable_from_catch_in_else(SemaContext *c, Expr *cond) { - assert(cond->expr_kind == EXPR_COND); + assert(cond->expr_kind == EXPR_COND && "Assumed cond"); Expr *last = VECLAST(cond->cond_expr); + assert(last); + + // Dive into any cast, because it might have been cast into boolean. while (last->expr_kind == EXPR_CAST) { last = exprptr(last->cast_expr.expr); } - if (!last || last->expr_kind != EXPR_CATCH_UNWRAP) return; + // Skip any non-unwraps + if (last->expr_kind != EXPR_CATCH_UNWRAP) return; + // If we have "if (catch x)" then this will unwrap x in the + // else branch. FOREACH(Expr *, expr, last->catch_unwrap_expr.exprs) { if (expr->expr_kind != EXPR_IDENTIFIER) continue; + Decl *decl = expr->identifier_expr.decl; if (decl->decl_kind != DECL_VAR) continue; assert(decl->type->type_kind == TYPE_OPTIONAL && "The variable should always be optional at this point."); + // Note that we could possibly have "if (catch x, x)" and in this case we'd + // unwrap twice, but that isn't really a problem. + // 5. Locals and globals may be unwrapped switch (decl->var.kind) { @@ -284,14 +292,15 @@ static void sema_unwrappable_from_catch_in_else(SemaContext *c, Expr *cond) default: continue; } - } -} +} // --- Sema analyse stmts - +/** + * 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) { directive = copy_ast_single(directive); @@ -300,21 +309,21 @@ static inline bool assert_create_from_contract(SemaContext *context, Ast *direct FOREACH(Expr *, expr, declexpr->expression_list) { - if (expr->expr_kind == EXPR_DECL) - { - SEMA_ERROR(expr, "Only expressions are allowed."); - return false; - } + if (expr->expr_kind == EXPR_DECL) RETURN_SEMA_ERROR(expr, "Only expressions are allowed in contracts."); CondResult result = COND_MISSING; if (!sema_analyse_cond_expr(context, expr, &result)) return false; const char *comment = directive->contract_stmt.contract.comment; if (!comment) comment = directive->contract_stmt.contract.expr_string; - if (result == COND_TRUE) continue; - if (result == COND_FALSE) + switch (result) { - sema_error_at(context, evaluation_location.a ? evaluation_location : expr->span, "%s", comment); - return false; + case COND_TRUE: + continue; + case COND_FALSE: + sema_error_at(context, evaluation_location.a ? evaluation_location : expr->span, "%s", comment); + return false; + case COND_MISSING: + break; } Ast *assert = new_ast(AST_ASSERT_STMT, expr->span); assert->assert_stmt.is_ensure = true; @@ -327,7 +336,8 @@ static inline bool assert_create_from_contract(SemaContext *context, Ast *direct return true; } -static inline bool sema_defer_by_result(AstId defer_top, AstId defer_bottom) +// Check whether a defer chain contains a try or a catch. +static inline bool sema_defer_has_try_or_catch(AstId defer_top, AstId defer_bottom) { AstId first = 0; while (defer_bottom != defer_top) @@ -339,17 +349,21 @@ static inline bool sema_defer_by_result(AstId defer_top, AstId defer_bottom) return false; } +// Print defers at return (from macro/block or from function) static inline void sema_inline_return_defers(SemaContext *context, Ast *stmt, AstId defer_top, AstId defer_bottom) { + // Store the cleanup defers, which will happen on try. stmt->return_stmt.cleanup = context_get_defers(context, defer_top, defer_bottom, true); - if (stmt->return_stmt.expr && IS_OPTIONAL(stmt->return_stmt.expr) && sema_defer_by_result(context->active_scope.defer_last, context->block_return_defer)) + + // If we have an optional return, then we create a cleanup_fail + if (stmt->return_stmt.expr && IS_OPTIONAL(stmt->return_stmt.expr) + && sema_defer_has_try_or_catch(context->active_scope.defer_last, context->block_return_defer)) { stmt->return_stmt.cleanup_fail = context_get_defers(context, context->active_scope.defer_last, context->block_return_defer, false); + return; } - else - { - stmt->return_stmt.cleanup_fail = stmt->return_stmt.cleanup ? astid(copy_ast_defer(astptr(stmt->return_stmt.cleanup))) : 0; - } + // Otherwise we make the cleanup fail be the same as the cleanup. + stmt->return_stmt.cleanup_fail = stmt->return_stmt.cleanup ? astid(copy_ast_defer(astptr(stmt->return_stmt.cleanup))) : 0; } static inline bool sema_return_optional_check_is_valid_in_scope(SemaContext *context, Expr *ret_expr) @@ -537,8 +551,7 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement { if (context->active_scope.in_defer) { - SEMA_ERROR(statement, "Return is not allowed inside of a defer."); - return false; + RETURN_SEMA_ERROR(statement, "Return is not allowed inside of a defer."); } // This might be a return in a function block or a macro which must be treated differently. @@ -546,6 +559,7 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement { return sema_analyse_block_exit_stmt(context, statement); } + // 1. We mark that the current scope ends with a jump. context->active_scope.jump_end = true; @@ -553,7 +567,6 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement assert(expected_rtype && "We should always have known type from a function return."); Expr *return_expr = statement->return_stmt.expr; - if (return_expr) { if (!sema_analyse_expr_rhs(context, expected_rtype, return_expr, type_is_optional(expected_rtype), NULL, false)) return false;