diff --git a/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php b/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php index 5e8723881e4..f96422e6dd0 100644 --- a/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php +++ b/rules/TypeDeclaration/PHPStan/ObjectTypeSpecifier.php @@ -64,6 +64,13 @@ public function narrowToFullyQualifiedOrAliasedObjectType( } if ($this->reflectionProvider->hasClass($className)) { + // keep the scope class reflection of the current class, as it is refreshed on class change, + // while the reflection provider one still reads the original file + $classReflection = $scope?->getClassReflection(); + if ($classReflection instanceof ClassReflection && $classReflection->getName() === $className) { + return new FullyQualifiedObjectType($className, null, $classReflection); + } + return new FullyQualifiedObjectType($className); } diff --git a/tests/Issues/ClassReflectionRefresh/Source/AddTraitByReflectionRector.php b/tests/Issues/ClassReflectionRefresh/Source/AddTraitByReflectionRector.php index bad1968199b..cf866a57485 100644 --- a/tests/Issues/ClassReflectionRefresh/Source/AddTraitByReflectionRector.php +++ b/tests/Issues/ClassReflectionRefresh/Source/AddTraitByReflectionRector.php @@ -47,7 +47,16 @@ public function refactor(Node $node): ?Class_ return null; } - if ($classReflection->hasTraitUse(SomeTrait::class)) { + // both the scope and the resolved type must see the refreshed class reflection + $typeClassReflection = $this->getType($node) + ->getObjectClassReflections()[0] ?? null; + + $hasTraitInScope = $classReflection->hasTraitUse(SomeTrait::class); + $hasTraitInType = $typeClassReflection instanceof ClassReflection && $typeClassReflection->hasTraitUse( + SomeTrait::class + ); + + if ($hasTraitInScope && $hasTraitInType) { return null; } diff --git a/tests/Issues/ClassReflectionRefreshOnModifier/ClassReflectionRefreshOnModifierTest.php b/tests/Issues/ClassReflectionRefreshOnModifier/ClassReflectionRefreshOnModifierTest.php new file mode 100644 index 00000000000..57325fa777c --- /dev/null +++ b/tests/Issues/ClassReflectionRefreshOnModifier/ClassReflectionRefreshOnModifierTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Issues/ClassReflectionRefreshOnModifier/Fixture/refresh_on_class_change.php.inc b/tests/Issues/ClassReflectionRefreshOnModifier/Fixture/refresh_on_class_change.php.inc new file mode 100644 index 00000000000..11dfb92a969 --- /dev/null +++ b/tests/Issues/ClassReflectionRefreshOnModifier/Fixture/refresh_on_class_change.php.inc @@ -0,0 +1,25 @@ + +----- + diff --git a/tests/Issues/ClassReflectionRefreshOnModifier/Fixture/refresh_on_class_change_with_imported_interface.php.inc b/tests/Issues/ClassReflectionRefreshOnModifier/Fixture/refresh_on_class_change_with_imported_interface.php.inc new file mode 100644 index 00000000000..ee2eb92f20e --- /dev/null +++ b/tests/Issues/ClassReflectionRefreshOnModifier/Fixture/refresh_on_class_change_with_imported_interface.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/tests/Issues/ClassReflectionRefreshOnModifier/Source/FinalizeByReflectionRector.php b/tests/Issues/ClassReflectionRefreshOnModifier/Source/FinalizeByReflectionRector.php new file mode 100644 index 00000000000..85692e0fdee --- /dev/null +++ b/tests/Issues/ClassReflectionRefreshOnModifier/Source/FinalizeByReflectionRector.php @@ -0,0 +1,49 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Class_ + { + $classReflection = $this->reflectionResolver->resolveClassReflection($node); + if (! $classReflection instanceof ClassReflection || $classReflection->isFinal()) { + return null; + } + + $node->flags |= Modifiers::FINAL; + + return $node; + } +} diff --git a/tests/Issues/ClassReflectionRefreshOnModifier/Source/ReadonlyOnFinalByReflectionRector.php b/tests/Issues/ClassReflectionRefreshOnModifier/Source/ReadonlyOnFinalByReflectionRector.php new file mode 100644 index 00000000000..9e137f45de5 --- /dev/null +++ b/tests/Issues/ClassReflectionRefreshOnModifier/Source/ReadonlyOnFinalByReflectionRector.php @@ -0,0 +1,63 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Class_ + { + $scopeClassReflection = $this->reflectionResolver->resolveClassReflection($node); + if (! $scopeClassReflection instanceof ClassReflection || ! $scopeClassReflection->isFinal()) { + return null; + } + + if ($scopeClassReflection->isReadOnly()) { + return null; + } + + $typeClassReflection = $this->getType($node) + ->getObjectClassReflections()[0] ?? null; + if (! $typeClassReflection instanceof ClassReflection || ! $typeClassReflection->isFinal()) { + return null; + } + + $node->flags |= Modifiers::READONLY; + + return $node; + } +} diff --git a/tests/Issues/ClassReflectionRefreshOnModifier/Source/SomeInterface.php b/tests/Issues/ClassReflectionRefreshOnModifier/Source/SomeInterface.php new file mode 100644 index 00000000000..fce8fdb4fef --- /dev/null +++ b/tests/Issues/ClassReflectionRefreshOnModifier/Source/SomeInterface.php @@ -0,0 +1,9 @@ +withRules([FinalizeByReflectionRector::class, ReadonlyOnFinalByReflectionRector::class]);