Merge branch 'main' into feat/Desktop-app

This commit is contained in:
Sparsh 2026-05-15 09:43:20 +05:30 • committed by GitHub
commit a815081e19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 307 additions and 26 deletions

View file

@ -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

View file

@ -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.
*/

View file

@ -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

View 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);
}
}

View 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);
}

View file

@ -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;