| author | |
| committer | |
| log | d1a98ccff481183d7fc53e45a902ef273c3d6aeb |
| tree | b03efbb135bae39fcf6968b505ad67e4c6a33bda |
| parent | 9ca8d9e21ad657b023c23db5c440fb79a3303771 |
| signature |
closes #30777 files changed, 218 insertions(+), 29 deletions(-)
src/all_types.hpp+23| ... | ... | @@ -2124,6 +2124,7 @@ enum ScopeId { |
| 2124 | 2124 | ScopeIdCompTime, |
| 2125 | 2125 | ScopeIdRuntime, |
| 2126 | 2126 | ScopeIdTypeOf, |
| 2127 | ScopeIdExpr, | |
| 2127 | 2128 | }; |
| 2128 | 2129 | |
| 2129 | 2130 | struct Scope { |
| ... | ... | @@ -2271,6 +2272,24 @@ struct ScopeTypeOf { |
| 2271 | 2272 | Scope base; |
| 2272 | 2273 | }; |
| 2273 | 2274 | |
| 2275 | enum MemoizedBool { | |
| 2276 | MemoizedBoolUnknown, | |
| 2277 | MemoizedBoolFalse, | |
| 2278 | MemoizedBoolTrue, | |
| 2279 | }; | |
| 2280 | ||
| 2281 | // This scope is created for each expression. | |
| 2282 | // It's used to identify when an instruction needs to be spilled, | |
| 2283 | // so that it can be accessed after a suspend point. | |
| 2284 | struct ScopeExpr { | |
| 2285 | Scope base; | |
| 2286 | ||
| 2287 | ScopeExpr **children_ptr; | |
| 2288 | size_t children_len; | |
| 2289 | ||
| 2290 | MemoizedBool need_spill; | |
| 2291 | }; | |
| 2292 | ||
| 2274 | 2293 | // synchronized with code in define_builtin_compile_vars |
| 2275 | 2294 | enum AtomicOrder { |
| 2276 | 2295 | AtomicOrderUnordered, |
| ... | ... | @@ -2510,6 +2529,10 @@ struct IrInstruction { |
| 2510 | 2529 | // with this child field. |
| 2511 | 2530 | IrInstruction *child; |
| 2512 | 2531 | IrBasicBlock *owner_bb; |
| 2532 | // Nearly any instruction can have to be stored as a local variable before suspending | |
| 2533 | // and then loaded after resuming, in case there is an expression with a suspend point | |
| 2534 | // in it, such as: x + await y | |
| 2535 | IrInstruction *spill; | |
| 2513 | 2536 | IrInstructionId id; |
| 2514 | 2537 | // true if this instruction was generated by zig and not from user code |
| 2515 | 2538 | bool is_gen; |
src/analyze.cpp+148-11| ... | ... | @@ -96,6 +96,30 @@ static ScopeDecls **get_container_scope_ptr(ZigType *type_entry) { |
| 96 | 96 | zig_unreachable(); |
| 97 | 97 | } |
| 98 | 98 | |
| 99 | static ScopeExpr *find_expr_scope(Scope *scope) { | |
| 100 | for (;;) { | |
| 101 | switch (scope->id) { | |
| 102 | case ScopeIdExpr: | |
| 103 | return reinterpret_cast<ScopeExpr *>(scope); | |
| 104 | case ScopeIdDefer: | |
| 105 | case ScopeIdDeferExpr: | |
| 106 | case ScopeIdDecls: | |
| 107 | case ScopeIdFnDef: | |
| 108 | case ScopeIdCompTime: | |
| 109 | case ScopeIdVarDecl: | |
| 110 | case ScopeIdCImport: | |
| 111 | case ScopeIdSuspend: | |
| 112 | case ScopeIdTypeOf: | |
| 113 | case ScopeIdBlock: | |
| 114 | return nullptr; | |
| 115 | case ScopeIdLoop: | |
| 116 | case ScopeIdRuntime: | |
| 117 | scope = scope->parent; | |
| 118 | continue; | |
| 119 | } | |
| 120 | } | |
| 121 | } | |
| 122 | ||
| 99 | 123 | ScopeDecls *get_container_scope(ZigType *type_entry) { |
| 100 | 124 | return *get_container_scope_ptr(type_entry); |
| 101 | 125 | } |
| ... | ... | @@ -203,6 +227,20 @@ Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent) { |
| 203 | 227 | return &scope->base; |
| 204 | 228 | } |
| 205 | 229 | |
| 230 | Scope *create_expr_scope(CodeGen *g, AstNode *node, Scope *parent) { | |
| 231 | ScopeExpr *scope = allocate<ScopeExpr>(1); | |
| 232 | init_scope(g, &scope->base, ScopeIdExpr, node, parent); | |
| 233 | ScopeExpr *parent_expr = find_expr_scope(parent); | |
| 234 | if (parent_expr != nullptr) { | |
| 235 | size_t new_len = parent_expr->children_len + 1; | |
| 236 | parent_expr->children_ptr = reallocate_nonzero<ScopeExpr *>( | |
| 237 | parent_expr->children_ptr, parent_expr->children_len, new_len); | |
| 238 | parent_expr->children_ptr[parent_expr->children_len] = scope; | |
| 239 | parent_expr->children_len = new_len; | |
| 240 | } | |
| 241 | return &scope->base; | |
| 242 | } | |
| 243 | ||
| 206 | 244 | ZigType *get_scope_import(Scope *scope) { |
| 207 | 245 | while (scope) { |
| 208 | 246 | if (scope->id == ScopeIdDecls) { |
| ... | ... | @@ -5654,6 +5692,69 @@ static ZigType *get_async_fn_type(CodeGen *g, ZigType *orig_fn_type) { |
| 5654 | 5692 | return fn_type; |
| 5655 | 5693 | } |
| 5656 | 5694 | |
| 5695 | // Traverse up to the very top ExprScope, which has children. | |
| 5696 | // We have just arrived at the top from a child. That child, | |
| 5697 | // and its next siblings, do not need to be marked. But the previous | |
| 5698 | // siblings do. | |
| 5699 | // x + (await y) | |
| 5700 | // vs | |
| 5701 | // (await y) + x | |
| 5702 | static void mark_suspension_point(Scope *scope) { | |
| 5703 | ScopeExpr *child_expr_scope = (scope->id == ScopeIdExpr) ? reinterpret_cast<ScopeExpr *>(scope) : nullptr; | |
| 5704 | for (;;) { | |
| 5705 | scope = scope->parent; | |
| 5706 | switch (scope->id) { | |
| 5707 | case ScopeIdDefer: | |
| 5708 | case ScopeIdDeferExpr: | |
| 5709 | case ScopeIdDecls: | |
| 5710 | case ScopeIdFnDef: | |
| 5711 | case ScopeIdCompTime: | |
| 5712 | case ScopeIdVarDecl: | |
| 5713 | case ScopeIdCImport: | |
| 5714 | case ScopeIdSuspend: | |
| 5715 | case ScopeIdTypeOf: | |
| 5716 | case ScopeIdBlock: | |
| 5717 | return; | |
| 5718 | case ScopeIdLoop: | |
| 5719 | case ScopeIdRuntime: | |
| 5720 | continue; | |
| 5721 | case ScopeIdExpr: { | |
| 5722 | ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope); | |
| 5723 | if (child_expr_scope != nullptr) { | |
| 5724 | for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) { | |
| 5725 | assert(i < parent_expr_scope->children_len); | |
| 5726 | parent_expr_scope->children_ptr[i]->need_spill = MemoizedBoolTrue; | |
| 5727 | } | |
| 5728 | } | |
| 5729 | parent_expr_scope->need_spill = MemoizedBoolTrue; | |
| 5730 | child_expr_scope = parent_expr_scope; | |
| 5731 | continue; | |
| 5732 | } | |
| 5733 | } | |
| 5734 | } | |
| 5735 | } | |
| 5736 | ||
| 5737 | static bool scope_needs_spill(Scope *scope) { | |
| 5738 | ScopeExpr *scope_expr = find_expr_scope(scope); | |
| 5739 | if (scope_expr == nullptr) return false; | |
| 5740 | ||
| 5741 | switch (scope_expr->need_spill) { | |
| 5742 | case MemoizedBoolUnknown: | |
| 5743 | if (scope_needs_spill(scope_expr->base.parent)) { | |
| 5744 | scope_expr->need_spill = MemoizedBoolTrue; | |
| 5745 | return true; | |
| 5746 | } else { | |
| 5747 | scope_expr->need_spill = MemoizedBoolFalse; | |
| 5748 | return false; | |
| 5749 | } | |
| 5750 | case MemoizedBoolFalse: | |
| 5751 | return false; | |
| 5752 | case MemoizedBoolTrue: | |
| 5753 | return true; | |
| 5754 | } | |
| 5755 | zig_unreachable(); | |
| 5756 | } | |
| 5757 | ||
| 5657 | 5758 | static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 5658 | 5759 | Error err; |
| 5659 | 5760 | |
| ... | ... | @@ -5786,21 +5887,17 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 5786 | 5887 | callee_frame_type, ""); |
| 5787 | 5888 | } |
| 5788 | 5889 | // Since this frame is async, an await might represent a suspend point, and |
| 5789 | // therefore need to spill. | |
| 5890 | // therefore need to spill. It also needs to mark expr scopes as having to spill. | |
| 5891 | // For example: foo() + await z | |
| 5892 | // The funtion call result of foo() must be spilled. | |
| 5790 | 5893 | for (size_t i = 0; i < fn->await_list.length; i += 1) { |
| 5791 | 5894 | IrInstructionAwaitGen *await = fn->await_list.at(i); |
| 5792 | // TODO If this is a noasync await, it doesn't need to spill | |
| 5895 | // TODO If this is a noasync await, it doesn't suspend | |
| 5793 | 5896 | // https://github.com/ziglang/zig/issues/3157 |
| 5794 | if (await->result_loc != nullptr) { | |
| 5795 | // If there's a result location, that is the spill | |
| 5897 | if (await->base.value.special != ConstValSpecialRuntime) { | |
| 5898 | // Known at comptime. No spill, no suspend. | |
| 5796 | 5899 | continue; |
| 5797 | 5900 | } |
| 5798 | if (!type_has_bits(await->base.value.type)) | |
| 5799 | continue; | |
| 5800 | if (await->base.value.special != ConstValSpecialRuntime) | |
| 5801 | continue; | |
| 5802 | if (await->base.ref_count == 0) | |
| 5803 | continue; | |
| 5804 | 5901 | if (await->target_fn != nullptr) { |
| 5805 | 5902 | // we might not need to suspend |
| 5806 | 5903 | analyze_fn_async(g, await->target_fn, false); |
| ... | ... | @@ -5809,13 +5906,53 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 5809 | 5906 | return ErrorSemanticAnalyzeFail; |
| 5810 | 5907 | } |
| 5811 | 5908 | if (!fn_is_async(await->target_fn)) { |
| 5812 | // This await does not represent a suspend point. No spill needed. | |
| 5909 | // This await does not represent a suspend point. No spill needed, | |
| 5910 | // and no need to mark ExprScope. | |
| 5813 | 5911 | continue; |
| 5814 | 5912 | } |
| 5815 | 5913 | } |
| 5914 | // This await is a suspend point, but it might not need a spill. | |
| 5915 | // We do need to mark the ExprScope as having a suspend point in it. | |
| 5916 | mark_suspension_point(await->base.scope); | |
| 5917 | ||
| 5918 | if (await->result_loc != nullptr) { | |
| 5919 | // If there's a result location, that is the spill | |
| 5920 | continue; | |
| 5921 | } | |
| 5922 | if (await->base.ref_count == 0) | |
| 5923 | continue; | |
| 5924 | if (!type_has_bits(await->base.value.type)) | |
| 5925 | continue; | |
| 5816 | 5926 | await->result_loc = ir_create_alloca(g, await->base.scope, await->base.source_node, fn, |
| 5817 | 5927 | await->base.value.type, ""); |
| 5818 | 5928 | } |
| 5929 | // Now that we've marked all the expr scopes that have to spill, we go over the instructions | |
| 5930 | // and spill the relevant ones. | |
| 5931 | for (size_t block_i = 0; block_i < fn->analyzed_executable.basic_block_list.length; block_i += 1) { | |
| 5932 | IrBasicBlock *block = fn->analyzed_executable.basic_block_list.at(block_i); | |
| 5933 | for (size_t instr_i = 0; instr_i < block->instruction_list.length; instr_i += 1) { | |
| 5934 | IrInstruction *instruction = block->instruction_list.at(instr_i); | |
| 5935 | if (instruction->id == IrInstructionIdAwaitGen || | |
| 5936 | instruction->id == IrInstructionIdVarPtr || | |
| 5937 | instruction->id == IrInstructionIdDeclRef || | |
| 5938 | instruction->id == IrInstructionIdAllocaGen) | |
| 5939 | { | |
| 5940 | // This instruction does its own spilling specially, or otherwise doesn't need it. | |
| 5941 | continue; | |
| 5942 | } | |
| 5943 | if (instruction->value.special != ConstValSpecialRuntime) | |
| 5944 | continue; | |
| 5945 | if (instruction->ref_count == 0) | |
| 5946 | continue; | |
| 5947 | if (!type_has_bits(instruction->value.type)) | |
| 5948 | continue; | |
| 5949 | if (scope_needs_spill(instruction->scope)) { | |
| 5950 | instruction->spill = ir_create_alloca(g, instruction->scope, instruction->source_node, | |
| 5951 | fn, instruction->value.type, ""); | |
| 5952 | } | |
| 5953 | } | |
| 5954 | } | |
| 5955 | ||
| 5819 | 5956 | FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; |
| 5820 | 5957 | ZigType *ptr_return_type = get_pointer_to_type(g, fn_type_id->return_type, false); |
| 5821 | 5958 |
src/analyze.hpp+1-1| ... | ... | @@ -114,6 +114,7 @@ ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn * |
| 114 | 114 | Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent); |
| 115 | 115 | Scope *create_runtime_scope(CodeGen *g, AstNode *node, Scope *parent, IrInstruction *is_comptime); |
| 116 | 116 | Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent); |
| 117 | Scope *create_expr_scope(CodeGen *g, AstNode *node, Scope *parent); | |
| 117 | 118 | |
| 118 | 119 | void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str); |
| 119 | 120 | ConstExprValue *create_const_str_lit(CodeGen *g, Buf *str); |
| ... | ... | @@ -261,5 +262,4 @@ void add_async_error_notes(CodeGen *g, ErrorMsg *msg, ZigFn *fn); |
| 261 | 262 | IrInstruction *ir_create_alloca(CodeGen *g, Scope *scope, AstNode *source_node, ZigFn *fn, |
| 262 | 263 | ZigType *var_type, const char *name_hint); |
| 263 | 264 | |
| 264 | ||
| 265 | 265 | #endif |
src/codegen.cpp+14-3| ... | ... | @@ -649,6 +649,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) { |
| 649 | 649 | case ScopeIdCompTime: |
| 650 | 650 | case ScopeIdRuntime: |
| 651 | 651 | case ScopeIdTypeOf: |
| 652 | case ScopeIdExpr: | |
| 652 | 653 | return get_di_scope(g, scope->parent); |
| 653 | 654 | } |
| 654 | 655 | zig_unreachable(); |
| ... | ... | @@ -1644,7 +1645,6 @@ static void gen_assign_raw(CodeGen *g, LLVMValueRef ptr, ZigType *ptr_type, |
| 1644 | 1645 | LLVMValueRef ored_value = LLVMBuildOr(g->builder, shifted_value, anded_containing_int, ""); |
| 1645 | 1646 | |
| 1646 | 1647 | gen_store(g, ored_value, ptr, ptr_type); |
| 1647 | return; | |
| 1648 | 1648 | } |
| 1649 | 1649 | |
| 1650 | 1650 | static void gen_var_debug_decl(CodeGen *g, ZigVar *var) { |
| ... | ... | @@ -1664,11 +1664,16 @@ static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstruction *instruction) { |
| 1664 | 1664 | if (instruction->id == IrInstructionIdAwaitGen) { |
| 1665 | 1665 | IrInstructionAwaitGen *await = reinterpret_cast<IrInstructionAwaitGen*>(instruction); |
| 1666 | 1666 | if (await->result_loc != nullptr) { |
| 1667 | instruction->llvm_value = get_handle_value(g, ir_llvm_value(g, await->result_loc), | |
| 1667 | return get_handle_value(g, ir_llvm_value(g, await->result_loc), | |
| 1668 | 1668 | await->result_loc->value.type->data.pointer.child_type, await->result_loc->value.type); |
| 1669 | return instruction->llvm_value; | |
| 1670 | 1669 | } |
| 1671 | 1670 | } |
| 1671 | if (instruction->spill != nullptr) { | |
| 1672 | ZigType *ptr_type = instruction->spill->value.type; | |
| 1673 | src_assert(ptr_type->id == ZigTypeIdPointer, instruction->source_node); | |
| 1674 | return get_handle_value(g, ir_llvm_value(g, instruction->spill), | |
| 1675 | ptr_type->data.pointer.child_type, instruction->spill->value.type); | |
| 1676 | } | |
| 1672 | 1677 | src_assert(instruction->value.special != ConstValSpecialRuntime, instruction->source_node); |
| 1673 | 1678 | assert(instruction->value.type); |
| 1674 | 1679 | render_const_val(g, &instruction->value, ""); |
| ... | ... | @@ -3786,6 +3791,7 @@ static void render_async_var_decls(CodeGen *g, Scope *scope) { |
| 3786 | 3791 | case ScopeIdCompTime: |
| 3787 | 3792 | case ScopeIdRuntime: |
| 3788 | 3793 | case ScopeIdTypeOf: |
| 3794 | case ScopeIdExpr: | |
| 3789 | 3795 | scope = scope->parent; |
| 3790 | 3796 | continue; |
| 3791 | 3797 | } |
| ... | ... | @@ -6049,6 +6055,11 @@ static void ir_render(CodeGen *g, ZigFn *fn_entry) { |
| 6049 | 6055 | set_debug_location(g, instruction); |
| 6050 | 6056 | } |
| 6051 | 6057 | instruction->llvm_value = ir_render_instruction(g, executable, instruction); |
| 6058 | if (instruction->spill != nullptr) { | |
| 6059 | LLVMValueRef spill_ptr = ir_llvm_value(g, instruction->spill); | |
| 6060 | gen_assign_raw(g, spill_ptr, instruction->spill->value.type, instruction->llvm_value); | |
| 6061 | instruction->llvm_value = nullptr; | |
| 6062 | } | |
| 6052 | 6063 | } |
| 6053 | 6064 | current_block->llvm_exit_block = LLVMGetInsertBlock(g->builder); |
| 6054 | 6065 | } |
src/ir.cpp+11-1| ... | ... | @@ -3364,6 +3364,7 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco |
| 3364 | 3364 | case ScopeIdCompTime: |
| 3365 | 3365 | case ScopeIdRuntime: |
| 3366 | 3366 | case ScopeIdTypeOf: |
| 3367 | case ScopeIdExpr: | |
| 3367 | 3368 | scope = scope->parent; |
| 3368 | 3369 | continue; |
| 3369 | 3370 | case ScopeIdDeferExpr: |
| ... | ... | @@ -3420,6 +3421,7 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o |
| 3420 | 3421 | case ScopeIdCompTime: |
| 3421 | 3422 | case ScopeIdRuntime: |
| 3422 | 3423 | case ScopeIdTypeOf: |
| 3424 | case ScopeIdExpr: | |
| 3423 | 3425 | scope = scope->parent; |
| 3424 | 3426 | continue; |
| 3425 | 3427 | case ScopeIdDeferExpr: |
| ... | ... | @@ -8158,7 +8160,15 @@ static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *sc |
| 8158 | 8160 | result_loc = no_result_loc(); |
| 8159 | 8161 | ir_build_reset_result(irb, scope, node, result_loc); |
| 8160 | 8162 | } |
| 8161 | IrInstruction *result = ir_gen_node_raw(irb, node, scope, lval, result_loc); | |
| 8163 | Scope *child_scope; | |
| 8164 | if (irb->exec->is_inline || | |
| 8165 | (irb->exec->fn_entry != nullptr && irb->exec->fn_entry->child_scope == scope)) | |
| 8166 | { | |
| 8167 | child_scope = scope; | |
| 8168 | } else { | |
| 8169 | child_scope = create_expr_scope(irb->codegen, node, scope); | |
| 8170 | } | |
| 8171 | IrInstruction *result = ir_gen_node_raw(irb, node, child_scope, lval, result_loc); | |
| 8162 | 8172 | if (result == irb->codegen->invalid_instruction) { |
| 8163 | 8173 | if (irb->exec->first_err_trace_msg == nullptr) { |
| 8164 | 8174 | irb->exec->first_err_trace_msg = irb->codegen->trace_err; |
std/event/future.zig+1-5| ... | ... | @@ -104,11 +104,7 @@ fn testFuture(loop: *Loop) void { |
| 104 | 104 | var b = async waitOnFuture(&future); |
| 105 | 105 | resolveFuture(&future); |
| 106 | 106 | |
| 107 | // TODO https://github.com/ziglang/zig/issues/3077 | |
| 108 | //const result = (await a) + (await b); | |
| 109 | const a_result = await a; | |
| 110 | const b_result = await b; | |
| 111 | const result = a_result + b_result; | |
| 107 | const result = (await a) + (await b); | |
| 112 | 108 | |
| 113 | 109 | testing.expect(result == 12); |
| 114 | 110 | } |
test/stage1/behavior/async_fn.zig+20-8| ... | ... | @@ -921,12 +921,10 @@ fn recursiveAsyncFunctionTest(comptime suspending_implementation: bool) type { |
| 921 | 921 | var sum: u32 = 0; |
| 922 | 922 | |
| 923 | 923 | f1_awaited = true; |
| 924 | const result_f1 = await f1; // TODO https://github.com/ziglang/zig/issues/3077 | |
| 925 | sum += try result_f1; | |
| 924 | sum += try await f1; | |
| 926 | 925 | |
| 927 | 926 | f2_awaited = true; |
| 928 | const result_f2 = await f2; // TODO https://github.com/ziglang/zig/issues/3077 | |
| 929 | sum += try result_f2; | |
| 927 | sum += try await f2; | |
| 930 | 928 | |
| 931 | 929 | return sum; |
| 932 | 930 | } |
| ... | ... | @@ -943,8 +941,7 @@ fn recursiveAsyncFunctionTest(comptime suspending_implementation: bool) type { |
| 943 | 941 | |
| 944 | 942 | fn amain(result: *u32) void { |
| 945 | 943 | var x = async fib(std.heap.direct_allocator, 10); |
| 946 | const res = await x; // TODO https://github.com/ziglang/zig/issues/3077 | |
| 947 | result.* = res catch unreachable; | |
| 944 | result.* = (await x) catch unreachable; | |
| 948 | 945 | } |
| 949 | 946 | }; |
| 950 | 947 | } |
| ... | ... | @@ -1002,8 +999,7 @@ test "@asyncCall using the result location inside the frame" { |
| 1002 | 999 | return 1234; |
| 1003 | 1000 | } |
| 1004 | 1001 | fn getAnswer(f: anyframe->i32, out: *i32) void { |
| 1005 | var res = await f; // TODO https://github.com/ziglang/zig/issues/3077 | |
| 1006 | out.* = res; | |
| 1002 | out.* = await f; | |
| 1007 | 1003 | } |
| 1008 | 1004 | }; |
| 1009 | 1005 | var data: i32 = 1; |
| ... | ... | @@ -1124,3 +1120,19 @@ test "await used in expression and awaiting fn with no suspend but async calling |
| 1124 | 1120 | }; |
| 1125 | 1121 | _ = async S.atest(); |
| 1126 | 1122 | } |
| 1123 | ||
| 1124 | test "await used in expression after a fn call" { | |
| 1125 | const S = struct { | |
| 1126 | fn atest() void { | |
| 1127 | var f1 = async add(3, 4); | |
| 1128 | var sum: i32 = 0; | |
| 1129 | sum = foo() + await f1; | |
| 1130 | expect(sum == 8); | |
| 1131 | } | |
| 1132 | async fn add(a: i32, b: i32) i32 { | |
| 1133 | return a + b; | |
| 1134 | } | |
| 1135 | fn foo() i32 { return 1; } | |
| 1136 | }; | |
| 1137 | _ = async S.atest(); | |
| 1138 | } |