Skip to content
Open
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
8 changes: 5 additions & 3 deletions src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ public function processAssignVar(
$var === $originalVar
&& $var->dim !== null
&& $scope->hasExpressionType($var)->yes()
&& !$scope->isExpressionTrackingOnly($var)
) {
$assignedPropertyExpr = new SetExistingOffsetValueTypeExpr(
$varForSetOffsetValue,
Expand Down Expand Up @@ -1067,7 +1068,7 @@ private function processArrayByRefItems(MutatingScope $scope, string $rootVarNam
*
* @return array{Type, list<array{Expr, Type}>}
*/
private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, array $offsetTypes, Type $offsetValueType, Type $valueToWrite, Scope $scope): array
private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, array $offsetTypes, Type $offsetValueType, Type $valueToWrite, MutatingScope $scope): array
{
$originalValueToWrite = $valueToWrite;

Expand All @@ -1080,13 +1081,13 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar
} else {
$has = $offsetValueType->hasOffsetValueType($offsetType);
if ($has->yes()) {
if ($scope->hasExpressionType($dimFetch)->yes()) {
if ($scope->hasExpressionType($dimFetch)->yes() && !$scope->isExpressionTrackingOnly($dimFetch)) {
$offsetValueType = $scope->getType($dimFetch);
} else {
$offsetValueType = $offsetValueType->getOffsetValueType($offsetType);
}
} elseif ($has->maybe()) {
if ($scope->hasExpressionType($dimFetch)->yes()) {
if ($scope->hasExpressionType($dimFetch)->yes() && !$scope->isExpressionTrackingOnly($dimFetch)) {
$generalizeOnWrite = false;
$offsetValueType = $scope->getType($dimFetch);
} else {
Expand Down Expand Up @@ -1122,6 +1123,7 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar
$offsetType !== null
&& $arrayDimFetch !== null
&& $scope->hasExpressionType($arrayDimFetch)->yes()
&& !$scope->isExpressionTrackingOnly($arrayDimFetch)
&& !$offsetValueType->hasOffsetValueType($offsetType)->no()
) {
$hasOffsetType = null;
Expand Down
21 changes: 18 additions & 3 deletions src/Analyser/ExpressionTypeHolder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public function __construct(
private readonly Expr $expr,
private readonly Type $type,
private readonly TrinaryLogic $certainty,
private readonly bool $trackingOnly = false,
)
{
}
Expand Down Expand Up @@ -52,22 +53,31 @@ public function equals(self $other): bool

public function and(self $other): self
{
$newTrackingOnly = $this->trackingOnly || $other->trackingOnly;

if ($this->type === $other->type || $this->type->equals($other->type)) {
if ($this->certainty->and($other->certainty)->yes()) {
$newCertainty = $this->certainty->and($other->certainty);
if ($newCertainty->yes() && !$newTrackingOnly) {
return $this;
}

if ($this->certainty->maybe()) {
if ($this->certainty->maybe() && $this->trackingOnly === $newTrackingOnly) {
return $this;
}

return $other;
return new self(
$this->expr,
$this->type,
$newCertainty,
$newTrackingOnly,
);
}

return new self(
$this->expr,
TypeCombinator::union($this->type, $other->type),
$this->certainty->and($other->certainty),
$newTrackingOnly,
);
}

Expand All @@ -86,4 +96,9 @@ public function getCertainty(): TrinaryLogic
return $this->certainty;
}

public function isTrackingOnly(): bool
{
return $this->trackingOnly;
}

}
36 changes: 35 additions & 1 deletion src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@
&& !$node instanceof Expr\Closure
&& !$node instanceof Expr\ArrowFunction
&& $this->hasExpressionType($node)->yes()
&& !$this->expressionTypes[$exprString]->isTrackingOnly()
) {
return $this->expressionTypes[$exprString]->getType();
}
Expand Down Expand Up @@ -1302,6 +1303,15 @@
return $this->expressionTypes[$exprString]->getCertainty();
}

public function isExpressionTrackingOnly(Expr $node): bool
{
$exprString = $this->getNodeKey($node);
if (!isset($this->expressionTypes[$exprString])) {
return false;
}
return $this->expressionTypes[$exprString]->isTrackingOnly();
}

/**
* @param MethodReflection|FunctionReflection|null $reflection
*/
Expand Down Expand Up @@ -4087,12 +4097,35 @@
$generalizedExpressions = [];
$newVariableTypeHolders = [];
foreach ($variableTypeHolders as $variableExprString => $variableTypeHolder) {
$invalidatedByGeneralization = false;
foreach ($generalizedExpressions as $generalizedExprString => $generalizedExpr) {
if (!$this->shouldInvalidateExpression($generalizedExprString, $generalizedExpr, $variableTypeHolder->getExpr(), $variableExprString)) {
continue;
}

continue 2;
$invalidatedByGeneralization = true;
break;
}

Check warning on line 4109 in src/Analyser/MutatingScope.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if ( $expr instanceof Expr\ArrayDimFetch && $expr->dim !== null - && $variableTypeHolder->getCertainty()->yes() + && !$variableTypeHolder->getCertainty()->no() && isset($otherVariableTypeHolders[$variableExprString]) && $otherVariableTypeHolders[$variableExprString]->getCertainty()->yes() ) {

Check warning on line 4109 in src/Analyser/MutatingScope.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if ( $expr instanceof Expr\ArrayDimFetch && $expr->dim !== null - && $variableTypeHolder->getCertainty()->yes() + && !$variableTypeHolder->getCertainty()->no() && isset($otherVariableTypeHolders[$variableExprString]) && $otherVariableTypeHolders[$variableExprString]->getCertainty()->yes() ) {
if ($invalidatedByGeneralization) {
$expr = $variableTypeHolder->getExpr();

Check warning on line 4111 in src/Analyser/MutatingScope.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ && $expr->dim !== null && $variableTypeHolder->getCertainty()->yes() && isset($otherVariableTypeHolders[$variableExprString]) - && $otherVariableTypeHolders[$variableExprString]->getCertainty()->yes() + && !$otherVariableTypeHolders[$variableExprString]->getCertainty()->no() ) { $newVariableTypeHolders[$variableExprString] = new ExpressionTypeHolder( $expr,

Check warning on line 4111 in src/Analyser/MutatingScope.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ && $expr->dim !== null && $variableTypeHolder->getCertainty()->yes() && isset($otherVariableTypeHolders[$variableExprString]) - && $otherVariableTypeHolders[$variableExprString]->getCertainty()->yes() + && !$otherVariableTypeHolders[$variableExprString]->getCertainty()->no() ) { $newVariableTypeHolders[$variableExprString] = new ExpressionTypeHolder( $expr,
if (
$expr instanceof Expr\ArrayDimFetch
&& $expr->dim !== null
&& $variableTypeHolder->getCertainty()->yes()
&& isset($otherVariableTypeHolders[$variableExprString])
&& $otherVariableTypeHolders[$variableExprString]->getCertainty()->yes()
) {
$newVariableTypeHolders[$variableExprString] = new ExpressionTypeHolder(
$expr,
$variableTypeHolder->getType(),
$variableTypeHolder->getCertainty(),
true,
);
continue;
}

continue;
}
if (!isset($otherVariableTypeHolders[$variableExprString])) {
$newVariableTypeHolders[$variableExprString] = $variableTypeHolder;
Expand All @@ -4109,6 +4142,7 @@
$variableTypeHolder->getExpr(),
$generalizedType,
$variableTypeHolder->getCertainty(),
$variableTypeHolder->isTrackingOnly(),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1343,4 +1343,11 @@ public function testArrayFindKeyExisting(): void
]);
}

public function testBug14598(): void
{
$this->reportPossiblyNonexistentConstantArrayOffset = true;

$this->analyse([__DIR__ . '/data/bug-14598.php'], []);
}

}
64 changes: 64 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/bug-14598.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php declare(strict_types = 1);

namespace Bug14598;

/**
* @param array<'A'|'B'|'C', array<int<0, 3>, 'off'|'on'>> $raw
* @return array<'A'|'B'|'C', string>
*/
function buildData(array $raw): array
{
$return = [];
foreach ($raw as $id => $sensors) {
$tmp[$id] = [];
$last = "off";
foreach ($sensors as $i => $stat) {
if ($last !== $stat) {
$tmp[$id][] = sprintf("%02d", $i);
$last = $stat;
}
}
$return[$id] = count($tmp[$id])
? implode(",", $tmp[$id])
: "invalid";
}

return $return;
}

/**
* @param array<'A'|'B'|'C', array<int, int>> $raw
*/
function simpleNestedForeach(array $raw): void
{
$tmp = [];
foreach ($raw as $id => $sensors) {
$tmp[$id] = [];
foreach ($sensors as $i => $stat) {
if ($i > 0) {
$tmp[$id][] = $stat;
}
}
echo count($tmp[$id]);
}
}

/**
* @param list<'A'|'B'|'C'> $keys
* @param array<int, int> $values
*/
function nestedWhileLoop(array $keys, array $values): void
{
$tmp = [];
foreach ($keys as $id) {
$tmp[$id] = [];
$i = 0;
while ($i < count($values)) {
if ($values[$i] > 0) {
$tmp[$id][] = $values[$i];
}
$i++;
}
echo count($tmp[$id]);
}
}
Loading