Treat decimal-int-string as a numeric string in looseCompare() instead of assuming inequality with non-decimal strings#5824
Open
phpstan-bot wants to merge 1 commit into
Conversation
…stead of assuming inequality with non-decimal strings - `AccessoryDecimalIntegerStringType::looseCompare()` wrongly returned `false` whenever the other operand was a string that is not a `decimal-int-string`. Numeric strings compare numerically in PHP, so `'2' == '02'` and `'2' == '2.0'` are `true`. - For the non-inverse case (an actual `decimal-int-string`), mirror `AccessoryNumericStringType::looseCompare()`: a decimal-int-string is a numeric, non-empty string, so it is never loosely equal to `null` or to a non-numeric string, but may equal any numeric value. - For the inverse case (`non-decimal-int-string`), always return `bool`: such a value may still be numeric (`"02"`, `"2.0"`, `"1e1"`) or empty (`""`), so it can be loosely equal to a decimal-int-string, another numeric value, or `null` (`'' == null` is `true`). - Probed sibling accessory string types: `AccessoryNumericStringType`, `AccessoryNonEmptyStringType`, `AccessoryLowercaseStringType`/`AccessoryUppercaseStringType` already guard numeric-string loose comparison correctly; no change needed there.
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
PHPStan reported
Loose comparison using == between decimal-int-string and non-decimal-int-string will always evaluate to false(and inferred the result typefalse) for comparisons that are actuallytrueat runtime. Because PHP compares numeric strings numerically,'2' == '02'and'2' == '2.0'are bothtrue. The fix teachesAccessoryDecimalIntegerStringType::looseCompare()to behave like a numeric string.Changes
src/Type/Accessory/AccessoryDecimalIntegerStringType.php: rewrotelooseCompare().decimal-int-string): a decimal-int-string is a numeric, non-empty string, so it mirrorsAccessoryNumericStringType::looseCompare()—falseonly againstnullor a definitely non-numeric string, otherwisebool.non-decimal-int-string): always returnsbool, since the value may be numeric ("02","2.0","1e1") or empty ("") and therefore can be loosely equal to a decimal-int-string, another numeric value, ornull.tests/PHPStan/Analyser/nsrt/bug-14793.php: new regression test.Root cause
The old implementation assumed a
decimal-int-stringcan never be loosely equal to a string that is not adecimal-int-string. That ignores PHP's numeric-string juggling: a non-decimal string such as"02","2.0"or"1e1"is still numeric and compares numerically. The same flawed assumption applied symmetrically to the inversenon-decimal-int-stringtype, and the unconditionalnullshort-circuit was wrong for the inverse type because""is a validnon-decimal-int-stringand'' == nullistrue.I probed the parallel accessory string types on the same axis (loose comparison of refined string types):
AccessoryNumericStringType,AccessoryNonEmptyStringType, andAccessoryLowercaseStringType/AccessoryUppercaseStringTypealready handle the numeric-string case correctly (the lowercase/uppercase variants explicitly fall back toboolwhen either side may be numeric), so no analogous fix was required there.Test
tests/PHPStan/Analyser/nsrt/bug-14793.phpasserts:decimal-int-string == non-decimal-int-string(both directions),== numeric-string,== '2.0',== '02'inferbool(previouslyfalse).decimal-int-string == 'foo'and== nullstill inferfalse(correct: non-numeric string / non-empty string vs null).non-decimal-int-string == nullinfersbool(previouslyfalse), because'' == nullistrue.Verified the test fails on the unpatched source (
Expected: bool, Actual: false) and passes with the fix.Fixes phpstan/phpstan#14793