Skip to content

Commit e9c208d

Browse files
committed
Merge branch 2.1.x into 2.2.x
2 parents dc813fe + 2f58f5f commit e9c208d

File tree

7 files changed

+282
-15
lines changed

7 files changed

+282
-15
lines changed

src/Rules/IssetCheck.php

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,10 @@ static function (Type $type) use ($typeMessageCallback): ?string {
182182
}
183183

184184
if (!$scope->hasExpressionType($expr)->yes()) {
185-
if ($expr instanceof Node\Expr\PropertyFetch) {
186-
return $this->checkUndefined($expr->var, $scope, $operatorDescription, $identifier);
187-
}
188-
189-
if ($expr->class instanceof Expr) {
190-
return $this->checkUndefined($expr->class, $scope, $operatorDescription, $identifier);
185+
$nativeReflection = $propertyReflection->getNativeReflection();
186+
if ($nativeReflection !== null && !$nativeReflection->getNativeReflection()->hasDefaultValue()) {
187+
return null;
191188
}
192-
193-
return null;
194189
}
195190
}
196191

src/Type/Php/ArrayFindFunctionReturnTypeExtension.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3737
return null;
3838
}
3939

40-
$arrayArg = $args[0]->value ?? null;
41-
$callbackArg = $args[1]->value ?? null;
40+
$arrayArg = $args[0]->value;
41+
$callbackArg = $args[1]->value;
4242

4343
$resultTypes = $this->arrayFilterFunctionReturnTypeHelper->getType($scope, $arrayArg, $callbackArg, null);
4444
$resultType = TypeCombinator::union(...array_map(static fn ($type) => $type->getIterableValueType(), $resultTypes->getArrays()));

tests/PHPStan/Rules/Variables/EmptyRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,18 @@ public function testBug7806(): void
223223
$this->analyse([__DIR__ . '/data/bug-7806.php'], []);
224224
}
225225

226+
public function testBug14393(): void
227+
{
228+
$this->treatPhpDocTypesAsCertain = true;
229+
230+
$this->analyse([__DIR__ . '/data/bug-14393.php'], [
231+
[
232+
'Variable $undefinedVar in empty() is never defined.',
233+
166,
234+
],
235+
]);
236+
}
237+
226238
#[RequiresPhp('>= 8.0')]
227239
public function testIssetAfterRememberedConstructor(): void
228240
{

tests/PHPStan/Rules/Variables/IssetRuleTest.php

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,10 @@ public function testNativePropertyTypes(): void
202202
{
203203
$this->treatPhpDocTypesAsCertain = true;
204204
$this->analyse([__DIR__ . '/data/isset-native-property-types.php'], [
205-
/*[
206-
// no way to achieve this with current PHP Reflection API
207-
// There's ReflectionClass::getDefaultProperties()
208-
// but it cannot differentiate between `public int $foo` and `public int $foo = null`;
205+
[
209206
'Property IssetNativePropertyTypes\Foo::$hasDefaultValue (int) in isset() is not nullable.',
210207
17,
211-
],*/
208+
],
212209
[
213210
'Property IssetNativePropertyTypes\Foo::$isAssignedBefore (int) in isset() is not nullable.',
214211
20,
@@ -529,4 +526,40 @@ public function testBug9503(): void
529526
$this->analyse([__DIR__ . '/data/bug-9503.php'], []);
530527
}
531528

529+
public function testBug14393(): void
530+
{
531+
$this->treatPhpDocTypesAsCertain = true;
532+
533+
$this->analyse([__DIR__ . '/data/bug-14393.php'], [
534+
[
535+
'Property Bug14393\MyClass::$i (int) in isset() is not nullable.',
536+
12,
537+
],
538+
[
539+
'Property Bug14393\MyClassPhpDoc::$i (int) in isset() is not nullable.',
540+
37,
541+
],
542+
[
543+
'Property Bug14393\MyClass::$i (int) in isset() is not nullable.',
544+
81,
545+
],
546+
[
547+
'Property Bug14393\MyClassPhpDoc::$i (int) in isset() is not nullable.',
548+
93,
549+
],
550+
[
551+
'Static property Bug14393\MyClassStatic::$i (int) in isset() is not nullable.',
552+
121,
553+
],
554+
[
555+
'Static property Bug14393\MyClassStatic::$i (int) in isset() is not nullable.',
556+
151,
557+
],
558+
[
559+
'Variable $undefinedVar in isset() is never defined.',
560+
165,
561+
],
562+
]);
563+
}
564+
532565
}

tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,4 +377,48 @@ public function testBug13921(): void
377377
]);
378378
}
379379

380+
public function testBug4846(): void
381+
{
382+
$this->analyse([__DIR__ . '/data/bug-4846.php'], [
383+
[
384+
'Property Bug4846\Foo::$alwaysString (string) on left side of ?? is not nullable.',
385+
13,
386+
],
387+
]);
388+
}
389+
390+
public function testBug14393(): void
391+
{
392+
$this->analyse([__DIR__ . '/data/bug-14393.php'], [
393+
[
394+
'Property Bug14393\MyClass::$i (int) on left side of ?? is not nullable.',
395+
11,
396+
],
397+
[
398+
'Property Bug14393\MyClassPhpDoc::$i (int) on left side of ?? is not nullable.',
399+
36,
400+
],
401+
[
402+
'Property Bug14393\MyClass::$i (int) on left side of ?? is not nullable.',
403+
80,
404+
],
405+
[
406+
'Property Bug14393\MyClassPhpDoc::$i (int) on left side of ?? is not nullable.',
407+
92,
408+
],
409+
[
410+
'Static property Bug14393\MyClassStatic::$i (int) on left side of ?? is not nullable.',
411+
120,
412+
],
413+
[
414+
'Static property Bug14393\MyClassStatic::$i (int) on left side of ?? is not nullable.',
415+
150,
416+
],
417+
[
418+
'Variable $undefinedVar on left side of ?? is never defined.',
419+
164,
420+
],
421+
]);
422+
}
423+
380424
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14393;
4+
5+
class MyClass
6+
{
7+
public int $i = 10;
8+
9+
public function doFoo(): void
10+
{
11+
var_dump($this->i ?? -1);
12+
var_dump(isset($this->i));
13+
var_dump(empty($this->i));
14+
}
15+
}
16+
17+
class MyClassUninitialized
18+
{
19+
public int $i;
20+
21+
public function doFoo(): void
22+
{
23+
var_dump($this->i ?? -1);
24+
var_dump(isset($this->i));
25+
var_dump(empty($this->i));
26+
}
27+
}
28+
29+
class MyClassPhpDoc
30+
{
31+
/** @var int */
32+
public $i;
33+
34+
public function doFoo(): void
35+
{
36+
var_dump($this->i ?? -1);
37+
var_dump(isset($this->i));
38+
var_dump(empty($this->i));
39+
}
40+
}
41+
42+
class MyClassNullable
43+
{
44+
public ?int $i;
45+
46+
public function doFoo(): void
47+
{
48+
var_dump($this->i ?? -1);
49+
var_dump(isset($this->i));
50+
var_dump(empty($this->i));
51+
}
52+
}
53+
54+
class MyClassNullableWithDefault
55+
{
56+
public ?int $i = 10;
57+
58+
public function doFoo(): void
59+
{
60+
var_dump($this->i ?? -1);
61+
var_dump(isset($this->i));
62+
var_dump(empty($this->i));
63+
}
64+
}
65+
66+
class MyClassNullableWithNullDefault
67+
{
68+
public ?int $i = null;
69+
70+
public function doFoo(): void
71+
{
72+
var_dump($this->i ?? -1);
73+
var_dump(isset($this->i));
74+
var_dump(empty($this->i));
75+
}
76+
}
77+
78+
$o = new MyClass();
79+
80+
var_dump($o->i ?? -1);
81+
var_dump(isset($o->i));
82+
var_dump(empty($o->i));
83+
84+
$o2 = new MyClassUninitialized();
85+
86+
var_dump($o2->i ?? -1);
87+
var_dump(isset($o2->i));
88+
var_dump(empty($o2->i));
89+
90+
$o3 = new MyClassPhpDoc();
91+
92+
var_dump($o3->i ?? -1);
93+
var_dump(isset($o3->i));
94+
var_dump(empty($o3->i));
95+
96+
$o4 = new MyClassNullable();
97+
98+
var_dump($o4->i ?? -1);
99+
var_dump(isset($o4->i));
100+
var_dump(empty($o4->i));
101+
102+
$o5 = new MyClassNullableWithDefault();
103+
104+
var_dump($o5->i ?? -1);
105+
var_dump(isset($o5->i));
106+
var_dump(empty($o5->i));
107+
108+
$o6 = new MyClassNullableWithNullDefault();
109+
110+
var_dump($o6->i ?? -1);
111+
var_dump(isset($o6->i));
112+
var_dump(empty($o6->i));
113+
114+
class MyClassStatic
115+
{
116+
public static int $i = 10;
117+
118+
public function doFoo(): void
119+
{
120+
var_dump(self::$i ?? -1);
121+
var_dump(isset(self::$i));
122+
var_dump(empty(self::$i));
123+
}
124+
}
125+
126+
class MyClassStaticUninitialized
127+
{
128+
public static int $i;
129+
130+
public function doFoo(): void
131+
{
132+
var_dump(self::$i ?? -1);
133+
var_dump(isset(self::$i));
134+
var_dump(empty(self::$i));
135+
}
136+
}
137+
138+
class MyClassStaticNullable
139+
{
140+
public static ?int $i = null;
141+
142+
public function doFoo(): void
143+
{
144+
var_dump(self::$i ?? -1);
145+
var_dump(isset(self::$i));
146+
var_dump(empty(self::$i));
147+
}
148+
}
149+
150+
var_dump(MyClassStatic::$i ?? -1);
151+
var_dump(isset(MyClassStatic::$i));
152+
var_dump(empty(MyClassStatic::$i));
153+
154+
var_dump(MyClassStaticUninitialized::$i ?? -1);
155+
var_dump(isset(MyClassStaticUninitialized::$i));
156+
var_dump(empty(MyClassStaticUninitialized::$i));
157+
158+
var_dump(MyClassStaticNullable::$i ?? -1);
159+
var_dump(isset(MyClassStaticNullable::$i));
160+
var_dump(empty(MyClassStaticNullable::$i));
161+
162+
// Test undefined variable with native-typed property access
163+
function testUndefinedVar(): void {
164+
var_dump($undefinedVar->i ?? -1);
165+
var_dump(isset($undefinedVar->i));
166+
var_dump(empty($undefinedVar->i));
167+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug4846;
4+
5+
class Foo
6+
{
7+
public string $alwaysString = '';
8+
9+
public ?string $nullableString = null;
10+
}
11+
12+
function (Foo $foo): void {
13+
echo $foo->alwaysString ?? 'string';
14+
15+
echo $foo->nullableString ?? 'string';
16+
};

0 commit comments

Comments
 (0)