test(php): 添加继承错误测试用例并实现继承兼容性检查

- 添加了类常量可见性错误的测试用例
- 添加了方法继承不兼容的测试用例
- 实现了属性重写的类型、可见性和只读性检查
- 实现了常量重写的类型和可见性检查
- 实现了方法重写的可见性检查
- 添加了继承错误相关的测试文件和断言逻辑
- 在翻译器中集成了继承兼容性验证功能
pull/3/head
韩天峰 2 months ago
parent 23a100caf4
commit 286330f45d
  1. 12
      phpunit/code/inheritance_error.php
  2. 12
      phpunit/code/inheritance_error_const_type.php
  3. 12
      phpunit/code/inheritance_error_const_visibility.php
  4. 12
      phpunit/code/inheritance_error_prop_readonly.php
  5. 12
      phpunit/code/inheritance_error_prop_type.php
  6. 12
      phpunit/code/inheritance_error_prop_visibility.php
  7. 12
      phpunit/code/inheritance_error_visibility.php
  8. 78
      phpunit/src/InheritanceErrorTest.php
  9. 82
      src/Php/Translator.php
  10. 16
      tests/core/classes/constants_visibility_error_002.phpt
  11. 18
      tests/core/classes/inheritance_004.phpt

@ -0,0 +1,12 @@
<?php
class A
{
function f() {}
}
class B extends A
{
function f($x) {}
}
function main() {}

@ -0,0 +1,12 @@
<?php
class A
{
public const int FOO = 1;
}
class B extends A
{
public const string FOO = "hello";
}
function main() {}

@ -0,0 +1,12 @@
<?php
class A
{
public const FOO = 1;
}
class B extends A
{
protected const FOO = 2;
}
function main() {}

@ -0,0 +1,12 @@
<?php
class A
{
public int $x = 1;
}
class B extends A
{
public readonly int $x = 2;
}
function main() {}

@ -0,0 +1,12 @@
<?php
class A
{
public int $x = 1;
}
class B extends A
{
public string $x = "hello";
}
function main() {}

@ -0,0 +1,12 @@
<?php
class A
{
public int $x = 1;
}
class B extends A
{
protected int $x = 2;
}
function main() {}

@ -0,0 +1,12 @@
<?php
class A
{
protected function f() {}
}
class B extends A
{
public function f() {}
}
function main() {}

@ -0,0 +1,78 @@
<?php
use PhpAot\Php\CompilerTest;
use PhpAot\Php\Exception\TestError;
use PHPUnit\Framework\TestCase;
class InheritanceErrorTest extends TestCase
{
private function exec(string $expected, string $file): void
{
try {
$compiler = CompilerTest::create(ROOT_PATH);
$testFile = __DIR__ . '/../code/' . $file;
$compiler->addFiles([$testFile]);
$compiler->prepareFile($testFile);
$compiler->convertFile($testFile);
} catch (TestError $exception) {
$this->assertStringContainsString($expected, $exception->getMessage());
return;
}
$this->fail('Expected TestError exception was not thrown');
}
public function testParameterCountMismatch()
{
$this->exec('must be compatible', 'inheritance_error.php');
}
public function testParameterTypeMismatch()
{
$this->exec('must be compatible', 'inheritance_error_type.php');
}
public function testReturnTypeMismatch()
{
$this->exec('must be compatible', 'inheritance_error_return.php');
}
public function testByRefMismatch()
{
$this->exec('must be compatible', 'inheritance_error_byref.php');
}
public function testVariadicMismatch()
{
$this->exec('must be compatible', 'inheritance_error_variadic.php');
}
public function testMethodVisibilityMismatch()
{
$this->exec('must be compatible', 'inheritance_error_visibility.php');
}
public function testPropertyTypeMismatch()
{
$this->exec('must be compatible', 'inheritance_error_prop_type.php');
}
public function testPropertyVisibilityMismatch()
{
$this->exec('must be compatible', 'inheritance_error_prop_visibility.php');
}
public function testConstantTypeMismatch()
{
$this->exec('must be compatible', 'inheritance_error_const_type.php');
}
public function testConstantVisibilityMismatch()
{
$this->exec('must be compatible', 'inheritance_error_const_visibility.php');
}
public function testPropertyReadonlyMismatch()
{
$this->exec('must be compatible', 'inheritance_error_prop_readonly.php');
}
}

@ -2521,6 +2521,9 @@ CODE;
}
}
$this->checkPropertyOverride($class);
$this->checkConstantOverride($class);
$className = $this->classDef->getNamespacedName();
$this->classesDefineInFile[$className] = $this->classDef;
@ -2884,9 +2887,7 @@ CODE;
$extends . '::' . $name . '()`');
}
$parentFuncDef = $methodDef->functionDef;
if ($parentFuncDef) {
$this->validateMethodOverrideSignature($v, $name, $childFuncDef, $parentFuncDef, $extends);
}
$this->validateMethodOverrideSignature($v, $name, $childFuncDef, $methodDef, $extends);
break;
}
}
@ -2896,7 +2897,7 @@ CODE;
Node\Stmt\ClassMethod $v,
string $methodName,
FunctionDef $childFuncDef,
FunctionDef $parentFuncDef,
MethodDef $parentMethodDef,
string $parentClass
): void {
$className = $this->getFullClassName();
@ -2906,6 +2907,16 @@ CODE;
"with `{$parentClass}::{$methodName}()`");
};
// Compare visibility (public/protected/private)
if (($this->methodDef->flags & Modifiers::VISIBILITY_MASK) !== ($parentMethodDef->flags & Modifiers::VISIBILITY_MASK)) {
$error('visibility mismatch');
}
$parentFuncDef = $parentMethodDef->functionDef;
if (!$parentFuncDef) {
return;
}
// Compare parameter count
if (count($childFuncDef->argInfoList) !== count($parentFuncDef->argInfoList)) {
$error('parameter count mismatch');
@ -2932,6 +2943,69 @@ CODE;
}
}
private function checkPropertyOverride(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $classStmt): void
{
$classDef = $this->classDef;
$className = $this->getFullClassName();
$chainNode = $classDef;
while ($chainNode->extends && !$chainNode->inheritedFromInternalClass) {
$parentClass = $chainNode->extends;
$chainNode = $this->getClass($parentClass);
if (!$chainNode) {
break;
}
foreach ($this->classDef->properties as $name => $childProp) {
if ($chainNode->hasProperty($name)) {
$parentProp = $chainNode->getProperty($name);
if ($childProp->type !== $parentProp->type || $childProp->class !== $parentProp->class) {
$this->fatalError($classStmt,
"Declaration of `{$className}::\${$name}` must be compatible " .
"with `{$parentClass}::\${$name}`");
}
if (($childProp->flags & Modifiers::VISIBILITY_MASK) !== ($parentProp->flags & Modifiers::VISIBILITY_MASK)) {
$this->fatalError($classStmt,
"Declaration of `{$className}::\${$name}` must be compatible " .
"with `{$parentClass}::\${$name}`");
}
if (($childProp->flags & Modifiers::READONLY) !== ($parentProp->flags & Modifiers::READONLY)) {
$this->fatalError($classStmt,
"Declaration of `{$className}::\${$name}` must be compatible " .
"with `{$parentClass}::\${$name}`");
}
}
}
}
}
private function checkConstantOverride(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $classStmt): void
{
$classDef = $this->classDef;
$className = $this->getFullClassName();
$chainNode = $classDef;
while ($chainNode->extends && !$chainNode->inheritedFromInternalClass) {
$parentClass = $chainNode->extends;
$chainNode = $this->getClass($parentClass);
if (!$chainNode) {
break;
}
foreach ($this->classDef->constants as $name => $childConst) {
if ($chainNode->hasConstant($name)) {
$parentConst = $chainNode->getConstant($name);
if ($childConst->type !== $parentConst->type || $childConst->class !== $parentConst->class) {
$this->fatalError($classStmt,
"Declaration of `{$className}::{$name}` must be compatible " .
"with `{$parentClass}::{$name}`");
}
if (($childConst->flags & Modifiers::VISIBILITY_MASK) !== ($parentConst->flags & Modifiers::VISIBILITY_MASK)) {
$this->fatalError($classStmt,
"Declaration of `{$className}::{$name}` must be compatible " .
"with `{$parentClass}::{$name}`");
}
}
}
}
}
protected function parseClassMethod(Node\Stmt\ClassMethod $v, array &$methodCodes): void
{
$name = $this->getMethodName($v);

@ -0,0 +1,16 @@
--TEST--
Class protected constant visibility error
--FILE--
<?php
class A {
protected const protectedConst = 'protectedConst';
}
var_dump(A::protectedConst);
?>
--EXPECTF--
Fatal error: Uncaught Error: Cannot access protected constant A::protectedConst in %s:6
Stack trace:
#0 {main}
thrown in %s on line 6

@ -0,0 +1,18 @@
--TEST--
ZE2 method inheritance without interfaces
--FILE--
<?php
class A
{
function f() {}
}
class B extends A
{
function f($x) {}
}
?>
--EXPECTF--
Fatal error: Declaration of B::f($x) must be compatible with A::f() in %sinheritance_004.php on line %d
Loading…
Cancel
Save