Skip to content

Commit

Permalink
Optimization & fix offset for appended values to constant arrays with…
Browse files Browse the repository at this point in the history
… optional keys
  • Loading branch information
ondrejmirtes committed Apr 2, 2022
1 parent 325abf4 commit 036056b
Show file tree
Hide file tree
Showing 33 changed files with 479 additions and 94 deletions.
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'] ?? []);
}

}

0 comments on commit 036056b

Please sign in to comment.