Skip to content

Commit ddf13dc

Browse files
staabmkeradus
authored andcommitted
Extract TooWideReturnTypeCheck (phpstan#4260)
1 parent 1779194 commit ddf13dc

11 files changed

+210
-197
lines changed

src/Rules/TooWideTypehints/TooWideArrowFunctionReturnTypehintRule.php

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
use PHPStan\DependencyInjection\RegisteredRule;
88
use PHPStan\Node\InArrowFunctionNode;
99
use PHPStan\Rules\Rule;
10-
use PHPStan\Rules\RuleErrorBuilder;
1110
use PHPStan\Type\UnionType;
12-
use PHPStan\Type\VerbosityLevel;
13-
use function sprintf;
1411

1512
/**
1613
* @implements Rule<InArrowFunctionNode>
@@ -19,6 +16,12 @@
1916
final class TooWideArrowFunctionReturnTypehintRule implements Rule
2017
{
2118

19+
public function __construct(
20+
private TooWideTypeCheck $check,
21+
)
22+
{
23+
}
24+
2225
public function getNodeType(): string
2326
{
2427
return InArrowFunctionNode::class;
@@ -41,23 +44,7 @@ public function processNode(Node $node, Scope $scope): array
4144
return [];
4245
}
4346

44-
$returnType = $scope->getType($expr);
45-
if ($returnType->isNull()->yes()) {
46-
return [];
47-
}
48-
$messages = [];
49-
foreach ($functionReturnType->getTypes() as $type) {
50-
if (!$type->isSuperTypeOf($returnType)->no()) {
51-
continue;
52-
}
53-
54-
$messages[] = RuleErrorBuilder::message(sprintf(
55-
'Anonymous function never returns %s so it can be removed from the return type.',
56-
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
57-
))->identifier('return.unusedType')->build();
58-
}
59-
60-
return $messages;
47+
return $this->check->checkAnonymousFunction($scope->getType($expr), $functionReturnType);
6148
}
6249

6350
}

src/Rules/TooWideTypehints/TooWideClosureReturnTypehintRule.php

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@
77
use PHPStan\DependencyInjection\RegisteredRule;
88
use PHPStan\Node\ClosureReturnStatementsNode;
99
use PHPStan\Rules\Rule;
10-
use PHPStan\Rules\RuleErrorBuilder;
1110
use PHPStan\Type\TypeCombinator;
1211
use PHPStan\Type\UnionType;
13-
use PHPStan\Type\VerbosityLevel;
1412
use function count;
15-
use function sprintf;
1613

1714
/**
1815
* @implements Rule<ClosureReturnStatementsNode>
@@ -21,6 +18,12 @@
2118
final class TooWideClosureReturnTypehintRule implements Rule
2219
{
2320

21+
public function __construct(
22+
private TooWideTypeCheck $check,
23+
)
24+
{
25+
}
26+
2427
public function getNodeType(): string
2528
{
2629
return ClosureReturnStatementsNode::class;
@@ -62,24 +65,7 @@ public function processNode(Node $node, Scope $scope): array
6265
return [];
6366
}
6467

65-
$returnType = TypeCombinator::union(...$returnTypes);
66-
if ($returnType->isNull()->yes()) {
67-
return [];
68-
}
69-
70-
$messages = [];
71-
foreach ($closureReturnType->getTypes() as $type) {
72-
if (!$type->isSuperTypeOf($returnType)->no()) {
73-
continue;
74-
}
75-
76-
$messages[] = RuleErrorBuilder::message(sprintf(
77-
'Anonymous function never returns %s so it can be removed from the return type.',
78-
$type->describe(VerbosityLevel::getRecommendedLevelByType($type)),
79-
))->identifier('return.unusedType')->build();
80-
}
81-
82-
return $messages;
68+
return $this->check->checkAnonymousFunction(TypeCombinator::union(...$returnTypes), $closureReturnType);
8369
}
8470

8571
}

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 TooWideTypeCheck $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 TooWideTypeCheck $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/TooWidePropertyTypeRule.php

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@
1010
use PHPStan\Rules\Properties\PropertyReflectionFinder;
1111
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
1212
use PHPStan\Rules\Rule;
13-
use PHPStan\Rules\RuleErrorBuilder;
14-
use PHPStan\Type\NullType;
1513
use PHPStan\Type\TypeCombinator;
1614
use PHPStan\Type\UnionType;
17-
use PHPStan\Type\VerbosityLevel;
1815
use function count;
1916
use function sprintf;
2017

@@ -28,6 +25,7 @@ final class TooWidePropertyTypeRule implements Rule
2825
public function __construct(
2926
private ReadWritePropertiesExtensionProvider $extensionProvider,
3027
private PropertyReflectionFinder $propertyReflectionFinder,
28+
private TooWideTypeCheck $check,
3129
)
3230
{
3331
}
@@ -100,27 +98,9 @@ public function processNode(Node $node, Scope $scope): array
10098

10199
$assignedType = TypeCombinator::union(...$assignedTypes);
102100
$propertyDescription = $this->describePropertyByName($propertyReflection, $propertyName);
103-
$verbosityLevel = VerbosityLevel::getRecommendedLevelByType($propertyType, $assignedType);
104-
foreach ($propertyType->getTypes() as $type) {
105-
if (!$type->isSuperTypeOf($assignedType)->no()) {
106-
continue;
107-
}
108-
109-
if ($property->getNativeType() === null && (new NullType())->isSuperTypeOf($type)->yes()) {
110-
continue;
111-
}
112-
113-
$errors[] = RuleErrorBuilder::message(sprintf(
114-
'%s (%s) is never assigned %s so it can be removed from the property type.',
115-
$propertyDescription,
116-
$propertyType->describe($verbosityLevel),
117-
$type->describe($verbosityLevel),
118-
))
119-
->identifier('property.unusedType')
120-
->line($property->getStartLine())
121-
->build();
101+
foreach ($this->check->checkProperty($property, $propertyType, $propertyDescription, $assignedType) as $error) {
102+
$errors[] = $error;
122103
}
123-
124104
}
125105
return $errors;
126106
}

0 commit comments

Comments
 (0)