Skip to content

Commit 1efb0ea

Browse files
Fix array_column inference
1 parent 0754f5b commit 1efb0ea

File tree

3 files changed

+59
-31
lines changed

3 files changed

+59
-31
lines changed

src/Type/Php/ArrayColumnHelper.php

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PHPStan\Analyser\Scope;
66
use PHPStan\DependencyInjection\AutowiredService;
77
use PHPStan\Php\PhpVersion;
8-
use PHPStan\ShouldNotHappenException;
98
use PHPStan\TrinaryLogic;
109
use PHPStan\Type\Accessory\AccessoryArrayListType;
1110
use PHPStan\Type\Accessory\NonEmptyArrayType;
@@ -39,14 +38,10 @@ public function getReturnValueType(Type $arrayType, Type $columnType, Scope $sco
3938
}
4039

4140
$iterableValueType = $arrayType->getIterableValueType();
42-
$returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false);
41+
[$returnValueType, $certainty] = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope);
4342

44-
if ($returnValueType === null) {
45-
$returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, true);
43+
if (!$certainty->yes()) {
4644
$iterableAtLeastOnce = TrinaryLogic::createMaybe();
47-
if ($returnValueType === null) {
48-
throw new ShouldNotHappenException();
49-
}
5045
}
5146

5247
return [$returnValueType, $iterableAtLeastOnce];
@@ -57,15 +52,12 @@ public function getReturnIndexType(Type $arrayType, Type $indexType, Scope $scop
5752
if (!$indexType->isNull()->yes()) {
5853
$iterableValueType = $arrayType->getIterableValueType();
5954

60-
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false);
61-
if ($type !== null) {
55+
[$type, $certainty] = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope);
56+
if ($certainty->yes()) {
6257
return $type;
6358
}
6459

65-
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true);
66-
if ($type !== null) {
67-
return TypeCombinator::union($type, new IntegerType());
68-
}
60+
return TypeCombinator::union($type, new IntegerType());
6961
}
7062

7163
return new IntegerType();
@@ -96,25 +88,20 @@ public function handleConstantArray(ConstantArrayType $arrayType, Type $columnTy
9688
$builder = ConstantArrayTypeBuilder::createEmpty();
9789

9890
foreach ($arrayType->getValueTypes() as $i => $iterableValueType) {
99-
$valueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false);
100-
if ($valueType === null) {
91+
[$valueType, $certainty] = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope);
92+
if (!$certainty->yes()) {
10193
return null;
10294
}
10395
if ($valueType instanceof NeverType) {
10496
continue;
10597
}
10698

10799
if (!$indexType->isNull()->yes()) {
108-
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false);
109-
if ($type !== null) {
100+
[$type, $certainty] = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope);
101+
if ($certainty->yes()) {
110102
$keyType = $type;
111103
} else {
112-
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true);
113-
if ($type !== null) {
114-
$keyType = TypeCombinator::union($type, new IntegerType());
115-
} else {
116-
$keyType = null;
117-
}
104+
$keyType = TypeCombinator::union($type, new IntegerType());
118105
}
119106
} else {
120107
$keyType = null;
@@ -129,11 +116,14 @@ public function handleConstantArray(ConstantArrayType $arrayType, Type $columnTy
129116
return $builder->getArray();
130117
}
131118

132-
private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $scope, bool $allowMaybe): ?Type
119+
/**
120+
* @return array{Type, TrinaryLogic}
121+
*/
122+
private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $scope): array
133123
{
134124
$offsetIsNull = $offsetOrProperty->isNull();
135125
if ($offsetIsNull->yes()) {
136-
return $type;
126+
return [$type, TrinaryLogic::createYes()];
137127
}
138128

139129
$returnTypes = [];
@@ -145,13 +135,13 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $
145135
if (!$type->canAccessProperties()->no()) {
146136
$propertyTypes = $offsetOrProperty->getConstantStrings();
147137
if ($propertyTypes === []) {
148-
return new MixedType();
138+
return [new MixedType(), TrinaryLogic::createMaybe()];
149139
}
150140
foreach ($propertyTypes as $propertyType) {
151141
$propertyName = $propertyType->getValue();
152142
$hasProperty = $type->hasProperty($propertyName);
153143
if ($hasProperty->maybe()) {
154-
return $allowMaybe ? new MixedType() : null;
144+
return [new MixedType(), TrinaryLogic::createMaybe()];
155145
}
156146
if (!$hasProperty->yes()) {
157147
continue;
@@ -161,21 +151,22 @@ private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $
161151
}
162152
}
163153

154+
$certainty = TrinaryLogic::createYes();
164155
if ($type->isOffsetAccessible()->yes()) {
165156
$hasOffset = $type->hasOffsetValueType($offsetOrProperty);
166-
if (!$allowMaybe && $hasOffset->maybe()) {
167-
return null;
157+
if ($hasOffset->maybe()) {
158+
$certainty = TrinaryLogic::createMaybe();
168159
}
169160
if (!$hasOffset->no()) {
170161
$returnTypes[] = $type->getOffsetValueType($offsetOrProperty);
171162
}
172163
}
173164

174165
if ($returnTypes === []) {
175-
return new NeverType();
166+
return [new NeverType(), TrinaryLogic::createYes()];
176167
}
177168

178-
return TypeCombinator::union(...$returnTypes);
169+
return [TypeCombinator::union(...$returnTypes), $certainty];
179170
}
180171

181172
private function castToArrayKeyType(Type $type): Type

tests/PHPStan/Rules/Variables/EmptyRuleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ public function testBug12658(): void
207207
$this->analyse([__DIR__ . '/data/bug-12658.php'], []);
208208
}
209209

210+
public function testBug10367(): void
211+
{
212+
$this->treatPhpDocTypesAsCertain = true;
213+
214+
$this->analyse([__DIR__ . '/data/bug-10367.php'], []);
215+
}
216+
210217
#[RequiresPhp('>= 8.0')]
211218
public function testIssetAfterRememberedConstructor(): void
212219
{
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug10367;
4+
5+
final class PackingSlipPdf
6+
{
7+
public function __construct(array $packingSlipItems, string $fullName)
8+
{
9+
if (empty($packingSlipItems)) {
10+
error_log('$packingSlipItems is empty: check company: ' . $fullName);
11+
} else {
12+
$freightNumbers = array_column($packingSlipItems, 3);
13+
if (empty($freightNumbers)) {
14+
error_log('$freightNumbers is empty: check company: ' . $fullName);
15+
error_log('$freightNumbers is empty: check dataset: ' . print_r($packingSlipItems[0], true));
16+
} elseif (empty($freightNumbers[0])) {
17+
error_log('$freightNumbers[0] is empty: check company: ' . $fullName);
18+
error_log('$freightNumbers[0] is empty: check freigthnumbers: ' . print_r($freightNumbers, true));
19+
error_log('$freightNumbers[0] is empty: check dataset: ' . print_r($packingSlipItems[0], true));
20+
}
21+
}
22+
}
23+
}
24+
25+
$testArray = [
26+
[1 => 123, 2 => 413, 4 => 132],
27+
[1 => 123, 2 => 413, 4 => 132],
28+
];
29+
30+
$packingSlipPdf = new PackingSlipPdf($testArray, "TestName");

0 commit comments

Comments
 (0)