Skip to content

Commit

Permalink
Improve intersect and union of SubtractableTypes
Browse files Browse the repository at this point in the history
Converts non-SubtractableTypes to SubtractableTypes in union to not loose subtracted type information.
Sorts the SubtractableTypes in intersect to not loose subtracted type information if SubtractableTypes are intersected where not all of them have subtracted types configured.
  • Loading branch information
herndlm committed Feb 21, 2022
1 parent 522af3e commit 7438b86
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 5 deletions.
18 changes: 16 additions & 2 deletions src/Type/TypeCombinator.php
Expand Up @@ -356,9 +356,10 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array
$isSuperType = $typeWithoutSubtractedTypeA->isSuperTypeOf($b);
}
if ($isSuperType->yes()) {
$subtractedType = null;
if ($b instanceof SubtractableType) {
$subtractedType = $b->getSubtractedType();
} else {
$subtractedType = new MixedType(false, $b);
}
$a = self::intersectWithSubtractedType($a, $subtractedType);
return [$a, null];
Expand All @@ -373,9 +374,10 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array
$isSuperType = $typeWithoutSubtractedTypeB->isSuperTypeOf($a);
}
if ($isSuperType->yes()) {
$subtractedType = null;
if ($a instanceof SubtractableType) {
$subtractedType = $a->getSubtractedType();
} else {
$subtractedType = new MixedType(false, $a);
}
$b = self::intersectWithSubtractedType($b, $subtractedType);
return [null, $b];
Expand Down Expand Up @@ -711,6 +713,18 @@ public static function intersect(Type ...$types): Type
$typesCount = count($types);
}

// move subtractables with subtracts before those without to avoid loosing them in the union logic
usort($types, static function (Type $a, Type $b): int {
if ($a instanceof SubtractableType && $a->getSubtractedType() !== null) {
return -1;
}
if ($b instanceof SubtractableType && $b->getSubtractedType() !== null) {
return 1;
}

return 0;
});

// transform IntegerType & ConstantIntegerType to ConstantIntegerType
// transform Child & Parent to Child
// transform Object & ~null to Object
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Expand Up @@ -697,7 +697,7 @@ public function dataAssignInIf(): array
$testScope,
'mixed',
TrinaryLogic::createYes(),
'mixed', // should be mixed~bool+1
'mixed~bool',
],
[
$testScope,
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -706,6 +706,7 @@ public function dataFileAsserts(): iterable

yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6488.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6624.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6672.php');
}

/**
Expand Down
28 changes: 28 additions & 0 deletions tests/PHPStan/Analyser/data/bug-6672.php
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace Bug6672;

use function PHPStan\Testing\assertType;

function foo(int $a, ?int $b, int $c, ?int $d, ?int $e): void
{
if ($a > 17) {
assertType('int<18, max>', $a);
}

if ($b > 17 || $b === null) {
assertType('int<18, max>|null', $b);
}

if ($c < 17) {
assertType('int<min, 16>', $c);
}

if ($d < 17 || $d === null) {
assertType('int<min, 16>|null', $d);
}

if ($e >= 17 && $e <= 19 || $e === null) {
assertType('int<17, 19>|null', $e);
}
}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/instanceof.php
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', $subject);
assertType('mixed~MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject);
assertType('bool', $subject instanceof $string);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/root-scope-maybe-defined.php
Expand Up @@ -16,7 +16,7 @@
\PHPStan\Testing\assertType('1', $baz);
}

\PHPStan\Testing\assertType('mixed', $baz);
\PHPStan\Testing\assertType('mixed~null', $baz);

function () {
\PHPStan\Testing\assertVariableCertainty(TrinaryLogic::createNo(), $foo);
Expand Down
16 changes: 16 additions & 0 deletions tests/PHPStan/Type/TypeCombinatorTest.php
Expand Up @@ -2074,6 +2074,14 @@ public function dataUnion(): iterable
UnionType::class,
'PHPStan\Fixture\AnotherTestEnum::ONE|PHPStan\Fixture\TestEnum::ONE',
];
yield [
[
new MixedType(false, new IntegerRangeType(17, null)),
new IntegerRangeType(19, null),
],
MixedType::class,
'mixed~int<17, 18>=implicit',
];
}

/**
Expand Down Expand Up @@ -3395,6 +3403,14 @@ public function dataIntersect(): iterable
NeverType::class,
'*NEVER*',
];
yield [
[
new MixedType(false, new IntegerRangeType(17, null)),
new MixedType(),
],
MixedType::class,
'mixed~int<17, max>=implicit',
];
}

/**
Expand Down

0 comments on commit 7438b86

Please sign in to comment.