feat(php): 支持接口常量继承功能

- 实现了类通过继承链查找接口常量的功能
- 添加了接口及其父接口常量的递归收集机制
- 在编译器中增加了对实现接口的类处理数组常量的支持
- 更新了常量访问检查逻辑以支持接口常量继承
- 添加了单元测试验证接口数组常量向实现类的传播
- 创建了AOT测试用例覆盖接口数组常量继承场景
- 重构了接口实现检查相关代码结构
pull/5/head
韩天峰 2 months ago
parent c4a8146661
commit 42ee6e3aa6
  1. 14
      phpunit/code/interface_array_constant_implements.php
  2. 15
      phpunit/src/InheritanceErrorTest.php
  3. 62
      src/Php/CompilerBase.php
  4. 53
      src/Php/Translator.php
  5. 60
      tests/aot/const/interface-array-const-001.phpt

@ -0,0 +1,14 @@
<?php
interface InterfaceArrayConstantContract
{
public const ITEMS = [1, 2, 3];
}
class InterfaceArrayConstantImpl implements InterfaceArrayConstantContract
{
}
function main()
{
var_dump(InterfaceArrayConstantImpl::ITEMS);
}

@ -142,6 +142,21 @@ class InheritanceErrorTest extends TestCase
$this->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');

@ -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<string>
*/
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<string, string> $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;

@ -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<string>
*/
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)) {

@ -0,0 +1,60 @@
--TEST--
interface array constant inherited by implementing class
--FILE--
<?php
interface InterfaceArrayConstSource
{
public const DATA = [
'php',
'aot',
];
}
class InterfaceArrayConstUser implements InterfaceArrayConstSource
{
}
interface InterfaceArrayConstChild extends InterfaceArrayConstSource
{
}
class InterfaceArrayConstChildUser implements InterfaceArrayConstChild
{
}
class InterfaceArrayConstParentUser implements InterfaceArrayConstSource
{
}
class InterfaceArrayConstGrandChildUser extends InterfaceArrayConstParentUser
{
}
function main()
{
var_dump(InterfaceArrayConstUser::DATA);
var_dump(InterfaceArrayConstUser::DATA[1]);
var_dump(InterfaceArrayConstChildUser::DATA);
var_dump(InterfaceArrayConstGrandChildUser::DATA);
}
?>
--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"
}
Loading…
Cancel
Save