mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Add printf format to $assert and $error #2183.
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
- `$eval` now also works with `@foo`, `#foo`, `$Foo` and `$foo` parameters #2114.
|
||||
- `@sprintf` macro (based on the `$$sprintf` builtin) allows compile time format strings #1874.
|
||||
- Improve error reports when encountering a broken "if-catch".
|
||||
- Add printf format to `$assert` and `$error` #2183.
|
||||
|
||||
### Fixes
|
||||
- `-2147483648`, MIN literals work correctly.
|
||||
|
||||
@@ -1214,7 +1214,7 @@ static inline Ast *parse_assert_stmt(ParseContext *c)
|
||||
// --- External functions
|
||||
|
||||
/**
|
||||
* ct_assert_stmt ::= CT_ASSERT constant_expression (':' constant_expression) ';'
|
||||
* ct_assert_stmt ::= CT_ASSERT constant_expression (':' constant_expression (',' constant_expression)*)? ';'
|
||||
* @param c
|
||||
* @return
|
||||
*/
|
||||
@@ -1227,11 +1227,16 @@ Ast *parse_ct_assert_stmt(ParseContext *c)
|
||||
{
|
||||
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_constant_expr(c), poisoned_ast);
|
||||
vec_add(ast->assert_stmt.args, expr);
|
||||
}
|
||||
return consume_eos(c, ast);
|
||||
}
|
||||
|
||||
/**
|
||||
* ct_error_stmt ::= CT_ERROR constant_expression) ';'
|
||||
* ct_error_stmt ::= CT_ERROR constant_expression (',' constant_expression)* ';'
|
||||
* @param c
|
||||
* @return
|
||||
*/
|
||||
@@ -1241,6 +1246,11 @@ Ast *parse_ct_error_stmt(ParseContext *c)
|
||||
advance_and_verify(c, TOKEN_CT_ERROR);
|
||||
ast->assert_stmt.expr = 0;
|
||||
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_constant_expr(c), poisoned_ast);
|
||||
vec_add(ast->assert_stmt.args, expr);
|
||||
}
|
||||
return consume_eos(c, ast);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ static inline bool sema_expr_analyse_swizzle(SemaContext *context, Expr *expr, b
|
||||
static inline int builtin_expected_args(BuiltinFunction func);
|
||||
static inline bool is_valid_atomicity(SemaContext *context, Expr *expr);
|
||||
static bool sema_check_alignment_expression(SemaContext *context, Expr *align);
|
||||
static bool sema_expr_analyse_sprintf(SemaContext *context, Expr *expr);
|
||||
|
||||
static bool sema_expr_is_valid_mask_for_value(SemaContext *context, Expr *expr, Expr *value)
|
||||
{
|
||||
@@ -306,56 +305,6 @@ bool sema_expr_analyse_rnd(SemaContext *context UNUSED, Expr *expr)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sema_expr_analyse_sprintf(SemaContext *context, Expr *expr)
|
||||
{
|
||||
Expr **args = expr->call_expr.arguments;
|
||||
FOREACH(Expr *, e, args)
|
||||
{
|
||||
if (!sema_analyse_expr(context, e)) return false;
|
||||
if (!sema_cast_const(e))
|
||||
{
|
||||
RETURN_SEMA_ERROR(e, "Expected a constant expression.");
|
||||
}
|
||||
}
|
||||
Expr *format = args[0];
|
||||
if (!expr_is_const_string(format))
|
||||
{
|
||||
RETURN_SEMA_ERROR(format, "Expected a constant format string.");
|
||||
}
|
||||
const char *inner_str = format->const_expr.bytes.ptr;
|
||||
ArraySize len = format->const_expr.bytes.len;
|
||||
scratch_buffer_clear();
|
||||
ArrayIndex current_index = 1;
|
||||
ArraySize param_count = vec_size(args);
|
||||
for (ArraySize i = 0; i < len; i++)
|
||||
{
|
||||
char c = inner_str[i];
|
||||
if (c == '%')
|
||||
{
|
||||
i++;
|
||||
switch (inner_str[i])
|
||||
{
|
||||
case 's':
|
||||
if (current_index == param_count) RETURN_SEMA_ERROR(format, "Too many arguments in format string.");
|
||||
expr_const_to_scratch_buffer(&(args[current_index++]->const_expr));
|
||||
continue;
|
||||
case '%':
|
||||
scratch_buffer_append_char('%');
|
||||
continue;
|
||||
default:
|
||||
RETURN_SEMA_ERROR(format, "Only '%%s' is supported for compile time sprintf.");
|
||||
}
|
||||
}
|
||||
scratch_buffer_append_char(c);
|
||||
}
|
||||
if (current_index != param_count)
|
||||
{
|
||||
RETURN_SEMA_ERROR(format, "Too many arguments to sprintf.");
|
||||
}
|
||||
expr_rewrite_const_string(expr, scratch_buffer_copy());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sema_expr_analyse_str_hash(SemaContext *context, Expr *expr)
|
||||
{
|
||||
Expr *inner = expr->call_expr.arguments[0];
|
||||
@@ -584,7 +533,7 @@ bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr)
|
||||
switch (func)
|
||||
{
|
||||
case BUILTIN_SPRINTF:
|
||||
return sema_expr_analyse_sprintf(context, expr);
|
||||
return sema_expr_analyse_sprintf(context, expr, args[0], &args[1], arg_count - 1);
|
||||
case BUILTIN_RND:
|
||||
return sema_expr_analyse_rnd(context, expr);
|
||||
case BUILTIN_STR_HASH:
|
||||
|
||||
@@ -469,6 +469,58 @@ CondResult sema_check_comp_time_bool(SemaContext *context, Expr *expr)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sema_expr_analyse_sprintf(SemaContext *context, Expr *expr, Expr *format_string, Expr **args, unsigned num_args)
|
||||
{
|
||||
if (!sema_analyse_expr(context, format_string)) return false;
|
||||
if (!sema_cast_const(format_string))
|
||||
{
|
||||
RETURN_SEMA_ERROR(format_string, "Expected a constant format string expression.");
|
||||
}
|
||||
for (unsigned i = 0; i < num_args; i++)
|
||||
{
|
||||
Expr *e = args[i];
|
||||
if (!sema_analyse_expr(context, e)) return false;
|
||||
if (!sema_cast_const(e))
|
||||
{
|
||||
RETURN_SEMA_ERROR(e, "Expected a constant expression.");
|
||||
}
|
||||
}
|
||||
if (!expr_is_const_string(format_string))
|
||||
{
|
||||
RETURN_SEMA_ERROR(format_string, "Expected a constant format string.");
|
||||
}
|
||||
const char *inner_str = format_string->const_expr.bytes.ptr;
|
||||
ArraySize len = format_string->const_expr.bytes.len;
|
||||
scratch_buffer_clear();
|
||||
ArrayIndex current_index = 0;
|
||||
for (ArraySize i = 0; i < len; i++)
|
||||
{
|
||||
char c = inner_str[i];
|
||||
if (c == '%')
|
||||
{
|
||||
i++;
|
||||
switch (inner_str[i])
|
||||
{
|
||||
case 's':
|
||||
if (current_index == num_args) RETURN_SEMA_ERROR(format_string, "Too many arguments in format string.");
|
||||
expr_const_to_scratch_buffer(&(args[current_index++]->const_expr));
|
||||
continue;
|
||||
case '%':
|
||||
scratch_buffer_append_char('%');
|
||||
continue;
|
||||
default:
|
||||
RETURN_SEMA_ERROR(format_string, "Only '%%s' is supported for compile time sprintf.");
|
||||
}
|
||||
}
|
||||
scratch_buffer_append_char(c);
|
||||
}
|
||||
if (current_index != num_args)
|
||||
{
|
||||
RETURN_SEMA_ERROR(expr, "Too many arguments to sprintf.");
|
||||
}
|
||||
expr_rewrite_const_string(expr, scratch_buffer_copy());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool sema_binary_is_expr_lvalue(SemaContext *context, Expr *top_expr, Expr *expr, bool *failed_ref)
|
||||
{
|
||||
|
||||
@@ -105,6 +105,7 @@ Expr *sema_expr_analyse_ct_arg_index(SemaContext *context, Expr *index_expr, uns
|
||||
Expr *sema_ct_eval_expr(SemaContext *context, bool is_type_eval, Expr *inner, bool report_missing);
|
||||
Expr *sema_resolve_string_ident(SemaContext *context, Expr *inner, bool report_missing);
|
||||
bool sema_analyse_asm(SemaContext *context, AsmInlineBlock *block, Ast *asm_stmt);
|
||||
bool sema_expr_analyse_sprintf(SemaContext *context, Expr *expr, Expr *format_string, Expr **args, unsigned num_args);
|
||||
|
||||
bool sema_bit_assignment_check(SemaContext *context, Expr *right, Decl *member);
|
||||
CondResult sema_check_comp_time_bool(SemaContext *context, Expr *expr);
|
||||
|
||||
@@ -352,6 +352,19 @@ static inline Expr *sema_dive_into_expression(Expr *expr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool sema_catch_in_cond(Expr *cond)
|
||||
{
|
||||
ASSERT(cond->expr_kind == EXPR_COND && "Assumed cond");
|
||||
|
||||
Expr *last = VECLAST(cond->cond_expr);
|
||||
ASSERT(last);
|
||||
last = sema_dive_into_expression(last);
|
||||
|
||||
// Skip any non-unwraps
|
||||
return last->expr_kind == EXPR_CATCH;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have "if (catch x)", then we want to unwrap x in the else clause.
|
||||
**/
|
||||
@@ -1940,7 +1953,11 @@ END:
|
||||
is_invalid = context->active_scope.is_invalid;
|
||||
SCOPE_OUTER_END;
|
||||
if (is_invalid) context->active_scope.is_invalid = is_invalid;
|
||||
if (!success) return false;
|
||||
if (!success)
|
||||
{
|
||||
if (then_jump && sema_catch_in_cond(cond)) context->active_scope.is_invalid = true;
|
||||
return false;
|
||||
}
|
||||
if (then_jump)
|
||||
{
|
||||
sema_unwrappable_from_catch_in_else(context, cond);
|
||||
@@ -2903,6 +2920,8 @@ bool sema_analyse_ct_assert_stmt(SemaContext *context, Ast *statement)
|
||||
{
|
||||
if (message_expr)
|
||||
{
|
||||
unsigned len = vec_size(statement->assert_stmt.args);
|
||||
if (len && !sema_expr_analyse_sprintf(context, message_expr, message_expr, statement->assert_stmt.args, len)) return false;
|
||||
sema_error_at(context, span, "%.*s", EXPAND_EXPR_STRING(message_expr));
|
||||
}
|
||||
else
|
||||
|
||||
5
test/test_suite/assert/format_ct_assert.c3
Normal file
5
test/test_suite/assert/format_ct_assert.c3
Normal file
@@ -0,0 +1,5 @@
|
||||
fn void main()
|
||||
{
|
||||
$error "Foo %s", 1; // #error: Foo 1
|
||||
$assert 0 > 1 : "Foo %s", 0.0002; // #error: Foo 0.0002
|
||||
}
|
||||
@@ -3,7 +3,7 @@ module foo;
|
||||
fn void foo()
|
||||
{
|
||||
int? x;
|
||||
if (catch err)
|
||||
if (catch x)
|
||||
{
|
||||
z = 1233; // #error: could not be found, did you spell it right
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user