Skip to content

Commit

Permalink
Fix interaction between template type and union type
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-lb authored and ondrejmirtes committed Feb 4, 2022
1 parent afc49fd commit 9512513
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 7 deletions.
7 changes: 5 additions & 2 deletions src/Type/UnionType.php
Expand Up @@ -92,7 +92,7 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic
return TrinaryLogic::createYes();
}

if ($type instanceof CompoundType && !$type instanceof CallableType) {
if ($type instanceof CompoundType && !$type instanceof CallableType && !$type instanceof TemplateUnionType) {
return $type->isAcceptedBy($this, $strictTypes);
}

Expand All @@ -106,7 +106,10 @@ public function accepts(Type $type, bool $strictTypes): TrinaryLogic

public function isSuperTypeOf(Type $otherType): TrinaryLogic
{
if ($otherType instanceof self || $otherType instanceof IterableType) {
if (
($otherType instanceof self && !$otherType instanceof TemplateUnionType)
|| $otherType instanceof IterableType
) {
return $otherType->isSubTypeOf($this);
}

Expand Down
8 changes: 8 additions & 0 deletions tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php
Expand Up @@ -2052,6 +2052,14 @@ public function testBug5258(): void
$this->analyse([__DIR__ . '/data/bug-5258.php'], []);
}

public function testBug5591(): void
{
$this->checkThisOnly = false;
$this->checkNullables = true;
$this->checkUnionTypes = true;
$this->analyse([__DIR__ . '/data/bug-5591.php'], []);
}

public function testGenericObjectLowerBound(): void
{
$this->checkThisOnly = false;
Expand Down
5 changes: 0 additions & 5 deletions tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php
Expand Up @@ -528,11 +528,6 @@ public function testTemplateUnion(): void
'Method ReturnTemplateUnion\Foo::doFoo2() should return T of bool|float|int|string but returns (T of bool|float|int|string)|null.',
25,
],
[
// should not be reported
'Method ReturnTemplateUnion\Foo::doFoo3() should return (T of bool|float|int|string)|null but returns (T of bool|float|int|string)|null.',
35,
],
]);
}

Expand Down
46 changes: 46 additions & 0 deletions tests/PHPStan/Rules/Methods/data/bug-5591.php
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Bug5591;

class EntityA {}
class EntityB {}

/**
* @template TEntity as object
*/
class TestClass
{
/** @var class-string<TEntity> The fully-qualified (::class) class name of the entity being managed. */
protected $entityClass;

/** @param TEntity|null $record */
public function outerMethod($record = null): void
{
$record = $this->innerMethod($record);
}

/**
* @param TEntity|null $record
*
* @return TEntity
*/
public function innerMethod($record = null): object
{
$class = $this->entityClass;
return new $class();
}
}

/**
* @template TEntity as EntityA|EntityB
* @extends TestClass<TEntity>
*/
class TestClass2 extends TestClass
{
public function outerMethod($record = null): void
{
$record = $this->innerMethod($record);
}
}
216 changes: 216 additions & 0 deletions tests/PHPStan/Type/UnionTypeTest.php
Expand Up @@ -344,6 +344,94 @@ public function dataIsSuperTypeOf(): Iterator
new IntersectionType([new StringType(), new CallableType()]),
TrinaryLogic::createNo(),
];

yield 'is super type of template-of-union equal to a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createYes(),
];

yield 'maybe super type of template-of-union equal to a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Bar'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createMaybe(),
];

yield 'is super type of template-of-string equal to a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new StringType(),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new StringType(),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createYes(),
];

yield 'maybe super type of template-of-string sub type of a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new StringType(),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Bar'),
'T',
new StringType(),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createMaybe(),
];
}

/**
Expand Down Expand Up @@ -774,6 +862,134 @@ public function dataAccepts(): array
new ClosureType([], new MixedType(), false),
TrinaryLogic::createYes(),
],
'accepts template-of-union equal to a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createYes(),
],
'maybe accepts template-of-union sub type of a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Bar'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createMaybe(),
],
'accepts template-of-string equal to a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new StringType(),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new StringType(),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createYes(),
],
'maybe accepts template-of-string sub type of a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new StringType(),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Bar'),
'T',
new StringType(),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createMaybe(),
],
'accepts template-of-union containing a union member' => [
new UnionType([
new IntegerType(),
new NullType(),
]),
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new IntegerType(),
new FloatType(),
]),
TemplateTypeVariance::createInvariant(),
),
TrinaryLogic::createMaybe(),
],
'accepts intersection with template-of-union equal to a union member' => [
new UnionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new ObjectType('Iterator'),
new ObjectType('IteratorAggregate'),
]),
TemplateTypeVariance::createInvariant(),
),
new NullType(),
]),
new IntersectionType([
TemplateTypeFactory::create(
TemplateTypeScope::createWithClass('Foo'),
'T',
new UnionType([
new ObjectType('Iterator'),
new ObjectType('IteratorAggregate'),
]),
TemplateTypeVariance::createInvariant(),
),
new ObjectType('Countable'),
]),
TrinaryLogic::createYes(),
],

];
}

Expand Down

0 comments on commit 9512513

Please sign in to comment.