Skip to content

Commit 9982a81

Browse files
committed
Fix edge case with lambda ending with match at end of argument
Godot can't parse the argument delimiter in this case, seems it tries to parse the comma (marking end of argument) as part of the match statement Fix 301
1 parent 9b1fff0 commit 9982a81

5 files changed

Lines changed: 83 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This file documents the changes made to the formatter with each release.
88

99
- You can not exclude files and folders using the `--exclude/-x` flag or `gdscript_formatter_exclude` in your editorconfig files (#299)
1010

11+
### Fixed
12+
13+
- Fixed edge case when a lambda is at the end of an argument list and ends with a match statement, causing the formatter to insert a trailing comma (#301)
14+
1115
## Release 0.23.0 (2026-07-24)
1216

1317
### Added

src/formatter.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,46 @@ fn process_container(
16371637
false
16381638
}
16391639

1640+
/// Returns true if the lambda ends with a match statement.
1641+
fn lambda_ends_with_match(lambda: tree_sitter::Node) -> bool {
1642+
let mut body = None;
1643+
let mut child_index = 0;
1644+
while child_index < lambda.child_count() {
1645+
if let Some(child) = lambda.child(child_index as u32)
1646+
&& GDScriptNodeKind::get_kind_from_ast_node(child) == GDScriptNodeKind::Body
1647+
{
1648+
body = Some(child);
1649+
break;
1650+
}
1651+
child_index += 1;
1652+
}
1653+
let Some(body) = body else {
1654+
return false;
1655+
};
1656+
let mut last_statement = None;
1657+
child_index = 0;
1658+
while child_index < body.named_child_count() {
1659+
last_statement = body.named_child(child_index as u32);
1660+
child_index += 1;
1661+
}
1662+
last_statement.is_some_and(|statement| {
1663+
if GDScriptNodeKind::get_kind_from_ast_node(statement) == GDScriptNodeKind::MatchBody {
1664+
return true;
1665+
}
1666+
let mut child_index = 0;
1667+
while child_index < statement.child_count() {
1668+
if let Some(child) = statement.child(child_index as u32)
1669+
&& GDScriptNodeKind::get_kind_from_ast_node(child)
1670+
== GDScriptNodeKind::MatchBody
1671+
{
1672+
return true;
1673+
}
1674+
child_index += 1;
1675+
}
1676+
false
1677+
})
1678+
}
1679+
16401680
// A container's children are: [open_delimiter, elements_and_commas...,
16411681
// close_delimiter]. The minimal single-element form is 3 children
16421682
// ([open, element, close]); child_count >= 4 means there is either a
@@ -1664,11 +1704,18 @@ fn process_container(
16641704
&& node.child(1).is_some_and(|child| {
16651705
GDScriptNodeKind::get_kind_from_ast_node(child) == GDScriptNodeKind::Lambda
16661706
});
1707+
// This works around an edge case of the GDScript parser in Godot failing to
1708+
// parse an unparenthesized lambda argument if its final statement is match
1709+
// and the formatter adds a comma to the last match branch.
1710+
let is_unparenthesized_lambda_with_match = is_single_lambda
1711+
&& node_kind == GDScriptNodeKind::Arguments
1712+
&& node.child(1).is_some_and(lambda_ends_with_match);
16671713
let needs_lambda_trailing_comma = is_single_lambda
16681714
&& matches!(
16691715
node_kind,
16701716
GDScriptNodeKind::Array | GDScriptNodeKind::Arguments
1671-
);
1717+
)
1718+
&& !is_unparenthesized_lambda_with_match;
16721719
if (contains_more_than_one_element || needs_lambda_trailing_comma)
16731720
&& !trailing_comma_handled
16741721
&& !last_was_comment

src/main.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,10 @@ fn find_gdscript_files(
461461
}
462462
}
463463
} else if let Some(extension) = current_path.extension() {
464-
if extension == "gd" {
465-
if !gdscript_formatter::editorconfig::is_excluded_by_editorconfig(&current_path) {
466-
gdscript_file_paths.push(current_path);
467-
}
464+
if extension == "gd"
465+
&& !gdscript_formatter::editorconfig::is_excluded_by_editorconfig(&current_path)
466+
{
467+
gdscript_file_paths.push(current_path);
468468
}
469469
}
470470
}

tests/expected/lambda.gd

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,17 @@ func issue_287() -> void:
5656
),
5757
self,
5858
)
59+
60+
61+
# Godot doesn't support a trailing comma in a lambda ending an argument list
62+
# when the lambda ends with a match statement. We make sure the formatter
63+
# doesn't insert a trailing comma in this case.
64+
func issue_301() -> void:
65+
[0, 2, 3].filter(
66+
func(value: int):
67+
match value:
68+
0:
69+
return false
70+
_:
71+
return true
72+
)

tests/input/lambda.gd

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,16 @@ func issue_287() -> void:
5050
),
5151
self,
5252
)
53+
54+
55+
# Godot doesn't support a trailing comma in a lambda ending an argument list
56+
# when the lambda ends with a match statement. We make sure the formatter
57+
# doesn't insert a trailing comma in this case.
58+
func issue_301() -> void:
59+
[0, 2, 3].filter(func(value: int):
60+
match value:
61+
0:
62+
return false
63+
_:
64+
return true
65+
)

0 commit comments

Comments
 (0)