Skip to content

Commit b350eb9

Browse files
authored
Extract ArrayColumnHelper from ArrayColumnFunctionReturnTypeExtension
1 parent 8f05c28 commit b350eb9

File tree

3 files changed

+204
-169
lines changed

3 files changed

+204
-169
lines changed

conf/config.neon

+3
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,9 @@ services:
11771177
tags:
11781178
- phpstan.broker.dynamicFunctionReturnTypeExtension
11791179

1180+
-
1181+
class: PHPStan\Type\Php\ArrayColumnHelper
1182+
11801183
-
11811184
class: PHPStan\Type\Php\ArrayColumnFunctionReturnTypeExtension
11821185
tags:

src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php

+5-169
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,17 @@
44

55
use PhpParser\Node\Expr\FuncCall;
66
use PHPStan\Analyser\Scope;
7-
use PHPStan\Php\PhpVersion;
87
use PHPStan\Reflection\FunctionReflection;
9-
use PHPStan\ShouldNotHappenException;
10-
use PHPStan\TrinaryLogic;
11-
use PHPStan\Type\Accessory\AccessoryArrayListType;
12-
use PHPStan\Type\Accessory\NonEmptyArrayType;
13-
use PHPStan\Type\ArrayType;
14-
use PHPStan\Type\Constant\ConstantArrayType;
15-
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
168
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
17-
use PHPStan\Type\IntegerType;
18-
use PHPStan\Type\MixedType;
19-
use PHPStan\Type\NeverType;
209
use PHPStan\Type\Type;
21-
use PHPStan\Type\TypeCombinator;
2210
use function count;
2311

2412
final class ArrayColumnFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
2513
{
2614

27-
public function __construct(private PhpVersion $phpVersion)
15+
public function __construct(
16+
private ArrayColumnHelper $arrayColumnHelper,
17+
)
2818
{
2919
}
3020

@@ -46,167 +36,13 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
4636

4737
$constantArrayTypes = $arrayType->getConstantArrays();
4838
if (count($constantArrayTypes) === 1) {
49-
$type = $this->handleConstantArray($constantArrayTypes[0], $columnType, $indexType, $scope);
39+
$type = $this->arrayColumnHelper->handleConstantArray($constantArrayTypes[0], $columnType, $indexType, $scope);
5040
if ($type !== null) {
5141
return $type;
5242
}
5343
}
5444

55-
return $this->handleAnyArray($arrayType, $columnType, $indexType, $scope);
56-
}
57-
58-
private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexType, Scope $scope): Type
59-
{
60-
$iterableAtLeastOnce = $arrayType->isIterableAtLeastOnce();
61-
if ($iterableAtLeastOnce->no()) {
62-
return new ConstantArrayType([], []);
63-
}
64-
65-
$iterableValueType = $arrayType->getIterableValueType();
66-
$returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false);
67-
68-
if ($returnValueType === null) {
69-
$returnValueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, true);
70-
$iterableAtLeastOnce = TrinaryLogic::createMaybe();
71-
if ($returnValueType === null) {
72-
throw new ShouldNotHappenException();
73-
}
74-
}
75-
76-
if ($returnValueType instanceof NeverType) {
77-
return new ConstantArrayType([], []);
78-
}
79-
80-
if ($indexType !== null) {
81-
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false);
82-
if ($type !== null) {
83-
$returnKeyType = $type;
84-
} else {
85-
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true);
86-
if ($type !== null) {
87-
$returnKeyType = TypeCombinator::union($type, new IntegerType());
88-
} else {
89-
$returnKeyType = new IntegerType();
90-
}
91-
}
92-
} else {
93-
$returnKeyType = new IntegerType();
94-
}
95-
96-
$returnType = new ArrayType($this->castToArrayKeyType($returnKeyType), $returnValueType);
97-
98-
if ($iterableAtLeastOnce->yes()) {
99-
$returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType());
100-
}
101-
if ($indexType === null) {
102-
$returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType());
103-
}
104-
105-
return $returnType;
106-
}
107-
108-
private function handleConstantArray(ConstantArrayType $arrayType, Type $columnType, ?Type $indexType, Scope $scope): ?Type
109-
{
110-
$builder = ConstantArrayTypeBuilder::createEmpty();
111-
112-
foreach ($arrayType->getValueTypes() as $i => $iterableValueType) {
113-
$valueType = $this->getOffsetOrProperty($iterableValueType, $columnType, $scope, false);
114-
if ($valueType === null) {
115-
return null;
116-
}
117-
if ($valueType instanceof NeverType) {
118-
continue;
119-
}
120-
121-
if ($indexType !== null) {
122-
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, false);
123-
if ($type !== null) {
124-
$keyType = $type;
125-
} else {
126-
$type = $this->getOffsetOrProperty($iterableValueType, $indexType, $scope, true);
127-
if ($type !== null) {
128-
$keyType = TypeCombinator::union($type, new IntegerType());
129-
} else {
130-
$keyType = null;
131-
}
132-
}
133-
} else {
134-
$keyType = null;
135-
}
136-
137-
if ($keyType !== null) {
138-
$keyType = $this->castToArrayKeyType($keyType);
139-
}
140-
$builder->setOffsetValueType($keyType, $valueType, $arrayType->isOptionalKey($i));
141-
}
142-
143-
return $builder->getArray();
144-
}
145-
146-
private function getOffsetOrProperty(Type $type, Type $offsetOrProperty, Scope $scope, bool $allowMaybe): ?Type
147-
{
148-
$offsetIsNull = $offsetOrProperty->isNull();
149-
if ($offsetIsNull->yes()) {
150-
return $type;
151-
}
152-
153-
$returnTypes = [];
154-
155-
if ($offsetIsNull->maybe()) {
156-
$returnTypes[] = $type;
157-
}
158-
159-
if (!$type->canAccessProperties()->no()) {
160-
$propertyTypes = $offsetOrProperty->getConstantStrings();
161-
if ($propertyTypes === []) {
162-
return new MixedType();
163-
}
164-
foreach ($propertyTypes as $propertyType) {
165-
$propertyName = $propertyType->getValue();
166-
$hasProperty = $type->hasProperty($propertyName);
167-
if ($hasProperty->maybe()) {
168-
return $allowMaybe ? new MixedType() : null;
169-
}
170-
if (!$hasProperty->yes()) {
171-
continue;
172-
}
173-
174-
$returnTypes[] = $type->getProperty($propertyName, $scope)->getReadableType();
175-
}
176-
}
177-
178-
if ($type->isOffsetAccessible()->yes()) {
179-
$hasOffset = $type->hasOffsetValueType($offsetOrProperty);
180-
if (!$allowMaybe && $hasOffset->maybe()) {
181-
return null;
182-
}
183-
if (!$hasOffset->no()) {
184-
$returnTypes[] = $type->getOffsetValueType($offsetOrProperty);
185-
}
186-
}
187-
188-
if ($returnTypes === []) {
189-
return new NeverType();
190-
}
191-
192-
return TypeCombinator::union(...$returnTypes);
193-
}
194-
195-
private function castToArrayKeyType(Type $type): Type
196-
{
197-
$isArray = $type->isArray();
198-
if ($isArray->yes()) {
199-
return $this->phpVersion->throwsTypeErrorForInternalFunctions() ? new NeverType() : new IntegerType();
200-
}
201-
if ($isArray->no()) {
202-
return $type->toArrayKey();
203-
}
204-
$withoutArrayType = TypeCombinator::remove($type, new ArrayType(new MixedType(), new MixedType()));
205-
$keyType = $withoutArrayType->toArrayKey();
206-
if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) {
207-
return $keyType;
208-
}
209-
return TypeCombinator::union($keyType, new IntegerType());
45+
return $this->arrayColumnHelper->handleAnyArray($arrayType, $columnType, $indexType, $scope);
21046
}
21147

21248
}

0 commit comments

Comments
 (0)