Skip to content

Commit e9343d7

Browse files
committed
Cover EmptyRule
1 parent 4e1889b commit e9343d7

File tree

5 files changed

+87
-16
lines changed

5 files changed

+87
-16
lines changed

src/Rules/IssetCheck.php

+9-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use PHPStan\Rules\Properties\PropertyReflectionFinder;
1111
use PHPStan\Type\NeverType;
1212
use PHPStan\Type\Type;
13-
use PHPStan\Type\TypeCombinator;
1413
use PHPStan\Type\VerbosityLevel;
1514
use function is_string;
1615
use function sprintf;
@@ -150,16 +149,20 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str
150149
&& $expr->name instanceof Node\Identifier
151150
&& $expr->var instanceof Expr\Variable
152151
&& $expr->var->name === 'this'
153-
&& !TypeCombinator::containsNull($propertyReflection->getNativeType())
152+
&& $propertyReflection->getNativeType()->isNull()->no()
154153
&& $scope->hasExpressionType(new PropertyInitializationExpr($propertyReflection->getName()))->yes()
155154
) {
156-
return RuleErrorBuilder::message(
155+
return $this->generateError(
156+
$propertyReflection->getNativeType(),
157157
sprintf(
158-
'%s cannot be null or uninitialized %s.',
158+
'%s %s',
159159
$this->propertyDescriptor->describeProperty($propertyReflection, $scope, $expr),
160160
$operatorDescription,
161161
),
162-
)->identifier(sprintf('%s.neverNullOrUninitialized', $identifier))->build();
162+
$typeMessageCallback,
163+
$identifier,
164+
'propertyNeverNullOrUninitialized',
165+
);
163166
}
164167

165168
if (!$scope->hasExpressionType($expr)->yes()) {
@@ -299,7 +302,7 @@ private function checkUndefined(Expr $expr, Scope $scope, string $operatorDescri
299302
/**
300303
* @param callable(Type): ?string $typeMessageCallback
301304
* @param ErrorIdentifier $identifier
302-
* @param 'variable'|'offset'|'property'|'expr' $identifierSecondPart
305+
* @param 'variable'|'offset'|'property'|'expr'|'propertyNeverNullOrUninitialized' $identifierSecondPart
303306
*/
304307
private function generateError(Type $type, string $message, callable $typeMessageCallback, string $identifier, string $identifierSecondPart): ?IdentifierRuleError
305308
{

tests/PHPStan/Rules/Variables/EmptyRuleTest.php

+21
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ protected function shouldTreatPhpDocTypesAsCertain(): bool
3232
return $this->treatPhpDocTypesAsCertain;
3333
}
3434

35+
public function shouldNarrowMethodScopeFromConstructor(): bool
36+
{
37+
return true;
38+
}
39+
3540
public function testRule(): void
3641
{
3742
$this->treatPhpDocTypesAsCertain = true;
@@ -206,4 +211,20 @@ public function testBug12658(): void
206211
$this->analyse([__DIR__ . '/data/bug-12658.php'], []);
207212
}
208213

214+
public function testIssetAfterRememberedConstructor(): void
215+
{
216+
$this->treatPhpDocTypesAsCertain = true;
217+
218+
$this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [
219+
[
220+
'Property IssetOrCoalesceOnNonNullableInitializedProperty\MoreEmptyCases::$false in empty() is always falsy.',
221+
93,
222+
],
223+
[
224+
'Property IssetOrCoalesceOnNonNullableInitializedProperty\MoreEmptyCases::$true in empty() is not falsy.',
225+
95,
226+
],
227+
]);
228+
}
229+
209230
}

tests/PHPStan/Rules/Variables/IssetRuleTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,8 @@ public function testIssetAfterRememberedConstructor(): void
491491

492492
$this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [
493493
[
494-
'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string cannot be null or uninitialized in isset().',
495-
36,
494+
'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string in isset() is not nullable.',
495+
34,
496496
],
497497
]);
498498
}

tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -367,8 +367,8 @@ public function testIssetAfterRememberedConstructor(): void
367367

368368
$this->analyse([__DIR__ . '/data/isset-after-remembered-constructor.php'], [
369369
[
370-
'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string cannot be null or uninitialized on left side of ??.',
371-
48,
370+
'Property IssetOrCoalesceOnNonNullableInitializedProperty\User::$string on left side of ?? is not nullable.',
371+
46,
372372
],
373373
]);
374374
}

tests/PHPStan/Rules/Variables/data/isset-after-remembered-constructor.php

+53-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
<?php // lint >= 7.4
1+
<?php // lint >= 8.2
22

33
namespace IssetOrCoalesceOnNonNullableInitializedProperty;
44

5-
use function PHPStan\debugScope;
6-
75
class User
86
{
97
private ?string $nullableString;
@@ -12,9 +10,9 @@ class User
1210

1311
private $untyped;
1412

15-
public function __construct(
16-
) {
17-
if (rand(0,1)) {
13+
public function __construct()
14+
{
15+
if (rand(0, 1)) {
1816
$this->nullableString = 'hello';
1917
$this->string = 'world';
2018
$this->maybeUninitializedString = 'something';
@@ -48,4 +46,53 @@ public function doBar(): void
4846
echo $this->string ?? 'default';
4947
echo $this->untyped ?? 'default';
5048
}
49+
50+
public function doFooBar(): void
51+
{
52+
if (empty($this->maybeUninitializedString)) {
53+
echo $this->maybeUninitializedString;
54+
}
55+
if (empty($this->nullableString)) {
56+
echo $this->nullableString;
57+
}
58+
if (empty($this->string)) {
59+
echo $this->string;
60+
}
61+
if (empty($this->untyped)) {
62+
echo $this->untyped;
63+
}
64+
}
65+
}
66+
67+
class MoreEmptyCases
68+
{
69+
private false|string $union;
70+
private false $false;
71+
private true $true;
72+
private bool $bool;
73+
74+
public function __construct()
75+
{
76+
if (rand(0, 1)) {
77+
$this->union = 'nope';
78+
$this->bool = true;
79+
} elseif (rand(10, 20)) {
80+
$this->union = false;
81+
$this->bool = false;
82+
}
83+
$this->false = false;
84+
$this->true = true;
85+
}
86+
87+
public function doFoo(): void
88+
{
89+
if (empty($this->union)) {
90+
}
91+
if (empty($this->bool)) {
92+
}
93+
if (empty($this->false)) {
94+
}
95+
if (empty($this->true)) {
96+
}
97+
}
5198
}

0 commit comments

Comments
 (0)