Skip to content

Commit 22d340b

Browse files
authored
Implemented NativeTypeExpr
1 parent 33d8481 commit 22d340b

File tree

10 files changed

+118
-53
lines changed

10 files changed

+118
-53
lines changed

src/Analyser/MutatingScope.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
3838
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
3939
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
40+
use PHPStan\Node\Expr\NativeTypeExpr;
4041
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
4142
use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
4243
use PHPStan\Node\Expr\PropertyInitializationExpr;
@@ -766,6 +767,12 @@ public function getType(Expr $node): Type
766767
if ($node instanceof TypeExpr) {
767768
return $node->getExprType();
768769
}
770+
if ($node instanceof NativeTypeExpr) {
771+
if ($this->nativeTypesPromoted) {
772+
return $node->getNativeType();
773+
}
774+
return $node->getPhpDocType();
775+
}
769776

770777
if ($node instanceof OriginalPropertyTypeExpr) {
771778
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($node->getPropertyFetch(), $this);

src/Analyser/NodeScopeResolver.php

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
9090
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
9191
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
92+
use PHPStan\Node\Expr\NativeTypeExpr;
9293
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
9394
use PHPStan\Node\Expr\PropertyInitializationExpr;
9495
use PHPStan\Node\Expr\SetExistingOffsetValueTypeExpr;
@@ -2662,20 +2663,16 @@ static function (): void {
26622663

26632664
$arrayArgType = $scope->getType($arrayArg);
26642665
$arrayArgNativeType = $scope->getNativeType($arrayArg);
2665-
26662666
$isArrayPop = $functionReflection->getName() === 'array_pop';
2667-
$newType = $isArrayPop ? $arrayArgType->popArray() : $arrayArgType->shiftArray();
2668-
$scope = $scope->invalidateExpression($arrayArg)->assignExpression(
2669-
$arrayArg,
2670-
$newType,
2671-
$isArrayPop ? $arrayArgNativeType->popArray() : $arrayArgNativeType->shiftArray(),
2672-
);
26732667

26742668
$scope = $this->processAssignVar(
26752669
$scope,
26762670
$stmt,
26772671
$arrayArg,
2678-
new TypeExpr($newType),
2672+
new NativeTypeExpr(
2673+
$isArrayPop ? $arrayArgType->popArray() : $arrayArgType->shiftArray(),
2674+
$isArrayPop ? $arrayArgNativeType->popArray() : $arrayArgNativeType->shiftArray(),
2675+
),
26792676
static function (Node $node, Scope $scope) use ($nodeCallback): void {
26802677
if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) {
26812678
return;
@@ -2694,17 +2691,16 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
26942691
&& in_array($functionReflection->getName(), ['array_push', 'array_unshift'], true)
26952692
&& count($expr->getArgs()) >= 2
26962693
) {
2697-
$arrayType = $this->getArrayFunctionAppendingType($functionReflection, $scope, $expr);
2698-
$arrayNativeType = $this->getArrayFunctionAppendingType($functionReflection, $scope->doNotTreatPhpDocTypesAsCertain(), $expr);
2699-
27002694
$arrayArg = $expr->getArgs()[0]->value;
2701-
$scope = $scope->invalidateExpression($arrayArg)->assignExpression($arrayArg, $arrayType, $arrayNativeType);
27022695

27032696
$scope = $this->processAssignVar(
27042697
$scope,
27052698
$stmt,
27062699
$arrayArg,
2707-
new TypeExpr($arrayType),
2700+
new NativeTypeExpr(
2701+
$this->getArrayFunctionAppendingType($functionReflection, $scope, $expr),
2702+
$this->getArrayFunctionAppendingType($functionReflection, $scope->doNotTreatPhpDocTypesAsCertain(), $expr),
2703+
),
27082704
static function (Node $node, Scope $scope) use ($nodeCallback): void {
27092705
if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) {
27102706
return;
@@ -2730,18 +2726,12 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
27302726
&& $functionReflection->getName() === 'shuffle'
27312727
) {
27322728
$arrayArg = $expr->getArgs()[0]->value;
2733-
$newType = $scope->getType($arrayArg)->shuffleArray();
2734-
$scope = $scope->assignExpression(
2735-
$arrayArg,
2736-
$newType,
2737-
$scope->getNativeType($arrayArg)->shuffleArray(),
2738-
);
27392729

27402730
$scope = $this->processAssignVar(
27412731
$scope,
27422732
$stmt,
27432733
$arrayArg,
2744-
new TypeExpr($newType),
2734+
new NativeTypeExpr($scope->getType($arrayArg)->shuffleArray(), $scope->getNativeType($arrayArg)->shuffleArray()),
27452735
static function (Node $node, Scope $scope) use ($nodeCallback): void {
27462736
if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) {
27472737
return;
@@ -2768,18 +2758,14 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
27682758
$lengthType = isset($expr->getArgs()[2]) ? $scope->getType($expr->getArgs()[2]->value) : new NullType();
27692759
$replacementType = isset($expr->getArgs()[3]) ? $scope->getType($expr->getArgs()[3]->value) : new ConstantArrayType([], []);
27702760

2771-
$newType = $arrayArgType->spliceArray($offsetType, $lengthType, $replacementType);
2772-
$scope = $scope->invalidateExpression($arrayArg)->assignExpression(
2773-
$arrayArg,
2774-
$newType,
2775-
$arrayArgNativeType->spliceArray($offsetType, $lengthType, $replacementType),
2776-
);
2777-
27782761
$scope = $this->processAssignVar(
27792762
$scope,
27802763
$stmt,
27812764
$arrayArg,
2782-
new TypeExpr($newType),
2765+
new NativeTypeExpr(
2766+
$arrayArgType->spliceArray($offsetType, $lengthType, $replacementType),
2767+
$arrayArgNativeType->spliceArray($offsetType, $lengthType, $replacementType),
2768+
),
27832769
static function (Node $node, Scope $scope) use ($nodeCallback): void {
27842770
if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) {
27852771
return;
@@ -2799,18 +2785,12 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
27992785
&& count($expr->getArgs()) >= 1
28002786
) {
28012787
$arrayArg = $expr->getArgs()[0]->value;
2802-
$newType = $this->getArraySortPreserveListFunctionType($scope->getType($arrayArg));
2803-
$scope = $scope->assignExpression(
2804-
$arrayArg,
2805-
$newType,
2806-
$this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg)),
2807-
);
28082788

28092789
$scope = $this->processAssignVar(
28102790
$scope,
28112791
$stmt,
28122792
$arrayArg,
2813-
new TypeExpr($newType),
2793+
new NativeTypeExpr($this->getArraySortPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortPreserveListFunctionType($scope->getNativeType($arrayArg))),
28142794
static function (Node $node, Scope $scope) use ($nodeCallback): void {
28152795
if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) {
28162796
return;
@@ -2830,18 +2810,12 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
28302810
&& count($expr->getArgs()) >= 1
28312811
) {
28322812
$arrayArg = $expr->getArgs()[0]->value;
2833-
$newType = $this->getArraySortDoNotPreserveListFunctionType($scope->getType($arrayArg));
2834-
$scope = $scope->assignExpression(
2835-
$arrayArg,
2836-
$newType,
2837-
$this->getArraySortDoNotPreserveListFunctionType($scope->getNativeType($arrayArg)),
2838-
);
28392813

28402814
$scope = $this->processAssignVar(
28412815
$scope,
28422816
$stmt,
28432817
$arrayArg,
2844-
new TypeExpr($newType),
2818+
new NativeTypeExpr($this->getArraySortDoNotPreserveListFunctionType($scope->getType($arrayArg)), $this->getArraySortDoNotPreserveListFunctionType($scope->getNativeType($arrayArg))),
28452819
static function (Node $node, Scope $scope) use ($nodeCallback): void {
28462820
if (!$node instanceof PropertyAssignNode && !$node instanceof VariableAssignNode) {
28472821
return;

src/Node/Expr/NativeTypeExpr.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Node\Expr;
4+
5+
use Override;
6+
use PhpParser\Node\Expr;
7+
use PHPStan\Node\VirtualNode;
8+
use PHPStan\Type\Type;
9+
10+
/**
11+
* @api
12+
*/
13+
final class NativeTypeExpr extends Expr implements VirtualNode
14+
{
15+
16+
/** @api */
17+
public function __construct(private Type $phpdocType, private Type $nativeType)
18+
{
19+
parent::__construct();
20+
}
21+
22+
public function getPhpDocType(): Type
23+
{
24+
return $this->phpdocType;
25+
}
26+
27+
public function getNativeType(): Type
28+
{
29+
return $this->nativeType;
30+
}
31+
32+
#[Override]
33+
public function getType(): string
34+
{
35+
return 'PHPStan_Node_NativeTypeExpr';
36+
}
37+
38+
/**
39+
* @return string[]
40+
*/
41+
#[Override]
42+
public function getSubNodeNames(): array
43+
{
44+
return [];
45+
}
46+
47+
}

src/Node/Printer/Printer.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPStan\Node\Expr\GetIterableKeyTypeExpr;
1010
use PHPStan\Node\Expr\GetIterableValueTypeExpr;
1111
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
12+
use PHPStan\Node\Expr\NativeTypeExpr;
1213
use PHPStan\Node\Expr\OriginalPropertyTypeExpr;
1314
use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
1415
use PHPStan\Node\Expr\PropertyInitializationExpr;
@@ -32,6 +33,11 @@ protected function pPHPStan_Node_TypeExpr(TypeExpr $expr): string // phpcs:ignor
3233
return sprintf('__phpstanType(%s)', $expr->getExprType()->describe(VerbosityLevel::precise()));
3334
}
3435

36+
protected function pPHPStan_Node_NativeTypeExpr(NativeTypeExpr $expr): string // phpcs:ignore
37+
{
38+
return sprintf('__phpstanNativeType(%s, %s)', $expr->getPhpDocType()->describe(VerbosityLevel::precise()), $expr->getNativeType()->describe(VerbosityLevel::precise()));
39+
}
40+
3541
protected function pPHPStan_Node_GetOffsetValueTypeExpr(GetOffsetValueTypeExpr $expr): string // phpcs:ignore
3642
{
3743
return sprintf('__phpstanGetOffsetValueType(%s, %s)', $this->p($expr->getVar()), $this->p($expr->getDim()));
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Bug11322;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function doFoo() {
8+
$result = ['map' => ['a' => 'b']];
9+
assertType("array{map: array{a: 'b'}}", $result);
10+
usort($result['map'], fn (string $a, string $b) => $a <=> $b);
11+
assertType("array{map: non-empty-list<'b'>}", $result);
12+
}

tests/PHPStan/Analyser/nsrt/shuffle.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public function normalArrays1(array $arr): void
1313
/** @var mixed[] $arr */
1414
shuffle($arr);
1515
assertType('list<mixed>', $arr);
16-
assertNativeType('list<mixed>', $arr);
16+
assertNativeType('list', $arr);
1717
assertType('list<int<0, max>>', array_keys($arr));
1818
assertType('list<mixed>', array_values($arr));
1919
}
@@ -23,7 +23,7 @@ public function normalArrays2(array $arr): void
2323
/** @var non-empty-array<string, int> $arr */
2424
shuffle($arr);
2525
assertType('non-empty-list<int>', $arr);
26-
assertNativeType('non-empty-list<int>', $arr);
26+
assertNativeType('list', $arr);
2727
assertType('non-empty-list<int<0, max>>', array_keys($arr));
2828
assertType('non-empty-list<int>', array_values($arr));
2929
}
@@ -67,7 +67,7 @@ public function constantArrays2(array $arr): void
6767
/** @var array{0?: 1, 1?: 2, 2?: 3} $arr */
6868
shuffle($arr);
6969
assertType('list<1|2|3>', $arr);
70-
assertNativeType('list<1|2|3>', $arr);
70+
assertNativeType('list', $arr);
7171
assertType('list<0|1|2>', array_keys($arr));
7272
assertType('list<1|2|3>', array_values($arr));
7373
}
@@ -107,7 +107,7 @@ public function constantArrays6(array $arr): void
107107
/** @var array{foo?: 1, bar: 2, }|array{baz: 3, foobar?: 4} $arr */
108108
shuffle($arr);
109109
assertType('non-empty-list<1|2|3|4>', $arr);
110-
assertNativeType('non-empty-list<1|2|3|4>', $arr);
110+
assertNativeType('list', $arr);
111111
assertType('non-empty-list<0|1>', array_keys($arr));
112112
assertType('non-empty-list<1|2|3|4>', array_values($arr));
113113
}

tests/PHPStan/Analyser/nsrt/sort.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,17 @@ public function normalArray(array $arr): void
9191
$arr1 = $arr;
9292
sort($arr1);
9393
assertType('list<string>', $arr1);
94-
assertNativeType('list<string>', $arr1);
94+
assertNativeType('list', $arr1);
9595

9696
$arr2 = $arr;
9797
rsort($arr2);
9898
assertType('list<string>', $arr2);
99-
assertNativeType('list<string>', $arr2);
99+
assertNativeType('list', $arr2);
100100

101101
$arr3 = $arr;
102102
usort($arr3, fn(int $a, int $b) => $a <=> $b);
103103
assertType('list<string>', $arr3);
104-
assertNativeType('list<string>', $arr3);
104+
assertNativeType('list', $arr3);
105105
}
106106

107107
public function mixed($arr): void

tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,4 +807,11 @@ public function testBug7863(): void
807807
]);
808808
}
809809

810+
public function testBug10595(): void
811+
{
812+
$this->checkImplicitMixed = true;
813+
814+
$this->analyse([__DIR__ . '/data/bug-10595.php'], []);
815+
}
816+
810817
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Bug10595;
4+
5+
function doFoo() {
6+
$test = [];
7+
$test[0] = [0, []];
8+
$test[0][1]['h'] = 'h';
9+
10+
foreach ($test as $value) {
11+
sort($value[1]);
12+
$value[1] = implode(',', $value[1]);
13+
$label = 'test' . $value[1];
14+
}
15+
}
16+

tests/PHPStan/Rules/Variables/EmptyRuleTest.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,6 @@ public function testBug6974(): void
9696
'Variable $a in empty() always exists and is always falsy.',
9797
12,
9898
],
99-
[
100-
'Variable $a in empty() always exists and is not falsy.',
101-
30,
102-
],
10399
]);
104100
}
105101

0 commit comments

Comments
 (0)