[PHP 8.6] Partial Function Application (PFA) - #229
Conversation
PHP 8.6 introduces Partial Function Application (PFA): `foo(1, ?)` turns a call into a Closure. nikic/php-parser 5.8.0 (latest, 2026-06-04) has no grammar for the `?` argument placeholder, so such sources cannot be analyzed at all. Pin down the interim contract of issue #224: - `tests/Stub/FileWithPartialFunctionApplication86.php` - PFA placeholders in function, method and closure bodies. Valid PHP 8.6, invalid PHP 8.5, therefore never included and deliberately kept out of `AbstractTestCase::getFilesToAnalyze()`, mirroring how `FileWithFunctionsFcc.php` is handled. - `tests/Stub/FileWithFccInBodies.php` - the PFA-adjacent syntax that is already parseable: `foo(...)` inside function-like bodies. - `tests/Php86PartialFunctionApplicationTest.php` - asserts that PFA sources surface a catchable `PhpParser\Error` (the engine does not wrap parser errors) instead of a truncated AST, that a failed parse does not poison the engine cache, that first-class callables in bodies still reflect cleanly with their `VariadicPlaceholder` node intact, and that `NodeExpressionResolver` degrades into `ReflectionException` for placeholder arguments and unhandled node types. Refs #224 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
|
@claude can you have a look here for feature request to nikic/php-parser or create it there? |
|
Checked upstream — a feature request already exists and is actively moving, so I did not create a duplicate:
So the follow-up here is exactly what this PR's body anticipates: once #1159 ships in a PHP-Parser release, bump the Worth subscribing to #1159 for release timing — I could not watch it from this session (repository outside my access scope). Generated by Claude Code |
Refs #224 (part of epic #219)
Why this is interim only
PHP 8.6 Partial Function Application (PFA) lets any call use a
?placeholder for a single open argument, evaluating to aClosure:The feature works on the local PHP 8.6.0beta1 runtime, but
nikic/php-parsercannot parse the?argument placeholder yet, so full reflection support is blocked upstream. This PR only lands the actionable part: coverage that documents current behavior so the follow-up is a visible change of behavior rather than an invisible one.Upstream status (checked 2026-08-25)
nikic/php-parserrelease^5.4, unchanged by this PR)?placeholder support?argument placeholders, or a placeholder-argument node in the CHANGELOG or in any release notes.VariadicPlaceholder, added in 4.13.0 for first-class callables. Its changelog entry states the representation "is intended to be forward-compatible with partial function application, just like the PHP feature itself."Verified locally against php-parser 5.8.0's newest supported grammar:
Sources to watch: nikic/PHP-Parser CHANGELOG and releases.
What this PR adds
tests/Stub/FileWithPartialFunctionApplication86.phpPFA placeholders in a function body, a method body, a closure body, and a static call. It is valid PHP 8.6 but invalid PHP 8.5 (confirmed with
php8.6 -l/php -l), so it:included /required;AbstractTestCase::getFilesToAnalyze(), since every parity data provider parses and includes the files listed there;which is the same treatment
tests/Stub/FileWithFunctionsFcc.phpalready gets for FCC in constant-expression positions. A test asserts the exclusion holds, so a future contributor cannot accidentally wire it into the parity providers.tests/Stub/FileWithFccInBodies.phpThe PFA-adjacent syntax that is parseable today —
foo(...)inside function, method and closure bodies. Ordinary runtime-valid PHP.tests/Php86PartialFunctionApplicationTest.php(16 tests)ReflectionEngine::parseFile()does not wrap parser errors (src/ReflectionEngine.php:280callsself::$parser->parse()directly), so what reaches the caller — throughReflectionEngine::parseFile()and through the publicReflectionFileconstructor — isPhpParser\Error, messageSyntax error, unexpected '?'. Both entry points are asserted, so the failure is loud and catchable rather than a truncated or corrupted AST.newexpressions.ReflectionEngine::$parsedFiles, so the same file re-parses cleanly once the grammar supports it.Return_ -> FuncCallwithisFirstClassCallable() === trueand aVariadicPlaceholderfirst argument.NodeExpressionResolverthrowsReflectionException(never a fatal) for a non-Argplaceholder argument in a function call, for the same in a constructor call, and for a node type it has no handler for — the three shapes a future PFA node would first hit.What full scope still needs (once upstream lands)
composer.jsonfromnikic/php-parser: ^5.4to the first release that parses PFA placeholders.ReflectionEngine::init()(src/ReflectionEngine.php:64) defaults toParserFactory::createForNewestSupportedVersion(), so the new grammar is picked up automatically even on a PHP 8.5 host. This should be asserted rather than assumed.tests/Php86PartialFunctionApplicationTest.phpfrom "raises a parse error" to "reflects cleanly" —FileWithPartialFunctionApplication86.phpalready carries the fixtures (function body, method body, closure body, static call) and its header documents the intended transition.Closure, or keeps throwingReflectionExceptionthe way user-defined FCC currently does (src/Resolver/NodeExpressionResolver.php:246). The graceful-degradation tests here pin the current fallback either way.included on a PHP 8.5 runtime, so it likely stays out ofgetFilesToAnalyze()and keeps its dedicated test.Local validation
vendor/bin/phpunit(PHP 8.5.9)php8.6 vendor/bin/phpunit(PHP 8.6.0beta1)vendor/bin/phpstan analyse src --no-progress(level 10)[OK] No errorsBaseline on
masterwas 13743 tests; the delta of +16 is exactly the new test class. Skipped/incomplete counts are unchanged from baseline. Nosrc/file and no dependency constraint is touched by this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn