Files
c3c/src/compiler/sema_stmts.c
2024-06-06 17:40:07 +02:00

3253 lines
100 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2020 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by a LGPLv3.0
// a copy of which can be found in the LICENSE file.
#include "sema_internal.h"
static inline bool sema_analyse_asm_stmt(SemaContext *context, Ast *stmt);
static inline bool sema_analyse_assert_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_break_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_compound_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_continue_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_ct_for_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_ct_foreach_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_ct_if_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_declare_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_defer_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_expr_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_for_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_if_stmt(SemaContext *context, Ast *statement);
static inline bool sema_analyse_nextcase_stmt(SemaContext *context, Ast *statement);
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_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 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);
static inline bool sema_analyse_try_unwrap_chain(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result);
static void sema_remove_unwraps_from_try(SemaContext *c, Expr *cond);
static inline bool sema_analyse_last_cond(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result);
static inline bool sema_analyse_cond_list(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result);
static inline bool sema_analyse_cond(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result);
static inline Decl *sema_analyse_label(SemaContext *context, Ast *stmt);
static bool context_labels_exist_in_scope(SemaContext *context);
static inline bool sema_analyse_then_overwrite(SemaContext *context, Ast *statement, AstId replacement);
static inline bool sema_analyse_catch_unwrap(SemaContext *context, Expr *expr);
static inline bool sema_analyse_compound_statement_no_scope(SemaContext *context, Ast *compound_statement);
static inline bool sema_check_type_case(SemaContext *context, Type *switch_type, Ast *case_stmt, Ast **cases, unsigned index);
static inline bool sema_check_value_case(SemaContext *context, Type *switch_type, Ast *case_stmt, Ast **cases, unsigned index, bool *if_chained, bool *max_ranged);
static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, Type *switch_type, Ast **cases, ExprAnySwitch *any_switch, Decl *var_holder);
static inline bool sema_analyse_statement_inner(SemaContext *context, Ast *statement);
static bool sema_analyse_require(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan source);
static bool sema_analyse_ensure(SemaContext *context, Ast *directive);
static bool sema_analyse_optional_returns(SemaContext *context, Ast *directive);
static inline bool sema_analyse_asm_stmt(SemaContext *context, Ast *stmt)
{
if (stmt->asm_block_stmt.is_string) return sema_analyse_asm_string_stmt(context, stmt);
AsmInlineBlock *block = stmt->asm_block_stmt.block;
AstId ast_id = block->asm_stmt;
scratch_buffer_clear();
while (ast_id)
{
Ast *ast = astptr(ast_id);
ast_id = ast->next;
if (!sema_analyse_asm(context, block, ast)) return false;
}
return true;
}
/**
* assert(foo), assert(foo, message), assert(try foo), assert(try foo, message)
*
* - Using the try construct we implicitly unpack the variable if present.
* - assert(false) will implicitly make the rest of the code marked as unreachable.
* - assert might also be used for 'ensure' and in this case violating it is a compile time error.
*/
static inline bool sema_analyse_assert_stmt(SemaContext *context, Ast *statement)
{
Expr *expr = exprptr(statement->assert_stmt.expr);
// Verify that the message is a string if it exists.
Expr *message_expr = exprptrzero(statement->assert_stmt.message);
if (message_expr)
{
if (!sema_analyse_ct_expr(context, message_expr)) return false;
if (!expr_is_const_string(message_expr)) RETURN_SEMA_ERROR(message_expr, "Expected a constant string as the error message.");
FOREACH_BEGIN(Expr *e, statement->assert_stmt.args)
if (!sema_analyse_expr(context, e)) return false;
if (IS_OPTIONAL(e)) RETURN_SEMA_ERROR(e, "Optionals cannot be used as assert arguments, use '?" "?', '!' or '!!' to fix this.");
if (type_is_void(e->type)) RETURN_SEMA_ERROR(e, "This expression is of type 'void', did you make a mistake?");
FOREACH_END();
}
CondResult result_no_resolve = COND_MISSING;
if (expr_is_const_bool(expr) && expr->resolve_status == RESOLVE_DONE)
{
result_no_resolve = expr->const_expr.b ? COND_TRUE : COND_FALSE;
}
// Check the conditional inside
CondResult result = COND_MISSING;
if (!sema_analyse_cond_expr(context, expr, &result)) return false;
// If it's constant, we process it differently.
if (result != COND_MISSING)
{
// It's true, then replace the statement with a nop.
if (expr->const_expr.b)
{
statement->ast_kind = AST_NOP_STMT;
return true;
}
// Was this 'assert(false)'?
if (result_no_resolve == COND_FALSE)
{
assert(result == COND_FALSE);
// If this is a test, then assert(false) is permitted.
if (context->call_env.current_function && context->call_env.current_function->func_decl.attr_test)
{
context->active_scope.jump_end = true;
return true;
}
// Otherwise, require unreachable.
RETURN_SEMA_ERROR(expr, "Use 'unreachable' instead of 'assert(false)'.");
}
// If it's ensure (and an error) we print an error.
if (!context->active_scope.jump_end && !context->active_scope.is_dead)
{
if (message_expr && expr_is_const(message_expr) && vec_size(statement->assert_stmt.args))
{
RETURN_SEMA_ERROR(expr, "%.*s", EXPAND_EXPR_STRING(message_expr));
}
if (statement->assert_stmt.is_ensure) RETURN_SEMA_ERROR(expr, "Contract violated.");
RETURN_SEMA_ERROR(expr, "This expression will always be 'false'.");
}
// Otherwise, continue, this is fine as it can't be reached.
}
return true;
}
/**
* break and break LABEL;
*/
static inline bool sema_analyse_break_stmt(SemaContext *context, Ast *statement)
{
// If there is no break target and there is no label,
// we skip.
if (!context->break_target && !statement->contbreak_stmt.is_label)
{
if (context_labels_exist_in_scope(context))
{
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;
}
// Is jump, and set it as resolved.
context->active_scope.jump_end = true;
statement->contbreak_stmt.is_resolved = true;
AstId defer_begin;
Ast *parent;
if (statement->contbreak_stmt.label.name)
{
// If we have a label, pick it and set the parent astid to that target.
ASSIGN_DECL_OR_RET(Decl *target, sema_analyse_label(context, statement), false);
// We don't need to do any checking since all(!) label constructs support break.
parent = astptr(target->label.parent);
defer_begin = target->label.defer;
}
else
{
// Jump to the default break target.
parent = context->break_target;
defer_begin = context->break_defer;
}
assert(parent);
parent->flow.has_break = true;
statement->contbreak_stmt.ast = astid(parent);
// Append the defers.
statement->contbreak_stmt.defers = context_get_defers(context, context->active_scope.defer_last, defer_begin, true);
return true;
}
/**
* The regular { }
*/
static inline bool sema_analyse_compound_stmt(SemaContext *context, Ast *statement)
{
bool success;
bool ends_with_jump;
SCOPE_START
success = sema_analyse_compound_statement_no_scope(context, statement);
ends_with_jump = context->active_scope.jump_end;
SCOPE_END;
// If this ends with a jump, then we know we don't need to certain analysis.
context->active_scope.jump_end = ends_with_jump;
return success;
}
/**
* continue and continue FOO;
*/
static inline bool sema_analyse_continue_stmt(SemaContext *context, Ast *statement)
{
// 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;
}
AstId defer_id;
Ast *parent;
if (statement->contbreak_stmt.label.name)
{
// If we have a label grab it.
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;
}
}
else
{
// Use default defer and ast.
defer_id = context->continue_defer;
parent = context->continue_target;
}
// This makes the active scope jump.
context->active_scope.jump_end = true;
// Link the parent and add the defers.
statement->contbreak_stmt.ast = astid(parent);
statement->contbreak_stmt.defers = context_get_defers(context, context->active_scope.defer_last, defer_id, true);
return true;
}
static void sema_unwrappable_from_catch_in_else(SemaContext *c, Expr *cond)
{
assert(cond->expr_kind == EXPR_COND);
Expr *last = VECLAST(cond->cond_expr);
while (last->expr_kind == EXPR_CAST)
{
last = exprptr(last->cast_expr.expr);
}
if (!last || last->expr_kind != EXPR_CATCH_UNWRAP) return;
Expr **unwrapped = last->catch_unwrap_expr.exprs;
VECEACH(unwrapped, i)
{
Expr *expr = unwrapped[i];
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.");
// 5. Locals and globals may be unwrapped
switch (decl->var.kind)
{
case VARDECL_LOCAL:
case VARDECL_GLOBAL:
sema_unwrap_var(c, decl);
break;
default:
continue;
}
}
}
// --- Sema analyse stmts
static inline bool assert_create_from_contract(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan evaluation_location)
{
directive = copy_ast_single(directive);
Expr *declexpr = directive->contract_stmt.contract.decl_exprs;
assert(declexpr->expr_kind == EXPR_EXPRESSION_LIST);
Expr **exprs = declexpr->expression_list;
VECEACH(exprs, j)
{
Expr *expr = exprs[j];
if (expr->expr_kind == EXPR_DECL)
{
SEMA_ERROR(expr, "Only expressions are allowed.");
return false;
}
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)
{
print_error_at(evaluation_location.a ? evaluation_location : expr->span, "%s", comment);
return false;
}
Ast *assert = new_ast(AST_ASSERT_STMT, expr->span);
assert->assert_stmt.is_ensure = true;
assert->assert_stmt.expr = exprid(expr);
Expr *comment_expr = expr_new(EXPR_CONST, expr->span);
expr_rewrite_to_string(comment_expr, comment);
assert->assert_stmt.message = exprid(comment_expr);
ast_append(asserts, assert);
}
return true;
}
static inline bool sema_defer_by_result(AstId defer_top, AstId defer_bottom)
{
AstId first = 0;
while (defer_bottom != defer_top)
{
Ast *defer = astptr(defer_top);
if (defer->defer_stmt.is_catch || defer->defer_stmt.is_try) return true;
defer_top = defer->defer_stmt.prev_defer;
}
return false;
}
static inline void sema_inline_return_defers(SemaContext *context, Ast *stmt, AstId defer_top, AstId defer_bottom)
{
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))
{
stmt->return_stmt.cleanup_fail = context_get_defers(context, context->active_scope.defer_last, context->block_return_defer, false);
}
else
{
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)
{
if (!IS_OPTIONAL(ret_expr) || !context->call_env.opt_returns) return true;
if (ret_expr->expr_kind != EXPR_OPTIONAL) return true;
Expr *inner = ret_expr->inner_expr;
if (!expr_is_const(inner)) return true;
assert(ret_expr->inner_expr->const_expr.const_kind == CONST_ERR);
Decl *fault = ret_expr->inner_expr->const_expr.enum_err_val;
FOREACH_BEGIN(Decl *opt, context->call_env.opt_returns)
if (opt->decl_kind == DECL_FAULT)
{
if (fault->type->decl == opt) return true;
continue;
}
if (opt == fault) return true;
FOREACH_END();
SEMA_ERROR(ret_expr, "This value does not match declared optional returns, it needs to be declared with the other optional returns.");
return false;
}
static bool sema_analyse_macro_constant_ensures(SemaContext *context, Expr *ret_expr)
{
assert(context->current_macro);
// This is a per return check, so we don't do it if the return expression is missing,
// or if it is optional, or obviously - if there are no '@ensure'.
if (!ret_expr || !context->macro_has_ensures || IS_OPTIONAL(ret_expr)) return true;
// If the return expression can't be flattened to a constant value, then
// we won't be able to do any constant ensure checks anyway, so skip.
if (!sema_flattened_expr_is_const(context, ret_expr)) return true;
AstId doc_directive = context->current_macro->func_decl.docs;
// 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);
while (doc_directive)
{
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);
assert(checks->expr_kind == EXPR_EXPRESSION_LIST);
Expr **exprs = checks->expression_list;
FOREACH_BEGIN(Expr *expr, exprs)
if (expr->expr_kind == EXPR_DECL)
{
SEMA_ERROR(expr, "Only expressions are allowed.");
success = false;
goto END;
}
CondResult result = COND_MISSING;
if (!sema_analyse_cond_expr(context, expr, &result))
{
success = false;
goto END;
}
// 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;
SEMA_ERROR(ret_expr, "%s", comment);
success = false;
goto END;
FOREACH_END();
}
END:
SCOPE_END;
context->return_expr = return_expr_old;
return success;
}
/**
* Handle exit in a macro or in an expression block.
* @param context
* @param statement
* @return
*/
static inline bool sema_analyse_block_exit_stmt(SemaContext *context, Ast *statement)
{
bool is_macro = (context->active_scope.flags & SCOPE_MACRO) != 0;
assert(context->active_scope.flags & (SCOPE_EXPR_BLOCK | SCOPE_MACRO));
statement->ast_kind = AST_BLOCK_EXIT_STMT;
context->active_scope.jump_end = true;
Type *block_type = context->expected_block_type;
Expr *ret_expr = statement->return_stmt.expr;
if (ret_expr)
{
if (block_type)
{
if (!sema_analyse_expr_rhs(context, block_type, ret_expr, true, NULL)) return false;
}
else
{
if (!sema_analyse_expr(context, ret_expr)) return false;
}
if (is_macro && !sema_return_optional_check_is_valid_in_scope(context, ret_expr)) return false;
}
else
{
if (block_type && type_no_optional(block_type) != type_void)
{
SEMA_ERROR(statement, "Expected a return value of type %s here.", type_quoted_error_string(block_type));
return false;
}
}
statement->return_stmt.block_exit_ref = context->block_exit_ref;
sema_inline_return_defers(context, statement, context->active_scope.defer_last, context->block_return_defer);
if (is_macro && !sema_analyse_macro_constant_ensures(context, ret_expr)) return false;
vec_add(context->returns, statement);
return true;
}
/**
* Prevent the common mistake of `return &a` where "a" is a local.
* @return true if the check is ok (no such escape)
*/
INLINE bool sema_check_not_stack_variable_escape(SemaContext *context, Expr *expr)
{
Expr *outer = expr;
while (expr->expr_kind == EXPR_CAST) expr = exprptr(expr->cast_expr.expr);
// We only want && and &
if (expr->expr_kind == EXPR_SUBSCRIPT_ADDR)
{
expr = exprptr(expr->subscript_expr.expr);
goto CHECK_ACCESS;
}
if (expr->expr_kind != EXPR_UNARY) return true;
if (expr->unary_expr.operator == UNARYOP_TADDR)
{
SEMA_ERROR(outer, "A pointer to a temporary value will be invalid once the function returns. Try copying the value to the heap or the temp memory instead.");
return false;
}
if (expr->unary_expr.operator != UNARYOP_ADDR) return true;
expr = expr->unary_expr.expr;
CHECK_ACCESS:
while (expr->expr_kind == EXPR_ACCESS) expr = expr->access_expr.parent;
if (expr->expr_kind != EXPR_IDENTIFIER) return true;
Decl *decl = expr->identifier_expr.decl;
if (decl->decl_kind != DECL_VAR) return true;
switch (decl->var.kind)
{
case VARDECL_LOCAL:
if (decl->var.is_static) return true;
switch (type_flatten(decl->type)->type_kind)
{
case TYPE_POINTER:
case TYPE_SLICE:
// &foo[2] is fine if foo is a pointer or slice.
return true;
default:
break;
}
FALLTHROUGH;
case VARDECL_PARAM:
break;
default:
return true;
}
SEMA_ERROR(outer, "A pointer to a local variable will be invalid once the function returns. "
"Allocate the data on the heap or temp memory to return a pointer.");
return false;
}
/**
* We have the following possibilities:
* 1. return
* 2. return <non void expr>
* 3. return <void expr>
*
* If we are in a block or a macro expansion we need to handle it differently.
*
* @param context
* @param statement
* @return
*/
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;
}
// This might be a return in a function block or a macro which must be treated differently.
if (context->active_scope.flags & (SCOPE_EXPR_BLOCK | SCOPE_MACRO))
{
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;
Type *expected_rtype = context->rtype;
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)) return false;
if (!sema_check_not_stack_variable_escape(context, return_expr)) return false;
if (!sema_return_optional_check_is_valid_in_scope(context, return_expr)) return false;
}
else
{
if (type_no_optional(expected_rtype)->canonical != type_void)
{
SEMA_ERROR(statement, "Expected to return a result of type %s.", type_to_error_string(expected_rtype));
return false;
}
statement->return_stmt.cleanup = context_get_defers(context, context->active_scope.defer_last, 0, true);
return true;
}
// Process any ensures.
sema_inline_return_defers(context, statement, context->active_scope.defer_last, 0);
if (context->call_env.ensures)
{
// Never generate an expression.
if (return_expr && return_expr->expr_kind == EXPR_OPTIONAL) goto SKIP_ENSURE;
AstId first = 0;
AstId *append_id = &first;
// Creating an assign statement
AstId doc_directive = context->call_env.current_function->func_decl.docs;
context->return_expr = return_expr;
while (doc_directive)
{
Ast *directive = astptr(doc_directive);
if (directive->contract_stmt.kind == CONTRACT_ENSURE)
{
bool success;
SCOPE_START_WITH_FLAGS(SCOPE_ENSURE);
success = assert_create_from_contract(context, directive, &append_id, statement->span);
SCOPE_END;
if (!success) return false;
}
doc_directive = directive->next;
}
if (!first) goto SKIP_ENSURE;
if (statement->return_stmt.cleanup)
{
Ast *last = ast_last(astptr(statement->return_stmt.cleanup));
last->next = first;
}
else
{
statement->return_stmt.cleanup = first;
}
}
SKIP_ENSURE:;
assert(type_no_optional(statement->return_stmt.expr->type)->canonical == type_no_optional(expected_rtype)->canonical);
return true;
}
static inline bool sema_expr_valid_try_expression(Expr *expr)
{
switch (expr->expr_kind)
{
case EXPR_BITASSIGN:
case EXPR_CATCH_UNWRAP:
case EXPR_COND:
case EXPR_POISONED:
case EXPR_CT_AND_OR:
case EXPR_CT_ARG:
case EXPR_CT_CALL:
case EXPR_CT_CASTABLE:
case EXPR_CT_IS_CONST:
case EXPR_CT_DEFINED:
case EXPR_CT_EVAL:
case EXPR_CT_IDENT:
UNREACHABLE
case EXPR_BINARY:
case EXPR_POINTER_OFFSET:
case EXPR_CAST:
case EXPR_UNARY:
case EXPR_POST_UNARY:
case EXPR_TERNARY:
case EXPR_LAST_FAULT:
return false;
case EXPR_BITACCESS:
case EXPR_BUILTIN:
case EXPR_BUILTIN_ACCESS:
case EXPR_CALL:
case EXPR_COMPILER_CONST:
case EXPR_COMPOUND_LITERAL:
case EXPR_CONST:
case EXPR_DECL:
case EXPR_DESIGNATED_INITIALIZER_LIST:
case EXPR_DESIGNATOR:
case EXPR_EMBED:
case EXPR_EXPRESSION_LIST:
case EXPR_EXPR_BLOCK:
case EXPR_MACRO_BLOCK:
case EXPR_OPTIONAL:
case EXPR_FORCE_UNWRAP:
case EXPR_GENERIC_IDENT:
case EXPR_GROUP:
case EXPR_HASH_IDENT:
case EXPR_IDENTIFIER:
case EXPR_INITIALIZER_LIST:
case EXPR_LAMBDA:
case EXPR_MACRO_BODY_EXPANSION:
case EXPR_NOP:
case EXPR_OPERATOR_CHARS:
case EXPR_OTHER_CONTEXT:
case EXPR_RETHROW:
case EXPR_RETVAL:
case EXPR_SLICE:
case EXPR_SLICE_ASSIGN:
case EXPR_SLICE_COPY:
case EXPR_STRINGIFY:
case EXPR_SUBSCRIPT:
case EXPR_SWIZZLE:
case EXPR_SUBSCRIPT_ADDR:
case EXPR_SUBSCRIPT_ASSIGN:
case EXPR_BENCHMARK_HOOK:
case EXPR_TEST_HOOK:
case EXPR_TRY_UNWRAP:
case EXPR_TRY_UNWRAP_CHAIN:
case EXPR_TYPEID:
case EXPR_TYPEID_INFO:
case EXPR_TYPEINFO:
case EXPR_ANYSWITCH:
case EXPR_VASPLAT:
case EXPR_MACRO_BODY:
case EXPR_ACCESS:
case EXPR_ASM:
return true;
}
UNREACHABLE
}
static inline bool sema_analyse_try_unwrap(SemaContext *context, Expr *expr)
{
assert(expr->expr_kind == EXPR_TRY_UNWRAP);
Expr *ident = expr->try_unwrap_expr.variable;
Expr *optional = expr->try_unwrap_expr.init;
// Case A. Unwrapping a single variable.
if (!optional)
{
if (!sema_analyse_expr(context, ident)) return false;
// The `try foo()` case.
if (ident->expr_kind != EXPR_IDENTIFIER)
{
if (!sema_expr_valid_try_expression(ident))
{
RETURN_SEMA_ERROR(ident, "You need to assign this expression to something in order to use it with 'if (try ...)'.");
}
expr->try_unwrap_expr.optional = ident;
expr->try_unwrap_expr.lhs = NULL;
expr->try_unwrap_expr.assign_existing = true;
expr->type = type_bool;
return true;
}
Decl *decl = ident->identifier_expr.decl;
if (decl->decl_kind != DECL_VAR)
{
SEMA_ERROR(ident, "Expected this to be the name of an optional variable, but it isn't. Did you mistype?");
return false;
}
if (!IS_OPTIONAL(decl))
{
if (decl->var.kind == VARDECL_UNWRAPPED)
{
SEMA_ERROR(ident, "This variable is already unwrapped, so you cannot use 'try' on it again, please remove the 'try'.");
return false;
}
SEMA_ERROR(ident, "Expected this variable to be an optional, otherwise it can't be used for unwrap, maybe you didn't intend to use 'try'?");
return false;
}
expr->try_unwrap_expr.decl = decl;
expr->type = type_bool;
sema_unwrap_var(context, decl);
expr->resolve_status = RESOLVE_DONE;
return true;
}
// Case B. We are unwrapping to a variable that may or may not exist.
bool implicit_declaration = false;
TypeInfo *var_type = expr->try_unwrap_expr.type;
// 1. Check if we are doing an implicit declaration.
if (!var_type && ident->expr_kind == EXPR_IDENTIFIER)
{
implicit_declaration = !sema_symbol_is_defined_in_scope(context, ident->identifier_expr.ident);
}
// 2. If we have a type for the variable, resolve it.
if (var_type)
{
if (!sema_resolve_type_info(context, var_type, RESOLVE_TYPE_DEFAULT)) return false;
if (IS_OPTIONAL(var_type))
{
SEMA_ERROR(var_type, "Only non-optional types may be used as types for 'try', please remove the '!'.");
return false;
}
}
// 3. We interpret this as an assignment to an existing variable.
if (!var_type && !implicit_declaration)
{
// 3a. Resolve the identifier.
if (!sema_analyse_expr_lvalue(context, ident)) return false;
// 3b. Make sure it's assignable
if (!sema_expr_check_assign(context, ident)) return false;
// 3c. It can't be optional either.
if (IS_OPTIONAL(ident))
{
if (ident->expr_kind == EXPR_IDENTIFIER)
{
SEMA_ERROR(ident, "This is an optional variable, you should only have non-optional variables on the left side unless you use 'try' without '='.");
}
else
{
SEMA_ERROR(ident, "This is an optional expression, it can't go on the left hand side of a 'try'.");
}
return false;
}
// 3d. We can now analyse the expression using the variable type.
if (!sema_analyse_expr(context, optional)) return false;
if (!IS_OPTIONAL(optional))
{
SEMA_ERROR(optional, "Expected an optional expression to 'try' here. If it isn't an optional, remove 'try'.");
return false;
}
if (!cast_implicit(context, optional, ident->type)) return false;
expr->try_unwrap_expr.assign_existing = true;
expr->try_unwrap_expr.lhs = ident;
}
else
{
// 4. We are creating a new variable
// 4a. If we had a variable type, then our expression must be an identifier.
if (ident->expr_kind != EXPR_IDENTIFIER)
{
SEMA_ERROR(ident, "A variable name was expected here.");
return false;
}
assert(ident->resolve_status != RESOLVE_DONE);
if (ident->identifier_expr.path)
{
SEMA_ERROR(ident->identifier_expr.path, "The variable may not have a path.");
return false;
}
if (ident->identifier_expr.is_const)
{
SEMA_ERROR(ident, "Expected a variable starting with a lower case letter.");
return false;
}
// 4b. Evaluate the expression
if (!sema_analyse_expr(context, optional)) return false;
if (!IS_OPTIONAL(optional))
{
SEMA_ERROR(optional, "Expected an optional expression to 'try' here. If it isn't an optional, remove 'try'.");
return false;
}
if (var_type)
{
if (!cast_implicit(context, optional, var_type->type)) return false;
}
// 4c. Create a type_info if needed.
if (!var_type)
{
var_type = type_info_new_base(optional->type->optional, optional->span);
}
// 4d. A new declaration is created.
Decl *decl = decl_new_var(ident->identifier_expr.ident, ident->span, var_type, VARDECL_LOCAL);
// 4e. Analyse it
if (!sema_analyse_var_decl(context, decl, true)) return false;
expr->try_unwrap_expr.decl = decl;
}
expr->try_unwrap_expr.optional = optional;
expr->type = type_bool;
expr->resolve_status = RESOLVE_DONE;
return true;
}
static inline bool sema_analyse_try_unwrap_chain(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result)
{
assert(cond_type == COND_TYPE_UNWRAP_BOOL || cond_type == COND_TYPE_UNWRAP);
assert(expr->expr_kind == EXPR_TRY_UNWRAP_CHAIN);
Expr **chain = expr->try_unwrap_chain_expr;
VECEACH(expr->try_unwrap_chain_expr, i)
{
Expr *chain_element = chain[i];
if (chain_element->expr_kind == EXPR_TRY_UNWRAP)
{
if (!sema_analyse_try_unwrap(context, chain_element)) return false;
continue;
}
bool old_is_fail = *result == COND_FALSE;
if (!sema_analyse_cond_expr(context, chain_element, result)) return false;
if (old_is_fail) *result = COND_FALSE;
}
expr->type = type_bool;
expr->resolve_status = RESOLVE_DONE;
return true;
}
static inline bool sema_analyse_catch_unwrap(SemaContext *context, Expr *expr)
{
Expr *ident = expr->catch_unwrap_expr.variable;
bool implicit_declaration = false;
TypeInfo *type = expr->catch_unwrap_expr.type;
if (!type && !ident)
{
expr->catch_unwrap_expr.lhs = NULL;
expr->catch_unwrap_expr.decl = NULL;
goto RESOLVE_EXPRS;
}
if (!type && ident->expr_kind == EXPR_IDENTIFIER)
{
implicit_declaration = !sema_symbol_is_defined_in_scope(context, ident->identifier_expr.ident);
}
if (!type && !implicit_declaration)
{
if (!sema_analyse_expr_lvalue(context, ident)) return false;
if (!sema_expr_check_assign(context, ident)) return false;
if (ident->type->canonical != type_anyfault)
{
SEMA_ERROR(ident, "Expected the variable to have the type %s, not %s.", type_quoted_error_string(type_anyfault),
type_quoted_error_string(ident->type));
return false;
}
expr->catch_unwrap_expr.lhs = ident;
expr->catch_unwrap_expr.decl = NULL;
}
else
{
type = type ? type : type_info_new_base(type_anyfault, expr->span);
if (!sema_resolve_type_info(context, type, RESOLVE_TYPE_DEFAULT)) return false;
if (type->type->canonical != type_anyfault)
{
SEMA_ERROR(type, "Expected the type to be %s, not %s.", type_quoted_error_string(type_anyfault),
type_quoted_error_string(type->type));
return false;
}
if (ident->expr_kind != EXPR_IDENTIFIER)
{
SEMA_ERROR(ident, "A variable name was expected here.");
return false;
}
assert(ident->resolve_status != RESOLVE_DONE);
if (ident->identifier_expr.path)
{
SEMA_ERROR(ident->identifier_expr.path, "The variable may not have a path.");
return false;
}
if (ident->identifier_expr.is_const)
{
SEMA_ERROR(ident, "Expected a variable starting with a lower case letter.");
return false;
}
// 4d. A new declaration is created.
Decl *decl = decl_new_var(ident->identifier_expr.ident, ident->span, type, VARDECL_LOCAL);
decl->var.no_init = true;
// 4e. Analyse it
if (!sema_analyse_var_decl(context, decl, true)) return false;
expr->catch_unwrap_expr.decl = decl;
expr->catch_unwrap_expr.lhs = NULL;
}
RESOLVE_EXPRS:;
Expr **exprs = expr->catch_unwrap_expr.exprs;
VECEACH(exprs, i)
{
Expr *fail = exprs[i];
if (!sema_analyse_expr(context, fail)) return false;
if (!type_is_optional(fail->type))
{
RETURN_SEMA_ERROR(fail, "This expression is not optional, did you add it by mistake?");
}
}
expr->type = type_anyfault;
expr->resolve_status = RESOLVE_DONE;
return true;
}
static void sema_remove_unwraps_from_try(SemaContext *c, Expr *cond)
{
assert(cond->expr_kind == EXPR_COND);
Expr *last = VECLAST(cond->cond_expr);
if (!last || last->expr_kind != EXPR_TRY_UNWRAP_CHAIN) return;
Expr **chain = last->try_unwrap_chain_expr;
VECEACH(chain, i)
{
Expr *expr = chain[i];
if (expr->expr_kind != EXPR_TRY_UNWRAP) continue;
if (expr->try_unwrap_expr.assign_existing) continue;
if (expr->try_unwrap_expr.optional)
{
sema_erase_var(c, expr->try_unwrap_expr.decl);
}
else
{
sema_erase_unwrapped(c, expr->try_unwrap_expr.decl);
}
}
}
static inline bool sema_analyse_last_cond(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result)
{
switch (expr->expr_kind)
{
case EXPR_TRY_UNWRAP_CHAIN:
if (cond_type != COND_TYPE_UNWRAP_BOOL && cond_type != COND_TYPE_UNWRAP)
{
SEMA_ERROR(expr, "Try unwrapping is only allowed inside of a 'while' or 'if' conditional.");
return false;
}
return sema_analyse_try_unwrap_chain(context, expr, cond_type, result);
case EXPR_CATCH_UNWRAP:
if (cond_type != COND_TYPE_UNWRAP_BOOL && cond_type != COND_TYPE_UNWRAP)
{
SEMA_ERROR(expr, "Catch unwrapping is only allowed inside of a 'while' or 'if' conditional, maybe '@catch(<expr>)' will do what you need?");
return false;
}
return sema_analyse_catch_unwrap(context, expr);
default:
break;
}
if (cond_type != COND_TYPE_EVALTYPE_VALUE) goto NORMAL_EXPR;
// Now we're analysing the last expression in a switch.
// Case 1: switch (var = variant_expr)
if (expr->expr_kind == EXPR_BINARY && expr->binary_expr.operator == BINARYOP_ASSIGN)
{
// No variable on the lhs? Then it can't be an any unwrap.
Expr *left = exprptr(expr->binary_expr.left);
if (left->resolve_status == RESOLVE_DONE || left->expr_kind != EXPR_IDENTIFIER || left->identifier_expr.path) goto NORMAL_EXPR;
// Does the identifier exist in the parent scope?
// then again it can't be an any unwrap.
if (sema_symbol_is_defined_in_scope(context, left->identifier_expr.ident)) goto NORMAL_EXPR;
Expr *right = exprptr(expr->binary_expr.right);
bool is_deref = right->expr_kind == EXPR_UNARY && right->unary_expr.operator == UNARYOP_DEREF;
if (is_deref) right = right->unary_expr.expr;
if (!sema_analyse_expr_rhs(context, NULL, right, false, NULL)) return false;
Type *type = right->type->canonical;
if (type == type_get_ptr(type_any) && is_deref)
{
is_deref = false;
right = exprptr(expr->binary_expr.right);
if (!sema_analyse_expr_rhs(context, NULL, right, false, NULL)) return false;
}
if (type != type_any) goto NORMAL_EXPR;
// Found an expansion here
expr->expr_kind = EXPR_ANYSWITCH;
expr->any_switch.new_ident = left->identifier_expr.ident;
expr->any_switch.span = left->span;
expr->any_switch.any_expr = right;
expr->any_switch.is_deref = is_deref;
expr->any_switch.is_assign = true;
expr->resolve_status = RESOLVE_DONE;
expr->type = type_typeid;
return true;
}
if (!sema_analyse_expr(context, expr)) return false;
Type *type = expr->type->canonical;
if (type != type_any) return true;
if (expr->expr_kind == EXPR_IDENTIFIER)
{
Decl *decl = expr->identifier_expr.decl;
expr->expr_kind = EXPR_ANYSWITCH;
expr->any_switch.is_deref = false;
expr->any_switch.is_assign = false;
expr->any_switch.variable = decl;
expr->type = type_typeid;
expr->resolve_status = RESOLVE_DONE;
return true;
}
return true;
NORMAL_EXPR:;
return sema_analyse_expr(context, expr);
}
/**
* An decl-expr-list is a list of a mixture of declarations and expressions.
* The last declaration or expression is propagated. So for example:
*
* int a = 3, b = 4, float c = 4.0
*
* In this case the final value is 4.0 and the type is float.
*/
static inline bool sema_analyse_cond_list(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result)
{
assert(expr->expr_kind == EXPR_COND);
Expr **dexprs = expr->cond_expr;
unsigned entries = vec_size(dexprs);
// 1. Special case, there are no entries, so the type is void
if (entries == 0)
{
expr->type = type_void;
return true;
}
// 2. Walk through each of our declarations / expressions as if they were regular expressions.
for (unsigned i = 0; i < entries - 1; i++)
{
if (!sema_analyse_expr(context, dexprs[i])) return false;
}
if (!sema_analyse_last_cond(context, dexprs[entries - 1], cond_type, result)) return false;
expr->type = dexprs[entries - 1]->type;
expr->resolve_status = RESOLVE_DONE;
return true;
}
/**
* Analyse a conditional expression:
*
* 1. The middle statement in a for loop
* 2. The expression in an if, while
* 3. The expression in a switch
*
* @param context the current context
* @param expr the conditional to evaluate
* @param cast_to_bool if the result is to be cast to bool after
* @return true if it passes analysis.
*/
static inline bool sema_analyse_cond(SemaContext *context, Expr *expr, CondType cond_type, CondResult *result)
{
bool cast_to_bool = cond_type == COND_TYPE_UNWRAP_BOOL;
assert(expr->expr_kind == EXPR_COND && "Conditional expressions should always be of type EXPR_DECL_LIST");
// 1. Analyse the declaration list.
ScopeFlags current_flags = context->active_scope.flags;
context->active_scope.flags |= SCOPE_COND;
bool success = sema_analyse_cond_list(context, expr, cond_type, result);
context->active_scope.flags = current_flags;
if (!success) return false;
// 2. If we get "void", either through a void call or an empty list,
// signal that.
if (type_is_void(expr->type))
{
SEMA_ERROR(expr, cast_to_bool ? "Expected a boolean expression." : "Expected an expression resulting in a value.");
return false;
}
// 3. We look at the last element (which is guaranteed to exist because
// the type was not void.
Expr *last = VECLAST(expr->cond_expr);
if (last->expr_kind == EXPR_DECL)
{
// 3c. The declaration case
Decl *decl = last->decl_expr;
Expr *init = decl->var.init_expr;
// 3d. We expect an initialization for the last declaration.
if (!init)
{
SEMA_ERROR(last, "Expected a declaration with initializer.");
return false;
}
// 3e. Expect that it isn't an optional
if (IS_OPTIONAL(init))
{
return sema_error_failed_cast(context, last, last->type, cast_to_bool ? type_bool : init->type);
return false;
}
if (cast_to_bool && cast_to_bool_kind(typeget(decl->var.type_info)) == CAST_ERROR)
{
SEMA_ERROR(last->decl_expr->var.init_expr, "The expression needs to be convertible to a boolean.");
return false;
}
if (cast_to_bool && expr_is_const_bool(init))
{
*result = init->const_expr.b ? COND_TRUE : COND_FALSE;
}
return true;
}
// 3a. Check for optional in case of an expression.
if (IS_OPTIONAL(last))
{
if (type_is_void(type_no_optional(last->type)) && cast_to_bool)
{
SEMA_ERROR(last, "Use '@ok(<expr>)' or '@catch(<expr>)' to explicitly convert a 'void!' to a boolean.");
return false;
}
SEMA_ERROR(last, "The expression may not be an optional, but was %s.", type_quoted_error_string(last->type));
return false;
}
// 3b. Cast to bool if that is needed
if (cast_to_bool)
{
if (!cast_explicit(context, last, type_bool)) return false;
}
if (expr_is_const_bool(last))
{
*result = last->const_expr.b ? COND_TRUE : COND_FALSE;
}
return true;
}
static inline bool sema_analyse_decls_stmt(SemaContext *context, Ast *statement)
{
bool should_nop = true;
FOREACH_BEGIN_IDX(i, Decl *decl, statement->decls_stmt)
VarDeclKind kind = decl->var.kind;
if (kind == VARDECL_LOCAL_CT_TYPE || kind == VARDECL_LOCAL_CT)
{
if (!sema_analyse_var_decl_ct(context, decl)) return false;
statement->decls_stmt[i] = NULL;
}
else
{
if (!sema_analyse_var_decl(context, decl, true)) return false;
should_nop = false;
}
FOREACH_END();
if (should_nop) statement->ast_kind = AST_NOP_STMT;
return true;
}
static inline bool sema_analyse_declare_stmt(SemaContext *context, Ast *statement)
{
VarDeclKind kind = statement->declare_stmt->var.kind;
bool erase = kind == VARDECL_LOCAL_CT_TYPE || kind == VARDECL_LOCAL_CT;
if (!sema_analyse_var_decl(context, statement->declare_stmt, true)) return false;
if (erase) statement->ast_kind = AST_NOP_STMT;
return true;
}
static inline bool sema_analyse_expr_stmt(SemaContext *context, Ast *statement)
{
Expr *expr = statement->expr_stmt;
if (!sema_analyse_expr(context, expr)) return false;
if (!sema_expr_check_discard(context, expr)) return false;
switch (expr->expr_kind)
{
case EXPR_CALL:
if (expr->call_expr.no_return) context->active_scope.jump_end = true;
break;
case EXPR_MACRO_BLOCK:
if (expr->macro_block.is_noreturn) context->active_scope.jump_end = true;
break;
case EXPR_CONST:
// Remove all const statements.
statement->ast_kind = AST_NOP_STMT;
break;
default:
break;
}
return true;
}
bool sema_analyse_defer_stmt_body(SemaContext *context, Ast *statement)
{
Ast *body = astptr(statement->defer_stmt.body);
if (body->ast_kind == AST_DEFER_STMT)
{
RETURN_SEMA_ERROR(body, "A defer may not have a body consisting of a raw 'defer', this looks like a mistake.");
}
if (body->ast_kind != AST_COMPOUND_STMT)
{
Ast *new_body = new_ast(AST_COMPOUND_STMT, body->span);
new_body->compound_stmt.first_stmt = astid(body);
body = new_body;
statement->defer_stmt.body = astid(body);
}
body->compound_stmt.parent_defer = astid(statement);
bool success = true;
SCOPE_START
context->active_scope.defer_last = 0;
context->active_scope.defer_start = 0;
context->active_scope.in_defer = statement;
PUSH_BREAKCONT(NULL);
PUSH_NEXT(NULL, NULL);
// Only ones allowed.
context->active_scope.flags = 0;
success = sema_analyse_statement(context, body);
POP_BREAKCONT();
POP_NEXT();
// We should never need to replace any defers here.
SCOPE_END;
return success;
}
static inline bool sema_analyse_defer_stmt(SemaContext *context, Ast *statement)
{
if (!sema_analyse_defer_stmt_body(context, statement)) return false;
statement->defer_stmt.prev_defer = context->active_scope.defer_last;
context->active_scope.defer_last = astid(statement);
return true;
}
static inline bool sema_analyse_for_cond(SemaContext *context, ExprId *cond_ref, bool *infinite)
{
ExprId cond_id = *cond_ref;
if (!cond_id)
{
*infinite = true;
return true;
}
Expr *cond = exprptr(cond_id);
CondResult result = COND_MISSING;
if (cond->expr_kind == EXPR_COND)
{
if (!sema_analyse_cond(context, cond, COND_TYPE_UNWRAP_BOOL, &result)) return false;
}
else
{
if (!sema_analyse_cond_expr(context, cond, &result)) return false;
}
// If this is const true, then set this to infinite and remove the expression.
if (result == COND_TRUE)
{
if (cond->expr_kind != EXPR_COND || vec_size(cond->cond_expr) == 1)
{
cond = NULL;
}
*infinite = true;
}
else
{
*infinite = false;
}
*cond_ref = cond ? exprid(cond) : 0;
return true;
}
static inline bool sema_analyse_for_stmt(SemaContext *context, Ast *statement)
{
bool success = true;
bool is_infinite = false;
Ast *body = astptr(statement->for_stmt.body);
assert(body);
if (body->ast_kind == AST_DEFER_STMT)
{
RETURN_SEMA_ERROR(body, "Looping over a raw 'defer' is not allowed, was this a mistake?");
}
bool do_loop = statement->for_stmt.flow.skip_first;
if (body->ast_kind != AST_COMPOUND_STMT && do_loop)
{
RETURN_SEMA_ERROR(body, "A do loop must use { } around its body.");
}
// Enter for scope
SCOPE_OUTER_START
if (statement->for_stmt.init)
{
success = sema_analyse_expr(context, exprptr(statement->for_stmt.init));
}
// Conditional scope start
SCOPE_START_WITH_LABEL(statement->for_stmt.flow.label)
if (!do_loop)
{
if (!sema_analyse_for_cond(context, &statement->for_stmt.cond, &is_infinite) || !success)
{
SCOPE_ERROR_END_OUTER();
return false;
}
}
PUSH_BREAKCONT(statement);
success = sema_analyse_statement(context, body);
statement->for_stmt.flow.no_exit = context->active_scope.jump_end;
POP_BREAKCONT();
// End for body scope
context_pop_defers_and_replace_ast(context, body);
SCOPE_END;
if (statement->for_stmt.flow.skip_first)
{
SCOPE_START
if (!sema_analyse_for_cond(context, &statement->for_stmt.cond, &is_infinite) || !success)
{
SCOPE_ERROR_END_OUTER();
return false;
}
SCOPE_END;
// Rewrite do { } while(true) to while(true) { }
if (is_infinite)
{
assert(!statement->for_stmt.cond);
statement->for_stmt.flow.skip_first = false;
}
}
if (success && statement->for_stmt.incr)
{
// Incr scope start
SCOPE_START
success = sema_analyse_expr(context, exprptr(statement->for_stmt.incr));
// Incr scope end
SCOPE_END;
}
// End for body scope
context_pop_defers_and_replace_ast(context, statement);
SCOPE_OUTER_END;
if (is_infinite && !statement->for_stmt.flow.has_break)
{
context->active_scope.jump_end = true;
}
return success;
}
/**
* foreach_stmt ::= foreach
* @param context
* @param statement
* @return
*/
static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statement)
{
// Pull out the relevant data.
Decl *var = declptr(statement->foreach_stmt.variable);
Decl *index = declptrzero(statement->foreach_stmt.index);
Expr *enumerator = exprptr(statement->foreach_stmt.enumeration);
AstId body = statement->foreach_stmt.body;
AstId first_stmt = 0;
AstId *succ = &first_stmt;
Expr **expressions = NULL;
bool is_reverse = statement->foreach_stmt.is_reverse;
bool value_by_ref = statement->foreach_stmt.value_by_ref;
bool success = true;
// First fold the enumerator expression, removing any () around it.
while (enumerator->expr_kind == EXPR_GROUP) enumerator = enumerator->inner_expr;
bool iterator_based = false;
// Conditional scope start
SCOPE_START
// In the case of foreach (int x : { 1, 2, 3 }) we will infer the int[] type, so pick out the number of elements.
Type *inferred_type = NULL;
// We may have an initializer list, in this case we rely on an inferred type.
if (expr_is_init_list(enumerator) || expr_is_const_initializer(enumerator))
{
bool may_be_array;
bool is_const_size;
MemberIndex size = sema_get_initializer_const_array_size(context, enumerator, &may_be_array, &is_const_size);
if (!may_be_array)
{
SEMA_ERROR(enumerator,
"This initializer appears to be a struct initializer when, an array initializer was expected.");
return SCOPE_POP_ERROR();
}
if (!is_const_size)
{
SEMA_ERROR(enumerator, "Only constant sized initializers may be implicitly initialized.");
return SCOPE_POP_ERROR();
}
if (size < 0)
{
SEMA_ERROR(enumerator, "The initializer mixes designated initialization with array initialization.");
return SCOPE_POP_ERROR();
}
assert(size >= 0);
TypeInfo *variable_type_info = vartype(var);
if (!variable_type_info)
{
SEMA_ERROR(var, "Add the type of your variable here if you want to iterate over an initializer list.");
return SCOPE_POP_ERROR();
}
// First infer the type of the variable.
if (!sema_resolve_type_info(context, variable_type_info, RESOLVE_TYPE_DEFAULT)) return false;
// And create the inferred type:
inferred_type = type_get_array(variable_type_info->type, (ArraySize)size);
}
// because we don't want the index + variable to move into the internal scope
if (!sema_analyse_inferred_expr(context, inferred_type, enumerator))
{
// Exit early here, because semantic checking might be messed up otherwise.
return SCOPE_POP_ERROR();
}
// And pop the cond scope.
SCOPE_END;
if (IS_OPTIONAL(enumerator))
{
SEMA_ERROR(enumerator, "The expression may not be optional.");
return false;
}
if (statement->foreach_stmt.index_by_ref)
{
assert(index);
SEMA_ERROR(index, "The index cannot be held by reference, did you accidentally add a '&'?");
return false;
}
// Insert a single deref as needed.
Type *flattened_type = enumerator->type->canonical;
if (flattened_type->type_kind == TYPE_POINTER)
{
// Something like Foo** will not be dereferenced, only Foo*
if (flattened_type->pointer->type_kind == TYPE_POINTER)
{
SEMA_ERROR(enumerator, "It is not possible to enumerate an expression of type %s.", type_quoted_error_string(enumerator->type));
return false;
}
expr_rewrite_insert_deref(enumerator);
}
// At this point we should have dereferenced any pointer or bailed.
assert(!type_is_pointer(enumerator->type));
// Check that we can even index this expression.
Type *value_type = type_get_indexed_type(enumerator->type);
if (value_type && value_by_ref) value_type = type_get_ptr(value_type);
Decl *len = NULL;
Decl *index_macro = NULL;
Type *index_type = type_usz;
if (!value_type)
{
len = sema_find_operator(context, enumerator->type, OVERLOAD_LEN);
Decl *by_val = sema_find_operator(context, enumerator->type, OVERLOAD_ELEMENT_AT);
Decl *by_ref = sema_find_operator(context, enumerator->type, OVERLOAD_ELEMENT_REF);
if (!len || (!by_val && !by_ref))
{
SEMA_ERROR(enumerator, "It's not possible to enumerate an expression of type %s.", type_quoted_error_string(enumerator->type));
return false;
}
if (!by_ref && value_by_ref)
{
SEMA_ERROR(enumerator, "%s does not support 'foreach' with the value by reference.", type_quoted_error_string(enumerator->type));
return false;
}
if (!decl_ok(len) || !decl_ok(by_val) || !decl_ok(by_ref)) return false;
index_macro = value_by_ref ? by_ref : by_val;
assert(index_macro);
index_type = index_macro->func_decl.signature.params[1]->type;
if (!type_is_integer(index_type))
{
SEMA_ERROR(enumerator, "Only integer indexed types may be used with foreach.");
return false;
}
TypeInfoId rtype = index_macro->func_decl.signature.rtype;
value_type = rtype ? type_infoptr(rtype)->type : NULL;
}
TypeInfo *type_info = vartype(var);
// Set up the value, assigning the type as needed.
// Element *value @noinit
if (!type_info)
{
type_info = type_info_new_base(value_type, var->span);
var->var.type_info = type_infoid(type_info);
}
if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false;
if (type_is_optional(type_info->type))
{
SEMA_ERROR(type_info, "The variable may not be an optional.");
return false;
}
// Set up the optional index parameter
Type *index_var_type = NULL;
if (index)
{
TypeInfo *idx_type_info = vartype(index);
if (!idx_type_info)
{
idx_type_info = type_info_new_base(index_type, enumerator->span);
index->var.type_info = type_infoid(idx_type_info);
}
if (!sema_resolve_type_info(context, idx_type_info, RESOLVE_TYPE_DEFAULT)) return false;
index_var_type = idx_type_info->type;
if (type_is_optional(index_var_type))
{
SEMA_ERROR(idx_type_info, "The index may not be an optional.");
return false;
}
if (!type_is_integer(type_flatten(index_var_type)))
{
SEMA_ERROR(idx_type_info,
"Index must be an integer type, '%s' is not valid.",
type_to_error_string(index_var_type));
return false;
}
}
// We either have "foreach (x : some_var)" or "foreach (x : some_call())"
// So we grab the former by address (implicit &) and the latter as the value.
assert(enumerator->resolve_status == RESOLVE_DONE);
bool is_addr = false;
bool is_variable = false;
if (enumerator->expr_kind == EXPR_IDENTIFIER)
{
enumerator->identifier_expr.decl->var.is_written = true;
is_variable = true;
}
else if (expr_may_addr(enumerator))
{
is_addr = true;
expr_insert_addr(enumerator);
}
Decl *temp = NULL;
if (is_variable)
{
temp = enumerator->identifier_expr.decl;
}
else
{
// Store either "Foo* __enum$ = &some_var;" or "Foo __enum$ = some_call()"
temp = decl_new_generated_var(enumerator->type, VARDECL_LOCAL, enumerator->span);
vec_add(expressions, expr_generate_decl(temp, enumerator));
}
// Create @__enum$.len() or @(*__enum$).len()
Expr *enum_val = expr_variable(temp);
enum_val->span = enumerator->span;
if (is_addr) expr_rewrite_insert_deref(enum_val);
Type *enumerator_type = type_flatten(enum_val->type);
Expr *len_call;
ArraySize array_len = 0;
if (len)
{
len_call = expr_new(EXPR_CALL, enumerator->span);
if (!sema_insert_method_call(context, len_call, len, enum_val, NULL)) return false;
}
else
{
if (enumerator_type->type_kind == TYPE_ARRAY)
{
array_len = enumerator_type->array.len;
len_call = NULL;
}
else
{
len_call = expr_new(EXPR_BUILTIN_ACCESS, enumerator->span);
if (!sema_analyse_expr(context, enum_val)) return false;
len_call->builtin_access_expr.inner = exprid(enum_val);
len_call->builtin_access_expr.kind = ACCESS_LEN;
len_call->resolve_status = RESOLVE_DONE;
len_call->type = type_isz;
}
}
bool is_single_pass = array_len == 1;
if (is_single_pass)
{
is_reverse = false;
}
Decl *idx_decl = decl_new_generated_var(index_type, VARDECL_LOCAL, index ? index->span : enumerator->span);
// IndexType __len$ = (IndexType)(@__enum$.len())
Decl *len_decl = NULL;
if (is_reverse)
{
if (!len_call)
{
// Create const len if missing.
len_call = expr_new_const_int(enumerator->span, type_isz, array_len);
}
if (!cast_implicit(context, len_call, index_type)) return false;
// __idx$ = (IndexType)(@__enum$.len()) (or const)
vec_add(expressions, expr_generate_decl(idx_decl, len_call));
}
else
{
if (len_call)
{
len_decl = decl_new_generated_var(index_type, VARDECL_LOCAL, enumerator->span);
if (!cast_implicit_silent(context, len_call, index_type))
{
SEMA_ERROR(enumerator,
"'foreach' is not supported, as the length %s cannot "
"be cast implicitly cast to %s - please update your definition.",
type_quoted_error_string(len_call->type), type_quoted_error_string(index_type));
if (len)
{
SEMA_NOTE(len, "The definition of 'len()' is here.");
decl_poison(len);
}
if (index_macro)
{
SEMA_NOTE(index_macro, "The index definition is here.");
decl_poison(index_macro);
}
return false;
}
vec_add(expressions, expr_generate_decl(len_decl, len_call));
}
Expr *idx_init = expr_new_const_int(idx_decl->span, index_type, 0);
vec_add(expressions, expr_generate_decl(idx_decl, idx_init));
}
// Add all declarations to the init
Expr *init_expr = expr_new(EXPR_EXPRESSION_LIST, var->span);
init_expr->expression_list = expressions;
Expr *update = NULL;
Expr *cond;
if (is_reverse)
{
// Create __idx$ > 0
cond = expr_new(EXPR_BINARY, idx_decl->span);
cond->binary_expr.operator = BINARYOP_GT;
cond->binary_expr.left = exprid(expr_variable(idx_decl));
Expr *rhs = expr_new_const_int(enumerator->span, index_type, 0);
cond->binary_expr.right = exprid(rhs);
// Create --__idx$
Expr *dec = expr_new(EXPR_UNARY, idx_decl->span);
dec->unary_expr.expr = expr_variable(idx_decl);
dec->unary_expr.operator = UNARYOP_DEC;
dec->unary_expr.no_wrap = true;
Ast *update_stmt = new_ast(AST_EXPR_STMT, idx_decl->span);
update_stmt->expr_stmt = dec;
ast_append(&succ, update_stmt);
}
else if (is_single_pass)
{
cond = expr_new_const_bool(idx_decl->span, type_bool, false);
}
else
{
// Create __idx$ < __len$
cond = expr_new(EXPR_BINARY, idx_decl->span);
cond->binary_expr.operator = BINARYOP_LT;
cond->binary_expr.left = exprid(expr_variable(idx_decl));
if (len_decl)
{
cond->binary_expr.right = exprid(expr_variable(len_decl));
}
else
{
Expr *rhs = expr_new_const_int(enumerator->span, type_isz, array_len);
cond->binary_expr.right = exprid(rhs);
}
// Create ++__idx$
update = expr_new(EXPR_UNARY, idx_decl->span);
update->unary_expr.expr = expr_variable(idx_decl);
update->unary_expr.operator = UNARYOP_INC;
update->unary_expr.no_wrap = true;
}
// Create IndexType index = __idx$
if (index)
{
Ast *declare_ast = new_ast(AST_DECLARE_STMT, var->span);
declare_ast->declare_stmt = index;
Expr *load_idx = expr_variable(idx_decl);
if (!cast_explicit(context, load_idx, index_var_type)) return false;
index->var.init_expr = load_idx;
ast_append(&succ, declare_ast);
}
// Create value = (*__$enum)[__idx$]
Ast *value_declare_ast = new_ast(AST_DECLARE_STMT, var->span);
value_declare_ast->declare_stmt = var;
Expr *subscript = expr_new(EXPR_SUBSCRIPT, var->span);
enum_val = expr_variable(temp);
enum_val->span = enumerator->span;
if (is_addr) expr_rewrite_insert_deref(enum_val);
subscript->subscript_expr.expr = exprid(enum_val);
if (array_len == 1)
{
subscript->subscript_expr.range.start = exprid(expr_new_const_int(var->span, idx_decl->type, 0));
}
else
{
subscript->subscript_expr.range.start = exprid(expr_variable(idx_decl));
}
if (value_by_ref)
{
Expr *addr = expr_new(EXPR_UNARY, subscript->span);
addr->unary_expr.operator = UNARYOP_ADDR;
addr->unary_expr.expr = subscript;
subscript = addr;
}
var->var.init_expr = subscript;
ast_append(&succ, value_declare_ast);
Ast *body_ast = astptr(body);
ast_append(&succ, body_ast);
Ast *compound_stmt = new_ast(AST_COMPOUND_STMT, body_ast->span);
compound_stmt->compound_stmt.first_stmt = first_stmt;
FlowCommon flow = statement->foreach_stmt.flow;
flow.skip_first = is_single_pass;
statement->for_stmt = (AstForStmt){ .init = exprid(init_expr),
.cond = exprid(cond),
.incr = update ? exprid(update) : 0,
.flow = flow,
.body = astid(compound_stmt),
};
statement->ast_kind = AST_FOR_STMT;
return sema_analyse_for_stmt(context, statement);
}
static inline bool sema_analyse_if_stmt(SemaContext *context, Ast *statement)
{
// IMPROVE
// convert
// if (!x) A(); else B();
// into
// if (x) B(); else A();
bool else_jump;
bool then_jump;
bool success;
Expr *cond = exprptr(statement->if_stmt.cond);
Ast *then = astptr(statement->if_stmt.then_body);
if (then->ast_kind == AST_DEFER_STMT)
{
RETURN_SEMA_ERROR(then, "An 'if' statement may not be followed by a raw 'defer' statement, this looks like a mistake.");
}
AstId else_id = statement->if_stmt.else_body;
Ast *else_body = else_id ? astptr(else_id) : NULL;
SCOPE_OUTER_START
CondType cond_type = then->ast_kind == AST_IF_CATCH_SWITCH_STMT
? COND_TYPE_UNWRAP : COND_TYPE_UNWRAP_BOOL;
CondResult result = COND_MISSING;
success = sema_analyse_cond(context, cond, cond_type, &result);
if (success && !ast_ok(then))
{
SEMA_ERROR(then,
"The 'then' part of a single line if-statement must start on the same line as the 'if' or use '{ }'");
success = false;
}
if (success && else_body)
{
bool then_has_braces = then->ast_kind == AST_COMPOUND_STMT || then->ast_kind == AST_IF_CATCH_SWITCH_STMT;
if (!then_has_braces)
{
SEMA_ERROR(then, "if-statements with an 'else' must use '{ }' even around a single statement.");
success = false;
}
if (success && else_body->ast_kind != AST_COMPOUND_STMT &&
else_body->ast_kind != AST_IF_STMT)
{
SEMA_ERROR(else_body,
"An 'else' must use '{ }' even around a single statement.");
success = false;
}
}
if (context->active_scope.jump_end && !context->active_scope.allow_dead_code)
{
SEMA_ERROR(then, "This code can never be executed.");
success = false;
}
if (then->ast_kind == AST_IF_CATCH_SWITCH_STMT)
{
DeclId label_id = statement->if_stmt.flow.label;
then->switch_stmt.flow.label = label_id;
statement->if_stmt.flow.label = 0;
Decl *label = declptrzero(label_id);
if (label) label->label.parent = astid(then);
SCOPE_START_WITH_LABEL(label_id);
success = success && sema_analyse_switch_stmt(context, then);
then_jump = context->active_scope.jump_end;
SCOPE_END;
}
else
{
SCOPE_START_WITH_LABEL(statement->if_stmt.flow.label);
if (result == COND_FALSE) context->active_scope.is_dead = true;
success = success && sema_analyse_statement(context, then);
then_jump = context->active_scope.jump_end;
SCOPE_END;
}
if (!success) goto END;
else_jump = false;
if (statement->if_stmt.else_body)
{
SCOPE_START_WITH_LABEL(statement->if_stmt.flow.label);
if (result == COND_TRUE) context->active_scope.is_dead = true;
sema_remove_unwraps_from_try(context, cond);
sema_unwrappable_from_catch_in_else(context, cond);
success = success && sema_analyse_statement(context, else_body);
else_jump = context->active_scope.jump_end;
SCOPE_END;
}
END:
context_pop_defers_and_replace_ast(context, statement);
SCOPE_OUTER_END;
if (!success) return false;
if (then_jump)
{
sema_unwrappable_from_catch_in_else(context, cond);
}
if (then_jump && else_jump && !statement->flow.has_break)
{
context->active_scope.jump_end = true;
}
return true;
}
static bool sema_analyse_asm_string_stmt(SemaContext *context, Ast *stmt)
{
Expr *body = exprptr(stmt->asm_block_stmt.asm_string);
if (!sema_analyse_ct_expr(context, body)) return false;
if (!expr_is_const_string(body))
{
SEMA_ERROR(body, "The asm statement expects a constant string.");
return false;
}
return true;
}
/**
* When jumping to a label
* @param context
* @param stmt
* @return
*/
static inline Decl *sema_analyse_label(SemaContext *context, Ast *stmt)
{
Label *label = stmt->ast_kind == AST_NEXTCASE_STMT ? &stmt->nextcase_stmt.label : &stmt->contbreak_stmt.label;
const char *name = label->name;
Decl *target = sema_find_label_symbol(context, name);
if (!target)
{
target = sema_find_label_symbol_anywhere(context, name);
if (target && target->decl_kind == DECL_LABEL)
{
if (context->active_scope.flags & SCOPE_EXPR_BLOCK)
{
switch (stmt->ast_kind)
{
case AST_BREAK_STMT:
SEMA_ERROR(stmt, "You cannot break out of an expression block.");
return poisoned_decl;
case AST_CONTINUE_STMT:
SEMA_ERROR(stmt, "You cannot use continue out of an expression block.");
return poisoned_decl;
case AST_NEXTCASE_STMT:
SEMA_ERROR(stmt, "You cannot use nextcase to exit an expression block.");
return poisoned_decl;
default:
UNREACHABLE
}
}
if (target->label.scope_defer != astid(context->active_scope.in_defer))
{
switch (stmt->ast_kind)
{
case AST_BREAK_STMT:
SEMA_ERROR(stmt, "You cannot break out of a defer.");
return poisoned_decl;
case AST_CONTINUE_STMT:
SEMA_ERROR(stmt, "You cannot use continue out of a defer.");
return poisoned_decl;
case AST_NEXTCASE_STMT:
SEMA_ERROR(stmt, "You cannot use nextcase out of a defer.");
return poisoned_decl;
default:
UNREACHABLE
}
}
SEMA_ERROR(stmt, "'%s' cannot be reached from the current scope.", name);
return poisoned_decl;
}
SEMA_ERROR(stmt, "A labelled statement with the name '%s' can't be found in the current scope.", name);
return poisoned_decl;
}
if (target->decl_kind != DECL_LABEL)
{
SEMA_ERROR(label, "Expected the name to match a label, not a constant.");
return poisoned_decl;
}
if (context->active_scope.in_defer)
{
if (target->label.scope_defer != astid(context->active_scope.in_defer))
{
switch (stmt->ast_kind)
{
case AST_BREAK_STMT:
SEMA_ERROR(stmt, "You cannot break out of a defer.");
return poisoned_decl;
case AST_CONTINUE_STMT:
SEMA_ERROR(stmt, "You cannot use continue out of a defer.");
return poisoned_decl;
case AST_NEXTCASE_STMT:
SEMA_ERROR(stmt, "You cannot use nextcase out of a defer.");
return poisoned_decl;
default:
UNREACHABLE
}
}
}
return target;
}
static bool context_labels_exist_in_scope(SemaContext *context)
{
Decl **locals = context->locals;
for (size_t local = context->active_scope.current_local; local > 0; local--)
{
if (locals[local - 1]->decl_kind == DECL_LABEL) return true;
}
return false;
}
static bool sema_analyse_nextcase_stmt(SemaContext *context, Ast *statement)
{
context->active_scope.jump_end = true;
if (!context->next_target && !statement->nextcase_stmt.label.name && !statement->nextcase_stmt.expr && !statement->nextcase_stmt.is_default)
{
if (context->next_switch)
{
SEMA_ERROR(statement, "A plain 'nextcase' is not allowed on the last case.");
}
else
{
SEMA_ERROR(statement, "'nextcase' can only be used inside of a switch.");
}
return false;
}
// TODO test that nextcase out of a switch using label correctly creates defers.
Ast *parent = context->next_switch;
if (statement->nextcase_stmt.label.name)
{
Decl *target = sema_analyse_label(context, statement);
if (!decl_ok(target)) return false;
parent = astptr(target->label.parent);
AstKind kind = parent->ast_kind;
if (kind != AST_SWITCH_STMT && kind != AST_IF_CATCH_SWITCH_STMT)
{
SEMA_ERROR(&statement->nextcase_stmt.label, "Expected the label to match a 'switch' or 'if-catch' statement.");
return false;
}
}
if (!parent)
{
SEMA_ERROR(statement, "No matching switch could be found.");
return false;
}
// Handle jump to default.
Ast **cases = parent->switch_stmt.cases;
if (statement->nextcase_stmt.is_default)
{
Ast *default_ast = NULL;
FOREACH_BEGIN(Ast *cs, cases)
if (cs->ast_kind == AST_DEFAULT_STMT)
{
default_ast = cs;
break;
}
FOREACH_END();
if (!default_ast) RETURN_SEMA_ERROR(statement, "There is no 'default' in the switch to jump to.");
statement->nextcase_stmt.defer_id = context_get_defers(context, context->active_scope.defer_last, parent->switch_stmt.defer, true);
statement->nextcase_stmt.case_switch_stmt = astid(default_ast);
statement->nextcase_stmt.switch_expr = NULL;
return true;
}
Expr *value = exprptrzero(statement->nextcase_stmt.expr);
statement->nextcase_stmt.switch_expr = NULL;
if (!value)
{
assert(context->next_target);
statement->nextcase_stmt.defer_id = context_get_defers(context, context->active_scope.defer_last, parent->switch_stmt.defer, true);
statement->nextcase_stmt.case_switch_stmt = astid(context->next_target);
return true;
}
Expr *cond = exprptrzero(parent->switch_stmt.cond);
if (!cond)
{
RETURN_SEMA_ERROR(statement, "'nextcase' cannot be used with an expressionless switch.");
}
if (value->expr_kind == EXPR_TYPEINFO)
{
TypeInfo *type_info = value->type_expr;
if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false;
statement->nextcase_stmt.defer_id = context_get_defers(context, context->active_scope.defer_last, parent->switch_stmt.defer, true);
if (cond->type->canonical != type_typeid)
{
SEMA_ERROR(statement, "Unexpected 'type' in as an 'nextcase' destination.");
SEMA_NOTE(statement, "The 'switch' here uses expected a type '%s'.", type_to_error_string(cond->type));
return false;
}
cases = parent->switch_stmt.cases;
Type *type = type_info->type->canonical;
VECEACH(cases, i)
{
Ast *case_stmt = cases[i];
if (case_stmt->ast_kind == AST_DEFAULT_STMT) continue;
Expr *expr = exprptr(case_stmt->case_stmt.expr);
if (expr_is_const(expr) && expr->const_expr.typeid == type)
{
statement->nextcase_stmt.case_switch_stmt = astid(case_stmt);
return true;
}
}
SEMA_ERROR(type_info, "There is no case for type '%s'.", type_to_error_string(type_info->type));
return false;
}
Type *expected_type = parent->ast_kind == AST_SWITCH_STMT ? cond->type : type_anyfault;
if (!sema_analyse_expr_rhs(context, expected_type, value, false, NULL)) return false;
statement->nextcase_stmt.defer_id = context_get_defers(context, context->active_scope.defer_last, parent->switch_stmt.defer, true);
if (expr_is_const(value))
{
VECEACH(parent->switch_stmt.cases, i)
{
Ast *case_stmt = parent->switch_stmt.cases[i];
Expr *from = exprptr(case_stmt->case_stmt.expr);
if (case_stmt->ast_kind == AST_DEFAULT_STMT) continue;
if (!expr_is_const(from)) goto VARIABLE_JUMP;
ExprConst *const_expr = &from->const_expr;
ExprConst *to_const_expr = case_stmt->case_stmt.to_expr ? &exprptr(case_stmt->case_stmt.to_expr)->const_expr : const_expr;
if (expr_const_in_range(&value->const_expr, const_expr, to_const_expr))
{
statement->nextcase_stmt.case_switch_stmt = astid(case_stmt);
return true;
}
}
SEMA_ERROR(value, "There is no 'case %s' in the switch, please check if a case is missing or if this value is incorrect.", expr_const_to_error_string(&value->const_expr));
return false;
}
VARIABLE_JUMP:
statement->nextcase_stmt.case_switch_stmt = astid(parent);
statement->nextcase_stmt.switch_expr = value;
return true;
}
static inline bool sema_analyse_then_overwrite(SemaContext *context, Ast *statement, AstId replacement)
{
if (!replacement)
{
statement->ast_kind = AST_NOP_STMT;
return true;
}
Ast *last = NULL;
AstId next = statement->next;
*statement = *astptr(replacement);
AstId current = astid(statement);
assert(current);
while (current)
{
Ast *ast = ast_next(&current);
if (!sema_analyse_statement(context, ast)) return false;
last = ast;
}
last = ast_last(last);
last->next = next;
return true;
}
static inline bool sema_analyse_ct_if_stmt(SemaContext *context, Ast *statement)
{
unsigned ct_context = sema_context_push_ct_stack(context);
CondResult res = sema_check_comp_time_bool(context, statement->ct_if_stmt.expr);
if (res == COND_MISSING) goto FAILED;
if (res == COND_TRUE)
{
if (sema_analyse_then_overwrite(context, statement, statement->ct_if_stmt.then)) goto SUCCESS;
goto FAILED;
}
Ast *elif = astptrzero(statement->ct_if_stmt.elif);
while (1)
{
if (!elif)
{
// Turn into NOP!
statement->ast_kind = AST_NOP_STMT;
goto SUCCESS;
}
// We found else, then just replace with that.
if (elif->ast_kind == AST_CT_ELSE_STMT)
{
if (sema_analyse_then_overwrite(context, statement, elif->ct_else_stmt)) goto SUCCESS;
goto FAILED;
}
assert(elif->ast_kind == AST_CT_IF_STMT);
res = sema_check_comp_time_bool(context, elif->ct_if_stmt.expr);
if (res == COND_MISSING) goto FAILED;
if (res == COND_TRUE)
{
if (sema_analyse_then_overwrite(context, statement, elif->ct_if_stmt.then)) goto SUCCESS;
goto FAILED;
}
elif = astptrzero(elif->ct_if_stmt.elif);
}
SUCCESS:
sema_context_pop_ct_stack(context, ct_context);
return true;
FAILED:
sema_context_pop_ct_stack(context, ct_context);
return false;
}
static inline bool sema_analyse_compound_statement_no_scope(SemaContext *context, Ast *compound_statement)
{
bool all_ok = ast_ok(compound_statement);
AstId current = compound_statement->compound_stmt.first_stmt;
Ast *ast = NULL;
while (current)
{
ast = ast_next(&current);
if (!sema_analyse_statement(context, ast))
{
ast_poison(ast);
all_ok = false;
}
}
AstId *next = ast ? &ast->next : &compound_statement->compound_stmt.first_stmt;
context_pop_defers(context, next);
return all_ok;
}
static inline bool sema_check_type_case(SemaContext *context, Type *switch_type, Ast *case_stmt, Ast **cases, unsigned index)
{
Expr *expr = exprptr(case_stmt->case_stmt.expr);
if (!sema_analyse_expr_rhs(context, type_typeid, expr, false, NULL)) return false;
if (expr_is_const(expr))
{
Type *my_type = expr->const_expr.typeid;
for (unsigned i = 0; i < index; i++)
{
Ast *other = cases[i];
if (other->ast_kind != AST_CASE_STMT) continue;
Expr *other_expr = exprptr(other->case_stmt.expr);
if (expr_is_const(other_expr) && other_expr->const_expr.typeid == my_type)
{
SEMA_ERROR(case_stmt, "The same type appears more than once.");
SEMA_NOTE(other, "Here is the case with that type.");
return false;
}
}
}
return true;
}
static inline bool sema_check_value_case(SemaContext *context, Type *switch_type, Ast *case_stmt, Ast **cases, unsigned index, bool *if_chained, bool *max_ranged)
{
assert(switch_type);
Expr *expr = exprptr(case_stmt->case_stmt.expr);
Expr *to_expr = exprptrzero(case_stmt->case_stmt.to_expr);
// 1. Try to do implicit conversion to the correct type.
if (!sema_analyse_expr_rhs(context, switch_type, expr, false, NULL)) return false;
if (to_expr && !sema_analyse_expr_rhs(context, switch_type, to_expr, false, NULL)) return false;
bool is_range = to_expr != NULL;
bool first_is_const = expr_is_const(expr);
if (!is_range && !first_is_const)
{
*if_chained = true;
return true;
}
if (is_range && (!expr_is_const_int(expr) || !expr_is_const(to_expr)))
{
print_error_at(extend_span_with_token(expr->span, to_expr->span), "Ranges must be constant integers.");
return false;
}
ExprConst *const_expr = &expr->const_expr;
ExprConst *to_const_expr = to_expr ? &to_expr->const_expr : const_expr;
if (!*max_ranged && is_range)
{
if (int_comp(const_expr->ixx, to_const_expr->ixx, BINARYOP_GT))
{
print_error_at(extend_span_with_token(expr->span, to_expr->span),
"The range is not valid because the first value (%s) is greater than the second (%s). "
"It would work if you swapped their order.",
int_to_str(const_expr->ixx, 10),
int_to_str(to_const_expr->ixx, 10));
return false;
}
Int128 range = int_sub(to_const_expr->ixx, const_expr->ixx).i;
Int128 max_range = { .low = active_target.switchrange_max_size };
if (i128_comp(range, max_range, type_i128) == CMP_GT)
{
*max_ranged = true;
}
}
for (unsigned i = 0; i < index; i++)
{
Ast *other = cases[i];
if (other->ast_kind != AST_CASE_STMT) continue;
Expr *other_expr = exprptr(other->case_stmt.expr);
if (!expr_is_const(other_expr)) continue;
ExprConst *other_const = &other_expr->const_expr;
ExprConst *other_to_const = other->case_stmt.to_expr ? &exprptr(other->case_stmt.to_expr)->const_expr : other_const;
if (expr_const_in_range(const_expr, other_const, other_to_const))
{
SEMA_ERROR(case_stmt, "The same case value appears more than once.");
SEMA_NOTE(other, "Here is the previous use of that value.");
return false;
}
}
return true;
}
INLINE const char *create_missing_enums_in_switch_error(Ast **cases, unsigned case_count, Decl **enums)
{
unsigned missing = vec_size(enums) - case_count;
scratch_buffer_clear();
if (missing == 1)
{
scratch_buffer_append("Enum value ");
}
else
{
scratch_buffer_printf("%u enum values were not handled in the switch: ", missing);
}
unsigned printed = 0;
FOREACH_BEGIN(Decl *decl, enums)
for (unsigned i = 0; i < case_count; i++)
{
Expr *e = exprptr(cases[i]->case_stmt.expr);
assert(expr_is_const_enum(e));
if (e->const_expr.enum_err_val == decl) goto CONTINUE;
}
if (++printed != 1)
{
scratch_buffer_append(printed == missing ? " and " : ", ");
}
scratch_buffer_append(decl->name);
if (printed > 2 && missing > 3)
{
scratch_buffer_append(", ...");
goto DONE;
}
if (printed == missing) goto DONE;
CONTINUE:;
FOREACH_END();
DONE:;
if (missing == 1)
{
scratch_buffer_append(" was not handled in the switch - either add it or add 'default'.");
return scratch_buffer_to_string();
}
scratch_buffer_append(" - either add them or use 'default'.");
return scratch_buffer_to_string();
}
static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, Type *switch_type, Ast **cases, ExprAnySwitch *any_switch, Decl *var_holder)
{
bool use_type_id = false;
if (!type_is_comparable(switch_type))
{
print_error_at(expr_span, "You cannot test '%s' for equality, and only values that supports '==' for comparison can be used in a switch.", type_to_error_string(switch_type));
return false;
}
// We need an if-chain if this isn't an enum/integer type.
Type *flat = type_flatten(switch_type);
TypeKind flat_switch_type_kind = flat->type_kind;
bool is_enum_switch = flat_switch_type_kind == TYPE_ENUM;
bool if_chain = !is_enum_switch && !type_kind_is_any_integer(flat_switch_type_kind);
Ast *default_case = NULL;
assert(context->active_scope.defer_start == context->active_scope.defer_last);
bool exhaustive = false;
unsigned case_count = vec_size(cases);
bool success = true;
bool max_ranged = false;
bool type_switch = switch_type == type_typeid;
for (unsigned i = 0; i < case_count; i++)
{
if (!success) break;
Ast *stmt = cases[i];
Ast *next = (i < case_count - 1) ? cases[i + 1] : NULL;
PUSH_NEXT(next, statement);
switch (stmt->ast_kind)
{
case AST_CASE_STMT:
if (type_switch)
{
if (!sema_check_type_case(context, switch_type, stmt, cases, i))
{
success = false;
break;;
}
}
else
{
if (!sema_check_value_case(context, switch_type, stmt, cases, i, &if_chain, &max_ranged))
{
success = false;
break;
}
}
break;
case AST_DEFAULT_STMT:
exhaustive = true;
if (default_case)
{
SEMA_ERROR(stmt, "'default' may only appear once in a single 'switch', please remove one.");
SEMA_NOTE(default_case, "Here is the previous use.");
success = false;
}
default_case = stmt;
break;
default:
UNREACHABLE;
}
POP_NEXT();
}
if (!exhaustive && is_enum_switch) exhaustive = case_count >= vec_size(flat->decl->enums.values);
bool all_jump_end = exhaustive;
for (unsigned i = 0; i < case_count; i++)
{
Ast *stmt = cases[i];
SCOPE_START
PUSH_BREAK(statement);
Ast *next = (i < case_count - 1) ? cases[i + 1] : NULL;
PUSH_NEXT(next, statement);
Ast *body = stmt->case_stmt.body;
if (stmt->ast_kind == AST_CASE_STMT && body && type_switch && var_holder && expr_is_const(exprptr(stmt->case_stmt.expr)))
{
if (any_switch->is_assign)
{
Type *real_type = type_get_ptr(exprptr(stmt->case_stmt.expr)->const_expr.typeid);
Decl *new_var = decl_new_var(any_switch->new_ident, any_switch->span,
type_info_new_base(any_switch->is_deref
? real_type->pointer : real_type, any_switch->span),
VARDECL_LOCAL);
Expr *var_result = expr_variable(var_holder);
if (!cast_explicit(context, var_result, real_type)) return false;
if (any_switch->is_deref)
{
expr_rewrite_insert_deref(var_result);
}
new_var->var.init_expr = var_result;
Ast *decl_ast = new_ast(AST_DECLARE_STMT, new_var->span);
decl_ast->declare_stmt = new_var;
ast_prepend(&body->compound_stmt.first_stmt, decl_ast);
}
else
{
Expr *expr = exprptr(stmt->case_stmt.expr);
Type *type = type_get_ptr(expr->const_expr.typeid);
Decl *alias = decl_new_var(var_holder->name, var_holder->span,
type_info_new_base(type, expr->span),
VARDECL_LOCAL);
Expr *ident_converted = expr_variable(var_holder);
if (!cast_explicit(context, ident_converted, type)) return false;
alias->var.init_expr = ident_converted;
alias->var.shadow = true;
Ast *decl_ast = new_ast(AST_DECLARE_STMT, alias->span);
decl_ast->declare_stmt = alias;
ast_prepend(&body->compound_stmt.first_stmt, decl_ast);
}
}
success = success && (!body || sema_analyse_compound_statement_no_scope(context, body));
POP_BREAK();
POP_NEXT();
if (!body && i < case_count - 1) continue;
all_jump_end &= context->active_scope.jump_end;
SCOPE_END;
}
if (is_enum_switch && !exhaustive && success)
{
RETURN_SEMA_ERROR(statement, create_missing_enums_in_switch_error(cases, case_count, flat->decl->enums.values));
}
if ((if_chain || max_ranged) && statement->flow.jump)
{
RETURN_SEMA_ERROR(statement, "Switch cannot use a jump table, please remove '@jump'.");
}
statement->flow.no_exit = all_jump_end;
statement->switch_stmt.flow.if_chain = if_chain || max_ranged;
return success;
}
static inline bool sema_analyse_ct_switch_stmt(SemaContext *context, Ast *statement)
{
unsigned ct_context = sema_context_push_ct_stack(context);
// Evaluate the switch statement
Expr *cond = exprptrzero(statement->ct_switch_stmt.cond);
if (cond && !sema_analyse_ct_expr(context, cond)) goto FAILED;
// If we have a type, then we do different evaluation
// compared to when it is a value.
Type *type = cond ? cond->type : type_bool;
bool is_type = false;
switch (type_flatten(type)->type_kind)
{
case TYPE_TYPEID:
is_type = true;
FALLTHROUGH;
case TYPE_ENUM:
case ALL_INTS:
case ALL_FLOATS:
case TYPE_BOOL:
break;
case TYPE_SLICE:
if (expr_is_const_string(cond)) break;
FALLTHROUGH;
default:
assert(cond);
SEMA_ERROR(cond, "Only types, strings, enums, integers, floats and booleans may be used with '$switch'.");
goto FAILED;
}
ExprConst *switch_expr_const = cond ? &cond->const_expr : NULL;
Ast **cases = statement->ct_switch_stmt.body;
unsigned case_count = vec_size(cases);
assert(case_count <= INT32_MAX);
int matched_case = (int)case_count;
int default_case = (int)case_count;
// Go through each case
for (unsigned i = 0; i < case_count; i++)
{
Ast *stmt = cases[i];
switch (stmt->ast_kind)
{
case AST_CASE_STMT:
{
Expr *expr = exprptr(stmt->case_stmt.expr);
Expr *to_expr = exprptrzero(stmt->case_stmt.to_expr);
if (to_expr && !type_is_integer(type))
{
SEMA_ERROR(to_expr, "$case ranges are only allowed for integers.");
goto FAILED;
}
if (is_type)
{
if (!sema_analyse_ct_expr(context, expr)) goto FAILED;
if (expr->type != type_typeid)
{
SEMA_ERROR(expr, "A type was expected here not %s.", type_quoted_error_string(expr->type));
goto FAILED;
}
}
else
{
if (!sema_analyse_expr_rhs(context, type, expr, false, NULL)) goto FAILED;
if (to_expr && !sema_analyse_expr_rhs(context, type, to_expr, false, NULL)) goto FAILED;
}
if (!expr_is_const(expr))
{
SEMA_ERROR(expr, "The $case must have a constant expression.");
goto FAILED;
}
if (!cond)
{
if (!expr->const_expr.b) continue;
if (matched_case == case_count) matched_case = (int)i;
continue;
}
if (to_expr && !expr_is_const(to_expr))
{
SEMA_ERROR(to_expr, "The $case must have a constant expression.");
goto FAILED;
}
ExprConst *const_expr = &expr->const_expr;
ExprConst *const_to_expr = to_expr ? &to_expr->const_expr : const_expr;
if (to_expr && expr_const_compare(const_expr, const_to_expr, BINARYOP_GT))
{
SEMA_ERROR(to_expr, "The end of a range must be less or equal to the beginning.");
goto FAILED;
}
// Check that it is unique.
for (unsigned j = 0; j < i; j++)
{
Ast *other_stmt = cases[j];
if (other_stmt->ast_kind == AST_DEFAULT_STMT) continue;
ExprConst *other_const = &exprptr(other_stmt->case_stmt.expr)->const_expr;
ExprConst *other_const_to = other_stmt->case_stmt.to_expr ? &exprptr(other_stmt->case_stmt.to_expr)->const_expr : other_const;
if (expr_const_in_range(const_expr, other_const, other_const_to))
{
SEMA_ERROR(stmt, "'%s' appears more than once.", expr_const_to_error_string(const_expr));
SEMA_NOTE(exprptr(cases[j]->case_stmt.expr), "The previous $case was here.");
goto FAILED;
}
}
if (is_type)
{
assert(const_expr == const_to_expr);
Type *switch_type = switch_expr_const->typeid;
Type *case_type = const_expr->typeid;
if (matched_case > i && type_is_subtype(case_type->canonical, switch_type->canonical))
{
matched_case = (int)i;
}
}
else if (expr_const_in_range(switch_expr_const, const_expr, const_to_expr))
{
matched_case = (int)i;
}
break;
}
case AST_DEFAULT_STMT:
if (default_case < case_count)
{
SEMA_ERROR(stmt, "More than one $default is not allowed.");
SEMA_NOTE(cases[default_case], "The previous $default was here.");
goto FAILED;
}
default_case = (int)i;
continue;
default:
UNREACHABLE;
}
}
if (matched_case == case_count) matched_case = default_case;
Ast *body = NULL;
for (int i = matched_case; i < case_count; i++)
{
body = cases[i]->case_stmt.body;
if (body) break;
}
if (!body)
{
statement->ast_kind = AST_NOP_STMT;
goto SUCCESS;
}
if (!sema_analyse_then_overwrite(context, statement, body->compound_stmt.first_stmt)) goto FAILED;
SUCCESS:
sema_context_pop_ct_stack(context, ct_context);
return true;
FAILED:
sema_context_pop_ct_stack(context, ct_context);
return false;
}
static inline bool sema_analyse_ct_foreach_stmt(SemaContext *context, Ast *statement)
{
unsigned ct_context = sema_context_push_ct_stack(context);
Expr *collection = exprptr(statement->ct_foreach_stmt.expr);
if (!sema_analyse_ct_expr(context, collection)) return false;
if (!expr_is_const_untyped_list(collection) && !expr_is_const_initializer(collection))
{
SEMA_ERROR(collection, "Expected a list to iterate over");
goto FAILED;
}
unsigned count;
ConstInitializer *initializer = NULL;
Expr **expressions = NULL;
Type *const_list_type = NULL;
if (expr_is_const_initializer(collection))
{
initializer = collection->const_expr.initializer;
ConstInitType init_type = initializer->kind;
const_list_type = type_flatten(collection->type);
if (const_list_type->type_kind == TYPE_ARRAY || const_list_type->type_kind == TYPE_VECTOR)
{
count = const_list_type->array.len;
}
else
{
// Empty list
if (init_type == CONST_INIT_ZERO)
{
sema_context_pop_ct_stack(context, ct_context);
statement->ast_kind = AST_NOP_STMT;
return true;
}
if (init_type != CONST_INIT_ARRAY_FULL)
{
SEMA_ERROR(collection, "Only regular arrays are allowed here.");
goto FAILED;
}
count = vec_size(initializer->init_array_full);
}
}
else
{
expressions = collection->const_expr.untyped_list;
count = vec_size(expressions);
}
Decl *index = declptrzero(statement->ct_foreach_stmt.index);
AstId start = 0;
if (index)
{
index->type = type_int;
if (!sema_add_local(context, index)) goto FAILED;
}
Decl *value = declptr(statement->ct_foreach_stmt.value);
if (!sema_add_local(context, value)) goto FAILED;
// Get the body
Ast *body = astptr(statement->ct_foreach_stmt.body);
AstId *current = &start;
unsigned loop_context = sema_context_push_ct_stack(context);
for (unsigned i = 0; i < count; i++)
{
sema_context_pop_ct_stack(context, loop_context);
Ast *compound_stmt = copy_ast_single(body);
if (expressions)
{
value->var.init_expr = expressions[i];
}
else
{
Expr *expr = expr_new(EXPR_CONST, collection->span);
if (!expr_rewrite_to_const_initializer_index(const_list_type, initializer, expr, i, false))
{
SEMA_ERROR(collection, "Complex expressions are not allowed.");
goto FAILED;
}
value->var.init_expr = expr;
}
if (index)
{
index->var.init_expr = expr_new_const_int(index->span, type_int, i);
index->type = type_int;
}
if (!sema_analyse_compound_stmt(context, compound_stmt)) goto FAILED;
*current = astid(compound_stmt);
current = &compound_stmt->next;
}
sema_context_pop_ct_stack(context, ct_context);
statement->ast_kind = AST_COMPOUND_STMT;
statement->compound_stmt.first_stmt = start;
return true;
FAILED:
sema_context_pop_ct_stack(context, ct_context);
return false;
}
static inline bool sema_analyse_switch_stmt(SemaContext *context, Ast *statement)
{
statement->switch_stmt.scope_defer = context->active_scope.in_defer;
SCOPE_START_WITH_LABEL(statement->switch_stmt.flow.label);
Expr *cond = exprptrzero(statement->switch_stmt.cond);
Type *switch_type;
ExprAnySwitch var_switch;
Decl *any_decl = NULL;
if (statement->ast_kind == AST_SWITCH_STMT)
{
CondResult res = COND_MISSING;
if (cond && !sema_analyse_cond(context, cond, COND_TYPE_EVALTYPE_VALUE, &res)) return false;
Expr *last = cond ? VECLAST(cond->cond_expr) : NULL;
switch_type = last ? last->type->canonical : type_bool;
if (last && last->expr_kind == EXPR_ANYSWITCH)
{
var_switch = last->any_switch;
Expr *inner;
if (var_switch.is_assign)
{
inner = expr_new(EXPR_DECL, last->span);
any_decl = decl_new_generated_var(type_any, VARDECL_LOCAL, last->span);
any_decl->var.init_expr = var_switch.any_expr;
inner->decl_expr = any_decl;
if (!sema_analyse_expr(context, inner)) return false;
}
else
{
inner = expr_new(EXPR_IDENTIFIER, last->span);
any_decl = var_switch.variable;
expr_resolve_ident(inner, any_decl);
inner->type = type_any;
}
expr_rewrite_to_builtin_access(last, inner, ACCESS_TYPEOFANY, type_typeid);
switch_type = type_typeid;
cond->type = type_typeid;
}
}
else
{
switch_type = type_anyfault;
}
statement->switch_stmt.defer = context->active_scope.defer_last;
if (!sema_analyse_switch_body(context, statement, cond ? cond->span : statement->span,
switch_type->canonical,
statement->switch_stmt.cases, any_decl ? &var_switch : NULL, any_decl))
{
return SCOPE_POP_ERROR();
}
context_pop_defers_and_replace_ast(context, statement);
SCOPE_END;
if (statement->flow.no_exit && !statement->flow.has_break)
{
context->active_scope.jump_end = true;
}
return true;
}
bool sema_analyse_ct_assert_stmt(SemaContext *context, Ast *statement)
{
Expr *expr = exprptrzero(statement->assert_stmt.expr);
ExprId message = statement->assert_stmt.message;
const char *msg = NULL;
Expr *message_expr = message ? exprptr(message) : NULL;
if (message_expr)
{
if (!sema_analyse_expr(context, message_expr)) return false;
if (message_expr->expr_kind != EXPR_CONST || message_expr->const_expr.const_kind != CONST_STRING)
{
SEMA_ERROR(message_expr, "Expected a string as the error message.");
}
}
CondResult res = expr ? sema_check_comp_time_bool(context, expr) : COND_FALSE;
if (res == COND_MISSING) return false;
SourceSpan span = expr ? expr->span : statement->span;
if (res == COND_FALSE)
{
if (message_expr)
{
print_error_at(span, "%.*s", EXPAND_EXPR_STRING(message_expr));
}
else
{
print_error_at(span, "Compile time assert failed.");
}
sema_print_inline(context);
return false;
}
statement->ast_kind = AST_NOP_STMT;
return true;
}
bool sema_analyse_ct_echo_stmt(SemaContext *context, Ast *statement)
{
Expr *message = statement->expr_stmt;
if (!sema_analyse_expr(context, message)) return false;
if (message->expr_kind != EXPR_CONST)
{
SEMA_ERROR(message, "Expected a constant value.");
return false;
}
printf("] ");
switch (message->const_expr.const_kind)
{
case CONST_FLOAT:
printf("%f\n", (double)message->const_expr.fxx.f);
break;
case CONST_INTEGER:
puts(int_to_str(message->const_expr.ixx, 10));
break;
case CONST_BOOL:
puts(message->const_expr.b ? "true" : "false");
break;
case CONST_ENUM:
case CONST_ERR:
puts(message->const_expr.enum_err_val->name);
break;
case CONST_STRING:
printf("%.*s\n", EXPAND_EXPR_STRING(message));
break;
case CONST_POINTER:
printf("%p\n", (void*)message->const_expr.ptr);
break;
case CONST_TYPEID:
puts(type_to_error_string(message->const_expr.typeid));
break;
case CONST_BYTES:
case CONST_INITIALIZER:
case CONST_UNTYPED_LIST:
case CONST_MEMBER:
SEMA_ERROR(message, "Unsupported type for '$echo'");
break;
}
statement->ast_kind = AST_NOP_STMT;
return true;
}
/**
* $for(<list of ct decl/expr>, <cond>, <incr>):
*/
static inline bool sema_analyse_ct_for_stmt(SemaContext *context, Ast *statement)
{
unsigned for_context = sema_context_push_ct_stack(context);
bool success = false;
ExprId init;
if ((init = statement->for_stmt.init))
{
Expr *init_expr = exprptr(init);
assert(init_expr->expr_kind == EXPR_EXPRESSION_LIST);
// Check the list of expressions.
FOREACH_BEGIN(Expr *expr, init_expr->expression_list)
// Only a subset of declarations are allowed. We check this here.
if (expr->expr_kind == EXPR_DECL)
{
Decl *decl = expr->decl_expr;
if (decl->decl_kind != DECL_VAR || (decl->var.kind != VARDECL_LOCAL_CT && decl->var.kind != VARDECL_LOCAL_CT_TYPE))
{
SEMA_ERROR(expr, "Only 'var $foo' and 'var $Type' declarations are allowed in '$for'");
goto FAILED;
}
if (!sema_analyse_var_decl_ct(context, decl)) goto FAILED;
continue;
}
// If expression evaluate it and make sure it is constant.
if (!sema_analyse_ct_expr(context, expr)) goto FAILED;
FOREACH_END();
}
ExprId condition = statement->for_stmt.cond;
ExprId incr = statement->for_stmt.incr;
Ast *body = astptr(statement->for_stmt.body);
AstId start = 0;
AstId *current = &start;
Expr **incr_list = incr ? exprptr(incr)->expression_list : NULL;
assert(condition);
// We set a maximum of macro iterations.
// we might consider reducing this.
unsigned current_ct_scope = sema_context_push_ct_stack(context);
for (int i = 0; i < MAX_MACRO_ITERATIONS; i++)
{
sema_context_pop_ct_stack(context, current_ct_scope);
// First evaluate the cond, which we note that we *must* have.
// we need to make a copy
Expr *copy = copy_expr_single(exprptr(condition));
CondResult result = COND_MISSING;
if (!sema_analyse_cond_expr(context, copy, &result)) goto FAILED;
if (result == COND_MISSING)
{
SEMA_ERROR(copy, "Expected a value that can be evaluated at compile time.");
goto FAILED;
}
// Break if we reached "false"
if (result == COND_FALSE) break;
// Otherwise we copy the body.
Ast *compound_stmt = copy_ast_single(body);
// Analyse the body
if (!sema_analyse_compound_statement_no_scope(context, compound_stmt)) goto FAILED;
// Append it.
*current = astid(compound_stmt);
current = &compound_stmt->next;
// Copy and evaluate all the expressions in "incr"
FOREACH_BEGIN(Expr *expr, incr_list)
if (!sema_analyse_ct_expr(context, copy_expr_single(expr))) goto FAILED;
FOREACH_END();
}
// Analysis is done turn the generated statements into a compound statement for lowering.
statement->ast_kind = AST_COMPOUND_STMT;
statement->compound_stmt = (AstCompoundStmt) { .first_stmt = start };
return true;
FAILED:
sema_context_pop_ct_stack(context, for_context);
return false;
}
static inline bool sema_analyse_statement_inner(SemaContext *context, Ast *statement)
{
switch (statement->ast_kind)
{
case AST_POISONED:
case AST_IF_CATCH_SWITCH_STMT:
case AST_CONTRACT:
case AST_ASM_STMT:
case AST_CONTRACT_FAULT:
UNREACHABLE
case AST_DECLS_STMT:
return sema_analyse_decls_stmt(context, statement);
case AST_ASM_BLOCK_STMT:
return sema_analyse_asm_stmt(context, statement);
case AST_ASSERT_STMT:
return sema_analyse_assert_stmt(context, statement);
case AST_BREAK_STMT:
return sema_analyse_break_stmt(context, statement);
case AST_CASE_STMT:
SEMA_ERROR(statement, "Unexpected 'case' outside of switch");
return false;
case AST_COMPOUND_STMT:
return sema_analyse_compound_stmt(context, statement);
case AST_CONTINUE_STMT:
return sema_analyse_continue_stmt(context, statement);
case AST_CT_ASSERT:
return sema_analyse_ct_assert_stmt(context, statement);
case AST_CT_IF_STMT:
return sema_analyse_ct_if_stmt(context, statement);
case AST_CT_ECHO_STMT:
return sema_analyse_ct_echo_stmt(context, statement);
case AST_DECLARE_STMT:
return sema_analyse_declare_stmt(context, statement);
case AST_DEFAULT_STMT:
SEMA_ERROR(statement, "Unexpected 'default' outside of switch");
return false;
case AST_DEFER_STMT:
return sema_analyse_defer_stmt(context, statement);
case AST_EXPR_STMT:
return sema_analyse_expr_stmt(context, statement);
case AST_FOREACH_STMT:
return sema_analyse_foreach_stmt(context, statement);
case AST_FOR_STMT:
return sema_analyse_for_stmt(context, statement);
case AST_IF_STMT:
return sema_analyse_if_stmt(context, statement);
case AST_NOP_STMT:
return true;
case AST_BLOCK_EXIT_STMT:
UNREACHABLE
case AST_RETURN_STMT:
return sema_analyse_return_stmt(context, statement);
case AST_SWITCH_STMT:
return sema_analyse_switch_stmt(context, statement);
case AST_NEXTCASE_STMT:
return sema_analyse_nextcase_stmt(context, statement);
case AST_CT_SWITCH_STMT:
return sema_analyse_ct_switch_stmt(context, statement);
case AST_CT_ELSE_STMT:
UNREACHABLE
case AST_CT_FOREACH_STMT:
return sema_analyse_ct_foreach_stmt(context, statement);
case AST_CT_FOR_STMT:
return sema_analyse_ct_for_stmt(context, statement);
}
UNREACHABLE
}
bool sema_analyse_statement(SemaContext *context, Ast *statement)
{
if (statement->ast_kind == AST_POISONED) return false;
bool dead_code = context->active_scope.jump_end;
if (!sema_analyse_statement_inner(context, statement)) return ast_poison(statement);
if (dead_code)
{
if (!context->active_scope.allow_dead_code)
{
context->active_scope.allow_dead_code = true;
// If we start with an don't start with an assert AND the scope is a macro, then it's bad.
if (statement->ast_kind != AST_ASSERT_STMT && statement->ast_kind != AST_NOP_STMT && !(context->active_scope.flags & SCOPE_MACRO))
{
SEMA_ERROR(statement, "This code will never execute.");
return ast_poison(statement);
}
// Remove it
statement->ast_kind = AST_NOP_STMT;
}
}
return true;
}
static bool sema_analyse_require(SemaContext *context, Ast *directive, AstId **asserts, SourceSpan span)
{
return assert_create_from_contract(context, directive, asserts, span);
}
static bool sema_analyse_ensure(SemaContext *context, Ast *directive)
{
Expr *declexpr = directive->contract_stmt.contract.decl_exprs;
assert(declexpr->expr_kind == EXPR_EXPRESSION_LIST);
VECEACH(declexpr->expression_list, j)
{
Expr *expr = declexpr->expression_list[j];
if (expr->expr_kind == EXPR_DECL)
{
SEMA_ERROR(expr, "Only expressions are allowed.");
return false;
}
}
return true;
}
static bool sema_analyse_optional_returns(SemaContext *context, Ast *directive)
{
Ast **returns = NULL;
context->call_env.opt_returns = NULL;
FOREACH_BEGIN(Ast *ret, directive->contract_stmt.faults)
if (ret->contract_fault.resolved) continue;
TypeInfo *type_info = ret->contract_fault.type;
const char *ident = ret->contract_fault.ident;
if (type_info->kind != TYPE_INFO_IDENTIFIER) RETURN_SEMA_ERROR(type_info, "Expected a fault name here.");
if (!sema_resolve_type_info(context, type_info, RESOLVE_TYPE_DEFAULT)) return false;
Type *type = type_info->type;
if (type->type_kind != TYPE_FAULTTYPE) RETURN_SEMA_ERROR(type_info, "A fault type is required.");
if (!ident)
{
ret->contract_fault.decl = type->decl;
ret->contract_fault.resolved = true;
goto NEXT;
}
Decl *decl = type->decl;
Decl **enums = decl->enums.values;
VECEACH(enums, j)
{
Decl *opt_value = enums[j];
if (opt_value->name == ident)
{
ret->contract_fault.decl = opt_value;
ret->contract_fault.resolved = true;
goto NEXT;
}
}
RETURN_SEMA_ERROR(ret, "No fault value '%s' found.", ident);
NEXT:;
vec_add(context->call_env.opt_returns, ret->contract_fault.decl);
FOREACH_END();
return true;
}
void sema_append_contract_asserts(AstId assert_first, Ast* compound_stmt)
{
assert(compound_stmt->ast_kind == AST_COMPOUND_STMT);
if (!assert_first) return;
Ast *ast = new_ast(AST_COMPOUND_STMT, compound_stmt->span);
ast->compound_stmt.first_stmt = assert_first;
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)
{
while (doc)
{
Ast *directive = astptr(doc);
switch (directive->contract_stmt.kind)
{
case CONTRACT_UNKNOWN:
case CONTRACT_PURE:
break;
case CONTRACT_REQUIRE:
if (!sema_analyse_require(context, directive, asserts, call_span)) return false;
break;
case CONTRACT_PARAM:
break;
case CONTRACT_OPTIONALS:
if (!sema_analyse_optional_returns(context, directive)) return false;
break;
case CONTRACT_ENSURE:
if (!sema_analyse_ensure(context, directive)) return false;
*has_ensures = true;
break;
}
doc = directive->next;
}
return true;
}
bool sema_analyse_function_body(SemaContext *context, Decl *func)
{
if (!decl_ok(func)) return false;
Signature *signature = &func->func_decl.signature;
FunctionPrototype *prototype = func->type->function.prototype;
assert(prototype);
context->original_inline_line = 0;
context->original_module = NULL;
context->call_env = (CallEnv) {
.current_function = func,
.kind = CALL_ENV_FUNCTION,
.pure = func->func_decl.signature.attrs.is_pure
};
context->rtype = prototype->rtype;
context->macro_call_depth = 0;
context->active_scope = (DynamicScope) {
.scope_id = 0,
.depth = 0,
.label_start = 0,
.current_local = 0
};
vec_resize(context->ct_locals, 0);
// Clear returns
vec_resize(context->returns, 0);
context->scope_id = 0;
context->continue_target = NULL;
context->next_target = 0;
context->next_switch = 0;
context->break_target = 0;
assert(func->func_decl.body);
Ast *body = astptr(func->func_decl.body);
Decl **lambda_params = NULL;
SCOPE_START
assert(context->active_scope.depth == 1);
Decl **params = signature->params;
VECEACH(params, i)
{
if (!sema_add_local(context, params[i])) return false;
}
if (func->func_decl.is_lambda)
{
lambda_params = copy_decl_list_single(func->func_decl.lambda_ct_parameters);
FOREACH_BEGIN(Decl *ct_param, lambda_params)
ct_param->var.is_read = false;
if (!sema_add_local(context, ct_param)) return false;
FOREACH_END();
}
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;
context->call_env.ensures = has_ensures;
bool is_naked = func->func_decl.attr_naked;
if (!is_naked) sema_append_contract_asserts(assert_first, body);
Type *canonical_rtype = type_no_optional(prototype->rtype)->canonical;
if (!sema_analyse_compound_statement_no_scope(context, body)) return false;
assert(context->active_scope.depth == 1);
if (!context->active_scope.jump_end && canonical_rtype != type_void)
{
SEMA_ERROR(func, "Missing return statement at the end of the function.");
return false;
}
SCOPE_END;
if (lambda_params)
{
FOREACH_BEGIN_IDX(i, Decl *ct_param, lambda_params)
func->func_decl.lambda_ct_parameters[i]->var.is_read = ct_param->var.is_read;
FOREACH_END();
}
return true;
}