diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 47101ea9fe..fdae76ab53 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1617,12 +1617,6 @@ parameters: count: 1 path: src/Type/Php/MbSubstituteCharacterDynamicReturnTypeExtension.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Type/Php/MethodExistsTypeSpecifyingExtension.php - - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType diff --git a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php index 3db54618e5..0e18dc0629 100644 --- a/src/Type/Php/MethodExistsTypeSpecifyingExtension.php +++ b/src/Type/Php/MethodExistsTypeSpecifyingExtension.php @@ -14,11 +14,9 @@ use PHPStan\Type\Accessory\HasMethodType; use PHPStan\Type\ClassStringType; use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\FunctionTypeSpecifyingExtension; -use PHPStan\Type\IntersectionType; use PHPStan\Type\ObjectWithoutClassType; -use PHPStan\Type\UnionType; +use PHPStan\Type\TypeCombinator; use function count; #[AutowiredService] @@ -50,45 +48,57 @@ public function specifyTypes( TypeSpecifierContext $context, ): SpecifiedTypes { - $methodNameType = $scope->getType($node->getArgs()[1]->value); - if (!$methodNameType instanceof ConstantStringType) { - return $this->typeSpecifier->create( - new FuncCall(new FullyQualified('method_exists'), $node->getRawArgs()), - new ConstantBooleanType(true), - $context, - $scope, - ); + $specifiedTypes = $this->typeSpecifier->create( + new FuncCall(new FullyQualified('method_exists'), $node->getRawArgs()), + new ConstantBooleanType(true), + $context, + $scope, + ); + + $methodNameTypes = $scope->getType($node->getArgs()[1]->value)->getConstantStrings(); + if ($methodNameTypes === []) { + return $specifiedTypes; } $objectType = $scope->getType($node->getArgs()[0]->value); if ($objectType->isString()->yes()) { if ($objectType->isClassString()->yes()) { - return $this->typeSpecifier->create( - $node->getArgs()[0]->value, - new IntersectionType([ + $types = []; + foreach ($methodNameTypes as $methodNameType) { + $types[] = TypeCombinator::intersect( $objectType, new HasMethodType($methodNameType->getValue()), - ]), + ); + } + + return $specifiedTypes->unionWith($this->typeSpecifier->create( + $node->getArgs()[0]->value, + TypeCombinator::union(...$types), $context, $scope, - ); + )); } - return new SpecifiedTypes([], []); + return $specifiedTypes; } - return $this->typeSpecifier->create( - $node->getArgs()[0]->value, - new UnionType([ - new IntersectionType([ + $types = []; + foreach ($methodNameTypes as $methodNameType) { + $types[] = TypeCombinator::union( + TypeCombinator::intersect( new ObjectWithoutClassType(), new HasMethodType($methodNameType->getValue()), - ]), + ), new ClassStringType(), - ]), + ); + } + + return $specifiedTypes->unionWith($this->typeSpecifier->create( + $node->getArgs()[0]->value, + TypeCombinator::union(...$types), $context, $scope, - ); + )); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-13272.php b/tests/PHPStan/Analyser/nsrt/bug-13272.php new file mode 100644 index 0000000000..8e653af27c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13272.php @@ -0,0 +1,32 @@ +