Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Update nikic/PHP-Parser to 5.9.0
Regenerated the native parser engine's reduce actions for the new
argument-list grammar (ArgPlaceholder, "..." in any argument position)
and ported the tightened \u{...} escape limit (0x10FFFF) to the native
String_::parseEscapeSequences(). Corpus fixtures cover both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ZXwmsVRBa9PE9htEXAnwo
  • Loading branch information
ondrejmirtes and claude committed Sep 21, 2026
commit 08677533c909fa44187bb9e9cff47580e867486f
2 changes: 1 addition & 1 deletion .github/workflows/phar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ permissions:
env:
# The php-parser version whose grammar tables and semantic actions the
# native parser engine (turbo-ext/src/parser/) was ported against.
SUPPORTED_PHP_PARSER_VERSION: "v5.8.0"
SUPPORTED_PHP_PARSER_VERSION: "v5.9.0"
# Composer for the turbo container legs, pinned by checksum. The distro
# composers are unusable there: jammy's 2.2.6 fatals under PHP 8.5, and
# Alpine's /usr/bin/composer is a shell wrapper hardwired to the
Expand Down
12 changes: 6 additions & 6 deletions compiler/box/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions compiler/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"nette/php-generator": "3.6.9",
"nette/schema": "^1.2.2",
"nette/utils": "^3.2.5",
"nikic/php-parser": "^5.8.0",
"nikic/php-parser": "^5.9.0",
"ondram/ci-detector": "^4.0",
"ondrejmirtes/better-reflection": "6.73.0.4",
"ondrejmirtes/composer-attribute-collector": "^1.2.0",
Expand Down
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion turbo-ext/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ differential coverage (in `$covered` there, or via its dedicated script).

## The native parser engine

`src/parser/` reimplements php-parser 5.8.0's LALR engine and node building
`src/parser/` reimplements php-parser 5.9.0's LALR engine and node building
(`PhpParser\ParserAbstract` + the generated `Parser\Php8`), shadowed through
the `PHPStan\Parser\ParserRunner` seam. The parsing tables are read at run
time from the first `Php8` parser object seen — they are generated data, so
Expand Down
2 changes: 1 addition & 1 deletion turbo-ext/src/parser/ParserEngine.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* phpstanturbo::ParserEngine — native port of php-parser 5.8.0's LALR engine
* phpstanturbo::ParserEngine — native port of php-parser 5.9.0's LALR engine
* and node building (vendor/nikic/php-parser: ParserAbstract.php +
* Parser/Php8.php), structured to mirror ParserAbstract method for method:
* doParse(), getAttributes(), emitError(), the semantic-action helpers
Expand Down
2 changes: 1 addition & 1 deletion turbo-ext/src/parser/ParserRunner.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* PHPStanTurbo\ParserRunner — native LALR engine for php-parser 5.8.0.
* PHPStanTurbo\ParserRunner — native LALR engine for php-parser 5.9.0.
* phpstanturbo::ParserEngine mirrors PhpParser\ParserAbstract::parse()/doParse()
* exactly; the generated reduce actions live in ParserRunnerActions*.cpp, the
* ported semantic helpers in ParserRunnerHelpers.cpp. PHP twin:
Expand Down
30 changes: 15 additions & 15 deletions turbo-ext/src/parser/ParserRunnerActions2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,34 +292,35 @@ bool ParserEngine::reduceRange2(int rule, int stackPos)
}
/* rule 330 */
case 330: {
semValue = arrayOf(PN_SEM(3, 2));
semValue = zv::Arr::empty();
return true;
}
/* rule 331 */
case 331: {
semValue = zv::Arr::empty();
semValue = PN_SEM(4, 2);
return true;
}
/* rule 332 */
case 332: {
semValue = PN_SEM(4, 2);
zv::Val t1 = newNode("Node\\Arg", PN_ATTRS(4, 1, 4), PN_SEM(4, 2), zv::Val::boolean(false), zv::Val::boolean(false), nullptr /* ctor default for omitted $name */);
semValue = arrayOf(t1);
return true;
}
/* rule 333 */
case 333: {
zv::Val t1 = newNode("Node\\Arg", PN_ATTRS(4, 1, 4), PN_SEM(4, 2), zv::Val::boolean(false), zv::Val::boolean(false), nullptr /* ctor default for omitted $name */);
semValue = arrayOf(t1);
zv::Val t1 = newNode("Node\\Arg", PN_ATTRS(3, 1, 1), PN_SEM(3, 1), zv::Val::boolean(false), zv::Val::boolean(false), nullptr /* ctor default for omitted $name */);
semValue = arrayOf(t1, PN_SEM(3, 3));
return true;
}
/* rule 334 */
case 334: {
semValue = arrayOf(PN_SEM(3, 2));
semValue = arrayOf(PN_SEM(1, 1));
return true;
}
/* rule 335 */
case 335: {
zv::Val t1 = newNode("Node\\Arg", PN_ATTRS(3, 1, 1), PN_SEM(3, 1), zv::Val::boolean(false), zv::Val::boolean(false), nullptr /* ctor default for omitted $name */);
semValue = arrayOf(t1, PN_SEM(3, 3));
pushOnto(PN_SEM(3, 1), PN_SEM(3, 3));
semValue = PN_SEM(3, 1);
return true;
}
/* rule 336 */
Expand All @@ -335,33 +336,32 @@ bool ParserEngine::reduceRange2(int rule, int stackPos)
}
/* rule 338 */
case 338: {
semValue = newNode("Node\\VariadicPlaceholder", PN_ATTRS(1, 1, 1));
semValue = newNode("Node\\Arg", PN_ATTRS(2, 1, 2), PN_SEM(2, 2), zv::Val::boolean(true), zv::Val::boolean(false), nullptr /* ctor default for omitted $name */);
return true;
}
/* rule 339 */
case 339: {
semValue = arrayOf(PN_SEM(1, 1));
semValue = newNode("Node\\Arg", PN_ATTRS(2, 1, 2), PN_SEM(2, 2), zv::Val::boolean(false), zv::Val::boolean(true), nullptr /* ctor default for omitted $name */);
return true;
}
/* rule 340 */
case 340: {
pushOnto(PN_SEM(3, 1), PN_SEM(3, 3));
semValue = PN_SEM(3, 1);
semValue = newNode("Node\\VariadicPlaceholder", PN_ATTRS(1, 1, 1));
return true;
}
/* rule 341 */
case 341: {
semValue = newNode("Node\\Arg", PN_ATTRS(2, 1, 2), PN_SEM(2, 2), zv::Val::boolean(true), zv::Val::boolean(false), nullptr /* ctor default for omitted $name */);
semValue = newNode("Node\\ArgPlaceholder", PN_ATTRS(1, 1, 1), nullptr);
return true;
}
/* rule 342 */
case 342: {
semValue = newNode("Node\\Arg", PN_ATTRS(2, 1, 2), PN_SEM(2, 2), zv::Val::boolean(false), zv::Val::boolean(true), nullptr /* ctor default for omitted $name */);
semValue = newNode("Node\\Arg", PN_ATTRS(3, 1, 3), PN_SEM(3, 3), zv::Val::boolean(false), zv::Val::boolean(false), PN_SEM(3, 1));
return true;
}
/* rule 343 */
case 343: {
semValue = newNode("Node\\Arg", PN_ATTRS(3, 1, 3), PN_SEM(3, 3), zv::Val::boolean(false), zv::Val::boolean(false), PN_SEM(3, 1));
semValue = newNode("Node\\ArgPlaceholder", PN_ATTRS(3, 1, 3), PN_SEM(3, 1));
return true;
}
/* rule 344 */
Expand Down
8 changes: 4 additions & 4 deletions turbo-ext/src/parser/ParserRunnerHelpers.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* ParserRunnerHelpers.cpp — native ports of php-parser 5.8.0's semantic-action
* ParserRunnerHelpers.cpp — native ports of php-parser 5.9.0's semantic-action
* helper methods (ParserAbstract.php lines 560-1341) plus the Node-side
* fromString builders they rely on (Int_/String_/Float_::fromString,
* Name::prepareName, Modifiers::verify*), as phpstanturbo::ParserEngine
Expand Down Expand Up @@ -305,7 +305,7 @@ static zend_string *stripTrailingNewline(zend_string *s)
return r;
}

/* String_::codePointToUtf8() — caller guarantees num <= 0x1FFFFF */
/* String_::codePointToUtf8() — caller guarantees num <= 0x10FFFF */
static void utf8Append(smart_str *out, zend_ulong num)
{
if (num <= 0x7F) {
Expand Down Expand Up @@ -424,8 +424,8 @@ zend_string *ParserEngine::parseEscapeSequences(zend_string *strIn, bool hasQuot
break;
}
ParsedNum cp = baseToNum(s + i + 3, digits, 16);
/* hexdec overflow → PHP_INT_MAX → codePointToUtf8 throws; > 0x1FFFFF throws */
if (cp.isDouble || cp.lval > 0x1FFFFF) {
/* hexdec overflow → PHP_INT_MAX → codePointToUtf8 throws; > 0x10FFFF throws */
if (cp.isDouble || cp.lval > 0x10FFFF) {
smart_str_free(&out);
zend_string_release(str);
fatalError("Invalid UTF-8 codepoint escape sequence: Codepoint too large", zv::Arr::empty());
Expand Down
39 changes: 39 additions & 0 deletions turbo-ext/tests/parser-fixtures/partial-function-application.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
// php-parser 5.9: "?" argument placeholders (ArgPlaceholder) and "..." in any
// argument position (VariadicPlaceholder) — PHP 8.6 partial function application
$f = strlen(?);
$f = foo(1, ?);
$f = foo(?, ...);
$f = foo(name: ?);
$f = foo(1, ?, name: ?, ...);
$f = foo(?, ?, ?);
$f = foo(...);
$f = foo(..., );
$f = $obj->foo(?);
$f = $obj->foo(1, ...);
$f = A::foo(?);
$f = static::foo(value: ?);
$f = $obj::{'foo'}(?);
$f = $callable(?, 2);
$f = foo(?)(?);
$f = foo(bar(?), ?);

// invalid, but accepted on the parser level
$f = new Foo(?);
$f = new Foo(...);
$f = new class(?) {};
$f = $obj?->foo(?);
$f = $obj?->foo(...);
$f = foo(..., 1);
$f = foo(?,);
$f = exit(?);
$f = die(name: ?);
$f = exit(...);
$f = clone($x, ?);
$f = clone(?);

#[Foo(?)]
function foo() {}

#[Foo(name: ?, ...)]
class Bar {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php
// php-parser 5.9 rejects code points above \u{10FFFF} (5.8 accepted up to \u{1FFFFF})
$a = <<<EOT
\u{1FFFFF}
EOT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
// php-parser 5.9 rejects code points above \u{10FFFF} (5.8 accepted up to \u{1FFFFF})
$a = "\u{110000}";
12 changes: 12 additions & 0 deletions turbo-ext/tests/parser-fixtures/unicode-escape-max-code-point.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
// php-parser 5.9: \u{10FFFF} is the highest accepted code point
$a = "\u{10FFFF}";
$b = "\u{10FFFF} $x";
$c = `\u{10FFFF}$x`;
$d = <<<EOT
\u{10FFFF}
EOT;
$e = <<<EOT
\u{10FFFF} $x
EOT;
$f = "\u{0}\u{7F}\u{80}\u{7FF}\u{800}\u{FFFF}\u{10000}";