diff --git a/src/Parser/UseAliasVisitor.php b/src/Parser/UseAliasVisitor.php new file mode 100644 index 00000000000..6ddf78c723a --- /dev/null +++ b/src/Parser/UseAliasVisitor.php @@ -0,0 +1,73 @@ + alias name (original case) keyed by lowercase alias name */ + private array $explicitAliases = []; + + #[Override] + public function enterNode(Node $node): ?Node + { + if ($node instanceof Node\Stmt\Namespace_) { + $this->explicitAliases = []; + } + + if ($node instanceof Use_ && $node->type === Use_::TYPE_NORMAL) { + foreach ($node->uses as $use) { + if ($use->alias === null) { + continue; + } + + $this->explicitAliases[strtolower($use->alias->name)] = $use->alias->name; + } + } + + if ($node instanceof GroupUse) { + foreach ($node->uses as $use) { + if ($use->type !== Use_::TYPE_NORMAL && $node->type !== Use_::TYPE_NORMAL) { + continue; + } + if ($use->alias === null) { + continue; + } + + $this->explicitAliases[strtolower($use->alias->name)] = $use->alias->name; + } + } + + if ($node instanceof Name) { + $originalName = $node->getAttribute('originalName'); + if ($originalName instanceof Name) { + $originalParts = $originalName->getParts(); + if (count($originalParts) === 1) { + $lowerOriginal = strtolower($originalParts[0]); + if ( + isset($this->explicitAliases[$lowerOriginal]) + && $this->explicitAliases[$lowerOriginal] === $originalParts[0] + ) { + $node->setAttribute(self::ATTRIBUTE_NAME, true); + } + } + } + } + + return null; + } + +} diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 6ef8f74b158..d08f598a779 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -19,6 +19,7 @@ use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Printer\NodeTypePrinter; +use PHPStan\Parser\UseAliasVisitor; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ExtendedParameterReflection; use PHPStan\Reflection\ExtendedParametersAcceptor; @@ -852,6 +853,10 @@ private function getOriginalClassNamePairsFromTypeNode(Identifier|Name|ComplexTy return []; } + if ($typeNode->getAttribute(UseAliasVisitor::ATTRIBUTE_NAME) === true) { + return []; + } + $resolvedName = $typeNode->toString(); $originalParts = $originalName->getParts(); $resolvedParts = $typeNode->getParts(); @@ -866,11 +871,6 @@ private function getOriginalClassNamePairsFromTypeNode(Identifier|Name|ComplexTy $originalCaseClassName = $originalName->toString(); } - if (strtolower($originalCaseClassName) !== strtolower($resolvedName)) { - // use alias, not just a case difference - return []; - } - if ($originalCaseClassName === $resolvedName) { return []; } diff --git a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php index 75d808498f4..dd08b8562da 100644 --- a/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ExistingClassesInTypehintsRuleTest.php @@ -662,4 +662,9 @@ public function testBug14205(): void $this->analyse([__DIR__ . '/data/bug-14205.php'], []); } + public function testBug14617(): void + { + $this->analyse([__DIR__ . '/data/bug-14617.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-14617.php b/tests/PHPStan/Rules/Methods/data/bug-14617.php new file mode 100644 index 00000000000..8a94720469f --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14617.php @@ -0,0 +1,19 @@ +