Skip to content

Commit 561ded3

Browse files
ondrejmirtesclaude
andcommitted
Treat the isList-from-shape inference in ConstantArrayType symmetrically
The empty-keys block unconditionally overrode `$isList` from the unsealed key type, while the non-empty path only filled it in when null — an asymmetry. No caller actually passes a non-null `$isList` for an empty CAT, so the override and the only-when-null path were equivalent today, but the rule was inconsistent on its face. Wrap both branches in `if ($isList === null)` so the constructor consistently says "trust the caller when given, infer from shape otherwise". Also drop the stale `makeList()` TODO — the comment was speculating about validation that would require `isList=Maybe` co-occurring with a non-list-compatible shape, a state that no source-level flow actually constructs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 36f8650 commit 561ded3

1 file changed

Lines changed: 18 additions & 16 deletions

File tree

src/Type/Constant/ConstantArrayType.php

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -144,25 +144,29 @@ public function __construct(
144144
{
145145
assert(count($keyTypes) === count($valueTypes));
146146

147-
$keyTypesCount = count($this->keyTypes);
148-
if ($keyTypesCount === 0) {
149-
if ($unsealed === null) {
150-
$isList = TrinaryLogic::createYes();
151-
} else {
152-
[$unsealedKeyType] = $unsealed;
153-
if ($unsealedKeyType instanceof NeverType && $unsealedKeyType->isExplicit()) {
147+
// Fill in `$isList` from the shape when the caller didn't pass one.
148+
// For empty CATs the answer derives from the unsealed key type
149+
// (no explicit keys to inspect); for non-empty ones the default
150+
// is `No` and the caller is expected to assert list-ness via
151+
// `makeList()` if appropriate.
152+
if ($isList === null) {
153+
if (count($this->keyTypes) === 0) {
154+
if ($unsealed === null) {
154155
$isList = TrinaryLogic::createYes();
155-
} elseif ($unsealedKeyType->isInteger()->yes()) {
156-
$isList = TrinaryLogic::createMaybe();
157156
} else {
158-
$isList = TrinaryLogic::createNo();
157+
[$unsealedKeyType] = $unsealed;
158+
if ($unsealedKeyType instanceof NeverType && $unsealedKeyType->isExplicit()) {
159+
$isList = TrinaryLogic::createYes();
160+
} elseif ($unsealedKeyType->isInteger()->yes()) {
161+
$isList = TrinaryLogic::createMaybe();
162+
} else {
163+
$isList = TrinaryLogic::createNo();
164+
}
159165
}
166+
} else {
167+
$isList = TrinaryLogic::createNo();
160168
}
161169
}
162-
163-
if ($isList === null) {
164-
$isList = TrinaryLogic::createNo();
165-
}
166170
$this->isList = $isList;
167171

168172
if ($unsealed !== null) {
@@ -3053,8 +3057,6 @@ public function makeList(): Type
30533057
return new NeverType();
30543058
}
30553059

3056-
// todo can't be a list if keyTypes are not subsequent integers, or if unsealed type is not int keys
3057-
30583060
return $this->recreate($this->keyTypes, $this->valueTypes, $this->nextAutoIndexes, $this->optionalKeys, TrinaryLogic::createYes(), $this->unsealed);
30593061
}
30603062

0 commit comments

Comments
 (0)