diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index c09dfb5662..ff82f0662a 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -16,8 +16,9 @@ class ChangedTypeMethodReflection implements ExtendedMethodReflection /** * @param ParametersAcceptorWithPhpDocs[] $variants + * @param ParametersAcceptorWithPhpDocs[]|null $namedArgumentsVariants */ - public function __construct(private ClassReflection $declaringClass, private ExtendedMethodReflection $reflection, private array $variants) + public function __construct(private ClassReflection $declaringClass, private ExtendedMethodReflection $reflection, private array $variants, private ?array $namedArgumentsVariants) { } @@ -63,7 +64,7 @@ public function getVariants(): array public function getNamedArgumentsVariants(): ?array { - return null; + return $this->namedArgumentsVariants; } public function isDeprecated(): TrinaryLogic diff --git a/src/Reflection/Php/PhpClassReflectionExtension.php b/src/Reflection/Php/PhpClassReflectionExtension.php index 85bc00865e..5e6dd843a3 100644 --- a/src/Reflection/Php/PhpClassReflectionExtension.php +++ b/src/Reflection/Php/PhpClassReflectionExtension.php @@ -595,7 +595,7 @@ private function createMethod( } } } - $variantsByType[$signatureType][] = $this->createNativeMethodVariant($methodSignature, $stubPhpDocParameterTypes, $stubPhpDocParameterVariadicity, $stubPhpDocReturnType, $phpDocParameterTypes, $phpDocReturnType, $phpDocParameterNameMapping, $stubPhpParameterOutTypes, $phpDocParameterOutTypes); + $variantsByType[$signatureType][] = $this->createNativeMethodVariant($methodSignature, $stubPhpDocParameterTypes, $stubPhpDocParameterVariadicity, $stubPhpDocReturnType, $phpDocParameterTypes, $phpDocReturnType, $phpDocParameterNameMapping, $stubPhpParameterOutTypes, $phpDocParameterOutTypes, $signatureType !== 'named'); } } @@ -796,6 +796,7 @@ private function createNativeMethodVariant( array $phpDocParameterNameMapping, array $stubPhpDocParameterOutTypes, array $phpDocParameterOutTypes, + bool $usePhpDocParameterNames, ): FunctionVariantWithPhpDocs { $parameters = []; @@ -820,7 +821,9 @@ private function createNativeMethodVariant( } $parameters[] = new NativeParameterWithPhpDocsReflection( - $phpDocParameterName, + $usePhpDocParameterNames + ? $phpDocParameterName + : $parameterSignature->getName(), $parameterSignature->isOptional(), $type ?? $parameterSignature->getType(), $phpDocType ?? new MixedType(), diff --git a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php index d68507fe49..d114609b35 100644 --- a/src/Reflection/SignatureMap/Php8SignatureMapProvider.php +++ b/src/Reflection/SignatureMap/Php8SignatureMapProvider.php @@ -255,8 +255,14 @@ private function getMergedSignatures(FunctionSignature $nativeSignature, array $ continue 2; } + // it seems that variadic parameters cannot be named in native functions/methods. + $nativeParam = $nativeParams[$i]; + if ($nativeParam->isVariadic()) { + break; + } + $parameters[] = new ParameterSignature( - $nativeParams[$i]->getName(), + $nativeParam->getName(), $functionParam->isOptional(), $functionParam->getType(), $functionParam->getNativeType(), diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index 9d3bdca420..61d7a2f00b 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -82,7 +82,7 @@ public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflectio private function transformMethodWithStaticType(ClassReflection $declaringClass, ExtendedMethodReflection $method): ExtendedMethodReflection { - $variants = array_map(fn (ParametersAcceptorWithPhpDocs $acceptor): ParametersAcceptorWithPhpDocs => new FunctionVariantWithPhpDocs( + $variantFn = fn (ParametersAcceptorWithPhpDocs $acceptor): ParametersAcceptorWithPhpDocs => new FunctionVariantWithPhpDocs( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), array_map( @@ -104,9 +104,14 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $this->transformStaticType($acceptor->getPhpDocReturnType()), $this->transformStaticType($acceptor->getNativeReturnType()), $acceptor->getCallSiteVarianceMap(), - ), $method->getVariants()); + ); + $variants = array_map($variantFn, $method->getVariants()); + $namedArgumentVariants = $method->getNamedArgumentsVariants(); + $namedArgumentVariants = $namedArgumentVariants !== null + ? array_map($variantFn, $namedArgumentVariants) + : null; - return new ChangedTypeMethodReflection($declaringClass, $method, $variants); + return new ChangedTypeMethodReflection($declaringClass, $method, $variants, $namedArgumentVariants); } private function transformStaticType(Type $type): Type diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 314bc754f8..a76cf064aa 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -77,7 +77,7 @@ public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflectio private function transformMethodWithStaticType(ClassReflection $declaringClass, ExtendedMethodReflection $method): ExtendedMethodReflection { - $variants = array_map(fn (ParametersAcceptorWithPhpDocs $acceptor): ParametersAcceptorWithPhpDocs => new FunctionVariantWithPhpDocs( + $variantFn = fn (ParametersAcceptorWithPhpDocs $acceptor): ParametersAcceptorWithPhpDocs => new FunctionVariantWithPhpDocs( $acceptor->getTemplateTypeMap(), $acceptor->getResolvedTemplateTypeMap(), array_map( @@ -99,9 +99,14 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $this->transformStaticType($acceptor->getPhpDocReturnType()), $this->transformStaticType($acceptor->getNativeReturnType()), $acceptor->getCallSiteVarianceMap(), - ), $method->getVariants()); + ); + $variants = array_map($variantFn, $method->getVariants()); + $namedArgumentsVariants = $method->getNamedArgumentsVariants(); + $namedArgumentsVariants = $namedArgumentsVariants !== null + ? array_map($variantFn, $namedArgumentsVariants) + : null; - return new ChangedTypeMethodReflection($declaringClass, $method, $variants); + return new ChangedTypeMethodReflection($declaringClass, $method, $variants, $namedArgumentsVariants); } private function transformStaticType(Type $type): Type diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 519e880dd7..35fbe17d18 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3079,4 +3079,43 @@ public function testTypedClassConstants(): void $this->analyse([__DIR__ . '/data/return-type-class-constant.php'], []); } + public function testNamedParametersForMultiVariantFunctions(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0'); + } + + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/call-methods-named-params-multivariant.php'], [ + [ + 'Unknown parameter $options in call to method XSLTProcessor::setParameter().', + 10, + ], + [ + 'Missing parameter $name (array) in call to method XSLTProcessor::setParameter().', + 10, + ], + [ + 'Unknown parameter $colno in call to method PDO::query().', + 15, + ], + [ + 'Unknown parameter $className in call to method PDO::query().', + 17, + ], + [ + 'Unknown parameter $constructorArgs in call to method PDO::query().', + 17, + ], + [ + 'Unknown parameter $className in call to method PDOStatement::setFetchMode().', + 22, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/call-methods-named-params-multivariant.php b/tests/PHPStan/Rules/Methods/data/call-methods-named-params-multivariant.php new file mode 100644 index 0000000000..e9377cb61a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/call-methods-named-params-multivariant.php @@ -0,0 +1,22 @@ += 8.0 + +namespace CallMethodsNamedParamsMultivariant; + + +$xslt = new \XSLTProcessor(); +$xslt->setParameter(namespace: 'ns', name:'aaa', value: 'bbb'); +$xslt->setParameter(namespace: 'ns', name: ['aaa' => 'bbb']); +// wrong +$xslt->setParameter(namespace: 'ns', options: ['aaa' => 'bbb']); + +$pdo = new \PDO('123'); +$pdo->query(query: 'SELECT 1', fetchMode: \PDO::FETCH_ASSOC); +// wrong +$pdo->query(query: 'SELECT 1', fetchMode: \PDO::FETCH_ASSOC, colno: 1); +// wrong +$pdo->query(query: 'SELECT 1', fetchMode: \PDO::FETCH_ASSOC, className: 'Foo', constructorArgs: []); + +$stmt = new \PDOStatement(); +$stmt->setFetchMode(mode: 5); +// wrong +$stmt->setFetchMode(mode: 5, className: 'aa');