From 1d1d649202779cca7b80d5a47268ca7bb6bc3c69 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 13 Jun 2026 03:05:14 +0300 Subject: [PATCH] fix master ci failures --- vlib/net/http/server_tls_idle.v | 3 + vlib/net/http/server_tls_notd_use_openssl.v | 1 + vlib/net/mbedtls/ssl_connection.c.v | 23 +- vlib/v/checker/fn.v | 354 ++++++++++---------- 4 files changed, 204 insertions(+), 177 deletions(-) diff --git a/vlib/net/http/server_tls_idle.v b/vlib/net/http/server_tls_idle.v index dcc1190bd..5c8494b5a 100644 --- a/vlib/net/http/server_tls_idle.v +++ b/vlib/net/http/server_tls_idle.v @@ -45,5 +45,8 @@ fn (mut t TlsIdleConnTracker) close_idle() { t.mu.unlock() for handle in handles { net.shutdown(handle) + $if windows { + net.close(handle) or {} + } } } diff --git a/vlib/net/http/server_tls_notd_use_openssl.v b/vlib/net/http/server_tls_notd_use_openssl.v index a04b84046..639c7d717 100644 --- a/vlib/net/http/server_tls_notd_use_openssl.v +++ b/vlib/net/http/server_tls_notd_use_openssl.v @@ -46,6 +46,7 @@ fn (mut s Server) listen_and_serve_tls() { cert_key: s.cert_key in_memory_verification: s.in_memory_verification validate: false // accept any client; servers don't verify clients by default + read_timeout: s.read_timeout alpn_protocols: alpn }) or { eprintln('Listening TLS on ${addr} failed, err: ${err}') diff --git a/vlib/net/mbedtls/ssl_connection.c.v b/vlib/net/mbedtls/ssl_connection.c.v index 7df540668..9fda881a3 100644 --- a/vlib/net/mbedtls/ssl_connection.c.v +++ b/vlib/net/mbedtls/ssl_connection.c.v @@ -7,6 +7,7 @@ import time const mbedtls_client_read_timeout_ms = $d('mbedtls_client_read_timeout_ms', 10_000) const mbedtls_server_read_timeout_ms = $d('mbedtls_server_read_timeout_ms', 41_000) const default_mbedtls_client_read_timeout = mbedtls_client_read_timeout_ms * time.millisecond +const default_mbedtls_server_read_timeout = mbedtls_server_read_timeout_ms * time.millisecond fn init_rng(mut ctr_drbg C.mbedtls_ctr_drbg_context, mut entropy C.mbedtls_entropy_context) ! { $if trace_ssl ? { @@ -226,10 +227,6 @@ fn (mut l SSLListener) init() ! { C.mbedtls_ssl_init(&l.ssl) C.mbedtls_ssl_config_init(&l.conf) init_rng(mut l.ctr_drbg, mut l.entropy)! - $if trace_mbedtls_timeouts ? { - dump(mbedtls_server_read_timeout_ms) - } - C.mbedtls_ssl_conf_read_timeout(&l.conf, mbedtls_server_read_timeout_ms) l.certs = &SSLCerts{} C.mbedtls_x509_crt_init(&l.certs.client_cert) C.mbedtls_pk_init(&l.certs.client_key) @@ -275,6 +272,11 @@ fn (mut l SSLListener) init() ! { return error_with_code("net.mbedtls SSLListener.init, mbedtls_ssl_config_defaults can't set config defaults ret: ${ret}", ret) } + listener_read_timeout := ssl_listener_read_timeout(l.config) + $if trace_mbedtls_timeouts ? { + dump(listener_read_timeout) + } + C.mbedtls_ssl_conf_read_timeout(&l.conf, ssl_read_timeout_ms(listener_read_timeout)) C.mbedtls_ssl_conf_ca_chain(&l.conf, &l.certs.cacert, unsafe { nil }) ret = C.mbedtls_ssl_conf_own_cert(&l.conf, &l.certs.client_cert, &l.certs.client_key) @@ -351,8 +353,10 @@ pub fn (mut l SSLListener) accept() !&SSLConn { fn (mut l SSLListener) accept_tcp_connection() !&SSLConn { mut conn := &SSLConn{ - config: l.config - opened: true + config: l.config + duration: ssl_listener_read_timeout(l.config) + read_timeout: ssl_listener_read_timeout(l.config) + opened: true } ip := [16]u8{} iplen := usize(0) @@ -465,6 +469,13 @@ fn ssl_read_timeout_ms(timeout time.Duration) u32 { return u32(timeout_ms) } +fn ssl_listener_read_timeout(config SSLConnectConfig) time.Duration { + if config.read_timeout == default_mbedtls_client_read_timeout { + return default_mbedtls_server_read_timeout + } + return config.read_timeout +} + fn ssl_timeout_deadline(timeout time.Duration) time.Time { if timeout <= 0 || timeout == net.infinite_timeout { return time.unix(0) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index bbfe43bc8..8f9710832 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2271,6 +2271,7 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. variadic_start := variadic_call_arg_start_idx(func, false) has_typed_variadic := func.is_variadic && !func.is_c_variadic for i, mut call_arg in node.args { + mut variadic_arg_handled := false if func.params.len == 0 { continue } @@ -2327,9 +2328,9 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. c.expr(mut call_arg.expr) } if i == args_len - 1 { - c.check_variadic_arg(call_arg.expr, typ, expected_type, param.typ, i + 1, - func.name, func.is_method, func.is_variadic, args_len == 1 && i == 0, - func.generic_names.len > 0, node.pos, call_arg.pos) + variadic_arg_handled = c.check_variadic_arg(call_arg.expr, typ, expected_type, + param.typ, i + 1, func.name, func.is_method, func.is_variadic, args_len == 1 + && i == 0, func.generic_names.len > 0, node.pos, call_arg.pos) } } else { c.expected_type = param.typ @@ -2555,160 +2556,164 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. c.error('literal argument cannot be passed as reference parameter `${c.table.type_to_str(param.typ)}`', call_arg.pos) } - c.check_expected_call_arg(arg_typ, c.unwrap_generic(param.typ), node.language, call_arg) or { - if param.typ.has_flag(.generic) { - continue - } - // When decomposing a generic variadic arg into a non-variadic function, - // each generic instantiation re-checks all branches (e.g. match arms), - // so the decomposed type may not match the target param type for - // unreachable branches. Skip the error in this case. - if has_decompose && call_arg.expr is ast.ArrayDecompose - && c.table.cur_concrete_types.len > 0 { - continue - } - if param_typ_sym.info is ast.Array && arg_typ_sym.info is ast.Array { - param_elem_type := c.table.unaliased_type(param_typ_sym.info.elem_type) - arg_elem_type := c.table.unaliased_type(arg_typ_sym.info.elem_type) - param_nr_muls := param.typ.nr_muls() - arg_nr_muls := if call_arg.is_mut { - arg_typ.nr_muls() + 1 - } else { - arg_typ.nr_muls() + if !variadic_arg_handled { + c.check_expected_call_arg(arg_typ, c.unwrap_generic(param.typ), node.language, call_arg) or { + if param.typ.has_flag(.generic) { + continue } - if param.typ.has_flag(.option) == arg_typ.has_flag(.option) - && param.typ.has_flag(.result) == arg_typ.has_flag(.result) - && param_nr_muls == arg_nr_muls - && param_typ_sym.info.nr_dims == arg_typ_sym.info.nr_dims - && param_elem_type == arg_elem_type { + // When decomposing a generic variadic arg into a non-variadic function, + // each generic instantiation re-checks all branches (e.g. match arms), + // so the decomposed type may not match the target param type for + // unreachable branches. Skip the error in this case. + if has_decompose && call_arg.expr is ast.ArrayDecompose + && c.table.cur_concrete_types.len > 0 { continue } - } else if arg_typ_sym.info is ast.MultiReturn { - arg_typs := arg_typ_sym.info.types - if !(has_typed_variadic && param_i >= variadic_start) { - if arg_typ_sym.info.types.len > func.params.len { - c.error('trying to pass ${arg_typ_sym.info.types.len} argument(s), but function expects ${func.params.len} argument(s)', - node.pos) - continue_check = false + if param_typ_sym.info is ast.Array && arg_typ_sym.info is ast.Array { + param_elem_type := c.table.unaliased_type(param_typ_sym.info.elem_type) + arg_elem_type := c.table.unaliased_type(arg_typ_sym.info.elem_type) + param_nr_muls := param.typ.nr_muls() + arg_nr_muls := if call_arg.is_mut { + arg_typ.nr_muls() + 1 + } else { + arg_typ.nr_muls() + } + if param.typ.has_flag(.option) == arg_typ.has_flag(.option) + && param.typ.has_flag(.result) == arg_typ.has_flag(.result) + && param_nr_muls == arg_nr_muls + && param_typ_sym.info.nr_dims == arg_typ_sym.info.nr_dims + && param_elem_type == arg_elem_type { + continue + } + } else if arg_typ_sym.info is ast.MultiReturn { + arg_typs := arg_typ_sym.info.types + if !(has_typed_variadic && param_i >= variadic_start) { + if arg_typ_sym.info.types.len > func.params.len { + c.error('trying to pass ${arg_typ_sym.info.types.len} argument(s), but function expects ${func.params.len} argument(s)', + node.pos) + continue_check = false + return ast.void_type + } + } + if !func.is_variadic && func.params.len < (param_i + arg_typ_sym.info.types.len) { + c.fn_call_error_have_want( + nr_params: func.params.len + nr_args: param_i + arg_typ_sym.info.types.len + params: func.params + args: node.args + pos: node.pos + ) return ast.void_type } - } - if !func.is_variadic && func.params.len < (param_i + arg_typ_sym.info.types.len) { - c.fn_call_error_have_want( - nr_params: func.params.len - nr_args: param_i + arg_typ_sym.info.types.len - params: func.params - args: node.args - pos: node.pos - ) - return ast.void_type - } - out: for n in 0 .. arg_typ_sym.info.types.len { - curr_arg := arg_typs[n] - multi_param := call_arg_param_for_fn(func, n + param_i, false) - c.check_expected_call_arg(curr_arg, c.unwrap_generic(multi_param.typ), - node.language, call_arg) or { - c.error('${err.msg()} in argument ${param_i + n + 1} to `${fn_name}` from ${c.table.type_to_str(arg_typ)}', - call_arg.pos) - continue out + out: for n in 0 .. arg_typ_sym.info.types.len { + curr_arg := arg_typs[n] + multi_param := call_arg_param_for_fn(func, n + param_i, false) + c.check_expected_call_arg(curr_arg, c.unwrap_generic(multi_param.typ), + node.language, call_arg) or { + c.error('${err.msg()} in argument ${param_i + n + 1} to `${fn_name}` from ${c.table.type_to_str(arg_typ)}', + call_arg.pos) + continue out + } } - } - nr_multi_values += arg_typ_sym.info.types.len - 1 - continue - } else if param_typ_sym.info is ast.Struct && arg_typ_sym.info is ast.Struct - && param_typ_sym.info.is_anon { - if c.is_anon_struct_compatible(param_typ_sym.info, arg_typ_sym.info) { - continue - } - } else if c.embeds_expected_call_arg_type(arg_typ, final_param_typ) { - continue - } - if c.pref.translated || c.file.is_translated { - // in case of variadic make sure to use array elem type for checks - // check_expected_call_arg already does this before checks also. - param_type := if param.typ.has_flag(.variadic) { - param_typ_sym.array_info().elem_type - } else { - param.typ - } - // TODO: duplicated logic in check_types() (check_types.v) - // Allow enums to be used as ints and vice versa in translated code - if param_type.idx() in ast.integer_type_idxs && arg_typ_sym.kind == .enum { + nr_multi_values += arg_typ_sym.info.types.len - 1 continue - } - if arg_typ.idx() in ast.integer_type_idxs && param_typ_sym.kind == .enum { + } else if param_typ_sym.info is ast.Struct && arg_typ_sym.info is ast.Struct + && param_typ_sym.info.is_anon { + if c.is_anon_struct_compatible(param_typ_sym.info, arg_typ_sym.info) { + continue + } + } else if c.embeds_expected_call_arg_type(arg_typ, final_param_typ) { continue } + if c.pref.translated || c.file.is_translated { + // in case of variadic make sure to use array elem type for checks + // check_expected_call_arg already does this before checks also. + param_type := if param.typ.has_flag(.variadic) { + param_typ_sym.array_info().elem_type + } else { + param.typ + } + // TODO: duplicated logic in check_types() (check_types.v) + // Allow enums to be used as ints and vice versa in translated code + if param_type.idx() in ast.integer_type_idxs && arg_typ_sym.kind == .enum { + continue + } + if arg_typ.idx() in ast.integer_type_idxs && param_typ_sym.kind == .enum { + continue + } - if (arg_typ == ast.bool_type && param_type.is_int()) - || (arg_typ.is_int() && param_type == ast.bool_type) { - continue - } + if (arg_typ == ast.bool_type && param_type.is_int()) + || (arg_typ.is_int() && param_type == ast.bool_type) { + continue + } - // In C unsafe number casts are used all the time (e.g. `char*` where - // `int*` is expected etc), so just allow them all. - mut param_is_number := c.table.unaliased_type(param_type).is_number() - if param_type.is_ptr() { - param_is_number = param_type.deref().is_number() - } - mut typ_is_number := c.table.unaliased_type(arg_typ).is_number() - if arg_typ.is_ptr() { - typ_is_number = arg_typ.deref().is_number() - } - if param_is_number && typ_is_number { - continue - } - // Allow voidptrs for everything - if param_type == ast.voidptr_type_idx || param_type == ast.nil_type_idx - || arg_typ == ast.voidptr_type_idx || arg_typ == ast.nil_type_idx { - continue - } - if param_type.is_any_kind_of_pointer() && arg_typ.is_any_kind_of_pointer() { - continue - } - unaliased_param_sym := c.table.sym(c.table.unaliased_type(param_type)) - unaliased_arg_sym := c.table.sym(c.table.unaliased_type(arg_typ)) - // Allow `[32]i8` as `&i8` etc - if ((unaliased_arg_sym.kind == .array_fixed || unaliased_arg_sym.kind == .array) - && (param_is_number - || c.table.unaliased_type(param_type).is_any_kind_of_pointer())) - || ((unaliased_param_sym.kind == .array_fixed - || unaliased_param_sym.kind == .array) - && (typ_is_number || c.table.unaliased_type(arg_typ).is_any_kind_of_pointer())) { - continue - } - // Allow `[N]anyptr` as `[N]anyptr` - if unaliased_arg_sym.info is ast.Array && unaliased_param_sym.info is ast.Array { - if unaliased_arg_sym.info.elem_type.is_any_kind_of_pointer() - && unaliased_param_sym.info.elem_type.is_any_kind_of_pointer() { + // In C unsafe number casts are used all the time (e.g. `char*` where + // `int*` is expected etc), so just allow them all. + mut param_is_number := c.table.unaliased_type(param_type).is_number() + if param_type.is_ptr() { + param_is_number = param_type.deref().is_number() + } + mut typ_is_number := c.table.unaliased_type(arg_typ).is_number() + if arg_typ.is_ptr() { + typ_is_number = arg_typ.deref().is_number() + } + if param_is_number && typ_is_number { continue } - } else if unaliased_arg_sym.info is ast.ArrayFixed - && unaliased_param_sym.info is ast.ArrayFixed { - if unaliased_arg_sym.info.elem_type.is_any_kind_of_pointer() - && unaliased_param_sym.info.elem_type.is_any_kind_of_pointer() { + // Allow voidptrs for everything + if param_type == ast.voidptr_type_idx || param_type == ast.nil_type_idx + || arg_typ == ast.voidptr_type_idx || arg_typ == ast.nil_type_idx { + continue + } + if param_type.is_any_kind_of_pointer() && arg_typ.is_any_kind_of_pointer() { + continue + } + unaliased_param_sym := c.table.sym(c.table.unaliased_type(param_type)) + unaliased_arg_sym := c.table.sym(c.table.unaliased_type(arg_typ)) + // Allow `[32]i8` as `&i8` etc + arg_is_array_like := unaliased_arg_sym.kind in [.array_fixed, .array] + param_is_array_like := unaliased_param_sym.kind in [.array_fixed, .array] + if arg_is_array_like && (param_is_number + || c.table.unaliased_type(param_type).is_any_kind_of_pointer()) { + continue + } + if param_is_array_like && (typ_is_number + || c.table.unaliased_type(arg_typ).is_any_kind_of_pointer()) { + continue + } + // Allow `[N]anyptr` as `[N]anyptr` + if unaliased_arg_sym.info is ast.Array && unaliased_param_sym.info is ast.Array { + if unaliased_arg_sym.info.elem_type.is_any_kind_of_pointer() + && unaliased_param_sym.info.elem_type.is_any_kind_of_pointer() { + continue + } + } else if unaliased_arg_sym.info is ast.ArrayFixed + && unaliased_param_sym.info is ast.ArrayFixed { + if unaliased_arg_sym.info.elem_type.is_any_kind_of_pointer() + && unaliased_param_sym.info.elem_type.is_any_kind_of_pointer() { + continue + } + } + // Allow `int` as `&i8` + if param_type.is_any_kind_of_pointer() && typ_is_number { + continue + } + // Allow `&i8` as `int` + if arg_typ.is_any_kind_of_pointer() && param_is_number { continue } } - // Allow `int` as `&i8` - if param_type.is_any_kind_of_pointer() && typ_is_number { - continue - } - // Allow `&i8` as `int` - if arg_typ.is_any_kind_of_pointer() && param_is_number { + // if first_sym.name == 'VContext' && f.params[0].name == 'ctx' { // TODO use int comparison for perf + //} + /* + if param_typ_sym.info is ast.Struct && param_typ_sym.name == 'VContext' { + c.note('ok', call_arg.pos) continue } + */ + c.error('${err.msg()} in argument ${i + nr_multi_values + 1} to `${fn_name}`', + call_arg.pos) } - // if first_sym.name == 'VContext' && f.params[0].name == 'ctx' { // TODO use int comparison for perf - //} - /* - if param_typ_sym.info is ast.Struct && param_typ_sym.name == 'VContext' { - c.note('ok', call_arg.pos) - continue - } - */ - c.error('${err.msg()} in argument ${i + nr_multi_values + 1} to `${fn_name}`', - call_arg.pos) } if final_param_sym.kind == .struct && arg_typ !in [ast.voidptr_type, ast.nil_type] && !c.check_multiple_ptr_match(arg_typ, param.typ, param, call_arg) { @@ -3710,6 +3715,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) c.resolve_short_syntax_call_arg_type(arg, exp_arg_typ, resolved_method_generic_names, resolved_method_concrete_types) } + mut variadic_arg_handled := false if has_typed_variadic && i >= variadic_start { variadic_sym := c.table.sym(exp_arg_typ) if variadic_sym.info is ast.Array { @@ -3754,9 +3760,9 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) } typ := c.expr(mut arg.expr) if i == node.args.len - 1 { - c.check_variadic_arg(arg.expr, typ, expected_type, param.typ, i + 1, method.name, - true, method.is_variadic, node.args.len == 1 && i == 0, - method.generic_names.len > 0, node.pos, arg.pos) + variadic_arg_handled = c.check_variadic_arg(arg.expr, typ, expected_type, + param.typ, i + 1, method.name, true, method.is_variadic, node.args.len == 1 + && i == 0, method.generic_names.len > 0, node.pos, arg.pos) } } else { c.expected_type = param.typ @@ -3870,38 +3876,41 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) c.error('literal argument cannot be passed as reference parameter `${c.table.type_to_str(param.typ)}`', arg.pos) } - c.check_expected_call_arg(c.unwrap_generic(got_arg_typ), exp_arg_typ, node.language, arg) or { - // str method, allow type with str method if fn arg is string - // Passing an int or a string array produces a c error here - // Deleting this condition results in proper V error messages - // if arg_typ_sym.kind == .string && typ_sym.has_method('str') { - // continue - // } - param_typ_sym := c.table.sym(exp_arg_typ) - arg_typ_sym := c.table.sym(got_arg_typ) - if param_typ_sym.info is ast.Array && arg_typ_sym.info is ast.Array { - param_elem_type := c.table.unaliased_type(param_typ_sym.info.elem_type) - arg_elem_type := c.table.unaliased_type(arg_typ_sym.info.elem_type) - if exp_arg_typ.has_flag(.option) == got_arg_typ.has_flag(.option) - && exp_arg_typ.has_flag(.result) == got_arg_typ.has_flag(.result) - && exp_arg_typ.nr_muls() == got_arg_typ.nr_muls() - && param_typ_sym.info.nr_dims == arg_typ_sym.info.nr_dims - && param_elem_type == arg_elem_type { + if !variadic_arg_handled { + c.check_expected_call_arg(c.unwrap_generic(got_arg_typ), exp_arg_typ, node.language, + arg) or { + // str method, allow type with str method if fn arg is string + // Passing an int or a string array produces a c error here + // Deleting this condition results in proper V error messages + // if arg_typ_sym.kind == .string && typ_sym.has_method('str') { + // continue + // } + param_typ_sym := c.table.sym(exp_arg_typ) + arg_typ_sym := c.table.sym(got_arg_typ) + if param_typ_sym.info is ast.Array && arg_typ_sym.info is ast.Array { + param_elem_type := c.table.unaliased_type(param_typ_sym.info.elem_type) + arg_elem_type := c.table.unaliased_type(arg_typ_sym.info.elem_type) + if exp_arg_typ.has_flag(.option) == got_arg_typ.has_flag(.option) + && exp_arg_typ.has_flag(.result) == got_arg_typ.has_flag(.result) + && exp_arg_typ.nr_muls() == got_arg_typ.nr_muls() + && param_typ_sym.info.nr_dims == arg_typ_sym.info.nr_dims + && param_elem_type == arg_elem_type { + continue + } + } + if c.is_optional_array_arg_compatible(got_arg_typ, exp_arg_typ) { continue } + receiver_name := if method.receiver_type.has_flag(.generic) + && method_concrete_types.len > 0 { + c.table.type_to_str(c.table.unwrap_generic_type(method.receiver_type.set_nr_muls(0), + method.generic_names, method_concrete_types)) + } else { + left_sym.name + } + c.error('${err.msg()} in argument ${i + 1} to `${receiver_name}.${method_name}`', + arg.pos) } - if c.is_optional_array_arg_compatible(got_arg_typ, exp_arg_typ) { - continue - } - receiver_name := if method.receiver_type.has_flag(.generic) - && method_concrete_types.len > 0 { - c.table.type_to_str(c.table.unwrap_generic_type(method.receiver_type.set_nr_muls(0), - method.generic_names, method_concrete_types)) - } else { - left_sym.name - } - c.error('${err.msg()} in argument ${i + 1} to `${receiver_name}.${method_name}`', - arg.pos) } if mut arg.expr is ast.LambdaExpr { // Calling fn is generic and lambda arg also is generic @@ -5628,7 +5637,7 @@ fn (mut c Checker) supports_veb_string_bound_param(typ ast.Type) bool { fn (mut c Checker) check_variadic_arg(arg_expr ast.Expr, typ ast.Type, expected_type ast.Type, param_typ ast.Type, arg_num int, fn_name string, is_method bool, fn_is_variadic bool, is_single_array_arg bool, - is_generic_fn bool, call_pos token.Pos, arg_pos token.Pos) { + is_generic_fn bool, call_pos token.Pos, arg_pos token.Pos) bool { kind := c.table.sym(typ).kind is_decompose := arg_expr is ast.ArrayDecompose mut allow_variadic_pass := false @@ -5656,11 +5665,14 @@ fn (mut c Checker) check_variadic_arg(arg_expr ast.Expr, typ ast.Type, expected_ && !param_typ.has_flag(.generic) && expected_type != typ { c.error('to pass `${arg_expr}` (${styp}) to `${fn_name}` (which accepts type `...${elem_styp}`), use `...${arg_expr}`', call_pos) + return true } else if is_decompose && !param_typ.has_flag(.generic) && expected_kind != .interface && expected_type.idx() != typ.idx() && typ != ast.void_type { c.error('cannot use `...${styp}` as `...${elem_styp}` in argument ${arg_num} to `${fn_name}`', arg_pos) + return true } + return kind == .array && !is_decompose && allow_variadic_pass } // autofree_expr_has_or_block_in_chain returns true if expr is a CallExpr whose call chain -- 2.39.5