1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00
psalm/tests/Loop/DoTest.php
Daniil Gentili 1986c8b4a8
Add support for strict arrays, fix type alias intersection, fix array_is_list assertion on non-lists (#8395)
* Immutable CodeLocation

* Remove excess clones

* Remove external clones

* Remove leftover clones

* Fix final clone issue

* Immutable storages

* Refactoring

* Fixes

* Fixes

* Fix

* Fix

* Fixes

* Simplify

* Fixes

* Fix

* Fixes

* Update

* Fix

* Cache global types

* Fix

* Update

* Update

* Fixes

* Fixes

* Refactor

* Fixes

* Fix

* Fix

* More caching

* Fix

* Fix

* Update

* Update

* Fix

* Fixes

* Update

* Refactor

* Update

* Fixes

* Break one more test

* Fix

* FIx

* Fix

* Fix

* Fix

* Fix

* Improve performance and readability

* Equivalent logic

* Fixes

* Revert

* Revert "Revert"

This reverts commit f9175100c8452c80559234200663fd4c4f4dd889.

* Fix

* Fix reference bug

* Make default TypeVisitor immutable

* Bugfix

* Remove clones

* Partial refactoring

* Refactoring

* Fixes

* Fix

* Fixes

* Fixes

* cs-fix

* Fix final bugs

* Add test

* Misc fixes

* Update

* Fixes

* Experiment with removing different property

* revert "Experiment with removing different property"

This reverts commit ac1156e077fc4ea633530d51096d27b6e88bfdf9.

* Uniform naming

* Uniform naming

* Hack hotfix

* Clean up $_FILES ref #8621

* Undo hack, try fixing properly

* Helper method

* Remove redundant call

* Partially fix bugs

* Cleanup

* Change defaults

* Fix bug

* Fix (?, hope this doesn't break anything else)

* cs-fix

* Review fixes

* Bugfix

* Bugfix

* Improve logic

* Add support for list{} and callable-list{} types, properly implement array_is_list assertions (fixes #8389)

* Default to sealed arrays

* Fix array_merge bug

* Fixes

* Fix

* Sealed type checks

* Properly infer properties-of and get_object_vars on final classes

* Fix array_map zipping

* Fix tests

* Fixes

* Fixes

* Fix more stuff

* Recursively resolve type aliases

* Fix typo

* Fixes

* Fix array_is_list assertion on keyed array

* Add BC docs

* Fixes

* fix

* Update

* Update

* Update

* Update

* Seal arrays with count assertions

* Fix #8528

* Fix

* Update

* Improve sealed array foreach logic

* get_object_vars on template properties

* Fix sealed array assertion reconciler logic

* Improved reconciler

* Add tests

* Single source of truth for test types

* Fix tests

* Fixup tests

* Fixup tests

* Fixup tests

* Update

* Fix tests

* Fix tests

* Final fixes

* Fixes

* Use list syntax only when needed

* Fix tests

* Cs-fix

* Update docs

* Update docs

* Update docs

* Update docs

* Update docs

* Document missing types

* Update docs

* Improve class-string-map docs

* Update

* Update

* I love working on psalm :)

* Keep arrays unsealed by default

* Fixup tests

* Fix syntax mistake

* cs-fix

* Fix typo

* Re-import missing types

* Keep strict types only in return types

* argc/argv fixes

* argc/argv fixes

* Fix test

* Comment-out valinor code, pinging @romm pls merge https://github.com/CuyZ/Valinor/pull/246 so we can add valinor to the psalm docs :)
2022-11-05 22:34:42 +01:00

428 lines
13 KiB
PHP

<?php
namespace Psalm\Tests\Loop;
use Psalm\Tests\TestCase;
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;
use const DIRECTORY_SEPARATOR;
class DoTest extends TestCase
{
use InvalidCodeAnalysisTestTrait;
use ValidCodeAnalysisTestTrait;
/**
*
*/
public function providerValidCodeParse(): iterable
{
return [
'doWhileVar' => [
'code' => '<?php
$worked = false;
do {
$worked = true;
}
while (rand(0,100) === 10);',
'assertions' => [
'$worked' => 'true',
],
],
'doWhileVarWithPossibleBreak' => [
'code' => '<?php
$a = false;
do {
if (rand(0, 1)) {
break;
}
if (rand(0, 1)) {
$a = true;
break;
}
$a = true;
}
while (rand(0,100) === 10);',
'assertions' => [
'$a' => 'bool',
],
],
'SKIPPED-doWhileVarWithPossibleBreakThatSetsToTrue' => [
'code' => '<?php
$a = false;
$b = false;
do {
$b = true;
if (rand(0, 1)) {
$a = true;
break;
}
$a = true;
}
while (rand(0,1));',
'assertions' => [
'$a' => 'true',
'$b' => 'true',
],
],
'doWhileVarWithPossibleBreakThatMaybeSetsToTrue' => [
'code' => '<?php
$a = false;
do {
if (rand(0, 1)) {
if (rand(0, 1)) {
$a = true;
}
break;
}
$a = true;
}
while (rand(0,1));',
'assertions' => [
'$a' => 'bool',
],
],
'doWhileVarWithPossibleInitialisingBreakNoInitialDefinition' => [
'code' => '<?php
do {
if (rand(0, 1)) {
$worked = true;
break;
}
$worked = true;
}
while (rand(0,100) === 10);',
'assertions' => [
'$worked' => 'true',
],
],
'doWhileUndefinedVar' => [
'code' => '<?php
do {
$result = (bool) rand(0,1);
} while (!$result);',
'assertions' => [
'$result' => 'true',
],
],
'doWhileVarAndBreak' => [
'code' => '<?php
/** @return void */
function foo(string $b) {}
do {
if (null === ($a = rand(0, 1) ? "hello" : null)) {
break;
}
/** @psalm-suppress MixedArgument */
foo($a);
}
while (rand(0,100) === 10);',
],
'doWhileWithNotEmptyCheck' => [
'code' => '<?php
class A {
/** @var A|null */
public $a;
public function __construct() {
$this->a = rand(0, 1) ? new A : null;
}
}
function takesA(A $a): void {}
$a = new A();
do {
takesA($a);
$a = $a->a;
} while ($a);',
'assertions' => [
'$a' => 'null',
],
],
'doWhileWithMethodCall' => [
'code' => '<?php
class A {
public function getParent(): ?A {
return rand(0, 1) ? new A() : null;
}
}
$a = new A();
do {
$a = $a->getParent();
} while ($a);',
'assertions' => [
'$a' => 'null',
],
],
'doWhileFirstGood' => [
'code' => '<?php
do {
$done = rand(0, 1) > 0;
} while (!$done);',
],
'doWhileWithIfException' => [
'code' => '<?php
class A
{
/**
* @var null|A
*/
public $parent;
public static function foo(A $a) : void
{
do {
if ($a->parent === null) {
throw new \Exception("bad");
}
$a = $a->parent;
} while (rand(0,1));
}
}',
],
'doWhileWithIfExceptionOutside' => [
'code' => '<?php
class A
{
/**
* @var null|A
*/
public $parent;
public static function foo(A $a) : void
{
if ($a->parent === null) {
throw new \Exception("bad");
}
do {
$a = $a->parent;
} while ($a->parent && rand(0, 1));
}
}',
],
'doWhileDefinedVar' => [
'code' => '<?php
$value = null;
do {
$count = rand(0, 1);
$value = 6;
} while ($count);',
],
'doWhileDefinedVarWithPossibleBreak' => [
'code' => '<?php
$value = null;
do {
if (rand(0, 1)) {
break;
}
$count = rand(0, 1);
$value = 6;
} while ($count);',
],
'invalidateBothByRefAssignmentsInDo' => [
'code' => '<?php
function foo(?string &$i) : void {}
function bar(?string &$i) : void {}
$c = null;
do {
if (!$c) {
foo($c);
} else {
bar($c);
}
} while (rand(0, 1));',
],
'doParentCall' => [
'code' => '<?php
class A {
/** @return A|false */
public function getParent() {
return rand(0, 1) ? new A : false;
}
}
$a = new A();
do {
$a = $a->getParent();
} while ($a !== false);',
],
'doCallInWhile' => [
'code' => '<?php
class A {
public function getParent() : ?A {
return rand(0, 1) ? new A : null;
}
}
$a = new A();
$i = 0;
do {
$i++;
} while ($a = $a->getParent());',
],
'doWithContinue' => [
'code' => '<?php
do {
if (rand(0, 1)) {
continue;
}
} while (rand(0, 1));',
],
'noEmptyArrayAccessComplaintInsideDo' => [
'code' => '<?php
$foo = [];
do {
if (isset($foo["bar"])) {}
$foo["bar"] = "bat";
} while (rand(0, 1));',
],
'noRedundantConditionAfterDoWhile' => [
'code' => '<?php
$i = 5;
do {} while (--$i > 0);
echo $i === 0;',
],
'doWhileNonInfinite' => [
'code' => '<?php
function foo(): int {
do {
$value = mt_rand(0, 10);
if ($value > 5) continue;
break;
} while (true);
return $value;
}',
],
'doNoRedundant' => [
'code' => '<?php
class Event {}
function fetchEvent(): ?Event {
return rand(0, 1) ? new Event() : null;
}
function nextEvent(bool $c): void {
do {
$e = fetchEvent();
} while ($c && $e);
}',
],
'doConditionInWhileAndIfWithSingleVar' => [
'code' => '<?php
$b = !!rand(0, 1);
do {
if (!$b) {
$b = !rand(0, 1);
}
} while (!$b);',
'assertions' => [
'$b' => 'true'
]
],
'doConditionInWhileAndIfWithTwoVars' => [
'code' => '<?php
$b = !!rand(0, 1);
do {
$s = rand(0, 1);
if (!$b && $s) {}
} while (!$b && $s);
if ($b) {}'
],
'regularAssignmentInsideDo' => [
'code' => '<?php
do {
$code = rand(0, 1);
echo "here";
} while ($code === 1);'
],
'destructuringAssignmentInsideDo' => [
'code' => '<?php
do {
[$code] = [rand(0, 1)];
echo "here";
} while ($code === 1);'
],
];
}
/**
*
*/
public function providerInvalidCodeParse(): iterable
{
return [
'doWhileVarWithPossibleBreakWithoutDefining' => [
'code' => '<?php
do {
if (rand(0, 1)) {
break;
}
$worked = true;
}
while (rand(0,1));
echo $worked;',
'error_message' => 'PossiblyUndefinedGlobalVariable',
],
'doWhileVarWithPossibleBreakThatMaybeSetsToTrueWithoutDefining' => [
'code' => '<?php
do {
if (rand(0, 1)) {
if (rand(0, 1)) {
$a = true;
}
break;
}
$a = true;
}
while (rand(0,1));
echo $a;',
'error_message' => 'PossiblyUndefinedGlobalVariable',
],
'SKIPPED-doWhileVarWithPossibleContinueWithoutDefining' => [
'code' => '<?php
do {
if (rand(0, 1)) {
continue;
}
$worked = true;
}
while (rand(0,1));
echo $worked;',
'error_message' => 'PossiblyUndefinedGlobalVariable',
],
'possiblyUndefinedArrayInDo' => [
'code' => '<?php
do {
$array[] = "hello";
} while (rand(0, 1));',
'error_message' => 'PossiblyUndefinedGlobalVariable - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:25 - Possibly undefined ' .
'global variable $array, first seen on line 3',
],
];
}
}