Skip to content

Commit abdc52e

Browse files
committed
Fix incorrect type narrowing of superglobal with dependent types
- Extended conditional expression skip logic in MutatingScope to handle superglobal variables not present in the other branch's expression types - Added exprContainsSuperGlobal() helper method to detect superglobal expressions for reuse across merge logic - New regression test in tests/PHPStan/Rules/Variables/data/bug-14421.php - Root cause: superglobals always exist but aren't tracked in expressionTypes unless narrowed, so the dependent type skip check failed to recognize them
1 parent 2083a81 commit abdc52e

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

src/Analyser/MutatingScope.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,20 @@ private function isGlobalVariable(string $variableName): bool
840840
return in_array($variableName, self::SUPERGLOBAL_VARIABLES, true);
841841
}
842842

843+
private function exprContainsSuperGlobal(Expr $expr): bool
844+
{
845+
$containsSuperGlobal = $expr->getAttribute(self::CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME);
846+
if ($containsSuperGlobal !== null) {
847+
return $containsSuperGlobal;
848+
}
849+
850+
$nodeFinder = new NodeFinder();
851+
$containsSuperGlobal = $nodeFinder->findFirst($expr, fn (Node $node) => $node instanceof Variable && is_string($node->name) && $this->isGlobalVariable($node->name)) !== null;
852+
$expr->setAttribute(self::CONTAINS_SUPER_GLOBAL_ATTRIBUTE_NAME, $containsSuperGlobal);
853+
854+
return $containsSuperGlobal;
855+
}
856+
843857
/** @api */
844858
public function hasConstant(Name $name): bool
845859
{
@@ -3603,9 +3617,13 @@ private function createConditionalExpressions(
36033617
}
36043618

36053619
foreach ($variableTypeGuards as $guardExprString => $guardHolder) {
3620+
$exprExistsInTheirs = array_key_exists($exprString, $theirExpressionTypes)
3621+
&& $theirExpressionTypes[$exprString]->getCertainty()->yes();
3622+
if (!$exprExistsInTheirs) {
3623+
$exprExistsInTheirs = $this->exprContainsSuperGlobal($holder->getExpr());
3624+
}
36063625
if (
3607-
array_key_exists($exprString, $theirExpressionTypes)
3608-
&& $theirExpressionTypes[$exprString]->getCertainty()->yes()
3626+
$exprExistsInTheirs
36093627
&& array_key_exists($guardExprString, $theirExpressionTypes)
36103628
&& $theirExpressionTypes[$guardExprString]->getCertainty()->yes()
36113629
&& !$guardHolder->getType()->isSuperTypeOf($theirExpressionTypes[$guardExprString]->getType())->no()

tests/PHPStan/Rules/Variables/IssetRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,4 +562,11 @@ public function testBug14393(): void
562562
]);
563563
}
564564

565+
public function testBug14421(): void
566+
{
567+
$this->treatPhpDocTypesAsCertain = true;
568+
569+
$this->analyse([__DIR__ . '/data/bug-14421.php'], []);
570+
}
571+
565572
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14421;
4+
5+
/** @phpstan-impure */
6+
function get_optional_int(): ?int {
7+
return random_int(0, 1) ? 42 : null;
8+
}
9+
10+
if (isset($_SESSION['a'])) {
11+
$b = $_SESSION['a'];
12+
}
13+
else {
14+
$b = get_optional_int();
15+
}
16+
if ($b !== null) {
17+
if (!isset($_SESSION['a'])) {
18+
echo 'this is absolutely possible';
19+
}
20+
}

0 commit comments

Comments
 (0)