Skip to content

Commit 9f415ae

Browse files
committed
Child class of parent class that uses a trait with a constant can redeclare the constant with a different value
1 parent 7f3ad70 commit 9f415ae

File tree

3 files changed

+70
-29
lines changed

3 files changed

+70
-29
lines changed

src/Rules/Traits/ConflictingTraitConstantsRule.php

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -44,33 +44,73 @@ public function processNode(Node $node, Scope $scope): array
4444
}
4545

4646
$classReflection = $scope->getClassReflection();
47-
$traitConstants = [];
47+
$recursiveTraitConstants = [];
4848
foreach ($classReflection->getTraits(true) as $trait) {
4949
foreach ($trait->getNativeReflection()->getReflectionConstants() as $constant) {
50-
$traitConstants[] = $constant;
50+
$recursiveTraitConstants[] = $constant;
5151
}
5252
}
5353

5454
$errors = [];
5555
foreach ($node->consts as $const) {
56-
foreach ($traitConstants as $traitConstant) {
57-
if ($traitConstant->getName() !== $const->name->toString()) {
56+
foreach ($recursiveTraitConstants as $recursiveTraitConstant) {
57+
if ($recursiveTraitConstant->getName() !== $const->name->toString()) {
5858
continue;
5959
}
6060

61-
foreach ($this->processSingleConstant($classReflection, $traitConstant, $node, $const->value) as $error) {
61+
foreach ($this->processSingleConstant($classReflection, $recursiveTraitConstant, $node) as $error) {
6262
$errors[] = $error;
6363
}
6464
}
6565
}
6666

67+
$immediateTraitConstants = [];
68+
foreach ($classReflection->getTraits() as $trait) {
69+
foreach ($trait->getNativeReflection()->getReflectionConstants() as $constant) {
70+
$immediateTraitConstants[] = $constant;
71+
}
72+
}
73+
74+
foreach ($node->consts as $const) {
75+
foreach ($immediateTraitConstants as $immediateTraitConstant) {
76+
if ($immediateTraitConstant->getName() !== $const->name->toString()) {
77+
continue;
78+
}
79+
80+
$classConstantValueType = $this->initializerExprTypeResolver->getType($const->value, InitializerExprContext::fromClassReflection($classReflection));
81+
$traitConstantValueType = $this->initializerExprTypeResolver->getType(
82+
$immediateTraitConstant->getValueExpression(),
83+
InitializerExprContext::fromClass(
84+
$immediateTraitConstant->getDeclaringClass()->getName(),
85+
$immediateTraitConstant->getDeclaringClass()->getFileName() !== false ? $immediateTraitConstant->getDeclaringClass()->getFileName() : null,
86+
),
87+
);
88+
if ($classConstantValueType->equals($traitConstantValueType)) {
89+
continue;
90+
}
91+
92+
$errors[] = RuleErrorBuilder::message(sprintf(
93+
'Constant %s::%s with value %s overriding constant %s::%s with different value %s should have the same value.',
94+
$classReflection->getDisplayName(),
95+
$immediateTraitConstant->getName(),
96+
$classConstantValueType->describe(VerbosityLevel::value()),
97+
$immediateTraitConstant->getDeclaringClass()->getName(),
98+
$immediateTraitConstant->getName(),
99+
$traitConstantValueType->describe(VerbosityLevel::value()),
100+
))
101+
->nonIgnorable()
102+
->identifier('classConstant.value')
103+
->build();
104+
}
105+
}
106+
67107
return $errors;
68108
}
69109

70110
/**
71111
* @return list<IdentifierRuleError>
72112
*/
73-
private function processSingleConstant(ClassReflection $classReflection, ReflectionClassConstant $traitConstant, Node\Stmt\ClassConst $classConst, Node\Expr $valueExpr): array
113+
private function processSingleConstant(ClassReflection $classReflection, ReflectionClassConstant $traitConstant, Node\Stmt\ClassConst $classConst): array
74114
{
75115
$errors = [];
76116
if ($traitConstant->isPublic()) {
@@ -225,29 +265,6 @@ private function processSingleConstant(ClassReflection $classReflection, Reflect
225265
}
226266
}
227267

228-
$classConstantValueType = $this->initializerExprTypeResolver->getType($valueExpr, InitializerExprContext::fromClassReflection($classReflection));
229-
$traitConstantValueType = $this->initializerExprTypeResolver->getType(
230-
$traitConstant->getValueExpression(),
231-
InitializerExprContext::fromClass(
232-
$traitDeclaringClass->getName(),
233-
$traitDeclaringClass->getFileName() !== false ? $traitDeclaringClass->getFileName() : null,
234-
),
235-
);
236-
if (!$classConstantValueType->equals($traitConstantValueType)) {
237-
$errors[] = RuleErrorBuilder::message(sprintf(
238-
'Constant %s::%s with value %s overriding constant %s::%s with different value %s should have the same value.',
239-
$classReflection->getDisplayName(),
240-
$traitConstant->getName(),
241-
$classConstantValueType->describe(VerbosityLevel::value()),
242-
$traitConstant->getDeclaringClass()->getName(),
243-
$traitConstant->getName(),
244-
$traitConstantValueType->describe(VerbosityLevel::value()),
245-
))
246-
->nonIgnorable()
247-
->identifier('classConstant.value')
248-
->build();
249-
}
250-
251268
return $errors;
252269
}
253270

tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PHPStan\Reflection\InitializerExprTypeResolver;
66
use PHPStan\Rules\Rule as TRule;
77
use PHPStan\Testing\RuleTestCase;
8+
use PHPUnit\Framework\Attributes\RequiresPhp;
89

910
/**
1011
* @extends RuleTestCase<ConflictingTraitConstantsRule>
@@ -78,4 +79,10 @@ public function testNativeTypes(): void
7879
]);
7980
}
8081

82+
#[RequiresPhp('>= 8.3')]
83+
public function testBug13119(): void
84+
{
85+
$this->analyse([__DIR__ . '/data/bug-13119.php'], []);
86+
}
87+
8188
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php // lint >= 8.3
2+
3+
namespace Bug13119;
4+
5+
trait Base {
6+
protected const array FOO = [];
7+
}
8+
9+
class Foo
10+
{
11+
use Base;
12+
}
13+
14+
class Bar extends Foo
15+
{
16+
protected const array FOO = [1, 2];
17+
}

0 commit comments

Comments
 (0)