fix(cpp): rank homogeneous braced init overloads

This commit is contained in:
azizur100389 2026-06-15 17:54:08 +01:00
parent 5e96a99b0d
commit 84abd91a0a
6 changed files with 194 additions and 0 deletions

View file

@ -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:

View file

@ -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;
}

View file

@ -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,

View file

@ -0,0 +1,41 @@
#include <initializer_list>
#include <vector>
namespace std {
template <typename T>
class initializer_list {};
template <typename T>
class vector {};
}
class InitListService {
public:
void consume(std::initializer_list<int> values) {}
void consume(int value) {}
void consumeVector(std::vector<int> values) {}
void consumeVector(int value) {}
void consumeMixed(std::initializer_list<int> values) {}
void consumeMixed(std::initializer_list<double> values) {}
void consumeEmpty(std::initializer_list<int> values) {}
void consumeEmpty(std::initializer_list<double> values) {}
void callHomogeneousInitList() {
consume({1, 2, 3});
}
void callHomogeneousVector() {
consumeVector({1, 2, 3});
}
void callHeterogeneousInitList() {
consumeMixed({1, 2.0});
}
void callEmptyInitList() {
consumeEmpty({});
}
};

View file

@ -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<int>',
]);
});
it('resolves homogeneous literal braces to container overloads', () => {
expect(singleTargetParameterTypes('callHomogeneousVector', 'consumeVector')).toEqual([
'std::vector<int>',
]);
});
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;

View file

@ -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<int>')).toBe(0);
expect(cppConversionRank('braced-init:int', 'std::vector<int>')).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<int>'],
[value('std::initializer_list')],
);
const byDoubleList = mkDef(
'f:double-list',
['std::initializer_list<double>'],
[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')]);