From e50f5174f3e3eef8f30aa682e833f271450f74ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:26:50 +0000 Subject: [PATCH 01/12] Fix wrongly reported empty array when using byRef in array items - Widen variable type to mixed when passed by reference inside an array literal ([&$var, ...]) - This fixes false positives like "Comparison operation > between 0 and 0 is always false" when variables are passed by reference through call_user_func_array - Added regression test in tests/PHPStan/Analyser/nsrt/bug-6799.php - The root cause was that ArrayHandler did not account for byRef array items, so PHPStan retained the initial precise type (e.g. array{}) even after a reference was created Closes https://github.com/phpstan/phpstan/issues/6799 --- src/Analyser/ExprHandler/ArrayHandler.php | 6 ++++ tests/PHPStan/Analyser/nsrt/bug-6799.php | 38 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-6799.php diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index e5fae7851b..4265b95d91 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -15,6 +15,7 @@ use PHPStan\Node\LiteralArrayItem; use PHPStan\Node\LiteralArrayNode; use PHPStan\Reflection\InitializerExprTypeResolver; +use PHPStan\Type\MixedType; use PHPStan\Type\Type; use function array_merge; @@ -66,6 +67,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $impurePoints = array_merge($impurePoints, $valueResult->getImpurePoints()); $isAlwaysTerminating = $isAlwaysTerminating || $valueResult->isAlwaysTerminating(); $scope = $valueResult->getScope(); + if (!$arrayItem->byRef) { + continue; + } + + $scope = $scope->assignExpression($arrayItem->value, new MixedType(), new MixedType()); } $nodeScopeResolver->callNodeCallback($nodeCallback, new LiteralArrayNode($expr, $itemNodes), $scope, $storage); diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799.php b/tests/PHPStan/Analyser/nsrt/bug-6799.php new file mode 100644 index 0000000000..04f1960848 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6799.php @@ -0,0 +1,38 @@ + $value) { + call_user_func_array(array($this, 'listingAddWhereFilterAtableDefault'), array(&$whereFilter, 'xxxx', $filters[$type], $value)); + } + assertType('mixed', $whereFilter); + } + } +} From 4437e703c7c112df39b7a98f8ad6a0cc2130bd3e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 12 Mar 2026 07:21:40 +0100 Subject: [PATCH 02/12] Update bug-6799.php --- tests/PHPStan/Analyser/nsrt/bug-6799.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799.php b/tests/PHPStan/Analyser/nsrt/bug-6799.php index 04f1960848..bbf25372bc 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799.php @@ -32,7 +32,7 @@ protected function listingAddWhereFilterAtable(array $filterValues, array &$wher foreach ($filterValues as $type => $value) { call_user_func_array(array($this, 'listingAddWhereFilterAtableDefault'), array(&$whereFilter, 'xxxx', $filters[$type], $value)); } - assertType('mixed', $whereFilter); + assertType('mixed', $whereFilter); // could be string[] } } } From 26f4d1c345cccba02d7e1c02a2f6dcf967edd170 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 12 Mar 2026 07:26:22 +0100 Subject: [PATCH 03/12] added regression test --- tests/PHPStan/Analyser/nsrt/bug-6799.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-6799b.php | 45 +++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-6799b.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799.php b/tests/PHPStan/Analyser/nsrt/bug-6799.php index bbf25372bc..86737c8c58 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799.php @@ -32,7 +32,7 @@ protected function listingAddWhereFilterAtable(array $filterValues, array &$wher foreach ($filterValues as $type => $value) { call_user_func_array(array($this, 'listingAddWhereFilterAtableDefault'), array(&$whereFilter, 'xxxx', $filters[$type], $value)); } - assertType('mixed', $whereFilter); // could be string[] + assertType('mixed', $whereFilter); // could be array } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799b.php b/tests/PHPStan/Analyser/nsrt/bug-6799b.php new file mode 100644 index 0000000000..79f77944b0 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6799b.php @@ -0,0 +1,45 @@ + $value) { + $this->listingAddWhereFilterAtableDefault($whereFilter, 'xxxxx', $filters[$type], $value); + } + assertType('array', $whereFilter); // could be string[] + } + } +} From b85e56af86684c956166088556fbca5769a8d11a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 12 Mar 2026 07:26:50 +0100 Subject: [PATCH 04/12] Update bug-6799b.php --- tests/PHPStan/Analyser/nsrt/bug-6799b.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799b.php b/tests/PHPStan/Analyser/nsrt/bug-6799b.php index 79f77944b0..057409e7ef 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799b.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799b.php @@ -39,7 +39,7 @@ protected function listingAddWhereFilterAtable(array $filterValues, array &$wher foreach ($filterValues as $type => $value) { $this->listingAddWhereFilterAtableDefault($whereFilter, 'xxxxx', $filters[$type], $value); } - assertType('array', $whereFilter); // could be string[] + assertType('array', $whereFilter); } } } From d4a22dea81f8c692cb3102bfacbca0c390325312 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Thu, 12 Mar 2026 16:36:51 +0000 Subject: [PATCH 05/12] Add comprehensive tests for byRef in array literals Address review feedback by adding tests for standalone `$b = [&$a]` patterns (not just call_user_func_array), including by-ref with keys and multiple by-ref items in a single array. Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-6799.php | 38 +++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799.php b/tests/PHPStan/Analyser/nsrt/bug-6799.php index 86737c8c58..dd338281cc 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799.php @@ -32,7 +32,43 @@ protected function listingAddWhereFilterAtable(array $filterValues, array &$wher foreach ($filterValues as $type => $value) { call_user_func_array(array($this, 'listingAddWhereFilterAtableDefault'), array(&$whereFilter, 'xxxx', $filters[$type], $value)); } - assertType('mixed', $whereFilter); // could be array + assertType('mixed', $whereFilter); } } } + +/** + * @param mixed $foo + */ +function foo($foo): void {} + +function testByRefInArray(): void +{ + $a = []; + assertType('array{}', $a); + + $b = [&$a]; + assertType('mixed', $a); + + foo($b); + assertType('mixed', $a); +} + +function testByRefInArrayWithKey(): void +{ + $a = 'hello'; + assertType("'hello'", $a); + + $b = ['key' => &$a]; + assertType('mixed', $a); +} + +function testMultipleByRefInArray(): void +{ + $a = 1; + $c = 'test'; + + $b = [&$a, 'normal', &$c]; + assertType('mixed', $a); + assertType('mixed', $c); +} From 501709a698e7c7a9cf714437ecc4ed28d479be05 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 13 Mar 2026 07:16:49 +0100 Subject: [PATCH 06/12] Create bug-6799c.php --- tests/PHPStan/Analyser/nsrt/bug-6799c.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-6799c.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799c.php b/tests/PHPStan/Analyser/nsrt/bug-6799c.php new file mode 100644 index 0000000000..cca6361076 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6799c.php @@ -0,0 +1,18 @@ + Date: Fri, 13 Mar 2026 07:20:18 +0100 Subject: [PATCH 07/12] Update DefinedVariableRuleTest.php --- .../Rules/Variables/DefinedVariableRuleTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 2dde59f142..97f6c0be3e 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1430,4 +1430,19 @@ public function testBug14117(): void ]); } + public function testBug6799c(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-6799c.php'], [ + [ + 'Variable $x might not be defined.', + 9 + ] + ]); + } + } From f6346f2f9860e5fab60a857ac45c19a2c58a032f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 13 Mar 2026 07:21:36 +0100 Subject: [PATCH 08/12] Update DefinedVariableRuleTest.php --- tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 97f6c0be3e..577ef930a9 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1440,8 +1440,8 @@ public function testBug6799c(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-6799c.php'], [ [ 'Variable $x might not be defined.', - 9 - ] + 9, + ], ]); } From 34b86916fcd3bff014cfadbf5b67a63e9573af21 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 13 Mar 2026 07:30:41 +0100 Subject: [PATCH 09/12] fix tests --- tests/PHPStan/Analyser/nsrt/bug-6799.php | 3 +++ tests/PHPStan/Analyser/nsrt/bug-6799b.php | 2 ++ .../NumberComparisonOperatorsConstantConditionRuleTest.php | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799.php b/tests/PHPStan/Analyser/nsrt/bug-6799.php index dd338281cc..0bf4792054 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799.php @@ -33,6 +33,9 @@ protected function listingAddWhereFilterAtable(array $filterValues, array &$wher call_user_func_array(array($this, 'listingAddWhereFilterAtableDefault'), array(&$whereFilter, 'xxxx', $filters[$type], $value)); } assertType('mixed', $whereFilter); + if (count($whereFilter) > 0) { + $where[] = "(" . implode(" AND ", $whereFilter) . ")"; + } } } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799b.php b/tests/PHPStan/Analyser/nsrt/bug-6799b.php index 057409e7ef..1aa9c0a116 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799b.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799b.php @@ -1,5 +1,7 @@ analyse([__DIR__ . '/../../Analyser/nsrt/bug-6799.php'], []); + } + } From c44bfbf03f5bd66216a41c4d623e6616ea4071ae Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 13 Mar 2026 07:39:27 +0100 Subject: [PATCH 10/12] Update bug-6799b.php --- tests/PHPStan/Analyser/nsrt/bug-6799b.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799b.php b/tests/PHPStan/Analyser/nsrt/bug-6799b.php index 1aa9c0a116..f0af35d829 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799b.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799b.php @@ -1,6 +1,6 @@ Date: Fri, 13 Mar 2026 09:16:44 +0100 Subject: [PATCH 11/12] Add some comments --- tests/PHPStan/Analyser/nsrt/bug-6799.php | 18 ++++++++++++++---- tests/PHPStan/Analyser/nsrt/bug-6799c.php | 9 +++++---- .../Variables/DefinedVariableRuleTest.php | 7 +------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799.php b/tests/PHPStan/Analyser/nsrt/bug-6799.php index 0bf4792054..99938ec9ac 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799.php @@ -51,7 +51,7 @@ function testByRefInArray(): void assertType('array{}', $a); $b = [&$a]; - assertType('mixed', $a); + assertType('mixed', $a); // Could stay array{} foo($b); assertType('mixed', $a); @@ -63,7 +63,10 @@ function testByRefInArrayWithKey(): void assertType("'hello'", $a); $b = ['key' => &$a]; - assertType('mixed', $a); + assertType('mixed', $a); // Could stay 'hello' + + $b['key'] = 42; + assertType('mixed', $a); // Could be 42 } function testMultipleByRefInArray(): void @@ -72,6 +75,13 @@ function testMultipleByRefInArray(): void $c = 'test'; $b = [&$a, 'normal', &$c]; - assertType('mixed', $a); - assertType('mixed', $c); + assertType('mixed', $a); // Could stay 1 + assertType('mixed', $c); // Could stay 'test' + + $b[0] = 2; + $b[1] = 'foo'; + $b[2] = 'bar'; + + assertType('mixed', $a); // Could be 2 + assertType('mixed', $c); // Could be 'bar' } diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799c.php b/tests/PHPStan/Analyser/nsrt/bug-6799c.php index cca6361076..72eddc8f79 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799c.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799c.php @@ -6,13 +6,14 @@ // https://3v4l.org/g5UjS +$x = null; +assertType('null', $x); $a = [&$x]; +assertType('mixed', $x); // Could stay null + function doFoo(array &$arr) { $arr[0] = 'string'; } -var_dump($x); -assertType('mixed', $x); doFoo($a); -var_dump($x); -assertType('mixed', $x); // could be string +assertType('mixed', $x); diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 577ef930a9..4dd5b0bced 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1437,12 +1437,7 @@ public function testBug6799c(): void $this->checkMaybeUndefinedVariables = true; $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-6799c.php'], [ - [ - 'Variable $x might not be defined.', - 9, - ], - ]); + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-6799c.php'], []); } } From 7ebae4440f7a2c4004429ef106091a11d5072a65 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Fri, 13 Mar 2026 08:28:17 +0000 Subject: [PATCH 12/12] Fix undefined variable false positive for by-reference array items Restore bug-6799c.php test to use undefined variable in [&$x] (the previous $x = null assignment defeated the test purpose). Allow undefined expressions in by-reference array items using the same set/unset pattern used for function call by-reference arguments. Co-Authored-By: Claude Opus 4.6 --- src/Analyser/ExprHandler/ArrayHandler.php | 5 +++++ tests/PHPStan/Analyser/nsrt/bug-6799c.php | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Analyser/ExprHandler/ArrayHandler.php b/src/Analyser/ExprHandler/ArrayHandler.php index 4265b95d91..6facf4fbec 100644 --- a/src/Analyser/ExprHandler/ArrayHandler.php +++ b/src/Analyser/ExprHandler/ArrayHandler.php @@ -61,6 +61,10 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex $scope = $keyResult->getScope(); } + if ($arrayItem->byRef) { + $scope = $nodeScopeResolver->lookForSetAllowedUndefinedExpressions($scope, $arrayItem->value); + } + $valueResult = $nodeScopeResolver->processExprNode($stmt, $arrayItem->value, $scope, $storage, $nodeCallback, $context->enterDeep()); $hasYield = $hasYield || $valueResult->hasYield(); $throwPoints = array_merge($throwPoints, $valueResult->getThrowPoints()); @@ -71,6 +75,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex continue; } + $scope = $nodeScopeResolver->lookForUnsetAllowedUndefinedExpressions($scope, $arrayItem->value); $scope = $scope->assignExpression($arrayItem->value, new MixedType(), new MixedType()); } $nodeScopeResolver->callNodeCallback($nodeCallback, new LiteralArrayNode($expr, $itemNodes), $scope, $storage); diff --git a/tests/PHPStan/Analyser/nsrt/bug-6799c.php b/tests/PHPStan/Analyser/nsrt/bug-6799c.php index 72eddc8f79..43e34ecb25 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-6799c.php +++ b/tests/PHPStan/Analyser/nsrt/bug-6799c.php @@ -6,10 +6,8 @@ // https://3v4l.org/g5UjS -$x = null; -assertType('null', $x); $a = [&$x]; -assertType('mixed', $x); // Could stay null +assertType('mixed', $x); function doFoo(array &$arr) { $arr[0] = 'string';