mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-21 00:21:30 +00:00
refactor(csharp-scope): remove as-unknown-as double casts in scope-resolver
Tighten three type boundaries that were previously papered over with
`as unknown as` casts:
* `CsharpResolveContext.allFilePaths`: `Set<string>` → `ReadonlySet<string>`.
The orchestrator only hands out a read-only view; drop the widening
cast at the resolver-adapter site.
* `resolveCsharpImportTarget`: call passes the narrow context directly.
`WorkspaceIndex` is `unknown` in the shared contract, so the
`as unknown as WorkspaceIndex` cast was gratuitous — structural
assignability covers it.
* `csharpMergeBindings`: drop unused `_scope: Scope` parameter. The
implementation never read it; the cast chain in `scope-resolver.ts`
existed only to satisfy an unused slot. LanguageProvider.mergeBindings
now wraps with a tiny arrow adapter; ScopeResolver.mergeBindings
passes through directly.
No runtime behavior change. `grep 'as unknown as' csharp/scope-resolver.ts`
returns zero matches.
Verified:
- npx tsc --noEmit clean
- C# unit + integration 462/462 passing (incl. Python integration)
This commit is contained in:
parent
0f18456539
commit
952350801c
5 changed files with 24 additions and 29 deletions
|
|
@ -159,7 +159,7 @@ export const csharpProvider = defineLanguage({
|
|||
interpretTypeBinding: interpretCsharpTypeBinding,
|
||||
bindingScopeFor: csharpBindingScopeFor,
|
||||
importOwningScope: csharpImportOwningScope,
|
||||
mergeBindings: csharpMergeBindings,
|
||||
mergeBindings: (_scope, bindings) => csharpMergeBindings(bindings),
|
||||
receiverBinding: csharpReceiverBinding,
|
||||
arityCompatibility: csharpArityCompatibility,
|
||||
resolveImportTarget: resolveCsharpImportTarget,
|
||||
|
|
|
|||
|
|
@ -22,13 +22,17 @@ import type { ParsedImport, WorkspaceIndex } from 'gitnexus-shared';
|
|||
|
||||
export interface CsharpResolveContext {
|
||||
readonly fromFile: string;
|
||||
readonly allFilePaths: Set<string>;
|
||||
readonly allFilePaths: ReadonlySet<string>;
|
||||
}
|
||||
|
||||
export function resolveCsharpImportTarget(
|
||||
parsedImport: ParsedImport,
|
||||
workspaceIndex: WorkspaceIndex,
|
||||
): string | null {
|
||||
// WorkspaceIndex is `unknown` in the shared contract (Ring 1
|
||||
// placeholder). The scope-resolution orchestrator hands us a
|
||||
// CsharpResolveContext-shaped object; narrow structurally rather
|
||||
// than via a cast chain so unexpected shapes return null cleanly.
|
||||
const ctx = workspaceIndex as CsharpResolveContext | undefined;
|
||||
if (
|
||||
ctx === undefined ||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
* earlier binding.
|
||||
*/
|
||||
|
||||
import type { BindingRef, Scope } from 'gitnexus-shared';
|
||||
import type { BindingRef } from 'gitnexus-shared';
|
||||
|
||||
const TIER_LOCAL = 0;
|
||||
const TIER_IMPORT = 1;
|
||||
|
|
@ -46,10 +46,7 @@ function tierOf(b: BindingRef): number {
|
|||
}
|
||||
}
|
||||
|
||||
export function csharpMergeBindings(
|
||||
_scope: Scope,
|
||||
bindings: readonly BindingRef[],
|
||||
): readonly BindingRef[] {
|
||||
export function csharpMergeBindings(bindings: readonly BindingRef[]): readonly BindingRef[] {
|
||||
if (bindings.length === 0) return bindings;
|
||||
|
||||
let bestTier = Number.POSITIVE_INFINITY;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* canonical shape.
|
||||
*/
|
||||
|
||||
import type { ParsedFile, Scope, WorkspaceIndex } from 'gitnexus-shared';
|
||||
import type { ParsedFile } from 'gitnexus-shared';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { buildMro, defaultLinearize } from '../../scope-resolution/passes/mro.js';
|
||||
import { populateClassOwnedMembers } from '../../scope-resolution/scope/walkers.js';
|
||||
|
|
@ -27,24 +27,19 @@ const csharpScopeResolver: ScopeResolver = {
|
|||
importEdgeReason: 'csharp-scope: using',
|
||||
|
||||
resolveImportTarget: (targetRaw, fromFile, allFilePaths) => {
|
||||
// CsharpResolveContext expects a mutable Set; the orchestrator
|
||||
// hands us a ReadonlySet — safe to widen since the resolver only
|
||||
// reads.
|
||||
const ws: CsharpResolveContext = {
|
||||
fromFile,
|
||||
allFilePaths: allFilePaths as Set<string>,
|
||||
};
|
||||
const ws: CsharpResolveContext = { fromFile, allFilePaths };
|
||||
// `WorkspaceIndex` is an opaque `unknown` placeholder in the
|
||||
// shared contract, so `ws` passes structurally without a cast.
|
||||
return resolveCsharpImportTarget(
|
||||
{ kind: 'namespace', localName: '_', importedName: '_', targetRaw },
|
||||
ws as unknown as WorkspaceIndex,
|
||||
ws,
|
||||
);
|
||||
},
|
||||
|
||||
// C# shadowing: local > using > using static.
|
||||
mergeBindings: (existing, incoming, scopeId) => {
|
||||
const fakeScope = { id: scopeId } as unknown as Scope;
|
||||
return [...csharpMergeBindings(fakeScope, [...existing, ...incoming])];
|
||||
},
|
||||
// C# shadowing: local > using > using static. The per-scope id is
|
||||
// unused by the C# implementation (shadowing is computed purely
|
||||
// from the binding tier), so we don't need to synthesize a Scope.
|
||||
mergeBindings: (existing, incoming) => [...csharpMergeBindings([...existing, ...incoming])],
|
||||
|
||||
// Adapter: csharpArityCompatibility uses (def, callsite); the
|
||||
// contract is (callsite, def).
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ 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 =>
|
||||
|
|
@ -94,43 +93,43 @@ describe('csharpMergeBindings — shadowing precedence', () => {
|
|||
it('local declaration shadows `using` import', () => {
|
||||
const local = binding('local', 'L');
|
||||
const imp = binding('import', 'I');
|
||||
expect(csharpMergeBindings(scope, [imp, local])).toEqual([local]);
|
||||
expect(csharpMergeBindings([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]);
|
||||
expect(csharpMergeBindings([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]);
|
||||
expect(csharpMergeBindings([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]);
|
||||
expect(csharpMergeBindings([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);
|
||||
expect(csharpMergeBindings([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);
|
||||
expect(csharpMergeBindings([ns, re, imp])).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('empty in → empty out', () => {
|
||||
expect(csharpMergeBindings(scope, [])).toEqual([]);
|
||||
expect(csharpMergeBindings([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue