2017-11-28 06:46:41 +01:00
|
|
|
<?php
|
|
|
|
namespace Psalm\Tests;
|
|
|
|
|
2019-06-26 22:52:29 +02:00
|
|
|
use const DIRECTORY_SEPARATOR;
|
|
|
|
|
2017-11-28 06:46:41 +01:00
|
|
|
class RedundantConditionTest extends TestCase
|
|
|
|
{
|
2018-11-06 03:57:36 +01:00
|
|
|
use Traits\ValidCodeAnalysisTestTrait;
|
|
|
|
use Traits\InvalidCodeAnalysisTestTrait;
|
2017-11-28 06:46:41 +01:00
|
|
|
|
|
|
|
/**
|
2019-03-01 21:55:20 +01:00
|
|
|
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
2017-11-28 06:46:41 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerValidCodeParse()
|
2017-11-28 06:46:41 +01:00
|
|
|
{
|
|
|
|
return [
|
|
|
|
'ignoreIssueAndAssign' => [
|
|
|
|
'<?php
|
2018-09-17 18:15:45 +02:00
|
|
|
function foo(): stdClass {
|
2017-11-28 06:46:41 +01:00
|
|
|
return new stdClass;
|
|
|
|
}
|
|
|
|
|
|
|
|
$b = null;
|
|
|
|
|
|
|
|
foreach ([0, 1] as $i) {
|
|
|
|
$a = foo();
|
|
|
|
|
|
|
|
if (!empty($a)) {
|
|
|
|
$b = $a;
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'assertions' => [
|
|
|
|
'$b' => 'null|stdClass',
|
|
|
|
],
|
|
|
|
'error_levels' => ['RedundantCondition'],
|
|
|
|
],
|
2017-11-28 23:27:19 +01:00
|
|
|
'byrefNoRedundantCondition' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param int $min ref
|
|
|
|
* @param int $other
|
|
|
|
*/
|
2018-01-11 21:50:45 +01:00
|
|
|
function testmin(&$min, int $other): void {
|
2017-11-28 23:27:19 +01:00
|
|
|
if (is_null($min)) {
|
|
|
|
$min = 3;
|
|
|
|
} elseif (!is_int($min)) {
|
|
|
|
$min = 5;
|
|
|
|
} elseif ($min < $other) {
|
|
|
|
$min = $other;
|
|
|
|
}
|
|
|
|
}',
|
2018-02-07 00:44:53 +01:00
|
|
|
'assertions' => [],
|
2018-02-07 21:20:47 +01:00
|
|
|
'error_levels' => [
|
|
|
|
'RedundantConditionGivenDocblockType',
|
|
|
|
'DocblockTypeContradiction',
|
|
|
|
],
|
2017-11-28 23:27:19 +01:00
|
|
|
],
|
2017-11-29 04:17:03 +01:00
|
|
|
'assignmentInIf' => [
|
|
|
|
'<?php
|
2018-01-11 21:50:45 +01:00
|
|
|
function test(int $x = null): int {
|
2017-11-29 04:17:03 +01:00
|
|
|
if (!$x && !($x = rand(0, 10))) {
|
|
|
|
echo "Failed to get non-empty x\n";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return $x;
|
|
|
|
}',
|
|
|
|
],
|
2017-12-02 23:57:58 +01:00
|
|
|
'noRedundantConditionAfterAssignment' => [
|
|
|
|
'<?php
|
|
|
|
/** @param int $i */
|
2018-01-11 21:50:45 +01:00
|
|
|
function foo($i): void {
|
2017-12-02 23:57:58 +01:00
|
|
|
if ($i !== null) {
|
|
|
|
$i = (int) $i;
|
|
|
|
|
|
|
|
if ($i) {}
|
|
|
|
}
|
|
|
|
}',
|
2018-02-07 00:44:53 +01:00
|
|
|
'assertions' => [],
|
2018-02-07 21:20:47 +01:00
|
|
|
'error_levels' => [
|
|
|
|
'RedundantConditionGivenDocblockType',
|
|
|
|
],
|
2017-12-02 23:57:58 +01:00
|
|
|
],
|
2017-12-03 06:58:24 +01:00
|
|
|
'noRedundantConditionAfterDocblockTypeNullCheck' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
|
|
|
/** @var ?int */
|
|
|
|
public $foo;
|
|
|
|
}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A|B $i
|
|
|
|
*/
|
2018-01-11 21:50:45 +01:00
|
|
|
function foo($i): void {
|
2017-12-03 06:58:24 +01:00
|
|
|
if (empty($i)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (get_class($i)) {
|
2018-11-29 06:05:56 +01:00
|
|
|
case A::class:
|
2017-12-03 06:58:24 +01:00
|
|
|
if ($i->foo) {}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}',
|
2018-02-07 00:44:53 +01:00
|
|
|
'assertions' => [],
|
2018-04-10 07:27:26 +02:00
|
|
|
'error_levels' => ['DocblockTypeContradiction'],
|
2017-12-03 06:58:24 +01:00
|
|
|
],
|
2017-12-03 07:24:47 +01:00
|
|
|
'noRedundantConditionTypeReplacementWithDocblock' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return A
|
|
|
|
*/
|
|
|
|
function getA() {
|
|
|
|
return new A();
|
|
|
|
}
|
|
|
|
|
|
|
|
$maybe_a = rand(0, 1) ? new A : null;
|
|
|
|
|
|
|
|
if ($maybe_a === null) {
|
|
|
|
$maybe_a = getA();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($maybe_a === null) {}',
|
2018-02-07 00:44:53 +01:00
|
|
|
'assertions' => [],
|
2018-02-07 21:20:47 +01:00
|
|
|
'error_levels' => [
|
|
|
|
'DocblockTypeContradiction',
|
|
|
|
],
|
2017-12-03 07:24:47 +01:00
|
|
|
],
|
2017-12-03 21:00:59 +01:00
|
|
|
'noRedundantConditionAfterPossiblyNullCheck' => [
|
|
|
|
'<?php
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$a = "hello";
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($a) {}',
|
|
|
|
'assertions' => [],
|
2017-12-06 06:56:00 +01:00
|
|
|
'error_levels' => ['PossiblyUndefinedGlobalVariable'],
|
2017-12-03 21:00:59 +01:00
|
|
|
],
|
2017-12-04 00:22:25 +01:00
|
|
|
'noRedundantConditionAfterFromDocblockRemoval' => [
|
|
|
|
'<?php
|
|
|
|
class A {
|
2018-09-10 06:13:59 +02:00
|
|
|
public function foo(): bool {
|
|
|
|
return (bool) rand(0, 1);
|
|
|
|
}
|
|
|
|
public function bar(): bool {
|
|
|
|
return (bool) rand(0, 1);
|
|
|
|
}
|
2017-12-04 00:22:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @return A */
|
|
|
|
function makeA() {
|
2018-09-10 06:13:59 +02:00
|
|
|
return new A;
|
2017-12-04 00:22:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$a = makeA();
|
|
|
|
|
|
|
|
if ($a === null) {
|
2018-09-10 06:13:59 +02:00
|
|
|
exit;
|
2017-12-04 00:22:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($a->foo() || $a->bar()) {}',
|
2018-02-07 00:44:53 +01:00
|
|
|
'assertions' => [],
|
2018-02-07 21:20:47 +01:00
|
|
|
'error_levels' => [
|
|
|
|
'DocblockTypeContradiction',
|
|
|
|
],
|
2017-12-04 00:22:25 +01:00
|
|
|
],
|
2017-12-06 06:35:41 +01:00
|
|
|
'noEmptyUndefinedArrayVar' => [
|
|
|
|
'<?php
|
|
|
|
if (rand(0,1)) {
|
2017-12-06 06:56:00 +01:00
|
|
|
/** @psalm-suppress UndefinedGlobalVariable */
|
2017-12-06 06:35:41 +01:00
|
|
|
$a = $b[0];
|
|
|
|
} else {
|
|
|
|
$a = null;
|
|
|
|
}
|
|
|
|
if ($a) {}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedAssignment', 'MixedArrayAccess'],
|
|
|
|
],
|
2017-12-09 21:51:38 +01:00
|
|
|
'noComplaintWithIsNumericThenIsEmpty' => [
|
|
|
|
'<?php
|
2018-01-11 21:50:45 +01:00
|
|
|
function takesString(string $s): void {
|
2017-12-09 21:51:38 +01:00
|
|
|
if (!is_numeric($s) || empty($s)) {}
|
|
|
|
}',
|
|
|
|
],
|
2017-12-10 23:36:33 +01:00
|
|
|
'noRedundantConditionOnTryCatchVars' => [
|
|
|
|
'<?php
|
2018-01-11 21:50:45 +01:00
|
|
|
function trycatch(): void {
|
2017-12-10 23:36:33 +01:00
|
|
|
$value = null;
|
|
|
|
try {
|
|
|
|
if (rand() % 2 > 0) {
|
|
|
|
throw new RuntimeException("Failed");
|
|
|
|
}
|
|
|
|
$value = new stdClass();
|
|
|
|
if (rand() % 2 > 0) {
|
|
|
|
throw new RuntimeException("Failed");
|
|
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
|
|
if ($value) {
|
|
|
|
var_export($value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($value) {}
|
|
|
|
}',
|
|
|
|
],
|
2017-12-11 17:32:14 +01:00
|
|
|
'noRedundantConditionInFalseCheck' => [
|
|
|
|
'<?php
|
|
|
|
$ch = curl_init();
|
|
|
|
if (!$ch) {}',
|
|
|
|
],
|
2017-12-14 04:04:37 +01:00
|
|
|
'noRedundantConditionInForCheck' => [
|
|
|
|
'<?php
|
|
|
|
class Node
|
|
|
|
{
|
|
|
|
/** @var Node|null */
|
|
|
|
public $next;
|
|
|
|
|
|
|
|
public function iterate(): void
|
|
|
|
{
|
|
|
|
for ($node = $this; $node !== null; $node = $node->next) {}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2017-12-14 17:23:20 +01:00
|
|
|
'noRedundantConditionComparingBool' => [
|
|
|
|
'<?php
|
2018-01-11 21:50:45 +01:00
|
|
|
function getBool(): bool {
|
2017-12-14 17:23:20 +01:00
|
|
|
return (bool)rand(0, 1);
|
|
|
|
}
|
|
|
|
|
2018-01-11 21:50:45 +01:00
|
|
|
function takesBool(bool $b): void {
|
2017-12-14 17:23:20 +01:00
|
|
|
if ($b === getBool()) {}
|
|
|
|
}',
|
|
|
|
],
|
2017-12-15 22:48:06 +01:00
|
|
|
'evaluateElseifProperly' => [
|
|
|
|
'<?php
|
|
|
|
/** @param string $str */
|
2018-01-11 21:50:45 +01:00
|
|
|
function foo($str): int {
|
2017-12-15 22:48:06 +01:00
|
|
|
if (is_null($str)) {
|
|
|
|
return 1;
|
|
|
|
} else if (strlen($str) < 1) {
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
return 2;
|
|
|
|
}',
|
2018-02-07 00:44:53 +01:00
|
|
|
'assertions' => [],
|
2018-02-07 21:20:47 +01:00
|
|
|
'error_levels' => [
|
|
|
|
'DocblockTypeContradiction',
|
|
|
|
'RedundantConditionGivenDocblockType',
|
|
|
|
],
|
2017-12-15 22:48:06 +01:00
|
|
|
],
|
2017-12-15 23:34:21 +01:00
|
|
|
'evaluateArrayCheck' => [
|
|
|
|
'<?php
|
2018-01-11 21:50:45 +01:00
|
|
|
function array_check(): void {
|
2017-12-15 23:34:21 +01:00
|
|
|
$data = ["f" => false];
|
|
|
|
while (rand(0, 1) > 0 && !$data["f"]) {
|
|
|
|
$data = ["f" => true];
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2018-01-08 05:59:17 +01:00
|
|
|
'mixedArrayAssignment' => [
|
|
|
|
'<?php
|
|
|
|
/** @param mixed $arr */
|
2018-01-11 21:50:45 +01:00
|
|
|
function foo($arr): void {
|
2018-01-08 05:59:17 +01:00
|
|
|
if ($arr["a"] === false) {
|
|
|
|
$arr["a"] = (bool) rand(0, 1);
|
|
|
|
if ($arr["a"] === false) {}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['MixedAssignment', 'MixedArrayAccess'],
|
|
|
|
],
|
2019-06-21 01:46:42 +02:00
|
|
|
'hardPhpTypeAssertionsOnDocblockBoolType' => [
|
|
|
|
'<?php
|
|
|
|
/** @param bool|null $bar */
|
|
|
|
function foo($bar): void {
|
|
|
|
if (!is_null($bar) && !is_bool($bar)) {
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($bar !== null) {}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
|
|
|
'error_levels' => ['DocblockTypeContradiction'],
|
|
|
|
],
|
|
|
|
'hardPhpTypeAssertionsOnDocblockStringType' => [
|
2018-01-08 16:32:58 +01:00
|
|
|
'<?php
|
|
|
|
/** @param string|null $bar */
|
2018-01-11 21:50:45 +01:00
|
|
|
function foo($bar): void {
|
2018-01-08 16:32:58 +01:00
|
|
|
if (!is_null($bar) && !is_string($bar)) {
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($bar !== null) {}
|
|
|
|
}',
|
2018-02-07 00:44:53 +01:00
|
|
|
'assertions' => [],
|
2019-02-27 20:02:02 +01:00
|
|
|
'error_levels' => ['DocblockTypeContradiction'],
|
2018-01-08 16:32:58 +01:00
|
|
|
],
|
2018-02-08 19:01:39 +01:00
|
|
|
'isObjectAssertionOnDocblockType' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
/** @param A|B $a */
|
|
|
|
function foo($a) : void {
|
|
|
|
if (!is_object($a)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($a instanceof A) {
|
|
|
|
|
|
|
|
} elseif ($a instanceof B) {
|
|
|
|
|
|
|
|
} else {
|
|
|
|
throw new \Exception("bad");
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'assertions' => [],
|
2018-04-10 07:27:26 +02:00
|
|
|
'error_levels' => ['RedundantConditionGivenDocblockType', 'DocblockTypeContradiction'],
|
2018-02-08 19:01:39 +01:00
|
|
|
],
|
2019-05-24 00:04:12 +02:00
|
|
|
'nullToMixedWithNullCheckWithArraykey' => [
|
2018-02-08 20:46:06 +01:00
|
|
|
'<?php
|
2019-05-24 00:04:12 +02:00
|
|
|
/** @return array<array-key, mixed> */
|
|
|
|
function getStrings(): array {
|
|
|
|
return ["hello", "world", 50];
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = getStrings();
|
|
|
|
|
|
|
|
if (is_string($a[0]) && strlen($a[0]) > 3) {}',
|
|
|
|
'assignments' => [],
|
|
|
|
'error_levels' => [],
|
|
|
|
],
|
|
|
|
'nullToMixedWithNullCheckWithIntKey' => [
|
|
|
|
'<?php
|
|
|
|
/** @return array<int, mixed> */
|
2018-02-08 20:46:06 +01:00
|
|
|
function getStrings(): array {
|
|
|
|
return ["hello", "world", 50];
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = getStrings();
|
|
|
|
|
|
|
|
if (is_string($a[0]) && strlen($a[0]) > 3) {}',
|
|
|
|
'assignments' => [],
|
|
|
|
'error_levels' => [],
|
|
|
|
],
|
2018-03-15 21:40:22 +01:00
|
|
|
'replaceFalseTypeWithTrueConditionalOnMixedEquality' => [
|
|
|
|
'<?php
|
|
|
|
function getData() {
|
|
|
|
return rand(0, 1) ? [1, 2, 3] : false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$a = false;
|
|
|
|
|
|
|
|
while ($i = getData()) {
|
|
|
|
if (!$a && $i[0] === 2) {
|
|
|
|
$a = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($a === false) {}
|
|
|
|
}',
|
|
|
|
'assignments' => [],
|
|
|
|
'error_levels' => ['MixedAssignment', 'MissingReturnType', 'MixedArrayAccess'],
|
|
|
|
],
|
2018-04-09 20:32:22 +02:00
|
|
|
'nullCoalescePossiblyUndefined' => [
|
|
|
|
'<?php
|
|
|
|
if (rand(0,1)) {
|
|
|
|
$options = ["option" => true];
|
|
|
|
}
|
|
|
|
|
|
|
|
$option = $options["option"] ?? false;
|
|
|
|
|
|
|
|
if ($option) {}',
|
|
|
|
'assignments' => [],
|
2018-05-31 00:56:44 +02:00
|
|
|
'error_levels' => ['MixedAssignment', 'MixedArrayAccess'],
|
2018-04-09 20:32:22 +02:00
|
|
|
],
|
2018-04-25 05:02:20 +02:00
|
|
|
'allowIntValueCheckAfterComparisonDueToOverflow' => [
|
|
|
|
'<?php
|
|
|
|
function foo(int $x) : void {
|
|
|
|
$x = $x + 1;
|
2018-04-25 05:12:01 +02:00
|
|
|
|
2018-04-25 05:02:20 +02:00
|
|
|
if (!is_int($x)) {
|
|
|
|
echo "Is a float.";
|
|
|
|
} else {
|
|
|
|
echo "Is an int.";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function bar(int $x) : void {
|
|
|
|
$x = $x + 1;
|
2018-04-25 05:12:01 +02:00
|
|
|
|
|
|
|
if (is_float($x)) {
|
|
|
|
echo "Is a float.";
|
|
|
|
} else {
|
|
|
|
echo "Is an int.";
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'allowIntValueCheckAfterComparisonDueToOverflowInc' => [
|
|
|
|
'<?php
|
|
|
|
function foo(int $x) : void {
|
|
|
|
$x++;
|
|
|
|
|
|
|
|
if (!is_int($x)) {
|
|
|
|
echo "Is a float.";
|
|
|
|
} else {
|
|
|
|
echo "Is an int.";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function bar(int $x) : void {
|
|
|
|
$x++;
|
|
|
|
|
2018-04-25 05:02:20 +02:00
|
|
|
if (is_float($x)) {
|
|
|
|
echo "Is a float.";
|
|
|
|
} else {
|
|
|
|
echo "Is an int.";
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'allowIntValueCheckAfterComparisonDueToConditionalOverflow' => [
|
|
|
|
'<?php
|
|
|
|
function foo(int $x) : void {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$x = $x + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_float($x)) {
|
|
|
|
echo "Is a float.";
|
|
|
|
} else {
|
|
|
|
echo "Is an int.";
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2018-05-05 22:17:54 +02:00
|
|
|
'changeStringValue' => [
|
|
|
|
'<?php
|
|
|
|
$concat = "";
|
|
|
|
foreach (["x", "y"] as $v) {
|
|
|
|
if ($concat != "") {
|
|
|
|
$concat .= ", ";
|
|
|
|
}
|
|
|
|
$concat .= "($v)";
|
|
|
|
}',
|
|
|
|
],
|
2018-05-20 02:31:48 +02:00
|
|
|
'arrayCanBeEmpty' => [
|
|
|
|
'<?php
|
|
|
|
$x = ["key" => "value"];
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$x = [];
|
|
|
|
}
|
|
|
|
if ($x) {
|
|
|
|
var_export($x);
|
|
|
|
}',
|
|
|
|
],
|
2018-05-24 18:23:50 +02:00
|
|
|
'arrayKeyExistsAccess' => [
|
|
|
|
'<?php
|
|
|
|
/** @param array<int, string> $arr */
|
|
|
|
function foo(array $arr) : void {
|
|
|
|
if (array_key_exists(1, $arr)) {
|
|
|
|
$a = ($arr[1] === "b") ? true : false;
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2018-06-17 19:20:37 +02:00
|
|
|
'noRedundantConditionStringNotFalse' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $s) : void {
|
|
|
|
if ($s != false ) {}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'noRedundantConditionStringNotTrue' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $s) : void {
|
|
|
|
if ($s != true ) {}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'noRedundantConditionBoolNotFalse' => [
|
|
|
|
'<?php
|
|
|
|
function foo(bool $s) : void {
|
|
|
|
if ($s !== false ) {}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'noRedundantConditionBoolNotTrue' => [
|
|
|
|
'<?php
|
|
|
|
function foo(bool $s) : void {
|
|
|
|
if ($s !== true ) {}
|
|
|
|
}',
|
|
|
|
],
|
2018-06-17 19:47:31 +02:00
|
|
|
'noRedundantConditionNullableBoolIsFalseOrTrue' => [
|
|
|
|
'<?php
|
|
|
|
function foo(?bool $s) : void {
|
|
|
|
if ($s === false ) {} elseif ($s === true) {}
|
|
|
|
}',
|
|
|
|
],
|
|
|
|
'noRedundantConditionNullableBoolIsTrueOrFalse' => [
|
|
|
|
'<?php
|
|
|
|
function foo(?bool $s) : void {
|
|
|
|
if ($s === true ) {} elseif ($s === false) {}
|
|
|
|
}',
|
|
|
|
],
|
2018-12-08 19:18:55 +01:00
|
|
|
'noRedundantConditionAfterCheckingMixedTwice' => [
|
|
|
|
'<?php
|
|
|
|
function foo($a) : void {
|
|
|
|
$b = $a ? 1 : 0;
|
|
|
|
$c = $a ? 1 : 0;
|
|
|
|
}',
|
|
|
|
[],
|
|
|
|
'error_levels' => ['MissingParamType'],
|
|
|
|
],
|
|
|
|
'notAlwaysTrueBinaryOp' => [
|
|
|
|
'<?php
|
|
|
|
function foo ($a) : void {
|
|
|
|
if (!$a) {}
|
|
|
|
$b = $a && rand(0, 1);
|
|
|
|
}',
|
|
|
|
[],
|
2019-03-23 19:27:54 +01:00
|
|
|
'error_levels' => ['MissingParamType'],
|
2018-12-08 19:18:55 +01:00
|
|
|
],
|
2018-12-19 22:10:09 +01:00
|
|
|
'noRedundantConditionAfterAssertingValue' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $t, bool $b) : void {
|
|
|
|
if (!$b && $t === "a") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($t === "c") {
|
|
|
|
if (!$b && bar($t)) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function bar(string $b) : bool {
|
|
|
|
return true;
|
|
|
|
}',
|
|
|
|
],
|
2019-01-08 00:22:42 +01:00
|
|
|
'noRedundantConditionBleed' => [
|
|
|
|
'<?php
|
|
|
|
$foo = getopt("i");
|
|
|
|
$i = $foo["i"];
|
|
|
|
|
|
|
|
/** @psalm-suppress TypeDoesNotContainNull */
|
|
|
|
if ($i === null) {
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($i) {}',
|
|
|
|
],
|
2019-02-18 17:39:05 +01:00
|
|
|
'emptyWithoutKnowingArrayType' => [
|
|
|
|
'<?php
|
|
|
|
function foo(array $a) : void {
|
|
|
|
if (!empty($a["foo"])) {
|
|
|
|
foreach ($a["foo"] as $key => $_) {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
unset($a["foo"][$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($a["foo"])) {}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
[],
|
2019-03-23 19:27:54 +01:00
|
|
|
['MixedAssignment', 'MixedArrayAccess', 'MixedArrayOffset'],
|
2019-02-18 17:39:05 +01:00
|
|
|
],
|
|
|
|
'emptyKnowingArrayType' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param array<string, array<string, int>> $a
|
|
|
|
*/
|
|
|
|
function foo(array $a) : void {
|
|
|
|
if (!empty($a["foo"])) {
|
|
|
|
foreach ($a["foo"] as $key => $_) {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
unset($a["foo"][$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($a["foo"])) {}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
],
|
2019-03-16 02:50:16 +01:00
|
|
|
'suppressRedundantConditionAfterAssertNonEmpty' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param array<int> $a
|
|
|
|
*/
|
|
|
|
function process(array $a): void {
|
|
|
|
assert(!empty($a));
|
|
|
|
/** @psalm-suppress RedundantConditionGivenDocblockType */
|
|
|
|
assert(is_int($a[0]));
|
|
|
|
}',
|
|
|
|
],
|
2019-03-26 03:30:40 +01:00
|
|
|
'allowChecksOnFalsyIf' => [
|
|
|
|
'<?php
|
|
|
|
function foo(?string $s) : string {
|
|
|
|
if ($s == null) {
|
|
|
|
if ($s === null) {}
|
|
|
|
|
|
|
|
return "hello";
|
|
|
|
} else {
|
|
|
|
return $s;
|
|
|
|
}
|
2019-07-05 22:24:00 +02:00
|
|
|
}',
|
2019-03-26 03:30:40 +01:00
|
|
|
],
|
2019-05-13 17:00:17 +02:00
|
|
|
'updateArrayAfterUnset' => [
|
2019-05-13 07:35:29 +02:00
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param string[] $arr
|
|
|
|
*/
|
|
|
|
function foo(string $s) : void {
|
|
|
|
$dict = ["a" => 1];
|
|
|
|
unset($dict[$s]);
|
|
|
|
if (count($dict)) {}
|
2019-07-05 22:24:00 +02:00
|
|
|
}',
|
2019-05-13 07:35:29 +02:00
|
|
|
],
|
2019-05-13 17:00:17 +02:00
|
|
|
'updateArrayAfterUnsetInLoop' => [
|
|
|
|
'<?php
|
|
|
|
/**
|
|
|
|
* @param string[] $arr
|
|
|
|
*/
|
|
|
|
function foo(array $arr) : void {
|
|
|
|
$dict = ["a" => 1, "b" => 2, "c" => 3];
|
|
|
|
|
|
|
|
foreach ($arr as $v) {
|
|
|
|
unset($dict[$v]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($dict)) {}
|
2019-07-05 22:24:00 +02:00
|
|
|
}',
|
2019-05-13 17:00:17 +02:00
|
|
|
],
|
2017-11-28 06:46:41 +01:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-03-01 21:55:20 +01:00
|
|
|
* @return iterable<string,array{string,error_message:string,2?:string[],3?:bool,4?:string}>
|
2017-11-28 06:46:41 +01:00
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
public function providerInvalidCodeParse()
|
2017-11-28 06:46:41 +01:00
|
|
|
{
|
|
|
|
return [
|
2017-12-09 20:53:39 +01:00
|
|
|
'ifFalse' => [
|
|
|
|
'<?php
|
2018-09-17 18:15:45 +02:00
|
|
|
$y = false;
|
2017-12-09 20:53:39 +01:00
|
|
|
if ($y) {}',
|
2018-04-10 07:27:26 +02:00
|
|
|
'error_message' => 'TypeDoesNotContainType',
|
2017-12-09 20:53:39 +01:00
|
|
|
],
|
|
|
|
'ifNotTrue' => [
|
|
|
|
'<?php
|
2018-09-17 18:15:45 +02:00
|
|
|
$y = true;
|
2017-12-09 20:53:39 +01:00
|
|
|
if (!$y) {}',
|
2018-04-10 07:27:26 +02:00
|
|
|
'error_message' => 'TypeDoesNotContainType',
|
2017-12-09 20:53:39 +01:00
|
|
|
],
|
|
|
|
'ifTrue' => [
|
|
|
|
'<?php
|
2018-09-17 18:15:45 +02:00
|
|
|
$y = true;
|
2017-12-09 20:53:39 +01:00
|
|
|
if ($y) {}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
2017-11-28 06:46:41 +01:00
|
|
|
'unnecessaryInstanceof' => [
|
|
|
|
'<?php
|
|
|
|
class One {
|
2018-02-07 21:20:47 +01:00
|
|
|
public function fooFoo() : void {}
|
2017-11-28 06:46:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$var = new One();
|
|
|
|
|
|
|
|
if ($var instanceof One) {
|
|
|
|
$var->fooFoo();
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'failedTypeResolution' => [
|
|
|
|
'<?php
|
|
|
|
class A { }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
function fooFoo(A $a) {
|
|
|
|
if ($a instanceof A) {
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'failedTypeResolutionWithDocblock' => [
|
|
|
|
'<?php
|
|
|
|
class A { }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A $a
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
function fooFoo(A $a) {
|
|
|
|
if ($a instanceof A) {
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'typeResolutionFromDocblockAndInstanceof' => [
|
|
|
|
'<?php
|
|
|
|
class A { }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A $a
|
|
|
|
* @return void
|
2018-02-07 21:20:47 +01:00
|
|
|
* @psalm-suppress RedundantConditionGivenDocblockType
|
2017-11-28 06:46:41 +01:00
|
|
|
*/
|
|
|
|
function fooFoo($a) {
|
|
|
|
if ($a instanceof A) {
|
|
|
|
if ($a instanceof A) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'typeResolutionRepeatingConditionWithSingleVar' => [
|
|
|
|
'<?php
|
|
|
|
$a = rand(0, 10) > 5;
|
|
|
|
if ($a && $a) {}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'typeResolutionRepeatingConditionWithVarInMiddle' => [
|
|
|
|
'<?php
|
|
|
|
$a = rand(0, 10) > 5;
|
|
|
|
$b = rand(0, 10) > 5;
|
|
|
|
if ($a && $b && $a) {}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
2018-05-07 20:52:45 +02:00
|
|
|
'typeResolutionRepeatingOredConditionWithSingleVar' => [
|
|
|
|
'<?php
|
|
|
|
$a = rand(0, 10) > 5;
|
|
|
|
if ($a || $a) {}',
|
|
|
|
'error_message' => 'ParadoxicalCondition',
|
|
|
|
],
|
|
|
|
'typeResolutionRepeatingOredConditionWithVarInMiddle' => [
|
|
|
|
'<?php
|
|
|
|
$a = rand(0, 10) > 5;
|
|
|
|
$b = rand(0, 10) > 5;
|
|
|
|
if ($a || $b || $a) {}',
|
|
|
|
'error_message' => 'ParadoxicalCondition',
|
|
|
|
],
|
2017-11-28 06:46:41 +01:00
|
|
|
'typeResolutionIsIntAndIsNumeric' => [
|
|
|
|
'<?php
|
|
|
|
$c = rand(0, 10) > 5 ? "hello" : 3;
|
|
|
|
if (is_int($c) && is_numeric($c)) {}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'typeResolutionWithInstanceOfAndNotEmpty' => [
|
|
|
|
'<?php
|
|
|
|
$x = rand(0, 10) > 5 ? new stdClass : null;
|
|
|
|
if ($x instanceof stdClass && $x) {}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'methodWithMeaninglessCheck' => [
|
|
|
|
'<?php
|
|
|
|
class One {
|
|
|
|
/** @return void */
|
|
|
|
public function fooFoo() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
class B {
|
|
|
|
/** @return void */
|
|
|
|
public function barBar(One $one) {
|
|
|
|
if (!$one) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
$one->fooFoo();
|
|
|
|
}
|
|
|
|
}',
|
2018-04-10 07:27:26 +02:00
|
|
|
'error_message' => 'TypeDoesNotContainType',
|
2017-11-28 06:46:41 +01:00
|
|
|
],
|
2018-05-07 07:26:06 +02:00
|
|
|
'twoVarLogicNotNestedWithElseifNegatedInIf' => [
|
2017-11-28 06:46:41 +01:00
|
|
|
'<?php
|
2018-01-11 21:50:45 +01:00
|
|
|
function foo(?string $a, ?string $b): ?string {
|
2017-11-28 06:46:41 +01:00
|
|
|
if ($a) {
|
|
|
|
$a = null;
|
|
|
|
} elseif ($b) {
|
|
|
|
// do nothing here
|
|
|
|
} else {
|
|
|
|
return "bad";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$a) return $b;
|
|
|
|
return $a;
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
2017-12-11 03:14:30 +01:00
|
|
|
'refineTypeInMethodCall' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/** @return ?A */
|
|
|
|
function getA() {
|
|
|
|
return rand(0, 1) ? new A : null;
|
|
|
|
}
|
|
|
|
|
2018-01-11 21:50:45 +01:00
|
|
|
function takesA(A $a): void {}
|
2017-12-11 03:14:30 +01:00
|
|
|
|
|
|
|
$a = getA();
|
|
|
|
if ($a instanceof A) {}
|
|
|
|
/** @psalm-suppress PossiblyNullArgument */
|
|
|
|
takesA($a);
|
|
|
|
if ($a instanceof A) {}',
|
2018-04-13 01:42:24 +02:00
|
|
|
'error_message' => 'RedundantCondition - src' . DIRECTORY_SEPARATOR . 'somefile.php:15',
|
2017-12-11 03:14:30 +01:00
|
|
|
],
|
2018-03-15 19:25:04 +01:00
|
|
|
'replaceFalseType' => [
|
|
|
|
'<?php
|
|
|
|
function foo(bool $b) : void {
|
|
|
|
if (!$b) {
|
|
|
|
$b = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($b) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'replaceTrueType' => [
|
|
|
|
'<?php
|
|
|
|
function foo(bool $b) : void {
|
|
|
|
if ($b) {
|
|
|
|
$b = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($b) {}
|
|
|
|
}',
|
2018-04-13 01:42:24 +02:00
|
|
|
'error_message' => 'TypeDoesNotContainType - src' . DIRECTORY_SEPARATOR . 'somefile.php:7',
|
2018-03-15 19:25:04 +01:00
|
|
|
],
|
2018-05-14 22:29:51 +02:00
|
|
|
'disallowFloatCheckAfterSettingToVar' => [
|
2018-04-25 05:02:20 +02:00
|
|
|
'<?php
|
|
|
|
function foo(int $x) : void {
|
|
|
|
if (rand(0, 1)) {
|
|
|
|
$x = 125;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_float($x)) {
|
|
|
|
echo "Is a float.";
|
|
|
|
} else {
|
|
|
|
echo "Is an int.";
|
|
|
|
}
|
|
|
|
}',
|
|
|
|
'error_message' => 'TypeDoesNotContainType - src' . DIRECTORY_SEPARATOR . 'somefile.php:7',
|
|
|
|
],
|
|
|
|
'disallowTwoIntValueChecksDueToConditionalOverflow' => [
|
|
|
|
'<?php
|
|
|
|
function foo(int $x) : void {
|
|
|
|
$x = $x + 1;
|
|
|
|
|
|
|
|
if (is_int($x)) {
|
|
|
|
} elseif (is_int($x)) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'TypeDoesNotContainType - src' . DIRECTORY_SEPARATOR . 'somefile.php:6',
|
|
|
|
],
|
2018-05-20 02:31:48 +02:00
|
|
|
'redundantEmptyArray' => [
|
|
|
|
'<?php
|
|
|
|
$x = ["key" => "value"];
|
|
|
|
if ($x) {
|
|
|
|
var_export($x);
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
2018-06-17 19:20:37 +02:00
|
|
|
'redundantConditionStringNotFalse' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $s) : void {
|
|
|
|
if ($s !== false ) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'redundantConditionStringNotTrue' => [
|
|
|
|
'<?php
|
|
|
|
function foo(string $s) : void {
|
|
|
|
if ($s !== true ) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'redundantConditionAfterRemovingFalse' => [
|
|
|
|
'<?php
|
|
|
|
$s = rand(0, 1) ? rand(0, 5) : false;
|
|
|
|
|
|
|
|
if ($s !== false) {
|
|
|
|
if (is_int($s)) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'redundantConditionAfterRemovingTrue' => [
|
|
|
|
'<?php
|
|
|
|
$s = rand(0, 1) ? rand(0, 5) : true;
|
|
|
|
|
|
|
|
if ($s !== true) {
|
|
|
|
if (is_int($s)) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
2018-06-26 00:02:05 +02:00
|
|
|
'impossibleNullEquality' => [
|
|
|
|
'<?php
|
|
|
|
$i = 5;
|
|
|
|
echo $i !== null;',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'impossibleTrueEquality' => [
|
|
|
|
'<?php
|
|
|
|
$i = 5;
|
|
|
|
echo $i !== true;',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'impossibleFalseEquality' => [
|
|
|
|
'<?php
|
|
|
|
$i = 5;
|
|
|
|
echo $i !== false;',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
|
|
|
'impossibleNumberEquality' => [
|
|
|
|
'<?php
|
|
|
|
$i = 5;
|
|
|
|
echo $i !== 3;',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
2018-12-08 19:18:55 +01:00
|
|
|
'alwaysTrueBinaryOp' => [
|
|
|
|
'<?php
|
|
|
|
function foo ($a) : void {
|
|
|
|
if (!$a) return;
|
|
|
|
$b = $a && rand(0, 1);
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
2019-03-23 19:27:54 +01:00
|
|
|
'error_levels' => ['MissingParamType'],
|
2018-12-08 19:18:55 +01:00
|
|
|
],
|
2019-03-16 17:34:48 +01:00
|
|
|
'negatedInstanceof' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
class B {}
|
|
|
|
|
|
|
|
function foo(A $a) : void {
|
|
|
|
if (!$a instanceof B) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantCondition',
|
|
|
|
],
|
2019-03-26 03:30:40 +01:00
|
|
|
'redundantInstanceof' => [
|
|
|
|
'<?php
|
|
|
|
/** @param Exception $a */
|
|
|
|
function foo($a) : void {
|
|
|
|
if ($a instanceof \Exception) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'RedundantConditionGivenDocblockType',
|
|
|
|
],
|
|
|
|
'preventDocblockTypesBeingIdenticalToTrue' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A $a
|
|
|
|
*/
|
|
|
|
function foo($a, $b) : void {
|
|
|
|
if ($a === true) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'DocblockTypeContradiction',
|
|
|
|
],
|
|
|
|
'preventDocblockTypesBeingIdenticalToTrueReversed' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A $a
|
|
|
|
*/
|
|
|
|
function foo($a, $b) : void {
|
|
|
|
if (true === $a) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'DocblockTypeContradiction',
|
|
|
|
],
|
|
|
|
'preventDocblockTypesBeingIdenticalToFalse' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A $a
|
|
|
|
*/
|
|
|
|
function foo($a, $b) : void {
|
|
|
|
if ($a === false) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'DocblockTypeContradiction',
|
|
|
|
],
|
|
|
|
'preventDocblockTypesBeingIdenticalToFalseReversed' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A $a
|
|
|
|
*/
|
|
|
|
function foo($a, $b) : void {
|
|
|
|
if (false === $a) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'DocblockTypeContradiction',
|
|
|
|
],
|
|
|
|
'preventDocblockTypesBeingSameAsEmptyArrayReversed' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A $a
|
|
|
|
*/
|
|
|
|
function foo($a, $b) : void {
|
|
|
|
if ([] == $a) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'DocblockTypeContradiction',
|
|
|
|
],
|
|
|
|
'preventDocblockTypesBeingIdenticalToEmptyArrayReversed' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param A $a
|
|
|
|
*/
|
|
|
|
function foo($a, $b) : void {
|
|
|
|
if ([] === $a) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'DocblockTypeContradiction',
|
|
|
|
],
|
|
|
|
'preventTypesBeingIdenticalToEmptyArrayReversed' => [
|
|
|
|
'<?php
|
|
|
|
class A {}
|
|
|
|
|
|
|
|
function foo(A $a, $b) : void {
|
|
|
|
if ([] === $a) {}
|
|
|
|
}',
|
|
|
|
'error_message' => 'TypeDoesNotContainType',
|
|
|
|
],
|
2017-11-28 06:46:41 +01:00
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|