[ 'code' => ' [ 'code' => ' [ 'code' => ' [ '$moo' => 'int', ], ], 'switchVariableWithFallthroughStatement' => [ 'code' => ' [ '$moo' => 'int', ], ], 'secondLoopWithNotNullCheck' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ '$b' => 'bool', ], ], 'assignInsideForeachWithBreak' => [ 'code' => ' [ '$b' => 'bool', ], ], 'nullCheckInsideForeachWithContinue' => [ 'code' => ' */ public static function loadMultiple() { return [new A, null]; } /** @return void */ public function barBar() { } } foreach (A::loadMultiple() as $a) { if ($a === null) { continue; } $a->barBar(); }', ], 'loopWithArrayKey' => [ 'code' => '>> $args * @return array[] */ function get_merged_dict(array $args) { $merged = array(); foreach ($args as $group) { foreach ($group as $key => $value) { if (isset($merged[$key])) { $merged[$key] = array_merge($merged[$key], $value); } else { $merged[$key] = $value; } } } return $merged; }', ], 'loopWithIfElseNoParadox' => [ 'code' => ' 5; foreach ([1, 2, 3] as $i) { if (rand(0, 5)) { $a[] = 5; continue; } if ($b) { continue; // if this is removed, no failure } else {} // if else is removed, no failure } if ($a) {}', ], 'bleedVarIntoOuterContextWithEmptyLoop' => [ 'code' => ' [ '$tag' => 'string', ], ], 'bleedVarIntoOuterContextWithRedefinedAsNull' => [ 'code' => ' [ '$tag' => 'null', ], ], 'bleedVarIntoOuterContextWithRedefinedAsNullAndBreak' => [ 'code' => ' [ '$tag' => 'null', ], ], 'bleedVarIntoOuterContextWithBreakInElse' => [ 'code' => ' [ '$tag' => 'null|string', ], ], 'bleedVarIntoOuterContextWithBreakInIf' => [ 'code' => ' [ '$tag' => 'null|string', ], ], 'bleedVarIntoOuterContextWithBreakInElseAndIntSet' => [ 'code' => ' [ '$tag' => 'int|null|string', ], ], 'bleedVarIntoOuterContextWithRedefineAndBreak' => [ 'code' => ' [ '$tag' => 'null', ], ], 'nullToMixedWithNullCheckNoContinue' => [ 'code' => ' [ '$a' => 'mixed', ], 'ignored_issues' => [ 'MixedAssignment', ], ], 'noMixedAssigmentWithIfAssertion' => [ 'code' => 'getProperties() as $property) { $message = $property->getValue($reflection->newInstance()); if (!is_string($message)) { throw new RuntimeException(); } }', ], 'noMixedAssigmentWithAssertion' => [ 'code' => 'getProperties() as $property) { $message = $property->getValue($reflection->newInstance()); assert(is_string($message)); }', ], 'nullToMixedWithNullCheckAndContinue' => [ 'code' => ' [ '$a' => 'mixed', ], 'ignored_issues' => [ 'MixedAssignment', ], ], 'falseToBoolExplicitBreak' => [ 'code' => ' [ '$a' => 'bool', ], ], 'falseToBoolExplicitContinue' => [ 'code' => ' [ '$a' => 'bool', ], ], 'falseToBoolInBreak' => [ 'code' => ' [ '$a' => 'bool', ], ], 'falseToBoolInContinue' => [ 'code' => ' [ '$a' => 'bool', ], ], 'falseToBoolInBreakAndContinue' => [ 'code' => ' [ '$a' => 'bool', ], ], 'falseToBoolInNestedForeach' => [ 'code' => ' [ '$a' => 'bool', ], ], 'falseToBoolAfterContinueAndBreak' => [ 'code' => ' 1) { $a = true; continue; } break; }', 'assertions' => [ '$a' => 'bool', ], ], 'variableDefinedInForeachAndIf' => [ 'code' => ' [ 'code' => ' [ 'code' => ' $value) { if ($value["foo"]) {} $r[] = $key; } }', 'assertions' => [], 'ignored_issues' => [ 'MixedAssignment', 'MixedArrayAccess', ], ], 'foreachLoopWithOKManipulation' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [], 'ignored_issues' => [ 'MixedAssignment', 'UndefinedThisPropertyAssignment', ], ], 'intersectionIterator' => [ 'code' => '&\Countable $object */ function doSomethingUseful($object) : void { echo count($object); foreach ($object as $foo) {} }', ], 'rawIteratorIteration' => [ 'code' => ' */ function getIterator(): Iterator { return new ArrayIterator([new Item()]); } foreach (getIterator() as $item) { echo $item->prop; }', ], 'seekableIteratorIteration' => [ 'code' => ' */ function getIterator(): \SeekableIterator { return new ArrayIterator([new Item()]); } foreach (getIterator() as $item) { echo $item->prop; }', ], 'arrayIteratorIteration' => [ 'code' => ' */ function getIterator(): \SeekableIterator { return new ArrayIterator([new Item()]); } foreach (getIterator() as $item) { echo $item->prop; }', ], 'templatedIteratorAggregateIteration' => [ 'code' => ' */ class Collection implements IteratorAggregate { /** * @var Item[] */ private $items = []; public function add(Item $item): void { $this->items[] = $item; } /** * @return ArrayIterator */ public function getIterator(): \ArrayIterator { return new ArrayIterator($this->items); } } $collection = new Collection(); $collection->add(new Item()); foreach ($collection as $item) { echo $item->prop; }', ], 'foreachIntersectionTraversable' => [ 'code' => ' */ $c = null; foreach ($c as $i) {}', ], 'iterateOverNonEmptyConstant' => [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => ' [ 'code' => '> */ function Foo(int $a, int $b, int ...$ints) : array { array_unshift($ints, $a, $b); return array_chunk($ints, 2); } foreach(Foo(1, 2, 3, 4, 5) as $ints) { echo $ints[0], ", ", ($ints[1] ?? "n/a"), "\n"; }', ], 'iteratorClassCurrent' => [ 'code' => 'items[] = $item; } /** * @return Value[] */ public function toArray(): array { return $this->items; } public function getIterator(): ValueCollectionIterator { return new ValueCollectionIterator($this); } public function count(): int { return \count($this->items); } public function isEmpty(): bool { return empty($this->items); } public function contains(Value $item): bool { foreach ($this->items as $_item) { if ($_item === $item) { return true; } } return false; } } /** * @psalm-suppress MissingTemplateParam */ class ValueCollectionIterator implements \Countable, \Iterator { /** * @var Value[] */ private $items; /** * @var int */ private $position = 0; public function __construct(ValueCollection $collection) { $this->items = $collection->toArray(); } public function count(): int { return \iterator_count($this); } public function rewind(): void { $this->position = 0; } public function valid(): bool { return $this->position < \count($this->items); } public function key(): int { return $this->position; } public function current(): Value { return $this->items[$this->position]; } public function next(): void { $this->position++; } } function foo(ValueCollection $v) : void { foreach ($v as $value) {} }', ], 'possibleRawObjectIterationFromIssetSuppressed' => [ 'code' => ' [ 'code' => ' [ 'code' => ' $arr */ function foo(Iterator $arr) : void { foreach ($arr as $a) {} }' ], 'foreachLoopInvalidation' => [ 'code' => ' [ 'code' => ' [ 'code' => ' */ class FooIterator implements \Iterator { private ?int $key = null; public function current(): string { return "a"; } public function next(): void { $this->key = $this->key === null ? 0 : $this->key + 1; } public function key(): int { if ($this->key === null) { throw new \Exception(); } return $this->key; } public function valid(): bool { return $this->key !== null && $this->key <= 3; } public function rewind(): void { $this->key = null; $this->next(); } } foreach (new FooIterator() as $key => $value) { echo $key . " " . $value; }' ], 'loopClosure' => [ 'code' => ' $currentIndexes */ function cartesianProduct(array $currentIndexes): void { while (rand(0, 1)) { array_map( function ($index) { echo $index; }, $currentIndexes ); /** @psalm-suppress PossiblyUndefinedArrayOffset */ $currentIndexes[0]++; } }' ], 'loopCanUpdateOuterWithoutBreak' => [ 'code' => ' $mappings */ function foo(string $id, array $mappings) : void { if ($id === "a") { foreach ($mappings as $value) { $id = $value; } } if (is_int($id)) {} }' ], 'loopCanUpdateOuterWithBreak' => [ 'code' => ' $mappings */ function foo(string $id, array $mappings) : void { if ($id === "a") { foreach ($mappings as $value) { if (rand(0, 1)) { $id = $value; break; } } } if (is_int($id)) {} }' ], 'loopCanUpdateOuterWithContinue' => [ 'code' => ' $mappings */ function foo(string $id, array $mappings) : void { if ($id === "a") { foreach ($mappings as $value) { if (rand(0, 1)) { $id = $value; continue; } } } if (is_int($id)) {} }' ], 'loopVarRedefinedAtLoopStart' => [ 'code' => ' $files */ function foo(array $files): void { $file = reset($files); foreach ($files as $file) { strlen($file); $file = 0; } }' ], ]; } public function providerInvalidCodeParse(): iterable { return [ 'switchVariableWithContinueOnce' => [ 'code' => ' 'PossiblyUndefinedGlobalVariable', ], 'possiblyUndefinedArrayInForeach' => [ 'code' => ' 'PossiblyUndefinedGlobalVariable - src' . DIRECTORY_SEPARATOR . 'somefile.php:3:25 - Possibly undefined ' . 'global variable $array, first seen on line 3', ], 'possibleUndefinedVariableInForeachAndIfWithBreak' => [ 'code' => ' 'PossiblyUndefinedGlobalVariable - src' . DIRECTORY_SEPARATOR . 'somefile.php:9:26 - Possibly undefined ' . 'global variable $a, first seen on line 4', ], 'possibleUndefinedVariableInForeachAndIf' => [ 'code' => ' 'PossiblyUndefinedGlobalVariable - src' . DIRECTORY_SEPARATOR . 'somefile.php:7:30 - Possibly undefined ' . 'global variable $a, first seen on line 4', ], 'implicitFourthLoopWithBadReturnType' => [ 'code' => ' 'InvalidReturnStatement', ], 'possiblyNullCheckInsideForeachWithNoLeaveStatement' => [ 'code' => ' */ public static function loadMultiple() { return [new A, null]; } /** @return void */ public function barBar() { } } foreach (A::loadMultiple() as $a) { if ($a === null) { // do nothing } $a->barBar(); }', 'error_message' => 'PossiblyNullReference', ], 'redundantConditionInForeachIf' => [ 'code' => ' 'RedundantCondition', ], 'redundantConditionInForeachWithIfElse' => [ 'code' => ' 'RedundantCondition', ], 'possiblyUndefinedVariableInForeachDueToBreakBefore' => [ 'code' => ' 'PossiblyUndefinedGlobalVariable', ], 'continueOutsideLoop' => [ 'code' => ' 'ContinueOutsideLoop', ], 'invalidIterator' => [ 'code' => ' 'InvalidIterator', ], 'rawObjectIteration' => [ 'code' => ' 'RawObjectIteration', ], 'possibleRawObjectIteration' => [ 'code' => ' 5) { $arr[] = new A; } else { $arr = new B; } foreach ($arr as $a) { bar($a); }', 'error_message' => 'PossibleRawObjectIteration', ], 'possibleRawObjectIterationFromIsset' => [ 'code' => ' 'PossibleRawObjectIteration', ], 'ifSpecificNonEmptyValues' => [ 'code' => ' 'RedundantCondition', ], 'ifSpecificNonEmptyStringValues' => [ 'code' => ' 'RedundantCondition', ], ]; } }