Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/Analyser/SpecifiedTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
namespace PHPStan\Analyser;

use PhpParser\Node\Expr;
use PHPStan\Type\MixedType;
use PHPStan\Type\SubtractableType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function array_map;

class SpecifiedTypes
{
Expand Down Expand Up @@ -122,7 +125,22 @@ public function unionWith(SpecifiedTypes $other): self

public function inverse(): self
{
return new self($this->sureNotTypes, $this->sureTypes, $this->overwrite, $this->newConditionalExpressionHolders);
$normalized = $this->normalize();

$inverseType = static function (Type $subtractedType) {
if ($subtractedType instanceof SubtractableType && $subtractedType->getSubtractedType() !== null) {
return TypeCombinator::union($subtractedType->getTypeWithoutSubtractedType(), $subtractedType->getSubtractedType());
}

return new MixedType(false, $subtractedType);
};

return new self(
array_map(static fn (array $sureType) => [$sureType[0], $inverseType($sureType[1])], $normalized->sureTypes),
array_map(static fn (array $sureNotType) => [$sureNotType[0], $inverseType($sureNotType[1])], $normalized->sureNotTypes),
$normalized->overwrite,
$normalized->newConditionalExpressionHolders,
);
}

private function normalize(): self
Expand Down
18 changes: 16 additions & 2 deletions src/Type/TypeCombinator.php
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
44 changes: 44 additions & 0 deletions tests/PHPStan/Analyser/data/bug-6672.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php declare(strict_types = 1);

namespace Bug6672;

use function PHPStan\Testing\assertType;

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

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

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

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

if ($e >= 17 && $e <= 19 || $e === null) {
assertType('int<17, 19>|null', $e);
} else {
assertType('int<min, 16>|int<20, max>', $e);
}

if ($f < 17 || $f > 19 || $f === null) {
assertType('int<min, 16>|int<20, max>|null', $f);
} else {
assertType('int<17, 19>', $f);
}
}
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', $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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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