fix(php): a namespace alias matches whatever case it is written in

PHP reads namespace names without regard to case, so after
use App\Fields as Field; the name field\FirstName is the same class as
Field\FirstName. The qualified-name resolver compared the alias as
written, found no import, and read the name relative to the file's own
namespace instead, so new field\FirstName() stayed unlinked. The alias
is now matched without regard to case.

This lands the first half of the contributor's PR. Its second half
refused an alias that two namespace { } blocks of one file bind to
different namespaces. That also dropped the first block's correct edge,
because the use map is file-wide, and the case doesn't occur in real
code: across 8 PHP repos (33,820 files), 185 files declare more than
one namespace, and none binds one alias twice outside strings. The fix
for those files is to scope each use statement to its namespace block,
which needs block positions the index doesn't keep yet: a braced
namespace gets no namespace node, and neither does a second unbraced
one, so it is left for its own change.

Validation: the new test fails on main and passes here, with the
native kernel and with CODEGRAPH_KERNEL=0. Graphs of php-parser,
composer, psalm, monica, laravel and phpunit are byte-identical to
main, as are the controls express and flask. Index CPU on laravel,
median of 3 interleaved: 53.3 s on main, 52.9 s here. The PHP and
Laravel suites pass in both engines (10 files, 67 tests).

Original PR #2369 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
danusha2345
2026-10-23 17:39:09 +00:00
committed by Colby Mchenry
co-authored by Claude Opus 5.5
parent 33dc2ae72b
commit 5c5bfd4051
3 changed files with 11 additions and 2 deletions
+3 -2
View File
@@ -2722,8 +2722,9 @@ export function resolvePhpQualifiedClassRef(
if (separator === 0) {
fqn = name.slice(1);
} else {
const head = name.slice(0, separator);
const imp = context.getImportMappings(ref.filePath, ref.language).find((i) => i.localName === head);
// PHP matches a namespace alias without regard to case.
const head = name.slice(0, separator).toLowerCase();
const imp = context.getImportMappings(ref.filePath, ref.language).find((i) => i.localName.toLowerCase() === head);
if (imp) {
fqn = imp.source.replace(/^\\/, '') + name.slice(separator);
} else {