mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
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:
parent
d37a729167
commit
60c5bc7a31
5 changed files with 249 additions and 18 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
13
gitnexus/test/fixtures/lang-resolution/cpp-adl-template-args/app.cpp
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/cpp-adl-template-args/app.cpp
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
12
gitnexus/test/fixtures/lang-resolution/cpp-adl-template-args/audit.h
vendored
Normal file
12
gitnexus/test/fixtures/lang-resolution/cpp-adl-template-args/audit.h
vendored
Normal 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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue