gitnexus 1.6.7-rc.4 → 1.6.7-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ingestion/languages/cpp/capture-side-channel.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/capture-side-channel.js +9 -2
- package/dist/core/ingestion/languages/cpp/captures.js +2 -0
- package/dist/core/ingestion/languages/cpp/import-decomposer.js +7 -0
- package/dist/core/ingestion/languages/cpp/member-lookup.d.ts +32 -0
- package/dist/core/ingestion/languages/cpp/member-lookup.js +461 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +4 -2
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +17 -2
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +56 -0
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/lbug/pool-adapter.js +14 -0
- package/dist/mcp/local/local-backend.js +124 -45
- package/package.json +1 -1
- package/scripts/bench/fts-evict-reload-rss.mjs +374 -0
- package/scripts/bench/fts-rss-verdict.mjs +105 -0
- package/scripts/install-duckdb-extension.mjs +22 -6
|
@@ -29,6 +29,7 @@ import type { ParsedFile } from '../../../../_shared/index.js';
|
|
|
29
29
|
import { type CppAdlSideChannel } from './adl.js';
|
|
30
30
|
import { type CppFileLocalSideChannel } from './file-local-linkage.js';
|
|
31
31
|
import { type CppTwoPhaseSideChannel } from './two-phase-lookup.js';
|
|
32
|
+
import { type CppMemberLookupSideChannel } from './member-lookup.js';
|
|
32
33
|
/**
|
|
33
34
|
* Plain JSON-serializable composite of every C++ capture-time side-channel
|
|
34
35
|
* slice for one file. Carried opaquely on `ParsedFile.captureSideChannel`.
|
|
@@ -48,6 +49,7 @@ export interface CppCaptureSideChannel {
|
|
|
48
49
|
readonly inlineNamespaceRanges: readonly string[];
|
|
49
50
|
readonly fileLocal: CppFileLocalSideChannel;
|
|
50
51
|
readonly twoPhase: CppTwoPhaseSideChannel;
|
|
52
|
+
readonly memberLookup: CppMemberLookupSideChannel;
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* `LanguageProvider.collectCaptureSideChannel` implementation for C++.
|
|
@@ -29,6 +29,7 @@ import { collectCppAdlSideChannel, applyCppAdlSideChannel } from './adl.js';
|
|
|
29
29
|
import { collectCppInlineNamespaceSideChannel, applyCppInlineNamespaceSideChannel, } from './inline-namespaces.js';
|
|
30
30
|
import { collectCppFileLocalSideChannel, applyCppFileLocalSideChannel, } from './file-local-linkage.js';
|
|
31
31
|
import { collectCppTwoPhaseSideChannel, applyCppTwoPhaseSideChannel, } from './two-phase-lookup.js';
|
|
32
|
+
import { applyCppMemberLookupSideChannel, collectCppMemberLookupSideChannel, } from './member-lookup.js';
|
|
32
33
|
/**
|
|
33
34
|
* `LanguageProvider.collectCaptureSideChannel` implementation for C++.
|
|
34
35
|
* Returns `undefined` when this file recorded no side-channel state at all, so
|
|
@@ -39,16 +40,19 @@ export function collectCppCaptureSideChannel(filePath) {
|
|
|
39
40
|
const inlineNamespaceRanges = collectCppInlineNamespaceSideChannel(filePath);
|
|
40
41
|
const fileLocal = collectCppFileLocalSideChannel(filePath);
|
|
41
42
|
const twoPhase = collectCppTwoPhaseSideChannel(filePath);
|
|
43
|
+
const memberLookup = collectCppMemberLookupSideChannel(filePath);
|
|
42
44
|
const isEmpty = adl.argInfoBySite.length === 0 &&
|
|
43
45
|
adl.noAdlSites.length === 0 &&
|
|
44
46
|
inlineNamespaceRanges.length === 0 &&
|
|
45
47
|
fileLocal.fileLocalNames.length === 0 &&
|
|
46
48
|
fileLocal.anonymousNamespaceRanges.length === 0 &&
|
|
47
49
|
twoPhase.dependentBases.length === 0 &&
|
|
48
|
-
twoPhase.dependentPackBaseClasses.length === 0
|
|
50
|
+
twoPhase.dependentPackBaseClasses.length === 0 &&
|
|
51
|
+
memberLookup.baseEdges.length === 0 &&
|
|
52
|
+
memberLookup.memberUsings.length === 0;
|
|
49
53
|
if (isEmpty)
|
|
50
54
|
return undefined;
|
|
51
|
-
return { kind: 'cpp', adl, inlineNamespaceRanges, fileLocal, twoPhase };
|
|
55
|
+
return { kind: 'cpp', adl, inlineNamespaceRanges, fileLocal, twoPhase, memberLookup };
|
|
52
56
|
}
|
|
53
57
|
/**
|
|
54
58
|
* `ScopeResolver.applyCaptureSideChannel` implementation for C++. Reads the
|
|
@@ -75,4 +79,7 @@ export function applyCppCaptureSideChannel(parsed) {
|
|
|
75
79
|
applyCppFileLocalSideChannel(parsed.filePath, data.fileLocal);
|
|
76
80
|
if (data.twoPhase !== undefined)
|
|
77
81
|
applyCppTwoPhaseSideChannel(parsed.filePath, data.twoPhase);
|
|
82
|
+
if (data.memberLookup !== undefined) {
|
|
83
|
+
applyCppMemberLookupSideChannel(parsed.filePath, data.memberLookup);
|
|
84
|
+
}
|
|
78
85
|
}
|
|
@@ -10,6 +10,7 @@ import { markCppDependentBase, markCppDependentPackBase } from './two-phase-look
|
|
|
10
10
|
import { markCppAdlSiteArgs, markCppAdlSiteNoAdl } from './adl.js';
|
|
11
11
|
import { markCppInlineNamespaceRange } from './inline-namespaces.js';
|
|
12
12
|
import { extractCppTemplateConstraints } from './constraint-extractor.js';
|
|
13
|
+
import { captureCppMemberLookupFacts } from './member-lookup.js';
|
|
13
14
|
export function emitCppScopeCaptures(sourceText, filePath, cachedTree) {
|
|
14
15
|
let tree = cachedTree;
|
|
15
16
|
if (tree === undefined) {
|
|
@@ -354,6 +355,7 @@ export function emitCppScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
354
355
|
// and the resolver can suppress unqualified-call binding to those
|
|
355
356
|
// bases per ISO C++ two-phase lookup.
|
|
356
357
|
detectCppDependentBases(tree.rootNode, filePath);
|
|
358
|
+
captureCppMemberLookupFacts(tree.rootNode, filePath);
|
|
357
359
|
return out;
|
|
358
360
|
}
|
|
359
361
|
function extractCppDeclarationReturnType(fnNode) {
|
|
@@ -67,6 +67,13 @@ function buildIncludeCapture(node, pathNode) {
|
|
|
67
67
|
export function splitCppUsingDecl(node) {
|
|
68
68
|
if (node.type !== 'using_declaration')
|
|
69
69
|
return null;
|
|
70
|
+
// A class-scope `using Base::member;` changes the derived class's member
|
|
71
|
+
// lookup set; it is not a namespace import. The C++ member-lookup sidecar
|
|
72
|
+
// captures it separately, so suppress import decomposition here.
|
|
73
|
+
for (let parent = node.parent; parent !== null; parent = parent.parent) {
|
|
74
|
+
if (parent.type === 'class_specifier' || parent.type === 'struct_specifier')
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
70
77
|
// Check for "namespace" keyword among anonymous children
|
|
71
78
|
let hasNamespaceKeyword = false;
|
|
72
79
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ParsedFile, ReferenceSite, SymbolDefinition } from '../../../../_shared/index.js';
|
|
2
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
3
|
+
import type { GraphNodeLookup } from '../../scope-resolution/graph-bridge/node-lookup.js';
|
|
4
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
5
|
+
import type { SemanticModel } from '../../model/semantic-model.js';
|
|
6
|
+
import type { ReceiverMemberResolution } from '../../scope-resolution/contract/scope-resolver.js';
|
|
7
|
+
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
|
8
|
+
interface CapturedBaseEdge {
|
|
9
|
+
readonly childName: string;
|
|
10
|
+
readonly childQualifiedName?: string;
|
|
11
|
+
readonly baseName: string;
|
|
12
|
+
readonly baseQualifiedName?: string;
|
|
13
|
+
readonly isVirtual: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface CapturedMemberUsing {
|
|
16
|
+
readonly childName: string;
|
|
17
|
+
readonly childQualifiedName?: string;
|
|
18
|
+
readonly baseName: string;
|
|
19
|
+
readonly baseQualifiedName?: string;
|
|
20
|
+
readonly memberName: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CppMemberLookupSideChannel {
|
|
23
|
+
readonly baseEdges: readonly CapturedBaseEdge[];
|
|
24
|
+
readonly memberUsings: readonly CapturedMemberUsing[];
|
|
25
|
+
}
|
|
26
|
+
export declare function clearCppMemberLookupState(): void;
|
|
27
|
+
export declare function captureCppMemberLookupFacts(root: SyntaxNode, filePath: string): void;
|
|
28
|
+
export declare function collectCppMemberLookupSideChannel(filePath: string): CppMemberLookupSideChannel;
|
|
29
|
+
export declare function applyCppMemberLookupSideChannel(filePath: string, data: CppMemberLookupSideChannel): void;
|
|
30
|
+
export declare function buildCppMemberLookupMro(graph: KnowledgeGraph, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup): Map<string, string[]>;
|
|
31
|
+
export declare function resolveCppReceiverMember(ownerDef: SymbolDefinition, memberName: string, callsite: ReferenceSite, _scopes: ScopeResolutionIndexes, model: SemanticModel): ReceiverMemberResolution | undefined;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { resolveDefGraphId } from '../../scope-resolution/graph-bridge/ids.js';
|
|
2
|
+
import { buildMro, defaultLinearize } from '../../scope-resolution/passes/mro.js';
|
|
3
|
+
import { isOverloadAmbiguousAfterNormalization, narrowOverloadCandidates, } from '../../scope-resolution/passes/overload-narrowing.js';
|
|
4
|
+
import { isClassLike } from '../../scope-resolution/scope/walkers.js';
|
|
5
|
+
import { cppConstraintCompatibility } from './constraint-filter.js';
|
|
6
|
+
import { cppConversionRank } from './conversion-rank.js';
|
|
7
|
+
const capturedByFile = new Map();
|
|
8
|
+
let directParentsByDefId = new Map();
|
|
9
|
+
let virtualEdges = new Set();
|
|
10
|
+
let ancestorsByDefId = new Map();
|
|
11
|
+
let memberUsingsByDefId = new Map();
|
|
12
|
+
let inheritedLookupCache = new Map();
|
|
13
|
+
const MAX_INHERITANCE_VISITS = 4096;
|
|
14
|
+
export function clearCppMemberLookupState() {
|
|
15
|
+
capturedByFile.clear();
|
|
16
|
+
directParentsByDefId = new Map();
|
|
17
|
+
virtualEdges = new Set();
|
|
18
|
+
ancestorsByDefId = new Map();
|
|
19
|
+
memberUsingsByDefId = new Map();
|
|
20
|
+
inheritedLookupCache = new Map();
|
|
21
|
+
}
|
|
22
|
+
export function captureCppMemberLookupFacts(root, filePath) {
|
|
23
|
+
const baseEdges = [];
|
|
24
|
+
const memberUsings = [];
|
|
25
|
+
const stack = [root];
|
|
26
|
+
while (stack.length > 0) {
|
|
27
|
+
const node = stack.pop();
|
|
28
|
+
if (node.type === 'class_specifier' || node.type === 'struct_specifier') {
|
|
29
|
+
const childName = classNameOf(node);
|
|
30
|
+
const childQualifiedName = classQualifiedNameOf(node);
|
|
31
|
+
if (childName !== '') {
|
|
32
|
+
const baseClause = directChildOfType(node, 'base_class_clause');
|
|
33
|
+
if (baseClause !== null) {
|
|
34
|
+
captureBaseEdges(baseClause, childName, childQualifiedName, baseEdges);
|
|
35
|
+
}
|
|
36
|
+
const body = directChildOfType(node, 'field_declaration_list');
|
|
37
|
+
if (body !== null) {
|
|
38
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
39
|
+
const child = body.namedChild(i);
|
|
40
|
+
if (child?.type !== 'using_declaration')
|
|
41
|
+
continue;
|
|
42
|
+
const parsed = parseMemberUsing(child, childName, childQualifiedName);
|
|
43
|
+
if (parsed !== undefined)
|
|
44
|
+
memberUsings.push(parsed);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
50
|
+
const child = node.child(i);
|
|
51
|
+
if (child !== null)
|
|
52
|
+
stack.push(child);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (baseEdges.length === 0 && memberUsings.length === 0) {
|
|
56
|
+
capturedByFile.delete(filePath);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
capturedByFile.set(filePath, { baseEdges, memberUsings });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function collectCppMemberLookupSideChannel(filePath) {
|
|
63
|
+
return capturedByFile.get(filePath) ?? { baseEdges: [], memberUsings: [] };
|
|
64
|
+
}
|
|
65
|
+
export function applyCppMemberLookupSideChannel(filePath, data) {
|
|
66
|
+
if (!Array.isArray(data.baseEdges) || !Array.isArray(data.memberUsings))
|
|
67
|
+
return;
|
|
68
|
+
if (data.baseEdges.length === 0 && data.memberUsings.length === 0) {
|
|
69
|
+
capturedByFile.delete(filePath);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
capturedByFile.set(filePath, {
|
|
73
|
+
baseEdges: data.baseEdges.slice(),
|
|
74
|
+
memberUsings: data.memberUsings.slice(),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export function buildCppMemberLookupMro(graph, parsedFiles, nodeLookup) {
|
|
78
|
+
populateResolvedHierarchy(graph, parsedFiles, nodeLookup);
|
|
79
|
+
return buildMro(graph, parsedFiles, nodeLookup, defaultLinearize);
|
|
80
|
+
}
|
|
81
|
+
export function resolveCppReceiverMember(ownerDef, memberName, callsite, _scopes, model) {
|
|
82
|
+
if (callsite.kind !== 'call')
|
|
83
|
+
return undefined;
|
|
84
|
+
const ownMethods = model.methods.lookupAllByOwner(ownerDef.nodeId, memberName);
|
|
85
|
+
const introduced = introducedDefinitions(ownerDef.nodeId, memberName, model);
|
|
86
|
+
if (introduced.length > 0) {
|
|
87
|
+
return chooseOverload(uniqueDefinitions([...ownMethods, ...introduced]), callsite);
|
|
88
|
+
}
|
|
89
|
+
// Direct declarations hide every base declaration. Let the shared path
|
|
90
|
+
// retain its existing overload/static filtering for this common case.
|
|
91
|
+
if (ownMethods.length > 0)
|
|
92
|
+
return undefined;
|
|
93
|
+
const lookup = inheritedLookupSet(ownerDef.nodeId, memberName, model);
|
|
94
|
+
if (lookup.kind === 'none')
|
|
95
|
+
return undefined;
|
|
96
|
+
if (lookup.kind === 'ambiguous')
|
|
97
|
+
return lookup;
|
|
98
|
+
return chooseOverload(lookup.definitions, callsite);
|
|
99
|
+
}
|
|
100
|
+
function collectInheritedOccurrences(ownerDefId, memberName, model, path, virtualAnchor, active, budget) {
|
|
101
|
+
if (budget.remaining <= 0) {
|
|
102
|
+
budget.truncated = true;
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
budget.remaining--;
|
|
106
|
+
if (active.has(ownerDefId))
|
|
107
|
+
return [];
|
|
108
|
+
const nextActive = new Set(active);
|
|
109
|
+
nextActive.add(ownerDefId);
|
|
110
|
+
const definitions = uniqueDefinitions([
|
|
111
|
+
...model.methods.lookupAllByOwner(ownerDefId, memberName),
|
|
112
|
+
...introducedDefinitions(ownerDefId, memberName, model),
|
|
113
|
+
]);
|
|
114
|
+
if (definitions.length > 0) {
|
|
115
|
+
return [{ ownerDefId, definitions, path, virtualAnchor }];
|
|
116
|
+
}
|
|
117
|
+
const results = [];
|
|
118
|
+
for (const parentDefId of directParentsByDefId.get(ownerDefId) ?? []) {
|
|
119
|
+
const edgeKey = `${ownerDefId}\0${parentDefId}`;
|
|
120
|
+
results.push(...collectInheritedOccurrences(parentDefId, memberName, model, [...path, parentDefId], virtualEdges.has(edgeKey) ? parentDefId : virtualAnchor, nextActive, budget));
|
|
121
|
+
}
|
|
122
|
+
return results;
|
|
123
|
+
}
|
|
124
|
+
function inheritedLookupSet(ownerDefId, memberName, model) {
|
|
125
|
+
const cacheKey = `${ownerDefId}\0${memberName}`;
|
|
126
|
+
const cached = inheritedLookupCache.get(cacheKey);
|
|
127
|
+
if (cached !== undefined)
|
|
128
|
+
return cached;
|
|
129
|
+
const budget = { remaining: MAX_INHERITANCE_VISITS, truncated: false };
|
|
130
|
+
const occurrences = collectInheritedOccurrences(ownerDefId, memberName, model, [], undefined, new Set(), budget);
|
|
131
|
+
if (budget.truncated) {
|
|
132
|
+
const conservative = {
|
|
133
|
+
kind: 'ambiguous',
|
|
134
|
+
candidateIds: uniqueDefinitions(occurrences.flatMap((entry) => entry.definitions)).map((definition) => definition.nodeId),
|
|
135
|
+
};
|
|
136
|
+
inheritedLookupCache.set(cacheKey, conservative);
|
|
137
|
+
return conservative;
|
|
138
|
+
}
|
|
139
|
+
if (occurrences.length === 0) {
|
|
140
|
+
const none = { kind: 'none' };
|
|
141
|
+
inheritedLookupCache.set(cacheKey, none);
|
|
142
|
+
return none;
|
|
143
|
+
}
|
|
144
|
+
// A declaration can dominate another lookup set only when the latter is
|
|
145
|
+
// reached through a shared virtual subobject. Ordinary ancestry alone is
|
|
146
|
+
// insufficient: declarations in one non-virtual branch do not hide members
|
|
147
|
+
// reached through a sibling base subobject.
|
|
148
|
+
const undominated = occurrences.filter((candidate) => !(candidate.virtualAnchor !== undefined &&
|
|
149
|
+
occurrences.some((other) => other.ownerDefId !== candidate.ownerDefId &&
|
|
150
|
+
isAncestor(candidate.ownerDefId, other.ownerDefId))));
|
|
151
|
+
const groups = new Map();
|
|
152
|
+
for (const occurrence of undominated) {
|
|
153
|
+
const key = occurrence.virtualAnchor !== undefined
|
|
154
|
+
? `virtual:${occurrence.virtualAnchor}:${occurrence.ownerDefId}`
|
|
155
|
+
: `path:${occurrence.path.join('>')}:${occurrence.ownerDefId}`;
|
|
156
|
+
const bucket = groups.get(key);
|
|
157
|
+
if (bucket === undefined)
|
|
158
|
+
groups.set(key, [occurrence]);
|
|
159
|
+
else
|
|
160
|
+
bucket.push(occurrence);
|
|
161
|
+
}
|
|
162
|
+
let result;
|
|
163
|
+
if (groups.size !== 1) {
|
|
164
|
+
result = {
|
|
165
|
+
kind: 'ambiguous',
|
|
166
|
+
candidateIds: uniqueDefinitions(undominated.flatMap((entry) => entry.definitions)).map((definition) => definition.nodeId),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
result = {
|
|
171
|
+
kind: 'candidates',
|
|
172
|
+
definitions: groups.values().next().value?.[0]?.definitions ?? [],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
inheritedLookupCache.set(cacheKey, result);
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
function introducedDefinitions(ownerDefId, memberName, model) {
|
|
179
|
+
const definitions = [];
|
|
180
|
+
for (const entry of memberUsingsByDefId.get(ownerDefId) ?? []) {
|
|
181
|
+
if (entry.memberName !== memberName)
|
|
182
|
+
continue;
|
|
183
|
+
definitions.push(...model.methods.lookupAllByOwner(entry.baseDefId, memberName));
|
|
184
|
+
}
|
|
185
|
+
return definitions;
|
|
186
|
+
}
|
|
187
|
+
function uniqueDefinitions(definitions) {
|
|
188
|
+
return [...new Map(definitions.map((definition) => [definition.nodeId, definition])).values()];
|
|
189
|
+
}
|
|
190
|
+
function chooseOverload(candidates, callsite) {
|
|
191
|
+
if (candidates.length === 0)
|
|
192
|
+
return undefined;
|
|
193
|
+
const narrowed = narrowOverloadCandidates(candidates, callsite.arity, callsite.argumentTypes, {
|
|
194
|
+
argumentTypeClasses: callsite.argumentTypeClasses,
|
|
195
|
+
conversionRankFn: cppConversionRank,
|
|
196
|
+
constraintCompatibility: cppConstraintCompatibility,
|
|
197
|
+
});
|
|
198
|
+
if (narrowed.length === 1)
|
|
199
|
+
return { kind: 'resolved', definition: narrowed[0] };
|
|
200
|
+
if (narrowed.length > 1 || isOverloadAmbiguousAfterNormalization(narrowed, callsite.arity)) {
|
|
201
|
+
return {
|
|
202
|
+
kind: 'ambiguous',
|
|
203
|
+
candidateIds: narrowed.map((candidate) => candidate.nodeId),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function populateResolvedHierarchy(graph, parsedFiles, nodeLookup) {
|
|
209
|
+
const defByGraphId = new Map();
|
|
210
|
+
const defById = new Map();
|
|
211
|
+
const defsByFileAndName = new Map();
|
|
212
|
+
for (const parsed of parsedFiles) {
|
|
213
|
+
for (const def of parsed.localDefs) {
|
|
214
|
+
if (!isClassLike(def.type))
|
|
215
|
+
continue;
|
|
216
|
+
const graphId = resolveDefGraphId(parsed.filePath, def, nodeLookup);
|
|
217
|
+
if (graphId === undefined)
|
|
218
|
+
continue;
|
|
219
|
+
defByGraphId.set(graphId, def);
|
|
220
|
+
defById.set(def.nodeId, def);
|
|
221
|
+
const names = new Set([simpleName(def), definitionQualifiedName(def)]);
|
|
222
|
+
for (const name of names) {
|
|
223
|
+
if (name === '')
|
|
224
|
+
continue;
|
|
225
|
+
const key = `${parsed.filePath}\0${name}`;
|
|
226
|
+
const bucket = defsByFileAndName.get(key);
|
|
227
|
+
if (bucket === undefined)
|
|
228
|
+
defsByFileAndName.set(key, [def]);
|
|
229
|
+
else
|
|
230
|
+
bucket.push(def);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const parents = new Map();
|
|
235
|
+
for (const rel of graph.iterRelationshipsByType('EXTENDS')) {
|
|
236
|
+
const child = defByGraphId.get(rel.sourceId);
|
|
237
|
+
const parent = defByGraphId.get(rel.targetId);
|
|
238
|
+
if (child === undefined || parent === undefined)
|
|
239
|
+
continue;
|
|
240
|
+
const bucket = parents.get(child.nodeId);
|
|
241
|
+
if (bucket === undefined)
|
|
242
|
+
parents.set(child.nodeId, [parent.nodeId]);
|
|
243
|
+
else
|
|
244
|
+
bucket.push(parent.nodeId);
|
|
245
|
+
}
|
|
246
|
+
directParentsByDefId = parents;
|
|
247
|
+
ancestorsByDefId = buildAncestorClosure(parents);
|
|
248
|
+
inheritedLookupCache = new Map();
|
|
249
|
+
const nextVirtualEdges = new Set();
|
|
250
|
+
const nextUsings = new Map();
|
|
251
|
+
for (const parsed of parsedFiles) {
|
|
252
|
+
const captured = capturedByFile.get(parsed.filePath);
|
|
253
|
+
if (captured === undefined)
|
|
254
|
+
continue;
|
|
255
|
+
for (const edge of captured.baseEdges) {
|
|
256
|
+
if (!edge.isVirtual)
|
|
257
|
+
continue;
|
|
258
|
+
for (const child of matchingChildren(parsed.filePath, edge.childName, edge.childQualifiedName, defsByFileAndName)) {
|
|
259
|
+
const parent = findCapturedParent(parents.get(child.nodeId) ?? [], edge.baseName, edge.baseQualifiedName, defById);
|
|
260
|
+
if (parent !== undefined)
|
|
261
|
+
nextVirtualEdges.add(`${child.nodeId}\0${parent.nodeId}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
for (const using of captured.memberUsings) {
|
|
265
|
+
const children = matchingChildren(parsed.filePath, using.childName, using.childQualifiedName, defsByFileAndName);
|
|
266
|
+
for (const child of children) {
|
|
267
|
+
const baseDef = findCapturedParent(parents.get(child.nodeId) ?? [], using.baseName, using.baseQualifiedName, defById);
|
|
268
|
+
if (baseDef === undefined)
|
|
269
|
+
continue;
|
|
270
|
+
const bucket = nextUsings.get(child.nodeId);
|
|
271
|
+
const entry = { baseDefId: baseDef.nodeId, memberName: using.memberName };
|
|
272
|
+
if (bucket === undefined)
|
|
273
|
+
nextUsings.set(child.nodeId, [entry]);
|
|
274
|
+
else
|
|
275
|
+
bucket.push(entry);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
virtualEdges = nextVirtualEdges;
|
|
280
|
+
memberUsingsByDefId = nextUsings;
|
|
281
|
+
}
|
|
282
|
+
function captureBaseEdges(baseClause, childName, childQualifiedName, output) {
|
|
283
|
+
let segmentStart = 0;
|
|
284
|
+
for (let i = 0; i < baseClause.childCount; i++) {
|
|
285
|
+
const child = baseClause.child(i);
|
|
286
|
+
if (child === null)
|
|
287
|
+
continue;
|
|
288
|
+
if (child.type === ',' || child.text === ',') {
|
|
289
|
+
segmentStart = i + 1;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (child.type !== 'type_identifier' &&
|
|
293
|
+
child.type !== 'template_type' &&
|
|
294
|
+
child.type !== 'qualified_identifier') {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
let isVirtual = false;
|
|
298
|
+
for (let j = segmentStart; j < i; j++) {
|
|
299
|
+
const modifier = baseClause.child(j);
|
|
300
|
+
if (modifier?.text === 'virtual')
|
|
301
|
+
isVirtual = true;
|
|
302
|
+
}
|
|
303
|
+
const baseQualifiedName = qualifiedTypeName(child.text);
|
|
304
|
+
const baseName = baseQualifiedName.split('.').at(-1) ?? '';
|
|
305
|
+
if (baseName !== '') {
|
|
306
|
+
output.push({
|
|
307
|
+
childName,
|
|
308
|
+
...(childQualifiedName !== childName ? { childQualifiedName } : {}),
|
|
309
|
+
baseName,
|
|
310
|
+
...(baseQualifiedName !== baseName ? { baseQualifiedName } : {}),
|
|
311
|
+
isVirtual,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function parseMemberUsing(node, childName, childQualifiedName) {
|
|
317
|
+
const qualified = node.namedChildren.find((child) => child.type === 'qualified_identifier');
|
|
318
|
+
if (qualified === undefined)
|
|
319
|
+
return undefined;
|
|
320
|
+
const parts = splitQualifiedSegments(qualified.text);
|
|
321
|
+
if (parts.length < 2)
|
|
322
|
+
return undefined;
|
|
323
|
+
const memberName = stripTemplateSuffix(parts.at(-1) ?? '');
|
|
324
|
+
const baseParts = parts.slice(0, -1).map(stripTemplateSuffix).filter(Boolean);
|
|
325
|
+
const baseName = baseParts.at(-1) ?? '';
|
|
326
|
+
const baseQualifiedName = baseParts.join('.');
|
|
327
|
+
if (baseName === '' || memberName === '')
|
|
328
|
+
return undefined;
|
|
329
|
+
return {
|
|
330
|
+
childName,
|
|
331
|
+
...(childQualifiedName !== childName ? { childQualifiedName } : {}),
|
|
332
|
+
baseName,
|
|
333
|
+
...(baseQualifiedName !== baseName ? { baseQualifiedName } : {}),
|
|
334
|
+
memberName,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function classNameOf(node) {
|
|
338
|
+
const name = node.childForFieldName?.('name');
|
|
339
|
+
return name === null || name === undefined ? '' : trailingIdentifier(name.text);
|
|
340
|
+
}
|
|
341
|
+
function classQualifiedNameOf(node) {
|
|
342
|
+
const parts = [classNameOf(node)];
|
|
343
|
+
let current = node.parent;
|
|
344
|
+
while (current !== null) {
|
|
345
|
+
if (current.type === 'class_specifier' || current.type === 'struct_specifier') {
|
|
346
|
+
const name = classNameOf(current);
|
|
347
|
+
if (name !== '')
|
|
348
|
+
parts.unshift(name);
|
|
349
|
+
}
|
|
350
|
+
else if (current.type === 'namespace_definition') {
|
|
351
|
+
const name = current.childForFieldName?.('name');
|
|
352
|
+
if (name !== null && name !== undefined) {
|
|
353
|
+
parts.unshift(...splitQualifiedSegments(name.text).map(stripTemplateSuffix).filter(Boolean));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
current = current.parent;
|
|
357
|
+
}
|
|
358
|
+
return parts.filter(Boolean).join('.');
|
|
359
|
+
}
|
|
360
|
+
function directChildOfType(node, type) {
|
|
361
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
362
|
+
const child = node.namedChild(i);
|
|
363
|
+
if (child?.type === type)
|
|
364
|
+
return child;
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function trailingIdentifier(value) {
|
|
369
|
+
return stripTemplateSuffix(splitQualifiedSegments(value).at(-1) ?? '');
|
|
370
|
+
}
|
|
371
|
+
function qualifiedTypeName(value) {
|
|
372
|
+
return splitQualifiedSegments(value).map(stripTemplateSuffix).filter(Boolean).join('.');
|
|
373
|
+
}
|
|
374
|
+
function splitQualifiedSegments(value) {
|
|
375
|
+
const parts = [];
|
|
376
|
+
let angleDepth = 0;
|
|
377
|
+
let segmentStart = 0;
|
|
378
|
+
for (let i = 0; i < value.length; i++) {
|
|
379
|
+
const char = value[i];
|
|
380
|
+
if (char === '<')
|
|
381
|
+
angleDepth++;
|
|
382
|
+
else if (char === '>' && angleDepth > 0)
|
|
383
|
+
angleDepth--;
|
|
384
|
+
else if (char === ':' && value[i + 1] === ':' && angleDepth === 0) {
|
|
385
|
+
const segment = value.slice(segmentStart, i).trim();
|
|
386
|
+
if (segment !== '')
|
|
387
|
+
parts.push(segment);
|
|
388
|
+
segmentStart = i + 2;
|
|
389
|
+
i++;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const tail = value.slice(segmentStart).trim();
|
|
393
|
+
if (tail !== '')
|
|
394
|
+
parts.push(tail);
|
|
395
|
+
return parts;
|
|
396
|
+
}
|
|
397
|
+
function stripTemplateSuffix(value) {
|
|
398
|
+
const templateStart = value.indexOf('<');
|
|
399
|
+
return (templateStart >= 0 ? value.slice(0, templateStart) : value).trim();
|
|
400
|
+
}
|
|
401
|
+
function simpleName(def) {
|
|
402
|
+
return def.qualifiedName?.split('.').at(-1) ?? '';
|
|
403
|
+
}
|
|
404
|
+
function definitionQualifiedName(def) {
|
|
405
|
+
const name = def.qualifiedName ?? '';
|
|
406
|
+
if (name === '' || def.namespacePrefix === undefined || def.namespacePrefix === '')
|
|
407
|
+
return name;
|
|
408
|
+
return name.startsWith(`${def.namespacePrefix}.`) ? name : `${def.namespacePrefix}.${name}`;
|
|
409
|
+
}
|
|
410
|
+
function matchingChildren(filePath, childName, childQualifiedName, defsByFileAndName) {
|
|
411
|
+
if (childQualifiedName !== undefined) {
|
|
412
|
+
const qualified = defsByFileAndName.get(`${filePath}\0${childQualifiedName}`) ?? [];
|
|
413
|
+
if (qualified.length > 0)
|
|
414
|
+
return qualified;
|
|
415
|
+
}
|
|
416
|
+
const simple = defsByFileAndName.get(`${filePath}\0${childName}`) ?? [];
|
|
417
|
+
return simple.length === 1 ? simple : [];
|
|
418
|
+
}
|
|
419
|
+
function findCapturedParent(parentIds, baseName, baseQualifiedName, defById) {
|
|
420
|
+
const candidates = parentIds
|
|
421
|
+
.map((id) => defById.get(id))
|
|
422
|
+
.filter((definition) => definition !== undefined);
|
|
423
|
+
if (baseQualifiedName !== undefined) {
|
|
424
|
+
const qualified = candidates.filter((definition) => {
|
|
425
|
+
const name = definitionQualifiedName(definition);
|
|
426
|
+
return name === baseQualifiedName || name.endsWith(`.${baseQualifiedName}`);
|
|
427
|
+
});
|
|
428
|
+
if (qualified.length === 1)
|
|
429
|
+
return qualified[0];
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
const simple = candidates.filter((definition) => simpleName(definition) === baseName);
|
|
433
|
+
return simple.length === 1 ? simple[0] : undefined;
|
|
434
|
+
}
|
|
435
|
+
function buildAncestorClosure(parents) {
|
|
436
|
+
const closure = new Map();
|
|
437
|
+
const visiting = new Set();
|
|
438
|
+
const ancestorsOf = (defId) => {
|
|
439
|
+
const cached = closure.get(defId);
|
|
440
|
+
if (cached !== undefined)
|
|
441
|
+
return cached;
|
|
442
|
+
if (visiting.has(defId))
|
|
443
|
+
return new Set();
|
|
444
|
+
visiting.add(defId);
|
|
445
|
+
const ancestors = new Set();
|
|
446
|
+
for (const parent of parents.get(defId) ?? []) {
|
|
447
|
+
ancestors.add(parent);
|
|
448
|
+
for (const ancestor of ancestorsOf(parent))
|
|
449
|
+
ancestors.add(ancestor);
|
|
450
|
+
}
|
|
451
|
+
visiting.delete(defId);
|
|
452
|
+
closure.set(defId, ancestors);
|
|
453
|
+
return ancestors;
|
|
454
|
+
};
|
|
455
|
+
for (const defId of parents.keys())
|
|
456
|
+
ancestorsOf(defId);
|
|
457
|
+
return closure;
|
|
458
|
+
}
|
|
459
|
+
function isAncestor(ancestorDefId, descendantDefId) {
|
|
460
|
+
return ancestorsByDefId.get(descendantDefId)?.has(ancestorDefId) === true;
|
|
461
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { findClassBindingInScope, findEnclosingClassDef, } from '../../scope-resolution/scope/walkers.js';
|
|
2
2
|
import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
3
|
-
import { buildMro, defaultLinearize } from '../../scope-resolution/passes/mro.js';
|
|
4
3
|
import { populateClassOwnedMembers, tagNamespacePrefixes, } from '../../scope-resolution/scope/walkers.js';
|
|
5
4
|
import { cppProvider } from '../c-cpp.js';
|
|
6
5
|
import { cppArityCompatibility } from './arity.js';
|
|
@@ -16,6 +15,7 @@ import { clearCppInlineNamespaces, populateCppInlineNamespaceScopes, resolveCppQ
|
|
|
16
15
|
import { populateCppRangeBindings } from './range-bindings.js';
|
|
17
16
|
import { cppConstraintCompatibility } from './constraint-filter.js';
|
|
18
17
|
import { clearCppUserDefinedConversions, populateCppUserDefinedConversions, } from './user-defined-conversions.js';
|
|
18
|
+
import { buildCppMemberLookupMro, clearCppMemberLookupState, resolveCppReceiverMember, } from './member-lookup.js';
|
|
19
19
|
/**
|
|
20
20
|
* Per-pass memo of the augmented `#include`-resolution file set
|
|
21
21
|
* (`allFilePaths` ∪ header paths), keyed on the two stable source sets.
|
|
@@ -69,6 +69,7 @@ export const cppScopeResolver = {
|
|
|
69
69
|
clearCppAdlState();
|
|
70
70
|
clearCppInlineNamespaces();
|
|
71
71
|
clearCppUserDefinedConversions();
|
|
72
|
+
clearCppMemberLookupState();
|
|
72
73
|
return scanCppHeaderFiles(repoPath);
|
|
73
74
|
},
|
|
74
75
|
resolveImportTarget: (targetRaw, fromFile, allFilePaths, resolutionConfig) => {
|
|
@@ -91,7 +92,7 @@ export const cppScopeResolver = {
|
|
|
91
92
|
// C++20 `requires P`) provably fail at the call site. Three-valued —
|
|
92
93
|
// `'unknown'` keeps the candidate, preserving "degrade not lie".
|
|
93
94
|
constraintCompatibility: cppConstraintCompatibility,
|
|
94
|
-
buildMro:
|
|
95
|
+
buildMro: buildCppMemberLookupMro,
|
|
95
96
|
// Worker-boundary restore (see `ScopeResolver.applyCaptureSideChannel`).
|
|
96
97
|
// `emitCppScopeCaptures` records per-file ADL call-site arg shapes
|
|
97
98
|
// (`markCppAdlSiteArgs`/`markCppAdlSiteNoAdl`), inline-/anonymous-namespace
|
|
@@ -212,6 +213,7 @@ export const cppScopeResolver = {
|
|
|
212
213
|
hoistTypeBindingsToModule: true,
|
|
213
214
|
// Enable receiver-bound explicit-`this` fallback only for C++.
|
|
214
215
|
resolveThisViaEnclosingClass: true,
|
|
216
|
+
resolveReceiverMember: resolveCppReceiverMember,
|
|
215
217
|
// The `isFileLocalDef` hook on the global free-call fallback names
|
|
216
218
|
// file-local linkage historically, but semantically gates "logically
|
|
217
219
|
// invisible cross-file" defs. C++ extends this to also reject class-
|
|
@@ -261,7 +261,7 @@
|
|
|
261
261
|
* Plan that introduced most of these invariants:
|
|
262
262
|
* `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
|
|
263
263
|
*/
|
|
264
|
-
import type { BindingRef, Callsite, ConstraintContext, ParsedFile, ScopeId, SupportedLanguages, SymbolDefinition } from '../../../../_shared/index.js';
|
|
264
|
+
import type { BindingRef, Callsite, ConstraintContext, ParsedFile, ReferenceSite, ScopeId, SupportedLanguages, SymbolDefinition } from '../../../../_shared/index.js';
|
|
265
265
|
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
266
266
|
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
267
267
|
import { LanguageProvider } from '../../language-provider.js';
|
|
@@ -275,6 +275,13 @@ import type { ConversionRankFn } from '../passes/overload-narrowing.js';
|
|
|
275
275
|
export type LinearizeStrategy = (classDefId: string, directParents: readonly string[], parentsByDefId: ReadonlyMap<string, readonly string[]>) => string[];
|
|
276
276
|
/** Result of `ScopeResolver.arityCompatibility` — mirrors `RegistryProviders.arityCompatibility`. */
|
|
277
277
|
export type ArityVerdict = 'compatible' | 'unknown' | 'incompatible';
|
|
278
|
+
export type ReceiverMemberResolution = {
|
|
279
|
+
readonly kind: 'resolved';
|
|
280
|
+
readonly definition: SymbolDefinition;
|
|
281
|
+
} | {
|
|
282
|
+
readonly kind: 'ambiguous';
|
|
283
|
+
readonly candidateIds: readonly string[];
|
|
284
|
+
};
|
|
278
285
|
/** Re-exported for ScopeResolver consumers — same shape as
|
|
279
286
|
* `RegistryProviders.constraintCompatibility`'s third parameter. */
|
|
280
287
|
export type { ConstraintContext } from '../../../../_shared/index.js';
|
|
@@ -367,7 +374,7 @@ export interface ScopeResolver {
|
|
|
367
374
|
* C++ is the first consumer; see `languages/cpp/constraint-filter.ts`
|
|
368
375
|
* for the Tier-A predicate registry and Kleene 3-valued evaluator.
|
|
369
376
|
*/
|
|
370
|
-
readonly constraintCompatibility?: (callsite:
|
|
377
|
+
readonly constraintCompatibility?: (callsite: ReferenceSite, def: SymbolDefinition, ctx: ConstraintContext) => ArityVerdict;
|
|
371
378
|
/**
|
|
372
379
|
* Compute the method-dispatch order for every Class def in the
|
|
373
380
|
* workspace. Python uses depth-first first-seen via
|
|
@@ -730,6 +737,14 @@ export interface ScopeResolver {
|
|
|
730
737
|
* `undefined` to fall through to the remaining cases.
|
|
731
738
|
*/
|
|
732
739
|
readonly resolveQualifiedReceiverMember?: (receiverName: string, memberName: string, callerScope: ScopeId, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], callsite?: Callsite) => SymbolDefinition | 'ambiguous' | undefined;
|
|
740
|
+
/**
|
|
741
|
+
* Optional language-specific member-lattice lookup. Runs for a resolved
|
|
742
|
+
* simple receiver type before the generic flattened-MRO walk. Languages
|
|
743
|
+
* with lookup-set semantics that cannot be represented by one linear MRO
|
|
744
|
+
* may resolve a member, report ambiguity (which suppresses fallback), or
|
|
745
|
+
* return undefined to retain the shared behavior.
|
|
746
|
+
*/
|
|
747
|
+
readonly resolveReceiverMember?: (ownerDef: SymbolDefinition, memberName: string, callsite: Callsite, scopes: ScopeResolutionIndexes, model: SemanticModel) => ReceiverMemberResolution | undefined;
|
|
733
748
|
/**
|
|
734
749
|
* Enable the receiver-bound Case 0.5 fallback for explicit `this`
|
|
735
750
|
* receivers (`this->m()` / `this.m()`) that resolves against the
|