diff --git a/phpunit/code/parent-abstract-method.php b/phpunit/code/parent-abstract-method.php new file mode 100644 index 00000000..979962be --- /dev/null +++ b/phpunit/code/parent-abstract-method.php @@ -0,0 +1,20 @@ +show(); + $t->error(); +} diff --git a/phpunit/src/ClassTest.php b/phpunit/src/ClassTest.php index 5144aeb5..1828e8ee 100644 --- a/phpunit/src/ClassTest.php +++ b/phpunit/src/ClassTest.php @@ -11,4 +11,9 @@ class ClassTest extends \BaseTest { $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'); + } } diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 6350c935..4f68e135 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -5209,8 +5209,9 @@ class CompilerBase extends \PhpAot\Core\Translator } $classDef = $this->getClass($class); while (true) { - if ($classDef->hasMethod($method)) { - return $classDef->getMethod($method)->flags; + $flags = $classDef->getMethodFlags($method); + if ($flags !== 0) { + return $flags; } if (!$classDef->extends || !$this->hasClass($classDef->extends)) { 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. * @@ -5392,6 +5401,7 @@ class CompilerBase extends \PhpAot\Core\Translator } $parentClass = $this->classDef->extends; $method = $this->parseIdentifier($expr->name); + $this->guardAbstractMethod($parentClass, $method, $expr); // TODO 是否转为 native 调用 if (empty($expr->args)) { return 'this_.call(' . $this->getMethodPtr($parentClass, $method) . ')'; diff --git a/src/Php/Entity/ClassDef.php b/src/Php/Entity/ClassDef.php index 1698bead..96a13f5b 100644 --- a/src/Php/Entity/ClassDef.php +++ b/src/Php/Entity/ClassDef.php @@ -43,6 +43,11 @@ class ClassDef extends ClassLikeDef * @var array */ public array $enumCases = []; + /** + * Abstract method name (lowercase) => flags + * @var array + */ + public array $abstractMethods = []; public ?Trait_ $trait = null; /** @@ -74,11 +79,33 @@ class ClassDef extends ClassLikeDef $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 { 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 { return isset($this->properties[$property]); diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index 28be8982..04a7df6c 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -616,6 +616,7 @@ class Preprocessor extends CompilerBase } } } + $this->classDef->addAbstractMethod($name, $flags); } $fullClassName = $this->getFullClassName();