Skip to content

Commit

Permalink
Fix subtractable StaticType
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 12, 2022
1 parent 6fba542 commit 0906336
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 8 deletions.
10 changes: 7 additions & 3 deletions src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,15 @@ public function isSuperTypeOf(Type $type): TrinaryLogic
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createMaybe();
}

$transformResult = static fn (TrinaryLogic $result) => $result;
if ($this->subtractedType !== null) {
$isSuperType = $this->subtractedType->isSuperTypeOf($type);
if ($isSuperType->yes()) {
return self::$superTypes[$thisDescription][$description] = TrinaryLogic::createNo();
}
if ($isSuperType->maybe()) {
$transformResult = static fn (TrinaryLogic $result) => $result->and(TrinaryLogic::createMaybe());
}
}

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

if ($thatClassName === $thisClassName) {
return TrinaryLogic::createYes();
return $transformResult(TrinaryLogic::createYes());
}

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

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

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

if ($thisClassReflection->isSubclassOf($thatClassName)) {
Expand Down
31 changes: 30 additions & 1 deletion src/Type/TypeCombinator.php
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,36 @@ private static function intersectWithSubtractedType(
return $a;
}

if ($b instanceof SubtractableType) {
if ($b instanceof IntersectionType) {
$subtractableTypes = [];
foreach ($b->getTypes() as $innerType) {
if (!$innerType instanceof SubtractableType) {
continue;
}

$subtractableTypes[] = $innerType;
}

if (count($subtractableTypes) === 0) {
return $a->getTypeWithoutSubtractedType();
}

$subtractedTypes = [];
foreach ($subtractableTypes as $subtractableType) {
if ($subtractableType->getSubtractedType() === null) {
continue;
}

$subtractedTypes[] = $subtractableType->getSubtractedType();
}

if (count($subtractedTypes) === 0) {
return $a->getTypeWithoutSubtractedType();

}

$subtractedType = self::union(...$subtractedTypes);
} elseif ($b instanceof SubtractableType) {
$subtractedType = $b->getSubtractedType();
if ($subtractedType === null) {
return $a->getTypeWithoutSubtractedType();
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/collected-data.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7550.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7580.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/this-subtractable.php');
}

/**
Expand Down
3 changes: 2 additions & 1 deletion tests/PHPStan/Analyser/data/bug-4117.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ public function getIterator(): ArrayIterator
public function broken(int $key)
{
$item = $this->items[$key] ?? null;
assertType('T (class Bug4117Types\GenericList, argument)|null', $item);
if ($item) {
assertType("T of mixed~0|0.0|''|'0'|array{}|false|null (class Bug4117Types\GenericList, argument)", $item);
} else {
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);
}

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

return $item;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/instanceof.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter
assertType('object', $subject);
assertType('bool', $subject instanceof $string);
} else {
assertType('mixed~MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject);
assertType('mixed', $subject);
assertType('bool', $subject instanceof $string);
}

Expand Down
109 changes: 109 additions & 0 deletions tests/PHPStan/Analyser/data/this-subtractable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace ThisSubtractable;

use function PHPStan\Testing\assertType;

class Foo
{

public function doFoo()
{
assertType('$this(ThisSubtractable\Foo)', $this);

if (!$this instanceof Bar && !$this instanceof Baz) {
assertType('$this(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz)', $this);
} else {
assertType('($this(ThisSubtractable\Foo)&ThisSubtractable\Bar)|($this(ThisSubtractable\Foo)&ThisSubtractable\Baz)', $this);
}

assertType('$this(ThisSubtractable\Foo)', $this);
}

public function doBar()
{
$s = $this->returnStatic();
assertType('static(ThisSubtractable\Foo)', $s);

if (!$s instanceof Bar && !$s instanceof Baz) {
assertType('static(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz)', $s);
} else {
assertType('(static(ThisSubtractable\Foo)&ThisSubtractable\Bar)|(static(ThisSubtractable\Foo)&ThisSubtractable\Baz)', $s);
}

assertType('static(ThisSubtractable\Foo)', $s);
}

public function doBaz(self $s)
{
assertType('ThisSubtractable\Foo', $s);

if (!$s instanceof Lorem && !$s instanceof Ipsum) {
assertType('ThisSubtractable\Foo', $s);
} else {
assertType('(ThisSubtractable\Foo&ThisSubtractable\Ipsum)|(ThisSubtractable\Foo&ThisSubtractable\Lorem)', $s);
}

assertType('ThisSubtractable\Foo', $s);
}

public function doBazz(self $s)
{
assertType('ThisSubtractable\Foo', $s);

if (!$s instanceof Bar && !$s instanceof Baz) {
assertType('ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz', $s);
} else {
assertType('ThisSubtractable\Bar|ThisSubtractable\Baz', $s);
}

assertType('ThisSubtractable\Foo', $s);
}

public function doBazzz(self $s)
{
assertType('ThisSubtractable\Foo', $s);
if (!method_exists($s, 'test123', $s)) {
return;
}

assertType('ThisSubtractable\Foo&hasMethod(test123)', $s);

if (!$s instanceof Bar && !$s instanceof Baz) {
assertType('ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz&hasMethod(test123)', $s);
} else {
assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))', $s);
}

assertType('(ThisSubtractable\Bar&hasMethod(test123))|(ThisSubtractable\Baz&hasMethod(test123))|(ThisSubtractable\Foo~ThisSubtractable\Bar|ThisSubtractable\Baz&hasMethod(test123))', $s);
}

/**
* @return static
*/
public function returnStatic()
{
return $this;
}

}

class Bar extends Foo
{

}

class Baz extends Foo
{

}

interface Lorem
{

}

interface Ipsum
{

}
10 changes: 10 additions & 0 deletions tests/PHPStan/Type/Generic/GenericClassStringTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
use PHPStan\TrinaryLogic;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\ClassStringType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ObjectWithoutClassType;
Expand Down Expand Up @@ -145,6 +147,14 @@ public function dataIsSuperTypeOf(): array
new ConstantStringType(Exception::class),
TrinaryLogic::createYes(),
],
18 => [
new GenericClassStringType(new ObjectType(Type::class, new UnionType([
new ObjectType(ConstantIntegerType::class),
new ObjectType(IntegerRangeType::class),
]))),
new ConstantStringType(IntegerType::class),
TrinaryLogic::createMaybe(),
],
];
}

Expand Down
50 changes: 48 additions & 2 deletions tests/PHPStan/Type/ObjectTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use Generator;
use InvalidArgumentException;
use Iterator;
Expand All @@ -18,6 +19,7 @@
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\HasMethodType;
use PHPStan\Type\Accessory\HasPropertyType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateTypeFactory;
Expand All @@ -26,6 +28,7 @@
use SimpleXMLElement;
use stdClass;
use Throwable;
use ThrowPoints\TryCatch\MyInvalidArgumentException;
use Traversable;
use function sprintf;

Expand Down Expand Up @@ -298,7 +301,7 @@ public function dataIsSuperTypeOf(): array
39 => [
new ObjectType(Throwable::class, new ObjectType(InvalidArgumentException::class)),
new ObjectType('Exception'),
TrinaryLogic::createYes(),
TrinaryLogic::createMaybe(),
],
40 => [
new ObjectType(Throwable::class, new ObjectType('Exception')),
Expand All @@ -313,7 +316,7 @@ public function dataIsSuperTypeOf(): array
42 => [
new ObjectType(Throwable::class, new ObjectType('Exception')),
new ObjectType(Throwable::class),
TrinaryLogic::createYes(),
TrinaryLogic::createMaybe(),
],
43 => [
new ObjectType(Throwable::class),
Expand Down Expand Up @@ -360,6 +363,49 @@ public function dataIsSuperTypeOf(): array
),
TrinaryLogic::createMaybe(),
],
49 => [
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
new ObjectType(InvalidArgumentException::class),
TrinaryLogic::createNo(),
],
50 => [
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
new ObjectType(MyInvalidArgumentException::class),
TrinaryLogic::createNo(),
],
51 => [
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
new ObjectType(LogicException::class),
TrinaryLogic::createMaybe(),
],
52 => [
new ObjectType(InvalidArgumentException::class, new ObjectType(MyInvalidArgumentException::class)),
new ObjectType(Exception::class),
TrinaryLogic::createMaybe(),
],
53 => [
new ObjectType(InvalidArgumentException::class, new ObjectType(MyInvalidArgumentException::class)),
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
TrinaryLogic::createNo(),
],
54 => [
new ObjectType(InvalidArgumentException::class),
new ObjectType(Exception::class, new ObjectType(InvalidArgumentException::class)),
TrinaryLogic::createNo(),
],
55 => [
new ObjectType(stdClass::class, new ObjectType(Throwable::class)),
new ObjectType(Throwable::class),
TrinaryLogic::createNo(),
],
56 => [
new ObjectType(Type::class, new UnionType([
new ObjectType(ConstantIntegerType::class),
new ObjectType(IntegerRangeType::class),
])),
new ObjectType(IntegerType::class),
TrinaryLogic::createMaybe(),
],
];
}

Expand Down
17 changes: 17 additions & 0 deletions tests/PHPStan/Type/StaticTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,23 @@ public function dataIsSuperTypeOf(): array
new ObjectType(FinalChild::class),
TrinaryLogic::createYes(),
],
[
new ThisType(
$reflectionProvider->getClass(\ThisSubtractable\Foo::class), // phpcs:ignore
new UnionType([new ObjectType(\ThisSubtractable\Bar::class), new ObjectType(\ThisSubtractable\Baz::class)]), // phpcs:ignore
),
new UnionType([
new IntersectionType([
new ThisType($reflectionProvider->getClass(\ThisSubtractable\Foo::class)), // phpcs:ignore
new ObjectType(\ThisSubtractable\Bar::class), // phpcs:ignore
]),
new IntersectionType([
new ThisType($reflectionProvider->getClass(\ThisSubtractable\Foo::class)), // phpcs:ignore
new ObjectType(\ThisSubtractable\Baz::class), // phpcs:ignore
]),
]),
TrinaryLogic::createNo(),
],
];
}

Expand Down
21 changes: 21 additions & 0 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2119,6 +2119,27 @@ public function dataUnion(): iterable
UnionType::class,
'$this(stdClass)|stdClass::foo',
];

yield [
[
new ThisType(
$reflectionProvider->getClass(\ThisSubtractable\Foo::class), // phpcs:ignore
new UnionType([new ObjectType(\ThisSubtractable\Bar::class), new ObjectType(\ThisSubtractable\Baz::class)]), // phpcs:ignore
),
new UnionType([
new IntersectionType([
new ThisType($reflectionProvider->getClass(\ThisSubtractable\Foo::class)), // phpcs:ignore
new ObjectType(\ThisSubtractable\Bar::class), // phpcs:ignore
]),
new IntersectionType([
new ThisType($reflectionProvider->getClass(\ThisSubtractable\Foo::class)), // phpcs:ignore
new ObjectType(\ThisSubtractable\Baz::class), // phpcs:ignore
]),
]),
],
ThisType::class,
'$this(ThisSubtractable\Foo)',
];
}

/**
Expand Down

0 comments on commit 0906336

Please sign in to comment.