Skip to content

Commit

Permalink
Understand that promoted property can come from renamed trait constru…
Browse files Browse the repository at this point in the history
…ctor
  • Loading branch information
ondrejmirtes committed Jul 5, 2023
1 parent 4dd3f75 commit eafba2e
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 3 deletions.
5 changes: 4 additions & 1 deletion src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,8 @@ private function processStmtNode(
throw new ShouldNotHappenException();
}

if ($stmt->name->toLowerString() === '__construct') {
$isFromTrait = $stmt->getAttribute('originalTraitMethodName') === '__construct';
if ($stmt->name->toLowerString() === '__construct' || $isFromTrait) {
foreach ($stmt->params as $param) {
if ($param->flags === 0) {
continue;
Expand All @@ -548,6 +549,7 @@ private function processStmtNode(
$phpDoc,
$phpDocParameterTypes[$param->var->name] ?? null,
true,
$isFromTrait,
$param,
false,
$scope->isInTrait(),
Expand Down Expand Up @@ -723,6 +725,7 @@ private function processStmtNode(
$docComment,
$phpDocType,
false,
false,
$prop,
$isReadOnly,
$scope->isInTrait(),
Expand Down
4 changes: 2 additions & 2 deletions src/Node/ClassPropertiesNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ public function getUninitializedProperties(
continue;
}
$originalProperties[$property->getName()] = $property;
$is = TrinaryLogic::createFromBoolean($property->isPromoted());
$is = TrinaryLogic::createFromBoolean($property->isPromoted() && !$property->isPromotedFromTrait());
$initialInitializedProperties[$property->getName()] = $is;
foreach ($constructors as $constructor) {
$initializedProperties[$constructor][$property->getName()] = $is;
}
if ($property->isPromoted()) {
if ($property->isPromoted() && !$property->isPromotedFromTrait()) {
continue;
}
$uninitializedProperties[$property->getName()] = $property;
Expand Down
6 changes: 6 additions & 0 deletions src/Node/ClassPropertyNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __construct(
private ?string $phpDoc,
private ?Type $phpDocType,
private bool $isPromoted,
private bool $isPromotedFromTrait,
Node $originalNode,
private bool $isReadonlyByPhpDoc,
private bool $isDeclaredInTrait,
Expand Down Expand Up @@ -52,6 +53,11 @@ public function isPromoted(): bool
return $this->isPromoted;
}

public function isPromotedFromTrait(): bool
{
return $this->isPromotedFromTrait;
}

public function getPhpDoc(): ?string
{
return $this->phpDoc;
Expand Down
14 changes: 14 additions & 0 deletions tests/PHPStan/Rules/Classes/data/bug-9577.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,17 @@ public function __construct(
$this->__traitConstruct($message);
}
}

class SpecializedException2
{
use StringableMessageTrait {
StringableMessageTrait::__construct as __traitConstruct;
}

public function __construct(
public int $code,
string $message,
) {
//$this->__traitConstruct($message);

This comment has been minimized.

Copy link
@k00ni

k00ni Jul 6, 2023

Is that a leftover?

This comment has been minimized.

Copy link
@ondrejmirtes

ondrejmirtes Jul 6, 2023

Author Member

Nope, that's commented out on purpose, to test that when the renamed constructor is not called, the property is reported as uninitialized: "Class Bug9577\SpecializedException2 has an uninitialized readonly property $message. Assign it in the constructor."

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,18 @@ public function testBug7649(): void
]);
}

public function testBug9577(): void
{
if (PHP_VERSION_ID < 80100) {
$this->markTestSkipped('Test requires PHP 8.1.');
}

$this->analyse([__DIR__ . '/../Classes/data/bug-9577.php'], [
[
'Class Bug9577\SpecializedException2 has an uninitialized readonly property $message. Assign it in the constructor.',
8,
],
]);
}

}

0 comments on commit eafba2e

Please sign in to comment.