Skip to content

Commit 9b91afb

Browse files
committed
[BE] List type
1 parent 5d17773 commit 9b91afb

32 files changed

+69
-97
lines changed

Diff for: changelog-2.0.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ When PHPStan 2.0 gets released, this will turn into [releases notes on GitHub](h
55
Major new features 🚀
66
=====================
77

8+
* **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen!
9+
* Lists are arrays with sequential integer keys starting at 0
10+
811
Bleeding edge (TODO move to other sections)
912
=====================
1013

@@ -30,8 +33,6 @@ Bleeding edge (TODO move to other sections)
3033
* Report `instanceof` of classes covered by backward compatibility promise but where the assumption might change (https://github.com/phpstan/phpstan-src/commit/996bc69fa977aa64f601bd82b8a0ae39be0cbeef)
3134
* Use explicit mixed for global array variables ([#1411](https://github.com/phpstan/phpstan-src/pull/1411)), thanks @herndlm!
3235
* Add `@readonly` rule that disallows default values ([#1391](https://github.com/phpstan/phpstan-src/pull/1391)), thanks @herndlm!
33-
* **Array `list` type** ([#1751](https://github.com/phpstan/phpstan-src/pull/1751)), #3311, #8185, #6243, thanks @rvanvelzen!
34-
* Lists are arrays with sequential integer keys starting at 0
3536
* Improve error wording of the NonexistentOffset, BooleanAndConstantConditionRule, and BooleanOrConstantConditionRule ([#1882](https://github.com/phpstan/phpstan-src/pull/1882)), thanks @VincentLanglet!
3637
* MissingMagicSerializationMethodsRule ([#1711](https://github.com/phpstan/phpstan-src/pull/1711)), #7482, thanks @staabm!
3738
* Stub files validation - detect duplicate classes and functions (https://github.com/phpstan/phpstan-src/commit/ddf8d5c3859c2c75c20f525a0e2ca8b99032373a, https://github.com/phpstan/phpstan-src/commit/17e4b74335e5235d7cd6708eb687a774a0eeead4)

Diff for: conf/bleedingEdge.neon

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ parameters:
1919
runtimeReflectionRules: true
2020
notAnalysedTrait: true
2121
curlSetOptTypes: true
22-
listType: true
2322
abstractTraitMethod: true
2423
missingMagicSerializationRule: true
2524
nullContextForVoidReturningFunctions: true

Diff for: conf/config.neon

-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ parameters:
5454
runtimeReflectionRules: false
5555
notAnalysedTrait: false
5656
curlSetOptTypes: false
57-
listType: false
5857
abstractTraitMethod: false
5958
missingMagicSerializationRule: false
6059
nullContextForVoidReturningFunctions: false

Diff for: conf/parametersSchema.neon

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ parametersSchema:
4949
runtimeReflectionRules: bool()
5050
notAnalysedTrait: bool()
5151
curlSetOptTypes: bool()
52-
listType: bool()
5352
abstractTraitMethod: bool()
5453
missingMagicSerializationRule: bool()
5554
nullContextForVoidReturningFunctions: bool()

Diff for: src/Analyser/MutatingScope.php

+9-8
Original file line numberDiff line numberDiff line change
@@ -526,10 +526,11 @@ public function getVariableType(string $variableName): Type
526526
return IntegerRangeType::fromInterval(1, null);
527527
}
528528
if ($variableName === 'argv') {
529-
return AccessoryArrayListType::intersectWith(TypeCombinator::intersect(
529+
return TypeCombinator::intersect(
530530
new ArrayType(new IntegerType(), new StringType()),
531531
new NonEmptyArrayType(),
532-
));
532+
new AccessoryArrayListType(),
533+
);
533534
}
534535
if ($this->canAnyVariableExist()) {
535536
return new MixedType();
@@ -3175,7 +3176,7 @@ private function enterFunctionLike(
31753176
if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) {
31763177
$parameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $parameterType);
31773178
} else {
3178-
$parameterType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $parameterType));
3179+
$parameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $parameterType), new AccessoryArrayListType());
31793180
}
31803181
}
31813182
$parameterNode = new Variable($parameter->getName());
@@ -3190,7 +3191,7 @@ private function enterFunctionLike(
31903191
if ($this->phpVersion->supportsNamedArguments() && $functionReflection->acceptsNamedArguments()) {
31913192
$nativeParameterType = new ArrayType(new UnionType([new IntegerType(), new StringType()]), $nativeParameterType);
31923193
} else {
3193-
$nativeParameterType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $nativeParameterType));
3194+
$nativeParameterType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $nativeParameterType), new AccessoryArrayListType());
31943195
}
31953196
}
31963197
$nativeExpressionTypes[$paramExprString] = ExpressionTypeHolder::createYes($parameterNode, $nativeParameterType);
@@ -3670,11 +3671,11 @@ public function getFunctionType($type, bool $isNullable, bool $isVariadic): Type
36703671
));
36713672
}
36723673

3673-
return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $this->getFunctionType(
3674+
return TypeCombinator::intersect(new ArrayType(new IntegerType(), $this->getFunctionType(
36743675
$type,
36753676
false,
36763677
false,
3677-
)));
3678+
)), new AccessoryArrayListType());
36783679
}
36793680

36803681
if ($type instanceof Name) {
@@ -5079,7 +5080,7 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type
50795080
$resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType());
50805081
}
50815082
if ($constantArraysA->isList()->yes() && $constantArraysB->isList()->yes()) {
5082-
$resultType = AccessoryArrayListType::intersectWith($resultType);
5083+
$resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType());
50835084
}
50845085
$resultTypes[] = $resultType;
50855086
}
@@ -5122,7 +5123,7 @@ private static function generalizeType(Type $a, Type $b, int $depth): Type
51225123
$resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType());
51235124
}
51245125
if ($generalArraysA->isList()->yes() && $generalArraysB->isList()->yes()) {
5125-
$resultType = AccessoryArrayListType::intersectWith($resultType);
5126+
$resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType());
51265127
}
51275128
if ($generalArraysA->isOversizedArray()->yes() && $generalArraysB->isOversizedArray()->yes()) {
51285129
$resultType = TypeCombinator::intersect($resultType, new OversizedArrayType());

Diff for: src/Analyser/NodeScopeResolver.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -2455,7 +2455,7 @@ static function (): void {
24552455
$functionReflection !== null
24562456
&& in_array($functionReflection->getName(), ['fopen', 'file_get_contents'], true)
24572457
) {
2458-
$scope = $scope->assignVariable('http_response_header', AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType())), new ArrayType(new IntegerType(), new StringType()), TrinaryLogic::createYes());
2458+
$scope = $scope->assignVariable('http_response_header', TypeCombinator::intersect(new ArrayType(new IntegerType(), new StringType()), new AccessoryArrayListType()), new ArrayType(new IntegerType(), new StringType()), TrinaryLogic::createYes());
24592459
}
24602460

24612461
if (
@@ -3841,7 +3841,7 @@ static function (?Type $offsetType, Type $valueType, bool $optional) use (&$arra
38413841
? TypeCombinator::intersect($array, new NonEmptyArrayType())
38423842
: $array;
38433843
$constantArray = $isList
3844-
? AccessoryArrayListType::intersectWith($constantArray)
3844+
? TypeCombinator::intersect($constantArray, new AccessoryArrayListType())
38453845
: $constantArray;
38463846
}
38473847

@@ -3884,7 +3884,7 @@ private function getArraySortPreserveListFunctionType(Type $type): Type
38843884
return $type;
38853885
}
38863886

3887-
$newArrayType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $type->getIterableValueType()));
3887+
$newArrayType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $type->getIterableValueType()), new AccessoryArrayListType());
38883888
if ($isIterableAtLeastOnce->yes()) {
38893889
$newArrayType = TypeCombinator::intersect($newArrayType, new NonEmptyArrayType());
38903890
}

Diff for: src/Analyser/TypeSpecifier.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ public function specifyTypesInCondition(
315315
if ($argType->isArray()->yes()) {
316316
$newType = new NonEmptyArrayType();
317317
if ($context->true() && $argType->isList()->yes()) {
318-
$newType = AccessoryArrayListType::intersectWith($newType);
318+
$newType = TypeCombinator::intersect($newType, new AccessoryArrayListType());
319319
}
320320

321321
$result = $result->unionWith(

Diff for: src/DependencyInjection/ContainerFactory.php

-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
use PHPStan\Reflection\ReflectionProvider;
3232
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
3333
use PHPStan\ShouldNotHappenException;
34-
use PHPStan\Type\Accessory\AccessoryArrayListType;
3534
use PHPStan\Type\Generic\TemplateTypeVariance;
3635
use PHPStan\Type\ObjectType;
3736
use Symfony\Component\Finder\Finder;
@@ -192,7 +191,6 @@ public static function postInitializeContainer(Container $container): void
192191
$container->getService('typeSpecifier');
193192

194193
BleedingEdgeToggle::setBleedingEdge($container->getParameter('featureToggles')['bleedingEdge']);
195-
AccessoryArrayListType::setListTypeEnabled($container->getParameter('featureToggles')['listType']);
196194
TemplateTypeVariance::setInvarianceCompositionEnabled($container->getParameter('featureToggles')['invarianceComposition']);
197195
}
198196

Diff for: src/PhpDoc/TypeNodeResolver.php

+4-8
Original file line numberDiff line numberDiff line change
@@ -409,15 +409,11 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
409409
return new NonAcceptingNeverType();
410410

411411
case 'list':
412-
return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType()));
412+
return TypeCombinator::intersect(new ArrayType(new IntegerType(), new MixedType()), new AccessoryArrayListType());
413413
case 'non-empty-list':
414-
return AccessoryArrayListType::intersectWith(TypeCombinator::intersect(
415-
new ArrayType(new IntegerType(), new MixedType()),
416-
new NonEmptyArrayType(),
417-
));
418-
case '__always-list':
419414
return TypeCombinator::intersect(
420415
new ArrayType(new IntegerType(), new MixedType()),
416+
new NonEmptyArrayType(),
421417
new AccessoryArrayListType(),
422418
);
423419

@@ -657,7 +653,7 @@ static function (string $variance): TemplateTypeVariance {
657653
return $arrayType;
658654
} elseif (in_array($mainTypeName, ['list', 'non-empty-list'], true)) {
659655
if (count($genericTypes) === 1) { // list<ValueType>
660-
$listType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $genericTypes[0]));
656+
$listType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $genericTypes[0]), new AccessoryArrayListType());
661657
if ($mainTypeName === 'non-empty-list') {
662658
return TypeCombinator::intersect($listType, new NonEmptyArrayType());
663659
}
@@ -995,7 +991,7 @@ private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $name
995991

996992
$arrayType = $builder->getArray();
997993
if ($typeNode->kind === ArrayShapeNode::KIND_LIST) {
998-
$arrayType = AccessoryArrayListType::intersectWith($arrayType);
994+
$arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
999995
}
1000996

1001997
return $arrayType;

Diff for: src/Reflection/InitializerExprTypeResolver.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ public function getArrayType(Expr\Array_ $expr, callable $getTypeCallback): Type
568568

569569
$arrayType = $arrayBuilder->getArray();
570570
if ($isList === true) {
571-
return AccessoryArrayListType::intersectWith($arrayType);
571+
return TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
572572
}
573573

574574
return $arrayType;
@@ -1041,7 +1041,7 @@ public function getPlusType(Expr $left, Expr $right, callable $getTypeCallback):
10411041
$arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
10421042
}
10431043
if ($leftType->isList()->yes() && $rightType->isList()->yes()) {
1044-
$arrayType = AccessoryArrayListType::intersectWith($arrayType);
1044+
$arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
10451045
}
10461046

10471047
return $arrayType;

Diff for: src/Rules/Functions/ArrayValuesRule.php

-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use PHPStan\Reflection\ReflectionProvider;
1111
use PHPStan\Rules\Rule;
1212
use PHPStan\Rules\RuleErrorBuilder;
13-
use PHPStan\Type\Accessory\AccessoryArrayListType;
1413
use PHPStan\Type\VerbosityLevel;
1514
use function count;
1615
use function sprintf;
@@ -39,10 +38,6 @@ public function processNode(Node $node, Scope $scope): array
3938
return [];
4039
}
4140

42-
if (AccessoryArrayListType::isListTypeEnabled() === false) {
43-
return [];
44-
}
45-
4641
if (!$this->reflectionProvider->hasFunction($node->name, $scope)) {
4742
return [];
4843
}

Diff for: src/Type/Accessory/AccessoryArrayListType.php

-21
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ class AccessoryArrayListType implements CompoundType, AccessoryType
3939
use NonRemoveableTypeTrait;
4040
use NonGeneralizableTypeTrait;
4141

42-
private static bool $enabled = false;
43-
4442
public function __construct()
4543
{
4644
}
@@ -468,25 +466,6 @@ public static function __set_state(array $properties): Type
468466
return new self();
469467
}
470468

471-
public static function setListTypeEnabled(bool $enabled): void
472-
{
473-
self::$enabled = $enabled;
474-
}
475-
476-
public static function isListTypeEnabled(): bool
477-
{
478-
return self::$enabled;
479-
}
480-
481-
public static function intersectWith(Type $type): Type
482-
{
483-
if (self::$enabled) {
484-
return TypeCombinator::intersect($type, new self());
485-
}
486-
487-
return $type;
488-
}
489-
490469
public function exponentiate(Type $exponent): Type
491470
{
492471
return new ErrorType();

Diff for: src/Type/ArrayType.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,12 @@ public function generalizeValues(): self
198198

199199
public function getKeysArray(): Type
200200
{
201-
return AccessoryArrayListType::intersectWith(new self(new IntegerType(), $this->getIterableKeyType()));
201+
return TypeCombinator::intersect(new self(new IntegerType(), $this->getIterableKeyType()), new AccessoryArrayListType());
202202
}
203203

204204
public function getValuesArray(): Type
205205
{
206-
return AccessoryArrayListType::intersectWith(new self(new IntegerType(), $this->itemType));
206+
return TypeCombinator::intersect(new self(new IntegerType(), $this->itemType), new AccessoryArrayListType());
207207
}
208208

209209
public function isIterable(): TrinaryLogic
@@ -569,7 +569,7 @@ public function shiftArray(): Type
569569

570570
public function shuffleArray(): Type
571571
{
572-
return AccessoryArrayListType::intersectWith(new self(new IntegerType(), $this->itemType));
572+
return TypeCombinator::intersect(new self(new IntegerType(), $this->itemType), new AccessoryArrayListType());
573573
}
574574

575575
public function isCallable(): TrinaryLogic

Diff for: src/Type/Constant/ConstantArrayType.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ public function shuffleArray(): Type
904904
$generalizedArray = TypeCombinator::intersect($generalizedArray, new NonEmptyArrayType());
905905
}
906906
if ($valuesArray->isList->yes()) {
907-
$generalizedArray = AccessoryArrayListType::intersectWith($generalizedArray);
907+
$generalizedArray = TypeCombinator::intersect($generalizedArray, new AccessoryArrayListType());
908908
}
909909

910910
return $generalizedArray;
@@ -1267,7 +1267,7 @@ public function generalize(GeneralizePrecision $precision): Type
12671267
}
12681268

12691269
if ($this->isList()->yes()) {
1270-
$arrayType = AccessoryArrayListType::intersectWith($arrayType);
1270+
$arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
12711271
}
12721272

12731273
if (count($accessoryTypes) > 0) {
@@ -1304,7 +1304,7 @@ public function generalizeToArray(): Type
13041304
$arrayType = TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
13051305
}
13061306
if ($this->isList->yes()) {
1307-
$arrayType = AccessoryArrayListType::intersectWith($arrayType);
1307+
$arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
13081308
}
13091309

13101310
return $arrayType;

Diff for: src/Type/Constant/ConstantArrayTypeBuilder.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ public function getArray(): Type
306306
}
307307

308308
if ($this->isList->yes()) {
309-
$array = AccessoryArrayListType::intersectWith($array);
309+
$array = TypeCombinator::intersect($array, new AccessoryArrayListType());
310310
}
311311

312312
return $array;

Diff for: src/Type/Constant/OversizedArrayBuilder.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public function build(Array_ $expr, callable $getTypeCallback): Type
9292

9393
$arrayType = new ArrayType($keyType, $valueType);
9494
if ($isList) {
95-
$arrayType = AccessoryArrayListType::intersectWith($arrayType);
95+
$arrayType = TypeCombinator::intersect($arrayType, new AccessoryArrayListType());
9696
}
9797

9898
return TypeCombinator::intersect($arrayType, new NonEmptyArrayType(), new OversizedArrayType());

Diff for: src/Type/MixedType.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public function getKeysArray(): Type
177177
return new ErrorType();
178178
}
179179

180-
return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new UnionType([new IntegerType(), new StringType()])));
180+
return TypeCombinator::intersect(new ArrayType(new IntegerType(), new UnionType([new IntegerType(), new StringType()])), new AccessoryArrayListType());
181181
}
182182

183183
public function getValuesArray(): Type
@@ -186,7 +186,7 @@ public function getValuesArray(): Type
186186
return new ErrorType();
187187
}
188188

189-
return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed)));
189+
return TypeCombinator::intersect(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed)), new AccessoryArrayListType());
190190
}
191191

192192
public function fillKeysArray(Type $valueType): Type
@@ -258,7 +258,7 @@ public function shuffleArray(): Type
258258
return new ErrorType();
259259
}
260260

261-
return AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed)));
261+
return TypeCombinator::intersect(new ArrayType(new IntegerType(), new MixedType($this->isExplicitMixed)), new AccessoryArrayListType());
262262
}
263263

264264
public function isCallable(): TrinaryLogic

Diff for: src/Type/Php/ArrayChunkFunctionReturnTypeExtension.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
8383

8484
$chunkType = self::getChunkType($arrayType, $preserveKeys);
8585

86-
$resultType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), $chunkType));
86+
$resultType = TypeCombinator::intersect(new ArrayType(new IntegerType(), $chunkType), new AccessoryArrayListType());
8787
if ($arrayType->isIterableAtLeastOnce()->yes()) {
8888
$resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType());
8989
}
@@ -99,7 +99,7 @@ private static function getChunkType(Type $type, ?bool $preserveKeys): Type
9999
$chunkType = $type;
100100
} else {
101101
$chunkType = new ArrayType(new IntegerType(), $type->getIterableValueType());
102-
$chunkType = AccessoryArrayListType::intersectWith($chunkType);
102+
$chunkType = TypeCombinator::intersect($chunkType, new AccessoryArrayListType());
103103
}
104104

105105
return TypeCombinator::intersect($chunkType, new NonEmptyArrayType());

Diff for: src/Type/Php/ArrayColumnFunctionReturnTypeExtension.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ private function handleAnyArray(Type $arrayType, Type $columnType, ?Type $indexT
9999
$returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType());
100100
}
101101
if ($indexType === null) {
102-
$returnType = AccessoryArrayListType::intersectWith($returnType);
102+
$returnType = TypeCombinator::intersect($returnType, new AccessoryArrayListType());
103103
}
104104

105105
return $returnType;

Diff for: src/Type/Php/ArrayFillFunctionReturnTypeExtension.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
7878

7979
$resultType = new ArrayType(new IntegerType(), $valueType);
8080
if ((new ConstantIntegerType(0))->isSuperTypeOf($startIndexType)->yes()) {
81-
$resultType = AccessoryArrayListType::intersectWith($resultType);
81+
$resultType = TypeCombinator::intersect($resultType, new AccessoryArrayListType());
8282
}
8383
if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($numberType)->yes()) {
8484
$resultType = TypeCombinator::intersect($resultType, new NonEmptyArrayType());

0 commit comments

Comments
 (0)