1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +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 implode;
use function is_int;
use function sort;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\StatementsSource;
@ -70,47 +71,43 @@ class ObjectLike extends \Psalm\Type\Atomic
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 */
return static::KEY . '{' .
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
)
) .
'}';
return static::KEY . '{' . implode(', ', $union_type_parts) . '}';
}
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 */
return static::KEY . '{' .
implode(
', ',
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
)
) .
implode(', ', $union_type_parts) .
'}'
. ($this->previous_value_type
? '<' . ($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\TTemplateParam;
use Psalm\Type\Atomic\TTrue;
use function sort;
use function str_replace;
use function str_split;
use function strpos;
@ -279,18 +280,15 @@ class Reconciler
&& (!$has_isset || substr($key, -1, 1) !== ']')
&& !($statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer)
) {
$reconcile_key = implode(
'&',
array_map(
/**
* @return string
*/
function (array $new_type_part_parts) {
return implode('|', $new_type_part_parts);
},
$new_type_parts
)
$reconciled_parts = array_map(
function (array $new_type_part_parts): string {
sort($new_type_part_parts);
return implode('|', $new_type_part_parts);
},
$new_type_parts
);
sort($reconciled_parts);
$reconcile_key = implode('&', $reconciled_parts);
self::triggerIssueForImpossible(
$result_type,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -94,7 +94,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}',
'<?php
/**
* @return string|null
* @return null|string
*/
function foo() {
return rand(0, 1) ? "hello" : null;
@ -110,7 +110,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}',
'<?php
/**
* @return string|null
* @return null|string
*/
function foo() {
return rand(0, 1) ? "hello" : null;
@ -265,7 +265,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}',
'<?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}}}
*/
@ -544,7 +544,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}',
'<?php
/**
* @return string|null
* @return null|string
*/
function foo() {
if (rand(0, 1)) {
@ -555,7 +555,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}
/**
* @return string|null
* @return null|string
*/
function bar() {
if (rand(0, 1)) {
@ -793,7 +793,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}',
'<?php
/**
* @return string|false
* @return false|string
*/
function foo() {
return rand(0, 1) ? "hello" : false;
@ -809,7 +809,7 @@ class ReturnTypeManipulationTest extends FileManipulationTest
}',
'<?php
/**
* @return string|null
* @return null|string
*/
function foo() {
return rand(0, 1) ? "hello" : null;

View File

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

View File

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

View File

@ -136,7 +136,7 @@ echo $a;';
'line_from' => 2,
'line_to' => 2,
'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_path' => 'somefile.php',
'snippet' => 'function psalmCanVerify(int $your_code): ?string {',

View File

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

View File

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

View File

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

View File

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

View File

@ -147,7 +147,7 @@ class MethodMutationTest extends TestCase
'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;
}
}',
'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' => [
'<?php

View File

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

View File

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

View File

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

View File

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

View File

@ -125,7 +125,7 @@ echo $a;';
'line_from' => 2,
'line_to' => 2,
'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_path' => 'somefile.php',
'snippet' => 'function psalmCanVerify(int $your_code): ?string {',
@ -210,7 +210,7 @@ echo $a;';
'engineId' => 'Psalm',
'ruleId' => 'MixedInferredReturnType',
'primaryLocation' => [
'message' => 'Could not verify return type \'string|null\' for psalmCanVerify',
'message' => 'Could not verify return type \'null|string\' for psalmCanVerify',
'filePath' => 'somefile.php',
'textRange' => [
'startLine' => 2,
@ -277,7 +277,7 @@ echo $a;';
$this->assertSame(
'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: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(
'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: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
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 {
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
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 {
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: 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
@ -405,7 +405,7 @@ INFO: PossiblyUndefinedGlobalVariable - somefile.php:15:6 - Possibly undefined g
'| SEVERITY | LINE | ISSUE | DESCRIPTION |' . "\n" .
'+----------+------+---------------------------------+---------------------------------------------------------------+' . "\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" .
'| INFO | 15 | PossiblyUndefinedGlobalVariable | Possibly undefined global variable $a, first seen on line 10 |' . "\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"/>
</file>
<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 name="somefile.php">
<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();',
'assertions' => [
'$blah' => 'string|null',
'$blah' => 'null|string',
],
],
'overrideReturnTypeInGrandparent' => [
@ -313,7 +313,7 @@ class ReturnTypeTest extends TestCase
$blah = (new C())->blah();',
'assertions' => [
'$blah' => 'string|null',
'$blah' => 'null|string',
],
],
'backwardsReturnType' => [

View File

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

View File

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

View File

@ -1258,7 +1258,7 @@ class ClassTemplateTest extends TestCase
$a_or_b = $random_collection->get();',
[
'$random_collection' => 'C<A>|C<B>',
'$a_or_b' => 'B|A',
'$a_or_b' => 'A|B',
],
],
'inferClosureParamTypeFromContext' => [
@ -2110,7 +2110,7 @@ class ClassTemplateTest extends TestCase
$mario = new CharacterRow(["id" => 5, "name" => "Mario", "height" => 3.5]);
$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' => [
'<?php

View File

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

View File

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

View File

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

View File

@ -41,7 +41,7 @@ class TypeParseTest extends TestCase
*/
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()
{
$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()
{
$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()
{
$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()
{
$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()
{
$this->assertSame(
'null|Traversable&Iterator<int>',
'Traversable&Iterator<int>|null',
(string) Type::parseString('Traversable&Iterator<int>|null')
);
}
@ -277,7 +277,7 @@ class TypeParseTest extends TestCase
*/
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()
{
$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}')
);
}
@ -804,7 +804,7 @@ class TypeParseTest extends TestCase
*/
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(
$very_large_type,

View File

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

View File

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