mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-23 00:41:36 +00:00
fix(csharp-scope): capture null-conditional receiver + operator decls
Adversarial review surfaced two Unit 1 bugs that would silently corrupt the graph once C# is flipped on the scope-resolution path: - `obj?.Save()` only emitted @reference.name, so receiver-bound resolution downgraded to the free-call fallback and could mis-link to an imported `Save`. Capture the conditional_access_expression receiver under @reference.receiver. - `operator_declaration` had @scope.function but no @declaration.method owner, so calls inside operator bodies were attributed to the enclosing class and the operator itself disappeared from method lookup. Capture the operator token as @declaration.name (downstream csharpMethodConfig normalizes to op_Addition etc.). - `conversion_operator_declaration` was missing from both scope and declaration sets. Added with the target type as the name anchor. Arity metadata for overload resolution remains deferred to Unit 5 and gated behind Unit 7's parity flip, as documented in captures.ts.
This commit is contained in:
parent
9cec7d16a2
commit
8f5f7cf228
2 changed files with 60 additions and 4 deletions
|
|
@ -52,6 +52,7 @@ const CSHARP_SCOPE_QUERY = `
|
|||
(destructor_declaration) @scope.function
|
||||
(local_function_statement) @scope.function
|
||||
(operator_declaration) @scope.function
|
||||
(conversion_operator_declaration) @scope.function
|
||||
;; Property accessors are blocks within a property; not scoped here.
|
||||
;; Anonymous methods / lambdas are not scoped — out of scope per plan.
|
||||
|
||||
|
|
@ -84,6 +85,21 @@ const CSHARP_SCOPE_QUERY = `
|
|||
(local_function_statement
|
||||
name: (identifier) @declaration.name) @declaration.function
|
||||
|
||||
;; Operator declarations — \`public static T operator +(T a, T b)\`.
|
||||
;; tree-sitter-c-sharp exposes the operator token under the \`operator:\`
|
||||
;; field (an anonymous node like \`+\`, \`-\`, \`==\`). Capture the whole
|
||||
;; node under @declaration.name so the extractor reads the operator
|
||||
;; symbol as the declared name; downstream csharpMethodConfig can
|
||||
;; normalize it (e.g. to \`op_Addition\`) when it runs.
|
||||
(operator_declaration
|
||||
operator: _ @declaration.name) @declaration.method
|
||||
|
||||
;; Conversion operators — \`public static explicit operator int(T x)\`.
|
||||
;; No operator token; the target type (\`int\`) identifies the conversion
|
||||
;; and serves as the name anchor.
|
||||
(conversion_operator_declaration
|
||||
type: _ @declaration.name) @declaration.method
|
||||
|
||||
(property_declaration
|
||||
name: (identifier) @declaration.name) @declaration.property
|
||||
|
||||
|
|
@ -197,11 +213,15 @@ const CSHARP_SCOPE_QUERY = `
|
|||
name: (identifier) @reference.name)) @reference.call.member
|
||||
|
||||
;; References — null-conditional member calls: \`obj?.Method()\`
|
||||
;; Positional descendants — conditional_access_expression wraps a
|
||||
;; receiver followed by a member_binding_expression containing an
|
||||
;; identifier. tree-sitter-c-sharp doesn't expose named fields here.
|
||||
;; conditional_access_expression wraps a receiver followed by a
|
||||
;; member_binding_expression. Capture the receiver explicitly so
|
||||
;; receiver-bound resolution doesn't silently downgrade the call to
|
||||
;; a free-call (which would misresolve to an imported \`Save\`).
|
||||
;; tree-sitter-c-sharp doesn't expose named fields here, so use
|
||||
;; positional wildcards.
|
||||
(invocation_expression
|
||||
function: (conditional_access_expression
|
||||
(_) @reference.receiver
|
||||
(member_binding_expression
|
||||
(identifier) @reference.name))) @reference.call.member
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,38 @@ describe('emitCsharpScopeCaptures — declarations', () => {
|
|||
expect(m!['@declaration.name'].text).toBe('_x');
|
||||
});
|
||||
|
||||
it('captures operator declarations as @declaration.method with the operator token as name', () => {
|
||||
// Caller attribution walks ownedDefs looking for method owners.
|
||||
// Without this, calls inside `operator +` bodies get attributed to
|
||||
// the enclosing class instead of the operator.
|
||||
const m = findMatch(
|
||||
'class T { public static T operator +(T a, T b) { return a; } }',
|
||||
(t) => t.includes('@declaration.method') && !t.includes('@scope.class'),
|
||||
);
|
||||
expect(m).toBeDefined();
|
||||
expect(m!['@declaration.name'].text).toBe('+');
|
||||
});
|
||||
|
||||
it('captures conversion operator declarations with the target type as name', () => {
|
||||
const m = findMatch('class T { public static explicit operator int(T x) { return 0; } }', (t) =>
|
||||
t.includes('@declaration.method'),
|
||||
);
|
||||
expect(m).toBeDefined();
|
||||
expect(m!['@declaration.name'].text).toBe('int');
|
||||
});
|
||||
|
||||
it('captures operator + conversion-operator as @scope.function', () => {
|
||||
const src = `
|
||||
class T {
|
||||
public static T operator +(T a, T b) { return a; }
|
||||
public static explicit operator int(T x) { return 0; }
|
||||
}
|
||||
`;
|
||||
const all = tagsFor(src);
|
||||
const fnScopes = all.filter((t) => t.includes('@scope.function')).length;
|
||||
expect(fnScopes).toBe(2);
|
||||
});
|
||||
|
||||
it('captures local function declarations', () => {
|
||||
const m = findMatch('class A { void M() { void Local() { } } }', (t) =>
|
||||
t.includes('@declaration.function'),
|
||||
|
|
@ -199,12 +231,16 @@ describe('emitCsharpScopeCaptures — references', () => {
|
|||
expect(m!['@reference.name'].text).toBe('Save');
|
||||
});
|
||||
|
||||
it('captures null-conditional member calls `obj?.Save()`', () => {
|
||||
it('captures null-conditional member calls `obj?.Save()` with a receiver', () => {
|
||||
// Regression guard: without the receiver capture, receiver-bound
|
||||
// resolution downgrades to free-call fallback and can mis-link to
|
||||
// an imported `Save`.
|
||||
const m = findMatch('class A { void M(User obj) { obj?.Save(); } }', (t) =>
|
||||
t.includes('@reference.call.member'),
|
||||
);
|
||||
expect(m).toBeDefined();
|
||||
expect(m!['@reference.name'].text).toBe('Save');
|
||||
expect(m!['@reference.receiver'].text).toBe('obj');
|
||||
});
|
||||
|
||||
it('captures object-creation expressions as constructor calls', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue