From 50db3cf46275dd096b9188418eeb276b994ffccd Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:29 +0300 Subject: [PATCH] parser: Allow to omit commas in multiline function signature block (fixes #22021) --- doc/docs.md | 11 +++++++++++ vlib/v/parser/fn.v | 15 ++++++++++++--- ..._decl_params_missing_comma_single_line_err.out | 3 +++ ...n_decl_params_missing_comma_single_line_err.vv | 1 + .../multiline_fn_signature_omitted_comma_test.v | 12 ++++++++++++ 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.out create mode 100644 vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.vv create mode 100644 vlib/v/tests/fns/multiline_fn_signature_omitted_comma_test.v diff --git a/doc/docs.md b/doc/docs.md index a767efc19..6b50e1976 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -380,6 +380,17 @@ fn sub(x int, y int) int { Again, the type comes after the argument's name. +When a function signature spans multiple lines, commas between parameters are optional: + +```v nofmt +fn greet( + salutation string + name string +) string { + return 'Hey, ${salutation} ${name}!' +} +``` + Just like in Go and C, functions cannot be overloaded. This simplifies the code and improves maintainability and readability. diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index f86b82fd8..c9760147d 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -428,6 +428,12 @@ fn (p &Parser) is_start_of_call_arg_expr() bool { } } +@[inline] +fn (p &Parser) can_omit_comma_between_fn_params() bool { + return p.tok.line_nr > p.prev_tok.line_nr + && p.tok.kind in [.name, .key_mut, .key_shared, .key_atomic, .ellipsis] +} + fn (mut p Parser) call_args() []ast.CallArg { prev_inside_call_args := p.inside_call_args p.inside_call_args = true @@ -1579,6 +1585,7 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { if alanguage != .v { p.check_for_impure_v(alanguage, type_pos[i]) } + can_omit_comma := p.can_omit_comma_between_fn_params() params << ast.Param{ pos: param_pos[i] name: para_name @@ -1591,8 +1598,8 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { on_newline: prev_param_newline != param_pos[i].line_nr } prev_param_newline = param_pos[i].line_nr - // if typ.typ.kind == .variadic && p.tok.kind == .comma { - if is_variadic && p.tok.kind == .comma && p.peek_tok.kind != .rpar { + if is_variadic && ((p.tok.kind == .comma && p.peek_tok.kind != .rpar) + || can_omit_comma) { p.error_with_pos('cannot use ...(variadic) with non-final parameter ${para_name}', param_pos[i]) return []ast.Param{}, false, false, false @@ -1602,7 +1609,9 @@ fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { p.error_with_pos('expecting `)`', p.prev_tok.pos()) return []ast.Param{}, false, false, false } - if p.tok.kind != .rpar { + if p.tok.kind == .comma { + p.next() + } else if p.tok.kind != .rpar && !p.can_omit_comma_between_fn_params() { p.check(.comma) } } diff --git a/vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.out b/vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.out new file mode 100644 index 000000000..1bff15017 --- /dev/null +++ b/vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.out @@ -0,0 +1,3 @@ +vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.vv:1:28: error: unexpected name `name`, expecting `,` + 1 | fn greet(salutation string name string) {} + | ~~~~ diff --git a/vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.vv b/vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.vv new file mode 100644 index 000000000..c8b237d3c --- /dev/null +++ b/vlib/v/parser/tests/fn_decl_params_missing_comma_single_line_err.vv @@ -0,0 +1 @@ +fn greet(salutation string name string) {} diff --git a/vlib/v/tests/fns/multiline_fn_signature_omitted_comma_test.v b/vlib/v/tests/fns/multiline_fn_signature_omitted_comma_test.v new file mode 100644 index 000000000..e3115313a --- /dev/null +++ b/vlib/v/tests/fns/multiline_fn_signature_omitted_comma_test.v @@ -0,0 +1,12 @@ +import os + +fn test_multiline_fn_signature_can_omit_commas() { + source_path := os.join_path(os.vtmp_dir(), 'issue_22021_multiline_fn_signature_${os.getpid()}.v') + source := "fn multiline_greet(\n\tsalutation string\n\tname string\n) string {\n\treturn 'Hey, ' + salutation + ' ' + name + '!'\n}\n\nfn main() {\n\tassert multiline_greet(\n\t\t'Mr.'\n\t\t'Joe'\n\t) == 'Hey, Mr. Joe!'\n\tgreeter := fn (salutation string\n\t\tname string) string {\n\t\treturn 'Hello, ' + salutation + ' ' + name + '!'\n\t}\n\tassert greeter('Ms.', 'Jane') == 'Hello, Ms. Jane!'\n}\n" + os.write_file(source_path, source)! + defer { + os.rm(source_path) or {} + } + res := os.execute('${os.quoted_path(@VEXE)} run ${os.quoted_path(source_path)}') + assert res.exit_code == 0, res.output +} -- 2.39.5