diff --git a/gitnexus/src/core/ingestion/languages/csharp/captures.ts b/gitnexus/src/core/ingestion/languages/csharp/captures.ts index c8db08d3b..e29552e78 100644 --- a/gitnexus/src/core/ingestion/languages/csharp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/csharp/captures.ts @@ -17,11 +17,7 @@ */ import type { Capture, CaptureMatch } from 'gitnexus-shared'; -import { - findNodeAtRange, - nodeToCapture, - syntheticCapture, -} from '../../utils/ast-helpers.js'; +import { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js'; import { splitUsingDirective } from './import-decomposer.js'; import { computeCsharpArityMetadata } from './arity-metadata.js'; import { synthesizeCsharpReceiverBinding } from './receiver-binding.js'; @@ -261,6 +257,8 @@ export function emitCsharpScopeCaptures( function synthesizeGenericTypeArgumentReferences(root: SyntaxNode): CaptureMatch[] { const out: CaptureMatch[] = []; + // Treat all generic type arguments as static type references, including + // declaration signatures and call-site generic instantiations. visit(root, (node) => { if (node.type !== 'generic_name') return; const args = findNamedChild(node, 'type_argument_list'); diff --git a/gitnexus/src/mcp/local/local-backend.ts b/gitnexus/src/mcp/local/local-backend.ts index 7c11afcf4..24d3fa6b4 100644 --- a/gitnexus/src/mcp/local/local-backend.ts +++ b/gitnexus/src/mcp/local/local-backend.ts @@ -1757,9 +1757,9 @@ export class LocalBackend { // Run incoming-ref queries in parallel — they are independent. const [ctorIncoming, fileIncoming, typedPropertyIncoming, typedProperties] = await Promise.all([ - executeParameterized( - repo.id, - ` + executeParameterized( + repo.id, + ` MATCH (n)-[hm:CodeRelation]->(ctor:Constructor) WHERE n.id = $symId AND hm.type = 'HAS_METHOD' MATCH (caller)-[r:CodeRelation]->(ctor) @@ -1767,11 +1767,11 @@ export class LocalBackend { RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind LIMIT 30 `, - { symId }, - ), - executeParameterized( - repo.id, - ` + { symId }, + ), + executeParameterized( + repo.id, + ` MATCH (f:File)-[rel:CodeRelation]->(n) WHERE n.id = $symId AND rel.type = 'DEFINES' MATCH (caller)-[r:CodeRelation]->(f) @@ -1779,11 +1779,11 @@ export class LocalBackend { RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind LIMIT 30 `, - { symId }, - ), - executeParameterized( - repo.id, - ` + { symId }, + ), + executeParameterized( + repo.id, + ` MATCH (p:\`Property\`) WHERE p.declaredType = $name OR p.declaredType STARTS WITH $genericPrefix @@ -1793,15 +1793,15 @@ export class LocalBackend { RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind LIMIT 30 `, - { - name: sym.name, - genericPrefix: `${sym.name}<`, - genericArg: `<${sym.name}>`, - }, - ), - executeParameterized( - repo.id, - ` + { + name: sym.name, + genericPrefix: `${sym.name}<`, + genericArg: `<${sym.name}>`, + }, + ), + executeParameterized( + repo.id, + ` MATCH (p:\`Property\`) WHERE p.declaredType = $name OR p.declaredType STARTS WITH $genericPrefix @@ -1810,13 +1810,13 @@ export class LocalBackend { p.declaredType AS declaredType LIMIT 30 `, - { - name: sym.name, - genericPrefix: `${sym.name}<`, - genericArg: `<${sym.name}>`, - }, - ), - ]); + { + name: sym.name, + genericPrefix: `${sym.name}<`, + genericArg: `<${sym.name}>`, + }, + ), + ]); typedPropertyRows = typedProperties; // Deduplicate by (relType, uid) — a caller can have multiple relation diff --git a/gitnexus/test/integration/resolvers/helpers.ts b/gitnexus/test/integration/resolvers/helpers.ts index 5368c1715..e92526b19 100644 --- a/gitnexus/test/integration/resolvers/helpers.ts +++ b/gitnexus/test/integration/resolvers/helpers.ts @@ -11,6 +11,9 @@ import type { GraphRelationship } from 'gitnexus-shared'; const LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES: Readonly>> = { csharp: new Set([ 'emits the using-import edge App/Program.cs -> Models/User.cs through the scope-resolution path', + // Generic type-argument USES edges are emitted by the registry-primary + // resolver only; the legacy DAG path does not synthesize these references. + 'emits USES edges for generic type arguments', ]), go: new Set([ // The legacy DAG path does not resolve method calls when the method is diff --git a/gitnexus/test/unit/scope-resolution/csharp/csharp-captures.test.ts b/gitnexus/test/unit/scope-resolution/csharp/csharp-captures.test.ts index f1241fa10..9fa9bf181 100644 --- a/gitnexus/test/unit/scope-resolution/csharp/csharp-captures.test.ts +++ b/gitnexus/test/unit/scope-resolution/csharp/csharp-captures.test.ts @@ -453,4 +453,16 @@ describe('emitCsharpScopeCaptures — references', () => { expect(names).toContain('USER_INFO'); expect(names).not.toContain('string'); }); + + it('captures call-site generic type arguments as type references', () => { + const matches = emitCsharpScopeCaptures( + 'class A { void M(IRepo repo) { repo.Get(); } }', + 'test.cs', + ); + const names = matches + .filter((m) => '@reference.type' in m) + .map((m) => m['@reference.name'].text); + + expect(names).toContain('USER_INFO'); + }); });