Skip to content

Commit 111799b

Browse files
rvanvelzenondrejmirtes
authored andcommitted
Support conditional types in phpdoc asserts
1 parent 26bf11c commit 111799b

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,17 @@ private function specifyTypesFromAsserts(TypeSpecifierContext $context, Expr\Cal
13131313
continue;
13141314
}
13151315

1316+
$assertedType = TypeTraverser::map($assert->getType(), static function (Type $type, callable $traverse) use ($argsMap, $scope): Type {
1317+
if ($type instanceof ConditionalTypeForParameter) {
1318+
$parameterName = substr($type->getParameterName(), 1);
1319+
if (array_key_exists($parameterName, $argsMap)) {
1320+
$type = $type->toConditional($scope->getType($argsMap[$parameterName]));
1321+
}
1322+
}
1323+
1324+
return $traverse($type);
1325+
});
1326+
13161327
$assertExpr = $assert->getParameter()->getExpr($parameterExpr);
13171328

13181329
$templateTypeMap = $parametersAcceptor->getResolvedTemplateTypeMap();
@@ -1334,19 +1345,19 @@ static function (Type $type, callable $traverse) use ($templateTypeMap, &$contai
13341345

13351346
$newTypes = $this->create(
13361347
$assertExpr,
1337-
$assert->getType(),
1348+
$assertedType,
13381349
$assert->isNegated() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createTrue(),
13391350
false,
13401351
$scope,
13411352
$containsUnresolvedTemplate || $assert->isEquality() ? $call : null,
13421353
);
13431354
$types = $types !== null ? $types->unionWith($newTypes) : $newTypes;
13441355

1345-
if (!$context->null() || !$assert->getType() instanceof ConstantBooleanType) {
1356+
if (!$context->null() || !$assertedType instanceof ConstantBooleanType) {
13461357
continue;
13471358
}
13481359

1349-
$subContext = $assert->getType()->getValue() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createFalse();
1360+
$subContext = $assertedType->getValue() ? TypeSpecifierContext::createTrue() : TypeSpecifierContext::createFalse();
13501361
if ($assert->isNegated()) {
13511362
$subContext = $subContext->negate();
13521363
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,7 @@ public function dataFileAsserts(): iterable
10881088
yield from $this->gatherAssertTypes(__DIR__ . '/data/assert-methods.php');
10891089
yield from $this->gatherAssertTypes(__DIR__ . '/data/assert-intersected.php');
10901090
yield from $this->gatherAssertTypes(__DIR__ . '/data/assert-invariant.php');
1091+
yield from $this->gatherAssertTypes(__DIR__ . '/data/assert-conditional.php');
10911092
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/docblock-assert-equality.php');
10921093
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8008.php');
10931094
yield from $this->gatherAssertTypes(__DIR__ . '/data/assert-class-type.php');
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace AssertConditional;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @phpstan-assert ($if is true ? true : false) $condition
9+
*/
10+
function assertIf(mixed $condition, bool $if)
11+
{
12+
}
13+
14+
function (mixed $value1, mixed $value2) {
15+
assertIf($value1, true);
16+
assertType('true', $value1);
17+
18+
assertIf($value2, false);
19+
assertType('false', $value2);
20+
};
21+
22+
/**
23+
* @template T of bool
24+
* @param T $if
25+
* @phpstan-assert (T is true ? true : false) $condition
26+
*/
27+
function assertIfTemplated(mixed $condition, bool $if)
28+
{
29+
}
30+
31+
function (mixed $value1, mixed $value2) {
32+
assertIfTemplated($value1, true);
33+
assertType('true', $value1);
34+
35+
assertIfTemplated($value2, false);
36+
assertType('false', $value2);
37+
};

0 commit comments

Comments
 (0)