Skip to content

Commit 0ed3255

Browse files
committed
Do not report too-wide nullable type in method return type in child class
1 parent 11de89e commit 0ed3255

File tree

4 files changed

+82
-12
lines changed

4 files changed

+82
-12
lines changed

src/Reflection/SignatureMap/FunctionSignatureMapProvider.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,11 @@ public function hasFunctionSignature(string $name): bool
5151
return array_key_exists(strtolower($name), $this->getSignatureMap());
5252
}
5353

54-
/** @return array{positional: array<int, FunctionSignature>, named: null} */
5554
public function getMethodSignatures(string $className, string $methodName, ?ReflectionMethod $reflectionMethod): array
5655
{
5756
return $this->getFunctionSignatures(sprintf('%s::%s', $className, $methodName), $className, $reflectionMethod);
5857
}
5958

60-
/** @return array{positional: array<int, FunctionSignature>, named: null} */
6159
public function getFunctionSignatures(string $functionName, ?string $className, ?ReflectionFunctionAbstract $reflectionFunction): array
6260
{
6361
$functionName = strtolower($functionName);

src/Rules/TooWideTypehints/TooWideTypeCheck.php

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public function checkProperty(
9191

9292
if (!$phpDocPropertyType instanceof MixedType || $phpDocPropertyType->isExplicitMixed()) {
9393
$phpDocPropertyType = TypeUtils::resolveLateResolvableTypes(TypehintHelper::decideType($nativePropertyType, $phpDocPropertyType));
94-
$narrowedPhpDocType = $this->narrowType($phpDocPropertyType, $assignedType, $scope, false);
94+
$narrowedPhpDocType = $this->narrowType($phpDocPropertyType, $assignedType, $scope, false, false);
9595
if (!$narrowedPhpDocType->equals($phpDocPropertyType)) {
9696
$phpDocPropertyTypeDescription = $phpDocPropertyType->describe(VerbosityLevel::getRecommendedLevelByType($phpDocPropertyType));
9797
return $this->createErrors(
@@ -110,7 +110,7 @@ public function checkProperty(
110110
}
111111

112112
$narrowedPhpDocType = SimultaneousTypeTraverser::map($phpDocPropertyType, $assignedType, function (Type $declaredType, Type $actualType, callable $traverse) use ($scope) {
113-
$narrowed = $this->narrowType($declaredType, $actualType, $scope, false);
113+
$narrowed = $this->narrowType($declaredType, $actualType, $scope, false, false);
114114
if (!$narrowed->equals($declaredType)) {
115115
return $narrowed;
116116
}
@@ -132,7 +132,7 @@ public function checkProperty(
132132
];
133133
}
134134

135-
$narrowedNativeType = $this->narrowType($nativePropertyType, $assignedType, $scope, true);
135+
$narrowedNativeType = $this->narrowType($nativePropertyType, $assignedType, $scope, false, true);
136136
if (!$narrowedNativeType->equals($nativePropertyType)) {
137137
$propertyTypeDescription = $nativePropertyType->describe(VerbosityLevel::getRecommendedLevelByType($nativePropertyType));
138138
return $this->createErrors(
@@ -213,7 +213,7 @@ public function checkFunctionReturnType(
213213
if (!$phpDocFunctionReturnType instanceof MixedType || $phpDocFunctionReturnType->isExplicitMixed()) {
214214
$phpDocFunctionReturnType = TypeUtils::resolveLateResolvableTypes(TypehintHelper::decideType($nativeFunctionReturnType, $phpDocFunctionReturnType));
215215

216-
$narrowedPhpDocType = $this->narrowType($phpDocFunctionReturnType, $returnType, $scope, false);
216+
$narrowedPhpDocType = $this->narrowType($phpDocFunctionReturnType, $returnType, $scope, false, false);
217217
if (!$narrowedPhpDocType->equals($phpDocFunctionReturnType)) {
218218
return $this->createErrors(
219219
$narrowedPhpDocType,
@@ -230,8 +230,8 @@ public function checkFunctionReturnType(
230230
return [];
231231
}
232232

233-
$narrowedPhpDocType = SimultaneousTypeTraverser::map($phpDocFunctionReturnType, $returnType, function (Type $declaredType, Type $actualReturnType, callable $traverse) use ($scope) {
234-
$narrowed = $this->narrowType($declaredType, $actualReturnType, $scope, false);
233+
$narrowedPhpDocType = SimultaneousTypeTraverser::map($phpDocFunctionReturnType, $returnType, function (Type $declaredType, Type $actualReturnType, callable $traverse) use ($scope, $checkDescendantClass) {
234+
$narrowed = $this->narrowType($declaredType, $actualReturnType, $scope, $checkDescendantClass, false);
235235
if (!$narrowed->equals($declaredType)) {
236236
return $narrowed;
237237
}
@@ -253,7 +253,7 @@ public function checkFunctionReturnType(
253253
];
254254
}
255255

256-
$narrowedNativeType = $this->narrowType($nativeFunctionReturnType, $returnType, $scope, true);
256+
$narrowedNativeType = $this->narrowType($nativeFunctionReturnType, $returnType, $scope, false, true);
257257
if (!$narrowedNativeType->equals($nativeFunctionReturnType)) {
258258
return $this->createErrors(
259259
$narrowedNativeType,
@@ -286,14 +286,14 @@ public function checkParameterOutType(
286286
): array
287287
{
288288
$parameterOutType = TypeUtils::resolveLateResolvableTypes($parameterOutType);
289-
$narrowedType = $this->narrowType($parameterOutType, $actualVariableType, $scope, false);
289+
$narrowedType = $this->narrowType($parameterOutType, $actualVariableType, $scope, false, false);
290290
if ($narrowedType->equals($parameterOutType)) {
291291
if (!$this->reportTooWideBool) {
292292
return [];
293293
}
294294

295295
$narrowedType = SimultaneousTypeTraverser::map($parameterOutType, $actualVariableType, function (Type $declaredType, Type $actualType, callable $traverse) use ($scope) {
296-
$narrowed = $this->narrowType($declaredType, $actualType, $scope, false);
296+
$narrowed = $this->narrowType($declaredType, $actualType, $scope, false, false);
297297
if (!$narrowed->equals($declaredType)) {
298298
return $narrowed;
299299
}
@@ -408,6 +408,7 @@ private function narrowType(
408408
Type $declaredType,
409409
Type $actualReturnType,
410410
Scope $scope,
411+
bool $checkDescendantClass,
411412
bool $native,
412413
): Type
413414
{
@@ -421,7 +422,20 @@ private function narrowType(
421422
$usedTypes = [];
422423
foreach ($declaredType->getTypes() as $innerType) {
423424
if ($innerType->isSuperTypeOf($actualReturnType)->no()) {
424-
continue;
425+
if (!$checkDescendantClass) {
426+
continue;
427+
}
428+
if ($innerType->isNull()->yes()) {
429+
$usedTypes[] = $innerType;
430+
continue;
431+
}
432+
if (
433+
!$actualReturnType->isTrue()->yes()
434+
&& !$actualReturnType->isFalse()->yes()
435+
&& !$actualReturnType->isNull()->yes()
436+
) {
437+
continue;
438+
}
425439
}
426440

427441
$usedTypes[] = $innerType;

tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ public function testNestedTooWideType(): void
284284
'Return type array<array{int, bool}> of method NestedTooWideMethodReturnType\Foo::dataProvider() can be narrowed to array<array{int, false}>.',
285285
11,
286286
],
287+
[
288+
'Return type array<array{int|null}> of method NestedTooWideMethodReturnType\Foo::dataProvider2() can be narrowed to array<array{int}>.',
289+
28,
290+
],
287291
]);
288292
}
289293

tests/PHPStan/Rules/TooWideTypehints/data/nested-too-wide-method-return-type.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,58 @@ public function dataProvider(): array
2222
];
2323
}
2424

25+
/**
26+
* @return array<array{int|null}>
27+
*/
28+
public function dataProvider2(): array
29+
{
30+
return [
31+
[
32+
1,
33+
],
34+
[
35+
2,
36+
],
37+
];
38+
}
39+
40+
}
41+
42+
class ParentClass
43+
{
44+
45+
/**
46+
* @return array<array{int|null}>
47+
*/
48+
public function doFoo(): array
49+
{
50+
return [];
51+
}
52+
53+
}
54+
55+
class ChildClass extends ParentClass
56+
{
57+
58+
public function doFoo(): array
59+
{
60+
return [
61+
[1],
62+
[2],
63+
];
64+
}
65+
66+
}
67+
68+
class ChildClassNull extends ParentClass
69+
{
70+
71+
public function doFoo(): array
72+
{
73+
return [
74+
[null],
75+
[null],
76+
];
77+
}
78+
2579
}

0 commit comments

Comments
 (0)