Skip to content

Commit ffa5133

Browse files
Improve array_search inference
1 parent 80d8fc6 commit ffa5133

19 files changed

+64
-46
lines changed

src/Type/Accessory/AccessoryArrayListType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
241241
return new MixedType();
242242
}
243243

244-
public function searchArray(Type $needleType): Type
244+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
245245
{
246246
return new MixedType();
247247
}

src/Type/Accessory/HasOffsetValueType.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,11 +262,15 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
262262
return new NonEmptyArrayType();
263263
}
264264

265-
public function searchArray(Type $needleType): Type
265+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
266266
{
267267
if (
268268
$needleType instanceof ConstantScalarType && $this->valueType instanceof ConstantScalarType
269-
&& $needleType->getValue() === $this->valueType->getValue()
269+
&& (
270+
$needleType->getValue() === $this->valueType->getValue()
271+
// @phpstan-ignore equal.notAllowed
272+
|| ($strict->no() && $needleType->getValue() == $this->valueType->getValue()) // phpcs:ignore
273+
)
270274
) {
271275
return new UnionType([
272276
new IntegerType(),

src/Type/Accessory/NonEmptyArrayType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
218218
return $this;
219219
}
220220

221-
public function searchArray(Type $needleType): Type
221+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
222222
{
223223
return new MixedType();
224224
}

src/Type/Accessory/OversizedArrayType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
214214
return $this;
215215
}
216216

217-
public function searchArray(Type $needleType): Type
217+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
218218
{
219219
return new MixedType();
220220
}

src/Type/ArrayType.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,8 +601,12 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
601601
return $this;
602602
}
603603

604-
public function searchArray(Type $needleType): Type
604+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
605605
{
606+
if ($strict->yes() && $this->getIterableValueType()->isSuperTypeOf($needleType)->no()) {
607+
return new ConstantBooleanType(false);
608+
}
609+
606610
return TypeCombinator::union($this->getIterableKeyType(), new ConstantBooleanType(false));
607611
}
608612

src/Type/Constant/ConstantArrayType.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -909,22 +909,31 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
909909
return $builder->getArray();
910910
}
911911

912-
public function searchArray(Type $needleType): Type
912+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
913913
{
914914
$matches = [];
915915
$hasIdenticalValue = false;
916916

917917
foreach ($this->valueTypes as $index => $valueType) {
918-
$isNeedleSuperType = $valueType->isSuperTypeOf($needleType);
919-
if ($isNeedleSuperType->no()) {
920-
continue;
918+
if ($strict->yes()) {
919+
$isNeedleSuperType = $valueType->isSuperTypeOf($needleType);
920+
if ($isNeedleSuperType->no()) {
921+
continue;
922+
}
921923
}
922924

923-
if ($needleType instanceof ConstantScalarType && $valueType instanceof ConstantScalarType
924-
&& $needleType->getValue() === $valueType->getValue()
925-
&& !$this->isOptionalKey($index)
926-
) {
927-
$hasIdenticalValue = true;
925+
if ($needleType instanceof ConstantScalarType && $valueType instanceof ConstantScalarType) {
926+
// @phpstan-ignore equal.notAllowed
927+
$isLooseEqual = $needleType->getValue() == $valueType->getValue(); // phpcs:ignore
928+
if (!$isLooseEqual) {
929+
continue;
930+
}
931+
if (
932+
($needleType->getValue() === $valueType->getValue() || ($strict->no() && $isLooseEqual))
933+
&& !$this->isOptionalKey($index)
934+
) {
935+
$hasIdenticalValue = true;
936+
}
928937
}
929938

930939
$matches[] = $this->keyTypes[$index];

src/Type/IntersectionType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -819,9 +819,9 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
819819
return $this->intersectTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys));
820820
}
821821

822-
public function searchArray(Type $needleType): Type
822+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
823823
{
824-
return $this->intersectTypes(static fn (Type $type): Type => $type->searchArray($needleType));
824+
return $this->intersectTypes(static fn (Type $type): Type => $type->searchArray($needleType, $strict));
825825
}
826826

827827
public function shiftArray(): Type

src/Type/MixedType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
264264
return new ArrayType(new MixedType($this->isExplicitMixed), new MixedType($this->isExplicitMixed));
265265
}
266266

267-
public function searchArray(Type $needleType): Type
267+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
268268
{
269269
if ($this->isArray()->no()) {
270270
return new ErrorType();

src/Type/NeverType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
336336
return new NeverType();
337337
}
338338

339-
public function searchArray(Type $needleType): Type
339+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
340340
{
341341
return new NeverType();
342342
}

src/Type/Php/ArraySearchFunctionDynamicReturnTypeExtension.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use PHPStan\Type\NeverType;
1212
use PHPStan\Type\NullType;
1313
use PHPStan\Type\Type;
14-
use PHPStan\Type\TypeCombinator;
1514
use function count;
1615

1716
final class ArraySearchFunctionDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
@@ -39,20 +38,14 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3938
}
4039

4140
if ($argsCount < 3) {
42-
return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false));
43-
}
44-
45-
$strictArgType = $scope->getType($functionCall->getArgs()[2]->value);
46-
if (!$strictArgType->isTrue()->yes()) {
47-
return TypeCombinator::union($haystackArgType->getIterableKeyType(), new ConstantBooleanType(false));
41+
$strictArgType = new ConstantBooleanType(false);
42+
} else {
43+
$strictArgType = $scope->getType($functionCall->getArgs()[2]->value);
4844
}
4945

5046
$needleArgType = $scope->getType($functionCall->getArgs()[0]->value);
51-
if ($haystackArgType->getIterableValueType()->isSuperTypeOf($needleArgType)->no()) {
52-
return new ConstantBooleanType(false);
53-
}
5447

55-
return $haystackArgType->searchArray($needleArgType);
48+
return $haystackArgType->searchArray($needleArgType, $strictArgType->isTrue());
5649
}
5750

5851
}

src/Type/StaticType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,9 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
450450
return $this->getStaticObjectType()->reverseArray($preserveKeys);
451451
}
452452

453-
public function searchArray(Type $needleType): Type
453+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
454454
{
455-
return $this->getStaticObjectType()->searchArray($needleType);
455+
return $this->getStaticObjectType()->searchArray($needleType, $strict);
456456
}
457457

458458
public function shiftArray(): Type

src/Type/Traits/LateResolvableTypeTrait.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,9 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
298298
return $this->resolve()->reverseArray($preserveKeys);
299299
}
300300

301-
public function searchArray(Type $needleType): Type
301+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
302302
{
303-
return $this->resolve()->searchArray($needleType);
303+
return $this->resolve()->searchArray($needleType, $strict);
304304
}
305305

306306
public function shiftArray(): Type

src/Type/Traits/MaybeArrayTypeTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
7979
return new ErrorType();
8080
}
8181

82-
public function searchArray(Type $needleType): Type
82+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
8383
{
8484
return new ErrorType();
8585
}

src/Type/Traits/NonArrayTypeTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
7979
return new ErrorType();
8080
}
8181

82-
public function searchArray(Type $needleType): Type
82+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
8383
{
8484
return new ErrorType();
8585
}

src/Type/Type.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public function popArray(): Type;
172172

173173
public function reverseArray(TrinaryLogic $preserveKeys): Type;
174174

175-
public function searchArray(Type $needleType): Type;
175+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type;
176176

177177
public function shiftArray(): Type;
178178

src/Type/UnionType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -779,9 +779,9 @@ public function reverseArray(TrinaryLogic $preserveKeys): Type
779779
return $this->unionTypes(static fn (Type $type): Type => $type->reverseArray($preserveKeys));
780780
}
781781

782-
public function searchArray(Type $needleType): Type
782+
public function searchArray(Type $needleType, TrinaryLogic $strict): Type
783783
{
784-
return $this->unionTypes(static fn (Type $type): Type => $type->searchArray($needleType));
784+
return $this->unionTypes(static fn (Type $type): Type => $type->searchArray($needleType, $strict));
785785
}
786786

787787
public function shiftArray(): Type

tests/PHPStan/Analyser/nsrt/array-search.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function normalArrays(array $arr, string $string): void
3030

3131
if (array_key_exists(17, $arr) && $arr[17] === 'foo') {
3232
assertType('int', array_search('foo', $arr, true));
33-
assertType('int|false', array_search('foo', $arr));
33+
assertType('int', array_search('foo', $arr));
3434
assertType('int|false', array_search($string, $arr, true));
3535
}
3636
}
@@ -39,25 +39,33 @@ public function constantArrays(array $arr, string $string): void
3939
{
4040
/** @var array{'a', 'b', 'c'} $arr */
4141
assertType('1', array_search('b', $arr, true));
42-
assertType('0|1|2|false', array_search('b', $arr));
42+
assertType('1', array_search('b', $arr));
4343
assertType('0|1|2|false', array_search($string, $arr, true));
44+
assertType('0|1|2|false', array_search($string, $arr, false));
4445

4546
/** @var array{} $arr */
4647
assertType('false', array_search('b', $arr, true));
4748
assertType('false', array_search('b', $arr));
4849
assertType('false', array_search($string, $arr, true));
50+
assertType('false', array_search($string, $arr, false));
51+
52+
/** @var array{1, '1', '2'} $arr */
53+
assertType('1', array_search('1', $arr, true));
54+
assertType('0|1', array_search('1', $arr));
55+
assertType('1|2|false', array_search($string, $arr, true));
56+
assertType('0|1|2|false', array_search($string, $arr, false));
4957
}
5058

5159
public function constantArraysWithOptionalKeys(array $arr, string $string): void
5260
{
5361
/** @var array{0: 'a', 1?: 'b', 2: 'c'} $arr */
5462
assertType('1|false', array_search('b', $arr, true));
55-
assertType('0|1|2|false', array_search('b', $arr));
63+
assertType('1|false', array_search('b', $arr));
5664
assertType('0|1|2|false', array_search($string, $arr, true));
5765

5866
/** @var array{0: 'a', 1?: 'b', 2: 'b'} $arr */
5967
assertType('1|2', array_search('b', $arr, true));
60-
assertType('0|1|2|false', array_search('b', $arr));
68+
assertType('1|2', array_search('b', $arr));
6169
assertType('0|1|2|false', array_search($string, $arr, true));
6270
}
6371

tests/PHPStan/Analyser/nsrt/bug-3789.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ function doFoo(string $needle, array $haystack): void {
1919
assertType('0|1|2', array_search('foo', $haystack, true));
2020

2121
assertType('0|1|2|false', array_search($needle, $haystack));
22-
assertType('0|1|2|false', array_search('foo', $haystack));
22+
assertType('0|1|2', array_search('foo', $haystack));
2323
}

tests/PHPStan/Analyser/nsrt/bug-7809.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
use function PHPStan\Testing\assertType;
77

88
function foo(bool $strict = false): void {
9-
assertType('0|1|2|false', array_search('c', ['a', 'b', 'c'], $strict));
9+
assertType('2', array_search('c', ['a', 'b', 'c'], $strict));
1010
}
1111

1212
function bar(): void{
1313
assertType('2', array_search('c', ['a', 'b', 'c'], true));
1414
}
1515

1616
function baz(): void{
17-
assertType('0|1|2|false', array_search('c', ['a', 'b', 'c'], false));
17+
assertType('2', array_search('c', ['a', 'b', 'c'], false));
1818
}

0 commit comments

Comments
 (0)