diff --git a/conf/config.neon b/conf/config.neon index 4097e0d85d..081dc1a4a6 100644 --- a/conf/config.neon +++ b/conf/config.neon @@ -913,6 +913,8 @@ services: class: PHPStan\Rules\Classes\LocalTypeAliasesCheck arguments: globalTypeAliases: %typeAliases% + checkMissingTypehints: %checkMissingTypehints% + absentTypeChecks: %featureToggles.absentTypeChecks% - class: PHPStan\Rules\Comparison\ConstantConditionRuleHelper diff --git a/src/Rules/Classes/LocalTypeAliasesCheck.php b/src/Rules/Classes/LocalTypeAliasesCheck.php index a8d1b1e476..74c7f5507b 100644 --- a/src/Rules/Classes/LocalTypeAliasesCheck.php +++ b/src/Rules/Classes/LocalTypeAliasesCheck.php @@ -8,12 +8,14 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\IdentifierRuleError; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\CircularTypeAliasErrorType; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\TemplateType; use PHPStan\Type\Type; use PHPStan\Type\TypeTraverser; +use PHPStan\Type\VerbosityLevel; use function array_key_exists; use function in_array; use function sprintf; @@ -28,6 +30,9 @@ public function __construct( private array $globalTypeAliases, private ReflectionProvider $reflectionProvider, private TypeNodeResolver $typeNodeResolver, + private MissingTypehintCheck $missingTypehintCheck, + private bool $checkMissingTypehints, + private bool $absentTypeChecks, ) { } @@ -169,6 +174,47 @@ public function check(ClassReflection $reflection): array return $traverse($type); }); + + if ($this->absentTypeChecks && !$foundError) { + if ($this->checkMissingTypehints) { + foreach ($this->missingTypehintCheck->getIterableTypesWithMissingValueTypehint($resolvedType) as $iterableType) { + $iterableTypeDescription = $iterableType->describe(VerbosityLevel::typeOnly()); + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with no value type specified in iterable type %s.', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $iterableTypeDescription, + )) + ->tip(MissingTypehintCheck::MISSING_ITERABLE_VALUE_TYPE_TIP) + ->identifier('missingType.iterableValue') + ->build(); + } + + foreach ($this->missingTypehintCheck->getNonGenericObjectTypesWithGenericClass($resolvedType) as [$name, $genericTypeNames]) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with generic %s but does not specify its types: %s', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $name, + implode(', ', $genericTypeNames), + )) + ->identifier('missingType.generics') + ->build(); + } + + foreach ($this->missingTypehintCheck->getCallablesWithMissingSignature($resolvedType) as $callableType) { + $errors[] = RuleErrorBuilder::message(sprintf( + '%s %s has type alias %s with no signature specified for %s.', + $reflection->getClassTypeDescription(), + $reflection->getDisplayName(), + $aliasName, + $callableType->describe(VerbosityLevel::typeOnly()), + ))->identifier('missingType.callable')->build(); + } + } + } } return $errors; diff --git a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php index e55f429317..b3b8689b84 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeAliasesRuleTest.php @@ -3,6 +3,7 @@ namespace PHPStan\Rules\Classes; use PHPStan\PhpDoc\TypeNodeResolver; +use PHPStan\Rules\MissingTypehintCheck; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use const PHP_VERSION_ID; @@ -20,6 +21,9 @@ protected function getRule(): Rule ['GlobalTypeAlias' => 'int|string'], $this->createReflectionProvider(), self::getContainer()->getByType(TypeNodeResolver::class), + new MissingTypehintCheck(true, true, true, true, []), + true, + true, ), ); } @@ -91,6 +95,19 @@ public function testRule(): void 'Invalid type definition detected in type alias InvalidTypeAlias.', 62, ], + [ + 'Class LocalTypeAliases\MissingTypehints has type alias NoIterableValue with no value type specified in iterable type array.', + 77, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], + [ + 'Class LocalTypeAliases\MissingTypehints has type alias NoGenerics with generic class LocalTypeAliases\Generic but does not specify its types: T', + 77, + ], + [ + 'Class LocalTypeAliases\MissingTypehints has type alias NoCallable with no signature specified for callable.', + 77, + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php index 49d73e9c5c..ba18681762 100644 --- a/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/LocalTypeTraitAliasesRuleTest.php @@ -91,6 +91,11 @@ public function testRule(): void 'Invalid type definition detected in type alias InvalidTypeAlias.', 62, ], + [ + 'Trait LocalTypeTraitAliases\MissingType has type alias NoIterablueValue with no value type specified in iterable type array.', + 69, + 'See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type', + ], ]); } diff --git a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php index a70cac8f8e..9dde0cdd29 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-aliases.php @@ -5,7 +5,7 @@ class ExistingClassAlias {} /** - * @phpstan-type ExportedTypeAlias \Countable&\Traversable + * @phpstan-type ExportedTypeAlias \Countable&\Traversable */ class Foo { @@ -68,3 +68,13 @@ class InvalidTypeDefinitionToIgnoreBecauseItsAParseErrorAlreadyReportedInInvalid { } + +/** + * @phpstan-type NoIterableValue = array + * @phpstan-type NoGenerics = Generic + * @phpstan-type NoCallable = array + */ +class MissingTypehints +{ + +} diff --git a/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php b/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php index d8c16b3e0e..6aaa554d52 100644 --- a/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php +++ b/tests/PHPStan/Rules/Classes/data/local-type-trait-aliases.php @@ -5,7 +5,7 @@ class ExistingClassAlias {} /** - * @phpstan-type ExportedTypeAlias \Countable&\Traversable + * @phpstan-type ExportedTypeAlias \Countable&\Traversable */ trait Foo { @@ -62,3 +62,11 @@ trait Generic trait Invalid { } + +/** + * @phpstan-type NoIterablueValue = array + */ +trait MissingType +{ + +}