Skip class name case check for type hints using explicit use ... as aliases#5671
Skip class name case check for type hints using explicit use ... as aliases#5671phpstan-bot wants to merge 10 commits into
use ... as aliases#5671Conversation
… aliases - Add `UseAliasVisitor` parser visitor that tracks explicit `use ... as` aliases and sets an `isExplicitUseAlias` attribute on resolved Name nodes whose original name matches an explicit alias exactly (case-sensitively) - In `FunctionDefinitionCheck::getOriginalClassNamePairsFromTypeNode()`, skip the case sensitivity check when the Name node is marked as an explicit alias - The false positive occurred because the method reconstructed the original-case class name by combining the resolved namespace prefix with the original alias name, producing e.g. `Foo\myclass` when the alias was `myclass` for `Foo\MyClass`, which was then incorrectly flagged as a case mismatch - Verified that analogous contexts (new, instanceof, catch, extends, implements, trait use, static access, PHPDoc types) are not affected because they pass the fully-resolved class name directly to `ClassCaseSensitivityCheck`
VincentLanglet
left a comment
There was a problem hiding this comment.
The condition
if (strtolower($originalCaseClassName) !== strtolower($resolvedName)) {
// use alias, not just a case difference
return [];
}
should not be needed anymore
The UseAliasVisitor now explicitly tracks `use ... as` aliases, making the old heuristic that compared lowercase class names unnecessary. The old check was a broad approximation; the new attribute-based approach is precise. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Done. Removed the redundant |
VincentLanglet
left a comment
There was a problem hiding this comment.
Shouldn't the fix be in the ClassCaseSensitivityCheck which already has the incomplete check
if (strtolower($realClassName) !== strtolower($className)) {
continue; // skip class alias
}
The isExplicitUseAlias attribute check belongs in ClassCaseSensitivityCheck (where the case error is generated) rather than in FunctionDefinitionCheck (where class name pairs are constructed). This centralizes the alias handling alongside the existing strtolower alias skip. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Done. Moved the |
VincentLanglet
left a comment
There was a problem hiding this comment.
Add more test case which wasn't fixed by the previous fix in FunctionDefinitionCheck
| if (strtolower($realClassName) !== strtolower($className)) { | ||
| continue; // skip class alias | ||
| } |
There was a problem hiding this comment.
Should/Could this if be removed too ?
There was a problem hiding this comment.
Here's a summary of what was done to address the review:
The strtolower check cannot be removed. I verified this by temporarily removing it and running the full test suite — ExistingClassesInClosureTypehintsRuleTest::testExistingClassInTypehint fails because line 35 of closure-typehints.php uses \ReturnTypes\FooAliaS, which is a class_alias() for ReturnTypes\Foo. Without the strtolower check, PHPStan incorrectly reports "Class ReturnTypes\Foo referenced with incorrect case: ReturnTypes\FooAliaS" — a false positive because FooAliaS is a completely different name (a class_alias), not a case mismatch.
The UseAliasVisitor only tracks use ... as aliases in the parser. The strtolower check handles class_alias() scenarios where the reflection-resolved class name is entirely different from the written name.
Changes made:
- Clarified the comment on the
strtolowercheck to explain it handlesclass_alias()specifically - Added closure test coverage (
bug-14617-closure.php+ test inExistingClassesInClosureTypehintsRuleTest) - Added nullable and union type test cases to the existing
bug-14617.php
All 12,076 tests pass and make phpstan reports no errors.
…eof, and property types These contexts go through ClassCaseSensitivityCheck but not through FunctionDefinitionCheck, so they weren't covered by the previous strtolower heuristic fix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Done. Added test cases for explicit
These contexts weren't covered by the previous |
The strtolower check in ClassCaseSensitivityCheck cannot be removed because it handles class_alias() scenarios where the alias name is completely different from the real class name (e.g., FooAlias for Foo). UseAliasVisitor only tracks `use ... as` aliases, not class_alias(). Removing it causes false positives like "Class Foo referenced with incorrect case: FooAliaS" for class_alias targets. Clarified the comment to explain what the check handles. Added test coverage for closures using explicit use aliases, and for nullable/union type hints with aliases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VincentLanglet
left a comment
There was a problem hiding this comment.
Does it mean we don't avoid false positive with something like ?
class_alias('Foo', 'foo');
?
VincentLanglet
left a comment
There was a problem hiding this comment.
Do we already have test for the
if (strtolower($realClassName) !== strtolower($className)) {
continue; // skip class_alias() where the alias is a completely different name
}
case ?
VincentLanglet
left a comment
There was a problem hiding this comment.
Skip ExistingClassesInTypehintsRuleTest::testBug14617 for PHP < 8
Adds a dedicated test that verifies the strtolower check in ClassCaseSensitivityCheck correctly skips class_alias() names that are completely different from the original class name. Previously this was only implicitly covered by line 35 of closure-typehints.php using \ReturnTypes\FooAliaS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test data uses union types (myclass|string, myclass|int) which require PHP 8.0+ to parse. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Done. Added |
|
All 12,081 tests pass (79,707 assertions). The commit has been pushed. Summary: Added an explicit test ( |
|
The branch is already up to date with the remote — no new commits to push. Summary: The reviewer asked whether |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VincentLanglet
left a comment
There was a problem hiding this comment.
Add back test case but just with
$callback = function (\ReturnTypes\FooAlias $a): \ReturnTypes\FooAlias {
return $a;
};
and not the callback2
Re-adds the test for class_alias() with FooAlias using correct case, without locking in the wrong-case behavior that may be tightened later. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Done. Added back the |
Covers the GroupUse code path in UseAliasVisitor that was not
exercised by existing tests. Tests use `use Ns\{A as a, B as b}`
syntax in both closure and method typehint contexts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
When a class is imported with an explicit alias via
use Foo\MyClass as myclass, PHPStan incorrectly reported "Class Foo\MyClass referenced with incorrect case: Foo\myclass" on type hints using the alias. The alias name is intentionally chosen by the user and should not trigger a case sensitivity error.Changes
src/Parser/UseAliasVisitor.php— a new parser visitor that:use ... asaliases (both regular and group use statements)isExplicitUseAliasattribute when the original name matches an explicit alias case-sensitivelysrc/Rules/FunctionDefinitionCheck.phpto check theisExplicitUseAliasattribute ingetOriginalClassNamePairsFromTypeNode()and skip the case sensitivity check for explicit aliasesRoot cause
FunctionDefinitionCheck::getOriginalClassNamePairsFromTypeNode()reconstructs the "original case" class name by combining the namespace prefix from the resolved name with the original name parts. Foruse Foo\MyClass as myclass, the original namemyclasswas combined with prefixFooto produceFoo\myclass. Sincestrtolower('Foo\myclass') === strtolower('Foo\MyClass'), the existing alias detection (which only catches aliases with entirely different names) did not fire, and the name was incorrectly flagged as a case mismatch.The fix distinguishes explicit
use ... asaliases from wrong-case references by tracking which aliases were explicitly defined via theaskeyword inusestatements.Analogous cases probed
The following contexts were tested and confirmed to NOT be affected (they pass the fully-resolved class name to
ClassCaseSensitivityCheck, bypassing the problematic reconstruction logic):newexpressions (InstantiationRule)instanceofexpressions (ExistingClassInInstanceOfRule)catchclauses (CaughtExceptionExistenceRule)ClassConstantRule)AccessStaticPropertiesCheck)StaticMethodCallCheck)ExistingClassesInPropertiesRule)@param,@return)Test
Added
tests/PHPStan/Rules/Methods/data/bug-14617.phpwith a regression test covering:newexpression with explicit alias (viaInstantiationRule, verified no error)Fixes phpstan/phpstan#14617