Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
58 changes: 16 additions & 42 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down
47 changes: 47 additions & 0 deletions src/Node/Expr/NativeTypeExpr.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace PHPStan\Node\Expr;

use Override;
use PhpParser\Node\Expr;
use PHPStan\Node\VirtualNode;
use PHPStan\Type\Type;

/**
* @api
*/
final class NativeTypeExpr extends Expr implements VirtualNode
{

/** @api */
public function __construct(private Type $phpdocType, private Type $nativeType)
{
parent::__construct();
}

public function getPhpDocType(): Type
{
return $this->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 [];
}

}
6 changes: 6 additions & 0 deletions src/Node/Printer/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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()));
Expand Down
12 changes: 12 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-11322.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Bug11322;

use function PHPStan\Testing\assertType;

function doFoo() {
$result = ['map' => ['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);
}
8 changes: 4 additions & 4 deletions tests/PHPStan/Analyser/nsrt/shuffle.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function normalArrays1(array $arr): void
/** @var mixed[] $arr */
shuffle($arr);
assertType('list<mixed>', $arr);
assertNativeType('list<mixed>', $arr);
assertNativeType('list', $arr);
assertType('list<int<0, max>>', array_keys($arr));
assertType('list<mixed>', array_values($arr));
}
Expand All @@ -23,7 +23,7 @@ public function normalArrays2(array $arr): void
/** @var non-empty-array<string, int> $arr */
shuffle($arr);
assertType('non-empty-list<int>', $arr);
assertNativeType('non-empty-list<int>', $arr);
assertNativeType('list', $arr);
assertType('non-empty-list<int<0, max>>', array_keys($arr));
assertType('non-empty-list<int>', array_values($arr));
}
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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));
}
Expand Down
6 changes: 3 additions & 3 deletions tests/PHPStan/Analyser/nsrt/sort.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,17 @@ public function normalArray(array $arr): void
$arr1 = $arr;
sort($arr1);
assertType('list<string>', $arr1);
assertNativeType('list<string>', $arr1);
assertNativeType('list', $arr1);

$arr2 = $arr;
rsort($arr2);
assertType('list<string>', $arr2);
assertNativeType('list<string>', $arr2);
assertNativeType('list', $arr2);

$arr3 = $arr;
usort($arr3, fn(int $a, int $b) => $a <=> $b);
assertType('list<string>', $arr3);
assertNativeType('list<string>', $arr3);
assertNativeType('list', $arr3);
}

public function mixed($arr): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -807,4 +807,11 @@ public function testBug7863(): void
]);
}

public function testBug10595(): void
{
$this->checkImplicitMixed = true;

$this->analyse([__DIR__ . '/data/bug-10595.php'], []);
}

}
16 changes: 16 additions & 0 deletions tests/PHPStan/Rules/Operators/data/bug-10595.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Bug10595;

function doFoo() {
$test = [];
$test[0] = [0, []];
$test[0][1]['h'] = 'h';

foreach ($test as $value) {
sort($value[1]);
$value[1] = implode(',', $value[1]);
$label = 'test' . $value[1];
}
}

4 changes: 0 additions & 4 deletions tests/PHPStan/Rules/Variables/EmptyRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,6 @@ public function testBug6974(): void
'Variable $a in empty() always exists and is always falsy.',
12,
],
[
'Variable $a in empty() always exists and is not falsy.',
30,
],
]);
}

Expand Down
Loading