|
19 | 19 | use PhpParser\Node\Expr\StaticPropertyFetch; |
20 | 20 | use PhpParser\Node\Name; |
21 | 21 | use PHPStan\Reflection\ReflectionProvider; |
| 22 | +use PHPStan\TrinaryLogic; |
22 | 23 | use PHPStan\Type\Accessory\HasOffsetType; |
23 | 24 | use PHPStan\Type\Accessory\HasPropertyType; |
24 | 25 | use PHPStan\Type\Accessory\NonEmptyArrayType; |
@@ -578,11 +579,21 @@ public function specifyTypesInCondition( |
578 | 579 | } elseif ($expr instanceof BooleanAnd || $expr instanceof LogicalAnd) { |
579 | 580 | $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context); |
580 | 581 | $rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context); |
581 | | - return $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->intersectWith($rightTypes); |
| 582 | + $types = $context->true() ? $leftTypes->unionWith($rightTypes) : $leftTypes->intersectWith($rightTypes); |
| 583 | + if ($context->false()) { |
| 584 | + return $this->processBooleanConditionalTypes($scope, $types, $leftTypes, $rightTypes); |
| 585 | + } |
| 586 | + |
| 587 | + return $types; |
582 | 588 | } elseif ($expr instanceof BooleanOr || $expr instanceof LogicalOr) { |
583 | 589 | $leftTypes = $this->specifyTypesInCondition($scope, $expr->left, $context); |
584 | 590 | $rightTypes = $this->specifyTypesInCondition($scope, $expr->right, $context); |
585 | | - return $context->true() ? $leftTypes->intersectWith($rightTypes) : $leftTypes->unionWith($rightTypes); |
| 591 | + $types = $context->true() ? $leftTypes->intersectWith($rightTypes) : $leftTypes->unionWith($rightTypes); |
| 592 | + if ($context->true()) { |
| 593 | + return $this->processBooleanConditionalTypes($scope, $types, $leftTypes, $rightTypes); |
| 594 | + } |
| 595 | + |
| 596 | + return $types; |
586 | 597 | } elseif ($expr instanceof Node\Expr\BooleanNot && !$context->null()) { |
587 | 598 | return $this->specifyTypesInCondition($scope, $expr->expr, $context->negate()); |
588 | 599 | } elseif ($expr instanceof Node\Expr\Assign) { |
@@ -744,6 +755,46 @@ private function handleDefaultTruthyOrFalseyContext(TypeSpecifierContext $contex |
744 | 755 | return new SpecifiedTypes(); |
745 | 756 | } |
746 | 757 |
|
| 758 | + private function processBooleanConditionalTypes(Scope $scope, SpecifiedTypes $types, SpecifiedTypes $leftTypes, SpecifiedTypes $rightTypes): SpecifiedTypes |
| 759 | + { |
| 760 | + $conditionExpressionTypes = []; |
| 761 | + foreach ($leftTypes->getSureNotTypes() as $exprString => [$expr, $type]) { |
| 762 | + if (!$expr instanceof Expr\Variable) { |
| 763 | + continue; |
| 764 | + } |
| 765 | + if (!is_string($expr->name)) { |
| 766 | + continue; |
| 767 | + } |
| 768 | + |
| 769 | + $conditionExpressionTypes[$exprString] = TypeCombinator::intersect($scope->getType($expr), $type); |
| 770 | + } |
| 771 | + |
| 772 | + if (count($conditionExpressionTypes) > 0) { |
| 773 | + $holders = []; |
| 774 | + foreach ($rightTypes->getSureNotTypes() as $exprString => [$expr, $type]) { |
| 775 | + if (!$expr instanceof Expr\Variable) { |
| 776 | + continue; |
| 777 | + } |
| 778 | + if (!is_string($expr->name)) { |
| 779 | + continue; |
| 780 | + } |
| 781 | + |
| 782 | + if (!isset($holders[$exprString])) { |
| 783 | + $holders[$exprString] = []; |
| 784 | + } |
| 785 | + |
| 786 | + $holders[$exprString][] = new ConditionalExpressionHolder( |
| 787 | + $conditionExpressionTypes, |
| 788 | + new VariableTypeHolder(TypeCombinator::remove($scope->getType($expr), $type), TrinaryLogic::createYes()) // todo yes is wrong |
| 789 | + ); |
| 790 | + } |
| 791 | + |
| 792 | + return new SpecifiedTypes($types->getSureTypes(), $types->getSureNotTypes(), false, $holders); |
| 793 | + } |
| 794 | + |
| 795 | + return $types; |
| 796 | + } |
| 797 | + |
747 | 798 | /** |
748 | 799 | * @param \PHPStan\Analyser\Scope $scope |
749 | 800 | * @param \PhpParser\Node\Expr\BinaryOp $binaryOperation |
|
0 commit comments