Skip to content

Commit baa3d1e

Browse files
authored
Merge branch refs/heads/1.12.x into 2.0.x
2 parents 9f54bdb + 80fdfab commit baa3d1e

File tree

3 files changed

+308
-0
lines changed

3 files changed

+308
-0
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,60 @@ public function specifyTypesInCondition(
784784
$scope,
785785
)->setRootExpr($expr),
786786
);
787+
} else {
788+
$varType = $scope->getType($var->var);
789+
if ($varType->isArray()->yes() && !$varType->isIterableAtLeastOnce()->no()) {
790+
$varIterableKeyType = $varType->getIterableKeyType();
791+
792+
if ($varIterableKeyType->isConstantScalarValue()->yes()) {
793+
$narrowedKey = TypeCombinator::union(
794+
$varIterableKeyType,
795+
TypeCombinator::remove($varIterableKeyType->toString(), new ConstantStringType('')),
796+
);
797+
798+
if (!$varType->hasOffsetValueType(new ConstantIntegerType(0))->no()) {
799+
$narrowedKey = TypeCombinator::union(
800+
$narrowedKey,
801+
new ConstantBooleanType(false),
802+
);
803+
}
804+
805+
if (!$varType->hasOffsetValueType(new ConstantIntegerType(1))->no()) {
806+
$narrowedKey = TypeCombinator::union(
807+
$narrowedKey,
808+
new ConstantBooleanType(true),
809+
);
810+
}
811+
812+
if (!$varType->hasOffsetValueType(new ConstantStringType(''))->no()) {
813+
$narrowedKey = TypeCombinator::addNull($narrowedKey);
814+
}
815+
816+
if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) {
817+
$narrowedKey = TypeCombinator::union($narrowedKey, new FloatType());
818+
}
819+
} else {
820+
$narrowedKey = new MixedType(
821+
false,
822+
new UnionType([
823+
new ArrayType(new MixedType(), new MixedType()),
824+
new ObjectWithoutClassType(),
825+
new ResourceType(),
826+
]),
827+
);
828+
}
829+
830+
$types = $types->unionWith(
831+
$this->create(
832+
$var->dim,
833+
$narrowedKey,
834+
$context,
835+
false,
836+
$scope,
837+
$rootExpr,
838+
),
839+
);
840+
}
787841
}
788842
}
789843

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug11716;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class TypeExpression
8+
{
9+
/**
10+
* @return '&'|'|'
11+
*/
12+
public function parse(string $glue): string
13+
{
14+
$seenGlues = ['|' => false, '&' => false];
15+
16+
assertType("array{|: false, &: false}", $seenGlues);
17+
18+
if ($glue !== '') {
19+
assertType('non-empty-string', $glue);
20+
21+
\assert(isset($seenGlues[$glue]));
22+
$seenGlues[$glue] = true;
23+
24+
assertType("'&'|'|'", $glue);
25+
assertType('array{|: bool, &: bool}', $seenGlues);
26+
} else {
27+
assertType("''", $glue);
28+
}
29+
30+
assertType("''|'&'|'|'", $glue);
31+
assertType("array{|: bool, &: bool}", $seenGlues);
32+
33+
return array_key_first($seenGlues);
34+
}
35+
}
36+
37+
/**
38+
* @param array<int, string> $arr
39+
*/
40+
function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): void {
41+
if (isset($generalArr[$mixed])) {
42+
assertType('mixed~(array|object|resource)', $mixed);
43+
} else {
44+
assertType('mixed', $mixed);
45+
}
46+
assertType('mixed', $mixed);
47+
48+
if (isset($generalArr[$i])) {
49+
assertType('int', $i);
50+
} else {
51+
assertType('int', $i);
52+
}
53+
assertType('int', $i);
54+
55+
if (isset($generalArr[$s])) {
56+
assertType('string', $s);
57+
} else {
58+
assertType('string', $s);
59+
}
60+
assertType('string', $s);
61+
62+
if (isset($arr[$mixed])) {
63+
assertType('mixed~(array|object|resource)', $mixed);
64+
} else {
65+
assertType('mixed', $mixed);
66+
}
67+
assertType('mixed', $mixed);
68+
69+
if (isset($arr[$i])) {
70+
assertType('int', $i);
71+
} else {
72+
assertType('int', $i);
73+
}
74+
assertType('int', $i);
75+
76+
if (isset($arr[$s])) {
77+
assertType('string', $s);
78+
} else {
79+
assertType('string', $s);
80+
}
81+
assertType('string', $s);
82+
}
83+
84+
/**
85+
* @param array<int, array<string, float>> $arr
86+
*/
87+
function multiDim($mixed, $mixed2, array $arr) {
88+
if (isset($arr[$mixed])) {
89+
assertType('mixed~(array|object|resource)', $mixed);
90+
} else {
91+
assertType('mixed', $mixed);
92+
}
93+
assertType('mixed', $mixed);
94+
95+
if (isset($arr[$mixed]) && isset($arr[$mixed][$mixed2])) {
96+
assertType('mixed~(array|object|resource)', $mixed);
97+
assertType('mixed~(array|object|resource)', $mixed2);
98+
} else {
99+
assertType('mixed', $mixed);
100+
}
101+
assertType('mixed', $mixed);
102+
103+
if (isset($arr[$mixed][$mixed2])) {
104+
assertType('mixed~(array|object|resource)', $mixed);
105+
assertType('mixed~(array|object|resource)', $mixed2);
106+
} else {
107+
assertType('mixed', $mixed);
108+
assertType('mixed', $mixed2);
109+
}
110+
assertType('mixed', $mixed);
111+
assertType('mixed', $mixed2);
112+
}
113+
114+
/**
115+
* @param array<int, string> $arr
116+
*/
117+
function emptyArrr($mixed, array $arr)
118+
{
119+
if (count($arr) !== 0) {
120+
return;
121+
}
122+
123+
assertType('array{}', $arr);
124+
if (isset($arr[$mixed])) {
125+
assertType('mixed', $mixed);
126+
} else {
127+
assertType('mixed', $mixed);
128+
}
129+
assertType('mixed', $mixed);
130+
}
131+
132+
function emptyString($mixed)
133+
{
134+
// see https://3v4l.org/XHZdr
135+
$arr = ['' => 1, 'a' => 2];
136+
if (isset($arr[$mixed])) {
137+
assertType("''|'a'|null", $mixed);
138+
} else {
139+
assertType('mixed', $mixed); // could be mixed~(''|'a'|null)
140+
}
141+
assertType('mixed', $mixed);
142+
}
143+
144+
function numericString($mixed, int $i, string $s)
145+
{
146+
$arr = ['1' => 1, '2' => 2];
147+
if (isset($arr[$mixed])) {
148+
assertType("1|2|'1'|'2'|float|true", $mixed);
149+
} else {
150+
assertType('mixed', $mixed);
151+
}
152+
assertType('mixed', $mixed);
153+
154+
$arr = ['0' => 1, '2' => 2];
155+
if (isset($arr[$mixed])) {
156+
assertType("0|2|'0'|'2'|float|false", $mixed);
157+
} else {
158+
assertType('mixed', $mixed);
159+
}
160+
assertType('mixed', $mixed);
161+
162+
$arr = ['1' => 1, '2' => 2];
163+
if (isset($arr[$i])) {
164+
assertType("1|2", $i);
165+
} else {
166+
assertType('int', $i);
167+
}
168+
assertType('int', $i);
169+
170+
$arr = ['1' => 1, '2' => 2, 3 => 3];
171+
if (isset($arr[$s])) {
172+
assertType("'1'|'2'|'3'", $s);
173+
} else {
174+
assertType('string', $s);
175+
}
176+
assertType('string', $s);
177+
178+
$arr = ['1' => 1, '2' => 2, 3 => 3];
179+
if (isset($arr[substr($s, 10)])) {
180+
assertType("string", $s);
181+
assertType("'1'|'2'|'3'", substr($s, 10));
182+
} else {
183+
assertType('string', $s);
184+
}
185+
assertType('string', $s);
186+
}
187+
188+
function intKeys($mixed)
189+
{
190+
$arr = [1 => 1, 2 => 2];
191+
if (isset($arr[$mixed])) {
192+
assertType("1|2|'1'|'2'|float|true", $mixed);
193+
} else {
194+
assertType('mixed', $mixed);
195+
}
196+
assertType('mixed', $mixed);
197+
198+
$arr = [0 => 0, 1 => 1, 2 => 2];
199+
if (isset($arr[$mixed])) {
200+
assertType("0|1|2|'0'|'1'|'2'|bool|float", $mixed);
201+
} else {
202+
assertType('mixed', $mixed);
203+
}
204+
assertType('mixed', $mixed);
205+
}
206+
207+
function arrayAccess(\ArrayAccess $arr, $mixed) {
208+
if (isset($arr[$mixed])) {
209+
assertType("mixed", $mixed);
210+
} else {
211+
assertType('mixed', $mixed);
212+
}
213+
assertType('mixed', $mixed);
214+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Bug8559;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class X
8+
{
9+
const KEYS = ['a' => 1, 'b' => 2];
10+
11+
/**
12+
* @phpstan-assert key-of<self::KEYS> $key
13+
* @return value-of<self::KEYS>
14+
*/
15+
public static function get(string $key): int
16+
{
17+
assert(isset(self::KEYS[$key]));
18+
assertType("'a'|'b'", $key);
19+
return self::KEYS[$key];
20+
}
21+
22+
/**
23+
* @phpstan-assert key-of<self::KEYS> $key
24+
* @return value-of<self::KEYS>
25+
*/
26+
public static function get2(string $key): int
27+
{
28+
assert(in_array($key, array_keys(self::KEYS), true));
29+
assertType("'a'|'b'", $key);
30+
return self::KEYS[$key];
31+
}
32+
}
33+
34+
$key = 'x';
35+
$v = X::get($key);
36+
assertType("*NEVER*", $key);
37+
38+
$key = 'a';
39+
$v = X::get($key);
40+
assertType("'a'", $key);

0 commit comments

Comments
 (0)