feat(kernel): R7b PHP walker — php module, tree-sitter-php 0.24.2 bump, php default-routed (#1380)
Fourth and final R7b batch-2 port, checklist-first recipe (docs/design/php-kernel-port-checklist.md). Grammar bump first, validated standalone with the diff ENUMERATED + CLASSIFIED (unlike rust/ruby the php bump is NOT graph-neutral): tree-sitter-php ^0.22 (tree-sitter-wasms, 2023) → v0.24.2, the full HTML-interleaving `php` variant (the walker calls LANGUAGE_PHP, never PHP_ONLY) — crate pinned =0.24.2, wasm built from tag 5b5627f's checked-in php/src/parser.c + scanner.c + shared common/scanner.h (all sha-matched against the crates.io tarball, ABI 14→15). Old-vs-new full-init diffs decompose completely into: (1) the anonymous_class wrapper shape (anon-class nodes/methods re-shape — 2,532 rows), (2) grouped nested-clause skip (absent in the gate repos, fixture-pinned), (3) 32 formerly-erroring files parsing clean (monolog Level.php, symfony Request/Response with 8.4 property hooks), (4) a survey-missed category found at gate time: the 8.4 parenthesis-free `new X()->m()` chaining misparse fix (86 garbage instantiates refs disappear, precision-positive), plus resolution RIPPLE proven mechanically (every remaining ref-table flip pairs 1:1 with a resolved edge on the opposite side; node rows byte-stable outside 1/3/4). Walker (java.rs chassis + the php specifics) preserves bug-for-bug: the visitNode hook (const_declaration at ANY scope → bare `constant` nodes, values never walked; trait-use → implements refs WITH filePath via the ruby port's REF_FLAG_FILE_PATH wire slot), FIRST-namespace whole-file scoping (braced namespaces scope nothing; namespaced files DROP top-level const value-ref targets), the import trio (single/aliased/grouped incl. the nested-clause skip, include/require static-literal-only, `Foo\Bar::Baz` use refs), the call-encoding zoo (DOT-joined scoped calls, `this->prop.m` #1251 encoding, `Cls::factory().m` fluent with inner args dropped, nullsafe `?->` emitting nothing, unsuppressed literal receivers), interface multi-extends first-base-only drop, anon-class methods as file-level functions (top) or vanishing (in-body), property type-hints emitting no field refs, the final-modifier-as-type signature quirk, HOF-gated string callables (skipGate) + array callables, and the `name`-node value-ref reader. Gates: sweeps 0-diff monolog 217/217, laravel-framework 3007/3008, symfony 10726/10737 (13,950 files byte-parity; 12 deferrals = exactly the predicted genuinely-broken fixtures, ≈0–0.1%); full-init dumps byte-identical ×3 (16.1k/354.2k/702.8k lines); kernel-php-parity suite (torture + drupal .module + leading-HTML fixtures, CRLF variants, wire-flag pin, defer) + php grammar-parity row; full suite 2,622 green ×2 under CODEGRAPH_KERNEL_EXPECT=1. DEFAULT_ROUTED += php (13 languages). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
1909931238
commit
a6c62d77df
@@ -0,0 +1,12 @@
|
||||
<html><body>
|
||||
<h1>Leading HTML keeps absolute rows</h1>
|
||||
<?php
|
||||
|
||||
function html_helper(): void
|
||||
{
|
||||
html_call();
|
||||
}
|
||||
?>
|
||||
<p>interleaved text</p>
|
||||
<?= html_echo() ?>
|
||||
</body></html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
const MODULE_MAX = 25;
|
||||
|
||||
/**
|
||||
* @Implements hook_cron().
|
||||
*/
|
||||
function torture_cron() {
|
||||
$v = MODULE_MAX;
|
||||
other_module_call();
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Contracts\Logger;
|
||||
use App\Contracts\Cache as CacheAlias;
|
||||
use Countable;
|
||||
use function App\Helpers\format_id;
|
||||
use const App\Config\MAX_TRIES;
|
||||
use App\Models\{User, Post as PostAlias, Sub\Deep};
|
||||
|
||||
require 'lib/plain.php';
|
||||
require_once('lib/parens.php');
|
||||
include 'lib/inc.php';
|
||||
include_once 'lib/inc_once.php';
|
||||
require __DIR__ . '/dynamic.php';
|
||||
|
||||
const TOP_LEVEL_MAX = 10;
|
||||
|
||||
// non-ASCII before a symbol: café ünïcode line
|
||||
function top_helper(?Logger $log, User|PostAlias $u, Logger&Countable $lc, (Foo&Bar)|Baz $dnf, \App\Models\User $qual, string $s, int ...$rest): UserModel
|
||||
{
|
||||
top_body_call();
|
||||
return new UserModel();
|
||||
}
|
||||
|
||||
/** Doc for Documented. */
|
||||
#[Registry(param: Logger::class)]
|
||||
class Documented extends BaseThing implements HasColor, \JsonSerializable
|
||||
{
|
||||
use SoftDeletes, Notify\Deeper;
|
||||
use ConflictA, ConflictB {
|
||||
ConflictA::greet insteadof ConflictB;
|
||||
ConflictB::greet as protected greetB;
|
||||
}
|
||||
|
||||
const MULTI_A = 1, MULTI_B = 2;
|
||||
final public const int TYPED_MAX = 5;
|
||||
|
||||
public ?Logger $logger, $fallback;
|
||||
private CacheAlias|string $union;
|
||||
protected static iterable $registry;
|
||||
public readonly int $count;
|
||||
var $legacy;
|
||||
final public Foo $finalTyped;
|
||||
|
||||
public function __construct(private Logger $promoted, protected string $name = 'x', ICache $plain = new NullMailer())
|
||||
{
|
||||
}
|
||||
|
||||
/** Doc over attribute. */
|
||||
#[Route('/x')]
|
||||
public function withAttr(): void
|
||||
{
|
||||
}
|
||||
|
||||
public static function make(): static
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function selfRet(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function nullableRet(): ?Logger
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function unionRet(): Foo|Bar
|
||||
{
|
||||
return new Foo();
|
||||
}
|
||||
|
||||
protected function callsZoo($x, User $u, $obj, $var, $cls, $arr, $a)
|
||||
{
|
||||
helper();
|
||||
\App\Helpers\format_id(1);
|
||||
App\Helpers\other(2);
|
||||
$x->m1();
|
||||
$this->m2();
|
||||
$this->prop->m3();
|
||||
$this->a->b->m4();
|
||||
$obj->prop->m5();
|
||||
UserModel::query();
|
||||
self::sHelper();
|
||||
static::sHelper();
|
||||
parent::pHelper();
|
||||
$var::vm();
|
||||
\Qual\Cls::qm();
|
||||
UserModel::factory($x)->where('a');
|
||||
$this->factory($x)->go();
|
||||
foo()->fluent();
|
||||
$a?->maybe()->chained();
|
||||
"chain"->upper();
|
||||
$fn = 'x';
|
||||
$fn();
|
||||
($x)('arg');
|
||||
strlen(...);
|
||||
$this->m2(...);
|
||||
Cls::sm(...);
|
||||
new UserModel();
|
||||
new \App\Models\User();
|
||||
new Models\User(1);
|
||||
new static();
|
||||
new self();
|
||||
new parent();
|
||||
new $cls();
|
||||
$anon = new class extends BaseAnon implements IAnon {
|
||||
public function anonMethod(): void
|
||||
{
|
||||
inner_anon_call();
|
||||
}
|
||||
};
|
||||
new Widget(make_arg());
|
||||
$m = match ($x) {
|
||||
1 => one_case(),
|
||||
default => other_case(),
|
||||
};
|
||||
$$x = 5;
|
||||
$interp = "{$this->x} and $u prefix";
|
||||
$here = <<<EOT
|
||||
heredoc {$this->y} text
|
||||
EOT;
|
||||
$now = <<<'EOT'
|
||||
nowdoc plain
|
||||
EOT;
|
||||
echo SomeCls::CONST_READ;
|
||||
$clsName = UserModel::class;
|
||||
$propRead = UserModel::$conn;
|
||||
$rel = self::REL_CONST;
|
||||
$qualRead = \Qual\Cls::QCONST;
|
||||
$suit = Suit::Hearts;
|
||||
}
|
||||
|
||||
public function nester($arr, $x): void
|
||||
{
|
||||
function innerNamed(): void
|
||||
{
|
||||
inner_call();
|
||||
}
|
||||
if (!class_exists('Poly')) {
|
||||
class Poly
|
||||
{
|
||||
const POLY_MAX = 3;
|
||||
|
||||
public function pm(): void
|
||||
{
|
||||
poly_call();
|
||||
}
|
||||
}
|
||||
}
|
||||
$c = function () use (&$x) {
|
||||
closure_call();
|
||||
};
|
||||
$a = fn ($v) => arrow_call($v);
|
||||
usort($arr, 'cmp_items');
|
||||
array_map('App\Svc\namespaced_fn', $arr);
|
||||
call_user_func([$this, 'm2']);
|
||||
call_user_func([UserModel::class, 'sm']);
|
||||
call_user_func(['Cls', 'sm']);
|
||||
register_shutdown_function('Cls::shutdown');
|
||||
$x->map('not_captured');
|
||||
plain_call('not_a_hof_string');
|
||||
}
|
||||
|
||||
public function reader(): int
|
||||
{
|
||||
$sum = MULTI_A + TYPED_MAX;
|
||||
$s = "interp TYPED_MAX read: {$this->x} MULTI_B";
|
||||
$varOccurrence = $MULTI_A;
|
||||
return $sum + self::TYPED_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
interface Shape extends Base1, Base2, \Qual\Base3
|
||||
{
|
||||
public function area(): float;
|
||||
|
||||
const SHAPE_KIND = 'geo';
|
||||
}
|
||||
|
||||
trait SoftDeletes
|
||||
{
|
||||
const TRAIT_CONST = 1;
|
||||
|
||||
public function restore(): void
|
||||
{
|
||||
$this->doRestore();
|
||||
}
|
||||
}
|
||||
|
||||
enum Suit: string implements HasColor
|
||||
{
|
||||
case Hearts = 'H';
|
||||
case Spades = 'S';
|
||||
|
||||
const ENUM_MAX = 4;
|
||||
|
||||
public function color(): string
|
||||
{
|
||||
return enum_color($this);
|
||||
}
|
||||
}
|
||||
|
||||
enum Pure
|
||||
{
|
||||
case A;
|
||||
case B;
|
||||
}
|
||||
|
||||
abstract class AbstractBase
|
||||
{
|
||||
abstract protected function hook(): void;
|
||||
}
|
||||
Reference in New Issue
Block a user