Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -1311,8 +1311,8 @@ private function resolveType(Expr $node): Type
$rightType = $this->getType($right);

if ($node instanceof Expr\AssignOp\Plus || $node instanceof Expr\BinaryOp\Plus) {
$leftConstantArrays = TypeUtils::getConstantArrays($leftType);
$rightConstantArrays = TypeUtils::getConstantArrays($rightType);
$leftConstantArrays = TypeUtils::getOldConstantArrays($leftType);
$rightConstantArrays = TypeUtils::getOldConstantArrays($rightType);

$leftCount = count($leftConstantArrays);
$rightCount = count($rightConstantArrays);
Expand All @@ -1322,10 +1322,18 @@ private function resolveType(Expr $node): Type
foreach ($rightConstantArrays as $rightConstantArray) {
foreach ($leftConstantArrays as $leftConstantArray) {
$newArrayBuilder = ConstantArrayTypeBuilder::createFromConstantArray($rightConstantArray);
foreach ($leftConstantArray->getKeyTypes() as $leftKeyType) {
foreach ($leftConstantArray->getKeyTypes() as $i => $leftKeyType) {
$optional = $leftConstantArray->isOptionalKey($i);
$valueType = $leftConstantArray->getOffsetValueType($leftKeyType);
if (!$optional) {
if ($rightConstantArray->hasOffsetValueType($leftKeyType)->maybe()) {
$valueType = TypeCombinator::union($valueType, $rightConstantArray->getOffsetValueType($leftKeyType));
}
}
$newArrayBuilder->setOffsetValueType(
$leftKeyType,
$leftConstantArray->getOffsetValueType($leftKeyType),
$valueType,
$optional,
);
}
$resultTypes[] = $newArrayBuilder->getArray();
Expand Down Expand Up @@ -4306,7 +4314,7 @@ public function specifyExpressionType(Expr $expr, Type $type, ?Type $nativeType
$this->parentScope,
);
} elseif ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) {
$constantArrays = TypeUtils::getConstantArrays($this->getType($expr->var));
$constantArrays = TypeUtils::getOldConstantArrays($this->getType($expr->var));
if (count($constantArrays) > 0) {
$setArrays = [];
$dimType = $this->getType($expr->dim);
Expand Down
42 changes: 27 additions & 15 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1863,7 +1863,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression

$arrayArg = $expr->getArgs()[0]->value;
$originalArrayType = $scope->getType($arrayArg);
$constantArrays = TypeUtils::getConstantArrays($originalArrayType);
$constantArrays = TypeUtils::getOldConstantArrays($originalArrayType);
if (
$functionReflection->getName() === 'array_push'
|| ($originalArrayType->isArray()->yes() && count($constantArrays) === 0)
Expand All @@ -1881,24 +1881,36 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
}

$defaultArrayType = $defaultArrayBuilder->getArray();
if (!$defaultArrayType instanceof ConstantArrayType) {
$arrayType = $originalArrayType;
foreach ($argumentTypes as $argType) {
$arrayType = $arrayType->setOffsetValueType(null, $argType);
}

$arrayTypes = [];
foreach ($constantArrays as $constantArray) {
$arrayType = $defaultArrayType;
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
$valueType = $constantArray->getValueTypes()[$i];
if ($keyType instanceof ConstantIntegerType) {
$keyType = null;
$scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType($arrayArg, TypeCombinator::intersect($arrayType, new NonEmptyArrayType()));
} else {
$arrayTypes = [];
foreach ($constantArrays as $constantArray) {
$arrayTypeBuilder = ConstantArrayTypeBuilder::createFromConstantArray($defaultArrayType);
foreach ($constantArray->getKeyTypes() as $i => $keyType) {
$valueType = $constantArray->getValueTypes()[$i];
if ($keyType instanceof ConstantIntegerType) {
$keyType = null;
}
$arrayTypeBuilder->setOffsetValueType(
$keyType,
$valueType,
$constantArray->isOptionalKey($i),
);
}
$arrayType = $arrayType->setOffsetValueType($keyType, $valueType);
$arrayTypes[] = $arrayTypeBuilder->getArray();
}
$arrayTypes[] = $arrayType;
}

$scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType(
$arrayArg,
TypeCombinator::union(...$arrayTypes),
);
$scope = $scope->invalidateExpression($arrayArg)->specifyExpressionType(
$arrayArg,
TypeCombinator::union(...$arrayTypes),
);
}
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -770,10 +770,6 @@ public function specifyTypesInCondition(
$vars = array_merge($vars, array_reverse($tmpVars));
}

if (count($vars) === 0) {
throw new ShouldNotHappenException();
}

$types = null;
foreach ($vars as $var) {
if ($var instanceof Expr\Variable && is_string($var->name)) {
Expand Down
2 changes: 1 addition & 1 deletion src/Rules/Arrays/InvalidKeyInArrayDimFetchRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function processNode(Node $node, Scope $scope): array
}

$varType = $scope->getType($node->var);
if (count(TypeUtils::getArrays($varType)) === 0) {
if (count(TypeUtils::getAnyArrays($varType)) === 0) {
return [];
}

Expand Down
2 changes: 1 addition & 1 deletion src/Rules/Comparison/ImpossibleCheckTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public function findSpecifiedType(
return null;
}

$constantArrays = TypeUtils::getConstantArrays($haystackType);
$constantArrays = TypeUtils::getOldConstantArrays($haystackType);
$needleType = $scope->getType($node->getArgs()[0]->value);
$valueType = $haystackType->getIterableValueType();
$constantNeedleTypesCount = count(TypeUtils::getConstantScalars($needleType));
Expand Down
17 changes: 15 additions & 2 deletions src/Rules/FunctionCallParametersCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
use PHPStan\Reflection\ResolvedFunctionVariant;
use PHPStan\Rules\PhpDoc\UnresolvableTypeHelper;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
Expand Down Expand Up @@ -94,11 +97,21 @@ public function check(
$argumentName = $arg->name->toString();
}
if ($arg->unpack) {
$arrays = TypeUtils::getConstantArrays($type);
$arrays = TypeUtils::getOldConstantArrays($type);
if (count($arrays) > 0) {
$minKeys = null;
foreach ($arrays as $array) {
$keysCount = count($array->getKeyTypes());
$countType = $array->count();
if ($countType instanceof ConstantIntegerType) {
$keysCount = $countType->getValue();
} elseif ($countType instanceof IntegerRangeType) {
$keysCount = $countType->getMin();
if ($keysCount === null) {
throw new ShouldNotHappenException();
}
} else {
throw new ShouldNotHappenException();
}
if ($minKeys !== null && $keysCount >= $minKeys) {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Type/Accessory/AccessoryLiteralStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public function toArray(): Type
return new ConstantArrayType(
[new ConstantIntegerType(0)],
[$this],
1,
[1],
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/Accessory/AccessoryNonEmptyStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public function toArray(): Type
return new ConstantArrayType(
[new ConstantIntegerType(0)],
[$this],
1,
[1],
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/Accessory/AccessoryNumericStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public function toArray(): Type
return new ConstantArrayType(
[new ConstantIntegerType(0)],
[$this],
1,
[1],
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/BooleanType.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function toArray(): Type
return new ConstantArrayType(
[new ConstantIntegerType(0)],
[$this],
1,
[1],
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/ClosureType.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ public function toArray(): Type
return new ConstantArrayType(
[new ConstantIntegerType(0)],
[$this],
1,
[1],
);
}

Expand Down
51 changes: 36 additions & 15 deletions src/Type/Constant/ConstantArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@
use function count;
use function implode;
use function in_array;
use function is_int;
use function is_string;
use function max;
use function pow;
use function sort;
use function sprintf;
use function strpos;

Expand All @@ -59,21 +61,31 @@ class ConstantArrayType extends ArrayType implements ConstantType
/** @var self[]|null */
private ?array $allArrays = null;

/** @var non-empty-list<int> */
private array $nextAutoIndexes;

/**
* @api
* @param array<int, ConstantIntegerType|ConstantStringType> $keyTypes
* @param array<int, Type> $valueTypes
* @param non-empty-list<int>|int $nextAutoIndexes
* @param int[] $optionalKeys
*/
public function __construct(
private array $keyTypes,
private array $valueTypes,
private int $nextAutoIndex = 0,
int|array $nextAutoIndexes = [0],
private array $optionalKeys = [],
)
{
assert(count($keyTypes) === count($valueTypes));

if (is_int($nextAutoIndexes)) {
$nextAutoIndexes = [$nextAutoIndexes];
}

$this->nextAutoIndexes = $nextAutoIndexes;

parent::__construct(
count($keyTypes) > 0 ? TypeCombinator::union(...$keyTypes) : new NeverType(true),
count($valueTypes) > 0 ? TypeCombinator::union(...$valueTypes) : new NeverType(true),
Expand All @@ -85,9 +97,20 @@ public function isEmpty(): bool
return count($this->keyTypes) === 0;
}

/**
* @return non-empty-list<int>
*/
public function getNextAutoIndexes(): array
{
return $this->nextAutoIndexes;
}

/**
* @deprecated
*/
public function getNextAutoIndex(): int
{
return $this->nextAutoIndex;
return $this->nextAutoIndexes[count($this->nextAutoIndexes) - 1];
}

/**
Expand Down Expand Up @@ -488,17 +511,12 @@ public function unsetOffset(Type $offsetType): Type
$k++;
}

return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndex, $newOptionalKeys);
return new self($newKeyTypes, $newValueTypes, $this->nextAutoIndexes, $newOptionalKeys);
}
}
}

$arrays = [];
foreach ($this->getAllArrays() as $tmp) {
$arrays[] = new self($tmp->keyTypes, $tmp->valueTypes, $tmp->nextAutoIndex, array_keys($tmp->keyTypes));
}

return TypeCombinator::union(...$arrays)->generalize(GeneralizePrecision::moreSpecific());
return new ArrayType($this->getKeyType(), $this->getItemType());
}

public function isIterableAtLeastOnce(): TrinaryLogic
Expand Down Expand Up @@ -533,7 +551,7 @@ public function removeLast(): self
array_pop($valueTypes);
$nextAutoindex = $removedKeyType instanceof ConstantIntegerType
? $removedKeyType->getValue()
: $this->nextAutoIndex;
: $this->getNextAutoIndex(); // @phpstan-ignore-line

return new self(
$keyTypes,
Expand Down Expand Up @@ -650,7 +668,7 @@ public function generalizeValues(): ArrayType
$valueTypes[] = $valueType->generalize(GeneralizePrecision::lessSpecific());
}

return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys);
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys);
}

/**
Expand Down Expand Up @@ -825,7 +843,7 @@ public function traverse(callable $cb): Type
return $this;
}

return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys);
return new self($this->keyTypes, $valueTypes, $this->nextAutoIndexes, $this->optionalKeys);
}

public function isKeysSupersetOf(self $otherArray): bool
Expand Down Expand Up @@ -873,7 +891,10 @@ public function mergeWith(self $otherArray): self

$optionalKeys = array_values(array_unique($optionalKeys));

return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $optionalKeys);
$nextAutoIndexes = array_unique(array_merge($this->nextAutoIndexes, $otherArray->nextAutoIndexes));
sort($nextAutoIndexes);

return new self($this->keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys);
}

/**
Expand Down Expand Up @@ -902,7 +923,7 @@ public function makeOffsetRequired(Type $offsetType): self
foreach ($optionalKeys as $j => $key) {
if ($i === $key) {
unset($optionalKeys[$j]);
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndex, array_values($optionalKeys));
return new self($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, array_values($optionalKeys));
}
}

Expand All @@ -917,7 +938,7 @@ public function makeOffsetRequired(Type $offsetType): self
*/
public static function __set_state(array $properties): Type
{
return new self($properties['keyTypes'], $properties['valueTypes'], $properties['nextAutoIndex'], $properties['optionalKeys'] ?? []);
return new self($properties['keyTypes'], $properties['valueTypes'], $properties['nextAutoIndexes'] ?? $properties['nextAutoIndex'], $properties['optionalKeys'] ?? []);
}

}
Loading