From 7fd86ee610e9c7e590dce3d669786a23e6faa6f0 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 00:44:00 +0900 Subject: [PATCH 01/22] Replace error-prone instanceof in Rules classes --- .../Api/NodeConnectingVisitorAttributesRule.php | 9 +++++---- .../Arrays/DuplicateKeysInLiteralArraysRule.php | 10 +++++----- src/Rules/Classes/RequireExtendsRule.php | 15 ++++++--------- .../Comparison/ConstantLooseComparisonRule.php | 7 +++---- .../Comparison/ImpossibleCheckTypeHelper.php | 7 ++++--- src/Rules/MissingTypehintCheck.php | 8 ++++---- src/Rules/PhpDoc/RequireExtendsCheck.php | 6 +++--- .../RequireImplementsDefinitionTraitRule.php | 9 +++++---- src/Rules/RuleLevelHelper.php | 8 +++----- .../TooWideMethodReturnTypehintRule.php | 7 +++---- src/Rules/UnusedFunctionParametersCheck.php | 9 ++++----- src/Rules/Variables/CompactVariablesRule.php | 10 +++++----- 12 files changed, 50 insertions(+), 55 deletions(-) diff --git a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php index 7ad74631b9..6458986f53 100644 --- a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php +++ b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php @@ -7,9 +7,9 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ObjectType; use function array_keys; +use function count; use function in_array; use function sprintf; use function str_starts_with; @@ -42,10 +42,11 @@ public function processNode(Node $node, Scope $scope): array return []; } $argType = $scope->getType($args[0]->value); - if (!$argType instanceof ConstantStringType) { + if (count($argType->getConstantStrings()) === 0) { return []; } - if (!in_array($argType->getValue(), ['parent', 'previous', 'next'], true)) { + $argValue = $argType->getConstantScalarValues()[0]; + if (!in_array($argValue, ['parent', 'previous', 'next'], true)) { return []; } if (!$scope->isInClass()) { @@ -67,7 +68,7 @@ public function processNode(Node $node, Scope $scope): array } return [ - RuleErrorBuilder::message(sprintf('Node attribute \'%s\' is no longer available.', $argType->getValue())) + RuleErrorBuilder::message(sprintf('Node attribute \'%s\' is no longer available.', $argValue)) ->identifier('phpParser.nodeConnectingAttribute') ->tip('See: https://phpstan.org/blog/preprocessing-ast-for-custom-rules') ->build(), diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 46b5e712e1..8146aa4019 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -13,6 +13,7 @@ use function array_keys; use function count; use function implode; +use function is_int; use function max; use function sprintf; use function var_export; @@ -70,12 +71,11 @@ public function processNode(Node $node, Scope $scope): array } } else { $keyType = $itemNode->getScope()->getType($key); - - $arrayKeyValue = $keyType->toArrayKey(); - if ($arrayKeyValue instanceof ConstantIntegerType) { + $arrayKeyValues = $keyType->toArrayKey()->getConstantScalarValues(); + if (count($arrayKeyValues) === 1 && is_int($arrayKeyValues[0])) { $autoGeneratedIndex = $autoGeneratedIndex === null - ? $arrayKeyValue->getValue() - : max($autoGeneratedIndex, $arrayKeyValue->getValue()); + ? $arrayKeyValues[0] + : max($autoGeneratedIndex, $arrayKeyValues[0]); } } diff --git a/src/Rules/Classes/RequireExtendsRule.php b/src/Rules/Classes/RequireExtendsRule.php index 036cf3aa85..5b28fb7b36 100644 --- a/src/Rules/Classes/RequireExtendsRule.php +++ b/src/Rules/Classes/RequireExtendsRule.php @@ -7,8 +7,8 @@ use PHPStan\Node\InClassNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; +use function count; use function sprintf; /** @@ -35,11 +35,8 @@ public function processNode(Node $node, Scope $scope): array $extendsTags = $interface->getRequireExtendsTags(); foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { - continue; - } - - if ($classReflection->is($type->getClassName())) { + $classNames = $type->getObjectClassNames(); + if (count($classNames) === 1 && $classReflection->is($classNames[0])) { continue; } @@ -60,11 +57,11 @@ public function processNode(Node $node, Scope $scope): array $extendsTags = $trait->getRequireExtendsTags(); foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { + $classNames = $type->getObjectClassNames(); + if (count($classNames) === 0) { continue; } - - if ($classReflection->is($type->getClassName())) { + if (count($classNames) === 1 && $classReflection->is($classNames[0])) { continue; } diff --git a/src/Rules/Comparison/ConstantLooseComparisonRule.php b/src/Rules/Comparison/ConstantLooseComparisonRule.php index 09961335e7..2371d4dd79 100644 --- a/src/Rules/Comparison/ConstantLooseComparisonRule.php +++ b/src/Rules/Comparison/ConstantLooseComparisonRule.php @@ -7,7 +7,6 @@ use PHPStan\Parser\LastConditionVisitor; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -37,7 +36,7 @@ public function processNode(Node $node, Scope $scope): array } $nodeType = $this->treatPhpDocTypesAsCertain ? $scope->getType($node) : $scope->getNativeType($node); - if (!$nodeType instanceof ConstantBooleanType) { + if (!$nodeType->isTrue()->yes() && !$nodeType->isFalse()->yes()) { return []; } @@ -47,7 +46,7 @@ public function processNode(Node $node, Scope $scope): array } $instanceofTypeWithoutPhpDocs = $scope->getNativeType($node); - if ($instanceofTypeWithoutPhpDocs instanceof ConstantBooleanType) { + if ($instanceofTypeWithoutPhpDocs->isTrue()->yes() || $instanceofTypeWithoutPhpDocs->isFalse()->yes()) { return $ruleErrorBuilder; } if (!$this->treatPhpDocTypesAsCertainTip) { @@ -57,7 +56,7 @@ public function processNode(Node $node, Scope $scope): array return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; - if (!$nodeType->getValue()) { + if ($nodeType->getConstantScalarValues()[0] === false) { return [ $addTip(RuleErrorBuilder::message(sprintf( 'Loose comparison using %s between %s and %s will always evaluate to false.', diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 48eea97d6f..332332a1d9 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -35,6 +35,7 @@ use function count; use function implode; use function in_array; +use function is_bool; use function is_string; use function sprintf; use function strtolower; @@ -68,12 +69,12 @@ public function findSpecifiedType( $functionName = strtolower((string) $node->name); if ($functionName === 'assert' && $argsCount >= 1) { $arg = $node->getArgs()[0]->value; - $assertValue = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->toBoolean(); - if (!$assertValue instanceof ConstantBooleanType) { + $assertValues = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->getConstantScalarValues(); + if (count($assertValues) === 0 || !is_bool($assertValues[0])) { return null; } - return $assertValue->getValue(); + return $assertValues[0]; } if (in_array($functionName, [ 'class_exists', diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index f6910907e1..042aac3cc8 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -108,11 +108,11 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array if ($type instanceof TemplateType) { return $type; } - if ($type instanceof ObjectType) { - $classReflection = $type->getClassReflection(); - if ($classReflection === null) { + if (count($type->getObjectClassNames()) > 0) { + if (count($type->getObjectClassReflections()) === 0) { return $type; } + $classReflection = $type->getObjectClassReflections()[0]; if (in_array($classReflection->getName(), self::ITERABLE_GENERIC_CLASS_NAMES, true)) { // checked by getIterableTypesWithMissingValueTypehint() already return $type; @@ -128,7 +128,7 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array } $resolvedType = TemplateTypeHelper::resolveToBounds($type); - if (!$resolvedType instanceof ObjectType) { + if (count($resolvedType->getObjectClassNames()) === 0) { throw new ShouldNotHappenException(); } diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 14f6d43647..c35129b00c 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -41,15 +41,15 @@ public function checkExtendsTags(Node $node, array $extendsTags): array foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { + if (count($type->getObjectClassNames()) === 0) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) ->identifier('requireExtends.nonObject') ->build(); continue; } - $class = $type->getClassName(); - $referencedClassReflection = $type->getClassReflection(); + $class = $type->getObjectClassNames()[0]; + $referencedClassReflection = $type->getObjectClassReflections()[0] ?? null; if ($referencedClassReflection === null) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 076ae342a0..778c54745d 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -9,9 +9,9 @@ use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; use function array_merge; +use function count; use function sprintf; use function strtolower; @@ -49,15 +49,16 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($implementsTags as $implementsTag) { $type = $implementsTag->getType(); - if (!$type instanceof ObjectType) { + $classNames = $type->getObjectClassNames(); + if (count($classNames) === 0) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) ->identifier('requireImplements.nonObject') ->build(); continue; } - $class = $type->getClassName(); - $referencedClassReflection = $type->getClassReflection(); + $class = $classNames[0]; + $referencedClassReflection = $type->getObjectClassReflections()[0] ?? null; if ($referencedClassReflection === null) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) ->discoveringSymbolsTip() diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 416583546f..eb025ac4e2 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -14,7 +14,6 @@ use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; use PHPStan\Type\NullType; -use PHPStan\Type\ObjectType; use PHPStan\Type\StrictMixedType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; @@ -304,10 +303,9 @@ private function findTypeToCheckImplementation( if ( $type instanceof UnionType && count($type->getTypes()) === 2 - && $type->getTypes()[0] instanceof ObjectType - && $type->getTypes()[1] instanceof ObjectType - && $type->getTypes()[0]->getClassName() === 'PhpParser\\Node\\Arg' - && $type->getTypes()[1]->getClassName() === 'PhpParser\\Node\\VariadicPlaceholder' + && $type->isObject()->yes() + && $type->getTypes()[0]->getObjectClassNames() === ['PhpParser\\Node\\Arg'] + && $type->getTypes()[1]->getObjectClassNames() === ['PhpParser\\Node\\VariadicPlaceholder'] && !$unionTypeCriteriaCallback($type) ) { $tip = 'Use ->getArgs() instead of ->args.'; diff --git a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php index 1c40c4a9ce..8737855175 100644 --- a/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php +++ b/src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php @@ -7,7 +7,6 @@ use PHPStan\Node\MethodReturnStatementsNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeUtils; use PHPStan\Type\UnionType; @@ -82,9 +81,9 @@ public function processNode(Node $node, Scope $scope): array $returnType = TypeCombinator::union(...$returnTypes); if ( - !$method->isPrivate() - && ($returnType->isNull()->yes() || $returnType instanceof ConstantBooleanType) - && !$isFirstDeclaration + !$isFirstDeclaration + && !$method->isPrivate() + && ($returnType->isNull()->yes() || $returnType->isTrue()->yes() || $returnType->isFalse()->yes()) ) { return []; } diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index 628041a032..4cd54af9ec 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -7,10 +7,10 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; -use PHPStan\Type\Constant\ConstantStringType; use function array_combine; use function array_map; use function array_merge; +use function count; use function is_array; use function is_string; use function sprintf; @@ -92,11 +92,10 @@ private function getUsedVariables(Scope $scope, $node): array ) { foreach ($node->getArgs() as $arg) { $argType = $scope->getType($arg->value); - if (!($argType instanceof ConstantStringType)) { - continue; + if (count($argType->getConstantStrings()) === 1) { + $constantStringType = $argType->getConstantStrings()[0]; + $variableNames[] = $constantStringType->getValue(); } - - $variableNames[] = $argType->getValue(); } } foreach ($node->getSubNodeNames() as $subNodeName) { diff --git a/src/Rules/Variables/CompactVariablesRule.php b/src/Rules/Variables/CompactVariablesRule.php index c6324f7678..a2468f1779 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -66,17 +66,17 @@ public function processNode(Node $node, Scope $scope): array } /** - * @return array + * @return list */ private function findConstantStrings(Type $type): array { - if ($type instanceof ConstantStringType) { - return [$type]; + if (count($type->getConstantStrings()) > 0) { + return $type->getConstantStrings(); } - if ($type instanceof ConstantArrayType) { + if (count($type->getConstantArrays()) > 1) { $result = []; - foreach ($type->getValueTypes() as $valueType) { + foreach ($type->getConstantArrays()[0]->getValueTypes() as $valueType) { $constantStrings = $this->findConstantStrings($valueType); $result = array_merge($result, $constantStrings); } From c7d029fc165eb390a0937fa29c9d93aa8af73e7b Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 01:38:31 +0900 Subject: [PATCH 02/22] wip --- phpstan-baseline.neon | 62 +------------------------------------------ 1 file changed, 1 insertion(+), 61 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d252aaef59..849c285b6d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -411,12 +411,6 @@ parameters: count: 1 path: src/Reflection/SignatureMap/Php8SignatureMapProvider.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/Api/NodeConnectingVisitorAttributesRule.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -429,12 +423,6 @@ parameters: count: 2 path: src/Rules/Classes/ImpossibleInstanceOfRule.php - - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 2 - path: src/Rules/Classes/RequireExtendsRule.php - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -459,12 +447,6 @@ parameters: count: 6 path: src/Rules/Comparison/BooleanOrConstantConditionRule.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 2 - path: src/Rules/Comparison/ConstantLooseComparisonRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType @@ -492,7 +474,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 2 + count: 1 path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php - @@ -645,18 +627,6 @@ parameters: count: 1 path: src/Rules/Methods/StaticMethodCallCheck.php - - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/PhpDoc/RequireExtendsCheck.php - - - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Generic\\GenericObjectType is error\-prone and deprecated\.$#' identifier: phpstanApi.instanceofType @@ -675,36 +645,6 @@ parameters: count: 1 path: src/Rules/RuleLevelHelper.php - - - message: '#^Doing instanceof PHPStan\\Type\\ObjectType is error\-prone and deprecated\. Use Type\:\:isObject\(\) or Type\:\:getObjectClassNames\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 2 - path: src/Rules/RuleLevelHelper.php - - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/TooWideTypehints/TooWideMethodReturnTypehintRule.php - - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/UnusedFunctionParametersCheck.php - - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantArrayType is error\-prone and deprecated\. Use Type\:\:getConstantArrays\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/Variables/CompactVariablesRule.php - - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/Variables/CompactVariablesRule.php - - message: ''' #^Call to deprecated method assertFileNotExists\(\) of class PHPUnit\\Framework\\Assert\: From 9394d68e5bfa47631e3dbbfa86d21dc72a59a6da Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 01:39:47 +0900 Subject: [PATCH 03/22] fixup! Replace error-prone instanceof in Rules classes --- src/Rules/Variables/CompactVariablesRule.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Rules/Variables/CompactVariablesRule.php b/src/Rules/Variables/CompactVariablesRule.php index a2468f1779..b9fc53b385 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Type; use function array_merge; @@ -70,11 +69,11 @@ public function processNode(Node $node, Scope $scope): array */ private function findConstantStrings(Type $type): array { - if (count($type->getConstantStrings()) > 0) { + if (count($type->getConstantStrings()) === 1) { return $type->getConstantStrings(); } - if (count($type->getConstantArrays()) > 1) { + if (count($type->getConstantArrays()) === 1) { $result = []; foreach ($type->getConstantArrays()[0]->getValueTypes() as $valueType) { $constantStrings = $this->findConstantStrings($valueType); From b829cec3d3a2b27084c960d3b6b6c83aa62acbed Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 01:54:07 +0900 Subject: [PATCH 04/22] Update src/Rules/Api/NodeConnectingVisitorAttributesRule.php Co-authored-by: Markus Staab --- src/Rules/Api/NodeConnectingVisitorAttributesRule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php index 6458986f53..fd87a86b2c 100644 --- a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php +++ b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php @@ -42,7 +42,7 @@ public function processNode(Node $node, Scope $scope): array return []; } $argType = $scope->getType($args[0]->value); - if (count($argType->getConstantStrings()) === 0) { + if (count($argType->getConstantStrings()) !== 1) { return []; } $argValue = $argType->getConstantScalarValues()[0]; From a6b22149ea276b111dacbcbb994e5ff438432011 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 02:03:23 +0900 Subject: [PATCH 05/22] fixup! Replace error-prone instanceof in Rules classes --- src/Rules/Comparison/ImpossibleCheckTypeHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 332332a1d9..7448b73c32 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -70,7 +70,7 @@ public function findSpecifiedType( if ($functionName === 'assert' && $argsCount >= 1) { $arg = $node->getArgs()[0]->value; $assertValues = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->getConstantScalarValues(); - if (count($assertValues) === 0 || !is_bool($assertValues[0])) { + if (count($assertValues) !== 1 || !is_bool($assertValues[0])) { return null; } From 0e3032a79cf5bbc368ded873b70b4b80a89696a5 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 02:09:39 +0900 Subject: [PATCH 06/22] fixup! fixup! Replace error-prone instanceof in Rules classes --- src/Rules/Variables/CompactVariablesRule.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Rules/Variables/CompactVariablesRule.php b/src/Rules/Variables/CompactVariablesRule.php index b9fc53b385..2e23f77473 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -9,6 +9,7 @@ use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Type; use function array_merge; +use function count; use function sprintf; use function strtolower; From 6d74a441b33250eb090f3e325f05330e5b777d35 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 02:43:42 +0900 Subject: [PATCH 07/22] fixup! Replace error-prone instanceof in Rules classes --- src/Rules/MissingTypehintCheck.php | 4 ++-- src/Rules/UnusedFunctionParametersCheck.php | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index 042aac3cc8..e28fe2f570 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -108,7 +108,7 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array if ($type instanceof TemplateType) { return $type; } - if (count($type->getObjectClassNames()) > 0) { + if ($type->isObject()->yes()) { if (count($type->getObjectClassReflections()) === 0) { return $type; } @@ -128,7 +128,7 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array } $resolvedType = TemplateTypeHelper::resolveToBounds($type); - if (count($resolvedType->getObjectClassNames()) === 0) { + if (!$resolvedType->isObject()->yes()) { throw new ShouldNotHappenException(); } diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index 4cd54af9ec..d1b0ecb70f 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -92,8 +92,7 @@ private function getUsedVariables(Scope $scope, $node): array ) { foreach ($node->getArgs() as $arg) { $argType = $scope->getType($arg->value); - if (count($argType->getConstantStrings()) === 1) { - $constantStringType = $argType->getConstantStrings()[0]; + foreach ($argType->getConstantStrings() as $constantStringType) { $variableNames[] = $constantStringType->getValue(); } } From 4013a0207d6b3ea06a1c09446a18411139fddae9 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 02:46:44 +0900 Subject: [PATCH 08/22] fixup! Replace error-prone instanceof in Rules classes --- src/Rules/PhpDoc/RequireExtendsCheck.php | 1 - src/Rules/UnusedFunctionParametersCheck.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index c35129b00c..4e620f8abe 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -8,7 +8,6 @@ use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; use function array_merge; use function count; diff --git a/src/Rules/UnusedFunctionParametersCheck.php b/src/Rules/UnusedFunctionParametersCheck.php index d1b0ecb70f..68c1549091 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -10,7 +10,6 @@ use function array_combine; use function array_map; use function array_merge; -use function count; use function is_array; use function is_string; use function sprintf; From ef062229fddb945ad2f251a2d9dbf7f9fadf5757 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 03:14:07 +0900 Subject: [PATCH 09/22] Revert src/Rules/MissingTypehintCheck.php --- src/Rules/MissingTypehintCheck.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rules/MissingTypehintCheck.php b/src/Rules/MissingTypehintCheck.php index e28fe2f570..f6910907e1 100644 --- a/src/Rules/MissingTypehintCheck.php +++ b/src/Rules/MissingTypehintCheck.php @@ -108,11 +108,11 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array if ($type instanceof TemplateType) { return $type; } - if ($type->isObject()->yes()) { - if (count($type->getObjectClassReflections()) === 0) { + if ($type instanceof ObjectType) { + $classReflection = $type->getClassReflection(); + if ($classReflection === null) { return $type; } - $classReflection = $type->getObjectClassReflections()[0]; if (in_array($classReflection->getName(), self::ITERABLE_GENERIC_CLASS_NAMES, true)) { // checked by getIterableTypesWithMissingValueTypehint() already return $type; @@ -128,7 +128,7 @@ public function getNonGenericObjectTypesWithGenericClass(Type $type): array } $resolvedType = TemplateTypeHelper::resolveToBounds($type); - if (!$resolvedType->isObject()->yes()) { + if (!$resolvedType instanceof ObjectType) { throw new ShouldNotHappenException(); } From 635f751ea2ac1af92660646460223a46de32bc91 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 7 Mar 2025 20:39:10 +0900 Subject: [PATCH 10/22] foreach $type->getConstantArrays() --- src/Rules/Variables/CompactVariablesRule.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Rules/Variables/CompactVariablesRule.php b/src/Rules/Variables/CompactVariablesRule.php index 2e23f77473..c75f7f6684 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -70,21 +70,19 @@ public function processNode(Node $node, Scope $scope): array */ private function findConstantStrings(Type $type): array { - if (count($type->getConstantStrings()) === 1) { + if (count($type->getConstantStrings()) > 0) { return $type->getConstantStrings(); } - if (count($type->getConstantArrays()) === 1) { - $result = []; - foreach ($type->getConstantArrays()[0]->getValueTypes() as $valueType) { + $result = []; + foreach ($type->getConstantArrays() as $constantArrayType) { + foreach ($constantArrayType->getValueTypes() as $valueType) { $constantStrings = $this->findConstantStrings($valueType); $result = array_merge($result, $constantStrings); } - - return $result; } - return []; + return $result; } } From ef358e48e0c7c533ef017651d022dfd4b622d628 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 00:21:13 +0900 Subject: [PATCH 11/22] Support multiple tags and improve interface handling in @phpstan-require-extends --- src/Rules/PhpDoc/RequireExtendsCheck.php | 59 +++++++++++-------- .../RequireExtendsDefinitionClassRuleTest.php | 6 +- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 4e620f8abe..57b4df3cf4 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -9,6 +9,8 @@ use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; +use function array_column; +use function array_map; use function array_merge; use function count; use function sprintf; @@ -47,32 +49,39 @@ public function checkExtendsTags(Node $node, array $extendsTags): array continue; } - $class = $type->getObjectClassNames()[0]; - $referencedClassReflection = $type->getObjectClassReflections()[0] ?? null; + $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); + $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); + foreach ($type->getObjectClassNames() as $class) { + $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; + if ($referencedClassReflection === null) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) + ->discoveringSymbolsTip() + ->identifier('class.notFound') + ->build(); + continue; + } - if ($referencedClassReflection === null) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) - ->discoveringSymbolsTip() - ->identifier('class.notFound') - ->build(); - continue; - } - - if (!$referencedClassReflection->isClass()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain non-class type %s.', $class)) - ->identifier(sprintf('requireExtends.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) - ->build(); - } elseif ($referencedClassReflection->isFinal()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain final class %s.', $class)) - ->identifier('requireExtends.finalClass') - ->build(); - } else { - $errors = array_merge( - $errors, - $this->classCheck->checkClassNames([ - new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), - ); + if ($referencedClassReflection->isInterface()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain an interface %s, expected a class.', $class)) + ->tip('If you meant an interface, use @phpstan-require-implements instead.') + ->identifier('requireExtends.interface') + ->build(); + } elseif (!$referencedClassReflection->isClass()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain non-class type %s.', $class)) + ->identifier(sprintf('requireExtends.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) + ->build(); + } elseif ($referencedClassReflection->isFinal()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends cannot contain final class %s.', $class)) + ->identifier('requireExtends.finalClass') + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); + } } } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index 5a24e75502..64e0b07dcd 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -45,8 +45,9 @@ public function testRule(): void 8, ], [ - 'PHPDoc tag @phpstan-require-extends cannot contain non-class type IncompatibleRequireExtends\SomeInterface.', + 'PHPDoc tag @phpstan-require-extends cannot contain an interface IncompatibleRequireExtends\SomeInterface, expected a class.', 13, + 'If you meant an interface, use @phpstan-require-implements instead.', ], [ $enumError, @@ -75,8 +76,9 @@ public function testRule(): void 121, ], [ - 'PHPDoc tag @phpstan-require-extends contains non-object type IncompatibleRequireExtends\UnresolvableExtendsInterface&stdClass.', + 'PHPDoc tag @phpstan-require-extends cannot contain an interface IncompatibleRequireExtends\UnresolvableExtendsInterface, expected a class.', 135, + 'If you meant an interface, use @phpstan-require-implements instead.', ], [ 'PHPDoc tag @phpstan-require-extends can only be used once.', From a6c09fbd23f64a5df4bbdab5543f7b8cdde27063 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 00:41:33 +0900 Subject: [PATCH 12/22] Added support for ConstantString unions in NodeConnectingVisitorAttributesRule --- .../NodeConnectingVisitorAttributesRule.php | 49 ++++++++++--------- ...odeConnectingVisitorAttributesRuleTest.php | 7 ++- .../Api/data/node-connecting-visitor.php | 5 ++ 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php index fd87a86b2c..282c2189d2 100644 --- a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php +++ b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php @@ -9,7 +9,6 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ObjectType; use function array_keys; -use function count; use function in_array; use function sprintf; use function str_starts_with; @@ -41,38 +40,40 @@ public function processNode(Node $node, Scope $scope): array if (!isset($args[0])) { return []; } + + $messages = []; $argType = $scope->getType($args[0]->value); - if (count($argType->getConstantStrings()) !== 1) { - return []; - } - $argValue = $argType->getConstantScalarValues()[0]; - if (!in_array($argValue, ['parent', 'previous', 'next'], true)) { - return []; - } - if (!$scope->isInClass()) { - return []; - } + foreach ($argType->getConstantStrings() as $constantString) { + $argValue = $constantString->getValue(); + if (!in_array($argValue, ['parent', 'previous', 'next'], true)) { + continue; + } - $classReflection = $scope->getClassReflection(); - $hasPhpStanInterface = false; - foreach (array_keys($classReflection->getInterfaces()) as $interfaceName) { - if (!str_starts_with($interfaceName, 'PHPStan\\')) { + if (!$scope->isInClass()) { continue; } - $hasPhpStanInterface = true; - } + $classReflection = $scope->getClassReflection(); + $hasPhpStanInterface = false; + foreach (array_keys($classReflection->getInterfaces()) as $interfaceName) { + if (!str_starts_with($interfaceName, 'PHPStan\\')) { + continue; + } - if (!$hasPhpStanInterface) { - return []; - } + $hasPhpStanInterface = true; + } - return [ - RuleErrorBuilder::message(sprintf('Node attribute \'%s\' is no longer available.', $argValue)) + if (!$hasPhpStanInterface) { + continue; + } + + $messages[] = RuleErrorBuilder::message(sprintf('Node attribute \'%s\' is no longer available.', $argValue)) ->identifier('phpParser.nodeConnectingAttribute') ->tip('See: https://phpstan.org/blog/preprocessing-ast-for-custom-rules') - ->build(), - ]; + ->build(); + } + + return $messages; } } diff --git a/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php b/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php index c7fe99bc18..b811039ec7 100644 --- a/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php +++ b/tests/PHPStan/Rules/Api/NodeConnectingVisitorAttributesRuleTest.php @@ -21,7 +21,12 @@ public function testRule(): void $this->analyse([__DIR__ . '/data/node-connecting-visitor.php'], [ [ 'Node attribute \'parent\' is no longer available.', - 18, + 22, + 'See: https://phpstan.org/blog/preprocessing-ast-for-custom-rules', + ], + [ + 'Node attribute \'parent\' is no longer available.', + 24, 'See: https://phpstan.org/blog/preprocessing-ast-for-custom-rules', ], ]); diff --git a/tests/PHPStan/Rules/Api/data/node-connecting-visitor.php b/tests/PHPStan/Rules/Api/data/node-connecting-visitor.php index e61c7c3a4d..93f18f28a9 100644 --- a/tests/PHPStan/Rules/Api/data/node-connecting-visitor.php +++ b/tests/PHPStan/Rules/Api/data/node-connecting-visitor.php @@ -8,6 +8,10 @@ class MyRule implements Rule { + + /** @var 'parent'|'myCustomAttribute' */ + public string $attrName; + public function getNodeType(): string { return Node::class; @@ -17,6 +21,7 @@ public function processNode(Node $node, Scope $scope): array { $parent = $node->getAttribute("parent"); $custom = $node->getAttribute("myCustomAttribute"); + $parent = $node->getAttribute($this->attrName); return []; } From 2ea305746dba426fd87741997f448288af6f371d Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 01:20:48 +0900 Subject: [PATCH 13/22] fixup! Replace error-prone instanceof in Rules classes --- .../RequireImplementsDefinitionTraitRule.php | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 778c54745d..184b569259 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -10,6 +10,8 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; +use function array_column; +use function array_map; use function array_merge; use function count; use function sprintf; @@ -49,35 +51,38 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($implementsTags as $implementsTag) { $type = $implementsTag->getType(); - $classNames = $type->getObjectClassNames(); - if (count($classNames) === 0) { + if (count($type->getObjectClassNames()) === 0) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) ->identifier('requireImplements.nonObject') ->build(); continue; } - $class = $classNames[0]; - $referencedClassReflection = $type->getObjectClassReflections()[0] ?? null; - if ($referencedClassReflection === null) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) - ->discoveringSymbolsTip() - ->identifier('class.notFound') - ->build(); - continue; - } + $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); + $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); + foreach ($type->getObjectClassNames() as $class) { + $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; + if ($referencedClassReflection === null) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) + ->discoveringSymbolsTip() + ->identifier('class.notFound') + ->build(); + continue; + } + + if (!$referencedClassReflection->isInterface()) { + $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements cannot contain non-interface type %s.', $class)) + ->identifier(sprintf('requireImplements.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) + ->build(); + } else { + $errors = array_merge( + $errors, + $this->classCheck->checkClassNames([ + new ClassNameNodePair($class, $node), + ], $this->checkClassCaseSensitivity), + ); + } - if (!$referencedClassReflection->isInterface()) { - $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements cannot contain non-interface type %s.', $class)) - ->identifier(sprintf('requireImplements.%s', strtolower($referencedClassReflection->getClassTypeDescription()))) - ->build(); - } else { - $errors = array_merge( - $errors, - $this->classCheck->checkClassNames([ - new ClassNameNodePair($class, $node), - ], $this->checkClassCaseSensitivity), - ); } } From 5139160e176f3b2b0567a12771a5403d73e54612 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 02:11:20 +0900 Subject: [PATCH 14/22] Added support for ConstantString unions in DuplicateKeysInLiteralArraysRule --- .../DuplicateKeysInLiteralArraysRule.php | 31 +++++++++---------- .../DuplicateKeysInLiteralArraysRuleTest.php | 12 +++++++ .../Rules/Arrays/data/duplicate-keys.php | 15 +++++++++ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 8146aa4019..23b8b5df76 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -9,7 +9,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\ConstantScalarType; use function array_keys; use function count; use function implode; @@ -79,29 +78,29 @@ public function processNode(Node $node, Scope $scope): array } } - if (!$keyType instanceof ConstantScalarType) { + if (count($keyType->getConstantScalarValues()) === 0) { $autoGeneratedIndex = false; continue; } - $value = $keyType->getValue(); - $printedValue = $key !== null - ? $this->exprPrinter->printExpr($key) - : $value; + foreach ($keyType->getConstantScalarValues() as $value) { + $printedValue = $key !== null + ? $this->exprPrinter->printExpr($key) + : $value; + $printedValues[$value][] = $printedValue; - $printedValues[$value][] = $printedValue; + if (!isset($valueLines[$value])) { + $valueLines[$value] = $item->getStartLine(); + } - if (!isset($valueLines[$value])) { - $valueLines[$value] = $item->getStartLine(); - } + $previousCount = count($values); + $values[$value] = $printedValue; + if ($previousCount !== count($values)) { + continue; + } - $previousCount = count($values); - $values[$value] = $printedValue; - if ($previousCount !== count($values)) { - continue; + $duplicateKeys[$value] = true; } - - $duplicateKeys[$value] = true; } $messages = []; diff --git a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php index 87b5a12e5f..6d775a2f7c 100644 --- a/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/DuplicateKeysInLiteralArraysRuleTest.php @@ -61,6 +61,18 @@ public function testDuplicateKeys(): void 'Array has 2 duplicate keys with value -41 (-41, -41).', 76, ], + [ + 'Array has 2 duplicate keys with value \'foo\' (\'foo\', $key).', + 102, + ], + [ + 'Array has 2 duplicate keys with value \'bar\' (\'bar\', $key).', + 103, + ], + [ + 'Array has 2 duplicate keys with value \'key\' (\'key\', $key2).', + 105, + ], ]); } diff --git a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php index 02176773ac..06dbbeb0f5 100644 --- a/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php +++ b/tests/PHPStan/Rules/Arrays/data/duplicate-keys.php @@ -92,4 +92,19 @@ public function doWithoutKeys(int $int) ]; } + /** + * @param 'foo'|'bar' $key + */ + public function doUnionKeys(string $key): void + { + $key2 = 'key'; + $a = [ + 'foo' => 'foo', + 'bar' => 'bar', + $key => 'foo|bar', + 'key' => 'bar', + $key2 => 'foo', + ]; + } + } From 3de5d015e56ee729256ddd35d2eceed6f11c03b9 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 02:35:10 +0900 Subject: [PATCH 15/22] Added support for ConstantString unions in RequireExtendsRule --- src/Rules/Classes/RequireExtendsRule.php | 66 ++++++++++--------- .../Rules/Classes/RequireExtendsRuleTest.php | 16 +++++ .../data/incompatible-require-extends.php | 22 +++++++ 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/src/Rules/Classes/RequireExtendsRule.php b/src/Rules/Classes/RequireExtendsRule.php index 5b28fb7b36..88c0b88ccd 100644 --- a/src/Rules/Classes/RequireExtendsRule.php +++ b/src/Rules/Classes/RequireExtendsRule.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; -use function count; use function sprintf; /** @@ -35,21 +34,24 @@ public function processNode(Node $node, Scope $scope): array $extendsTags = $interface->getRequireExtendsTags(); foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - $classNames = $type->getObjectClassNames(); - if (count($classNames) === 1 && $classReflection->is($classNames[0])) { - continue; - } + foreach ($type->getObjectClassNames() as $className) { + if ($classReflection->is($className)) { + continue; + } + + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Interface %s requires implementing class to extend %s, but %s does not.', + $interface->getDisplayName(), + $type->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + ), + ) + ->identifier('class.missingExtends') + ->build(); - $errors[] = RuleErrorBuilder::message( - sprintf( - 'Interface %s requires implementing class to extend %s, but %s does not.', - $interface->getDisplayName(), - $type->describe(VerbosityLevel::typeOnly()), - $classReflection->getDisplayName(), - ), - ) - ->identifier('class.missingExtends') - ->build(); + break; + } } } @@ -57,24 +59,24 @@ public function processNode(Node $node, Scope $scope): array $extendsTags = $trait->getRequireExtendsTags(); foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - $classNames = $type->getObjectClassNames(); - if (count($classNames) === 0) { - continue; - } - if (count($classNames) === 1 && $classReflection->is($classNames[0])) { - continue; - } + foreach ($type->getObjectClassNames() as $className) { + if ($classReflection->is($className)) { + continue; + } + + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Trait %s requires using class to extend %s, but %s does not.', + $trait->getDisplayName(), + $type->describe(VerbosityLevel::typeOnly()), + $classReflection->getDisplayName(), + ), + ) + ->identifier('class.missingExtends') + ->build(); - $errors[] = RuleErrorBuilder::message( - sprintf( - 'Trait %s requires using class to extend %s, but %s does not.', - $trait->getDisplayName(), - $type->describe(VerbosityLevel::typeOnly()), - $classReflection->getDisplayName(), - ), - ) - ->identifier('class.missingExtends') - ->build(); + break; + } } } diff --git a/tests/PHPStan/Rules/Classes/RequireExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/RequireExtendsRuleTest.php index 0ca40d9fa4..03f3361bef 100644 --- a/tests/PHPStan/Rules/Classes/RequireExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/RequireExtendsRuleTest.php @@ -52,6 +52,22 @@ public function testRule(): void 'Trait IncompatibleRequireExtends\ValidPsalmTrait requires using class to extend IncompatibleRequireExtends\SomeClass, but class@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:163 does not.', 163, ], + [ + 'Interface IncompatibleRequireExtends\RequireNonExisstentUnionClassinterface requires implementing class to extend IncompatibleRequireExtends\NonExistentClass|IncompatibleRequireExtends\SomeClass, but IncompatibleRequireExtends\RequireNonExisstentUnionClassinterface@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:185 does not.', + 185, + ], + [ + 'Interface IncompatibleRequireExtends\RequireNonExisstentUnionClassinterface requires implementing class to extend IncompatibleRequireExtends\NonExistentClass|IncompatibleRequireExtends\SomeClass, but IncompatibleRequireExtends\SomeClass@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:187 does not.', + 187, + ], + [ + 'Trait IncompatibleRequireExtends\RequireNonExisstentUnionClassTrait requires using class to extend IncompatibleRequireExtends\NonExistentClass|IncompatibleRequireExtends\SomeClass, but class@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:194 does not.', + 194, + ], + [ + 'Trait IncompatibleRequireExtends\RequireNonExisstentUnionClassTrait requires using class to extend IncompatibleRequireExtends\NonExistentClass|IncompatibleRequireExtends\SomeClass, but IncompatibleRequireExtends\SomeClass@anonymous/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php:198 does not.', + 198, + ], ]; $this->analyse([__DIR__ . '/../PhpDoc/data/incompatible-require-extends.php'], $expectedErrors); diff --git a/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php b/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php index eb362e5b61..6b5af9e340 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php +++ b/tests/PHPStan/Rules/PhpDoc/data/incompatible-require-extends.php @@ -176,3 +176,25 @@ trait TooMuchExtends {} * @phpstan-require-extends SomeOtherClass */ interface TooMuchExtendsIface {} + +/** + * @phpstan-require-extends SomeClass|NonExistentClass + */ +interface RequireNonExisstentUnionClassinterface {} + +new class implements RequireNonExisstentUnionClassinterface {}; + +new class extends SomeClass implements RequireNonExisstentUnionClassinterface {}; + +/** + * @phpstan-require-extends SomeClass|NonExistentClass + */ +trait RequireNonExisstentUnionClassTrait {} + +new class { + use RequireNonExisstentUnionClassTrait; +}; + +new class extends SomeClass { + use RequireNonExisstentUnionClassTrait; +}; From 0bb0d3ccf98a469adec3a86f218c6df59ca725e4 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 02:52:18 +0900 Subject: [PATCH 16/22] fixup! Replace error-prone instanceof in Rules classes --- src/Rules/Comparison/ConstantLooseComparisonRule.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Rules/Comparison/ConstantLooseComparisonRule.php b/src/Rules/Comparison/ConstantLooseComparisonRule.php index 2371d4dd79..616d6b6165 100644 --- a/src/Rules/Comparison/ConstantLooseComparisonRule.php +++ b/src/Rules/Comparison/ConstantLooseComparisonRule.php @@ -8,6 +8,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; +use function in_array; use function sprintf; /** @@ -56,7 +57,7 @@ public function processNode(Node $node, Scope $scope): array return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; - if ($nodeType->getConstantScalarValues()[0] === false) { + if (in_array(false, $nodeType->getConstantScalarValues(), true)) { return [ $addTip(RuleErrorBuilder::message(sprintf( 'Loose comparison using %s between %s and %s will always evaluate to false.', From 36d6f05fe30e851a6db6bcc366274bc6e43bf2d9 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 02:58:15 +0900 Subject: [PATCH 17/22] update baseline --- phpstan-baseline.neon | 6 ------ 1 file changed, 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 849c285b6d..495bdbefb7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -411,12 +411,6 @@ parameters: count: 1 path: src/Reflection/SignatureMap/Php8SignatureMapProvider.php - - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantBooleanType is error\-prone and deprecated\. Use Type\:\:isTrue\(\) or Type\:\:isFalse\(\) instead\.$#' identifier: phpstanApi.instanceofType From eade6d315824e632d9ea71e66e0832a0e7d44c49 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 03:20:44 +0900 Subject: [PATCH 18/22] fix tests --- .../PhpDoc/RequireExtendsDefinitionClassRuleTest.php | 10 ++++++++++ .../PhpDoc/RequireExtendsDefinitionTraitRuleTest.php | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index 64e0b07dcd..c5e2120ccd 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -84,6 +84,16 @@ public function testRule(): void 'PHPDoc tag @phpstan-require-extends can only be used once.', 178, ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', + 183, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\NonExistentClass.', + 183, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], ]); } diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php index 746c3b9007..c4d6ff5be8 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -45,6 +45,16 @@ public function testRule(): void 'PHPDoc tag @phpstan-require-extends can only be used once.', 171, ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', + 192, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\NonExistentClass.', + 192, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], ]); } From 92bf49fdd8a3f37b5a9152cdec6025dc0e6fb0ec Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 03:39:32 +0900 Subject: [PATCH 19/22] sort --- src/Rules/PhpDoc/RequireExtendsCheck.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 57b4df3cf4..9f1a3bb0b4 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -13,6 +13,7 @@ use function array_map; use function array_merge; use function count; +use function sort; use function sprintf; use function strtolower; @@ -49,9 +50,11 @@ public function checkExtendsTags(Node $node, array $extendsTags): array continue; } + $classNames = $type->getObjectClassNames(); + sort($classNames); $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); - foreach ($type->getObjectClassNames() as $class) { + foreach ($classNames as $class) { $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; if ($referencedClassReflection === null) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains unknown class %s.', $class)) From a7a4e32288a1cfb2c75cb70432927ac22144da32 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 8 Mar 2025 03:41:08 +0900 Subject: [PATCH 20/22] fixup! fix tests --- .../Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php | 4 ++-- .../Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index c5e2120ccd..6a40619bea 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php @@ -85,12 +85,12 @@ public function testRule(): void 178, ], [ - 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\NonExistentClass.', 183, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], [ - 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\NonExistentClass.', + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', 183, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], diff --git a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php index c4d6ff5be8..121fc0df34 100644 --- a/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionTraitRuleTest.php @@ -46,12 +46,12 @@ public function testRule(): void 171, ], [ - 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\NonExistentClass.', 192, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], [ - 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\NonExistentClass.', + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', 192, 'Learn more at https://phpstan.org/user-guide/discovering-symbols', ], From 0dd9a8517e0e422a33f66224127224451a410b0b Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 12 Mar 2025 01:46:15 +0900 Subject: [PATCH 21/22] Fix Co-authored-by: Vincent Langlet --- src/Rules/Comparison/ConstantLooseComparisonRule.php | 3 +-- src/Rules/Comparison/ImpossibleCheckTypeHelper.php | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Rules/Comparison/ConstantLooseComparisonRule.php b/src/Rules/Comparison/ConstantLooseComparisonRule.php index 616d6b6165..b7d3fe977d 100644 --- a/src/Rules/Comparison/ConstantLooseComparisonRule.php +++ b/src/Rules/Comparison/ConstantLooseComparisonRule.php @@ -8,7 +8,6 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\VerbosityLevel; -use function in_array; use function sprintf; /** @@ -57,7 +56,7 @@ public function processNode(Node $node, Scope $scope): array return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); }; - if (in_array(false, $nodeType->getConstantScalarValues(), true)) { + if ($nodeType->isFalse()->yes()) { return [ $addTip(RuleErrorBuilder::message(sprintf( 'Loose comparison using %s between %s and %s will always evaluate to false.', diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 7448b73c32..6cbe4b1b59 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -35,7 +35,6 @@ use function count; use function implode; use function in_array; -use function is_bool; use function is_string; use function sprintf; use function strtolower; @@ -69,12 +68,13 @@ public function findSpecifiedType( $functionName = strtolower((string) $node->name); if ($functionName === 'assert' && $argsCount >= 1) { $arg = $node->getArgs()[0]->value; - $assertValues = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->getConstantScalarValues(); - if (count($assertValues) !== 1 || !is_bool($assertValues[0])) { + $assertValue = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->toBoolean(); + if (! $assertValue->isTrue()->yes() && ! $assertValue->isFalse()->yes()) { return null; } - return $assertValues[0]; + /** @var bool */ + return $assertValue->getConstantScalarValues()[0]; } if (in_array($functionName, [ 'class_exists', From 5d96286b92bd4530ce24192432b2dc5e97352c13 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 12 Mar 2025 01:51:43 +0900 Subject: [PATCH 22/22] Avoid calling methods twice with variables Co-authored-by: Markus Staab --- src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php | 5 +++-- src/Rules/PhpDoc/RequireExtendsCheck.php | 4 ++-- src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php | 5 +++-- src/Rules/Variables/CompactVariablesRule.php | 5 +++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 23b8b5df76..7e54a2cb15 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -78,12 +78,13 @@ public function processNode(Node $node, Scope $scope): array } } - if (count($keyType->getConstantScalarValues()) === 0) { + $keyValues = $keyType->getConstantScalarValues(); + if (count($keyValues) === 0) { $autoGeneratedIndex = false; continue; } - foreach ($keyType->getConstantScalarValues() as $value) { + foreach ($keyValues as $value) { $printedValue = $key !== null ? $this->exprPrinter->printExpr($key) : $value; diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 9f1a3bb0b4..7decad9c63 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -43,14 +43,14 @@ public function checkExtendsTags(Node $node, array $extendsTags): array foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (count($type->getObjectClassNames()) === 0) { + $classNames = $type->getObjectClassNames(); + if (count($classNames) === 0) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-extends contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) ->identifier('requireExtends.nonObject') ->build(); continue; } - $classNames = $type->getObjectClassNames(); sort($classNames); $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); diff --git a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 184b569259..e974a7c055 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -51,7 +51,8 @@ public function processNode(Node $node, Scope $scope): array $errors = []; foreach ($implementsTags as $implementsTag) { $type = $implementsTag->getType(); - if (count($type->getObjectClassNames()) === 0) { + $classNames = $type->getObjectClassNames(); + if (count($classNames) === 0) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains non-object type %s.', $type->describe(VerbosityLevel::typeOnly()))) ->identifier('requireImplements.nonObject') ->build(); @@ -60,7 +61,7 @@ public function processNode(Node $node, Scope $scope): array $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); - foreach ($type->getObjectClassNames() as $class) { + foreach ($classNames as $class) { $referencedClassReflection = $referencedClassReflectionsMap[$class] ?? null; if ($referencedClassReflection === null) { $errors[] = RuleErrorBuilder::message(sprintf('PHPDoc tag @phpstan-require-implements contains unknown class %s.', $class)) diff --git a/src/Rules/Variables/CompactVariablesRule.php b/src/Rules/Variables/CompactVariablesRule.php index c75f7f6684..8555675bd3 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -70,8 +70,9 @@ public function processNode(Node $node, Scope $scope): array */ private function findConstantStrings(Type $type): array { - if (count($type->getConstantStrings()) > 0) { - return $type->getConstantStrings(); + $constantStrings = $type->getConstantStrings(); + if (count($constantStrings) > 0) { + return $constantStrings; } $result = [];