Repository navigation
Narrow loose comparisons by the values each side can loosely equal - #6938
Open
ondrejmirtes wants to merge 1 commit into
Open
ondrejmirtes wants to merge 1 commit into
ondrejmirtes wants to merge 1 commit into
Conversation
ondrejmirtes
force-pushed
the
float-range-type
branch
from
November 4, 2026 08:50
4861e95 to
b3bab80
Compare
ondrejmirtes
force-pushed
the
loose-equality-narrowing
branch
from
November 4, 2026 08:51
6041002 to
3064e79
Compare
ondrejmirtes
force-pushed
the
float-range-type
branch
from
November 4, 2026 10:29
b3bab80 to
41b8214
Compare
ondrejmirtes
force-pushed
the
loose-equality-narrowing
branch
from
November 4, 2026 10:29
3064e79 to
ae9fc74
Compare
Type gets getPossiblyLooselyEqualType() and getAlwaysLooselyEqualType(), which LooseComparisonHelper computes for the configured PHP version: the values `$this == $x` can be true for, and the values it is always true for. The true branch of `==` intersects the other side with the first, the false branch removes the second, and resolveEqualType() answers false or true when the other side misses the first or lies within the second. This replaces the `== 0` and `== ''` branches that hedged between PHP 7 and PHP 8. Objects equal 1 and true; only Stringable and GMP objects equal strings and only GMP, BcMath\Number and SimpleXMLElement objects equal other numbers or false. NAN equals only true. Two numeric strings compare as numbers, so `==` and a loose in_array() no longer narrow such a pair as `===`, and an enum compared with a possible true is no longer strict in in_array(). A loose in_array() now narrows the needle as well. Cast truthiness gets its own value sets instead of `== 0` and `== ''`, which (int) 'abc', (int) 0.5 and (string) 0 do not follow. Closes phpstan/phpstan#8224
ondrejmirtes
force-pushed
the
loose-equality-narrowing
branch
from
November 4, 2026 11:22
ae9fc74 to
2737e8b
Compare
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.
Stacked on #6937. Review only the last commit.
Adds
Type::getPossiblyLooselyEqualType(PhpVersion)andType::getAlwaysLooselyEqualType(PhpVersion). For the configured PHP version, the first is the set of values$this == $xcan be true for, and the second is the set it is always true for.LooseComparisonHelpercomputes both.How they are used
==narrowing. The true branch intersects each side with the other side's possible set. The false branch removes the other side's always set. This replaces the== 0and== ''special cases that hedged between PHP 7 and PHP 8.resolveEqualType(). WhenlooseCompare()is undecided, it now answers false when the other side is outside the possible set, and true when it is inside the always set.in_array(). It now narrows the needle too, and makes the haystack non-empty in the true branch.true.(int),(float)and(string)get their own zero and falsy sets, because(int) 'abc',(int) 0.5and(string) 0do not follow== 0and== ''.mixedstaysmixed. When the true branch would turnmixedinto a union containing objects such asGMP|Stringable, the subject is left asmixed. This covers explicit, implicit and subtracted mixed, and templates bounded by mixed, for==,switchand loosein_array(). Enum cases are precise enough, so they still narrow. Every other type still narrows, for examplestring|bool|null == 'a'gives'a'|trueandobject|string == 'a'gives'a'|GMP|Stringable. Note thatmixed == 0andmixed == ''now staymixed.PHP version ranges.
Typemethods still take onePhpVersion. Callers that have a scope expandScope::getPhpVersion()withLooseComparisonHelper::getRepresentativePhpVersions(): one version per range that compares alike, split at 8.0 and 8.4. The possible sets are unioned, the always sets are intersected, andlooseCompare()is constant only when every version agrees. With a 7.4–8.5 range,0 == 'abc'isbooland$int == 'abc'narrows to0. Insideif (PHP_VERSION_ID >= 80000)the PHP 8 rules apply. Scope-less initializer expressions keep the single configured version.Runtime facts in the sets. These were measured on PHP 7.4, 8.0 and 8.5:
1andtrue.Stringableand GMP objects equal strings. On PHP 7, any object can.BcMath\NumberandSimpleXMLElementobjects equal other numbers orfalse.true.INF == 'INF'is true from PHP 8.Trade-off. Two numeric strings compare as numbers (
'1E3' == '1e3'), so a pair of numeric strings is no longer narrowed as if it were===. Some types get noisier but sound unions as a result, for example in bug-12312.The Turbo mirrors are in the same commit. Self-analysis is byte-identical with Turbo on and off.
Closes phpstan/phpstan#8224