From d61022c346c1f14e301db28885804f2dbe4a1ef7 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:28 +0300 Subject: [PATCH] all: Cannot use Option/Result with []thread (fixes #17607) --- doc/docs.md | 22 ++ vlib/v/checker/fn.v | 33 +- vlib/v/checker/tests/go_wait_or.out | 21 -- vlib/v/gen/c/array.v | 8 +- vlib/v/gen/c/cgen.v | 295 ++++++++++++++---- .../fixed_array_of_threads_wait_test.v | 18 ++ vlib/v/tests/fns/go_wait_option_test.v | 34 ++ 7 files changed, 330 insertions(+), 101 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index 068bd62bf..a767efc19 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -4750,6 +4750,28 @@ fn main() { // Output: All jobs finished: [1, 4, 9, 16, 25, 36, 49, 64, 81] ``` +When the spawned function returns `?T` or `!T`, waiting on the thread array returns +`?[]T` or `![]T`. That means the batch can be handled with `or`, `?`, or `!` the same +way as a single thread handle. + +```v +fn maybe_square(i int) !int { + if i < 0 { + return error('negative input') + } + return i * i +} + +fn main() { + mut threads := []thread !int{} + for i in 1 .. 4 { + threads << spawn maybe_square(i) + } + values := threads.wait() or { panic(err) } + println(values) +} +``` + ### Channels Channels are the preferred way to communicate between threads. They allow threads to exchange data diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index bb40d5c8d..c3102ed1b 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -3595,6 +3595,21 @@ fn (mut c Checker) spawn_expr(mut node ast.SpawnExpr) ast.Type { } } +fn (mut c Checker) thread_array_wait_return_type(thread_ret_type ast.Type) ast.Type { + payload_type := thread_ret_type.clear_option_and_result() + if payload_type == ast.void_type { + return thread_ret_type + } + mut return_type := ast.idx_to_type(c.table.find_or_register_array(payload_type)) + if thread_ret_type.has_flag(.option) { + return_type = return_type.set_flag(.option) + } + if thread_ret_type.has_flag(.result) { + return_type = return_type.set_flag(.result) + } + return return_type +} + fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type { // TODO: copypasta from spawn_expr ret_type := c.call_expr(mut node.call_expr) @@ -4321,14 +4336,7 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as c.error('`.wait()` does not have any arguments', arg0.pos) } thread_ret_type := c.unwrap_generic(elem_sym.thread_info().return_type) - if thread_ret_type.has_flag(.option) { - c.error('`.wait()` cannot be called for an array when thread functions return options. Iterate over the arrays elements instead and handle each returned option with `or`.', - node.pos) - } else if thread_ret_type.has_flag(.result) { - c.error('`.wait()` cannot be called for an array when thread functions return results. Iterate over the arrays elements instead and handle each returned result with `or`.', - node.pos) - } - node.return_type = c.table.find_or_register_array(thread_ret_type) + node.return_type = c.thread_array_wait_return_type(thread_ret_type) } else { c.error('`${left_sym.name}` has no method `wait()` (only thread handles and arrays of them have)', node.left.pos()) @@ -4618,14 +4626,7 @@ fn (mut c Checker) fixed_array_builtin_method_call(mut node ast.CallExpr, left_t c.error('`.wait()` does not have any arguments', arg0.pos) } thread_ret_type := c.unwrap_generic(elem_sym.thread_info().return_type) - if thread_ret_type.has_flag(.option) { - c.error('`.wait()` cannot be called for an array when thread functions return options. Iterate over the arrays elements instead and handle each returned option with `or`.', - node.pos) - } else if thread_ret_type.has_flag(.result) { - c.error('`.wait()` cannot be called for an array when thread functions return results. Iterate over the arrays elements instead and handle each returned result with `or`.', - node.pos) - } - node.return_type = c.table.find_or_register_array(thread_ret_type) + node.return_type = c.thread_array_wait_return_type(thread_ret_type) } else { c.error('`${left_sym.name}` has no method `wait()` (only thread handles and arrays of them have)', node.left.pos()) diff --git a/vlib/v/checker/tests/go_wait_or.out b/vlib/v/checker/tests/go_wait_or.out index ad3ba8d2a..407e4365c 100644 --- a/vlib/v/checker/tests/go_wait_or.out +++ b/vlib/v/checker/tests/go_wait_or.out @@ -40,27 +40,6 @@ vlib/v/checker/tests/go_wait_or.vv:27:15: error: unexpected `?`, the function `w | ^ 28 | tg3 := [ 29 | spawn f(0), -vlib/v/checker/tests/go_wait_or.vv:32:6: error: `.wait()` cannot be called for an array when thread functions return options. Iterate over the arrays elements instead and handle each returned option with `or`. - 30 | spawn f(1), - 31 | ] - 32 | tg3.wait() or { panic('problem') } - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 33 | for t in tg3 { - 34 | a := t.wait() -vlib/v/checker/tests/go_wait_or.vv:32:13: error: unexpected `or` block, the function `wait` does not return an Option or a Result - 30 | spawn f(1), - 31 | ] - 32 | tg3.wait() or { panic('problem') } - | ~~~~~~~~~~~~~~~~~~~~~~~ - 33 | for t in tg3 { - 34 | a := t.wait() -vlib/v/checker/tests/go_wait_or.vv:45:6: error: `.wait()` cannot be called for an array when thread functions return options. Iterate over the arrays elements instead and handle each returned option with `or`. - 43 | spawn g(1), - 44 | ] - 45 | tg4.wait() - | ~~~~~~ - 46 | tg4[0].wait() - 47 | spawn g(3) or { panic('problem') } vlib/v/checker/tests/go_wait_or.vv:47:13: error: option handling cannot be done in `spawn` call. Do it when calling `.wait()` 45 | tg4.wait() 46 | tg4[0].wait() diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index 66bc5f7ac..b588b63db 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -1887,9 +1887,7 @@ fn (mut g Gen) gen_array_wait(node ast.CallExpr) { thread_type := arr.array_info().elem_type thread_sym := g.table.sym(thread_type) thread_ret_type := thread_sym.thread_info().return_type - unwrapped_ret_type := g.unwrap_generic(thread_ret_type) - elsymcname := g.table.sym(unwrapped_ret_type).cname - fn_name := g.register_thread_array_wait_call(elsymcname) + fn_name := g.register_thread_array_wait_call(g.unwrap_generic(thread_ret_type)) g.write('${fn_name}(') if g.array_receiver_is_indirect(node.left, resolved_left_type) { g.write('*') @@ -1903,9 +1901,7 @@ fn (mut g Gen) gen_fixed_array_wait(node ast.CallExpr) { thread_type := arr.array_fixed_info().elem_type thread_sym := g.table.sym(thread_type) thread_ret_type := thread_sym.thread_info().return_type - unwrapped_ret_type := g.unwrap_generic(thread_ret_type) - elsymcname := g.table.sym(unwrapped_ret_type).cname - fn_name := g.register_thread_fixed_array_wait_call(node, elsymcname) + fn_name := g.register_thread_fixed_array_wait_call(node, g.unwrap_generic(thread_ret_type)) g.write('${fn_name}(') g.expr(node.left) g.write(')') diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 858fdea47..5ef481c0f 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1811,10 +1811,29 @@ fn (mut g Gen) register_thread_wait_call(eltyp string) { g.gowrappers.writeln('}') } -fn (mut g Gen) register_thread_array_wait_call(eltyp string) string { - is_void := eltyp == 'void' - thread_typ := if is_void { '__v_thread' } else { '__v_thread_${eltyp}' } - ret_typ := if is_void { 'void' } else { 'Array_${eltyp}' } +fn (mut g Gen) thread_array_wait_return_type(thread_ret_type ast.Type) ast.Type { + payload_type := thread_ret_type.clear_option_and_result() + if payload_type == ast.void_type { + return thread_ret_type + } + mut return_type := ast.idx_to_type(g.table.find_or_register_array(payload_type)) + if thread_ret_type.has_flag(.option) { + return_type = return_type.set_flag(.option) + } + if thread_ret_type.has_flag(.result) { + return_type = return_type.set_flag(.result) + } + return return_type +} + +fn (mut g Gen) register_thread_array_wait_call(thread_ret_type ast.Type) string { + payload_type := thread_ret_type.clear_option_and_result() + is_plain_void := thread_ret_type == ast.void_type + is_void_payload := payload_type == ast.void_type + thread_typ := g.gen_gohandle_name(thread_ret_type) + waiter_fn_name := '${thread_typ}_wait' + ret_type := g.thread_array_wait_return_type(thread_ret_type) + ret_styp := g.styp(ret_type) thread_arr_typ := 'Array_${thread_typ}' fn_name := '${thread_arr_typ}_wait' mut should_register := false @@ -1825,43 +1844,125 @@ fn (mut g Gen) register_thread_array_wait_call(eltyp string) string { } } if should_register { - if is_void { - g.register_thread_void_wait_call() - g.waiter_fn_definitions.writeln('void ${fn_name}(${thread_arr_typ} a);') - g.gowrappers.writeln(' -void ${fn_name}(${thread_arr_typ} a) { - for (${ast.int_type_name} i = 0; i < a.len; ++i) { - ${thread_typ} t = ((${thread_typ}*)a.data)[i]; - if (t == 0) continue; - __v_thread_wait(t); - } -}') - } else { - g.register_thread_wait_call(eltyp) - g.waiter_fn_definitions.writeln('${ret_typ} ${fn_name}(${thread_arr_typ} a);') - g.gowrappers.writeln(' -${ret_typ} ${fn_name}(${thread_arr_typ} a) { - ${ret_typ} res = builtin____new_array_with_default(a.len, a.len, sizeof(${eltyp}), 0); - for (${ast.int_type_name} i = 0; i < a.len; ++i) { - ${thread_typ} t = ((${thread_typ}*)a.data)[i];') - if g.pref.os == .windows { + thread_ret_styp := g.styp(thread_ret_type) + g.create_waiter_handler(thread_ret_type, thread_ret_styp, thread_typ) + g.waiter_fn_definitions.writeln('${ret_styp} ${fn_name}(${thread_arr_typ} a);') + g.gowrappers.writeln(' +${ret_styp} ${fn_name}(${thread_arr_typ} a) {') + if !is_void_payload { + payload_arr_styp := g.base_type(ret_type.clear_option_and_result()) + payload_styp := g.styp(payload_type) + g.gowrappers.writeln('\t${payload_arr_styp} res = builtin____new_array_with_default(a.len, a.len, sizeof(${payload_styp}), 0);') + } + if thread_ret_type.has_option_or_result() { + g.gowrappers.writeln('\tbool has_failure = false;') + g.gowrappers.writeln('\t${thread_ret_styp} first_failure = {0};') + } + g.gowrappers.writeln('\tfor (${ast.int_type_name} i = 0; i < a.len; ++i) {') + g.gowrappers.writeln('\t\t${thread_typ} t = ((${thread_typ}*)a.data)[i];') + if g.pref.os == .windows { + if is_plain_void { + g.gowrappers.writeln('\t\tif (t == 0) continue;') + } else { g.gowrappers.writeln('\t\tif (t.handle == 0) continue;') + } + } else { + g.gowrappers.writeln('\t\tif (t == 0) continue;') + } + if thread_ret_type.has_flag(.option) { + payload_arr_styp := if is_void_payload { + '' } else { - g.gowrappers.writeln('\t\tif (t == 0) continue;') + g.base_type(ret_type.clear_option_and_result()) + } + payload_styp := if is_void_payload { '' } else { g.styp(payload_type) } + g.gowrappers.writeln('\t\t${thread_ret_styp} waited = ${waiter_fn_name}(t);') + g.gowrappers.writeln('\t\tif (waited.state != 0) {') + g.gowrappers.writeln('\t\t\tif (!has_failure) {') + g.gowrappers.writeln('\t\t\t\thas_failure = true;') + g.gowrappers.writeln('\t\t\t\tfirst_failure = waited;') + g.gowrappers.writeln('\t\t\t}') + g.gowrappers.writeln('\t\t\tcontinue;') + g.gowrappers.writeln('\t\t}') + if !is_void_payload { + g.gowrappers.writeln('\t\t((${payload_styp}*)res.data)[i] = *((${payload_styp}*)waited.data);') + } + g.gowrappers.writeln('\t}') + g.gowrappers.writeln('\tif (has_failure) {') + if !is_void_payload { + g.gowrappers.writeln('\t\tbuiltin__array_free(&res);') + } + if is_void_payload { + g.gowrappers.writeln('\t\treturn first_failure;') + } else { + g.gowrappers.writeln('\t\treturn (${ret_styp}){ .state = first_failure.state, .err = first_failure.err, .data = {E_STRUCT} };') } - g.gowrappers.writeln('\t\t((${eltyp}*)res.data)[i] = __v_thread_${eltyp}_wait(t); - } - return res; -}') + g.gowrappers.writeln('\t}') + if is_void_payload { + g.gowrappers.writeln('\treturn (${ret_styp}){0};') + } else { + g.gowrappers.writeln('\t${ret_styp} waited_all = {0};') + g.gowrappers.writeln('\tbuiltin___option_ok(&(${payload_arr_styp}[]) { res }, (${option_name}*)(&waited_all), sizeof(${payload_arr_styp}));') + g.gowrappers.writeln('\treturn waited_all;') + } + } else if thread_ret_type.has_flag(.result) { + payload_arr_styp := if is_void_payload { + '' + } else { + g.base_type(ret_type.clear_option_and_result()) + } + payload_styp := if is_void_payload { '' } else { g.styp(payload_type) } + g.gowrappers.writeln('\t\t${thread_ret_styp} waited = ${waiter_fn_name}(t);') + g.gowrappers.writeln('\t\tif (waited.is_error) {') + g.gowrappers.writeln('\t\t\tif (!has_failure) {') + g.gowrappers.writeln('\t\t\t\thas_failure = true;') + g.gowrappers.writeln('\t\t\t\tfirst_failure = waited;') + g.gowrappers.writeln('\t\t\t}') + g.gowrappers.writeln('\t\t\tcontinue;') + g.gowrappers.writeln('\t\t}') + if !is_void_payload { + g.gowrappers.writeln('\t\t((${payload_styp}*)res.data)[i] = *((${payload_styp}*)waited.data);') + } + g.gowrappers.writeln('\t}') + g.gowrappers.writeln('\tif (has_failure) {') + if !is_void_payload { + g.gowrappers.writeln('\t\tbuiltin__array_free(&res);') + } + if is_void_payload { + g.gowrappers.writeln('\t\treturn first_failure;') + } else { + g.gowrappers.writeln('\t\treturn (${ret_styp}){ .is_error = true, .err = first_failure.err, .data = {E_STRUCT} };') + } + g.gowrappers.writeln('\t}') + if is_void_payload { + g.gowrappers.writeln('\treturn (${ret_styp}){0};') + } else { + g.gowrappers.writeln('\t${ret_styp} waited_all = {0};') + g.gowrappers.writeln('\tbuiltin___result_ok(&(${payload_arr_styp}[]) { res }, (${result_name}*)(&waited_all), sizeof(${payload_arr_styp}));') + g.gowrappers.writeln('\treturn waited_all;') + } + } else if is_void_payload { + g.gowrappers.writeln('\t\t${waiter_fn_name}(t);') + g.gowrappers.writeln('\t}') + } else { + payload_styp := g.styp(payload_type) + g.gowrappers.writeln('\t\t((${payload_styp}*)res.data)[i] = ${waiter_fn_name}(t);') + g.gowrappers.writeln('\t}') + g.gowrappers.writeln('\treturn res;') } + g.gowrappers.writeln('}') } return fn_name } -fn (mut g Gen) register_thread_fixed_array_wait_call(node ast.CallExpr, eltyp string) string { - is_void := eltyp == 'void' - thread_typ := if is_void { '__v_thread' } else { '__v_thread_${eltyp}' } - ret_typ := if is_void { 'void' } else { 'Array_${eltyp}' } +fn (mut g Gen) register_thread_fixed_array_wait_call(node ast.CallExpr, thread_ret_type ast.Type) string { + payload_type := thread_ret_type.clear_option_and_result() + is_plain_void := thread_ret_type == ast.void_type + is_void_payload := payload_type == ast.void_type + thread_typ := g.gen_gohandle_name(thread_ret_type) + waiter_fn_name := '${thread_typ}_wait' + ret_type := g.thread_array_wait_return_type(thread_ret_type) + ret_styp := g.styp(ret_type) rec_sym := g.table.sym(node.receiver_type) len := (rec_sym.info as ast.ArrayFixed).size thread_arr_typ := rec_sym.cname @@ -1874,35 +1975,113 @@ fn (mut g Gen) register_thread_fixed_array_wait_call(node ast.CallExpr, eltyp st } } if should_register { - if is_void { - g.register_thread_void_wait_call() - g.waiter_fn_definitions.writeln('void ${fn_name}(${thread_arr_typ} a);') - g.gowrappers.writeln(' -void ${fn_name}(${thread_arr_typ} a) { - for (${ast.int_type_name} i = 0; i < ${len}; ++i) { - ${thread_typ} t = a[i]; - if (t == 0) continue; - __v_thread_wait(t); - } -}') - } else { - g.register_thread_wait_call(eltyp) - g.waiter_fn_definitions.writeln('${ret_typ} ${fn_name}(${thread_arr_typ} a);') - g.gowrappers.writeln(' -${ret_typ} ${fn_name}(${thread_arr_typ} a) { - ${ret_typ} res = builtin____new_array_with_default(${len}, ${len}, sizeof(${eltyp}), 0); - for (${ast.int_type_name} i = 0; i < ${len}; ++i) { - ${thread_typ} t = ((${thread_typ}*)a)[i];') - if g.pref.os == .windows { + thread_ret_styp := g.styp(thread_ret_type) + g.create_waiter_handler(thread_ret_type, thread_ret_styp, thread_typ) + g.waiter_fn_definitions.writeln('${ret_styp} ${fn_name}(${thread_arr_typ} a);') + g.gowrappers.writeln(' +${ret_styp} ${fn_name}(${thread_arr_typ} a) {') + if !is_void_payload { + payload_arr_styp := g.base_type(ret_type.clear_option_and_result()) + payload_styp := g.styp(payload_type) + g.gowrappers.writeln('\t${payload_arr_styp} res = builtin____new_array_with_default(${len}, ${len}, sizeof(${payload_styp}), 0);') + } + if thread_ret_type.has_option_or_result() { + g.gowrappers.writeln('\tbool has_failure = false;') + g.gowrappers.writeln('\t${thread_ret_styp} first_failure = {0};') + } + g.gowrappers.writeln('\tfor (${ast.int_type_name} i = 0; i < ${len}; ++i) {') + g.gowrappers.writeln('\t\t${thread_typ} t = a[i];') + if g.pref.os == .windows { + if is_plain_void { + g.gowrappers.writeln('\t\tif (t == 0) continue;') + } else { g.gowrappers.writeln('\t\tif (t.handle == 0) continue;') + } + } else { + g.gowrappers.writeln('\t\tif (t == 0) continue;') + } + if thread_ret_type.has_flag(.option) { + payload_arr_styp := if is_void_payload { + '' } else { - g.gowrappers.writeln('\t\tif (t == 0) continue;') + g.base_type(ret_type.clear_option_and_result()) + } + payload_styp := if is_void_payload { '' } else { g.styp(payload_type) } + g.gowrappers.writeln('\t\t${thread_ret_styp} waited = ${waiter_fn_name}(t);') + g.gowrappers.writeln('\t\tif (waited.state != 0) {') + g.gowrappers.writeln('\t\t\tif (!has_failure) {') + g.gowrappers.writeln('\t\t\t\thas_failure = true;') + g.gowrappers.writeln('\t\t\t\tfirst_failure = waited;') + g.gowrappers.writeln('\t\t\t}') + g.gowrappers.writeln('\t\t\tcontinue;') + g.gowrappers.writeln('\t\t}') + if !is_void_payload { + g.gowrappers.writeln('\t\t((${payload_styp}*)res.data)[i] = *((${payload_styp}*)waited.data);') + } + g.gowrappers.writeln('\t}') + g.gowrappers.writeln('\tif (has_failure) {') + if !is_void_payload { + g.gowrappers.writeln('\t\tbuiltin__array_free(&res);') + } + if is_void_payload { + g.gowrappers.writeln('\t\treturn first_failure;') + } else { + g.gowrappers.writeln('\t\treturn (${ret_styp}){ .state = first_failure.state, .err = first_failure.err, .data = {E_STRUCT} };') } - g.gowrappers.writeln('\t\t((${eltyp}*)res.data)[i] = __v_thread_${eltyp}_wait(t); - } - return res; -}') + g.gowrappers.writeln('\t}') + if is_void_payload { + g.gowrappers.writeln('\treturn (${ret_styp}){0};') + } else { + g.gowrappers.writeln('\t${ret_styp} waited_all = {0};') + g.gowrappers.writeln('\tbuiltin___option_ok(&(${payload_arr_styp}[]) { res }, (${option_name}*)(&waited_all), sizeof(${payload_arr_styp}));') + g.gowrappers.writeln('\treturn waited_all;') + } + } else if thread_ret_type.has_flag(.result) { + payload_arr_styp := if is_void_payload { + '' + } else { + g.base_type(ret_type.clear_option_and_result()) + } + payload_styp := if is_void_payload { '' } else { g.styp(payload_type) } + g.gowrappers.writeln('\t\t${thread_ret_styp} waited = ${waiter_fn_name}(t);') + g.gowrappers.writeln('\t\tif (waited.is_error) {') + g.gowrappers.writeln('\t\t\tif (!has_failure) {') + g.gowrappers.writeln('\t\t\t\thas_failure = true;') + g.gowrappers.writeln('\t\t\t\tfirst_failure = waited;') + g.gowrappers.writeln('\t\t\t}') + g.gowrappers.writeln('\t\t\tcontinue;') + g.gowrappers.writeln('\t\t}') + if !is_void_payload { + g.gowrappers.writeln('\t\t((${payload_styp}*)res.data)[i] = *((${payload_styp}*)waited.data);') + } + g.gowrappers.writeln('\t}') + g.gowrappers.writeln('\tif (has_failure) {') + if !is_void_payload { + g.gowrappers.writeln('\t\tbuiltin__array_free(&res);') + } + if is_void_payload { + g.gowrappers.writeln('\t\treturn first_failure;') + } else { + g.gowrappers.writeln('\t\treturn (${ret_styp}){ .is_error = true, .err = first_failure.err, .data = {E_STRUCT} };') + } + g.gowrappers.writeln('\t}') + if is_void_payload { + g.gowrappers.writeln('\treturn (${ret_styp}){0};') + } else { + g.gowrappers.writeln('\t${ret_styp} waited_all = {0};') + g.gowrappers.writeln('\tbuiltin___result_ok(&(${payload_arr_styp}[]) { res }, (${result_name}*)(&waited_all), sizeof(${payload_arr_styp}));') + g.gowrappers.writeln('\treturn waited_all;') + } + } else if is_void_payload { + g.gowrappers.writeln('\t\t${waiter_fn_name}(t);') + g.gowrappers.writeln('\t}') + } else { + payload_styp := g.styp(payload_type) + g.gowrappers.writeln('\t\t((${payload_styp}*)res.data)[i] = ${waiter_fn_name}(t);') + g.gowrappers.writeln('\t}') + g.gowrappers.writeln('\treturn res;') } + g.gowrappers.writeln('}') } return fn_name } diff --git a/vlib/v/tests/builtin_arrays/fixed_array_of_threads_wait_test.v b/vlib/v/tests/builtin_arrays/fixed_array_of_threads_wait_test.v index d45ebc493..c372aa2db 100644 --- a/vlib/v/tests/builtin_arrays/fixed_array_of_threads_wait_test.v +++ b/vlib/v/tests/builtin_arrays/fixed_array_of_threads_wait_test.v @@ -13,3 +13,21 @@ fn test_fixed_array_of_threads_wait() { println(results) assert results == [true, true] } + +fn fixed_ok(n int) !int { + if n < 0 { + return error('fixed boom') + } + return n +} + +fn test_fixed_array_of_threads_wait_result() { + threads := [ + spawn fixed_ok(1), + spawn fixed_ok(2), + ]! + + results := threads.wait() or { []int{} } + + assert results == [1, 2] +} diff --git a/vlib/v/tests/fns/go_wait_option_test.v b/vlib/v/tests/fns/go_wait_option_test.v index 7cd04e457..9f67aa13d 100644 --- a/vlib/v/tests/fns/go_wait_option_test.v +++ b/vlib/v/tests/fns/go_wait_option_test.v @@ -80,6 +80,40 @@ fn test_array_val_iter() { assert res[2] == 1.5 } +fn wait_array_option_values() ?[]f64 { + mut threads := []thread ?f64{} + for i in 0 .. 3 { + threads << spawn f(i) + } + values := threads.wait()? + return values +} + +fn wait_array_result_void() ! { + mut threads := []thread !{} + threads << spawn ok_result_void(true) + threads << spawn ok_result_void(false) + threads.wait()! +} + +fn ok_result_void(ok bool) ! { + if !ok { + return error('boom') + } +} + +fn test_array_wait_option_propagates() { + assert wait_array_option_values() or { []f64{} } == [0.0, 1.5, 3.0] +} + +fn test_array_wait_result_void_propagates() { + wait_array_result_void() or { + assert err.msg() == 'boom' + return + } + assert false +} + // For issue 16065 fn get_only_a_option_return(return_none bool) ? { if return_none { -- 2.39.5