Skip to content

Commit 0cd5642

Browse files
Kenoc42f
andauthored
Allow macrocall in function def syntax (#456)
* Allow macrocall in function def syntax Goes with JuliaLang/julia#55040 * Update src/parser.jl Co-authored-by: Claire Foster <aka.c42f@gmail.com> * Test to cover function declaration with `var""` syntax * Make `function (@f(x)) body end` an ambiguity error 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. --------- Co-authored-by: Claire Foster <aka.c42f@gmail.com>
1 parent d2e61f0 commit 0cd5642

File tree

3 files changed

+42
-9
lines changed

3 files changed

+42
-9
lines changed

src/parser.jl

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,14 +2128,15 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
21282128
# * The whole function declaration, in parens
21292129
bump(ps, TRIVIA_FLAG)
21302130
is_empty_tuple = peek(ps, skip_newlines=true) == K")"
2131-
opts = parse_brackets(ps, K")") do _, _, _, _
2131+
opts = parse_brackets(ps, K")") do had_commas, had_splat, num_semis, num_subexprs
21322132
_parsed_call = was_eventually_call(ps)
21332133
_needs_parse_call = peek(ps, 2) KSet"( ."
2134-
_is_anon_func = !_needs_parse_call && !_parsed_call
2134+
_is_anon_func = (!_needs_parse_call && !_parsed_call) || had_commas
21352135
return (needs_parameters = _is_anon_func,
21362136
is_anon_func = _is_anon_func,
21372137
parsed_call = _parsed_call,
2138-
needs_parse_call = _needs_parse_call)
2138+
needs_parse_call = _needs_parse_call,
2139+
maybe_grouping_parens = !had_commas && !had_splat && num_semis == 0 && num_subexprs == 1)
21392140
end
21402141
is_anon_func = opts.is_anon_func
21412142
parsed_call = opts.parsed_call
@@ -2146,7 +2147,14 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
21462147
# function (x,y) end ==> (function (tuple-p x y) (block))
21472148
# function (x=1) end ==> (function (tuple-p (= x 1)) (block))
21482149
# function (;x=1) end ==> (function (tuple-p (parameters (= x 1))) (block))
2150+
# function (f(x),) end ==> (function (tuple-p (call f x)) (block))
2151+
ambiguous_parens = opts.maybe_grouping_parens &&
2152+
peek_behind(ps).kind in KSet"macrocall $"
21492153
emit(ps, mark, K"tuple", PARENS_FLAG)
2154+
if ambiguous_parens
2155+
# Got something like `(@f(x))`. Is it anon `(@f(x),)` or named sig `@f(x)` ??
2156+
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.")
2157+
end
21502158
elseif is_empty_tuple
21512159
# Weird case which is consistent with parse_paren but will be
21522160
# rejected in lowering
@@ -2175,19 +2183,23 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
21752183
end
21762184
end
21772185
end
2178-
if peek(ps, skip_newlines=true) == K"end" && !is_anon_func && !parsed_call
2179-
return false
2180-
end
21812186
if needs_parse_call
21822187
# Parse function argument list
21832188
# function f(x,y) end ==> (function (call f x y) (block))
21842189
# function f{T}() end ==> (function (call (curly f T)) (block))
21852190
# function A.f() end ==> (function (call (. A f)) (block))
21862191
parse_call_chain(ps, mark)
2187-
if peek_behind(ps).kind != K"call"
2192+
sig_kind = peek_behind(ps).kind
2193+
if sig_kind in KSet"Identifier var $" && peek(ps, skip_newlines=true) == K"end"
2194+
# function f end ==> (function f)
2195+
# function $f end ==> (function $f)
2196+
return false
2197+
elseif sig_kind == K"macrocall"
2198+
min_supported_version(v"1.12", ps, mark, "macro call as function signature")
2199+
elseif sig_kind != K"call"
21882200
# function f body end ==> (function (error f) (block body))
21892201
emit(ps, mark, K"error",
2190-
error="Invalid signature in $(is_function ? "function" : "macro") definition")
2202+
error="Invalid signature in $(is_function ? "function" : "macro") definition")
21912203
end
21922204
end
21932205
if is_function && peek(ps) == K"::"
@@ -3511,7 +3523,11 @@ function parse_atom(ps::ParseState, check_identifiers=true)
35113523
# + ==> +
35123524
# .+ ==> (. +)
35133525
# .= ==> (. =)
3514-
bump_dotsplit(ps, emit_dot_node=true)
3526+
if is_dotted(peek_token(ps))
3527+
bump_dotsplit(ps, emit_dot_node=true)
3528+
else
3529+
bump(ps, remap_kind=K"Identifier")
3530+
end
35153531
if check_identifiers && !is_valid_identifier(leading_kind)
35163532
# += ==> (error +=)
35173533
# ? ==> (error ?)

test/diagnostics.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ end
4141
@test diagnostic("\n+ (x, y)") ==
4242
Diagnostic(3, 3, :error, "whitespace not allowed between prefix function call and argument list")
4343

44+
@test diagnostic("function (\$f) body end") ==
45+
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.")
46+
4447
@test diagnostic("A.@B.x", only_first=true) ==
4548
Diagnostic(3, 4, :error, "`@` must appear on first or last macro name component")
4649
@test diagnostic("@M.(x)") ==

test/parser.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,11 @@ tests = [
571571
"function (x,y) end" => "(function (tuple-p x y) (block))"
572572
"function (x=1) end" => "(function (tuple-p (= x 1)) (block))"
573573
"function (;x=1) end" => "(function (tuple-p (parameters (= x 1))) (block))"
574+
"function (f(x),) end" => "(function (tuple-p (call f x)) (block))"
575+
"function (@f(x);) end" => "(function (tuple-p (macrocall-p @f x) (parameters)) (block))"
576+
"function (@f(x)...) end" => "(function (tuple-p (... (macrocall-p @f x))) (block))"
577+
"function (@f(x)) end" => "(function (error (tuple-p (macrocall-p @f x))) (block))"
578+
"function (\$f) end" => "(function (error (tuple-p (\$ f))) (block))"
574579
"function ()(x) end" => "(function (call (tuple-p) x) (block))"
575580
"function (A).f() end" => "(function (call (. (parens A) f)) (block))"
576581
"function (:)() end" => "(function (call (parens :)) (block))"
@@ -589,6 +594,7 @@ tests = [
589594
"function f end" => "(function f)"
590595
"function f \n\n end" => "(function f)"
591596
"function \$f end" => "(function (\$ f))"
597+
"function var\".\" end" => "(function (var .))"
592598
"macro f end" => "(macro f)"
593599
# Function argument list
594600
"function f(x,y) end" => "(function (call f x y) (block))"
@@ -611,6 +617,11 @@ tests = [
611617
# body
612618
"function f() \n a \n b end" => "(function (call f) (block a b))"
613619
"function f() end" => "(function (call f) (block))"
620+
# Macrocall as sig
621+
((v=v"1.12",), "function @callmemacro(a::Int) \n 1 \n end") => "(function (macrocall-p @callmemacro (::-i a Int)) (block 1))"
622+
((v=v"1.12",), "function @callmemacro(a::T, b::T) where T <: Int64\n3\nend") => "(function (where (macrocall-p @callmemacro (::-i a T) (::-i b T)) (<: T Int64)) (block 3))"
623+
((v=v"1.12",), "function @callmemacro(a::Int, b::Int, c::Int)::Float64\n4\nend") => "(function (::-i (macrocall-p @callmemacro (::-i a Int) (::-i b Int) (::-i c Int)) Float64) (block 4))"
624+
((v=v"1.12",), "function @f()() end") => "(function (call (macrocall-p @f)) (block))"
614625
# Errors
615626
"function" => "(function (error (error)) (block (error)) (error-t))"
616627
],
@@ -1000,6 +1011,9 @@ tests = [
10001011
"public[7] = 5" => "(= (ref public 7) 5)"
10011012
"public() = 6" => "(function-= (call public) 6)"
10021013
]),
1014+
JuliaSyntax.parse_stmts => [
1015+
((v = v"1.12",), "@callmemacro(b::Float64) = 2") => "(= (macrocall-p @callmemacro (::-i b Float64)) 2)"
1016+
],
10031017
JuliaSyntax.parse_docstring => [
10041018
""" "notdoc" ] """ => "(string \"notdoc\")"
10051019
""" "notdoc" \n] """ => "(string \"notdoc\")"

0 commit comments

Comments
 (0)