diff --git a/rules-tests/Strict/Rector/Empty_/DisallowedEmptyRuleFixerRector/Fixture/skip_untyped_property.php.inc b/rules-tests/Strict/Rector/Empty_/DisallowedEmptyRuleFixerRector/Fixture/skip_untyped_property.php.inc new file mode 100644 index 00000000000..41b813b2cd3 --- /dev/null +++ b/rules-tests/Strict/Rector/Empty_/DisallowedEmptyRuleFixerRector/Fixture/skip_untyped_property.php.inc @@ -0,0 +1,18 @@ +items); + } + + public function isNotEmpty() + { + return ! empty($this->items); + } +} diff --git a/rules/Strict/NodeAnalyzer/UninitializedPropertyAnalyzer.php b/rules/Strict/NodeAnalyzer/UninitializedPropertyAnalyzer.php index fdc4391bcf5..b73d90c5f3b 100644 --- a/rules/Strict/NodeAnalyzer/UninitializedPropertyAnalyzer.php +++ b/rules/Strict/NodeAnalyzer/UninitializedPropertyAnalyzer.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\StaticPropertyFetch; use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Property; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ThisType; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\NodeTypeResolver; @@ -22,7 +23,8 @@ public function __construct( private AstResolver $astResolver, private NodeTypeResolver $nodeTypeResolver, private ConstructorAssignDetector $constructorAssignDetector, - private NodeNameResolver $nodeNameResolver + private NodeNameResolver $nodeNameResolver, + private ReflectionProvider $reflectionProvider ) { } @@ -45,13 +47,20 @@ public function isUninitialized(Expr $expr): bool return false; } + $propertyName = (string) $this->nodeNameResolver->getName($expr); + + // fast-path: a typed property with an explicit default is always initialized, + // so we can skip the heavy class parsing below + if ($this->hasTypedDefaultProperty($className, $propertyName)) { + return false; + } + $classLike = $this->astResolver->resolveClassFromName($className); if (! $classLike instanceof ClassLike) { return false; } - $propertyName = (string) $this->nodeNameResolver->getName($expr); $property = $classLike->getProperty($propertyName); if (! $property instanceof Property) { @@ -68,4 +77,22 @@ public function isUninitialized(Expr $expr): bool return ! $this->constructorAssignDetector->isPropertyAssigned($classLike, $propertyName); } + + private function hasTypedDefaultProperty(string $className, string $propertyName): bool + { + if (! $this->reflectionProvider->hasClass($className)) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($className); + if (! $classReflection->hasNativeProperty($propertyName)) { + return false; + } + + $nativeReflectionProperty = $classReflection->getNativeProperty($propertyName) + ->getNativeReflection(); + + // an untyped property has an implicit null default, which is not an explicit initialization + return $nativeReflectionProperty->hasType() && $nativeReflectionProperty->hasDefaultValue(); + } }