-
Notifications
You must be signed in to change notification settings - Fork 426
/
ConditionalReturnTypeRuleHelper.php
110 lines (92 loc) · 3.57 KB
/
ConditionalReturnTypeRuleHelper.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php declare(strict_types = 1);
namespace PHPStan\Rules\PhpDoc;
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ConditionalType;
use PHPStan\Type\ConditionalTypeForParameter;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\StaticType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\VerbosityLevel;
use function array_key_exists;
use function count;
use function sprintf;
use function substr;
class ConditionalReturnTypeRuleHelper
{
/**
* @return RuleError[]
*/
public function check(ParametersAcceptorWithPhpDocs $acceptor): array
{
$conditionalTypes = [];
$parametersByName = [];
foreach ($acceptor->getParameters() as $parameter) {
TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$conditionalTypes): Type {
if ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) {
$conditionalTypes[] = $type;
}
return $traverse($type);
});
if ($parameter->getOutType() !== null) {
TypeTraverser::map($parameter->getOutType(), static function (Type $type, callable $traverse) use (&$conditionalTypes): Type {
if ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) {
$conditionalTypes[] = $type;
}
return $traverse($type);
});
}
$parametersByName[$parameter->getName()] = $parameter;
}
TypeTraverser::map($acceptor->getReturnType(), static function (Type $type, callable $traverse) use (&$conditionalTypes): Type {
if ($type instanceof ConditionalType || $type instanceof ConditionalTypeForParameter) {
$conditionalTypes[] = $type;
}
return $traverse($type);
});
$errors = [];
foreach ($conditionalTypes as $conditionalType) {
if ($conditionalType instanceof ConditionalType) {
$subjectType = $conditionalType->getSubject();
if ($subjectType instanceof StaticType) {
continue;
}
$templateTypes = [];
TypeTraverser::map($subjectType, static function (Type $type, callable $traverse) use (&$templateTypes): Type {
if ($type instanceof TemplateType) {
$templateTypes[] = $type;
return $type;
}
return $traverse($type);
});
if (count($templateTypes) === 0) {
$errors[] = RuleErrorBuilder::message(sprintf('Conditional return type uses subject type %s which is not part of PHPDoc @template tags.', $subjectType->describe(VerbosityLevel::typeOnly())))->build();
continue;
}
} else {
$parameterName = substr($conditionalType->getParameterName(), 1);
if (!array_key_exists($parameterName, $parametersByName)) {
$errors[] = RuleErrorBuilder::message(sprintf('Conditional return type references unknown parameter $%s.', $parameterName))->build();
continue;
}
$subjectType = $parametersByName[$parameterName]->getType();
}
$targetType = $conditionalType->getTarget();
$isTargetSuperType = $targetType->isSuperTypeOf($subjectType);
if ($isTargetSuperType->maybe()) {
continue;
}
$verbosity = VerbosityLevel::getRecommendedLevelByType($subjectType, $targetType);
$errors[] = RuleErrorBuilder::message(sprintf(
'Condition "%s" in conditional return type is always %s.',
sprintf('%s %s %s', $subjectType->describe($verbosity), $conditionalType->isNegated() ? 'is not' : 'is', $targetType->describe($verbosity)),
$conditionalType->isNegated()
? ($isTargetSuperType->yes() ? 'false' : 'true')
: ($isTargetSuperType->yes() ? 'true' : 'false'),
))->build();
}
return $errors;
}
}