aboutsummaryrefslogtreecommitdiff
path: root/src/check_stmt.cpp
diff options
context:
space:
mode:
authorCourtney Strachan <courtney.strachan@gmail.com>2025-10-06 02:41:44 +0100
committerGitHub <noreply@github.com>2025-10-06 02:41:44 +0100
commit6de2d6e8ca687c989bbb7806e5cbe8d791e425bf (patch)
tree03a2e0a84c7c1530215f8e3f59a7f643b39b3677 /src/check_stmt.cpp
parentdbbe96ae5c343f0e803de6ee508207a62571534f (diff)
parent0f97382fa3e46da80705c00dfe02f3deb9562e4f (diff)
Merge branch 'odin-lang:master' into master
Diffstat (limited to 'src/check_stmt.cpp')
-rw-r--r--src/check_stmt.cpp256
1 files changed, 179 insertions, 77 deletions
diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp
index e81996566..5e7a8e323 100644
--- a/src/check_stmt.cpp
+++ b/src/check_stmt.cpp
@@ -418,7 +418,7 @@ gb_internal bool check_is_terminating(Ast *node, String const &label) {
-gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, Operand *rhs) {
+gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, Operand *rhs, String context_name) {
if (rhs->mode == Addressing_Invalid) {
return nullptr;
}
@@ -430,8 +430,6 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
Ast *node = unparen_expr(lhs->expr);
- check_no_copy_assignment(*rhs, str_lit("assignment"));
-
// NOTE(bill): Ignore assignments to '_'
if (is_blank_ident(node)) {
check_assignment(ctx, rhs, nullptr, str_lit("assignment to '_' identifier"));
@@ -486,7 +484,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
}
if (ident_node != nullptr) {
ast_node(i, Ident, ident_node);
- e = scope_lookup(ctx->scope, i->token.string);
+ e = scope_lookup(ctx->scope, i->token.string, i->hash);
if (e != nullptr && e->kind == Entity_Variable) {
used = (e->flags & EntityFlag_Used) != 0; // NOTE(bill): Make backup just in case
}
@@ -630,7 +628,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
ctx->bit_field_bit_size = lhs_e->Variable.bit_field_bit_size;
}
- check_assignment(ctx, rhs, assignment_type, str_lit("assignment"));
+ check_assignment(ctx, rhs, assignment_type, context_name);
ctx->bit_field_bit_size = prev_bit_field_bit_size;
@@ -976,7 +974,14 @@ gb_internal void check_unroll_range_stmt(CheckerContext *ctx, Ast *node, u32 mod
Type *t = base_type(operand.type);
switch (t->kind) {
case Type_Basic:
- if (is_type_string(t) && t->Basic.kind != Basic_cstring) {
+ if (is_type_string16(t) && t->Basic.kind != Basic_cstring) {
+ val0 = t_rune;
+ val1 = t_int;
+ inline_for_depth = exact_value_i64(operand.value.value_string.len);
+ if (unroll_count > 0) {
+ error(node, "#unroll(%lld) does not support strings", cast(long long)unroll_count);
+ }
+ } else if (is_type_string(t) && t->Basic.kind != Basic_cstring) {
val0 = t_rune;
val1 = t_int;
inline_for_depth = exact_value_i64(operand.value.value_string.len);
@@ -1238,7 +1243,11 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags
add_to_seen_map(ctx, &seen, upper_op, x, lhs, rhs);
- if (is_type_string(x.type)) {
+ if (is_type_string16(x.type)) {
+ // NOTE(bill): Force dependency for strings here
+ add_package_dependency(ctx, "runtime", "string16_le");
+ add_package_dependency(ctx, "runtime", "string16_lt");
+ } else if (is_type_string(x.type)) {
// NOTE(bill): Force dependency for strings here
add_package_dependency(ctx, "runtime", "string_le");
add_package_dependency(ctx, "runtime", "string_lt");
@@ -1593,6 +1602,20 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_
error_line("\tSuggestion: Was '#partial switch' wanted?\n");
}
}
+
+ if (build_context.strict_style) {
+ Token stok = ss->token;
+ for_array(i, bs->stmts) {
+ Ast *stmt = bs->stmts[i];
+ if (stmt->kind != Ast_CaseClause) {
+ continue;
+ }
+ Token ctok = stmt->CaseClause.token;
+ if (ctok.pos.column > stok.pos.column) {
+ error(ctok, "With '-strict-style', 'case' statements must share the same column as the 'switch' token");
+ }
+ }
+ }
}
gb_internal void check_block_stmt_for_errors(CheckerContext *ctx, Ast *body) {
@@ -1758,7 +1781,16 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
switch (t->kind) {
case Type_Basic:
- if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) {
+ if (t->Basic.kind == Basic_string16) {
+ is_possibly_addressable = false;
+ array_add(&vals, t_rune);
+ array_add(&vals, t_int);
+ if (is_reverse) {
+ add_package_dependency(ctx, "runtime", "string16_decode_last_rune");
+ } else {
+ add_package_dependency(ctx, "runtime", "string16_decode_rune");
+ }
+ } else if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) {
is_possibly_addressable = false;
array_add(&vals, t_rune);
array_add(&vals, t_int);
@@ -1780,8 +1812,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
error(node, "Iteration over a bit_set of an enum is not allowed runtime type information (RTTI) has been disallowed");
}
if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) {
- String name = rs->vals[0]->Ident.token.string;
- Entity *found = scope_lookup(ctx->scope, name);
+ AstIdent *ident = &rs->vals[0]->Ident;
+ String name = ident->token.string;
+ Entity *found = scope_lookup(ctx->scope, name, ident->hash);
if (found && are_types_identical(found->type, t->BitSet.elem)) {
ERROR_BLOCK();
gbString s = expr_to_string(expr);
@@ -1825,8 +1858,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
error(node, "#reverse for is not supported for map types, as maps are unordered");
}
if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) {
- String name = rs->vals[0]->Ident.token.string;
- Entity *found = scope_lookup(ctx->scope, name);
+ AstIdent *ident = &rs->vals[0]->Ident;
+ String name = ident->token.string;
+ Entity *found = scope_lookup(ctx->scope, name, ident->hash);
if (found && are_types_identical(found->type, t->Map.key)) {
ERROR_BLOCK();
gbString s = expr_to_string(expr);
@@ -1931,7 +1965,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags)
}
auto rhs = slice_from_array(vals);
- auto lhs = slice_make<Ast *>(temporary_allocator(), rhs.count);
+ auto lhs = temporary_slice_make<Ast *>(rhs.count);
slice_copy(&lhs, rs->vals);
isize addressable_index = cast(isize)is_map;
@@ -2108,10 +2142,12 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f
if (init_type == nullptr) {
init_type = t_invalid;
} else if (is_type_polymorphic(base_type(init_type))) {
+ /* DISABLED: This error seems too aggressive for instantiated generic types.
gbString str = type_to_string(init_type);
error(vd->type, "Invalid use of a polymorphic type '%s' in variable declaration", str);
gb_string_free(str);
init_type = t_invalid;
+ */
}
if (init_type == t_invalid && entity_count == 1 && (mod_flags & (Stmt_BreakAllowed|Stmt_FallthroughAllowed))) {
Entity *e = entities[0];
@@ -2402,7 +2438,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
isize lhs_count = as->lhs.count;
if (lhs_count == 0) {
- error(as->op, "Missing lhs in assignment statement");
+ error(as->op, "Missing LHS in assignment statement");
return;
}
@@ -2435,7 +2471,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
if (lhs_to_ignore[i]) {
continue;
}
- check_assignment_variable(ctx, &lhs_operands[i], &rhs_operands[i]);
+ check_assignment_variable(ctx, &lhs_operands[i], &rhs_operands[i], str_lit("assignment"));
}
if (lhs_count != rhs_count) {
error(as->lhs[0], "Assignment count mismatch '%td' = '%td'", lhs_count, rhs_count);
@@ -2445,11 +2481,11 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
// a += 1; // Single-sided
Token op = as->op;
if (as->lhs.count != 1 || as->rhs.count != 1) {
- error(op, "Assignment operation '%.*s' requires single-valued expressions", LIT(op.string));
+ error(op, "Assignment operator '%.*s' requires single-valued operands", LIT(op.string));
return;
}
if (!gb_is_between(op.kind, Token__AssignOpBegin+1, Token__AssignOpEnd-1)) {
- error(op, "Unknown Assignment operation '%.*s'", LIT(op.string));
+ error(op, "Unknown assignment operator '%.*s'", LIT(op.string));
return;
}
Operand lhs = {Addressing_Invalid};
@@ -2458,15 +2494,16 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
ast_node(be, BinaryExpr, binary_expr);
be->op = op;
be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
- // NOTE(bill): Only use the first one will be used
+ // NOTE(bill): Only use the first one will be used
be->left = as->lhs[0];
be->right = as->rhs[0];
check_expr(ctx, &lhs, as->lhs[0]);
check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
if (rhs.mode != Addressing_Invalid) {
- // NOTE(bill): Only use the first one will be used
- check_assignment_variable(ctx, &lhs, &rhs);
+ be->op.string = substring(be->op.string, 0, be->op.string.len - 1);
+ rhs.expr = binary_expr;
+ check_assignment_variable(ctx, &lhs, &rhs, str_lit("assignment operation"));
}
}
}
@@ -2504,6 +2541,78 @@ gb_internal void check_if_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
check_close_scope(ctx);
}
+// NOTE(bill): This is very basic escape analysis
+// This needs to be improved tremendously, and a lot of it done during the
+// middle-end (or LLVM side) to improve checks and error messages
+void check_unsafe_return(Operand const &o, Type *type, Ast *expr) {
+ auto const unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) {
+ gbString s = expr_to_string(o.expr);
+ if (extra_type) {
+ gbString t = type_to_string(extra_type);
+ error(o.expr, "It is unsafe to return %s ('%s') of type ('%s') from a procedure, as it uses the current stack frame's memory", msg, s, t);
+ gb_string_free(t);
+ } else {
+ error(o.expr, "It is unsafe to return %s ('%s') from a procedure, as it uses the current stack frame's memory", msg, s);
+ }
+ gb_string_free(s);
+ };
+
+ if (type == nullptr || expr == nullptr) {
+ return;
+ }
+
+ if (expr->kind == Ast_CompoundLit && is_type_slice(type)) {
+ ast_node(cl, CompoundLit, expr);
+ if (cl->elems.count == 0) {
+ return;
+ }
+ unsafe_return_error(o, "a compound literal of a slice");
+ } else if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) {
+ Ast *x = unparen_expr(expr->UnaryExpr.expr);
+ Entity *e = entity_of_node(x);
+ if (is_entity_local_variable(e)) {
+ unsafe_return_error(o, "the address of a local variable");
+ } else if (x->kind == Ast_CompoundLit) {
+ unsafe_return_error(o, "the address of a compound literal");
+ } else if (x->kind == Ast_IndexExpr) {
+ Entity *f = entity_of_node(x->IndexExpr.expr);
+ if (f && (is_type_array_like(f->type) || is_type_matrix(f->type))) {
+ if (is_entity_local_variable(f)) {
+ unsafe_return_error(o, "the address of an indexed variable", f->type);
+ }
+ }
+ } else if (x->kind == Ast_MatrixIndexExpr) {
+ Entity *f = entity_of_node(x->MatrixIndexExpr.expr);
+ if (f && (is_type_matrix(f->type) && is_entity_local_variable(f))) {
+ unsafe_return_error(o, "the address of an indexed variable", f->type);
+ }
+ }
+ } else if (expr->kind == Ast_SliceExpr) {
+ Ast *x = unparen_expr(expr->SliceExpr.expr);
+ Entity *e = entity_of_node(x);
+ if (is_entity_local_variable(e) && is_type_array(e->type)) {
+ unsafe_return_error(o, "a slice of a local variable");
+ } else if (x->kind == Ast_CompoundLit) {
+ unsafe_return_error(o, "a slice of a compound literal");
+ }
+ } else if (o.mode == Addressing_Constant && is_type_slice(type)) {
+ ERROR_BLOCK();
+ unsafe_return_error(o, "a compound literal of a slice");
+ error_line("\tNote: A constant slice value will use the memory of the current stack frame\n");
+ } else if (expr->kind == Ast_CompoundLit) {
+ ast_node(cl, CompoundLit, expr);
+ for (Ast *elem : cl->elems) {
+ if (elem->kind == Ast_FieldValue) {
+ ast_node(fv, FieldValue, elem);
+ Entity *e = entity_of_node(fv->field);
+ if (e != nullptr) {
+ check_unsafe_return(o, e->type, fv->value);
+ }
+ }
+ }
+ }
+}
+
gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
ast_node(rs, ReturnStmt, node);
@@ -2532,8 +2641,9 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
result_count = proc_type->Proc.results->Tuple.variables.count;
}
- auto operands = array_make<Operand>(heap_allocator(), 0, 2*rs->results.count);
- defer (array_free(&operands));
+ TEMPORARY_ALLOCATOR_GUARD();
+
+ auto operands = array_make<Operand>(temporary_allocator(), 0, 2*rs->results.count);
check_unpack_arguments(ctx, result_entities, result_count, &operands, rs->results, UnpackFlag_AllowOk);
@@ -2579,61 +2689,7 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
expr = unparen_expr(arg);
}
- auto unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) {
- gbString s = expr_to_string(o.expr);
- if (extra_type) {
- gbString t = type_to_string(extra_type);
- error(o.expr, "It is unsafe to return %s ('%s') of type ('%s') from a procedure, as it uses the current stack frame's memory", msg, s, t);
- gb_string_free(t);
- } else {
- error(o.expr, "It is unsafe to return %s ('%s') from a procedure, as it uses the current stack frame's memory", msg, s);
- }
- gb_string_free(s);
- };
-
-
- // NOTE(bill): This is very basic escape analysis
- // This needs to be improved tremendously, and a lot of it done during the
- // middle-end (or LLVM side) to improve checks and error messages
- if (expr->kind == Ast_CompoundLit && is_type_slice(o.type)) {
- ast_node(cl, CompoundLit, expr);
- if (cl->elems.count == 0) {
- continue;
- }
- unsafe_return_error(o, "a compound literal of a slice");
- } else if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) {
- Ast *x = unparen_expr(expr->UnaryExpr.expr);
- Entity *e = entity_of_node(x);
- if (is_entity_local_variable(e)) {
- unsafe_return_error(o, "the address of a local variable");
- } else if (x->kind == Ast_CompoundLit) {
- unsafe_return_error(o, "the address of a compound literal");
- } else if (x->kind == Ast_IndexExpr) {
- Entity *f = entity_of_node(x->IndexExpr.expr);
- if (f && (is_type_array_like(f->type) || is_type_matrix(f->type))) {
- if (is_entity_local_variable(f)) {
- unsafe_return_error(o, "the address of an indexed variable", f->type);
- }
- }
- } else if (x->kind == Ast_MatrixIndexExpr) {
- Entity *f = entity_of_node(x->MatrixIndexExpr.expr);
- if (f && (is_type_matrix(f->type) && is_entity_local_variable(f))) {
- unsafe_return_error(o, "the address of an indexed variable", f->type);
- }
- }
- } else if (expr->kind == Ast_SliceExpr) {
- Ast *x = unparen_expr(expr->SliceExpr.expr);
- Entity *e = entity_of_node(x);
- if (is_entity_local_variable(e) && is_type_array(e->type)) {
- unsafe_return_error(o, "a slice of a local variable");
- } else if (x->kind == Ast_CompoundLit) {
- unsafe_return_error(o, "a slice of a compound literal");
- }
- } else if (o.mode == Addressing_Constant && is_type_slice(o.type)) {
- ERROR_BLOCK();
- unsafe_return_error(o, "a compound literal of a slice");
- error_line("\tNote: A constant slice value will use the memory of the current stack frame\n");
- }
+ check_unsafe_return(o, o.type, expr);
}
}
@@ -2755,6 +2811,51 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags)
if (ctx->decl) {
ctx->decl->defer_used += 1;
}
+
+ // NOTE(bill): Handling errors/warnings
+
+ Ast *stmt = ds->stmt;
+ Ast *original_stmt = stmt;
+
+ if (stmt->kind == Ast_BlockStmt && stmt->BlockStmt.stmts.count == 0) {
+ break; // empty defer statement
+ }
+
+ bool is_singular = true;
+ while (is_singular && stmt->kind == Ast_BlockStmt) {
+ Ast *inner_stmt = nullptr;
+ for (Ast *s : stmt->BlockStmt.stmts) {
+ if (s->kind == Ast_EmptyStmt) {
+ continue;
+ }
+ if (inner_stmt != nullptr) {
+ is_singular = false;
+ break;
+ }
+ inner_stmt = s;
+ }
+
+ if (inner_stmt != nullptr) {
+ stmt = inner_stmt;
+ }
+ }
+ if (!is_singular) {
+ stmt = original_stmt;
+ }
+
+ switch (stmt->kind) {
+ case_ast_node(as, AssignStmt, stmt);
+ if (as->op.kind != Token_Eq) {
+ break;
+ }
+ for (Ast *lhs : as->lhs) {
+ Entity *e = entity_of_node(lhs);
+ if (e && e->flags & EntityFlag_Result) {
+ error(lhs, "Assignments to named return values within 'defer' will not affect the value that is returned");
+ }
+ }
+ case_end;
+ }
}
case_end;
@@ -2812,6 +2913,7 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags)
case Ast_BlockStmt:
case Ast_IfStmt:
case Ast_SwitchStmt:
+ case Ast_TypeSwitchStmt:
if (token.kind != Token_break) {
error(bs->label, "Label '%.*s' can only be used with 'break'", LIT(e->token.string));
}