merge: sync origin/main into ADL template branch

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/ac900193-369d-473b-a5d6-56c78341cae0

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-14 19:31:42 +00:00 • committed by GitHub
parent 0f9be71905
commit 113b54f7ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 121 additions and 18 deletions

View file

@ -15,13 +15,14 @@
*
* ## Current boundary
*
* The current implementation covers class-typed arguments (value and pointer)
* and template specializations with explicit type arguments:
* 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)
*
* Reference arguments, function-pointer arguments, base-class associated
* namespaces, and the rest of the full closure are still deliberately excluded.
* 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
@ -61,8 +62,8 @@ 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 where `simpleClassName !== ''`, including class pointers and
* references whose declarator chain resolves to a named class type.
*/
export interface CppAdlArgInfo {
/** Simple class-like type name (last segment of qualified name); empty
@ -163,8 +164,8 @@ export function populateCppAssociatedNamespaces(parsed: ParsedFile): void {
*
* Fires only when:
* - the call site is not in `noAdlSites` (parenthesized form), AND
* - at least one argument resolves to a named class type (value or
* pointer, but not reference, function pointer, literal, or primitive).
* - at least one argument resolves to a named class type (value,
* pointer, or reference; but not function pointer, literal, or primitive).
*/
export function pickCppAdlCandidates(
site: {
@ -185,8 +186,6 @@ 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.isReference) continue;
if (arg.simpleClassName !== '') {
// For template args this may be the template name itself (e.g.
// `vector`); simple-name lookup can match project classes with the

View file

@ -803,13 +803,14 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo {
// Unwrap declarator chain to find pointer/reference markers and the
// variable name. `init_declarator > pointer_declarator > identifier`
// means pointer-typed; repeated pointer wrappers still count as pointer
// typed; `init_declarator > reference_declarator > ...` means
// reference-typed; bare `init_declarator > identifier` is value.
// typed; `init_declarator > reference_declarator > ...` (or
// `rvalue_reference_declarator`) means reference-typed; bare
// `init_declarator > identifier` is value.
// Function-pointer wrappers (`pointer_declarator > function_declarator`)
// must not contribute ADL associated namespaces.
let isFunctionPointer = false;
let isPointer = false;
let isReference = false;
let isFunctionPointer = false;
let inner: SyntaxNode = declarator;
let nameText: string | null = null;
let safety = 16; // bound walk depth defensively
@ -825,7 +826,7 @@ function lookupAdlIdentifierType(identNode: SyntaxNode): CppAdlArgInfo {
inner = next;
continue;
}
if (inner.type === 'reference_declarator') {
if (inner.type === 'reference_declarator' || inner.type === 'rvalue_reference_declarator') {
isReference = true;
// reference_declarator has a single child (the inner declarator).
let next: SyntaxNode | null = null;

View file

@ -222,10 +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.
// Current boundary: class-typed value/pointer args and template
// Current boundary: class-typed value/pointer/reference args and template
// specializations with explicit type arguments contribute associated
// namespaces. Reference args, function-pointer args, base-class
// associated namespaces, and full ordinary+ADL merge remain excluded.
// 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,21 @@
#include "audit.h"
namespace app {
void runRef() {
audit::Event e;
audit::Event& s = e;
record(s);
}
void runConstRef() {
audit::Event e;
const audit::Event& constEventRef = e;
recordConst(constEventRef);
}
void runPrimitiveRef() {
int n = 0;
int& r = n;
note(r);
}
}

View file

@ -0,0 +1,5 @@
#pragma once
namespace audit {
struct Event {};
}

View file

@ -0,0 +1,8 @@
#pragma once
#include "audit.h"
namespace audit {
void record(Event& e);
void recordConst(const Event& e);
}

View file

@ -0,0 +1,9 @@
#include "audit.h"
namespace app {
void runRvalueRef() {
audit::Event e;
audit::Event&& rr = static_cast<audit::Event&&>(e);
record(rr);
}
}

View file

@ -0,0 +1,5 @@
#pragma once
namespace audit {
struct Event {};
}

View file

@ -0,0 +1,7 @@
#pragma once
#include "audit.h"
namespace audit {
void record(Event&& e);
}

View file

@ -2103,7 +2103,7 @@ 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 / reference / template-spec args excluded.
// typed args; pointer and reference args included, template-spec args excluded.
// ---------------------------------------------------------------------------
describe('C++ ADL — basic associated-namespace closure', () => {
@ -2161,6 +2161,54 @@ describe('C++ ADL — pointer arg unwrapping', () => {
});
});
describe('C++ ADL — reference arg unwrapping', () => {
let result: PipelineResult;
beforeAll(async () => {
result = await runPipelineFromRepo(
path.join(FIXTURES, 'cpp-adl-reference-arg-boundary'),
() => {},
);
}, 60000);
it('record(s) where s is audit::Event& resolves to audit::record via ADL', () => {
const calls = getRelationships(result, 'CALLS');
const recordCalls = calls.filter((c) => c.source === 'runRef' && c.target === 'record');
expect(recordCalls.length).toBe(1);
expect(recordCalls[0].targetFilePath).toContain('record.h');
});
it('recordConst(cs) where cs is const audit::Event& resolves via ADL', () => {
const calls = getRelationships(result, 'CALLS');
const recordCalls = calls.filter(
(c) => c.source === 'runConstRef' && c.target === 'recordConst',
);
expect(recordCalls.length).toBe(1);
expect(recordCalls[0].targetFilePath).toContain('record.h');
});
it('note(r) where r is int& emits zero CALLS edges (primitive ref)', () => {
const calls = getRelationships(result, 'CALLS');
const noteCalls = calls.filter((c) => c.source === 'runPrimitiveRef' && c.target === 'note');
expect(noteCalls.length).toBe(0);
});
});
describe('C++ ADL — rvalue reference args participate', () => {
let result: PipelineResult;
beforeAll(async () => {
result = await runPipelineFromRepo(path.join(FIXTURES, 'cpp-adl-rvalue-ref'), () => {});
}, 60000);
it('record(rr) where rr is audit::Event&& resolves to audit::record via ADL', () => {
const calls = getRelationships(result, 'CALLS');
const recordCalls = calls.filter((c) => c.source === 'runRvalueRef' && c.target === 'record');
expect(recordCalls.length).toBe(1);
expect(recordCalls[0].targetFilePath).toContain('record-rvalue.h');
});
});
describe('C++ ADL — function pointer args do not participate', () => {
let result: PipelineResult;