Skip to content

Commit 82f3af5

Browse files
jrmajorondrejmirtes
authored andcommitted
Understand variadic arg type with @no-named-args
1 parent b24249b commit 82f3af5

9 files changed

+71
-10
lines changed

src/Analyser/MutatingScope.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,7 +2475,8 @@ public function enterClassMethod(
24752475
bool $isDeprecated,
24762476
bool $isInternal,
24772477
bool $isFinal,
2478-
?bool $isPure = null,
2478+
?bool $isPure,
2479+
bool $acceptsNamedArguments,
24792480
): self
24802481
{
24812482
if (!$this->isInClass()) {
@@ -2499,6 +2500,7 @@ public function enterClassMethod(
24992500
$isInternal,
25002501
$isFinal,
25012502
$isPure,
2503+
$acceptsNamedArguments,
25022504
),
25032505
!$classMethod->isStatic(),
25042506
);
@@ -2576,7 +2578,8 @@ public function enterFunction(
25762578
bool $isDeprecated,
25772579
bool $isInternal,
25782580
bool $isFinal,
2579-
?bool $isPure = null,
2581+
?bool $isPure,
2582+
bool $acceptsNamedArguments,
25802583
): self
25812584
{
25822585
return $this->enterFunctionLike(
@@ -2595,6 +2598,7 @@ public function enterFunction(
25952598
$isInternal,
25962599
$isFinal,
25972600
$isPure,
2601+
$acceptsNamedArguments,
25982602
),
25992603
false,
26002604
);
@@ -2610,7 +2614,7 @@ private function enterFunctionLike(
26102614
foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameter) {
26112615
$parameterType = $parameter->getType();
26122616
if ($parameter->isVariadic()) {
2613-
if ($this->phpVersion->supportsNamedArguments()) {
2617+
if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) {
26142618
$parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType);
26152619
} else {
26162620
$parameterType = new ArrayType(new IntegerType(), $parameterType);
@@ -2620,7 +2624,7 @@ private function enterFunctionLike(
26202624

26212625
$nativeParameterType = $parameter->getNativeType();
26222626
if ($parameter->isVariadic()) {
2623-
if ($this->phpVersion->supportsNamedArguments()) {
2627+
if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) {
26242628
$nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType);
26252629
} else {
26262630
$nativeParameterType = new ArrayType(new IntegerType(), $nativeParameterType);

src/Analyser/NodeScopeResolver.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ private function processStmtNode(
398398
$hasYield = false;
399399
$throwPoints = [];
400400
$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback);
401-
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt);
401+
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments] = $this->getPhpDocs($scope, $stmt);
402402

403403
foreach ($stmt->params as $param) {
404404
$this->processParamNode($param, $scope, $nodeCallback);
@@ -419,6 +419,7 @@ private function processStmtNode(
419419
$isInternal,
420420
$isFinal,
421421
$isPure,
422+
$acceptsNamedArguments,
422423
);
423424
$nodeCallback(new InFunctionNode($stmt), $functionScope);
424425

@@ -453,7 +454,7 @@ private function processStmtNode(
453454
$hasYield = false;
454455
$throwPoints = [];
455456
$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback);
456-
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt);
457+
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments] = $this->getPhpDocs($scope, $stmt);
457458

458459
foreach ($stmt->params as $param) {
459460
$this->processParamNode($param, $scope, $nodeCallback);
@@ -474,6 +475,7 @@ private function processStmtNode(
474475
$isInternal,
475476
$isFinal,
476477
$isPure,
478+
$acceptsNamedArguments,
477479
);
478480

479481
if ($stmt->name->toLowerString() === '__construct') {
@@ -637,7 +639,7 @@ private function processStmtNode(
637639

638640
foreach ($stmt->props as $prop) {
639641
$this->processStmtNode($prop, $scope, $nodeCallback);
640-
[,,,,,,,,,$isReadOnly, $docComment] = $this->getPhpDocs($scope, $stmt);
642+
[,,,,,,,,,,$isReadOnly, $docComment] = $this->getPhpDocs($scope, $stmt);
641643
$nodeCallback(
642644
new ClassPropertyNode(
643645
$prop->name->toString(),
@@ -3828,7 +3830,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection
38283830
}
38293831

38303832
/**
3831-
* @return array{TemplateTypeMap, Type[], ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, string|null}
3833+
* @return array{TemplateTypeMap, Type[], ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null}
38323834
*/
38333835
public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array
38343836
{
@@ -3841,6 +3843,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
38413843
$isInternal = false;
38423844
$isFinal = false;
38433845
$isPure = false;
3846+
$acceptsNamedArguments = true;
38443847
$isReadOnly = $scope->isInClass() && $scope->getClassReflection()->isImmutable();
38453848
$docComment = $node->getDocComment() !== null
38463849
? $node->getDocComment()->getText()
@@ -3948,10 +3951,11 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
39483951
$isInternal = $resolvedPhpDoc->isInternal();
39493952
$isFinal = $resolvedPhpDoc->isFinal();
39503953
$isPure = $resolvedPhpDoc->isPure();
3954+
$acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments();
39513955
$isReadOnly = $isReadOnly || $resolvedPhpDoc->isReadOnly();
39523956
}
39533957

3954-
return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $isReadOnly, $docComment];
3958+
return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment];
39553959
}
39563960

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

src/PhpDoc/PhpDocNodeResolver.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,11 @@ public function resolveHasConsistentConstructor(PhpDocNode $phpDocNode): bool
491491
return false;
492492
}
493493

494+
public function resolveAcceptsNamedArguments(PhpDocNode $phpDocNode): bool
495+
{
496+
return count($phpDocNode->getTagsByName('@no-named-arguments')) === 0;
497+
}
498+
494499
private function shouldSkipType(string $tagName, Type $type): bool
495500
{
496501
if (strpos($tagName, '@psalm-') !== 0) {

src/PhpDoc/ResolvedPhpDocBlock.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ class ResolvedPhpDocBlock
102102

103103
private ?bool $hasConsistentConstructor = null;
104104

105+
private ?bool $acceptsNamedArguments = null;
106+
105107
private function __construct()
106108
{
107109
}
@@ -161,6 +163,7 @@ public static function createEmpty(): self
161163
$self->isPure = null;
162164
$self->isReadOnly = false;
163165
$self->hasConsistentConstructor = false;
166+
$self->acceptsNamedArguments = true;
164167

165168
return $self;
166169
}
@@ -207,6 +210,7 @@ public function merge(array $parents, array $parentPhpDocBlocks): self
207210
$result->isPure = $this->isPure();
208211
$result->isReadOnly = $this->isReadOnly();
209212
$result->hasConsistentConstructor = $this->hasConsistentConstructor();
213+
$result->acceptsNamedArguments = $this->acceptsNamedArguments();
210214

211215
return $result;
212216
}
@@ -527,6 +531,16 @@ public function hasConsistentConstructor(): bool
527531
return $this->hasConsistentConstructor;
528532
}
529533

534+
public function acceptsNamedArguments(): bool
535+
{
536+
if ($this->acceptsNamedArguments === null) {
537+
$this->acceptsNamedArguments = $this->phpDocNodeResolver->resolveAcceptsNamedArguments(
538+
$this->phpDocNode,
539+
);
540+
}
541+
return $this->acceptsNamedArguments;
542+
}
543+
530544
public function getTemplateTypeMap(): TemplateTypeMap
531545
{
532546
return $this->templateTypeMap;

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ private function inferAndCachePropertyTypes(
872872
$constructor,
873873
$namespace,
874874
)->enterClass($declaringClass);
875-
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->nodeScopeResolver->getPhpDocs($classScope, $methodNode);
875+
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments] = $this->nodeScopeResolver->getPhpDocs($classScope, $methodNode);
876876
$methodScope = $classScope->enterClassMethod(
877877
$methodNode,
878878
$templateTypeMap,
@@ -884,6 +884,7 @@ private function inferAndCachePropertyTypes(
884884
$isInternal,
885885
$isFinal,
886886
$isPure,
887+
$acceptsNamedArguments,
887888
);
888889

889890
$propertyTypes = [];

src/Reflection/Php/PhpFunctionFromParserNodeReflection.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public function __construct(
5353
private bool $isInternal,
5454
private bool $isFinal,
5555
private ?bool $isPure,
56+
private bool $acceptsNamedArguments,
5657
)
5758
{
5859
$this->functionLike = $functionLike;
@@ -207,6 +208,11 @@ public function isGenerator(): bool
207208
return $this->nodeIsOrContainsYield($this->functionLike);
208209
}
209210

211+
public function acceptsNamedArguments(): bool
212+
{
213+
return $this->acceptsNamedArguments;
214+
}
215+
210216
private function nodeIsOrContainsYield(Node $node): bool
211217
{
212218
if ($node instanceof Node\Expr\Yield_) {

src/Reflection/Php/PhpMethodFromParserNodeReflection.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public function __construct(
4343
bool $isInternal,
4444
bool $isFinal,
4545
?bool $isPure,
46+
bool $acceptsNamedArguments,
4647
)
4748
{
4849
$name = strtolower($classMethod->name->name);
@@ -83,6 +84,7 @@ public function __construct(
8384
$isInternal,
8485
$isFinal || $classMethod->isFinal(),
8586
$isPure,
87+
$acceptsNamedArguments,
8688
);
8789
}
8890

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ public function dataFileAsserts(): iterable
535535

536536
if (PHP_VERSION_ID >= 80000) {
537537
yield from $this->gatherAssertTypes(__DIR__ . '/data/variadic-parameter-php8.php');
538+
yield from $this->gatherAssertTypes(__DIR__ . '/data/no-named-arguments.php');
538539
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4896.php');
539540
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5843.php');
540541
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace NoNamedArguments;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @no-named-arguments
9+
*/
10+
function noNamedArgumentsInFunction(float ...$args)
11+
{
12+
assertType('array<int, float>', $args);
13+
}
14+
15+
class Baz
16+
{
17+
/**
18+
* @no-named-arguments
19+
*/
20+
public function noNamedArgumentsInMethod(float ...$args)
21+
{
22+
assertType('array<int, float>', $args);
23+
}
24+
}

0 commit comments

Comments
 (0)