|
67 | 67 | use PHPStan\DependencyInjection\AutowiredService; |
68 | 68 | use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider; |
69 | 69 | use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider; |
| 70 | +use PHPStan\DependencyInjection\Type\ParameterClosureThisExtensionProvider; |
70 | 71 | use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider; |
71 | 72 | use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider; |
72 | 73 | use PHPStan\File\FileHelper; |
@@ -272,6 +273,7 @@ public function __construct( |
272 | 273 | private readonly TypeSpecifier $typeSpecifier, |
273 | 274 | private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider, |
274 | 275 | private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider, |
| 276 | + private readonly ParameterClosureThisExtensionProvider $parameterClosureThisExtensionProvider, |
275 | 277 | private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider, |
276 | 278 | private readonly ScopeFactory $scopeFactory, |
277 | 279 | #[AutowiredParameter] |
@@ -5060,6 +5062,55 @@ private function processPropertyHooks( |
5060 | 5062 | } |
5061 | 5063 | } |
5062 | 5064 |
|
| 5065 | + /** |
| 5066 | + * @param FunctionReflection|MethodReflection|null $calleeReflection |
| 5067 | + */ |
| 5068 | + public function resolveClosureThisType( |
| 5069 | + ?CallLike $call, |
| 5070 | + $calleeReflection, |
| 5071 | + ParameterReflection $parameter, |
| 5072 | + MutatingScope $scope, |
| 5073 | + ): ?Type |
| 5074 | + { |
| 5075 | + if ($call instanceof FuncCall && $calleeReflection instanceof FunctionReflection) { |
| 5076 | + foreach ($this->parameterClosureThisExtensionProvider->getFunctionParameterClosureThisExtensions() as $extension) { |
| 5077 | + if (! $extension->isFunctionSupported($calleeReflection, $parameter)) { |
| 5078 | + continue; |
| 5079 | + } |
| 5080 | + $type = $extension->getClosureThisTypeFromFunctionCall($calleeReflection, $call, $parameter, $scope); |
| 5081 | + if ($type !== null) { |
| 5082 | + return $type; |
| 5083 | + } |
| 5084 | + } |
| 5085 | + } elseif ($call instanceof StaticCall && $calleeReflection instanceof MethodReflection) { |
| 5086 | + foreach ($this->parameterClosureThisExtensionProvider->getStaticMethodParameterClosureThisExtensions() as $extension) { |
| 5087 | + if (! $extension->isStaticMethodSupported($calleeReflection, $parameter)) { |
| 5088 | + continue; |
| 5089 | + } |
| 5090 | + $type = $extension->getClosureThisTypeFromStaticMethodCall($calleeReflection, $call, $parameter, $scope); |
| 5091 | + if ($type !== null) { |
| 5092 | + return $type; |
| 5093 | + } |
| 5094 | + } |
| 5095 | + } elseif ($call instanceof MethodCall && $calleeReflection instanceof MethodReflection) { |
| 5096 | + foreach ($this->parameterClosureThisExtensionProvider->getMethodParameterClosureThisExtensions() as $extension) { |
| 5097 | + if (! $extension->isMethodSupported($calleeReflection, $parameter)) { |
| 5098 | + continue; |
| 5099 | + } |
| 5100 | + $type = $extension->getClosureThisTypeFromMethodCall($calleeReflection, $call, $parameter, $scope); |
| 5101 | + if ($type !== null) { |
| 5102 | + return $type; |
| 5103 | + } |
| 5104 | + } |
| 5105 | + } |
| 5106 | + |
| 5107 | + if ($parameter instanceof ExtendedParameterReflection) { |
| 5108 | + return $parameter->getClosureThisType(); |
| 5109 | + } |
| 5110 | + |
| 5111 | + return null; |
| 5112 | + } |
| 5113 | + |
5063 | 5114 | /** |
5064 | 5115 | * @param MethodReflection|FunctionReflection|null $calleeReflection |
5065 | 5116 | * @param callable(Node $node, Scope $scope): void $nodeCallback |
@@ -5163,11 +5214,13 @@ private function processArgs( |
5163 | 5214 | if ( |
5164 | 5215 | $closureBindScope === null |
5165 | 5216 | && $parameter instanceof ExtendedParameterReflection |
5166 | | - && $parameter->getClosureThisType() !== null |
5167 | 5217 | && !$arg->value->static |
5168 | 5218 | ) { |
5169 | | - $restoreThisScope = $scopeToPass; |
5170 | | - $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType(), TrinaryLogic::createYes()); |
| 5219 | + $closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass); |
| 5220 | + if ($closureThisType !== null) { |
| 5221 | + $restoreThisScope = $scopeToPass; |
| 5222 | + $scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()); |
| 5223 | + } |
5171 | 5224 | } |
5172 | 5225 |
|
5173 | 5226 | if ($parameter !== null) { |
@@ -5217,10 +5270,12 @@ private function processArgs( |
5217 | 5270 | if ( |
5218 | 5271 | $closureBindScope === null |
5219 | 5272 | && $parameter instanceof ExtendedParameterReflection |
5220 | | - && $parameter->getClosureThisType() !== null |
5221 | 5273 | && !$arg->value->static |
5222 | 5274 | ) { |
5223 | | - $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType(), TrinaryLogic::createYes()); |
| 5275 | + $closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass); |
| 5276 | + if ($closureThisType !== null) { |
| 5277 | + $scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()); |
| 5278 | + } |
5224 | 5279 | } |
5225 | 5280 |
|
5226 | 5281 | if ($parameter !== null) { |
|
0 commit comments