Skip to content

Commit c30e9a4

Browse files
committed
TooWideMethodReturnTypehintRule - always report for final methods in bleeding edge
1 parent 942afbf commit c30e9a4

9 files changed

+226
-8
lines changed

Diff for: conf/bleedingEdge.neon

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ parameters:
2424
missingMagicSerializationRule: true
2525
nullContextForVoidReturningFunctions: true
2626
unescapeStrings: true
27+
alwaysCheckTooWideReturnTypeFinalMethods: true
2728
duplicateStubs: true
2829
invarianceComposition: true
2930
alwaysTrueAlwaysReported: true

Diff for: conf/config.level4.neon

+1
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ services:
198198
class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule
199199
arguments:
200200
checkProtectedAndPublicMethods: %checkTooWideReturnTypesInProtectedAndPublicMethods%
201+
alwaysCheckFinal: %featureToggles.alwaysCheckTooWideReturnTypeFinalMethods%
201202
tags:
202203
- phpstan.rules.rule
203204

Diff for: conf/config.neon

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ parameters:
5959
missingMagicSerializationRule: false
6060
nullContextForVoidReturningFunctions: false
6161
unescapeStrings: false
62+
alwaysCheckTooWideReturnTypeFinalMethods: false
6263
duplicateStubs: false
6364
invarianceComposition: false
6465
alwaysTrueAlwaysReported: false

Diff for: conf/parametersSchema.neon

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ parametersSchema:
5454
missingMagicSerializationRule: bool()
5555
nullContextForVoidReturningFunctions: bool()
5656
unescapeStrings: bool()
57+
alwaysCheckTooWideReturnTypeFinalMethods: bool()
5758
duplicateStubs: bool()
5859
invarianceComposition: bool()
5960
alwaysTrueAlwaysReported: bool()

Diff for: src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php

+13-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
class TooWideMethodReturnTypehintRule implements Rule
2222
{
2323

24-
public function __construct(private bool $checkProtectedAndPublicMethods)
24+
public function __construct(private bool $checkProtectedAndPublicMethods, private bool $alwaysCheckFinal)
2525
{
2626
}
2727

@@ -35,10 +35,19 @@ public function processNode(Node $node, Scope $scope): array
3535
$method = $node->getMethodReflection();
3636
$isFirstDeclaration = $method->getPrototype()->getDeclaringClass() === $method->getDeclaringClass();
3737
if (!$method->isPrivate()) {
38-
if (!$this->checkProtectedAndPublicMethods) {
38+
if ($this->alwaysCheckFinal) {
39+
if (!$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) {
40+
if (!$this->checkProtectedAndPublicMethods) {
41+
return [];
42+
}
43+
44+
if ($isFirstDeclaration) {
45+
return [];
46+
}
47+
}
48+
} elseif (!$this->checkProtectedAndPublicMethods) {
3949
return [];
40-
}
41-
if ($isFirstDeclaration && !$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) {
50+
} elseif ($isFirstDeclaration && !$method->getDeclaringClass()->isFinal() && !$method->isFinal()->yes()) {
4251
return [];
4352
}
4453
}

Diff for: src/Rules/Whitespace/FileWhitespaceRule.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function processNode(Node $node, Scope $scope): array
4343
private array $lastNodes = [];
4444

4545
/**
46-
* @return int|Node|null
46+
* @return int|null
4747
*/
4848
public function enterNode(Node $node)
4949
{

Diff for: src/Type/Php/FilterFunctionReturnTypeHelper.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function __construct(private ReflectionProvider $reflectionProvider, priv
5555
$this->flagsString = new ConstantStringType('flags');
5656
}
5757

58-
public function getOffsetValueType(Type $inputType, Type $offsetType, ?Type $filterType, ?Type $flagsType): ?Type
58+
public function getOffsetValueType(Type $inputType, Type $offsetType, ?Type $filterType, ?Type $flagsType): Type
5959
{
6060
$inexistentOffsetType = $this->hasFlag($this->getConstant('FILTER_NULL_ON_FAILURE'), $flagsType)
6161
? new ConstantBooleanType(false)
@@ -73,7 +73,7 @@ public function getOffsetValueType(Type $inputType, Type $offsetType, ?Type $fil
7373
: $filteredType;
7474
}
7575

76-
public function getInputType(Type $typeType, Type $varNameType, ?Type $filterType, ?Type $flagsType): ?Type
76+
public function getInputType(Type $typeType, Type $varNameType, ?Type $filterType, ?Type $flagsType): Type
7777
{
7878
$this->supportedFilterInputTypes ??= TypeCombinator::union(
7979
$this->reflectionProvider->getConstant(new Node\Name('INPUT_GET'), null)->getValueType(),

Diff for: tests/PHPStan/Rules/TooWideTypehints/TooWideMethodReturnTypehintRuleTest.php

+143-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212
class TooWideMethodReturnTypehintRuleTest extends RuleTestCase
1313
{
1414

15+
private bool $checkProtectedAndPublicMethods = true;
16+
17+
private bool $alwaysCheckFinal = false;
18+
1519
protected function getRule(): Rule
1620
{
17-
return new TooWideMethodReturnTypehintRule(true);
21+
return new TooWideMethodReturnTypehintRule($this->checkProtectedAndPublicMethods, $this->alwaysCheckFinal);
1822
}
1923

2024
public function testPrivate(): void
@@ -102,4 +106,142 @@ public function testBug6175(): void
102106
$this->analyse([__DIR__ . '/data/bug-6175.php'], []);
103107
}
104108

109+
public function dataAlwaysCheckFinal(): iterable
110+
{
111+
yield [
112+
false,
113+
false,
114+
[
115+
[
116+
'Method MethodTooWideReturnAlwaysCheckFinal\Foo::test() never returns null so it can be removed from the return type.',
117+
8,
118+
],
119+
[
120+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test() never returns null so it can be removed from the return type.',
121+
28,
122+
],
123+
[
124+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test() never returns null so it can be removed from the return type.',
125+
48,
126+
],
127+
],
128+
];
129+
130+
yield [
131+
true,
132+
false,
133+
[
134+
[
135+
'Method MethodTooWideReturnAlwaysCheckFinal\Foo::test() never returns null so it can be removed from the return type.',
136+
8,
137+
],
138+
[
139+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test() never returns null so it can be removed from the return type.',
140+
28,
141+
],
142+
[
143+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test2() never returns null so it can be removed from the return type.',
144+
33,
145+
],
146+
[
147+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test3() never returns null so it can be removed from the return type.',
148+
38,
149+
],
150+
[
151+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test() never returns null so it can be removed from the return type.',
152+
48,
153+
],
154+
[
155+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test2() never returns null so it can be removed from the return type.',
156+
53,
157+
],
158+
[
159+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test3() never returns null so it can be removed from the return type.',
160+
58,
161+
],
162+
],
163+
];
164+
165+
yield [
166+
false,
167+
true,
168+
[
169+
[
170+
'Method MethodTooWideReturnAlwaysCheckFinal\Foo::test() never returns null so it can be removed from the return type.',
171+
8,
172+
],
173+
[
174+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test() never returns null so it can be removed from the return type.',
175+
28,
176+
],
177+
[
178+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test2() never returns null so it can be removed from the return type.',
179+
33,
180+
],
181+
[
182+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test3() never returns null so it can be removed from the return type.',
183+
38,
184+
],
185+
[
186+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test() never returns null so it can be removed from the return type.',
187+
48,
188+
],
189+
[
190+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test2() never returns null so it can be removed from the return type.',
191+
53,
192+
],
193+
[
194+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test3() never returns null so it can be removed from the return type.',
195+
58,
196+
],
197+
],
198+
];
199+
200+
yield [
201+
true,
202+
true,
203+
[
204+
[
205+
'Method MethodTooWideReturnAlwaysCheckFinal\Foo::test() never returns null so it can be removed from the return type.',
206+
8,
207+
],
208+
[
209+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test() never returns null so it can be removed from the return type.',
210+
28,
211+
],
212+
[
213+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test2() never returns null so it can be removed from the return type.',
214+
33,
215+
],
216+
[
217+
'Method MethodTooWideReturnAlwaysCheckFinal\FinalFoo::test3() never returns null so it can be removed from the return type.',
218+
38,
219+
],
220+
[
221+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test() never returns null so it can be removed from the return type.',
222+
48,
223+
],
224+
[
225+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test2() never returns null so it can be removed from the return type.',
226+
53,
227+
],
228+
[
229+
'Method MethodTooWideReturnAlwaysCheckFinal\FooFinalMethods::test3() never returns null so it can be removed from the return type.',
230+
58,
231+
],
232+
],
233+
];
234+
}
235+
236+
/**
237+
* @dataProvider dataAlwaysCheckFinal
238+
* @param list<array{0: string, 1: int, 2?: string|null}> $expectedErrors
239+
*/
240+
public function testAlwaysCheckFinal(bool $checkProtectedAndPublicMethods, bool $alwaysCheckFinal, array $expectedErrors): void
241+
{
242+
$this->checkProtectedAndPublicMethods = $checkProtectedAndPublicMethods;
243+
$this->alwaysCheckFinal = $alwaysCheckFinal;
244+
$this->analyse([__DIR__ . '/data/method-too-wide-return-always-check-final.php'], $expectedErrors);
245+
}
246+
105247
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace MethodTooWideReturnAlwaysCheckFinal;
4+
5+
class Foo
6+
{
7+
8+
private function test(): ?int
9+
{
10+
return 1;
11+
}
12+
13+
protected function test2(): ?int
14+
{
15+
return 1;
16+
}
17+
18+
public function test3(): ?int
19+
{
20+
return 1;
21+
}
22+
23+
}
24+
25+
final class FinalFoo
26+
{
27+
28+
private function test(): ?int
29+
{
30+
return 1;
31+
}
32+
33+
protected function test2(): ?int
34+
{
35+
return 1;
36+
}
37+
38+
public function test3(): ?int
39+
{
40+
return 1;
41+
}
42+
43+
}
44+
45+
class FooFinalMethods
46+
{
47+
48+
private function test(): ?int
49+
{
50+
return 1;
51+
}
52+
53+
final protected function test2(): ?int
54+
{
55+
return 1;
56+
}
57+
58+
final public function test3(): ?int
59+
{
60+
return 1;
61+
}
62+
63+
}

0 commit comments

Comments
 (0)