Skip to content

Commit 77aedcd

Browse files
ondrejmirtesclaude
andcommitted
Move list-contradiction NeverType out of unsetOffset into tryRemove
`unsetOffset` is a "what does this array look like after unsetting X?" operation — its result is always still an array. The three identical `elseif ($this->isList->yes() && $newIsList->no()) { return new NeverType(); }` returns sprinkled through the three branches were subtraction semantics leaking up from the only two callers that pass `preserveListCertainty = true`: the `HasOffsetType` and `HasOffsetValueType` arms of `tryRemove`. Push that check up to its actual home in `tryRemove`. The unset branches now consistently return the post-unset shape; `tryRemove` decides on its own that "definitely-a-list minus a definitely-present key = empty set" and returns `NeverType` for that one case. Behaviour is preserved (full test suite green). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dfe0c76 commit 77aedcd

1 file changed

Lines changed: 10 additions & 8 deletions

File tree

src/Type/Constant/ConstantArrayType.php

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -859,8 +859,6 @@ public function unsetOffset(Type $offsetType, bool $preserveListCertainty = fals
859859
);
860860
if (!$preserveListCertainty) {
861861
$newIsList = $newIsList->and(TrinaryLogic::createMaybe());
862-
} elseif ($this->isList->yes() && $newIsList->no()) {
863-
return new NeverType();
864862
}
865863

866864
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $optionalKeys, $newIsList);
@@ -1744,12 +1742,16 @@ public function tryRemove(Type $typeToRemove): ?Type
17441742
return new ConstantArrayType([], []);
17451743
}
17461744

1747-
if ($typeToRemove instanceof HasOffsetType) {
1748-
return $this->unsetOffset($typeToRemove->getOffsetType(), true);
1749-
}
1750-
1751-
if ($typeToRemove instanceof HasOffsetValueType) {
1752-
return $this->unsetOffset($typeToRemove->getOffsetType(), true);
1745+
if ($typeToRemove instanceof HasOffsetType || $typeToRemove instanceof HasOffsetValueType) {
1746+
$unsetResult = $this->unsetOffset($typeToRemove->getOffsetType(), true);
1747+
// When the source was definitely a list but the post-unset shape
1748+
// definitely isn't (e.g. unsetting a non-optional leading key
1749+
// creates a hole), no value of $this could have lacked the
1750+
// removed key — the subtraction yields the empty set.
1751+
if ($this->isList->yes() && $unsetResult->isList()->no()) {
1752+
return new NeverType();
1753+
}
1754+
return $unsetResult;
17531755
}
17541756

17551757
return null;

0 commit comments

Comments
 (0)