From 42ee6e3aa60780c8a38a50e748be1656577f74f6 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 30 Jun 2026 10:24:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(php):=20=E6=94=AF=E6=8C=81=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=B8=B8=E9=87=8F=E7=BB=A7=E6=89=BF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了类通过继承链查找接口常量的功能 - 添加了接口及其父接口常量的递归收集机制 - 在编译器中增加了对实现接口的类处理数组常量的支持 - 更新了常量访问检查逻辑以支持接口常量继承 - 添加了单元测试验证接口数组常量向实现类的传播 - 创建了AOT测试用例覆盖接口数组常量继承场景 - 重构了接口实现检查相关代码结构 --- .../interface_array_constant_implements.php | 14 +++++ phpunit/src/InheritanceErrorTest.php | 15 +++++ src/Php/CompilerBase.php | 62 ++++++++++++++++++- src/Php/Translator.php | 53 +++++++++------- .../aot/const/interface-array-const-001.phpt | 60 ++++++++++++++++++ 5 files changed, 180 insertions(+), 24 deletions(-) create mode 100644 phpunit/code/interface_array_constant_implements.php create mode 100644 tests/aot/const/interface-array-const-001.phpt diff --git a/phpunit/code/interface_array_constant_implements.php b/phpunit/code/interface_array_constant_implements.php new file mode 100644 index 00000000..2cb28fba --- /dev/null +++ b/phpunit/code/interface_array_constant_implements.php @@ -0,0 +1,14 @@ +assertStringContainsString('php::updateConstant("InterfaceArrayConstant", "ITEMS"', file_get_contents($extensionFile)); } + public function testInterfaceArrayConstantPropagatesToImplementingClass() + { + global $translator; + $compiler = CompilerTest::create(ROOT_PATH); + $translator = $compiler; + $testFile = __DIR__ . '/../code/interface_array_constant_implements.php'; + $compiler->addFiles([$testFile]); + $compiler->prepareFile($testFile); + $cppFile = $compiler->convertFile($testFile); + $extensionFile = $compiler->genExtension(); + + $this->assertStringContainsString('php_interfacearrayconstantcontract__items', file_get_contents($cppFile)); + $this->assertStringContainsString('php::updateConstant("InterfaceArrayConstantImpl", "ITEMS"', file_get_contents($extensionFile)); + } + public function testConcreteClassMustImplementInheritedAbstractMethod() { $this->exec('must implement abstract method', 'abstract_parent_method_missing.php'); diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 1921cf09..4c928170 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -1979,15 +1979,16 @@ class CompilerBase extends \PhpAot\Core\Translator } $classDef = $this->getClass($class); + $originClassDef = $classDef; $constDef = null; // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法 while (true) { if (!$classDef->hasConstant($const)) { if (!$classDef->extends) { - return false; + break; } if (!$this->hasClass($classDef->extends)) { - return false; + break; } $classDef = $this->getClass($classDef->extends); } else { @@ -1995,7 +1996,24 @@ class CompilerBase extends \PhpAot\Core\Translator break; } } - if (!$this->checkAccessible($classDef, $constDef->flags)) { + if ($constDef === null) { + foreach ($this->getClassImplementedInterfaces($originClassDef) as $interfaceName) { + if (!$this->hasInterface($interfaceName)) { + continue; + } + $interfaceDef = $this->getInterface($interfaceName); + if (!$interfaceDef->hasConstant($const)) { + continue; + } + $classDef = $interfaceDef; + $constDef = $interfaceDef->constants[$const]; + break; + } + } + if ($constDef === null) { + return false; + } + if ($classDef instanceof ClassDef && !$this->checkAccessible($classDef, $constDef->flags)) { $this->fatalError($expr, 'Constant `' . $classDef->getNamespacedName() . '::' . $const . '` is not accessible'); } if ($constDef->type === self::TYPE_ARRAY) { @@ -2006,6 +2024,44 @@ class CompilerBase extends \PhpAot\Core\Translator } } + /** + * @return array + */ + protected function getClassImplementedInterfaces(ClassDef $classDef): array + { + $interfaces = []; + $current = $classDef; + while (true) { + foreach ($current->implements as $interfaceName) { + $this->collectInterfaceAndParents($interfaceName, $interfaces); + } + if (!$current->extends || !$this->hasClass($current->extends)) { + break; + } + $current = $this->getClass($current->extends); + } + + return array_values($interfaces); + } + + /** + * @param array $interfaces + */ + private function collectInterfaceAndParents(string $interfaceName, array &$interfaces): void + { + if (isset($interfaces[$interfaceName])) { + return; + } + $interfaces[$interfaceName] = $interfaceName; + if (!$this->hasInterface($interfaceName)) { + return; + } + $interfaceDef = $this->getInterface($interfaceName); + foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parentInterface) { + $this->collectInterfaceAndParents($parentInterface, $interfaces); + } + } + protected function resetReturnType(Node\Stmt\Return_ $node, string $type): void { $oriType = $this->functionDef->returnType; diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 8ee92cb6..06ba9390 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -999,6 +999,21 @@ CODE; } $parentName = $this->escapeClass($parentDef->extends); } + + foreach ($this->getClassImplementedInterfaces($classDef) as $interfaceName) { + if (!$this->hasInterface($interfaceName)) { + continue; + } + $interfaceDef = $this->getInterface($interfaceName); + foreach ($interfaceDef->constants as $constant) { + if ($constant->type === self::TYPE_ARRAY && !isset($ownConstNames[$constant->name])) { + $ownConstNames[$constant->name] = true; + $classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true); + $classConstStr = $this->genCharPtr($constant->name); + $code .= "php::updateConstant($classNameStr, $classConstStr, php::null);\n"; + } + } + } } // 扩展模式,需要在 RSHUTDOWN 阶段中清理函数、类、属性表 @@ -1941,6 +1956,22 @@ CODE; } $parentName = $this->escapeClass($parentDef->extends); } + + foreach ($this->getClassImplementedInterfaces($classDef) as $interfaceName) { + if (!$this->hasInterface($interfaceName)) { + continue; + } + $interfaceDef = $this->getInterface($interfaceName); + foreach ($interfaceDef->constants as $constant) { + if ($constant->type === self::TYPE_ARRAY && !isset($ownConstNames[$constant->name])) { + $ownConstNames[$constant->name] = true; + $constName = self::PREFIX . $this->getNativeName($constant->name, $interfaceDef->namespace, $interfaceDef->name); + $classNameStr = $this->genCharPtr($classDef->getNamespacedName(false), true); + $classConstStr = $this->genCharPtr($constant->name); + $code .= "php::updateConstant($classNameStr, $classConstStr, {$constName});\n"; + } + } + } } return $code; @@ -3175,31 +3206,11 @@ CODE; private function checkInterfaceImplementations(Node\Stmt\Class_|Node\Stmt\Enum_ $classStmt): void { $classDef = $this->classDef; - foreach ($this->getImplementedInterfacesForClass($classDef) as $interfaceName) { + foreach ($this->getClassImplementedInterfaces($classDef) as $interfaceName) { $this->checkInterfaceImplementation($classStmt, $classDef, $interfaceName); } } - /** - * @return array - */ - private function getImplementedInterfacesForClass(ClassDef $classDef): array - { - $interfaces = []; - $current = $classDef; - while (true) { - foreach ($current->implements as $interfaceName) { - $interfaces[$interfaceName] = $interfaceName; - } - if (!$current->extends || !$this->hasClass($current->extends)) { - break; - } - $current = $this->getClass($current->extends); - } - - return array_values($interfaces); - } - private function checkInterfaceImplementation(NodeAbstract $node, ClassDef $classDef, string $interfaceName): void { if ($this->isInternalInterface($interfaceName)) { diff --git a/tests/aot/const/interface-array-const-001.phpt b/tests/aot/const/interface-array-const-001.phpt new file mode 100644 index 00000000..d5b7ec08 --- /dev/null +++ b/tests/aot/const/interface-array-const-001.phpt @@ -0,0 +1,60 @@ +--TEST-- +interface array constant inherited by implementing class +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + string(3) "php" + [1]=> + string(3) "aot" +} +string(3) "aot" +array(2) { + [0]=> + string(3) "php" + [1]=> + string(3) "aot" +} +array(2) { + [0]=> + string(3) "php" + [1]=> + string(3) "aot" +}