From e8f2c5a48a7d5214925b9722b26270ca7eb3dd3c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 4 Jul 2021 01:38:43 +0100 Subject: [PATCH] [Experimental] Add 'try' and `or_else' built-in procedures --- examples/demo/demo.odin | 141 +++++++++++++++++ src/check_builtin.cpp | 182 ++++++++++++++++++++++ src/check_expr.cpp | 71 ++++----- src/check_stmt.cpp | 2 +- src/checker_builtin_procs.hpp | 6 + src/llvm_backend.cpp | 276 +++++++++++++++++++++++++++++++++- 6 files changed, 628 insertions(+), 50 deletions(-) diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index cbffc1956..5538d5b1c 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1999,6 +1999,146 @@ relative_data_types :: proc() { fmt.println(rel_slice[1]); } +try_and_or_else :: proc() { + fmt.println("\n#try(...) and or_else(...)"); + // IMPORTANT NOTE: 'try' and 'or_else' are experimental features and subject to change/removal + + Foo :: struct {}; + Error :: enum { + None, + Something, + Whatever, + }; + + bar :: proc(ok: bool) -> (f: Foo, err: Error) { + if !ok { + err = .Something; + } + return; + } + + + try_return_value :: proc() -> Error { + // This is a common idiom, where the end value of an expression + // may not be 'nil' or may be 'false' + f0, err := bar(true); + if err != nil { + return err; + } + _ = f0; + + // 'try' is a lovely shorthand that does this check automatically + // and returns early if necessary + f1 := try(bar(true)); + fmt.println(#procedure); + fmt.println(f1); + + f2 := try(bar(false)); + fmt.println(#procedure); + fmt.println(f2); + return .None; + } + + try_return_value2 :: proc() -> (i: int, err: Error) { + // 'try' will work within procedures with multiple return values + // However, the return values must be named + // 'try' effectively pops off the last value and checks it + // And then returns the rest of the values, meaning it works + // for as many return values as possible + i = 0; + f0, f0_err := bar(true); + if f0_err != nil { + err = f0_err; + return; + } + fmt.println(#procedure); + fmt.println(f0); + + // The above can be translated into 'try' + i = 1; + f1 := try(bar(true)); + fmt.println(#procedure); + fmt.println(f1); + + i = 2; + + f2 := try(bar(false)); + fmt.println(#procedure); + fmt.println(f2); + + i = 3; + + return i, .None; + } + + try_return_value4 :: proc() -> (i: int, j: f64, k: bool, err: Error) { + f := try(bar(false)); + fmt.println(#procedure); + fmt.println(f); + return 123, 456, true, .None; + } + + + try_optional_ok :: proc() -> bool { + m: map[string]int; + /* + f1, ok := m["hellope"]; + if !ok { + return false; + } + */ + // 'try' equivalent + f2 := try(m["hellope"]); + fmt.println(f2); + return true; + } + + { + // 'try' examples + err := try_return_value(); + fmt.println(err); + + ok := try_optional_ok(); + fmt.println(ok); + + i, err2 := try_return_value2(); + fmt.println(i); + fmt.println(err2); + + a, b, c, err4 := try_return_value4(); + assert(a == 0 && b == 0 && c == false && err4 == .Something); + } + { + // 'or_else' does a similar value check as 'try' but instead of doing an + // early return, it will give a default value to be used instead + + m: map[string]int; + i: int; + ok: bool; + + if i, ok = m["hellope"]; !ok { + i = 123; + } + // The above can be mapped to 'or_else' + i = or_else(m["hellope"], 123); + + assert(i == 123); + } + { + // 'or_else' can be used with type assertions too, as they + // have optional ok semantics + v: union{int, f64}; + i: int; + i = or_else(v.(int), 123); + i = or_else(v.?, 123); // Type inference magic + assert(i == 123); + + m: Maybe(int); + i = or_else(m.?, 456); + assert(i == 456); + } +} + main :: proc() { when true { the_basics(); @@ -2031,5 +2171,6 @@ main :: proc() { union_maybe(); explicit_context_definition(); relative_data_types(); + try_and_or_else(); } } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 378779dc6..23c489482 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -47,6 +47,179 @@ BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_boolean_end - type_has_nil, }; +void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_) { + switch (x->mode) { + case Addressing_MapIndex: + case Addressing_OptionalOk: + case Addressing_OptionalOkPtr: + if (val_type_) *val_type_ = x->type; + break; + default: + if (ok_type_) *ok_type_ = x->type; + return; + } + + Ast *expr = unparen_expr(x->expr); + + if (expr->kind == Ast_CallExpr) { + Type *pt = base_type(type_of_expr(expr->CallExpr.proc)); + if (is_type_proc(pt)) { + Type *tuple = pt->Proc.results; + add_type_and_value(&c->checker->info, x->expr, x->mode, tuple, x->value); + + if (pt->Proc.result_count >= 2) { + if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type; + } + expr->CallExpr.optional_ok_one = false; + x->type = tuple; + return; + } + } + + Type *tuple = make_optional_ok_type(x->type); + if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type; + add_type_and_value(&c->checker->info, x->expr, x->mode, tuple, x->value); + x->type = tuple; + GB_ASSERT(is_type_tuple(type_of_expr(x->expr))); +} + +void check_try_split_types(CheckerContext *c, Operand *x, String const &name, Type **left_type_, Type **right_type_) { + Type *left_type = nullptr; + Type *right_type = nullptr; + if (x->type->kind == Type_Tuple) { + auto const &vars = x->type->Tuple.variables; + auto lhs = array_slice(vars, 0, vars.count-1); + auto rhs = vars[vars.count-1]; + if (lhs.count == 1) { + left_type = lhs[0]->type; + } else if (lhs.count != 0) { + left_type = alloc_type_tuple(); + left_type->Tuple.variables = array_make_from_ptr(lhs.data, lhs.count, lhs.count); + } + + right_type = rhs->type; + } else { + check_promote_optional_ok(c, x, &left_type, &right_type); + } + + if (left_type_) *left_type_ = left_type; + if (right_type_) *right_type_ = right_type; + + if (!type_has_nil(right_type) && !is_type_boolean(right_type)) { + gbString str = type_to_string(right_type); + error(x->expr, "'%.*s' expects an \"optional ok\" like value, or an n-valued expression where the last value is either a boolean or can be compared against 'nil', got %s", LIT(name), str); + gb_string_free(str); + } +} + +bool check_builtin_try(CheckerContext *c, Operand *operand, String const &name, Ast *call, Type *type_hint) { + ast_node(ce, CallExpr, call); + + Operand x = {}; + check_multi_expr_with_type_hint(c, &x, ce->args[0], type_hint); + if (x.mode == Addressing_Invalid) { + return false; + } + + if (c->in_defer) { + error(call, "'%.*s' cannot be used within a defer statement", LIT(name)); + } + + Type *left_type = nullptr; + Type *right_type = nullptr; + check_try_split_types(c, &x, name, &left_type, &right_type); + add_type_and_value(&c->checker->info, ce->args[0], x.mode, x.type, x.value); + + if (c->curr_proc_sig == nullptr) { + error(call, "'%.*s' can only be used within a procedure", LIT(name)); + } + Type *proc_type = base_type(c->curr_proc_sig); + GB_ASSERT(proc_type->kind == Type_Proc); + Type *result_type = proc_type->Proc.results; + if (result_type == nullptr) { + error(call, "'%.*s' requires the current procedure to have at least one return value", LIT(name)); + } else { + GB_ASSERT(result_type->kind == Type_Tuple); + + auto const &vars = result_type->Tuple.variables; + Type *end_type = vars[vars.count-1]->type; + + if (vars.count > 1) { + if (!proc_type->Proc.has_named_results) { + error(call, "'%.*s' within a procedure with more than 1 return value requires that the return values are named, allowing for early return", LIT(name)); + } + } + + Operand rhs = {}; + rhs.type = right_type; + rhs.mode = Addressing_Value; + + // TODO(bill): better error message + if (!check_is_assignable_to(c, &rhs, end_type)) { + gbString a = type_to_string(right_type); + gbString b = type_to_string(end_type); + gbString ret_type = type_to_string(result_type); + error(call, "Cannot assign end value of type '%s' to '%s' in '%.*s'", a, b, LIT(name)); + if (vars.count == 1) { + error_line("\tProcedure return value type: %s\n", ret_type); + } else { + error_line("\tProcedure return value types: (%s)\n", ret_type); + } + gb_string_free(ret_type); + gb_string_free(b); + gb_string_free(a); + } + } + + + if (left_type != nullptr) { + operand->mode = Addressing_Value; + operand->type = left_type; + } else { + operand->mode = Addressing_NoValue; + operand->type = nullptr; + } + + return true; +} + +bool check_builtin_or_else(CheckerContext *c, Operand *operand, String const &name, Ast *call, Type *type_hint) { + ast_node(ce, CallExpr, call); + + Operand x = {}; + Operand y = {}; + check_multi_expr_with_type_hint(c, &x, ce->args[0], type_hint); + if (x.mode == Addressing_Invalid) { + return false; + } + + check_multi_expr(c, &y, ce->args[1]); + error_operand_no_value(&y); + if (y.mode == Addressing_Invalid) { + return false; + } + + Type *left_type = nullptr; + Type *right_type = nullptr; + check_try_split_types(c, &x, name, &left_type, &right_type); + add_type_and_value(&c->checker->info, ce->args[0], x.mode, x.type, x.value); + + if (left_type != nullptr) { + check_assignment(c, &y, left_type, name); + } else { + // TODO(bill): better error message + error(call, "'%.*s' does not return a value", LIT(name)); + } + + + if (left_type == nullptr) { + left_type = t_invalid; + } + operand->mode = Addressing_Value; + operand->type = left_type; + + return true; +} bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) { @@ -86,6 +259,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 // NOTE(bill): The first arg may be a Type, this will be checked case by case break; + case BuiltinProc_try: + case BuiltinProc_or_else: + // NOTE(bill): The first arg may be a tuple + break; + case BuiltinProc_DIRECTIVE: { ast_node(bd, BasicDirective, ce->proc); String name = bd->name.string; @@ -1713,6 +1891,10 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } + case BuiltinProc_try: + return check_builtin_try(c, operand, builtin_name, call, type_hint); + case BuiltinProc_or_else: + return check_builtin_or_else(c, operand, builtin_name, call, type_hint); case BuiltinProc_simd_vector: { Operand x = {}; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 537fb73cc..57d10f60e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -111,6 +111,8 @@ Type *make_soa_struct_dynamic_array(CheckerContext *ctx, Ast *array_typ_expr, As bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint); +void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_); + Entity *entity_from_expr(Ast *expr) { expr = unparen_expr(expr); switch (expr->kind) { @@ -4045,26 +4047,7 @@ bool check_assignment_arguments(CheckerContext *ctx, Array const &lhs, val1.mode = Addressing_Value; val1.type = t_untyped_bool; - - if (expr->kind == Ast_CallExpr) { - Type *pt = base_type(type_of_expr(expr->CallExpr.proc)); - if (is_type_proc(pt)) { - do_normal = false; - Type *tuple = pt->Proc.results; - add_type_and_value(&c->checker->info, o.expr, o.mode, tuple, o.value); - - if (pt->Proc.result_count >= 2) { - Type *t1 = tuple->Tuple.variables[1]->type; - val1.type = t1; - } - expr->CallExpr.optional_ok_one = false; - } - } - - if (do_normal) { - Type *tuple = make_optional_ok_type(o.type); - add_type_and_value(&c->checker->info, o.expr, o.mode, tuple, o.value); - } + check_promote_optional_ok(c, &o, nullptr, &val1.type); if (expr->kind == Ast_TypeAssertion && (o.mode == Addressing_OptionalOk || o.mode == Addressing_OptionalOkPtr)) { @@ -4170,26 +4153,7 @@ bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count, val1.mode = Addressing_Value; val1.type = t_untyped_bool; - - if (expr->kind == Ast_CallExpr) { - Type *pt = base_type(type_of_expr(expr->CallExpr.proc)); - if (is_type_proc(pt)) { - do_normal = false; - Type *tuple = pt->Proc.results; - add_type_and_value(&c->checker->info, o.expr, o.mode, tuple, o.value); - - if (pt->Proc.result_count >= 2) { - Type *t1 = tuple->Tuple.variables[1]->type; - val1.type = t1; - } - expr->CallExpr.optional_ok_one = false; - } - } - - if (do_normal) { - Type *tuple = make_optional_ok_type(o.type); - add_type_and_value(&c->checker->info, o.expr, o.mode, tuple, o.value); - } + check_promote_optional_ok(c, &o, nullptr, &val1.type); if (expr->kind == Ast_TypeAssertion && (o.mode == Addressing_OptionalOk || o.mode == Addressing_OptionalOkPtr)) { @@ -8192,6 +8156,21 @@ void check_multi_expr(CheckerContext *c, Operand *o, Ast *e) { o->mode = Addressing_Invalid; } +void check_multi_expr_with_type_hint(CheckerContext *c, Operand *o, Ast *e, Type *type_hint) { + check_expr_base(c, o, e, type_hint); + switch (o->mode) { + default: + return; // NOTE(bill): Valid + case Addressing_NoValue: + error_operand_no_value(o); + break; + case Addressing_Type: + error_operand_not_expression(o); + break; + } + o->mode = Addressing_Invalid; +} + void check_not_tuple(CheckerContext *c, Operand *o) { if (o->mode == Addressing_Value) { // NOTE(bill): Tuples are not first class thus never named @@ -8472,9 +8451,15 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) { case_ast_node(ta, TypeAssertion, node); str = write_expr_to_string(str, ta->expr, shorthand); - str = gb_string_appendc(str, ".("); - str = write_expr_to_string(str, ta->type, shorthand); - str = gb_string_append_rune(str, ')'); + if (ta->type != nullptr && + ta->type->kind == Ast_UnaryExpr && + ta->type->UnaryExpr.op.kind == Token_Question) { + str = gb_string_appendc(str, ".?"); + } else { + str = gb_string_appendc(str, ".("); + str = write_expr_to_string(str, ta->type, shorthand); + str = gb_string_append_rune(str, ')'); + } case_end; case_ast_node(tc, TypeCast, node); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 24960b33c..d68524994 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1667,7 +1667,7 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) { GB_ASSERT(ctx->curr_proc_sig != nullptr); if (ctx->in_defer) { - error(rs->token, "You cannot 'return' within a defer statement"); + error(rs->token, "'return' cannot be used within a defer statement"); break; } diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 57b5d7eb9..a72231f8b 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -33,6 +33,9 @@ enum BuiltinProcId { BuiltinProc_soa_zip, BuiltinProc_soa_unzip, + BuiltinProc_try, + BuiltinProc_or_else, + BuiltinProc_DIRECTIVE, // NOTE(bill): This is used for specialized hash-prefixed procedures // "Intrinsics" @@ -263,6 +266,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("soa_zip"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("soa_unzip"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, + {STR_LIT("try"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, + {STR_LIT("or_else"), 2, false, Expr_Expr, BuiltinProcPkg_builtin}, + {STR_LIT(""), 0, true, Expr_Expr, BuiltinProcPkg_builtin}, // DIRECTIVE diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index bf5f33e4b..f146956c2 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -5339,14 +5339,14 @@ void lb_build_assignment(lbProcedure *p, Array &lvals, Slice cons } } -void lb_build_return_stmt(lbProcedure *p, AstReturnStmt *rs) { +void lb_build_return_stmt(lbProcedure *p, Slice const &return_results) { lb_ensure_abi_function_type(p->module, p); lbValue res = {}; TypeTuple *tuple = &p->type->Proc.results->Tuple; isize return_count = p->type->Proc.result_count; - isize res_count = rs->results.count; + isize res_count = return_results.count; lbFunctionType *ft = lb_get_function_type(p->module, p, p->type); bool return_by_pointer = ft->ret.kind == lbArg_Indirect; @@ -5365,7 +5365,7 @@ void lb_build_return_stmt(lbProcedure *p, AstReturnStmt *rs) { GB_ASSERT(found); res = lb_emit_load(p, *found); } else { - res = lb_build_expr(p, rs->results[0]); + res = lb_build_expr(p, return_results[0]); res = lb_emit_conv(p, res, e->type); } if (p->type->Proc.has_named_results) { @@ -5382,7 +5382,7 @@ void lb_build_return_stmt(lbProcedure *p, AstReturnStmt *rs) { if (res_count != 0) { for (isize res_index = 0; res_index < res_count; res_index++) { - lbValue res = lb_build_expr(p, rs->results[res_index]); + lbValue res = lb_build_expr(p, return_results[res_index]); Type *t = res.type; if (t->kind == Type_Tuple) { for_array(i, t->Tuple.variables) { @@ -5893,7 +5893,7 @@ void lb_build_stmt(lbProcedure *p, Ast *node) { case_end; case_ast_node(rs, ReturnStmt, node); - lb_build_return_stmt(p, rs); + lb_build_return_stmt(p, rs->results); case_end; case_ast_node(is, IfStmt, node); @@ -9553,6 +9553,257 @@ lbValue lb_soa_unzip(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) { return lb_addr_load(p, res); } +lbValue lb_emit_try(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) { + Ast *arg = ce->args[0]; + + lbValue lhs = {}; + lbValue rhs = {}; + + TypeAndValue const &arg_tav = type_and_value_of_expr(arg); + lbValue value = lb_build_expr(p, arg); + + if (is_type_tuple(value.type)) { + i32 n = cast(i32)(value.type->Tuple.variables.count-1); + if (value.type->Tuple.variables.count == 2) { + lhs = lb_emit_struct_ev(p, value, 0); + } else { + lbAddr lhs_addr = lb_add_local_generated(p, tv.type, false); + lbValue lhs_ptr = lb_addr_get_ptr(p, lhs_addr); + for (i32 i = 0; i < n; i++) { + lb_emit_store(p, lb_emit_struct_ep(p, lhs_ptr, i), lb_emit_struct_ev(p, value, i)); + } + lhs = lb_addr_load(p, lhs_addr); + } + rhs = lb_emit_struct_ev(p, value, n); + } else { + rhs = value; + } + + GB_ASSERT(rhs.value != nullptr); + + lbValue do_early_return = {}; + if (is_type_boolean(rhs.type)) { + do_early_return = lb_emit_unary_arith(p, Token_Not, rhs, t_bool); + } else { + GB_ASSERT(type_has_nil(rhs.type)); + do_early_return = lb_emit_comp_against_nil(p, Token_NotEq, rhs); + } + + GB_ASSERT(do_early_return.value != nullptr); + + + lbBlock *return_block = lb_create_block(p, "try.return", false); + lbBlock *continue_block = lb_create_block(p, "try.continue", false); + lb_emit_if(p, do_early_return, return_block, continue_block); + lb_start_block(p, return_block); + + { + Type *proc_type = base_type(p->type); + // TODO(bill): multiple return values + Type *results = proc_type->Proc.results; + GB_ASSERT(results != nullptr && results->kind == Type_Tuple); + TypeTuple *tuple = &results->Tuple; + + isize return_count = tuple->variables.count; + // TODO(bill) multiple + GB_ASSERT(return_count != 0); + + lbFunctionType *ft = lb_get_function_type(p->module, p, proc_type); + bool return_by_pointer = ft->ret.kind == lbArg_Indirect; + + lbValue res = {}; + if (return_count == 1) { + Entity *e = tuple->variables[0]; + res = lb_emit_conv(p, rhs, e->type); + + if (p->type->Proc.has_named_results) { + // NOTE(bill): store the named values before returning + if (e->token.string != "") { + lbValue *found = map_get(&p->module->values, hash_entity(e)); + GB_ASSERT(found != nullptr); + lb_emit_store(p, *found, lb_emit_conv(p, res, e->type)); + } + } + } else { + GB_ASSERT(p->type->Proc.has_named_results); + auto results = array_make(permanent_allocator(), 0, return_count); + + for (isize res_index = 0; res_index < return_count; res_index++) { + Entity *e = tuple->variables[res_index]; + lbValue *found = map_get(&p->module->values, hash_entity(e)); + GB_ASSERT(found); + lbValue res = lb_emit_load(p, *found); + array_add(&results, res); + } + + GB_ASSERT(results.count == return_count); + + auto named_results = slice_make(temporary_allocator(), results.count); + auto values = slice_make(temporary_allocator(), results.count); + + // NOTE(bill): store the named values before returning + for_array(i, p->type->Proc.results->Tuple.variables) { + Entity *e = p->type->Proc.results->Tuple.variables[i]; + if (e->kind != Entity_Variable) { + continue; + } + + if (e->token.string == "") { + continue; + } + lbValue *found = map_get(&p->module->values, hash_entity(e)); + GB_ASSERT(found != nullptr); + named_results[i] = *found; + values[i] = lb_emit_conv(p, results[i], e->type); + } + + for_array(i, named_results) { + lb_emit_store(p, named_results[i], values[i]); + } + + Type *ret_type = p->type->Proc.results; + + // NOTE(bill): Doesn't need to be zero because it will be initialized in the loops + if (return_by_pointer) { + res = p->return_ptr.addr; + } else { + res = lb_add_local_generated(p, ret_type, false).addr; + } + + auto result_values = slice_make(temporary_allocator(), results.count); + auto result_eps = slice_make(temporary_allocator(), results.count); + + for_array(i, results) { + result_values[i] = lb_emit_conv(p, results[i], tuple->variables[i]->type); + } + for_array(i, results) { + result_eps[i] = lb_emit_struct_ep(p, res, cast(i32)i); + } + for_array(i, result_values) { + lb_emit_store(p, result_eps[i], result_values[i]); + } + + if (return_by_pointer) { + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + LLVMBuildRetVoid(p->builder); + goto end; + } + + res = lb_emit_load(p, res); + } + + + if (return_by_pointer) { + if (res.value != nullptr) { + LLVMBuildStore(p->builder, res.value, p->return_ptr.addr.value); + } else { + LLVMBuildStore(p->builder, LLVMConstNull(p->abi_function_type->ret.type), p->return_ptr.addr.value); + } + + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + + LLVMBuildRetVoid(p->builder); + } else { + LLVMValueRef ret_val = res.value; + ret_val = OdinLLVMBuildTransmute(p, ret_val, p->abi_function_type->ret.type); + if (p->abi_function_type->ret.cast_type != nullptr) { + ret_val = OdinLLVMBuildTransmute(p, ret_val, p->abi_function_type->ret.cast_type); + } + + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + LLVMBuildRet(p->builder, ret_val); + } + } + +end:; + lb_start_block(p, continue_block); + + if (tv.type != nullptr) { + return lb_emit_conv(p, lhs, tv.type); + } + return {}; +} + + +lbValue lb_emit_or_else(lbProcedure *p, AstCallExpr *ce, TypeAndValue const &tv) { + Ast *arg = ce->args[0]; + Ast *else_value = ce->args[1]; + + lbValue lhs = {}; + lbValue rhs = {}; + + TypeAndValue const &arg_tav = type_and_value_of_expr(arg); + if (unparen_expr(arg)->kind == Ast_TypeAssertion) { + GB_ASSERT_MSG(is_type_tuple(arg_tav.type), "%s", type_to_string(arg_tav.type)); + } + lbValue value = lb_build_expr(p, arg); + + if (is_type_tuple(value.type)) { + i32 end_index = cast(i32)(value.type->Tuple.variables.count-1); + if (value.type->Tuple.variables.count == 2) { + lhs = lb_emit_struct_ev(p, value, 0); + } else { + lbAddr lhs_addr = lb_add_local_generated(p, tv.type, false); + lbValue lhs_ptr = lb_addr_get_ptr(p, lhs_addr); + for (i32 i = 0; i < end_index; i++) { + lb_emit_store(p, lb_emit_struct_ep(p, lhs_ptr, i), lb_emit_struct_ev(p, value, i)); + } + lhs = lb_addr_load(p, lhs_addr); + } + rhs = lb_emit_struct_ev(p, value, end_index); + } else { + rhs = value; + } + + GB_ASSERT(rhs.value != nullptr); + + lbValue has_value = {}; + if (is_type_boolean(rhs.type)) { + has_value = rhs; + } else { + GB_ASSERT_MSG(type_has_nil(rhs.type), "%s", type_to_string(rhs.type)); + has_value = lb_emit_comp_against_nil(p, Token_CmpEq, rhs); + } + + GB_ASSERT(has_value.value != nullptr); + + LLVMValueRef incoming_values[2] = {}; + LLVMBasicBlockRef incoming_blocks[2] = {}; + + GB_ASSERT(else_value != nullptr); + lbBlock *then = lb_create_block(p, "or_else.then"); + lbBlock *done = lb_create_block(p, "or_else.done"); // NOTE(bill): Append later + lbBlock *else_ = lb_create_block(p, "or_else.else"); + + lb_emit_if(p, has_value, then, else_); + lb_start_block(p, then); + + Type *type = default_type(tv.type); + + incoming_values[0] = lb_emit_conv(p, lhs, type).value; + + lb_emit_jump(p, done); + lb_start_block(p, else_); + + incoming_values[1] = lb_emit_conv(p, lb_build_expr(p, else_value), type).value; + + lb_emit_jump(p, done); + lb_start_block(p, done); + + lbValue res = {}; + res.value = LLVMBuildPhi(p->builder, lb_type(p->module, type), ""); + res.type = type; + + GB_ASSERT(p->curr_block->preds.count >= 2); + incoming_blocks[0] = p->curr_block->preds[0]->block; + incoming_blocks[1] = p->curr_block->preds[1]->block; + + LLVMAddIncoming(res.value, incoming_values, incoming_blocks, 2); + + return res; +} + + lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, BuiltinProcId id) { ast_node(ce, CallExpr, expr); @@ -9942,6 +10193,12 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, case BuiltinProc_soa_unzip: return lb_soa_unzip(p, ce, tv); + case BuiltinProc_try: + return lb_emit_try(p, ce, tv); + + case BuiltinProc_or_else: + return lb_emit_or_else(p, ce, tv); + // "Intrinsics" @@ -11132,7 +11389,14 @@ lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, lbValue x) { lbValue res = {}; res.type = t_llvm_bool; Type *t = x.type; - if (is_type_pointer(t)) { + if (is_type_enum(t)) { + if (op_kind == Token_CmpEq) { + res.value = LLVMBuildIsNull(p->builder, x.value, ""); + } else if (op_kind == Token_NotEq) { + res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); + } + return res; + } else if (is_type_pointer(t)) { if (op_kind == Token_CmpEq) { res.value = LLVMBuildIsNull(p->builder, x.value, ""); } else if (op_kind == Token_NotEq) {