fix(php): 修复trait成员冲突检测逻辑

- 修复trait常量冲突检查,只在不兼容时抛出错误
- 修复trait属性冲突检查,只在不兼容时抛出错误
- 添加类型节点转字符串的辅助方法
- 实现兼容性检查方法验证trait常量和属性是否一致
- 更新测试用例验证trait成员兼容性和冲突场景
pull/5/head
韩天峰 2 months ago
parent 42ee6e3aa6
commit 72b80276ba
  1. 14
      phpunit/code/trait_constant_conflict.php
  2. 16
      phpunit/code/trait_member_compatible.php
  3. 14
      phpunit/code/trait_property_conflict.php
  4. 15
      phpunit/src/InheritanceErrorTest.php
  5. 70
      src/Php/Translator.php

@ -0,0 +1,14 @@
<?php
trait TraitConstantConflict
{
public const VALUE = 1;
}
class TraitConstantConflictUser
{
use TraitConstantConflict;
public const VALUE = 2;
}
function main() {}

@ -0,0 +1,16 @@
<?php
trait TraitMemberCompatible
{
public const VALUE = 1;
public int $count = 1;
}
class TraitMemberCompatibleUser
{
use TraitMemberCompatible;
public const VALUE = 1;
public int $count = 1;
}
function main() {}

@ -0,0 +1,14 @@
<?php
trait TraitPropertyConflict
{
public int $count = 1;
}
class TraitPropertyConflictUser
{
use TraitPropertyConflict;
public int $count = 2;
}
function main() {}

@ -167,6 +167,21 @@ class InheritanceErrorTest extends TestCase
$this->exec('must implement method', 'interface_abstract_parent_missing.php');
}
public function testCompatibleTraitMemberDuplicatesCompile()
{
$this->assertCompiles('trait_member_compatible.php');
}
public function testTraitConstantConflict()
{
$this->exec('constant `VALUE` conflicts', 'trait_constant_conflict.php');
}
public function testTraitPropertyConflict()
{
$this->exec('property `count` conflicts', 'trait_property_conflict.php');
}
public function testAbstractMethodSignatureMismatch()
{
$this->exec('must be compatible', 'abstract_method_signature_mismatch.php');

@ -2585,24 +2585,54 @@ CODE;
foreach ($traitStmt->consts as $k2 => $const) {
$constName = strtolower($const->name->toString());
if (isset($constants[$constName])) {
unset($traitStmts[$k1][$k2]);
unset($traitStmt->consts[$k2]);
if (!$traitStmt->consts) {
unset($traitStmts[$k1]);
}
continue;
}
if (isset($traitConstants[$constName])) {
$this->fatalError($classStmt, "Trait `{$traitFullName}` constant `{$constName}` already exists");
[$existingConstStmt, $existingConst] = $traitConstants[$constName];
if ($existingConstStmt->flags !== $traitStmt->flags ||
$this->typeNodeToStringOrNull($existingConstStmt->type) !== $this->typeNodeToStringOrNull($traitStmt->type) ||
$this->printer->prettyPrintExpr($existingConst->value) !== $this->printer->prettyPrintExpr($const->value)) {
$this->fatalError($classStmt, "Trait `{$traitFullName}` constant `{$constName}` already exists");
}
unset($traitStmt->consts[$k2]);
if (!$traitStmt->consts) {
unset($traitStmts[$k1]);
}
continue;
}
$traitConstants[$constName] = $const;
$traitConstants[$constName] = [$traitStmt, $const];
}
}
if ($traitStmt instanceof Node\Stmt\Property) {
foreach ($traitStmt->props as $k2 => $prop) {
$propName = strtolower($prop->name->toString());
if (isset($properties[$propName])) {
unset($traitStmts[$k1][$k2]);
unset($traitStmt->props[$k2]);
if (!$traitStmt->props) {
unset($traitStmts[$k1]);
}
continue;
}
if (isset($traitProperties[$propName])) {
$this->fatalError($classStmt, "Trait `{$traitFullName}` property `{$propName}` already exists");
[$existingPropStmt, $existingProp] = $traitProperties[$propName];
$existingDefault = $existingProp->default ? $this->printer->prettyPrintExpr($existingProp->default) : null;
$propDefault = $prop->default ? $this->printer->prettyPrintExpr($prop->default) : null;
if ($existingPropStmt->flags !== $traitStmt->flags ||
$this->typeNodeToStringOrNull($existingPropStmt->type) !== $this->typeNodeToStringOrNull($traitStmt->type) ||
$existingDefault !== $propDefault) {
$this->fatalError($classStmt, "Trait `{$traitFullName}` property `{$propName}` already exists");
}
unset($traitStmt->props[$k2]);
if (!$traitStmt->props) {
unset($traitStmts[$k1]);
}
continue;
}
$traitProperties[$propName] = $prop;
$traitProperties[$propName] = [$traitStmt, $prop];
}
}
}
@ -2717,6 +2747,11 @@ CODE;
return $this->printer->prettyPrint([$typeNode]);
}
private function typeNodeToStringOrNull(?NodeAbstract $typeNode): ?string
{
return $typeNode ? $this->typeNodeToString($typeNode) : null;
}
protected function parseClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string
{
$this->class = $this->parseIdentifier($class->name);
@ -3389,12 +3424,18 @@ CODE;
// 将 Trait 中定义的 常量、静态常量、属性、方法、静态属性复制到当前类中
foreach ($traitDef->constants as $const) {
if ($classDef->hasConstant($const->name)) {
if (!$this->isCompatibleTraitConstant($classDef->getConstant($const->name), $const)) {
$this->fatalError($v, "Trait `{$traitFullName}` constant `{$const->name}` conflicts with class `{$classDef->getNamespacedName(false)}`");
}
continue;
}
$classDef->constants[$const->name] = $const;
}
foreach ($traitDef->properties as $prop) {
if ($classDef->hasProperty($prop->name)) {
if (!$this->isCompatibleTraitProperty($classDef->getProperty($prop->name), $prop)) {
$this->fatalError($v, "Trait `{$traitFullName}` property `{$prop->name}` conflicts with class `{$classDef->getNamespacedName(false)}`");
}
continue;
}
$classDef->properties[$prop->name] = $prop;
@ -3454,6 +3495,23 @@ CODE;
}
}
private function isCompatibleTraitConstant(ConstantDef $existing, ConstantDef $incoming): bool
{
return $existing->flags === $incoming->flags &&
$existing->type === $incoming->type &&
$existing->class === $incoming->class &&
$existing->value === $incoming->value;
}
private function isCompatibleTraitProperty(PropertyDef $existing, PropertyDef $incoming): bool
{
return $existing->flags === $incoming->flags &&
$existing->type === $incoming->type &&
$existing->class === $incoming->class &&
$existing->nullable === $incoming->nullable &&
$existing->default === $incoming->default;
}
protected function parseForeachObject(Foreach_ $node): string
{
$obj = $this->parseIdentifier($node->expr);

Loading…
Cancel
Save