Skip to content

Commit aa25920

Browse files
committed
Improve error message for expression after keyword
For maps and lists: $ elixir -e "%{foo: :bar, :baz => :bat}" ** (SyntaxError) nofile:1: unexpected expression after keyword list. Keyword lists must always come last in lists and maps. Therefore, this is not allowed: [some: :value, :another] %{some: :value, another => value} Instead, reorder it to be the last entry: [:another, some: :value] %{another => value, some: :value} Syntax error after: ',' (elixir 1.13.0-dev) lib/code.ex:655: Code.eval_string_with_error_handling/3 For calls: $ elixir -e "call foo: :bar, :baz" ** (SyntaxError) nofile:1: unexpected expression after keyword list. Keyword lists must always come as the last argument. Therefore, this is not allowed: function_call(1, some: :option, 2) Instead, wrap the keyword in brackets: function_call(1, [some: :option], 2) Syntax error after: ',' (elixir 1.13.0-dev) lib/code.ex:655: Code.eval_string_with_error_handling/3 Closes #10973.
1 parent 64dcd3f commit aa25920

File tree

2 files changed

+61
-19
lines changed

2 files changed

+61
-19
lines changed

lib/elixir/src/elixir_parser.yrl

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Nonterminals
2020
call_args_no_parens_comma_expr call_args_no_parens_all call_args_no_parens_many
2121
call_args_no_parens_many_strict
2222
stab stab_eoe stab_expr stab_op_eol_and_expr stab_parens_many
23-
kw_eol kw_base kw call_args_no_parens_kw_expr call_args_no_parens_kw
23+
kw_eol kw_base kw_data kw_call call_args_no_parens_kw_expr call_args_no_parens_kw
2424
dot_op dot_alias dot_bracket_identifier dot_call_identifier
2525
dot_identifier dot_op_identifier dot_do_identifier dot_paren_identifier
2626
do_block fn_eoe do_eoe end_eoe block_eoe block_item block_list
@@ -285,7 +285,7 @@ number -> char : handle_number(?exprs('$1'), '$1', number_value('$1')).
285285
parens_call -> dot_call_identifier call_args_parens : build_parens('$1', '$2', {[], []}).
286286
parens_call -> dot_call_identifier call_args_parens call_args_parens : build_nested_parens('$1', '$2', '$3', {[], []}).
287287

288-
bracket_arg -> open_bracket kw close_bracket : build_access_arg('$1', '$2', '$3').
288+
bracket_arg -> open_bracket kw_data close_bracket : build_access_arg('$1', '$2', '$3').
289289
bracket_arg -> open_bracket container_expr close_bracket : build_access_arg('$1', '$2', '$3').
290290
bracket_arg -> open_bracket container_expr ',' close_bracket : build_access_arg('$1', '$2', '$4').
291291

@@ -513,7 +513,7 @@ container_args_base -> container_args_base ',' container_expr : ['$3' | '$1'].
513513

514514
container_args -> container_args_base : reverse('$1').
515515
container_args -> container_args_base ',' : reverse('$1').
516-
container_args -> container_args_base ',' kw : reverse(['$3' | '$1']).
516+
container_args -> container_args_base ',' kw_call : reverse(['$3' | '$1']).
517517

518518
% Function calls with parentheses
519519

@@ -528,16 +528,16 @@ call_args_parens -> open_paren ')' :
528528
{newlines_pair('$1', '$2'), []}.
529529
call_args_parens -> open_paren no_parens_expr close_paren :
530530
{newlines_pair('$1', '$3'), ['$2']}.
531-
call_args_parens -> open_paren kw_base close_paren :
532-
{newlines_pair('$1', '$3'), [reverse('$2')]}.
533-
call_args_parens -> open_paren kw_base ',' close_paren :
534-
warn_trailing_comma('$3'), {newlines_pair('$1', '$4'), [reverse('$2')]}.
531+
call_args_parens -> open_paren kw_call close_paren :
532+
{newlines_pair('$1', '$3'), ['$2']}.
533+
call_args_parens -> open_paren kw_call ',' close_paren :
534+
warn_trailing_comma('$3'), {newlines_pair('$1', '$4'), ['$2']}.
535535
call_args_parens -> open_paren call_args_parens_base close_paren :
536536
{newlines_pair('$1', '$3'), reverse('$2')}.
537-
call_args_parens -> open_paren call_args_parens_base ',' kw_base close_paren :
538-
{newlines_pair('$1', '$5'), reverse([reverse('$4') | '$2'])}.
539-
call_args_parens -> open_paren call_args_parens_base ',' kw_base ',' close_paren :
540-
warn_trailing_comma('$5'), {newlines_pair('$1', '$6'), reverse([reverse('$4') | '$2'])}.
537+
call_args_parens -> open_paren call_args_parens_base ',' kw_call close_paren :
538+
{newlines_pair('$1', '$5'), reverse(['$4' | '$2'])}.
539+
call_args_parens -> open_paren call_args_parens_base ',' kw_call ',' close_paren :
540+
warn_trailing_comma('$5'), {newlines_pair('$1', '$6'), reverse(['$4' | '$2'])}.
541541

542542
% KV
543543

@@ -551,21 +551,27 @@ kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false, [{format, ke
551551
kw_base -> kw_eol container_expr : [{'$1', '$2'}].
552552
kw_base -> kw_base ',' kw_eol container_expr : [{'$3', '$4'} | '$1'].
553553

554-
kw -> kw_base : reverse('$1').
555-
kw -> kw_base ',' : reverse('$1').
554+
kw_call -> kw_base : reverse('$1').
555+
kw_call -> kw_base ',' : reverse('$1').
556+
kw_call -> kw_base ',' matched_expr : error_bad_keyword_call_follow_up('$2').
557+
558+
kw_data -> kw_base : reverse('$1').
559+
kw_data -> kw_base ',' : reverse('$1').
560+
kw_data -> kw_base ',' matched_expr : error_bad_keyword_data_follow_up('$2').
556561

557562
call_args_no_parens_kw_expr -> kw_eol matched_expr : {'$1', '$2'}.
558563
call_args_no_parens_kw_expr -> kw_eol no_parens_expr : {'$1', '$2'}.
559564

560565
call_args_no_parens_kw -> call_args_no_parens_kw_expr : ['$1'].
561566
call_args_no_parens_kw -> call_args_no_parens_kw_expr ',' call_args_no_parens_kw : ['$1' | '$3'].
567+
call_args_no_parens_kw -> call_args_no_parens_kw_expr ',' matched_expr : error_bad_keyword_call_follow_up('$2').
562568

563569
% Lists
564570

565-
list_args -> kw : '$1'.
571+
list_args -> kw_data : '$1'.
566572
list_args -> container_args_base : reverse('$1').
567573
list_args -> container_args_base ',' : reverse('$1').
568-
list_args -> container_args_base ',' kw : reverse('$1', '$3').
574+
list_args -> container_args_base ',' kw_data : reverse('$1', '$3').
569575

570576
list -> open_bracket ']' : build_list('$1', [], '$2').
571577
list -> open_bracket list_args close_bracket : build_list('$1', '$2', '$3').
@@ -595,8 +601,8 @@ assoc_expr -> parens_call : '$1'.
595601
assoc_update -> matched_expr pipe_op_eol assoc_expr : {'$2', '$1', ['$3']}.
596602
assoc_update -> unmatched_expr pipe_op_eol assoc_expr : {'$2', '$1', ['$3']}.
597603

598-
assoc_update_kw -> matched_expr pipe_op_eol kw : {'$2', '$1', '$3'}.
599-
assoc_update_kw -> unmatched_expr pipe_op_eol kw : {'$2', '$1', '$3'}.
604+
assoc_update_kw -> matched_expr pipe_op_eol kw_data : {'$2', '$1', '$3'}.
605+
assoc_update_kw -> unmatched_expr pipe_op_eol kw_data : {'$2', '$1', '$3'}.
600606

601607
assoc_base -> assoc_expr : ['$1'].
602608
assoc_base -> assoc_base ',' assoc_expr : ['$3' | '$1'].
@@ -607,9 +613,9 @@ assoc -> assoc_base ',' : reverse('$1').
607613
map_op -> '%{}' : '$1'.
608614
map_op -> '%{}' eol : '$1'.
609615

610-
map_close -> kw close_curly : {'$1', '$2'}.
616+
map_close -> kw_data close_curly : {'$1', '$2'}.
611617
map_close -> assoc close_curly : {'$1', '$2'}.
612-
map_close -> assoc_base ',' kw close_curly : {reverse('$1', '$3'), '$4'}.
618+
map_close -> assoc_base ',' kw_data close_curly : {reverse('$1', '$3'), '$4'}.
613619

614620
map_args -> open_curly '}' : build_map('$1', [], '$2').
615621
map_args -> open_curly map_close : build_map('$1', element(1, '$2'), element(2, '$2')).
@@ -1053,6 +1059,24 @@ error_bad_atom(Token) ->
10531059
"If the '.' was meant to be part of the atom's name, "
10541060
"the atom name must be quoted. Syntax error before: ", "'.'").
10551061

1062+
error_bad_keyword_call_follow_up(Token) ->
1063+
return_error(meta_from_token(Token),
1064+
"unexpected expression after keyword list. Keyword lists must always come as the last argument. Therefore, this is not allowed:\n\n"
1065+
" function_call(1, some: :option, 2)\n\n"
1066+
"Instead, wrap the keyword in brackets:\n\n"
1067+
" function_call(1, [some: :option], 2)\n\n"
1068+
"Syntax error after: ", "','").
1069+
1070+
error_bad_keyword_data_follow_up(Token) ->
1071+
return_error(meta_from_token(Token),
1072+
"unexpected expression after keyword list. Keyword lists must always come last in lists and maps. Therefore, this is not allowed:\n\n"
1073+
" [some: :value, :another]\n"
1074+
" %{some: :value, another => value}\n\n"
1075+
"Instead, reorder it to be the last entry:\n\n"
1076+
" [:another, some: :value]\n"
1077+
" %{another => value, some: :value}\n\n"
1078+
"Syntax error after: ", "','").
1079+
10561080
error_no_parens_strict(Token) ->
10571081
return_error(meta_from_token(Token), "unexpected parentheses. If you are making a "
10581082
"function call, do not insert spaces between the function name and the "

lib/elixir/test/elixir/kernel/errors_test.exs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,24 @@ defmodule Kernel.ErrorsTest do
211211
'foo = 1; %{put_in(foo.bar.baz, nil), foo}'
212212
end
213213

214+
test "expression after keyword lists" do
215+
assert_eval_raise SyntaxError,
216+
~r"unexpected expression after keyword list",
217+
'call foo: 1, :bar'
218+
219+
assert_eval_raise SyntaxError,
220+
~r"unexpected expression after keyword list",
221+
'call(foo: 1, :bar)'
222+
223+
assert_eval_raise SyntaxError,
224+
~r"unexpected expression after keyword list",
225+
'[foo: 1, :bar]'
226+
227+
assert_eval_raise SyntaxError,
228+
~r"unexpected expression after keyword list",
229+
'%{foo: 1, :bar => :bar}'
230+
end
231+
214232
test "struct fields on defstruct" do
215233
assert_eval_raise ArgumentError, "struct field names must be atoms, got: 1", '''
216234
defmodule Kernel.ErrorsTest.StructFieldsOnDefstruct do

0 commit comments

Comments
 (0)