From 252b112405e5619d7a049303a34ec5f7140d91b6 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Tue, 30 Jun 2026 10:51:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(php):=20=E5=AE=9E=E7=8E=B0=E7=BB=A7?= =?UTF-8?q?=E6=89=BF=E4=B8=AD=E6=96=B9=E6=B3=95=E9=87=8D=E5=86=99=E7=9A=84?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=85=BC=E5=AE=B9=E6=80=A7=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加对final方法重写错误的检测和报告 - 实现返回类型的协变性检查,允许子类返回更具体的类型 - 实现参数类型的逆变性检查,允许子类参数接受更通用的类型 - 添加从无返回类型到有返回类型的合法转换支持 - 增强方法重写签名验证逻辑以支持PHP类型系统规则 - 添加新的测试用例覆盖各种继承错误场景 --- .../code/inheritance_error_final_method.php | 15 ++++ ...nheritance_error_param_covariant_class.php | 23 ++++++ ...tance_error_return_contravariant_class.php | 25 +++++++ ...nce_parameter_type_contravariant_class.php | 23 ++++++ ...nheritance_return_type_covariant_class.php | 25 +++++++ ...itance_return_type_covariant_from_none.php | 17 +++++ phpunit/src/InheritanceErrorTest.php | 30 ++++++++ src/Php/Translator.php | 73 +++++++++++++++++-- 8 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 phpunit/code/inheritance_error_final_method.php create mode 100644 phpunit/code/inheritance_error_param_covariant_class.php create mode 100644 phpunit/code/inheritance_error_return_contravariant_class.php create mode 100644 phpunit/code/inheritance_parameter_type_contravariant_class.php create mode 100644 phpunit/code/inheritance_return_type_covariant_class.php create mode 100644 phpunit/code/inheritance_return_type_covariant_from_none.php diff --git a/phpunit/code/inheritance_error_final_method.php b/phpunit/code/inheritance_error_final_method.php new file mode 100644 index 00000000..b2f62163 --- /dev/null +++ b/phpunit/code/inheritance_error_final_method.php @@ -0,0 +1,15 @@ +exec('must be compatible', 'inheritance_error_return.php'); } + public function testReturnTypeCannotBeContravariant() + { + $this->exec('must be compatible', 'inheritance_error_return_contravariant_class.php'); + } + + public function testParameterTypeCannotBeCovariant() + { + $this->exec('must be compatible', 'inheritance_error_param_covariant_class.php'); + } + public function testByRefMismatch() { $this->exec('must be compatible', 'inheritance_error_byref.php'); @@ -73,6 +83,11 @@ class InheritanceErrorTest extends TestCase $this->exec('must be compatible', 'inheritance_error_static.php'); } + public function testCannotOverrideFinalMethod() + { + $this->exec('Cannot override final method', 'inheritance_error_final_method.php'); + } + public function testInterfaceMethodStaticMismatch() { $this->exec('must be compatible', 'interface_method_static_mismatch.php'); @@ -83,6 +98,21 @@ class InheritanceErrorTest extends TestCase $this->assertCompiles('inheritance_optional_param_allowed.php'); } + public function testChildMayDeclareReturnTypeWhenParentHasNone() + { + $this->assertCompiles('inheritance_return_type_covariant_from_none.php'); + } + + public function testChildReturnTypeMayBeCovariant() + { + $this->assertCompiles('inheritance_return_type_covariant_class.php'); + } + + public function testChildParameterTypeMayBeContravariant() + { + $this->assertCompiles('inheritance_parameter_type_contravariant_class.php'); + } + public function testPropertyTypeMismatch() { $this->exec('must be compatible', 'inheritance_error_prop_type.php'); diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 7a69a90e..cfe04d6c 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -3159,9 +3159,13 @@ CODE; } // 父类是内置类 if ($classDef->inheritedFromInternalClass) { - if (Reflection::getClassMethodModifiers($extends, $name) & \ReflectionMethod::IS_PRIVATE) { + $modifiers = Reflection::getClassMethodModifiers($extends, $name); + if ($modifiers & \ReflectionMethod::IS_PRIVATE) { goto _error; } + if ($modifiers & \ReflectionMethod::IS_FINAL) { + goto _final_error; + } break; } $classDef = $this->getClass($extends); @@ -3173,6 +3177,12 @@ CODE; 'Cannot override private method `' . $extends . '::' . $name . '()`'); } + if ($methodDef->flags & Modifiers::FINAL) { + _final_error: + $this->fatalError($v, + 'Cannot override final method `' . + $extends . '::' . $name . '()`'); + } $this->validateMethodOverrideSignature($v, $name, $this->methodDef, $methodDef, $extends); break; } @@ -3213,9 +3223,7 @@ CODE; return; } - // Compare return type - if ($childFuncDef->returnType !== $parentFuncDef->returnType || - $childFuncDef->returnClass !== $parentFuncDef->returnClass) { + if (!$this->isReturnTypeOverrideCompatible($childFuncDef, $parentFuncDef)) { $error('return type mismatch'); } @@ -3231,7 +3239,7 @@ CODE; $error("missing parameter #{$i}"); } $childArg = $childFuncDef->argInfoList[$i]; - if ($childArg->type !== $parentArg->type || $childArg->class !== $parentArg->class) { + if (!$this->isParameterTypeOverrideCompatible($childArg, $parentArg)) { $error("parameter #{$i} type mismatch"); } if ($childArg->byRef !== $parentArg->byRef) { @@ -3251,6 +3259,61 @@ CODE; } } + private function isReturnTypeOverrideCompatible(FunctionDef $childFuncDef, FunctionDef $parentFuncDef): bool + { + if ($parentFuncDef->returnTypeUndeclared) { + return true; + } + if ($childFuncDef->returnTypeUndeclared) { + return false; + } + if ($parentFuncDef->returnTypeCheck || $childFuncDef->returnTypeCheck) { + return $parentFuncDef->returnTypeStr === $childFuncDef->returnTypeStr; + } + if ($parentFuncDef->returnType === self::TYPE_VAR) { + return true; + } + if ($childFuncDef->returnType !== $parentFuncDef->returnType) { + return false; + } + if ($parentFuncDef->returnType !== self::TYPE_OBJECT) { + return true; + } + if ($childFuncDef->returnClass === $parentFuncDef->returnClass) { + return true; + } + if (!$childFuncDef->returnClass || !$parentFuncDef->returnClass) { + return false; + } + return $this->isInheritedFrom($childFuncDef->returnClass, $parentFuncDef->returnClass); + } + + private function isParameterTypeOverrideCompatible(ArgInfo $childArg, ArgInfo $parentArg): bool + { + if ($parentArg->typeCheck || $childArg->typeCheck) { + return $parentArg->typeStr === $childArg->typeStr; + } + if ($childArg->undeclared || $childArg->type === self::TYPE_VAR) { + return true; + } + if ($parentArg->undeclared || $parentArg->type === self::TYPE_VAR) { + return false; + } + if ($childArg->type !== $parentArg->type) { + return false; + } + if ($parentArg->type !== self::TYPE_OBJECT) { + return true; + } + if ($childArg->class === $parentArg->class) { + return true; + } + if (!$childArg->class || !$parentArg->class) { + return false; + } + return $this->isInheritedFrom($parentArg->class, $childArg->class); + } + private function checkInterfaceImplementations(Node\Stmt\Class_|Node\Stmt\Enum_ $classStmt): void { $classDef = $this->classDef;