From 4fbb42833edb94ad3cf89ebdaa817811ab1232f1 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Tue, 27 Jan 2026 13:32:08 +0100 Subject: [PATCH] - Crash when creating `$Type*` where `$Type` is an optional type #2848 - Crashes when using `io::EOF~!` in various unhandled places. #2848 --- lib/std/core/mem.c3 | 19 ++- releasenotes.md | 2 + src/compiler/llvm_codegen_expr.c | 16 +- src/compiler/llvm_codegen_internal.h | 4 +- src/compiler/sema_expr.c | 4 +- src/compiler/sema_stmts.c | 2 +- src/compiler/sema_types.c | 35 ++-- test/test_suite/errors/allocating_opt.c3 | 11 ++ .../expressions/always_rethrow_in_assert.c3 | 9 + .../optional_in_vaarg_and_switch.c3t | 154 ++++++++++++++++++ .../expressions/rethrow_in_comp.c3t | 51 ++++++ .../expressions/rethrow_in_ternary.c3t | 73 +++++++++ .../set_optional_through_member.c3 | 11 ++ 13 files changed, 365 insertions(+), 26 deletions(-) create mode 100644 test/test_suite/errors/allocating_opt.c3 create mode 100644 test/test_suite/expressions/always_rethrow_in_assert.c3 create mode 100644 test/test_suite/expressions/optional_in_vaarg_and_switch.c3t create mode 100644 test/test_suite/expressions/rethrow_in_comp.c3t create mode 100644 test/test_suite/expressions/rethrow_in_ternary.c3t create mode 100644 test/test_suite/expressions/set_optional_through_member.c3 diff --git a/lib/std/core/mem.c3 b/lib/std/core/mem.c3 index 15ef3bf1e..3d239b2f0 100644 --- a/lib/std/core/mem.c3 +++ b/lib/std/core/mem.c3 @@ -838,6 +838,7 @@ macro new_aligned($Type, #init = ...) @nodiscard @safemacro <* @param $Type : "The type to allocate" + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @return "A pointer to uninitialized data for the type $Type" *> @@ -850,7 +851,8 @@ macro alloc($Type) @nodiscard @param $Type : "The type to allocate" @param padding : "The padding to add after the allocation" - @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" + @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" @return "A pointer to uninitialized data for the type $Type" *> macro alloc_with_padding($Type, usz padding) @nodiscard @@ -864,6 +866,7 @@ macro alloc_with_padding($Type, usz padding) @nodiscard exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. @param $Type : "The type to allocate" + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" @return "A pointer to uninitialized data for the type $Type with the proper alignment" *> macro alloc_aligned($Type) @nodiscard @@ -875,6 +878,7 @@ macro alloc_aligned($Type) @nodiscard @param $Type : "The type to allocate" @param #init : "The optional initializer" + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to temporary data of type $Type." *> @@ -894,6 +898,7 @@ macro tnew($Type, #init = ...) @nodiscard @safemacro @param padding : "The padding to add after the allocation" @param #init : "The optional initializer" + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" @require !$defined(#init) ||| $defined($Type a = #init) : "#init must be an initializer for the type" @return "A pointer to temporary data of type $Type with added padding at the end." *> @@ -908,17 +913,24 @@ macro temp_with_padding($Type, usz padding, #init = ...) @nodiscard @safemacro $endif } +<* + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" +*> macro talloc($Type) @nodiscard { return tmalloc($Type.sizeof, $Type.alignof); } +<* + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" +*> macro talloc_with_padding($Type, usz padding) @nodiscard { return tmalloc($Type.sizeof + padding, $Type.alignof); } <* + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_array_aligned' instead" *> macro new_array($Type, usz elements) @nodiscard @@ -929,6 +941,8 @@ macro new_array($Type, usz elements) @nodiscard <* Allocate using an aligned allocation. This is necessary for types with a default memory alignment exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" *> macro new_array_aligned($Type, usz elements) @nodiscard { @@ -936,6 +950,7 @@ macro new_array_aligned($Type, usz elements) @nodiscard } <* + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_array_aligned' instead" *> macro alloc_array($Type, usz elements) @nodiscard @@ -946,6 +961,8 @@ macro alloc_array($Type, usz elements) @nodiscard <* Allocate using an aligned allocation. This is necessary for types with a default memory alignment exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. + + @require $Type.kindof != OPTIONAL : "Expected a non-optional type" *> macro alloc_array_aligned($Type, usz elements) @nodiscard { diff --git a/releasenotes.md b/releasenotes.md index f953a3955..9f9c27ac8 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -137,6 +137,8 @@ - Fix alignment for uint128 to 16 with WASM targets. - Incorrect assert in struct alignment checking #2841 - Packed structs sometimes not lowered as such. +- Crash when creating `$Type*` where `$Type` is an optional type #2848 +- Crashes when using `io::EOF~!` in various unhandled places. #2848 ### Stdlib changes - Add `ThreadPool` join function to wait for all threads to finish in the pool without destroying the threads. diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index f18816db0..1048eb9a7 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -4452,7 +4452,7 @@ static inline void llvm_emit_force_unwrap_expr(GenContext *c, BEValue *be_value, POP_CATCH(); // Emit success and to end. - llvm_emit_br(c, no_err_block); + bool emit_no_err = llvm_emit_br(c, no_err_block); POP_CATCH(); @@ -4469,8 +4469,11 @@ static inline void llvm_emit_force_unwrap_expr(GenContext *c, BEValue *be_value, vec_add(varargs, error_var_ref); llvm_emit_panic(c, "Force unwrap failed!", loc, "Unexpected fault '%s' was unwrapped!", varargs); } - llvm_emit_block(c, no_err_block); - EMIT_EXPR_LOC(c, expr); + if (emit_no_err) + { + llvm_emit_block(c, no_err_block); + EMIT_EXPR_LOC(c, expr); + } } @@ -4614,6 +4617,7 @@ void gencontext_emit_ternary_expr(GenContext *c, BEValue *value, Expr *expr) Expr *cond = exprptr(expr->ternary_expr.cond); llvm_emit_expr(c, value, cond); llvm_value_rvalue(c, value); + RETURN_ON_EMPTY_BLOCK(value); Expr *else_expr = exprptr(expr->ternary_expr.else_expr); Expr *then_expr = exprptr(expr->ternary_expr.then_expr); @@ -5992,6 +5996,7 @@ static void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr { llvm_emit_expr(c, value_ref, arg); llvm_value_fold_optional(c, value_ref); + RETURN_ON_EMPTY_BLOCK(result_value); continue; } Decl *decl = sig->params[i]; @@ -5999,11 +6004,13 @@ static void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr if (vararg_splat) { llvm_emit_vasplat_expr(c, value_ref, vararg_splat, param); + RETURN_ON_EMPTY_BLOCK(result_value); continue; } if (varargs) { llvm_emit_varargs_expr(c, value_ref, varargs, param); + RETURN_ON_EMPTY_BLOCK(result_value); continue; } // Just set the size to zero. @@ -6017,6 +6024,7 @@ static void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr BEValue *value_ref = &values[arg_count + i]; llvm_emit_expr(c, value_ref, vararg); llvm_value_fold_optional(c, value_ref); + RETURN_ON_EMPTY_BLOCK(result_value); } } @@ -7016,10 +7024,12 @@ static void llvm_emit_make_any(GenContext *c, BEValue *value, Expr *expr) llvm_emit_expr(c, value, expr->make_any_expr.inner); llvm_value_rvalue(c, value); + RETURN_ON_EMPTY_BLOCK(value); BEValue typeid_val; Expr *typeid = expr->make_any_expr.typeid; llvm_emit_expr(c, &typeid_val, typeid); llvm_value_rvalue(c, &typeid_val); + RETURN_ON_EMPTY_BLOCK(value); llvm_value_aggregate_two(c, value, expr->type, value->value, typeid_val.value); } diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index 626178e5e..83e6ff1fd 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -594,8 +594,8 @@ void llvm_emit_debug_local_var(GenContext *c, Decl *var); #define EMIT_SPAN(c, x) do { if (c->debug.builder) llvm_emit_debug_location(c, x); } while (0) #define PUSH_DEFER_ERROR(val__) LLVMValueRef def_err__ = c->defer_error_var; c->defer_error_var = val__ #define POP_DEFER_ERROR() c->defer_error_var = def_err__ -#define RETURN_ON_EMPTY_BLOCK(value__) do { if (!c->current_block) { llvm_value_set_empty(value__); return; }} while(0) -#define RETURN_ON_EMPTY_BLOCK_VOID() do { if (!c->current_block) { return; }} while(0) +#define RETURN_ON_EMPTY_BLOCK(value__) do { if (!llvm_is_global_eval(c) && !c->current_block) { llvm_value_set_empty(value__); return; }} while(0) +#define RETURN_ON_EMPTY_BLOCK_VOID() do { if (!llvm_is_global_eval(c) && !c->current_block) { return; }} while(0) LLVMAtomicOrdering llvm_atomic_ordering(Atomicity atomicity); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 0a28db3c9..9916d801a 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -3731,7 +3731,7 @@ static inline bool sema_call_analyse_member_set(SemaContext *context, Expr *expr } Expr *access = expr_new_expr(target_kind == TYPE_BITSTRUCT ? EXPR_BITACCESS : EXPR_ACCESS_RESOLVED, expr); access->access_resolved_expr = (ExprResolvedAccess) { .parent = inner, .ref = decl }; - access->type = decl->type; + access->type = type_add_optional(decl->type, IS_OPTIONAL(inner)); access->resolve_status = RESOLVE_DONE; expr->expr_kind = EXPR_BINARY; expr->binary_expr = (ExprBinary) { .left = exprid(access), .right = exprid(arg), .operator = BINARYOP_ASSIGN }; @@ -3759,7 +3759,7 @@ static inline bool sema_call_analyse_member_get(SemaContext *context, Expr *expr } expr->expr_kind = target_kind == TYPE_BITSTRUCT ? EXPR_BITACCESS : EXPR_ACCESS_RESOLVED; expr->access_resolved_expr = (ExprResolvedAccess) { .parent = inner, .ref = decl }; - expr->type = decl->type; + expr->type = type_add_optional(decl->type, IS_OPTIONAL(inner)); return true; } diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 5bd329a93..6727b6bd0 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -122,7 +122,7 @@ static inline bool sema_analyse_assert_stmt(SemaContext *context, Ast *statement case STORAGE_NORMAL: break; case STORAGE_WILDCARD: - UNREACHABLE + RETURN_SEMA_ERROR(e, "This value is always rethrown and doesn't have a definite type. This is not valid."); case STORAGE_VOID: RETURN_SEMA_ERROR(e, "This expression is of type 'void', did you make a mistake?"); case STORAGE_COMPILE_TIME: diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index 963090c8c..9e8b95272 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -99,6 +99,10 @@ bool sema_resolve_array_like_len(SemaContext *context, TypeInfo *type_info, Arra static inline bool sema_check_array_type(SemaContext *context, TypeInfo *original_info, Type *base, TypeInfoKind kind, ArraySize len, Type **result_ref) { + if (base->type_kind == TYPE_OPTIONAL) + { + RETURN_SEMA_ERROR(original_info, "You cannot form an array with an optional element type."); + } Type *distinct_base = type_flatten(base); if (type_is_infer_type(distinct_base)) @@ -585,40 +589,37 @@ static inline bool sema_resolve_type(SemaContext *context, TypeInfo *type_info, if (!sema_resolve_ptr_type(context, type_info, resolve_kind)) return type_info_poison(type_info); break; } -APPEND_QUALIFIERS: +APPEND_QUALIFIERS:; + Type *type = type_no_optional(type_info->type); switch (kind) { case TYPE_COMPRESSED_NONE: break; case TYPE_COMPRESSED_PTR: - if (!sema_check_ptr_type(context, type_info, type_info->type)) return type_info_poison(type_info); - type_info->type = type_get_ptr(type_info->type); + if (!sema_check_ptr_type(context, type_info, type)) return type_info_poison(type_info); + type = type_get_ptr(type); break; case TYPE_COMPRESSED_SUB: - if (!sema_check_array_type(context, type_info, type_info->type, TYPE_INFO_SLICE, 0, &type_info->type)) return type_info_poison(type_info); + if (!sema_check_array_type(context, type_info, type, TYPE_INFO_SLICE, 0, &type)) return type_info_poison(type_info); break; case TYPE_COMPRESSED_SUBPTR: - if (!sema_check_array_type(context, type_info, type_info->type, TYPE_INFO_SLICE, 0, &type_info->type)) return type_info_poison(type_info); - type_info->type = type_get_ptr(type_info->type); + if (!sema_check_array_type(context, type_info, type, TYPE_INFO_SLICE, 0, &type)) return type_info_poison(type_info); + type = type_get_ptr(type); break; case TYPE_COMPRESSED_PTRPTR: - if (!sema_check_ptr_type(context, type_info, type_info->type)) return type_info_poison(type_info); - type_info->type = type_get_ptr(type_get_ptr(type_info->type)); + if (!sema_check_ptr_type(context, type_info, type)) return type_info_poison(type_info); + type = type_get_ptr(type_get_ptr(type)); break; case TYPE_COMPRESSED_PTRSUB: - if (!sema_check_ptr_type(context, type_info, type_info->type)) return type_info_poison(type_info); - type_info->type = type_get_slice(type_get_ptr(type_info->type)); + if (!sema_check_ptr_type(context, type_info, type)) return type_info_poison(type_info); + type = type_get_slice(type_get_ptr(type)); break; case TYPE_COMPRESSED_SUBSUB: - if (!sema_check_array_type(context, type_info, type_info->type, TYPE_INFO_SLICE, 0, &type_info->type)) return type_info_poison(type_info); - type_info->type = type_get_slice(type_info->type); + if (!sema_check_array_type(context, type_info, type, TYPE_INFO_SLICE, 0, &type)) return type_info_poison(type_info); + type = type_get_slice(type); break; } - if (type_info->optional) - { - Type *type = type_info->type; - if (!type_is_optional(type)) type_info->type = type_get_optional(type); - } + type_info->type = type_add_optional(type, type_info->optional || type_is_optional(type_info->type)); type_info->resolve_status = RESOLVE_DONE; return true; } diff --git a/test/test_suite/errors/allocating_opt.c3 b/test/test_suite/errors/allocating_opt.c3 new file mode 100644 index 000000000..1a58afce3 --- /dev/null +++ b/test/test_suite/errors/allocating_opt.c3 @@ -0,0 +1,11 @@ +interface TestProto +{ +} +struct Test +{ + void* abc; +} +fn void main() +{ + TestProto z = mem::alloc(Test?); // #error: "$Type.kindof != OPTIONAL" violated: 'Expected a non-optional type'. +} \ No newline at end of file diff --git a/test/test_suite/expressions/always_rethrow_in_assert.c3 b/test/test_suite/expressions/always_rethrow_in_assert.c3 new file mode 100644 index 000000000..93d472e9e --- /dev/null +++ b/test/test_suite/expressions/always_rethrow_in_assert.c3 @@ -0,0 +1,9 @@ +import std; +macro @test(#e1, #e2) +{ + assert(#e1 == #e2, "", io::EOF~!); // #error: This value is always rethrown and doesn't have a definite type. This is not valid +} +fn usz foo() +{ + @test("", ""); +} \ No newline at end of file diff --git a/test/test_suite/expressions/optional_in_vaarg_and_switch.c3t b/test/test_suite/expressions/optional_in_vaarg_and_switch.c3t new file mode 100644 index 000000000..88ff7d403 --- /dev/null +++ b/test/test_suite/expressions/optional_in_vaarg_and_switch.c3t @@ -0,0 +1,154 @@ +// #target: macos-x64 +module test; +import std::io; +fn void? test1(int x) +{ + io::printfn(io::EOF~!, x); +} + +fn void test2() +{ + int? tok; + switch (fault err = @catch(tok)) + { + default: + io::printfn("error: %s, %d", err, (usz)err~!!); + } +} +fn void main() +{ + fault a = @catch(test1(0)); + test2(); +} + +/* #expect: test.ll + +define i64 @test.test1(i32 %0) #0 { +entry: + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @std.io.EOF to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %entry + %1 = load i64, ptr %error_var, align 8 + ret i64 %1 +} + +; Function Attrs: nounwind uwtable +define void @test.test2() #0 { +entry: + %tok = alloca i32, align 4 + %tok.f = alloca i64, align 8 + %err = alloca i64, align 8 + %blockret = alloca i64, align 8 + %f = alloca i64, align 8 + %switch = alloca i64, align 8 + %varargslots = alloca [2 x %any], align 16 + %error_var = alloca i64, align 8 + %varargslots1 = alloca [1 x %any], align 16 + %indirectarg = alloca %"any[]", align 8 + store i64 0, ptr %tok.f, align 8 + store i32 0, ptr %tok, align 4 + br label %testblock + +testblock: ; preds = %entry + %optval = load i64, ptr %tok.f, align 8 + %not_err = icmp eq i64 %optval, 0 + %0 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %0, label %after_check, label %assign_optional + +assign_optional: ; preds = %testblock + store i64 %optval, ptr %f, align 8 + br label %end_block + +after_check: ; preds = %testblock + store i64 0, ptr %f, align 8 + br label %end_block + +end_block: ; preds = %after_check, %assign_optional + %1 = load i64, ptr %f, align 8 + %i2b = icmp ne i64 %1, 0 + br i1 %i2b, label %if.then, label %if.exit + +if.then: ; preds = %end_block + %2 = load i64, ptr %f, align 8 + store i64 %2, ptr %blockret, align 8 + br label %expr_block.exit + +if.exit: ; preds = %end_block + store i64 0, ptr %blockret, align 8 + br label %expr_block.exit + +expr_block.exit: ; preds = %if.exit, %if.then + %3 = load i64, ptr %blockret, align 8 + store i64 %3, ptr %err, align 8 + %4 = load i64, ptr %err, align 8 + store i64 %4, ptr %switch, align 8 + br label %switch.entry + +switch.entry: ; preds = %expr_block.exit + %5 = load i64, ptr %switch, align 8 + br label %switch.default + +switch.default: ; preds = %switch.entry + %6 = insertvalue %any undef, ptr %err, 0 + %7 = insertvalue %any %6, i64 ptrtoint (ptr @"$ct.fault" to i64), 1 + store %any %7, ptr %varargslots, align 16 + %8 = load i64, ptr %err, align 8 + store i64 %8, ptr %error_var, align 8 + br label %panic_block + +panic_block: ; preds = %switch.default + %9 = insertvalue %any undef, ptr %error_var, 0 + %10 = insertvalue %any %9, i64 ptrtoint (ptr @"$ct.fault" to i64), 1 + store %any %10, ptr %varargslots1, align 16 + %11 = insertvalue %"any[]" undef, ptr %varargslots1, 0 + %"$$temp" = insertvalue %"any[]" %11, i64 1, 1 + store %"any[]" %"$$temp", ptr %indirectarg, align 8 + call void @std.core.builtin.panicf(ptr @.panic_msg, i64 36, ptr @.file, i64 31, ptr @.func, i64 5, i32 14, ptr byval(%"any[]") align 8 %indirectarg) #2 + unreachable +} + +; Function Attrs: nounwind uwtable +define void @test.main() #0 { +entry: + %a = alloca i64, align 8 + %blockret = alloca i64, align 8 + %f = alloca i64, align 8 + br label %testblock + +testblock: ; preds = %entry + %0 = call i64 @test.test1(i32 0) + %not_err = icmp eq i64 %0, 0 + %1 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %1, label %after_check, label %assign_optional + +assign_optional: ; preds = %testblock + store i64 %0, ptr %f, align 8 + br label %end_block + +after_check: ; preds = %testblock + store i64 0, ptr %f, align 8 + br label %end_block + +end_block: ; preds = %after_check, %assign_optional + %2 = load i64, ptr %f, align 8 + %i2b = icmp ne i64 %2, 0 + br i1 %i2b, label %if.then, label %if.exit + +if.then: ; preds = %end_block + %3 = load i64, ptr %f, align 8 + store i64 %3, ptr %blockret, align 8 + br label %expr_block.exit + +if.exit: ; preds = %end_block + store i64 0, ptr %blockret, align 8 + br label %expr_block.exit + +expr_block.exit: ; preds = %if.exit, %if.then + %4 = load i64, ptr %blockret, align 8 + store i64 %4, ptr %a, align 8 + call void @test.test2() + ret void +} + diff --git a/test/test_suite/expressions/rethrow_in_comp.c3t b/test/test_suite/expressions/rethrow_in_comp.c3t new file mode 100644 index 000000000..743da1ec3 --- /dev/null +++ b/test/test_suite/expressions/rethrow_in_comp.c3t @@ -0,0 +1,51 @@ +// #target: macos-x64 +module test; +import std::io; +fn void? canFail() +{ + if (io::EOF~! == 69) { } +} +fn int main() +{ + if (catch err = canFail()) { } + return 0; +} + +/* #expect: test.ll + +define i64 @test.canFail() #0 { +entry: + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @std.io.EOF to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %entry + %0 = load i64, ptr %error_var, align 8 + ret i64 %0 +} + +define i32 @main() #0 { +entry: + %err = alloca i64, align 8 + br label %testblock + +testblock: ; preds = %entry + %0 = call i64 @test.canFail() + %not_err = icmp eq i64 %0, 0 + %1 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %1, label %after_check, label %assign_optional + +assign_optional: ; preds = %testblock + store i64 %0, ptr %err, align 8 + br label %end_block + +after_check: ; preds = %testblock + store i64 0, ptr %err, align 8 + br label %end_block + +end_block: ; preds = %after_check, %assign_optional + %2 = load i64, ptr %err, align 8 + %i2b = icmp ne i64 %2, 0 + ret i32 0 +} + diff --git a/test/test_suite/expressions/rethrow_in_ternary.c3t b/test/test_suite/expressions/rethrow_in_ternary.c3t new file mode 100644 index 000000000..26de5d1ac --- /dev/null +++ b/test/test_suite/expressions/rethrow_in_ternary.c3t @@ -0,0 +1,73 @@ +// #target: macos-x64 +module test; +import std::io; +faultdef ABC ; +fn int? hello(int a) +{ + return io::EOF~! ? ABC~ : a; +} +fn void? bye() { } + +fn void main() +{ + if (catch err = hello(-1), bye()) { } +} + +/* #expect: test.ll + +define i64 @test.hello(ptr %0, i32 %1) #0 { +entry: + %reterr = alloca i64, align 8 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @std.io.EOF to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %entry + %2 = load i64, ptr %error_var, align 8 + ret i64 %2 +} + +; Function Attrs: nounwind uwtable +define i64 @test.bye() #0 { +entry: + ret i64 0 +} + +define void @test.main() #0 { +entry: + %err = alloca i64, align 8 + %retparam = alloca i32, align 4 + br label %testblock + +testblock: ; preds = %entry + %0 = call i64 @test.hello(ptr %retparam, i32 -1) + %not_err = icmp eq i64 %0, 0 + %1 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %1, label %after_check, label %assign_optional + +assign_optional: ; preds = %testblock + store i64 %0, ptr %err, align 8 + br label %end_block + +after_check: ; preds = %testblock + br label %testblock1 + +testblock1: ; preds = %after_check + %2 = call i64 @test.bye() + %not_err2 = icmp eq i64 %2, 0 + %3 = call i1 @llvm.expect.i1(i1 %not_err2, i1 true) + br i1 %3, label %after_check4, label %assign_optional3 + +assign_optional3: ; preds = %testblock1 + store i64 %2, ptr %err, align 8 + br label %end_block + +after_check4: ; preds = %testblock1 + store i64 0, ptr %err, align 8 + br label %end_block + +end_block: ; preds = %after_check4, %assign_optional3, %assign_optional + %4 = load i64, ptr %err, align 8 + %i2b = icmp ne i64 %4, 0 + ret void +} diff --git a/test/test_suite/expressions/set_optional_through_member.c3 b/test/test_suite/expressions/set_optional_through_member.c3 new file mode 100644 index 000000000..5a0dd5651 --- /dev/null +++ b/test/test_suite/expressions/set_optional_through_member.c3 @@ -0,0 +1,11 @@ +bitstruct Foo : long +{ + int a : 2..4; + uint b: 5..10; +} +fn int main() +{ + Foo? d = { 1, 11 }; + Foo.membersof[0].set(d, 3); // #error: You cannot assign to an optional value. + return 0; +} \ No newline at end of file