Skip to content

Commit e20e934

Browse files
committed
Fix union and intersection between StaticType and ThisType
1 parent addecc7 commit e20e934

File tree

8 files changed

+140
-9
lines changed

8 files changed

+140
-9
lines changed

src/Type/Enum/EnumCaseObjectType.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
6969
return $type->isSubTypeOf($this);
7070
}
7171

72-
return $type->isSuperTypeOf($this)->yes() ? TrinaryLogic::createMaybe() : TrinaryLogic::createNo();
72+
$parent = new parent($this->getClassName(), $this->getSubtractedType(), $this->getClassReflection());
73+
74+
return $parent->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe());
7375
}
7476

7577
public function subtract(Type $type): Type

src/Type/StaticType.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function getClassName(): string
6161
return $this->baseClass;
6262
}
6363

64-
public function getClassReflection(): ?ClassReflection
64+
public function getClassReflection(): ClassReflection
6565
{
6666
return $this->classReflection;
6767
}
@@ -419,7 +419,7 @@ public function getTypeWithoutSubtractedType(): Type
419419
public function changeSubtractedType(?Type $subtractedType): Type
420420
{
421421
$classReflection = $this->getClassReflection();
422-
if ($classReflection !== null && $classReflection->isEnum() && $subtractedType !== null) {
422+
if ($classReflection->isEnum() && $subtractedType !== null) {
423423
$cases = [];
424424
foreach (array_keys($classReflection->getEnumCases()) as $constantName) {
425425
$cases[$constantName] = new EnumCaseObjectType($classReflection->getName(), $constantName);
@@ -443,10 +443,10 @@ public function changeSubtractedType(?Type $subtractedType): Type
443443
}
444444

445445
if (count($cases) === 1) {
446-
return $cases[0];
446+
return TypeCombinator::intersect($this, $cases[0]);
447447
}
448448

449-
return new UnionType(array_values($cases));
449+
return TypeCombinator::intersect($this, new UnionType(array_values($cases)));
450450
}
451451

452452
return new self($this->classReflection, $subtractedType);
@@ -459,7 +459,7 @@ public function getSubtractedType(): ?Type
459459

460460
public function tryRemove(Type $typeToRemove): ?Type
461461
{
462-
if ($this->isSuperTypeOf($typeToRemove)->yes()) {
462+
if ($this->getStaticObjectType()->isSuperTypeOf($typeToRemove)->yes()) {
463463
return $this->subtract($typeToRemove);
464464
}
465465

src/Type/ThisType.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Reflection\ClassReflection;
66
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
7+
use PHPStan\TrinaryLogic;
78
use function sprintf;
89

910
/** @api */
@@ -31,6 +32,31 @@ public function describe(VerbosityLevel $level): string
3132
return sprintf('$this(%s)', $this->getStaticObjectType()->describe($level));
3233
}
3334

35+
public function isSuperTypeOf(Type $type): TrinaryLogic
36+
{
37+
if ($type instanceof self) {
38+
return $this->getStaticObjectType()->isSuperTypeOf($type);
39+
}
40+
41+
if ($type instanceof CompoundType) {
42+
return $type->isSubTypeOf($this);
43+
}
44+
45+
$parent = new parent($this->getClassReflection(), $this->getSubtractedType());
46+
47+
return $parent->isSuperTypeOf($type)->and(TrinaryLogic::createMaybe());
48+
}
49+
50+
public function changeSubtractedType(?Type $subtractedType): Type
51+
{
52+
$type = parent::changeSubtractedType($subtractedType);
53+
if ($type instanceof parent) {
54+
return new self($type->getClassReflection(), $subtractedType);
55+
}
56+
57+
return $type;
58+
}
59+
3460
/**
3561
* @param mixed[] $properties
3662
*/

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,7 @@ public function dataFileAsserts(): iterable
922922
yield from $this->gatherAssertTypes(__DIR__ . '/data/remember-possibly-impure-function-values.php');
923923
yield from $this->gatherAssertTypes(__DIR__ . '/data/emptyiterator.php');
924924
yield from $this->gatherAssertTypes(__DIR__ . '/data/collected-data.php');
925+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7550.php');
925926
}
926927

927928
/**
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Bug7550;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
/**
10+
* @return static
11+
*/
12+
private function _load()
13+
{
14+
return $this;
15+
}
16+
17+
public function reload(): void
18+
{
19+
$res = $this->_load();
20+
assertType('static(Bug7550\Foo)', $res);
21+
if ($res !== $this) {
22+
throw new \Exception('y');
23+
}
24+
25+
assertType('$this(Bug7550\Foo)', $this);
26+
assertType('$this(Bug7550\Foo)', $res);
27+
}
28+
}

tests/PHPStan/Analyser/data/enums.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ enum Foo
1313
public function doFoo(): void
1414
{
1515
if ($this === self::ONE) {
16-
assertType(self::class . '::ONE', $this);
16+
assertType('$this(EnumTypeAssertions\Foo)&' . self::class . '::ONE', $this);
1717
return;
18+
} else {
19+
assertType('$this(EnumTypeAssertions\Foo)&' . self::class . '::TWO', $this);
1820
}
1921

20-
assertType(self::class . '::TWO', $this);
22+
assertType('$this(EnumTypeAssertions\Foo)&' . self::class . '::TWO', $this);
2123
}
2224

2325
}

tests/PHPStan/Rules/Comparison/MatchExpressionRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public function testEnums(): void
138138
19,
139139
],
140140
[
141-
'Match expression does not handle remaining values: MatchEnums\Foo::THREE|MatchEnums\Foo::TWO',
141+
'Match expression does not handle remaining values: ($this(MatchEnums\Foo)&MatchEnums\Foo::TWO)|($this(MatchEnums\Foo)&MatchEnums\Foo::THREE)',
142142
35,
143143
],
144144
[

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,43 @@ public function dataUnion(): iterable
20822082
MixedType::class,
20832083
'mixed~int<17, 18>=implicit',
20842084
];
2085+
2086+
$reflectionProvider = $this->createReflectionProvider();
2087+
yield [
2088+
[
2089+
new StaticType($reflectionProvider->getClass(stdClass::class)),
2090+
new ThisType($reflectionProvider->getClass(stdClass::class)),
2091+
],
2092+
StaticType::class,
2093+
'static(stdClass)',
2094+
];
2095+
2096+
yield [
2097+
[
2098+
new StaticType($reflectionProvider->getClass(stdClass::class)),
2099+
new ObjectType(stdClass::class),
2100+
],
2101+
ObjectType::class,
2102+
'stdClass',
2103+
];
2104+
2105+
yield [
2106+
[
2107+
new StaticType($reflectionProvider->getClass(stdClass::class)),
2108+
new EnumCaseObjectType(stdClass::class, 'foo'),
2109+
],
2110+
UnionType::class,
2111+
'static(stdClass)|stdClass::foo',
2112+
];
2113+
2114+
yield [
2115+
[
2116+
new ThisType($reflectionProvider->getClass(stdClass::class)),
2117+
new EnumCaseObjectType(stdClass::class, 'foo'),
2118+
],
2119+
UnionType::class,
2120+
'$this(stdClass)|stdClass::foo',
2121+
];
20852122
}
20862123

20872124
/**
@@ -3411,6 +3448,41 @@ public function dataIntersect(): iterable
34113448
MixedType::class,
34123449
'mixed~int<17, max>=implicit',
34133450
];
3451+
yield [
3452+
[
3453+
new StaticType($reflectionProvider->getClass(stdClass::class)),
3454+
new ThisType($reflectionProvider->getClass(stdClass::class)),
3455+
],
3456+
ThisType::class,
3457+
'$this(stdClass)',
3458+
];
3459+
3460+
yield [
3461+
[
3462+
new StaticType($reflectionProvider->getClass(stdClass::class)),
3463+
new ObjectType(stdClass::class),
3464+
],
3465+
StaticType::class,
3466+
'static(stdClass)',
3467+
];
3468+
3469+
yield [
3470+
[
3471+
new StaticType($reflectionProvider->getClass(stdClass::class)),
3472+
new EnumCaseObjectType(stdClass::class, 'foo'),
3473+
],
3474+
IntersectionType::class,
3475+
'static(stdClass)&stdClass::foo',
3476+
];
3477+
3478+
yield [
3479+
[
3480+
new ThisType($reflectionProvider->getClass(stdClass::class)),
3481+
new EnumCaseObjectType(stdClass::class, 'foo'),
3482+
],
3483+
IntersectionType::class,
3484+
'$this(stdClass)&stdClass::foo',
3485+
];
34143486
}
34153487

34163488
/**

0 commit comments

Comments
 (0)