Skip to content

Commit 7e126f6

Browse files
committed
Better "missing iterable value type" for unsealed types
1 parent fd67793 commit 7e126f6

4 files changed

Lines changed: 70 additions & 0 deletions

File tree

src/Rules/MissingTypehintCheck.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\Type\ClosureType;
1515
use PHPStan\Type\ConditionalType;
1616
use PHPStan\Type\ConditionalTypeForParameter;
17+
use PHPStan\Type\Constant\ConstantArrayType;
1718
use PHPStan\Type\Generic\GenericObjectType;
1819
use PHPStan\Type\Generic\GenericStaticType;
1920
use PHPStan\Type\Generic\TemplateType;
@@ -23,6 +24,7 @@
2324
use PHPStan\Type\ObjectType;
2425
use PHPStan\Type\Type;
2526
use PHPStan\Type\TypeTraverser;
27+
use PHPStan\Type\UnionType;
2628
use PHPStan\Type\VerbosityLevel;
2729
use Traversable;
2830
use function array_filter;
@@ -88,6 +90,26 @@ public function getIterableTypesWithMissingValueTypehint(Type $type): array
8890
return $type;
8991
}
9092
if ($type->isIterable()->yes()) {
93+
if ($type->isConstantArray()->yes()) {
94+
$type = TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$descriptions) {
95+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
96+
return $traverse($type);
97+
}
98+
99+
if ($type instanceof ConstantArrayType) {
100+
$unsealed = $type->getUnsealedTypes();
101+
if ($unsealed !== null) {
102+
$iterableUnsealedValue = $unsealed[1];
103+
if ($iterableUnsealedValue instanceof MixedType && !$iterableUnsealedValue->isExplicitMixed()) {
104+
$descriptions[] = 'unsealed extra keys (...)';
105+
}
106+
return $traverse($type->dropUnsealedTypes());
107+
}
108+
}
109+
110+
return $traverse($type);
111+
});
112+
}
91113
$iterableValue = $type->getIterableValueType();
92114
if ($iterableValue instanceof MixedType && !$iterableValue->isExplicitMixed()) {
93115
$descriptions[] = sprintf('iterable type %s', $type->describe(VerbosityLevel::typeOnly()));

src/Type/Constant/ConstantArrayType.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,21 @@ public function getUnsealedTypes(): ?array
190190
return $this->unsealed;
191191
}
192192

193+
/**
194+
* @internal
195+
*/
196+
public function dropUnsealedTypes(): self
197+
{
198+
return $this->recreate(
199+
$this->keyTypes,
200+
$this->valueTypes,
201+
$this->nextAutoIndexes,
202+
$this->optionalKeys,
203+
$this->isList,
204+
null,
205+
);
206+
}
207+
193208
/**
194209
* @param list<ConstantIntegerType|ConstantStringType> $keyTypes
195210
* @param array<int, Type> $valueTypes

tests/PHPStan/Rules/Methods/MissingMethodParameterTypehintRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ public function testRule(): void
8686
'Method MissingMethodParameterTypehint\Baz::acceptsGenericWithSomeDefaults() has parameter $c with generic class MissingMethodParameterTypehint\GenericClassWithSomeDefaults but does not specify its types: T, U (1-2 required)',
8787
270,
8888
],
89+
[
90+
'Method MissingMethodParameterTypehint\UnsealedArrayShape::doFoo() has parameter $a with no value type specified in unsealed extra keys (...).',
91+
284,
92+
MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP,
93+
],
94+
[
95+
'Method MissingMethodParameterTypehint\UnsealedArrayShape::doBar() has parameter $a with no value type specified in unsealed extra keys (...).',
96+
293,
97+
MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP,
98+
],
8999
];
90100

91101
$this->analyse([__DIR__ . '/data/missing-method-parameter-typehint.php'], $errors);

tests/PHPStan/Rules/Methods/data/missing-method-parameter-typehint.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,26 @@ public function acceptsGenericWithSomeDefaults(GenericClassWithSomeDefaults $c)
273273
}
274274

275275
}
276+
277+
class UnsealedArrayShape
278+
{
279+
280+
/**
281+
* @param array{a: int, ...} $a
282+
* @param array{a: int, ...<mixed>} $b
283+
*/
284+
public function doFoo(array $a, array $b): void
285+
{
286+
287+
}
288+
289+
/**
290+
* @param non-empty-array{a?: int, b?: int, ...} $a
291+
* @param non-empty-array{a?: int, b?: int, ...<mixed>} $b
292+
*/
293+
public function doBar(array $a, array $b): void
294+
{
295+
296+
}
297+
298+
}

0 commit comments

Comments
 (0)