Skip to content

[PHP 8.6] Partial Function Application (PFA) - #229

Draft
lisachenko wants to merge 2 commits into
masterfrom
claude/php86-224-pfa-interim
Draft

[PHP 8.6] Partial Function Application (PFA)#229
lisachenko wants to merge 2 commits into
masterfrom
claude/php86-224-pfa-interim

Conversation

@lisachenko

@lisachenko lisachenko commented Aug 25, 2026

Copy link
Copy Markdown
Member

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 a Closure:

$makeSlug = str_replace(' ', '-', ?);

The feature works on the local PHP 8.6.0beta1 runtime, but nikic/php-parser cannot 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)

Item Value
Latest nikic/php-parser release 5.8.0 (2026-06-04)
Version installed here 5.8.0 (constraint ^5.4, unchanged by this PR)
PFA / ? placeholder support None. No mention of PHP 8.6, partial function application, ? argument placeholders, or a placeholder-argument node in the CHANGELOG or in any release notes.
Closest existing support 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:

$f = str_replace(" ", "-", ?);   -> PhpParser\Error: Syntax error, unexpected '?', expecting ')' on line 1
$g = foo(?, 1);                  -> PhpParser\Error: Syntax error, unexpected '?' on line 1
class A { function m() { return strlen(...); } }  -> parses fine

Sources to watch: nikic/PHP-Parser CHANGELOG and releases.

What this PR adds

tests/Stub/FileWithPartialFunctionApplication86.php

PFA 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:

  • is never included / required;
  • is deliberately not listed in AbstractTestCase::getFilesToAnalyze(), since every parity data provider parses and includes the files listed there;

which is the same treatment tests/Stub/FileWithFunctionsFcc.php already 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.php

The PFA-adjacent syntax that is parseable today — foo(...) inside function, method and closure bodies. Ordinary runtime-valid PHP.

tests/Php86PartialFunctionApplicationTest.php (16 tests)

  • Which exception actually surfaces: ReflectionEngine::parseFile() does not wrap parser errors (src/ReflectionEngine.php:280 calls self::$parser->parse() directly), so what reaches the caller — through ReflectionEngine::parseFile() and through the public ReflectionFile constructor — is PhpParser\Error, message Syntax error, unexpected '?'. Both entry points are asserted, so the failure is loud and catchable rather than a truncated or corrupted AST.
  • Placeholder positions: a data provider covers trailing, leading, and multiple placeholders, plus placeholders inside method bodies, static calls and new expressions.
  • Cache hygiene: a failed parse stores nothing in ReflectionEngine::$parsedFiles, so the same file re-parses cleanly once the grammar supports it.
  • No FCC regression: the FCC-in-bodies stub still reflects — function/method names, parameter counts, required-parameter counts, default values — and the method body AST is asserted to still be Return_ -> FuncCall with isFirstClassCallable() === true and a VariadicPlaceholder first argument.
  • Graceful resolver degradation: NodeExpressionResolver throws ReflectionException (never a fatal) for a non-Arg placeholder 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)

  1. Bump the constraint in composer.json from nikic/php-parser: ^5.4 to the first release that parses PFA placeholders.
  2. No engine change should be required: ReflectionEngine::init() (src/ReflectionEngine.php:64) defaults to ParserFactory::createForNewestSupportedVersion(), so the new grammar is picked up automatically even on a PHP 8.5 host. This should be asserted rather than assumed.
  3. Flip the stub's assertions in tests/Php86PartialFunctionApplicationTest.php from "raises a parse error" to "reflects cleanly" — FileWithPartialFunctionApplication86.php already carries the fixtures (function body, method body, closure body, static call) and its header documents the intended transition.
  4. Decide the resolver contract for PFA nodes: whether a PFA expression in a constant-expression position resolves to a Closure, or keeps throwing ReflectionException the way user-defined FCC currently does (src/Resolver/NodeExpressionResolver.php:246). The graceful-degradation tests here pin the current fallback either way.
  5. Reconsider inclusion in the parity providers once the file is parseable — it still cannot be included on a PHP 8.5 runtime, so it likely stays out of getFilesToAnalyze() and keeps its dedicated test.

Local validation

Check Result
vendor/bin/phpunit (PHP 8.5.9) OK — 13759 tests, 15438 assertions, 133 skipped, 2 incomplete, 0 failures
php8.6 vendor/bin/phpunit (PHP 8.6.0beta1) OK — 13759 tests, 15438 assertions, 133 skipped, 3 incomplete, 0 failures
vendor/bin/phpstan analyse src --no-progress (level 10) [OK] No errors

Baseline on master was 13743 tests; the delta of +16 is exactly the new test class. Skipped/incomplete counts are unchanged from baseline. No src/ file and no dependency constraint is touched by this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn

claude added 2 commits August 25, 2026 10:00
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
@lisachenko

Copy link
Copy Markdown
Member Author

@claude can you have a look here for feature request to nikic/php-parser or create it there?

Copy link
Copy Markdown
Member Author

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 nikic/php-parser constraint, flip this PR's "raises a parse error" assertions to "reflects cleanly", and decide the NodeExpressionResolver contract for ArgPlaceholder in constant-expression positions. The new node names (ArgPlaceholder with a name property) match the graceful-degradation tests already landed here.

Worth subscribing to #1159 for release timing — I could not watch it from this session (repository outside my access scope).


Generated by Claude Code

@lisachenko lisachenko changed the title [PHP 8.6] PFA interim: behavior-documenting coverage while blocked on PHP-Parser [PHP 8.6] Partial Function Application (PFA) Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

2 participants