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/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/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; 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); + } +} 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 @@ +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 @@ +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']); + } + } + } +}