Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,7 @@ public function enterTrait(ClassReflection $traitReflection): self
* @param array<string, bool> $immediatelyInvokedCallableParameters
* @param array<string, Type> $phpDocClosureThisTypeParameters
* @param array<string, bool> $phpDocPureUnlessCallableIsImpureParameters
* @param array<string, Type> $phpDocClosureScopeTypeParameters
*/
public function enterClassMethod(
Node\Stmt\ClassMethod $classMethod,
Expand All @@ -1678,6 +1679,7 @@ public function enterClassMethod(
bool $isConstructor = false,
?ResolvedPhpDocBlock $resolvedPhpDocBlock = null,
array $phpDocPureUnlessCallableIsImpureParameters = [],
array $phpDocClosureScopeTypeParameters = [],
): self
{
if (!$this->isInClass()) {
Expand Down Expand Up @@ -1714,6 +1716,7 @@ public function enterClassMethod(
$isConstructor,
$this->attributeReflectionFactory->fromAttrGroups($classMethod->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $classMethod)),
$phpDocPureUnlessCallableIsImpureParameters,
array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureScopeTypeParameters),
),
!$classMethod->isStatic(),
);
Expand Down Expand Up @@ -1881,6 +1884,7 @@ private function getParameterAttributes(ClassMethod|Function_|PropertyHook $func
* @param array<string, bool> $immediatelyInvokedCallableParameters
* @param array<string, Type> $phpDocClosureThisTypeParameters
* @param array<string, bool> $pureUnlessCallableIsImpureParameters
* @param array<string, Type> $phpDocClosureScopeTypeParameters
*/
public function enterFunction(
Node\Stmt\Function_ $function,
Expand All @@ -1899,6 +1903,7 @@ public function enterFunction(
array $immediatelyInvokedCallableParameters = [],
array $phpDocClosureThisTypeParameters = [],
array $pureUnlessCallableIsImpureParameters = [],
array $phpDocClosureScopeTypeParameters = [],
): self
{
return $this->enterFunctionLike(
Expand All @@ -1925,6 +1930,7 @@ public function enterFunction(
$phpDocClosureThisTypeParameters,
$this->attributeReflectionFactory->fromAttrGroups($function->attrGroups, InitializerExprContext::fromStubParameter(null, $this->getFile(), $function)),
$pureUnlessCallableIsImpureParameters,
$phpDocClosureScopeTypeParameters,
),
false,
);
Expand Down
52 changes: 39 additions & 13 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1797,6 +1797,43 @@ private function resolveClosureThisType(
return null;
}

/**
* @param FunctionReflection|MethodReflection|null $calleeReflection
* @return array{MutatingScope, MutatingScope|null}
*/
private function applyClosureThisAndScope(
MutatingScope $scopeToPass,
?CallLike $call,
$calleeReflection,
ParameterReflection $parameter,
bool $isStatic,
): array
{
if (!$parameter instanceof ExtendedParameterReflection) {
return [$scopeToPass, null];
}

$closureThisType = null;
if (!$isStatic) {
$closureThisType = $this->resolveClosureThisType($call, $calleeReflection, $parameter, $scopeToPass);
}
$closureScopeType = $parameter->getClosureScopeType();

if ($closureThisType === null && $closureScopeType === null) {
return [$scopeToPass, null];
}

$restoreThisScope = $scopeToPass;
if ($closureThisType !== null) {
$scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes());
}
if ($closureScopeType !== null) {
$scopeToPass = $scopeToPass->withClosureBindScopeClasses($closureScopeType->getObjectClassNames());
}

return [$scopeToPass, $restoreThisScope];
}

/**
* @param MethodReflection|FunctionReflection|null $calleeReflection
* @param ParametersAcceptor[] $parametersAcceptors
Expand Down Expand Up @@ -2039,14 +2076,8 @@ public function processArgs(
if (
$closureBindScopeFactory === null
&& $parameter instanceof ExtendedParameterReflection
&& !$arg->value->static
) {
$closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass);
if ($closureThisType !== null) {
$restoreThisScope = $scopeToPass;
$scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes())
->withClosureBindScopeClasses($closureThisType->getObjectClassNames());
}
[$scopeToPass, $restoreThisScope] = $this->applyClosureThisAndScope($scopeToPass, $callLike, $calleeReflection, $parameter, $arg->value->static);
}

if ($parameter !== null) {
Expand Down Expand Up @@ -2131,13 +2162,8 @@ public function processArgs(
if (
$closureBindScopeFactory === null
&& $parameter instanceof ExtendedParameterReflection
&& !$arg->value->static
) {
$closureThisType = $this->resolveClosureThisType($callLike, $calleeReflection, $parameter, $scopeToPass);
if ($closureThisType !== null) {
$scopeToPass = $scopeToPass->assignVariable('this', $closureThisType, new ObjectWithoutClassType(), TrinaryLogic::createYes())
->withClosureBindScopeClasses($closureThisType->getObjectClassNames());
}
[$scopeToPass] = $this->applyClosureThisAndScope($scopeToPass, $callLike, $calleeReflection, $parameter, $arg->value->static);
}

if ($parameter !== null) {
Expand Down
15 changes: 13 additions & 2 deletions src/Analyser/PhpDocsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,15 @@ public function __construct(
}

/**
* @return array{TemplateTypeMap, array<string, Type>, array<string, bool>, array<string, Type>, ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array<string, Type>, array<(string|int), VarTag>, bool, ?ResolvedPhpDocBlock, array<string, bool>}
* @return array{TemplateTypeMap, array<string, Type>, array<string, bool>, array<string, Type>, ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array<string, Type>, array<(string|int), VarTag>, bool, ?ResolvedPhpDocBlock, array<string, bool>, array<string, Type>}
*/
public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array
{
$templateTypeMap = TemplateTypeMap::createEmpty();
$phpDocParameterTypes = [];
$phpDocImmediatelyInvokedCallableParameters = [];
$phpDocClosureThisTypeParameters = [];
$phpDocClosureScopeTypeParameters = [];
$phpDocReturnType = null;
$phpDocThrowType = null;
$deprecatedDescription = null;
Expand Down Expand Up @@ -181,6 +182,16 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
}
$phpDocClosureThisTypeParameters[$paramName] = $paramClosureThisType;
}
foreach ($resolvedPhpDoc->getParamClosureScopeTags() as $paramName => $paramClosureScopeTag) {
if (array_key_exists($paramName, $phpDocClosureScopeTypeParameters)) {
continue;
}
$paramClosureScopeType = $paramClosureScopeTag->getType();
if ($scope->isInClass()) {
$paramClosureScopeType = $this->transformStaticType($scope->getClassReflection(), $paramClosureScopeType);
}
$phpDocClosureScopeTypeParameters[$paramName] = $paramClosureScopeType;
}

foreach ($resolvedPhpDoc->getParamOutTags() as $paramName => $paramOutTag) {
$phpDocParameterOutTypes[$paramName] = $paramOutTag->getType();
Expand Down Expand Up @@ -228,7 +239,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
}
}

return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation, $resolvedPhpDoc, $phpDocPureUnlessCallableIsImpureParameters];
return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation, $resolvedPhpDoc, $phpDocPureUnlessCallableIsImpureParameters, $phpDocClosureScopeTypeParameters];
}

private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $nativeReturnType): ?Type
Expand Down
3 changes: 2 additions & 1 deletion src/Analyser/StmtHandler/ClassMethodHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function processStmt(
): InternalStatementResult
{
$nodeScopeResolver->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback);
[$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $phpDocComment, $asserts, $selfOutType, $phpDocParameterOutTypes, , , , $pureUnlessCallableIsImpureParameters] = $this->phpDocsResolver->getPhpDocs($scope, $stmt);
[$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $phpDocComment, $asserts, $selfOutType, $phpDocParameterOutTypes, , , , $pureUnlessCallableIsImpureParameters, $phpDocClosureScopeTypeParameters] = $this->phpDocsResolver->getPhpDocs($scope, $stmt);

foreach ($stmt->params as $param) {
$nodeScopeResolver->processParamNode($stmt, $param, $scope, $storage, $nodeCallback);
Expand Down Expand Up @@ -104,6 +104,7 @@ public function processStmt(
$isConstructor,
null,
$pureUnlessCallableIsImpureParameters,
$phpDocClosureScopeTypeParameters,
);

if (!$scope->isInClass()) {
Expand Down
3 changes: 2 additions & 1 deletion src/Analyser/StmtHandler/FunctionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function processStmt(
): InternalStatementResult
{
$nodeScopeResolver->processAttributeGroups($stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback);
[$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, , $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts,, $phpDocParameterOutTypes, , , , $pureUnlessCallableIsImpureParameters] = $this->phpDocsResolver->getPhpDocs($scope, $stmt);
[$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, , $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts,, $phpDocParameterOutTypes, , , , $pureUnlessCallableIsImpureParameters, $phpDocClosureScopeTypeParameters] = $this->phpDocsResolver->getPhpDocs($scope, $stmt);

foreach ($stmt->params as $param) {
$nodeScopeResolver->processParamNode($stmt, $param, $scope, $storage, $nodeCallback);
Expand Down Expand Up @@ -88,6 +88,7 @@ public function processStmt(
$phpDocImmediatelyInvokedCallableParameters,
$phpDocClosureThisTypeParameters,
$pureUnlessCallableIsImpureParameters,
$phpDocClosureScopeTypeParameters,
);
$functionReflection = $functionScope->getFunction();
if (!$functionReflection instanceof PhpFunctionFromParserNodeReflection) {
Expand Down
78 changes: 51 additions & 27 deletions src/Dependency/DependencyResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,15 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
if ($parameter->getClosureThisType() === null) {
continue;
if ($parameter->getClosureThisType() !== null) {
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
if ($parameter->getClosureScopeType() !== null) {
foreach ($parameter->getClosureScopeType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
}
}
Expand Down Expand Up @@ -229,11 +233,15 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
if ($parameter->getClosureThisType() === null) {
continue;
if ($parameter->getClosureThisType() !== null) {
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
if ($parameter->getClosureScopeType() !== null) {
foreach ($parameter->getClosureScopeType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
}
}
Expand Down Expand Up @@ -267,11 +275,15 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
if ($parameter->getClosureThisType() === null) {
continue;
if ($parameter->getClosureThisType() !== null) {
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
if ($parameter->getClosureScopeType() !== null) {
foreach ($parameter->getClosureScopeType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
}
}
Expand Down Expand Up @@ -334,18 +346,22 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
$this->addClassToDependencies($methodReflection->getDeclaringClass()->getName(), $dependenciesReflections);
foreach ($methodReflection->getVariants() as $methodVariant) {
foreach ($methodVariant->getParameters() as $parameter) {
if ($parameter->getOutType() !== null) {
foreach ($parameter->getOutType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
if ($parameter->getClosureThisType() === null) {
continue;
if ($parameter->getOutType() !== null) {
foreach ($parameter->getOutType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
if ($parameter->getClosureThisType() !== null) {
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
if ($parameter->getClosureScopeType() !== null) {
foreach ($parameter->getClosureScopeType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
}
}
}
}
Expand All @@ -360,11 +376,15 @@ public function resolveDependencies(Node $node, Scope $scope): NodeDependencies
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
if ($parameter->getClosureThisType() === null) {
continue;
if ($parameter->getClosureThisType() !== null) {
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
if ($parameter->getClosureScopeType() !== null) {
foreach ($parameter->getClosureScopeType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
}
}
Expand Down Expand Up @@ -838,11 +858,15 @@ private function extractFromParametersAcceptor(
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
if ($parameter->getClosureThisType() === null) {
continue;
if ($parameter->getClosureThisType() !== null) {
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
foreach ($parameter->getClosureThisType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
if ($parameter->getClosureScopeType() !== null) {
foreach ($parameter->getClosureScopeType()->getReferencedClasses() as $referencedClass) {
$this->addClassToDependencies($referencedClass, $dependenciesReflections);
}
}
}

Expand Down
23 changes: 22 additions & 1 deletion src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use PHPStan\PhpDoc\Tag\MethodTag;
use PHPStan\PhpDoc\Tag\MethodTagParameter;
use PHPStan\PhpDoc\Tag\MixinTag;
use PHPStan\PhpDoc\Tag\ParamClosureScopeTag;
use PHPStan\PhpDoc\Tag\ParamClosureThisTag;
use PHPStan\PhpDoc\Tag\ParamOutTag;
use PHPStan\PhpDoc\Tag\ParamTag;
Expand Down Expand Up @@ -47,7 +48,6 @@
use function array_reverse;
use function count;
use function in_array;
use function method_exists;
use function str_starts_with;
use function substr;

Expand Down Expand Up @@ -424,6 +424,27 @@ public function resolveParamClosureThisTags(PhpDocNode $phpDocNode, NameScope $n
return $closureThisTypes;
}

/**
* @return array<string, ParamClosureScopeTag>
*/
public function resolveParamClosureScopeTags(PhpDocNode $phpDocNode, NameScope $nameScope): array
{
$closureScopeTypes = [];
foreach (['@param-closure-scope', '@phpstan-param-closure-scope'] as $tagName) {
foreach ($phpDocNode->getParamClosureScopeTagValues($tagName) as $tagValue) {
$parameterName = substr($tagValue->parameterName, 1);
$closureScopeTypes[$parameterName] = new ParamClosureScopeTag(
TypeCombinator::intersect(
$this->typeNodeResolver->resolve($tagValue->type, $nameScope),
new ObjectWithoutClassType(),
),
);
}
}

return $closureScopeTypes;
}

public function resolveReturnTag(PhpDocNode $phpDocNode, NameScope $nameScope): ?ReturnTag
{
$resolved = null;
Expand Down
Loading
Loading