diff --git a/releasenotes.md b/releasenotes.md index 7418a0d3b..140cdb414 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -85,6 +85,7 @@ - Allow enums to use a distinct type as the backing type. - Update addition and subtraction on enums. - `@ensure` checks only non-optional results. +- `assert` may now take varargs for formatting. ### Stdlib changes - Updated posix/win32 stdlib namespacing diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index d817fff83..882d2c9ab 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1374,6 +1374,7 @@ typedef struct bool is_ensure; ExprId message; ExprId expr; + Expr **args; } AstAssertStmt; diff --git a/src/compiler/copying.c b/src/compiler/copying.c index a0c0d2e60..e272f6a41 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -586,6 +586,7 @@ RETRY: case AST_CT_ASSERT: MACRO_COPY_EXPRID(ast->assert_stmt.expr); MACRO_COPY_EXPRID(ast->assert_stmt.message); + MACRO_COPY_EXPR_LIST(ast->assert_stmt.args); break; case AST_BREAK_STMT: case AST_CONTINUE_STMT: diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index eeb9224aa..84a65aecc 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -1001,16 +1001,28 @@ static inline void llvm_emit_assert_stmt(GenContext *c, Ast *ast) llvm_emit_cond_br(c, &value, on_ok, on_fail); llvm_emit_block(c, on_fail); SourceSpan loc = assert_expr->span; - const char *error; - if (ast->assert_stmt.message) + const char *error = NULL; + Expr *message_expr = exprptrzero(ast->assert_stmt.message); + BEValue *values = NULL; + if (message_expr) { error = exprptr(ast->assert_stmt.message)->const_expr.string.chars; + Expr **args = ast->assert_stmt.args; + if (vec_size(args)) + { + FOREACH_BEGIN(Expr *arg, args) + BEValue var; + llvm_emit_expr(c, &var, arg); + llvm_emit_any_from_value(c, &var, arg->type); + vec_add(values, var); + FOREACH_END(); + } } else { error = "Assert violation"; } - llvm_emit_panic(c, error, loc, NULL, NULL); + llvm_emit_panic(c, values ? NULL : error, loc, values ? error : NULL, values); llvm_emit_br(c, on_ok); llvm_emit_block(c, on_ok); } diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 95733ecbe..b4ed654dc 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -1080,7 +1080,14 @@ static inline Ast *parse_assert_stmt(ParseContext *c) if (try_consume(c, TOKEN_COMMA)) { - ASSIGN_EXPRID_OR_RET(ast->assert_stmt.message, parse_expr(c), poisoned_ast); + Expr **args = NULL; + ASSIGN_EXPRID_OR_RET(ast->assert_stmt.message, parse_constant_expr(c), poisoned_ast); + while (try_consume(c, TOKEN_COMMA)) + { + ASSIGN_EXPR_OR_RET(Expr *expr, parse_expr(c), poisoned_ast); + vec_add(args, expr); + } + ast->assert_stmt.args = args; } TRY_CONSUME_OR_RET(TOKEN_RPAREN, "The ending ')' was expected here.", poisoned_ast); return consume_eos(c, ast); diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index e2c80ff9b..469de3af7 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -82,14 +82,12 @@ static inline bool sema_analyse_assert_stmt(SemaContext *context, Ast *statement if (message_expr) { if (!sema_analyse_expr(context, message_expr)) return false; - if (!expr_is_const_string(message_expr)) - { - SEMA_ERROR(message_expr, "Expected a string as the error message."); - return false; - } + if (!expr_is_const_string(message_expr)) RETURN_SEMA_ERROR(message_expr, "Expected a string as the error message."); + FOREACH_BEGIN(Expr *e, statement->assert_stmt.args) + if (!sema_analyse_expr(context, e)) return false; + FOREACH_END(); } - // Handle force unwrapping using assert, e.g. assert(try x) if (expr->expr_kind == EXPR_TRY_UNWRAP_CHAIN) { @@ -111,7 +109,7 @@ static inline bool sema_analyse_assert_stmt(SemaContext *context, Ast *statement // If it's ensure (and an error) we print an error. if (statement->assert_stmt.is_ensure) { - if (message_expr) + if (message_expr && expr_is_const(message_expr) && vec_size(statement->assert_stmt.args)) { SEMA_ERROR(expr, "%.*s", EXPAND_EXPR_STRING(message_expr)); } diff --git a/src/version.h b/src/version.h index 69f43f2d8..f85e77f2b 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.538" \ No newline at end of file +#define COMPILER_VERSION "0.4.539" \ No newline at end of file diff --git a/test/test_suite/assert/assertf.c3t b/test/test_suite/assert/assertf.c3t new file mode 100644 index 000000000..194229887 --- /dev/null +++ b/test/test_suite/assert/assertf.c3t @@ -0,0 +1,65 @@ +// #target: macos-x64 +// #safe: yes +module main; + +fn void! main() +{ + for (usz i = 0; i < 100000000; i++) + { + assert(i != 2, "Test %s %s", i, i * 2); + } +} + +/* #expect: main.ll + +define i64 @main.main() #0 { +entry: + %i = alloca i64, align 8 + %taddr = alloca i64, align 8 + %varargslots = alloca [2 x %any], align 16 + %indirectarg = alloca %"any[]", align 8 + %reterr = alloca i64, align 8 + store i64 0, ptr %i, align 8 + br label %loop.cond + +loop.cond: ; preds = %assert_ok, %entry + %0 = load i64, ptr %i, align 8 + %gt = icmp ugt i64 100000000, %0 + br i1 %gt, label %loop.body, label %loop.exit + +loop.body: ; preds = %loop.cond + %1 = load i64, ptr %i, align 8 + %neq = icmp ne i64 2, %1 + br i1 %neq, label %assert_ok, label %assert_fail + +assert_fail: ; preds = %loop.body + %2 = insertvalue %any undef, ptr %i, 0 + %3 = insertvalue %any %2, i64 ptrtoint (ptr @"$ct.ulong" to i64), 1 + %4 = load i64, ptr %i, align 8 + %mul = mul i64 %4, 2 + store i64 %mul, ptr %taddr, align 8 + %5 = insertvalue %any undef, ptr %taddr, 0 + %6 = insertvalue %any %5, i64 ptrtoint (ptr @"$ct.long" to i64), 1 + %7 = getelementptr inbounds [2 x %any], ptr %varargslots, i64 0, i64 0 + store %any %3, ptr %7, align 16 + %8 = getelementptr inbounds [2 x %any], ptr %varargslots, i64 0, i64 1 + store %any %6, ptr %8, align 16 + %9 = insertvalue %"any[]" undef, ptr %varargslots, 0 + %10 = insertvalue %"any[]" %9, i64 2, 1 + store %"any[]" %10, ptr %indirectarg, align 8 + call void @std.core.builtin.panicf(ptr @.panic_msg, i64 10, ptr @.file, i64 10, ptr @.func, i64 4, i32 7, ptr byval(%"any[]") align 8 %indirectarg) + br label %assert_ok + +assert_ok: ; preds = %assert_fail, %loop.body + %11 = load i64, ptr %i, align 8 + %neq1 = icmp ne i64 2, %11 + call void @llvm.assume(i1 %neq1) + %12 = load i64, ptr %i, align 8 + %add = add i64 %12, 1 + store i64 %add, ptr %i, align 8 + br label %loop.cond + +loop.exit: ; preds = %loop.cond + ret i64 0 +} +