Skip to content

Commit 38fe693

Browse files
committed
MethodSignatureRule - fixed error message
1 parent a9e9b05 commit 38fe693

File tree

3 files changed

+95
-12
lines changed

3 files changed

+95
-12
lines changed

src/Rules/Methods/MethodSignatureRule.php

+18-12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\Rules\RuleErrorBuilder;
1313
use PHPStan\TrinaryLogic;
1414
use PHPStan\Type\MixedType;
15+
use PHPStan\Type\Type;
1516
use PHPStan\Type\TypehintHelper;
1617
use PHPStan\Type\VerbosityLevel;
1718
use PHPStan\Type\VoidType;
@@ -70,22 +71,22 @@ public function processNode(Node $node, Scope $scope): array
7071
continue;
7172
}
7273

73-
$returnTypeCompatibility = $this->checkReturnTypeCompatibility($parameters, $parentParameters);
74+
[$returnTypeCompatibility, $returnType, $parentReturnType] = $this->checkReturnTypeCompatibility($parameters, $parentParameters);
7475
if ($returnTypeCompatibility->no() || (!$returnTypeCompatibility->yes() && $this->reportMaybes)) {
7576
$errors[] = RuleErrorBuilder::message(sprintf(
7677
'Return type (%s) of method %s::%s() should be %s with return type (%s) of method %s::%s()',
77-
$parameters->getPhpDocReturnType()->describe(VerbosityLevel::value()),
78+
$returnType->describe(VerbosityLevel::value()),
7879
$method->getDeclaringClass()->getDisplayName(),
7980
$method->getName(),
8081
$returnTypeCompatibility->no() ? 'compatible' : 'covariant',
81-
$parentParameters->getPhpDocReturnType()->describe(VerbosityLevel::value()),
82+
$parentReturnType->describe(VerbosityLevel::value()),
8283
$parentMethod->getDeclaringClass()->getDisplayName(),
8384
$parentMethod->getName()
8485
))->build();
8586
}
8687

8788
$parameterResults = $this->checkParameterTypeCompatibility($parameters->getParameters(), $parentParameters->getParameters());
88-
foreach ($parameterResults as $parameterIndex => $parameterResult) {
89+
foreach ($parameterResults as $parameterIndex => [$parameterResult, $parameterType, $parentParameterType]) {
8990
if ($parameterResult->yes()) {
9091
continue;
9192
}
@@ -98,12 +99,12 @@ public function processNode(Node $node, Scope $scope): array
9899
'Parameter #%d $%s (%s) of method %s::%s() should be %s with parameter $%s (%s) of method %s::%s()',
99100
$parameterIndex + 1,
100101
$parameter->getName(),
101-
$parameter->getType()->describe(VerbosityLevel::value()),
102+
$parameterType->describe(VerbosityLevel::value()),
102103
$method->getDeclaringClass()->getDisplayName(),
103104
$method->getName(),
104105
$parameterResult->no() ? 'compatible' : 'contravariant',
105106
$parentParameter->getName(),
106-
$parentParameter->getType()->describe(VerbosityLevel::value()),
107+
$parentParameterType->describe(VerbosityLevel::value()),
107108
$parentMethod->getDeclaringClass()->getDisplayName(),
108109
$parentMethod->getName()
109110
))->build();
@@ -141,10 +142,15 @@ private function collectParentMethods(string $methodName, ClassReflection $class
141142
return $parentMethods;
142143
}
143144

145+
/**
146+
* @param ParametersAcceptorWithPhpDocs $currentVariant
147+
* @param ParametersAcceptorWithPhpDocs $parentVariant
148+
* @return array{TrinaryLogic, Type, Type}
149+
*/
144150
private function checkReturnTypeCompatibility(
145151
ParametersAcceptorWithPhpDocs $currentVariant,
146152
ParametersAcceptorWithPhpDocs $parentVariant
147-
): TrinaryLogic
153+
): array
148154
{
149155
$returnType = TypehintHelper::decideType(
150156
$currentVariant->getNativeReturnType(),
@@ -156,21 +162,21 @@ private function checkReturnTypeCompatibility(
156162
);
157163
// Allow adding `void` return type hints when the parent defines no return type
158164
if ($returnType instanceof VoidType && $parentReturnType instanceof MixedType) {
159-
return TrinaryLogic::createYes();
165+
return [TrinaryLogic::createYes(), $returnType, $parentReturnType];
160166
}
161167

162168
// We can return anything
163169
if ($parentReturnType instanceof VoidType) {
164-
return TrinaryLogic::createYes();
170+
return [TrinaryLogic::createYes(), $returnType, $parentReturnType];
165171
}
166172

167-
return $parentReturnType->isSuperTypeOf($returnType);
173+
return [$parentReturnType->isSuperTypeOf($returnType), $returnType, $parentReturnType];
168174
}
169175

170176
/**
171177
* @param \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] $parameters
172178
* @param \PHPStan\Reflection\ParameterReflectionWithPhpDocs[] $parentParameters
173-
* @return array<int, TrinaryLogic>
179+
* @return array<int, array{TrinaryLogic, Type, Type}>
174180
*/
175181
private function checkParameterTypeCompatibility(
176182
array $parameters,
@@ -193,7 +199,7 @@ private function checkParameterTypeCompatibility(
193199
$parentParameter->getPhpDocType()
194200
);
195201

196-
$parameterResults[] = $parameterType->isSuperTypeOf($parentParameterType);
202+
$parameterResults[] = [$parameterType->isSuperTypeOf($parentParameterType), $parameterType, $parentParameterType];
197203
}
198204

199205
return $parameterResults;

tests/PHPStan/Rules/Methods/MethodSignatureRuleTest.php

+20
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,24 @@ public function testBug3997(): void
216216
]);
217217
}
218218

219+
public function testBug4003(): void
220+
{
221+
$this->reportMaybes = true;
222+
$this->reportStatic = true;
223+
$this->analyse([__DIR__ . '/data/bug-4003.php'], [
224+
[
225+
'Return type (string) of method Bug4003\Baz::foo() should be compatible with return type (int) of method Bug4003\Boo::foo()',
226+
15,
227+
],
228+
[
229+
'Parameter #1 $test (string) of method Bug4003\Ipsum::doFoo() should be compatible with parameter $test (int) of method Bug4003\Lorem::doFoo()',
230+
38,
231+
],
232+
[
233+
'Return type (void) of method Bug4003\Amet::bar() should be compatible with return type (*NEVER*) of method Bug4003\Dolor::bar()',
234+
54,
235+
],
236+
]);
237+
}
238+
219239
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Bug4003;
4+
5+
class Boo
6+
{
7+
/** @return int */
8+
public function foo()
9+
{
10+
return 1;
11+
}
12+
}
13+
14+
class Baz extends Boo {
15+
public function foo(): string
16+
{
17+
return 'test';
18+
}
19+
}
20+
21+
22+
class Lorem
23+
{
24+
25+
public function doFoo(int $test)
26+
{
27+
28+
}
29+
30+
}
31+
32+
class Ipsum extends Lorem
33+
{
34+
35+
/**
36+
* @param string $test
37+
*/
38+
public function doFoo($test)
39+
{
40+
41+
}
42+
43+
}
44+
45+
interface Dolor {
46+
/**
47+
* @return void
48+
* @phpstan-return never
49+
*/
50+
public function bar();
51+
}
52+
53+
class Amet implements Dolor {
54+
public function bar(): void {
55+
throw new \Exception();
56+
}
57+
}

0 commit comments

Comments
 (0)