feat(php): 添加抽象方法支持和调用保护

- 在 ClassDef 中添加 abstractMethods 数组存储抽象方法
- 实现 addAbstractMethod 和 hasAbstractMethod 方法
- 添加 getMethodFlags 方法统一获取方法标志
- 在 Preprocessor 中解析并添加抽象方法到类定义
- 实现 guardAbstractMethod 防止调用抽象方法
- 在父类方法调用中添加抽象方法检查
- 更新单元测试验证抽象方法调用错误处理
pull/1/head
韩天峰 2 months ago
parent 35f0775435
commit dbcadeb873
  1. 20
      phpunit/code/parent-abstract-method.php
  2. 5
      phpunit/src/ClassTest.php
  3. 14
      src/Php/CompilerBase.php
  4. 27
      src/Php/Entity/ClassDef.php
  5. 1
      src/Php/Preprocessor.php

@ -0,0 +1,20 @@
<?php
abstract class AbsBase {
abstract function show();
}
class Child extends AbsBase {
function show() {
echo "Call to function show()\n";
}
function error() {
parent::show();
}
}
function main() {
$t = new Child();
$t->show();
$t->error();
}

@ -11,4 +11,9 @@ class ClassTest extends \BaseTest
{ {
$this->exec('Cannot access protected property `settings` of class `DevConfig`', 'protected-property.php'); $this->exec('Cannot access protected property `settings` of class `DevConfig`', 'protected-property.php');
} }
public function testCallAbstractParentMethod()
{
$this->exec('Cannot call abstract method `AbsBase::show()`', 'parent-abstract-method.php');
}
} }

@ -5209,8 +5209,9 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$classDef = $this->getClass($class); $classDef = $this->getClass($class);
while (true) { while (true) {
if ($classDef->hasMethod($method)) { $flags = $classDef->getMethodFlags($method);
return $classDef->getMethod($method)->flags; if ($flags !== 0) {
return $flags;
} }
if (!$classDef->extends || !$this->hasClass($classDef->extends)) { if (!$classDef->extends || !$this->hasClass($classDef->extends)) {
return 0; return 0;
@ -5219,6 +5220,14 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
} }
protected function guardAbstractMethod(string $class, string $method, Node $expr): void
{
$flags = $this->getMethodFlags($class, $method);
if ($flags & Modifiers::ABSTRACT) {
$this->fatalError($expr, "Cannot call abstract method `{$class}::{$method}()`");
}
}
/** /**
* Determine whether a method call can be devirtualized to a direct native call. * Determine whether a method call can be devirtualized to a direct native call.
* *
@ -5392,6 +5401,7 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$parentClass = $this->classDef->extends; $parentClass = $this->classDef->extends;
$method = $this->parseIdentifier($expr->name); $method = $this->parseIdentifier($expr->name);
$this->guardAbstractMethod($parentClass, $method, $expr);
// TODO 是否转为 native 调用 // TODO 是否转为 native 调用
if (empty($expr->args)) { if (empty($expr->args)) {
return 'this_.call(' . $this->getMethodPtr($parentClass, $method) . ')'; return 'this_.call(' . $this->getMethodPtr($parentClass, $method) . ')';

@ -43,6 +43,11 @@ class ClassDef extends ClassLikeDef
* @var array<string, int|string|null> * @var array<string, int|string|null>
*/ */
public array $enumCases = []; public array $enumCases = [];
/**
* Abstract method name (lowercase) => flags
* @var array<string, int>
*/
public array $abstractMethods = [];
public ?Trait_ $trait = null; public ?Trait_ $trait = null;
/** /**
@ -74,11 +79,33 @@ class ClassDef extends ClassLikeDef
$this->methods[strtolower($method->name)] = $method; $this->methods[strtolower($method->name)] = $method;
} }
public function addAbstractMethod(string $name, int $flags): void
{
$this->abstractMethods[strtolower($name)] = $flags;
}
public function hasMethod(string $method): bool public function hasMethod(string $method): bool
{ {
return isset($this->methods[strtolower($method)]); return isset($this->methods[strtolower($method)]);
} }
public function hasAbstractMethod(string $method): bool
{
return isset($this->abstractMethods[strtolower($method)]);
}
/**
* Returns method flags for concrete or abstract methods. Returns 0 if not found.
*/
public function getMethodFlags(string $method): int
{
$lower = strtolower($method);
if (isset($this->methods[$lower])) {
return $this->methods[$lower]->flags;
}
return $this->abstractMethods[$lower] ?? 0;
}
public function hasProperty(string $property): bool public function hasProperty(string $property): bool
{ {
return isset($this->properties[$property]); return isset($this->properties[$property]);

@ -616,6 +616,7 @@ class Preprocessor extends CompilerBase
} }
} }
} }
$this->classDef->addAbstractMethod($name, $flags);
} }
$fullClassName = $this->getFullClassName(); $fullClassName = $this->getFullClassName();

Loading…
Cancel
Save