Skip to content
27 changes: 25 additions & 2 deletions src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
use PHPStan\Node\PropertyAssignNode;
use PHPStan\Node\VariableAssignNode;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\AccessoryArrayListType;
Expand Down Expand Up @@ -84,6 +85,7 @@ final class AssignHandler implements ExprHandler
public function __construct(
private TypeSpecifier $typeSpecifier,
private PhpVersion $phpVersion,
private ReflectionProvider $reflectionProvider,
)
{
}
Expand Down Expand Up @@ -861,10 +863,13 @@ private function processSureTypesForConditionalExpressionsAfterAssign(Scope $sco
if ($expr->name === $variableName) {
continue;
}
} elseif ($expr instanceof FuncCall) {
if ($this->isBuiltinFunctionCallWithPossibleSideEffects($expr, $scope)) {
continue;
}
} elseif (
!$expr instanceof PropertyFetch
&& !$expr instanceof ArrayDimFetch
&& !$expr instanceof FuncCall
) {
continue;
}
Expand Down Expand Up @@ -900,10 +905,13 @@ private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $
if ($expr->name === $variableName) {
continue;
}
} elseif ($expr instanceof FuncCall) {
if ($this->isBuiltinFunctionCallWithPossibleSideEffects($expr, $scope)) {
continue;
}
} elseif (
!$expr instanceof PropertyFetch
&& !$expr instanceof ArrayDimFetch
&& !$expr instanceof FuncCall
) {
continue;
}
Expand All @@ -924,6 +932,21 @@ private function processSureNotTypesForConditionalExpressionsAfterAssign(Scope $
return $conditionalExpressions;
}

private function isBuiltinFunctionCallWithPossibleSideEffects(Expr $expr, Scope $scope): bool
{
if (!$expr instanceof FuncCall || !$expr->name instanceof Name) {
return false;
}

if (!$this->reflectionProvider->hasFunction($expr->name, $scope)) {
return false;
}

$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope);

return $functionReflection->isBuiltin() && !$functionReflection->hasSideEffects()->no();
}

/**
* @param list<ArrayDimFetch> $dimFetchStack
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,4 +384,11 @@ public function testBug10305(): void
]);
}

public function testBug14473(): void
{
$this->treatPhpDocTypesAsCertain = true;

$this->analyse([__DIR__ . '/data/bug-14473.php'], []);
}

}
27 changes: 27 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-14473.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace Bug14473;

$link = mysqli_connect('host', 'user', 'pass', 'database') or die('Could not connect: ' . mysqli_connect_error());

// (assume long-running code that can cause the connection to time out)

$link = mysqli_connect('host', 'user', 'pass', 'database') or die('Could not reconnect: ' . mysqli_connect_error());

// okay, so let's make certain that the connection is closed
mysqli_close($link);

$link = mysqli_connect('host', 'user', 'pass', 'database') or die('Could not reconnect: ' . mysqli_connect_error());

// close it and destroy the variable
mysqli_close($link);
unset($link);

$link = mysqli_connect('host', 'user', 'pass', 'database') or die('Could not reconnect: ' . mysqli_connect_error());

// close, destroy ...
mysqli_close($link);
unset($link);

// ... and assign to a different variable
$newLink = mysqli_connect('host', 'user', 'pass', 'database') or die('Could not reconnect: ' . mysqli_connect_error());
Loading