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
737cdaf692
13 changed files with 337 additions and 28 deletions
|
|
@ -21,8 +21,9 @@
|
|||
* - `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.
|
||||
* Function-pointer arguments and the rest of the full closure are still
|
||||
* deliberately excluded. V2 additionally walks class ancestors (via MRO),
|
||||
* so base-class enclosing namespaces also contribute associated namespaces.
|
||||
*
|
||||
* The current implementation also short-circuits to ADL only when ordinary lookup is empty
|
||||
* (`findCallableBindingInScope` returned undefined). ISO C++ would
|
||||
|
|
@ -259,10 +260,19 @@ function addAssociatedNamespaceForClassName(
|
|||
associatedNamespaces: Set<string>,
|
||||
): void {
|
||||
if (simpleClassName.length === 0) return;
|
||||
const classDef = findCppClassDefBySimpleName(simpleClassName, scopes);
|
||||
if (classDef === undefined) return;
|
||||
const classLookup = findCppClassDefBySimpleName(simpleClassName, scopes);
|
||||
if (classLookup === undefined) return;
|
||||
const { classDef, ambiguous } = classLookup;
|
||||
const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
|
||||
if (nsQName !== undefined) associatedNamespaces.add(nsQName);
|
||||
// Preserve V1 collision behavior for the direct class namespace, but avoid
|
||||
// amplifying a same-simple-name collision by walking an arbitrary class's
|
||||
// full MRO chain.
|
||||
if (ambiguous) return;
|
||||
for (const ancestorDefId of scopes.methodDispatch.mroFor(classDef.nodeId)) {
|
||||
const ancestorNsQName = classToNamespaceQualifiedName.get(ancestorDefId);
|
||||
if (ancestorNsQName !== undefined) associatedNamespaces.add(ancestorNsQName);
|
||||
}
|
||||
}
|
||||
|
||||
/** Walk upward from a Class scope, finding the innermost enclosing
|
||||
|
|
@ -357,17 +367,25 @@ function findNamespaceDefInScope(scope: {
|
|||
}
|
||||
|
||||
/** Find a class-like def by simple name across the workspace. V1
|
||||
* arbitrary-pick on collisions (multiple classes share the simple name);
|
||||
* C++ ADL strictness would require full type-driven lookup, but V1
|
||||
* trades that for simplicity. */
|
||||
* still arbitrary-picks the first class on collisions (multiple classes
|
||||
* share the simple name), but reports the collision so callers can avoid
|
||||
* amplifying that uncertainty (for example by skipping MRO expansion).
|
||||
* C++ ADL strictness would require full type-driven lookup. */
|
||||
function findCppClassDefBySimpleName(
|
||||
simpleName: string,
|
||||
scopes: ScopeResolutionIndexes,
|
||||
): SymbolDefinition | undefined {
|
||||
): { classDef: SymbolDefinition; ambiguous: boolean } | undefined {
|
||||
let firstMatch: SymbolDefinition | undefined;
|
||||
for (const def of scopes.defs.byId.values()) {
|
||||
if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface') continue;
|
||||
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
||||
if (simple === simpleName) return def;
|
||||
if (simple !== simpleName) continue;
|
||||
if (firstMatch === undefined) {
|
||||
firstMatch = def;
|
||||
continue;
|
||||
}
|
||||
return { classDef: firstMatch, ambiguous: true };
|
||||
}
|
||||
return undefined;
|
||||
if (firstMatch === undefined) return undefined;
|
||||
return { classDef: firstMatch, ambiguous: false };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { getTreeSitterBufferSize } from '../../constants.js';
|
|||
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
||||
import { splitCppInclude, splitCppUsingDecl } from './import-decomposer.js';
|
||||
import { computeCppDeclarationArity, computeCppCallArity } from './arity-metadata.js';
|
||||
import { markFileLocal } from './file-local-linkage.js';
|
||||
import { markCppAnonymousNamespaceRange, markFileLocal } from './file-local-linkage.js';
|
||||
import { markCppDependentBase } from './two-phase-lookup.js';
|
||||
import { markCppAdlSiteArgs, markCppAdlSiteNoAdl, type CppAdlArgInfo } from './adl.js';
|
||||
import { markCppInlineNamespaceRange } from './inline-namespaces.js';
|
||||
|
|
@ -200,20 +200,37 @@ export function emitCppScopeCaptures(
|
|||
// namespace's source range so `populateCppInlineNamespaceScopes`
|
||||
// (during populateOwners) can match it back to the corresponding
|
||||
// Namespace scope.
|
||||
if (grouped['@declaration.namespace'] !== undefined) {
|
||||
const anchor = grouped['@declaration.namespace']!;
|
||||
const nsNode = findNodeAtRange(tree.rootNode, anchor.range, 'namespace_definition');
|
||||
if (nsNode !== null && isInlineNamespace(nsNode)) {
|
||||
// `@declaration.namespace` fires only for NAMED namespaces (the query
|
||||
// requires a `name: (namespace_identifier)` child). Use the unconditional
|
||||
// `@scope.namespace` capture so the anonymous-namespace branch also runs.
|
||||
const namespaceScopeAnchor = grouped['@declaration.namespace'] ?? grouped['@scope.namespace'];
|
||||
if (namespaceScopeAnchor !== undefined) {
|
||||
const nsNode = findNodeAtRange(
|
||||
tree.rootNode,
|
||||
namespaceScopeAnchor.range,
|
||||
'namespace_definition',
|
||||
);
|
||||
if (nsNode !== null) {
|
||||
// Range coords stored in the shared Range shape use 1-based
|
||||
// line numbers (see `ast-helpers.ts` rangeForNode where
|
||||
// `startPosition.row + 1` is applied). Match that convention so
|
||||
// `populateCppInlineNamespaceScopes` can join against `Scope.range`.
|
||||
markCppInlineNamespaceRange(filePath, {
|
||||
// the populators can join against `Scope.range`.
|
||||
const nsRange = {
|
||||
startLine: nsNode.startPosition.row + 1,
|
||||
startCol: nsNode.startPosition.column,
|
||||
endLine: nsNode.endPosition.row + 1,
|
||||
endCol: nsNode.endPosition.column,
|
||||
});
|
||||
};
|
||||
if (isInlineNamespace(nsNode)) {
|
||||
markCppInlineNamespaceRange(filePath, nsRange);
|
||||
}
|
||||
// Anonymous namespace: `namespace_definition` with no `name` field.
|
||||
// Recorded so `expandCppWildcardNames` can propagate its members
|
||||
// to including TUs even though their names are also `markFileLocal`'d
|
||||
// (which blocks the global free-call fallback's cross-file path).
|
||||
if ((nsNode.childForFieldName?.('name') ?? null) === null) {
|
||||
markCppAnonymousNamespaceRange(filePath, nsRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,33 @@ const fileLocalNames = new Map<string, Set<string>>();
|
|||
*/
|
||||
const nonGloballyVisibleNodeIds = new Map<string, Set<string>>();
|
||||
|
||||
/**
|
||||
* Per-file set of source-range keys identifying `namespace { ... }` blocks.
|
||||
* Resolved to `ScopeId`s in `populateCppAnonymousNamespaceScopes` and
|
||||
* consumed via `isCppAnonymousNamespaceScope`.
|
||||
*
|
||||
* Anonymous namespaces have file-local linkage but, unlike `static`, their
|
||||
* members propagate to any TU that `#include`s the declaring file — each
|
||||
* including TU gets its own internal-linkage copy. So for wildcard import
|
||||
* expansion (`expandCppWildcardNames`) we treat anonymous-namespace owned
|
||||
* defs as if declared at the enclosing scope. Cross-file unqualified
|
||||
* lookup that does NOT go through `#include` is still blocked by the
|
||||
* `isFileLocal` mark recorded on the def's name.
|
||||
*/
|
||||
const anonymousNamespaceRangesByFile = new Map<string, Set<string>>();
|
||||
const anonymousNamespaceScopeIds = new Set<ScopeId>();
|
||||
|
||||
interface RangeKeyShape {
|
||||
readonly startLine: number;
|
||||
readonly startCol: number;
|
||||
readonly endLine: number;
|
||||
readonly endCol: number;
|
||||
}
|
||||
|
||||
function rangeKey(r: RangeKeyShape): string {
|
||||
return `${r.startLine}:${r.startCol}:${r.endLine}:${r.endCol}`;
|
||||
}
|
||||
|
||||
/** Record a symbol name as file-local (static or anonymous namespace). */
|
||||
export function markFileLocal(filePath: string, name: string): void {
|
||||
let names = fileLocalNames.get(filePath);
|
||||
|
|
@ -50,10 +77,51 @@ export function isFileLocal(filePath: string, name: string): boolean {
|
|||
return fileLocalNames.get(filePath)?.has(name) ?? false;
|
||||
}
|
||||
|
||||
/** Capture-time: record an anonymous `namespace_definition` source range. */
|
||||
export function markCppAnonymousNamespaceRange(filePath: string, range: RangeKeyShape): void {
|
||||
let set = anonymousNamespaceRangesByFile.get(filePath);
|
||||
if (set === undefined) {
|
||||
set = new Set();
|
||||
anonymousNamespaceRangesByFile.set(filePath, set);
|
||||
}
|
||||
set.add(rangeKey(range));
|
||||
}
|
||||
|
||||
/** Predicate consumed by `populateCppNonGloballyVisible` and
|
||||
* `expandCppWildcardNames` to exempt anonymous-namespace scopes from
|
||||
* the cross-file unqualified-lookup exclusion that applies to ordinary
|
||||
* named namespaces. */
|
||||
export function isCppAnonymousNamespaceScope(scopeId: ScopeId): boolean {
|
||||
return anonymousNamespaceScopeIds.has(scopeId);
|
||||
}
|
||||
|
||||
/** Clear tracked file-local names (call at start of each resolution pass). */
|
||||
export function clearFileLocalNames(): void {
|
||||
fileLocalNames.clear();
|
||||
nonGloballyVisibleNodeIds.clear();
|
||||
anonymousNamespaceRangesByFile.clear();
|
||||
anonymousNamespaceScopeIds.clear();
|
||||
}
|
||||
|
||||
/** Resolve recorded anonymous-namespace source ranges to `ScopeId`s.
|
||||
* Must run inside `populateOwners` BEFORE `populateCppNonGloballyVisible`
|
||||
* consults the resolved set. */
|
||||
export function populateCppAnonymousNamespaceScopes(parsed: {
|
||||
readonly filePath: string;
|
||||
readonly scopes: readonly {
|
||||
readonly id: ScopeId;
|
||||
readonly kind: string;
|
||||
readonly range: RangeKeyShape;
|
||||
}[];
|
||||
}): void {
|
||||
const ranges = anonymousNamespaceRangesByFile.get(parsed.filePath);
|
||||
if (ranges === undefined || ranges.size === 0) return;
|
||||
for (const scope of parsed.scopes) {
|
||||
if (scope.kind !== 'Namespace') continue;
|
||||
if (ranges.has(rangeKey(scope.range))) {
|
||||
anonymousNamespaceScopeIds.add(scope.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -87,6 +155,13 @@ export function populateCppNonGloballyVisible(parsed: {
|
|||
// ISO C++ `[namespace.def]/p4`. Skip them here so cross-file
|
||||
// unqualified lookup can still see their callable defs.
|
||||
if (scope.kind === 'Namespace' && isCppInlineNamespaceScope(scope.id)) continue;
|
||||
// Anonymous namespaces give internal linkage but their contents are
|
||||
// visible at the enclosing scope within the same TU and propagate to
|
||||
// any TU that `#include`s the declaring file. The `isFileLocal` mark
|
||||
// (recorded on the def's name in this file) still blocks cross-file
|
||||
// unqualified lookup that does not go through #include, so dropping
|
||||
// the structural visibility exclusion here is safe.
|
||||
if (scope.kind === 'Namespace' && anonymousNamespaceScopeIds.has(scope.id)) continue;
|
||||
for (const def of scope.ownedDefs) {
|
||||
set.add(def.nodeId);
|
||||
}
|
||||
|
|
@ -191,9 +266,18 @@ export function expandCppWildcardNames(
|
|||
// including TU. When the owning scope is unknown we default to
|
||||
// include (preserves prior behavior for any def whose structural
|
||||
// ownership wasn't recorded in `Scope.ownedDefs`).
|
||||
//
|
||||
// Anonymous namespaces are exempt: their members propagate to the
|
||||
// enclosing scope of any TU that #includes the declaring file (each
|
||||
// including TU gets its own internal-linkage copy per ISO C++).
|
||||
const ownerScope = ownerScopeByNodeId.get(def.nodeId);
|
||||
const ownerIsAnonymousNamespace =
|
||||
ownerScope !== undefined &&
|
||||
ownerScope.kind === 'Namespace' &&
|
||||
anonymousNamespaceScopeIds.has(ownerScope.id);
|
||||
if (
|
||||
ownerScope !== undefined &&
|
||||
!ownerIsAnonymousNamespace &&
|
||||
(ownerScope.kind === 'Namespace' || ownerScope.kind === 'Class')
|
||||
) {
|
||||
continue;
|
||||
|
|
@ -201,7 +285,11 @@ export function expandCppWildcardNames(
|
|||
|
||||
const name = simpleName(def);
|
||||
if (name === '') continue;
|
||||
if (isFileLocal(target.filePath, name)) continue;
|
||||
// Same exemption for the `isFileLocal` mark — anonymous-namespace
|
||||
// names are recorded as file-local to suppress the global free-call
|
||||
// fallback's cross-file leak, but they MUST still propagate through
|
||||
// wildcard import expansion to including TUs.
|
||||
if (!ownerIsAnonymousNamespace && isFileLocal(target.filePath, name)) continue;
|
||||
if (seen.has(name)) continue;
|
||||
seen.add(name);
|
||||
names.push(name);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
expandCppWildcardNames,
|
||||
isFileLocal,
|
||||
clearFileLocalNames,
|
||||
populateCppAnonymousNamespaceScopes,
|
||||
populateCppNonGloballyVisible,
|
||||
isCppDefGloballyVisible,
|
||||
} from './file-local-linkage.js';
|
||||
|
|
@ -97,10 +98,11 @@ export const cppScopeResolver: ScopeResolver = {
|
|||
|
||||
populateOwners: (parsed: ParsedFile) => {
|
||||
populateClassOwnedMembers(parsed);
|
||||
// Resolve inline-namespace ranges (recorded at capture time) to
|
||||
// ScopeIds BEFORE `populateCppNonGloballyVisible` runs, so the
|
||||
// inline-namespace exemption sees the populated Set.
|
||||
// Resolve inline- and anonymous-namespace ranges (recorded at capture
|
||||
// time) to ScopeIds BEFORE `populateCppNonGloballyVisible` runs, so
|
||||
// both exemptions see the populated Sets.
|
||||
populateCppInlineNamespaceScopes(parsed);
|
||||
populateCppAnonymousNamespaceScopes(parsed);
|
||||
// Track namespace-nested and class-nested defs so the global free-call
|
||||
// fallback and wildcard expansion can suppress them as unqualified
|
||||
// cross-file callables.
|
||||
|
|
|
|||
14
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/app.cpp
vendored
Normal file
14
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/app.cpp
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include "base_lib.h"
|
||||
|
||||
namespace app {
|
||||
struct Token : base_one::Base {};
|
||||
|
||||
void run() {
|
||||
Token t;
|
||||
collide(t);
|
||||
}
|
||||
}
|
||||
|
||||
namespace other {
|
||||
struct Token : base_two::Base {};
|
||||
}
|
||||
11
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/base_lib.h
vendored
Normal file
11
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-collision/base_lib.h
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
namespace base_one {
|
||||
struct Base {};
|
||||
void collide(Base);
|
||||
}
|
||||
|
||||
namespace base_two {
|
||||
struct Base {};
|
||||
void collide(Base);
|
||||
}
|
||||
16
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-negative/app.cpp
vendored
Normal file
16
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces-negative/app.cpp
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#include "base_lib.h"
|
||||
|
||||
namespace app {
|
||||
struct HiddenDerived : HiddenBase {};
|
||||
struct MissingDerived : missing_ns::UnknownBase {};
|
||||
|
||||
void run_hidden() {
|
||||
HiddenDerived d;
|
||||
hidden_probe(d);
|
||||
}
|
||||
|
||||
void run_missing() {
|
||||
MissingDerived d;
|
||||
unresolved_probe(d);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
namespace {
|
||||
struct HiddenBase {};
|
||||
void hidden_probe(HiddenBase);
|
||||
}
|
||||
22
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/app.cpp
vendored
Normal file
22
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/app.cpp
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include "base_lib.h"
|
||||
|
||||
namespace app {
|
||||
struct Derived : base_lib::Base {};
|
||||
struct MultiLevel : middle_lib::Mid {};
|
||||
struct DiamondDerived : diamond_lib::LeftBranch, diamond_lib::RightBranch {};
|
||||
|
||||
void run_single() {
|
||||
Derived d;
|
||||
log(d);
|
||||
}
|
||||
|
||||
void run_multi() {
|
||||
MultiLevel m;
|
||||
trace(m);
|
||||
}
|
||||
|
||||
void run_diamond() {
|
||||
DiamondDerived d;
|
||||
ping(d);
|
||||
}
|
||||
}
|
||||
20
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/base_lib.h
vendored
Normal file
20
gitnexus/test/fixtures/lang-resolution/cpp-adl-base-associated-namespaces/base_lib.h
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
namespace base_lib {
|
||||
struct Base {};
|
||||
void log(Base);
|
||||
|
||||
struct Root {};
|
||||
void trace(Root);
|
||||
}
|
||||
|
||||
namespace middle_lib {
|
||||
struct Mid : base_lib::Root {};
|
||||
}
|
||||
|
||||
namespace diamond_lib {
|
||||
struct DiamondBase {};
|
||||
struct LeftBranch : DiamondBase {};
|
||||
struct RightBranch : DiamondBase {};
|
||||
void ping(DiamondBase);
|
||||
}
|
||||
|
|
@ -2127,6 +2127,92 @@ describe('C++ ADL — basic associated-namespace closure', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('C++ ADL — base-class associated namespaces', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cpp-adl-base-associated-namespaces'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('resolves log(d) to base_lib::log via ADL when Derived inherits from base_lib::Base', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const logCalls = calls.filter((c) => c.source === 'run_single' && c.target === 'log');
|
||||
expect(logCalls.length).toBe(1);
|
||||
expect(logCalls[0].targetFilePath).toContain('base_lib.h');
|
||||
const targetNode = result.graph.getNode(logCalls[0].rel.targetId);
|
||||
expect(logCalls[0].rel.targetId).toBe('Function:base_lib.h:log');
|
||||
expect(targetNode?.properties.parameterTypes).toEqual(['Base']);
|
||||
});
|
||||
|
||||
it('resolves trace(m) via full MRO walk when MultiLevel inherits via middle_lib::Mid -> base_lib::Root', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const traceCalls = calls.filter((c) => c.source === 'run_multi' && c.target === 'trace');
|
||||
expect(traceCalls.length).toBe(1);
|
||||
expect(traceCalls[0].targetFilePath).toContain('base_lib.h');
|
||||
const targetNode = result.graph.getNode(traceCalls[0].rel.targetId);
|
||||
expect(traceCalls[0].rel.targetId).toBe('Function:base_lib.h:trace');
|
||||
expect(targetNode?.properties.parameterTypes).toEqual(['Root']);
|
||||
});
|
||||
|
||||
it('diamond inheritance contributes base namespace once (no duplicate/crash)', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const pingCalls = calls.filter((c) => c.source === 'run_diamond' && c.target === 'ping');
|
||||
expect(pingCalls.length).toBe(1);
|
||||
expect(pingCalls[0].targetFilePath).toContain('base_lib.h');
|
||||
const targetNode = result.graph.getNode(pingCalls[0].rel.targetId);
|
||||
expect(pingCalls[0].rel.targetId).toBe('Function:base_lib.h:ping');
|
||||
expect(targetNode?.properties.parameterTypes).toEqual(['DiamondBase']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ ADL — base-class namespace MRO with simple-name class collisions', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cpp-adl-base-associated-namespaces-collision'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('does NOT emit CALLS for collide(t) when class-name lookup is ambiguous', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const collideCalls = calls.filter((c) => c.source === 'run' && c.target === 'collide');
|
||||
expect(collideCalls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ ADL — base-class namespace mapping skips anonymous/unresolved bases', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cpp-adl-base-associated-namespaces-negative'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('hidden_probe(d) still resolves via ordinary lookup when declaration is visible', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const hiddenProbeCalls = calls.filter(
|
||||
(c) => c.source === 'run_hidden' && c.target === 'hidden_probe',
|
||||
);
|
||||
expect(hiddenProbeCalls.length).toBe(1);
|
||||
expect(hiddenProbeCalls[0].targetFilePath).toContain('base_lib.h');
|
||||
});
|
||||
|
||||
it('unresolved_probe(d) emits zero CALLS when base class cannot be resolved', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const unresolvedProbeCalls = calls.filter(
|
||||
(c) => c.source === 'run_missing' && c.target === 'unresolved_probe',
|
||||
);
|
||||
expect(unresolvedProbeCalls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ ADL — parenthesized name suppresses ADL', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
|
|
|
|||
|
|
@ -122,12 +122,6 @@ const LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES: Readonly<Record<string, Readonly
|
|||
// (PR #1520 review follow-up plan 2026-05-13-001 U3); backporting
|
||||
// is out of scope.
|
||||
'Derived<T>::g() -> f() does NOT bind to Base<T>::f (dependent base)',
|
||||
// The legacy DAG path has no V1/V2 ADL boundary — pointer-typed
|
||||
// arguments resolve via the workspace-wide simple-name walk. The
|
||||
// scope-resolver V1 ADL pass excludes pointer args (closure rules
|
||||
// deferred to V2) per plan 2026-05-13-001 U2 / R4. Scope-resolver-
|
||||
// only correctness win; backporting is out of scope.
|
||||
'record(p) where p is audit::Event* emits zero CALLS — V1 ADL excludes pointer args',
|
||||
// The legacy DAG path has no ADL_AMBIGUOUS suppression sentinel.
|
||||
// When ADL surfaces multiple overloads that collide after C++
|
||||
// int/long normalization, legacy picks the first match arbitrarily.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ import {
|
|||
|
||||
const csharpNamespaceRootImportTest =
|
||||
'emits the using-import edge App/Program.cs -> Models/User.cs through the scope-resolution path';
|
||||
const cppBaseNamespaceAdlTests = [
|
||||
'resolves log(d) to base_lib::log via ADL when Derived inherits from base_lib::Base',
|
||||
'resolves trace(m) via full MRO walk when MultiLevel inherits via middle_lib::Mid -> base_lib::Root',
|
||||
'diamond inheritance contributes base namespace once (no duplicate/crash)',
|
||||
] as const;
|
||||
|
||||
describe('resolver parity expected legacy failures', () => {
|
||||
it('uses the same env var convention as the parity workflow', () => {
|
||||
|
|
@ -44,4 +49,14 @@ describe('resolver parity expected legacy failures', () => {
|
|||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('does not mark cpp base-namespace ADL coverage as expected failures in legacy parity', () => {
|
||||
for (const testName of cppBaseNamespaceAdlTests) {
|
||||
expect(
|
||||
isLegacyResolverParityExpectedFailure('cpp', testName, {
|
||||
REGISTRY_PRIMARY_CPP: '0',
|
||||
}),
|
||||
).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue