From 902e6c355f50a5d72545826fef7521ce04ec6aef Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 20 Oct 2024 20:26:55 +0200 Subject: [PATCH 1/6] Decorate with reason --- .../ConstantConditionRuleHelper.php | 2 +- .../ImpossibleCheckTypeFunctionCallRule.php | 10 ++- .../Comparison/ImpossibleCheckTypeHelper.php | 83 +++++++++++-------- .../ImpossibleCheckTypeMethodCallRule.php | 10 ++- ...mpossibleCheckTypeStaticMethodCallRule.php | 11 ++- ...ingFunctionsDynamicReturnTypeExtension.php | 2 +- 6 files changed, 72 insertions(+), 46 deletions(-) diff --git a/src/Rules/Comparison/ConstantConditionRuleHelper.php b/src/Rules/Comparison/ConstantConditionRuleHelper.php index 9271ef2782..7b02cce2da 100644 --- a/src/Rules/Comparison/ConstantConditionRuleHelper.php +++ b/src/Rules/Comparison/ConstantConditionRuleHelper.php @@ -64,7 +64,7 @@ public function shouldSkip(Scope $scope, Expr $expr): bool || $expr instanceof MethodCall || $expr instanceof Expr\StaticCall ) { - $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $expr); + $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $expr)[0]; if ($isAlways !== null) { return true; } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index 9033aa3865..ef90093103 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -41,17 +41,21 @@ public function processNode(Node $node, Scope $scope): array if (strtolower($functionName) === 'is_a') { return []; } - $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); + [$isAlways, $reasons] = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); if ($isAlways === null) { return []; } - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder { + if (count($reasons) > 0) { + return $ruleErrorBuilder->acceptsReasonsTip($reasons); + } + if (!$this->treatPhpDocTypesAsCertain) { return $ruleErrorBuilder; } - $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); + $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node)[0]; if ($isAlways !== null) { return $ruleErrorBuilder; } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 39a4da06bd..c39b71c5d0 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -53,14 +53,17 @@ public function __construct( { } + /** + * @return array{bool|null, list} + */ public function findSpecifiedType( Scope $scope, Expr $node, - ): ?bool + ): array { if ($node instanceof FuncCall) { if ($node->isFirstClassCallable()) { - return null; + return [null, []]; } $argsCount = count($node->getArgs()); if ($node->name instanceof Node\Name) { @@ -69,10 +72,10 @@ public function findSpecifiedType( $arg = $node->getArgs()[0]->value; $assertValue = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->toBoolean(); if (!$assertValue instanceof ConstantBooleanType) { - return null; + return [null, []]; } - return $assertValue->getValue(); + return [$assertValue->getValue(), []]; } if (in_array($functionName, [ 'class_exists', @@ -80,23 +83,23 @@ public function findSpecifiedType( 'trait_exists', 'enum_exists', ], true)) { - return null; + return [null, []]; } if (in_array($functionName, ['count', 'sizeof'], true)) { - return null; + return [null, []]; } elseif ($functionName === 'defined') { - return null; + return [null, []]; } elseif ($functionName === 'array_search') { - return null; + return [null, []]; } elseif ($functionName === 'in_array' && $argsCount >= 2) { $haystackArg = $node->getArgs()[1]->value; $haystackType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($haystackArg) : $scope->getNativeType($haystackArg)); if ($haystackType instanceof MixedType) { - return null; + return [null, []]; } if (!$haystackType->isArray()->yes()) { - return null; + return [null, []]; } $needleArg = $node->getArgs()[0]->value; @@ -113,36 +116,36 @@ public function findSpecifiedType( || $haystackType->getIterableValueType()->isEnum()->yes(); if (!$isStrictComparison) { - return null; + return [null, []]; } $valueType = $haystackType->getIterableValueType(); $constantNeedleTypesCount = count($needleType->getFiniteTypes()); $constantHaystackTypesCount = count($valueType->getFiniteTypes()); - $isNeedleSupertype = $needleType->isSuperTypeOf($valueType); + $isNeedleSupertype = $needleType->isSuperTypeOfWithReason($valueType); if ($haystackType->isConstantArray()->no()) { if ($haystackType->isIterableAtLeastOnce()->yes()) { // In this case the generic implementation via typeSpecifier fails, because the argument types cannot be narrowed down. if ($constantNeedleTypesCount === 1 && $constantHaystackTypesCount === 1) { - if ($isNeedleSupertype->yes()) { - return true; + if ($isNeedleSupertype->result->yes()) { + return [true, $isNeedleSupertype->reasons]; } - if ($isNeedleSupertype->no()) { - return false; + if ($isNeedleSupertype->result->no()) { + return [false, $isNeedleSupertype->reasons]; } } - return null; + return [null, []]; } } if (!$haystackType instanceof ConstantArrayType || count($haystackType->getValueTypes()) > 0) { $haystackArrayTypes = $haystackType->getArrays(); if (count($haystackArrayTypes) === 1 && $haystackArrayTypes[0]->getIterableValueType() instanceof NeverType) { - return null; + return [null, []]; } - if ($isNeedleSupertype->maybe() || $isNeedleSupertype->yes()) { + if ($isNeedleSupertype->result->maybe() || $isNeedleSupertype->result->yes()) { foreach ($haystackArrayTypes as $haystackArrayType) { if ($haystackArrayType instanceof ConstantArrayType) { foreach ($haystackArrayType->getValueTypes() as $i => $haystackArrayValueType) { @@ -164,18 +167,18 @@ public function findSpecifiedType( } } - return null; + return [null, []]; } } - if ($isNeedleSupertype->yes()) { + if ($isNeedleSupertype->result->yes()) { $hasConstantNeedleTypes = $constantNeedleTypesCount > 0; $hasConstantHaystackTypes = $constantHaystackTypesCount > 0; if ( (!$hasConstantNeedleTypes && !$hasConstantHaystackTypes) || $hasConstantNeedleTypes !== $hasConstantHaystackTypes ) { - return null; + return [null, []]; } } } @@ -186,7 +189,7 @@ public function findSpecifiedType( if ($objectType instanceof ConstantStringType && !$this->reflectionProvider->hasClass($objectType->getValue()) ) { - return false; + return [false, []]; } $methodArg = $node->getArgs()[1]->value; @@ -199,11 +202,11 @@ public function findSpecifiedType( if ($objectType->getObjectClassNames() !== []) { if ($objectType->hasMethod($methodType->getValue())->yes()) { - return true; + return [true, []]; } if ($objectType->hasMethod($methodType->getValue())->no()) { - return false; + return [false, []]; } } @@ -219,7 +222,7 @@ public function findSpecifiedType( if ($genericType instanceof TypeWithClassName) { if ($genericType->hasMethod($methodType->getValue())->yes()) { - return true; + return [true, []]; } $classReflection = $genericType->getClassReflection(); @@ -227,7 +230,7 @@ public function findSpecifiedType( $classReflection !== null && $classReflection->isFinal() && $genericType->hasMethod($methodType->getValue())->no()) { - return false; + return [false, []]; } } } @@ -240,7 +243,7 @@ public function findSpecifiedType( // don't validate types on overwrite if ($specifiedTypes->shouldOverwrite()) { - return null; + return [null, []]; } $sureTypes = $specifiedTypes->getSureTypes(); @@ -249,15 +252,15 @@ public function findSpecifiedType( $rootExpr = $specifiedTypes->getRootExpr(); if ($rootExpr !== null) { if (self::isSpecified($typeSpecifierScope, $node, $rootExpr)) { - return null; + return [null, []]; } $rootExprType = ($this->treatPhpDocTypesAsCertain ? $scope->getType($rootExpr) : $scope->getNativeType($rootExpr)); if ($rootExprType instanceof ConstantBooleanType) { - return $rootExprType->getValue(); + return [$rootExprType->getValue(), []]; } - return null; + return [null, []]; } $results = []; @@ -277,7 +280,12 @@ public function findSpecifiedType( /** @var Type $resultType */ $resultType = $sureType[1]; - $results[] = $resultType->isSuperTypeOf($argumentType); + $isSuperType = $resultType->isSuperTypeOfWithReason($argumentType); + if ($isSuperType->result->no()) { + return [false, $isSuperType->reasons]; + } + + $results[] = $isSuperType->result; } foreach ($sureNotTypes as $sureNotType) { @@ -295,15 +303,20 @@ public function findSpecifiedType( /** @var Type $resultType */ $resultType = $sureNotType[1]; - $results[] = $resultType->isSuperTypeOf($argumentType)->negate(); + $isSuperType = $resultType->isSuperTypeOfWithReason($argumentType); + if ($isSuperType->result->yes()) { + return [false, $isSuperType->reasons]; + } + + $results[] = $isSuperType->result->negate(); } if (count($results) === 0) { - return null; + return [null, []]; } $result = TrinaryLogic::createYes()->and(...$results); - return $result->maybe() ? null : $result->yes(); + return $result->maybe() ? [null, []] : [$result->yes(), []]; } private static function isSpecified(Scope $scope, Expr $node, Expr $expr): bool diff --git a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php index 7bbcecefd7..11ed4cbed7 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php @@ -39,17 +39,21 @@ public function processNode(Node $node, Scope $scope): array return []; } - $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); + [$isAlways, $reasons] = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); if ($isAlways === null) { return []; } - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder { + if (count($reasons)) { + return $ruleErrorBuilder->acceptsReasonsTip($reasons); + } + if (!$this->treatPhpDocTypesAsCertain) { return $ruleErrorBuilder; } - $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); + $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node)[0]; if ($isAlways !== null) { return $ruleErrorBuilder; } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index df4504cb0e..ba2dca4905 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -39,17 +39,22 @@ public function processNode(Node $node, Scope $scope): array return []; } - $isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); + [$isAlways, $reasons] = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node); if ($isAlways === null) { return []; } - $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node): RuleErrorBuilder { + $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder { + if (count($reasons) > 0) { + var_dump($reasons);die; + return $ruleErrorBuilder->acceptsReasonsTip($reasons); + } + if (!$this->treatPhpDocTypesAsCertain) { return $ruleErrorBuilder; } - $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node); + $isAlways = $this->impossibleCheckTypeHelper->doNotTreatPhpDocTypesAsCertain()->findSpecifiedType($scope, $node)[0]; if ($isAlways !== null) { return $ruleErrorBuilder; } diff --git a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php index 1f715dad2c..bcf946505e 100644 --- a/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php +++ b/src/Type/Php/TypeSpecifyingFunctionsDynamicReturnTypeExtension.php @@ -57,7 +57,7 @@ public function getTypeFromFunctionCall( $isAlways = $this->getHelper()->findSpecifiedType( $scope, $functionCall, - ); + )[0]; if ($isAlways === null) { return null; } From 12271ac1d4a2118d30d12423829a9fc07f6a5b8f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 20 Oct 2024 20:27:20 +0200 Subject: [PATCH 2/6] POC --- .../Accessory/AccessoryLowercaseStringType.php | 4 +++- .../ImpossibleCheckTypeFunctionCallRuleTest.php | 15 +++++++++++++++ .../PHPStan/Rules/Comparison/data/bug-11799.php | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-11799.php diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index fe57034af1..12c59e01f2 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -110,7 +110,9 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult return $otherType->isSuperTypeOfWithReason($this); } - return (new IsSuperTypeOfResult($otherType->isLowercaseString(), [])) + return (new IsSuperTypeOfResult($otherType->isLowercaseString(), [ + sprintf("%s is not lowercase.", $otherType->describe(VerbosityLevel::value())), + ])) ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 16529f3a74..3971cfb0f0 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1102,4 +1102,19 @@ public function testAlwaysTruePregMatch(): void $this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []); } + public function testBug11799(): void + { + $this->checkAlwaysTrueCheckTypeFunctionCall = true; + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-11799.php'], [ + [ + "Call to function in_array() with arguments string, array{'publishDate', 'approvedAt', 'allowedValues'} and true will always evaluate to false.", + 11, + "• 'publishDate' is not lowercase. +• 'approvedAt' is not lowercase. +• 'allowedValues' is not lowercase." + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-11799.php b/tests/PHPStan/Rules/Comparison/data/bug-11799.php new file mode 100644 index 0000000000..9d2b2168b8 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-11799.php @@ -0,0 +1,16 @@ + Date: Sun, 20 Oct 2024 20:29:43 +0200 Subject: [PATCH 3/6] Fix --- .../ImpossibleCheckTypeFunctionCallRule.php | 1 + .../Comparison/ImpossibleCheckTypeMethodCallRule.php | 3 ++- .../ImpossibleCheckTypeStaticMethodCallRule.php | 2 +- src/Type/Accessory/AccessoryLowercaseStringType.php | 12 +++++++++--- .../ImpossibleCheckTypeFunctionCallRuleTest.php | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index ef90093103..814f10f695 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -7,6 +7,7 @@ use PHPStan\Parser\LastConditionVisitor; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; +use function count; use function sprintf; use function strtolower; diff --git a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php index 11ed4cbed7..ba80f0e262 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php @@ -10,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use function count; use function sprintf; /** @@ -45,7 +46,7 @@ public function processNode(Node $node, Scope $scope): array } $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder { - if (count($reasons)) { + if (count($reasons) > 0) { return $ruleErrorBuilder->acceptsReasonsTip($reasons); } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index ba2dca4905..95c870a0be 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -10,6 +10,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use function count; use function sprintf; /** @@ -46,7 +47,6 @@ public function processNode(Node $node, Scope $scope): array $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder { if (count($reasons) > 0) { - var_dump($reasons);die; return $ruleErrorBuilder->acceptsReasonsTip($reasons); } diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 12c59e01f2..ac23e48f37 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -30,6 +30,7 @@ use PHPStan\Type\Type; use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; +use function sprintf; class AccessoryLowercaseStringType implements CompoundType, AccessoryType { @@ -110,9 +111,14 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult return $otherType->isSuperTypeOfWithReason($this); } - return (new IsSuperTypeOfResult($otherType->isLowercaseString(), [ - sprintf("%s is not lowercase.", $otherType->describe(VerbosityLevel::value())), - ])) + $isLowercase = $otherType->isLowercaseString(); + + return (new IsSuperTypeOfResult( + $isLowercase, + $otherType->isString()->yes() && $isLowercase->no() + ? [sprintf("%s is not lowercase.", $otherType->describe(VerbosityLevel::value()))] + : [] + )) ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 3971cfb0f0..2edb59a477 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1112,7 +1112,7 @@ public function testBug11799(): void 11, "• 'publishDate' is not lowercase. • 'approvedAt' is not lowercase. -• 'allowedValues' is not lowercase." +• 'allowedValues' is not lowercase.", ], ]); } From 3b11d002d4ec1b4b60284b9b2022d1841f3f752e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 19 Nov 2024 20:22:41 +0100 Subject: [PATCH 4/6] Merge tips --- src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php | 2 +- src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php | 2 +- .../Comparison/ImpossibleCheckTypeStaticMethodCallRule.php | 2 +- src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php | 2 +- src/Rules/RuleErrorBuilder.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php index 814f10f695..438cc73911 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php @@ -49,7 +49,7 @@ public function processNode(Node $node, Scope $scope): array $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder { if (count($reasons) > 0) { - return $ruleErrorBuilder->acceptsReasonsTip($reasons); + $ruleErrorBuilder->acceptsReasonsTip($reasons); } if (!$this->treatPhpDocTypesAsCertain) { diff --git a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php index ba80f0e262..13d9bfd091 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeMethodCallRule.php @@ -47,7 +47,7 @@ public function processNode(Node $node, Scope $scope): array $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder { if (count($reasons) > 0) { - return $ruleErrorBuilder->acceptsReasonsTip($reasons); + $ruleErrorBuilder->acceptsReasonsTip($reasons); } if (!$this->treatPhpDocTypesAsCertain) { diff --git a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php index 95c870a0be..a3dab270a7 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRule.php @@ -47,7 +47,7 @@ public function processNode(Node $node, Scope $scope): array $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $reasons): RuleErrorBuilder { if (count($reasons) > 0) { - return $ruleErrorBuilder->acceptsReasonsTip($reasons); + $ruleErrorBuilder->acceptsReasonsTip($reasons); } if (!$this->treatPhpDocTypesAsCertain) { diff --git a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php index 7d9c764132..6549c1e90b 100644 --- a/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php +++ b/src/Rules/Comparison/StrictComparisonOfDifferentTypesRule.php @@ -56,7 +56,7 @@ public function processNode(Node $node, Scope $scope): array $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $nodeTypeResult): RuleErrorBuilder { $reasons = $nodeTypeResult->reasons; if (count($reasons) > 0) { - return $ruleErrorBuilder->acceptsReasonsTip($reasons); + $ruleErrorBuilder->acceptsReasonsTip($reasons); } if (!$this->treatPhpDocTypesAsCertain) { diff --git a/src/Rules/RuleErrorBuilder.php b/src/Rules/RuleErrorBuilder.php index bf673c0fe8..958e5bdd23 100644 --- a/src/Rules/RuleErrorBuilder.php +++ b/src/Rules/RuleErrorBuilder.php @@ -208,7 +208,7 @@ public function acceptsReasonsTip(array $reasons): self */ public function treatPhpDocTypesAsCertainTip(): self { - return $this->tip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); + return $this->addTip('Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'); } /** From 0087cd29ef7a33f774ed7a1c54e4c879005fca84 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 19 Nov 2024 20:36:41 +0100 Subject: [PATCH 5/6] Fix tests --- ...rictComparisonOfDifferentTypesRuleTest.php | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 01157d82e1..ab1bb867f8 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -277,7 +277,8 @@ public function testStrictComparison(): void [ 'Strict comparison using === between lowercase-string|false and \'AB\' will always evaluate to false.', 1014, - $tipText, + "• 'AB' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ], [ 'Strict comparison using === between mixed and null will always evaluate to false.', @@ -451,7 +452,8 @@ public function testStrictComparisonWithoutAlwaysTrue(): void [ 'Strict comparison using === between lowercase-string|false and \'AB\' will always evaluate to false.', 1014, - $tipText, + "• 'AB' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ], [ 'Strict comparison using === between mixed and null will always evaluate to false.', @@ -1098,27 +1100,32 @@ public function testLowercaseString(): void [ "Strict comparison using === between lowercase-string and 'AB' will always evaluate to false.", 10, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + "• 'AB' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ], [ "Strict comparison using === between 'AB' and lowercase-string will always evaluate to false.", 11, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + "• 'AB' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ], [ "Strict comparison using !== between 'AB' and lowercase-string will always evaluate to true.", 12, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + "• 'AB' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ], [ "Strict comparison using === between lowercase-string and 'aBc' will always evaluate to false.", 15, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + "• 'aBc' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ], [ "Strict comparison using !== between lowercase-string and 'aBc' will always evaluate to true.", 16, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + "• 'aBc' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ], ]; @@ -1126,13 +1133,15 @@ public function testLowercaseString(): void $errors[] = [ "Strict comparison using === between lowercase-string|false and 'AB' will always evaluate to false.", 28, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + "• 'AB' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ]; } else { $errors[] = [ "Strict comparison using === between lowercase-string and 'AB' will always evaluate to false.", 28, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + "• 'AB' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ]; } @@ -1153,15 +1162,18 @@ public function testHashing(): void [ "Strict comparison using === between lowercase-string&non-falsy-string and 'ABC' will always evaluate to false.", 9, + "'ABC' is not lowercase.", ], [ "Strict comparison using === between (lowercase-string&non-falsy-string)|false and 'ABC' will always evaluate to false.", 12, + "'ABC' is not lowercase.", ], [ "Strict comparison using === between (lowercase-string&non-falsy-string)|(non-falsy-string&numeric-string) and 'A' will always evaluate to false.", 31, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', + "• 'A' is not lowercase. +• Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.", ], ]); } From 2ed4fb7c0b8a9d22e422c96225a166cf2174bd2d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 19 Nov 2024 21:02:11 +0100 Subject: [PATCH 6/6] Fix lint --- src/Type/Accessory/AccessoryLowercaseStringType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index ac23e48f37..f5d40548e2 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -116,8 +116,8 @@ public function isSubTypeOfWithReason(Type $otherType): IsSuperTypeOfResult return (new IsSuperTypeOfResult( $isLowercase, $otherType->isString()->yes() && $isLowercase->no() - ? [sprintf("%s is not lowercase.", $otherType->describe(VerbosityLevel::value()))] - : [] + ? [sprintf('%s is not lowercase.', $otherType->describe(VerbosityLevel::value()))] + : [], )) ->and($otherType instanceof self ? IsSuperTypeOfResult::createYes() : IsSuperTypeOfResult::createMaybe()); }