diff --git a/packages/VendorLocker/Exception/UnresolvableClassException.php b/packages/VendorLocker/Exception/UnresolvableClassException.php new file mode 100644 index 00000000000..2936f4e1bab --- /dev/null +++ b/packages/VendorLocker/Exception/UnresolvableClassException.php @@ -0,0 +1,9 @@ +getParentClassMethod($classMethod) instanceof MethodReflection; + try { + $parentClassMethod = $this->resolveParentClassMethod($classMethod); + + return $parentClassMethod instanceof MethodReflection; + } catch (UnresolvableClassException) { + // we don't know all involved parents. + return null; + } } public function getParentClassMethod(ClassMethod $classMethod): ?MethodReflection + { + try { + $parentClassMethod = $this->resolveParentClassMethod($classMethod); + + return $parentClassMethod; + } catch (UnresolvableClassException) { + // we don't know all involved parents. + throw new ShouldNotHappenException('Unable to resolve involved class. You are likely missing hasParentClassMethod() before calling getParentClassMethod().'); + } + } + + /** + * @throws UnresolvableClassException + */ + private function resolveParentClassMethod(ClassMethod $classMethod): ?MethodReflection { $classReflection = $this->reflectionResolver->resolveClassReflection($classMethod); if (! $classReflection instanceof ClassReflection) { - return null; + // we can't resolve the class, so we don't know. + throw new UnresolvableClassException(); } /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($classMethod); - $parentClassReflection = $classReflection->getParentClass(); - while ($parentClassReflection instanceof ClassReflection) { + $currentClassReflection = $classReflection; + while ($this->hasClassParent($currentClassReflection)) { + $parentClassReflection = $currentClassReflection->getParentClass(); + if (!$parentClassReflection instanceof ClassReflection) { + // per AST we have a parent class, but our reflection classes are not able to load its class definition/signature + throw new UnresolvableClassException(); + } + if ($parentClassReflection->hasNativeMethod($methodName)) { return $parentClassReflection->getNativeMethod($methodName); } - $parentClassReflection = $parentClassReflection->getParentClass(); + $currentClassReflection = $parentClassReflection; } foreach ($classReflection->getInterfaces() as $interfaceReflection) { @@ -57,6 +90,14 @@ public function getParentClassMethod(ClassMethod $classMethod): ?MethodReflectio return null; } + private function hasClassParent(ClassReflection $classReflection):bool { + // XXX rework this hack, after https://github.com/phpstan/phpstan-src/pull/2563 landed + $nativeReflection = $classReflection->getNativeReflection(); + $betterReflectionClass = $this->privatesAccessor->getPrivateProperty($nativeReflection, 'betterReflectionClass'); + $parentClassName = $this->privatesAccessor->getPrivateProperty($betterReflectionClass, 'parentClassName'); + return $parentClassName !== null; + } + public function shouldSkipReturnTypeChange(ClassMethod $classMethod, Type $parentType): bool { if ($classMethod->returnType === null) { diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/skip_unknown_parent.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/skip_unknown_parent.php.inc new file mode 100644 index 00000000000..41285f968dc --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/skip_unknown_parent.php.inc @@ -0,0 +1,19 @@ +someTypedService->run($value); + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictParamRector/Fixture/skip_unknown_parent.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictParamRector/Fixture/skip_unknown_parent.php.inc new file mode 100644 index 00000000000..53d9b5349d3 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictParamRector/Fixture/skip_unknown_parent.php.inc @@ -0,0 +1,9 @@ +items = $args; + } +} diff --git a/rules/Privatization/Guard/ParentPropertyLookupGuard.php b/rules/Privatization/Guard/ParentPropertyLookupGuard.php index cffad4a4448..1935800134f 100644 --- a/rules/Privatization/Guard/ParentPropertyLookupGuard.php +++ b/rules/Privatization/Guard/ParentPropertyLookupGuard.php @@ -48,6 +48,7 @@ public function isLegal(Property $property, ?ClassReflection $classReflection): return false; } + // XXX rework this hack, after https://github.com/phpstan/phpstan-src/pull/2563 landed $nativeReflection = $classReflection->getNativeReflection(); $betterReflectionClass = $this->privatesAccessor->getPrivateProperty( $nativeReflection, diff --git a/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromPropertyTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromPropertyTypeRector.php index f8d16f41a43..41c0cab3fe1 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromPropertyTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromPropertyTypeRector.php @@ -123,7 +123,7 @@ function (Node $node) use ($paramName): bool { continue; } - if ($this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($node)) { + if ($this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($node) !== false) { return null; } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector.php index a316b784867..f7f5fa358ff 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector.php @@ -146,7 +146,7 @@ private function shouldSkipClassMethod(ClassMethod $classMethod): bool return true; } - return $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod); + return $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod) !== false; } private function mirrorParamType( diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictParamRector.php b/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictParamRector.php index caf2ef3a48b..fee82c31a30 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictParamRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromStrictParamRector.php @@ -202,7 +202,7 @@ private function shouldSkipNode(ClassMethod|Function_|Closure $node): bool } if ($node instanceof ClassMethod) { - if ($this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($node)) { + if ($this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($node) !== false) { return true; } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector.php b/rules/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector.php index 576a590b5fe..ec2e97ea955 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/StrictArrayParamDimFetchRector.php @@ -76,7 +76,7 @@ public function refactor(Node $node): ?Node { $hasChanged = false; - if ($node instanceof ClassMethod && $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($node)) { + if ($node instanceof ClassMethod && $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($node) !== false) { return null; } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/StrictStringParamConcatRector.php b/rules/TypeDeclaration/Rector/ClassMethod/StrictStringParamConcatRector.php index e8ff5d77f2b..f19113d7c11 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/StrictStringParamConcatRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/StrictStringParamConcatRector.php @@ -75,7 +75,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if ($node instanceof ClassMethod && $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($node)) { + if ($node instanceof ClassMethod && $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($node) !== false) { return null; } diff --git a/rules/TypeDeclaration/Rector/Param/ParamTypeFromStrictTypedPropertyRector.php b/rules/TypeDeclaration/Rector/Param/ParamTypeFromStrictTypedPropertyRector.php index 37adc45e238..fe6ff60f39a 100644 --- a/rules/TypeDeclaration/Rector/Param/ParamTypeFromStrictTypedPropertyRector.php +++ b/rules/TypeDeclaration/Rector/Param/ParamTypeFromStrictTypedPropertyRector.php @@ -115,7 +115,7 @@ private function decorateParamWithType(ClassMethod $classMethod, Param $param, S return null; } - if ($this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod)) { + if ($this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod) !== false) { return null; } diff --git a/src/PhpParser/Node/Value/ValueResolver.php b/src/PhpParser/Node/Value/ValueResolver.php index 17eb848866f..d10bcdded66 100644 --- a/src/PhpParser/Node/Value/ValueResolver.php +++ b/src/PhpParser/Node/Value/ValueResolver.php @@ -333,6 +333,7 @@ private function resolveClassFromSelfStaticParent(ClassConstFetch $classConstFet ); } + // XXX rework this hack, after https://github.com/phpstan/phpstan-src/pull/2563 landed // ensure parent class name still resolved even not autoloaded $nativeReflection = $classReflection->getNativeReflection(); $betterReflectionClass = $this->privatesAccessor->getPrivateProperty(