Skip to content

Commit fc1584c

Browse files
github-actions[bot]phpstan-bot
authored andcommitted
Fix phpstan/phpstan#10690: skip unused private constant check for trait constants
- Added ClassConstant wrapper class (similar to ClassMethod) that tracks isDeclaredInTrait - Updated ClassStatementsGatherer to wrap ClassConst nodes with trait context - Updated ClassConstantsNode with getClassConstants() method preserving backward compatibility - UnusedPrivateConstantRule now skips constants declared in traits - Added regression test in tests/PHPStan/Rules/DeadCode/data/bug-10690.php
1 parent d8f5be7 commit fc1584c

6 files changed

Lines changed: 84 additions & 5 deletions

File tree

src/Node/ClassConstant.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Node;
4+
5+
use PhpParser\Node\Stmt\ClassConst;
6+
7+
/**
8+
* @api
9+
*/
10+
final class ClassConstant
11+
{
12+
13+
public function __construct(
14+
private ClassConst $node,
15+
private bool $isDeclaredInTrait,
16+
)
17+
{
18+
}
19+
20+
public function getNode(): ClassConst
21+
{
22+
return $this->node;
23+
}
24+
25+
public function isDeclaredInTrait(): bool
26+
{
27+
return $this->isDeclaredInTrait;
28+
}
29+
30+
}

src/Node/ClassConstantsNode.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PhpParser\NodeAbstract;
99
use PHPStan\Node\Constant\ClassConstantFetch;
1010
use PHPStan\Reflection\ClassReflection;
11+
use function array_map;
1112

1213
/**
1314
* @api
@@ -16,7 +17,7 @@ final class ClassConstantsNode extends NodeAbstract implements VirtualNode
1617
{
1718

1819
/**
19-
* @param ClassConst[] $constants
20+
* @param ClassConstant[] $constants
2021
* @param ClassConstantFetch[] $fetches
2122
*/
2223
public function __construct(private ClassLike $class, private array $constants, private array $fetches, private ClassReflection $classReflection)
@@ -33,6 +34,14 @@ public function getClass(): ClassLike
3334
* @return ClassConst[]
3435
*/
3536
public function getConstants(): array
37+
{
38+
return array_map(static fn (ClassConstant $constant) => $constant->getNode(), $this->constants);
39+
}
40+
41+
/**
42+
* @return ClassConstant[]
43+
*/
44+
public function getClassConstants(): array
3645
{
3746
return $this->constants;
3847
}

src/Node/ClassStatementsGatherer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ final class ClassStatementsGatherer
4747
/** @var array<int, PropertyWrite|PropertyRead> */
4848
private array $propertyUsages = [];
4949

50-
/** @var Node\Stmt\ClassConst[] */
50+
/** @var ClassConstant[] */
5151
private array $constants = [];
5252

5353
/** @var ClassConstantFetch[] */
@@ -103,7 +103,7 @@ public function getPropertyUsages(): array
103103
}
104104

105105
/**
106-
* @return Node\Stmt\ClassConst[]
106+
* @return ClassConstant[]
107107
*/
108108
public function getConstants(): array
109109
{
@@ -165,7 +165,7 @@ private function gatherNodes(Node $node, Scope $scope): void
165165
return;
166166
}
167167
if ($node instanceof Node\Stmt\ClassConst) {
168-
$this->constants[] = $node;
168+
$this->constants[] = new ClassConstant($node, $scope->isInTrait());
169169
return;
170170
}
171171
if ($node instanceof MethodCall || $node instanceof StaticCall) {

src/Rules/DeadCode/UnusedPrivateConstantRule.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@ public function processNode(Node $node, Scope $scope): array
3838
$classType = new ObjectType($classReflection->getName(), classReflection: $classReflection);
3939

4040
$constants = [];
41-
foreach ($node->getConstants() as $constant) {
41+
foreach ($node->getClassConstants() as $classConstant) {
42+
$constant = $classConstant->getNode();
4243
if (!$constant->isPrivate()) {
4344
continue;
4445
}
4546

47+
if ($classConstant->isDeclaredInTrait()) {
48+
continue;
49+
}
50+
4651
foreach ($constant->consts as $const) {
4752
$constantName = $const->name->toString();
4853

tests/PHPStan/Rules/DeadCode/UnusedPrivateConstantRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ public function testBug9765(): void
8888
$this->analyse([__DIR__ . '/data/bug-9765.php'], []);
8989
}
9090

91+
#[RequiresPhp('>= 8.2')]
92+
public function testBug10690(): void
93+
{
94+
$this->analyse([__DIR__ . '/data/bug-10690.php'], []);
95+
}
96+
9197
#[RequiresPhp('>= 8.3')]
9298
public function testDynamicConstantFetch(): void
9399
{
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php // lint >= 8.2
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug10690;
6+
7+
trait MyTrait
8+
{
9+
private const AAA='aaa';
10+
private const BBB='bbb';
11+
}
12+
13+
final class First
14+
{
15+
use MyTrait;
16+
17+
function a(): string {
18+
return self::AAA.self::BBB;
19+
}
20+
}
21+
22+
final class SecondConsumer
23+
{
24+
use MyTrait;
25+
26+
function b(): string {
27+
return self::AAA;
28+
}
29+
}

0 commit comments

Comments
 (0)