mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-04 02:31:36 +00:00
Merge branch 'main' into feat/Desktop-app
This commit is contained in:
commit
a815081e19
6 changed files with 307 additions and 26 deletions
|
|
@ -15,14 +15,14 @@
|
|||
*
|
||||
* ## 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 and reference-typed class args (`audit::Event* p`,
|
||||
* `audit::Event& r`, `audit::Event&& rr`): they contribute the pointee /
|
||||
* referred class's enclosing namespace too. 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, pointer,
|
||||
* and reference) and template specializations with explicit type arguments:
|
||||
* - `audit::Event e`, `audit::Event* p`, `audit::Event** pp`
|
||||
* - `audit::Event& r`, `audit::Event&& rr`
|
||||
* - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
|
||||
*
|
||||
* 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,8 +67,20 @@ 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;
|
||||
/** 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[]>();
|
||||
|
|
@ -169,11 +181,7 @@ 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;
|
||||
const classDef = findCppClassDefBySimpleName(arg.simpleClassName, scopes);
|
||||
if (classDef === undefined) continue;
|
||||
const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
|
||||
if (nsQName !== undefined) associatedNamespaces.add(nsQName);
|
||||
collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces);
|
||||
}
|
||||
if (associatedNamespaces.size === 0) return undefined;
|
||||
|
||||
|
|
@ -222,6 +230,41 @@ export function pickCppAdlCandidates(
|
|||
return ADL_AMBIGUOUS;
|
||||
}
|
||||
|
||||
function collectAssociatedNamespacesForAdlArg(
|
||||
arg: CppAdlArgInfo,
|
||||
scopes: ScopeResolutionIndexes,
|
||||
associatedNamespaces: Set<string>,
|
||||
): void {
|
||||
// For template args this may be the template name itself (e.g. `vector`);
|
||||
// simple-name lookup can match project classes with the same name (known
|
||||
// V1/V2 simplification).
|
||||
addAssociatedNamespaceForClassName(arg.simpleClassName, scopes, associatedNamespaces);
|
||||
|
||||
// Includes template-owner namespaces (e.g. `std` in std::vector<T>). If
|
||||
// that surfaces extra candidates, ADL_AMBIGUOUS suppression below prevents
|
||||
// arbitrary edge emission.
|
||||
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) {
|
||||
addAssociatedNamespaceForClassName(className, scopes, associatedNamespaces);
|
||||
}
|
||||
}
|
||||
|
||||
function addAssociatedNamespaceForClassName(
|
||||
simpleClassName: string,
|
||||
scopes: ScopeResolutionIndexes,
|
||||
associatedNamespaces: Set<string>,
|
||||
): void {
|
||||
if (simpleClassName.length === 0) return;
|
||||
const classDef = findCppClassDefBySimpleName(simpleClassName, scopes);
|
||||
if (classDef === undefined) return;
|
||||
const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
|
||||
if (nsQName !== undefined) associatedNamespaces.add(nsQName);
|
||||
}
|
||||
|
||||
/** Walk upward from a Class scope, finding the innermost enclosing
|
||||
* Namespace scope, and return that namespace's qualified name (dot-
|
||||
* joined, outermost-first). Returns '' when the class has no enclosing
|
||||
|
|
|
|||
|
|
@ -720,12 +720,14 @@ function isParenthesizedFunctionCall(callNode: SyntaxNode): boolean {
|
|||
|
||||
/**
|
||||
* Per-argument ADL classification: walk each argument of a free call and
|
||||
* decide whether it resolves to a directly-named class or class-pointer
|
||||
* type (ADL fires) or to an excluded shape such as a reference, function
|
||||
* pointer, primitive, literal, or template specialization.
|
||||
* classify its declared type for associated-namespace lookup.
|
||||
*
|
||||
* Class-typed values and class pointers (`N::S`, `N::S*`, `N::S**`) all
|
||||
* preserve the pointee class name for associated-namespace lookup.
|
||||
* Value/pointer/reference class-typed args and template specializations
|
||||
* with explicit type arguments contribute; function pointers, primitives,
|
||||
* literals, and other unsupported shapes produce an empty result.
|
||||
*
|
||||
* Class-typed values/pointers/references (`N::S`, `N::S*`, `N::S&`) all
|
||||
* preserve the class name for associated-namespace lookup.
|
||||
* Function pointers remain excluded even when their return type names a
|
||||
* class, because the associated entity is the pointed-to function type,
|
||||
* not the return type.
|
||||
|
|
@ -743,7 +745,14 @@ function inferCppCallAdlArgs(callNode: SyntaxNode): CppAdlArgInfo[] {
|
|||
return out;
|
||||
}
|
||||
|
||||
const EMPTY_ADL_ARG: CppAdlArgInfo = { simpleClassName: '' };
|
||||
const ADL_TEMPLATE_RECURSION_MAX_DEPTH = 8;
|
||||
const EMPTY_ADL_ARG: CppAdlArgInfo = {
|
||||
simpleClassName: '',
|
||||
templateSimpleClassName: '',
|
||||
templateNamespace: '',
|
||||
templateArgClassNames: [],
|
||||
templateArgNamespaces: [],
|
||||
};
|
||||
|
||||
function classifyAdlArg(argNode: SyntaxNode): CppAdlArgInfo {
|
||||
// Literals and primitive-shaped expressions never have associated namespaces.
|
||||
|
|
@ -845,29 +854,175 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo {
|
|||
if (isFunctionPointer || nameText !== varName) continue;
|
||||
|
||||
const simpleClassName = extractAdlSimpleTypeName(typeNode);
|
||||
return { simpleClassName };
|
||||
const {
|
||||
templateSimpleClassName,
|
||||
templateNamespace,
|
||||
templateArgClassNames,
|
||||
templateArgNamespaces,
|
||||
} = extractAdlTemplateInfo(typeNode);
|
||||
return {
|
||||
simpleClassName,
|
||||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -222,8 +222,10 @@ export const cppScopeResolver: ScopeResolver = {
|
|||
// C++ argument-dependent / Koenig lookup (U2 of plan 2026-05-13-001).
|
||||
// Fires after `findCallableBindingInScope` returns undefined; surfaces
|
||||
// candidates from the associated namespaces of class-typed arguments.
|
||||
// V1 limitation: only direct enclosing-namespace closure for value
|
||||
// class-typed args; pointer/reference/template-spec args excluded.
|
||||
// Current boundary: class-typed value/pointer/reference args and template
|
||||
// specializations with explicit type arguments contribute associated
|
||||
// namespaces. Function-pointer args, base-class associated namespaces,
|
||||
// and full ordinary+ADL merge remain excluded.
|
||||
resolveAdlCandidates: (site, callerParsed, scopes, parsedFiles) => {
|
||||
// `using ns::name;` introduces `name` into ordinary unqualified lookup.
|
||||
// For template-class method bodies, lexical scope walks can miss this
|
||||
|
|
|
|||
23
gitnexus/test/fixtures/lang-resolution/cpp-adl-template-args/app.cpp
vendored
Normal file
23
gitnexus/test/fixtures/lang-resolution/cpp-adl-template-args/app.cpp
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#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);
|
||||
}
|
||||
|
||||
void runArray() {
|
||||
std::array<N::T, 4> a;
|
||||
applyArray(a);
|
||||
}
|
||||
|
||||
void runStdConflict() {
|
||||
std::vector<N::T> v;
|
||||
applyStdConflict(v);
|
||||
}
|
||||
}
|
||||
19
gitnexus/test/fixtures/lang-resolution/cpp-adl-template-args/audit.h
vendored
Normal file
19
gitnexus/test/fixtures/lang-resolution/cpp-adl-template-args/audit.h
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#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);
|
||||
void applyArray(std::array<T, 4> a);
|
||||
void applyStdConflict(std::vector<T> v);
|
||||
}
|
||||
|
||||
namespace std {
|
||||
void applyStdConflict(vector<N::T> v);
|
||||
}
|
||||
|
|
@ -2103,7 +2103,9 @@ describe('C++ two-phase template lookup — cross-file namespace variant', () =>
|
|||
// Free-function calls with class-typed arguments must consider candidates
|
||||
// declared in the argument's enclosing namespace (associated namespace).
|
||||
// V1 boundary: only direct enclosing-namespace closure for value class-
|
||||
// typed args; pointer and reference args included, template-spec args excluded.
|
||||
// typed args; pointer/reference args and template specializations with
|
||||
// explicit type arguments included. Function pointers and base-class
|
||||
// associated namespaces remain excluded.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('C++ ADL — basic associated-namespace closure', () => {
|
||||
|
|
@ -2276,6 +2278,43 @@ 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');
|
||||
});
|
||||
|
||||
it('applyArray(a) where a is std::array<N::T, 4> resolves to N::applyArray (non-type arg ignored)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const applyCalls = calls.filter((c) => c.source === 'runArray' && c.target === 'applyArray');
|
||||
expect(applyCalls.length).toBe(1);
|
||||
expect(applyCalls[0].targetFilePath).toContain('audit.h');
|
||||
});
|
||||
|
||||
it('applyStdConflict(v) is suppressed when ADL surfaces both N and std candidates', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const applyCalls = calls.filter(
|
||||
(c) => c.source === 'runStdConflict' && c.target === 'applyStdConflict',
|
||||
);
|
||||
expect(applyCalls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ ADL — int/long-collision overloads suppress via OVERLOAD_AMBIGUOUS', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue