gitnexus 1.6.5 → 1.6.6-rc.10
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/_shared/index.d.ts +2 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.d.ts +37 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +4 -2
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.js +8 -23
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +20 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/analyze.js +65 -3
- package/dist/cli/eval-server.d.ts +14 -3
- package/dist/cli/eval-server.js +79 -9
- package/dist/cli/index.js +3 -1
- package/dist/cli/serve.js +6 -1
- package/dist/cli/wiki.d.ts +1 -0
- package/dist/cli/wiki.js +32 -8
- package/dist/core/ingestion/language-provider.d.ts +29 -0
- package/dist/core/ingestion/languages/c-cpp.js +46 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.d.ts +17 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +51 -2
- package/dist/core/ingestion/languages/cpp/arity.d.ts +4 -1
- package/dist/core/ingestion/languages/cpp/arity.js +4 -1
- package/dist/core/ingestion/languages/cpp/captures.js +73 -0
- package/dist/core/ingestion/languages/cpp/constraint-extractor.d.ts +73 -0
- package/dist/core/ingestion/languages/cpp/constraint-extractor.js +308 -0
- package/dist/core/ingestion/languages/cpp/constraint-filter.d.ts +31 -0
- package/dist/core/ingestion/languages/cpp/constraint-filter.js +135 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +6 -0
- package/dist/core/ingestion/languages/cpp/type-classifier.d.ts +26 -0
- package/dist/core/ingestion/languages/cpp/type-classifier.js +49 -0
- package/dist/core/ingestion/model/field-registry.d.ts +17 -3
- package/dist/core/ingestion/model/field-registry.js +19 -4
- package/dist/core/ingestion/model/owned-members-lookup.d.ts +19 -0
- package/dist/core/ingestion/model/owned-members-lookup.js +43 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +2 -1
- package/dist/core/ingestion/model/symbol-table.js +3 -0
- package/dist/core/ingestion/model/type-registry.d.ts +9 -0
- package/dist/core/ingestion/model/type-registry.js +18 -0
- package/dist/core/ingestion/parsing-processor.js +35 -2
- package/dist/core/ingestion/resolve-references.d.ts +3 -1
- package/dist/core/ingestion/resolve-references.js +5 -1
- package/dist/core/ingestion/scope-extractor.js +63 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +21 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +14 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +11 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +83 -28
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +30 -7
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +49 -18
- 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 +10 -4
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +4 -2
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +48 -9
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +3 -0
- package/dist/core/ingestion/utils/template-arguments.d.ts +19 -0
- package/dist/core/ingestion/utils/template-arguments.js +30 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +2 -1
- package/dist/core/ingestion/workers/parse-worker.js +1 -0
- package/dist/core/lbug/lbug-adapter.d.ts +3 -1
- package/dist/core/lbug/lbug-adapter.js +33 -19
- package/dist/core/lbug/lbug-config.d.ts +1 -1
- package/dist/core/lbug/lbug-config.js +1 -1
- package/dist/core/lbug/pool-adapter.d.ts +0 -7
- package/dist/core/lbug/pool-adapter.js +10 -35
- package/dist/core/lbug/query-params.d.ts +1 -0
- package/dist/core/lbug/query-params.js +21 -0
- package/dist/core/search/bm25-index.js +4 -6
- package/dist/core/wiki/generator.d.ts +13 -0
- package/dist/core/wiki/generator.js +45 -4
- package/dist/core/wiki/llm-client.d.ts +1 -1
- package/dist/core/wiki/llm-client.js +23 -6
- package/dist/mcp/local/local-backend.d.ts +18 -3
- package/dist/mcp/local/local-backend.js +105 -11
- package/dist/mcp/tools.js +10 -0
- package/dist/server/api.d.ts +3 -0
- package/dist/server/api.js +36 -24
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owner-keyed member lookup for Step 2 (RFC #909 / PR #1656).
|
|
3
|
+
*
|
|
4
|
+
* Merges MethodRegistry + FieldRegistry hits for `(ownerDefId, memberName)`
|
|
5
|
+
* in O(1) map time per registry — no `defs.byId` scan. Callers that omit
|
|
6
|
+
* this helper and leave `ownedMembersByOwner` unset fall back to an O(|defs|)
|
|
7
|
+
* compatibility scan inside `lookupCore.collectOwnedMembers`.
|
|
8
|
+
*/
|
|
9
|
+
const EMPTY = Object.freeze([]);
|
|
10
|
+
/**
|
|
11
|
+
* Production hook for `RegistryContext.ownedMembersByOwner`.
|
|
12
|
+
* Returns `[]` on miss (authoritative indexed empty) — never `undefined`.
|
|
13
|
+
*
|
|
14
|
+
* Merges hits from all three owner-keyed registries (methods, fields,
|
|
15
|
+
* nested types) under the same `(ownerDefId, memberName)` key. The
|
|
16
|
+
* caller's `acceptedKinds` filter in `lookupCore` picks the right subset.
|
|
17
|
+
*/
|
|
18
|
+
export function lookupOwnedMembersByOwner(model, ownerDefId, memberName) {
|
|
19
|
+
const methods = model.methods.lookupAllByOwner(ownerDefId, memberName);
|
|
20
|
+
const fields = model.fields.lookupAllByOwner(ownerDefId, memberName);
|
|
21
|
+
const nestedTypes = model.types.lookupAllByOwner(ownerDefId, memberName);
|
|
22
|
+
const methodCount = methods.length;
|
|
23
|
+
const fieldCount = fields.length;
|
|
24
|
+
const typeCount = nestedTypes.length;
|
|
25
|
+
const total = methodCount + fieldCount + typeCount;
|
|
26
|
+
if (total === 0)
|
|
27
|
+
return EMPTY;
|
|
28
|
+
if (methodCount === total)
|
|
29
|
+
return methods;
|
|
30
|
+
if (fieldCount === total)
|
|
31
|
+
return fields;
|
|
32
|
+
if (typeCount === total)
|
|
33
|
+
return nestedTypes;
|
|
34
|
+
const merged = new Array(total);
|
|
35
|
+
let i = 0;
|
|
36
|
+
for (let j = 0; j < methodCount; j++)
|
|
37
|
+
merged[i++] = methods[j];
|
|
38
|
+
for (let j = 0; j < fieldCount; j++)
|
|
39
|
+
merged[i++] = fields[j];
|
|
40
|
+
for (let j = 0; j < typeCount; j++)
|
|
41
|
+
merged[i++] = nestedTypes[j];
|
|
42
|
+
return merged;
|
|
43
|
+
}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* import from `./model/` here, you are going the wrong way — move the
|
|
34
34
|
* logic up the dependency chain instead.
|
|
35
35
|
*/
|
|
36
|
-
import type { NodeLabel, SymbolDefinition } from '../../../_shared/index.js';
|
|
36
|
+
import type { NodeLabel, ParameterTypeClass, SymbolDefinition } from '../../../_shared/index.js';
|
|
37
37
|
/**
|
|
38
38
|
* Class-like NodeLabels — used for qualifiedName fallback inside
|
|
39
39
|
* `SymbolTable.add()` and (via import into `model/registration-table.ts`)
|
|
@@ -97,6 +97,7 @@ export interface AddMetadata {
|
|
|
97
97
|
parameterCount?: number;
|
|
98
98
|
requiredParameterCount?: number;
|
|
99
99
|
parameterTypes?: string[];
|
|
100
|
+
parameterTypeClasses?: ParameterTypeClass[];
|
|
100
101
|
returnType?: string;
|
|
101
102
|
declaredType?: string;
|
|
102
103
|
templateArguments?: string[];
|
|
@@ -128,6 +128,9 @@ export const createSymbolTable = () => {
|
|
|
128
128
|
...(metadata?.parameterTypes !== undefined
|
|
129
129
|
? { parameterTypes: metadata.parameterTypes }
|
|
130
130
|
: {}),
|
|
131
|
+
...(metadata?.parameterTypeClasses !== undefined
|
|
132
|
+
? { parameterTypeClasses: metadata.parameterTypeClasses }
|
|
133
|
+
: {}),
|
|
131
134
|
...(metadata?.returnType !== undefined ? { returnType: metadata.returnType } : {}),
|
|
132
135
|
...(metadata?.declaredType !== undefined ? { declaredType: metadata.declaredType } : {}),
|
|
133
136
|
...(metadata?.templateArguments !== undefined
|
|
@@ -27,12 +27,21 @@ export interface TypeRegistry {
|
|
|
27
27
|
* Returned array is a view into the live index — do not mutate.
|
|
28
28
|
*/
|
|
29
29
|
lookupImplByName(name: string): readonly SymbolDefinition[];
|
|
30
|
+
/**
|
|
31
|
+
* Look up nested-type defs registered under `(ownerNodeId, simpleName)`
|
|
32
|
+
* in registration order. Returns `[]` on miss. Used by Step 2 Receiver/MRO
|
|
33
|
+
* resolution when the receiver's owner declares nested classes/structs/
|
|
34
|
+
* enums/typedefs/etc. that the caller's `acceptedKinds` includes.
|
|
35
|
+
*/
|
|
36
|
+
lookupAllByOwner(ownerNodeId: string, simpleName: string): readonly SymbolDefinition[];
|
|
30
37
|
}
|
|
31
38
|
export interface MutableTypeRegistry extends TypeRegistry {
|
|
32
39
|
/** Register a class-like type by name and qualified name. */
|
|
33
40
|
registerClass(name: string, qualifiedName: string, def: SymbolDefinition): void;
|
|
34
41
|
/** Register a Rust Impl block by name. */
|
|
35
42
|
registerImpl(name: string, def: SymbolDefinition): void;
|
|
43
|
+
/** Register a nested type under its owner. Appends when the key already exists. */
|
|
44
|
+
registerByOwner(ownerNodeId: string, simpleName: string, def: SymbolDefinition): void;
|
|
36
45
|
/** Clear all entries. */
|
|
37
46
|
clear(): void;
|
|
38
47
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Eagerly-populated indexes keyed by symbol name and qualified name.
|
|
6
6
|
* Also includes a separate index for Rust Impl blocks.
|
|
7
7
|
*/
|
|
8
|
+
const EMPTY = Object.freeze([]);
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
// Factory
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
@@ -12,6 +13,7 @@ export const createTypeRegistry = () => {
|
|
|
12
13
|
const classByName = new Map();
|
|
13
14
|
const classByQualifiedName = new Map();
|
|
14
15
|
const implByName = new Map();
|
|
16
|
+
const nestedByOwner = new Map();
|
|
15
17
|
const lookupClassByName = (name) => {
|
|
16
18
|
return classByName.get(name) ?? [];
|
|
17
19
|
};
|
|
@@ -21,6 +23,9 @@ export const createTypeRegistry = () => {
|
|
|
21
23
|
const lookupImplByName = (name) => {
|
|
22
24
|
return implByName.get(name) ?? [];
|
|
23
25
|
};
|
|
26
|
+
const lookupAllByOwner = (ownerNodeId, simpleName) => {
|
|
27
|
+
return nestedByOwner.get(`${ownerNodeId}\0${simpleName}`) ?? EMPTY;
|
|
28
|
+
};
|
|
24
29
|
const registerClass = (name, qualifiedName, def) => {
|
|
25
30
|
const existing = classByName.get(name);
|
|
26
31
|
if (existing) {
|
|
@@ -46,17 +51,30 @@ export const createTypeRegistry = () => {
|
|
|
46
51
|
implByName.set(name, [def]);
|
|
47
52
|
}
|
|
48
53
|
};
|
|
54
|
+
const registerByOwner = (ownerNodeId, simpleName, def) => {
|
|
55
|
+
const key = `${ownerNodeId}\0${simpleName}`;
|
|
56
|
+
const existing = nestedByOwner.get(key);
|
|
57
|
+
if (existing) {
|
|
58
|
+
existing.push(def);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
nestedByOwner.set(key, [def]);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
49
64
|
const clear = () => {
|
|
50
65
|
classByName.clear();
|
|
51
66
|
classByQualifiedName.clear();
|
|
52
67
|
implByName.clear();
|
|
68
|
+
nestedByOwner.clear();
|
|
53
69
|
};
|
|
54
70
|
return {
|
|
55
71
|
lookupClassByName,
|
|
56
72
|
lookupClassByQualifiedName,
|
|
57
73
|
lookupImplByName,
|
|
74
|
+
lookupAllByOwner,
|
|
58
75
|
registerClass,
|
|
59
76
|
registerImpl,
|
|
77
|
+
registerByOwner,
|
|
60
78
|
clear,
|
|
61
79
|
};
|
|
62
80
|
};
|
|
@@ -11,7 +11,7 @@ import { getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCapt
|
|
|
11
11
|
import { detectFrameworkFromAST } from './framework-detection.js';
|
|
12
12
|
import { buildTypeEnv } from './type-env.js';
|
|
13
13
|
import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from './utils/method-props.js';
|
|
14
|
-
import { extractTemplateArguments, templateArgumentsIdTag } from './utils/template-arguments.js';
|
|
14
|
+
import { extractTemplateArguments, templateArgumentsIdTag, templateConstraintsIdTag, } from './utils/template-arguments.js';
|
|
15
15
|
import { logger } from '../logger.js';
|
|
16
16
|
import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from './constants.js';
|
|
17
17
|
// ============================================================================
|
|
@@ -57,6 +57,7 @@ export const mergeChunkResults = (graph, symbolTable, chunkResults) => {
|
|
|
57
57
|
parameterCount: sym.parameterCount,
|
|
58
58
|
requiredParameterCount: sym.requiredParameterCount,
|
|
59
59
|
parameterTypes: sym.parameterTypes,
|
|
60
|
+
parameterTypeClasses: sym.parameterTypeClasses,
|
|
60
61
|
returnType: sym.returnType,
|
|
61
62
|
declaredType: sym.declaredType,
|
|
62
63
|
templateArguments: sym.templateArguments,
|
|
@@ -499,7 +500,35 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
499
500
|
classTemplateArguments.length > 0
|
|
500
501
|
? templateArgumentsIdTag(classTemplateArguments)
|
|
501
502
|
: '';
|
|
502
|
-
|
|
503
|
+
// SFINAE / `requires`-clause aware ID disambiguation (issue #1579).
|
|
504
|
+
// Function-template overloads with identical parameterTypes but
|
|
505
|
+
// mutually-exclusive constraints (e.g. `enable_if_t<is_integral_v<T>>`
|
|
506
|
+
// vs `enable_if_t<is_floating_point_v<T>>`) need distinct graph
|
|
507
|
+
// nodes so the constraint-filter step in `narrowOverloadCandidates`
|
|
508
|
+
// has two candidates to narrow between. Without this tag they
|
|
509
|
+
// collapse to a single Function node and the SFINAE call resolves
|
|
510
|
+
// to only one edge regardless of which overload's constraint holds.
|
|
511
|
+
// The provider hook is the right invocation point — parsing-processor
|
|
512
|
+
// sees raw tree-sitter matches without the `@`-prefixed synthetic
|
|
513
|
+
// captures `scope-extractor` consumes, so we delegate extraction to
|
|
514
|
+
// the language adapter (C++ implements this; other languages opt out).
|
|
515
|
+
let parsedTemplateConstraints = undefined;
|
|
516
|
+
let constraintsTag = '';
|
|
517
|
+
if ((nodeLabel === 'Function' || nodeLabel === 'Method') &&
|
|
518
|
+
provider.extractTemplateConstraints !== undefined &&
|
|
519
|
+
definitionNode !== null) {
|
|
520
|
+
try {
|
|
521
|
+
parsedTemplateConstraints = provider.extractTemplateConstraints(definitionNode);
|
|
522
|
+
if (parsedTemplateConstraints !== undefined) {
|
|
523
|
+
constraintsTag = templateConstraintsIdTag(parsedTemplateConstraints);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
parsedTemplateConstraints = undefined;
|
|
528
|
+
constraintsTag = '';
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${classTemplateTag}${arityTag}${constraintsTag}`);
|
|
503
532
|
const classNodeForSymbol = definitionNodeForRange || definitionNode || nameNode;
|
|
504
533
|
const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
|
|
505
534
|
(classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
|
|
@@ -528,6 +557,9 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
528
557
|
...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
|
|
529
558
|
? { templateArguments: classTemplateArguments }
|
|
530
559
|
: {}),
|
|
560
|
+
...(parsedTemplateConstraints !== undefined
|
|
561
|
+
? { templateConstraints: parsedTemplateConstraints }
|
|
562
|
+
: {}),
|
|
531
563
|
...(frameworkHint
|
|
532
564
|
? {
|
|
533
565
|
astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
|
|
@@ -579,6 +611,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
579
611
|
parameterCount: methodProps.parameterCount,
|
|
580
612
|
requiredParameterCount: methodProps.requiredParameterCount,
|
|
581
613
|
parameterTypes: methodProps.parameterTypes,
|
|
614
|
+
parameterTypeClasses: methodProps.parameterTypeClasses,
|
|
582
615
|
returnType: methodProps.returnType,
|
|
583
616
|
declaredType,
|
|
584
617
|
templateArguments: classTemplateArguments,
|
|
@@ -37,12 +37,14 @@
|
|
|
37
37
|
* resolution but not emitted as multiple edges; callers that want
|
|
38
38
|
* branch-on-ambiguity behavior should consume the registries directly.
|
|
39
39
|
*/
|
|
40
|
-
import { CLASS_KINDS, FIELD_KINDS, METHOD_KINDS, type ReferenceIndex, type RegistryProviders } from '../../_shared/index.js';
|
|
40
|
+
import { CLASS_KINDS, FIELD_KINDS, METHOD_KINDS, type ReferenceIndex, type RegistryContext, type RegistryProviders } from '../../_shared/index.js';
|
|
41
41
|
import type { ScopeResolutionIndexes } from './model/scope-resolution-indexes.js';
|
|
42
42
|
export interface ResolveReferencesInput {
|
|
43
43
|
readonly scopes: ScopeResolutionIndexes;
|
|
44
44
|
/** Provider hooks consumed by the registries (e.g. `arityCompatibility`). */
|
|
45
45
|
readonly providers?: RegistryProviders;
|
|
46
|
+
/** Required owner-keyed member lookup used by Step 2 receiver/MRO walks. */
|
|
47
|
+
readonly ownedMembersByOwner: RegistryContext['ownedMembersByOwner'];
|
|
46
48
|
}
|
|
47
49
|
export interface ResolveStats {
|
|
48
50
|
readonly sitesProcessed: number;
|
|
@@ -51,6 +51,7 @@ export function resolveReferenceSites(input) {
|
|
|
51
51
|
defs: scopes.defs,
|
|
52
52
|
qualifiedNames: scopes.qualifiedNames,
|
|
53
53
|
moduleScopes: scopes.moduleScopes,
|
|
54
|
+
ownedMembersByOwner: input.ownedMembersByOwner,
|
|
54
55
|
methodDispatch: scopes.methodDispatch,
|
|
55
56
|
providers,
|
|
56
57
|
};
|
|
@@ -135,7 +136,10 @@ function lookupForSite(site, classRegistry, methodRegistry, fieldRegistry) {
|
|
|
135
136
|
case 'write': {
|
|
136
137
|
// Try field first; fall through to method then class so bare-name
|
|
137
138
|
// reads of a function (e.g. `cb = save`) still resolve.
|
|
138
|
-
const
|
|
139
|
+
const fieldOpts = {
|
|
140
|
+
...(site.explicitReceiver !== undefined ? { explicitReceiver: site.explicitReceiver } : {}),
|
|
141
|
+
};
|
|
142
|
+
const fieldHits = fieldRegistry.lookup(site.name, site.inScope, fieldOpts);
|
|
139
143
|
if (fieldHits.length > 0)
|
|
140
144
|
return fieldHits;
|
|
141
145
|
const methodHits = methodRegistry.lookup(site.name, site.inScope);
|
|
@@ -372,8 +372,10 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
|
|
|
372
372
|
const parameterCount = parseIntCapture(match['@declaration.parameter-count']);
|
|
373
373
|
const requiredParameterCount = parseIntCapture(match['@declaration.required-parameter-count']);
|
|
374
374
|
const parameterTypes = parseJsonStringArrayCapture(match['@declaration.parameter-types']);
|
|
375
|
+
const parameterTypeClasses = parseJsonParameterTypeClassesCapture(match['@declaration.parameter-type-classes']);
|
|
375
376
|
const declaredType = match['@declaration.field-type']?.text;
|
|
376
377
|
const returnType = match['@declaration.return-type']?.text;
|
|
378
|
+
const templateConstraints = parseJsonCapture(match['@declaration.template-constraints']);
|
|
377
379
|
return {
|
|
378
380
|
nodeId: makeDefId(filePath, anchor.range, type, nameCap.text),
|
|
379
381
|
filePath,
|
|
@@ -382,17 +384,77 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
|
|
|
382
384
|
...(parameterCount !== undefined ? { parameterCount } : {}),
|
|
383
385
|
...(requiredParameterCount !== undefined ? { requiredParameterCount } : {}),
|
|
384
386
|
...(parameterTypes !== undefined ? { parameterTypes } : {}),
|
|
387
|
+
...(parameterTypeClasses !== undefined ? { parameterTypeClasses } : {}),
|
|
385
388
|
...(declaredType !== undefined ? { declaredType } : {}),
|
|
386
389
|
...(returnType !== undefined ? { returnType } : {}),
|
|
387
390
|
...(templateArguments !== undefined ? { templateArguments } : {}),
|
|
391
|
+
...(templateConstraints !== undefined ? { templateConstraints } : {}),
|
|
388
392
|
};
|
|
389
393
|
}
|
|
394
|
+
/** Parse an opaque JSON payload synthesized by per-language captures
|
|
395
|
+
* (e.g. C++ `@declaration.template-constraints`). Producer owns the
|
|
396
|
+
* shape; shared code threads it through as `unknown` per the
|
|
397
|
+
* `SymbolDefinition.templateConstraints` contract. */
|
|
398
|
+
function parseJsonCapture(cap) {
|
|
399
|
+
if (cap === undefined)
|
|
400
|
+
return undefined;
|
|
401
|
+
try {
|
|
402
|
+
return JSON.parse(cap.text);
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
390
408
|
function parseIntCapture(cap) {
|
|
391
409
|
if (cap === undefined)
|
|
392
410
|
return undefined;
|
|
393
411
|
const n = Number.parseInt(cap.text, 10);
|
|
394
412
|
return Number.isFinite(n) ? n : undefined;
|
|
395
413
|
}
|
|
414
|
+
function parseJsonParameterTypeClassesCapture(cap) {
|
|
415
|
+
if (cap === undefined)
|
|
416
|
+
return undefined;
|
|
417
|
+
try {
|
|
418
|
+
const parsed = JSON.parse(cap.text);
|
|
419
|
+
if (!Array.isArray(parsed))
|
|
420
|
+
return undefined;
|
|
421
|
+
const out = [];
|
|
422
|
+
for (const item of parsed) {
|
|
423
|
+
if (item === null || typeof item !== 'object')
|
|
424
|
+
return undefined;
|
|
425
|
+
const o = item;
|
|
426
|
+
if (typeof o.base !== 'string')
|
|
427
|
+
return undefined;
|
|
428
|
+
if (o.cv !== 'none' &&
|
|
429
|
+
o.cv !== 'const' &&
|
|
430
|
+
o.cv !== 'volatile' &&
|
|
431
|
+
o.cv !== 'const volatile' &&
|
|
432
|
+
o.cv !== 'unknown') {
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
if (o.indirection !== 'value' &&
|
|
436
|
+
o.indirection !== 'lvalue-ref' &&
|
|
437
|
+
o.indirection !== 'rvalue-ref' &&
|
|
438
|
+
o.indirection !== 'pointer' &&
|
|
439
|
+
o.indirection !== 'unknown') {
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
if (typeof o.pointerDepth !== 'number' || !Number.isFinite(o.pointerDepth)) {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
out.push({
|
|
446
|
+
base: o.base,
|
|
447
|
+
cv: o.cv,
|
|
448
|
+
indirection: o.indirection,
|
|
449
|
+
pointerDepth: o.pointerDepth,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return out;
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return undefined;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
396
458
|
function parseJsonStringArrayCapture(cap) {
|
|
397
459
|
if (cap === undefined)
|
|
398
460
|
return undefined;
|
|
@@ -747,6 +809,7 @@ const KNOWN_SUB_TAGS = new Set([
|
|
|
747
809
|
'@declaration.parameter-count',
|
|
748
810
|
'@declaration.required-parameter-count',
|
|
749
811
|
'@declaration.parameter-types',
|
|
812
|
+
'@declaration.template-constraints',
|
|
750
813
|
]);
|
|
751
814
|
/**
|
|
752
815
|
* Return the anchor capture for a match — the one whose name begins with
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
* Plan that introduced most of these invariants:
|
|
251
251
|
* `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
|
|
252
252
|
*/
|
|
253
|
-
import type { BindingRef, Callsite, ParsedFile, ScopeId, SupportedLanguages, SymbolDefinition } from '../../../../_shared/index.js';
|
|
253
|
+
import type { BindingRef, Callsite, ConstraintContext, ParsedFile, ScopeId, SupportedLanguages, SymbolDefinition } from '../../../../_shared/index.js';
|
|
254
254
|
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
255
255
|
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
256
256
|
import { LanguageProvider } from '../../language-provider.js';
|
|
@@ -264,6 +264,9 @@ import type { ConversionRankFn } from '../passes/overload-narrowing.js';
|
|
|
264
264
|
export type LinearizeStrategy = (classDefId: string, directParents: readonly string[], parentsByDefId: ReadonlyMap<string, readonly string[]>) => string[];
|
|
265
265
|
/** Result of `ScopeResolver.arityCompatibility` — mirrors `RegistryProviders.arityCompatibility`. */
|
|
266
266
|
export type ArityVerdict = 'compatible' | 'unknown' | 'incompatible';
|
|
267
|
+
/** Re-exported for ScopeResolver consumers — same shape as
|
|
268
|
+
* `RegistryProviders.constraintCompatibility`'s third parameter. */
|
|
269
|
+
export type { ConstraintContext } from '../../../../_shared/index.js';
|
|
267
270
|
export interface ScopeResolver {
|
|
268
271
|
/** Identity for telemetry + per-language flag check. */
|
|
269
272
|
readonly language: SupportedLanguages;
|
|
@@ -337,6 +340,23 @@ export interface ScopeResolver {
|
|
|
337
340
|
* `(def, callsite)` and need an adapter at the wiring site.
|
|
338
341
|
*/
|
|
339
342
|
arityCompatibility(callsite: Callsite, def: SymbolDefinition): ArityVerdict;
|
|
343
|
+
/**
|
|
344
|
+
* Per-language constraint compatibility between a callsite and a
|
|
345
|
+
* candidate `def` that carries `templateConstraints` metadata.
|
|
346
|
+
* Mirrors `arityCompatibility` semantics: the three-valued verdict
|
|
347
|
+
* MUST treat `'unknown'` as keep-candidate (monotonicity — adding
|
|
348
|
+
* a predicate can only narrow correctly, never produce a wrong
|
|
349
|
+
* edge). Consulted by `narrowOverloadCandidates` after the arity
|
|
350
|
+
* and parameter-type filters.
|
|
351
|
+
*
|
|
352
|
+
* Optional. Languages without constrained-overload semantics
|
|
353
|
+
* (SFINAE, `requires` clauses, trait bounds, conditional types)
|
|
354
|
+
* leave this undefined and the constraint filter is a pass-through.
|
|
355
|
+
*
|
|
356
|
+
* C++ is the first consumer; see `languages/cpp/constraint-filter.ts`
|
|
357
|
+
* for the Tier-A predicate registry and Kleene 3-valued evaluator.
|
|
358
|
+
*/
|
|
359
|
+
readonly constraintCompatibility?: (callsite: Callsite, def: SymbolDefinition, ctx: ConstraintContext) => ArityVerdict;
|
|
340
360
|
/**
|
|
341
361
|
* Compute the method-dispatch order for every Class def in the
|
|
342
362
|
* workspace. Python uses depth-first first-seen via
|
|
@@ -41,6 +41,7 @@ export declare function resolveDefGraphId(filePath: string, def: {
|
|
|
41
41
|
type?: NodeLabel;
|
|
42
42
|
parameterTypes?: readonly string[];
|
|
43
43
|
templateArguments?: readonly string[];
|
|
44
|
+
templateConstraints?: unknown;
|
|
44
45
|
}, nodeLookup: GraphNodeLookup): string | undefined;
|
|
45
46
|
/** Derive the simple (unqualified) name of a def from its `qualifiedName`. */
|
|
46
47
|
export declare function simpleQualifiedName(def: SymbolDefinition): string | undefined;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { generateId } from '../../../../lib/utils.js';
|
|
20
20
|
import { qualifiedKey, simpleKey } from '../graph-bridge/node-lookup.js';
|
|
21
|
+
import { templateConstraintsIdTag } from '../../utils/template-arguments.js';
|
|
21
22
|
/**
|
|
22
23
|
* Labels that may legitimately ANCHOR a CALLS/ACCESSES edge as the
|
|
23
24
|
* source ("caller"). A Variable / Property can be the TARGET of an
|
|
@@ -68,6 +69,19 @@ export function resolveDefGraphId(filePath, def, nodeLookup) {
|
|
|
68
69
|
if (qn === undefined || qn.length === 0)
|
|
69
70
|
return undefined;
|
|
70
71
|
if (def.type !== undefined) {
|
|
72
|
+
// SFINAE / `requires`-clause disambiguation (issue #1579) — try the
|
|
73
|
+
// constraint-fingerprinted key FIRST. Two function-template overloads
|
|
74
|
+
// with identical `parameterTypes` but mutually-exclusive SFINAE
|
|
75
|
+
// constraints route to their distinct graph nodes via this key.
|
|
76
|
+
// Must run before the parameter-types key because both overloads
|
|
77
|
+
// share the latter.
|
|
78
|
+
if ((def.type === 'Function' || def.type === 'Method') &&
|
|
79
|
+
def.templateConstraints !== undefined) {
|
|
80
|
+
const cKey = qualifiedKey(filePath, def.type, `${qn}${templateConstraintsIdTag(def.templateConstraints)}`);
|
|
81
|
+
const cHit = nodeLookup.get(cKey);
|
|
82
|
+
if (cHit !== undefined)
|
|
83
|
+
return cHit;
|
|
84
|
+
}
|
|
71
85
|
// Overload disambiguation: when the def carries parameter types,
|
|
72
86
|
// try the parameter-typed key first so same-name same-arity
|
|
73
87
|
// overloads route to their distinct graph nodes.
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* `SymbolDefinition.nodeId` values into the legacy graph-node ID
|
|
18
18
|
* format that downstream consumers (queries, edges, MCP) expect.
|
|
19
19
|
*/
|
|
20
|
+
import { templateConstraintsIdTag } from '../../utils/template-arguments.js';
|
|
20
21
|
/**
|
|
21
22
|
* Parse a qualified name out of a Function/Method node id.
|
|
22
23
|
*
|
|
@@ -86,6 +87,17 @@ export function buildGraphNodeLookup(graph) {
|
|
|
86
87
|
// Each overload is unique — set unconditionally.
|
|
87
88
|
lookup.set(pKey, node.id);
|
|
88
89
|
}
|
|
90
|
+
// SFINAE / `requires`-clause disambiguation (issue #1579) — register
|
|
91
|
+
// a constraint-fingerprinted key so resolveDefGraphId can locate the
|
|
92
|
+
// correct overload by hashing the def's `templateConstraints`. Mirrors
|
|
93
|
+
// the parameter-types key but keys on the opaque constraint payload
|
|
94
|
+
// instead, separating two `process<T>` overloads whose
|
|
95
|
+
// `parameterTypes=['T']` would otherwise collide.
|
|
96
|
+
const tConstraints = props.templateConstraints;
|
|
97
|
+
if (tConstraints !== undefined && (node.label === 'Function' || node.label === 'Method')) {
|
|
98
|
+
const cKey = qualifiedKey(props.filePath, node.label, `${qualified}${templateConstraintsIdTag(tConstraints)}`);
|
|
99
|
+
lookup.set(cKey, node.id);
|
|
100
|
+
}
|
|
89
101
|
if ((node.label === 'Class' ||
|
|
90
102
|
node.label === 'Struct' ||
|
|
91
103
|
node.label === 'Interface' ||
|
|
@@ -22,6 +22,7 @@ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexe
|
|
|
22
22
|
import type { SemanticModel } from '../../model/semantic-model.js';
|
|
23
23
|
import type { WorkspaceResolutionIndex } from '../workspace-index.js';
|
|
24
24
|
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
25
|
+
import type { ScopeResolver } from '../contract/scope-resolver.js';
|
|
25
26
|
import { type ConversionRankFn } from './overload-narrowing.js';
|
|
26
27
|
export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, _referenceIndex: {
|
|
27
28
|
readonly bySourceScope: ReadonlyMap<ScopeId, readonly Reference[]>;
|
|
@@ -44,6 +45,12 @@ export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: Scop
|
|
|
44
45
|
};
|
|
45
46
|
}, callerParsed: ParsedFile, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[]) => readonly SymbolDefinition[] | undefined;
|
|
46
47
|
readonly conversionRankFn?: ConversionRankFn;
|
|
48
|
+
/** Optional per-language constraint hook threaded into
|
|
49
|
+
* `narrowOverloadCandidates`. Drops candidates whose template
|
|
50
|
+
* constraints (e.g. C++ `enable_if_t`, C++20 `requires`) provably
|
|
51
|
+
* fail at the call site. Three-valued; `'unknown'` keeps the
|
|
52
|
+
* candidate (monotonicity). */
|
|
53
|
+
readonly constraintCompatibility?: ScopeResolver['constraintCompatibility'];
|
|
47
54
|
}): number;
|
|
48
55
|
/** Walk up from the call-site scope to the enclosing class scope,
|
|
49
56
|
* pick a method member by name with overload narrowing on arity +
|
|
@@ -63,4 +70,7 @@ export declare function pickImplicitThisOverload(site: {
|
|
|
63
70
|
readonly name: string;
|
|
64
71
|
readonly arity?: number;
|
|
65
72
|
readonly argumentTypes?: readonly string[];
|
|
66
|
-
}, scopes: ScopeResolutionIndexes, workspaceIndex: WorkspaceResolutionIndex, model: SemanticModel,
|
|
73
|
+
}, scopes: ScopeResolutionIndexes, workspaceIndex: WorkspaceResolutionIndex, model: SemanticModel, hookCtx?: {
|
|
74
|
+
readonly conversionRankFn?: ConversionRankFn;
|
|
75
|
+
readonly constraintCompatibility?: ScopeResolver['constraintCompatibility'];
|
|
76
|
+
}): SymbolDefinition | undefined;
|