1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 09:37:59 +01:00

Normalize stringified type names (#2239)

This change makes stringified types more normalized. Concretely it sorts
all union types, reconciled types, and sorts the keys within object-like
types.
This commit is contained in:
lhchavez 2019-10-16 22:14:33 -07:00 committed by Matthew Brown
parent e8618371fb
commit 216f991b0c
36 changed files with 206 additions and 211 deletions

View File

@ -7,6 +7,7 @@ use function count;
use function get_class; use function get_class;
use function implode; use function implode;
use function is_int; use function is_int;
use function sort;
use Psalm\Codebase; use Psalm\Codebase;
use Psalm\CodeLocation; use Psalm\CodeLocation;
use Psalm\StatementsSource; use Psalm\StatementsSource;
@ -70,47 +71,43 @@ class ObjectLike extends \Psalm\Type\Atomic
public function __toString() public function __toString()
{ {
$union_type_parts = array_map(
/**
* @param string|int $name
* @param Union $type
*
* @return string
*/
function ($name, Union $type) {
return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type;
},
array_keys($this->properties),
$this->properties
);
sort($union_type_parts);
/** @psalm-suppress MixedOperand */ /** @psalm-suppress MixedOperand */
return static::KEY . '{' . return static::KEY . '{' . implode(', ', $union_type_parts) . '}';
implode(
', ',
array_map(
/**
* @param string|int $name
* @param Union $type
*
* @return string
*/
function ($name, Union $type) {
return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type;
},
array_keys($this->properties),
$this->properties
)
) .
'}';
} }
public function getId() public function getId()
{ {
$union_type_parts = array_map(
/**
* @param string|int $name
* @param Union $type
*
* @return string
*/
function ($name, Union $type) {
return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type->getId();
},
array_keys($this->properties),
$this->properties
);
sort($union_type_parts);
/** @psalm-suppress MixedOperand */ /** @psalm-suppress MixedOperand */
return static::KEY . '{' . return static::KEY . '{' .
implode( implode(', ', $union_type_parts) .
', ',
array_map(
/**
* @param string|int $name
* @param Union $type
*
* @return string
*/
function ($name, Union $type) {
return $name . ($type->possibly_undefined ? '?' : '') . ': ' . $type->getId();
},
array_keys($this->properties),
$this->properties
)
) .
'}' '}'
. ($this->previous_value_type . ($this->previous_value_type
? '<' . ($this->previous_key_type ? $this->previous_key_type->getId() . ', ' : '') ? '<' . ($this->previous_key_type ? $this->previous_key_type->getId() . ', ' : '')

View File

@ -42,6 +42,7 @@ use Psalm\Type\Atomic\TScalar;
use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Atomic\TTrue; use Psalm\Type\Atomic\TTrue;
use function sort;
use function str_replace; use function str_replace;
use function str_split; use function str_split;
use function strpos; use function strpos;
@ -279,18 +280,15 @@ class Reconciler
&& (!$has_isset || substr($key, -1, 1) !== ']') && (!$has_isset || substr($key, -1, 1) !== ']')
&& !($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer) && !($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer)
) { ) {
$reconcile_key = implode( $reconciled_parts = array_map(
'&', function (array $new_type_part_parts): string {
array_map( sort($new_type_part_parts);
/** return implode('|', $new_type_part_parts);
* @return string },
*/ $new_type_parts
function (array $new_type_part_parts) {
return implode('|', $new_type_part_parts);
},
$new_type_parts
)
); );
sort($reconciled_parts);
$reconcile_key = implode('&', $reconciled_parts);
self::triggerIssueForImpossible( self::triggerIssueForImpossible(
$result_type, $result_type,

View File

@ -7,6 +7,7 @@ use function array_shift;
use function array_values; use function array_values;
use function count; use function count;
use function get_class; use function get_class;
use function implode;
use function is_string; use function is_string;
use Psalm\Codebase; use Psalm\Codebase;
use Psalm\CodeLocation; use Psalm\CodeLocation;
@ -26,7 +27,9 @@ use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TString; use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Atomic\TTemplateParam;
use function reset; use function reset;
use function sort;
use function strpos; use function strpos;
use function strval;
use function substr; use function substr;
class Union class Union
@ -284,10 +287,7 @@ class Union
public function __toString() public function __toString()
{ {
if (empty($this->types)) { $types = [];
return '';
}
$s = '';
$printed_int = false; $printed_int = false;
$printed_float = false; $printed_float = false;
@ -314,18 +314,16 @@ class Union
$printed_int = true; $printed_int = true;
} }
$s .= $type . '|'; $types[] = strval($type);
} }
return substr($s, 0, -1) ?: ''; sort($types);
return implode('|', $types);
} }
public function getKey() : string public function getKey() : string
{ {
if (empty($this->types)) { $types = [];
return '';
}
$s = '';
$printed_int = false; $printed_int = false;
$printed_float = false; $printed_float = false;
@ -337,28 +335,29 @@ class Union
continue; continue;
} }
$s .= 'float|'; $types[] = 'float';
$printed_float = true; $printed_float = true;
} elseif ($type instanceof TLiteralString) { } elseif ($type instanceof TLiteralString) {
if ($printed_string) { if ($printed_string) {
continue; continue;
} }
$s .= 'string|'; $types[] = 'string';
$printed_string = true; $printed_string = true;
} elseif ($type instanceof TLiteralInt) { } elseif ($type instanceof TLiteralInt) {
if ($printed_int) { if ($printed_int) {
continue; continue;
} }
$s .= 'int|'; $types[] = 'int';
$printed_int = true; $printed_int = true;
} else { } else {
$s .= $type->getKey() . '|'; $types[] = strval($type->getKey());
} }
} }
return substr($s, 0, -1) ?: ''; sort($types);
return implode('|', $types);
} }
/** /**
@ -370,12 +369,12 @@ class Union
return $this->id; return $this->id;
} }
$s = ''; $types = [];
foreach ($this->types as $type) { foreach ($this->types as $type) {
$s .= $type->getId() . '|'; $types[] = strval($type->getId());
} }
sort($types);
$id = substr($s, 0, -1); $id = implode('|', $types);
$this->id = $id; $this->id = $id;
@ -409,7 +408,7 @@ class Union
$printed_float = false; $printed_float = false;
$printed_string = false; $printed_string = false;
$s = ''; $types = [];
foreach ($this->types as $type) { foreach ($this->types as $type) {
$type_string = $type->toNamespacedString($namespace, $aliased_classes, $this_class, $use_phpdoc_format); $type_string = $type->toNamespacedString($namespace, $aliased_classes, $this_class, $use_phpdoc_format);
@ -434,10 +433,11 @@ class Union
$printed_int = true; $printed_int = true;
} }
$s .= $type_string . '|'; $types[] = $type_string;
} }
return substr($s, 0, -1) ?: ''; sort($types);
return implode('|', $types);
} }
/** /**

View File

@ -25,7 +25,7 @@ class ArgTest extends TestCase
sort($b); sort($b);
', ',
'assertions' => [ 'assertions' => [
'$a' => 'array{b: int, a: int}', '$a' => 'array{a: int, b: int}',
'$b' => 'array<int, int>', '$b' => 'array<int, int>',
], ],
], ],
@ -37,8 +37,8 @@ class ArgTest extends TestCase
array_push($b, (bool)rand(0, 1)); array_push($b, (bool)rand(0, 1));
', ',
'assertions' => [ 'assertions' => [
'$a' => 'array<string|int, int|bool>', '$a' => 'array<int|string, bool|int>',
'$b' => 'array<string|int, int|bool>', '$b' => 'array<int|string, bool|int>',
], ],
], ],
'byRefArgAssignment' => [ 'byRefArgAssignment' => [

View File

@ -373,7 +373,7 @@ class ArrayAccessTest extends TestCase
$doc->loadXML("<node key=\"value\"/>"); $doc->loadXML("<node key=\"value\"/>");
$e = $doc->getElementsByTagName("node")[0];', $e = $doc->getElementsByTagName("node")[0];',
[ [
'$e' => 'null|DOMElement', '$e' => 'DOMElement|null',
], ],
], ],
'getOnArrayAcccess' => [ 'getOnArrayAcccess' => [

View File

@ -130,7 +130,7 @@ class ArrayAssignmentTest extends TestCase
break; break;
}', }',
'assertions' => [ 'assertions' => [
'$out' => 'list<string|int>', '$out' => 'list<int|string>',
], ],
], ],
'genericArrayCreationWithElementsAddedInSwitchWithNothing' => [ 'genericArrayCreationWithElementsAddedInSwitchWithNothing' => [
@ -150,7 +150,7 @@ class ArrayAssignmentTest extends TestCase
// do nothing // do nothing
}', }',
'assertions' => [ 'assertions' => [
'$out' => 'list<string|int>', '$out' => 'list<int|string>',
], ],
], ],
'implicit2dIntArrayCreation' => [ 'implicit2dIntArrayCreation' => [
@ -455,7 +455,7 @@ class ArrayAssignmentTest extends TestCase
$a["a"] = 5; $a["a"] = 5;
$a[0] = 3;', $a[0] = 3;',
'assertions' => [ 'assertions' => [
'$a' => 'array{a: int, 0: int}', '$a' => 'array{0: int, a: int}',
], ],
], ],
'updateStringIntKey2' => [ 'updateStringIntKey2' => [
@ -467,7 +467,7 @@ class ArrayAssignmentTest extends TestCase
$b[$string] = 5; $b[$string] = 5;
$b[0] = 3;', $b[0] = 3;',
'assertions' => [ 'assertions' => [
'$b' => 'array{c: int, 0: int}', '$b' => 'array{0: int, c: int}',
], ],
], ],
'updateStringIntKey3' => [ 'updateStringIntKey3' => [
@ -504,7 +504,7 @@ class ArrayAssignmentTest extends TestCase
$e[$int] = 3; $e[$int] = 3;
$e[$string] = 5;', $e[$string] = 5;',
'assertions' => [ 'assertions' => [
'$e' => 'non-empty-array<string|int, int>', '$e' => 'non-empty-array<int|string, int>',
], ],
], ],
'updateStringIntKeyWithIntRootAndNumberOffset' => [ 'updateStringIntKeyWithIntRootAndNumberOffset' => [
@ -517,7 +517,7 @@ class ArrayAssignmentTest extends TestCase
$a[0]["a"] = 5; $a[0]["a"] = 5;
$a[0][0] = 3;', $a[0][0] = 3;',
'assertions' => [ 'assertions' => [
'$a' => 'array{0: array{a: int, 0: int}}', '$a' => 'array{0: array{0: int, a: int}}',
], ],
], ],
'updateStringIntKeyWithIntRoot' => [ 'updateStringIntKeyWithIntRoot' => [
@ -545,10 +545,10 @@ class ArrayAssignmentTest extends TestCase
$e[0][$int] = 3; $e[0][$int] = 3;
$e[0][$string] = 5;', $e[0][$string] = 5;',
'assertions' => [ 'assertions' => [
'$b' => 'array{0: non-empty-array<string|int, int>}', '$b' => 'array{0: non-empty-array<int|string, int>}',
'$c' => 'array{0: non-empty-array<int|string, int>}', '$c' => 'array{0: non-empty-array<int|string, int>}',
'$d' => 'array{0: non-empty-array<int|string, int>}', '$d' => 'array{0: non-empty-array<int|string, int>}',
'$e' => 'array{0: non-empty-array<string|int, int>}', '$e' => 'array{0: non-empty-array<int|string, int>}',
], ],
], ],
'updateStringIntKeyWithObjectLikeRootAndNumberOffset' => [ 'updateStringIntKeyWithObjectLikeRootAndNumberOffset' => [
@ -561,7 +561,7 @@ class ArrayAssignmentTest extends TestCase
$a["root"]["a"] = 5; $a["root"]["a"] = 5;
$a["root"][0] = 3;', $a["root"][0] = 3;',
'assertions' => [ 'assertions' => [
'$a' => 'array{root: array{a: int, 0: int}}', '$a' => 'array{root: array{0: int, a: int}}',
], ],
], ],
'updateStringIntKeyWithObjectLikeRoot' => [ 'updateStringIntKeyWithObjectLikeRoot' => [
@ -589,10 +589,10 @@ class ArrayAssignmentTest extends TestCase
$e["root"][$int] = 3; $e["root"][$int] = 3;
$e["root"][$string] = 5;', $e["root"][$string] = 5;',
'assertions' => [ 'assertions' => [
'$b' => 'array{root: non-empty-array<string|int, int>}', '$b' => 'array{root: non-empty-array<int|string, int>}',
'$c' => 'array{root: non-empty-array<int|string, int>}', '$c' => 'array{root: non-empty-array<int|string, int>}',
'$d' => 'array{root: non-empty-array<int|string, int>}', '$d' => 'array{root: non-empty-array<int|string, int>}',
'$e' => 'array{root: non-empty-array<string|int, int>}', '$e' => 'array{root: non-empty-array<int|string, int>}',
], ],
], ],
'mixedArrayAssignmentWithStringKeys' => [ 'mixedArrayAssignmentWithStringKeys' => [
@ -812,7 +812,7 @@ class ArrayAssignmentTest extends TestCase
$a_keys = array_keys($a);', $a_keys = array_keys($a);',
'assertions' => [ 'assertions' => [
'$a' => 'array{0: string, 1: int}', '$a' => 'array{0: string, 1: int}',
'$a_values' => 'non-empty-list<string|int>', '$a_values' => 'non-empty-list<int|string>',
'$a_keys' => 'list<int>', '$a_keys' => 'list<int>',
], ],
], ],
@ -1019,7 +1019,7 @@ class ArrayAssignmentTest extends TestCase
$a = (array) (rand(0, 1) ? [1 => "one"] : 0); $a = (array) (rand(0, 1) ? [1 => "one"] : 0);
$b = (array) null;', $b = (array) null;',
'assertions' => [ 'assertions' => [
'$a' => 'array{1?: string, 0?: int}', '$a' => 'array{0?: int, 1?: string}',
'$b' => 'array<empty, empty>', '$b' => 'array<empty, empty>',
], ],
], ],

View File

@ -150,7 +150,7 @@ class ClassTest extends TestCase
$class = mt_rand(0, 1) === 1 ? Foo::class : Bar::class; $class = mt_rand(0, 1) === 1 ? Foo::class : Bar::class;
$object = new $class();', $object = new $class();',
'assertions' => [ 'assertions' => [
'$object' => 'Foo|Bar', '$object' => 'Bar|Foo',
], ],
], ],
'instantiateClassAndIsA' => [ 'instantiateClassAndIsA' => [

View File

@ -156,10 +156,10 @@ class ClassMoveTest extends \Psalm\Tests\TestCase
class B {} class B {}
/** /**
* @param null|B $a * @param B|null $a
* @param string | null $b * @param string | null $b
* @param callable():B $c * @param callable():B $c
* @return null|B * @return B|null
*/ */
function foo(?B $a, $b, $c) : ?B { function foo(?B $a, $b, $c) : ?B {
return $a; return $a;
@ -338,7 +338,7 @@ class ClassMoveTest extends \Psalm\Tests\TestCase
use Exception; use Exception;
class B { class B {
/** @var null|Exception */ /** @var Exception|null */
public $x; public $x;
/** /**
@ -403,12 +403,12 @@ class ClassMoveTest extends \Psalm\Tests\TestCase
/** /**
* @param Bahh $b * @param Bahh $b
* @param Bahh::FOO|Bahh::FAR $c * @param Bahh::FAR|Bahh::FOO $c
*/ */
function doSomething(Bahh $b, int $c) : void {} function doSomething(Bahh $b, int $c) : void {}
class A { class A {
/** @var null|Bahh */ /** @var Bahh|null */
public $x = null; public $x = null;
} }
} }
@ -455,29 +455,29 @@ class ClassMoveTest extends \Psalm\Tests\TestCase
public $x = null; public $x = null;
/** @var ?B */ /** @var ?B */
public $y = null; public $y = null;
/** @var null|A|B */ /** @var A|B|null */
public $z = null; public $z = null;
} }
}', }',
'<?php '<?php
namespace Bar\Baz { namespace Bar\Baz {
class A { class A {
/** @var null|B */ /** @var B|null */
public $x = null; public $x = null;
/** @var null|self */ /** @var null|self */
public $y = null; public $y = null;
/** @var null|self|B|\Foo\C */ /** @var B|\Foo\C|null|self */
public $z = null; public $z = null;
} }
} }
namespace Bar\Baz { namespace Bar\Baz {
class B { class B {
/** @var null|A */ /** @var A|null */
public $x = null; public $x = null;
/** @var null|self */ /** @var null|self */
public $y = null; public $y = null;
/** @var null|A|self|\Foo\C */ /** @var A|\Foo\C|null|self */
public $z = null; public $z = null;
} }
} }
@ -487,11 +487,11 @@ class ClassMoveTest extends \Psalm\Tests\TestCase
use Bar\Baz\B; use Bar\Baz\B;
class C { class C {
/** @var null|A */ /** @var A|null */
public $x = null; public $x = null;
/** @var null|B */ /** @var B|null */
public $y = null; public $y = null;
/** @var null|A|B */ /** @var A|B|null */
public $z = null; public $z = null;
} }
}', }',
@ -534,12 +534,12 @@ class ClassMoveTest extends \Psalm\Tests\TestCase
/** /**
* @param Kappa $b * @param Kappa $b
* @param Kappa::FOO|Kappa::FAR $c * @param Kappa::FAR|Kappa::FOO $c
*/ */
function doSomething(Kappa $b, int $c) : void {} function doSomething(Kappa $b, int $c) : void {}
class A { class A {
/** @var null|Kappa */ /** @var Kappa|null */
public $x = null; public $x = null;
} }
} }

View File

@ -346,7 +346,7 @@ class MethodMoveTest extends \Psalm\Tests\TestCase
/** /**
* @param A $a1 * @param A $a1
* Some description * Some description
* @param null|A * @param A|null
* $a2 * $a2
* @param array<int, A> $a3 * @param array<int, A> $a3
* @return A * @return A
@ -424,7 +424,7 @@ class MethodMoveTest extends \Psalm\Tests\TestCase
/** /**
* @param A $a1 * @param A $a1
* Some description * Some description
* @param null|A * @param A|null
* $a2 * $a2
* @param array<int, A> $a3 * @param array<int, A> $a3
* @return A * @return A
@ -505,7 +505,7 @@ class MethodMoveTest extends \Psalm\Tests\TestCase
/** /**
* @param A $a1 * @param A $a1
* Some description * Some description
* @param null|A * @param A|null
* $a2 * $a2
* @param array<int, A> $a3 * @param array<int, A> $a3
* @return A * @return A

View File

@ -120,22 +120,22 @@ class NamespaceMoveTest extends \Psalm\Tests\TestCase
'<?php '<?php
namespace Bar\Baz { namespace Bar\Baz {
class A { class A {
/** @var null|B */ /** @var B|null */
public $x = null; public $x = null;
/** @var null|self */ /** @var null|self */
public $y = null; public $y = null;
/** @var null|self|B|\Foo\C */ /** @var B|\Foo\C|null|self */
public $z = null; public $z = null;
} }
} }
namespace Bar\Baz { namespace Bar\Baz {
class B { class B {
/** @var null|A */ /** @var A|null */
public $x = null; public $x = null;
/** @var null|self */ /** @var null|self */
public $y = null; public $y = null;
/** @var null|A|self|\Foo\C */ /** @var A|\Foo\C|null|self */
public $z = null; public $z = null;
} }
} }
@ -145,11 +145,11 @@ class NamespaceMoveTest extends \Psalm\Tests\TestCase
use Bar\Baz\B; use Bar\Baz\B;
class C { class C {
/** @var null|A */ /** @var A|null */
public $x = null; public $x = null;
/** @var null|B */ /** @var B|null */
public $y = null; public $y = null;
/** @var null|A|B */ /** @var A|B|null */
public $z = null; public $z = null;
} }
}', }',

View File

@ -202,7 +202,7 @@ class PropertyMoveTest extends \Psalm\Tests\TestCase
class B { class B {
/** @var null|int */ /** @var int|null */
public static $fooBar; public static $fooBar;
} }

View File

@ -94,7 +94,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}', }',
'<?php '<?php
/** /**
* @return string|null * @return null|string
*/ */
function foo() { function foo() {
return rand(0, 1) ? "hello" : null; return rand(0, 1) ? "hello" : null;
@ -110,7 +110,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}', }',
'<?php '<?php
/** /**
* @return string|null * @return null|string
*/ */
function foo() { function foo() {
return rand(0, 1) ? "hello" : null; return rand(0, 1) ? "hello" : null;
@ -265,7 +265,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}', }',
'<?php '<?php
/** /**
* @return ((int[]|int)[]|int)[] * @return ((int|int[])[]|int)[]
* *
* @psalm-return array{a: int, b: int, c: array{a: int, b: int, c: array{a: int, b: int, c: int}}} * @psalm-return array{a: int, b: int, c: array{a: int, b: int, c: array{a: int, b: int, c: int}}}
*/ */
@ -544,7 +544,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}', }',
'<?php '<?php
/** /**
* @return string|null * @return null|string
*/ */
function foo() { function foo() {
if (rand(0, 1)) { if (rand(0, 1)) {
@ -555,7 +555,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
} }
/** /**
* @return string|null * @return null|string
*/ */
function bar() { function bar() {
if (rand(0, 1)) { if (rand(0, 1)) {
@ -793,7 +793,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}', }',
'<?php '<?php
/** /**
* @return string|false * @return false|string
*/ */
function foo() { function foo() {
return rand(0, 1) ? "hello" : false; return rand(0, 1) ? "hello" : false;
@ -809,7 +809,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}', }',
'<?php '<?php
/** /**
* @return string|null * @return null|string
*/ */
function foo() { function foo() {
return rand(0, 1) ? "hello" : null; return rand(0, 1) ? "hello" : null;

View File

@ -125,7 +125,7 @@ class FunctionCallTest extends TestCase
'assertions' => [ 'assertions' => [
'$a' => 'int', '$a' => 'int',
'$b' => 'float', '$b' => 'float',
'$c' => 'numeric|null', '$c' => 'null|numeric',
], ],
'error_levels' => ['MixedAssignment', 'MixedArgument'], 'error_levels' => ['MixedAssignment', 'MixedArgument'],
], ],
@ -244,21 +244,21 @@ class FunctionCallTest extends TestCase
'<?php '<?php
$d = array_reverse(["a", "b", 1, "d" => 4]);', $d = array_reverse(["a", "b", 1, "d" => 4]);',
'assertions' => [ 'assertions' => [
'$d' => 'non-empty-array<string|int, string|int>', '$d' => 'non-empty-array<int|string, int|string>',
], ],
], ],
'arrayReverseDontPreserveKeyExplicitArg' => [ 'arrayReverseDontPreserveKeyExplicitArg' => [
'<?php '<?php
$d = array_reverse(["a", "b", 1, "d" => 4], false);', $d = array_reverse(["a", "b", 1, "d" => 4], false);',
'assertions' => [ 'assertions' => [
'$d' => 'non-empty-array<string|int, string|int>', '$d' => 'non-empty-array<int|string, int|string>',
], ],
], ],
'arrayReversePreserveKey' => [ 'arrayReversePreserveKey' => [
'<?php '<?php
$d = array_reverse(["a", "b", 1], true);', $d = array_reverse(["a", "b", 1], true);',
'assertions' => [ 'assertions' => [
'$d' => 'non-empty-array<int, string|int>', '$d' => 'non-empty-array<int, int|string>',
], ],
], ],
'arrayDiff' => [ 'arrayDiff' => [
@ -498,7 +498,7 @@ class FunctionCallTest extends TestCase
$a[] = "hello"; $a[] = "hello";
$b = array_pop($a);', $b = array_pop($a);',
'assertions' => [ 'assertions' => [
'$b' => 'string|mixed', '$b' => 'mixed|string',
], ],
'error_levels' => [ 'error_levels' => [
'MixedAssignment', 'MixedAssignment',
@ -603,7 +603,7 @@ class FunctionCallTest extends TestCase
foo($a3);', foo($a3);',
'assertions' => [ 'assertions' => [
'$a3' => 'array{hi: int, bye: int}', '$a3' => 'array{bye: int, hi: int}',
], ],
], ],
'arrayRand' => [ 'arrayRand' => [
@ -688,7 +688,7 @@ class FunctionCallTest extends TestCase
$b = getenv("some_key");', $b = getenv("some_key");',
'assertions' => [ 'assertions' => [
'$a' => 'array<array-key, string>', '$a' => 'array<array-key, string>',
'$b' => 'string|false', '$b' => 'false|string',
], ],
], ],
'arrayPopNotNullable' => [ 'arrayPopNotNullable' => [
@ -758,7 +758,7 @@ class FunctionCallTest extends TestCase
} }
/** /**
* @param string[] $arr * @param string[] $arr
* @return string|false * @return false|string
*/ */
function bat(array $arr) { function bat(array $arr) {
return current($arr); return current($arr);
@ -777,7 +777,7 @@ class FunctionCallTest extends TestCase
return $a; return $a;
} }
/** /**
* @return string|false * @return false|string
*/ */
function bat(string $s) { function bat(string $s) {
return file_get_contents($s); return file_get_contents($s);
@ -959,7 +959,7 @@ class FunctionCallTest extends TestCase
$arr = ["one", "two", "three"]; $arr = ["one", "two", "three"];
$n = next($arr);', $n = next($arr);',
'assertions' => [ 'assertions' => [
'$n' => 'string|false', '$n' => 'false|string',
], ],
], ],
'iteratorToArray' => [ 'iteratorToArray' => [
@ -1038,7 +1038,7 @@ class FunctionCallTest extends TestCase
'strtrWithPossiblyFalseFirstArg' => [ 'strtrWithPossiblyFalseFirstArg' => [
'<?php '<?php
/** /**
* @param string|false $str * @param false|string $str
* @param array<string, string> $replace_pairs * @param array<string, string> $replace_pairs
* @return string * @return string
*/ */
@ -1270,7 +1270,7 @@ class FunctionCallTest extends TestCase
$d = [1, 2, 3]; $d = [1, 2, 3];
array_splice($d, -1, 1);', array_splice($d, -1, 1);',
'assertions' => [ 'assertions' => [
'$a' => 'non-empty-array<int, string|int>', '$a' => 'non-empty-array<int, int|string>',
'$b' => 'array{0: string, 1: string, 2: string}', '$b' => 'array{0: string, 1: string, 2: string}',
'$c' => 'array{0: int, 1: int, 2: int}', '$c' => 'array{0: int, 1: int, 2: int}',
], ],
@ -1298,7 +1298,7 @@ class FunctionCallTest extends TestCase
'<?php '<?php
$a = @file_get_contents("foo");', $a = @file_get_contents("foo");',
'assertions' => [ 'assertions' => [
'$a' => 'string|false', '$a' => 'false|string',
], ],
], ],
'arraySlicePreserveKeys' => [ 'arraySlicePreserveKeys' => [
@ -1512,7 +1512,7 @@ class FunctionCallTest extends TestCase
'$b' => 'int', '$b' => 'int',
'$c' => 'float', '$c' => 'float',
'$d' => 'float', '$d' => 'float',
'$e' => 'int|float', '$e' => 'float|int',
], ],
], ],
'hashInit70' => [ 'hashInit70' => [
@ -1630,8 +1630,8 @@ class FunctionCallTest extends TestCase
$b = mktime($_GET["foo"]); $b = mktime($_GET["foo"]);
$c = mktime(1, 2, 3);', $c = mktime(1, 2, 3);',
'assertions' => [ 'assertions' => [
'$a' => 'int|false', '$a' => 'false|int',
'$b' => 'int|false', '$b' => 'false|int',
'$c' => 'int', '$c' => 'int',
], ],
], ],
@ -1684,9 +1684,9 @@ class FunctionCallTest extends TestCase
'<?php '<?php
sscanf("10:05:03", "%d:%d:%d", $hours, $minutes, $seconds);', sscanf("10:05:03", "%d:%d:%d", $hours, $minutes, $seconds);',
'assertions' => [ 'assertions' => [
'$hours' => 'string|int|float', '$hours' => 'float|int|string',
'$minutes' => 'string|int|float', '$minutes' => 'float|int|string',
'$seconds' => 'string|int|float', '$seconds' => 'float|int|string',
], ],
], ],
'inferArrayMapReturnType' => [ 'inferArrayMapReturnType' => [

View File

@ -60,7 +60,7 @@ class IssetTest extends TestCase
$b = rand(0, 10) > 5 ? "hello" : null; $b = rand(0, 10) > 5 ? "hello" : null;
$a = $b ?? null;', $a = $b ?? null;',
'assertions' => [ 'assertions' => [
'$a' => 'string|null', '$a' => 'null|string',
], ],
], ],
'issetKeyedOffset' => [ 'issetKeyedOffset' => [
@ -75,7 +75,7 @@ class IssetTest extends TestCase
$foo["a"] = "hello"; $foo["a"] = "hello";
}', }',
'assertions' => [ 'assertions' => [
'$foo[\'a\']' => 'string|mixed', '$foo[\'a\']' => 'mixed|string',
], ],
'error_levels' => [], 'error_levels' => [],
], ],
@ -102,7 +102,7 @@ class IssetTest extends TestCase
$foo["a"] = $foo["a"] ?? "hello";', $foo["a"] = $foo["a"] ?? "hello";',
'assertions' => [ 'assertions' => [
'$foo[\'a\']' => 'string|mixed', '$foo[\'a\']' => 'mixed|string',
], ],
'error_levels' => ['MixedAssignment'], 'error_levels' => ['MixedAssignment'],
], ],

View File

@ -136,7 +136,7 @@ echo $a;';
'line_from' => 2, 'line_from' => 2,
'line_to' => 2, 'line_to' => 2,
'type' => 'MixedInferredReturnType', 'type' => 'MixedInferredReturnType',
'message' => 'Could not verify return type \'string|null\' for psalmCanVerify', 'message' => 'Could not verify return type \'null|string\' for psalmCanVerify',
'file_name' => 'somefile.php', 'file_name' => 'somefile.php',
'file_path' => 'somefile.php', 'file_path' => 'somefile.php',
'snippet' => 'function psalmCanVerify(int $your_code): ?string {', 'snippet' => 'function psalmCanVerify(int $your_code): ?string {',

View File

@ -361,7 +361,7 @@ class ForeachTest extends \Psalm\Tests\TestCase
} }
}', }',
'assignments' => [ 'assignments' => [
'$tag' => 'string|null', '$tag' => 'null|string',
], ],
], ],
'bleedVarIntoOuterContextWithBreakInIf' => [ 'bleedVarIntoOuterContextWithBreakInIf' => [
@ -375,7 +375,7 @@ class ForeachTest extends \Psalm\Tests\TestCase
} }
}', }',
'assignments' => [ 'assignments' => [
'$tag' => 'string|null', '$tag' => 'null|string',
], ],
], ],
'bleedVarIntoOuterContextWithBreakInElseAndIntSet' => [ 'bleedVarIntoOuterContextWithBreakInElseAndIntSet' => [
@ -389,7 +389,7 @@ class ForeachTest extends \Psalm\Tests\TestCase
} }
}', }',
'assignments' => [ 'assignments' => [
'$tag' => 'string|int|null', '$tag' => 'int|null|string',
], ],
], ],
'bleedVarIntoOuterContextWithRedefineAndBreak' => [ 'bleedVarIntoOuterContextWithRedefineAndBreak' => [

View File

@ -123,7 +123,7 @@ class WhileTest extends \Psalm\Tests\TestCase
$a = $a->parent; $a = $a->parent;
}', }',
'assertions' => [ 'assertions' => [
'$a' => 'null|A', '$a' => 'A|null',
], ],
], ],
'loopWithNoParadox' => [ 'loopWithNoParadox' => [

View File

@ -197,7 +197,7 @@ class MagicMethodAnnotationTest extends TestCase
'$a' => 'string', '$a' => 'string',
'$b' => 'mixed', '$b' => 'mixed',
'$c' => 'bool', '$c' => 'bool',
'$d' => 'array<array-key, string|int>', '$d' => 'array<array-key, int|string>',
'$e' => 'callable():string', '$e' => 'callable():string',
], ],
], ],

View File

@ -101,7 +101,7 @@ class MethodCallTest extends TestCase
$b = (new DateTimeImmutable())->modify("+3 hours");', $b = (new DateTimeImmutable())->modify("+3 hours");',
'assertions' => [ 'assertions' => [
'$yesterday' => 'false|MyDate', '$yesterday' => 'MyDate|false',
'$b' => 'DateTimeImmutable', '$b' => 'DateTimeImmutable',
], ],
], ],
@ -182,7 +182,7 @@ class MethodCallTest extends TestCase
$a = $xml->asXML(); $a = $xml->asXML();
$b = $xml->asXML("foo.xml");', $b = $xml->asXML("foo.xml");',
'assertions' => [ 'assertions' => [
'$a' => 'string|false', '$a' => 'false|string',
'$b' => 'bool', '$b' => 'bool',
], ],
], ],

View File

@ -147,7 +147,7 @@ class MethodMutationTest extends TestCase
'somefile.php' 'somefile.php'
); );
$this->assertSame('null|User', (string)$method_context->vars_in_scope['$this->user']); $this->assertSame('User|null', (string)$method_context->vars_in_scope['$this->user']);
} }
/** /**

View File

@ -641,7 +641,7 @@ class MethodSignatureTest extends TestCase
return $s; return $s;
} }
}', }',
'error_message' => 'Argument 1 of B::foo has wrong type \'string\', expecting \'string|null\' as', 'error_message' => 'Argument 1 of B::foo has wrong type \'string\', expecting \'null|string\' as',
], ],
'mismatchingCovariantReturn' => [ 'mismatchingCovariantReturn' => [
'<?php '<?php

View File

@ -35,7 +35,7 @@ class Php55Test extends TestCase
$a = $number; $a = $number;
}', }',
'assertions' => [ 'assertions' => [
'$a' => 'null|int', '$a' => 'int|null',
], ],
], ],
'finally' => [ 'finally' => [

View File

@ -55,7 +55,7 @@ class Php70Test extends TestCase
$arr = ["hello", "goodbye"]; $arr = ["hello", "goodbye"];
$a = $arr[rand(0, 10)] ?? null;', $a = $arr[rand(0, 10)] ?? null;',
'assertions' => [ 'assertions' => [
'$a' => 'string|null', '$a' => 'null|string',
], ],
], ],
'nullCoalesceWithNullableOnLeft' => [ 'nullCoalesceWithNullableOnLeft' => [

View File

@ -21,7 +21,7 @@ class Php71Test extends TestCase
$a = a();', $a = a();',
'assertions' => [ 'assertions' => [
'$a' => 'string|null', '$a' => 'null|string',
], ],
], ],
'nullableReturnTypeInDocblock' => [ 'nullableReturnTypeInDocblock' => [

View File

@ -243,7 +243,7 @@ class PropertyTypeTest extends TestCase
$b = $a->foo; $b = $a->foo;
}', }',
'assertions' => [ 'assertions' => [
'$b' => 'null|int|string', '$b' => 'int|null|string',
], ],
], ],
'sharedPropertyInElseIf' => [ 'sharedPropertyInElseIf' => [
@ -270,7 +270,7 @@ class PropertyTypeTest extends TestCase
$b = $a->foo; $b = $a->foo;
}', }',
'assertions' => [ 'assertions' => [
'$b' => 'null|int|string', '$b' => 'int|null|string',
], ],
], ],
'nullablePropertyCheck' => [ 'nullablePropertyCheck' => [
@ -750,7 +750,7 @@ class PropertyTypeTest extends TestCase
$node = new Node(); $node = new Node();
$next = $node->next;', $next = $node->next;',
'assertions' => [ 'assertions' => [
'$next' => 'null|Node', '$next' => 'Node|null',
], ],
], ],
'perPropertySuppress' => [ 'perPropertySuppress' => [

View File

@ -125,7 +125,7 @@ echo $a;';
'line_from' => 2, 'line_from' => 2,
'line_to' => 2, 'line_to' => 2,
'type' => 'MixedInferredReturnType', 'type' => 'MixedInferredReturnType',
'message' => 'Could not verify return type \'string|null\' for psalmCanVerify', 'message' => 'Could not verify return type \'null|string\' for psalmCanVerify',
'file_name' => 'somefile.php', 'file_name' => 'somefile.php',
'file_path' => 'somefile.php', 'file_path' => 'somefile.php',
'snippet' => 'function psalmCanVerify(int $your_code): ?string {', 'snippet' => 'function psalmCanVerify(int $your_code): ?string {',
@ -210,7 +210,7 @@ echo $a;';
'engineId' => 'Psalm', 'engineId' => 'Psalm',
'ruleId' => 'MixedInferredReturnType', 'ruleId' => 'MixedInferredReturnType',
'primaryLocation' => [ 'primaryLocation' => [
'message' => 'Could not verify return type \'string|null\' for psalmCanVerify', 'message' => 'Could not verify return type \'null|string\' for psalmCanVerify',
'filePath' => 'somefile.php', 'filePath' => 'somefile.php',
'textRange' => [ 'textRange' => [
'startLine' => 2, 'startLine' => 2,
@ -277,7 +277,7 @@ echo $a;';
$this->assertSame( $this->assertSame(
'somefile.php:3:10:error - Cannot find referenced variable $as_you 'somefile.php:3:10:error - Cannot find referenced variable $as_you
somefile.php:2:42:error - Could not verify return type \'string|null\' for psalmCanVerify somefile.php:2:42:error - Could not verify return type \'null|string\' for psalmCanVerify
somefile.php:7:6:error - Const CHANGE_ME is not defined somefile.php:7:6:error - Const CHANGE_ME is not defined
somefile.php:15:6:warning - Possibly undefined global variable $a, first seen on line 10 somefile.php:15:6:warning - Possibly undefined global variable $a, first seen on line 10
', ',
@ -296,7 +296,7 @@ somefile.php:15:6:warning - Possibly undefined global variable $a, first seen on
$this->assertSame( $this->assertSame(
'somefile.php:3: [E0001] UndefinedVariable: Cannot find referenced variable $as_you (column 10) 'somefile.php:3: [E0001] UndefinedVariable: Cannot find referenced variable $as_you (column 10)
somefile.php:2: [E0001] MixedInferredReturnType: Could not verify return type \'string|null\' for psalmCanVerify (column 42) somefile.php:2: [E0001] MixedInferredReturnType: Could not verify return type \'null|string\' for psalmCanVerify (column 42)
somefile.php:7: [E0001] UndefinedConstant: Const CHANGE_ME is not defined (column 6) somefile.php:7: [E0001] UndefinedConstant: Const CHANGE_ME is not defined (column 6)
somefile.php:15: [W0001] PossiblyUndefinedGlobalVariable: Possibly undefined global variable $a, first seen on line 10 (column 6) somefile.php:15: [W0001] PossiblyUndefinedGlobalVariable: Possibly undefined global variable $a, first seen on line 10 (column 6)
', ',
@ -318,7 +318,7 @@ somefile.php:15: [W0001] PossiblyUndefinedGlobalVariable: Possibly undefined glo
'ERROR: UndefinedVariable - somefile.php:3:10 - Cannot find referenced variable $as_you 'ERROR: UndefinedVariable - somefile.php:3:10 - Cannot find referenced variable $as_you
return $as_you . "type"; return $as_you . "type";
ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type \'string|null\' for psalmCanVerify ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type \'null|string\' for psalmCanVerify
function psalmCanVerify(int $your_code): ?string { function psalmCanVerify(int $your_code): ?string {
ERROR: UndefinedConstant - somefile.php:7:6 - Const CHANGE_ME is not defined ERROR: UndefinedConstant - somefile.php:7:6 - Const CHANGE_ME is not defined
@ -347,7 +347,7 @@ echo $a
'ERROR: UndefinedVariable - somefile.php:3:10 - Cannot find referenced variable $as_you 'ERROR: UndefinedVariable - somefile.php:3:10 - Cannot find referenced variable $as_you
return $as_you . "type"; return $as_you . "type";
ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type \'string|null\' for psalmCanVerify ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type \'null|string\' for psalmCanVerify
function psalmCanVerify(int $your_code): ?string { function psalmCanVerify(int $your_code): ?string {
ERROR: UndefinedConstant - somefile.php:7:6 - Const CHANGE_ME is not defined ERROR: UndefinedConstant - somefile.php:7:6 - Const CHANGE_ME is not defined
@ -373,7 +373,7 @@ echo CHANGE_ME;
'ERROR: UndefinedVariable - somefile.php:3:10 - Cannot find referenced variable $as_you 'ERROR: UndefinedVariable - somefile.php:3:10 - Cannot find referenced variable $as_you
ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type \'string|null\' for psalmCanVerify ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type \'null|string\' for psalmCanVerify
ERROR: UndefinedConstant - somefile.php:7:6 - Const CHANGE_ME is not defined ERROR: UndefinedConstant - somefile.php:7:6 - Const CHANGE_ME is not defined
@ -405,7 +405,7 @@ INFO: PossiblyUndefinedGlobalVariable - somefile.php:15:6 - Possibly undefined g
'| SEVERITY | LINE | ISSUE | DESCRIPTION |' . "\n" . '| SEVERITY | LINE | ISSUE | DESCRIPTION |' . "\n" .
'+----------+------+---------------------------------+---------------------------------------------------------------+' . "\n" . '+----------+------+---------------------------------+---------------------------------------------------------------+' . "\n" .
'| ERROR | 3 | UndefinedVariable | Cannot find referenced variable $as_you |' . "\n" . '| ERROR | 3 | UndefinedVariable | Cannot find referenced variable $as_you |' . "\n" .
'| ERROR | 2 | MixedInferredReturnType | Could not verify return type \'string|null\' for psalmCanVerify |' . "\n" . '| ERROR | 2 | MixedInferredReturnType | Could not verify return type \'null|string\' for psalmCanVerify |' . "\n" .
'| ERROR | 7 | UndefinedConstant | Const CHANGE_ME is not defined |' . "\n" . '| ERROR | 7 | UndefinedConstant | Const CHANGE_ME is not defined |' . "\n" .
'| INFO | 15 | PossiblyUndefinedGlobalVariable | Possibly undefined global variable $a, first seen on line 10 |' . "\n" . '| INFO | 15 | PossiblyUndefinedGlobalVariable | Possibly undefined global variable $a, first seen on line 10 |' . "\n" .
'+----------+------+---------------------------------+---------------------------------------------------------------+' . "\n", '+----------+------+---------------------------------+---------------------------------------------------------------+' . "\n",
@ -428,7 +428,7 @@ INFO: PossiblyUndefinedGlobalVariable - somefile.php:15:6 - Possibly undefined g
<error line="3" column="10" severity="error" message="UndefinedVariable: Cannot find referenced variable $as_you"/> <error line="3" column="10" severity="error" message="UndefinedVariable: Cannot find referenced variable $as_you"/>
</file> </file>
<file name="somefile.php"> <file name="somefile.php">
<error line="2" column="42" severity="error" message="MixedInferredReturnType: Could not verify return type \'string|null\' for psalmCanVerify"/> <error line="2" column="42" severity="error" message="MixedInferredReturnType: Could not verify return type \'null|string\' for psalmCanVerify"/>
</file> </file>
<file name="somefile.php"> <file name="somefile.php">
<error line="7" column="6" severity="error" message="UndefinedConstant: Const CHANGE_ME is not defined"/> <error line="7" column="6" severity="error" message="UndefinedConstant: Const CHANGE_ME is not defined"/>

View File

@ -291,7 +291,7 @@ class ReturnTypeTest extends TestCase
$blah = (new B())->blah();', $blah = (new B())->blah();',
'assertions' => [ 'assertions' => [
'$blah' => 'string|null', '$blah' => 'null|string',
], ],
], ],
'overrideReturnTypeInGrandparent' => [ 'overrideReturnTypeInGrandparent' => [
@ -313,7 +313,7 @@ class ReturnTypeTest extends TestCase
$blah = (new C())->blah();', $blah = (new C())->blah();',
'assertions' => [ 'assertions' => [
'$blah' => 'string|null', '$blah' => 'null|string',
], ],
], ],
'backwardsReturnType' => [ 'backwardsReturnType' => [

View File

@ -167,7 +167,7 @@ class ScopeTest extends TestCase
throw new \Exception("bad"); throw new \Exception("bad");
}', }',
'assertions' => [ 'assertions' => [
'$a' => 'string|null', '$a' => 'null|string',
], ],
], ],
'repeatAssertionWithOther' => [ 'repeatAssertionWithOther' => [
@ -180,7 +180,7 @@ class ScopeTest extends TestCase
} }
}', }',
'assertions' => [ 'assertions' => [
'$a' => 'string|null', '$a' => 'null|string',
], ],
'error_levels' => ['PossiblyFalseArgument'], 'error_levels' => ['PossiblyFalseArgument'],
], ],

View File

@ -536,7 +536,7 @@ class ClassTemplateExtendsTest extends TestCase
$b = $a->findOne();', $b = $a->findOne();',
[ [
'$a' => 'AnotherRepo', '$a' => 'AnotherRepo',
'$b' => 'null|SpecificEntity', '$b' => 'SpecificEntity|null',
], ],
], ],
'templateExtendsTwiceAndBound' => [ 'templateExtendsTwiceAndBound' => [
@ -562,7 +562,7 @@ class ClassTemplateExtendsTest extends TestCase
$b = $a->findOne();', $b = $a->findOne();',
[ [
'$a' => 'SpecificRepo', '$a' => 'SpecificRepo',
'$b' => 'null|SpecificEntity', '$b' => 'SpecificEntity|null',
], ],
], ],
'multipleArgConstraints' => [ 'multipleArgConstraints' => [
@ -1071,7 +1071,7 @@ class ClassTemplateExtendsTest extends TestCase
$a = (new Service)->first();', $a = (new Service)->first();',
[ [
'$a' => 'null|int', '$a' => 'int|null',
], ],
], ],
'splObjectStorage' => [ 'splObjectStorage' => [

View File

@ -1258,7 +1258,7 @@ class ClassTemplateTest extends TestCase
$a_or_b = $random_collection->get();', $a_or_b = $random_collection->get();',
[ [
'$random_collection' => 'C<A>|C<B>', '$random_collection' => 'C<A>|C<B>',
'$a_or_b' => 'B|A', '$a_or_b' => 'A|B',
], ],
], ],
'inferClosureParamTypeFromContext' => [ 'inferClosureParamTypeFromContext' => [
@ -2110,7 +2110,7 @@ class ClassTemplateTest extends TestCase
$mario = new CharacterRow(["id" => 5, "name" => "Mario", "height" => 3.5]); $mario = new CharacterRow(["id" => 5, "name" => "Mario", "height" => 3.5]);
$mario->ame = "Luigi";', $mario->ame = "Luigi";',
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects string(id)|string(name)|string(height), string(ame) provided', 'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects string(height)|string(id)|string(name), string(ame) provided',
], ],
'specialiseTypeBeforeReturning' => [ 'specialiseTypeBeforeReturning' => [
'<?php '<?php

View File

@ -52,7 +52,7 @@ class FunctionClassStringTemplateTest extends TestCase
$a = (new I)->loader(FooChild::class);', $a = (new I)->loader(FooChild::class);',
'assertions' => [ 'assertions' => [
'$a' => 'null|FooChild', '$a' => 'FooChild|null',
], ],
], ],
'upcastIterableToTraversable' => [ 'upcastIterableToTraversable' => [

View File

@ -190,7 +190,7 @@ class FunctionTemplateTest extends TestCase
$b = ["a" => 5, "c" => 6]; $b = ["a" => 5, "c" => 6];
$a = my_array_pop($b);', $a = my_array_pop($b);',
'assertions' => [ 'assertions' => [
'$a' => 'null|int', '$a' => 'int|null',
'$b' => 'array<string, int>', '$b' => 'array<string, int>',
], ],
], ],

View File

@ -129,7 +129,7 @@ class TypeCombinationTest extends TestCase
], ],
], ],
'intOrTrueOrFalseToBool' => [ 'intOrTrueOrFalseToBool' => [
'int|bool', 'bool|int',
[ [
'int', 'int',
'false', 'false',
@ -137,7 +137,7 @@ class TypeCombinationTest extends TestCase
], ],
], ],
'intOrBoolOrTrueToBool' => [ 'intOrBoolOrTrueToBool' => [
'int|bool', 'bool|int',
[ [
'int', 'int',
'bool', 'bool',
@ -145,7 +145,7 @@ class TypeCombinationTest extends TestCase
], ],
], ],
'intOrTrueOrBoolToBool' => [ 'intOrTrueOrBoolToBool' => [
'int|bool', 'bool|int',
[ [
'int', 'int',
'true', 'true',
@ -188,7 +188,7 @@ class TypeCombinationTest extends TestCase
], ],
], ],
'arrayMixedOrStringKeys' => [ 'arrayMixedOrStringKeys' => [
'array<int|string|mixed, string>', 'array<int|mixed|string, string>',
[ [
'array<int|string,string>', 'array<int|string,string>',
'array<mixed,string>', 'array<mixed,string>',
@ -202,7 +202,7 @@ class TypeCombinationTest extends TestCase
], ],
], ],
'arrayBigCombination' => [ 'arrayBigCombination' => [
'array<array-key, int|float|string>', 'array<array-key, float|int|string>',
[ [
'array<int|float>', 'array<int|float>',
'array<string>', 'array<string>',
@ -244,49 +244,49 @@ class TypeCombinationTest extends TestCase
], ],
], ],
'arrayTraversableToIterableWithParams' => [ 'arrayTraversableToIterableWithParams' => [
'iterable<int, string|bool>', 'iterable<int, bool|string>',
[ [
'array<int, string>', 'array<int, string>',
'Traversable<int, bool>', 'Traversable<int, bool>',
], ],
], ],
'arrayIterableToIterableWithParams' => [ 'arrayIterableToIterableWithParams' => [
'iterable<int, string|bool>', 'iterable<int, bool|string>',
[ [
'array<int, string>', 'array<int, string>',
'iterable<int, bool>', 'iterable<int, bool>',
], ],
], ],
'iterableArrayToIterableWithParams' => [ 'iterableArrayToIterableWithParams' => [
'iterable<int, string|bool>', 'iterable<int, bool|string>',
[ [
'iterable<int, string>', 'iterable<int, string>',
'array<int, bool>', 'array<int, bool>',
], ],
], ],
'traversableIterableToIterableWithParams' => [ 'traversableIterableToIterableWithParams' => [
'iterable<int, string|bool>', 'iterable<int, bool|string>',
[ [
'Traversable<int, string>', 'Traversable<int, string>',
'iterable<int, bool>', 'iterable<int, bool>',
], ],
], ],
'iterableTraversableToIterableWithParams' => [ 'iterableTraversableToIterableWithParams' => [
'iterable<int, string|bool>', 'iterable<int, bool|string>',
[ [
'iterable<int, string>', 'iterable<int, string>',
'Traversable<int, bool>', 'Traversable<int, bool>',
], ],
], ],
'arrayObjectAndParamsWithEmptyArray' => [ 'arrayObjectAndParamsWithEmptyArray' => [
'array<empty, empty>|ArrayObject<int, string>', 'ArrayObject<int, string>|array<empty, empty>',
[ [
'ArrayObject<int, string>', 'ArrayObject<int, string>',
'array<empty, empty>', 'array<empty, empty>',
], ],
], ],
'emptyArrayWithArrayObjectAndParams' => [ 'emptyArrayWithArrayObjectAndParams' => [
'array<empty, empty>|ArrayObject<int, string>', 'ArrayObject<int, string>|array<empty, empty>',
[ [
'array<empty, empty>', 'array<empty, empty>',
'ArrayObject<int, string>', 'ArrayObject<int, string>',
@ -319,7 +319,7 @@ class TypeCombinationTest extends TestCase
], ],
], ],
'aAndAOfB' => [ 'aAndAOfB' => [
'A<B>|A', 'A|A<B>',
[ [
'A', 'A',
'A<B>', 'A<B>',
@ -340,28 +340,28 @@ class TypeCombinationTest extends TestCase
], ],
], ],
'combineObjectTypeWithIntKeyedArray' => [ 'combineObjectTypeWithIntKeyedArray' => [
'array<int|string, string|int>', 'array<int|string, int|string>',
[ [
'array{a: int}', 'array{a: int}',
'array<int, string>', 'array<int, string>',
], ],
], ],
'combineNestedObjectTypeWithObjectLikeIntKeyedArray' => [ 'combineNestedObjectTypeWithObjectLikeIntKeyedArray' => [
'array{a: array<int|string, string|int>}', 'array{a: array<int|string, int|string>}',
[ [
'array{a: array{a: int}}', 'array{a: array{a: int}}',
'array{a: array<int, string>}', 'array{a: array<int, string>}',
], ],
], ],
'combineIntKeyedObjectTypeWithNestedIntKeyedArray' => [ 'combineIntKeyedObjectTypeWithNestedIntKeyedArray' => [
'array<int, array<int|string, string|int>>', 'array<int, array<int|string, int|string>>',
[ [
'array<int, array{a:int}>', 'array<int, array{a:int}>',
'array<int, array<int, string>>', 'array<int, array<int, string>>',
], ],
], ],
'combineNestedObjectTypeWithNestedIntKeyedArray' => [ 'combineNestedObjectTypeWithNestedIntKeyedArray' => [
'array<int|string, array<int|string, string|int>>', 'array<int|string, array<int|string, int|string>>',
[ [
'array{a: array{a: int}}', 'array{a: array{a: int}}',
'array<int, array<int, string>>', 'array<int, array<int, string>>',

View File

@ -41,7 +41,7 @@ class TypeParseTest extends TestCase
*/ */
public function testThisToStaticUnion() public function testThisToStaticUnion()
{ {
$this->assertSame('static|A', (string) Type::parseString('$this|A')); $this->assertSame('A|static', (string) Type::parseString('$this|A'));
} }
/** /**
@ -81,7 +81,7 @@ class TypeParseTest extends TestCase
*/ */
public function testNullableUnion() public function testNullableUnion()
{ {
$this->assertSame('string|int|null', (string) Type::parseString('?(string|int)')); $this->assertSame('int|null|string', (string) Type::parseString('?(string|int)'));
} }
/** /**
@ -97,7 +97,7 @@ class TypeParseTest extends TestCase
*/ */
public function testNullableOrNullable() public function testNullableOrNullable()
{ {
$this->assertSame('string|int|null', (string) Type::parseString('?string|?int')); $this->assertSame('int|null|string', (string) Type::parseString('?string|?int'));
} }
/** /**
@ -170,7 +170,7 @@ class TypeParseTest extends TestCase
*/ */
public function testIntersectionOrNull() public function testIntersectionOrNull()
{ {
$this->assertSame('null|I1&I2', (string) Type::parseString('I1&I2|null')); $this->assertSame('I1&I2|null', (string) Type::parseString('I1&I2|null'));
} }
/** /**
@ -178,7 +178,7 @@ class TypeParseTest extends TestCase
*/ */
public function testNullOrIntersection() public function testNullOrIntersection()
{ {
$this->assertSame('null|I1&I2', (string) Type::parseString('null|I1&I2')); $this->assertSame('I1&I2|null', (string) Type::parseString('null|I1&I2'));
} }
/** /**
@ -195,7 +195,7 @@ class TypeParseTest extends TestCase
public function testTraversableAndIteratorOrNull() public function testTraversableAndIteratorOrNull()
{ {
$this->assertSame( $this->assertSame(
'null|Traversable&Iterator<int>', 'Traversable&Iterator<int>|null',
(string) Type::parseString('Traversable&Iterator<int>|null') (string) Type::parseString('Traversable&Iterator<int>|null')
); );
} }
@ -277,7 +277,7 @@ class TypeParseTest extends TestCase
*/ */
public function testPhpDocUnionOfArraysOrObject() public function testPhpDocUnionOfArraysOrObject()
{ {
$this->assertSame('array<array-key, A|B>|C', (string) Type::parseString('A[]|B[]|C')); $this->assertSame('C|array<array-key, A|B>', (string) Type::parseString('A[]|B[]|C'));
} }
/** /**
@ -380,7 +380,7 @@ class TypeParseTest extends TestCase
public function testObjectLikeWithGenericArgs() public function testObjectLikeWithGenericArgs()
{ {
$this->assertSame( $this->assertSame(
'array{a: array<int, string|int>, b: string}', 'array{a: array<int, int|string>, b: string}',
(string) Type::parseString('array{a: array<int, string|int>, b: string}') (string) Type::parseString('array{a: array<int, string|int>, b: string}')
); );
} }
@ -804,7 +804,7 @@ class TypeParseTest extends TestCase
*/ */
public function testVeryLargeType() public function testVeryLargeType()
{ {
$very_large_type = 'array{a: Closure():(array<mixed, mixed>|null), b?: Closure():array<mixed, mixed>, c?: Closure():array<mixed, mixed>, d?: Closure():array<mixed, mixed>, e?: Closure():(array{f: null|string, g: null|string, h: null|string, i: string, j: mixed, k: mixed, l: mixed, m: mixed, n: bool, o?: array{0: string}}|null), p?: Closure():(array{f: null|string, g: null|string, h: null|string, q: string, i: string, j: mixed, k: mixed, l: mixed, m: mixed, n: bool, o?: array{0: string}}|null), r?: Closure():(array<mixed, mixed>|null), s: array<mixed, mixed>}|null'; $very_large_type = 'array{a: Closure():(array<mixed, mixed>|null), b?: Closure():array<mixed, mixed>, c?: Closure():array<mixed, mixed>, d?: Closure():array<mixed, mixed>, e?: Closure():(array{f: null|string, g: null|string, h: null|string, i: string, j: mixed, k: mixed, l: mixed, m: mixed, n: bool, o?: array{0: string}}|null), p?: Closure():(array{f: null|string, g: null|string, h: null|string, i: string, j: mixed, k: mixed, l: mixed, m: mixed, n: bool, o?: array{0: string}}|null), q: string, r?: Closure():(array<mixed, mixed>|null), s: array<mixed, mixed>}|null';
$this->assertSame( $this->assertSame(
$very_large_type, $very_large_type,

View File

@ -91,7 +91,7 @@ class TypeReconciliationTest extends TestCase
return [ return [
'notNullWithObject' => ['MyObject', '!null', 'MyObject'], 'notNullWithObject' => ['MyObject', '!null', 'MyObject'],
'notNullWithObjectPipeNull' => ['MyObject', '!null', 'MyObject|null'], 'notNullWithObjectPipeNull' => ['MyObject', '!null', 'MyObject|null'],
'notNullWithMyObjectPipeFalse' => ['false|MyObject', '!null', 'MyObject|false'], 'notNullWithMyObjectPipeFalse' => ['MyObject|false', '!null', 'MyObject|false'],
'notNullWithMixed' => ['mixed', '!null', 'mixed'], 'notNullWithMixed' => ['mixed', '!null', 'mixed'],
'notEmptyWithMyObject' => ['MyObject', '!falsy', 'MyObject'], 'notEmptyWithMyObject' => ['MyObject', '!falsy', 'MyObject'],
@ -127,14 +127,14 @@ class TypeReconciliationTest extends TestCase
'nullableClassString' => ['null', 'falsy', '?class-string'], 'nullableClassString' => ['null', 'falsy', '?class-string'],
'mixedOrNullNotFalsy' => ['non-empty-mixed', '!falsy', 'mixed|null'], 'mixedOrNullNotFalsy' => ['non-empty-mixed', '!falsy', 'mixed|null'],
'mixedOrNullFalsy' => ['null|empty-mixed', 'falsy', 'mixed|null'], 'mixedOrNullFalsy' => ['empty-mixed|null', 'falsy', 'mixed|null'],
'nullableClassStringFalsy' => ['null', 'falsy', 'class-string<A>|null'], 'nullableClassStringFalsy' => ['null', 'falsy', 'class-string<A>|null'],
'nullableClassStringEqualsNull' => ['null', '=null', 'class-string<A>|null'], 'nullableClassStringEqualsNull' => ['null', '=null', 'class-string<A>|null'],
'nullableClassStringTruthy' => ['class-string<A>', '!falsy', 'class-string<A>|null'], 'nullableClassStringTruthy' => ['class-string<A>', '!falsy', 'class-string<A>|null'],
'iterableToArray' => ['array<int, int>', 'array', 'iterable<int, int>'], 'iterableToArray' => ['array<int, int>', 'array', 'iterable<int, int>'],
'iterableToTraversable' => ['Traversable<int, int>', 'Traversable', 'iterable<int, int>'], 'iterableToTraversable' => ['Traversable<int, int>', 'Traversable', 'iterable<int, int>'],
'callableToCallableArray' => ['callable-array{0: string|object, 1: string}', 'array', 'callable'], 'callableToCallableArray' => ['callable-array{0: object|string, 1: string}', 'array', 'callable'],
'callableOrArrayToCallableArray' => ['array<array-key, mixed>|callable-array{0: string|object, 1: string}', 'array', 'callable|array'], 'callableOrArrayToCallableArray' => ['array<array-key, mixed>|callable-array{0: object|string, 1: string}', 'array', 'callable|array'],
'traversableToIntersection' => ['Countable&Traversable', 'Traversable', 'Countable'], 'traversableToIntersection' => ['Countable&Traversable', 'Traversable', 'Countable'],
'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', '!array', 'iterable'], 'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', '!array', 'iterable'],
'iterableWithParamsToTraversableWithParams' => ['Traversable<int, string>', '!array', 'iterable<int, string>'], 'iterableWithParamsToTraversableWithParams' => ['Traversable<int, string>', '!array', 'iterable<int, string>'],
@ -239,7 +239,7 @@ class TypeReconciliationTest extends TestCase
$out = $a; $out = $a;
}', }',
'assertions' => [ 'assertions' => [
'$out' => 'null|A', '$out' => 'A|null',
], ],
], ],
'notInstanceOfProperty' => [ 'notInstanceOfProperty' => [
@ -268,7 +268,7 @@ class TypeReconciliationTest extends TestCase
$out = $a->foo; $out = $a->foo;
}', }',
'assertions' => [ 'assertions' => [
'$out' => 'null|B', '$out' => 'B|null',
], ],
'error_levels' => [], 'error_levels' => [],
], ],
@ -297,7 +297,7 @@ class TypeReconciliationTest extends TestCase
$out = $a->foo; $out = $a->foo;
}', }',
'assertions' => [ 'assertions' => [
'$out' => 'null|B', '$out' => 'B|null',
], ],
'error_levels' => [], 'error_levels' => [],
], ],

View File

@ -731,7 +731,7 @@ class TypeTest extends TestCase
echo $var;', echo $var;',
'assertions' => [ 'assertions' => [
'$var' => 'string|int', '$var' => 'int|string',
], ],
], ],
'typeMixedAdjustment' => [ 'typeMixedAdjustment' => [
@ -746,7 +746,7 @@ class TypeTest extends TestCase
echo $var;', echo $var;',
'assertions' => [ 'assertions' => [
'$var' => 'string|int', '$var' => 'int|string',
], ],
], ],
'typeAdjustmentIfNull' => [ 'typeAdjustmentIfNull' => [