Skip to content

Commit d00b45c

Browse files
rvanvelzenondrejmirtes
authored andcommitted
Add compatibility rule
1 parent 9e511e7 commit d00b45c

File tree

4 files changed

+107
-0
lines changed

4 files changed

+107
-0
lines changed

conf/config.level2.neon

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ rules:
3535
- PHPStan\Rules\PhpDoc\MethodConditionalReturnTypeRule
3636
- PHPStan\Rules\PhpDoc\FunctionAssertRule
3737
- PHPStan\Rules\PhpDoc\MethodAssertRule
38+
- PHPStan\Rules\PhpDoc\IncompatibleSelfOutTypeRule
3839
- PHPStan\Rules\PhpDoc\IncompatibleClassConstantPhpDocTypeRule
3940
- PHPStan\Rules\PhpDoc\IncompatiblePhpDocTypeRule
4041
- PHPStan\Rules\PhpDoc\IncompatiblePropertyPhpDocTypeRule
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PhpDoc;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Node\InClassMethodNode;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use PHPStan\ShouldNotHappenException;
11+
use PHPStan\Type\ObjectType;
12+
use PHPStan\Type\VerbosityLevel;
13+
14+
/**
15+
* @implements Rule<InClassMethodNode>
16+
*/
17+
class IncompatibleSelfOutTypeRule implements Rule
18+
{
19+
20+
public function getNodeType(): string
21+
{
22+
return InClassMethodNode::class;
23+
}
24+
25+
public function processNode(Node $node, Scope $scope): array
26+
{
27+
$method = $scope->getFunction();
28+
if ($method === null) {
29+
throw new ShouldNotHappenException();
30+
}
31+
32+
$selfOutType = $method->getSelfOutType();
33+
if ($selfOutType === null) {
34+
return [];
35+
}
36+
37+
$classReflection = $method->getDeclaringClass();
38+
$classType = new ObjectType($classReflection->getName(), null, $classReflection);
39+
40+
if (!$classType->isSuperTypeOf($selfOutType)->no()) {
41+
return [];
42+
}
43+
44+
return [
45+
RuleErrorBuilder::message(sprintf(
46+
'Out type %s is not compatible with %s.',
47+
$selfOutType->describe(VerbosityLevel::precise()),
48+
$classType->describe(VerbosityLevel::precise()),
49+
))->build(),
50+
];
51+
}
52+
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\PhpDoc;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<IncompatibleSelfOutTypeRule>
10+
*/
11+
class IncompatibleSelfOutTypeRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new IncompatibleSelfOutTypeRule();
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/incompatible-self-out-type.php'], [
22+
[
23+
'Out type int is not compatible with IncompatibleSelfOutType\A.',
24+
23,
25+
],
26+
]);
27+
}
28+
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace IncompatibleSelfOutType;
4+
5+
/**
6+
* @template T
7+
*/
8+
interface A
9+
{
10+
/** @phpstan-self-out self */
11+
public function one();
12+
13+
/**
14+
* @template NewT
15+
* @param NewT $param
16+
* @phpstan-self-out self<NewT>
17+
*/
18+
public function two($param);
19+
20+
/**
21+
* @phpstan-self-out int
22+
*/
23+
public function three();
24+
}

0 commit comments

Comments
 (0)