diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 1f843048b2..b4da779f33 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2020,7 +2020,7 @@ public function restoreThis(self $restoreThisScope): self $expressionTypes, $nativeExpressionTypes, $this->conditionalExpressions, - $this->inClosureBindScopeClasses, + $restoreThisScope->inClosureBindScopeClasses, $this->anonymousFunctionReflection, $this->inFirstLevelStatement, [], @@ -2059,6 +2059,31 @@ public function isInClosureBind(): bool return $this->inClosureBindScopeClasses !== []; } + /** + * @param list $scopeClasses + */ + public function withClosureBindScopeClasses(array $scopeClasses): self + { + return $this->scopeFactory->create( + $this->context, + $this->isDeclareStrictTypes(), + $this->getFunction(), + $this->getNamespace(), + $this->expressionTypes, + $this->nativeExpressionTypes, + $this->conditionalExpressions, + $scopeClasses, + $this->anonymousFunctionReflection, + $this->isInFirstLevelStatement(), + $this->currentlyAssignedExpressions, + $this->currentlyAllowedUndefinedExpressions, + $this->inFunctionCallsStack, + $this->afterExtractCall, + $this->parentScope, + $this->nativeTypesPromoted, + ); + } + /** * @api * @param ParameterReflection[]|null $callableParameters @@ -2397,6 +2422,23 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type false, )), new AccessoryArrayListType()]); } + if ( + $type instanceof Name + && $this->inClosureBindScopeClasses !== [] + && $this->inClosureBindScopeClasses !== ['static'] + && in_array($type->toLowerString(), ['static', 'self', 'parent'], true) + && $this->reflectionProvider->hasClass($this->inClosureBindScopeClasses[0]) + ) { + return $this->initializerExprTypeResolver->getFunctionType( + $type, + $isNullable, + false, + InitializerExprContext::fromClassReflection( + $this->reflectionProvider->getClass($this->inClosureBindScopeClasses[0]), + ), + ); + } + return $this->initializerExprTypeResolver->getFunctionType($type, $isNullable, false, InitializerExprContext::fromScope($this)); } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index cb407c048a..a4bbb443c6 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3656,7 +3656,8 @@ public function processArgs( $closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass); if ($closureThisType !== null) { $restoreThisScope = $scopeToPass; - $scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()); + $scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()) + ->withClosureBindScopeClasses($closureThisType->getObjectClassNames()); } } @@ -3722,7 +3723,8 @@ public function processArgs( ) { $closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass); if ($closureThisType !== null) { - $scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()); + $scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes()) + ->withClosureBindScopeClasses($closureThisType->getObjectClassNames()); } } diff --git a/tests/PHPStan/Analyser/nsrt/param-closure-this.php b/tests/PHPStan/Analyser/nsrt/param-closure-this.php index 96f9fb8f15..ec32bbde8d 100644 --- a/tests/PHPStan/Analyser/nsrt/param-closure-this.php +++ b/tests/PHPStan/Analyser/nsrt/param-closure-this.php @@ -316,3 +316,31 @@ public function doFoo(): void } } + +class StaticReturnClosureThis +{ + + /** + * @param-closure-this Foo $cb + */ + public function withFoo(callable $cb): void + { + + } + +} + +class TestStaticResolutionInParamClosureThis +{ + + public function test(StaticReturnClosureThis $o): void + { + $o->withFoo(function (): static { + assertType('ParamClosureThis\Foo', $this); + return $this; + }); + + $o->withFoo(fn (): static => $this); + } + +}