|
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,52 @@ 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 | + $type = $extension->getClosureThisTypeFromFunctionCall($calleeReflection, $call, $parameter, $scope); |
| 5079 | + if ($type !== null) { |
| 5080 | + return $type; |
| 5081 | + } |
| 5082 | + } |
| 5083 | + } |
| 5084 | + } elseif ($call instanceof StaticCall && $calleeReflection instanceof MethodReflection) { |
| 5085 | + foreach ($this->parameterClosureThisExtensionProvider->getStaticMethodParameterClosureThisExtensions() as $extension) { |
| 5086 | + if ($extension->isStaticMethodSupported($calleeReflection, $parameter)) { |
| 5087 | + $type = $extension->getClosureThisTypeFromStaticMethodCall($calleeReflection, $call, $parameter, $scope); |
| 5088 | + if ($type !== null) { |
| 5089 | + return $type; |
| 5090 | + } |
| 5091 | + } |
| 5092 | + } |
| 5093 | + } elseif ($call instanceof MethodCall && $calleeReflection instanceof MethodReflection) { |
| 5094 | + foreach ($this->parameterClosureThisExtensionProvider->getMethodParameterClosureThisExtensions() as $extension) { |
| 5095 | + if ($extension->isMethodSupported($calleeReflection, $parameter)) { |
| 5096 | + $type = $extension->getClosureThisTypeFromMethodCall($calleeReflection, $call, $parameter, $scope); |
| 5097 | + if ($type !== null) { |
| 5098 | + return $type; |
| 5099 | + } |
| 5100 | + } |
| 5101 | + } |
| 5102 | + } |
| 5103 | + |
| 5104 | + if ($parameter instanceof ExtendedParameterReflection) { |
| 5105 | + return $parameter->getClosureThisType(); |
| 5106 | + } |
| 5107 | + |
| 5108 | + return null; |
| 5109 | + } |
| 5110 | + |
5063 | 5111 | /** |
5064 | 5112 | * @param MethodReflection|FunctionReflection|null $calleeReflection |
5065 | 5113 | * @param callable(Node $node, Scope $scope): void $nodeCallback |
@@ -5160,14 +5208,12 @@ private function processArgs( |
5160 | 5208 |
|
5161 | 5209 | if ($arg->value instanceof Expr\Closure) { |
5162 | 5210 | $restoreThisScope = null; |
5163 | | - if ( |
5164 | | - $closureBindScope === null |
5165 | | - && $parameter instanceof ExtendedParameterReflection |
5166 | | - && $parameter->getClosureThisType() !== null |
5167 | | - && !$arg->value->static |
5168 | | - ) { |
5169 | | - $restoreThisScope = $scopeToPass; |
5170 | | - $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType(), TrinaryLogic::createYes()); |
| 5211 | + if ($closureBindScope === null && !$arg->value->static) { |
| 5212 | + $closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass); |
| 5213 | + if ($closureThisType !== null) { |
| 5214 | + $restoreThisScope = $scopeToPass; |
| 5215 | + $scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()); |
| 5216 | + } |
5171 | 5217 | } |
5172 | 5218 |
|
5173 | 5219 | if ($parameter !== null) { |
@@ -5214,13 +5260,11 @@ private function processArgs( |
5214 | 5260 |
|
5215 | 5261 | $scope = $this->processImmediatelyCalledCallable($scope, $invalidateExpressions, $uses); |
5216 | 5262 | } elseif ($arg->value instanceof Expr\ArrowFunction) { |
5217 | | - if ( |
5218 | | - $closureBindScope === null |
5219 | | - && $parameter instanceof ExtendedParameterReflection |
5220 | | - && $parameter->getClosureThisType() !== null |
5221 | | - && !$arg->value->static |
5222 | | - ) { |
5223 | | - $scopeToPass = $scopeToPass->assignVariable('this', $parameter->getClosureThisType(), new ObjectWithoutClassType(), TrinaryLogic::createYes()); |
| 5263 | + if ($closureBindScope === null && !$arg->value->static) { |
| 5264 | + $closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass); |
| 5265 | + if ($closureThisType !== null) { |
| 5266 | + $scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()); |
| 5267 | + } |
5224 | 5268 | } |
5225 | 5269 |
|
5226 | 5270 | if ($parameter !== null) { |
|
0 commit comments