From 286330f45de9583ffbccdc11c5e4b81cd0067a70 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 23 Jun 2026 10:31:55 +0800 Subject: [PATCH] =?UTF-8?q?test(php):=20=E6=B7=BB=E5=8A=A0=E7=BB=A7?= =?UTF-8?q?=E6=89=BF=E9=94=99=E8=AF=AF=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E5=B9=B6=E5=AE=9E=E7=8E=B0=E7=BB=A7=E6=89=BF=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了类常量可见性错误的测试用例 - 添加了方法继承不兼容的测试用例 - 实现了属性重写的类型、可见性和只读性检查 - 实现了常量重写的类型和可见性检查 - 实现了方法重写的可见性检查 - 添加了继承错误相关的测试文件和断言逻辑 - 在翻译器中集成了继承兼容性验证功能 --- phpunit/code/inheritance_error.php | 12 +++ phpunit/code/inheritance_error_const_type.php | 12 +++ .../inheritance_error_const_visibility.php | 12 +++ .../code/inheritance_error_prop_readonly.php | 12 +++ phpunit/code/inheritance_error_prop_type.php | 12 +++ .../inheritance_error_prop_visibility.php | 12 +++ phpunit/code/inheritance_error_visibility.php | 12 +++ phpunit/src/InheritanceErrorTest.php | 78 ++++++++++++++++++ src/Php/Translator.php | 82 ++++++++++++++++++- .../constants_visibility_error_002.phpt | 16 ++++ tests/core/classes/inheritance_004.phpt | 18 ++++ 11 files changed, 274 insertions(+), 4 deletions(-) create mode 100644 phpunit/code/inheritance_error.php create mode 100644 phpunit/code/inheritance_error_const_type.php create mode 100644 phpunit/code/inheritance_error_const_visibility.php create mode 100644 phpunit/code/inheritance_error_prop_readonly.php create mode 100644 phpunit/code/inheritance_error_prop_type.php create mode 100644 phpunit/code/inheritance_error_prop_visibility.php create mode 100644 phpunit/code/inheritance_error_visibility.php create mode 100644 phpunit/src/InheritanceErrorTest.php create mode 100644 tests/core/classes/constants_visibility_error_002.phpt create mode 100644 tests/core/classes/inheritance_004.phpt diff --git a/phpunit/code/inheritance_error.php b/phpunit/code/inheritance_error.php new file mode 100644 index 00000000..cd4a3c43 --- /dev/null +++ b/phpunit/code/inheritance_error.php @@ -0,0 +1,12 @@ +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'); + } +} diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 1a6362f9..d8494388 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.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); diff --git a/tests/core/classes/constants_visibility_error_002.phpt b/tests/core/classes/constants_visibility_error_002.phpt new file mode 100644 index 00000000..c417b947 --- /dev/null +++ b/tests/core/classes/constants_visibility_error_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +Class protected constant visibility error +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot access protected constant A::protectedConst in %s:6 +Stack trace: +#0 {main} + thrown in %s on line 6 diff --git a/tests/core/classes/inheritance_004.phpt b/tests/core/classes/inheritance_004.phpt new file mode 100644 index 00000000..cd232c84 --- /dev/null +++ b/tests/core/classes/inheritance_004.phpt @@ -0,0 +1,18 @@ +--TEST-- +ZE2 method inheritance without interfaces +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::f($x) must be compatible with A::f() in %sinheritance_004.php on line %d