feat(php): 实现继承中方法重写的类型兼容性检查

- 添加对final方法重写错误的检测和报告
- 实现返回类型的协变性检查,允许子类返回更具体的类型
- 实现参数类型的逆变性检查,允许子类参数接受更通用的类型
- 添加从无返回类型到有返回类型的合法转换支持
- 增强方法重写签名验证逻辑以支持PHP类型系统规则
- 添加新的测试用例覆盖各种继承错误场景
pull/5/head
韩天峰 2 months ago
parent 09be0226cb
commit 252b112405
  1. 15
      phpunit/code/inheritance_error_final_method.php
  2. 23
      phpunit/code/inheritance_error_param_covariant_class.php
  3. 25
      phpunit/code/inheritance_error_return_contravariant_class.php
  4. 23
      phpunit/code/inheritance_parameter_type_contravariant_class.php
  5. 25
      phpunit/code/inheritance_return_type_covariant_class.php
  6. 17
      phpunit/code/inheritance_return_type_covariant_from_none.php
  7. 30
      phpunit/src/InheritanceErrorTest.php
  8. 73
      src/Php/Translator.php

@ -0,0 +1,15 @@
<?php
class InheritanceFinalMethodParent
{
final public function run(): void
{
}
}
class InheritanceFinalMethodChild extends InheritanceFinalMethodParent
{
public function run(): void
{
}
}

@ -0,0 +1,23 @@
<?php
class InheritanceParamCovariantParentType
{
}
class InheritanceParamCovariantChildType extends InheritanceParamCovariantParentType
{
}
class InheritanceParamCovariantParent
{
public function handle(InheritanceParamCovariantParentType $value): void
{
}
}
class InheritanceParamCovariantChild extends InheritanceParamCovariantParent
{
public function handle(InheritanceParamCovariantChildType $value): void
{
}
}

@ -0,0 +1,25 @@
<?php
class InheritanceReturnContravariantParentType
{
}
class InheritanceReturnContravariantChildType extends InheritanceReturnContravariantParentType
{
}
class InheritanceReturnContravariantParent
{
public function make(): InheritanceReturnContravariantChildType
{
return new InheritanceReturnContravariantChildType();
}
}
class InheritanceReturnContravariantChild extends InheritanceReturnContravariantParent
{
public function make(): InheritanceReturnContravariantParentType
{
return new InheritanceReturnContravariantParentType();
}
}

@ -0,0 +1,23 @@
<?php
class InheritanceParamContravariantParentType
{
}
class InheritanceParamContravariantChildType extends InheritanceParamContravariantParentType
{
}
class InheritanceParamContravariantParent
{
public function handle(InheritanceParamContravariantChildType $value): void
{
}
}
class InheritanceParamContravariantChild extends InheritanceParamContravariantParent
{
public function handle(InheritanceParamContravariantParentType $value): void
{
}
}

@ -0,0 +1,25 @@
<?php
class InheritanceReturnCovariantParentType
{
}
class InheritanceReturnCovariantChildType extends InheritanceReturnCovariantParentType
{
}
class InheritanceReturnCovariantParent
{
public function make(): InheritanceReturnCovariantParentType
{
return new InheritanceReturnCovariantParentType();
}
}
class InheritanceReturnCovariantChild extends InheritanceReturnCovariantParent
{
public function make(): InheritanceReturnCovariantChildType
{
return new InheritanceReturnCovariantChildType();
}
}

@ -0,0 +1,17 @@
<?php
class InheritanceReturnNoneParent
{
public function run()
{
return 1;
}
}
class InheritanceReturnNoneChild extends InheritanceReturnNoneParent
{
public function run(): int
{
return 1;
}
}

@ -48,6 +48,16 @@ class InheritanceErrorTest extends TestCase
$this->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');

@ -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;

Loading…
Cancel
Save