diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 2e1adcf1c68..f8f7767d7aa 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -182,15 +182,10 @@ static function (Type $type) use ($typeMessageCallback): ?string { } if (!$scope->hasExpressionType($expr)->yes()) { - if ($expr instanceof Node\Expr\PropertyFetch) { - return $this->checkUndefined($expr->var, $scope, $operatorDescription, $identifier); - } - - if ($expr->class instanceof Expr) { - return $this->checkUndefined($expr->class, $scope, $operatorDescription, $identifier); + $nativeReflection = $propertyReflection->getNativeReflection(); + if ($nativeReflection !== null && !$nativeReflection->getNativeReflection()->hasDefaultValue()) { + return null; } - - return null; } } diff --git a/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php b/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php index 93655fa68c7..091433436c3 100644 --- a/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayFindFunctionReturnTypeExtension.php @@ -37,8 +37,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return null; } - $arrayArg = $args[0]->value ?? null; - $callbackArg = $args[1]->value ?? null; + $arrayArg = $args[0]->value; + $callbackArg = $args[1]->value; $resultTypes = $this->arrayFilterFunctionReturnTypeHelper->getType($scope, $arrayArg, $callbackArg, null); $resultType = TypeCombinator::union(...array_map(static fn ($type) => $type->getIterableValueType(), $resultTypes->getArrays())); diff --git a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php index 873c89fb869..d14d6c24d84 100644 --- a/tests/PHPStan/Rules/Variables/EmptyRuleTest.php +++ b/tests/PHPStan/Rules/Variables/EmptyRuleTest.php @@ -223,6 +223,18 @@ public function testBug7806(): void $this->analyse([__DIR__ . '/data/bug-7806.php'], []); } + public function testBug14393(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-14393.php'], [ + [ + 'Variable $undefinedVar in empty() is never defined.', + 166, + ], + ]); + } + #[RequiresPhp('>= 8.0')] public function testIssetAfterRememberedConstructor(): void { diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 11f2b39f1fd..fd841e49b16 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -202,13 +202,10 @@ public function testNativePropertyTypes(): void { $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/isset-native-property-types.php'], [ - /*[ - // no way to achieve this with current PHP Reflection API - // There's ReflectionClass::getDefaultProperties() - // but it cannot differentiate between `public int $foo` and `public int $foo = null`; + [ 'Property IssetNativePropertyTypes\Foo::$hasDefaultValue (int) in isset() is not nullable.', 17, - ],*/ + ], [ 'Property IssetNativePropertyTypes\Foo::$isAssignedBefore (int) in isset() is not nullable.', 20, @@ -529,4 +526,40 @@ public function testBug9503(): void $this->analyse([__DIR__ . '/data/bug-9503.php'], []); } + public function testBug14393(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-14393.php'], [ + [ + 'Property Bug14393\MyClass::$i (int) in isset() is not nullable.', + 12, + ], + [ + 'Property Bug14393\MyClassPhpDoc::$i (int) in isset() is not nullable.', + 37, + ], + [ + 'Property Bug14393\MyClass::$i (int) in isset() is not nullable.', + 81, + ], + [ + 'Property Bug14393\MyClassPhpDoc::$i (int) in isset() is not nullable.', + 93, + ], + [ + 'Static property Bug14393\MyClassStatic::$i (int) in isset() is not nullable.', + 121, + ], + [ + 'Static property Bug14393\MyClassStatic::$i (int) in isset() is not nullable.', + 151, + ], + [ + 'Variable $undefinedVar in isset() is never defined.', + 165, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 32cfa69d97b..d6a06d85810 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -377,4 +377,48 @@ public function testBug13921(): void ]); } + public function testBug4846(): void + { + $this->analyse([__DIR__ . '/data/bug-4846.php'], [ + [ + 'Property Bug4846\Foo::$alwaysString (string) on left side of ?? is not nullable.', + 13, + ], + ]); + } + + public function testBug14393(): void + { + $this->analyse([__DIR__ . '/data/bug-14393.php'], [ + [ + 'Property Bug14393\MyClass::$i (int) on left side of ?? is not nullable.', + 11, + ], + [ + 'Property Bug14393\MyClassPhpDoc::$i (int) on left side of ?? is not nullable.', + 36, + ], + [ + 'Property Bug14393\MyClass::$i (int) on left side of ?? is not nullable.', + 80, + ], + [ + 'Property Bug14393\MyClassPhpDoc::$i (int) on left side of ?? is not nullable.', + 92, + ], + [ + 'Static property Bug14393\MyClassStatic::$i (int) on left side of ?? is not nullable.', + 120, + ], + [ + 'Static property Bug14393\MyClassStatic::$i (int) on left side of ?? is not nullable.', + 150, + ], + [ + 'Variable $undefinedVar on left side of ?? is never defined.', + 164, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-14393.php b/tests/PHPStan/Rules/Variables/data/bug-14393.php new file mode 100644 index 00000000000..c03ca903c33 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-14393.php @@ -0,0 +1,167 @@ +i ?? -1); + var_dump(isset($this->i)); + var_dump(empty($this->i)); + } +} + +class MyClassUninitialized +{ + public int $i; + + public function doFoo(): void + { + var_dump($this->i ?? -1); + var_dump(isset($this->i)); + var_dump(empty($this->i)); + } +} + +class MyClassPhpDoc +{ + /** @var int */ + public $i; + + public function doFoo(): void + { + var_dump($this->i ?? -1); + var_dump(isset($this->i)); + var_dump(empty($this->i)); + } +} + +class MyClassNullable +{ + public ?int $i; + + public function doFoo(): void + { + var_dump($this->i ?? -1); + var_dump(isset($this->i)); + var_dump(empty($this->i)); + } +} + +class MyClassNullableWithDefault +{ + public ?int $i = 10; + + public function doFoo(): void + { + var_dump($this->i ?? -1); + var_dump(isset($this->i)); + var_dump(empty($this->i)); + } +} + +class MyClassNullableWithNullDefault +{ + public ?int $i = null; + + public function doFoo(): void + { + var_dump($this->i ?? -1); + var_dump(isset($this->i)); + var_dump(empty($this->i)); + } +} + +$o = new MyClass(); + +var_dump($o->i ?? -1); +var_dump(isset($o->i)); +var_dump(empty($o->i)); + +$o2 = new MyClassUninitialized(); + +var_dump($o2->i ?? -1); +var_dump(isset($o2->i)); +var_dump(empty($o2->i)); + +$o3 = new MyClassPhpDoc(); + +var_dump($o3->i ?? -1); +var_dump(isset($o3->i)); +var_dump(empty($o3->i)); + +$o4 = new MyClassNullable(); + +var_dump($o4->i ?? -1); +var_dump(isset($o4->i)); +var_dump(empty($o4->i)); + +$o5 = new MyClassNullableWithDefault(); + +var_dump($o5->i ?? -1); +var_dump(isset($o5->i)); +var_dump(empty($o5->i)); + +$o6 = new MyClassNullableWithNullDefault(); + +var_dump($o6->i ?? -1); +var_dump(isset($o6->i)); +var_dump(empty($o6->i)); + +class MyClassStatic +{ + public static int $i = 10; + + public function doFoo(): void + { + var_dump(self::$i ?? -1); + var_dump(isset(self::$i)); + var_dump(empty(self::$i)); + } +} + +class MyClassStaticUninitialized +{ + public static int $i; + + public function doFoo(): void + { + var_dump(self::$i ?? -1); + var_dump(isset(self::$i)); + var_dump(empty(self::$i)); + } +} + +class MyClassStaticNullable +{ + public static ?int $i = null; + + public function doFoo(): void + { + var_dump(self::$i ?? -1); + var_dump(isset(self::$i)); + var_dump(empty(self::$i)); + } +} + +var_dump(MyClassStatic::$i ?? -1); +var_dump(isset(MyClassStatic::$i)); +var_dump(empty(MyClassStatic::$i)); + +var_dump(MyClassStaticUninitialized::$i ?? -1); +var_dump(isset(MyClassStaticUninitialized::$i)); +var_dump(empty(MyClassStaticUninitialized::$i)); + +var_dump(MyClassStaticNullable::$i ?? -1); +var_dump(isset(MyClassStaticNullable::$i)); +var_dump(empty(MyClassStaticNullable::$i)); + +// Test undefined variable with native-typed property access +function testUndefinedVar(): void { + var_dump($undefinedVar->i ?? -1); + var_dump(isset($undefinedVar->i)); + var_dump(empty($undefinedVar->i)); +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-4846.php b/tests/PHPStan/Rules/Variables/data/bug-4846.php new file mode 100644 index 00000000000..a6fbb2e660a --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-4846.php @@ -0,0 +1,16 @@ +alwaysString ?? 'string'; + + echo $foo->nullableString ?? 'string'; +};