Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix interaction between template type and union type #992

Merged
merged 1 commit into from Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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