Skip to content

Commit b650df6

Browse files
authored
Array is non-empty even when in_array is not strict
1 parent b543e8f commit b650df6

File tree

8 files changed

+396
-3
lines changed

8 files changed

+396
-3
lines changed

src/Rules/Comparison/ImpossibleCheckTypeHelper.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public function findSpecifiedType(
8888
return null;
8989
} elseif ($functionName === 'array_search') {
9090
return null;
91-
} elseif ($functionName === 'in_array' && $argsCount >= 3) {
91+
} elseif ($functionName === 'in_array' && $argsCount >= 2) {
9292
$haystackArg = $node->getArgs()[1]->value;
9393
$haystackType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($haystackArg) : $scope->getNativeType($haystackArg));
9494
if ($haystackType instanceof MixedType) {
@@ -101,6 +101,21 @@ public function findSpecifiedType(
101101

102102
$needleArg = $node->getArgs()[0]->value;
103103
$needleType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($needleArg) : $scope->getNativeType($needleArg));
104+
105+
$isStrictComparison = false;
106+
if ($argsCount >= 3) {
107+
$strictNodeType = $scope->getType($node->getArgs()[2]->value);
108+
$isStrictComparison = $strictNodeType->isTrue()->yes();
109+
}
110+
111+
$isStrictComparison = $isStrictComparison
112+
|| $needleType->isEnum()->yes()
113+
|| $haystackType->getIterableValueType()->isEnum()->yes();
114+
115+
if (!$isStrictComparison) {
116+
return null;
117+
}
118+
104119
$valueType = $haystackType->getIterableValueType();
105120
$constantNeedleTypesCount = count($needleType->getFiniteTypes());
106121
$constantHaystackTypesCount = count($valueType->getFiniteTypes());

src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,24 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
8686
|| $arrayValueType->isEnum()->yes();
8787

8888
if (!$isStrictComparison) {
89+
if (
90+
$context->true()
91+
&& $arrayType->isArray()->yes()
92+
&& $arrayType->getIterableValueType()->isSuperTypeOf($needleType)->yes()
93+
) {
94+
return $this->typeSpecifier->create(
95+
$node->getArgs()[1]->value,
96+
TypeCombinator::intersect($arrayType, new NonEmptyArrayType()),
97+
$context,
98+
false,
99+
$scope,
100+
);
101+
}
102+
89103
return new SpecifiedTypes();
90104
}
91105

92106
$specifiedTypes = new SpecifiedTypes();
93-
94107
if (
95108
$context->true()
96109
|| (

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,10 @@ public function dataFileAsserts(): iterable
14421442
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9397.php');
14431443
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-10080.php');
14441444
yield from $this->gatherAssertTypes(__DIR__ . '/data/http-response-header.php');
1445+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9662.php');
1446+
if (PHP_VERSION_ID >= 80100) {
1447+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9662-enums.php');
1448+
}
14451449
yield from $this->gatherAssertTypes(__DIR__ . '/data/impure-error-log.php');
14461450
yield from $this->gatherAssertTypes(__DIR__ . '/data/falsy-isset.php');
14471451
yield from $this->gatherAssertTypes(__DIR__ . '/data/falsey-coalesce.php');
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php // lint >= 8.1
2+
3+
namespace Bug9662Enums;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
enum Suit
8+
{
9+
case Hearts;
10+
case Diamonds;
11+
case Clubs;
12+
case Spades;
13+
}
14+
15+
/**
16+
* @param array<Suit> $suite
17+
*/
18+
function doEnum(array $suite, array $arr) {
19+
if (in_array('NotAnEnumCase', $suite) === false) {
20+
assertType('array<Bug9662Enums\Suit>', $suite);
21+
} else {
22+
assertType("non-empty-array<Bug9662Enums\Suit>", $suite);
23+
}
24+
assertType('array<Bug9662Enums\Suit>', $suite);
25+
26+
if (in_array(Suit::Hearts, $suite) === false) {
27+
assertType('array<Bug9662Enums\Suit~Bug9662Enums\Suit::Hearts>', $suite);
28+
} else {
29+
assertType("non-empty-array<Bug9662Enums\Suit>", $suite);
30+
}
31+
assertType('array<Bug9662Enums\Suit>', $suite);
32+
33+
34+
if (in_array(Suit::Hearts, $arr) === false) {
35+
assertType('array<mixed~Bug9662Enums\Suit::Hearts>', $arr);
36+
} else {
37+
assertType("non-empty-array", $arr);
38+
}
39+
assertType('array', $arr);
40+
41+
42+
if (in_array('NotAnEnumCase', $arr) === false) {
43+
assertType('array', $arr);
44+
} else {
45+
assertType("non-empty-array", $arr);
46+
}
47+
assertType('array', $arr);
48+
}
49+
50+
enum StringBackedSuit: string
51+
{
52+
case Hearts = 'H';
53+
case Diamonds = 'D';
54+
case Clubs = 'C';
55+
case Spades = 'S';
56+
}
57+
58+
/**
59+
* @param array<StringBackedSuit> $suite
60+
*/
61+
function doBackedEnum(array $suite, array $arr, string $s, int $i, $mixed) {
62+
if (in_array('NotAnEnumCase', $suite) === false) {
63+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
64+
} else {
65+
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
66+
}
67+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
68+
69+
if (in_array(StringBackedSuit::Hearts, $suite) === false) {
70+
assertType('array<Bug9662Enums\StringBackedSuit~Bug9662Enums\StringBackedSuit::Hearts>', $suite);
71+
} else {
72+
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
73+
}
74+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
75+
76+
77+
if (in_array($s, $suite) === false) {
78+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
79+
} else {
80+
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
81+
}
82+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
83+
84+
if (in_array($i, $suite) === false) {
85+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
86+
} else {
87+
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
88+
}
89+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
90+
91+
if (in_array($mixed, $suite) === false) {
92+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
93+
} else {
94+
assertType("non-empty-array<Bug9662Enums\StringBackedSuit>", $suite);
95+
}
96+
assertType('array<Bug9662Enums\StringBackedSuit>', $suite);
97+
98+
99+
if (in_array(StringBackedSuit::Hearts, $arr) === false) {
100+
assertType('array<mixed~Bug9662Enums\StringBackedSuit::Hearts>', $arr);
101+
} else {
102+
assertType("non-empty-array", $arr);
103+
}
104+
assertType('array', $arr);
105+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
3+
namespace Bug9662;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param array<mixed> $a
9+
* @param array<string> $strings
10+
* @return void
11+
*/
12+
function doFoo(string $s, $a, $strings, $mixed) {
13+
if (in_array('foo', $a, true)) {
14+
assertType('non-empty-array', $a);
15+
} else {
16+
assertType("array<mixed~'foo'>", $a);
17+
}
18+
assertType('array', $a);
19+
20+
if (in_array('foo', $a, false)) {
21+
assertType('non-empty-array', $a);
22+
} else {
23+
assertType("array", $a);
24+
}
25+
assertType('array', $a);
26+
27+
if (in_array('foo', $a)) {
28+
assertType('non-empty-array', $a);
29+
} else {
30+
assertType("array", $a);
31+
}
32+
assertType('array', $a);
33+
34+
if (in_array('0', $a)) {
35+
assertType('non-empty-array', $a);
36+
} else {
37+
assertType("array", $a);
38+
}
39+
assertType('array', $a);
40+
41+
if (in_array('1', $a)) {
42+
assertType('non-empty-array', $a);
43+
} else {
44+
assertType("array", $a);
45+
}
46+
assertType('array', $a);
47+
48+
if (in_array(true, $a)) {
49+
assertType('non-empty-array', $a);
50+
} else {
51+
assertType("array", $a);
52+
}
53+
assertType('array', $a);
54+
55+
if (in_array(false, $a)) {
56+
assertType('non-empty-array', $a);
57+
} else {
58+
assertType("array", $a);
59+
}
60+
assertType('array', $a);
61+
62+
if (in_array($s, $a, true)) {
63+
assertType('non-empty-array', $a);
64+
} else {
65+
assertType("array", $a);
66+
}
67+
assertType('array', $a);
68+
69+
if (in_array($s, $a, false)) {
70+
assertType('non-empty-array', $a);
71+
} else {
72+
assertType("array", $a);
73+
}
74+
assertType('array', $a);
75+
76+
if (in_array($s, $a)) {
77+
assertType('non-empty-array', $a);
78+
} else {
79+
assertType("array", $a);
80+
}
81+
assertType('array', $a);
82+
83+
if (in_array($mixed, $strings, true)) {
84+
assertType('non-empty-array<string>', $strings);
85+
} else {
86+
assertType("array<string>", $strings);
87+
}
88+
assertType('array<string>', $strings);
89+
90+
if (in_array($mixed, $strings, false)) {
91+
assertType('array<string>', $strings);
92+
} else {
93+
assertType("array<string>", $strings);
94+
}
95+
assertType('array<string>', $strings);
96+
97+
if (in_array($mixed, $strings)) {
98+
assertType('array<string>', $strings);
99+
} else {
100+
assertType("array<string>", $strings);
101+
}
102+
assertType('array<string>', $strings);
103+
104+
if (in_array($s, $strings, true)) {
105+
assertType('non-empty-array<string>', $strings);
106+
} else {
107+
assertType("array<string>", $strings);
108+
}
109+
assertType('array<string>', $strings);
110+
111+
if (in_array($s, $strings, false)) {
112+
assertType('non-empty-array<string>', $strings);
113+
} else {
114+
assertType("array<string>", $strings);
115+
}
116+
assertType('array<string>', $strings);
117+
118+
if (in_array($s, $strings)) {
119+
assertType('non-empty-array<string>', $strings);
120+
} else {
121+
assertType("array<string>", $strings);
122+
}
123+
assertType('array<string>', $strings);
124+
125+
if (in_array($s, $strings, true) === true) {
126+
assertType('non-empty-array<string>', $strings);
127+
} else {
128+
assertType("array<string>", $strings);
129+
}
130+
assertType('array<string>', $strings);
131+
132+
if (in_array($s, $strings, false) === true) {
133+
assertType('non-empty-array<string>', $strings);
134+
} else {
135+
assertType("array<string>", $strings);
136+
}
137+
assertType('array<string>', $strings);
138+
139+
if (in_array($s, $strings) === true) {
140+
assertType('non-empty-array<string>', $strings);
141+
} else {
142+
assertType("array<string>", $strings);
143+
}
144+
assertType('array<string>', $strings);
145+
146+
if (in_array($s, $strings, true) === false) {
147+
assertType('array<string>', $strings);
148+
} else {
149+
assertType("non-empty-array<string>", $strings);
150+
}
151+
assertType('array<string>', $strings);
152+
153+
if (in_array($s, $strings, false) === false) {
154+
assertType('array<string>', $strings);
155+
} else {
156+
assertType("non-empty-array<string>", $strings);
157+
}
158+
assertType('array<string>', $strings);
159+
160+
if (in_array($s, $strings) === false) {
161+
assertType('array<string>', $strings);
162+
} else {
163+
assertType("non-empty-array<string>", $strings);
164+
}
165+
assertType('array<string>', $strings);
166+
}
167+
168+
/**
169+
* Add new delivery prices.
170+
*
171+
* @param array $price_list Prices list in multiple arrays (changed to array since 1.5.0)
172+
* @param bool $delete
173+
*/
174+
function addDeliveryPrice($price_list, $delete = false): void
175+
{
176+
if (!$price_list) {
177+
return;
178+
}
179+
180+
$keys = array_keys($price_list[0]);
181+
if (!in_array('id_shop', $keys)) {
182+
$keys[] = 'id_shop';
183+
}
184+
if (!in_array('id_shop_group', $keys)) {
185+
$keys[] = 'id_shop_group';
186+
}
187+
188+
var_dump($keys);
189+
}

0 commit comments

Comments
 (0)