Skip to content

Commit

Permalink
Properties set in the native constructor are initialized in additiona…
Browse files Browse the repository at this point in the history
…l constructors
  • Loading branch information
ondrejmirtes committed Aug 8, 2023
1 parent 7031d82 commit 1b0c6a0
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 7 deletions.
35 changes: 29 additions & 6 deletions src/Node/ClassPropertiesNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use PHPStan\TrinaryLogic;
use PHPStan\Type\NeverType;
use PHPStan\Type\TypeUtils;
use function array_diff_key;
use function array_key_exists;
use function array_keys;
use function in_array;
Expand Down Expand Up @@ -157,6 +158,11 @@ public function getUninitializedProperties(
$prematureAccess = [];
$additionalAssigns = [];

$initializedInConstructor = [];
if ($classReflection->hasConstructor()) {
$initializedInConstructor = array_diff_key($uninitializedProperties, $this->collectUninitializedProperties([$classReflection->getConstructor()->getName()], $uninitializedProperties));
}

foreach ($this->getPropertyUsages() as $usage) {
$fetch = $usage->getFetch();
if (!$fetch instanceof PropertyFetch) {
Expand Down Expand Up @@ -213,6 +219,13 @@ public function getUninitializedProperties(
}
} elseif (array_key_exists($propertyName, $initializedPropertiesMap)) {
$hasInitialization = $initializedPropertiesMap[$propertyName]->or($usageScope->hasExpressionType(new PropertyInitializationExpr($propertyName)));
if (
strtolower($function->getName()) !== '__construct'
&& array_key_exists($propertyName, $initializedInConstructor)
&& in_array($function->getName(), $constructors, true)
) {
continue;
}
if (!$hasInitialization->yes()) {
$prematureAccess[] = [
$propertyName,
Expand All @@ -224,7 +237,21 @@ public function getUninitializedProperties(
}
}

foreach (array_keys($methodsCalledFromConstructor) as $constructor) {
return [
$this->collectUninitializedProperties(array_keys($methodsCalledFromConstructor), $uninitializedProperties),
$prematureAccess,
$additionalAssigns,
];
}

/**
* @param list<string> $constructors
* @param array<string, ClassPropertyNode> $uninitializedProperties
* @return array<string, ClassPropertyNode>
*/
private function collectUninitializedProperties(array $constructors, array $uninitializedProperties): array
{
foreach ($constructors as $constructor) {
$lowerConstructorName = strtolower($constructor);
if (!array_key_exists($lowerConstructorName, $this->returnStatementNodes)) {
continue;
Expand Down Expand Up @@ -275,11 +302,7 @@ public function getUninitializedProperties(
}
}

return [
$uninitializedProperties,
$prematureAccess,
$additionalAssigns,
];
return $uninitializedProperties;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ protected function getRule(): Rule
[
'UninitializedProperty\\TestCase::setUp',
'Bug9619\\AdminPresenter::startup',
'Bug9619\\AdminPresenter2::startup',
'Bug9619\\AdminPresenter3::startup',
'Bug9619\\AdminPresenter3::startup2',
],
),
);
Expand Down Expand Up @@ -181,7 +184,12 @@ public function testEfabricaLatteBug(): void

public function testBug9619(): void
{
$this->analyse([__DIR__ . '/data/bug-9619.php'], []);
$this->analyse([__DIR__ . '/data/bug-9619.php'], [
[
'Access to an uninitialized property Bug9619\AdminPresenter3::$user.',
55,
],
]);
}

}
36 changes: 36 additions & 0 deletions tests/PHPStan/Rules/Properties/data/bug-9619.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,39 @@ public function startup()
}
}
}

class AdminPresenter2
{
private User $user;

public function __construct(User $user)
{
$this->user = $user;
}

public function startup()
{
// do not report uninitialized property - it's initialized for sure
if (!$this->user->isLoggedIn()) {
// do something
}
}
}

class AdminPresenter3
{
private \stdClass $user;

public function startup()
{
$this->user = new \stdClass();
}

public function startup2()
{
// we cannot be sure which additional constructor gets called first
if (!$this->user->loggedIn) {
// do something
}
}
}

0 comments on commit 1b0c6a0

Please sign in to comment.