Skip to content

Commit 6e05c6d

Browse files
staabmphpstan-bot
authored andcommitted
Fix phpstan/phpstan#14455: missing has-offset via conditional type
- Added processBooleanNotSureSureConditionalTypes method to TypeSpecifier that cross-pairs sureNotType conditions with sureType results - Registered the new method in both BooleanAnd (false context) and BooleanOr (true context) conditional holder generation - New regression test in tests/PHPStan/Analyser/nsrt/bug-14455.php - Root cause: existing methods only paired sureTypes with sureTypes and sureNotTypes with sureNotTypes, missing the cross-pairing needed when one side of a compound condition produces sureNotTypes and the other produces sureTypes (e.g. empty($arr['key']) && $type === 'filter')
1 parent 2d99669 commit 6e05c6d

2 files changed

Lines changed: 98 additions & 0 deletions

File tree

src/Analyser/TypeSpecifier.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,8 @@ public function specifyTypesInCondition(
741741
$this->processBooleanNotSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders),
742742
$this->processBooleanSureConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders),
743743
$this->processBooleanSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders),
744+
$this->processBooleanNotSureSureConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders),
745+
$this->processBooleanNotSureSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders),
744746
))->setRootExpr($expr);
745747
}
746748

@@ -790,6 +792,8 @@ public function specifyTypesInCondition(
790792
$this->processBooleanNotSureConditionalTypes($scope, $rightTypes, $leftTypes),
791793
$this->processBooleanSureConditionalTypes($scope, $leftTypes, $rightTypes),
792794
$this->processBooleanSureConditionalTypes($scope, $rightTypes, $leftTypes),
795+
$this->processBooleanNotSureSureConditionalTypes($scope, $leftTypes, $rightTypes),
796+
$this->processBooleanNotSureSureConditionalTypes($scope, $rightTypes, $leftTypes),
793797
))->setRootExpr($expr);
794798
}
795799

@@ -2119,6 +2123,73 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy
21192123
return [];
21202124
}
21212125

2126+
/**
2127+
* @return array<string, ConditionalExpressionHolder[]>
2128+
*/
2129+
private function processBooleanNotSureSureConditionalTypes(Scope $scope, SpecifiedTypes $conditionTypes, SpecifiedTypes $resultTypes): array
2130+
{
2131+
$conditionExpressionTypes = [];
2132+
foreach ($conditionTypes->getSureNotTypes() as $exprString => [$expr, $type]) {
2133+
if (!$expr instanceof Expr\Variable) {
2134+
continue;
2135+
}
2136+
if (!is_string($expr->name)) {
2137+
continue;
2138+
}
2139+
2140+
$conditionExpressionTypes[$exprString] = ExpressionTypeHolder::createYes(
2141+
$expr,
2142+
TypeCombinator::intersect($scope->getType($expr), $type),
2143+
);
2144+
}
2145+
2146+
if (count($conditionExpressionTypes) > 0) {
2147+
$holders = [];
2148+
foreach ($resultTypes->getSureTypes() as $exprString => [$expr, $type]) {
2149+
if (!$expr instanceof Expr\Variable) {
2150+
continue;
2151+
}
2152+
if (!is_string($expr->name)) {
2153+
continue;
2154+
}
2155+
2156+
if (!isset($holders[$exprString])) {
2157+
$holders[$exprString] = [];
2158+
}
2159+
2160+
$conditions = $conditionExpressionTypes;
2161+
foreach ($conditions as $conditionExprString => $conditionExprTypeHolder) {
2162+
$conditionExpr = $conditionExprTypeHolder->getExpr();
2163+
if (!$conditionExpr instanceof Expr\Variable) {
2164+
continue;
2165+
}
2166+
if (!is_string($conditionExpr->name)) {
2167+
continue;
2168+
}
2169+
if ($conditionExpr->name !== $expr->name) {
2170+
continue;
2171+
}
2172+
2173+
unset($conditions[$conditionExprString]);
2174+
}
2175+
2176+
if (count($conditions) === 0) {
2177+
continue;
2178+
}
2179+
2180+
$holder = new ConditionalExpressionHolder(
2181+
$conditions,
2182+
ExpressionTypeHolder::createYes($expr, TypeCombinator::intersect($scope->getType($expr), $type)),
2183+
);
2184+
$holders[$exprString][$holder->getKey()] = $holder;
2185+
}
2186+
2187+
return $holders;
2188+
}
2189+
2190+
return [];
2191+
}
2192+
21222193
/**
21232194
* @return array{Expr, ConstantScalarType, Type}|null
21242195
*/
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Bug14455;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param array<string, mixed> $aggregation
9+
* @param non-falsy-string $type
10+
*/
11+
function testTriviallyTrueConditionSkipped(array $aggregation, string $type): void
12+
{
13+
if (empty($aggregation['field']) && $type === 'filter') {
14+
return;
15+
}
16+
17+
assertType("array<string, mixed>", $aggregation);
18+
assertType('non-falsy-string', $type);
19+
20+
if ($type === 'filter') {
21+
assertType("non-empty-array<string, mixed>&hasOffset('field')", $aggregation);
22+
} else {
23+
assertType("array<string, mixed>", $aggregation);
24+
}
25+
26+
assertType('non-falsy-string', $type);
27+
}

0 commit comments

Comments
 (0)