Skip to content

Commit 944d18b

Browse files
Decorate with reason
1 parent fd645ff commit 944d18b

12 files changed

+135
-57
lines changed

src/Rules/Comparison/ConstantConditionRuleHelper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function shouldSkip(Scope $scope, Expr $expr): bool
5151
|| $expr instanceof MethodCall
5252
|| $expr instanceof Expr\StaticCall
5353
) {
54-
$isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $expr);
54+
$isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $expr)[0];
5555
if ($isAlways !== null) {
5656
return true;
5757
}

src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\Parser\LastConditionVisitor;
88
use PHPStan\Rules\Rule;
99
use PHPStan\Rules\RuleErrorBuilder;
10+
use function count;
1011
use function sprintf;
1112

1213
/**
@@ -36,17 +37,21 @@ public function processNode(Node $node, Scope $scope): array
3637
}
3738

3839
$functionName = (string) $node->name;
39-
$isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node);
40+
[$isAlways, $reasons] = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node);
4041
if ($isAlways === null) {
4142
return [];
4243
}
4344

44-
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder {
45+
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder {
46+
if (count($reasons) > 0) {
47+
$ruleErrorBuilder->acceptsReasonsTip($reasons);
48+
}
49+
4550
if (!$this->treatPhpDocTypesAsCertain) {
4651
return $ruleErrorBuilder;
4752
}
4853

49-
$isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node);
54+
$isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node)[0];
5055
if ($isAlways !== null) {
5156
return $ruleErrorBuilder;
5257
}

src/Rules/Comparison/ImpossibleCheckTypeHelper.php

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,17 @@ public function __construct(
5454
{
5555
}
5656

57+
/**
58+
* @return array{bool|null, list<string>}
59+
*/
5760
public function findSpecifiedType(
5861
Scope $scope,
5962
Expr $node,
60-
): ?bool
63+
): array
6164
{
6265
if ($node instanceof FuncCall) {
6366
if ($node->isFirstClassCallable()) {
64-
return null;
67+
return [null, []];
6568
}
6669
$argsCount = count($node->getArgs());
6770
if ($node->name instanceof Node\Name) {
@@ -70,34 +73,34 @@ public function findSpecifiedType(
7073
$arg = $node->getArgs()[0]->value;
7174
$assertValue = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->toBoolean();
7275
if (!$assertValue instanceof ConstantBooleanType) {
73-
return null;
76+
return [null, []];
7477
}
7578

76-
return $assertValue->getValue();
79+
return [$assertValue->getValue(), []];
7780
}
7881
if (in_array($functionName, [
7982
'class_exists',
8083
'interface_exists',
8184
'trait_exists',
8285
'enum_exists',
8386
], true)) {
84-
return null;
87+
return [null, []];
8588
}
8689
if (in_array($functionName, ['count', 'sizeof'], true)) {
87-
return null;
90+
return [null, []];
8891
} elseif ($functionName === 'defined') {
89-
return null;
92+
return [null, []];
9093
} elseif ($functionName === 'array_search') {
91-
return null;
94+
return [null, []];
9295
} elseif ($functionName === 'in_array' && $argsCount >= 2) {
9396
$haystackArg = $node->getArgs()[1]->value;
9497
$haystackType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($haystackArg) : $scope->getNativeType($haystackArg));
9598
if ($haystackType instanceof MixedType) {
96-
return null;
99+
return [null, []];
97100
}
98101

99102
if (!$haystackType->isArray()->yes()) {
100-
return null;
103+
return [null, []];
101104
}
102105

103106
$needleArg = $node->getArgs()[0]->value;
@@ -114,7 +117,7 @@ public function findSpecifiedType(
114117
|| $haystackType->getIterableValueType()->isEnum()->yes();
115118

116119
if (!$isStrictComparison) {
117-
return null;
120+
return [null, []];
118121
}
119122

120123
$valueType = $haystackType->getIterableValueType();
@@ -125,25 +128,25 @@ public function findSpecifiedType(
125128
if ($haystackType->isIterableAtLeastOnce()->yes()) {
126129
// In this case the generic implementation via typeSpecifier fails, because the argument types cannot be narrowed down.
127130
if ($constantNeedleTypesCount === 1 && $constantHaystackTypesCount === 1) {
128-
if ($isNeedleSupertype->yes()) {
129-
return true;
131+
if ($isNeedleSupertype->result->yes()) {
132+
return [true, $isNeedleSupertype->reasons];
130133
}
131-
if ($isNeedleSupertype->no()) {
132-
return false;
134+
if ($isNeedleSupertype->result->no()) {
135+
return [false, $isNeedleSupertype->reasons];
133136
}
134137
}
135138

136-
return null;
139+
return [null, []];
137140
}
138141
}
139142

140143
if (!$haystackType instanceof ConstantArrayType || count($haystackType->getValueTypes()) > 0) {
141144
$haystackArrayTypes = $haystackType->getArrays();
142145
if (count($haystackArrayTypes) === 1 && $haystackArrayTypes[0]->getIterableValueType() instanceof NeverType) {
143-
return null;
146+
return [null, []];
144147
}
145148

146-
if ($isNeedleSupertype->maybe() || $isNeedleSupertype->yes()) {
149+
if ($isNeedleSupertype->result->maybe() || $isNeedleSupertype->result->yes()) {
147150
foreach ($haystackArrayTypes as $haystackArrayType) {
148151
if ($haystackArrayType instanceof ConstantArrayType) {
149152
foreach ($haystackArrayType->getValueTypes() as $i => $haystackArrayValueType) {
@@ -165,18 +168,18 @@ public function findSpecifiedType(
165168
}
166169
}
167170

168-
return null;
171+
return [null, []];
169172
}
170173
}
171174

172-
if ($isNeedleSupertype->yes()) {
175+
if ($isNeedleSupertype->result->yes()) {
173176
$hasConstantNeedleTypes = $constantNeedleTypesCount > 0;
174177
$hasConstantHaystackTypes = $constantHaystackTypesCount > 0;
175178
if (
176179
(!$hasConstantNeedleTypes && !$hasConstantHaystackTypes)
177180
|| $hasConstantNeedleTypes !== $hasConstantHaystackTypes
178181
) {
179-
return null;
182+
return [null, []];
180183
}
181184
}
182185
}
@@ -187,7 +190,7 @@ public function findSpecifiedType(
187190
if ($objectType instanceof ConstantStringType
188191
&& !$this->reflectionProvider->hasClass($objectType->getValue())
189192
) {
190-
return false;
193+
return [false, []];
191194
}
192195

193196
$methodArg = $node->getArgs()[1]->value;
@@ -200,11 +203,11 @@ public function findSpecifiedType(
200203

201204
if ($objectType->getObjectClassNames() !== []) {
202205
if ($objectType->hasMethod($methodType->getValue())->yes()) {
203-
return true;
206+
return [true, []];
204207
}
205208

206209
if ($objectType->hasMethod($methodType->getValue())->no()) {
207-
return false;
210+
return [false, []];
208211
}
209212
}
210213

@@ -220,15 +223,15 @@ public function findSpecifiedType(
220223

221224
if ($genericType instanceof TypeWithClassName) {
222225
if ($genericType->hasMethod($methodType->getValue())->yes()) {
223-
return true;
226+
return [true, []];
224227
}
225228

226229
$classReflection = $genericType->getClassReflection();
227230
if (
228231
$classReflection !== null
229232
&& $classReflection->isFinal()
230233
&& $genericType->hasMethod($methodType->getValue())->no()) {
231-
return false;
234+
return [false, []];
232235
}
233236
}
234237
}
@@ -245,7 +248,7 @@ public function findSpecifiedType(
245248

246249
// don't validate types on overwrite
247250
if ($specifiedTypes->shouldOverwrite()) {
248-
return null;
251+
return [null, []];
249252
}
250253

251254
$sureTypes = $specifiedTypes->getSureTypes();
@@ -254,15 +257,15 @@ public function findSpecifiedType(
254257
$rootExpr = $specifiedTypes->getRootExpr();
255258
if ($rootExpr !== null) {
256259
if (self::isSpecified($typeSpecifierScope, $node, $rootExpr)) {
257-
return null;
260+
return [null, []];
258261
}
259262

260263
$rootExprType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($rootExpr) : $scope->getNativeType($rootExpr));
261264
if ($rootExprType instanceof ConstantBooleanType) {
262-
return $rootExprType->getValue();
265+
return [$rootExprType->getValue(), []];
263266
}
264267

265-
return null;
268+
return [null, []];
266269
}
267270

268271
$results = [];
@@ -282,7 +285,12 @@ public function findSpecifiedType(
282285
/** @var Type $resultType */
283286
$resultType = $sureType[1];
284287

285-
$results[] = $resultType->isSuperTypeOf($argumentType)->result;
288+
$isSuperType = $resultType->isSuperTypeOf($argumentType);
289+
if ($isSuperType->result->no()) {
290+
return [false, $isSuperType->reasons];
291+
}
292+
293+
$results[] = $isSuperType->result;
286294
}
287295

288296
foreach ($sureNotTypes as $sureNotType) {
@@ -300,15 +308,20 @@ public function findSpecifiedType(
300308
/** @var Type $resultType */
301309
$resultType = $sureNotType[1];
302310

303-
$results[] = $resultType->isSuperTypeOf($argumentType)->negate()->result;
311+
$isSuperType = $resultType->isSuperTypeOf($argumentType);
312+
if ($isSuperType->result->yes()) {
313+
return [false, $isSuperType->reasons];
314+
}
315+
316+
$results[] = $isSuperType->result->negate();
304317
}
305318

306319
if (count($results) === 0) {
307-
return null;
320+
return [null, []];
308321
}
309322

310323
$result = TrinaryLogic::createYes()->and(...$results);
311-
return $result->maybe() ? null : $result->yes();
324+
return $result->maybe() ? [null, []] : [$result->yes(), []];
312325
}
313326

314327
private static function isSpecified(Scope $scope, Expr $node, Expr $expr): bool

src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Rules\Rule;
1111
use PHPStan\Rules\RuleErrorBuilder;
1212
use PHPStan\ShouldNotHappenException;
13+
use function count;
1314
use function sprintf;
1415

1516
/**
@@ -38,17 +39,21 @@ public function processNode(Node $node, Scope $scope): array
3839
return [];
3940
}
4041

41-
$isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node);
42+
[$isAlways, $reasons] = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node);
4243
if ($isAlways === null) {
4344
return [];
4445
}
4546

46-
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder {
47+
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder {
48+
if (count($reasons) > 0) {
49+
$ruleErrorBuilder->acceptsReasonsTip($reasons);
50+
}
51+
4752
if (!$this->treatPhpDocTypesAsCertain) {
4853
return $ruleErrorBuilder;
4954
}
5055

51-
$isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node);
56+
$isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node)[0];
5257
if ($isAlways !== null) {
5358
return $ruleErrorBuilder;
5459
}

src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Rules\Rule;
1111
use PHPStan\Rules\RuleErrorBuilder;
1212
use PHPStan\ShouldNotHappenException;
13+
use function count;
1314
use function sprintf;
1415

1516
/**
@@ -38,17 +39,21 @@ public function processNode(Node $node, Scope $scope): array
3839
return [];
3940
}
4041

41-
$isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node);
42+
[$isAlways, $reasons] = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node);
4243
if ($isAlways === null) {
4344
return [];
4445
}
4546

46-
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder {
47+
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder {
48+
if (count($reasons) > 0) {
49+
$ruleErrorBuilder->acceptsReasonsTip($reasons);
50+
}
51+
4752
if (!$this->treatPhpDocTypesAsCertain) {
4853
return $ruleErrorBuilder;
4954
}
5055

51-
$isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node);
56+
$isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node)[0];
5257
if ($isAlways !== null) {
5358
return $ruleErrorBuilder;
5459
}

src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function processNode(Node $node, Scope $scope): array
6161
$addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $nodeTypeResult): RuleErrorBuilder {
6262
$reasons = $nodeTypeResult->reasons;
6363
if (count($reasons) > 0) {
64-
return $ruleErrorBuilder->acceptsReasonsTip($reasons);
64+
$ruleErrorBuilder->acceptsReasonsTip($reasons);
6565
}
6666

6767
if (!$this->treatPhpDocTypesAsCertain) {

src/Rules/RuleErrorBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public function acceptsReasonsTip(array $reasons): self
207207
*/
208208
public function treatPhpDocTypesAsCertainTip(): self
209209
{
210-
return $this->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
210+
return $this->addTip('Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.');
211211
}
212212

213213
/**

src/Type/Accessory/AccessoryLowercaseStringType.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use PHPStan\Type\Type;
3232
use PHPStan\Type\UnionType;
3333
use PHPStan\Type\VerbosityLevel;
34+
use function sprintf;
3435

3536
class AccessoryLowercaseStringType implements CompoundType, AccessoryType
3637
{
@@ -96,7 +97,14 @@ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult
9697
return $otherType->isSuperTypeOf($this);
9798
}
9899

99-
return (new IsSuperTypeOfResult($otherType->isLowercaseString(), []))
100+
$isLowercase = $otherType->isLowercaseString();
101+
102+
return (new IsSuperTypeOfResult(
103+
$isLowercase,
104+
$otherType->isString()->yes() && $isLowercase->no()
105+
? [sprintf('%s is not lowercase.', $otherType->describe(VerbosityLevel::value()))]
106+
: [],
107+
))
100108
->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe());
101109
}
102110

src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function getTypeFromFunctionCall(
5757
$isAlways = $this->getHelper()->findSpecifiedType(
5858
$scope,
5959
$functionCall,
60-
);
60+
)[0];
6161
if ($isAlways === null) {
6262
return null;
6363
}

0 commit comments

Comments
 (0)