Skip to content

Commit 5c4a12d

Browse files
committed
Specify conditional types for all falsey scalars
1 parent 45a62ae commit 5c4a12d

File tree

4 files changed

+35
-5
lines changed

4 files changed

+35
-5
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@
173173
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
174174
use PHPStan\Type\Constant\ConstantIntegerType;
175175
use PHPStan\Type\Constant\ConstantStringType;
176+
use PHPStan\Type\ConstantTypeHelper;
176177
use PHPStan\Type\FileTypeMapper;
177178
use PHPStan\Type\GeneralizePrecision;
178179
use PHPStan\Type\Generic\TemplateTypeHelper;
@@ -6108,6 +6109,35 @@ private function processAssignVar(
61086109
$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $falseySpecifiedTypes, $falseyType);
61096110
}
61106111

6112+
foreach ([null, false, 0, 0.0, '', '0', []] as $falseyScalar) {
6113+
$falseyType = ConstantTypeHelper::getTypeFromValue($falseyScalar);
6114+
$withoutFalseyType = TypeCombinator::remove($type, $falseyType);
6115+
if (
6116+
$withoutFalseyType->equals($type)
6117+
|| $withoutFalseyType->equals($truthyType)
6118+
) {
6119+
continue;
6120+
}
6121+
6122+
$astNode = match ($falseyScalar) {
6123+
null => new ConstFetch(new Name('null')),
6124+
false => new ConstFetch(new Name('false')),
6125+
0 => new Node\Scalar\Int_($falseyScalar),
6126+
0.0 => new Node\Scalar\Float_($falseyScalar),
6127+
'', '0' => new Node\Scalar\String_($falseyScalar),
6128+
[] => new Node\Expr\Array_($falseyScalar),
6129+
};
6130+
$notConditionExpr = new Expr\BinaryOp\NotIdentical($assignedExpr, $astNode);
6131+
$notSpecifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $notConditionExpr, TypeSpecifierContext::createTrue());
6132+
$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $notSpecifiedTypes, $withoutFalseyType);
6133+
$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $notSpecifiedTypes, $withoutFalseyType);
6134+
6135+
$conditionExpr = new Expr\BinaryOp\Identical($assignedExpr, $astNode);
6136+
$specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $conditionExpr, TypeSpecifierContext::createTrue());
6137+
$conditionalExpressions = $this->processSureTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $specifiedTypes, $falseyType);
6138+
$conditionalExpressions = $this->processSureNotTypesForConditionalExpressionsAfterAssign($scope, $var->name, $conditionalExpressions, $specifiedTypes, $falseyType);
6139+
}
6140+
61116141
$this->callNodeCallback($nodeCallback, new VariableAssignNode($var, $assignedExpr), $scopeBeforeAssignEval, $storage);
61126142
$scope = $scope->assignVariable($var->name, $type, $scope->getNativeType($assignedExpr), TrinaryLogic::createYes());
61136143
foreach ($conditionalExpressions as $exprString => $holders) {

tests/PHPStan/Analyser/nsrt/bug-13546.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ function mixedLast($mixed): void
6666
function firstInCondition(array $array)
6767
{
6868
if (($key = array_key_first($array)) !== null) {
69-
assertType('list<string>', $array); // could be 'non-empty-list<string>'
69+
assertType('non-empty-list<string>', $array);
7070
return $array[$key];
7171
}
72-
assertType('list<string>', $array);
72+
assertType('array{}', $array);
7373
return null;
7474
}
7575

tests/PHPStan/Analyser/nsrt/bug-14081.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function firstNotNull(array $array): mixed
4848
{
4949
if (($key = array_key_first($array)) !== null) {
5050
assertType('int<0, max>', $key);
51-
assertType('list<string>', $array); // could be non-empty-list<string>
51+
assertType('non-empty-list<string>', $array);
5252
assertType('string', $array[$key]);
5353
return $array[$key];
5454
}
@@ -60,7 +60,7 @@ function lastNotNull(array $array): mixed
6060
{
6161
if (($key = array_key_last($array)) !== null) {
6262
assertType('int<0, max>', $key);
63-
assertType('list<string>', $array); // could be non-empty-list<string>
63+
assertType('non-empty-list<string>', $array);
6464
assertType('string', $array[$key]);
6565
return $array[$key];
6666
}

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,7 @@ public function testArrayDimFetchAfterArraySearch(): void
840840

841841
$this->analyse([__DIR__ . '/data/array-dim-after-array-search.php'], [
842842
[
843-
'Offset int|string might not exist on array.',
843+
'Offset int|string might not exist on non-empty-array.',
844844
20,
845845
],
846846
]);

0 commit comments

Comments
 (0)