From 7b81e25c6f585b0d08ac3b5d07500474a4e4fed4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 17:31:04 +0000 Subject: [PATCH 01/10] Initial plan From e333c96cf96a6a3bb44888c8ff2de9a9cfa445e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 18:27:52 +0000 Subject: [PATCH 02/10] cpp ADL V2: free-function reference args contribute enclosing namespace Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/24805583-c0c4-4ef8-978f-b874bd917947 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- .../src/core/ingestion/languages/cpp/adl.ts | 92 ++++++++++++++++--- .../core/ingestion/languages/cpp/captures.ts | 58 +++++++++++- .../cpp-adl-free-func-ref-overloaded/app.cpp | 7 ++ .../cpp-adl-free-func-ref-overloaded/utils.h | 7 ++ .../cpp-adl-free-func-ref/app.cpp | 7 ++ .../cpp-adl-free-func-ref/utils.h | 6 ++ .../test/integration/resolvers/cpp.test.ts | 55 +++++++++++ 7 files changed, 215 insertions(+), 17 deletions(-) create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref-overloaded/app.cpp create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref-overloaded/utils.h create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref/app.cpp create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref/utils.h diff --git a/gitnexus/src/core/ingestion/languages/cpp/adl.ts b/gitnexus/src/core/ingestion/languages/cpp/adl.ts index 4c7b034b6d..7e4e60cc74 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/adl.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/adl.ts @@ -15,12 +15,23 @@ * * ## Current boundary * - * The current implementation covers ONE associated-entity rule: an argument that's a directly-named - * class type (`audit::Event e`) contributes its **direct enclosing - * namespace** to the candidate set. V2 extends that one step to - * pointer-typed class args (`audit::Event* p`, `audit::Event** pp`): - * they contribute the pointee class's enclosing namespace too. Reference - * arguments, function-pointer arguments, template specializations, + * The current implementation covers TWO associated-entity rules: + * 1. An argument that's a directly-named class type (`audit::Event e`) + * contributes its **direct enclosing namespace** to the candidate set. + * V2 extends that one step to pointer-typed class args + * (`audit::Event* p`, `audit::Event** pp`): they contribute the + * pointee class's enclosing namespace too. + * 2. A free-function reference argument (`utils::worker`, or an + * unqualified identifier that resolves to a free function in the + * workspace) contributes the function's **enclosing namespace**. + * For qualified refs (e.g. `utils::worker`) the namespace is + * extracted directly from the qualifier. For unqualified refs, the + * workspace is searched for any Function def with that simple name. + * Overloaded function references contribute the namespace if any + * overload exists (V1 simplification; no overload-resolution ranking). + * + * Reference arguments, locally-declared function-pointer variables (e.g. + * `void (*g)()`), member-function-pointer args, template specializations, * base-class associated namespaces, and the rest of the full closure are * still deliberately excluded. * @@ -64,6 +75,7 @@ import { * Per-argument shape information collected at capture time. ADL fires for * arguments where `simpleClassName !== ''` AND `!isReference`, including * class pointers whose declarator chain resolves to a named class type. + * Arguments that are free-function references use `functionRefText`. */ export interface CppAdlArgInfo { /** Simple class-like type name (last segment of qualified name); empty @@ -74,6 +86,14 @@ export interface CppAdlArgInfo { readonly isPointer: boolean; /** True when the variable's declarator was a `reference_declarator`. */ readonly isReference: boolean; + /** When set, the arg is a reference to a free function (not a locally- + * declared function-pointer variable). Contains the identifier text as + * written in source (e.g. `"utils::worker"` or `"worker"`). ADL + * contributes the function's enclosing namespace to the associated set. + * For qualified refs the namespace is extracted from the qualifier + * directly; for unqualified refs the workspace is searched for any + * Function def with that simple name. */ + readonly functionRefText?: string; } const argInfoBySite = new Map(); @@ -171,15 +191,19 @@ export function pickCppAdlCandidates( const args = argInfoBySite.get(key); if (args === undefined || args.length === 0) return undefined; - // Collect associated namespace QNames from every participating class-typed arg. + // Collect associated namespace QNames from every participating class-typed arg + // and from function-reference args. const associatedNamespaces = new Set(); for (const arg of args) { - if (arg.simpleClassName === '') continue; - if (arg.isReference) continue; - const classDef = findCppClassDefBySimpleName(arg.simpleClassName, scopes); - if (classDef === undefined) continue; - const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId); - if (nsQName !== undefined) associatedNamespaces.add(nsQName); + if (arg.simpleClassName !== '' && !arg.isReference) { + const classDef = findCppClassDefBySimpleName(arg.simpleClassName, scopes); + if (classDef === undefined) continue; + const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId); + if (nsQName !== undefined) associatedNamespaces.add(nsQName); + } + if (arg.functionRefText !== undefined) { + collectFunctionRefNamespaces(arg.functionRefText, parsedFiles, associatedNamespaces); + } } if (associatedNamespaces.size === 0) return undefined; @@ -334,3 +358,45 @@ function findCppClassDefBySimpleName( } return undefined; } + +/** + * Contribute associated namespaces for a function-reference argument. + * + * - **Qualified refs** (`utils::worker`, `outer::inner::fn`): the namespace + * is extracted directly from the qualifier text (converting `::` to `.` for + * dot-joined QName matching). No workspace search needed. + * - **Unqualified refs** (`worker`): the workspace is searched for any + * Function/Method def whose simple name matches. Every distinct enclosing + * namespace found is added — overloads across the same namespace produce + * a single entry; V1 simplification does not select a specific overload. + */ +function collectFunctionRefNamespaces( + refText: string, + parsedFiles: readonly ParsedFile[], + out: Set, +): void { + const colonIdx = refText.lastIndexOf('::'); + if (colonIdx !== -1) { + // Qualified: extract namespace prefix and normalise :: → dot notation. + const nsText = refText.slice(0, colonIdx).replace(/::/g, '.'); + if (nsText !== '') out.add(nsText); + return; + } + + // Unqualified: search all namespace scopes for a Function def with this + // simple name and contribute its enclosing namespace. + for (const parsed of parsedFiles) { + const scopesById = new Map(); + for (const sc of parsed.scopes) scopesById.set(sc.id, sc); + for (const scope of parsed.scopes) { + if (scope.kind !== 'Namespace') continue; + for (const def of scope.ownedDefs) { + if (def.type !== 'Function' && def.type !== 'Method') continue; + const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? ''; + if (simple !== refText) continue; + const nsQName = computeNamespaceQName(scope, scopesById); + if (nsQName !== '') out.add(nsQName); + } + } + } +} diff --git a/gitnexus/src/core/ingestion/languages/cpp/captures.ts b/gitnexus/src/core/ingestion/languages/cpp/captures.ts index 17b6962aa9..6610e2dc12 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/captures.ts @@ -759,17 +759,28 @@ function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo { ) { return EMPTY_ADL_ARG; } + // Qualified function reference — e.g. `utils::worker`. The namespace is + // encoded in the qualifier and will be extracted at resolution time. + if (argNode.type === 'qualified_identifier') { + return { simpleClassName: '', isPointer: false, isReference: false, functionRefText: argNode.text }; + } // Variable reference — look up its declared type (preserving pointer / // reference / qualified-name shape; the existing arity-narrowing helper // strips this info). if (argNode.type === 'identifier') { - return lookupAdlIdentifierType(argNode); + const result = lookupAdlIdentifierType(argNode); + if (result === null) { + // Not found in the local compound_statement scope — could be a + // free-function reference (unqualified name, namespace scope). + return { simpleClassName: '', isPointer: false, isReference: false, functionRefText: argNode.text }; + } + return result; } // Other shapes (calls, member access, operators) — V1 unsupported. return EMPTY_ADL_ARG; } -function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo { +function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo | null { const varName = identNode.text; let scope: SyntaxNode | null = identNode.parent; while ( @@ -779,8 +790,9 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo { ) { scope = scope.parent; } - if (scope === null) return EMPTY_ADL_ARG; + if (scope === null) return null; + let foundAsLocalFunctionPointer = false; for (let i = 0; i < scope.childCount; i++) { const stmt = scope.child(i); if (stmt === null || stmt.type !== 'declaration') continue; @@ -808,6 +820,9 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo { if (inner.type === 'pointer_declarator') { if (findFirstDescendantOfType(inner, 'function_declarator') !== null) { isFunctionPointer = true; + // Extract the name from within the function-pointer declarator chain + // so `foundAsLocalFunctionPointer` can detect a matching declaration. + nameText = extractDeclaratorLeafName(inner); break; } isPointer = true; @@ -839,18 +854,30 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo { } if (inner.type === 'function_declarator') { isFunctionPointer = true; + // Extract the name from the inner declarator (e.g. `(*g)` in `void (*g)()`). + const innerDecl = inner.childForFieldName('declarator'); + if (innerDecl !== null) nameText = extractDeclaratorLeafName(innerDecl); break; } // Reached the leaf — usually `identifier`. Take its text. nameText = inner.text; break; } + if (nameText === varName && isFunctionPointer) { + // Explicitly declared as a function-pointer variable — must not be + // treated as a free-function reference by the caller. + foundAsLocalFunctionPointer = true; + continue; + } if (isFunctionPointer || nameText !== varName) continue; const simpleClassName = extractAdlSimpleTypeName(typeNode); return { simpleClassName, isPointer, isReference }; } - return EMPTY_ADL_ARG; + // If the identifier was found in local scope as a function-pointer variable, + // return EMPTY_ADL_ARG so the caller does NOT treat it as a free-function + // reference. Otherwise return null to indicate "not in local scope". + return foundAsLocalFunctionPointer ? EMPTY_ADL_ARG : null; } /** Extract the simple class-like type name from a `type:` field node. @@ -871,6 +898,29 @@ function extractAdlSimpleTypeName(typeNode: SyntaxNode): string { return ''; } +/** + * Walk a declarator node chain, unwrapping pointer/reference/function/ + * parenthesized wrappers, and return the text of the innermost identifier. + * Returns `null` when no identifier is found within `safety` steps. + * Used by `lookupAdlIdentifierType` to extract the variable name from + * function-pointer declarator trees such as `(*g)()` in `void (*g)()`. + */ +function extractDeclaratorLeafName(node: SyntaxNode): string | null { + let cur: SyntaxNode = node; + let safety = 16; + while (safety-- > 0) { + if (cur.type === 'identifier' || cur.type === 'type_identifier') return cur.text; + // Common wrapper nodes — follow the 'declarator' field when present. + const next = + cur.childForFieldName('declarator') ?? + // parenthesized_declarator: single named child + (cur.type === 'parenthesized_declarator' ? cur.namedChild(0) : null); + if (next === null) return null; + cur = next; + } + return null; +} + /** * Check if a C++ function_definition or declaration has `static` storage class. */ diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref-overloaded/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref-overloaded/app.cpp new file mode 100644 index 0000000000..8d509774df --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref-overloaded/app.cpp @@ -0,0 +1,7 @@ +#include "utils.h" + +namespace caller { + void run() { + accept(utils::worker); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref-overloaded/utils.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref-overloaded/utils.h new file mode 100644 index 0000000000..53b82ca1e2 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref-overloaded/utils.h @@ -0,0 +1,7 @@ +#pragma once + +namespace utils { + void worker(); + void worker(int n); + void with_callback(int n); +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref/app.cpp new file mode 100644 index 0000000000..1a51ca699a --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref/app.cpp @@ -0,0 +1,7 @@ +#include "utils.h" + +namespace caller { + void run() { + with_callback(utils::worker); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref/utils.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref/utils.h new file mode 100644 index 0000000000..33467d7ce4 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-free-func-ref/utils.h @@ -0,0 +1,6 @@ +#pragma once + +namespace utils { + void worker(); + void with_callback(int n); +} diff --git a/gitnexus/test/integration/resolvers/cpp.test.ts b/gitnexus/test/integration/resolvers/cpp.test.ts index cd95cfae79..956667500a 100644 --- a/gitnexus/test/integration/resolvers/cpp.test.ts +++ b/gitnexus/test/integration/resolvers/cpp.test.ts @@ -2251,6 +2251,61 @@ describe('C++ ADL — int/long-collision overloads suppress via OVERLOAD_AMBIGUO }); }); +// --------------------------------------------------------------------------- +// ADL V2 — free-function reference args contribute their namespace. +// +// ISO C++ [basic.lookup.argdep]: an overloaded-function set (or single +// function) passed as an argument contributes the function's enclosing +// namespace to the associated set. `captures.ts` records a `functionRefText` +// on the CppAdlArgInfo when it detects a qualified_identifier arg or an +// unqualified identifier that is not in local scope; `adl.ts` extracts the +// namespace at resolution time. +// --------------------------------------------------------------------------- + +describe('C++ ADL — qualified free-function reference contributes its namespace', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-free-func-ref'), + () => {}, + ); + }, 60000); + + it('with_callback(utils::worker) resolves to utils::with_callback via ADL', () => { + const calls = getRelationships(result, 'CALLS'); + const cbCalls = calls.filter((c) => c.source === 'run' && c.target === 'with_callback'); + // Ordinary lookup inside caller::run finds nothing (no `using`, no local + // declaration). utils::worker is a qualified_identifier argument, so ADL + // contributes `utils` to the associated-namespace set. utils::with_callback + // is then discovered as the sole candidate. + expect(cbCalls.length).toBe(1); + expect(cbCalls[0].targetFilePath).toContain('utils.h'); + }); +}); + +describe('C++ ADL — overloaded free-function reference does not crash', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-free-func-ref-overloaded'), + () => {}, + ); + }, 60000); + + it('with_callback(utils::worker) with overloaded utils::worker still resolves utils::with_callback via ADL', () => { + const calls = getRelationships(result, 'CALLS'); + const cbCalls = calls.filter((c) => c.source === 'run' && c.target === 'with_callback'); + // utils::worker has two overloads (worker() and worker(int)). V1 + // simplification: contribute the namespace if ANY overload exists in the + // workspace, regardless of which one would be selected. The namespace + // `utils` is still added, and utils::with_callback is discovered. + expect(cbCalls.length).toBe(1); + expect(cbCalls[0].targetFilePath).toContain('utils.h'); + }); +}); + // --------------------------------------------------------------------------- // U5 (follow-up plan 2026-05-13-001): inline namespace transitive walking. // `inline namespace v1 { ... }` makes its members reachable through the From 7e4ecd455505c965b70775c1fddbd8d5e6b300a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 07:29:34 +0000 Subject: [PATCH 03/10] merge: resolve conflicts with origin/main and fix overloaded fixture app.cpp Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/136aeffe-45da-47e2-95dd-e3883e85fad7 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- gitnexus/src/core/ingestion/languages/cpp/captures.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitnexus/src/core/ingestion/languages/cpp/captures.ts b/gitnexus/src/core/ingestion/languages/cpp/captures.ts index 5726086505..85b7bb3200 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/captures.ts @@ -788,7 +788,7 @@ function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo { // Qualified function reference — e.g. `utils::worker`. The namespace is // encoded in the qualifier and will be extracted at resolution time. if (argNode.type === 'qualified_identifier') { - return { simpleClassName: '', isPointer: false, isReference: false, functionRefText: argNode.text }; + return { simpleClassName: '', templateSimpleClassName: '', templateNamespace: '', templateArgClassNames: [], templateArgNamespaces: [], functionRefText: argNode.text }; } // Variable reference — look up its declared type (preserving pointer / // reference / qualified-name shape; the existing arity-narrowing helper @@ -798,7 +798,7 @@ function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo { if (result === null) { // Not found in the local compound_statement scope — could be a // free-function reference (unqualified name, namespace scope). - return { simpleClassName: '', isPointer: false, isReference: false, functionRefText: argNode.text }; + return { simpleClassName: '', templateSimpleClassName: '', templateNamespace: '', templateArgClassNames: [], templateArgNamespaces: [], functionRefText: argNode.text }; } return result; } From 477c45ff3fa60297e1a4c19400095c9bb9281305 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 07:51:27 +0000 Subject: [PATCH 04/10] fix(finding-1): replace ISO C++ [basic.lookup.argdep] misstatement with GitNexus-approximation label Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/fa37c0dd-65f9-4dd4-9811-617227a37073 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- .../src/core/ingestion/languages/cpp/adl.ts | 40 +++++++++++-------- .../test/integration/resolvers/cpp.test.ts | 17 +++++--- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/gitnexus/src/core/ingestion/languages/cpp/adl.ts b/gitnexus/src/core/ingestion/languages/cpp/adl.ts index b3617177da..0c0be85657 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/adl.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/adl.ts @@ -24,17 +24,22 @@ * V2 additionally walks class ancestors (via MRO), so base-class enclosing * namespaces also contribute associated namespaces. * - * V2 also handles **free-function reference arguments**: passing a function - * reference like `utils::worker` contributes `utils` to the associated set, - * enabling resolution of unqualified calls like `with_callback(utils::worker)` - * to `utils::with_callback`. For qualified refs the namespace is extracted - * directly from the qualifier; for unqualified refs the workspace is searched - * for any Function def with that simple name. Overloaded function references - * contribute the namespace if any overload exists (V1 simplification; no - * overload-resolution ranking). + * **GitNexus approximation (not strict ISO C++ ADL):** passing a qualified + * function reference like `utils::worker` contributes `utils` to the associated + * set, enabling resolution of unqualified calls like `with_callback(utils::worker)` + * to `utils::with_callback`. Under ISO C++ `[basic.lookup.argdep]`, associated + * entities for function-type arguments come from the **parameter types and return + * type** of each function in the overload set — NOT the function's enclosing + * namespace. For `void worker()`, the standard-compliant associated set is empty. + * GitNexus instead contributes the enclosing namespace of any Function/Method + * def whose simple name matches, because it enables the dominant real-world ADL + * pattern at reasonable precision cost. * - * Locally-declared function-pointer variables (e.g. `void (*g)()`) and - * member-function-pointer args are explicitly excluded. + * For qualified refs (e.g. `utils::worker`) the namespace is confirmed via a + * workspace lookup (only contributed when a Function/Method named `worker` exists + * in `utils`). For unqualified refs the workspace is searched for any Function + * def with that simple name. Locally-declared function-pointer variables + * (e.g. `void (*g)()`) and function parameters are excluded from this path. * * The current implementation also short-circuits to ADL only when ordinary lookup is empty * (`findCallableBindingInScope` returned undefined). ISO C++ would @@ -94,13 +99,14 @@ export interface CppAdlArgInfo { /** Enclosing namespaces extracted from explicit type template arguments, * recursively bounded. */ readonly templateArgNamespaces: readonly string[]; - /** When set, the arg is a reference to a free function (not a locally- - * declared function-pointer variable). Contains the identifier text as - * written in source (e.g. `"utils::worker"` or `"worker"`). ADL - * contributes the function's enclosing namespace to the associated set. - * For qualified refs the namespace is extracted from the qualifier - * directly; for unqualified refs the workspace is searched for any - * Function def with that simple name. */ + /** When set, the arg is a potential free-function reference (not a locally- + * declared function-pointer variable or function parameter). Contains the + * identifier text as written in source (e.g. `"utils::worker"` or + * `"worker"`). GitNexus approximation: the function's enclosing namespace + * is contributed to the ADL associated set. For qualified refs a workspace + * lookup confirms a Function/Method with that simple name exists in the + * namespace before contributing; for unqualified refs every namespace + * containing a matching Function/Method def is contributed. */ readonly functionRefText?: string; } diff --git a/gitnexus/test/integration/resolvers/cpp.test.ts b/gitnexus/test/integration/resolvers/cpp.test.ts index f7f26911b8..3f0cc533c0 100644 --- a/gitnexus/test/integration/resolvers/cpp.test.ts +++ b/gitnexus/test/integration/resolvers/cpp.test.ts @@ -2427,12 +2427,17 @@ describe('C++ ADL — int/long-collision overloads suppress via OVERLOAD_AMBIGUO // --------------------------------------------------------------------------- // ADL V2 — free-function reference args contribute their namespace. // -// ISO C++ [basic.lookup.argdep]: an overloaded-function set (or single -// function) passed as an argument contributes the function's enclosing -// namespace to the associated set. `captures.ts` records a `functionRefText` -// on the CppAdlArgInfo when it detects a qualified_identifier arg or an -// unqualified identifier that is not in local scope; `adl.ts` extracts the -// namespace at resolution time. +// GitNexus approximation (not strict ISO C++ ADL): when a qualified_identifier +// like `utils::worker` is passed as an argument, GitNexus contributes the +// enclosing namespace (`utils`) to the associated set, provided a Function or +// Method named `worker` is found in the `utils` namespace at resolution time. +// Under ISO C++ [basic.lookup.argdep] the associated entities for a function-type +// argument come from the parameter types and return type of the overload set — +// NOT the function's enclosing namespace. For `void worker()`, the standard- +// compliant associated set is empty. The approximation captures the dominant +// real-world pattern (pass a utility function → find its sibling) at the cost +// of potential false positives when an unrelated function with the same simple +// name exists in the same namespace (bounded by the workspace-function lookup). // --------------------------------------------------------------------------- describe('C++ ADL — qualified free-function reference contributes its namespace', () => { From b910092b7e78aceb3b97177b51fe888ce70be64a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 07:57:04 +0000 Subject: [PATCH 05/10] fix(finding-2): verify Function/Method exists in namespace before contributing via qualified_identifier arg Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/fa37c0dd-65f9-4dd4-9811-617227a37073 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- .../src/core/ingestion/languages/cpp/adl.ts | 36 ++++++++++++++++--- .../core/ingestion/languages/cpp/captures.ts | 6 ++-- .../cpp-adl-qualified-variable-arg/app.cpp | 14 ++++++++ .../cpp-adl-qualified-variable-arg/data.h | 6 ++++ .../test/integration/resolvers/cpp.test.ts | 24 +++++++++++++ 5 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-qualified-variable-arg/app.cpp create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-qualified-variable-arg/data.h diff --git a/gitnexus/src/core/ingestion/languages/cpp/adl.ts b/gitnexus/src/core/ingestion/languages/cpp/adl.ts index 0c0be85657..7e0da9d1f8 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/adl.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/adl.ts @@ -424,12 +424,17 @@ function findCppClassDefBySimpleName( * Contribute associated namespaces for a function-reference argument. * * - **Qualified refs** (`utils::worker`, `outer::inner::fn`): the namespace - * is extracted directly from the qualifier text (converting `::` to `.` for - * dot-joined QName matching). No workspace search needed. + * is extracted from the qualifier text (converting `::` to `.` for dot-joined + * QName matching). A workspace lookup then **verifies** that a Function or + * Method def named `worker` (the simple name after the last `::`) actually + * exists in the extracted namespace. This prevents false positives from + * namespace-qualified variables, enum values, and static data members, which + * also produce `qualified_identifier` AST nodes in tree-sitter-cpp (the + * AST node type alone does not distinguish functions from non-function names). * - **Unqualified refs** (`worker`): the workspace is searched for any * Function/Method def whose simple name matches. Every distinct enclosing * namespace found is added — overloads across the same namespace produce - * a single entry; V1 simplification does not select a specific overload. + * a single entry; GitNexus does not select a specific overload at this stage. */ function collectFunctionRefNamespaces( refText: string, @@ -438,9 +443,30 @@ function collectFunctionRefNamespaces( ): void { const colonIdx = refText.lastIndexOf('::'); if (colonIdx !== -1) { - // Qualified: extract namespace prefix and normalise :: → dot notation. + // Qualified ref: extract namespace prefix and normalise :: → dot notation. const nsText = refText.slice(0, colonIdx).replace(/::/g, '.'); - if (nsText !== '') out.add(nsText); + if (nsText === '') return; + const simpleName = refText.slice(colonIdx + 2); + // Verify that a Function/Method named `simpleName` exists in `nsText`. + // Without this guard every `a::b` qualified_identifier arg (variable, + // enum value, static member, type alias) would blindly contribute `a` + // to the associated set and risk a false-positive CALLS edge. + for (const parsed of parsedFiles) { + const scopesById = new Map(); + for (const sc of parsed.scopes) scopesById.set(sc.id, sc); + for (const scope of parsed.scopes) { + if (scope.kind !== 'Namespace') continue; + if (computeNamespaceQName(scope, scopesById) !== nsText) continue; + for (const def of scope.ownedDefs) { + if (def.type !== 'Function' && def.type !== 'Method') continue; + const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? ''; + if (simple === simpleName) { + out.add(nsText); + return; // Namespace confirmed; no need to scan further files. + } + } + } + } return; } diff --git a/gitnexus/src/core/ingestion/languages/cpp/captures.ts b/gitnexus/src/core/ingestion/languages/cpp/captures.ts index 85b7bb3200..2f8a1290e4 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/captures.ts @@ -785,8 +785,10 @@ function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo { ) { return EMPTY_ADL_ARG; } - // Qualified function reference — e.g. `utils::worker`. The namespace is - // encoded in the qualifier and will be extracted at resolution time. + // Qualified expression (a::b) — may be a function, variable, enum value, + // or static member. Record as a potential function reference; resolution + // time verifies via workspace lookup that a Function/Method with this simple + // name exists in the extracted namespace before contributing to the set. if (argNode.type === 'qualified_identifier') { return { simpleClassName: '', templateSimpleClassName: '', templateNamespace: '', templateArgClassNames: [], templateArgNamespaces: [], functionRefText: argNode.text }; } diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-qualified-variable-arg/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-qualified-variable-arg/app.cpp new file mode 100644 index 0000000000..44dcf8618b --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-qualified-variable-arg/app.cpp @@ -0,0 +1,14 @@ +#include "data.h" + +namespace caller { + // data::value is a namespace-qualified VARIABLE, not a function. + // ADL must NOT contribute `data` to the associated namespace set — the + // argument type is `int`, which has no associated namespaces in ISO C++. + // GitNexus guards: collectFunctionRefNamespaces verifies a Function/Method + // named `value` exists in `data` before contributing. Since `data::value` + // is a variable (not a function), `data` is NOT added, and process() is + // not resolved via ADL. + void run() { + process(data::value); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-qualified-variable-arg/data.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-qualified-variable-arg/data.h new file mode 100644 index 0000000000..37cfefd9d4 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-qualified-variable-arg/data.h @@ -0,0 +1,6 @@ +#pragma once + +namespace data { + extern int value; + void process(int n); +} diff --git a/gitnexus/test/integration/resolvers/cpp.test.ts b/gitnexus/test/integration/resolvers/cpp.test.ts index 3f0cc533c0..19a2c2bdf8 100644 --- a/gitnexus/test/integration/resolvers/cpp.test.ts +++ b/gitnexus/test/integration/resolvers/cpp.test.ts @@ -2484,6 +2484,30 @@ describe('C++ ADL — overloaded free-function reference does not crash', () => }); }); +describe('C++ ADL — namespace-qualified variable arg does NOT contribute namespace', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-qualified-variable-arg'), + () => {}, + ); + }, 60000); + + it('process(data::value) emits zero CALLS edges — data::value is a variable, not a function', () => { + const calls = getRelationships(result, 'CALLS'); + const processCalls = calls.filter((c) => c.source === 'run' && c.target === 'process'); + // data::value is a namespace-qualified integer variable. tree-sitter-cpp + // produces a qualified_identifier AST node regardless of whether `value` + // denotes a function, variable, enum, or static member. The GitNexus guard + // in collectFunctionRefNamespaces verifies that a Function/Method named + // `value` exists in the `data` namespace before contributing it. Since + // `data::value` is an int variable, `data` is never added to the associated + // set, so data::process is never found as an ADL candidate. + expect(processCalls.length).toBe(0); + }); +}); + // --------------------------------------------------------------------------- // U5 (follow-up plan 2026-05-13-001): inline namespace transitive walking. // `inline namespace v1 { ... }` makes its members reachable through the From d1a31b7399a402922a04675af8a7b14f67c20e8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 08:04:47 +0000 Subject: [PATCH 06/10] fix(finding-3): function parameters in parameter_list no longer misclassified as free-function refs Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/fa37c0dd-65f9-4dd4-9811-617227a37073 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- .../core/ingestion/languages/cpp/captures.ts | 55 +++++++++++++++++++ .../cpp-adl-param-not-free-func-ref/app.cpp | 14 +++++ .../cpp-adl-param-not-free-func-ref/utils.h | 10 ++++ .../test/integration/resolvers/cpp.test.ts | 25 +++++++++ 4 files changed, 104 insertions(+) create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-param-not-free-func-ref/app.cpp create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-param-not-free-func-ref/utils.h diff --git a/gitnexus/src/core/ingestion/languages/cpp/captures.ts b/gitnexus/src/core/ingestion/languages/cpp/captures.ts index 2f8a1290e4..c840f3479d 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/captures.ts @@ -808,6 +808,53 @@ function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo { return EMPTY_ADL_ARG; } +/** + * Returns `true` when `varName` appears as a parameter name in the nearest + * enclosing `function_definition` or `function_declarator` that contains + * `identNode`. Parameters live in `parameter_list` (a sibling of the + * `compound_statement`), so the `compound_statement`-local declaration scan + * in `lookupAdlIdentifierType` would not find them — causing them to be + * mistakenly classified as potential free-function references. + * + * In tree-sitter-cpp a `function_definition` does NOT expose `parameters` + * as a direct named field; parameters live inside the nested + * `function_declarator`. For `function_declarator` nodes the `parameters` + * field IS direct. Both cases are handled below. + */ +function isIdentifierAFunctionParameter(identNode: SyntaxNode, varName: string): boolean { + let node: SyntaxNode | null = identNode.parent; + let safety = 64; + while (node !== null && safety-- > 0) { + let params: SyntaxNode | null = null; + if (node.type === 'function_declarator') { + // parameters is a direct field on function_declarator. + params = node.childForFieldName('parameters'); + } else if (node.type === 'function_definition') { + // function_definition carries parameters inside its `declarator` field + // (which is a function_declarator). Walk through it. + const decl = node.childForFieldName('declarator'); + if (decl !== null && decl.type === 'function_declarator') { + params = decl.childForFieldName('parameters'); + } + } + if (params !== null) { + for (let i = 0; i < params.namedChildCount; i++) { + const param = params.namedChild(i); + if (param === null) continue; + const declNode = param.childForFieldName('declarator'); + if (declNode === null) continue; + const leafName = extractDeclaratorLeafName(declNode); + if (leafName === varName) return true; + } + // Only check the immediately enclosing function — do not climb further. + break; + } + if (node.type === 'translation_unit') break; + node = node.parent; + } + return false; +} + function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo | null { const varName = identNode.text; let scope: SyntaxNode | null = identNode.parent; @@ -820,6 +867,14 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo | null { } if (scope === null) return null; + // Function parameters live in the enclosing function's `parameter_list`, + // NOT inside the `compound_statement`, so the declaration scan below would + // never find them and would return `null` — incorrectly triggering the + // free-function-reference path. Check the parameter_list first. + if (isIdentifierAFunctionParameter(identNode, varName)) { + return EMPTY_ADL_ARG; + } + let foundAsLocalFunctionPointer = false; for (let i = 0; i < scope.childCount; i++) { const stmt = scope.child(i); diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-param-not-free-func-ref/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-param-not-free-func-ref/app.cpp new file mode 100644 index 0000000000..be2028739b --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-param-not-free-func-ref/app.cpp @@ -0,0 +1,14 @@ +#include "utils.h" + +namespace caller { + // `callback` is a plain int parameter, not a function reference. + // Without the fix: `callback` is not found in the compound_statement + // (parameters live in parameter_list) → lookupAdlIdentifierType returns null + // → treated as free-function ref → workspace scan finds utils::callback + // → `utils` added to ADL set → run_with resolves to utils::run_with (false positive). + // With the fix: isIdentifierAFunctionParameter detects `callback` in the + // parameter_list → returns EMPTY_ADL_ARG → no namespace contributed → 0 CALLS edges. + void run(int callback) { + run_with(callback); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-param-not-free-func-ref/utils.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-param-not-free-func-ref/utils.h new file mode 100644 index 0000000000..e25ac913f4 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-param-not-free-func-ref/utils.h @@ -0,0 +1,10 @@ +#pragma once + +namespace utils { + // A function named `callback` exists in the `utils` namespace. Without the + // parameter-list guard, passing a function *parameter* also named `callback` + // would trigger a workspace scan, find utils::callback, contribute `utils` + // to the ADL set, and emit a false-positive CALLS edge to utils::run_with. + void callback(); + void run_with(int n); +} diff --git a/gitnexus/test/integration/resolvers/cpp.test.ts b/gitnexus/test/integration/resolvers/cpp.test.ts index 19a2c2bdf8..50bb023467 100644 --- a/gitnexus/test/integration/resolvers/cpp.test.ts +++ b/gitnexus/test/integration/resolvers/cpp.test.ts @@ -2508,6 +2508,31 @@ describe('C++ ADL — namespace-qualified variable arg does NOT contribute names }); }); +describe('C++ ADL — function parameter does NOT trigger free-function-ref ADL', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-param-not-free-func-ref'), + () => {}, + ); + }, 60000); + + it('run_with(callback) emits zero CALLS edges when callback is a parameter, not a function reference', () => { + const calls = getRelationships(result, 'CALLS'); + const runWithCalls = calls.filter((c) => c.source === 'run' && c.target === 'run_with'); + // `callback` is an int parameter of `caller::run`. Function parameters + // live in the parameter_list, not in the compound_statement, so the + // local-scope declaration scan would not find it and would return null — + // previously misclassifying it as an unqualified free-function reference. + // The workspace contains utils::callback(), so the scan would find it and + // contribute `utils` to the ADL set, emitting a false-positive CALLS edge + // to utils::run_with. isIdentifierAFunctionParameter now catches this and + // returns EMPTY_ADL_ARG, preventing the workspace scan entirely. + expect(runWithCalls.length).toBe(0); + }); +}); + // --------------------------------------------------------------------------- // U5 (follow-up plan 2026-05-13-001): inline namespace transitive walking. // `inline namespace v1 { ... }` makes its members reachable through the From ac17366eda62caf73f9d1b272eecdc18bc5ddd6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 08:05:30 +0000 Subject: [PATCH 07/10] doc(finding-4): document typedef/using-aliased function-pointer limitation in lookupAdlIdentifierType Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/fa37c0dd-65f9-4dd4-9811-617227a37073 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- .../src/core/ingestion/languages/cpp/captures.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gitnexus/src/core/ingestion/languages/cpp/captures.ts b/gitnexus/src/core/ingestion/languages/cpp/captures.ts index c840f3479d..d57e545ecc 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/captures.ts @@ -969,6 +969,18 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo | null { // If the identifier was found in local scope as a function-pointer variable, // return EMPTY_ADL_ARG so the caller does NOT treat it as a free-function // reference. Otherwise return null to indicate "not in local scope". + // + // Known limitation (Finding 4): variables whose type is a typedef/using alias + // for a function-pointer type are NOT detected here. For example: + // using Callback = void (*)(); + // Callback g; + // foo(g); // `g`'s declarator is `identifier` with type `Callback` + // The declarator has no `pointer_declarator` wrapper, so `isFunctionPointer` + // stays false and `extractAdlSimpleTypeName` returns `"Callback"`. ADL then + // looks for a class named `Callback`; if none exists, this degrades to + // EMPTY_ADL_ARG (class not found → no namespace contributed). If a class + // named `Callback` does exist, a spurious namespace contribution could occur. + // Risk is low in practice; a future fix should resolve the typedef/alias chain. return foundAsLocalFunctionPointer ? EMPTY_ADL_ARG : null; } From 46e2eb119db789a82cc28ae7d3a3b062f318b64d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 08:09:26 +0000 Subject: [PATCH 08/10] test(finding-5): add negative fixtures for local-fp shadowing free-func and unqualified namespace collision Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/fa37c0dd-65f9-4dd4-9811-617227a37073 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- .../app.cpp | 13 ++++++ .../audit.h | 11 +++++ .../cpp-adl-unqualified-ref-collision/app.cpp | 14 ++++++ .../cpp-adl-unqualified-ref-collision/lib.h | 12 +++++ .../test/integration/resolvers/cpp.test.ts | 46 +++++++++++++++++++ 5 files changed, 96 insertions(+) create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-local-fp-shadows-free-func/app.cpp create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-local-fp-shadows-free-func/audit.h create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-unqualified-ref-collision/app.cpp create mode 100644 gitnexus/test/fixtures/lang-resolution/cpp-adl-unqualified-ref-collision/lib.h diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-local-fp-shadows-free-func/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-local-fp-shadows-free-func/app.cpp new file mode 100644 index 0000000000..1fb977e3b0 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-local-fp-shadows-free-func/app.cpp @@ -0,0 +1,13 @@ +#include "audit.h" + +namespace app { + void run() { + // `g` is a locally-declared function-pointer variable. audit::g() also + // exists in the workspace. The local-fp guard (foundAsLocalFunctionPointer) + // must detect `g` as a function-pointer variable declaration and return + // EMPTY_ADL_ARG, preventing the workspace scan that would otherwise find + // audit::g and contribute `audit` to the ADL associated set. + void (*g)(); + record(g); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-local-fp-shadows-free-func/audit.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-local-fp-shadows-free-func/audit.h new file mode 100644 index 0000000000..46df335efb --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-local-fp-shadows-free-func/audit.h @@ -0,0 +1,11 @@ +#pragma once + +namespace audit { + // A free function named `g` exists in the workspace. Without the local-fp + // guard, a locally-declared `void (*g)()` variable would fall through to + // EMPTY_ADL_ARG and not be treated as a free-function ref — but this test + // specifically verifies that the local fp variable shadows the workspace + // function of the same name and no namespace is contributed. + void g(); + void record(void (*fn)()); +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-unqualified-ref-collision/app.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-adl-unqualified-ref-collision/app.cpp new file mode 100644 index 0000000000..e57a2bd6b0 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-unqualified-ref-collision/app.cpp @@ -0,0 +1,14 @@ +#include "lib.h" + +namespace caller { + void run() { + // Unqualified `worker` — not in local compound_statement scope → treated + // as a potential free-function reference. The workspace scan finds + // worker() in BOTH alpha and beta namespaces, so BOTH are added to the + // associated set. run_with() exists in both namespaces as well, so the + // lookup yields two candidates (alpha::run_with, beta::run_with). + // GitNexus ADL_AMBIGUOUS sentinel suppresses the edge — the caller emits + // zero CALLS edges rather than picking one arbitrarily. + run_with(worker); + } +} diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-adl-unqualified-ref-collision/lib.h b/gitnexus/test/fixtures/lang-resolution/cpp-adl-unqualified-ref-collision/lib.h new file mode 100644 index 0000000000..aa1daa331c --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-adl-unqualified-ref-collision/lib.h @@ -0,0 +1,12 @@ +#pragma once + +namespace alpha { + // `worker` exists in both alpha and beta namespaces. + void worker(); + void run_with(void (*fn)()); +} + +namespace beta { + void worker(); + void run_with(void (*fn)()); +} diff --git a/gitnexus/test/integration/resolvers/cpp.test.ts b/gitnexus/test/integration/resolvers/cpp.test.ts index 50bb023467..3073b4e9fe 100644 --- a/gitnexus/test/integration/resolvers/cpp.test.ts +++ b/gitnexus/test/integration/resolvers/cpp.test.ts @@ -2541,6 +2541,52 @@ describe('C++ ADL — function parameter does NOT trigger free-function-ref ADL' // `resolveQualifiedReceiverMember` hook on the ScopeResolver contract. // --------------------------------------------------------------------------- +describe('C++ ADL — local function-pointer var shadows same-named free function', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-local-fp-shadows-free-func'), + () => {}, + ); + }, 60000); + + it('record(g) emits zero CALLS edges even though audit::g() exists in the workspace', () => { + const calls = getRelationships(result, 'CALLS'); + const recordCalls = calls.filter((c) => c.source === 'run' && c.target === 'record'); + // `g` is a locally-declared `void (*g)()` variable. `audit::g()` also + // exists in the workspace. Without the foundAsLocalFunctionPointer guard, + // `g` would not be detected in the compound_statement (it IS there, but + // as a function-pointer declarator), and the workspace scan would find + // audit::g, contribute `audit` to the ADL set, and emit a false-positive + // CALLS edge to audit::record. The guard correctly returns EMPTY_ADL_ARG, + // so no namespace is contributed and no edge is emitted. + expect(recordCalls.length).toBe(0); + }); +}); + +describe('C++ ADL — unqualified free-function ref with namespace collision', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-adl-unqualified-ref-collision'), + () => {}, + ); + }, 60000); + + it('run_with(worker) emits zero CALLS edges when worker exists in two namespaces', () => { + const calls = getRelationships(result, 'CALLS'); + const runWithCalls = calls.filter((c) => c.source === 'run' && c.target === 'run_with'); + // Unqualified `worker` → workspace scan finds alpha::worker and beta::worker. + // Both alpha and beta are added to the associated set. run_with() exists in + // both namespaces → two candidates → ADL_AMBIGUOUS sentinel → zero CALLS + // edges (suppressed rather than arbitrary pick). + expect(runWithCalls.length).toBe(0); + }); +}); + + describe('C++ inline namespace — outer::foo resolves to inline child', () => { let result: PipelineResult; From 6c761363331416d450b8c10dc5689762012367c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 08:37:54 +0000 Subject: [PATCH 09/10] chore(autofix): apply prettier + eslint fixes via /autofix command --- .../core/ingestion/languages/cpp/captures.ts | 18 ++++++++++++++++-- .../test/integration/resolvers/cpp.test.ts | 6 +----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/gitnexus/src/core/ingestion/languages/cpp/captures.ts b/gitnexus/src/core/ingestion/languages/cpp/captures.ts index d57e545ecc..5c74950bb3 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/captures.ts @@ -790,7 +790,14 @@ function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo { // time verifies via workspace lookup that a Function/Method with this simple // name exists in the extracted namespace before contributing to the set. if (argNode.type === 'qualified_identifier') { - return { simpleClassName: '', templateSimpleClassName: '', templateNamespace: '', templateArgClassNames: [], templateArgNamespaces: [], functionRefText: argNode.text }; + return { + simpleClassName: '', + templateSimpleClassName: '', + templateNamespace: '', + templateArgClassNames: [], + templateArgNamespaces: [], + functionRefText: argNode.text, + }; } // Variable reference — look up its declared type (preserving pointer / // reference / qualified-name shape; the existing arity-narrowing helper @@ -800,7 +807,14 @@ function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo { if (result === null) { // Not found in the local compound_statement scope — could be a // free-function reference (unqualified name, namespace scope). - return { simpleClassName: '', templateSimpleClassName: '', templateNamespace: '', templateArgClassNames: [], templateArgNamespaces: [], functionRefText: argNode.text }; + return { + simpleClassName: '', + templateSimpleClassName: '', + templateNamespace: '', + templateArgClassNames: [], + templateArgNamespaces: [], + functionRefText: argNode.text, + }; } return result; } diff --git a/gitnexus/test/integration/resolvers/cpp.test.ts b/gitnexus/test/integration/resolvers/cpp.test.ts index 3073b4e9fe..7b8e7f6f94 100644 --- a/gitnexus/test/integration/resolvers/cpp.test.ts +++ b/gitnexus/test/integration/resolvers/cpp.test.ts @@ -2444,10 +2444,7 @@ describe('C++ ADL — qualified free-function reference contributes its namespac let result: PipelineResult; beforeAll(async () => { - result = await runPipelineFromRepo( - path.join(FIXTURES, 'cpp-adl-free-func-ref'), - () => {}, - ); + result = await runPipelineFromRepo(path.join(FIXTURES, 'cpp-adl-free-func-ref'), () => {}); }, 60000); it('with_callback(utils::worker) resolves to utils::with_callback via ADL', () => { @@ -2586,7 +2583,6 @@ describe('C++ ADL — unqualified free-function ref with namespace collision', ( }); }); - describe('C++ inline namespace — outer::foo resolves to inline child', () => { let result: PipelineResult; From 1f5ce779a7a6c9fc5d7d0e47c5479cc98aa1fe78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 08:53:51 +0000 Subject: [PATCH 10/10] fix(legacy-parity): skip two new negative-fixture tests from legacy DAG parity run Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/64ffbaf1-f442-4a2b-8542-4afa500d9182 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> --- gitnexus/test/integration/resolvers/helpers.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gitnexus/test/integration/resolvers/helpers.ts b/gitnexus/test/integration/resolvers/helpers.ts index 63d59e3c95..bb7904ba74 100644 --- a/gitnexus/test/integration/resolvers/helpers.ts +++ b/gitnexus/test/integration/resolvers/helpers.ts @@ -171,6 +171,15 @@ const LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES: Readonly::g_unqualified() -> f() does NOT bind to Base::f', 'Derived::g_this() -> this->f() resolves to Base::f (1 edge)', 'Derived::g() -> this->f() emits zero CALLS edges when only hidden derived overload is arity-incompatible', + // PR #1598: ADL free-function reference arg negative fixtures rely on + // scope-resolver-only correctness. The legacy DAG falls back to + // `pickUniqueGlobalCallable` which resolves the callee by simple-name + // workspace lookup, ignoring argument analysis. These fixtures expect + // zero CALLS edges (the registry-primary path correctly avoids a false- + // positive), but the legacy path emits one edge via the global fallback. + // Scope-resolver-only correctness wins; backporting is out of scope. + 'process(data::value) emits zero CALLS edges \u2014 data::value is a variable, not a function', + 'run_with(callback) emits zero CALLS edges when callback is a parameter, not a function reference', ]), };