Skip to content

Commit fd77c6b

Browse files
phpstan-botclaude
andcommitted
Move intersection array acceptance guard from IntersectionType to ArrayType
Move the non-array component re-check from IntersectionType::isAcceptedBy() to ArrayType::accepts(), following the pattern used by ConstantArrayType. When IntersectionType::isAcceptedBy uses lazyMaxMin and short-circuits on Yes from array<mixed>, ArrayType::accepts now verifies non-array components (callable, HasOffsetValueType, etc.) are also compatible. Remove IntersectionTypeTest additions (isAcceptedBy and isSubTypeOf tests) as requested by reviewer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e06310a commit fd77c6b

4 files changed

Lines changed: 16 additions & 173 deletions

File tree

phpstan-baseline.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ parameters:
900900
-
901901
rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated.
902902
identifier: phpstanApi.instanceofType
903-
count: 1
903+
count: 2
904904
path: src/Type/ArrayType.php
905905

906906
-

src/Type/ArrayType.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,21 @@ public function getConstantArrays(): array
9797
public function accepts(Type $type, bool $strictTypes): AcceptsResult
9898
{
9999
if ($type instanceof CompoundType) {
100-
return $type->isAcceptedBy($this, $strictTypes);
100+
$result = $type->isAcceptedBy($this, $strictTypes);
101+
102+
if ($result->yes() && $type instanceof IntersectionType && $type->isArray()->yes()) {
103+
foreach ($type->getTypes() as $innerType) {
104+
if ($innerType->isArray()->yes()) {
105+
continue;
106+
}
107+
$innerResult = $this->accepts($innerType, $strictTypes);
108+
if ($innerResult->no()) {
109+
return $innerResult;
110+
}
111+
}
112+
}
113+
114+
return $result;
101115
}
102116

103117
if ($type instanceof ConstantArrayType) {

src/Type/IntersectionType.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -304,18 +304,6 @@ public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsRes
304304
static fn (Type $innerType) => $acceptingType->accepts($innerType, $strictTypes),
305305
);
306306

307-
if ($result->yes() && $acceptingType->isArray()->yes()) {
308-
foreach ($this->types as $innerType) {
309-
if ($innerType->isArray()->yes()) {
310-
continue;
311-
}
312-
$innerResult = $acceptingType->accepts($innerType, $strictTypes);
313-
if ($innerResult->no()) {
314-
return $innerResult;
315-
}
316-
}
317-
}
318-
319307
if ($this->isOversizedArray()->yes()) {
320308
if (!$result->no()) {
321309
return AcceptsResult::createYes();

tests/PHPStan/Type/IntersectionTypeTest.php

Lines changed: 0 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -109,118 +109,6 @@ public function testAccepts(Type $type, Type $otherType, TrinaryLogic $expectedR
109109
);
110110
}
111111

112-
/**
113-
* @return Iterator<int, array{Type, Type, TrinaryLogic}>
114-
*/
115-
public static function dataIsAcceptedBy(): Iterator
116-
{
117-
// array&callable isAcceptedBy array - success
118-
yield [
119-
new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]),
120-
new ArrayType(new MixedType(), new MixedType()),
121-
TrinaryLogic::createYes(),
122-
];
123-
124-
// array&callable isAcceptedBy array<int> - failure
125-
yield [
126-
new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]),
127-
new ArrayType(new MixedType(), new IntegerType()),
128-
TrinaryLogic::createNo(),
129-
];
130-
131-
// array&callable isAcceptedBy constantArray{stdClass, string} - maybe
132-
yield [
133-
new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]),
134-
new ConstantArrayType(
135-
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
136-
[new UnionType([new ObjectType('stdClass'), new StringType()]), new StringType()],
137-
),
138-
TrinaryLogic::createMaybe(),
139-
];
140-
141-
// array&callable isAcceptedBy constantArray{string, string} - maybe
142-
yield [
143-
new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]),
144-
new ConstantArrayType(
145-
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
146-
[new StringType(), new StringType()],
147-
),
148-
TrinaryLogic::createMaybe(),
149-
];
150-
151-
// array&hasOffsetValue isAcceptedBy array - success
152-
yield [
153-
new IntersectionType([
154-
new ArrayType(new MixedType(), new MixedType()),
155-
new NonEmptyArrayType(),
156-
new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()),
157-
]),
158-
new ArrayType(new MixedType(), new MixedType()),
159-
TrinaryLogic::createYes(),
160-
];
161-
162-
// array&hasOffsetValue isAcceptedBy array - failure
163-
yield [
164-
new IntersectionType([
165-
new ArrayType(new MixedType(), new MixedType()),
166-
new NonEmptyArrayType(),
167-
new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()),
168-
]),
169-
new ArrayType(new MixedType(), new StringType()),
170-
TrinaryLogic::createNo(),
171-
];
172-
173-
// array&hasOffsetValue isAcceptedBy array<int> - success (matching value type)
174-
yield [
175-
new IntersectionType([
176-
new ArrayType(new MixedType(), new MixedType()),
177-
new NonEmptyArrayType(),
178-
new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()),
179-
]),
180-
new ArrayType(new MixedType(), new IntegerType()),
181-
TrinaryLogic::createYes(),
182-
];
183-
184-
// array&hasOffsetValue isAcceptedBy constantArray{int, int} - success
185-
yield [
186-
new IntersectionType([
187-
new ArrayType(new MixedType(), new MixedType()),
188-
new NonEmptyArrayType(),
189-
new HasOffsetValueType(new ConstantIntegerType(0), new IntegerType()),
190-
]),
191-
new ConstantArrayType(
192-
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
193-
[new IntegerType(), new IntegerType()],
194-
),
195-
TrinaryLogic::createMaybe(),
196-
];
197-
198-
// array&hasOffsetValue isAcceptedBy constantArray{string, string} - failure
199-
yield [
200-
new IntersectionType([
201-
new ArrayType(new MixedType(), new MixedType()),
202-
new NonEmptyArrayType(),
203-
new HasOffsetValueType(new ConstantIntegerType(0), new IntegerType()),
204-
]),
205-
new ConstantArrayType(
206-
[new ConstantIntegerType(0), new ConstantIntegerType(1)],
207-
[new StringType(), new StringType()],
208-
),
209-
TrinaryLogic::createNo(),
210-
];
211-
}
212-
213-
#[DataProvider('dataIsAcceptedBy')]
214-
public function testIsAcceptedBy(Type $type, Type $acceptingType, TrinaryLogic $expectedResult): void
215-
{
216-
$actualResult = $acceptingType->accepts($type, true)->result;
217-
$this->assertSame(
218-
$expectedResult->describe(),
219-
$actualResult->describe(),
220-
sprintf('%s -> isAcceptedBy(%s)', $type->describe(VerbosityLevel::precise()), $acceptingType->describe(VerbosityLevel::precise())),
221-
);
222-
}
223-
224112
public static function dataIsCallable(): array
225113
{
226114
return [
@@ -474,53 +362,6 @@ public static function dataIsSubTypeOf(): Iterator
474362
]),
475363
TrinaryLogic::createYes(),
476364
];
477-
478-
// array&callable isSubTypeOf array - success
479-
yield [
480-
new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]),
481-
new ArrayType(new MixedType(), new MixedType()),
482-
TrinaryLogic::createYes(),
483-
];
484-
485-
// array&callable isSubTypeOf array<int> - failure
486-
yield [
487-
new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]),
488-
new ArrayType(new MixedType(), new IntegerType()),
489-
TrinaryLogic::createNo(),
490-
];
491-
492-
// array&hasOffsetValue isSubTypeOf array - success
493-
yield [
494-
new IntersectionType([
495-
new ArrayType(new MixedType(), new MixedType()),
496-
new NonEmptyArrayType(),
497-
new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()),
498-
]),
499-
new ArrayType(new MixedType(), new MixedType()),
500-
TrinaryLogic::createYes(),
501-
];
502-
503-
// array&hasOffsetValue isSubTypeOf array<int> - maybe
504-
yield [
505-
new IntersectionType([
506-
new ArrayType(new MixedType(), new MixedType()),
507-
new NonEmptyArrayType(),
508-
new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()),
509-
]),
510-
new ArrayType(new MixedType(), new IntegerType()),
511-
TrinaryLogic::createMaybe(),
512-
];
513-
514-
// array&hasOffsetValue isSubTypeOf array<string> - failure
515-
yield [
516-
new IntersectionType([
517-
new ArrayType(new MixedType(), new MixedType()),
518-
new NonEmptyArrayType(),
519-
new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()),
520-
]),
521-
new ArrayType(new MixedType(), new StringType()),
522-
TrinaryLogic::createNo(),
523-
];
524365
}
525366

526367
#[DataProvider('dataIsSubTypeOf')]

0 commit comments

Comments
 (0)