diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-07 20:51:59 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-07 20:51:59 +0900 |
| commit | 18a37a098157a98edbc8473e9c0d8dff3e8a88fa (patch) | |
| tree | 471682a4493fc8ddc1577a6f1e3c3d9c203ebf2c /scripts/plugin-stub-generator | |
| parent | cd9e4a2b67cdea258e1daa2d9d830b7643bc19bb (diff) | |
| download | php-shirabe-18a37a098157a98edbc8473e9c0d8dff3e8a88fa.tar.gz php-shirabe-18a37a098157a98edbc8473e9c0d8dff3e8a88fa.tar.zst php-shirabe-18a37a098157a98edbc8473e9c0d8dff3e8a88fa.zip | |
fix(plugin): make proxy stub property access an explicit error
A proxy stub declares none of the real class's instance properties, and the
`__get`/`__set` forwarders were emitted only for classes that declare a public
one. Every other property access therefore got PHP's own answer for an
undeclared property — null on a read, a dynamic property on a write — so plugin
code reading state the entity holds ran on with null and failed somewhere else
entirely, or not at all.
Emit the forwarders on every root stub, add `__isset`/`__unset` alongside them
so `isset()` cannot answer false silently either, and serve all four from one
dispatcher that answers the state the Rust-side entity exposes and rejects every
other name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'scripts/plugin-stub-generator')
| -rw-r--r-- | scripts/plugin-stub-generator/src/Generator.php | 29 |
1 files changed, 14 insertions, 15 deletions
diff --git a/scripts/plugin-stub-generator/src/Generator.php b/scripts/plugin-stub-generator/src/Generator.php index 395155dd..12967787 100644 --- a/scripts/plugin-stub-generator/src/Generator.php +++ b/scripts/plugin-stub-generator/src/Generator.php @@ -12,8 +12,7 @@ use PhpParser\Node\Stmt\Interface_; /** * Emits the proxy stub files deterministically from the Composer sources and the classifier * report. Anything the emitter cannot faithfully proxy (by-ref or variadic parameters, - * magic methods, public properties, non-public constants) fails generation instead of - * degrading silently. + * magic methods, non-public constants) fails generation instead of degrading silently. */ final class Generator { @@ -59,7 +58,6 @@ final class Generator PHP; private const PROPERTY_FORWARDERS = <<<'PHP' - /** The real class declares public properties; every access forwards to the entity. */ public function __get($name) { return \ShirabeRpcRuntime::callRust($this->__rhandle, '__get', [$name]); @@ -69,6 +67,16 @@ final class Generator { \ShirabeRpcRuntime::callRust($this->__rhandle, '__set', [$name, $value]); } + + public function __isset($name): bool + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, '__isset', [$name]); + } + + public function __unset($name): void + { + \ShirabeRpcRuntime::callRust($this->__rhandle, '__unset', [$name]); + } PHP; private Project $project; @@ -202,20 +210,13 @@ final class Generator $isRoot = true; } - $hasPublicInstanceProperties = false; + // Instance properties are entity state, so the stub declares none of them. $staticProperties = []; foreach ($class->getProperties() as $property) { - if (!$property->isPublic()) { - continue; - } - if ($property->isStatic()) { + if ($property->isPublic() && $property->isStatic()) { // A public static property reads no instance state; its real declaration is // materialized so it lives locally in the worker, like static methods. $staticProperties[] = $file->verbatim($property->getStartLine(), $property->getEndLine()); - } else { - // Instance properties are entity state: the stub declares none and lets the - // __get/__set forwarders below carry every access to the Rust side. - $hasPublicInstanceProperties = true; } } @@ -331,6 +332,7 @@ final class Generator $members = []; if ($isRoot) { $members[] = self::BOILERPLATE; + $members[] = self::PROPERTY_FORWARDERS; } if ($isRoot || $constructor !== null) { $members[] = $this->renderConstructor($fqcn, $constructor, $file); @@ -341,9 +343,6 @@ final class Generator if ($staticProperties !== []) { $members[] = implode("\n", $staticProperties); } - if ($hasPublicInstanceProperties) { - $members[] = self::PROPERTY_FORWARDERS; - } $members = array_merge($members, $staticMethods, $methodTexts); $body = implode("\n\n", $members); |
