diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index bd637913bc..aed8352077 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -409,16 +409,25 @@ public function check( if ($nativePropertyReflection === null) { continue; } - if (!$nativePropertyReflection->isReadOnly()) { - continue; - } - if ($nativePropertyReflection->isStatic()) { - $propertyDescription = sprintf('static readonly property %s::$%s', $propertyReflection->getDeclaringClass()->getDisplayName(), $propertyReflection->getName()); + if ($nativePropertyReflection->isReadOnly()) { + if ($nativePropertyReflection->isStatic()) { + $errorFormat = 'static readonly property %s::$%s'; + } else { + $errorFormat = 'readonly property %s::$%s'; + } + } elseif ($nativePropertyReflection->isReadOnlyByPhpDoc()) { + if ($nativePropertyReflection->isStatic()) { + $errorFormat = 'static @readonly property %s::$%s'; + } else { + $errorFormat = '@readonly property %s::$%s'; + } } else { - $propertyDescription = sprintf('readonly property %s::$%s', $propertyReflection->getDeclaringClass()->getDisplayName(), $propertyReflection->getName()); + continue; } + $propertyDescription = sprintf($errorFormat, $propertyReflection->getDeclaringClass()->getDisplayName(), $propertyReflection->getName()); + $errors[] = RuleErrorBuilder::message(sprintf( '%s is passed by reference so it does not accept %s.', $this->describeParameter($parameter, $argumentName === null ? $i + 1 : null), diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index 4eb95d8449..0bf4133b79 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -1982,4 +1982,41 @@ public function testBug3107(): void $this->analyse([__DIR__ . '/data/bug-3107.php'], []); } + public function testBug12676(): void + { + $errors = [ + [ + 'Parameter #1 $array is passed by reference so it does not accept @readonly property Bug12676\A::$a.', + 15, + ], + [ + 'Parameter #1 $array is passed by reference so it does not accept @readonly property Bug12676\B::$readonlyArr.', + 25, + ], + [ + 'Parameter #1 $array is passed by reference so it does not accept static @readonly property Bug12676\C::$readonlyArr.', + 35, + ], + ]; + + if (PHP_VERSION_ID < 80000) { + $errors = [ + [ + 'Parameter #1 $array_arg is passed by reference so it does not accept @readonly property Bug12676\A::$a.', + 15, + ], + [ + 'Parameter #1 $array_arg is passed by reference so it does not accept @readonly property Bug12676\B::$readonlyArr.', + 25, + ], + [ + 'Parameter #1 $array_arg is passed by reference so it does not accept static @readonly property Bug12676\C::$readonlyArr.', + 35, + ], + ]; + } + + $this->analyse([__DIR__ . '/data/bug-12676.php'], $errors); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-12676.php b/tests/PHPStan/Rules/Functions/data/bug-12676.php new file mode 100644 index 0000000000..419705e89c --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-12676.php @@ -0,0 +1,37 @@ + */ + public array $a; + + public function __construct() { + $this->a = ['b' => 2, 'a' => 1]; + ksort($this->a); + } +} + +class B { + /** @readonly */ + public array $readonlyArr; + + public function __construct() { + $this->readonlyArr = ['b' => 2, 'a' => 1]; + ksort($this->readonlyArr); + } +} + +class C { + /** @readonly */ + static public array $readonlyArr; + + public function __construct() { + self::$readonlyArr = ['b' => 2, 'a' => 1]; + ksort(self::$readonlyArr); + } +}