|
45 | 45 | use PHPStan\Type\Accessory\NonEmptyArrayType; |
46 | 46 | use PHPStan\Type\ArrayType; |
47 | 47 | use PHPStan\Type\BooleanType; |
| 48 | +use PHPStan\Type\ClosureType; |
48 | 49 | use PHPStan\Type\ConditionalTypeForParameter; |
49 | 50 | use PHPStan\Type\Constant\ConstantArrayType; |
50 | 51 | use PHPStan\Type\Constant\ConstantArrayTypeBuilder; |
@@ -570,6 +571,13 @@ public function specifyTypesInCondition( |
570 | 571 | } |
571 | 572 | } |
572 | 573 |
|
| 574 | + return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); |
| 575 | + } elseif ($expr instanceof FuncCall && !($expr->name instanceof Name)) { |
| 576 | + $specifiedTypes = $this->specifyTypesFromCallableCall($context, $expr, $scope); |
| 577 | + if ($specifiedTypes !== null) { |
| 578 | + return $specifiedTypes; |
| 579 | + } |
| 580 | + |
573 | 581 | return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); |
574 | 582 | } elseif ($expr instanceof MethodCall && $expr->name instanceof Node\Identifier) { |
575 | 583 | $methodCalledOnType = $scope->getType($expr->var); |
@@ -1764,6 +1772,75 @@ static function (Type $type, callable $traverse) use ($templateTypeMap, &$contai |
1764 | 1772 | return $types; |
1765 | 1773 | } |
1766 | 1774 |
|
| 1775 | + private function specifyTypesFromCallableCall(TypeSpecifierContext $context, FuncCall $call, Scope $scope): ?SpecifiedTypes |
| 1776 | + { |
| 1777 | + if (!$call->name instanceof Expr) { |
| 1778 | + return null; |
| 1779 | + } |
| 1780 | + |
| 1781 | + $calleeType = $scope->getType($call->name); |
| 1782 | + $args = $call->getArgs(); |
| 1783 | + |
| 1784 | + $assertions = null; |
| 1785 | + $parametersAcceptor = null; |
| 1786 | + |
| 1787 | + // Check for ClosureType with assertions (from first-class callables) |
| 1788 | + if ($calleeType->isCallable()->yes()) { |
| 1789 | + foreach ($calleeType->getCallableParametersAcceptors($scope) as $variant) { |
| 1790 | + if (!$variant instanceof ClosureType) { |
| 1791 | + continue; |
| 1792 | + } |
| 1793 | + |
| 1794 | + $variantAssertions = $variant->getAsserts(); |
| 1795 | + if ($variantAssertions->getAll() === []) { |
| 1796 | + continue; |
| 1797 | + } |
| 1798 | + |
| 1799 | + $assertions = $variantAssertions; |
| 1800 | + $parametersAcceptor = $variant; |
| 1801 | + break; |
| 1802 | + } |
| 1803 | + } |
| 1804 | + |
| 1805 | + // Check for constant string callables (e.g. $f = 'is_positive_int'; $f($v)) |
| 1806 | + if ($assertions === null) { |
| 1807 | + foreach ($calleeType->getConstantStrings() as $constantString) { |
| 1808 | + if ($constantString->getValue() === '') { |
| 1809 | + continue; |
| 1810 | + } |
| 1811 | + $functionName = new Name($constantString->getValue()); |
| 1812 | + if (!$this->reflectionProvider->hasFunction($functionName, $scope)) { |
| 1813 | + continue; |
| 1814 | + } |
| 1815 | + |
| 1816 | + $functionReflection = $this->reflectionProvider->getFunction($functionName, $scope); |
| 1817 | + $functionAssertions = $functionReflection->getAsserts(); |
| 1818 | + if ($functionAssertions->getAll() === []) { |
| 1819 | + continue; |
| 1820 | + } |
| 1821 | + |
| 1822 | + $assertions = $functionAssertions; |
| 1823 | + if (count($args) > 0) { |
| 1824 | + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $args, $functionReflection->getVariants(), $functionReflection->getNamedArgumentsVariants()); |
| 1825 | + } |
| 1826 | + break; |
| 1827 | + } |
| 1828 | + } |
| 1829 | + |
| 1830 | + if ($assertions === null || $assertions->getAll() === [] || $parametersAcceptor === null) { |
| 1831 | + return null; |
| 1832 | + } |
| 1833 | + |
| 1834 | + $asserts = $assertions->mapTypes(static fn (Type $type) => TemplateTypeHelper::resolveTemplateTypes( |
| 1835 | + $type, |
| 1836 | + $parametersAcceptor->getResolvedTemplateTypeMap(), |
| 1837 | + $parametersAcceptor instanceof ExtendedParametersAcceptor ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), |
| 1838 | + TemplateTypeVariance::createInvariant(), |
| 1839 | + )); |
| 1840 | + |
| 1841 | + return $this->specifyTypesFromAsserts($context, $call, $asserts, $parametersAcceptor, $scope); |
| 1842 | + } |
| 1843 | + |
1767 | 1844 | /** |
1768 | 1845 | * @return array<string, ConditionalExpressionHolder[]> |
1769 | 1846 | */ |
|
0 commit comments