Skip to content

Commit ae238a1

Browse files
committed
Parameter name remapping when inheriting phpDocs
1 parent 86ec127 commit ae238a1

File tree

7 files changed

+301
-19
lines changed

7 files changed

+301
-19
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2535,12 +2535,22 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike $functionLike): array
25352535
throw new \PHPStan\ShouldNotHappenException();
25362536
}
25372537
$functionName = $functionLike->name->name;
2538+
$positionalParameterNames = array_map(static function (Node\Param $param): string {
2539+
if (!$param->var instanceof Variable || !is_string($param->var->name)) {
2540+
throw new \PHPStan\ShouldNotHappenException();
2541+
}
2542+
2543+
return $param->var->name;
2544+
}, $functionLike->getParams());
25382545
$phpDocBlock = PhpDocBlock::resolvePhpDocBlockForMethod(
25392546
$docComment,
25402547
$scope->getClassReflection(),
25412548
$trait,
25422549
$functionLike->name->name,
2543-
$file
2550+
$file,
2551+
null,
2552+
$positionalParameterNames,
2553+
$positionalParameterNames
25442554
);
25452555

25462556
if ($phpDocBlock !== null) {
@@ -2572,6 +2582,9 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike $functionLike): array
25722582
}
25732583
return $tag->getType();
25742584
}, $resolvedPhpDoc->getParamTags());
2585+
if ($phpDocBlock !== null) {
2586+
$phpDocParameterTypes = $phpDocBlock->transformArrayKeysWithParameterNameMapping($phpDocParameterTypes);
2587+
}
25752588
$nativeReturnType = $scope->getFunctionType($functionLike->getReturnType(), false, false);
25762589
$phpDocReturnType = $this->getPhpDocReturnType($phpDocBlock, $resolvedPhpDoc, $nativeReturnType);
25772590
$phpDocThrowType = $resolvedPhpDoc->getThrowsTag() !== null ? $resolvedPhpDoc->getThrowsTag()->getType() : null;

src/PhpDoc/PhpDocBlock.php

Lines changed: 141 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,32 @@ class PhpDocBlock
2626
/** @var bool */
2727
private $explicit;
2828

29+
/** @var array<string, string> */
30+
private $parameterNameMapping;
31+
32+
/**
33+
* @param string $docComment
34+
* @param string $file
35+
* @param \PHPStan\Reflection\ClassReflection $classReflection
36+
* @param string|null $trait
37+
* @param bool $explicit
38+
* @param array<string, string> $parameterNameMapping
39+
*/
2940
private function __construct(
3041
string $docComment,
3142
string $file,
3243
ClassReflection $classReflection,
3344
?string $trait,
34-
bool $explicit
45+
bool $explicit,
46+
array $parameterNameMapping
3547
)
3648
{
3749
$this->docComment = $docComment;
3850
$this->file = $file;
3951
$this->classReflection = $classReflection;
4052
$this->trait = $trait;
4153
$this->explicit = $explicit;
54+
$this->parameterNameMapping = $parameterNameMapping;
4255
}
4356

4457
public function getDocComment(): string
@@ -66,13 +79,44 @@ public function isExplicit(): bool
6679
return $this->explicit;
6780
}
6881

82+
/**
83+
* @template T
84+
* @param array<string, T> $array
85+
* @return array<string, T>
86+
*/
87+
public function transformArrayKeysWithParameterNameMapping(array $array): array
88+
{
89+
$newArray = [];
90+
foreach ($array as $key => $value) {
91+
if (!array_key_exists($key, $this->parameterNameMapping)) {
92+
continue;
93+
}
94+
$newArray[$this->parameterNameMapping[$key]] = $value;
95+
}
96+
97+
return $newArray;
98+
}
99+
100+
/**
101+
* @param string|null $docComment
102+
* @param \PHPStan\Reflection\ClassReflection $classReflection
103+
* @param string|null $trait
104+
* @param string $propertyName
105+
* @param string $file
106+
* @param bool|null $explicit
107+
* @param array<int, string> $originalPositionalParameterNames
108+
* @param array<int, string> $newPositionalParameterNames
109+
* @return self|null
110+
*/
69111
public static function resolvePhpDocBlockForProperty(
70112
?string $docComment,
71113
ClassReflection $classReflection,
72114
?string $trait,
73115
string $propertyName,
74116
string $file,
75-
?bool $explicit = null
117+
?bool $explicit,
118+
array $originalPositionalParameterNames, // unused
119+
array $newPositionalParameterNames // unused
76120
): ?self
77121
{
78122
return self::resolvePhpDocBlock(
@@ -84,17 +128,32 @@ public static function resolvePhpDocBlockForProperty(
84128
'hasNativeProperty',
85129
'getNativeProperty',
86130
__FUNCTION__,
87-
$explicit
131+
$explicit,
132+
[],
133+
[]
88134
);
89135
}
90136

137+
/**
138+
* @param string|null $docComment
139+
* @param \PHPStan\Reflection\ClassReflection $classReflection
140+
* @param string|null $trait
141+
* @param string $methodName
142+
* @param string $file
143+
* @param bool|null $explicit
144+
* @param array<int, string> $originalPositionalParameterNames
145+
* @param array<int, string> $newPositionalParameterNames
146+
* @return self|null
147+
*/
91148
public static function resolvePhpDocBlockForMethod(
92149
?string $docComment,
93150
ClassReflection $classReflection,
94151
?string $trait,
95152
string $methodName,
96153
string $file,
97-
?bool $explicit = null
154+
?bool $explicit,
155+
array $originalPositionalParameterNames,
156+
array $newPositionalParameterNames
98157
): ?self
99158
{
100159
return self::resolvePhpDocBlock(
@@ -106,10 +165,26 @@ public static function resolvePhpDocBlockForMethod(
106165
'hasNativeMethod',
107166
'getNativeMethod',
108167
__FUNCTION__,
109-
$explicit
168+
$explicit,
169+
$originalPositionalParameterNames,
170+
$newPositionalParameterNames
110171
);
111172
}
112173

174+
/**
175+
* @param string|null $docComment
176+
* @param \PHPStan\Reflection\ClassReflection $classReflection
177+
* @param string|null $trait
178+
* @param string $name
179+
* @param string $file
180+
* @param string $hasMethodName
181+
* @param string $getMethodName
182+
* @param string $resolveMethodName
183+
* @param bool|null $explicit
184+
* @param array<int, string> $originalPositionalParameterNames
185+
* @param array<int, string> $newPositionalParameterNames
186+
* @return self|null
187+
*/
113188
private static function resolvePhpDocBlock(
114189
?string $docComment,
115190
ClassReflection $classReflection,
@@ -119,7 +194,9 @@ private static function resolvePhpDocBlock(
119194
string $hasMethodName,
120195
string $getMethodName,
121196
string $resolveMethodName,
122-
?bool $explicit
197+
?bool $explicit,
198+
array $originalPositionalParameterNames,
199+
array $newPositionalParameterNames
123200
): ?self
124201
{
125202
if (
@@ -135,7 +212,8 @@ private static function resolvePhpDocBlock(
135212
$hasMethodName,
136213
$getMethodName,
137214
$resolveMethodName,
138-
$explicit ?? $docComment !== null
215+
$explicit ?? $docComment !== null,
216+
$originalPositionalParameterNames
139217
);
140218
if ($phpDocBlockFromClass !== null) {
141219
return $phpDocBlockFromClass;
@@ -149,27 +227,48 @@ private static function resolvePhpDocBlock(
149227
$hasMethodName,
150228
$getMethodName,
151229
$resolveMethodName,
152-
$explicit ?? $docComment !== null
230+
$explicit ?? $docComment !== null,
231+
$originalPositionalParameterNames
153232
);
154233
if ($phpDocBlockFromClass !== null) {
155234
return $phpDocBlockFromClass;
156235
}
157236
}
158237
}
159238

239+
$parameterNameMapping = [];
240+
foreach ($originalPositionalParameterNames as $i => $parameterName) {
241+
if (!array_key_exists($i, $newPositionalParameterNames)) {
242+
continue;
243+
}
244+
$parameterNameMapping[$newPositionalParameterNames[$i]] = $parameterName;
245+
}
246+
160247
return $docComment !== null
161-
? new self($docComment, $file, $classReflection, $trait, $explicit ?? true)
248+
? new self($docComment, $file, $classReflection, $trait, $explicit ?? true, $parameterNameMapping)
162249
: null;
163250
}
164251

252+
/**
253+
* @param \PHPStan\Reflection\ClassReflection $classReflection
254+
* @param string|null $trait
255+
* @param string $name
256+
* @param string $hasMethodName
257+
* @param string $getMethodName
258+
* @param string $resolveMethodName
259+
* @param bool $explicit
260+
* @param array<int, string> $positionalParameterNames $positionalParameterNames
261+
* @return self|null
262+
*/
165263
private static function resolvePhpDocBlockRecursive(
166264
ClassReflection $classReflection,
167265
?string $trait,
168266
string $name,
169267
string $hasMethodName,
170268
string $getMethodName,
171269
string $resolveMethodName,
172-
bool $explicit
270+
bool $explicit,
271+
array $positionalParameterNames = []
173272
): ?self
174273
{
175274
$phpDocBlockFromClass = self::resolvePhpDocBlockFromClass(
@@ -178,7 +277,8 @@ private static function resolvePhpDocBlockRecursive(
178277
$hasMethodName,
179278
$getMethodName,
180279
$resolveMethodName,
181-
$explicit
280+
$explicit,
281+
$positionalParameterNames
182282
);
183283

184284
if ($phpDocBlockFromClass !== null) {
@@ -194,20 +294,32 @@ private static function resolvePhpDocBlockRecursive(
194294
$hasMethodName,
195295
$getMethodName,
196296
$resolveMethodName,
197-
$explicit
297+
$explicit,
298+
$positionalParameterNames
198299
);
199300
}
200301

201302
return null;
202303
}
203304

305+
/**
306+
* @param \PHPStan\Reflection\ClassReflection $classReflection
307+
* @param string $name
308+
* @param string $hasMethodName
309+
* @param string $getMethodName
310+
* @param string $resolveMethodName
311+
* @param bool $explicit
312+
* @param array<int, string> $positionalParameterNames
313+
* @return self|null
314+
*/
204315
private static function resolvePhpDocBlockFromClass(
205316
ClassReflection $classReflection,
206317
string $name,
207318
string $hasMethodName,
208319
string $getMethodName,
209320
string $resolveMethodName,
210-
bool $explicit
321+
bool $explicit,
322+
array $positionalParameterNames
211323
): ?self
212324
{
213325
if ($classReflection->getFileNameWithPhpDocs() !== null && $classReflection->$hasMethodName($name)) {
@@ -223,10 +335,22 @@ private static function resolvePhpDocBlockFromClass(
223335
return null;
224336
}
225337

226-
if ($parentReflection instanceof PhpPropertyReflection || $parentReflection instanceof ResolvedPropertyReflection || $parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) {
338+
if ($parentReflection instanceof PhpPropertyReflection || $parentReflection instanceof ResolvedPropertyReflection) {
339+
$traitReflection = $parentReflection->getDeclaringTrait();
340+
$positionalMethodParameterNames = [];
341+
} elseif ($parentReflection instanceof PhpMethodReflection || $parentReflection instanceof ResolvedMethodReflection) {
227342
$traitReflection = $parentReflection->getDeclaringTrait();
343+
$methodVariants = $parentReflection->getVariants();
344+
$positionalMethodParameterNames = [];
345+
if (count($methodVariants) === 1) {
346+
$methodParameters = $methodVariants[0]->getParameters();
347+
foreach ($methodParameters as $methodParameter) {
348+
$positionalMethodParameterNames[] = $methodParameter->getName();
349+
}
350+
}
228351
} else {
229352
$traitReflection = null;
353+
$positionalMethodParameterNames = [];
230354
}
231355

232356
$trait = $traitReflection !== null
@@ -240,7 +364,9 @@ private static function resolvePhpDocBlockFromClass(
240364
$trait,
241365
$name,
242366
$classReflection->getFileNameWithPhpDocs(),
243-
$explicit
367+
$explicit,
368+
$positionalParameterNames,
369+
$positionalMethodParameterNames
244370
);
245371
}
246372
}

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,10 @@ private function createProperty(
222222
$declaringClassReflection,
223223
null,
224224
$propertyName,
225-
$declaringClassReflection->getFileName()
225+
$declaringClassReflection->getFileName(),
226+
null,
227+
[],
228+
[]
226229
);
227230
if ($phpDocBlock !== null) {
228231
$declaringTraitName = $this->findPropertyTrait(
@@ -488,16 +491,22 @@ private function createMethod(
488491
$declaringTraitName = $this->findMethodTrait($methodReflection);
489492
$resolvedPhpDoc = $this->findMethodPhpDocIncludingAncestors($declaringClassName, $methodReflection->getName());
490493
$stubPhpDocString = null;
494+
$phpDocBlock = null;
491495
if ($resolvedPhpDoc === null) {
492496
if ($declaringClass->getFileName() !== false) {
493497
$docComment = $methodReflection->getDocComment();
494-
498+
$positionalParameterNames = array_map(static function (\ReflectionParameter $parameter): string {
499+
return $parameter->getName();
500+
}, $methodReflection->getParameters());
495501
$phpDocBlock = PhpDocBlock::resolvePhpDocBlockForMethod(
496502
$docComment,
497503
$declaringClass,
498504
$declaringTraitName,
499505
$methodReflection->getName(),
500-
$declaringClass->getFileName()
506+
$declaringClass->getFileName(),
507+
null,
508+
$positionalParameterNames,
509+
$positionalParameterNames
501510
);
502511

503512
if ($phpDocBlock !== null) {
@@ -544,6 +553,9 @@ private function createMethod(
544553
$phpDocBlockClassReflection->getActiveTemplateTypeMap()
545554
);
546555
}, $resolvedPhpDoc->getParamTags());
556+
if ($phpDocBlock !== null) {
557+
$phpDocParameterTypes = $phpDocBlock->transformArrayKeysWithParameterNameMapping($phpDocParameterTypes);
558+
}
547559
$nativeReturnType = TypehintHelper::decideTypeFromReflection(
548560
$methodReflection->getReturnType(),
549561
null,

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9778,6 +9778,11 @@ public function dataBug2740(): array
97789778
return $this->gatherAssertTypes(__DIR__ . '/data/bug-2740.php');
97799779
}
97809780

9781+
public function dataPhpDocInheritanceParameterRemapping(): array
9782+
{
9783+
return $this->gatherAssertTypes(__DIR__ . '/data/inheritdoc-parameter-remapping.php');
9784+
}
9785+
97819786
/**
97829787
* @dataProvider dataBug2574
97839788
* @dataProvider dataBug2577
@@ -9797,6 +9802,7 @@ public function dataBug2740(): array
97979802
* @dataProvider dataComplexGenericsExample
97989803
* @dataProvider dataBug2648
97999804
* @dataProvider dataBug2740
9805+
* @dataProvider dataPhpDocInheritanceParameterRemapping
98009806
* @param ConstantStringType $expectedType
98019807
* @param Type $actualType
98029808
*/

0 commit comments

Comments
 (0)