Skip to content

Commit

Permalink
Make function (@f(x)) body end an ambiguity error
Browse files Browse the repository at this point in the history
This case is ambiguous as it might be either one of the following;
require the user to explicitly disambiguate between them

```
function (@f(x),)
    body
end

function @f(x)
    body
end
```

For the same reasons, `function ($f) body end` is also ambiguous.

Also fix parsing of `function (f(x),) end` to correctly emit a tuple.
  • Loading branch information
c42f committed Aug 2, 2024
1 parent 5913117 commit 6f62399
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 3 deletions.
14 changes: 11 additions & 3 deletions src/parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2110,14 +2110,15 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
# * The whole function declaration, in parens
bump(ps, TRIVIA_FLAG)
is_empty_tuple = peek(ps, skip_newlines=true) == K")"
opts = parse_brackets(ps, K")") do _, _, _, _
opts = parse_brackets(ps, K")") do had_commas, had_splat, num_semis, num_subexprs
_parsed_call = was_eventually_call(ps)
_needs_parse_call = peek(ps, 2) KSet"( ."
_is_anon_func = !_needs_parse_call && !_parsed_call
_is_anon_func = (!_needs_parse_call && !_parsed_call) || had_commas
return (needs_parameters = _is_anon_func,
is_anon_func = _is_anon_func,
parsed_call = _parsed_call,
needs_parse_call = _needs_parse_call)
needs_parse_call = _needs_parse_call,
maybe_grouping_parens = !had_commas && !had_splat && num_semis == 0 && num_subexprs == 1)
end
is_anon_func = opts.is_anon_func
parsed_call = opts.parsed_call
Expand All @@ -2128,7 +2129,14 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
# function (x,y) end ==> (function (tuple-p x y) (block))
# function (x=1) end ==> (function (tuple-p (= x 1)) (block))
# function (;x=1) end ==> (function (tuple-p (parameters (= x 1))) (block))
# function (f(x),) end ==> (function (tuple-p (call f x)) (block))
ambiguous_parens = opts.maybe_grouping_parens &&
peek_behind(ps).kind in KSet"macrocall $"
emit(ps, mark, K"tuple", PARENS_FLAG)
if ambiguous_parens
# Got something like `(@f(x))`. Is it anon `(@f(x),)` or named sig `@f(x)` ??
emit(ps, mark, K"error", error="Ambiguous signature. Add a trailing comma if this is a 1-argument anonymous function; remove parentheses if this is a macro call acting as function signature.")
end
elseif is_empty_tuple
# Weird case which is consistent with parse_paren but will be
# rejected in lowering
Expand Down
3 changes: 3 additions & 0 deletions test/diagnostics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ end
@test diagnostic("\n+ (x, y)") ==
Diagnostic(3, 3, :error, "whitespace not allowed between prefix function call and argument list")

@test diagnostic("function (\$f) body end") ==
Diagnostic(10, 13, :error, "Ambiguous signature. Add a trailing comma if this is a 1-argument anonymous function; remove parentheses if this is a macro call acting as function signature.")

@test diagnostic("[email protected]", only_first=true) ==
Diagnostic(3, 4, :error, "`@` must appear on first or last macro name component")
@test diagnostic("@M.(x)") ==
Expand Down
5 changes: 5 additions & 0 deletions test/parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,11 @@ tests = [
"function (x,y) end" => "(function (tuple-p x y) (block))"
"function (x=1) end" => "(function (tuple-p (= x 1)) (block))"
"function (;x=1) end" => "(function (tuple-p (parameters (= x 1))) (block))"
"function (f(x),) end" => "(function (tuple-p (call f x)) (block))"
"function (@f(x);) end" => "(function (tuple-p (macrocall-p @f x) (parameters)) (block))"
"function (@f(x)...) end" => "(function (tuple-p (... (macrocall-p @f x))) (block))"
"function (@f(x)) end" => "(function (error (tuple-p (macrocall-p @f x))) (block))"
"function (\$f) end" => "(function (error (tuple-p (\$ f))) (block))"
"function ()(x) end" => "(function (call (tuple-p) x) (block))"
"function (A).f() end" => "(function (call (. (parens A) f)) (block))"
"function (:)() end" => "(function (call (parens :)) (block))"
Expand Down

0 comments on commit 6f62399

Please sign in to comment.