Skip to content

Commit e300c7e

Browse files
Preserve variable certainty when narrowing the base of a nullsafe operator (#5870)
Co-authored-by: VincentLanglet <9052536+VincentLanglet@users.noreply.github.com>
1 parent 8698991 commit e300c7e

4 files changed

Lines changed: 125 additions & 2 deletions

File tree

src/Analyser/ExprHandler/Helper/NonNullabilityHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function ensureShallowNonNullability(MutatingScope $scope, Scope $origina
7070
$exprToSpecify,
7171
$exprTypeWithoutNull,
7272
TypeCombinator::removeNull($nativeType),
73-
TrinaryLogic::createYes(),
73+
$certainty,
7474
);
7575

7676
return new EnsuredNonNullabilityResult(

src/Analyser/NodeScopeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2653,7 +2653,7 @@ private function lookForExpressionCallback(MutatingScope $scope, Expr $expr, Clo
26532653

26542654
if ($expr instanceof ArrayDimFetch) {
26552655
$scope = $this->lookForExpressionCallback($scope, $expr->var, $callback);
2656-
} elseif ($expr instanceof PropertyFetch || $expr instanceof Expr\NullsafePropertyFetch) {
2656+
} elseif ($expr instanceof PropertyFetch || $expr instanceof Expr\NullsafePropertyFetch || $expr instanceof Expr\NullsafeMethodCall) {
26572657
$scope = $this->lookForExpressionCallback($scope, $expr->var, $callback);
26582658
} elseif ($expr instanceof StaticPropertyFetch && $expr->class instanceof Expr) {
26592659
$scope = $this->lookForExpressionCallback($scope, $expr->class, $callback);

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,33 @@ public function testNullsafeIsset(): void
757757
$this->analyse([__DIR__ . '/data/variable-nullsafe-isset.php'], []);
758758
}
759759

760+
#[RequiresPhp('>= 8.0')]
761+
public function testBug7291(): void
762+
{
763+
$this->cliArgumentsVariablesRegistered = true;
764+
$this->polluteScopeWithLoopInitialAssignments = false;
765+
$this->checkMaybeUndefinedVariables = true;
766+
$this->polluteScopeWithAlwaysIterableForeach = true;
767+
$this->analyse([__DIR__ . '/data/bug-7291.php'], [
768+
[
769+
'Variable $a might not be defined.',
770+
23,
771+
],
772+
[
773+
'Variable $b might not be defined.',
774+
32,
775+
],
776+
[
777+
'Variable $c might not be defined.',
778+
41,
779+
],
780+
[
781+
'Variable $d might not be defined.',
782+
50,
783+
],
784+
]);
785+
}
786+
760787
public function testBug1306(): void
761788
{
762789
$this->cliArgumentsVariablesRegistered = true;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug7291;
4+
5+
class Foo
6+
{
7+
8+
public ?Foo $foo = null;
9+
10+
public function bar(): self
11+
{
12+
return $this;
13+
}
14+
15+
}
16+
17+
function bareProp(): void
18+
{
19+
if (rand(0, 1)) {
20+
$a = rand(0, 1) ? new Foo() : null;
21+
}
22+
23+
echo $a?->foo; // warn
24+
}
25+
26+
function bareMethod(): void
27+
{
28+
if (rand(0, 1)) {
29+
$b = rand(0, 1) ? new Foo() : null;
30+
}
31+
32+
echo $b?->bar(); // warn
33+
}
34+
35+
function bareChain(): void
36+
{
37+
if (rand(0, 1)) {
38+
$c = rand(0, 1) ? new Foo() : null;
39+
}
40+
41+
echo $c?->bar()?->foo; // warn
42+
}
43+
44+
function notNullableStillWarns(): void
45+
{
46+
if (rand(0, 1)) {
47+
$d = new Foo();
48+
}
49+
50+
echo $d?->foo; // warn
51+
}
52+
53+
function propCoalesce(): void
54+
{
55+
if (rand(0, 1)) {
56+
$e = rand(0, 1) ? new Foo() : null;
57+
}
58+
59+
echo $e?->foo ?? 0; // no warn, ?? handles it
60+
}
61+
62+
function methodCoalesce(): void
63+
{
64+
if (rand(0, 1)) {
65+
$f = rand(0, 1) ? new Foo() : null;
66+
}
67+
68+
echo $f?->bar() ?? 0; // no warn, ?? handles it
69+
}
70+
71+
function propIsset(): void
72+
{
73+
if (rand(0, 1)) {
74+
$g = rand(0, 1) ? new Foo() : null;
75+
}
76+
77+
var_dump(isset($g?->foo)); // no warn, isset handles it
78+
}
79+
80+
function propEmpty(): void
81+
{
82+
if (rand(0, 1)) {
83+
$h = rand(0, 1) ? new Foo() : null;
84+
}
85+
86+
var_dump(empty($h?->foo)); // no warn, empty handles it
87+
}
88+
89+
function methodEmpty(): void
90+
{
91+
if (rand(0, 1)) {
92+
$i = rand(0, 1) ? new Foo() : null;
93+
}
94+
95+
var_dump(empty($i?->bar())); // no warn, empty handles it
96+
}

0 commit comments

Comments
 (0)