mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-04 02:31:36 +00:00
feat(csharp-scope): Unit 4 — mergeBindings (using precedence)
Three-tier shadowing, same shape as Python's LEGB merge:
0: local — class members, locals, parameters
1: using — namespace / named / reexport (equal tier; compiler
requires explicit qualifier if two using collide)
2: wildcard — `using static X.Y;` static-member imports
Within the surviving tier, de-dup by DefId (last-write-wins) so a
re-declared `using` cleanly replaces its earlier binding. Explicit
interface implementations bind under their qualified name in the
extractor layer, so they don't collide with plain simple names here.
7 new tests pin precedence + dedup semantics. 54/54 C# scope-resolution
unit tests pass.
This commit is contained in:
parent
dd7e553d07
commit
3c79058088
2 changed files with 122 additions and 1 deletions
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* C# shadowing precedence for the `mergeBindings` hook.
|
||||
*
|
||||
* Tier ranking (lower wins in shadowing):
|
||||
*
|
||||
* - 0: `local` — a class member, method, local variable, or parameter
|
||||
* declared in this scope.
|
||||
* - 1: `import` / `namespace` / `reexport` — `using System;`,
|
||||
* `using System.Collections.Generic;`, `using Alias = Foo;`.
|
||||
* All three using flavors that introduce a name at this scope
|
||||
* tier together; the compiler resolves ambiguity by requiring
|
||||
* an explicit qualifier when two `using`s collide, but for
|
||||
* receiver-typed dispatch we treat them as equivalent tiers.
|
||||
* - 2: `wildcard` — `using static System.Math;` brings static
|
||||
* members in; any local or `using` with the same simple name
|
||||
* shadows.
|
||||
*
|
||||
* Explicit interface implementations (`void IFoo.Bar() { }`) bind under
|
||||
* the qualified name in the extractor layer, so they never collide with
|
||||
* a plain `Bar` at this layer.
|
||||
*
|
||||
* Within a surviving tier we de-dup by `DefId`, last-write-wins so a
|
||||
* `using` re-declared further down the file cleanly replaces the
|
||||
* earlier binding.
|
||||
*/
|
||||
|
||||
import type { BindingRef, Scope } from 'gitnexus-shared';
|
||||
|
||||
const TIER_LOCAL = 0;
|
||||
const TIER_IMPORT = 1;
|
||||
const TIER_WILDCARD = 2;
|
||||
const TIER_UNKNOWN = 3;
|
||||
|
||||
function tierOf(b: BindingRef): number {
|
||||
switch (b.origin) {
|
||||
case 'local':
|
||||
return TIER_LOCAL;
|
||||
case 'reexport':
|
||||
case 'import':
|
||||
case 'namespace':
|
||||
return TIER_IMPORT;
|
||||
case 'wildcard':
|
||||
return TIER_WILDCARD;
|
||||
default:
|
||||
return TIER_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
export function csharpMergeBindings(
|
||||
_scope: Scope,
|
||||
bindings: readonly BindingRef[],
|
||||
): readonly BindingRef[] {
|
||||
if (bindings.length === 0) return bindings;
|
||||
|
||||
let bestTier = Number.POSITIVE_INFINITY;
|
||||
for (const b of bindings) bestTier = Math.min(bestTier, tierOf(b));
|
||||
const survivors = bindings.filter((b) => tierOf(b) === bestTier);
|
||||
|
||||
const seen = new Map<string, BindingRef>();
|
||||
for (const b of survivors) seen.set(b.def.nodeId, b);
|
||||
return [...seen.values()];
|
||||
}
|
||||
|
|
@ -16,7 +16,16 @@ import {
|
|||
csharpImportOwningScope,
|
||||
csharpReceiverBinding,
|
||||
} from '../../../../src/core/ingestion/languages/csharp/simple-hooks.js';
|
||||
import type { CaptureMatch, ParsedImport, Scope, ScopeTree, TypeRef } from 'gitnexus-shared';
|
||||
import { csharpMergeBindings } from '../../../../src/core/ingestion/languages/csharp/merge-bindings.js';
|
||||
import type {
|
||||
BindingRef,
|
||||
CaptureMatch,
|
||||
ParsedImport,
|
||||
Scope,
|
||||
ScopeTree,
|
||||
SymbolDefinition,
|
||||
TypeRef,
|
||||
} from 'gitnexus-shared';
|
||||
|
||||
function fakeScope(
|
||||
kind: Scope['kind'],
|
||||
|
|
@ -73,6 +82,56 @@ describe('csharpImportOwningScope', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('csharpMergeBindings — shadowing precedence', () => {
|
||||
const scope = fakeScope('Function');
|
||||
const def = (nodeId: string): SymbolDefinition =>
|
||||
({ nodeId, filePath: 't.cs', type: 'Function' }) as SymbolDefinition;
|
||||
const binding = (origin: BindingRef['origin'], nodeId: string): BindingRef =>
|
||||
({ def: def(nodeId), origin }) as BindingRef;
|
||||
|
||||
it('local declaration shadows `using` import', () => {
|
||||
const local = binding('local', 'L');
|
||||
const imp = binding('import', 'I');
|
||||
expect(csharpMergeBindings(scope, [imp, local])).toEqual([local]);
|
||||
});
|
||||
|
||||
it('explicit `using` shadows `using static` (wildcard)', () => {
|
||||
const imp = binding('import', 'I');
|
||||
const wc = binding('wildcard', 'W');
|
||||
expect(csharpMergeBindings(scope, [wc, imp])).toEqual([imp]);
|
||||
});
|
||||
|
||||
it('local shadows both `using` and `using static`', () => {
|
||||
const local = binding('local', 'L');
|
||||
const imp = binding('import', 'I');
|
||||
const wc = binding('wildcard', 'W');
|
||||
expect(csharpMergeBindings(scope, [wc, imp, local])).toEqual([local]);
|
||||
});
|
||||
|
||||
it('keeps overload siblings at the same tier', () => {
|
||||
const a = binding('local', 'A');
|
||||
const b = binding('local', 'B');
|
||||
expect(csharpMergeBindings(scope, [a, b])).toEqual([a, b]);
|
||||
});
|
||||
|
||||
it('dedupes same-nodeId bindings', () => {
|
||||
const a = binding('local', 'A');
|
||||
const a2 = binding('local', 'A');
|
||||
expect(csharpMergeBindings(scope, [a, a2])).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('namespace and reexport tie with explicit import (same tier)', () => {
|
||||
const ns = binding('namespace', 'N');
|
||||
const re = binding('reexport', 'R');
|
||||
const imp = binding('import', 'I');
|
||||
expect(csharpMergeBindings(scope, [ns, re, imp])).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('empty in → empty out', () => {
|
||||
expect(csharpMergeBindings(scope, [])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('csharpReceiverBinding', () => {
|
||||
it('returns the `this` type binding for an instance method scope', () => {
|
||||
const binding: TypeRef = { rawName: 'User', source: 'self' } as unknown as TypeRef;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue