fix(cpp): include template specialization namespaces in ADL

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e5c28930-1213-46ed-a5e0-b6e1c0729522

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-14 17:39:57 +00:00 • committed by GitHub
parent d37a729167
commit 60c5bc7a31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 249 additions and 18 deletions

View file

@ -15,14 +15,13 @@
*
* ## 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,
* base-class associated namespaces, and the rest of the full closure are
* still deliberately excluded.
* The current implementation covers class-typed arguments (value and pointer)
* and template specializations with explicit type arguments:
* - `audit::Event e`, `audit::Event* p`, `audit::Event** pp`
* - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
*
* Reference arguments, function-pointer arguments, base-class associated
* namespaces, and the rest of the full closure are still deliberately excluded.
*
* The current implementation also short-circuits to ADL only when ordinary lookup is empty
* (`findCallableBindingInScope` returned undefined). ISO C++ would
@ -67,13 +66,25 @@ import {
*/
export interface CppAdlArgInfo {
/** Simple class-like type name (last segment of qualified name); empty
* for primitives, literals, function pointers, template specs, etc. */
* for primitives, literals, function pointers, etc. */
readonly simpleClassName: string;
/** True when the variable's declarator contained one or more
* `pointer_declarator` wrappers. */
readonly isPointer: boolean;
/** True when the variable's declarator was a `reference_declarator`. */
readonly isReference: boolean;
/** Template's own simple class-like name (e.g. `vector` for
* `std::vector<N::T>`), empty when arg type is not a template spec. */
readonly templateSimpleClassName: string;
/** Template's own enclosing namespace (dot-qualified, e.g. `std`), empty
* when unavailable / unqualified. */
readonly templateNamespace: string;
/** Class-like names extracted from explicit type template arguments,
* recursively bounded. */
readonly templateArgClassNames: readonly string[];
/** Enclosing namespaces extracted from explicit type template arguments,
* recursively bounded. */
readonly templateArgNamespaces: readonly string[];
}
const argInfoBySite = new Map<string, readonly CppAdlArgInfo[]>();
@ -174,12 +185,28 @@ export function pickCppAdlCandidates(
// Collect associated namespace QNames from every participating class-typed arg.
const associatedNamespaces = new Set<string>();
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 !== '') {
const classDef = findCppClassDefBySimpleName(arg.simpleClassName, scopes);
if (classDef !== undefined) {
const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
if (nsQName !== undefined) associatedNamespaces.add(nsQName);
}
}
if (arg.templateNamespace.length > 0) associatedNamespaces.add(arg.templateNamespace);
for (const ns of arg.templateArgNamespaces) {
if (ns.length > 0) associatedNamespaces.add(ns);
}
for (const className of arg.templateArgClassNames) {
if (className.length === 0) continue;
const classDef = findCppClassDefBySimpleName(className, scopes);
if (classDef === undefined) continue;
const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
if (nsQName !== undefined) associatedNamespaces.add(nsQName);
}
}
if (associatedNamespaces.size === 0) return undefined;

View file

@ -743,7 +743,16 @@ function inferCppCallAdlArgs(callNode: SyntaxNode): CppAdlArgInfo[] {
return out;
}
const EMPTY_ADL_ARG: CppAdlArgInfo = { simpleClassName: '', isPointer: false, isReference: false };
const ADL_TEMPLATE_RECURSION_MAX_DEPTH = 8;
const EMPTY_ADL_ARG: CppAdlArgInfo = {
simpleClassName: '',
isPointer: false,
isReference: false,
templateSimpleClassName: '',
templateNamespace: '',
templateArgClassNames: [],
templateArgNamespaces: [],
};
function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo {
// Literals and primitive-shaped expressions never have associated namespaces.
@ -848,29 +857,177 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo {
if (isFunctionPointer || nameText !== varName) continue;
const simpleClassName = extractAdlSimpleTypeName(typeNode);
return { simpleClassName, isPointer, isReference };
const {
templateSimpleClassName,
templateNamespace,
templateArgClassNames,
templateArgNamespaces,
} = extractAdlTemplateInfo(typeNode);
return {
simpleClassName,
isPointer,
isReference,
templateSimpleClassName,
templateNamespace,
templateArgClassNames,
templateArgNamespaces,
};
}
return EMPTY_ADL_ARG;
}
/** Extract the simple class-like type name from a `type:` field node.
* Returns '' for primitives, template specializations, and any other
* Returns '' for primitives and any other
* unsupported type-only shape. Function pointers are filtered at the
* declarator level in `lookupAdlIdentifierType`. */
function extractAdlSimpleTypeName(typeNode: SyntaxNode): string {
if (typeNode.type === 'type_descriptor') {
const innerType = typeNode.childForFieldName('type');
if (innerType !== null) return extractAdlSimpleTypeName(innerType);
for (let i = 0; i < typeNode.childCount; i++) {
const child = typeNode.child(i);
if (child === null) continue;
if (
child.type === 'type_identifier' ||
child.type === 'qualified_identifier' ||
child.type === 'template_type'
) {
return extractAdlSimpleTypeName(child);
}
}
return '';
}
if (typeNode.type === 'primitive_type') return '';
if (typeNode.type === 'sized_type_specifier') return '';
if (typeNode.type === 'type_identifier') return typeNode.text;
if (typeNode.type === 'template_type') {
const nameNode = typeNode.childForFieldName('name');
if (nameNode !== null) return extractAdlSimpleTypeName(nameNode);
const id = findFirstDescendantOfType(typeNode, 'type_identifier');
return id !== null ? id.text : '';
}
if (typeNode.type === 'qualified_identifier') {
const nameNode = typeNode.childForFieldName('name');
if (nameNode !== null) return extractAdlSimpleTypeName(nameNode);
const id = findFirstDescendantOfType(typeNode, 'type_identifier');
return id !== null ? id.text : '';
}
// template_type (e.g. `vector<int>`), function pointers, decltype — V1 excludes.
// Function pointers, decltype, etc — unsupported for ADL participation.
return '';
}
function extractAdlTypeNamespace(typeNode: SyntaxNode): string {
if (typeNode.type === 'type_descriptor') {
const innerType = typeNode.childForFieldName('type');
if (innerType !== null) return extractAdlTypeNamespace(innerType);
for (let i = 0; i < typeNode.childCount; i++) {
const child = typeNode.child(i);
if (child === null) continue;
if (
child.type === 'qualified_identifier' ||
child.type === 'template_type' ||
child.type === 'type_identifier'
) {
return extractAdlTypeNamespace(child);
}
}
return '';
}
if (typeNode.type === 'template_type') {
const nameNode = typeNode.childForFieldName('name');
return nameNode !== null ? extractAdlTypeNamespace(nameNode) : '';
}
if (typeNode.type === 'qualified_identifier') {
const scope = typeNode.childForFieldName('scope');
if (scope !== null) return normalizeCppNamespaceQName(scope.text);
return extractNamespaceFromQualifiedText(typeNode.text);
}
return '';
}
function extractAdlTemplateInfo(typeNode: SyntaxNode): {
templateSimpleClassName: string;
templateNamespace: string;
templateArgClassNames: string[];
templateArgNamespaces: string[];
} {
const templateTypeNode = findTemplateTypeNode(typeNode);
if (templateTypeNode === null) {
return {
templateSimpleClassName: '',
templateNamespace: '',
templateArgClassNames: [],
templateArgNamespaces: [],
};
}
const templateArgClassNames: string[] = [];
const templateArgNamespaces: string[] = [];
collectAdlTemplateArgs(templateTypeNode, 0, templateArgClassNames, templateArgNamespaces);
return {
templateSimpleClassName: extractAdlSimpleTypeName(templateTypeNode),
templateNamespace: extractAdlTypeNamespace(typeNode),
templateArgClassNames,
templateArgNamespaces,
};
}
function collectAdlTemplateArgs(
templateTypeNode: SyntaxNode,
depth: number,
outClassNames: string[],
outNamespaces: string[],
): void {
if (depth >= ADL_TEMPLATE_RECURSION_MAX_DEPTH) return;
if (templateTypeNode.type !== 'template_type') return;
const argList =
templateTypeNode.childForFieldName('arguments') ??
findChildOfType(templateTypeNode, ['template_argument_list']);
if (argList === null) return;
for (let i = 0; i < argList.namedChildCount; i++) {
const arg = argList.namedChild(i);
if (arg === null || arg.type !== 'type_descriptor') continue;
const simpleClassName = extractAdlSimpleTypeName(arg);
if (simpleClassName.length > 0) outClassNames.push(simpleClassName);
const ns = extractAdlTypeNamespace(arg);
if (ns.length > 0) outNamespaces.push(ns);
const nestedType = arg.childForFieldName('type');
const nestedTemplate = nestedType !== null ? findTemplateTypeNode(nestedType) : null;
if (nestedTemplate !== null) {
collectAdlTemplateArgs(nestedTemplate, depth + 1, outClassNames, outNamespaces);
}
}
}
function findTemplateTypeNode(typeNode: SyntaxNode): SyntaxNode | null {
if (typeNode.type === 'template_type') return typeNode;
if (typeNode.type === 'type_descriptor') {
const innerType = typeNode.childForFieldName('type');
if (innerType !== null) return findTemplateTypeNode(innerType);
return null;
}
if (typeNode.type === 'qualified_identifier') {
const nameNode = typeNode.childForFieldName('name');
if (nameNode !== null) return findTemplateTypeNode(nameNode);
return null;
}
return null;
}
function normalizeCppNamespaceQName(text: string): string {
const normalized = text.replace(/^::/, '').replace(/::$/, '').replace(/::/g, '.');
return normalized;
}
function extractNamespaceFromQualifiedText(text: string): string {
const cleaned = text.replace(/\s+/g, '');
const idx = cleaned.lastIndexOf('::');
if (idx <= 0) return '';
return normalizeCppNamespaceQName(cleaned.slice(0, idx));
}
/**
* Check if a C++ function_definition or declaration has `static` storage class.
*/

View file

@ -0,0 +1,13 @@
#include "audit.h"
namespace app {
void run() {
std::vector<N::T> v;
apply(v);
}
void runNested() {
std::map<std::string, std::vector<N::T>> m;
applyNested(m);
}
}

View file

@ -0,0 +1,12 @@
#pragma once
#include <map>
#include <string>
#include <vector>
namespace N {
struct T {};
void apply(std::vector<T> v);
void applyNested(std::map<std::string, std::vector<T>> m);
}

View file

@ -2228,6 +2228,28 @@ describe('C++ ADL — pointer-to-pointer args participate', () => {
});
});
describe('C++ ADL — template specialization args contribute associated namespaces', () => {
let result: PipelineResult;
beforeAll(async () => {
result = await runPipelineFromRepo(path.join(FIXTURES, 'cpp-adl-template-args'), () => {});
}, 60000);
it('apply(v) where v is std::vector<N::T> resolves to N::apply via ADL template-arg namespace', () => {
const calls = getRelationships(result, 'CALLS');
const applyCalls = calls.filter((c) => c.source === 'run' && c.target === 'apply');
expect(applyCalls.length).toBe(1);
expect(applyCalls[0].targetFilePath).toContain('audit.h');
});
it('applyNested(m) where m is std::map<std::string, std::vector<N::T>> resolves via nested template-arg namespace', () => {
const calls = getRelationships(result, 'CALLS');
const applyCalls = calls.filter((c) => c.source === 'runNested' && c.target === 'applyNested');
expect(applyCalls.length).toBe(1);
expect(applyCalls[0].targetFilePath).toContain('audit.h');
});
});
describe('C++ ADL — int/long-collision overloads suppress via OVERLOAD_AMBIGUOUS', () => {
let result: PipelineResult;