From 0b1a45660e97a6cd50bacf130787d34c7a00521d Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sun, 26 Jul 2026 10:55:41 +0800 Subject: [PATCH] fix(compiler): resolve class and interface dependency registration issues - Update ReturnInfo::equalsApartFromPhpDocAndRefcount to consider PHPDoc types in arginfo equivalence checks to prevent incorrect Reflection type exposure - Rename method from equalsApartFromPhpDocAndRefcount to equalsApartFromRefcount for better clarity and consistency - Modify Translator.php to use hasClass and hasInterface methods instead of symbols->hasClass and symbols->hasInterface for proper dependency resolution - Add comprehensive tests for class registration preserving reverse inheritance - Add tests for magic methods with implicit return types maintaining distinct Zend arginfo during registration - Include tests for interface registration handling child-before-parent scenarios - Add cross-namespace interface dependency resolution tests - Create diamond inheritance pattern registration preservation tests --- src/Translator.php | 4 +- src/gen_stub.php | 14 +++- ...lass-registration-reverse-inheritance.phpt | 39 ++++++++++ ...face-registration-child-before-parent.phpt | 35 +++++++++ ...nterface-registration-cross-namespace.phpt | 35 +++++++++ .../class/interface-registration-diamond.phpt | 39 ++++++++++ .../implicit-return-arginfo.phpt | 76 +++++++++++++++++++ .../implicit-serialization-arginfo.phpt | 54 +++++++++++++ 8 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 tests/compiler/class/class-registration-reverse-inheritance.phpt create mode 100644 tests/compiler/class/interface-registration-child-before-parent.phpt create mode 100644 tests/compiler/class/interface-registration-cross-namespace.phpt create mode 100644 tests/compiler/class/interface-registration-diamond.phpt create mode 100644 tests/compiler/magic_methods/implicit-return-arginfo.phpt create mode 100644 tests/compiler/magic_methods/implicit-serialization-arginfo.phpt diff --git a/src/Translator.php b/src/Translator.php index dc70708b..47f4a0cb 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -2423,7 +2423,7 @@ CODE; if ($parent) { // 不存在的父类,说明可能是内置类 $tmpCe = $this->getParentClassCe($classDef); - if (!$this->symbols->hasClass($parent)) { + if (!$this->hasClass($parent)) { $sorter->add($tmpCe); } $deps[] = $tmpCe; @@ -2433,7 +2433,7 @@ CODE; if ($implements) { foreach ($implements as $interface) { $tmpCe = self::PREFIX . 'class_entry_' . $this->escapeCeName($interface); - if (!$this->symbols->hasInterface($interface)) { + if (!$this->hasInterface($interface)) { $sorter->add($tmpCe); } $deps[] = $tmpCe; diff --git a/src/gen_stub.php b/src/gen_stub.php index 9224f5fb..1725b61d 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -1155,9 +1155,17 @@ class ReturnInfo { $this->setRefcount($refcount); } - public function equalsApartFromPhpDocAndRefcount(ReturnInfo $other): bool { + public function equalsApartFromRefcount(ReturnInfo $other): bool { + // PHPDoc types are used as the effective arginfo type for generated + // TypePHP stubs. They therefore participate in arginfo equivalence: + // aliasing two functions that only differ in their inferred return + // type can expose the wrong Reflection type and may make Zend reject a + // magic method during class registration. + $effectiveType = $this->type ?? $this->phpDocType; + $otherEffectiveType = $other->type ?? $other->phpDocType; + return $this->byRef === $other->byRef - && StubType::equals($this->type, $other->type) + && StubType::equals($effectiveType, $otherEffectiveType) && $this->tentativeReturnType === $other->tentativeReturnType; } @@ -1501,7 +1509,7 @@ class FuncInfo { } } - return $this->return->equalsApartFromPhpDocAndRefcount($other->return) + return $this->return->equalsApartFromRefcount($other->return) && $this->numRequiredArgs === $other->numRequiredArgs && $this->cond === $other->cond; } diff --git a/tests/compiler/class/class-registration-reverse-inheritance.phpt b/tests/compiler/class/class-registration-reverse-inheritance.phpt new file mode 100644 index 00000000..27b85849 --- /dev/null +++ b/tests/compiler/class/class-registration-reverse-inheritance.phpt @@ -0,0 +1,39 @@ +--TEST-- +class entry registration preserves reverse-declared class and interface dependencies +--FILE-- +name(), "\n"; +} +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +root diff --git a/tests/compiler/class/interface-registration-child-before-parent.phpt b/tests/compiler/class/interface-registration-child-before-parent.phpt new file mode 100644 index 00000000..ab2b35f8 --- /dev/null +++ b/tests/compiler/class/interface-registration-child-before-parent.phpt @@ -0,0 +1,35 @@ +--TEST-- +class entry registration preserves a child interface dependency declared before its parent +--FILE-- +getMessage(), "\n"; + } +} +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +invalid cache key diff --git a/tests/compiler/class/interface-registration-cross-namespace.phpt b/tests/compiler/class/interface-registration-cross-namespace.phpt new file mode 100644 index 00000000..066790a5 --- /dev/null +++ b/tests/compiler/class/interface-registration-cross-namespace.phpt @@ -0,0 +1,35 @@ +--TEST-- +class entry registration resolves cross-namespace interface dependencies before implementors +--FILE-- + +--EXPECT-- +bool(true) +bool(true) diff --git a/tests/compiler/class/interface-registration-diamond.phpt b/tests/compiler/class/interface-registration-diamond.phpt new file mode 100644 index 00000000..be311ed7 --- /dev/null +++ b/tests/compiler/class/interface-registration-diamond.phpt @@ -0,0 +1,39 @@ +--TEST-- +class entry registration preserves all dependencies in a reverse-declared interface diamond +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/compiler/magic_methods/implicit-return-arginfo.phpt b/tests/compiler/magic_methods/implicit-return-arginfo.phpt new file mode 100644 index 00000000..da133c5f --- /dev/null +++ b/tests/compiler/magic_methods/implicit-return-arginfo.phpt @@ -0,0 +1,76 @@ +--TEST-- +Magic methods with implicit return types keep distinct Zend arginfo +--FILE-- + 42]; + + public function render() + { + return 'rendered'; + } + + public function __toString() + { + return $this->render(); + } + + /** + * @return bool + */ + public function __isset($name) + { + return isset($this->values[$name]); + } + + /** + * @return void + */ + public function __unset($name) + { + unset($this->values[$name]); + } + + public function __set($name, $value) + { + $this->values[$name] = $value; + } + + public function __serialize() + { + return $this->values; + } + + public function __unserialize($data) + { + $this->values = $data; + } +} + +function returnType(string $method): string +{ + $type = (new ReflectionMethod(ImplicitMagicReturnTypes::class, $method))->getReturnType(); + return $type ? (string) $type : 'none'; +} + +function main(): void +{ + $value = new ImplicitMagicReturnTypes(); + + var_dump((string) $value); + + foreach (['__toString', '__isset', '__unset', '__set', '__serialize', '__unserialize'] as $method) { + echo $method, ': ', returnType($method), PHP_EOL; + } +} +?> +--EXPECT-- +string(8) "rendered" +__toString: string +__isset: bool +__unset: void +__set: void +__serialize: array +__unserialize: void diff --git a/tests/compiler/magic_methods/implicit-serialization-arginfo.phpt b/tests/compiler/magic_methods/implicit-serialization-arginfo.phpt new file mode 100644 index 00000000..f2b12904 --- /dev/null +++ b/tests/compiler/magic_methods/implicit-serialization-arginfo.phpt @@ -0,0 +1,54 @@ +--TEST-- +Implicit serialization magic method return types are not aliased by arginfo deduplication +--FILE-- + 7]; + } + + public function __unserialize($data) + { + } + + public function __sleep() + { + return []; + } + + public function __wakeup() + { + } +} + +function serializationReturnType(string $method): string +{ + $type = (new ReflectionMethod(ImplicitSerializationReturnTypes::class, $method))->getReturnType(); + return $type ? (string) $type : 'none'; +} + +function main(): void +{ + foreach (['noReturnType', '__serialize', '__unserialize', '__sleep', '__wakeup'] as $method) { + echo $method, ': ', serializationReturnType($method), PHP_EOL; + } + + $serialized = serialize(new ImplicitSerializationReturnTypes()); + var_dump(str_contains($serialized, 'value')); +} +?> +--EXPECT-- +noReturnType: none +__serialize: array +__unserialize: void +__sleep: array +__wakeup: void +bool(true)