Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions src/Type/Generic/TemplateTypeFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantFloatType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\IterableType;
Expand Down Expand Up @@ -39,14 +41,17 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou
}

$boundClass = get_class($bound);
if ($bound instanceof ObjectType && ($boundClass === ObjectType::class || $bound instanceof TemplateType)) {
return new TemplateObjectType($scope, $strategy, $variance, $name, $bound, $default);
}

if ($bound instanceof GenericObjectType && ($boundClass === GenericObjectType::class || $bound instanceof TemplateType)) {
return new TemplateGenericObjectType($scope, $strategy, $variance, $name, $bound, $default);
}

// Catches plain ObjectType and any other object subtype without a dedicated
// Template* class (e.g. enum-case object types), preserving the precise bound
// instead of widening it to TemplateMixedType.
if ($bound instanceof ObjectType) {
return new TemplateObjectType($scope, $strategy, $variance, $name, $bound, $default);
}

if ($bound instanceof ObjectWithoutClassType && ($boundClass === ObjectWithoutClassType::class || $bound instanceof TemplateType)) {
return new TemplateObjectWithoutClassType($scope, $strategy, $variance, $name, $bound, $default);
}
Expand All @@ -71,19 +76,19 @@ public static function create(TemplateTypeScope $scope, string $name, ?Type $bou
return new TemplateConstantStringType($scope, $strategy, $variance, $name, $bound, $default);
}

if ($bound instanceof IntegerType && ($boundClass === IntegerType::class || $bound instanceof TemplateType)) {
if ($bound instanceof IntegerType && ($boundClass === IntegerType::class || $bound instanceof IntegerRangeType || $bound instanceof TemplateType)) {
return new TemplateIntegerType($scope, $strategy, $variance, $name, $bound, $default);
}

if ($bound instanceof ConstantIntegerType && ($boundClass === ConstantIntegerType::class || $bound instanceof TemplateType)) {
return new TemplateConstantIntegerType($scope, $strategy, $variance, $name, $bound, $default);
}

if ($bound instanceof FloatType && ($boundClass === FloatType::class || $bound instanceof TemplateType)) {
if ($bound instanceof FloatType && ($boundClass === FloatType::class || $bound instanceof ConstantFloatType || $bound instanceof TemplateType)) {
return new TemplateFloatType($scope, $strategy, $variance, $name, $bound, $default);
}

if ($bound instanceof BooleanType && ($boundClass === BooleanType::class || $bound instanceof TemplateType)) {
if ($bound instanceof BooleanType && ($boundClass === BooleanType::class || $bound->isTrue()->yes() || $bound->isFalse()->yes() || $bound instanceof TemplateType)) {
return new TemplateBooleanType($scope, $strategy, $variance, $name, $bound, $default);
}

Expand Down
60 changes: 60 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-10083.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php // lint >= 8.1

namespace Bug10083;

use function PHPStan\Testing\assertType;

enum Foo
{

case Abc;
case Bcd;

}

/**
* @template TFoo of Foo::*
* @param TFoo $foo
*/
function checkFoo($foo): void
{
}

/**
* @template TFoo of Foo::*
* @param TFoo $foo
*/
function narrowEnum($foo): void
{
if (Foo::Abc === $foo) {
assertType('TFoo of Bug10083\Foo::Abc (function Bug10083\narrowEnum(), argument)', $foo);
} else {
assertType('TFoo of Bug10083\Foo::Bcd (function Bug10083\narrowEnum(), argument)', $foo);
}

$filter = Foo::Abc === $foo ? 'Abc' : 'Bcd';
assertType('TFoo of Bug10083\Foo::Abc (function Bug10083\narrowEnum(), argument)|TFoo of Bug10083\Foo::Bcd (function Bug10083\narrowEnum(), argument)', $foo);
checkFoo($foo);
}

/**
* @template TInt of int
* @param TInt $int
*/
function narrowIntRange($int): void
{
if ($int >= 0 && $int <= 5) {
assertType('TInt of int<0, 5> (function Bug10083\narrowIntRange(), argument)', $int);
}
}

/**
* @template TFloat of 1.0|2.0
* @param TFloat $float
*/
function narrowFloat($float): void
{
if ($float === 1.0) {
assertType('TFloat of 1.0 (function Bug10083\narrowFloat(), argument)', $float);
}
}
5 changes: 3 additions & 2 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\GenericStaticType;
use PHPStan\Type\Generic\TemplateBenevolentUnionType;
use PHPStan\Type\Generic\TemplateBooleanType;
use PHPStan\Type\Generic\TemplateIntersectionType;
use PHPStan\Type\Generic\TemplateMixedType;
use PHPStan\Type\Generic\TemplateObjectType;
Expand Down Expand Up @@ -6227,8 +6228,8 @@ public static function dataRemove(): array
TemplateTypeVariance::createInvariant(),
),
new ConstantBooleanType(false),
TemplateMixedType::class, // should be TemplateConstantBooleanType
'T (class Foo, parameter)', // should be T of true
TemplateBooleanType::class,
'T of true (class Foo, parameter)',
],
[
new ObjectShapeType(['foo' => new IntegerType()], []),
Expand Down
Loading