Skip to content

Commit 932b3b4

Browse files
committed
check default type validity
1 parent e0396cf commit 932b3b4

19 files changed

+182
-1
lines changed

src/Rules/FunctionCallParametersCheck.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ static function (Type $type, callable $traverse) use (&$returnTemplateTypes): Ty
444444
$parameterTemplateTypes = [];
445445
foreach ($originalParametersAcceptor->getParameters() as $parameter) {
446446
TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$parameterTemplateTypes): Type {
447-
if ($type instanceof TemplateType) {
447+
if ($type instanceof TemplateType && $type->getDefault() === null) {
448448
$parameterTemplateTypes[$type->getName()] = true;
449449
return $type;
450450
}

src/Rules/Generics/ClassTemplateTypeRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public function processNode(Node $node, Scope $scope): array
4949
sprintf('PHPDoc tag @template for %s cannot have existing type alias %%s as its name.', $displayName),
5050
sprintf('PHPDoc tag @template %%s for %s has invalid bound type %%s.', $displayName),
5151
sprintf('PHPDoc tag @template %%s for %s with bound type %%s is not supported.', $displayName),
52+
sprintf('PHPDoc tag @template %%s for %s has invalid default type %%s.', $displayName),
53+
sprintf('Default type %%s in PHPDoc tag @template %%s for %s is not subtype of bound type %%s.', $displayName),
5254
);
5355
}
5456

src/Rules/Generics/FunctionTemplateTypeRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public function processNode(Node $node, Scope $scope): array
6060
sprintf('PHPDoc tag @template for function %s() cannot have existing type alias %%s as its name.', $escapedFunctionName),
6161
sprintf('PHPDoc tag @template %%s for function %s() has invalid bound type %%s.', $escapedFunctionName),
6262
sprintf('PHPDoc tag @template %%s for function %s() with bound type %%s is not supported.', $escapedFunctionName),
63+
sprintf('PHPDoc tag @template %%s for function %s() has invalid default type %%s.', $escapedFunctionName),
64+
sprintf('Default type %%s in PHPDoc tag @template %%s for function %s() is not subtype of bound type %%s.', $escapedFunctionName),
6365
);
6466
}
6567

src/Rules/Generics/InterfaceTemplateTypeRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public function processNode(Node $node, Scope $scope): array
4646
sprintf('PHPDoc tag @template for interface %s cannot have existing type alias %%s as its name.', $escapadInterfaceName),
4747
sprintf('PHPDoc tag @template %%s for interface %s has invalid bound type %%s.', $escapadInterfaceName),
4848
sprintf('PHPDoc tag @template %%s for interface %s with bound type %%s is not supported.', $escapadInterfaceName),
49+
sprintf('PHPDoc tag @template %%s for interface %s has invalid default type %%s.', $escapadInterfaceName),
50+
sprintf('Default type %%s in PHPDoc tag @template %%s for interface %s is not subtype of bound type %%s.', $escapadInterfaceName),
4951
);
5052
}
5153

src/Rules/Generics/MethodTagTemplateTypeCheck.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public function check(
6161
sprintf('PHPDoc tag @method template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName),
6262
sprintf('PHPDoc tag @method template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName),
6363
sprintf('PHPDoc tag @method template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName),
64+
sprintf('PHPDoc tag @method template %%s for method %s::%s() has invalid default type %%s', $escapedClassName, $escapedMethodName),
65+
sprintf('Default type %%s in PHPDoc tag @method template %%s for method %s::%s() is not subtype of bound type %%s', $escapedClassName, $escapedMethodName),
6466
));
6567

6668
foreach (array_keys($methodTemplateTags) as $name) {

src/Rules/Generics/MethodTemplateTypeRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ public function processNode(Node $node, Scope $scope): array
6666
sprintf('PHPDoc tag @template for method %s::%s() cannot have existing type alias %%s as its name.', $escapedClassName, $escapedMethodName),
6767
sprintf('PHPDoc tag @template %%s for method %s::%s() has invalid bound type %%s.', $escapedClassName, $escapedMethodName),
6868
sprintf('PHPDoc tag @template %%s for method %s::%s() with bound type %%s is not supported.', $escapedClassName, $escapedMethodName),
69+
sprintf('PHPDoc tag @template %%s for method %s::%s() has invalid default type %%s.', $escapedClassName, $escapedMethodName),
70+
sprintf('Default type %%s in PHPDoc tag @template %%s for method %s::%s() is not subtype of bound type %%s.', $escapedClassName, $escapedMethodName),
6971
);
7072

7173
$classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes();

src/Rules/Generics/TemplateTypeCheck.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ public function check(
6262
string $sameTemplateTypeNameAsTypeMessage,
6363
string $invalidBoundTypeMessage,
6464
string $notSupportedBoundMessage,
65+
string $invalidDefaultTypeMessage,
66+
string $defaultNotSubtypeOfBoundMessage,
6567
): array
6668
{
6769
$messages = [];
@@ -141,6 +143,46 @@ public function check(
141143
foreach ($genericObjectErrors as $genericObjectError) {
142144
$messages[] = $genericObjectError;
143145
}
146+
147+
$defaultType = $templateTag->getDefault();
148+
if ($defaultType === null) {
149+
continue;
150+
}
151+
152+
foreach ($defaultType->getReferencedClasses() as $referencedClass) {
153+
if (
154+
$this->reflectionProvider->hasClass($referencedClass)
155+
&& !$this->reflectionProvider->getClass($referencedClass)->isTrait()
156+
) {
157+
continue;
158+
}
159+
160+
$messages[] = RuleErrorBuilder::message(sprintf($invalidDefaultTypeMessage, $templateTagName, $referencedClass))
161+
->identifier('generics.invalidTemplateDefault')
162+
->build();
163+
}
164+
165+
$classNameNodePairs = array_map(static fn (string $referencedClass): ClassNameNodePair => new ClassNameNodePair($referencedClass, $node), $defaultType->getReferencedClasses());
166+
$messages = array_merge($messages, $this->classCheck->checkClassNames($classNameNodePairs, $this->checkClassCaseSensitivity));
167+
168+
$genericDefaultErrors = $this->genericObjectTypeCheck->check(
169+
$defaultType,
170+
sprintf('PHPDoc tag @template %s default contains generic type %%s but class %%s is not generic.', $escapedTemplateTagName),
171+
sprintf('PHPDoc tag @template %s default has type %%s which does not specify all template types of class %%s: %%s', $escapedTemplateTagName),
172+
sprintf('PHPDoc tag @template %s default has type %%s which specifies %%d template types, but class %%s supports only %%d: %%s', $escapedTemplateTagName),
173+
sprintf('Type %%s in generic type %%s in PHPDoc tag @template %s default is not subtype of template type %%s of class %%s.', $escapedTemplateTagName),
174+
sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @template %s default is in conflict with %%s template type %%s of %%s %%s.', $escapedTemplateTagName),
175+
sprintf('Call-site variance of %%s in generic type %%s in PHPDoc tag @template %s default is redundant, template type %%s of %%s %%s has the same variance.', $escapedTemplateTagName),
176+
);
177+
foreach ($genericDefaultErrors as $genericDefaultError) {
178+
$messages[] = $genericDefaultError;
179+
}
180+
181+
if ($boundType->accepts($defaultType, $scope->isDeclareStrictTypes())->no()) {
182+
$messages[] = RuleErrorBuilder::message(sprintf($defaultNotSubtypeOfBoundMessage, $defaultType->describe(VerbosityLevel::typeOnly()), $templateTagName, $boundType->describe(VerbosityLevel::typeOnly())))
183+
->identifier('generics.templateDefaultOutOfBounds')
184+
->build();
185+
}
144186
}
145187

146188
return $messages;

src/Rules/Generics/TraitTemplateTypeRule.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public function processNode(Node $node, Scope $scope): array
6060
sprintf('PHPDoc tag @template for trait %s cannot have existing type alias %%s as its name.', $escapedTraitName),
6161
sprintf('PHPDoc tag @template %%s for trait %s has invalid bound type %%s.', $escapedTraitName),
6262
sprintf('PHPDoc tag @template %%s for trait %s with bound type %%s is not supported.', $escapedTraitName),
63+
sprintf('PHPDoc tag @template %%s for trait %s has invalid default type %%s.', $escapedTraitName),
64+
sprintf('Default type %%s in PHPDoc tag @template %%s for trait %s is not subtype of bound type %%s.', $escapedTraitName),
6365
);
6466
}
6567

src/Rules/PhpDoc/GenericCallableRuleHelper.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public function check(
6060
sprintf('PHPDoc tag %s template of %s cannot have existing type alias %%s as its name.', $location, $typeDescription),
6161
sprintf('PHPDoc tag %s template %%s of %s has invalid bound type %%s.', $location, $typeDescription),
6262
sprintf('PHPDoc tag %s template %%s of %s with bound type %%s is not supported.', $location, $typeDescription),
63+
sprintf('PHPDoc tag %s template %%s of %s has invalid default type %%s.', $location, $typeDescription),
64+
sprintf('Default type %%s in PHPDoc tag %s template %%s of %s is not subtype of bound type %%s.', $location, $typeDescription),
6365
);
6466

6567
$templateTags = $type->getTemplateTags();

tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ public function testRule(): void
8686
'Call-site variance of contravariant int in generic type ClassTemplateType\Consecteur<contravariant int> in PHPDoc tag @template W is in conflict with covariant template type T of class ClassTemplateType\Consecteur.',
8787
113,
8888
],
89+
[
90+
'PHPDoc tag @template T for class ClassTemplateType\Elit has invalid default type ClassTemplateType\Zazzzu.',
91+
121,
92+
],
93+
[
94+
'Default type bool in PHPDoc tag @template T for class ClassTemplateType\Venenatis is not subtype of bound type object.',
95+
129,
96+
],
8997
]);
9098
}
9199

0 commit comments

Comments
 (0)