Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3c644c5

Browse files
phpstan-botclaude
andauthored
Skip class name case check for type hints using explicit use ... as aliases (#5671)
Co-authored-by: Claude Opus 4.6 <[email protected]>
1 parent c9d33e8 commit 3c644c5

21 files changed

Lines changed: 270 additions & 6 deletions

src/Parser/UseAliasVisitor.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use Override;
6+
use PhpParser\Node;
7+
use PhpParser\Node\Name;
8+
use PhpParser\Node\Stmt\GroupUse;
9+
use PhpParser\Node\Stmt\Use_;
10+
use PhpParser\NodeVisitorAbstract;
11+
use PHPStan\DependencyInjection\AutowiredService;
12+
use function count;
13+
use function strtolower;
14+
15+
#[AutowiredService]
16+
final class UseAliasVisitor extends NodeVisitorAbstract
17+
{
18+
19+
public const ATTRIBUTE_NAME = 'isExplicitUseAlias';
20+
21+
/** @var array<string, string> alias name (original case) keyed by lowercase alias name */
22+
private array $explicitAliases = [];
23+
24+
#[Override]
25+
public function enterNode(Node $node): ?Node
26+
{
27+
if ($node instanceof Node\Stmt\Namespace_) {
28+
$this->explicitAliases = [];
29+
}
30+
31+
if ($node instanceof Use_ && $node->type === Use_::TYPE_NORMAL) {
32+
foreach ($node->uses as $use) {
33+
if ($use->alias === null) {
34+
continue;
35+
}
36+
37+
$this->explicitAliases[strtolower($use->alias->name)] = $use->alias->name;
38+
}
39+
}
40+
41+
if ($node instanceof GroupUse) {
42+
foreach ($node->uses as $use) {
43+
if ($use->type !== Use_::TYPE_NORMAL && $node->type !== Use_::TYPE_NORMAL) {
44+
continue;
45+
}
46+
if ($use->alias === null) {
47+
continue;
48+
}
49+
50+
$this->explicitAliases[strtolower($use->alias->name)] = $use->alias->name;
51+
}
52+
}
53+
54+
if ($node instanceof Name) {
55+
$originalName = $node->getAttribute('originalName');
56+
if ($originalName instanceof Name) {
57+
$originalParts = $originalName->getParts();
58+
if (count($originalParts) === 1) {
59+
$lowerOriginal = strtolower($originalParts[0]);
60+
if (
61+
isset($this->explicitAliases[$lowerOriginal])
62+
&& $this->explicitAliases[$lowerOriginal] === $originalParts[0]
63+
) {
64+
$node->setAttribute(self::ATTRIBUTE_NAME, true);
65+
}
66+
}
67+
}
68+
}
69+
70+
return null;
71+
}
72+
73+
}

src/Rules/ClassCaseSensitivityCheck.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\DependencyInjection\AutowiredParameter;
66
use PHPStan\DependencyInjection\AutowiredService;
7+
use PHPStan\Parser\UseAliasVisitor;
78
use PHPStan\Reflection\ReflectionProvider;
89
use function sprintf;
910
use function strtolower;
@@ -38,7 +39,10 @@ public function checkClassNames(array $pairs): array
3839
}
3940
$realClassName = $classReflection->getName();
4041
if (strtolower($realClassName) !== strtolower($className)) {
41-
continue; // skip class alias
42+
continue; // skip class_alias() where the alias is a completely different name
43+
}
44+
if ($pair->getNode()->getAttribute(UseAliasVisitor::ATTRIBUTE_NAME) === true) {
45+
continue;
4246
}
4347
if ($realClassName === $className) {
4448
continue;

src/Rules/FunctionDefinitionCheck.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -866,11 +866,6 @@ private function getOriginalClassNamePairsFromTypeNode(Identifier|Name|ComplexTy
866866
$originalCaseClassName = $originalName->toString();
867867
}
868868

869-
if (strtolower($originalCaseClassName) !== strtolower($resolvedName)) {
870-
// use alias, not just a case difference
871-
return [];
872-
}
873-
874869
if ($originalCaseClassName === $resolvedName) {
875870
return [];
876871
}

tests/PHPStan/Rules/Classes/ExistingClassInClassExtendsRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,9 @@ public function testReadonly(): void
153153
]);
154154
}
155155

156+
public function testBug14617(): void
157+
{
158+
$this->analyse([__DIR__ . '/data/bug-14617.php'], []);
159+
}
160+
156161
}

tests/PHPStan/Rules/Classes/ExistingClassInInstanceOfRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,9 @@ public function testRememberClassExistsFromConstructor(): void
8686
$this->analyse([__DIR__ . '/data/remember-class-exists-from-constructor.php'], []);
8787
}
8888

89+
public function testBug14617(): void
90+
{
91+
$this->analyse([__DIR__ . '/data/bug-14617.php'], []);
92+
}
93+
8994
}

tests/PHPStan/Rules/Classes/ExistingClassesInClassImplementsRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,9 @@ public function testBug8889(): void
9595
]);
9696
}
9797

98+
public function testBug14617(): void
99+
{
100+
$this->analyse([__DIR__ . '/data/bug-14617.php'], []);
101+
}
102+
98103
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14617Classes;
4+
5+
class MyClass {}
6+
7+
interface MyInterface {}
8+
9+
namespace Bug14617Classes\Consumer;
10+
11+
use Bug14617Classes\MyClass as myclass;
12+
use Bug14617Classes\MyInterface as myinterface;
13+
14+
class Foo extends myclass implements myinterface {
15+
public myclass $prop;
16+
}
17+
18+
function test(mixed $x): void {
19+
if ($x instanceof myclass) {
20+
}
21+
}

tests/PHPStan/Rules/Functions/CallToNonExistentFunctionRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public function testCallToIncorrectCaseFunctionName(): void
6969
]);
7070
}
7171

72+
public function testBug14617UseFunctionAlias(): void
73+
{
74+
$this->analyse([__DIR__ . '/data/bug-14617-use-function-alias.php'], []);
75+
}
76+
7277
public function testMatchExprAnalysis(): void
7378
{
7479
$this->analyse([__DIR__ . '/data/match-expr-analysis.php'], [

tests/PHPStan/Rules/Functions/ExistingClassesInClosureTypehintsRuleTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ public function testExistingClassInTypehint(): void
7373
]);
7474
}
7575

76+
public function testBug14617(): void
77+
{
78+
$this->analyse([__DIR__ . '/data/bug-14617-closure.php'], []);
79+
}
80+
81+
public function testBug14617GroupUse(): void
82+
{
83+
$this->analyse([__DIR__ . '/data/bug-14617-group-use.php'], []);
84+
}
85+
86+
public function testClassAliasCaseSensitivity(): void
87+
{
88+
$this->analyse([__DIR__ . '/data/class-alias-case-sensitivity.php'], []);
89+
}
90+
7691
public function testValidTypehintPhp71(): void
7792
{
7893
$this->analyse([__DIR__ . '/data/closure-7.1-typehints.php'], [
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug14617Closure;
4+
5+
class MyClass {}
6+
7+
namespace Bug14617Closure\Consumer;
8+
9+
use Bug14617Closure\MyClass as myclass;
10+
11+
$callback = function (myclass $a): myclass {
12+
return $a;
13+
};

0 commit comments

Comments
 (0)