Skip to content

Commit fa4e7d2

Browse files
committed
Prevent creation of IntersectionType with just HasOffsetValueType accessories
1 parent bfa881c commit fa4e7d2

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

src/Analyser/MutatingScope.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
use PHPStan\ShouldNotHappenException;
6464
use PHPStan\TrinaryLogic;
6565
use PHPStan\Type\Accessory\AccessoryArrayListType;
66+
use PHPStan\Type\Accessory\AccessoryType;
6667
use PHPStan\Type\Accessory\HasOffsetValueType;
6768
use PHPStan\Type\Accessory\NonEmptyArrayType;
6869
use PHPStan\Type\Accessory\OversizedArrayType;
@@ -3163,10 +3164,15 @@ public function addTypeToExpression(Expr $expr, Type $type): self
31633164
return $this->specifyExpressionType($expr, $newType, $newType, TrinaryLogic::createYes());
31643165
}
31653166

3167+
$newNativeType = TypeCombinator::intersect($type, $nativeType);
3168+
if ($newNativeType instanceof AccessoryType) {
3169+
$newNativeType = $nativeType;
3170+
}
3171+
31663172
return $this->specifyExpressionType(
31673173
$expr,
31683174
TypeCombinator::intersect($type, $originalExprType),
3169-
TypeCombinator::intersect($type, $nativeType),
3175+
$newNativeType,
31703176
TrinaryLogic::createYes(),
31713177
);
31723178
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@ public function testBug2823(): void
220220
$this->assertNoErrors($errors);
221221
}
222222

223+
public function testBug14604(): void
224+
{
225+
// crash
226+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-14604.php');
227+
$this->assertNoErrors($errors);
228+
}
229+
223230
public function testBug13424(): void
224231
{
225232
// crash
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Bug14604;
4+
5+
final class A
6+
{
7+
/** @return array{} */
8+
public function selectedSearchValues(): array
9+
{
10+
/** @var array{from: string, to: string} $dates */
11+
$dates = ($_GET['dates'] ?? []) ?: throw new \Exception('No Dates selected');
12+
if (empty($dates['from']) || empty($dates['to'])) {
13+
throw new \Exception('Dates not selected');
14+
}
15+
16+
/** @var array{latitude: string, longitude: string} $dates */
17+
$locations = ($_GET['location'] ?? []) ?: throw new \Exception('No Location selected');
18+
19+
return [];
20+
}
21+
}
22+
23+
final class B
24+
{
25+
/** @return array<string, string> */
26+
public function mixedKeyEmpty(): array
27+
{
28+
/** @var array<string, string> $foo */
29+
$foo = ($_GET['foo'] ?? []) ?: throw new \Exception();
30+
$dynKey = (string) $_GET['k'];
31+
if (empty($foo['a']) || empty($foo[$dynKey])) {
32+
throw new \Exception();
33+
}
34+
35+
return $foo;
36+
}
37+
}
38+
39+
final class C
40+
{
41+
/** @return array<int, string> */
42+
public function countThenEmpty(): array
43+
{
44+
/** @var array<int, string> $foo */
45+
$foo = ($_GET['foo'] ?? []) ?: throw new \Exception();
46+
if (count($foo) >= 2) {
47+
if (empty($foo[0])) {
48+
throw new \Exception();
49+
}
50+
51+
return $foo;
52+
}
53+
54+
return [];
55+
}
56+
}

0 commit comments

Comments
 (0)