mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
fix(php): inject FQN-keyed module-scope bindings (Codex #1497 finding 1)
Extend populatePhpNamespaceSiblings with Step 3b: for every PHP file's Module scope, inject a binding entry keyed by the fully-qualified class name (`App\Models\User`) for every class-like def in the workspace. This routes FQN-receivers like `\App\Other\User` to the exact namespace-qualified class regardless of which simple-name `User` the caller's `use` imports shadowed. Why module-scope bindingAugmentations instead of mutating def.qualifiedName: the shared QualifiedNameIndex consumes def.qualifiedName at finalize time, but PHP class defs need to remain keyed by simple name throughout the rest of the pipeline (MRO, method-dispatch-index, namespace-siblings step 3). An earlier attempt to rewrite def.qualifiedName to namespace-prefixed form cascaded into 32 unrelated test failures across receiver-binding, MRO, and heritage. The bindingAugmentations channel is purpose-built for adding post-finalize visibility without mutating shared semantic state, and `findClassBindingInScope`'s scope-chain walk already consumes it via `lookupBindingsAt` — wiring is zero-touch. Cost: O(PHP files × class-like defs) augmentation entries. Typical PHP project: hundreds × hundreds = bounded. Verified locally: - Registry-primary: 178/178 PHP tests pass (including the new FQN regression). - Legacy DAG: 174 passed, 3 existing skips, 1 failure (the FQN test, expected — legacy DAG has no namespace augmentation channel; U5 registers it in LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES.php). - Cross-language: 794/794 (C#, Python, TypeScript, Go, C) pass. The PHP-only augmentation does not touch shared resolution code. - tsc clean. Plan: docs/plans/2026-05-11-002-fix-php-fqn-and-overload-codex-findings-plan.md (U3)
This commit is contained in:
parent
1d69b3f5fa
commit
4ffe5449ac
1 changed files with 34 additions and 2 deletions
|
|
@ -117,8 +117,8 @@ const namespaceByFilePath = new Map<string, string>();
|
|||
/**
|
||||
* Read the cached PHP namespace for a given filePath. Returns `''` (global)
|
||||
* when the file has no namespace_definition or hasn't been processed yet.
|
||||
* Callers should only consult this AFTER `populatePhpNamespaceSiblings` has
|
||||
* run for the current resolution.
|
||||
* Callers should only consult this AFTER either `populatePhpClassQualifiedNames`
|
||||
* or `populatePhpNamespaceSiblings` has run for the current resolution.
|
||||
*/
|
||||
export function getPhpNamespaceForFile(filePath: string): string {
|
||||
return namespaceByFilePath.get(filePath) ?? '';
|
||||
|
|
@ -240,6 +240,38 @@ export function populatePhpNamespaceSiblings(
|
|||
}
|
||||
}
|
||||
|
||||
// Step 3b: Inject fully-qualified-name bindings into every PHP file's
|
||||
// Module scope. PHP `\App\Models\User` (leading-backslash FQN) and
|
||||
// `App\Models\User` (already-qualified relative) on a parameter or
|
||||
// typed receiver must resolve to the exact namespace-qualified class
|
||||
// regardless of which simple-name `User` the caller's `use` imports
|
||||
// shadowed. The shared `findClassBindingInScope` scope-chain walk
|
||||
// consumes these augmentations via `lookupBindingsAt`, so adding the
|
||||
// qualified key on every file's module scope routes FQN-receivers to
|
||||
// the right def. Codex PR #1497 review, finding 1.
|
||||
//
|
||||
// Cost: O(PHP files × class-like defs in the workspace) augmentation
|
||||
// entries. Bounded and acceptable in practice — typical PHP projects
|
||||
// have hundreds of files and classes, not tens of thousands.
|
||||
for (const parsed of parsedFiles) {
|
||||
const moduleScope = parsed.scopes.find((s) => s.kind === 'Module');
|
||||
if (moduleScope === undefined) continue;
|
||||
const moduleScopeId = moduleScope.id;
|
||||
|
||||
for (const [ns, bucket] of buckets) {
|
||||
if (ns === '') continue; // global-namespace classes have no qualified form to register
|
||||
for (const def of bucket.classDefs) {
|
||||
const q = def.qualifiedName ?? '';
|
||||
const simpleName = q.includes('\\') ? q.slice(q.lastIndexOf('\\') + 1) : q;
|
||||
if (simpleName === '') continue;
|
||||
const fqn = `${ns}\\${simpleName}`;
|
||||
const arr = getAugmentationBucket(augmentations, moduleScopeId, fqn);
|
||||
if (arr.some((b) => b.def.nodeId === def.nodeId)) continue;
|
||||
arr.push({ def, origin: 'namespace' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Mirror return-type bindings from same-namespace sibling files.
|
||||
// This enables chain-follow like `$c->greet()->save()` where `greet()`
|
||||
// returns `Greeting` (declared in A.php, same namespace) and `Greeting`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue