mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-22 00:31:17 +00:00
feat(csharp-scope): parity Unit 6d — using-static member injection
Closes 2 parity failures (6 → 4). `using static X.Y.Z;` now injects
every public static method of class Z into the importer's module
scope, so `Record("hi")` (without `Logger.` qualifier) resolves to
`Logger.Record` as a free call.
`languages/csharp/namespace-siblings.ts`: regex-scan each file's
source for `using static X.Y.Z;` directives. For each, look up the
class Z in the `X.Y` namespace bucket, walk its owning file's
localDefs for method/function members with `ownerId === Z.nodeId`,
and inject them as `origin: 'import'` bindings in the importer's
module-scope finalized bindings map. `findCallableBindingInScope`
then picks them up via its imported-bindings check.
Closes: variadic `Record(params string[])` + heritage arity
narrowing `WriteAudit`.
Python parity 204/204 on both flag paths; legacy C# 175/175 green;
4 C# parity failures remain (interface-dispatch pass + type-based
overload disambiguation).
This commit is contained in:
parent
5ee345271f
commit
8828fcacf6
1 changed files with 59 additions and 0 deletions
|
|
@ -27,6 +27,9 @@ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexe
|
|||
/** `namespace Foo.Bar { ... }` or `namespace Foo.Bar;` — capture the dotted name. */
|
||||
const NAMESPACE_RE = /\bnamespace\s+([A-Za-z_][A-Za-z0-9_.]*)\s*[;{]/g;
|
||||
|
||||
/** `using static Foo.Bar.Baz;` — capture the dotted static-class path. */
|
||||
const USING_STATIC_RE = /\busing\s+static\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/g;
|
||||
|
||||
/** Content of a file, keyed by filePath. Caller sources this from the
|
||||
* pipeline's file list before the pass runs. */
|
||||
export interface CsharpSiblingInputs {
|
||||
|
|
@ -186,6 +189,62 @@ export function populateCsharpNamespaceSiblings(
|
|||
}
|
||||
}
|
||||
|
||||
// `using static X.Y.Z;` — expose every public static method of
|
||||
// class Z as a free-callable binding in the importer's module
|
||||
// scope, so `Record(...)` (without `Logger.` qualifier) resolves
|
||||
// to `Logger.Record`.
|
||||
for (const parsed of parsedFiles) {
|
||||
const content = inputs.fileContents.get(parsed.filePath);
|
||||
if (content === undefined) continue;
|
||||
const moduleScope = parsed.scopes.find((s) => s.kind === 'Module');
|
||||
if (moduleScope === undefined) continue;
|
||||
|
||||
USING_STATIC_RE.lastIndex = 0;
|
||||
let sm: RegExpExecArray | null;
|
||||
while ((sm = USING_STATIC_RE.exec(content)) !== null) {
|
||||
const fullPath = sm[1]!;
|
||||
const lastDot = fullPath.lastIndexOf('.');
|
||||
if (lastDot === -1) continue;
|
||||
const className = fullPath.slice(lastDot + 1);
|
||||
const enclosingNs = fullPath.slice(0, lastDot);
|
||||
|
||||
// Find the target class in the named namespace bucket.
|
||||
const bucket = buckets.get(enclosingNs);
|
||||
if (bucket === undefined) continue;
|
||||
const targetDef = bucket.classDefs.find((d) => {
|
||||
const q = d.qualifiedName ?? '';
|
||||
const simple = q.includes('.') ? q.slice(q.lastIndexOf('.') + 1) : q;
|
||||
return simple === className;
|
||||
});
|
||||
if (targetDef === undefined) continue;
|
||||
|
||||
// Inject the class's member methods into the importer's module
|
||||
// scope. `memberByOwner` wasn't built yet here, so we walk the
|
||||
// file's localDefs to find members with `ownerId === targetDef.nodeId`.
|
||||
const targetFile = parsedFiles.find((p) => p.filePath === targetDef.filePath);
|
||||
if (targetFile === undefined) continue;
|
||||
for (const memberDef of targetFile.localDefs) {
|
||||
if ((memberDef as { ownerId?: string }).ownerId !== targetDef.nodeId) continue;
|
||||
if (memberDef.type !== 'Method' && memberDef.type !== 'Function') continue;
|
||||
const mq = memberDef.qualifiedName ?? '';
|
||||
const simpleName = mq.includes('.') ? mq.slice(mq.lastIndexOf('.') + 1) : mq;
|
||||
if (simpleName === '') continue;
|
||||
|
||||
// Add to `indexes.bindings[moduleScope]` so
|
||||
// `findCallableBindingInScope` picks it up.
|
||||
let scopeBindings = finalized.get(moduleScope.id);
|
||||
if (scopeBindings === undefined) {
|
||||
scopeBindings = new Map<string, BindingRef[]>();
|
||||
finalized.set(moduleScope.id, scopeBindings);
|
||||
}
|
||||
const existing = scopeBindings.get(simpleName) ?? [];
|
||||
if (existing.some((b) => b.def.nodeId === memberDef.nodeId)) continue;
|
||||
existing.push({ def: memberDef, origin: 'import' });
|
||||
scopeBindings.set(simpleName, existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-namespace imports: for each file's `using X;` directive,
|
||||
// if `X` matches a known namespace bucket, inject that bucket's
|
||||
// classes into the importer's module scope. This is what makes
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue