Skip to content

Commit 65330d3

Browse files
committed
IllegalConstructorStaticCallRule - fix for renamed trait constructor
1 parent eafba2e commit 65330d3

File tree

3 files changed

+83
-5
lines changed

3 files changed

+83
-5
lines changed

src/Rules/Methods/IllegalConstructorStaticCallRule.php

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Rules\RuleErrorBuilder;
9+
use function array_key_exists;
910
use function array_map;
1011
use function in_array;
12+
use function sprintf;
1113
use function strtolower;
1214

1315
/**
@@ -37,16 +39,19 @@ public function processNode(Node $node, Scope $scope): array
3739
];
3840
}
3941

40-
private function isCollectCallingConstructor(Node $node, Scope $scope): bool
42+
private function isCollectCallingConstructor(Node\Expr\StaticCall $node, Scope $scope): bool
4143
{
42-
if (!$node instanceof Node\Expr\StaticCall) {
43-
return true;
44-
}
4544
// __construct should be called from inside constructor
46-
if ($scope->getFunction() !== null && $scope->getFunction()->getName() !== '__construct') {
45+
if ($scope->getFunction() === null) {
4746
return false;
4847
}
4948

49+
if ($scope->getFunction()->getName() !== '__construct') {
50+
if (!$this->isInRenamedTraitConstructor($scope)) {
51+
return false;
52+
}
53+
}
54+
5055
if (!$scope->isInClass()) {
5156
return false;
5257
}
@@ -60,4 +65,27 @@ private function isCollectCallingConstructor(Node $node, Scope $scope): bool
6065
return in_array(strtolower($scope->resolveName($node->class)), $parentClasses, true);
6166
}
6267

68+
private function isInRenamedTraitConstructor(Scope $scope): bool
69+
{
70+
if (!$scope->isInClass()) {
71+
return false;
72+
}
73+
74+
if (!$scope->isInTrait()) {
75+
return false;
76+
}
77+
78+
if ($scope->getFunction() === null) {
79+
return false;
80+
}
81+
82+
$traitAliases = $scope->getClassReflection()->getNativeReflection()->getTraitAliases();
83+
$functionName = $scope->getFunction()->getName();
84+
if (!array_key_exists($functionName, $traitAliases)) {
85+
return false;
86+
}
87+
88+
return $traitAliases[$functionName] === sprintf('%s::%s', $scope->getTraitReflection()->getName(), '__construct');
89+
}
90+
6391
}

tests/PHPStan/Rules/Methods/IllegalConstructorStaticCallRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Rules\Rule;
66
use PHPStan\Testing\RuleTestCase;
7+
use const PHP_VERSION_ID;
78

89
/**
910
* @extends RuleTestCase<IllegalConstructorStaticCallRule>
@@ -46,4 +47,13 @@ public function testMethods(): void
4647
]);
4748
}
4849

50+
public function testBug9577(): void
51+
{
52+
if (PHP_VERSION_ID < 80100) {
53+
$this->markTestSkipped('Test requires PHP 8.1.');
54+
}
55+
56+
$this->analyse([__DIR__ . '/data/bug-9577.php'], []);
57+
}
58+
4959
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug9577IllegalConstructorStaticCall;
4+
5+
trait StringableMessageTrait
6+
{
7+
public function __construct(
8+
private readonly \Stringable $StringableMessage,
9+
int $code = 0,
10+
?\Throwable $previous = null,
11+
) {
12+
parent::__construct((string) $StringableMessage, $code, $previous);
13+
}
14+
15+
public function getStringableMessage(): \Stringable
16+
{
17+
return $this->StringableMessage;
18+
}
19+
}
20+
21+
class SpecializedException extends \RuntimeException
22+
{
23+
use StringableMessageTrait {
24+
StringableMessageTrait::__construct as __traitConstruct;
25+
}
26+
27+
public function __construct(
28+
private readonly object $aService,
29+
\Stringable $StringableMessage,
30+
int $code = 0,
31+
?\Throwable $previous = null,
32+
) {
33+
$this->__traitConstruct($StringableMessage, $code, $previous);
34+
}
35+
36+
public function getService(): object
37+
{
38+
return $this->aService;
39+
}
40+
}

0 commit comments

Comments
 (0)