Repository navigation
Report str_replace()/str_ireplace()/preg_replace()/preg_filter() calls with fewer replacements than search values - #7007
Open
phpstan-bot wants to merge 4 commits into
Conversation
This was referenced Nov 8, 2026
…()` calls with fewer replacements than search values - New bleeding-edge rule `ReplaceCountMismatchRule` (level 5, feature toggle `replaceCountMismatch`): when both the search/pattern and the replace/replacement arguments are arrays and the replacement array size is definitely smaller than the search array size, report it, because PHP silently replaces the remaining values with an empty string. - Uses `Type::getArraySize()` + `isSmallerThan()` so constant arrays, unions of shapes and non-empty lists are handled; only definite mismatches are reported. - A string replacement and an explicitly empty replacement array (a deliberate "remove all" idiom) are not reported. - Applied to the whole family of replace functions with the same semantics: str_replace, str_ireplace, preg_replace, preg_filter. - Respects treatPhpDocTypesAsCertain and adds the tip when only PHPDoc types prove the mismatch.
staabm
force-pushed
the
create-pull-request/patch-kniiekt
branch
from
November 9, 2026 20:12
1eb0d93 to
c7d91d5
Compare
staabm
requested changes
Nov 9, 2026
Comment on lines
+88
to
+91
| $args = $normalizedFuncCall->getArgs(); | ||
| if (!array_key_exists(0, $args) || !array_key_exists(1, $args)) { | ||
| return []; | ||
| } |
Contributor
There was a problem hiding this comment.
check the args count instead and do so very early in the method - because its fast
Collaborator
Author
There was a problem hiding this comment.
I made all three changes staabm asked for in three local commits (not pushed). make tests, make phpstan and make cs all pass.
- Bail out early on argument count (
fbd86667f): the rule now returns at the very start ofprocessNode()when the call has fewer than two arguments, before looking up the function. I kept the existing key check after the arguments are reordered. With named arguments that leave outsearchorreplace, the reordered list can have a gap at position 0 or 1, and a plain count wouldn't catch that. - Identifier (
75d824b6f): every reported call now usesargument.replaceCount. ThestrReplace/pregReplacenames per function are gone, soFUNCTIONSis now just a lookup list. - Error message (
0bcc27082): the message is nowCall to %s is using fewer replacements than search values., filled with the function name (e.g.Call to str_replace is using …). That made the parameter-name lookup and the size output unused, so I removed them, and updated the expected messages inReplaceCountMismatchRuleTest.
The PR description still shows the old message and the strReplace.replaceCount / pregReplace.replaceCount identifiers, so it should be updated when these are pushed.
| } | ||
|
|
||
| $errorBuilder = RuleErrorBuilder::message(sprintf( | ||
| 'Parameter #2 $%s of function %s has fewer elements (%s) than parameter #1 $%s (%s), the remaining values will be replaced with an empty string.', |
Contributor
There was a problem hiding this comment.
a better error message would be:
Call to %s is using fewer replacements than search values.
| $replaceType->getArraySize()->describe(VerbosityLevel::value()), | ||
| $parameters[0]->getName(), | ||
| $searchType->getArraySize()->describe(VerbosityLevel::value()), | ||
| ))->identifier(sprintf('%s.replaceCount', self::FUNCTIONS[$functionName])); |
Contributor
There was a problem hiding this comment.
identifier should be argument.replaceCount
…an two arguments Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
str_replace(['a', 'b', 'c'], ['x', 'y'], $s)is valid PHP: the search values without a matching replacement are replaced with an empty string. This is rarely intended and usually a typo. This PR adds a bleeding-edge rule that reports these calls.Changes
src/Rules/Functions/ReplaceCountMismatchRule.php: new level-5 rule, gated by thereplaceCountMismatchfeature toggle (enabled inconf/bleedingEdge.neon, declared inconf/config.neonandconf/parametersSchema.neon).str_replace,str_ireplace,preg_replaceandpreg_filter. Identifiers arestrReplace.replaceCountandpregReplace.replaceCount.list<string>, or a union of shapes that overlaps)ArgumentsNormalizer, and handlestreatPhpDocTypesAsCertain, adding the tip when only PHPDoc types prove the mismatch.Root cause
PHPStan had no check comparing the sizes of the search and replace arrays. The rule compares
getArraySize()of both arguments withisSmallerThan(). This works for constant arrays, array shapes, unions of shapes and non-empty lists alike.Probed and out of scope:
str_replace('a', ['x'], $s)(string search with an array replace) throws a TypeError on PHP 8 but is not reported today. That comes from the function signature, not a count mismatch, so it is left for a separate change.Test
tests/PHPStan/Rules/Functions/ReplaceCountMismatchRuleTest.phpwithdata/bug-3889.php. It contains the issue's reproducer verbatim, the string-replacement case from the comments (no error), and variants for:str_ireplace,preg_replace,preg_filter, named arguments (separate PHP 8.0+ fixturedata/bug-3889-named-args.php), PHPDoc-only shapes (with the tip), lists, non-empty lists, unions of shapes, and empty/equal/longer replacement arrays.Fixes phpstan/phpstan#3889
🤖 Generated with Claude Code