fix(php): 修复继承检查中的可见性宽化和参数匹配逻辑

- 实现了PHP允许的方法可见性宽化(如protected到public),但禁止窄化
- 添加了子类可以添加可选尾随参数的支持
- 修复了必需参数数量比较逻辑,确保子类不能要求比父类更多的必需参数
- 添加了对缺少父类声明参数的检查
- 实现了额外子类参数必须为可选或可变参数的验证
- 添加了可见性等级比较函数来正确处理继承可见性检查
pull/5/head
韩天峰 2 months ago
parent 918a52d7fb
commit a01548ae96
  1. 24
      phpunit/src/InheritanceErrorTest.php
  2. 43
      src/Php/Translator.php

@ -21,6 +21,18 @@ class InheritanceErrorTest extends TestCase
$this->fail('Expected TestError exception was not thrown');
}
private function assertCompiles(string $file): void
{
global $translator;
$compiler = CompilerTest::create(ROOT_PATH);
$translator = $compiler;
$testFile = __DIR__ . '/../code/' . $file;
$compiler->addFiles([$testFile]);
$compiler->prepareFile($testFile);
$compiler->convertFile($testFile);
$this->addToAssertionCount(1);
}
public function testParameterCountMismatch()
{
$this->exec('must be compatible', 'inheritance_error.php');
@ -48,7 +60,17 @@ class InheritanceErrorTest extends TestCase
public function testMethodVisibilityMismatch()
{
$this->exec('must be compatible', 'inheritance_error_visibility.php');
$this->exec('must be compatible', 'inheritance_error_visibility_narrow.php');
}
public function testMethodVisibilityWideningIsAllowed()
{
$this->assertCompiles('inheritance_error_visibility.php');
}
public function testChildMayAddOptionalTrailingParameter()
{
$this->assertCompiles('inheritance_optional_param_allowed.php');
}
public function testPropertyTypeMismatch()

@ -2966,8 +2966,9 @@ CODE;
"with `{$parentClass}::{$methodName}()`");
};
// Compare visibility (public/protected/private)
if (($this->methodDef->flags & Modifiers::VISIBILITY_MASK) !== ($parentMethodDef->flags & Modifiers::VISIBILITY_MASK)) {
// PHP allows widening visibility in overrides (e.g. protected -> public),
// but forbids narrowing it.
if ($this->getVisibilityRank($this->methodDef->flags) < $this->getVisibilityRank($parentMethodDef->flags)) {
$error('visibility mismatch');
}
@ -2976,19 +2977,23 @@ CODE;
return;
}
// Compare parameter count
if (count($childFuncDef->argInfoList) !== count($parentFuncDef->argInfoList)) {
$error('parameter count mismatch');
}
// Compare return type
if ($childFuncDef->returnType !== $parentFuncDef->returnType ||
$childFuncDef->returnClass !== $parentFuncDef->returnClass) {
$error('return type mismatch');
}
// Compare each parameter
// Child methods may add optional trailing parameters, but they cannot
// require more arguments than the parent contract.
if ($childFuncDef->argCountRequired > $parentFuncDef->argCountRequired) {
$error('required parameter count mismatch');
}
// Compare each parent-declared parameter position.
foreach ($parentFuncDef->argInfoList as $i => $parentArg) {
if (!isset($childFuncDef->argInfoList[$i])) {
$error("missing parameter #{$i}");
}
$childArg = $childFuncDef->argInfoList[$i];
if ($childArg->type !== $parentArg->type || $childArg->class !== $parentArg->class) {
$error("parameter #{$i} type mismatch");
@ -3000,6 +3005,28 @@ CODE;
$error("parameter #{$i} variadic mismatch");
}
}
// Any extra child parameters must be optional or variadic.
for ($i = count($parentFuncDef->argInfoList); $i < count($childFuncDef->argInfoList); $i++) {
$childArg = $childFuncDef->argInfoList[$i];
if (!$childArg->variadic && $childArg->defaultValue === null) {
$error("extra required parameter #{$i}");
}
}
}
private function getVisibilityRank(int $flags): int
{
if ($flags & Modifiers::PUBLIC) {
return 3;
}
if ($flags & Modifiers::PROTECTED) {
return 2;
}
if ($flags & Modifiers::PRIVATE) {
return 1;
}
return 3;
}
private function checkPropertyOverride(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $classStmt): void

Loading…
Cancel
Save