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" +}