From 8543f483e2b26b7f7ab92a5ad2b41a9a5864ac3f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Mon, 8 Jul 2024 18:01:18 +0000 Subject: [PATCH 1/4] Allow macrocall in function def syntax Goes with https://github.com/JuliaLang/julia/pull/55040 --- src/parser.jl | 20 ++++++++++++++------ test/parser.jl | 8 ++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/parser.jl b/src/parser.jl index 34666b59..d48cae6b 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -2157,19 +2157,23 @@ function parse_function_signature(ps::ParseState, is_function::Bool) end end end - if peek(ps, skip_newlines=true) == K"end" && !is_anon_func && !parsed_call - return false - end if needs_parse_call # Parse function argument list # function f(x,y) end ==> (function (call f x y) (block)) # function f{T}() end ==> (function (call (curly f T)) (block)) # function A.f() end ==> (function (call (. A f)) (block)) parse_call_chain(ps, mark) - if peek_behind(ps).kind != K"call" + sig_kind = peek_behind(ps).kind + if sig_kind in KSet"Identifier var $" && peek(ps, skip_newlines=true) == K"end" + # function f end ==> (function f) + # function $f end ==> (function $f) + return false + elseif sig_kind == K"macrocall" + min_supported_version(v"1.12", ps, mark, "macrocall function sig") + elseif sig_kind != K"call" # function f body end ==> (function (error f) (block body)) emit(ps, mark, K"error", - error="Invalid signature in $(is_function ? "function" : "macro") definition") + error="Invalid signature in $(is_function ? "function" : "macro") definition") end end if is_function && peek(ps) == K"::" @@ -3494,7 +3498,11 @@ function parse_atom(ps::ParseState, check_identifiers=true) # + ==> + # .+ ==> (. +) # .= ==> (. =) - bump_dotsplit(ps, emit_dot_node=true) + if is_dotted(peek_token(ps)) + bump_dotsplit(ps, emit_dot_node=true) + else + bump(ps, remap_kind=K"Identifier") + end if check_identifiers && !is_valid_identifier(leading_kind) # += ==> (error +=) # ? ==> (error ?) diff --git a/test/parser.jl b/test/parser.jl index 3ac3ed36..35e53ec8 100644 --- a/test/parser.jl +++ b/test/parser.jl @@ -603,6 +603,11 @@ tests = [ # body "function f() \n a \n b end" => "(function (call f) (block a b))" "function f() end" => "(function (call f) (block))" + # Macrocall as sig + ((v=v"1.12",), "function @callmemacro(a::Int) \n 1 \n end") => "(function (macrocall-p @callmemacro (::-i a Int)) (block 1))" + ((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))" + ((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))" + ((v=v"1.12",), "function @f()() end") => "(function (call (macrocall-p @f)) (block))" # Errors "function" => "(function (error (error)) (block (error)) (error-t))" ], @@ -963,6 +968,9 @@ tests = [ "public[7] = 5" => "(= (ref public 7) 5)" "public() = 6" => "(= (call public) 6)" ]), + JuliaSyntax.parse_stmts => [ + ((v = v"1.12",), "@callmemacro(b::Float64) = 2") => "(= (macrocall-p @callmemacro (::-i b Float64)) 2)" + ], JuliaSyntax.parse_docstring => [ """ "notdoc" ] """ => "(string \"notdoc\")" """ "notdoc" \n] """ => "(string \"notdoc\")" From 0dc7e7bd6b41e29610ca970d6e438f988a65a600 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Fri, 26 Jul 2024 03:41:52 +0200 Subject: [PATCH 2/4] Update src/parser.jl Co-authored-by: Claire Foster --- src/parser.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.jl b/src/parser.jl index d48cae6b..6d2bd893 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -2169,7 +2169,7 @@ function parse_function_signature(ps::ParseState, is_function::Bool) # function $f end ==> (function $f) return false elseif sig_kind == K"macrocall" - min_supported_version(v"1.12", ps, mark, "macrocall function sig") + min_supported_version(v"1.12", ps, mark, "macro call as function signature") elseif sig_kind != K"call" # function f body end ==> (function (error f) (block body)) emit(ps, mark, K"error", From 5913117b4977fd10895e2c483b3fb9051b5cdf04 Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Tue, 30 Jul 2024 17:35:11 +1000 Subject: [PATCH 3/4] Test to cover function declaration with `var""` syntax --- test/parser.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/parser.jl b/test/parser.jl index 35e53ec8..80b7ffec 100644 --- a/test/parser.jl +++ b/test/parser.jl @@ -581,6 +581,7 @@ tests = [ "function f end" => "(function f)" "function f \n\n end" => "(function f)" "function \$f end" => "(function (\$ f))" + "function var\".\" end" => "(function (var .))" "macro f end" => "(macro f)" # Function argument list "function f(x,y) end" => "(function (call f x y) (block))" From 6f62399ab3565d7a1942d5d431a98a475b1a8e76 Mon Sep 17 00:00:00 2001 From: Claire Foster Date: Fri, 2 Aug 2024 14:19:48 +1000 Subject: [PATCH 4/4] 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. --- src/parser.jl | 14 +++++++++++--- test/diagnostics.jl | 3 +++ test/parser.jl | 5 +++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/parser.jl b/src/parser.jl index 6d2bd893..831887b2 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -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 @@ -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 diff --git a/test/diagnostics.jl b/test/diagnostics.jl index 1d1f9e5d..97ee25a0 100644 --- a/test/diagnostics.jl +++ b/test/diagnostics.jl @@ -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("A.@B.x", only_first=true) == Diagnostic(3, 4, :error, "`@` must appear on first or last macro name component") @test diagnostic("@M.(x)") == diff --git a/test/parser.jl b/test/parser.jl index 80b7ffec..c5ab1d5e 100644 --- a/test/parser.jl +++ b/test/parser.jl @@ -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))"