Skip to content

Commit 124af5f

Browse files
Respect original variable type when using extract on optional keys (#4450)
Co-authored-by: Markus Staab <maggus.staab@googlemail.com>
1 parent c37bc22 commit 124af5f

File tree

4 files changed

+76
-1
lines changed

4 files changed

+76
-1
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3068,7 +3068,17 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
30683068
}
30693069
foreach ($properties as $name => $type) {
30703070
$optional = in_array($name, $optionalProperties, true) || $refCount[$name] < count($constantArrays);
3071-
$scope = $scope->assignVariable($name, $type, $type, $optional ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes());
3071+
3072+
if (!$optional) {
3073+
$scope = $scope->assignVariable($name, $type, $type, TrinaryLogic::createYes());
3074+
} else {
3075+
$hasVariable = $scope->hasVariableType($name);
3076+
if (!$hasVariable->no()) {
3077+
$type = TypeCombinator::union($scope->getVariableType($name), $type);
3078+
}
3079+
3080+
$scope = $scope->assignVariable($name, $type, $type, $scope->hasVariableType($name)->or(TrinaryLogic::createMaybe()));
3081+
}
30723082
}
30733083
} else {
30743084
$scope = $scope->afterExtractCall();

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ private static function findTestFiles(): iterable
235235
yield __DIR__ . '/../Rules/Comparison/data/bug-5365.php';
236236
yield __DIR__ . '/../Rules/Comparison/data/bug-6551.php';
237237
yield __DIR__ . '/../Rules/Variables/data/bug-9403.php';
238+
yield __DIR__ . '/../Rules/Variables/data/bug-12364.php';
238239
yield __DIR__ . '/../Rules/Methods/data/bug-9542.php';
239240
yield __DIR__ . '/../Rules/Functions/data/bug-9803.php';
240241
yield __DIR__ . '/../Rules/PhpDoc/data/bug-10594.php';

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,24 @@ public function testIsStringNarrowsCertainty(): void
10111011
]);
10121012
}
10131013

1014+
public function testBug12364(): void
1015+
{
1016+
$this->cliArgumentsVariablesRegistered = true;
1017+
$this->polluteScopeWithLoopInitialAssignments = true;
1018+
$this->checkMaybeUndefinedVariables = true;
1019+
$this->polluteScopeWithAlwaysIterableForeach = true;
1020+
$this->analyse([__DIR__ . '/data/bug-12364.php'], [
1021+
[
1022+
'Variable $z might not be defined.',
1023+
20,
1024+
],
1025+
[
1026+
'Variable $z might not be defined.',
1027+
23,
1028+
],
1029+
]);
1030+
}
1031+
10141032
public function testDiscussion10252(): void
10151033
{
10161034
$this->cliArgumentsVariablesRegistered = true;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace Bug12364;
4+
5+
use PHPStan\TrinaryLogic;
6+
use function PHPStan\Testing\assertType;
7+
use function PHPStan\Testing\assertVariableCertainty;
8+
9+
/** @return array{x: string, y?: string, z?: string} */
10+
function foo(): array {
11+
return [ 'x' => 'foo' ];
12+
}
13+
14+
$x = $y = null;
15+
assertType('null', $x);
16+
assertType('null', $y);
17+
extract(foo());
18+
assertType('string', $x);
19+
assertType('string|null', $y); // <-- should be: null|string
20+
assertType('mixed', $z);
21+
assertVariableCertainty(TrinaryLogic::createYes(), $x);
22+
assertVariableCertainty(TrinaryLogic::createYes(), $y);
23+
assertVariableCertainty(TrinaryLogic::createMaybe(), $z);
24+
var_dump($x);
25+
var_dump($y); // <-- does exist
26+
27+
/** @return array{xx: string, yy?: string} */
28+
function foo2(): array {
29+
return [ 'xx' => 'foo' ];
30+
}
31+
32+
function testUndefined()
33+
{
34+
if (rand(0, 1)) {
35+
$xx = $yy = 0;
36+
assertType('0', $xx);
37+
assertType('0', $yy);
38+
}
39+
40+
extract(foo2());
41+
assertType('string', $xx);
42+
43+
if (isset($yy)) {
44+
assertType('0|string', $yy);
45+
}
46+
}

0 commit comments

Comments
 (0)