Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Type/IntersectionType.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private function getSortedTypes(): array
return $this->types;
}

$this->types = UnionTypeHelper::sortTypes($this->types);
$this->types = UnionTypeHelper::sortTypes($this->types, true);
$this->sortedTypes = true;

return $this->types;
Expand Down
19 changes: 17 additions & 2 deletions src/Type/UnionTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ final class UnionTypeHelper
* @param list<T> $types
* @return list<T>
*/
public static function sortTypes(array $types): array
public static function sortTypes(array $types, bool $sortClassesBeforeInterfaces = false): array
{
if (count($types) > 1024) {
return $types;
}

usort($types, static function (Type $a, Type $b): int {
usort($types, static function (Type $a, Type $b) use ($sortClassesBeforeInterfaces): int {
if ($a instanceof NullType) {
return 1;
} elseif ($b instanceof NullType) {
Expand Down Expand Up @@ -121,6 +121,21 @@ public static function sortTypes(array $types): array
return self::compareStrings($a->describe(VerbosityLevel::precise()), $b->describe(VerbosityLevel::precise()));
}

if ($sortClassesBeforeInterfaces) {
$aClassReflections = $a->getObjectClassReflections();
$bClassReflections = $b->getObjectClassReflections();
if (count($aClassReflections) === 1 && count($bClassReflections) === 1) {
$aIsInterface = $aClassReflections[0]->isInterface();
$bIsInterface = $bClassReflections[0]->isInterface();
if (!$aIsInterface && $bIsInterface) {
return -1;
}
if ($aIsInterface && !$bIsInterface) {
return 1;
}
}
}

return self::compareStrings($a->describe(VerbosityLevel::typeOnly()), $b->describe(VerbosityLevel::typeOnly()));
});
return $types;
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-6649.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Collection {}
class SubCollection extends Collection {
/** @param TKey $key */
public function __construct($key) {
assertType('TKey of Bug6649\Bar&Bug6649\Foo (class Bug6649\SubCollection, argument)', $key);
assertType('TKey of Bug6649\Foo&Bug6649\Bar (class Bug6649\SubCollection, argument)', $key);
}

public static function test(): void {
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-7344.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function doFoo($maleOrFemale): void
return;
}

assertType('(Bug7344\Female&Bug7344\IsEntity)|(Bug7344\IsEntity&Bug7344\Male)', $maleOrFemale);
assertType('(Bug7344\Female&Bug7344\IsEntity)|(Bug7344\Male&Bug7344\IsEntity)', $maleOrFemale);
assertType('int', $maleOrFemale->getModel());
}
}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/array-destructuring.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function (\stdClass $obj) {
assertType('int', $dateArray['d']);
assertType('lowercase-string&uppercase-string', $intArrayForRewritingFirstElement[0]);
assertType('int', $intArrayForRewritingFirstElement[1]);
assertType('ArrayAccess&stdClass', $obj);
assertType('stdClass&ArrayAccess', $obj);
assertType('stdClass', $newArray['newKey']);
assertType('true', $assocKey);
assertType('\'foo\'', $assocFoo);
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-4557.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Bar extends Foo
public function doBar(): void
{
$mock = $this->createMock(\stdClass::class);
assertType('Bug4557\\MockObject&stdClass', $mock);
assertType('stdClass&Bug4557\\MockObject', $mock);
}

/** @return T */
Expand Down
55 changes: 55 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-7343.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);

namespace Bug7343;

use function PHPStan\Testing\assertType;

interface I
{
}

interface I2
{
}

abstract class HelloWorld
{

public function sayHello(): void
{
/** @var I&static $a */
$a = $this->obj();
assertType('static(Bug7343\HelloWorld)&Bug7343\I', $a);

/** @var static&I $b */
$b = $this->obj();
assertType('static(Bug7343\HelloWorld)&Bug7343\I', $b);

/** @var $this&I2 $c */
$c = $this->obj();
assertType('$this(Bug7343\HelloWorld)&Bug7343\I2', $c);

/** @var I2&$this $d */
$d = $this->obj();
assertType('$this(Bug7343\HelloWorld)&Bug7343\I2', $d);

/** @var self&I $e */
$e = $this->obj();
assertType('Bug7343\HelloWorld&Bug7343\I', $e);

/** @var I&self $f */
$f = $this->obj();
assertType('Bug7343\HelloWorld&Bug7343\I', $f);

/** @var I2&self&I $g */
$g = $this->obj();
assertType('Bug7343\HelloWorld&Bug7343\I&Bug7343\I2', $g);

/** @var \ArrayAccess<int, int>&self $h */
$h = $this->obj();
assertType('Bug7343\HelloWorld&ArrayAccess<int, int>', $h);
}

abstract public function obj(): object;

}
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/nsrt/bug-9341.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public static function parse(): mixed
if (!is_a($class, MyInterface::class, true)) {
return false;
}
assertType('class-string<Bug9341\MyInterface&static(Bug9341\MyAbstractBase)>', $class);
assertType('class-string<static(Bug9341\MyAbstractBase)&Bug9341\MyInterface>', $class);
$fileObject = new $class();
assertType('Bug9341\MyInterface&static(Bug9341\MyAbstractBase)', $fileObject);
assertType('static(Bug9341\MyAbstractBase)&Bug9341\MyInterface', $fileObject);
return $fileObject;
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Analyser/nsrt/closure-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function closureNewThisIntersection(stdClass $foo) {
}

(function () {
assertType('DateTimeInterface&stdClass', $this);
assertType('stdClass&DateTimeInterface', $this);
})->call($foo);
}

Expand All @@ -64,7 +64,7 @@ public function arrowFunctionNewThisIntersection(stdClass $foo) {
return;
}

(fn () => assertType('DateTimeInterface&stdClass', $this))->call($foo);
(fn () => assertType('stdClass&DateTimeInterface', $this))->call($foo);
}

}
6 changes: 3 additions & 3 deletions tests/PHPStan/Analyser/nsrt/instanceof.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function someMethod(Expr $foo, Foo $intersected)
assertType('InstanceOfNamespace\Foo', $self);
assertType('static(InstanceOfNamespace\Foo)', $static);
assertType('static(InstanceOfNamespace\Foo)', clone $static);
assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $intersected);
assertType('InstanceOfNamespace\Foo&InstanceOfNamespace\BarInterface', $intersected);
assertType('$this(InstanceOfNamespace\Foo)&InstanceOfNamespace\BarInterface', $this);
assertType('InstanceOfNamespace\BarParent', $parent);
}
Expand Down Expand Up @@ -107,7 +107,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter
}

if ($subject instanceof $intersection) {
assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $subject);
assertType('InstanceOfNamespace\Foo&InstanceOfNamespace\BarInterface', $subject);
assertType('bool', $subject instanceof $intersection);
assertType('true', $subject instanceof BarInterface);
assertType('true', $subject instanceof Foo);
Expand Down Expand Up @@ -200,7 +200,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter
}

if ($instance instanceof $classString) {
assertType('InstanceOfNamespace\BarInterface&InstanceOfNamespace\Foo', $instance);
assertType('InstanceOfNamespace\Foo&InstanceOfNamespace\BarInterface', $instance);
assertType('bool', $instance instanceof $classString);
} else {
assertType('InstanceOfNamespace\BarInterface', $instance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ public function testBug3782(): void
{
$this->analyse([__DIR__ . '/data/bug-3782.php'], [
[
'Cannot access offset (int|string) on $this(Bug3782\HelloWorld)|(ArrayAccess&Bug3782\HelloWorld).',
'Cannot access offset (int|string) on $this(Bug3782\HelloWorld)|(Bug3782\HelloWorld&ArrayAccess).',
11,
],
]);
Expand Down
Loading