fix: address 4 findings — K&R arity, function-pointer docs, prototype docs, _filePath rename

Finding 1: K&R empty parameter list `int foo()` now returns unknown arity `{}`
instead of `{parameterCount:0}`. Distinguishes from explicit `int foo(void)`.
3 unit tests cover K&R definition, prototype, and void comparison.

Finding 2: Added code comment documenting function-pointer-variable call
capture as known architectural trade-off (same as Go resolver).

Finding 3: Added code comment documenting prototype/definition duplication
as graph-quality concern (no false CALLS edges).

Finding 4: Renamed `_filePath` → `filePath` in captures.ts since it is
actively used in markStaticName().

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/ab2164f7-972f-4ea8-81fa-a14ce20d7cce

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-11 07:06:57 +00:00 committed by GitHub
parent dcc764a2a4
commit 48924fcfa8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 54 additions and 2 deletions

View file

@ -26,6 +26,13 @@ export function computeCDeclarationArity(node: SyntaxNode): CArityInfo {
}
}
// K&R old-style declaration: `int foo()` has an empty parameter_list with
// no parameter_declaration or variadic_parameter children. Per C89/C99,
// this means the function accepts an unspecified number/types of arguments —
// NOT zero arguments. Return unknown arity to avoid false 'incompatible'.
// `int foo(void)` is the explicit zero-parameter form and is handled below.
if (params.length === 0) return {};
// (void) means zero parameters
if (params.length === 1 && params[0].type === 'parameter_declaration') {
const typeNode = params[0].childForFieldName('type');

View file

@ -14,7 +14,7 @@ import { markStaticName } from './static-linkage.js';
export function emitCScopeCaptures(
sourceText: string,
_filePath: string,
filePath: string,
cachedTree?: unknown,
): readonly CaptureMatch[] {
let tree = cachedTree as ReturnType<ReturnType<typeof getCParser>['parse']> | undefined;
@ -102,7 +102,7 @@ export function emitCScopeCaptures(
if (hasStaticStorageClass(fnNode)) {
const nameText = grouped['@declaration.name']?.text;
if (nameText !== undefined) {
markStaticName(_filePath, nameText);
markStaticName(filePath, nameText);
}
}
}

View file

@ -53,6 +53,11 @@ const C_SCOPE_QUERY = `
declarator: (identifier) @declaration.name))) @declaration.function
;; Declarations function declaration (prototype)
;; Note: Both prototypes and definitions are captured as @declaration.function.
;; This may produce duplicate Function nodes in the knowledge graph when a
;; function is declared in a header and defined in a .c file. CALLS edges
;; resolve correctly through scope-based wildcard import chains; the
;; duplication is a graph-quality concern only (no false edges).
(declaration
declarator: (function_declarator
declarator: (identifier) @declaration.name)) @declaration.function
@ -114,6 +119,12 @@ const C_SCOPE_QUERY = `
declarator: (identifier) @type-binding.name)) @type-binding.assignment
;; References free calls
;; Note: This also captures calls through function pointer variables (e.g. fp(x))
;; since tree-sitter-c produces structurally identical AST nodes for both direct
;; function calls and function-pointer-variable calls. A type-based guard to
;; distinguish variable-calls from function-calls is not implemented this is a
;; known architectural trade-off shared with the Go resolver. The uniqueness
;; constraint in pickUniqueGlobalCallable limits false edge exposure.
(call_expression
function: (identifier) @reference.name) @reference.call.free

View file

@ -101,6 +101,40 @@ describe('computeCDeclarationArity', () => {
expect(arity.parameterCount).toBe(1);
expect(arity.requiredParameterCount).toBe(1);
});
it('returns unknown arity for K&R empty parameter list int foo()', () => {
const node = parseFunctionNode('int foo() { return 0; }');
expect(node).not.toBeNull();
const arity = computeCDeclarationArity(node!);
// K&R old-style: unspecified parameters, NOT zero parameters
expect(arity.parameterCount).toBeUndefined();
expect(arity.requiredParameterCount).toBeUndefined();
expect(arity.parameterTypes).toBeUndefined();
});
it('distinguishes K&R int foo() from explicit int foo(void)', () => {
const knrNode = parseFunctionNode('int foo() { return 0; }');
const voidNode = parseFunctionNode('int foo(void) { return 0; }');
expect(knrNode).not.toBeNull();
expect(voidNode).not.toBeNull();
const knrArity = computeCDeclarationArity(knrNode!);
const voidArity = computeCDeclarationArity(voidNode!);
// K&R: unknown arity
expect(knrArity.parameterCount).toBeUndefined();
// Explicit void: zero params
expect(voidArity.parameterCount).toBe(0);
expect(voidArity.requiredParameterCount).toBe(0);
});
it('returns unknown arity for K&R prototype int foo();', () => {
const node = parseFunctionNode('int foo();');
expect(node).not.toBeNull();
const arity = computeCDeclarationArity(node!);
expect(arity.parameterCount).toBeUndefined();
expect(arity.requiredParameterCount).toBeUndefined();
});
});
describe('computeCCallArity', () => {