Skip to content

Commit 3b64b12

Browse files
committed
Fixed iterable<T> vs. empty constant array
1 parent a44bad9 commit 3b64b12

File tree

5 files changed

+84
-0
lines changed

5 files changed

+84
-0
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public function __construct(array $keyTypes, array $valueTypes, int $nextAutoInd
5757
$this->nextAutoIndex = $nextAutoIndex;
5858
}
5959

60+
public function isEmpty(): bool
61+
{
62+
return count($this->keyTypes) === 0;
63+
}
64+
6065
public function getNextAutoIndex(): int
6166
{
6267
return $this->nextAutoIndex;

src/Type/IterableType.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Type;
44

55
use PHPStan\TrinaryLogic;
6+
use PHPStan\Type\Constant\ConstantArrayType;
67
use PHPStan\Type\Generic\TemplateMixedType;
78
use PHPStan\Type\Generic\TemplateType;
89
use PHPStan\Type\Generic\TemplateTypeMap;
@@ -53,6 +54,9 @@ public function getReferencedClasses(): array
5354

5455
public function accepts(Type $type, bool $strictTypes): TrinaryLogic
5556
{
57+
if ($type instanceof ConstantArrayType && $type->isEmpty()) {
58+
return TrinaryLogic::createYes();
59+
}
5660
if ($type->isIterable()->yes()) {
5761
return $this->getIterableValueType()->accepts($type->getIterableValueType(), $strictTypes)
5862
->and($this->getIterableKeyType()->accepts($type->getIterableKeyType(), $strictTypes));
@@ -118,6 +122,10 @@ public function isSubTypeOf(Type $otherType): TrinaryLogic
118122
$limit = TrinaryLogic::createMaybe();
119123
}
120124

125+
if ($otherType instanceof ConstantArrayType && $otherType->isEmpty()) {
126+
return TrinaryLogic::createMaybe();
127+
}
128+
121129
return $limit->and(
122130
$otherType->isIterable(),
123131
$otherType->getIterableValueType()->isSuperTypeOf($this->itemType),

tests/PHPStan/Rules/Methods/data/returnTypes.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,3 +1212,17 @@ public function key()
12121212
}
12131213

12141214
}
1215+
1216+
class Bug3072
1217+
{
1218+
1219+
/**
1220+
* @template T
1221+
* @return iterable<T>
1222+
*/
1223+
public function getIterable(): iterable
1224+
{
1225+
return [];
1226+
}
1227+
1228+
}

tests/PHPStan/Type/Constant/ConstantArrayTypeTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,12 @@ public function dataIsSuperTypeOf(): iterable
182182
new ArrayType(new MixedType(), new MixedType()),
183183
TrinaryLogic::createMaybe(),
184184
];
185+
186+
yield [
187+
new ConstantArrayType([], []),
188+
new IterableType(new MixedType(false), new MixedType(true)),
189+
TrinaryLogic::createMaybe(),
190+
];
185191
}
186192

187193
/**

tests/PHPStan/Type/IterableTypeTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Type\Accessory\HasMethodType;
77
use PHPStan\Type\Accessory\HasPropertyType;
88
use PHPStan\Type\Constant\ConstantArrayType;
9+
use PHPStan\Type\Generic\TemplateMixedType;
910
use PHPStan\Type\Generic\TemplateTypeFactory;
1011
use PHPStan\Type\Generic\TemplateTypeScope;
1112
use PHPStan\Type\Generic\TemplateTypeVariance;
@@ -46,6 +47,11 @@ public function dataIsSuperTypeOf(): array
4647
new ObjectType('Iterator'),
4748
TrinaryLogic::createMaybe(),
4849
],
50+
[
51+
new IterableType(new MixedType(false), new MixedType(true)),
52+
new ConstantArrayType([], []),
53+
TrinaryLogic::createYes(),
54+
],
4955
];
5056
}
5157

@@ -299,4 +305,49 @@ public function testDescribe(Type $type, string $expect): void
299305
$this->assertSame($expect, $result);
300306
}
301307

308+
public function dataAccepts(): array
309+
{
310+
/** @var TemplateMixedType $t */
311+
$t = TemplateTypeFactory::create(
312+
TemplateTypeScope::createWithFunction('foo'),
313+
'T',
314+
null,
315+
TemplateTypeVariance::createInvariant()
316+
);
317+
return [
318+
[
319+
new IterableType(
320+
new MixedType(),
321+
$t,
322+
),
323+
new ConstantArrayType([], []),
324+
TrinaryLogic::createYes(),
325+
],
326+
[
327+
new IterableType(
328+
new MixedType(),
329+
$t->toArgument()
330+
),
331+
new ConstantArrayType([], []),
332+
TrinaryLogic::createYes(),
333+
],
334+
];
335+
}
336+
337+
/**
338+
* @dataProvider dataAccepts
339+
* @param IterableType $iterableType
340+
* @param Type $otherType
341+
* @param TrinaryLogic $expectedResult
342+
*/
343+
public function testAccepts(IterableType $iterableType, Type $otherType, TrinaryLogic $expectedResult): void
344+
{
345+
$actualResult = $iterableType->accepts($otherType, true);
346+
$this->assertSame(
347+
$expectedResult->describe(),
348+
$actualResult->describe(),
349+
sprintf('%s -> accepts(%s)', $iterableType->describe(VerbosityLevel::precise()), $otherType->describe(VerbosityLevel::precise()))
350+
);
351+
}
352+
302353
}

0 commit comments

Comments
 (0)