diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index c016b2869e..1296a67968 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -519,19 +519,28 @@ public function specifyTypesInCondition( $methodReflection = $scope->getMethodReflection($methodCalledOnType, $expr->name->name); if ($methodReflection !== null) { $referencedClasses = $methodCalledOnType->getObjectClassNames(); - if ( - count($referencedClasses) === 1 - && $this->reflectionProvider->hasClass($referencedClasses[0]) - ) { - $methodClassReflection = $this->reflectionProvider->getClass($referencedClasses[0]); + $specifiedTypes = null; + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $methodClassReflection = $this->reflectionProvider->getClass($referencedClass); foreach ($this->getMethodTypeSpecifyingExtensionsForClass($methodClassReflection->getName()) as $extension) { if (!$extension->isMethodSupported($methodReflection, $expr, $context)) { continue; } - return $extension->specifyTypes($methodReflection, $expr, $scope, $context); + if ($specifiedTypes !== null) { + $specifiedTypes = $specifiedTypes->unionWith($extension->specifyTypes($methodReflection, $expr, $scope, $context)); + } else { + $specifiedTypes = $extension->specifyTypes($methodReflection, $expr, $scope, $context); + } } } + if ($specifiedTypes !== null) { + return $specifiedTypes; + } // lazy create parametersAcceptor, as creation can be expensive $parametersAcceptor = null; @@ -572,19 +581,28 @@ public function specifyTypesInCondition( $staticMethodReflection = $scope->getMethodReflection($calleeType, $expr->name->name); if ($staticMethodReflection !== null) { $referencedClasses = $calleeType->getObjectClassNames(); - if ( - count($referencedClasses) === 1 - && $this->reflectionProvider->hasClass($referencedClasses[0]) - ) { - $staticMethodClassReflection = $this->reflectionProvider->getClass($referencedClasses[0]); + $specifiedTypes = null; + foreach ($referencedClasses as $referencedClass) { + if (!$this->reflectionProvider->hasClass($referencedClass)) { + continue; + } + + $staticMethodClassReflection = $this->reflectionProvider->getClass($referencedClass); foreach ($this->getStaticMethodTypeSpecifyingExtensionsForClass($staticMethodClassReflection->getName()) as $extension) { if (!$extension->isStaticMethodSupported($staticMethodReflection, $expr, $context)) { continue; } - return $extension->specifyTypes($staticMethodReflection, $expr, $scope, $context); + if ($specifiedTypes !== null) { + $specifiedTypes = $specifiedTypes->unionWith($extension->specifyTypes($staticMethodReflection, $expr, $scope, $context)); + } else { + $specifiedTypes = $extension->specifyTypes($staticMethodReflection, $expr, $scope, $context); + } } } + if ($specifiedTypes !== null) { + return $specifiedTypes; + } // lazy create parametersAcceptor, as creation can be expensive $parametersAcceptor = null; diff --git a/tests/PHPStan/Analyser/MultipleTypeSpecifyingExtension.neon b/tests/PHPStan/Analyser/MultipleTypeSpecifyingExtension.neon new file mode 100644 index 0000000000..98b803ee5e --- /dev/null +++ b/tests/PHPStan/Analyser/MultipleTypeSpecifyingExtension.neon @@ -0,0 +1,21 @@ +services: + - + class: PHPStan\Tests\AssertionClassMethodTypeSpecifyingExtension + arguments: + nullContext: true + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension + - + class: PHPStan\Tests\AssertionClassStaticMethodTypeSpecifyingExtension + arguments: + nullContext: true + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension + - + class: PHPStan\Tests\AssertionClassMethodMultipleTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension + - + class: PHPStan\Tests\AssertionClassStaticMethodMultipleTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension diff --git a/tests/PHPStan/Analyser/MultipleTypeSpecifyingExtensionTypeInferenceTest.php b/tests/PHPStan/Analyser/MultipleTypeSpecifyingExtensionTypeInferenceTest.php new file mode 100644 index 0000000000..20934574ca --- /dev/null +++ b/tests/PHPStan/Analyser/MultipleTypeSpecifyingExtensionTypeInferenceTest.php @@ -0,0 +1,35 @@ +gatherAssertTypes(__DIR__ . '/data/multiple-type-specifying-extensions-1.php'); + } + + /** + * @dataProvider dataTypeSpecifyingExtensionsTrue + * @param mixed ...$args + */ + public function testTypeSpecifyingExtensionsTrue( + string $assertType, + string $file, + ...$args, + ): void + { + $this->assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/MultipleTypeSpecifyingExtension.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/data/multiple-type-specifying-extensions-1.php b/tests/PHPStan/Analyser/data/multiple-type-specifying-extensions-1.php new file mode 100644 index 0000000000..0231eccdf0 --- /dev/null +++ b/tests/PHPStan/Analyser/data/multiple-type-specifying-extensions-1.php @@ -0,0 +1,16 @@ +assertString($foo); +\PHPStan\Tests\AssertionClass::assertInt($bar); + +assertType('non-empty-string', $foo); +assertType('int<1, max>', $bar); + diff --git a/tests/PHPStan/Tests/AssertionClassMethodMultipleTypeSpecifyingExtension.php b/tests/PHPStan/Tests/AssertionClassMethodMultipleTypeSpecifyingExtension.php new file mode 100644 index 0000000000..0aa5b16adc --- /dev/null +++ b/tests/PHPStan/Tests/AssertionClassMethodMultipleTypeSpecifyingExtension.php @@ -0,0 +1,40 @@ +getName() === 'assertString'; + } + + public function specifyTypes( + MethodReflection $methodReflection, + MethodCall $node, + Scope $scope, + TypeSpecifierContext $context, + ): SpecifiedTypes + { + return new SpecifiedTypes(['$foo' => [$node->getArgs()[0]->value, new AccessoryNonEmptyStringType()]]); + } + +} diff --git a/tests/PHPStan/Tests/AssertionClassStaticMethodMultipleTypeSpecifyingExtension.php b/tests/PHPStan/Tests/AssertionClassStaticMethodMultipleTypeSpecifyingExtension.php new file mode 100644 index 0000000000..d5de5a1323 --- /dev/null +++ b/tests/PHPStan/Tests/AssertionClassStaticMethodMultipleTypeSpecifyingExtension.php @@ -0,0 +1,40 @@ +getName() === 'assertInt'; + } + + public function specifyTypes( + MethodReflection $staticMethodReflection, + StaticCall $node, + Scope $scope, + TypeSpecifierContext $context, + ): SpecifiedTypes + { + return new SpecifiedTypes(['$foo' => [$node->getArgs()[0]->value, IntegerRangeType::createAllGreaterThan(0)]]); + } + +}