Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 73bb9eb

Browse files
authored
Add @param-out support
1 parent faf7a98 commit 73bb9eb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1332
-124
lines changed

‎phpstan-baseline.neon

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ parameters:
143143
count: 1
144144
path: src/PhpDoc/PhpDocBlock.php
145145

146+
-
147+
message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getParamOutTypeTagV...' will always evaluate to true\\.$#"
148+
count: 1
149+
path: src/PhpDoc/PhpDocNodeResolver.php
150+
146151
-
147152
message: "#^Call to function method_exists\\(\\) with PHPStan\\\\PhpDocParser\\\\Ast\\\\PhpDoc\\\\PhpDocNode and 'getSelfOutTypeTagVa...' will always evaluate to true\\.$#"
148153
count: 1
@@ -161,6 +166,11 @@ parameters:
161166
count: 1
162167
path: src/PhpDoc/StubValidator.php
163168

169+
-
170+
message: "#^Return type \\(PHPStan\\\\PhpDoc\\\\Tag\\\\ParamOutTag\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\ParamOutTag\\:\\:withType\\(\\) should be covariant with return type \\(static\\(PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\)\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\:\\:withType\\(\\)$#"
171+
count: 1
172+
path: src/PhpDoc/Tag/ParamOutTag.php
173+
164174
-
165175
message: "#^Return type \\(PHPStan\\\\PhpDoc\\\\Tag\\\\ParamTag\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\ParamTag\\:\\:withType\\(\\) should be covariant with return type \\(static\\(PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\)\\) of method PHPStan\\\\PhpDoc\\\\Tag\\\\TypedTag\\:\\:withType\\(\\)$#"
166176
count: 1

‎src/Analyser/MutatingScope.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,6 +2517,7 @@ public function enterTrait(ClassReflection $traitReflection): self
25172517
/**
25182518
* @api
25192519
* @param Type[] $phpDocParameterTypes
2520+
* @param Type[] $parameterOutTypes
25202521
*/
25212522
public function enterClassMethod(
25222523
Node\Stmt\ClassMethod $classMethod,
@@ -2533,6 +2534,7 @@ public function enterClassMethod(
25332534
?Assertions $asserts = null,
25342535
?Type $selfOutType = null,
25352536
?string $phpDocComment = null,
2537+
array $parameterOutTypes = [],
25362538
): self
25372539
{
25382540
if (!$this->isInClass()) {
@@ -2560,6 +2562,7 @@ public function enterClassMethod(
25602562
$asserts ?? Assertions::createEmpty(),
25612563
$selfOutType,
25622564
$phpDocComment,
2565+
array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes),
25632566
),
25642567
!$classMethod->isStatic(),
25652568
);
@@ -2626,6 +2629,7 @@ private function getRealParameterDefaultValues(Node\FunctionLike $functionLike):
26262629
/**
26272630
* @api
26282631
* @param Type[] $phpDocParameterTypes
2632+
* @param Type[] $parameterOutTypes
26292633
*/
26302634
public function enterFunction(
26312635
Node\Stmt\Function_ $function,
@@ -2641,6 +2645,7 @@ public function enterFunction(
26412645
bool $acceptsNamedArguments = true,
26422646
?Assertions $asserts = null,
26432647
?string $phpDocComment = null,
2648+
array $parameterOutTypes = [],
26442649
): self
26452650
{
26462651
return $this->enterFunctionLike(
@@ -2662,6 +2667,7 @@ public function enterFunction(
26622667
$acceptsNamedArguments,
26632668
$asserts ?? Assertions::createEmpty(),
26642669
$phpDocComment,
2670+
array_map(static fn (Type $type): Type => TemplateTypeHelper::toArgument($type), $parameterOutTypes),
26652671
),
26662672
false,
26672673
);

‎src/Analyser/NodeScopeResolver.php

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
use PHPStan\Reflection\MethodReflection;
115115
use PHPStan\Reflection\Native\NativeMethodReflection;
116116
use PHPStan\Reflection\Native\NativeParameterReflection;
117+
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
117118
use PHPStan\Reflection\ParametersAcceptor;
118119
use PHPStan\Reflection\ParametersAcceptorSelector;
119120
use PHPStan\Reflection\Php\PhpMethodReflection;
@@ -407,7 +408,7 @@ private function processStmtNode(
407408
$hasYield = false;
408409
$throwPoints = [];
409410
$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback);
410-
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts] = $this->getPhpDocs($scope, $stmt);
411+
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts,, $phpDocParameterOutTypes] = $this->getPhpDocs($scope, $stmt);
411412

412413
foreach ($stmt->params as $param) {
413414
$this->processParamNode($param, $scope, $nodeCallback);
@@ -431,6 +432,7 @@ private function processStmtNode(
431432
$acceptsNamedArguments,
432433
$asserts,
433434
$phpDocComment,
435+
$phpDocParameterOutTypes,
434436
);
435437
$functionReflection = $functionScope->getFunction();
436438
if (!$functionReflection instanceof FunctionReflection) {
@@ -470,7 +472,7 @@ private function processStmtNode(
470472
$hasYield = false;
471473
$throwPoints = [];
472474
$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback);
473-
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts, $selfOutType] = $this->getPhpDocs($scope, $stmt);
475+
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, , $phpDocComment, $asserts, $selfOutType, $phpDocParameterOutTypes] = $this->getPhpDocs($scope, $stmt);
474476

475477
foreach ($stmt->params as $param) {
476478
$this->processParamNode($param, $scope, $nodeCallback);
@@ -495,6 +497,7 @@ private function processStmtNode(
495497
$asserts,
496498
$selfOutType,
497499
$phpDocComment,
500+
$phpDocParameterOutTypes,
498501
);
499502

500503
if ($stmt->name->toLowerString() === '__construct') {
@@ -1806,6 +1809,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
18061809
$functionReflection->getVariants(),
18071810
);
18081811
}
1812+
18091813
if ($parametersAcceptor !== null) {
18101814
$expr = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $expr) ?? $expr;
18111815
}
@@ -2043,11 +2047,13 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
20432047
}
20442048
}
20452049
}
2050+
20462051
if ($parametersAcceptor !== null) {
20472052
$expr = ArgumentsNormalizer::reorderMethodArguments($parametersAcceptor, $expr) ?? $expr;
20482053
}
20492054
$result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context);
20502055
$scope = $result->getScope();
2056+
20512057
if ($methodReflection !== null) {
20522058
$hasSideEffects = $methodReflection->hasSideEffects();
20532059
if ($hasSideEffects->yes() || $methodReflection->getName() === '__construct') {
@@ -2174,12 +2180,14 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
21742180
$throwPoints[] = ThrowPoint::createImplicit($scope, $expr);
21752181
}
21762182
}
2183+
21772184
if ($parametersAcceptor !== null) {
21782185
$expr = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $expr) ?? $expr;
21792186
}
21802187
$result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->getArgs(), $scope, $nodeCallback, $context, $closureBindScope ?? null);
21812188
$scope = $result->getScope();
21822189
$scopeFunction = $scope->getFunction();
2190+
21832191
if (
21842192
$methodReflection !== null
21852193
&& !$methodReflection->isStatic()
@@ -2529,6 +2537,7 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
25292537
$throwPoints[] = ThrowPoint::createImplicit($scope, $expr);
25302538
}
25312539
}
2540+
25322541
if ($parametersAcceptor !== null) {
25332542
$expr = ArgumentsNormalizer::reorderNewArguments($parametersAcceptor, $expr) ?? $expr;
25342543
}
@@ -3272,8 +3281,21 @@ private function processArgs(
32723281
?MutatingScope $closureBindScope = null,
32733282
): ExpressionResult
32743283
{
3284+
$paramOutTypes = [];
32753285
if ($parametersAcceptor !== null) {
32763286
$parameters = $parametersAcceptor->getParameters();
3287+
3288+
foreach ($parameters as $parameter) {
3289+
if (!$parameter instanceof ParameterReflectionWithPhpDocs) {
3290+
continue;
3291+
}
3292+
3293+
if ($parameter->getOutType() === null) {
3294+
continue;
3295+
}
3296+
3297+
$paramOutTypes[$parameter->getName()] = TemplateTypeHelper::resolveTemplateTypes($parameter->getOutType(), $parametersAcceptor->getResolvedTemplateTypeMap());
3298+
}
32773299
}
32783300

32793301
if ($calleeReflection !== null) {
@@ -3286,20 +3308,29 @@ private function processArgs(
32863308
$originalArg = $arg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE) ?? $arg;
32873309
$nodeCallback($originalArg, $scope);
32883310
if (isset($parameters) && $parametersAcceptor !== null) {
3311+
$byRefType = new MixedType();
32893312
$assignByReference = false;
32903313
if (isset($parameters[$i])) {
32913314
$assignByReference = $parameters[$i]->passedByReference()->createsNewVariable();
32923315
$parameterType = $parameters[$i]->getType();
3316+
3317+
if (isset($paramOutTypes[$parameters[$i]->getName()])) {
3318+
$byRefType = $paramOutTypes[$parameters[$i]->getName()];
3319+
}
32933320
} elseif (count($parameters) > 0 && $parametersAcceptor->isVariadic()) {
32943321
$lastParameter = $parameters[count($parameters) - 1];
32953322
$assignByReference = $lastParameter->passedByReference()->createsNewVariable();
32963323
$parameterType = $lastParameter->getType();
3324+
3325+
if (isset($paramOutTypes[$lastParameter->getName()])) {
3326+
$byRefType = $paramOutTypes[$lastParameter->getName()];
3327+
}
32973328
}
32983329

32993330
if ($assignByReference) {
33003331
$argValue = $arg->value;
33013332
if ($argValue instanceof Variable && is_string($argValue->name)) {
3302-
$scope = $scope->assignVariable($argValue->name, newMixedType(), new MixedType());
3333+
$scope = $scope->assignVariable($argValue->name, $byRefType, new MixedType());
33033334
}
33043335
}
33053336
}
@@ -4039,7 +4070,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection
40394070
}
40404071

40414072
/**
4042-
* @return array{TemplateTypeMap, Type[], ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type}
4073+
* @return array{TemplateTypeMap, array<string, Type>, ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array<string, Type>}
40434074
*/
40444075
public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array
40454076
{
@@ -4065,6 +4096,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
40654096
$trait = $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null;
40664097
$resolvedPhpDoc = null;
40674098
$functionName = null;
4099+
$phpDocParameterOutTypes = [];
40684100

40694101
if ($node instanceof Node\Stmt\ClassMethod) {
40704102
if (!$scope->isInClass()) {
@@ -4149,6 +4181,9 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
41494181
}
41504182
$phpDocParameterTypes[$paramName] = $paramType;
41514183
}
4184+
foreach ($resolvedPhpDoc->getParamOutTags() as $paramName => $paramOutTag) {
4185+
$phpDocParameterOutTypes[$paramName] = $paramOutTag->getType();
4186+
}
41524187
if ($node instanceof Node\FunctionLike) {
41534188
$nativeReturnType = $scope->getFunctionType($node->getReturnType(), false, false);
41544189
$phpDocReturnType = $this->getPhpDocReturnType($resolvedPhpDoc, $nativeReturnType);
@@ -4168,7 +4203,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
41684203
$selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null;
41694204
}
41704205

4171-
return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType];
4206+
return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes];
41724207
}
41734208

41744209
private function transformStaticType(ClassReflection $declaringClass, Type $type): Type

‎src/PhpDoc/PhpDocNodeResolver.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\PhpDoc\Tag\MethodTag;
1212
use PHPStan\PhpDoc\Tag\MethodTagParameter;
1313
use PHPStan\PhpDoc\Tag\MixinTag;
14+
use PHPStan\PhpDoc\Tag\ParamOutTag;
1415
use PHPStan\PhpDoc\Tag\ParamTag;
1516
use PHPStan\PhpDoc\Tag\PropertyTag;
1617
use PHPStan\PhpDoc\Tag\ReturnTag;
@@ -318,6 +319,34 @@ public function resolveParamTags(PhpDocNode $phpDocNode, NameScope $nameScope):
318319
return $resolved;
319320
}
320321

322+
/**
323+
* @return array<string, ParamOutTag>
324+
*/
325+
public function resolveParamOutTags(PhpDocNode $phpDocNode, NameScope $nameScope): array
326+
{
327+
if (!method_exists($phpDocNode, 'getParamOutTypeTagValues')) {
328+
return [];
329+
}
330+
331+
$resolved = [];
332+
333+
foreach (['@param-out', '@psalm-param-out', '@phpstan-param-out'] as $tagName) {
334+
foreach ($phpDocNode->getParamOutTypeTagValues($tagName) as $tagValue) {
335+
$parameterName = substr($tagValue->parameterName, 1);
336+
$parameterType = $this->typeNodeResolver->resolve($tagValue->type, $nameScope);
337+
if ($this->shouldSkipType($tagName, $parameterType)) {
338+
continue;
339+
}
340+
341+
$resolved[$parameterName] = new ParamOutTag(
342+
$parameterType,
343+
);
344+
}
345+
}
346+
347+
return $resolved;
348+
}
349+
321350
public function resolveReturnTag(PhpDocNode $phpDocNode, NameScope $nameScope): ?ReturnTag
322351
{
323352
$resolved = null;

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /