Skip to content

Commit d3e3da3

Browse files
authored
Fix spurious error nullCoalesce.offset (#5145)
1 parent 6e91a8c commit d3e3da3

File tree

6 files changed

+98
-7
lines changed

6 files changed

+98
-7
lines changed

src/Analyser/ExprHandler/Helper/NonNullabilityHelper.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function ensureShallowNonNullability(MutatingScope $scope, Scope $origina
4242
$originalNativeType = $originalScope->getNativeType($exprToSpecify);
4343

4444
return new EnsuredNonNullabilityResult($scope, [
45-
new EnsuredNonNullabilityResultExpression($exprToSpecify, $originalExprType, $originalNativeType, $certainty),
45+
new EnsuredNonNullabilityResultExpression($exprToSpecify, $originalExprType, $originalNativeType, $hasExpressionType),
4646
]);
4747
}
4848
return new EnsuredNonNullabilityResult($scope, []);
@@ -55,16 +55,11 @@ public function ensureShallowNonNullability(MutatingScope $scope, Scope $origina
5555
// To properly revert this, we must also save and restore the parent expression's type.
5656
if ($exprToSpecify instanceof Expr\ArrayDimFetch && $exprToSpecify->dim !== null) {
5757
$parentExpr = $exprToSpecify->var;
58-
$parentCertainty = TrinaryLogic::createYes();
59-
$hasParentExpressionType = $originalScope->hasExpressionType($parentExpr);
60-
if (!$hasParentExpressionType->no()) {
61-
$parentCertainty = $hasParentExpressionType;
62-
}
6358
$specifiedExpressions[] = new EnsuredNonNullabilityResultExpression(
6459
$parentExpr,
6560
$scope->getType($parentExpr),
6661
$scope->getNativeType($parentExpr),
67-
$parentCertainty,
62+
$originalScope->hasExpressionType($parentExpr),
6863
);
6964
}
7065

@@ -104,6 +99,10 @@ public function ensureNonNullability(MutatingScope $scope, Expr $expr): EnsuredN
10499
public function revertNonNullability(MutatingScope $scope, array $specifiedExpressions): MutatingScope
105100
{
106101
foreach ($specifiedExpressions as $specifiedExpressionResult) {
102+
if ($specifiedExpressionResult->getCertainty()->no()) {
103+
$scope = $scope->invalidateExpression($specifiedExpressionResult->getExpression());
104+
continue;
105+
}
107106
$scope = $scope->specifyExpressionType(
108107
$specifiedExpressionResult->getExpression(),
109108
$specifiedExpressionResult->getOriginalType(),

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ private static function findTestFiles(): iterable
253253
yield __DIR__ . '/data/dio-functions.php';
254254
}
255255

256+
yield __DIR__ . '/../Rules/Variables/data/bug-13921.php';
256257
yield __DIR__ . '/../Rules/Arrays/data/bug-14234.php';
257258
yield __DIR__ . '/../Rules/Arrays/data/bug-11679.php';
258259
yield __DIR__ . '/../Rules/Methods/data/bug-4801.php';

tests/PHPStan/Rules/Comparison/BooleanOrConstantConditionRuleTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,4 +371,17 @@ public function testBug4004(): void
371371
$this->analyse([__DIR__ . '/data/bug-4004.php'], []);
372372
}
373373

374+
#[RequiresPhp('>= 8.0')]
375+
public function testBug10305(): void
376+
{
377+
$this->treatPhpDocTypesAsCertain = true;
378+
379+
$this->analyse([__DIR__ . '/data/bug-10305.php'], [
380+
[
381+
'Result of || is always true.',
382+
10,
383+
],
384+
]);
385+
}
386+
374387
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug10305;
4+
5+
class HelloWorld
6+
{
7+
public null|string $prop;
8+
9+
public function isPropertySet(): bool {
10+
return isset($this->prop) || null === $this->prop;
11+
}
12+
}

tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,4 +362,14 @@ public function testBug14213(): void
362362
]);
363363
}
364364

365+
public function testBug13921(): void
366+
{
367+
$this->analyse([__DIR__ . '/data/bug-13921.php'], [
368+
[
369+
'Offset 0 on non-empty-list<array<string|null>> on left side of ?? always exists and is not nullable.',
370+
19,
371+
],
372+
]);
373+
}
374+
365375
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Bug13921;
4+
5+
use SimpleXMLElement;
6+
use function PHPStan\Testing\assertType;
7+
8+
/** @param list<array<?string>> $x */
9+
function foo(array $x): void {
10+
var_dump($x[0]['bar'] ?? null);
11+
assertType("list<array<string|null>>", $x);
12+
var_dump($x[0] ?? null);
13+
}
14+
15+
/** @param non-empty-list<array<?string>> $x */
16+
function nonEmptyFoo(array $x): void {
17+
var_dump($x[0]['bar'] ?? null);
18+
assertType("non-empty-list<array<string|null>>", $x);
19+
var_dump($x[0] ?? null);
20+
}
21+
22+
/** @param list<array<?string>> $x */
23+
function bar(array $x): void {
24+
var_dump($x[0] ?? null);
25+
assertType("list<array<string|null>>", $x);
26+
var_dump($x[0]['bar'] ?? null);
27+
}
28+
29+
/** @param list<array<?string>> $x */
30+
function baz(array $x): void {
31+
var_dump($x[1] ?? null);
32+
assertType("list<array<string|null>>", $x);
33+
var_dump($x[0]['bar'] ?? null);
34+
}
35+
36+
/** @param list<array<?string>> $x */
37+
function boo(array $x): void {
38+
var_dump($x[0]['bar'] ?? null);
39+
assertType("list<array<string|null>>", $x);
40+
var_dump($x[1] ?? null);
41+
}
42+
43+
function doBar(array $array)
44+
{
45+
if (isset($array['foo'])) {
46+
assertType("mixed~null", $array['foo']);
47+
assertType("non-empty-array&hasOffsetValue('foo', mixed~null)", $array);
48+
}
49+
}
50+
51+
/** @param list<SimpleXMLElement> $x */
52+
function sooSimpleElement(array $x): void {
53+
var_dump($x[0]['bar'] ?? null);
54+
assertType("list<SimpleXMLElement>", $x);
55+
var_dump($x[0] ?? null);
56+
}

0 commit comments

Comments
 (0)