Skip to content

Commit 53345d2

Browse files
committed
Use pre-args scope for value types in array_push/array_unshift to avoid false mutations from nested by-ref calls
1 parent 22923ac commit 53345d2

2 files changed

Lines changed: 31 additions & 5 deletions

File tree

src/Analyser/ExprHandler/FuncCallHandler.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
267267
}
268268
}
269269

270+
$scopeBeforeArgs = $scope;
270271
$argsResult = $nodeScopeResolver->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $normalizedExpr, $scope, $storage, $nodeCallbackForArgs, $context);
271272
$scope = $argsResult->getScope();
272273
$hasYield = $argsResult->hasYield();
@@ -395,8 +396,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
395396
$stmt,
396397
$arrayArg,
397398
new NativeTypeExpr(
398-
$this->getArrayFunctionAppendingType($functionReflection, $scope, $normalizedExpr),
399-
$this->getArrayFunctionAppendingType($functionReflection, $scope->doNotTreatPhpDocTypesAsCertain(), $normalizedExpr),
399+
$this->getArrayFunctionAppendingType($functionReflection, $scope, $scopeBeforeArgs, $normalizedExpr),
400+
$this->getArrayFunctionAppendingType($functionReflection, $scope->doNotTreatPhpDocTypesAsCertain(), $scopeBeforeArgs->doNotTreatPhpDocTypesAsCertain(), $normalizedExpr),
400401
),
401402
$nodeCallback,
402403
)->getScope();
@@ -621,7 +622,7 @@ private function getFunctionThrowPoint(
621622
return null;
622623
}
623624

624-
private function getArrayFunctionAppendingType(FunctionReflection $functionReflection, Scope $scope, FuncCall $expr): Type
625+
private function getArrayFunctionAppendingType(FunctionReflection $functionReflection, Scope $scope, Scope $valuesScope, FuncCall $expr): Type
625626
{
626627
$arrayArg = $expr->getArgs()[0]->value;
627628
$arrayType = $scope->getType($arrayArg);
@@ -667,7 +668,7 @@ private function getArrayFunctionAppendingType(FunctionReflection $functionRefle
667668
$arrayTypeBuilder = $prepend ? ConstantArrayTypeBuilder::createEmpty() : ConstantArrayTypeBuilder::createFromConstantArray($constantArray);
668669

669670
$setOffsetValueTypes(
670-
$scope,
671+
$valuesScope,
671672
$callArgs,
672673
static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arrayTypeBuilder): void {
673674
$arrayTypeBuilder->setOffsetValueType($offsetType, $valueType, $optional);
@@ -707,7 +708,7 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
707708
}
708709

709710
$setOffsetValueTypes(
710-
$scope,
711+
$valuesScope,
711712
$callArgs,
712713
static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arrayType): void {
713714
$isIterableAtLeastOnce = $arrayType->isIterableAtLeastOnce()->yes() || !$optional;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Bug13510;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
final class Foo
8+
{
9+
10+
/** @param non-empty-list<int> $arr */
11+
public function test(array $arr): void
12+
{
13+
array_unshift($arr, array_pop($arr));
14+
assertType('non-empty-list<int>', $arr);
15+
}
16+
17+
/** @param non-empty-list<int> $arr */
18+
public function testTwoLines(array $arr): void
19+
{
20+
$popped = array_pop($arr);
21+
array_unshift($arr, $popped);
22+
assertType('non-empty-list<int>', $arr);
23+
}
24+
25+
}

0 commit comments

Comments
 (0)