Skip to content

Commit 617a3a8

Browse files
committed
Rework phpDoc inheritance to resolve through reflection instead of re-walking the hierarchy
1 parent c2075d3 commit 617a3a8

31 files changed

Lines changed: 760 additions & 921 deletions

src/Analyser/MutatingScope.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
use PHPStan\Php\PhpVersion;
5858
use PHPStan\Php\PhpVersionFactory;
5959
use PHPStan\Php\PhpVersions;
60+
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
6061
use PHPStan\Reflection\Assertions;
6162
use PHPStan\Reflection\AttributeReflection;
6263
use PHPStan\Reflection\AttributeReflectionFactory;
@@ -2143,6 +2144,7 @@ public function enterClassMethod(
21432144
array $immediatelyInvokedCallableParameters = [],
21442145
array $phpDocClosureThisTypeParameters = [],
21452146
bool $isConstructor = false,
2147+
?ResolvedPhpDocBlock $resolvedPhpDocBlock = null,
21462148
): self
21472149
{
21482150
if (!$this->isInClass()) {
@@ -2172,6 +2174,7 @@ public function enterClassMethod(
21722174
$asserts ?? Assertions::createEmpty(),
21732175
$selfOutType,
21742176
$phpDocComment,
2177+
$resolvedPhpDocBlock,
21752178
array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $parameterOutTypes),
21762179
$immediatelyInvokedCallableParameters,
21772180
array_map(fn (Type $type): Type => $this->transformStaticType(TemplateTypeHelper::toArgument($type)), $phpDocClosureThisTypeParameters),
@@ -2195,6 +2198,7 @@ public function enterPropertyHook(
21952198
?string $deprecatedDescription,
21962199
bool $isDeprecated,
21972200
?string $phpDocComment,
2201+
?ResolvedPhpDocBlock $resolvedPhpDocBlock = null,
21982202
): self
21992203
{
22002204
if (!$this->isInClass()) {
@@ -2259,6 +2263,7 @@ public function enterPropertyHook(
22592263
Assertions::createEmpty(),
22602264
null,
22612265
$phpDocComment,
2266+
$resolvedPhpDocBlock,
22622267
[],
22632268
[],
22642269
[],

src/Analyser/NodeScopeResolver.php

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5376,7 +5376,7 @@ private function processPropertyHooks(
53765376
$this->callNodeCallback($nodeCallback, $hook, $scope, $storage);
53775377
$this->processAttributeGroups($stmt, $hook->attrGroups, $scope, $storage, $nodeCallback);
53785378

5379-
[, $phpDocParameterTypes,,,, $phpDocThrowType,,,,,,,, $phpDocComment] = $this->getPhpDocs($scope, $hook);
5379+
[, $phpDocParameterTypes,,,, $phpDocThrowType,,,,,,,, $phpDocComment,,,,,, $resolvedPhpDoc] = $this->getPhpDocs($scope, $hook);
53805380

53815381
foreach ($hook->params as $param) {
53825382
$this->processParamNode($stmt, $param, $scope, $storage, $nodeCallback);
@@ -5394,6 +5394,7 @@ private function processPropertyHooks(
53945394
$deprecatedDescription,
53955395
$isDeprecated,
53965396
$phpDocComment,
5397+
$resolvedPhpDoc,
53975398
);
53985399
$hookReflection = $hookScope->getFunction();
53995400
if (!$hookReflection instanceof PhpMethodFromParserNodeReflection) {
@@ -7266,7 +7267,7 @@ private function processNodesForCalledMethod($node, ExpressionResultStorage $sto
72667267
}
72677268

72687269
/**
7269-
* @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}
7270+
* @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}
72707271
*/
72717272
public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array
72727273
{
@@ -7309,12 +7310,20 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
73097310

73107311
return $param->var->name;
73117312
}, $node->getParams());
7313+
$currentResolvedPhpDoc = null;
7314+
if ($docComment !== null) {
7315+
$currentResolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc(
7316+
$file,
7317+
$class,
7318+
$trait,
7319+
$node->name->name,
7320+
$docComment,
7321+
);
7322+
}
73127323
$resolvedPhpDoc = $this->phpDocInheritanceResolver->resolvePhpDocForMethod(
7313-
$docComment,
7314-
$file,
73157324
$scope->getClassReflection(),
7316-
$trait,
73177325
$node->name->name,
7326+
$currentResolvedPhpDoc,
73187327
$positionalParameterNames,
73197328
);
73207329

@@ -7416,16 +7425,17 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
74167425
$isPure = $resolvedPhpDoc->isPure();
74177426
$isAllowedPrivateMutation = $resolvedPhpDoc->isAllowedPrivateMutation();
74187427
$acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments();
7419-
if ($acceptsNamedArguments && $scope->isInClass()) {
7420-
$acceptsNamedArguments = $scope->getClassReflection()->acceptsNamedArguments();
7421-
}
74227428
$isReadOnly = $isReadOnly || $resolvedPhpDoc->isReadOnly();
74237429
$asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc);
74247430
$selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null;
74257431
$varTags = $resolvedPhpDoc->getVarTags();
74267432
}
74277433

7428-
return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation];
7434+
if ($acceptsNamedArguments && $scope->isInClass()) {
7435+
$acceptsNamedArguments = $scope->getClassReflection()->acceptsNamedArguments();
7436+
}
7437+
7438+
return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation, $resolvedPhpDoc];
74297439
}
74307440

74317441
private function transformStaticType(ClassReflection $declaringClass, Type $type): Type
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDoc;
4+
5+
use PHPStan\PhpDoc\Tag\AssertTagParameter;
6+
use PHPStan\Type\ConditionalTypeForParameter;
7+
use PHPStan\Type\Type;
8+
use PHPStan\Type\TypeTraverser;
9+
use function array_key_exists;
10+
use function substr;
11+
12+
final class InheritedPhpDocParameterMapping
13+
{
14+
15+
/**
16+
* @param array<string, string> $parameterNameMapping
17+
*/
18+
public function __construct(
19+
private array $parameterNameMapping,
20+
)
21+
{
22+
}
23+
24+
/**
25+
* @template T
26+
* @param array<string, T> $array
27+
* @return array<string, T>
28+
*/
29+
public function transformArrayKeysWithParameterNameMapping(array $array): array
30+
{
31+
$newArray = [];
32+
foreach ($array as $key => $value) {
33+
if (!array_key_exists($key, $this->parameterNameMapping)) {
34+
continue;
35+
}
36+
$newArray[$this->parameterNameMapping[$key]] = $value;
37+
}
38+
39+
return $newArray;
40+
}
41+
42+
public function transformConditionalReturnTypeWithParameterNameMapping(Type $type): Type
43+
{
44+
$nameMapping = $this->parameterNameMapping;
45+
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($nameMapping): Type {
46+
if ($type instanceof ConditionalTypeForParameter) {
47+
$parameterName = substr($type->getParameterName(), 1);
48+
if (array_key_exists($parameterName, $nameMapping)) {
49+
$type = $type->changeParameterName('$' . $nameMapping[$parameterName]);
50+
}
51+
}
52+
53+
return $traverse($type);
54+
});
55+
}
56+
57+
public function transformAssertTagParameterWithParameterNameMapping(AssertTagParameter $parameter): AssertTagParameter
58+
{
59+
$parameterName = substr($parameter->getParameterName(), 1);
60+
if (array_key_exists($parameterName, $this->parameterNameMapping)) {
61+
$parameter = $parameter->changeParameterName('$' . $this->parameterNameMapping[$parameterName]);
62+
}
63+
64+
return $parameter;
65+
}
66+
67+
}

0 commit comments

Comments
 (0)