Sort classes before interfaces when describing an IntersectionType#5873
Closed
phpstan-bot wants to merge 1 commit into
Closed
Sort classes before interfaces when describing an IntersectionType#5873phpstan-bot wants to merge 1 commit into
IntersectionType#5873phpstan-bot wants to merge 1 commit into
Conversation
- Add an opt-in `$sortClassesBeforeInterfaces` flag to `UnionTypeHelper::sortTypes()`; when set, object types backed by a single non-interface class reflection sort before object types backed by an interface. - `IntersectionType::getSortedTypes()` passes the flag, so `self`/`static`/`$this` (and any concrete class) are now always placed before interfaces in the intersection, e.g. `static(HelloWorld)&I` and `$this(HelloWorld)&I2` instead of the previous inconsistent `I&static(HelloWorld)` vs `$this(HelloWorld)&I2`. - The flag is left off for `UnionType` ordering, so union descriptions are unchanged. - Updated test expectations that asserted the old interface-first ordering.
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
In an
IntersectionTypeconsisting of a class and one or more interfaces, the members were ordered purely by their textual description. Becausestatic/$this/selfand interface names sort inconsistently against each other ($sorts before letters, butstatic/selfsort by their first letter), PHPStan produced inconsistent output:I&static(HelloWorld)(interface first) yet$this(HelloWorld)&I2(class first). This made error messages and dumped types harder to read.The fix sorts the concrete class before the interfaces, so the leading type of an intersection is always the class, regardless of how the names happen to compare alphabetically.
Changes
src/Type/UnionTypeHelper.php:sortTypes()gains an opt-inbool $sortClassesBeforeInterfacesparameter (defaultfalse). When enabled and both compared types resolve to exactly one class reflection, a non-interface class sorts before an interface.src/Type/IntersectionType.php:getSortedTypes()callssortTypes(..., true)so the new ordering applies to intersection descriptions only. Union ordering is intentionally left untouched.tests/PHPStan/Analyser/nsrt/bug-9341.php,bug-4557.php,instanceof.php,array-destructuring.php,closure-types.phptests/PHPStan/Analyser/data/bug-6649.php,bug-7344.phptests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.phpRoot cause
IntersectionTypedescribes its members in the order produced byUnionTypeHelper::sortTypes(), whose final fallback comparesdescribe()strings. For object members that comparison is unstable with respect to the class/interface distinction:static/$this/selfdescribe with prefixes (static(...),$this(...), or the bare class name) that sort differently against interface names. The result was that the class sometimes landed before and sometimes after the interfaces. Sorting by the class-vs-interface property — using each type'sgetObjectClassReflections()+ClassReflection::isInterface()rather than its description — removes the dependency on alphabetical accidents.Test
tests/PHPStan/Analyser/nsrt/bug-7343.phpcoveringI&static,static&I,$this&I2,I2&$this,self&I,I&self, a class with multiple interfaces (self&I&I2), and a class intersected with a generic interface (self&ArrayAccess<int,int>). All now describe the class first. Verified the test fails on thestatic/Icases before the fix and passes after.class-string<...>): they all benefit from the same fix, and their existing test expectations were updated accordingly. Union ordering was deliberately not changed.Fixes phpstan/phpstan#7343