Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ When assigning to an array offset, NodeScopeResolver must distinguish:

Misusing these leads to false positives like "might not be a list" or incorrect offset-exists checks. The fix is in `NodeScopeResolver` where property/variable assignments are processed.

This distinction also applies in `MutatingScope::enterForeach()`. When a foreach loop iterates by reference (`foreach ($list as &$value)`), modifying `$value` changes an existing offset, not a new one. The `IntertwinedVariableByReferenceWithExpr` created for the no-key by-reference case must use `SetExistingOffsetValueTypeExpr` (not `SetOffsetValueTypeExpr`) so that `AccessoryArrayListType::setExistingOffsetValueType()` preserves the list type. Using `SetOffsetValueTypeExpr` causes `AccessoryArrayListType::setOffsetValueType()` to return `ErrorType` for non-null/non-zero offsets, destroying the list type in the intersection.

### ConstantArrayType and offset tracking

Many bugs involve `ConstantArrayType` (array shapes with known keys). Common issues:
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -3023,7 +3023,7 @@ public function enterForeach(self $originalScope, Expr $iteratee, string $valueN
);
if ($valueByRef && $iterateeType->isArray()->yes() && $iterateeType->isConstantArray()->no()) {
$scope = $scope->assignExpression(
new IntertwinedVariableByReferenceWithExpr($valueName, $iteratee, new SetOffsetValueTypeExpr(
new IntertwinedVariableByReferenceWithExpr($valueName, $iteratee, new SetExistingOffsetValueTypeExpr(
$iteratee,
new GetIterableKeyTypeExpr($iteratee),
new Variable($valueName),
Expand Down
53 changes: 53 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-13809.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types = 1);

namespace Bug13809;

use function PHPStan\Testing\assertType;

/**
* @param list<mixed> $list
*/
function foo(array $list): void
{
foreach ($list as &$value) {
$value = 'foo';
}

assertType('list<mixed>', $list);
}

/**
* @param list<mixed> $list
*/
function bar(array $list): void
{
foreach ($list as $key => &$value) {
$value = 'foo';
}

assertType("list<'foo'>", $list);
}

/**
* @param list<string> $list
*/
function baz(array $list): void
{
foreach ($list as &$value) {
$value = 'bar';
}

assertType('list<string>', $list);
}

/**
* @param list<int> $list
*/
function qux(array $list): void
{
foreach ($list as &$value) {
$value = $value + 1;
}

assertType('list<int>', $list);
}
Loading