Skip to content

Commit

Permalink
Nullable union template type bound is actually supported
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jan 30, 2022
1 parent e8509b7 commit 1d8b2eb
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 25 deletions.
43 changes: 18 additions & 25 deletions src/Rules/Generics/TemplateTypeCheck.php
Expand Up @@ -22,9 +22,7 @@
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeAliasResolver;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;
use function array_map;
Expand Down Expand Up @@ -95,29 +93,24 @@ public function check(
$messages = array_merge($messages, $this->classCaseSensitivityCheck->checkClassNames($classNameNodePairs));
}

TypeTraverser::map($templateTag->getBound(), static function (Type $type, callable $traverse) use (&$messages, $notSupportedBoundMessage, $templateTagName): Type {
$boundClass = get_class($type);
if (
$boundClass === MixedType::class
|| $boundClass === ConstantArrayType::class
|| $boundClass === ArrayType::class
|| $boundClass === StringType::class
|| $boundClass === IntegerType::class
|| $boundClass === FloatType::class
|| $boundClass === BooleanType::class
|| $boundClass === ObjectWithoutClassType::class
|| $boundClass === ObjectType::class
|| $boundClass === GenericObjectType::class
|| $type instanceof UnionType
|| $type instanceof TemplateType
) {
return $traverse($type);
}

$messages[] = RuleErrorBuilder::message(sprintf($notSupportedBoundMessage, $templateTagName, $type->describe(VerbosityLevel::typeOnly())))->build();

return $type;
});
$boundType = $templateTag->getBound();
$boundTypeClass = get_class($boundType);
if (
$boundTypeClass !== MixedType::class
&& $boundTypeClass !== ConstantArrayType::class
&& $boundTypeClass !== ArrayType::class
&& $boundTypeClass !== StringType::class
&& $boundTypeClass !== IntegerType::class
&& $boundTypeClass !== FloatType::class
&& $boundTypeClass !== BooleanType::class
&& $boundTypeClass !== ObjectWithoutClassType::class
&& $boundTypeClass !== ObjectType::class
&& $boundTypeClass !== GenericObjectType::class
&& !$boundType instanceof UnionType
&& !$boundType instanceof TemplateType
) {
$messages[] = RuleErrorBuilder::message(sprintf($notSupportedBoundMessage, $templateTagName, $boundType->describe(VerbosityLevel::typeOnly())))->build();
}

$escapedTemplateTagName = SprintfHelper::escapeFormatString($templateTagName);
$genericObjectErrors = $this->genericObjectTypeCheck->check(
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -629,6 +629,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5817.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/array-column.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/template-null-bound.php');
}

/**
Expand Down
26 changes: 26 additions & 0 deletions tests/PHPStan/Analyser/data/template-null-bound.php
@@ -0,0 +1,26 @@
<?php

namespace TemplateNullBound;

use function PHPStan\Testing\assertType;

class Foo
{

/**
* @template T of ?int
* @param T $p
* @return T
*/
public function doFoo(?int $p): ?int
{
return $p;
}

}

function (Foo $f, ?int $i): void {
assertType('null', $f->doFoo(null));
assertType('int', $f->doFoo(1));
assertType('int|null', $f->doFoo($i));
};
4 changes: 4 additions & 0 deletions tests/PHPStan/Rules/Generics/FunctionTemplateTypeRuleTest.php
Expand Up @@ -43,6 +43,10 @@ public function testRule(): void
'PHPDoc tag @template T for function FunctionTemplateType\resourceBound() with bound type resource is not supported.',
50,
],
[
'PHPDoc tag @template T for function FunctionTemplateType\nullNotSupported() with bound type null is not supported.',
68,
],
]);
}

Expand Down
12 changes: 12 additions & 0 deletions tests/PHPStan/Rules/Generics/data/function-template.php
Expand Up @@ -63,3 +63,15 @@ function nakano()
{

}

/** @template T of null */
function nullNotSupported()
{

}

/** @template T of ?int */
function nullableUnionSupported()
{

}

0 comments on commit 1d8b2eb

Please sign in to comment.