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/ClassCaseSensitivityCheck.php b/src/Rules/ClassCaseSensitivityCheck.php index bec217c30f3..788153ff245 100644 --- a/src/Rules/ClassCaseSensitivityCheck.php +++ b/src/Rules/ClassCaseSensitivityCheck.php @@ -4,6 +4,7 @@ use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\Parser\UseAliasVisitor; use PHPStan\Reflection\ReflectionProvider; use function sprintf; use function strtolower; @@ -38,7 +39,10 @@ public function checkClassNames(array $pairs): array } $realClassName = $classReflection->getName(); if (strtolower($realClassName) !== strtolower($className)) { - continue; // skip class alias + continue; // skip class_alias() where the alias is a completely different name + } + if ($pair->getNode()->getAttribute(UseAliasVisitor::ATTRIBUTE_NAME) === true) { + continue; } if ($realClassName === $className) { continue; diff --git a/src/Rules/FunctionDefinitionCheck.php b/src/Rules/FunctionDefinitionCheck.php index 6ef8f74b158..c888ed886f0 100644 --- a/src/Rules/FunctionDefinitionCheck.php +++ b/src/Rules/FunctionDefinitionCheck.php @@ -866,11 +866,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/Classes/ExistingClassInClassExtendsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php index f73cf3c97c2..f7b9da55c9c 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php @@ -153,4 +153,9 @@ public function testReadonly(): void ]); } + public function testBug14617(): void + { + $this->analyse([__DIR__ . '/data/bug-14617.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php index 85fdebcb5aa..30512a74c3c 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php @@ -86,4 +86,9 @@ public function testRememberClassExistsFromConstructor(): void $this->analyse([__DIR__ . '/data/remember-class-exists-from-constructor.php'], []); } + public function testBug14617(): void + { + $this->analyse([__DIR__ . '/data/bug-14617.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php index d1c031f752b..cef3fee647d 100644 --- a/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php @@ -95,4 +95,9 @@ public function testBug8889(): void ]); } + public function testBug14617(): void + { + $this->analyse([__DIR__ . '/data/bug-14617.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-14617.php b/tests/PHPStan/Rules/Classes/data/bug-14617.php new file mode 100644 index 00000000000..2398a9a70fc --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-14617.php @@ -0,0 +1,21 @@ +analyse([__DIR__ . '/data/bug-14617-closure.php'], []); + } + + public function testBug14617GroupUse(): void + { + $this->analyse([__DIR__ . '/data/bug-14617-group-use.php'], []); + } + + public function testClassAliasCaseSensitivity(): void + { + $this->analyse([__DIR__ . '/data/class-alias-case-sensitivity.php'], []); + } + public function testValidTypehintPhp71(): void { $this->analyse([__DIR__ . '/data/closure-7.1-typehints.php'], [ diff --git a/tests/PHPStan/Rules/Functions/data/bug-14617-closure.php b/tests/PHPStan/Rules/Functions/data/bug-14617-closure.php new file mode 100644 index 00000000000..8e2d87a2aca --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-14617-closure.php @@ -0,0 +1,13 @@ +analyse([__DIR__ . '/data/bug-14205.php'], []); } + #[RequiresPhp('>= 8.0.0')] + public function testBug14617(): void + { + $this->analyse([__DIR__ . '/data/bug-14617.php'], []); + } + + public function testBug14617GroupUse(): void + { + $this->analyse([__DIR__ . '/data/bug-14617-group-use.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-14617-group-use.php b/tests/PHPStan/Rules/Methods/data/bug-14617-group-use.php new file mode 100644 index 00000000000..607531d9a70 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14617-group-use.php @@ -0,0 +1,16 @@ +analyse([__DIR__ . '/data/intersection-types.php'], $errors); } + public function testBug14617(): void + { + $this->analyse([__DIR__ . '/../Classes/data/bug-14617.php'], []); + } + }