From 68430754fa4f20b0023dd28fd0b31731f317309e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 28 Mar 2025 12:34:07 +0100 Subject: [PATCH 01/12] WIP Instance vs Static properties --- src/Reflection/ClassReflection.php | 163 +++++++++++++++++- .../Php/PhpClassReflectionExtension.php | 42 ++++- ...endsPropertiesClassReflectionExtension.php | 96 ++++++++++- ...PropertiesClassReflectionExtensionTest.php | 4 +- .../Annotations/DeprecatedAnnotationsTest.php | 12 +- .../Annotations/InternalAnnotationsTest.php | 15 +- 6 files changed, 318 insertions(+), 14 deletions(-) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 05a9f9b6a6..4ca5db6faa 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -81,6 +81,12 @@ final class ClassReflection /** @var ExtendedPropertyReflection[] */ private array $properties = []; + /** @var ExtendedPropertyReflection[] */ + private array $instanceProperties = []; + + /** @var ExtendedPropertyReflection[] */ + private array $staticProperties = []; + /** @var RealClassClassConstantReflection[] */ private array $constants = []; @@ -147,6 +153,12 @@ final class ClassReflection /** @var array */ private array $hasPropertyCache = []; + /** @var array */ + private array $hasInstancePropertyCache = []; + + /** @var array */ + private array $hasStaticPropertyCache = []; + /** * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions @@ -460,6 +472,9 @@ private function allowsDynamicPropertiesExtensions(): bool return false; } + /** + * @deprecated Use hasInstanceProperty or hasStaticProperty instead + */ public function hasProperty(string $propertyName): bool { if (array_key_exists($propertyName, $this->hasPropertyCache)) { @@ -479,6 +494,11 @@ public function hasProperty(string $propertyName): bool } } + // For BC purpose + if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) { + return $this->hasPropertyCache[$propertyName] = true; + } + if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) { return $this->hasPropertyCache[$propertyName] = true; } @@ -486,6 +506,49 @@ public function hasProperty(string $propertyName): bool return $this->hasPropertyCache[$propertyName] = false; } + public function hasInstanceProperty(string $propertyName): bool + { + if (array_key_exists($propertyName, $this->hasInstancePropertyCache)) { + return $this->hasInstancePropertyCache[$propertyName]; + } + + if ($this->isEnum()) { + return $this->hasInstancePropertyCache[$propertyName] = $this->hasNativeProperty($propertyName); + } + + foreach ($this->propertiesClassReflectionExtensions as $i => $extension) { + if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) { + break; + } + if ($extension->hasProperty($this, $propertyName)) { + return $this->hasInstancePropertyCache[$propertyName] = true; + } + } + + if ($this->requireExtendsPropertiesClassReflectionExtension->hasInstanceProperty($this, $propertyName)) { + return $this->hasPropertyCache[$propertyName] = true; + } + + return $this->hasPropertyCache[$propertyName] = false; + } + + public function hasStaticProperty(string $propertyName): bool + { + if (array_key_exists($propertyName, $this->hasStaticPropertyCache)) { + return $this->hasStaticPropertyCache[$propertyName]; + } + + if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) { + return $this->hasStaticPropertyCache[$propertyName] = true; + } + + if ($this->requireExtendsPropertiesClassReflectionExtension->hasStaticProperty($this, $propertyName)) { + return $this->hasStaticPropertyCache[$propertyName] = true; + } + + return $this->hasStaticPropertyCache[$propertyName] = false; + } + public function hasMethod(string $methodName): bool { if (array_key_exists($methodName, $this->hasMethodCache)) { @@ -630,6 +693,20 @@ public function evictPrivateSymbols(): void unset($this->properties[$name]); } + foreach ($this->instanceProperties as $name => $property) { + if (!$property->isPrivate()) { + continue; + } + + unset($this->instanceProperties[$name]); + } + foreach ($this->staticProperties as $name => $property) { + if (!$property->isPrivate()) { + continue; + } + + unset($this->staticProperties[$name]); + } foreach ($this->methods as $name => $method) { if (!$method->isPrivate()) { continue; @@ -640,6 +717,7 @@ public function evictPrivateSymbols(): void $this->getPhpExtension()->evictPrivateSymbols($this->getCacheKey()); } + /** @deprecated Use getInstanceProperty or getStaticProperty */ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { if ($this->isEnum()) { @@ -669,6 +747,15 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco } } + // For BC purpose + if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) { + $property = $this->wrapExtendedProperty($this->getPhpExtension()->getStaticProperty($this, $propertyName)); + if ($scope->canReadProperty($property)) { + return $this->properties[$key] = $property; + } + $this->properties[$key] = $property; + } + if (!isset($this->properties[$key])) { if ($this->requireExtendsPropertiesClassReflectionExtension->hasProperty($this, $propertyName)) { $property = $this->requireExtendsPropertiesClassReflectionExtension->getProperty($this, $propertyName); @@ -683,9 +770,83 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco return $this->properties[$key]; } + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + if ($this->isEnum()) { + return $this->getNativeProperty($propertyName); + } + + $key = $propertyName; + if ($scope->isInClass()) { + $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey()); + } + + if (!isset($this->instanceProperties[$key])) { + foreach ($this->propertiesClassReflectionExtensions as $i => $extension) { + if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) { + break; + } + + if (!$extension->hasProperty($this, $propertyName)) { + continue; + } + + $property = $this->wrapExtendedProperty($extension->getProperty($this, $propertyName)); + if ($scope->canReadProperty($property)) { + return $this->instanceProperties[$key] = $property; + } + $this->instanceProperties[$key] = $property; + } + } + + if (!isset($this->instanceProperties[$key])) { + if ($this->requireExtendsPropertiesClassReflectionExtension->hasInstanceProperty($this, $propertyName)) { + $property = $this->requireExtendsPropertiesClassReflectionExtension->getInstanceProperty($this, $propertyName); + $this->instanceProperties[$key] = $property; + } + } + + if (!isset($this->instanceProperties[$key])) { + throw new MissingPropertyFromReflectionException($this->getName(), $propertyName); + } + + return $this->instanceProperties[$key]; + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + $key = $propertyName; + if ($scope->isInClass()) { + $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey()); + } + + if (!isset($this->staticProperties[$key])) { + if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) { + $property = $this->wrapExtendedProperty($this->getPhpExtension()->getStaticProperty($this, $propertyName)); + if ($scope->canReadProperty($property)) { + return $this->staticProperties[$key] = $property; + } + $this->staticProperties[$key] = $property; + } + } + + if (!isset($this->staticProperties[$key])) { + if ($this->requireExtendsPropertiesClassReflectionExtension->hasStaticProperty($this, $propertyName)) { + $property = $this->requireExtendsPropertiesClassReflectionExtension->getStaticProperty($this, $propertyName); + $this->staticProperties[$key] = $property; + } + } + + if (!isset($this->staticProperties[$key])) { + throw new MissingPropertyFromReflectionException($this->getName(), $propertyName); + } + + return $this->staticProperties[$key]; + } + public function hasNativeProperty(string $propertyName): bool { - return $this->getPhpExtension()->hasProperty($this, $propertyName); + return $this->getPhpExtension()->hasNativeProperty($this, $propertyName); } public function getNativeProperty(string $propertyName): PhpPropertyReflection diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index a0d4d17471..fc13b3c8e5 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -71,6 +71,9 @@ final class PhpClassReflectionExtension /** @var ExtendedPropertyReflection[][] */ private array $propertiesIncludingAnnotations = []; + /** @var ExtendedPropertyReflection[][] */ + private array $staticPropertiesIncludingAnnotations = []; + /** @var PhpPropertyReflection[][] */ private array $nativeProperties = []; @@ -117,6 +120,17 @@ public function evictPrivateSymbols(string $classCacheKey): void unset($this->propertiesIncludingAnnotations[$key][$name]); } } + foreach ($this->staticPropertiesIncludingAnnotations as $key => $properties) { + if ($key !== $classCacheKey) { + continue; + } + foreach ($properties as $name => $property) { + if (!$property->isPrivate()) { + continue; + } + unset($this->staticPropertiesIncludingAnnotations[$key][$name]); + } + } foreach ($this->nativeProperties as $key => $properties) { if ($key !== $classCacheKey) { continue; @@ -154,7 +168,10 @@ public function evictPrivateSymbols(string $classCacheKey): void public function hasProperty(ClassReflection $classReflection, string $propertyName): bool { - return $classReflection->getNativeReflection()->hasProperty($propertyName); + $nativeReflection = $classReflection->getNativeReflection(); + + return $nativeReflection->hasProperty($propertyName) + && !$nativeReflection->getProperty($propertyName)->isStatic(); } public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection @@ -166,6 +183,28 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa return $this->propertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName]; } + public function hasStaticProperty(ClassReflection $classReflection, string $propertyName): bool + { + $nativeReflection = $classReflection->getNativeReflection(); + + return $nativeReflection->hasProperty($propertyName) + && $nativeReflection->getProperty($propertyName)->isStatic(); + } + + public function getStaticProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection + { + if (!isset($this->staticPropertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName])) { + $this->staticPropertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName] = $this->createProperty($classReflection, $propertyName, true); + } + + return $this->staticPropertiesIncludingAnnotations[$classReflection->getCacheKey()][$propertyName]; + } + + public function hasNativeProperty(ClassReflection $classReflection, string $propertyName): bool + { + return $classReflection->getNativeReflection()->hasProperty($propertyName); + } + public function getNativeProperty(ClassReflection $classReflection, string $propertyName): PhpPropertyReflection { if (!isset($this->nativeProperties[$classReflection->getCacheKey()][$propertyName])) { @@ -177,6 +216,7 @@ public function getNativeProperty(ClassReflection $classReflection, string $prop return $this->nativeProperties[$classReflection->getCacheKey()][$propertyName]; } + // TODO: Find the difference between createInstanceProperty and createStaticProperty private function createProperty( ClassReflection $classReflection, string $propertyName, diff --git a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php index 550a7bee59..a0615b82f2 100644 --- a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php +++ b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php @@ -5,20 +5,33 @@ use PHPStan\Analyser\OutOfClassScope; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedPropertyReflection; -use PHPStan\Reflection\PropertiesClassReflectionExtension; use PHPStan\ShouldNotHappenException; +use PHPStan\TrinaryLogic; +use PHPStan\Type\Type; -final class RequireExtendsPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension +final class RequireExtendsPropertiesClassReflectionExtension { + /** @deprecated Use hasInstanceProperty or hasStaticProperty */ public function hasProperty(ClassReflection $classReflection, string $propertyName): bool { - return $this->findProperty($classReflection, $propertyName) !== null; + return $this->findProperty( + $classReflection, + $propertyName, + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + ) !== null; } + /** @deprecated Use getInstanceProperty or getStaticProperty */ public function getProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection { - $property = $this->findProperty($classReflection, $propertyName); + $property = $this->findProperty( + $classReflection, + $propertyName, + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + ); if ($property === null) { throw new ShouldNotHappenException(); } @@ -26,7 +39,74 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa return $property; } - private function findProperty(ClassReflection $classReflection, string $propertyName): ?ExtendedPropertyReflection + public function hasInstanceProperty(ClassReflection $classReflection, string $propertyName): bool + { + return $this->findProperty( + $classReflection, + $propertyName, + // TODO: Use hasInstanceProperty + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), + // TODO: Use getInstanceProperty + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + ) !== null; + } + + public function getInstanceProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection + { + $property = $this->findProperty( + $classReflection, + $propertyName, + // TODO: Use hasInstanceProperty + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), + // TODO: Use getInstanceProperty + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + ); + if ($property === null) { + throw new ShouldNotHappenException(); + } + + return $property; + } + + public function hasStaticProperty(ClassReflection $classReflection, string $propertyName): bool + { + return $this->findProperty( + $classReflection, + $propertyName, + // TODO: Use hasStaticProperty + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), + // TODO: Use getStaticProperty + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + ) !== null; + } + + public function getStaticProperty(ClassReflection $classReflection, string $propertyName): ExtendedPropertyReflection + { + $property = $this->findProperty( + $classReflection, + $propertyName, + // TODO: Use hasStaticProperty + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), + // TODO: Use getStaticProperty + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + ); + if ($property === null) { + throw new ShouldNotHappenException(); + } + + return $property; + } + + /** + * @param callable(Type, string): TrinaryLogic $propertyHasser + * @param callable(Type, string): ExtendedPropertyReflection $propertyGetter + */ + private function findProperty( + ClassReflection $classReflection, + string $propertyName, + callable $propertyHasser, + callable $propertyGetter, + ): ?ExtendedPropertyReflection { if (!$classReflection->isInterface()) { return null; @@ -36,16 +116,16 @@ private function findProperty(ClassReflection $classReflection, string $property foreach ($requireExtendsTags as $requireExtendsTag) { $type = $requireExtendsTag->getType(); - if (!$type->hasProperty($propertyName)->yes()) { + if (!$propertyHasser($type, $propertyName)->yes()) { continue; } - return $type->getProperty($propertyName, new OutOfClassScope()); + return $propertyGetter($type, $propertyName); } $interfaces = $classReflection->getInterfaces(); foreach ($interfaces as $interface) { - $property = $this->findProperty($interface, $propertyName); + $property = $this->findProperty($interface, $propertyName, $propertyHasser, $propertyGetter); if ($property !== null) { return $property; } diff --git a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php index 35d8075f8b..d54337a07a 100644 --- a/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php +++ b/tests/PHPStan/Reflection/Annotations/AnnotationsPropertiesClassReflectionExtensionTest.php @@ -287,11 +287,11 @@ public function testProperties(string $className, array $properties): void $scope->method('canWriteProperty')->willReturn(true); foreach ($properties as $propertyName => $expectedPropertyData) { $this->assertTrue( - $class->hasProperty($propertyName), + $class->hasInstanceProperty($propertyName), sprintf('Class %s does not define property %s.', $className, $propertyName), ); - $property = $class->getProperty($propertyName, $scope); + $property = $class->getInstanceProperty($propertyName, $scope); $this->assertSame( $expectedPropertyData['class'], $property->getDeclaringClass()->getName(), diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index 48c4197868..c4b7f2f0f1 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -38,6 +38,8 @@ public function dataDeprecatedAnnotations(): array ], 'property' => [ 'foo' => null, + ], + 'staticProperty' => [ 'staticFoo' => null, ], ], @@ -56,6 +58,8 @@ public function dataDeprecatedAnnotations(): array ], 'property' => [ 'deprecatedFoo' => null, + ], + 'staticProperty' => [ 'deprecatedStaticFoo' => null, ], ], @@ -112,7 +116,13 @@ public function testDeprecatedAnnotations(bool $deprecated, string $className, ? } foreach ($deprecatedAnnotations['property'] ?? [] as $propertyName => $deprecatedMessage) { - $propertyAnnotation = $class->getProperty($propertyName, $scope); + $propertyAnnotation = $class->getInstanceProperty($propertyName, $scope); + $this->assertSame($deprecated, $propertyAnnotation->isDeprecated()->yes()); + $this->assertSame($deprecatedMessage, $propertyAnnotation->getDeprecatedDescription()); + } + + foreach ($deprecatedAnnotations['staticProperty'] ?? [] as $propertyName => $deprecatedMessage) { + $propertyAnnotation = $class->getStaticProperty($propertyName, $scope); $this->assertSame($deprecated, $propertyAnnotation->isDeprecated()->yes()); $this->assertSame($deprecatedMessage, $propertyAnnotation->getDeprecatedDescription()); } diff --git a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php index d7af0d248f..bf7630e694 100644 --- a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php @@ -31,6 +31,8 @@ public function dataInternalAnnotations(): array ], 'property' => [ 'foo', + ], + 'staticProperty' => [ 'staticFoo', ], ], @@ -48,6 +50,8 @@ public function dataInternalAnnotations(): array ], 'property' => [ 'internalFoo', + ], + 'staticProperty' => [ 'internalStaticFoo', ], ], @@ -88,6 +92,8 @@ public function dataInternalAnnotations(): array ], 'property' => [ 'foo', + ], + 'staticProperty' => [ 'staticFoo', ], ], @@ -102,6 +108,8 @@ public function dataInternalAnnotations(): array ], 'property' => [ 'internalFoo', + ], + 'staticProperty' => [ 'internalStaticFoo', ], ], @@ -132,7 +140,12 @@ public function testInternalAnnotations(bool $internal, string $className, array } foreach ($internalAnnotations['property'] ?? [] as $propertyName) { - $propertyAnnotation = $class->getProperty($propertyName, $scope); + $propertyAnnotation = $class->getInstanceProperty($propertyName, $scope); + $this->assertSame($internal, $propertyAnnotation->isInternal()->yes()); + } + + foreach ($internalAnnotations['staticProperty'] ?? [] as $propertyName) { + $propertyAnnotation = $class->getStaticProperty($propertyName, $scope); $this->assertSame($internal, $propertyAnnotation->isInternal()->yes()); } From 2151184f3df201fcac7d9c8297c4f51d13b91f9c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 28 Mar 2025 13:37:10 +0100 Subject: [PATCH 02/12] Solve deprecations --- src/Rules/Properties/AccessPropertiesCheck.php | 9 ++++++--- src/Rules/Properties/AccessStaticPropertiesRule.php | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php index 467cf98a99..635cb769cf 100644 --- a/src/Rules/Properties/AccessPropertiesCheck.php +++ b/src/Rules/Properties/AccessPropertiesCheck.php @@ -67,6 +67,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $scope, NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), sprintf('Access to property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), + // TODO use hasInstanceProperty static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(), ); $type = $typeResult->getType(); @@ -93,6 +94,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string ]; } + // TODO use hasInstanceProperty $has = $type->hasProperty($name); if (!$has->no() && $this->canAccessUndefinedProperties($scope, $node)) { return []; @@ -124,12 +126,12 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $propertyClassReflection = $this->reflectionProvider->getClass($classNames[0]); $parentClassReflection = $propertyClassReflection->getParentClass(); while ($parentClassReflection !== null) { - if ($parentClassReflection->hasProperty($name)) { + if ($parentClassReflection->hasInstanceProperty($name)) { if ($write) { - if ($scope->canWriteProperty($parentClassReflection->getProperty($name, $scope))) { + if ($scope->canWriteProperty($parentClassReflection->getInstanceProperty($name, $scope))) { return []; } - } elseif ($scope->canReadProperty($parentClassReflection->getProperty($name, $scope))) { + } elseif ($scope->canReadProperty($parentClassReflection->getInstanceProperty($name, $scope))) { return []; } @@ -173,6 +175,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string ]; } + // TODO use getInstanceProperty $propertyReflection = $type->getProperty($name, $scope); if ($propertyReflection->isStatic()) { return [ diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 94e526da0c..b5fc816782 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -135,6 +135,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $scope, NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->class), sprintf('Access to static property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), + // TODO Use hasStaticProperty static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(), ); $classType = $classTypeResult->getType(); @@ -167,6 +168,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]); } + // TODO Use hasStaticProperty $has = $classType->hasProperty($name); if (!$has->no() && $scope->isUndefinedExpressionAllowed($node)) { return []; @@ -183,8 +185,8 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $parentClassReflection = $propertyClassReflection->getParentClass(); while ($parentClassReflection !== null) { - if ($parentClassReflection->hasProperty($name)) { - if ($scope->canReadProperty($parentClassReflection->getProperty($name, $scope))) { + if ($parentClassReflection->hasStaticProperty($name)) { + if ($scope->canReadProperty($parentClassReflection->getStaticProperty($name, $scope))) { return []; } return [ @@ -209,6 +211,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]); } + // TODO Use getStaticProperty and update the if $property = $classType->getProperty($name, $scope); if (!$property->isStatic()) { $hasPropertyTypes = TypeUtils::getHasPropertyTypes($classType); From be5cf4d9c81240540a789d75ee8fc0a87169a71c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 28 Mar 2025 14:31:15 +0100 Subject: [PATCH 03/12] Introduce new methods --- src/Analyser/MutatingScope.php | 31 +++- src/Analyser/Scope.php | 5 + src/Type/ClosureType.php | 30 +++ src/Type/Enum/EnumCaseObjectType.php | 25 ++- src/Type/Generic/GenericObjectType.php | 24 +++ src/Type/IntersectionType.php | 66 +++++++ src/Type/MixedType.php | 42 +++++ src/Type/NeverType.php | 30 +++ src/Type/NonexistentParentClassType.php | 30 +++ src/Type/ObjectShapeType.php | 38 +++- src/Type/ObjectType.php | 174 ++++++++++++++++++ src/Type/StaticType.php | 64 +++++++ src/Type/StrictMixedType.php | 30 +++ src/Type/Traits/LateResolvableTypeTrait.php | 30 +++ src/Type/Traits/MaybeObjectTypeTrait.php | 42 +++++ src/Type/Traits/NonObjectTypeTrait.php | 30 +++ src/Type/Traits/ObjectTypeTrait.php | 42 +++++ src/Type/Type.php | 15 ++ src/Type/UnionType.php | 66 +++++++ .../data/class-implements-out-of-phpstan.php | 30 +++ 20 files changed, 838 insertions(+), 6 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 32b7233344..e728bfbbd5 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -6062,7 +6062,10 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName, return $this->transformVoidToNull($parametersAcceptor->getReturnType(), $methodCall); } - /** @api */ + /** + * @api + * @deprecated Use getInstancePropertyReflection or getStaticPropertyReflection instead + */ public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection { if ($typeWithProperty instanceof UnionType) { @@ -6075,6 +6078,32 @@ public function getPropertyReflection(Type $typeWithProperty, string $propertyNa return $typeWithProperty->getProperty($propertyName, $this); } + /** @api */ + public function getInstancePropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection + { + if ($typeWithProperty instanceof UnionType) { + $typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasInstanceProperty($propertyName)->yes()); + } + if (!$typeWithProperty->hasInstanceProperty($propertyName)->yes()) { + return null; + } + + return $typeWithProperty->getInstanceProperty($propertyName, $this); + } + + /** @api */ + public function getStaticPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection + { + if ($typeWithProperty instanceof UnionType) { + $typeWithProperty = $typeWithProperty->filterTypes(static fn (Type $innerType) => $innerType->hasStaticProperty($propertyName)->yes()); + } + if (!$typeWithProperty->hasStaticProperty($propertyName)->yes()) { + return null; + } + + return $typeWithProperty->getStaticProperty($propertyName, $this); + } + /** * @param PropertyFetch|Node\Expr\StaticPropertyFetch $propertyFetch */ diff --git a/src/Analyser/Scope.php b/src/Analyser/Scope.php index 1134614b2f..c817f9e1a4 100644 --- a/src/Analyser/Scope.php +++ b/src/Analyser/Scope.php @@ -75,8 +75,13 @@ public function getMaybeDefinedVariables(): array; public function hasConstant(Name $name): bool; + /** @deprecated Use getInstancePropertyReflection or getStaticPropertyReflection instead */ public function getPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; + public function getInstancePropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; + + public function getStaticPropertyReflection(Type $typeWithProperty, string $propertyName): ?ExtendedPropertyReflection; + public function getMethodReflection(Type $typeWithMethod, string $methodName): ?ExtendedMethodReflection; public function getConstantReflection(Type $typeWithConstant, string $constantName): ?ClassConstantReflection; diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 9a12b00fbb..37adc98017 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -315,6 +315,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember return $this->objectType->getUnresolvedPropertyPrototype($propertyName, $scope); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return $this->objectType->hasInstanceProperty($propertyName); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->objectType->getInstanceProperty($propertyName, $scope); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + return $this->objectType->getUnresolvedInstancePropertyPrototype($propertyName, $scope); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return $this->objectType->hasStaticProperty($propertyName); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->objectType->getStaticProperty($propertyName, $scope); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + return $this->objectType->getUnresolvedStaticPropertyPrototype($propertyName, $scope); + } + public function canCallMethods(): TrinaryLogic { return $this->objectType->canCallMethods(); diff --git a/src/Type/Enum/EnumCaseObjectType.php b/src/Type/Enum/EnumCaseObjectType.php index 5e803a6af9..682a316678 100644 --- a/src/Type/Enum/EnumCaseObjectType.php +++ b/src/Type/Enum/EnumCaseObjectType.php @@ -8,6 +8,7 @@ use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ClassMemberAccessAnswerer; use PHPStan\Reflection\ClassReflection; +use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Php\EnumPropertyReflection; use PHPStan\Reflection\Php\EnumUnresolvedPropertyPrototypeReflection; use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; @@ -122,10 +123,15 @@ public function tryRemove(Type $typeToRemove): ?Type } public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection { $classReflection = $this->getClassReflection(); if ($classReflection === null) { - return parent::getUnresolvedPropertyPrototype($propertyName, $scope); + return parent::getUnresolvedInstancePropertyPrototype($propertyName, $scope); } if ($propertyName === 'name') { @@ -148,7 +154,22 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember } } - return parent::getUnresolvedPropertyPrototype($propertyName, $scope); + return parent::getUnresolvedInstancePropertyPrototype($propertyName, $scope); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); } public function getBackingValueType(): ?Type diff --git a/src/Type/Generic/GenericObjectType.php b/src/Type/Generic/GenericObjectType.php index a2bdadd7ae..54bef86b29 100644 --- a/src/Type/Generic/GenericObjectType.php +++ b/src/Type/Generic/GenericObjectType.php @@ -232,6 +232,30 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember return $prototype->doNotResolveTemplateTypeMapToBounds(); } + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $prototype = parent::getUnresolvedInstancePropertyPrototype($propertyName, $scope); + + return $prototype->doNotResolveTemplateTypeMapToBounds(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $prototype = parent::getUnresolvedStaticPropertyPrototype($propertyName, $scope); + + return $prototype->doNotResolveTemplateTypeMapToBounds(); + } + public function getMethod(string $methodName, ClassMemberAccessAnswerer $scope): ExtendedMethodReflection { return $this->getUnresolvedMethodPrototype($methodName, $scope)->getTransformedMethod(); diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 149536a573..363712a05e 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -533,6 +533,72 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember return new IntersectionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->hasInstanceProperty($propertyName)); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $propertyPrototypes = []; + foreach ($this->types as $type) { + if (!$type->hasProperty($propertyName)->yes()) { + continue; + } + + $propertyPrototypes[] = $type->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->withFechedOnType($this); + } + + $propertiesCount = count($propertyPrototypes); + if ($propertiesCount === 0) { + throw new ShouldNotHappenException(); + } + + if ($propertiesCount === 1) { + return $propertyPrototypes[0]; + } + + return new IntersectionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->hasStaticProperty($propertyName)); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $propertyPrototypes = []; + foreach ($this->types as $type) { + if (!$type->hasStaticProperty($propertyName)->yes()) { + continue; + } + + $propertyPrototypes[] = $type->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->withFechedOnType($this); + } + + $propertiesCount = count($propertyPrototypes); + if ($propertiesCount === 0) { + throw new ShouldNotHappenException(); + } + + if ($propertiesCount === 1) { + return $propertyPrototypes[0]; + } + + return new IntersectionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); + } + public function canCallMethods(): TrinaryLogic { return $this->intersectResults(static fn (Type $type): TrinaryLogic => $type->canCallMethods()); diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 487a474827..ada8a8beae 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -405,6 +405,48 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember ); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static fn (Type $type): Type => $type, + ); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createYes(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static fn (Type $type): Type => $type, + ); + } + public function canCallMethods(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 518ffa8f4a..344d2950ed 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -138,6 +138,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember throw new ShouldNotHappenException(); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); + } + public function canCallMethods(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index 0b91e093e6..1d5153f731 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -77,6 +77,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember throw new ShouldNotHappenException(); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); + } + public function canCallMethods(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index 1a1beed6c0..a6ae8075c1 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -90,6 +90,21 @@ public function getObjectClassReflections(): array } public function hasProperty(string $propertyName): TrinaryLogic + { + return $this->hasInstanceProperty($propertyName); + } + + public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getInstanceProperty($propertyName, $scope); + } + + public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope); + } + + public function hasInstanceProperty(string $propertyName): TrinaryLogic { if (!array_key_exists($propertyName, $this->properties)) { return TrinaryLogic::createNo(); @@ -102,12 +117,12 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createYes(); } - public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); } - public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection { if (!array_key_exists($propertyName, $this->properties)) { throw new ShouldNotHappenException(); @@ -122,6 +137,23 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember ); } + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + // TODO Change the implementation + return $this->hasInstanceProperty($propertyName); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + // TODO Change the implementation + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope); + } + public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 0859825701..5470c0a3d6 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -94,6 +94,12 @@ class ObjectType implements TypeWithClassName, SubtractableType /** @var array>> */ private static array $properties = []; + /** @var array>> */ + private static array $instanceProperties = []; + + /** @var array>> */ + private static array $staticProperties = []; + /** @var array> */ private static array $ancestors = []; @@ -124,6 +130,8 @@ public static function resetCaches(): void self::$superTypes = []; self::$methods = []; self::$properties = []; + self::$instanceProperties = []; + self::$staticProperties = []; self::$ancestors = []; self::$enumCases = []; } @@ -243,6 +251,172 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember ); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return TrinaryLogic::createMaybe(); + } + + if ($classReflection->hasInstanceProperty($propertyName)) { + return TrinaryLogic::createYes(); + } + + if ($classReflection->allowsDynamicProperties()) { + return TrinaryLogic::createMaybe(); + } + + if (!$classReflection->isFinal()) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + if (!$scope->isInClass()) { + $canAccessProperty = 'no'; + } else { + $canAccessProperty = $scope->getClassReflection()->getName(); + } + $description = $this->describeCache(); + + if (isset(self::$instanceProperties[$description][$propertyName][$canAccessProperty])) { + return self::$instanceProperties[$description][$propertyName][$canAccessProperty]; + } + + $nakedClassReflection = $this->getNakedClassReflection(); + if ($nakedClassReflection === null) { + throw new ClassNotFoundException($this->className); + } + + if ($nakedClassReflection->isEnum()) { + if ( + $propertyName === 'name' + || ($propertyName === 'value' && $nakedClassReflection->isBackedEnum()) + ) { + $properties = []; + foreach ($this->getEnumCases() as $enumCase) { + $properties[] = $enumCase->getUnresolvedPropertyPrototype($propertyName, $scope); + } + + if (count($properties) > 0) { + if (count($properties) === 1) { + return $properties[0]; + } + + return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $properties); + } + } + } + + if (!$nakedClassReflection->hasNativeProperty($propertyName)) { + $nakedClassReflection = $this->getClassReflection(); + } + + if ($nakedClassReflection === null) { + throw new ClassNotFoundException($this->className); + } + + $property = $nakedClassReflection->getInstanceProperty($propertyName, $scope); + + $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName()); + $resolvedClassReflection = null; + if ($ancestor !== null && $ancestor->hasInstanceProperty($propertyName)->yes()) { + $resolvedClassReflection = $ancestor->getClassReflection(); + if ($ancestor !== $this) { + $property = $ancestor->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getNakedProperty(); + } + } + if ($resolvedClassReflection === null) { + $resolvedClassReflection = $property->getDeclaringClass(); + } + + return self::$instanceProperties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection( + $property, + $resolvedClassReflection, + true, + $this, + ); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + $classReflection = $this->getClassReflection(); + if ($classReflection === null) { + return TrinaryLogic::createMaybe(); + } + + if ($classReflection->hasStaticProperty($propertyName)) { + return TrinaryLogic::createYes(); + } + + if (!$classReflection->isFinal()) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + if (!$scope->isInClass()) { + $canAccessProperty = 'no'; + } else { + $canAccessProperty = $scope->getClassReflection()->getName(); + } + $description = $this->describeCache(); + + if (isset(self::$staticProperties[$description][$propertyName][$canAccessProperty])) { + return self::$staticProperties[$description][$propertyName][$canAccessProperty]; + } + + $nakedClassReflection = $this->getNakedClassReflection(); + if ($nakedClassReflection === null) { + throw new ClassNotFoundException($this->className); + } + + if (!$nakedClassReflection->hasNativeProperty($propertyName)) { + $nakedClassReflection = $this->getClassReflection(); + } + + if ($nakedClassReflection === null) { + throw new ClassNotFoundException($this->className); + } + + $property = $nakedClassReflection->getStaticProperty($propertyName, $scope); + + $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName()); + $resolvedClassReflection = null; + if ($ancestor !== null && $ancestor->hasStaticProperty($propertyName)->yes()) { + $resolvedClassReflection = $ancestor->getClassReflection(); + if ($ancestor !== $this) { + $property = $ancestor->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getNakedProperty(); + } + } + if ($resolvedClassReflection === null) { + $resolvedClassReflection = $property->getDeclaringClass(); + } + + return self::$staticProperties[$description][$propertyName][$canAccessProperty] = new CalledOnTypeUnresolvedPropertyPrototypeReflection( + $property, + $resolvedClassReflection, + true, + $this, + ); + } + public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection { $classReflection = $this->getNakedClassReflection(); diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 9db20e5b34..9a21560777 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -237,6 +237,70 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember ); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return $this->getStaticObjectType()->hasInstanceProperty($propertyName); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $staticObject = $this->getStaticObjectType(); + $nakedProperty = $staticObject->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getNakedProperty(); + + $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName()); + $classReflection = null; + if ($ancestor !== null) { + $classReflection = $ancestor->getClassReflection(); + } + if ($classReflection === null) { + $classReflection = $nakedProperty->getDeclaringClass(); + } + + return new CallbackUnresolvedPropertyPrototypeReflection( + $nakedProperty, + $classReflection, + false, + fn (Type $type): Type => $this->transformStaticType($type, $scope), + ); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return $this->getStaticObjectType()->hasStaticProperty($propertyName); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $staticObject = $this->getStaticObjectType(); + $nakedProperty = $staticObject->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getNakedProperty(); + + $ancestor = $this->getAncestorWithClassName($nakedProperty->getDeclaringClass()->getName()); + $classReflection = null; + if ($ancestor !== null) { + $classReflection = $ancestor->getClassReflection(); + } + if ($classReflection === null) { + $classReflection = $nakedProperty->getDeclaringClass(); + } + + return new CallbackUnresolvedPropertyPrototypeReflection( + $nakedProperty, + $classReflection, + false, + fn (Type $type): Type => $this->transformStaticType($type, $scope), + ); + } + public function canCallMethods(): TrinaryLogic { return $this->getStaticObjectType()->canCallMethods(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 0ff25dc124..71678b77a4 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -135,6 +135,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember throw new ShouldNotHappenException(); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); + } + public function canCallMethods(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 5eb703077f..94632dd807 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -113,6 +113,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember return $this->resolve()->getUnresolvedPropertyPrototype($propertyName, $scope); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return $this->resolve()->hasInstanceProperty($propertyName); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->resolve()->getInstanceProperty($propertyName, $scope); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + return $this->resolve()->getUnresolvedInstancePropertyPrototype($propertyName, $scope); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return $this->resolve()->hasStaticProperty($propertyName); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->resolve()->getStaticProperty($propertyName, $scope); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + return $this->resolve()->getUnresolvedPropertyPrototype($propertyName, $scope); + } + public function canCallMethods(): TrinaryLogic { return $this->resolve()->canCallMethods(); diff --git a/src/Type/Traits/MaybeObjectTypeTrait.php b/src/Type/Traits/MaybeObjectTypeTrait.php index ff50c721d3..777c22b7c4 100644 --- a/src/Type/Traits/MaybeObjectTypeTrait.php +++ b/src/Type/Traits/MaybeObjectTypeTrait.php @@ -61,6 +61,48 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember ); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static fn (Type $type): Type => $type, + ); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static fn (Type $type): Type => $type, + ); + } + public function canCallMethods(): TrinaryLogic { return TrinaryLogic::createMaybe(); diff --git a/src/Type/Traits/NonObjectTypeTrait.php b/src/Type/Traits/NonObjectTypeTrait.php index d16b86c9b1..0d55341b46 100644 --- a/src/Type/Traits/NonObjectTypeTrait.php +++ b/src/Type/Traits/NonObjectTypeTrait.php @@ -46,6 +46,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember throw new ShouldNotHappenException(); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createNo(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + throw new ShouldNotHappenException(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + throw new ShouldNotHappenException(); + } + public function canCallMethods(): TrinaryLogic { return TrinaryLogic::createNo(); diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 45fc20121f..3774e1e253 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -72,6 +72,48 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember ); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static fn (Type $type): Type => $type, + ); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return TrinaryLogic::createMaybe(); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $property = new DummyPropertyReflection(); + return new CallbackUnresolvedPropertyPrototypeReflection( + $property, + $property->getDeclaringClass(), + false, + static fn (Type $type): Type => $type, + ); + } + public function canCallMethods(): TrinaryLogic { return TrinaryLogic::createYes(); diff --git a/src/Type/Type.php b/src/Type/Type.php index 15886a053c..a2da132974 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -74,12 +74,27 @@ public function describe(VerbosityLevel $level): string; public function canAccessProperties(): TrinaryLogic; + /** @deprecated Use hasInstanceProperty or hasStaticProperty instead */ public function hasProperty(string $propertyName): TrinaryLogic; + /** @deprecated Use getInstanceProperty or getStaticProperty instead */ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection; + /** @deprecated Use getUnresolvedInstancePropertyPrototype or getUnresolvedStaticPropertyPrototype instead */ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; + public function hasInstanceProperty(string $propertyName): TrinaryLogic; + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection; + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; + + public function hasStaticProperty(string $propertyName): TrinaryLogic; + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection; + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection; + public function canCallMethods(): TrinaryLogic; public function hasMethod(string $methodName): TrinaryLogic; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 08d678152a..6967350d98 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -490,6 +490,72 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); } + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasInstanceProperty($propertyName)); + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $propertyPrototypes = []; + foreach ($this->types as $type) { + if (!$type->hasInstanceProperty($propertyName)->yes()) { + continue; + } + + $propertyPrototypes[] = $type->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->withFechedOnType($this); + } + + $propertiesCount = count($propertyPrototypes); + if ($propertiesCount === 0) { + throw new ShouldNotHappenException(); + } + + if ($propertiesCount === 1) { + return $propertyPrototypes[0]; + } + + return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); + } + + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->hasStaticProperty($propertyName)); + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + { + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection + { + $propertyPrototypes = []; + foreach ($this->types as $type) { + if (!$type->hasStaticProperty($propertyName)->yes()) { + continue; + } + + $propertyPrototypes[] = $type->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->withFechedOnType($this); + } + + $propertiesCount = count($propertyPrototypes); + if ($propertiesCount === 0) { + throw new ShouldNotHappenException(); + } + + if ($propertiesCount === 1) { + return $propertyPrototypes[0]; + } + + return new UnionTypeUnresolvedPropertyPrototypeReflection($propertyName, $propertyPrototypes); + } + public function canCallMethods(): TrinaryLogic { return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->canCallMethods()); diff --git a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php index 6ded7325fb..44204e0f2c 100644 --- a/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php +++ b/tests/PHPStan/Rules/Api/data/class-implements-out-of-phpstan.php @@ -98,6 +98,36 @@ public function getUnresolvedPropertyPrototype(string $propertyName, ClassMember // TODO: Implement getUnresolvedPropertyPrototype() method. } + public function hasInstanceProperty(string $propertyName): \PHPStan\TrinaryLogic + { + // TODO: Implement hasInstanceProperty() method. + } + + public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\PropertyReflection + { + // TODO: Implement getInstanceProperty() method. + } + + public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection + { + // TODO: Implement getUnresolvedInstancePropertyPrototype() method. + } + + public function hasStaticProperty(string $propertyName): \PHPStan\TrinaryLogic + { + // TODO: Implement hasStaticProperty() method. + } + + public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\PropertyReflection + { + // TODO: Implement getStaticProperty() method. + } + + public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection + { + // TODO: Implement getUnresolvedStaticPropertyPrototype() method. + } + public function canCallMethods(): \PHPStan\TrinaryLogic { // TODO: Implement canCallMethods() method. From 4574a21608d2f5e1e6796f5907d7e397413f6e43 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 28 Mar 2025 14:42:00 +0100 Subject: [PATCH 04/12] Solve tests --- tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php index e14d95f33e..8ade9becb8 100644 --- a/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Api/ApiClassImplementsRuleTest.php @@ -42,22 +42,22 @@ public function testRuleOutOfPhpStan(): void ], [ 'Implementing PHPStan\Reflection\ReflectionProvider is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 333, + 363, $tip, ], [ 'Implementing PHPStan\Analyser\Scope is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 338, + 368, $tip, ], [ 'Implementing PHPStan\Reflection\FunctionReflection is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 343, + 373, $tip, ], [ 'Implementing PHPStan\Reflection\ExtendedMethodReflection is not covered by backward compatibility promise. The interface might change in a minor PHPStan version.', - 347, + 377, $tip, ], ]); From 0291c75e196fe68003a74d5d7e01d09a505f9aad Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 29 Mar 2025 10:32:02 +0100 Subject: [PATCH 05/12] More --- ...endsPropertiesClassReflectionExtension.php | 24 ++++------- .../Properties/AccessPropertiesCheck.php | 29 ++++++------- .../Properties/AccessStaticPropertiesRule.php | 41 +++++++++---------- src/Type/Accessory/HasPropertyType.php | 22 ++++++++++ src/Type/ObjectShapeType.php | 17 -------- src/Type/Traits/ObjectTypeTrait.php | 4 +- .../PHPStan/Type/BenevolentUnionTypeTest.php | 8 ++-- 7 files changed, 68 insertions(+), 77 deletions(-) diff --git a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php index a0615b82f2..27b51f7fdd 100644 --- a/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php +++ b/src/Reflection/RequireExtension/RequireExtendsPropertiesClassReflectionExtension.php @@ -44,10 +44,8 @@ public function hasInstanceProperty(ClassReflection $classReflection, string $pr return $this->findProperty( $classReflection, $propertyName, - // TODO: Use hasInstanceProperty - static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), - // TODO: Use getInstanceProperty - static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasInstanceProperty($propertyName), + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getInstanceProperty($propertyName, new OutOfClassScope()) ) !== null; } @@ -56,10 +54,8 @@ public function getInstanceProperty(ClassReflection $classReflection, string $pr $property = $this->findProperty( $classReflection, $propertyName, - // TODO: Use hasInstanceProperty - static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), - // TODO: Use getInstanceProperty - static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasInstanceProperty($propertyName), + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getInstanceProperty($propertyName, new OutOfClassScope()) ); if ($property === null) { throw new ShouldNotHappenException(); @@ -73,10 +69,8 @@ public function hasStaticProperty(ClassReflection $classReflection, string $prop return $this->findProperty( $classReflection, $propertyName, - // TODO: Use hasStaticProperty - static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), - // TODO: Use getStaticProperty - static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasStaticProperty($propertyName), + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getStaticProperty($propertyName, new OutOfClassScope()) ) !== null; } @@ -85,10 +79,8 @@ public function getStaticProperty(ClassReflection $classReflection, string $prop $property = $this->findProperty( $classReflection, $propertyName, - // TODO: Use hasStaticProperty - static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasProperty($propertyName), - // TODO: Use getStaticProperty - static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getProperty($propertyName, new OutOfClassScope()) + static fn (Type $type, string $propertyName): TrinaryLogic => $type->hasStaticProperty($propertyName), + static fn (Type $type, string $propertyName): ExtendedPropertyReflection => $type->getStaticProperty($propertyName, new OutOfClassScope()) ); if ($property === null) { throw new ShouldNotHappenException(); diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php index 635cb769cf..c8664dec8f 100644 --- a/src/Rules/Properties/AccessPropertiesCheck.php +++ b/src/Rules/Properties/AccessPropertiesCheck.php @@ -67,8 +67,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $scope, NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->var), sprintf('Access to property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), - // TODO use hasInstanceProperty - static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(), + static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasInstanceProperty($name)->yes(), ); $type = $typeResult->getType(); if ($type instanceof ErrorType) { @@ -94,8 +93,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string ]; } - // TODO use hasInstanceProperty - $has = $type->hasProperty($name); + $has = $type->hasInstanceProperty($name); if (!$has->no() && $this->canAccessUndefinedProperties($scope, $node)) { return []; } @@ -159,6 +157,16 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string } } + if ($type->hasStaticProperty($name)->yes()) { + return [ + RuleErrorBuilder::message(sprintf( + 'Non-static access to static property %s::$%s.', + $type->getStaticProperty($name, $scope)->getDeclaringClass()->getDisplayName(), + $name, + ))->identifier('staticProperty.nonStaticAccess')->build(), + ]; + } + $ruleErrorBuilder = RuleErrorBuilder::message(sprintf( 'Access to an undefined property %s::$%s.', $typeForDescribe->describe(VerbosityLevel::typeOnly()), @@ -175,18 +183,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string ]; } - // TODO use getInstanceProperty - $propertyReflection = $type->getProperty($name, $scope); - if ($propertyReflection->isStatic()) { - return [ - RuleErrorBuilder::message(sprintf( - 'Non-static access to static property %s::$%s.', - $propertyReflection->getDeclaringClass()->getDisplayName(), - $name, - ))->identifier('staticProperty.nonStaticAccess')->build(), - ]; - } - + $propertyReflection = $type->getInstanceProperty($name, $scope); if ($write) { if ($scope->canWriteProperty($propertyReflection)) { return []; diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index b5fc816782..3166e7f4d5 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -135,8 +135,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $scope, NullsafeOperatorHelper::getNullsafeShortcircuitedExprRespectingScope($scope, $node->class), sprintf('Access to static property $%s on an unknown class %%s.', SprintfHelper::escapeFormatString($name)), - // TODO Use hasStaticProperty - static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasProperty($name)->yes(), + static fn (Type $type): bool => $type->canAccessProperties()->yes() && $type->hasStaticProperty($name)->yes(), ); $classType = $classTypeResult->getType(); if ($classType instanceof ErrorType) { @@ -168,8 +167,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]); } - // TODO Use hasStaticProperty - $has = $classType->hasProperty($name); + $has = $classType->hasStaticProperty($name); if (!$has->no() && $scope->isUndefinedExpressionAllowed($node)) { return []; } @@ -202,6 +200,23 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, } } + if ($classType->hasInstanceProperty($name)->yes()) { + $hasPropertyTypes = TypeUtils::getHasPropertyTypes($classType); + foreach ($hasPropertyTypes as $hasPropertyType) { + if ($hasPropertyType->getPropertyName() === $name) { + return []; + } + } + + return array_merge($messages, [ + RuleErrorBuilder::message(sprintf( + 'Static access to instance property %s::$%s.', + $classType->getInstanceProperty($name, $scope)->getDeclaringClass()->getDisplayName(), + $name, + ))->identifier('property.staticAccess')->build(), + ]); + } + return array_merge($messages, [ RuleErrorBuilder::message(sprintf( 'Access to an undefined static property %s::$%s.', @@ -211,25 +226,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]); } - // TODO Use getStaticProperty and update the if $property = $classType->getProperty($name, $scope); - if (!$property->isStatic()) { - $hasPropertyTypes = TypeUtils::getHasPropertyTypes($classType); - foreach ($hasPropertyTypes as $hasPropertyType) { - if ($hasPropertyType->getPropertyName() === $name) { - return []; - } - } - - return array_merge($messages, [ - RuleErrorBuilder::message(sprintf( - 'Static access to instance property %s::$%s.', - $property->getDeclaringClass()->getDisplayName(), - $name, - ))->identifier('property.staticAccess')->build(), - ]); - } - if (!$scope->canReadProperty($property)) { return array_merge($messages, [ RuleErrorBuilder::message(sprintf( diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index 71b6b42759..c2dd076bcc 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -72,6 +72,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult public function isSuperTypeOf(Type $type): IsSuperTypeOfResult { + // TODO return new IsSuperTypeOfResult($type->hasProperty($this->propertyName), []); } @@ -87,6 +88,7 @@ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult $limit = IsSuperTypeOfResult::createMaybe(); } + // TODO return $limit->and(new IsSuperTypeOfResult($otherType->hasProperty($this->propertyName), [])); } @@ -120,6 +122,26 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createMaybe(); } + // TODO + public function hasInstanceProperty(string $propertyName): TrinaryLogic + { + if ($this->propertyName === $propertyName) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + + // TODO + public function hasStaticProperty(string $propertyName): TrinaryLogic + { + if ($this->propertyName === $propertyName) { + return TrinaryLogic::createYes(); + } + + return TrinaryLogic::createMaybe(); + } + public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array { return [new TrivialParametersAcceptor()]; diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index a6ae8075c1..a8d6514786 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -137,23 +137,6 @@ public function getUnresolvedInstancePropertyPrototype(string $propertyName, Cla ); } - public function hasStaticProperty(string $propertyName): TrinaryLogic - { - // TODO Change the implementation - return $this->hasInstanceProperty($propertyName); - } - - public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection - { - return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); - } - - public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection - { - // TODO Change the implementation - return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope); - } - public function accepts(Type $type, bool $strictTypes): AcceptsResult { if ($type instanceof CompoundType) { diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index 3774e1e253..40535c4018 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -79,7 +79,7 @@ public function hasInstanceProperty(string $propertyName): TrinaryLogic public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); } public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection @@ -100,7 +100,7 @@ public function hasStaticProperty(string $propertyName): TrinaryLogic public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection diff --git a/tests/PHPStan/Type/BenevolentUnionTypeTest.php b/tests/PHPStan/Type/BenevolentUnionTypeTest.php index 18a37f8be0..cb2f6c7ae3 100644 --- a/tests/PHPStan/Type/BenevolentUnionTypeTest.php +++ b/tests/PHPStan/Type/BenevolentUnionTypeTest.php @@ -48,7 +48,7 @@ public function testCanAccessProperties(BenevolentUnionType $type, TrinaryLogic ); } - public function dataHasProperty(): Iterator + public function dataHasInstanceProperty(): Iterator { yield [ new BenevolentUnionType([ @@ -75,10 +75,10 @@ public function dataHasProperty(): Iterator ]; } - /** @dataProvider dataHasProperty */ - public function testHasProperty(BenevolentUnionType $type, string $propertyName, TrinaryLogic $expectedResult): void + /** @dataProvider dataHasInstanceProperty */ + public function testHasInstanceProperty(BenevolentUnionType $type, string $propertyName, TrinaryLogic $expectedResult): void { - $actualResult = $type->hasProperty($propertyName); + $actualResult = $type->hasInstanceProperty($propertyName); $this->assertSame( $expectedResult->describe(), $actualResult->describe(), From 637626ef513567367b49d288689ca9b183ac7af4 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 29 Mar 2025 12:07:55 +0100 Subject: [PATCH 06/12] Solve deprecations --- src/Analyser/MutatingScope.php | 9 +++++++-- src/Analyser/NodeScopeResolver.php | 8 ++++---- src/Node/ClassPropertiesNode.php | 2 +- ...ixinPropertiesClassReflectionExtension.php | 4 ++-- ...AccessPrivatePropertyThroughStaticRule.php | 7 ++----- .../Properties/AccessStaticPropertiesRule.php | 2 +- src/Type/IntersectionType.php | 2 +- src/Type/ObjectShapeType.php | 16 +++++++-------- src/Type/ObjectType.php | 20 +------------------ ...nPropertyConstructorThrowTypeExtension.php | 2 +- src/Type/Traits/LateResolvableTypeTrait.php | 2 +- src/Type/Traits/MaybeObjectTypeTrait.php | 4 ++-- 12 files changed, 31 insertions(+), 47 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index e728bfbbd5..f4babe36c3 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4237,7 +4237,7 @@ public function assignInitializedProperty(Type $fetchedOnType, string $propertyN return $this; } - $propertyReflection = $this->getPropertyReflection($fetchedOnType, $propertyName); + $propertyReflection = $this->getInstancePropertyReflection($fetchedOnType, $propertyName); if ($propertyReflection === null) { return $this; } @@ -6109,7 +6109,12 @@ public function getStaticPropertyReflection(Type $typeWithProperty, string $prop */ private function propertyFetchType(Type $fetchedOnType, string $propertyName, Expr $propertyFetch): ?Type { - $propertyReflection = $this->getPropertyReflection($fetchedOnType, $propertyName); + if ($propertyFetch instanceof PropertyFetch) { + $propertyReflection = $this->getInstancePropertyReflection($fetchedOnType, $propertyName); + } else { + $propertyReflection = $this->getStaticPropertyReflection($fetchedOnType, $propertyName); + } + if ($propertyReflection === null) { return null; } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0135b2dab7..b0931b6ae1 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3021,7 +3021,7 @@ static function (): void { } else { $propertyName = $expr->name->toString(); $propertyHolderType = $scopeBeforeVar->getType($expr->var); - $propertyReflection = $scopeBeforeVar->getPropertyReflection($propertyHolderType, $propertyName); + $propertyReflection = $scopeBeforeVar->getInstancePropertyReflection($propertyHolderType, $propertyName); if ($propertyReflection !== null && $this->phpVersion->supportsPropertyHooks()) { $propertyDeclaringClass = $propertyReflection->getDeclaringClass(); if ($propertyDeclaringClass->hasNativeProperty($propertyName)) { @@ -5563,8 +5563,8 @@ static function (): void { } $propertyHolderType = $scope->getType($var->var); - if ($propertyName !== null && $propertyHolderType->hasProperty($propertyName)->yes()) { - $propertyReflection = $propertyHolderType->getProperty($propertyName, $scope); + if ($propertyName !== null && $propertyHolderType->hasInstanceProperty($propertyName)->yes()) { + $propertyReflection = $propertyHolderType->getInstanceProperty($propertyName, $scope); $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { @@ -5635,7 +5635,7 @@ static function (): void { $scope = $result->getScope(); if ($propertyName !== null) { - $propertyReflection = $scope->getPropertyReflection($propertyHolderType, $propertyName); + $propertyReflection = $scope->getStaticPropertyReflection($propertyHolderType, $propertyName); $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { diff --git a/src/Node/ClassPropertiesNode.php b/src/Node/ClassPropertiesNode.php index ec4dcba592..d114f34914 100644 --- a/src/Node/ClassPropertiesNode.php +++ b/src/Node/ClassPropertiesNode.php @@ -198,7 +198,7 @@ public function getUninitializedProperties( continue; } - $propertyReflection = $usageScope->getPropertyReflection($fetchedOnType, $propertyName); + $propertyReflection = $usageScope->getInstancePropertyReflection($fetchedOnType, $propertyName); if ($propertyReflection === null) { continue; } diff --git a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php index 4b21f92451..78b94e4d69 100644 --- a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php @@ -54,12 +54,12 @@ private function findProperty(ClassReflection $classReflection, string $property $this->inProcess[$typeDescription][$propertyName] = true; - if (!$type->hasProperty($propertyName)->yes()) { + if (!$type->hasInstanceProperty($propertyName)->yes()) { unset($this->inProcess[$typeDescription][$propertyName]); continue; } - $property = $type->getProperty($propertyName, new OutOfClassScope()); + $property = $type->getInstanceProperty($propertyName, new OutOfClassScope()); unset($this->inProcess[$typeDescription][$propertyName]); return $property; diff --git a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php index 548e176c1d..70b1242690 100644 --- a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php +++ b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php @@ -36,17 +36,14 @@ public function processNode(Node $node, Scope $scope): array } $classType = $scope->resolveTypeByName($className); - if (!$classType->hasProperty($propertyName)->yes()) { + if (!$classType->hasStaticProperty($propertyName)->yes()) { return []; } - $property = $classType->getProperty($propertyName, $scope); + $property = $classType->getStaticProperty($propertyName, $scope); if (!$property->isPrivate()) { return []; } - if (!$property->isStatic()) { - return []; - } if ($scope->isInClass() && $scope->getClassReflection()->isFinal()) { return []; diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 3166e7f4d5..3ce5d5e50d 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -226,7 +226,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, ]); } - $property = $classType->getProperty($name, $scope); + $property = $classType->getStaticProperty($name, $scope); if (!$scope->canReadProperty($property)) { return array_merge($messages, [ RuleErrorBuilder::message(sprintf( diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 363712a05e..1ea13f645a 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -547,7 +547,7 @@ public function getUnresolvedInstancePropertyPrototype(string $propertyName, Cla { $propertyPrototypes = []; foreach ($this->types as $type) { - if (!$type->hasProperty($propertyName)->yes()) { + if (!$type->hasInstanceProperty($propertyName)->yes()) { continue; } diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index a8d6514786..264b9674c1 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -158,7 +158,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult $result = AcceptsResult::createYes(); $scope = new OutOfClassScope(); foreach ($this->properties as $propertyName => $propertyType) { - $typeHasProperty = $type->hasProperty($propertyName); + $typeHasProperty = $type->hasInstanceProperty($propertyName); $hasProperty = new AcceptsResult( $typeHasProperty, $typeHasProperty->yes() ? [] : [ @@ -183,7 +183,7 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult $result = $result->and($hasProperty); try { - $otherProperty = $type->getProperty($propertyName, $scope); + $otherProperty = $type->getInstanceProperty($propertyName, $scope); } catch (MissingPropertyFromReflectionException) { return new AcceptsResult( $result->result, @@ -270,7 +270,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult $result = IsSuperTypeOfResult::createYes(); $scope = new OutOfClassScope(); foreach ($this->properties as $propertyName => $propertyType) { - $hasProperty = new IsSuperTypeOfResult($type->hasProperty($propertyName), []); + $hasProperty = new IsSuperTypeOfResult($type->hasInstanceProperty($propertyName), []); if ($hasProperty->no()) { if (in_array($propertyName, $this->optionalProperties, true)) { continue; @@ -284,7 +284,7 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult $result = $result->and($hasProperty); try { - $otherProperty = $type->getProperty($propertyName, $scope); + $otherProperty = $type->getInstanceProperty($propertyName, $scope); } catch (MissingPropertyFromReflectionException) { return $result; } @@ -381,12 +381,12 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap $typeMap = TemplateTypeMap::createEmpty(); $scope = new OutOfClassScope(); foreach ($this->properties as $name => $propertyType) { - if ($receivedType->hasProperty($name)->no()) { + if ($receivedType->hasInstanceProperty($name)->no()) { continue; } try { - $receivedProperty = $receivedType->getProperty($name, $scope); + $receivedProperty = $receivedType->getInstanceProperty($name, $scope); } catch (MissingPropertyFromReflectionException) { continue; } @@ -477,10 +477,10 @@ public function traverseSimultaneously(Type $right, callable $cb): Type $scope = new OutOfClassScope(); foreach ($this->properties as $name => $propertyType) { - if (!$right->hasProperty($name)->yes()) { + if (!$right->hasInstanceProperty($name)->yes()) { return $this; } - $transformed = $cb($propertyType, $right->getProperty($name, $scope)->getReadableType()); + $transformed = $cb($propertyType, $right->getInstanceProperty($name, $scope)->getReadableType()); if ($transformed !== $propertyType) { $stillOriginal = false; } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 5470c0a3d6..45d0f67205 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -303,7 +303,7 @@ public function getUnresolvedInstancePropertyPrototype(string $propertyName, Cla ) { $properties = []; foreach ($this->getEnumCases() as $enumCase) { - $properties[] = $enumCase->getUnresolvedPropertyPrototype($propertyName, $scope); + $properties[] = $enumCase->getUnresolvedInstancePropertyPrototype($propertyName, $scope); } if (count($properties) > 0) { @@ -417,24 +417,6 @@ public function getUnresolvedStaticPropertyPrototype(string $propertyName, Class ); } - public function getPropertyWithoutTransformingStatic(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection - { - $classReflection = $this->getNakedClassReflection(); - if ($classReflection === null) { - throw new ClassNotFoundException($this->className); - } - - if (!$classReflection->hasProperty($propertyName)) { - $classReflection = $this->getClassReflection(); - } - - if ($classReflection === null) { - throw new ClassNotFoundException($this->className); - } - - return $classReflection->getProperty($propertyName, $scope); - } - public function getReferencedClasses(): array { return [$this->className]; diff --git a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php index b988a7883a..05bdd01dc7 100644 --- a/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php +++ b/src/Type/Php/ReflectionPropertyConstructorThrowTypeExtension.php @@ -40,7 +40,7 @@ public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflect $classReflection = $this->reflectionProvider->getClass($constantString->getValue()); foreach ($propertyType->getConstantStrings() as $constantPropertyString) { - if (!$classReflection->hasProperty($constantPropertyString->getValue())) { + if (!$classReflection->hasInstanceProperty($constantPropertyString->getValue())) { return $methodReflection->getThrowType(); } } diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 94632dd807..335053c39a 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -140,7 +140,7 @@ public function getStaticProperty(string $propertyName, ClassMemberAccessAnswere public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection { - return $this->resolve()->getUnresolvedPropertyPrototype($propertyName, $scope); + return $this->resolve()->getUnresolvedStaticPropertyPrototype($propertyName, $scope); } public function canCallMethods(): TrinaryLogic diff --git a/src/Type/Traits/MaybeObjectTypeTrait.php b/src/Type/Traits/MaybeObjectTypeTrait.php index 777c22b7c4..537f1d6b91 100644 --- a/src/Type/Traits/MaybeObjectTypeTrait.php +++ b/src/Type/Traits/MaybeObjectTypeTrait.php @@ -68,7 +68,7 @@ public function hasInstanceProperty(string $propertyName): TrinaryLogic public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + return $this->getUnresolvedInstancePropertyPrototype($propertyName, $scope)->getTransformedProperty(); } public function getUnresolvedInstancePropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection @@ -89,7 +89,7 @@ public function hasStaticProperty(string $propertyName): TrinaryLogic public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection { - return $this->getUnresolvedPropertyPrototype($propertyName, $scope)->getTransformedProperty(); + return $this->getUnresolvedStaticPropertyPrototype($propertyName, $scope)->getTransformedProperty(); } public function getUnresolvedStaticPropertyPrototype(string $propertyName, ClassMemberAccessAnswerer $scope): UnresolvedPropertyPrototypeReflection From fabcf34dbee54ec1c703aeb0ae23f597317bcf2b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 29 Mar 2025 12:31:12 +0100 Subject: [PATCH 07/12] Fix tests --- src/Type/ObjectShapeType.php | 7 +++++++ tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php | 4 ---- .../Analyser/nsrt/bug-nullsafe-prop-static-access.php | 1 - 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Type/ObjectShapeType.php b/src/Type/ObjectShapeType.php index 264b9674c1..2ad26f2cfa 100644 --- a/src/Type/ObjectShapeType.php +++ b/src/Type/ObjectShapeType.php @@ -174,12 +174,19 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult if (in_array($propertyName, $this->optionalProperties, true)) { continue; } + return $hasProperty; } if ($hasProperty->maybe() && in_array($propertyName, $this->optionalProperties, true)) { $hasProperty = AcceptsResult::createYes(); } + if (!$hasProperty->yes() && $type->hasStaticProperty($propertyName)->yes()) { + return new AcceptsResult(TrinaryLogic::createNo(), [ + sprintf('Property %s::$%s is static.', $type->getStaticProperty($propertyName, $scope)->getDeclaringClass()->getDisplayName(), $propertyName), + ]); + } + $result = $result->and($hasProperty); try { diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 8b89e0935e..4a19e970f9 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -9118,10 +9118,6 @@ public function dataUnionProperties(): array 'UnionProperties\Bar|UnionProperties\Foo', '$something->doSomething', ], - [ - 'UnionProperties\Bar|UnionProperties\Foo', - '$something::$doSomething', - ], ]; } diff --git a/tests/PHPStan/Analyser/nsrt/bug-nullsafe-prop-static-access.php b/tests/PHPStan/Analyser/nsrt/bug-nullsafe-prop-static-access.php index 14d4ecf708..82639be3e9 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-nullsafe-prop-static-access.php +++ b/tests/PHPStan/Analyser/nsrt/bug-nullsafe-prop-static-access.php @@ -26,5 +26,4 @@ function foo(?A $a): void \PHPStan\Testing\assertType('string|null', $a?->b->get()); \PHPStan\Testing\assertType('int|null', $a?->b::$value); - \PHPStan\Testing\assertType('int|null', $a?->b->value); } From 9653e8591763b51004fe76dd31105440853edb21 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 29 Mar 2025 12:46:43 +0100 Subject: [PATCH 08/12] Solve deprecations --- .../InitializerExprTypeResolver.php | 4 +-- .../DeadCode/UnusedPrivatePropertyRule.php | 9 ++++- .../Properties/PropertyReflectionFinder.php | 33 ++++++++++++++----- ...ArrayColumnFunctionReturnTypeExtension.php | 4 +-- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index fb935ac630..d74125e5bd 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -403,11 +403,11 @@ public function getType(Expr $expr, InitializerExprContext $context): Type if ($expr instanceof PropertyFetch && $expr->name instanceof Identifier) { $fetchedOnType = $this->getType($expr->var, $context); - if (!$fetchedOnType->hasProperty($expr->name->name)->yes()) { + if (!$fetchedOnType->hasInstanceProperty($expr->name->name)->yes()) { return new ErrorType(); } - return $fetchedOnType->getProperty($expr->name->name, new OutOfClassScope())->getReadableType(); + return $fetchedOnType->getInstanceProperty($expr->name->name, new OutOfClassScope())->getReadableType(); } return new MixedType(); diff --git a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php index 239e732056..ed17441c78 100644 --- a/src/Rules/DeadCode/UnusedPrivatePropertyRule.php +++ b/src/Rules/DeadCode/UnusedPrivatePropertyRule.php @@ -176,7 +176,14 @@ public function processNode(Node $node, Scope $scope): array if (!array_key_exists($propertyName, $properties)) { continue; } - $propertyReflection = $usageScope->getPropertyReflection($fetchedOnType, $propertyName); + + $propertyNode = $properties[$propertyName]['node']; + if ($propertyNode->isStatic()) { + $propertyReflection = $usageScope->getStaticPropertyReflection($fetchedOnType, $propertyName); + } else { + $propertyReflection = $usageScope->getInstancePropertyReflection($fetchedOnType, $propertyName); + } + if ($propertyReflection === null) { if (!$classType->isSuperTypeOf($fetchedOnType)->no()) { if ($usage instanceof PropertyRead) { diff --git a/src/Rules/Properties/PropertyReflectionFinder.php b/src/Rules/Properties/PropertyReflectionFinder.php index 67b2785fa9..809a15b1a0 100644 --- a/src/Rules/Properties/PropertyReflectionFinder.php +++ b/src/Rules/Properties/PropertyReflectionFinder.php @@ -31,7 +31,7 @@ public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): a $reflections = []; $propertyHolderType = $scope->getType($propertyFetch->var); foreach ($names as $name) { - $reflection = $this->findPropertyReflection( + $reflection = $this->findInstancePropertyReflection( $propertyHolderType, $name, $propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical( @@ -63,7 +63,7 @@ public function findPropertyReflectionsFromNode($propertyFetch, Scope $scope): a $reflections = []; foreach ($names as $name) { - $reflection = $this->findPropertyReflection( + $reflection = $this->findStaticPropertyReflection( $propertyHolderType, $name, $propertyFetch->name instanceof Expr ? $scope->filterByTruthyValue(new Expr\BinaryOp\Identical( @@ -89,13 +89,13 @@ public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?F if ($propertyFetch instanceof Node\Expr\PropertyFetch) { $propertyHolderType = $scope->getType($propertyFetch->var); if ($propertyFetch->name instanceof Node\Identifier) { - return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); + return $this->findInstancePropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); } $nameType = $scope->getType($propertyFetch->name); $nameTypeConstantStrings = $nameType->getConstantStrings(); if (count($nameTypeConstantStrings) === 1) { - return $this->findPropertyReflection($propertyHolderType, $nameTypeConstantStrings[0]->getValue(), $scope); + return $this->findInstancePropertyReflection($propertyHolderType, $nameTypeConstantStrings[0]->getValue(), $scope); } return null; @@ -111,16 +111,33 @@ public function findPropertyReflectionFromNode($propertyFetch, Scope $scope): ?F $propertyHolderType = $scope->getType($propertyFetch->class); } - return $this->findPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); + return $this->findStaticPropertyReflection($propertyHolderType, $propertyFetch->name->name, $scope); } - private function findPropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection + private function findInstancePropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection { - if (!$propertyHolderType->hasProperty($propertyName)->yes()) { + if (!$propertyHolderType->hasInstanceProperty($propertyName)->yes()) { return null; } - $originalProperty = $propertyHolderType->getProperty($propertyName, $scope); + $originalProperty = $propertyHolderType->getInstanceProperty($propertyName, $scope); + + return new FoundPropertyReflection( + $originalProperty, + $scope, + $propertyName, + $originalProperty->getReadableType(), + $originalProperty->getWritableType(), + ); + } + + private function findStaticPropertyReflection(Type $propertyHolderType, string $propertyName, Scope $scope): ?FoundPropertyReflection + { + if (!$propertyHolderType->hasStaticProperty($propertyName)->yes()) { + return null; + } + + $originalProperty = $propertyHolderType->getStaticProperty($propertyName, $scope); return new FoundPropertyReflection( $originalProperty, diff --git a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php index 51d8999c2f..d627cde8c4 100644 --- a/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php +++ b/src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php @@ -163,7 +163,7 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $ } foreach ($propertyTypes as $propertyType) { $propertyName = $propertyType->getValue(); - $hasProperty = $type->hasProperty($propertyName); + $hasProperty = $type->hasInstanceProperty($propertyName); if ($hasProperty->maybe()) { return $allowMaybe ? new MixedType() : null; } @@ -171,7 +171,7 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $ continue; } - $returnTypes[] = $type->getProperty($propertyName, $scope)->getReadableType(); + $returnTypes[] = $type->getInstanceProperty($propertyName, $scope)->getReadableType(); } } From 3d16f3038b80c2d3ff9ac9136a7f70a3752ecc24 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 29 Mar 2025 13:14:35 +0100 Subject: [PATCH 09/12] Fix test --- src/Type/ObjectType.php | 1 - .../Rules/Properties/TypesAssignedToPropertiesRuleTest.php | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 45d0f67205..b27d0e3ab1 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -21,7 +21,6 @@ use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ExtendedPropertyReflection; use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; -use PHPStan\Reflection\PropertyReflection; use PHPStan\Reflection\ReflectionProviderStaticAccessor; use PHPStan\Reflection\TrivialParametersAcceptor; use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection; diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 2ba73fdc96..ab3755a05c 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -43,10 +43,6 @@ public function testTypesAssignedToProperties(): void 'Static property PropertiesAssignedTypes\Foo::$staticStringProperty (string) does not accept int.', 37, ], - [ - 'Property PropertiesAssignedTypes\Ipsum::$parentStringProperty (string) does not accept int.', - 39, - ], [ 'Property PropertiesAssignedTypes\Foo::$unionPropertySelf (array|(iterable&PropertiesAssignedTypes\Collection)) does not accept PropertiesAssignedTypes\Foo.', 44, From 620d0398b1fb611da8e90d22075345fa59c1694f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 29 Mar 2025 13:21:38 +0100 Subject: [PATCH 10/12] Solve deprecation --- src/Dependency/DependencyResolver.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index 77a4f957fe..f82bb71a12 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -253,7 +253,7 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies } if ($node->name instanceof Node\Identifier) { - $propertyReflection = $scope->getPropertyReflection($fetchedOnType, $node->name->toString()); + $propertyReflection = $scope->getInstancePropertyReflection($fetchedOnType, $node->name->toString()); if ($propertyReflection !== null) { $this->addClassToDependencies($propertyReflection->getDeclaringClass()->getName(), $dependenciesReflections); } @@ -369,13 +369,13 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies $className = $scope->resolveName($node->class); if ($this->reflectionProvider->hasClass($className)) { $propertyClassReflection = $this->reflectionProvider->getClass($className); - if ($propertyClassReflection->hasProperty($node->name->toString())) { - $propertyReflection = $propertyClassReflection->getProperty($node->name->toString(), $scope); + if ($propertyClassReflection->hasStaticProperty($node->name->toString())) { + $propertyReflection = $propertyClassReflection->getStaticProperty($node->name->toString(), $scope); $this->addClassToDependencies($propertyReflection->getDeclaringClass()->getName(), $dependenciesReflections); } } } else { - $propertyReflection = $scope->getPropertyReflection($scope->getType($node->class), $node->name->toString()); + $propertyReflection = $scope->getStaticPropertyReflection($scope->getType($node->class), $node->name->toString()); if ($propertyReflection !== null) { $this->addClassToDependencies($propertyReflection->getDeclaringClass()->getName(), $dependenciesReflections); } From ae4eb451081996484226e7efde786358e3195c9f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 29 Mar 2025 13:56:58 +0100 Subject: [PATCH 11/12] Add non regression test --- .../AccessStaticPropertiesRuleTest.php | 5 +++++ .../PHPStan/Rules/Properties/data/bug-12775.php | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/PHPStan/Rules/Properties/data/bug-12775.php diff --git a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php index 7060aeecea..def7b0d98b 100644 --- a/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessStaticPropertiesRuleTest.php @@ -309,4 +309,9 @@ public function testBug8333(): void ]); } + public function testBug12775(): void + { + $this->analyse([__DIR__ . '/data/bug-12775.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-12775.php b/tests/PHPStan/Rules/Properties/data/bug-12775.php new file mode 100644 index 0000000000..bdade366f3 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12775.php @@ -0,0 +1,17 @@ + Date: Sat, 29 Mar 2025 14:23:25 +0100 Subject: [PATCH 12/12] Review --- src/Dependency/DependencyResolver.php | 2 +- src/Reflection/ClassReflection.php | 31 ++++++------------- .../Properties/AccessStaticPropertiesRule.php | 2 +- src/Type/ObjectType.php | 2 +- .../Annotations/DeprecatedAnnotationsTest.php | 2 +- .../Annotations/InternalAnnotationsTest.php | 2 +- 6 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/Dependency/DependencyResolver.php b/src/Dependency/DependencyResolver.php index f82bb71a12..3c32803347 100644 --- a/src/Dependency/DependencyResolver.php +++ b/src/Dependency/DependencyResolver.php @@ -370,7 +370,7 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies if ($this->reflectionProvider->hasClass($className)) { $propertyClassReflection = $this->reflectionProvider->getClass($className); if ($propertyClassReflection->hasStaticProperty($node->name->toString())) { - $propertyReflection = $propertyClassReflection->getStaticProperty($node->name->toString(), $scope); + $propertyReflection = $propertyClassReflection->getStaticProperty($node->name->toString()); $this->addClassToDependencies($propertyReflection->getDeclaringClass()->getName(), $dependenciesReflections); } } diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 4ca5db6faa..ce2a006ee8 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -813,35 +813,24 @@ public function getInstanceProperty(string $propertyName, ClassMemberAccessAnswe return $this->instanceProperties[$key]; } - public function getStaticProperty(string $propertyName, ClassMemberAccessAnswerer $scope): ExtendedPropertyReflection + public function getStaticProperty(string $propertyName): ExtendedPropertyReflection { $key = $propertyName; - if ($scope->isInClass()) { - $key = sprintf('%s-%s', $key, $scope->getClassReflection()->getCacheKey()); - } - - if (!isset($this->staticProperties[$key])) { - if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) { - $property = $this->wrapExtendedProperty($this->getPhpExtension()->getStaticProperty($this, $propertyName)); - if ($scope->canReadProperty($property)) { - return $this->staticProperties[$key] = $property; - } - $this->staticProperties[$key] = $property; - } + if (isset($this->staticProperties[$key])) { + return $this->staticProperties[$key]; } - if (!isset($this->staticProperties[$key])) { - if ($this->requireExtendsPropertiesClassReflectionExtension->hasStaticProperty($this, $propertyName)) { - $property = $this->requireExtendsPropertiesClassReflectionExtension->getStaticProperty($this, $propertyName); - $this->staticProperties[$key] = $property; - } + if ($this->getPhpExtension()->hasStaticProperty($this, $propertyName)) { + $property = $this->wrapExtendedProperty($this->getPhpExtension()->getStaticProperty($this, $propertyName)); + return $this->staticProperties[$key] = $property; } - if (!isset($this->staticProperties[$key])) { - throw new MissingPropertyFromReflectionException($this->getName(), $propertyName); + if ($this->requireExtendsPropertiesClassReflectionExtension->hasStaticProperty($this, $propertyName)) { + $property = $this->requireExtendsPropertiesClassReflectionExtension->getStaticProperty($this, $propertyName); + return $this->staticProperties[$key] = $property; } - return $this->staticProperties[$key]; + throw new MissingPropertyFromReflectionException($this->getName(), $propertyName); } public function hasNativeProperty(string $propertyName): bool diff --git a/src/Rules/Properties/AccessStaticPropertiesRule.php b/src/Rules/Properties/AccessStaticPropertiesRule.php index 3ce5d5e50d..2502f115f6 100644 --- a/src/Rules/Properties/AccessStaticPropertiesRule.php +++ b/src/Rules/Properties/AccessStaticPropertiesRule.php @@ -184,7 +184,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, while ($parentClassReflection !== null) { if ($parentClassReflection->hasStaticProperty($name)) { - if ($scope->canReadProperty($parentClassReflection->getStaticProperty($name, $scope))) { + if ($scope->canReadProperty($parentClassReflection->getStaticProperty($name))) { return []; } return [ diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index b27d0e3ab1..0a0d19f4d9 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -394,7 +394,7 @@ public function getUnresolvedStaticPropertyPrototype(string $propertyName, Class throw new ClassNotFoundException($this->className); } - $property = $nakedClassReflection->getStaticProperty($propertyName, $scope); + $property = $nakedClassReflection->getStaticProperty($propertyName); $ancestor = $this->getAncestorWithClassName($property->getDeclaringClass()->getName()); $resolvedClassReflection = null; diff --git a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php index c4b7f2f0f1..0610a37549 100644 --- a/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/DeprecatedAnnotationsTest.php @@ -122,7 +122,7 @@ public function testDeprecatedAnnotations(bool $deprecated, string $className, ? } foreach ($deprecatedAnnotations['staticProperty'] ?? [] as $propertyName => $deprecatedMessage) { - $propertyAnnotation = $class->getStaticProperty($propertyName, $scope); + $propertyAnnotation = $class->getStaticProperty($propertyName); $this->assertSame($deprecated, $propertyAnnotation->isDeprecated()->yes()); $this->assertSame($deprecatedMessage, $propertyAnnotation->getDeprecatedDescription()); } diff --git a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php index bf7630e694..749f719194 100644 --- a/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php +++ b/tests/PHPStan/Reflection/Annotations/InternalAnnotationsTest.php @@ -145,7 +145,7 @@ public function testInternalAnnotations(bool $internal, string $className, array } foreach ($internalAnnotations['staticProperty'] ?? [] as $propertyName) { - $propertyAnnotation = $class->getStaticProperty($propertyName, $scope); + $propertyAnnotation = $class->getStaticProperty($propertyName); $this->assertSame($internal, $propertyAnnotation->isInternal()->yes()); }