diff --git a/gitnexus/src/core/ingestion/languages/cpp/captures.ts b/gitnexus/src/core/ingestion/languages/cpp/captures.ts index ef39076f1..6618d6c04 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/captures.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/captures.ts @@ -22,6 +22,8 @@ import { markCppInlineNamespaceRange } from './inline-namespaces.js'; import { extractCppTemplateConstraints } from './constraint-extractor.js'; import { captureCppMemberLookupFacts } from './member-lookup.js'; +const CPP_BRACED_INIT_TYPE_PREFIX = 'braced-init:'; + export function emitCppScopeCaptures( sourceText: string, filePath: string, @@ -1022,6 +1024,8 @@ function unknownTypeClass(base: string): ParameterTypeClass { */ function inferCppLiteralType(node: SyntaxNode): string { switch (node.type) { + case 'initializer_list': + return inferCppBracedInitType(node); case 'number_literal': { const text = node.text; // Floating-point literals contain '.', 'e', 'E', or end with 'f'/'F' @@ -1053,6 +1057,25 @@ function inferCppLiteralType(node: SyntaxNode): string { } } +function inferCppBracedInitType(node: SyntaxNode): string { + const elementTypes: string[] = []; + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child === null) continue; + if (child.type === ',' || child.type === '{' || child.type === '}') continue; + const elementType = inferCppLiteralType(child); + if (elementType === '' || elementType.startsWith(CPP_BRACED_INIT_TYPE_PREFIX)) { + return `${CPP_BRACED_INIT_TYPE_PREFIX}unknown`; + } + elementTypes.push(elementType); + } + if (elementTypes.length === 0) return `${CPP_BRACED_INIT_TYPE_PREFIX}unknown`; + const first = elementTypes[0]; + return elementTypes.every((type) => type === first) + ? `${CPP_BRACED_INIT_TYPE_PREFIX}${first}` + : `${CPP_BRACED_INIT_TYPE_PREFIX}unknown`; +} + /** * Look up the declared type of a variable by scanning sibling declarations * in the enclosing compound_statement (function body). Handles: diff --git a/gitnexus/src/core/ingestion/languages/cpp/conversion-rank.ts b/gitnexus/src/core/ingestion/languages/cpp/conversion-rank.ts index 330deea09..547920783 100644 --- a/gitnexus/src/core/ingestion/languages/cpp/conversion-rank.ts +++ b/gitnexus/src/core/ingestion/languages/cpp/conversion-rank.ts @@ -32,6 +32,22 @@ const INTEGRAL_PROMOTION = new Map([ ['bool', 'int'], ]); +const BRACED_INIT_TYPE_PREFIX = 'braced-init:'; +const BRACED_INIT_CONTAINER_TYPES = new Set([ + 'array', + 'deque', + 'list', + 'set', + 'std::array', + 'std::deque', + 'std::list', + 'std::set', + 'std::unordered_set', + 'std::vector', + 'unordered_set', + 'vector', +]); + /** * Return the conversion rank from `argType` to `paramType`. * @@ -46,6 +62,11 @@ export function cppConversionRank( argTypeClass?: ParameterTypeClass, paramTypeClass?: ParameterTypeClass, ): number { + const bracedInitElementType = parseBracedInitElementType(argType); + if (bracedInitElementType !== undefined) { + if (bracedInitElementType === 'unknown') return Infinity; + return bracedInitConversionRank(paramType); + } if (argType === paramType) { return exactShapeCompatible(argTypeClass, paramTypeClass) ? 0 : Infinity; } @@ -60,6 +81,32 @@ export function cppConversionRank( return Infinity; } +function parseBracedInitElementType(argType: string): string | undefined { + const elementType = argType.slice(BRACED_INIT_TYPE_PREFIX.length); + return argType.startsWith(BRACED_INIT_TYPE_PREFIX) && elementType !== '' + ? elementType + : undefined; +} + +function bracedInitConversionRank(paramType: string): number { + const targetBase = bracedInitTargetBase(paramType); + if (targetBase === 'initializer_list' || targetBase === 'std::initializer_list') return 0; + if (BRACED_INIT_CONTAINER_TYPES.has(targetBase)) return 1; + return Infinity; +} + +function bracedInitTargetBase(paramType: string): string { + let type = paramType.trim(); + type = type.replace(/\b(const|volatile|restrict|mutable|constexpr)\b/g, '').trim(); + type = type.replace(/[&*]+\s*$/, '').trim(); + while (type.includes('<')) { + const stripped = type.replace(/<[^<>]*>/g, ''); + if (stripped === type) break; + type = stripped; + } + return type.trim(); +} + function isPointer(typeClass: ParameterTypeClass | undefined): boolean { return typeClass?.indirection === 'pointer' && typeClass.pointerDepth > 0; } diff --git a/gitnexus/src/core/ingestion/scope-resolution/passes/overload-narrowing.ts b/gitnexus/src/core/ingestion/scope-resolution/passes/overload-narrowing.ts index b69252ce2..a2766ad90 100644 --- a/gitnexus/src/core/ingestion/scope-resolution/passes/overload-narrowing.ts +++ b/gitnexus/src/core/ingestion/scope-resolution/passes/overload-narrowing.ts @@ -51,6 +51,8 @@ import type { SymbolDefinition, } from 'gitnexus-shared'; +const CONVERSION_ONLY_ARG_TYPE_PREFIXES = ['braced-init:']; + /** * Per-slot conversion-rank function. Returns a numeric cost for * converting `argType` to `paramType`: @@ -176,6 +178,7 @@ export function narrowOverloadCandidates( hookCtx.argumentTypeClasses, ); if (ranked.length > 0) result = ranked; + else if (hasConversionOnlyArgType(argTypes)) result = []; } } @@ -222,6 +225,12 @@ export function narrowOverloadCandidates( return result; } +function hasConversionOnlyArgType(argTypes: readonly string[]): boolean { + return argTypes.some((type) => + CONVERSION_ONLY_ARG_TYPE_PREFIXES.some((prefix) => type.startsWith(prefix)), + ); +} + function exactTypeSlotMatches( argType: string, paramType: string, diff --git a/gitnexus/test/fixtures/lang-resolution/cpp-braced-init-list-overload/service.cpp b/gitnexus/test/fixtures/lang-resolution/cpp-braced-init-list-overload/service.cpp new file mode 100644 index 000000000..913be99b9 --- /dev/null +++ b/gitnexus/test/fixtures/lang-resolution/cpp-braced-init-list-overload/service.cpp @@ -0,0 +1,41 @@ +#include +#include + +namespace std { +template +class initializer_list {}; + +template +class vector {}; +} + +class InitListService { +public: + void consume(std::initializer_list values) {} + void consume(int value) {} + + void consumeVector(std::vector values) {} + void consumeVector(int value) {} + + void consumeMixed(std::initializer_list values) {} + void consumeMixed(std::initializer_list values) {} + + void consumeEmpty(std::initializer_list values) {} + void consumeEmpty(std::initializer_list values) {} + + void callHomogeneousInitList() { + consume({1, 2, 3}); + } + + void callHomogeneousVector() { + consumeVector({1, 2, 3}); + } + + void callHeterogeneousInitList() { + consumeMixed({1, 2.0}); + } + + void callEmptyInitList() { + consumeEmpty({}); + } +}; diff --git a/gitnexus/test/integration/resolvers/cpp.test.ts b/gitnexus/test/integration/resolvers/cpp.test.ts index 925602a5c..80f2c9b2e 100644 --- a/gitnexus/test/integration/resolvers/cpp.test.ts +++ b/gitnexus/test/integration/resolvers/cpp.test.ts @@ -1230,6 +1230,52 @@ describe('C++ overload disambiguation by parameter types', () => { // ── Phase P: Same-arity overloads — cross-file + chain resolution ───────── +describe('C++ braced-init-list overload disambiguation (#1899 A8 conservative)', () => { + let result: PipelineResult; + + beforeAll(async () => { + result = await runPipelineFromRepo( + path.join(FIXTURES, 'cpp-braced-init-list-overload'), + () => {}, + ); + }, 60000); + + const callsFrom = (source: string, target: string) => + getRelationships(result, 'CALLS').filter( + (edge) => edge.source === source && edge.target === target, + ); + + const singleTargetParameterTypes = (source: string, target: string) => { + const calls = callsFrom(source, target); + expect(calls).toHaveLength(1); + const [call] = calls; + expect(call).toBeDefined(); + return call === undefined + ? undefined + : result.graph.getNode(call.rel.targetId)?.properties.parameterTypes; + }; + + it('resolves homogeneous literal braces to initializer_list overloads', () => { + expect(singleTargetParameterTypes('callHomogeneousInitList', 'consume')).toEqual([ + 'std::initializer_list', + ]); + }); + + it('resolves homogeneous literal braces to container overloads', () => { + expect(singleTargetParameterTypes('callHomogeneousVector', 'consumeVector')).toEqual([ + 'std::vector', + ]); + }); + + it('suppresses heterogeneous braced-init lists instead of guessing an element type', () => { + expect(callsFrom('callHeterogeneousInitList', 'consumeMixed')).toHaveLength(0); + }); + + it('suppresses empty braced-init lists instead of guessing an element type', () => { + expect(callsFrom('callEmptyInitList', 'consumeEmpty')).toHaveLength(0); + }); +}); + describe('C++ same-arity overload cross-file and chain resolution', () => { let result: PipelineResult; diff --git a/gitnexus/test/unit/scope-resolution/cpp/cpp-overload-ranking.test.ts b/gitnexus/test/unit/scope-resolution/cpp/cpp-overload-ranking.test.ts index 7203e0633..9efd2a2a3 100644 --- a/gitnexus/test/unit/scope-resolution/cpp/cpp-overload-ranking.test.ts +++ b/gitnexus/test/unit/scope-resolution/cpp/cpp-overload-ranking.test.ts @@ -102,6 +102,34 @@ describe('cppConversionRank user-defined conversion ranks (#1631)', () => { }); }); +describe('cppConversionRank braced-init-list ranks (#1899)', () => { + it('ranks homogeneous braced-init lists toward initializer_list and containers', () => { + expect(cppConversionRank('braced-init:int', 'std::initializer_list')).toBe(0); + expect(cppConversionRank('braced-init:int', 'std::vector')).toBe(1); + expect(cppConversionRank('braced-init:int', 'int')).toBe(Infinity); + }); + + it('suppresses unknown braced-init lists when conversion ranking finds no viable target', () => { + const byIntList = mkDef( + 'f:int-list', + ['std::initializer_list'], + [value('std::initializer_list')], + ); + const byDoubleList = mkDef( + 'f:double-list', + ['std::initializer_list'], + [value('std::initializer_list')], + ); + + const result = narrowOverloadCandidates([byIntList, byDoubleList], 1, ['braced-init:unknown'], { + argumentTypeClasses: [value('braced-init:unknown')], + conversionRankFn: cppConversionRank, + }); + + expect(result).toEqual([]); + }); +}); + describe('narrowOverloadCandidates with C++ pointer-rank sidecars (#1637)', () => { it('selects pointer overload for nullptr over bool overload', () => { const byPointer = mkDef('f:intptr', ['int'], [pointer('int')]);