Skip to content

Commit 27178c3

Browse files
committed
Extract TooWideReturnTypeCheck->checkFunction()
1 parent 4df1a1b commit 27178c3

File tree

5 files changed

+107
-133
lines changed

5 files changed

+107
-133
lines changed

src/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRule.php

Lines changed: 14 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,6 @@
77
use PHPStan\DependencyInjection\RegisteredRule;
88
use PHPStan\Node\FunctionReturnStatementsNode;
99
use PHPStan\Rules\Rule;
10-
use PHPStan\Rules\RuleErrorBuilder;
11-
use PHPStan\Type\TypeCombinator;
12-
use PHPStan\Type\TypeUtils;
13-
use PHPStan\Type\UnionType;
14-
use PHPStan\Type\VerbosityLevel;
15-
use PHPStan\Type\VoidType;
16-
use function count;
1710
use function sprintf;
1811

1912
/**
@@ -23,6 +16,12 @@
2316
final class TooWideFunctionReturnTypehintRule implements Rule
2417
{
2518

19+
public function __construct(
20+
private TooWideReturnTypeCheck $check,
21+
)
22+
{
23+
}
24+
2625
public function getNodeType(): string
2726
{
2827
return FunctionReturnStatementsNode::class;
@@ -33,61 +32,15 @@ public function processNode(Node $node, Scope $scope): array
3332
$function = $node->getFunctionReflection();
3433

3534
$functionReturnType = $function->getReturnType();
36-
$functionReturnType = TypeUtils::resolveLateResolvableTypes($functionReturnType);
37-
if (!$functionReturnType instanceof UnionType) {
38-
return [];
39-
}
40-
$statementResult = $node->getStatementResult();
41-
if ($statementResult->hasYield()) {
42-
return [];
43-
}
44-
45-
$returnStatements = $node->getReturnStatements();
46-
if (count($returnStatements) === 0) {
47-
return [];
48-
}
49-
50-
$returnTypes = [];
51-
foreach ($returnStatements as $returnStatement) {
52-
$returnNode = $returnStatement->getReturnNode();
53-
if ($returnNode->expr === null) {
54-
$returnTypes[] = new VoidType();
55-
continue;
56-
}
57-
58-
$returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr);
59-
}
60-
61-
if (!$statementResult->isAlwaysTerminating()) {
62-
$returnTypes[] = new VoidType();
63-
}
64-
65-
$returnType = TypeCombinator::union(...$returnTypes);
66-
67-
$messages = [];
68-
foreach ($functionReturnType->getTypes() as $type) {
69-
if (!$type->isSuperTypeOf($returnType)->no()) {
70-
continue;
71-
}
72-
73-
if ($type->isNull()->yes() && !$node->hasNativeReturnTypehint()) {
74-
foreach ($node->getExecutionEnds() as $executionEnd) {
75-
if ($executionEnd->getStatementResult()->isAlwaysTerminating()) {
76-
continue;
77-
}
78-
79-
continue 2;
80-
}
81-
}
82-
83-
$messages[] = RuleErrorBuilder::message(sprintf(
84-
'Function %s() never returns %s so it can be removed from the return type.',
35+
return $this->check->checkFunction(
36+
$node,
37+
$functionReturnType,
38+
sprintf(
39+
'Function %s()',
8540
$function->getName(),
86-
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
87-
))->identifier('return.unusedType')->build();
88-
}
89-
90-
return $messages;
41+
),
42+
false,
43+
);
9144
}
9245

9346
}

src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php

Lines changed: 9 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@
88
use PHPStan\DependencyInjection\RegisteredRule;
99
use PHPStan\Node\MethodReturnStatementsNode;
1010
use PHPStan\Rules\Rule;
11-
use PHPStan\Rules\RuleErrorBuilder;
12-
use PHPStan\Type\TypeCombinator;
13-
use PHPStan\Type\TypeUtils;
14-
use PHPStan\Type\UnionType;
15-
use PHPStan\Type\VerbosityLevel;
16-
use PHPStan\Type\VoidType;
17-
use function count;
1811
use function sprintf;
1912

2013
/**
@@ -27,6 +20,7 @@ final class TooWideMethodReturnTypehintRule implements Rule
2720
public function __construct(
2821
#[AutowiredParameter(ref: '%checkTooWideReturnTypesInProtectedAndPublicMethods%')]
2922
private bool $checkProtectedAndPublicMethods,
23+
private TooWideReturnTypeCheck $check,
3024
)
3125
{
3226
}
@@ -56,69 +50,16 @@ public function processNode(Node $node, Scope $scope): array
5650
}
5751

5852
$methodReturnType = $method->getReturnType();
59-
$methodReturnType = TypeUtils::resolveLateResolvableTypes($methodReturnType);
60-
if (!$methodReturnType instanceof UnionType) {
61-
return [];
62-
}
63-
$statementResult = $node->getStatementResult();
64-
if ($statementResult->hasYield()) {
65-
return [];
66-
}
67-
68-
$returnStatements = $node->getReturnStatements();
69-
if (count($returnStatements) === 0) {
70-
return [];
71-
}
72-
73-
$returnTypes = [];
74-
foreach ($returnStatements as $returnStatement) {
75-
$returnNode = $returnStatement->getReturnNode();
76-
if ($returnNode->expr === null) {
77-
$returnTypes[] = new VoidType();
78-
continue;
79-
}
80-
81-
$returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr);
82-
}
83-
84-
if (!$statementResult->isAlwaysTerminating()) {
85-
$returnTypes[] = new VoidType();
86-
}
87-
88-
$returnType = TypeCombinator::union(...$returnTypes);
89-
if (
90-
!$isFirstDeclaration
91-
&& !$method->isPrivate()
92-
&& ($returnType->isNull()->yes() || $returnType->isTrue()->yes() || $returnType->isFalse()->yes())
93-
) {
94-
return [];
95-
}
96-
97-
$messages = [];
98-
foreach ($methodReturnType->getTypes() as $type) {
99-
if (!$type->isSuperTypeOf($returnType)->no()) {
100-
continue;
101-
}
102-
103-
if ($type->isNull()->yes() && !$node->hasNativeReturnTypehint()) {
104-
foreach ($node->getExecutionEnds() as $executionEnd) {
105-
if ($executionEnd->getStatementResult()->isAlwaysTerminating()) {
106-
continue;
107-
}
108-
109-
continue 2;
110-
}
111-
}
112-
113-
$messages[] = RuleErrorBuilder::message(sprintf(
114-
'Method %s::%s() never returns %s so it can be removed from the return type.',
53+
return $this->check->checkFunction(
54+
$node,
55+
$methodReturnType,
56+
sprintf(
57+
'Method %s::%s()',
11558
$method->getDeclaringClass()->getDisplayName(),
11659
$method->getName(),
117-
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
118-
))->identifier('return.unusedType')->build();
119-
}
120-
121-
return $messages;
60+
),
61+
!$isFirstDeclaration && !$method->isPrivate(),
62+
);
12263
}
12364

12465
}

src/Rules/TooWideTypehints/TooWideReturnTypeCheck.php

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,98 @@
33
namespace PHPStan\Rules\TooWideTypehints;
44

55
use PHPStan\DependencyInjection\AutowiredService;
6+
use PHPStan\Node\FunctionReturnStatementsNode;
7+
use PHPStan\Node\MethodReturnStatementsNode;
68
use PHPStan\Rules\IdentifierRuleError;
79
use PHPStan\Rules\RuleErrorBuilder;
810
use PHPStan\Type\Type;
11+
use PHPStan\Type\TypeCombinator;
12+
use PHPStan\Type\TypeUtils;
913
use PHPStan\Type\UnionType;
1014
use PHPStan\Type\VerbosityLevel;
15+
use PHPStan\Type\VoidType;
16+
use function count;
1117
use function sprintf;
1218

1319
#[AutowiredService]
1420
final class TooWideReturnTypeCheck
1521
{
1622

23+
/**
24+
* @return list<IdentifierRuleError>
25+
*/
26+
public function checkFunction(
27+
MethodReturnStatementsNode|FunctionReturnStatementsNode $node,
28+
Type $functionReturnType,
29+
string $functionDescription,
30+
bool $checkDescendantClass,
31+
): array
32+
{
33+
$functionReturnType = TypeUtils::resolveLateResolvableTypes($functionReturnType);
34+
if (!$functionReturnType instanceof UnionType) {
35+
return [];
36+
}
37+
$statementResult = $node->getStatementResult();
38+
if ($statementResult->hasYield()) {
39+
return [];
40+
}
41+
42+
$returnStatements = $node->getReturnStatements();
43+
if (count($returnStatements) === 0) {
44+
return [];
45+
}
46+
47+
$returnTypes = [];
48+
foreach ($returnStatements as $returnStatement) {
49+
$returnNode = $returnStatement->getReturnNode();
50+
if ($returnNode->expr === null) {
51+
$returnTypes[] = new VoidType();
52+
continue;
53+
}
54+
55+
$returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr);
56+
}
57+
58+
if (!$statementResult->isAlwaysTerminating()) {
59+
$returnTypes[] = new VoidType();
60+
}
61+
62+
$returnType = TypeCombinator::union(...$returnTypes);
63+
64+
// Do not require to have @return null/true/false in descendant classes
65+
if (
66+
$checkDescendantClass
67+
&& ($returnType->isNull()->yes() || $returnType->isTrue()->yes() || $returnType->isFalse()->yes())
68+
) {
69+
return [];
70+
}
71+
72+
$messages = [];
73+
foreach ($functionReturnType->getTypes() as $type) {
74+
if (!$type->isSuperTypeOf($returnType)->no()) {
75+
continue;
76+
}
77+
78+
if ($type->isNull()->yes() && !$node->hasNativeReturnTypehint()) {
79+
foreach ($node->getExecutionEnds() as $executionEnd) {
80+
if ($executionEnd->getStatementResult()->isAlwaysTerminating()) {
81+
continue;
82+
}
83+
84+
continue 2;
85+
}
86+
}
87+
88+
$messages[] = RuleErrorBuilder::message(sprintf(
89+
'%s never returns %s so it can be removed from the return type.',
90+
$functionDescription,
91+
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
92+
))->identifier('return.unusedType')->build();
93+
}
94+
95+
return $messages;
96+
}
97+
1798
/**
1899
* @return list<IdentifierRuleError>
19100
*/
@@ -32,8 +113,7 @@ public function checkAnonymousFunction(
32113
}
33114

34115
$messages[] = RuleErrorBuilder::message(sprintf(
35-
'%s never returns %s so it can be removed from the return type.',
36-
'Anonymous function',
116+
'Anonymous function never returns %s so it can be removed from the return type.',
37117
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
38118
))->identifier('return.unusedType')->build();
39119
}

tests/PHPStan/Rules/TooWideTypehints/TooWideFunctionReturnTypehintRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class TooWideFunctionReturnTypehintRuleTest extends RuleTestCase
1313

1414
protected function getRule(): Rule
1515
{
16-
return new TooWideFunctionReturnTypehintRule();
16+
return new TooWideFunctionReturnTypehintRule(new TooWideReturnTypeCheck());
1717
}
1818

1919
public function testRule(): void

tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class TooWideMethodReturnTypehintRuleTest extends RuleTestCase
1717

1818
protected function getRule(): Rule
1919
{
20-
return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods);
20+
return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods, new TooWideReturnTypeCheck());
2121
}
2222

2323
public function testPrivate(): void

0 commit comments

Comments
 (0)