diff --git a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php index 57e3b76710..b5a24cdcf9 100644 --- a/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/ReplaceFunctionsDynamicReturnTypeExtension.php @@ -99,6 +99,13 @@ 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 = TypeCombinator::intersect( + new ArrayType(new MixedType(), new MixedType()), + $scope->getType($functionCall->getArgs()[1]->value), + ); + $replaceArgumentType = $secondArgumentType->getIterableValueType(); } } diff --git a/tests/PHPStan/Analyser/nsrt/strtr.php b/tests/PHPStan/Analyser/nsrt/strtr.php index 5bc9fd6679..0ac591b546 100644 --- a/tests/PHPStan/Analyser/nsrt/strtr.php +++ b/tests/PHPStan/Analyser/nsrt/strtr.php @@ -7,8 +7,9 @@ /** * @param non-empty-string $nonEmptyString * @param non-falsy-string $nonFalseyString + * @param mixed $mixed */ -function doFoo(string $s, $nonEmptyString, $nonFalseyString) { +function doFoo(string $s, $nonEmptyString, $nonFalseyString, $mixed) { assertType('string', strtr($s, 'f', 'b')); assertType('string', strtr($s, ['f' => 'b'])); assertType('string', strtr($s, ['f' => 'b', 'o' => 'a'])); @@ -24,4 +25,27 @@ 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])); + + assertType('non-empty-string', strtr($nonEmptyString, rand(0, 1) ? [$s => $nonEmptyString] : null)); + assertType('non-empty-string', strtr($nonEmptyString, rand(0, 1) ? [$nonEmptyString => $nonEmptyString] : null)); + assertType('non-empty-string', strtr($nonEmptyString, rand(0, 1) ? [$nonFalseyString => $nonFalseyString] : null)); + + assertType('non-empty-string', strtr($nonFalseyString, rand(0, 1) ? [$s => $nonEmptyString] : null)); + assertType('non-falsy-string', strtr($nonFalseyString, rand(0, 1) ? [$nonEmptyString => $nonFalseyString] : null)); + assertType('non-falsy-string', strtr($nonFalseyString, rand(0, 1) ? [$nonFalseyString => $nonFalseyString] : null)); + + assertType('string', strtr($nonEmptyString, $mixed)); + assertType('string', strtr($nonFalseyString, $mixed)); } 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']) + ); + } +}