Skip to content

Commit

Permalink
Understand variadic arg type with @no-named-args
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmajor authored and ondrejmirtes committed May 28, 2022
1 parent b24249b commit 0f33a29
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 10 deletions.
12 changes: 8 additions & 4 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -2475,7 +2475,8 @@ public function enterClassMethod(
bool $isDeprecated,
bool $isInternal,
bool $isFinal,
?bool $isPure = null,
?bool $isPure,
bool $acceptsNamedArguments,
): self
{
if (!$this->isInClass()) {
Expand All @@ -2499,6 +2500,7 @@ public function enterClassMethod(
$isInternal,
$isFinal,
$isPure,
$acceptsNamedArguments,
),
!$classMethod->isStatic(),
);
Expand Down Expand Up @@ -2576,7 +2578,8 @@ public function enterFunction(
bool $isDeprecated,
bool $isInternal,
bool $isFinal,
?bool $isPure = null,
?bool $isPure,
bool $acceptsNamedArguments,
): self
{
return $this->enterFunctionLike(
Expand All @@ -2595,6 +2598,7 @@ public function enterFunction(
$isInternal,
$isFinal,
$isPure,
$acceptsNamedArguments,
),
false,
);
Expand All @@ -2610,7 +2614,7 @@ private function enterFunctionLike(
foreach (ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getParameters() as $parameter) {
$parameterType = $parameter->getType();
if ($parameter->isVariadic()) {
if ($this->phpVersion->supportsNamedArguments()) {
if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) {
$parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType);
} else {
$parameterType = new ArrayType(new IntegerType(), $parameterType);
Expand All @@ -2620,7 +2624,7 @@ private function enterFunctionLike(

$nativeParameterType = $parameter->getNativeType();
if ($parameter->isVariadic()) {
if ($this->phpVersion->supportsNamedArguments()) {
if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) {
$nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType);
} else {
$nativeParameterType = new ArrayType(new IntegerType(), $nativeParameterType);
Expand Down
14 changes: 9 additions & 5 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ private function processStmtNode(
$hasYield = false;
$throwPoints = [];
$this->processAttributeGroups($stmt->attrGroups, $scope, $nodeCallback);
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->getPhpDocs($scope, $stmt);
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments] = $this->getPhpDocs($scope, $stmt);

foreach ($stmt->params as $param) {
$this->processParamNode($param, $scope, $nodeCallback);
Expand All @@ -419,6 +419,7 @@ private function processStmtNode(
$isInternal,
$isFinal,
$isPure,
$acceptsNamedArguments,
);
$nodeCallback(new InFunctionNode($stmt), $functionScope);

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

foreach ($stmt->params as $param) {
$this->processParamNode($param, $scope, $nodeCallback);
Expand All @@ -474,6 +475,7 @@ private function processStmtNode(
$isInternal,
$isFinal,
$isPure,
$acceptsNamedArguments,
);

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

foreach ($stmt->props as $prop) {
$this->processStmtNode($prop, $scope, $nodeCallback);
[,,,,,,,,,$isReadOnly, $docComment] = $this->getPhpDocs($scope, $stmt);
[,,,,,,,,,,$isReadOnly, $docComment] = $this->getPhpDocs($scope, $stmt);
$nodeCallback(
new ClassPropertyNode(
$prop->name->toString(),
Expand Down Expand Up @@ -3828,7 +3830,7 @@ private function processNodesForTraitUse($node, ClassReflection $traitReflection
}

/**
* @return array{TemplateTypeMap, Type[], ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, string|null}
* @return array{TemplateTypeMap, Type[], ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null}
*/
public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array
{
Expand All @@ -3841,6 +3843,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
$isInternal = false;
$isFinal = false;
$isPure = false;
$acceptsNamedArguments = true;
$isReadOnly = $scope->isInClass() && $scope->getClassReflection()->isImmutable();
$docComment = $node->getDocComment() !== null
? $node->getDocComment()->getText()
Expand Down Expand Up @@ -3948,10 +3951,11 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
$isInternal = $resolvedPhpDoc->isInternal();
$isFinal = $resolvedPhpDoc->isFinal();
$isPure = $resolvedPhpDoc->isPure();
$acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments();
$isReadOnly = $isReadOnly || $resolvedPhpDoc->isReadOnly();
}

return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $isReadOnly, $docComment];
return [$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment];
}

private function transformStaticType(ClassReflection $declaringClass, Type $type): Type
Expand Down
5 changes: 5 additions & 0 deletions src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,11 @@ public function resolveHasConsistentConstructor(PhpDocNode $phpDocNode): bool
return false;
}

public function resolveAcceptsNamedArguments(PhpDocNode $phpDocNode): bool
{
return count($phpDocNode->getTagsByName('@no-named-arguments')) === 0;
}

private function shouldSkipType(string $tagName, Type $type): bool
{
if (strpos($tagName, '@psalm-') !== 0) {
Expand Down
14 changes: 14 additions & 0 deletions src/PhpDoc/ResolvedPhpDocBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class ResolvedPhpDocBlock

private ?bool $hasConsistentConstructor = null;

private ?bool $acceptsNamedArguments = null;

private function __construct()
{
}
Expand Down Expand Up @@ -161,6 +163,7 @@ public static function createEmpty(): self
$self->isPure = null;
$self->isReadOnly = false;
$self->hasConsistentConstructor = false;
$self->acceptsNamedArguments = true;

return $self;
}
Expand Down Expand Up @@ -207,6 +210,7 @@ public function merge(array $parents, array $parentPhpDocBlocks): self
$result->isPure = $this->isPure();
$result->isReadOnly = $this->isReadOnly();
$result->hasConsistentConstructor = $this->hasConsistentConstructor();
$result->acceptsNamedArguments = $this->acceptsNamedArguments();

return $result;
}
Expand Down Expand Up @@ -527,6 +531,16 @@ public function hasConsistentConstructor(): bool
return $this->hasConsistentConstructor;
}

public function acceptsNamedArguments(): bool
{
if ($this->acceptsNamedArguments === null) {
$this->acceptsNamedArguments = $this->phpDocNodeResolver->resolveAcceptsNamedArguments(
$this->phpDocNode,
);
}
return $this->acceptsNamedArguments;
}

public function getTemplateTypeMap(): TemplateTypeMap
{
return $this->templateTypeMap;
Expand Down
3 changes: 2 additions & 1 deletion src/Reflection/Php/PhpClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ private function inferAndCachePropertyTypes(
$constructor,
$namespace,
)->enterClass($declaringClass);
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure] = $this->nodeScopeResolver->getPhpDocs($classScope, $methodNode);
[$templateTypeMap, $phpDocParameterTypes, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments] = $this->nodeScopeResolver->getPhpDocs($classScope, $methodNode);
$methodScope = $classScope->enterClassMethod(
$methodNode,
$templateTypeMap,
Expand All @@ -884,6 +884,7 @@ private function inferAndCachePropertyTypes(
$isInternal,
$isFinal,
$isPure,
$acceptsNamedArguments,
);

$propertyTypes = [];
Expand Down
6 changes: 6 additions & 0 deletions src/Reflection/Php/PhpFunctionFromParserNodeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public function __construct(
private bool $isInternal,
private bool $isFinal,
private ?bool $isPure,
private bool $acceptsNamedArguments,
)
{
$this->functionLike = $functionLike;
Expand Down Expand Up @@ -207,6 +208,11 @@ public function isGenerator(): bool
return $this->nodeIsOrContainsYield($this->functionLike);
}

public function acceptsNamedArguments(): bool
{
return $this->acceptsNamedArguments;
}

private function nodeIsOrContainsYield(Node $node): bool
{
if ($node instanceof Node\Expr\Yield_) {
Expand Down
2 changes: 2 additions & 0 deletions src/Reflection/Php/PhpMethodFromParserNodeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function __construct(
bool $isInternal,
bool $isFinal,
?bool $isPure,
bool $acceptsNamedArguments,
)
{
$name = strtolower($classMethod->name->name);
Expand Down Expand Up @@ -83,6 +84,7 @@ public function __construct(
$isInternal,
$isFinal || $classMethod->isFinal(),
$isPure,
$acceptsNamedArguments,
);
}

Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ public function dataFileAsserts(): iterable

if (PHP_VERSION_ID >= 80000) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/variadic-parameter-php8.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/no-named-arguments.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4896.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5843.php');
}
Expand Down
24 changes: 24 additions & 0 deletions tests/PHPStan/Analyser/data/no-named-arguments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace NoNamedArguments;

use function PHPStan\Testing\assertType;

/**
* @no-named-arguments
*/
function noNamedArgumentsInFunction(float ...$args)
{
assertType('array<int, float>', $args);
}

class Baz
{
/**
* @no-named-arguments
*/
public function noNamedArgumentsInMethod(float ...$args)
{
assertType('array<int, float>', $args);
}
}

0 comments on commit 0f33a29

Please sign in to comment.