Skip to content

Commit 2cca490

Browse files
committed
Fix multiline function signature parsing
Multiline function signatures with type annotations were incorrectly parsed as tuples instead of calls when newlines appeared between parentheses. For example: ```julia function ( ::A )() end ``` was parsed as `(function (tuple ...) (block))` instead of the correct `(function (call (parens ...)) (block))`, inconsistent with the single-line version `function (::A)() end`. The issue was in parse_function_signature where `peek(ps, 2)` was used to detect if a call pattern follows the closing parenthesis, but this didn't skip newlines. Changed to `peek(ps, 2, skip_newlines=true)` to properly detect the opening parenthesis of the argument list even when separated by whitespace. 🤖 Generated with [Claude Code](https://claude.ai/code) better...
1 parent c500f3d commit 2cca490

File tree

2 files changed

+32
-12
lines changed

2 files changed

+32
-12
lines changed

src/parser.jl

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,17 +1996,30 @@ function parse_function(ps::ParseState)
19961996
else
19971997
if peek(ps) == K"("
19981998
bump(ps, TRIVIA_FLAG)
1999-
# When an initial parenthesis is present, we might either have the
2000-
# function name or the argument list in an anonymous function. We
2001-
# use parse_brackets directly here (rather than dispatching to it
2002-
# via parse_atom) so we can distinguish these two cases by peeking
2003-
# at the following parenthesis, if present.
2004-
#
2005-
# The flisp parser disambiguates this case quite differently,
2006-
# producing less consistent syntax for anonymous functions.
2007-
parse_brackets(ps, K")") do _, _, _, _
1999+
# Handle `function (...)` specially so we can tell whether the parens
2000+
# wrap the function name (possibly with type annotations) or an
2001+
# anonymous-argument list.
2002+
is_empty_tuple = false
2003+
parse_brackets(ps, K")") do had_commas, had_splat, num_semis, num_subexprs
20082004
bump_closing_token(ps, K")")
2009-
is_anon_func = peek(ps) != K"("
2005+
is_empty_tuple = num_subexprs == 0
2006+
# A single-subexpression tuple without commas/splats/semicolons
2007+
# is the ambiguous case we treat as a potential grouped name.
2008+
single_expr_without_tuple = !had_commas && !had_splat &&
2009+
num_semis == 0 && num_subexprs == 1
2010+
next_kind = single_expr_without_tuple ? peek(ps, skip_newlines=true) : peek(ps)
2011+
needs_parse_call = next_kind KSet`( .`
2012+
# Determine whether the parens represent an anonymous-argument list.
2013+
is_anon_func = if had_commas || had_splat || num_semis > 0
2014+
# Commas, splats, or semicolons mean tuple-style arguments.
2015+
true
2016+
elseif is_empty_tuple
2017+
# An empty tuple should still allow a following call.
2018+
false
2019+
else
2020+
# Otherwise treat it as arguments when no call follows.
2021+
!needs_parse_call
2022+
end
20102023
return (needs_parameters = is_anon_func,
20112024
eq_is_kw_before_semi = is_anon_func,
20122025
eq_is_kw_after_semi = is_anon_func)
@@ -2017,6 +2030,9 @@ function parse_function(ps::ParseState)
20172030
# function (x=1) end ==> (function (tuple (kw x 1)) (block))
20182031
# function (;x=1) end ==> (function (tuple (parameters (kw x 1))) (block))
20192032
emit(ps, def_mark, K"tuple")
2033+
elseif is_empty_tuple
2034+
# function ()(x) end ==> (function (call (tuple) x) (block))
2035+
emit(ps, def_mark, K"tuple")
20202036
else
20212037
# function (:)() end ==> (function (call :) (block))
20222038
# function (x::T)() end ==> (function (call (:: x T)) (block))
@@ -3392,4 +3408,3 @@ function parse_atom(ps::ParseState, check_identifiers=true)
33923408
bump(ps, error="invalid syntax atom")
33933409
end
33943410
end
3395-

test/parser.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,12 @@ tests = [
435435
"function (:)() end" => "(function (call :) (block))"
436436
"function (x::T)() end"=> "(function (call (:: x T)) (block))"
437437
"function (::T)() end" => "(function (call (:: T)) (block))"
438+
"function (\n ::T\n )() end" => "(function (call (:: T)) (block))"
439+
"function (\n x::T\n )() end" => "(function (call (:: x T)) (block))"
440+
"function (\n ::T\n )(x, y) end" => "(function (call (:: T) x y) (block))"
441+
"function (\n f\n )() end" => "(function (call f) (block))"
442+
"function ()(\n x\n ) end" => "(function (call (tuple) x) (block))"
443+
"function (\n x::T,\n y::U\n )() end" => "(function (tuple (:: x T) (:: y U)) (block (tuple)))"
438444
"function begin() end" => "(function (call (error begin)) (block))"
439445
"function f() end" => "(function (call f) (block))"
440446
"function type() end" => "(function (call type) (block))"
@@ -805,4 +811,3 @@ end
805811
@test test_parse(JuliaSyntax.parse_eq, "a \u2212= b") == "(-= a b)"
806812
@test test_parse(JuliaSyntax.parse_eq, "a .\u2212= b") == "(.-= a b)"
807813
end
808-

0 commit comments

Comments
 (0)