Skip to content

Commit

Permalink
Merge pull request #9380 from weirdan/array-in-foreach-is-not-empty
Browse files Browse the repository at this point in the history
  • Loading branch information
weirdan committed Feb 23, 2023
2 parents 2cf3db6 + 5ccbc23 commit d0a1400
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 4 deletions.
3 changes: 0 additions & 3 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,6 @@
</RedundantCondition>
</file>
<file src="src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php">
<PossiblyInvalidArrayOffset>
<code>$fixed_type_tokens[$i - 1]</code>
</PossiblyInvalidArrayOffset>
<PossiblyUndefinedArrayOffset>
<code>$source_param_string</code>
</PossiblyUndefinedArrayOffset>
Expand Down
1 change: 1 addition & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
</plugins>

<issueHandlers>
<Trace errorLevel="error"/>
<PossiblyNullOperand errorLevel="suppress"/>

<DeprecatedMethod>
Expand Down
15 changes: 15 additions & 0 deletions src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
use Psalm\Internal\Scope\LoopScope;
use Psalm\Internal\Type\AssertionReconciler;
use Psalm\Internal\Type\Comparator\AtomicTypeComparator;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Issue\ImpureMethodCall;
Expand All @@ -36,6 +37,7 @@
use Psalm\IssueBuffer;
use Psalm\Node\Expr\VirtualMethodCall;
use Psalm\Node\VirtualIdentifier;
use Psalm\Storage\Assertion;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\Scalar;
Expand Down Expand Up @@ -268,6 +270,19 @@ public static function analyze(
$foreach_context->vars_in_scope[$context_var_id] = $context_type;
}

if ($var_id && $foreach_context->hasVariable($var_id)) {
// refine the type of the array variable we iterate over
// if we entered loop body, the array cannot be empty
$foreach_context->vars_in_scope[$var_id] = AssertionReconciler::reconcile(
new Assertion\NonEmpty(),
$foreach_context->vars_in_scope[$var_id],
null,
$statements_analyzer,
true, // inside loop ?
$statements_analyzer->getTemplateTypeMap() ?? [],
);
}

$foreach_context->inside_loop = true;
$foreach_context->break_types[] = 'loop';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,8 @@ private static function getConditionalSanitizedTypeTokens(

// spaces are allowed before $foo in get(string $foo) magic method
// definitions, but we want to remove them in this instance
if (isset($fixed_type_tokens[$i - 1])
if ($i > 0
&& isset($fixed_type_tokens[$i - 1])
&& $fixed_type_tokens[$i - 1][0][0] === ' '
) {
unset($fixed_type_tokens[$i - 1]);
Expand Down
24 changes: 24 additions & 0 deletions tests/Loop/ForeachTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,18 @@ function foo(array $files): void
}
}',
],
'arrayIsNotEmptyInForeachLoop' => [
'code' => <<<'PHP'
<?php
/** @return non-empty-array */
function f(array $a): array {
foreach ($a as $_) {
return $a;
}
throw new RuntimeException;
}
PHP,
],
];
}

Expand Down Expand Up @@ -1373,6 +1385,18 @@ function foo(array $a) : void {
if ($a) {}',
'error_message' => 'RedundantCondition',
],
'arrayCanBeEmptyOutsideTheLoop' => [
'code' => <<<'PHP'
<?php
/** @return non-empty-array */
function f(array $a): array {
foreach ($a as $_) {
}
return $a;
}
PHP,
'error_message' => 'LessSpecificReturnStatement',
],
];
}
}

0 comments on commit d0a1400

Please sign in to comment.