2017-02-18 02:50:47 +01:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2019-12-07 19:38:06 +01:00
|
|
|
class IssetTest extends \Psalm\Tests\TestCase
|
2017-02-18 02:50:47 +01:00
|
|
|
{
|
2019-12-07 19:38:06 +01:00
|
|
|
use \Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
|
|
|
|
use \Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
|
2017-02-18 02:50:47 +01:00
|
|
|
|
|
|
|
/**
|
2019-03-01 21:55:20 +01:00
|
|
|
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
2017-02-18 02:50:47 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerValidCodeParse()
|
2017-02-18 02:50:47 +01:00
|
|
|
{
|
2017-04-25 05:45:02 +02:00
|
|
|
return [
|
2018-05-18 17:02:50 +02:00
|
|
|
'issetWithSimpleAssignment' => [
|
|
|
|
'<?php
|
|
|
|
$array = [];
|
|
|
|
|
|
|
|
if (isset($array[$a = 5])) {
|
|
|
|
print "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
print $a;',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['EmptyArrayAccess'],
|
|
|
|
],
|
|
|
|
'issetWithMultipleAssignments' => [
|
|
|
|
'<?php
|
|
|
|
if (rand(0, 4) > 2) {
|
|
|
|
$arr = [5 => [3 => "hello"]];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($arr[$a = 5][$b = 3])) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
echo $a;
|
|
|
|
echo $b;',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedArrayAccess'],
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
'isset' => [
|
|
|
|
'<?php
|
|
|
|
$a = isset($b) ? $b : null;',
|
|
|
|
'assertions' => [
|
2018-12-08 19:18:55 +01:00
|
|
|
'$a' => 'mixed|null',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_levels' => ['MixedAssignment'],
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'nullCoalesce' => [
|
|
|
|
'<?php
|
|
|
|
$a = $b ?? null;',
|
|
|
|
'assertions' => [
|
2018-12-08 19:18:55 +01:00
|
|
|
'$a' => 'mixed|null',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
2017-05-27 02:05:57 +02:00
|
|
|
'error_levels' => ['MixedAssignment'],
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'nullCoalesceWithGoodVariable' => [
|
|
|
|
'<?php
|
2017-11-28 06:46:41 +01:00
|
|
|
$b = rand(0, 10) > 5 ? "hello" : null;
|
2017-04-25 05:45:02 +02:00
|
|
|
$a = $b ?? null;',
|
|
|
|
'assertions' => [
|
2019-10-17 07:14:33 +02:00
|
|
|
'$a' => 'null|string',
|
2017-05-27 02:05:57 +02:00
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'issetKeyedOffset' => [
|
|
|
|
'<?php
|
2019-02-07 18:25:57 +01:00
|
|
|
function getArray() : array {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$foo = getArray();
|
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
if (!isset($foo["a"])) {
|
|
|
|
$foo["a"] = "hello";
|
|
|
|
}',
|
|
|
|
'assertions' => [
|
2019-10-17 07:14:33 +02:00
|
|
|
'$foo[\'a\']' => 'mixed|string',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'error_levels' => [],
|
|
|
|
],
|
|
|
|
'issetKeyedOffsetORFalse' => [
|
|
|
|
'<?php
|
|
|
|
/** @return void */
|
|
|
|
function takesString(string $str) {}
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
$bar = rand(0, 1) ? ["foo" => "bar"] : false;
|
2017-06-29 16:22:49 +02:00
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
if (isset($bar["foo"])) {
|
|
|
|
takesString($bar["foo"]);
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
2018-01-10 01:33:39 +01:00
|
|
|
'error_levels' => ['PossiblyInvalidArrayAccess'],
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'nullCoalesceKeyedOffset' => [
|
|
|
|
'<?php
|
2019-02-07 18:25:57 +01:00
|
|
|
function getArray() : array {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$foo = getArray();
|
|
|
|
|
2017-04-25 05:45:02 +02:00
|
|
|
$foo["a"] = $foo["a"] ?? "hello";',
|
|
|
|
'assertions' => [
|
2019-10-17 07:14:33 +02:00
|
|
|
'$foo[\'a\']' => 'mixed|string',
|
2017-04-25 05:45:02 +02:00
|
|
|
],
|
|
|
|
'error_levels' => ['MixedAssignment'],
|
2017-05-27 02:05:57 +02:00
|
|
|
],
|
2017-11-28 22:52:52 +01:00
|
|
|
'noRedundantConditionOnMixed' => [
|
|
|
|
'<?php
|
2018-01-11 21:50:45 +01:00
|
|
|
function testarray(array $data): void {
|
2017-11-28 22:52:52 +01:00
|
|
|
foreach ($data as $item) {
|
2018-12-19 22:15:19 +01:00
|
|
|
if (isset($item["a"]) && isset($item["b"]["c"])) {
|
2017-11-28 22:52:52 +01:00
|
|
|
echo "Found\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
2018-01-10 01:33:39 +01:00
|
|
|
'error_levels' => ['MixedAssignment', 'MixedArrayAccess'],
|
2017-11-28 22:52:52 +01:00
|
|
|
],
|
2017-12-14 01:46:58 +01:00
|
|
|
'testUnset' => [
|
|
|
|
'<?php
|
|
|
|
$foo = ["a", "b", "c"];
|
|
|
|
foreach ($foo as $bar) {}
|
|
|
|
unset($foo, $bar);
|
|
|
|
|
2018-01-11 21:50:45 +01:00
|
|
|
function foo(): void {
|
2017-12-14 01:46:58 +01:00
|
|
|
$foo = ["a", "b", "c"];
|
|
|
|
foreach ($foo as $bar) {}
|
|
|
|
unset($foo, $bar);
|
|
|
|
}',
|
|
|
|
],
|
2017-12-19 00:47:17 +01:00
|
|
|
'issetObjectLike' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [
|
|
|
|
"profile" => [
|
|
|
|
"foo" => "bar",
|
|
|
|
],
|
|
|
|
"groups" => [
|
|
|
|
"foo" => "bar",
|
|
|
|
"hide" => rand() % 2 > 0,
|
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($arr as $item) {
|
|
|
|
if (!isset($item["hide"]) || !$item["hide"]) {}
|
|
|
|
}',
|
|
|
|
],
|
2018-01-29 21:47:25 +01:00
|
|
|
'issetPropertyAffirmsObject' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
/** @var ?int */
|
|
|
|
public $id;
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesA(?A $a): A {
|
|
|
|
if (isset($a->id)) {
|
|
|
|
return $a;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new A();
|
|
|
|
}',
|
|
|
|
],
|
2018-02-17 17:24:08 +01:00
|
|
|
'issetVariableKeysWithoutChange' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [[1, 2, 3], null, [1, 2, 3], null];
|
2019-08-11 05:22:48 +02:00
|
|
|
$b = rand(0, 2);
|
|
|
|
$c = rand(0, 2);
|
2018-02-17 17:24:08 +01:00
|
|
|
if (isset($arr[$b][$c])) {
|
|
|
|
echo $arr[$b][$c];
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetNonNullArrayKey' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param array<int, int> $arr
|
|
|
|
*/
|
|
|
|
function foo(array $arr) : int {
|
|
|
|
$b = rand(0, 3);
|
|
|
|
if (!isset($arr[$b])) {
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}
|
|
|
|
return $arr[$b];
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetArrayOffsetConditionalCreationWithInt' => [
|
|
|
|
'<?php
|
|
|
|
/** @param array<int, string> $arr */
|
|
|
|
function foo(array $arr) : string {
|
|
|
|
if (!isset($arr[0])) {
|
|
|
|
$arr[0] = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
return $arr[0];
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetArrayOffsetConditionalCreationWithVariable' => [
|
|
|
|
'<?php
|
|
|
|
/** @param array<int, string> $arr */
|
|
|
|
function foo(array $arr) : string {
|
|
|
|
$b = 5;
|
|
|
|
|
|
|
|
if (!isset($arr[$b])) {
|
|
|
|
$arr[$b] = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
return $arr[$b];
|
|
|
|
}',
|
|
|
|
],
|
2018-03-08 21:57:46 +01:00
|
|
|
'noExceptionOnBracketString' => [
|
|
|
|
'<?php
|
|
|
|
if (isset($foo["bar[]"])) {}',
|
|
|
|
],
|
2018-04-06 22:23:10 +02:00
|
|
|
'issetArrayOffsetAndProperty' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
/** @var ?B */
|
|
|
|
public $b;
|
|
|
|
}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A[] $arr
|
|
|
|
*/
|
|
|
|
function takesAList(array $arr) : B {
|
|
|
|
if (isset($arr[1]->b)) {
|
|
|
|
return $arr[1]->b;
|
|
|
|
}
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}',
|
|
|
|
],
|
2018-04-07 00:28:22 +02:00
|
|
|
'allowUnknownAdditionToInt' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [1, 1, 1, 1, 2, 5, 3, 2];
|
|
|
|
$cumulative = [];
|
|
|
|
|
|
|
|
foreach ($arr as $val) {
|
|
|
|
if (isset($cumulative[$val])) {
|
|
|
|
$cumulative[$val] = $cumulative[$val] + 1;
|
|
|
|
} else {
|
|
|
|
$cumulative[$val] = 1;
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'allowUnknownArrayMergeToInt' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [1, 1, 1, 1, 2, 5, 3, 2];
|
|
|
|
$cumulative = [];
|
|
|
|
|
|
|
|
foreach ($arr as $val) {
|
|
|
|
if (isset($cumulative[$val])) {
|
|
|
|
$cumulative[$val] = array_merge($cumulative[$val], [$val]);
|
|
|
|
} else {
|
|
|
|
$cumulative[$val] = [$val];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($cumulative as $arr) {
|
|
|
|
foreach ($arr as $val) {
|
|
|
|
takesInt($val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function takesInt(int $i) : void {}',
|
|
|
|
],
|
2018-04-11 20:19:42 +02:00
|
|
|
'returnArrayWithDefinedKeys' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param array{bar?: int, foo: int|string} $arr
|
|
|
|
* @return array{bar: int, foo: string}|null
|
|
|
|
*/
|
|
|
|
function foo(array $arr) : ?array {
|
|
|
|
if (!isset($arr["bar"])) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_int($arr["foo"])) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $arr;
|
|
|
|
}',
|
|
|
|
],
|
2018-05-31 00:56:44 +02:00
|
|
|
'arrayAccessAfterOneIsset' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [];
|
|
|
|
|
|
|
|
foreach ([1, 2, 3] as $foo) {
|
|
|
|
if (!isset($arr["bar"])) {
|
|
|
|
$arr["bar"] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
echo $arr["bar"];
|
|
|
|
}',
|
|
|
|
],
|
2018-04-11 20:19:42 +02:00
|
|
|
'arrayAccessAfterTwoIssets' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [];
|
|
|
|
|
|
|
|
foreach ([1, 2, 3] as $foo) {
|
|
|
|
if (!isset($arr["foo"])) {
|
|
|
|
$arr["foo"] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($arr["bar"])) {
|
|
|
|
$arr["bar"] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
echo $arr["bar"];
|
|
|
|
}',
|
|
|
|
],
|
2018-05-05 23:30:18 +02:00
|
|
|
'issetAdditionalVar' => [
|
|
|
|
'<?php
|
|
|
|
class Example {
|
|
|
|
const FOO = "foo";
|
|
|
|
/**
|
|
|
|
* @param array{bar:string} $params
|
|
|
|
*/
|
|
|
|
public function test(array $params) : bool {
|
|
|
|
if (isset($params[self::FOO])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($params["bat"])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-23 19:27:54 +01:00
|
|
|
}',
|
2018-05-05 23:30:18 +02:00
|
|
|
],
|
2018-05-18 17:02:50 +02:00
|
|
|
'noRedundantConditionAfterIsset' => [
|
|
|
|
'<?php
|
|
|
|
/** @param array<string, array<int, string>> $arr */
|
|
|
|
function foo(array $arr, string $k) : void {
|
|
|
|
if (!isset($arr[$k])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($arr[$k][0]) {}
|
|
|
|
}',
|
|
|
|
],
|
2018-05-31 02:54:03 +02:00
|
|
|
'mixedArrayIsset' => [
|
|
|
|
'<?php
|
|
|
|
$a = isset($_GET["a"]) ? $_GET["a"] : "";
|
|
|
|
if ($a) {}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedAssignment', 'MixedArrayAccess'],
|
|
|
|
],
|
2018-08-24 22:48:14 +02:00
|
|
|
'mixedArrayIssetGetStringVar' => [
|
|
|
|
'<?php
|
|
|
|
if (isset($_GET["b"]) && is_string($_GET["b"])) {
|
|
|
|
echo $_GET["b"];
|
|
|
|
}',
|
|
|
|
],
|
2019-10-02 01:31:08 +02:00
|
|
|
'regularArrayAccessInLoopAfterIsset' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [];
|
|
|
|
while (rand(0, 1)) {
|
|
|
|
if (!isset($arr["a"]["b"])) {
|
|
|
|
$arr["a"]["b"] = "foo";
|
|
|
|
}
|
|
|
|
echo $arr["a"]["b"];
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'conditionalArrayAccessInLoopAfterIssetWithAltAssignment' => [
|
2018-05-31 21:07:03 +02:00
|
|
|
'<?php
|
|
|
|
$arr = [];
|
|
|
|
while (rand(0, 1)) {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
if (!isset($arr["a"]["b"])) {
|
|
|
|
$arr["a"]["b"] = "foo";
|
|
|
|
}
|
|
|
|
echo $arr["a"]["b"];
|
|
|
|
} else {
|
|
|
|
$arr["c"] = "foo";
|
|
|
|
}
|
2019-03-23 19:27:54 +01:00
|
|
|
}',
|
2018-05-31 21:07:03 +02:00
|
|
|
],
|
2018-06-01 17:16:42 +02:00
|
|
|
'issetVarInLoopBeforeAssignment' => [
|
|
|
|
'<?php
|
|
|
|
function foo() : void {
|
|
|
|
while (rand(0, 1)) {
|
|
|
|
if (!isset($foo)) {
|
|
|
|
$foo = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2018-06-08 15:31:21 +02:00
|
|
|
'issetOnArrayAccess' => [
|
|
|
|
'<?php
|
|
|
|
function foo(ArrayAccess $arr) : void {
|
2019-08-23 05:26:04 +02:00
|
|
|
$a = isset($arr["a"]) ? $arr["a"] : 4;
|
2018-06-08 15:31:21 +02:00
|
|
|
takesInt($a);
|
|
|
|
}
|
|
|
|
function takesInt(int $i) : void {}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedAssignment', 'MixedArgument'],
|
2018-08-21 06:43:45 +02:00
|
|
|
],
|
|
|
|
'noParadoxOnMultipleNotIssets' => [
|
|
|
|
'<?php
|
|
|
|
/** @var array */
|
|
|
|
$array = [];
|
|
|
|
function sameString(string $string): string {
|
|
|
|
return $string;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($array[sameString("key1")]) || isset($array[sameString("key2")])) {
|
|
|
|
throw new \InvalidArgumentException();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($array[sameString("key3")]) || !isset($array[sameString("key4")])) {
|
|
|
|
throw new \InvalidArgumentException();
|
2019-03-23 19:27:54 +01:00
|
|
|
}',
|
2018-08-21 06:43:45 +02:00
|
|
|
],
|
2019-12-07 19:38:06 +01:00
|
|
|
'notIssetOneOrOtherSimple' => [
|
2018-09-10 06:13:59 +02:00
|
|
|
'<?php
|
|
|
|
$foo = [
|
|
|
|
"one" => rand(0,1) ? new DateTime : null,
|
2018-11-06 03:57:36 +01:00
|
|
|
"two" => rand(0,1) ? new DateTime : null,
|
2018-09-10 06:13:59 +02:00
|
|
|
"three" => new DateTime
|
|
|
|
];
|
|
|
|
|
|
|
|
if (!(isset($foo["one"]) || isset($foo["two"]))) {
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
echo $foo["one"]->format("Y");',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['PossiblyNullReference'],
|
|
|
|
],
|
|
|
|
'notIssetOneOrOtherWithoutAssert' => [
|
|
|
|
'<?php
|
|
|
|
$foo = [
|
|
|
|
"one" => rand(0,1) ? new DateTime : null,
|
2018-11-06 03:57:36 +01:00
|
|
|
"two" => rand(0,1) ? new DateTime : null,
|
2018-09-10 06:13:59 +02:00
|
|
|
"three" => new DateTime
|
|
|
|
];
|
|
|
|
|
2019-12-07 19:38:06 +01:00
|
|
|
$a = isset($foo["one"]) || isset($foo["two"]);
|
2018-09-10 06:13:59 +02:00
|
|
|
|
|
|
|
echo $foo["one"]->format("Y");',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['PossiblyNullReference'],
|
|
|
|
],
|
|
|
|
'notIssetOneOrOtherWithAssert' => [
|
|
|
|
'<?php
|
|
|
|
$foo = [
|
|
|
|
"one" => rand(0,1) ? new DateTime : null,
|
2018-11-06 03:57:36 +01:00
|
|
|
"two" => rand(0,1) ? new DateTime : null,
|
2018-09-10 06:13:59 +02:00
|
|
|
"three" => new DateTime
|
|
|
|
];
|
|
|
|
|
|
|
|
assert(isset($foo["one"]) || isset($foo["two"]));
|
|
|
|
|
|
|
|
echo $foo["one"]->format("Y");',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['PossiblyNullReference'],
|
|
|
|
],
|
2018-12-15 00:52:29 +01:00
|
|
|
'assertArrayAfterIssetStringOffset' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param string|array $a
|
|
|
|
*/
|
|
|
|
function _renderInput($a) : array {
|
|
|
|
if (isset($a["foo"], $a["bar"])) {
|
|
|
|
return $a;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
2019-03-23 19:27:54 +01:00
|
|
|
}',
|
2018-12-15 00:52:29 +01:00
|
|
|
],
|
|
|
|
'assertMoreComplicatedArrayAfterIssetStringOffset' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param string|int $val
|
|
|
|
* @param string|array $text
|
|
|
|
* @param array $data
|
|
|
|
*/
|
|
|
|
function _renderInput($val, $text, $data) : array {
|
|
|
|
if (is_int($val) && isset($text["foo"], $text["bar"])) {
|
|
|
|
$radio = $text;
|
|
|
|
} else {
|
|
|
|
$radio = ["value" => $val, "text" => $text];
|
|
|
|
}
|
|
|
|
return $radio;
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedAssignment'],
|
2018-12-19 22:15:19 +01:00
|
|
|
],
|
|
|
|
'assertAfterIsset' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param mixed $arr
|
|
|
|
*/
|
|
|
|
function foo($arr) : void {
|
|
|
|
if (empty($arr)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($arr["a"]) && isset($arr["b"])) {}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedAssignment'],
|
|
|
|
],
|
2018-12-20 07:06:43 +01:00
|
|
|
'noCrashAfterIsset' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param string[] $columns
|
|
|
|
* @param mixed[] $options
|
|
|
|
*/
|
|
|
|
function foo(array $columns, array $options) : void {
|
|
|
|
$arr = $options["b"];
|
|
|
|
|
|
|
|
foreach ($arr as $a) {
|
|
|
|
if (isset($columns[$a]["c"])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedAssignment', 'MixedArrayOffset', 'InvalidArrayOffset'],
|
|
|
|
],
|
2019-02-15 21:58:36 +01:00
|
|
|
'sessionNullCoalesce' => [
|
|
|
|
'<?php
|
2019-03-23 19:27:54 +01:00
|
|
|
$a = $_SESSION ?? [];',
|
2019-02-15 21:58:36 +01:00
|
|
|
],
|
2020-01-15 03:25:20 +01:00
|
|
|
'sessionIssetNull' => [
|
|
|
|
'<?php
|
|
|
|
$a = isset($_SESSION) ? $_SESSION : [];',
|
|
|
|
],
|
2019-06-04 19:03:17 +02:00
|
|
|
'issetSeparateNegated' => [
|
|
|
|
'<?php
|
|
|
|
function foo(?string $a, ?string $b): string {
|
|
|
|
if (!isset($a) || !isset($b)) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
return $a . $b;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetMultipleNegated' => [
|
|
|
|
'<?php
|
|
|
|
function foo(?string $a, ?string $b): string {
|
|
|
|
if (!isset($a, $b)) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
return $a . $b;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetMultipleNegatedWithExtraClause' => [
|
|
|
|
'<?php
|
|
|
|
function foo(?string $a, ?string $b): string {
|
|
|
|
if (!(isset($a, $b) && rand(0, 1))) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
return $a . $b;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetMultipleNotNegated' => [
|
|
|
|
'<?php
|
|
|
|
function foo(?string $a, ?string $b): string {
|
|
|
|
if (isset($a, $b)) {
|
|
|
|
return $a . $b;
|
|
|
|
}
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}',
|
|
|
|
],
|
2019-06-04 20:08:49 +02:00
|
|
|
'issetNotIssetTest' => [
|
|
|
|
'<?php
|
|
|
|
class B {
|
|
|
|
/** @var string */
|
|
|
|
public $c = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo(array $a, B $b, string $s): void {
|
|
|
|
if ($s !== "bar" && !isset($a[$b->c])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($s !== "bar" && isset($a[$b->c])) {
|
|
|
|
// do something
|
|
|
|
} else {
|
|
|
|
// something else
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2019-10-02 01:31:08 +02:00
|
|
|
'issetOnNestedObjectlikeOneLevel' => [
|
2019-10-01 14:46:37 +02:00
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param array{a:array} $array
|
|
|
|
* @return array{a:array{b:mixed}}
|
|
|
|
* @throw \LogicException
|
|
|
|
*/
|
|
|
|
function level3($array) {
|
|
|
|
if (!isset($array["a"]["b"])) {
|
|
|
|
throw new \LogicException();
|
|
|
|
}
|
|
|
|
return $array;
|
|
|
|
}'
|
|
|
|
],
|
2019-10-02 01:31:08 +02:00
|
|
|
'issetOnStringArrayShouldInformArrayness' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param string[] $a
|
|
|
|
* @return array{b: string}
|
|
|
|
*/
|
|
|
|
function foo(array $a) {
|
|
|
|
if (isset($a["b"])) {
|
|
|
|
return $a;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}'
|
|
|
|
],
|
|
|
|
'arrayKeyExistsOnStringArrayShouldInformArrayness' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param string[] $a
|
|
|
|
* @return array{b: string}
|
|
|
|
*/
|
|
|
|
function foo(array $a) {
|
|
|
|
if (array_key_exists("b", $a)) {
|
|
|
|
return $a;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}'
|
|
|
|
],
|
2019-10-07 22:59:05 +02:00
|
|
|
'issetOnArrayTwice' => [
|
|
|
|
'<?php
|
|
|
|
function foo(array $options): void {
|
|
|
|
if (!isset($options["a"])) {
|
|
|
|
$options["a"] = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($options["b"])) {
|
|
|
|
$options["b"] = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($options["b"] === 2) {}
|
|
|
|
}'
|
|
|
|
],
|
2019-10-22 16:40:37 +02:00
|
|
|
'listDestructuringErrorSuppress' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $s) : void {
|
|
|
|
@list(, $port) = explode(":", $s);
|
|
|
|
echo isset($port) ? "cool" : "uncool";
|
|
|
|
}',
|
|
|
|
],
|
2019-11-25 22:00:16 +01:00
|
|
|
'listDestructuringErrorSuppressWithFirstString' => [
|
2019-11-25 21:38:54 +01:00
|
|
|
'<?php
|
|
|
|
function foo(string $s) : string {
|
|
|
|
@list($port, $starboard) = explode(":", $s);
|
|
|
|
return $port;
|
|
|
|
}',
|
|
|
|
],
|
2019-12-04 18:23:26 +01:00
|
|
|
'accessAfterArrayExistsVariable' => [
|
|
|
|
'<?php
|
|
|
|
abstract class P {
|
|
|
|
const MAP = [
|
|
|
|
A::class => 1,
|
|
|
|
B::class => 2,
|
|
|
|
C::class => 3,
|
|
|
|
];
|
|
|
|
|
|
|
|
public function foo(string $s) : int {
|
|
|
|
$a = static::class;
|
|
|
|
if (!isset(self::MAP[$a])) {
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}
|
|
|
|
return self::MAP[$a];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class A extends P {}
|
|
|
|
class B extends P {}
|
|
|
|
class C extends P {}'
|
|
|
|
],
|
|
|
|
'accessAfterArrayExistsStaticClass' => [
|
|
|
|
'<?php
|
|
|
|
abstract class P {
|
|
|
|
const MAP = [
|
|
|
|
A::class => 1,
|
|
|
|
B::class => 2,
|
|
|
|
C::class => 3,
|
|
|
|
];
|
|
|
|
|
|
|
|
public function foo(string $s) : int {
|
|
|
|
if (!isset(self::MAP[static::class])) {
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}
|
|
|
|
return self::MAP[static::class];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class A extends P {}
|
|
|
|
class B extends P {}
|
|
|
|
class C extends P {}'
|
|
|
|
],
|
2019-12-22 02:42:39 +01:00
|
|
|
'issetCreateObjectLikeWithType' => [
|
|
|
|
'<?php
|
|
|
|
function foo(array $options): void {
|
|
|
|
if (isset($options["a"])) {
|
|
|
|
$options["b"] = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (\is_array($options["b"])) {}
|
2019-12-22 13:36:16 +01:00
|
|
|
}'
|
|
|
|
],
|
|
|
|
'issetOnThing' => [
|
|
|
|
'<?php
|
|
|
|
function foo() : void {
|
|
|
|
$p = [false, false];
|
|
|
|
$i = rand(0, 1);
|
|
|
|
if (rand(0, 1) && isset($p[$i])) {
|
|
|
|
$p[$i] = true;
|
|
|
|
}
|
2019-12-22 02:42:39 +01:00
|
|
|
|
2019-12-22 13:36:16 +01:00
|
|
|
foreach ($p as $q) {
|
|
|
|
if ($q) {}
|
|
|
|
}
|
|
|
|
}',
|
2019-12-22 02:42:39 +01:00
|
|
|
],
|
2020-01-14 06:53:38 +01:00
|
|
|
'issetOnNullableObjectWithNullCoalesce' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public bool $s = true;
|
|
|
|
}
|
|
|
|
function foo(?A $a) : string {
|
|
|
|
if (rand(0, 1) && !($a->s ?? false)) {
|
|
|
|
return "foo";
|
|
|
|
}
|
|
|
|
return "bar";
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetOnNullableObjectWithIsset' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public bool $s = true;
|
|
|
|
}
|
|
|
|
function foo(?A $a) : string {
|
|
|
|
if (rand(0, 1) && !(isset($a->s) ? $a->s : false)) {
|
|
|
|
return "foo";
|
|
|
|
}
|
|
|
|
return "bar";
|
|
|
|
}',
|
|
|
|
],
|
2020-01-15 00:28:32 +01:00
|
|
|
'issetOnMethodCallInsideFunctionCall' => [
|
|
|
|
'<?php
|
|
|
|
class C {
|
|
|
|
public function foo() : ?string {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo(C $c) : void {
|
|
|
|
strlen($c->foo() ?? "");
|
|
|
|
}'
|
|
|
|
],
|
|
|
|
'issetOnMethodCallInsideMethodCall' => [
|
|
|
|
'<?php
|
|
|
|
class C {
|
|
|
|
public function foo() : ?string {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo(C $c) : void {
|
|
|
|
new DateTime($c->foo() ?? "");
|
2020-01-15 03:54:02 +01:00
|
|
|
}',
|
|
|
|
],
|
|
|
|
'methodCallAfterIsset' => [
|
|
|
|
'<?php
|
|
|
|
class B {
|
|
|
|
public function bar() : void {}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @psalm-suppress MissingConstructor */
|
|
|
|
class A {
|
|
|
|
/** @var B */
|
|
|
|
public $foo;
|
|
|
|
|
|
|
|
public function init() : void {
|
|
|
|
if (isset($this->foo)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$this->foo = new B;
|
|
|
|
} else {
|
|
|
|
$this->foo = new B;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->foo->bar();
|
|
|
|
}
|
2020-01-15 00:28:32 +01:00
|
|
|
}'
|
|
|
|
],
|
2020-02-01 16:58:13 +01:00
|
|
|
'issetOnArrayOfArraysReturningStringInElse' => [
|
2020-01-31 23:27:39 +01:00
|
|
|
'<?php
|
2020-02-01 16:58:13 +01:00
|
|
|
function foo(int $i) : string {
|
|
|
|
/** @var array<int, array<string, string>> */
|
|
|
|
$tokens = [];
|
|
|
|
|
|
|
|
if (isset($tokens[$i]["a"])) {
|
|
|
|
return "hello";
|
|
|
|
} else {
|
|
|
|
return $tokens[$i]["b"];
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetOnArrayOfObjectsAssertingOnIssetValue' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public ?string $name = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo(int $i) : void {
|
|
|
|
/** @var array<int, A> */
|
|
|
|
$tokens = [];
|
|
|
|
|
|
|
|
if (isset($tokens[$i]->name) && $tokens[$i]->name === "hello") {}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetOnArrayOfObjectsAssertingOnNotIssetValue' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
public ?string $name = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo(int $i) : void {
|
|
|
|
/** @var array<int, A> */
|
|
|
|
$tokens = [];
|
|
|
|
|
|
|
|
if (!isset($tokens[$i])) {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$tokens[$i] = new A();
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
echo $tokens[$i]->name;
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetOnArrayOfMixed' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MixedArrayAccess
|
|
|
|
* @psalm-suppress MixedArgument
|
|
|
|
*/
|
|
|
|
function foo(int $i) : void {
|
|
|
|
/** @var array */
|
2020-01-31 23:27:39 +01:00
|
|
|
$tokens = [];
|
|
|
|
|
|
|
|
if (!isset($tokens[$i]["a"])) {
|
2020-02-01 16:58:13 +01:00
|
|
|
echo $tokens[$i]["b"];
|
2020-01-31 23:27:39 +01:00
|
|
|
}
|
2020-02-01 16:58:13 +01:00
|
|
|
}',
|
|
|
|
],
|
|
|
|
'issetOnArrayOfArrays' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @psalm-suppress MixedArgument
|
|
|
|
*/
|
|
|
|
function foo(int $i) : void {
|
|
|
|
/** @var array<array> */
|
|
|
|
$tokens = [];
|
2020-01-31 23:27:39 +01:00
|
|
|
|
2020-02-01 16:58:13 +01:00
|
|
|
if (!isset($tokens[$i]["a"])) {
|
|
|
|
echo $tokens[$i]["b"];
|
|
|
|
}
|
2020-01-31 23:27:39 +01:00
|
|
|
}',
|
|
|
|
],
|
2020-02-01 16:58:13 +01:00
|
|
|
'issetOnArrayOfArrayOfStrings' => [
|
2020-01-31 23:27:39 +01:00
|
|
|
'<?php
|
2020-02-01 16:58:13 +01:00
|
|
|
function foo(int $i) : void {
|
2020-01-31 23:27:39 +01:00
|
|
|
/** @var array<int, array<string, string>> */
|
|
|
|
$tokens = [];
|
|
|
|
|
2020-02-01 16:58:13 +01:00
|
|
|
if (!isset($tokens[$i]["a"])) {
|
|
|
|
echo $tokens[$i]["b"];
|
2020-01-31 23:27:39 +01:00
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2017-04-25 05:45:02 +02:00
|
|
|
];
|
2017-02-18 02:50:47 +01:00
|
|
|
}
|
2018-01-10 01:33:39 +01:00
|
|
|
|
|
|
|
/**
|
2019-03-01 21:55:20 +01:00
|
|
|
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
|
2018-01-10 01:33:39 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerInvalidCodeParse()
|
2018-01-10 01:33:39 +01:00
|
|
|
{
|
|
|
|
return [
|
|
|
|
'complainAboutBadCallInIsset' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
$a = isset(A::foo()[0]);',
|
|
|
|
'error_message' => 'UndefinedMethod',
|
|
|
|
],
|
2018-02-17 17:24:08 +01:00
|
|
|
'issetVariableKeysWithChange' => [
|
|
|
|
'<?php
|
|
|
|
$arr = [[1, 2, 3], null, [1, 2, 3], null];
|
|
|
|
$b = 2;
|
|
|
|
$c = 0;
|
|
|
|
if (isset($arr[$b][$c])) {
|
|
|
|
$b = 1;
|
|
|
|
echo $arr[$b][$c];
|
|
|
|
}',
|
2018-05-05 23:30:18 +02:00
|
|
|
'error_message' => 'NullArrayAccess',
|
|
|
|
],
|
|
|
|
'issetAdditionalVarWithSealedObjectLike' => [
|
|
|
|
'<?php
|
|
|
|
class Example {
|
|
|
|
const FOO = "foo";
|
|
|
|
public function test() : bool {
|
|
|
|
$params = ["bar" => "bat"];
|
|
|
|
|
|
|
|
if (isset($params[self::FOO])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'InvalidArrayOffset',
|
2018-02-17 17:24:08 +01:00
|
|
|
],
|
2019-11-25 21:38:54 +01:00
|
|
|
'listDestructuringErrorSuppress' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $s) : string {
|
|
|
|
@list($port) = explode(":", $s, -1);
|
|
|
|
return $port;
|
|
|
|
}',
|
|
|
|
'error_message' => 'NullableReturnStatement',
|
|
|
|
],
|
2019-12-13 14:06:40 +01:00
|
|
|
'undefinedVarInNullCoalesce' => [
|
|
|
|
'<?php
|
|
|
|
function bar(): void {
|
|
|
|
$do_baz = $config["do_it"] ?? false;
|
|
|
|
if ($do_baz) {
|
|
|
|
baz();
|
|
|
|
}
|
|
|
|
}',
|
2019-12-13 14:11:04 +01:00
|
|
|
'error_message' => 'UndefinedVariable'
|
2019-12-13 14:06:40 +01:00
|
|
|
],
|
2020-01-11 21:58:40 +01:00
|
|
|
'issetNullVar' => [
|
|
|
|
'<?php
|
|
|
|
function four(?string $s) : void {
|
|
|
|
if ($s === null) {
|
|
|
|
if (isset($s)) {}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'TypeDoesNotContainType',
|
|
|
|
],
|
2020-01-14 03:27:09 +01:00
|
|
|
'stringIsAlwaysSet' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $s) : string {
|
|
|
|
if (!isset($s)) {
|
|
|
|
return "foo";
|
|
|
|
}
|
|
|
|
return "bar";
|
|
|
|
}',
|
|
|
|
'error_message' => 'TypeDoesNotContainType'
|
|
|
|
],
|
2020-02-01 16:58:13 +01:00
|
|
|
'issetOnArrayOfArraysReturningString' => [
|
2020-01-31 23:27:39 +01:00
|
|
|
'<?php
|
2020-02-01 16:58:13 +01:00
|
|
|
function foo(int $i) : ?string {
|
2020-01-31 23:27:39 +01:00
|
|
|
/** @var array<array> */
|
|
|
|
$tokens = [];
|
|
|
|
|
|
|
|
if (!isset($tokens[$i]["a"])) {
|
2020-02-01 16:58:13 +01:00
|
|
|
return $tokens[$i]["a"];
|
2020-01-31 23:27:39 +01:00
|
|
|
}
|
|
|
|
|
2020-02-01 16:58:13 +01:00
|
|
|
return "hello";
|
2020-01-31 23:27:39 +01:00
|
|
|
}',
|
|
|
|
'error_message' => 'PossiblyUndefinedArrayOffset',
|
|
|
|
],
|
2020-03-24 17:01:39 +01:00
|
|
|
'accessAfterIssetCheckOnFalsableArray' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @return array{b?: string}|false
|
|
|
|
*/
|
|
|
|
function returnPossiblyFalseArray() {
|
|
|
|
return rand(0, 1) ? false : (rand(0, 1) ? ["b" => "hello"] : []);
|
|
|
|
}
|
|
|
|
|
|
|
|
function foo() : void {
|
|
|
|
$arr = returnPossiblyFalseArray();
|
|
|
|
/** @psalm-suppress PossiblyInvalidArrayAccess */
|
|
|
|
if (!isset($arr["b"])) {}
|
|
|
|
echo $arr["b"];
|
|
|
|
}',
|
|
|
|
'error_message' => 'PossiblyInvalidArrayAccess',
|
|
|
|
],
|
2018-01-10 01:33:39 +01:00
|
|
|
];
|
|
|
|
}
|
2017-02-18 02:50:47 +01:00
|
|
|
}
|