Skip to content

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

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

lisachenko wants to merge 5 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)

Full PHP 8.6 Partial Function Application support

PFA lets any call use a ? placeholder for a single open argument, evaluating to a Closure:

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

This PR started as interim behavior-documenting coverage while parsing was blocked upstream. nikic/PHP-Parser#1159 merged on 2026-09-05 and shipped in php-parser 5.9.0 on 2026-09-13 (ArgPlaceholder node), so the follow-up planned in this PR's history is now implemented here:

  • composer.json: nikic/php-parser bumped ^5.4^5.9; rector/rector bumped ^2.0^2.6.7 (see below).
  • Tests flipped from "raises a parse error" to "reflects cleanly": the engine and the public ReflectionFile entry point reflect the PFA stub (functions, classes, signatures) on every host runtime — confirming ParserFactory::createForNewestSupportedVersion() picks the 5.9 grammar up on a PHP 8.5 host with no engine change; every placeholder position parses (trailing, leading, multiple, named f(name: ?), method/static/new contexts) with the expected ArgPlaceholder count; method bodies keep their ArgPlaceholder nodes with isPartialFunctionApplication() true.
  • Resolver contract (decided per the interim plan): a placeholder argument in a constant-expression position degrades into ReflectionException — the same contract user-defined first-class callables already have. The message is generalized ("Cannot statically resolve a placeholder argument in a function/constructor call") since the existing instanceof Arg guard covers ArgPlaceholder and VariadicPlaceholder alike; dedicated tests pin all three shapes.
  • Cache hygiene re-pinned with a genuinely broken source (PFA no longer fails to parse).
  • Guarded PHP 8.6 test includes the stub, compares parsed vs native signatures, and executes a partial application (Closure returned, ('a b') → 'a-b').
  • FCC regression guard kept; note that php-parser 5.9 deliberately reports a first-class callable as a special case of PFA (isPartialFunctionApplication() === true for foo(...)), which the test documents.
  • The stub stays out of AbstractTestCase::getFilesToAnalyze() — it now parses everywhere but is still a compile error when included on PHP 8.5.

Why the rector floor bump is needed

Rector's package bootstrap.php preloads its bundled, unprefixed php-parser copy whenever PHPUnit ≥ 12 is running — which the PHPUnit 13 bump (#236) activated, silently shadowing the project's own php-parser in every phpunit run. Rector 2.6.7 (2026-09-13) is the first release bundling php-parser ^5.9; with an older rector, the lowest-dependencies CI job would run the suite against a shadowed pre-PFA parser and fail. Worth keeping in mind generally: under PHPUnit ≥ 12, the test suite exercises rector's bundled php-parser build, not the locked nikic/php-parser package.

Local validation

Check Result
vendor/bin/phpunit (PHP 8.5.9, php-parser 5.9.0, PHPUnit 13.3.4) OK — 13810 tests, 16139 assertions, 135 skipped, 2 incomplete, 0 failures
php8.6 vendor/bin/phpunit (PHP 8.6.0beta1) OK — 15041 tests, 30385 assertions, 142 skipped, 2 incomplete, 0 failures
PFA test class on 8.6 OK — 20 tests, 63 assertions, 0 skipped
vendor/bin/phpstan analyse src --no-progress (level 10) [OK] No errors

🤖 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

Copy link
Copy Markdown
Member Author

Upstream update: nikic/PHP-Parser#1159 (partial function application support, ArgPlaceholder node) was merged into master on 2026-09-05 (commit 23c8e36, all checks green), closing upstream #1158.

No tagged release contains it yet — latest is still 5.8.0. My recurring check keeps watching; as soon as a release ships PFA parsing, I'll do the full follow-up on this branch (constraint bump, flip the parse-error assertions to positive reflection tests, ArgPlaceholder handling in NodeExpressionResolver, full gate on 8.5 + 8.6) and report here. Merge stays with @lisachenko.


Generated by Claude Code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
…r ^5.9

nikic/php-parser 5.9.0 parses the PFA `?` placeholder into ArgPlaceholder
nodes, so PFA-containing sources now reflect cleanly on every supported
host runtime:

- bump the nikic/php-parser constraint to ^5.9
- flip the interim parse-error tests to positive reflection assertions:
  engine and ReflectionFile reflect the PFA stub, every placeholder
  position (incl. named placeholders) parses, method bodies keep their
  ArgPlaceholder nodes and isPartialFunctionApplication() flag
- keep the resolver contract: a placeholder argument in a
  constant-expression position degrades into ReflectionException (message
  generalized to cover ArgPlaceholder and VariadicPlaceholder alike)
- pin cache hygiene with a genuinely broken source now that PFA parses
- guarded PHP 8.6 test executes the stub and checks native behavior

Note: a first-class callable reports isPartialFunctionApplication() true
in php-parser 5.9 by design; the FCC regression test documents that.

Refs #224

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn
… 5.9

Rector's bootstrap preloads its own unprefixed php-parser copy whenever
PHPUnit >= 12 is running, shadowing the project's php-parser. Rector
2.6.7 is the first release bundling php-parser ^5.9, so older versions
would break the PFA tests in the lowest-dependencies CI job.

Refs #224

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sy8BcM8ivUEpu8uVm7wADn

Copy link
Copy Markdown
Member Author

php-parser 5.9.0 shipped PFA support on 2026-09-13, so this PR is now the full implementation — the "On Hold" reason is resolved. Pushed in two commits (33d0d4c, b2ef543), PR body updated to match:

  • nikic/php-parser ^5.4^5.9; parse-error assertions flipped to positive reflection (all placeholder positions incl. named f(name: ?); ArgPlaceholder in method bodies; isPartialFunctionApplication()), no engine change needed — createForNewestSupportedVersion() picks the grammar up on a PHP 8.5 host, as asserted.
  • Resolver contract: placeholder arguments in constant-expression positions throw ReflectionException (same as user-defined FCC), message generalized for both placeholder node kinds.
  • Guarded 8.6 test executes the stub natively (partial application returns a working Closure).
  • One finding worth your attention: since the PHPUnit 13 bump (build(deps-dev): update phpunit/phpunit requirement from ^11.0.7 to ^13.3.1 #236), Rector's bootstrap preloads its bundled unprefixed php-parser into every phpunit run, shadowing the project's own copy. Rector 2.6.7 is the first release bundling php-parser ^5.9, so I also raised rector/rector to ^2.6.7 — otherwise the lowest-deps CI job would test against a shadowed pre-PFA parser and fail.

Local gate green: 13,810 tests on 8.5 / 15,041 on 8.6 (PFA class 20/20 on 8.6, partial application evaluates to 'a-b'), PHPStan level 10 clean. I've stopped the recurring upstream check; merge decision is yours as always.


Generated by Claude Code

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