From fb4e0a1e51145e8f76fb29283f24eb23704d65b2 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 21 Oct 2025 11:23:56 +0200 Subject: [PATCH 1/2] Fix strtr inferences --- ...aceFunctionsDynamicReturnTypeExtension.php | 6 +++++ .../Rules/Methods/CallMethodsRuleTest.php | 10 ++++++++ .../PHPStan/Rules/Methods/data/bug-13708.php | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-13708.php diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index 7d1242ffea..d157258f0d 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -98,6 +98,12 @@ private function getPreliminarilyResolvedTypeFromFunctionCall( if ($replaceArgumentType->isArray()->yes()) { $replaceArgumentType = $replaceArgumentType->getIterableValueType(); } + } elseif ($functionReflection->getName() === 'strtr' && isset($functionCall->getArgs()[1])) { + // `strtr` has two signatures: `strtr($string1, $string2, $string3)` and `strtr($string1, $array)` + $secondArgumentType = $scope->getType($functionCall->getArgs()[1]->value); + if ($secondArgumentType->isArray()->yes()) { + $replaceArgumentType = $secondArgumentType->getIterableValueType(); + } } } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 0dbebf0da3..294a72488e 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3656,6 +3656,16 @@ public function testBug5642(): void ]); } + public function testBug13708(): void + { + $this->checkThisOnly = false; + $this->checkNullables = false; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = false; + + $this->analyse([__DIR__ . '/data/bug-13708.php'], []); + } + public function testBug3396(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/bug-13708.php b/tests/PHPStan/Rules/Methods/data/bug-13708.php new file mode 100644 index 0000000000..867a56ff52 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-13708.php @@ -0,0 +1,23 @@ +takeNonEmpty( + strtr('change {me}', ['{me}' => 'me']) + ); + } +} From 93b42a2e34eaa04095d1d7f1cf8888a03724571f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 21 Oct 2025 21:00:27 +0200 Subject: [PATCH 2/2] Add assertion --- tests/PHPStan/Analyser/nsrt/strtr.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/strtr.php b/tests/PHPStan/Analyser/nsrt/strtr.php index 5bc9fd6679..44dc4981c2 100644 --- a/tests/PHPStan/Analyser/nsrt/strtr.php +++ b/tests/PHPStan/Analyser/nsrt/strtr.php @@ -24,4 +24,16 @@ function doFoo(string $s, $nonEmptyString, $nonFalseyString) { assertType('non-empty-string', strtr($nonFalseyString, $s, $nonEmptyString)); assertType('non-falsy-string', strtr($nonFalseyString, $nonEmptyString, $nonFalseyString)); assertType('non-falsy-string', strtr($nonFalseyString, $nonFalseyString, $nonFalseyString)); + + assertType('string', strtr($s, [$s => $nonEmptyString])); + assertType('string', strtr($s, [$nonEmptyString => $nonEmptyString])); + assertType('string', strtr($s, [$nonFalseyString => $nonFalseyString])); + + assertType('non-empty-string', strtr($nonEmptyString, [$s => $nonEmptyString])); + assertType('non-empty-string', strtr($nonEmptyString, [$nonEmptyString => $nonEmptyString])); + assertType('non-empty-string', strtr($nonEmptyString, [$nonFalseyString => $nonFalseyString])); + + assertType('non-empty-string', strtr($nonFalseyString, [$s => $nonEmptyString])); + assertType('non-falsy-string', strtr($nonFalseyString, [$nonEmptyString => $nonFalseyString])); + assertType('non-falsy-string', strtr($nonFalseyString, [$nonFalseyString => $nonFalseyString])); }