Skip to content

fix(parser): accept a trailing comma in a closure's use() list - #2284

Open
phcorp wants to merge 3 commits into
illegalstudio:mainfrom
phcorp:fix/closure-use-trailing-comma
Open

phcorp wants to merge 3 commits into
illegalstudio:mainfrom
phcorp:fix/closure-use-trailing-comma

Conversation

@phcorp

@phcorp phcorp commented Nov 8, 2026 •

Copy link
Copy Markdown

Summary

PHP 8.0+ allows a trailing comma in a closure's use list. Formatters add one whenever the list spans several lines:

$f = function () use (
    $a,
    &$b,
): int { /* … */ };

elephc rejected this with Expected variable in use() capture list: after a ,, the capture loop always expected another variable. The list now also ends when ) follows the comma.

This PR also closes the opposite gap. main accepted an empty use () and compiled it, while PHP rejects it (unexpected token ")", expecting variable). The parser now requires at least one capture. use (,) and use ($a,,) are still rejected.

Found while compiling a Symfony/TUI application, where formatter output uses this form throughout.

Repro

<?php
$a = 100; $b = 0;
$f = function () use (
    $a,
    &$b,
): int { $b = 10; return $a; };
echo $f() + $b, "\n";
$g = function () use ($a,) { return $a; };
echo $g(), "\n";
output
main error: Expected variable in use() capture list
this branch 110 / 100
php 8.5 110 / 100
input php 8.5 main this branch
use (&$a,) ok error ok
static function () use ($a,) ok error ok
use () syntax error accepted Expected variable in use() capture list
use (,) syntax error error Expected variable in use() capture list
use ($a,,) syntax error error Expected variable in use() capture list
fn() use ($a,) => $a syntax error error error (arrow functions are unchanged)

How it works

parse_closure:

*pos += 1;
// A trailing comma closes the list: `use ($a, $b,)` (PHP 8.0+).
if tokens.get(*pos).map(|(token, _)| token) == Some(&Token::RParen) {
    break;
}
…
// PHP's grammar requires at least one capture: `use ()` is a syntax error.
if captures.is_empty() {
    return Err(CompileError::new(span, "Expected variable in use() capture list"));
}

The trailing-comma check runs only after a capture has been read, inside the !captures.is_empty() branch. A leading , therefore still reaches the "expected variable" error. The empty-list check reuses the same diagnostic, so all malformed lists report the same message.

Tests

  • test_parse_closure_use_list_trailing_comma (parser): a one-line and a multi-line list keep both captures and the &$b by-reference flag.
  • test_closure_use_list_trailing_comma (codegen): runs a multi-line by-value plus by-reference list, and a static function () use ($c,). It prints 110|7, matching PHP under ELEPHC_PHP_CHECK=1.
  • test_error_closure_use_list_requires_a_capture and test_error_closure_use_list_double_trailing_comma (errors): use (), use (,) and use ($a,,) are rejected.

Run against main's parser, the codegen test fails at parse time and the use () error test fails because main accepts the list. Runs on this branch:

  • cargo test --test parser_tests closure: 15 passed
  • cargo test --test error_tests closure: 35 passed
  • ELEPHC_PHP_CHECK=1 cargo test --test codegen_tests test_closure_use_list_trailing_comma: 1 passed

Note

CI status: this PR comes from a fork, so the CI, PDO Live Databases and PHP extension host SDK workflows are waiting for maintainer approval (action_required). The focused tests above ran locally on macOS arm64, and the codegen tests were also compared with PHP 8.5.12 (ELEPHC_PHP_CHECK=1). This is a front-end change (parser/checker only) with no target-specific code.

Limits

  • This fixes the AOT parser only. The eval() parser in crates/elephc-magician still rejects the form: eval('$a = 1; $f = function () use ($a,) { return $a; }; return $f();') fails with Parse error: eval() fragment is invalid.

Follow-ups

@github-actions github-actions Bot added area:parser Touches parsing or AST construction. size:xs Very small pull request. type:fix Corrects broken or incompatible behavior. labels Nov 8, 2026
@github-actions github-actions Bot added size:s Small pull request. and removed size:xs Very small pull request. labels Nov 8, 2026
@phcorp
phcorp marked this pull request as ready for review November 8, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:parser Touches parsing or AST construction. size:s Small pull request. type:fix Corrects broken or incompatible behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant