Skip to content

Commit 0906336

Browse files
committed
Fix subtractable StaticType
1 parent 6fba542 commit 0906336

10 files changed

Lines changed: 246 additions & 8 deletions

src/Type/ObjectType.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,15 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
268268
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
269269
}
270270

271+
$transformResult = static fn (TrinaryLogic $result) => $result;
271272
if ($this->subtractedType !== null) {
272273
$isSuperType = $this->subtractedType->isSuperTypeOf($type);
273274
if ($isSuperType->yes()) {
274275
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
275276
}
277+
if ($isSuperType->maybe()) {
278+
$transformResult = static fn (TrinaryLogic $result) => $result->and(TrinaryLogic::createMaybe());
279+
}
276280
}
277281

278282
if (
@@ -289,7 +293,7 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
289293
$thatClassName = $type->getClassName();
290294

291295
if ($thatClassName === $thisClassName) {
292-
return TrinaryLogic::createYes();
296+
return $transformResult(TrinaryLogic::createYes());
293297
}
294298

295299
$reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
@@ -302,11 +306,11 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
302306
$thatClassReflection = $reflectionProvider->getClass($thatClassName);
303307

304308
if ($thisClassReflection->getName() === $thatClassReflection->getName()) {
305-
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes();
309+
return self::$superTypes[$thisDescription][$description] = $transformResult(TrinaryLogic::createYes());
306310
}
307311

308312
if ($thatClassReflection->isSubclassOf($thisClassName)) {
309-
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createYes();
313+
return self::$superTypes[$thisDescription][$description] = $transformResult(TrinaryLogic::createYes());
310314
}
311315

312316
if ($thisClassReflection->isSubclassOf($thatClassName)) {

src/Type/TypeCombinator.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,36 @@ private static function intersectWithSubtractedType(
459459
return $a;
460460
}
461461

462-
if ($b instanceof SubtractableType) {
462+
if ($b instanceof IntersectionType) {
463+
$subtractableTypes = [];
464+
foreach ($b->getTypes() as $innerType) {
465+
if (!$innerType instanceof SubtractableType) {
466+
continue;
467+
}
468+
469+
$subtractableTypes[] = $innerType;
470+
}
471+
472+
if (count($subtractableTypes) === 0) {
473+
return $a->getTypeWithoutSubtractedType();
474+
}
475+
476+
$subtractedTypes = [];
477+
foreach ($subtractableTypes as $subtractableType) {
478+
if ($subtractableType->getSubtractedType() === null) {
479+
continue;
480+
}
481+
482+
$subtractedTypes[] = $subtractableType->getSubtractedType();
483+
}
484+
485+
if (count($subtractedTypes) === 0) {
486+
return $a->getTypeWithoutSubtractedType();
487+
488+
}
489+
490+
$subtractedType = self::union(...$subtractedTypes);
491+
} elseif ($b instanceof SubtractableType) {
463492
$subtractedType = $b->getSubtractedType();
464493
if ($subtractedType === null) {
465494
return $a->getTypeWithoutSubtractedType();

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,7 @@ public function dataFileAsserts(): iterable
925925
yield from $this->gatherAssertTypes(__DIR__ . '/data/collected-data.php');
926926
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7550.php');
927927
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7580.php');
928+
yield from $this->gatherAssertTypes(__DIR__ . '/data/this-subtractable.php');
928929
}
929930

930931
/**

tests/PHPStan/Analyser/data/bug-4117.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ public function getIterator(): ArrayIterator
3030
public function broken(int $key)
3131
{
3232
$item = $this->items[$key] ?? null;
33+
assertType('T (class Bug4117Types\GenericList, argument)|null', $item);
3334
if ($item) {
3435
assertType("T of mixed~0|0.0|''|'0'|array{}|false|null (class Bug4117Types\GenericList, argument)", $item);
3536
} else {
3637
assertType("(array{}&T (class Bug4117Types\GenericList, argument))|(0.0&T (class Bug4117Types\GenericList, argument))|(0&T (class Bug4117Types\GenericList, argument))|(''&T (class Bug4117Types\GenericList, argument))|('0'&T (class Bug4117Types\GenericList, argument))|(T (class Bug4117Types\GenericList, argument)&false)|null", $item);
3738
}
3839

39-
assertType("T of mixed~0|0.0|''|'0'|array{}|false|null (class Bug4117Types\GenericList, argument)|null", $item);
40+
assertType('T (class Bug4117Types\GenericList, argument)|null', $item);
4041

4142
return $item;
4243
}

tests/PHPStan/Analyser/data/instanceof.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter
156156
assertType('object', $subject);
157157
assertType('bool', $subject instanceof $string);
158158
} else {
159-
assertType('mixed~MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject);
159+
assertType('mixed', $subject);
160160
assertType('bool', $subject instanceof $string);
161161
}
162162

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace ThisSubtractable;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function doFoo()
11+
{
12+
assertType('$this(ThisSubtractable\Foo)', $this);
13+
14+
if (!$this instanceof Bar && !$this instanceof Baz) {
15+
assertType('$this(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz)', $this);
16+
} else {
17+
assertType('($this(ThisSubtractable\Foo)&ThisSubtractable\Bar)|($this(ThisSubtractable\Foo)&ThisSubtractable\Baz)', $this);
18+
}
19+
20+
assertType('$this(ThisSubtractable\Foo)', $this);
21+
}
22+
23+
public function doBar()
24+
{
25+
$s = $this->returnStatic();
26+
assertType('static(ThisSubtractable\Foo)', $s);
27+
28+
if (!$s instanceof Bar && !$s instanceof Baz) {
29+
assertType('static(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz)', $s);
30+
} else {
31+
assertType('(static(ThisSubtractable\Foo)&ThisSubtractable\Bar)|(static(ThisSubtractable\Foo)&ThisSubtractable\Baz)', $s);
32+
}
33+
34+
assertType('static(ThisSubtractable\Foo)', $s);
35+
}
36+
37+
public function doBaz(self $s)
38+
{
39+
assertType('ThisSubtractable\Foo', $s);
40+
41+
if (!$s instanceof Lorem && !$s instanceof Ipsum) {
42+
assertType('ThisSubtractable\Foo', $s);
43+
} else {
44+
assertType('(ThisSubtractable\Foo&ThisSubtractable\Ipsum)|(ThisSubtractable\Foo&ThisSubtractable\Lorem)', $s);
45+
}
46+
47+
assertType('ThisSubtractable\Foo', $s);
48+
}
49+
50+
public function doBazz(self $s)
51+
{
52+
assertType('ThisSubtractable\Foo', $s);
53+
54+
if (!$s instanceof Bar && !$s instanceof Baz) {
55+
assertType('ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz', $s);
56+
} else {
57+
assertType('ThisSubtractable\Bar|ThisSubtractable\Baz', $s);
58+
}
59+
60+
assertType('ThisSubtractable\Foo', $s);
61+
}
62+
63+
public function doBazzz(self $s)
64+
{
65+
assertType('ThisSubtractable\Foo', $s);
66+
if (!method_exists($s, 'test123', $s)) {
67+
return;
68+
}
69+
70+
assertType('ThisSubtractable\Foo&hasMethod(test123)', $s);
71+
72+
if (!$s instanceof Bar && !$s instanceof Baz) {
73+
assertType('ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz&hasMethod(test123)', $s);
74+
} else {
75+
assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))', $s);
76+
}
77+
78+
assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))|(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz&hasMethod(test123))', $s);
79+
}
80+
81+
/**
82+
* @return static
83+
*/
84+
public function returnStatic()
85+
{
86+
return $this;
87+
}
88+
89+
}
90+
91+
class Bar extends Foo
92+
{
93+
94+
}
95+
96+
class Baz extends Foo
97+
{
98+
99+
}
100+
101+
interface Lorem
102+
{
103+
104+
}
105+
106+
interface Ipsum
107+
{
108+
109+
}

tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
use PHPStan\TrinaryLogic;
1010
use PHPStan\Type\BenevolentUnionType;
1111
use PHPStan\Type\ClassStringType;
12+
use PHPStan\Type\Constant\ConstantIntegerType;
1213
use PHPStan\Type\Constant\ConstantStringType;
14+
use PHPStan\Type\IntegerRangeType;
1315
use PHPStan\Type\IntegerType;
1416
use PHPStan\Type\ObjectType;
1517
use PHPStan\Type\ObjectWithoutClassType;
@@ -145,6 +147,14 @@ public function dataIsSuperTypeOf(): array
145147
new ConstantStringType(Exception::class),
146148
TrinaryLogic::createYes(),
147149
],
150+
18 => [
151+
new GenericClassStringType(new ObjectType(Type::class, new UnionType([
152+
new ObjectType(ConstantIntegerType::class),
153+
new ObjectType(IntegerRangeType::class),
154+
]))),
155+
new ConstantStringType(IntegerType::class),
156+
TrinaryLogic::createMaybe(),
157+
],
148158
];
149159
}
150160

tests/PHPStan/Type/ObjectTypeTest.php

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use DateTime;
1111
use DateTimeImmutable;
1212
use DateTimeInterface;
13+
use Exception;
1314
use Generator;
1415
use InvalidArgumentException;
1516
use Iterator;
@@ -18,6 +19,7 @@
1819
use PHPStan\TrinaryLogic;
1920
use PHPStan\Type\Accessory\HasMethodType;
2021
use PHPStan\Type\Accessory\HasPropertyType;
22+
use PHPStan\Type\Constant\ConstantIntegerType;
2123
use PHPStan\Type\Constant\ConstantStringType;
2224
use PHPStan\Type\Generic\GenericObjectType;
2325
use PHPStan\Type\Generic\TemplateTypeFactory;
@@ -26,6 +28,7 @@
2628
use SimpleXMLElement;
2729
use stdClass;
2830
use Throwable;
31+
use ThrowPoints\TryCatch\MyInvalidArgumentException;
2932
use Traversable;
3033
use function sprintf;
3134

@@ -298,7 +301,7 @@ public function dataIsSuperTypeOf(): array
298301
39 => [
299302
new ObjectType(Throwable::class, new ObjectType(InvalidArgumentException::class)),
300303
new ObjectType('Exception'),
301-
TrinaryLogic::createYes(),
304+
TrinaryLogic::createMaybe(),
302305
],
303306
40 => [
304307
new ObjectType(Throwable::class, new ObjectType('Exception')),
@@ -313,7 +316,7 @@ public function dataIsSuperTypeOf(): array
313316
42 => [
314317
new ObjectType(Throwable::class, new ObjectType('Exception')),
315318
new ObjectType(Throwable::class),
316-
TrinaryLogic::createYes(),
319+
TrinaryLogic::createMaybe(),
317320
],
318321
43 => [
319322
new ObjectType(Throwable::class),
@@ -360,6 +363,49 @@ public function dataIsSuperTypeOf(): array
360363
),
361364
TrinaryLogic::createMaybe(),
362365
],
366+
49 => [
367+
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
368+
new ObjectType(InvalidArgumentException::class),
369+
TrinaryLogic::createNo(),
370+
],
371+
50 => [
372+
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
373+
new ObjectType(MyInvalidArgumentException::class),
374+
TrinaryLogic::createNo(),
375+
],
376+
51 => [
377+
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
378+
new ObjectType(LogicException::class),
379+
TrinaryLogic::createMaybe(),
380+
],
381+
52 => [
382+
new ObjectType(InvalidArgumentException::class, new ObjectType(MyInvalidArgumentException::class)),
383+
new ObjectType(Exception::class),
384+
TrinaryLogic::createMaybe(),
385+
],
386+
53 => [
387+
new ObjectType(InvalidArgumentException::class, new ObjectType(MyInvalidArgumentException::class)),
388+
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
389+
TrinaryLogic::createNo(),
390+
],
391+
54 => [
392+
new ObjectType(InvalidArgumentException::class),
393+
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
394+
TrinaryLogic::createNo(),
395+
],
396+
55 => [
397+
new ObjectType(stdClass::class, new ObjectType(Throwable::class)),
398+
new ObjectType(Throwable::class),
399+
TrinaryLogic::createNo(),
400+
],
401+
56 => [
402+
new ObjectType(Type::class, new UnionType([
403+
new ObjectType(ConstantIntegerType::class),
404+
new ObjectType(IntegerRangeType::class),
405+
])),
406+
new ObjectType(IntegerType::class),
407+
TrinaryLogic::createMaybe(),
408+
],
363409
];
364410
}
365411

tests/PHPStan/Type/StaticTypeTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,23 @@ public function dataIsSuperTypeOf(): array
253253
new ObjectType(FinalChild::class),
254254
TrinaryLogic::createYes(),
255255
],
256+
[
257+
new ThisType(
258+
$reflectionProvider->getClass(\ThisSubtractable\Foo::class), // phpcs:ignore
259+
new UnionType([new ObjectType(\ThisSubtractable\Bar::class), new ObjectType(\ThisSubtractable\Baz::class)]), // phpcs:ignore
260+
),
261+
new UnionType([
262+
new IntersectionType([
263+
new ThisType($reflectionProvider->getClass(\ThisSubtractable\Foo::class)), // phpcs:ignore
264+
new ObjectType(\ThisSubtractable\Bar::class), // phpcs:ignore
265+
]),
266+
new IntersectionType([
267+
new ThisType($reflectionProvider->getClass(\ThisSubtractable\Foo::class)), // phpcs:ignore
268+
new ObjectType(\ThisSubtractable\Baz::class), // phpcs:ignore
269+
]),
270+
]),
271+
TrinaryLogic::createNo(),
272+
],
256273
];
257274
}
258275

tests/PHPStan/Type/TypeCombinatorTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,6 +2119,27 @@ public function dataUnion(): iterable
21192119
UnionType::class,
21202120
'$this(stdClass)|stdClass::foo',
21212121
];
2122+
2123+
yield [
2124+
[
2125+
new ThisType(
2126+
$reflectionProvider->getClass(\ThisSubtractable\Foo::class), // phpcs:ignore
2127+
new UnionType([new ObjectType(\ThisSubtractable\Bar::class), new ObjectType(\ThisSubtractable\Baz::class)]), // phpcs:ignore
2128+
),
2129+
new UnionType([
2130+
new IntersectionType([
2131+
new ThisType($reflectionProvider->getClass(\ThisSubtractable\Foo::class)), // phpcs:ignore
2132+
new ObjectType(\ThisSubtractable\Bar::class), // phpcs:ignore
2133+
]),
2134+
new IntersectionType([
2135+
new ThisType($reflectionProvider->getClass(\ThisSubtractable\Foo::class)), // phpcs:ignore
2136+
new ObjectType(\ThisSubtractable\Baz::class), // phpcs:ignore
2137+
]),
2138+
]),
2139+
],
2140+
ThisType::class,
2141+
'$this(ThisSubtractable\Foo)',
2142+
];
21222143
}
21232144

21242145
/**

0 commit comments

Comments
 (0)