diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d252aaef59..495bdbefb7 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -411,30 +411,12 @@ 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 - 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 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 +441,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 +468,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 +621,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 +639,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\: diff --git a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php index 7ad74631b9..282c2189d2 100644 --- a/src/Rules/Api/NodeConnectingVisitorAttributesRule.php +++ b/src/Rules/Api/NodeConnectingVisitorAttributesRule.php @@ -7,7 +7,6 @@ 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 in_array; @@ -41,37 +40,40 @@ public function processNode(Node $node, Scope $scope): array if (!isset($args[0])) { return []; } + + $messages = []; $argType = $scope->getType($args[0]->value); - if (!$argType instanceof ConstantStringType) { - return []; - } - if (!in_array($argType->getValue(), ['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.', $argType->getValue())) + 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/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php index 46b5e712e1..7e54a2cb15 100644 --- a/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php +++ b/src/Rules/Arrays/DuplicateKeysInLiteralArraysRule.php @@ -9,10 +9,10 @@ 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; +use function is_int; use function max; use function sprintf; use function var_export; @@ -70,38 +70,38 @@ 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]); } } - if (!$keyType instanceof ConstantScalarType) { + $keyValues = $keyType->getConstantScalarValues(); + if (count($keyValues) === 0) { $autoGeneratedIndex = false; continue; } - $value = $keyType->getValue(); - $printedValue = $key !== null - ? $this->exprPrinter->printExpr($key) - : $value; + foreach ($keyValues 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/src/Rules/Classes/RequireExtendsRule.php b/src/Rules/Classes/RequireExtendsRule.php index 036cf3aa85..88c0b88ccd 100644 --- a/src/Rules/Classes/RequireExtendsRule.php +++ b/src/Rules/Classes/RequireExtendsRule.php @@ -7,7 +7,6 @@ use PHPStan\Node\InClassNode; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; use function sprintf; @@ -35,24 +34,24 @@ public function processNode(Node $node, Scope $scope): array $extendsTags = $interface->getRequireExtendsTags(); foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { - continue; - } + foreach ($type->getObjectClassNames() as $className) { + if ($classReflection->is($className)) { + continue; + } - if ($classReflection->is($type->getClassName())) { - 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; + } } } @@ -60,24 +59,24 @@ public function processNode(Node $node, Scope $scope): array $extendsTags = $trait->getRequireExtendsTags(); foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { - continue; - } + foreach ($type->getObjectClassNames() as $className) { + if ($classReflection->is($className)) { + continue; + } - if ($classReflection->is($type->getClassName())) { - 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/src/Rules/Comparison/ConstantLooseComparisonRule.php b/src/Rules/Comparison/ConstantLooseComparisonRule.php index 09961335e7..b7d3fe977d 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->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 48eea97d6f..6cbe4b1b59 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -69,11 +69,12 @@ public function findSpecifiedType( if ($functionName === 'assert' && $argsCount >= 1) { $arg = $node->getArgs()[0]->value; $assertValue = ($this->treatPhpDocTypesAsCertain ? $scope->getType($arg) : $scope->getNativeType($arg))->toBoolean(); - if (!$assertValue instanceof ConstantBooleanType) { + if (! $assertValue->isTrue()->yes() && ! $assertValue->isFalse()->yes()) { return null; } - return $assertValue->getValue(); + /** @var bool */ + return $assertValue->getConstantScalarValues()[0]; } if (in_array($functionName, [ 'class_exists', diff --git a/src/Rules/PhpDoc/RequireExtendsCheck.php b/src/Rules/PhpDoc/RequireExtendsCheck.php index 14f6d43647..7decad9c63 100644 --- a/src/Rules/PhpDoc/RequireExtendsCheck.php +++ b/src/Rules/PhpDoc/RequireExtendsCheck.php @@ -8,10 +8,12 @@ use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; +use function array_column; +use function array_map; use function array_merge; use function count; +use function sort; use function sprintf; use function strtolower; @@ -41,39 +43,48 @@ public function checkExtendsTags(Node $node, array $extendsTags): array foreach ($extendsTags as $extendsTag) { $type = $extendsTag->getType(); - if (!$type instanceof ObjectType) { + $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; } - $class = $type->getClassName(); - $referencedClassReflection = $type->getClassReflection(); + sort($classNames); + $referencedClassReflections = array_map(static fn ($reflection) => [$reflection, $reflection->getName()], $type->getObjectClassReflections()); + $referencedClassReflectionsMap = array_column($referencedClassReflections, 0, 1); + 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)) + ->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/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php index 076ae342a0..e974a7c055 100644 --- a/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php +++ b/src/Rules/PhpDoc/RequireImplementsDefinitionTraitRule.php @@ -9,9 +9,11 @@ use PHPStan\Rules\ClassNameNodePair; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; +use function array_column; +use function array_map; use function array_merge; +use function count; use function sprintf; use function strtolower; @@ -49,34 +51,39 @@ 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(); - 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 ($classNames 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), - ); } } 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..68c1549091 100644 --- a/src/Rules/UnusedFunctionParametersCheck.php +++ b/src/Rules/UnusedFunctionParametersCheck.php @@ -7,7 +7,6 @@ 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; @@ -92,11 +91,9 @@ private function getUsedVariables(Scope $scope, $node): array ) { foreach ($node->getArgs() as $arg) { $argType = $scope->getType($arg->value); - if (!($argType instanceof ConstantStringType)) { - continue; + foreach ($argType->getConstantStrings() as $constantStringType) { + $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..8555675bd3 100644 --- a/src/Rules/Variables/CompactVariablesRule.php +++ b/src/Rules/Variables/CompactVariablesRule.php @@ -6,10 +6,10 @@ 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; +use function count; use function sprintf; use function strtolower; @@ -66,25 +66,24 @@ public function processNode(Node $node, Scope $scope): array } /** - * @return array + * @return list */ private function findConstantStrings(Type $type): array { - if ($type instanceof ConstantStringType) { - return [$type]; + $constantStrings = $type->getConstantStrings(); + if (count($constantStrings) > 0) { + return $constantStrings; } - if ($type instanceof ConstantArrayType) { - $result = []; - foreach ($type->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; } } 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 []; } 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', + ]; + } + } 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/RequireExtendsDefinitionClassRuleTest.php b/tests/PHPStan/Rules/PhpDoc/RequireExtendsDefinitionClassRuleTest.php index 5a24e75502..6a40619bea 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,13 +76,24 @@ 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.', 178, ], + [ + '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\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 746c3b9007..121fc0df34 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\NonExistentClass.', + 192, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], + [ + 'PHPDoc tag @phpstan-require-extends contains unknown class IncompatibleRequireExtends\SomeClass.', + 192, + 'Learn more at https://phpstan.org/user-guide/discovering-symbols', + ], ]); } 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; +};