Skip to content

Commit

Permalink
Support int and string as template type bound
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 25, 2021
1 parent 756af18 commit 768bfab
Show file tree
Hide file tree
Showing 19 changed files with 307 additions and 24 deletions.
4 changes: 4 additions & 0 deletions src/Rules/Generics/TemplateTypeCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
use PHPStan\Rules\ClassNameNodePair;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Generic\TemplateTypeScope;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\UnionType;
Expand Down Expand Up @@ -107,6 +109,8 @@ public function check(
$boundClass = get_class($type);
if (
$boundClass === MixedType::class
|| $boundClass === StringType::class
|| $boundClass === IntegerType::class
|| $boundClass === ObjectWithoutClassType::class
|| $boundClass === ObjectType::class
|| $type instanceof UnionType
Expand Down
58 changes: 58 additions & 0 deletions src/Type/Generic/TemplateIntegerType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Generic;

use PHPStan\Type\IntegerType;
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
use PHPStan\Type\Type;

final class TemplateIntegerType extends IntegerType implements TemplateType
{

use TemplateTypeTrait;
use UndecidedComparisonCompoundTypeTrait;

public function __construct(
TemplateTypeScope $scope,
TemplateTypeStrategy $templateTypeStrategy,
TemplateTypeVariance $templateTypeVariance,
string $name
)
{
$this->scope = $scope;
$this->strategy = $templateTypeStrategy;
$this->variance = $templateTypeVariance;
$this->name = $name;
$this->bound = new IntegerType();
}

public function toArgument(): TemplateType
{
return new self(
$this->scope,
new TemplateTypeArgumentStrategy(),
$this->variance,
$this->name
);
}

protected function shouldGeneralizeInferredType(): bool
{
return false;
}

/**
* @param mixed[] $properties
* @return Type
*/
public static function __set_state(array $properties): Type
{
return new self(
$properties['scope'],
$properties['strategy'],
$properties['variance'],
$properties['name']
);
}

}
58 changes: 58 additions & 0 deletions src/Type/Generic/TemplateStringType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Generic;

use PHPStan\Type\StringType;
use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
use PHPStan\Type\Type;

final class TemplateStringType extends StringType implements TemplateType
{

use TemplateTypeTrait;
use UndecidedComparisonCompoundTypeTrait;

public function __construct(
TemplateTypeScope $scope,
TemplateTypeStrategy $templateTypeStrategy,
TemplateTypeVariance $templateTypeVariance,
string $name
)
{
$this->scope = $scope;
$this->strategy = $templateTypeStrategy;
$this->variance = $templateTypeVariance;
$this->name = $name;
$this->bound = new StringType();
}

public function toArgument(): TemplateType
{
return new self(
$this->scope,
new TemplateTypeArgumentStrategy(),
$this->variance,
$this->name
);
}

protected function shouldGeneralizeInferredType(): bool
{
return false;
}

/**
* @param mixed[] $properties
* @return Type
*/
public static function __set_state(array $properties): Type
{
return new self(
$properties['scope'],
$properties['strategy'],
$properties['variance'],
$properties['name']
);
}

}
17 changes: 14 additions & 3 deletions src/Type/Generic/TemplateTypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@

use PHPStan\PhpDoc\Tag\TemplateTag;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;

final class TemplateTypeFactory
{

public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance): Type
public static function create(TemplateTypeScope $scope, string $name, ?Type $bound, TemplateTypeVariance $variance): TemplateType
{
$strategy = new TemplateTypeParameterStrategy();

Expand All @@ -22,13 +25,21 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou
}

$boundClass = get_class($bound);
if ($boundClass === ObjectType::class) {
if ($bound instanceof TypeWithClassName && $boundClass === ObjectType::class) {
return new TemplateObjectType($scope, $strategy, $variance, $name, $bound->getClassName());
}
if ($boundClass === ObjectWithoutClassType::class) {
return new TemplateObjectWithoutClassType($scope, $strategy, $variance, $name);
}

if ($boundClass === StringType::class) {
return new TemplateStringType($scope, $strategy, $variance, $name);
}

if ($boundClass === IntegerType::class) {
return new TemplateIntegerType($scope, $strategy, $variance, $name);
}

if ($boundClass === MixedType::class) {
return new TemplateMixedType($scope, $strategy, $variance, $name);
}
Expand All @@ -46,7 +57,7 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou
return new TemplateMixedType($scope, $strategy, $variance, $name);
}

public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): Type
public static function fromTemplateTag(TemplateTypeScope $scope, TemplateTag $tag): TemplateType
{
return self::create($scope, $tag->getName(), $tag->getBound(), $tag->getVariance());
}
Expand Down
7 changes: 6 additions & 1 deletion src/Type/Generic/TemplateTypeTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap

if ($this->getBound()->isSuperTypeOf($receivedType)->yes()) {
return new TemplateTypeMap([
$this->name => TemplateTypeHelper::generalizeType($receivedType),
$this->name => $this->shouldGeneralizeInferredType() ? TemplateTypeHelper::generalizeType($receivedType) : $receivedType,
]);
}

Expand All @@ -172,4 +172,9 @@ public function getVariance(): TemplateTypeVariance
return $this->variance;
}

protected function shouldGeneralizeInferredType(): bool
{
return true;
}

}
8 changes: 5 additions & 3 deletions tests/PHPStan/Generics/TemplateTypeFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace PHPStan\Generics;

use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeFactory;
use PHPStan\Type\Generic\TemplateTypeScope;
use PHPStan\Type\Generic\TemplateTypeVariance;
Expand Down Expand Up @@ -36,7 +35,11 @@ public function dataCreate(): array
],
[
new StringType(),
new MixedType(),
new StringType(),
],
[
new IntegerType(),
new IntegerType(),
],
[
new ErrorType(),
Expand Down Expand Up @@ -77,7 +80,6 @@ public function testCreate(?Type $bound, Type $expectedBound): void
TemplateTypeVariance::createInvariant()
);

$this->assertInstanceOf(TemplateType::class, $templateType);
$this->assertTrue(
$expectedBound->equals($templateType->getBound()),
sprintf('%s -> equals(%s)', $expectedBound->describe(VerbosityLevel::precise()), $templateType->getBound()->describe(VerbosityLevel::precise()))
Expand Down
4 changes: 2 additions & 2 deletions tests/PHPStan/Rules/Generics/ClassTemplateTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function testRule(): void
16,
],
[
'PHPDoc tag @template T for class ClassTemplateType\Baz with bound type int is not supported.',
'PHPDoc tag @template T for class ClassTemplateType\Baz with bound type float is not supported.',
24,
],
[
Expand All @@ -53,7 +53,7 @@ public function testRule(): void
50,
],
[
'PHPDoc tag @template T for anonymous class with bound type int is not supported.',
'PHPDoc tag @template T for anonymous class with bound type float is not supported.',
55,
],
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function testRule(): void
16,
],
[
'PHPDoc tag @template T for function FunctionTemplateType\baz() with bound type int is not supported.',
'PHPDoc tag @template T for function FunctionTemplateType\baz() with bound type float is not supported.',
24,
],
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function testRule(): void
16,
],
[
'PHPDoc tag @template T for interface InterfaceTemplateType\Baz with bound type int is not supported.',
'PHPDoc tag @template T for interface InterfaceTemplateType\Baz with bound type float is not supported.',
24,
],
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function testRule(): void
37,
],
[
'PHPDoc tag @template T for method MethodTemplateType\Baz::doFoo() with bound type int is not supported.',
'PHPDoc tag @template T for method MethodTemplateType\Baz::doFoo() with bound type float is not supported.',
50,
],
[
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Generics/TraitTemplateTypeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function testRule(): void
16,
],
[
'PHPDoc tag @template T for trait TraitTemplateType\Baz with bound type int is not supported.',
'PHPDoc tag @template T for trait TraitTemplateType\Baz with bound type float is not supported.',
24,
],
[
Expand Down
37 changes: 37 additions & 0 deletions tests/PHPStan/Rules/Generics/data/bug-3769.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,40 @@ function foo(
function fooUnion($foo): void {
assertType('T of Exception|stdClass (function Bug3769\fooUnion(), argument)', $foo);
}

/**
* @template T
* @param T $a
* @return T
*/
function mixedBound($a)
{
return $a;
}

/**
* @template T of int
* @param T $a
* @return T
*/
function intBound(int $a)
{
return $a;
}

/**
* @template T of string
* @param T $a
* @return T
*/
function stringBound(string $a)
{
return $a;
}

function (): void {
assertType('int', mixedBound(1));
assertType('string', mixedBound('str'));
assertType('1', intBound(1));
assertType('\'str\'', stringBound('str'));
};
4 changes: 2 additions & 2 deletions tests/PHPStan/Rules/Generics/data/class-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Bar
}

/**
* @template T of int
* @template T of float
*/
class Baz
{
Expand Down Expand Up @@ -52,7 +52,7 @@ class Ipsum

};

new /** @template T of int */ class
new /** @template T of float */ class
{

};
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Generics/data/function-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function bar()
}

/**
* @template T of int
* @template T of float
*/
function baz()
{
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Generics/data/interface-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface Bar
}

/**
* @template T of int
* @template T of float
*/
interface Baz
{
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Generics/data/method-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Baz
{

/**
* @template T of int
* @template T of float
*/
public function doFoo()
{
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Rules/Generics/data/trait-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ trait Bar
}

/**
* @template T of int
* @template T of float
*/
trait Baz
{
Expand Down
Loading

0 comments on commit 768bfab

Please sign in to comment.