diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 9ae174f46b..bbc886a410 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -6005,6 +6005,9 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n $isPure = $resolvedPhpDoc->isPure(); $isAllowedPrivateMutation = $resolvedPhpDoc->isAllowedPrivateMutation(); $acceptsNamedArguments = $resolvedPhpDoc->acceptsNamedArguments(); + if ($acceptsNamedArguments && $scope->isInClass()) { + $acceptsNamedArguments = $scope->getClassReflection()->acceptsNamedArguments(); + } $isReadOnly = $isReadOnly || $resolvedPhpDoc->isReadOnly(); $asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc); $selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null; diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 8106faf39d..7c9a569dcc 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -102,6 +102,8 @@ class ClassReflection private ?bool $hasConsistentConstructor = null; + private ?bool $acceptsNamedArguments = null; + private ?TemplateTypeMap $templateTypeMap = null; private ?TemplateTypeMap $activeTemplateTypeMap = null; @@ -1243,6 +1245,16 @@ public function hasConsistentConstructor(): bool return $this->hasConsistentConstructor; } + public function acceptsNamedArguments(): bool + { + if ($this->acceptsNamedArguments === null) { + $resolvedPhpDoc = $this->getResolvedPhpDoc(); + $this->acceptsNamedArguments = $resolvedPhpDoc === null || $resolvedPhpDoc->acceptsNamedArguments(); + } + + return $this->acceptsNamedArguments; + } + public function isFinalByKeyword(): bool { if ($this->isAnonymous()) { diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 9c14a0571c..719d610443 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -614,4 +614,20 @@ public function testGenericsInCallableInConstructor(): void $this->analyse([__DIR__ . '/data/generics-in-callable-in-constructor.php'], []); } + public function testBug11275(): void + { + if (PHP_VERSION_ID < 70400) { + $this->markTestSkipped('Test requires PHP 7.4.'); + } + + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-11275.php'], [ + [ + 'Property Bug11275\D::$b (list) does not accept array.', + 50, + 'array might not be a list.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-11275.php b/tests/PHPStan/Rules/Properties/data/bug-11275.php new file mode 100644 index 0000000000..cf54868d05 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-11275.php @@ -0,0 +1,52 @@ += 7.4 + +namespace Bug11275; + +/** + * @no-named-arguments + */ +final class A +{ + /** + * @var list + */ + private array $b; + + public function __construct(B ...$b) + { + $this->b = $b; + } +} + +final class B +{ +} + +final class C +{ + /** + * @var list + */ + private array $b; + + /** + * @no-named-arguments + */ + public function __construct(B ...$b) + { + $this->b = $b; + } +} + +final class D +{ + /** + * @var list + */ + private array $b; + + public function __construct(B ...$b) + { + $this->b = $b; + } +}