From 947bcb54c0d0e461d3f7cb934118a1cb690fb940 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 4 Aug 2025 10:45:32 +0200 Subject: [PATCH 1/5] Do not report non existent offset when it s an array creation --- .../NonexistentOffsetInArrayDimFetchRule.php | 25 +++++++++++++++++++ ...nexistentOffsetInArrayDimFetchRuleTest.php | 13 ++++++++++ tests/PHPStan/Rules/Arrays/data/bug-12447.php | 9 +++++++ 3 files changed, 47 insertions(+) create mode 100644 tests/PHPStan/Rules/Arrays/data/bug-12447.php diff --git a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php index 92a03b36bf..f624f1a097 100644 --- a/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php +++ b/src/Rules/Arrays/NonexistentOffsetInArrayDimFetchRule.php @@ -3,6 +3,8 @@ namespace PHPStan\Rules\Arrays; use PhpParser\Node; +use PhpParser\Node\Expr\ArrayDimFetch; +use PhpParser\Node\Expr\Variable; use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredParameter; @@ -11,6 +13,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Rules\RuleLevelHelper; +use PHPStan\TrinaryLogic; use PHPStan\Type\ErrorType; use PHPStan\Type\Type; use PHPStan\Type\VerbosityLevel; @@ -42,6 +45,10 @@ public function getNodeType(): string public function processNode(Node $node, Scope $scope): array { + if ($this->isImplicitArrayCreation($node, $scope)->yes()) { + return []; + } + if ($node->dim !== null) { $dimType = $scope->getType($node->dim); $unknownClassPattern = sprintf('Access to offset %s on an unknown class %%s.', SprintfHelper::escapeFormatString($dimType->describe(VerbosityLevel::value()))); @@ -153,4 +160,22 @@ public function processNode(Node $node, Scope $scope): array ); } + private function isImplicitArrayCreation(Node\Expr\ArrayDimFetch $node, Scope $scope): TrinaryLogic + { + $varNode = $node->var; + while ($varNode instanceof ArrayDimFetch) { + $varNode = $varNode->var; + } + + if (!$varNode instanceof Variable) { + return TrinaryLogic::createNo(); + } + + if (!is_string($varNode->name)) { + return TrinaryLogic::createNo(); + } + + return $scope->hasVariableType($varNode->name)->negate(); + } + } diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index a481085088..c95ec10de6 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -926,4 +926,17 @@ public function testBug3747(): void $this->analyse([__DIR__ . '/data/bug-3747.php'], []); } + public function testBug12447(): void + { + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12447.php'], [ + [ + 'Cannot access an offset on mixed.', + 5, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-12447.php b/tests/PHPStan/Rules/Arrays/data/bug-12447.php new file mode 100644 index 0000000000..fb596d1306 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-12447.php @@ -0,0 +1,9 @@ + Date: Mon, 4 Aug 2025 19:44:25 +0200 Subject: [PATCH 2/5] Add asserts --- tests/PHPStan/Analyser/nsrt/bug-12447.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12447.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-12447.php b/tests/PHPStan/Analyser/nsrt/bug-12447.php new file mode 100644 index 0000000000..21f35c89e2 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12447.php @@ -0,0 +1,15 @@ + Date: Mon, 4 Aug 2025 21:54:05 +0200 Subject: [PATCH 3/5] Remove some ErrorType check --- src/Analyser/NodeScopeResolver.php | 26 ++++++++++++++++--- tests/PHPStan/Analyser/nsrt/bug-12447-bis.php | 22 ++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12447-bis.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 0c25c7271b..b56cc4864d 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5628,10 +5628,9 @@ private function processAssignVar( $varNativeType = $scope->getNativeType($var); // 4. compose types - if ($varType instanceof ErrorType) { + $isImplicitArrayCreation = $this->isImplicitArrayCreation($dimFetchStack, $scope); + if ($isImplicitArrayCreation->yes()) { $varType = new ConstantArrayType([], []); - } - if ($varNativeType instanceof ErrorType) { $varNativeType = new ConstantArrayType([], []); } $offsetValueType = $varType; @@ -6018,6 +6017,27 @@ static function (): void { return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints); } + /** + * @param list $dimFetchStack + */ + private function isImplicitArrayCreation(array $dimFetchStack, Scope $scope): TrinaryLogic + { + if (count($dimFetchStack) === 0) { + return TrinaryLogic::createNo(); + } + + $varNode = $dimFetchStack[0]->var; + if (!$varNode instanceof Variable) { + return TrinaryLogic::createNo(); + } + + if (!is_string($varNode->name)) { + return TrinaryLogic::createNo(); + } + + return $scope->hasVariableType($varNode->name)->negate(); + } + /** * @param list $dimFetchStack * @param list $offsetTypes diff --git a/tests/PHPStan/Analyser/nsrt/bug-12447-bis.php b/tests/PHPStan/Analyser/nsrt/bug-12447-bis.php new file mode 100644 index 0000000000..14ba281334 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12447-bis.php @@ -0,0 +1,22 @@ +doFoo(); + assertType('*ERROR*', $a); + $a[] = 5; + assertType('mixed', $a); + } +} From d34fd3dac1d55ecb11e15c8ed5a8e239ed9ec48d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 6 Aug 2025 00:52:04 +0200 Subject: [PATCH 4/5] Add non regression tests --- ...rictComparisonOfDifferentTypesRuleTest.php | 5 ++++ .../Rules/Comparison/data/bug-3803.php | 16 +++++++++++ .../PHPStan/Rules/Variables/UnsetRuleTest.php | 5 ++++ .../PHPStan/Rules/Variables/data/bug-4204.php | 27 +++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-3803.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-4204.php diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index a0efad16b4..7af7eb4d5c 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -986,6 +986,11 @@ public function testBug12748(): void $this->analyse([__DIR__ . '/data/bug-12748.php'], []); } + public function testBug3803(): void + { + $this->analyse([__DIR__ . '/data/bug-3803.php'], []); + } + public function testBug11019(): void { $this->analyse([__DIR__ . '/data/bug-11019.php'], []); diff --git a/tests/PHPStan/Rules/Comparison/data/bug-3803.php b/tests/PHPStan/Rules/Comparison/data/bug-3803.php new file mode 100644 index 0000000000..0a4db43f68 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-3803.php @@ -0,0 +1,16 @@ + $chars */ +function fun(array $chars) : void{ + $string = ""; + foreach($chars as $k => $v){ + $string[$k] = $v; + } + if($string === "wheee"){ + var_dump("yes"); + } +} + +fun(["w", "h", "e", "e", "e"]); diff --git a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php index c9cb149958..9f071f96e9 100644 --- a/tests/PHPStan/Rules/Variables/UnsetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/UnsetRuleTest.php @@ -65,6 +65,11 @@ public function testBug4289(): void $this->analyse([__DIR__ . '/data/bug-4289.php'], []); } + public function testBug4204(): void + { + $this->analyse([__DIR__ . '/data/bug-4204.php'], []); + } + public function testBug5223(): void { $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-5223.php'], [ diff --git a/tests/PHPStan/Rules/Variables/data/bug-4204.php b/tests/PHPStan/Rules/Variables/data/bug-4204.php new file mode 100644 index 0000000000..bb822b4317 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-4204.php @@ -0,0 +1,27 @@ + $blocks + */ + public function sayHello(array $blocks): void + { + foreach ($blocks as $block) { + $settings = $block->getSettings(); + + if (isset($settings['name'])) { + // switch name with code key + $settings['code'] = $settings['name']; + unset($settings['name']); + } + } + } +} From 0e7855c8479b3da51c44602ce9a13a7a67f25e4c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 6 Aug 2025 00:54:29 +0200 Subject: [PATCH 5/5] Fix ci --- src/Type/ObjectWithoutClassType.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Type/ObjectWithoutClassType.php b/src/Type/ObjectWithoutClassType.php index 8b8fb422a8..a3b47c7686 100644 --- a/src/Type/ObjectWithoutClassType.php +++ b/src/Type/ObjectWithoutClassType.php @@ -4,7 +4,6 @@ use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; -use PHPStan\TrinaryLogic; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; use PHPStan\Type\Traits\ObjectTypeTrait;