diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 1bd45cfcf0..8be043daf2 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -37,6 +37,7 @@ use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Node\Expr\GetIterableValueTypeExpr; use PHPStan\Node\Expr\GetOffsetValueTypeExpr; +use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalPropertyTypeExpr; use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr; use PHPStan\Node\Expr\PropertyInitializationExpr; @@ -766,6 +767,12 @@ public function getType(Expr $node): Type if ($node instanceof TypeExpr) { return $node->getExprType(); } + if ($node instanceof NativeTypeExpr) { + if ($this->nativeTypesPromoted) { + return $node->getNativeType(); + } + return $node->getPhpDocType(); + } if ($node instanceof OriginalPropertyTypeExpr) { $propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->getPropertyFetch(), $this); diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 311f70d4c6..3e463197e8 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -89,6 +89,7 @@ use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Node\Expr\GetIterableValueTypeExpr; use PHPStan\Node\Expr\GetOffsetValueTypeExpr; +use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalPropertyTypeExpr; use PHPStan\Node\Expr\PropertyInitializationExpr; use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr; @@ -2662,20 +2663,16 @@ static function (): void { $arrayArgType = $scope->getType($arrayArg); $arrayArgNativeType = $scope->getNativeType($arrayArg); - $isArrayPop = $functionReflection->getName() === 'array_pop'; - $newType = $isArrayPop ? $arrayArgType->popArray() : $arrayArgType->shiftArray(); - $scope = $scope->invalidateExpression($arrayArg)->assignExpression( - $arrayArg, - $newType, - $isArrayPop ? $arrayArgNativeType->popArray() : $arrayArgNativeType->shiftArray(), - ); $scope = $this->processAssignVar( $scope, $stmt, $arrayArg, - new TypeExpr($newType), + new NativeTypeExpr( + $isArrayPop ? $arrayArgType->popArray() : $arrayArgType->shiftArray(), + $isArrayPop ? $arrayArgNativeType->popArray() : $arrayArgNativeType->shiftArray(), + ), static function (Node $node, Scope $scope) use ($nodeCallback): void { if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { return; @@ -2694,17 +2691,16 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { && in_array($functionReflection->getName(), ['array_push', 'array_unshift'], true) && count($expr->getArgs()) >= 2 ) { - $arrayType = $this->getArrayFunctionAppendingType($functionReflection, $scope, $expr); - $arrayNativeType = $this->getArrayFunctionAppendingType($functionReflection, $scope->doNotTreatPhpDocTypesAsCertain(), $expr); - $arrayArg = $expr->getArgs()[0]->value; - $scope = $scope->invalidateExpression($arrayArg)->assignExpression($arrayArg, $arrayType, $arrayNativeType); $scope = $this->processAssignVar( $scope, $stmt, $arrayArg, - new TypeExpr($arrayType), + new NativeTypeExpr( + $this->getArrayFunctionAppendingType($functionReflection, $scope, $expr), + $this->getArrayFunctionAppendingType($functionReflection, $scope->doNotTreatPhpDocTypesAsCertain(), $expr), + ), static function (Node $node, Scope $scope) use ($nodeCallback): void { if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { return; @@ -2730,18 +2726,12 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { && $functionReflection->getName() === 'shuffle' ) { $arrayArg = $expr->getArgs()[0]->value; - $newType = $scope->getType($arrayArg)->shuffleArray(); - $scope = $scope->assignExpression( - $arrayArg, - $newType, - $scope->getNativeType($arrayArg)->shuffleArray(), - ); $scope = $this->processAssignVar( $scope, $stmt, $arrayArg, - new TypeExpr($newType), + new NativeTypeExpr($scope->getType($arrayArg)->shuffleArray(), $scope->getNativeType($arrayArg)->shuffleArray()), static function (Node $node, Scope $scope) use ($nodeCallback): void { if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { return; @@ -2768,18 +2758,14 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { $lengthType = isset($expr->getArgs()[2]) ? $scope->getType($expr->getArgs()[2]->value) : new NullType(); $replacementType = isset($expr->getArgs()[3]) ? $scope->getType($expr->getArgs()[3]->value) : new ConstantArrayType([], []); - $newType = $arrayArgType->spliceArray($offsetType, $lengthType, $replacementType); - $scope = $scope->invalidateExpression($arrayArg)->assignExpression( - $arrayArg, - $newType, - $arrayArgNativeType->spliceArray($offsetType, $lengthType, $replacementType), - ); - $scope = $this->processAssignVar( $scope, $stmt, $arrayArg, - new TypeExpr($newType), + new NativeTypeExpr( + $arrayArgType->spliceArray($offsetType, $lengthType, $replacementType), + $arrayArgNativeType->spliceArray($offsetType, $lengthType, $replacementType), + ), static function (Node $node, Scope $scope) use ($nodeCallback): void { if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { return; @@ -2799,18 +2785,12 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { && count($expr->getArgs()) >= 1 ) { $arrayArg = $expr->getArgs()[0]->value; - $newType = $this->getArraySortPreserveListFunctionType($scope->getType($arrayArg)); - $scope = $scope->assignExpression( - $arrayArg, - $newType, - $this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg)), - ); $scope = $this->processAssignVar( $scope, $stmt, $arrayArg, - new TypeExpr($newType), + new NativeTypeExpr($this->getArraySortPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg))), static function (Node $node, Scope $scope) use ($nodeCallback): void { if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { return; @@ -2830,18 +2810,12 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void { && count($expr->getArgs()) >= 1 ) { $arrayArg = $expr->getArgs()[0]->value; - $newType = $this->getArraySortDoNotPreserveListFunctionType($scope->getType($arrayArg)); - $scope = $scope->assignExpression( - $arrayArg, - $newType, - $this->getArraySortDoNotPreserveListFunctionType($scope->getNativeType($arrayArg)), - ); $scope = $this->processAssignVar( $scope, $stmt, $arrayArg, - new TypeExpr($newType), + new NativeTypeExpr($this->getArraySortDoNotPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortDoNotPreserveListFunctionType($scope->getNativeType($arrayArg))), static function (Node $node, Scope $scope) use ($nodeCallback): void { if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) { return; diff --git a/src/Node/Expr/NativeTypeExpr.php b/src/Node/Expr/NativeTypeExpr.php new file mode 100644 index 0000000000..b3160ed79d --- /dev/null +++ b/src/Node/Expr/NativeTypeExpr.php @@ -0,0 +1,47 @@ +phpdocType; + } + + public function getNativeType(): Type + { + return $this->nativeType; + } + + #[Override] + public function getType(): string + { + return 'PHPStan_Node_NativeTypeExpr'; + } + + /** + * @return string[] + */ + #[Override] + public function getSubNodeNames(): array + { + return []; + } + +} diff --git a/src/Node/Printer/Printer.php b/src/Node/Printer/Printer.php index b9579329f3..a9fb822528 100644 --- a/src/Node/Printer/Printer.php +++ b/src/Node/Printer/Printer.php @@ -9,6 +9,7 @@ use PHPStan\Node\Expr\GetIterableKeyTypeExpr; use PHPStan\Node\Expr\GetIterableValueTypeExpr; use PHPStan\Node\Expr\GetOffsetValueTypeExpr; +use PHPStan\Node\Expr\NativeTypeExpr; use PHPStan\Node\Expr\OriginalPropertyTypeExpr; use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr; use PHPStan\Node\Expr\PropertyInitializationExpr; @@ -32,6 +33,11 @@ protected function pPHPStan_Node_TypeExpr(TypeExpr $expr): string // phpcs:ignor return sprintf('__phpstanType(%s)', $expr->getExprType()->describe(VerbosityLevel::precise())); } + protected function pPHPStan_Node_NativeTypeExpr(NativeTypeExpr $expr): string // phpcs:ignore + { + return sprintf('__phpstanNativeType(%s, %s)', $expr->getPhpDocType()->describe(VerbosityLevel::precise()), $expr->getNativeType()->describe(VerbosityLevel::precise())); + } + protected function pPHPStan_Node_GetOffsetValueTypeExpr(GetOffsetValueTypeExpr $expr): string // phpcs:ignore { return sprintf('__phpstanGetOffsetValueType(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim())); diff --git a/tests/PHPStan/Analyser/nsrt/bug-11322.php b/tests/PHPStan/Analyser/nsrt/bug-11322.php new file mode 100644 index 0000000000..3be6f535ac --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11322.php @@ -0,0 +1,12 @@ + ['a' => 'b']]; + assertType("array{map: array{a: 'b'}}", $result); + usort($result['map'], fn (string $a, string $b) => $a <=> $b); + assertType("array{map: non-empty-list<'b'>}", $result); +} diff --git a/tests/PHPStan/Analyser/nsrt/shuffle.php b/tests/PHPStan/Analyser/nsrt/shuffle.php index cf85e3528b..6b699e598a 100644 --- a/tests/PHPStan/Analyser/nsrt/shuffle.php +++ b/tests/PHPStan/Analyser/nsrt/shuffle.php @@ -13,7 +13,7 @@ public function normalArrays1(array $arr): void /** @var mixed[] $arr */ shuffle($arr); assertType('list', $arr); - assertNativeType('list', $arr); + assertNativeType('list', $arr); assertType('list>', array_keys($arr)); assertType('list', array_values($arr)); } @@ -23,7 +23,7 @@ public function normalArrays2(array $arr): void /** @var non-empty-array $arr */ shuffle($arr); assertType('non-empty-list', $arr); - assertNativeType('non-empty-list', $arr); + assertNativeType('list', $arr); assertType('non-empty-list>', array_keys($arr)); assertType('non-empty-list', array_values($arr)); } @@ -67,7 +67,7 @@ public function constantArrays2(array $arr): void /** @var array{0?: 1, 1?: 2, 2?: 3} $arr */ shuffle($arr); assertType('list<1|2|3>', $arr); - assertNativeType('list<1|2|3>', $arr); + assertNativeType('list', $arr); assertType('list<0|1|2>', array_keys($arr)); assertType('list<1|2|3>', array_values($arr)); } @@ -107,7 +107,7 @@ public function constantArrays6(array $arr): void /** @var array{foo?: 1, bar: 2, }|array{baz: 3, foobar?: 4} $arr */ shuffle($arr); assertType('non-empty-list<1|2|3|4>', $arr); - assertNativeType('non-empty-list<1|2|3|4>', $arr); + assertNativeType('list', $arr); assertType('non-empty-list<0|1>', array_keys($arr)); assertType('non-empty-list<1|2|3|4>', array_values($arr)); } diff --git a/tests/PHPStan/Analyser/nsrt/sort.php b/tests/PHPStan/Analyser/nsrt/sort.php index 88dd95b55a..93dfe0d147 100644 --- a/tests/PHPStan/Analyser/nsrt/sort.php +++ b/tests/PHPStan/Analyser/nsrt/sort.php @@ -91,17 +91,17 @@ public function normalArray(array $arr): void $arr1 = $arr; sort($arr1); assertType('list', $arr1); - assertNativeType('list', $arr1); + assertNativeType('list', $arr1); $arr2 = $arr; rsort($arr2); assertType('list', $arr2); - assertNativeType('list', $arr2); + assertNativeType('list', $arr2); $arr3 = $arr; usort($arr3, fn(int $a, int $b) => $a <=> $b); assertType('list', $arr3); - assertNativeType('list', $arr3); + assertNativeType('list', $arr3); } public function mixed($arr): void diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index 8335aecdf2..bfca28ee4f 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -807,4 +807,11 @@ public function testBug7863(): void ]); } + public function testBug10595(): void + { + $this->checkImplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-10595.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Operators/data/bug-10595.php b/tests/PHPStan/Rules/Operators/data/bug-10595.php new file mode 100644 index 0000000000..cb38ce3d46 --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/bug-10595.php @@ -0,0 +1,16 @@ +