diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 398cee24d4..d9998024e1 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -541,6 +541,7 @@ pub: mod string // 'math.bits' is_deprecated bool is_pub bool + is_c_variadic bool is_variadic bool is_anon bool is_noreturn bool // true, when [noreturn] is used on a fn @@ -630,6 +631,7 @@ pub: pub struct Fn { pub: is_variadic bool + is_c_variadic bool language Language is_pub bool is_ctor_new bool // `[use_new] fn JS.Array.prototype.constructor()` diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 0ff911df5f..f6a8c3a2d8 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -214,13 +214,15 @@ fn (t &Table) stringify_fn_after_name(node &FnDecl, mut f strings.Builder, cur_m if !is_type_only { f.write_string(' ') } - if node.is_variadic && is_last_param { + if node.is_variadic && is_last_param && !node.is_c_variadic { f.write_string('...') } f.write_string(s) } if !is_last_param { f.write_string(',') + } else if node.is_c_variadic { + f.write_string(', ...') } nparams_on_pline++ old_pline = pline diff --git a/vlib/v/fmt/tests/c_varargs_keep.vv b/vlib/v/fmt/tests/c_varargs_keep.vv new file mode 100644 index 0000000000..954da99239 --- /dev/null +++ b/vlib/v/fmt/tests/c_varargs_keep.vv @@ -0,0 +1,23 @@ +@[typedef] +struct C.va_list {} + +fn C.va_start(voidptr, voidptr) +fn C.va_end(voidptr) + +fn C.vfprintf(&C.FILE, &char, C.va_list) int + +fn C.printf(&char, ...) + +fn t2(fmt voidptr, ...) { + ap := C.va_list{} + C.va_start(ap, fmt) + C.vfprintf(C.stderr, fmt, ap) + C.va_end(ap) +} + +fn main() { + a := 1 + b := 2.5 + t2(c'%s : %d : %.2f\n', c'foo', a, b) + C.printf(c'%s\n', c'foo') +} diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index e38edf7187..9037df5fe5 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -224,10 +224,12 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { mut is_call := false mut gen_or := false mut blank_assign := false + mut is_va_list := false // C varargs mut ident := ast.Ident{ scope: unsafe { nil } } left_sym := g.table.sym(g.unwrap_generic(var_type)) + is_va_list = left_sym.language == .c && left_sym.name == 'C.va_list' if mut left is ast.Ident { ident = left g.curr_var_name << ident.name @@ -612,7 +614,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } g.write('${ret_styp} (${msvc_call_conv}*${fn_name}) (') def_pos := g.definitions.len - g.fn_decl_params(right_sym.info.func.params, unsafe { nil }, false) + g.fn_decl_params(right_sym.info.func.params, unsafe { nil }, false, + false) g.definitions.go_back(g.definitions.len - def_pos) g.write(')${call_conv_attribute_suffix}') } @@ -693,9 +696,12 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { g.expr(left) } g.is_assign_lhs = false - if is_fixed_array_var { + if is_fixed_array_var || is_va_list { if is_decl { g.writeln(';') + if is_va_list { + continue + } } } else if !g.is_arraymap_set && !str_add && !op_overloaded { g.write(' ${op} ') diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 4f21095dca..87130fa33f 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -7750,7 +7750,7 @@ static inline __shared__${interface_name} ${shared_fn_name}(__shared__${cctype}* ...params[0] typ: st.set_nr_muls(1) } - fargs, _, _ := g.fn_decl_params(params, unsafe { nil }, false) + fargs, _, _ := g.fn_decl_params(params, unsafe { nil }, false, false) mut parameter_name := g.out.cut_last(g.out.len - params_start_pos) if st.is_ptr() { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index cab00d70fe..2f02ebf5d7 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -290,7 +290,8 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { g.write(sig) g.definitions.write_string(sig) } else { - g.fn_decl_params(call_fn.func.params, unsafe { nil }, call_fn.func.is_variadic) + g.fn_decl_params(call_fn.func.params, unsafe { nil }, call_fn.func.is_variadic, + call_fn.func.is_c_variadic) } g.writeln(') {') @@ -384,7 +385,8 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { g.write(fn_header) } arg_start_pos := g.out.len - fargs, fargtypes, heap_promoted := g.fn_decl_params(node.params, node.scope, node.is_variadic) + fargs, fargtypes, heap_promoted := g.fn_decl_params(node.params, node.scope, node.is_variadic, + node.is_c_variadic) if is_closure { g.nr_closures++ } @@ -695,7 +697,7 @@ fn (mut g Gen) write_defer_stmts_when_needed() { } } -fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic bool) ([]string, []string, []bool) { +fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic bool, is_c_variadic bool) ([]string, []string, []bool) { mut fparams := []string{} mut fparamtypes := []string{} mut heap_promoted := []bool{} @@ -720,7 +722,7 @@ fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic func := info.func g.write('${g.typ(func.return_type)} (*${caname})(') g.definitions.write_string('${g.typ(func.return_type)} (*${caname})(') - g.fn_decl_params(func.params, unsafe { nil }, func.is_variadic) + g.fn_decl_params(func.params, unsafe { nil }, func.is_variadic, func.is_c_variadic) g.write(')') g.definitions.write_string(')') fparams << caname @@ -756,7 +758,7 @@ fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic g.definitions.write_string(', ') } } - if g.pref.translated && is_variadic { + if (g.pref.translated && is_variadic) || is_c_variadic { g.write(', ... ') g.definitions.write_string(', ... ') } diff --git a/vlib/v/gen/c/testdata/c_varargs.c.must_have b/vlib/v/gen/c/testdata/c_varargs.c.must_have new file mode 100644 index 0000000000..76b0018a5a --- /dev/null +++ b/vlib/v/gen/c/testdata/c_varargs.c.must_have @@ -0,0 +1,2 @@ +VV_LOCAL_SYMBOL void main__t2(voidptr fmt, ... ); +VV_LOCAL_SYMBOL void main__t2(voidptr fmt, ... ) { \ No newline at end of file diff --git a/vlib/v/gen/c/testdata/c_varargs.out b/vlib/v/gen/c/testdata/c_varargs.out new file mode 100644 index 0000000000..fbaf5c04cb --- /dev/null +++ b/vlib/v/gen/c/testdata/c_varargs.out @@ -0,0 +1,2 @@ +foo : 1 : 2.50 +foo diff --git a/vlib/v/gen/c/testdata/c_varargs.vv b/vlib/v/gen/c/testdata/c_varargs.vv new file mode 100644 index 0000000000..fa5210d473 --- /dev/null +++ b/vlib/v/gen/c/testdata/c_varargs.vv @@ -0,0 +1,23 @@ +@[typedef] +struct C.va_list {} + +fn C.va_start(voidptr, voidptr) +fn C.va_end(voidptr) + +fn C.vfprintf(&C.FILE, &char, C.va_list) int + +fn C.printf(&char, ...) + +fn t2(fmt voidptr, ...) { + ap := C.va_list{} + C.va_start(ap, fmt) + C.vfprintf(C.stdout, fmt, ap) + C.va_end(ap) +} + +fn main() { + a := 1 + b := 2.5 + t2(c'%s : %d : %.2f\n', c'foo', a, b) + C.printf(c'%s\n', c'foo') +} diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index acee18244b..e92d590ed1 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -413,7 +413,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { } } // Params - params_t, are_params_type_only, mut is_variadic := p.fn_params() + params_t, are_params_type_only, mut is_variadic, mut is_c_variadic := p.fn_params() if is_c2v_variadic { is_variadic = true } @@ -563,6 +563,7 @@ run them via `v file.v` instead', params: params return_type: return_type is_variadic: is_variadic + is_c_variadic: is_c_variadic generic_names: generic_names is_pub: is_pub is_deprecated: is_deprecated @@ -626,6 +627,7 @@ run them via `v file.v` instead', is_direct_arr: is_direct_arr is_pub: is_pub is_variadic: is_variadic + is_c_variadic: is_c_variadic is_main: is_main is_test: is_test is_keep_alive: is_keep_alive @@ -783,7 +785,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn { } inherited_vars_name := inherited_vars.map(it.name) _, generic_names := p.parse_generic_types() - params, _, is_variadic := p.fn_params() + params, _, is_variadic, _ := p.fn_params() for param in params { if param.name == '' && p.table.sym(param.typ).kind != .placeholder { p.error_with_pos('use `_` to name an unused parameter', param.pos) @@ -876,10 +878,11 @@ fn (mut p Parser) anon_fn() ast.AnonFn { } // part of fn declaration -fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { +fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { p.check(.lpar) mut params := []ast.Param{} mut is_variadic := false + mut is_c_variadic := false // `int, int, string` (no names, just types) param_name := if p.tok.kind == .name && p.tok.lit.len > 0 && p.tok.lit[0].is_capital() { p.prepend_mod(p.tok.lit) @@ -901,7 +904,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { for p.tok.kind != .rpar { if p.tok.kind == .eof { p.error_with_pos('expecting `)`', p.tok.pos()) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic @@ -918,13 +921,18 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { if p.tok.kind == .ellipsis { p.next() is_variadic = true + is_c_variadic = p.tok.kind == .rpar + if is_c_variadic { + p.check(.rpar) + return params, types_only, is_variadic, is_c_variadic + } } pos := p.tok.pos() mut param_type := p.parse_type() type_pos := pos.extend(p.prev_tok.pos()) if param_type == 0 { // error is added in parse_type - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } if is_mut { if !param_type.has_flag(.generic) { @@ -941,7 +949,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { } } else if is_shared || is_atomic { p.error_with_pos('generic object cannot be `atomic`or `shared`', pos) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } if param_type.is_ptr() && p.table.sym(param_type).kind == .struct_ { param_type = param_type.ref() @@ -960,14 +968,14 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { } if p.tok.kind == .eof { p.error_with_pos('expecting `)`', p.prev_tok.pos()) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } if p.tok.kind == .comma { if is_variadic { p.error_with_pos('cannot use ...(variadic) with non-final parameter no ${param_no}', pos) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } p.next() } @@ -987,14 +995,14 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { param_no++ if param_no > 1024 { p.error_with_pos('too many parameters', pos) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } } } else { for p.tok.kind != .rpar { if p.tok.kind == .eof { p.error_with_pos('expecting `)`', p.tok.pos()) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic @@ -1002,6 +1010,12 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { if is_mut { p.next() } + if p.tok.kind == .ellipsis && p.peek_tok.kind == .rpar { + p.check(.ellipsis) + p.check(.rpar) + return params, types_only, true, true + } + mut param_pos := [p.tok.pos()] name := p.check_name() mut param_names := [name] @@ -1035,13 +1049,18 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { if p.tok.kind == .ellipsis { p.next() is_variadic = true + is_c_variadic = p.tok.kind == .rpar + if is_c_variadic { + p.check(.rpar) + return params, types_only, is_variadic, is_c_variadic + } } pos := p.tok.pos() mut typ := p.parse_type() type_pos[0] = pos.extend(p.prev_tok.pos()) if typ == 0 { // error is added in parse_type - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } if is_mut { if !typ.has_flag(.generic) { @@ -1059,7 +1078,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { } else if is_shared || is_atomic { p.error_with_pos('generic object cannot be `atomic` or `shared`', pos) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } if typ.is_ptr() && p.table.sym(typ).kind == .struct_ { typ = typ.ref() @@ -1097,12 +1116,12 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { if is_variadic && p.tok.kind == .comma && p.peek_tok.kind != .rpar { p.error_with_pos('cannot use ...(variadic) with non-final parameter ${para_name}', param_pos[i]) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } } if p.tok.kind == .eof { p.error_with_pos('expecting `)`', p.prev_tok.pos()) - return []ast.Param{}, false, false + return []ast.Param{}, false, false, false } if p.tok.kind != .rpar { p.check(.comma) @@ -1110,7 +1129,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool) { } } p.check(.rpar) - return params, types_only, is_variadic + return params, types_only, is_variadic, is_c_variadic } fn (mut p Parser) spawn_expr() ast.SpawnExpr { diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index 155db20de0..95e1f8b7e3 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -311,7 +311,7 @@ fn (mut p Parser) parse_fn_type(name string, generic_types []ast.Type) ast.Type mut has_generic := false line_nr := p.tok.line_nr - params, _, is_variadic := p.fn_params() + params, _, is_variadic, is_c_variadic := p.fn_params() for param in params { if param.typ.has_flag(.generic) { has_generic = true @@ -341,6 +341,7 @@ fn (mut p Parser) parse_fn_type(name string, generic_types []ast.Type) ast.Type name: name params: params is_variadic: is_variadic + is_c_variadic: is_c_variadic return_type: return_type return_type_pos: return_type_pos generic_names: generic_names diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 8761130f10..b8835c6364 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -655,7 +655,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { p.error_with_pos('duplicate method `${name}`', method_start_pos) return ast.InterfaceDecl{} } - params_t, _, is_variadic := p.fn_params() // TODO: merge ast.Param and ast.Arg to avoid this + params_t, _, is_variadic, _ := p.fn_params() // TODO: merge ast.Param and ast.Arg to avoid this mut params := [ ast.Param{ name: 'x'