mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-05 02:43:32 +00:00
fix(cpp): rank braced init overloads by element shape
This commit is contained in:
parent
f69c21e170
commit
f998253320
17 changed files with 300 additions and 48 deletions
|
|
@ -20,6 +20,8 @@ export interface ParameterTypeClass {
|
|||
indirection: 'value' | 'lvalue-ref' | 'rvalue-ref' | 'pointer' | 'unknown';
|
||||
/** Number of pointer markers when indirection is `pointer`; otherwise 0. */
|
||||
pointerDepth: number;
|
||||
/** Normalized top-level template arguments, when a language preserves them. */
|
||||
templateArguments?: string[];
|
||||
}
|
||||
|
||||
export interface SymbolDefinition {
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ export function classifyCppParameterType(
|
|||
cv,
|
||||
indirection,
|
||||
pointerDepth,
|
||||
...templateArgumentsFor(`${source} ${rawType} ${declaratorText ?? ''}`),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -213,6 +214,39 @@ function unknownTypeClass(base: string): ParameterTypeClass {
|
|||
};
|
||||
}
|
||||
|
||||
function templateArgumentsFor(rawType: string): Pick<ParameterTypeClass, 'templateArguments'> {
|
||||
const args = parseTopLevelTemplateArguments(rawType);
|
||||
return args === undefined ? {} : { templateArguments: args };
|
||||
}
|
||||
|
||||
function parseTopLevelTemplateArguments(rawType: string): string[] | undefined {
|
||||
const start = rawType.indexOf('<');
|
||||
if (start < 0) return undefined;
|
||||
|
||||
const args: string[] = [];
|
||||
let depth = 0;
|
||||
let argStart = start + 1;
|
||||
for (let i = start + 1; i < rawType.length; i++) {
|
||||
const ch = rawType[i];
|
||||
if (ch === '<') {
|
||||
depth++;
|
||||
} else if (ch === '>') {
|
||||
if (depth === 0) {
|
||||
const finalArg = rawType.slice(argStart, i).trim();
|
||||
if (finalArg.length > 0) args.push(normalizeCppParamType(finalArg));
|
||||
return args.length > 0 ? args : undefined;
|
||||
}
|
||||
depth--;
|
||||
} else if (ch === ',' && depth === 0) {
|
||||
const arg = rawType.slice(argStart, i).trim();
|
||||
if (arg.length > 0) args.push(normalizeCppParamType(arg));
|
||||
argStart = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findFuncDeclarator(node: SyntaxNode): SyntaxNode | null {
|
||||
let decl = node.childForFieldName('declarator');
|
||||
if (decl === null) {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ import { markCppAdlSiteArgs, markCppAdlSiteNoAdl, type CppAdlArgInfo } from './a
|
|||
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:';
|
||||
import { CPP_BRACED_INIT_TYPE_PREFIX } from './conversion-rank.js';
|
||||
|
||||
export function emitCppScopeCaptures(
|
||||
sourceText: string,
|
||||
|
|
@ -1065,15 +1064,15 @@ function inferCppBracedInitType(node: SyntaxNode): string {
|
|||
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`;
|
||||
return `${CPP_BRACED_INIT_TYPE_PREFIX}unknown:${elementTypes.length + 1}`;
|
||||
}
|
||||
elementTypes.push(elementType);
|
||||
}
|
||||
if (elementTypes.length === 0) return `${CPP_BRACED_INIT_TYPE_PREFIX}unknown`;
|
||||
if (elementTypes.length === 0) return `${CPP_BRACED_INIT_TYPE_PREFIX}unknown:0`;
|
||||
const first = elementTypes[0];
|
||||
return elementTypes.every((type) => type === first)
|
||||
? `${CPP_BRACED_INIT_TYPE_PREFIX}${first}`
|
||||
: `${CPP_BRACED_INIT_TYPE_PREFIX}unknown`;
|
||||
? `${CPP_BRACED_INIT_TYPE_PREFIX}${first}:${elementTypes.length}`
|
||||
: `${CPP_BRACED_INIT_TYPE_PREFIX}unknown:${elementTypes.length}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
*/
|
||||
|
||||
import type { ParameterTypeClass } from 'gitnexus-shared';
|
||||
import { normalizeCppParamType } from './arity-metadata.js';
|
||||
import { hasCppUserDefinedConversion } from './user-defined-conversions.js';
|
||||
|
||||
/** Set of normalized arithmetic types that support implicit conversion. */
|
||||
|
|
@ -32,7 +33,9 @@ const INTEGRAL_PROMOTION = new Map([
|
|||
['bool', 'int'],
|
||||
]);
|
||||
|
||||
const BRACED_INIT_TYPE_PREFIX = 'braced-init:';
|
||||
export const CPP_BRACED_INIT_TYPE_PREFIX = 'braced-init:';
|
||||
export const CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES = [CPP_BRACED_INIT_TYPE_PREFIX] as const;
|
||||
|
||||
const BRACED_INIT_CONTAINER_TYPES = new Set([
|
||||
'array',
|
||||
'deque',
|
||||
|
|
@ -48,6 +51,11 @@ const BRACED_INIT_CONTAINER_TYPES = new Set([
|
|||
'vector',
|
||||
]);
|
||||
|
||||
interface BracedInitArgType {
|
||||
elementType: string;
|
||||
elementCount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the conversion rank from `argType` to `paramType`.
|
||||
*
|
||||
|
|
@ -62,10 +70,19 @@ export function cppConversionRank(
|
|||
argTypeClass?: ParameterTypeClass,
|
||||
paramTypeClass?: ParameterTypeClass,
|
||||
): number {
|
||||
const bracedInitElementType = parseBracedInitElementType(argType);
|
||||
if (bracedInitElementType !== undefined) {
|
||||
if (bracedInitElementType === 'unknown') return Infinity;
|
||||
return bracedInitConversionRank(paramType);
|
||||
const bracedInitType = parseBracedInitArgType(argType);
|
||||
if (bracedInitType !== undefined) {
|
||||
if (bracedInitType.elementType === 'unknown') return Infinity;
|
||||
if (bracedInitType.elementCount === 1) {
|
||||
const scalarRank = cppConversionRank(
|
||||
bracedInitType.elementType,
|
||||
paramType,
|
||||
undefined,
|
||||
paramTypeClass,
|
||||
);
|
||||
if (isFinite(scalarRank)) return scalarRank;
|
||||
}
|
||||
return bracedInitConversionRank(paramType, bracedInitType, paramTypeClass);
|
||||
}
|
||||
if (argType === paramType) {
|
||||
return exactShapeCompatible(argTypeClass, paramTypeClass) ? 0 : Infinity;
|
||||
|
|
@ -81,30 +98,77 @@ 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 parseBracedInitArgType(argType: string): BracedInitArgType | undefined {
|
||||
if (!argType.startsWith(CPP_BRACED_INIT_TYPE_PREFIX)) return undefined;
|
||||
const payload = argType.slice(CPP_BRACED_INIT_TYPE_PREFIX.length);
|
||||
if (payload === '') return undefined;
|
||||
const separator = payload.lastIndexOf(':');
|
||||
if (separator > 0) {
|
||||
const countText = payload.slice(separator + 1);
|
||||
if (/^\d+$/.test(countText)) {
|
||||
return {
|
||||
elementType: payload.slice(0, separator),
|
||||
elementCount: Number(countText),
|
||||
};
|
||||
}
|
||||
}
|
||||
return { elementType: payload };
|
||||
}
|
||||
|
||||
function bracedInitConversionRank(paramType: string): number {
|
||||
function bracedInitConversionRank(
|
||||
paramType: string,
|
||||
argType: BracedInitArgType,
|
||||
paramTypeClass?: ParameterTypeClass,
|
||||
): number {
|
||||
const targetBase = bracedInitTargetBase(paramType);
|
||||
if (targetBase === 'initializer_list' || targetBase === 'std::initializer_list') return 0;
|
||||
if (BRACED_INIT_CONTAINER_TYPES.has(targetBase)) return 1;
|
||||
if (targetBase === 'initializer_list' || targetBase === 'std::initializer_list') {
|
||||
return bracedInitValueTypeMatches(paramType, argType, paramTypeClass) ? 0 : Infinity;
|
||||
}
|
||||
if (BRACED_INIT_CONTAINER_TYPES.has(targetBase)) {
|
||||
return bracedInitValueTypeMatches(paramType, argType, paramTypeClass) ? 4 : Infinity;
|
||||
}
|
||||
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;
|
||||
function bracedInitValueTypeMatches(
|
||||
paramType: string,
|
||||
argType: BracedInitArgType,
|
||||
paramTypeClass?: ParameterTypeClass,
|
||||
): boolean {
|
||||
const valueType = bracedInitTargetValueType(paramType, paramTypeClass);
|
||||
if (valueType === undefined) return false;
|
||||
return isFinite(cppConversionRank(argType.elementType, valueType));
|
||||
}
|
||||
|
||||
function bracedInitTargetValueType(
|
||||
paramType: string,
|
||||
paramTypeClass?: ParameterTypeClass,
|
||||
): string | undefined {
|
||||
return firstTemplateArgument(paramType) ?? paramTypeClass?.templateArguments?.[0];
|
||||
}
|
||||
|
||||
function firstTemplateArgument(rawType: string): string | undefined {
|
||||
const start = rawType.indexOf('<');
|
||||
if (start < 0) return undefined;
|
||||
|
||||
let depth = 0;
|
||||
for (let i = start + 1; i < rawType.length; i++) {
|
||||
const ch = rawType[i];
|
||||
if (ch === '<') {
|
||||
depth++;
|
||||
} else if (ch === '>') {
|
||||
if (depth === 0) return bracedInitTargetBase(rawType.slice(start + 1, i));
|
||||
depth--;
|
||||
} else if (ch === ',' && depth === 0) {
|
||||
return bracedInitTargetBase(rawType.slice(start + 1, i));
|
||||
}
|
||||
}
|
||||
return type.trim();
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function bracedInitTargetBase(paramType: string): string {
|
||||
return normalizeCppParamType(paramType);
|
||||
}
|
||||
|
||||
function isPointer(typeClass: ParameterTypeClass | undefined): boolean {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
isOverloadAmbiguousAfterNormalization,
|
||||
narrowOverloadCandidates,
|
||||
} from '../../scope-resolution/passes/overload-narrowing.js';
|
||||
import { cppConversionRank } from './conversion-rank.js';
|
||||
import { CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES, cppConversionRank } from './conversion-rank.js';
|
||||
|
||||
interface RangeKey {
|
||||
readonly startLine: number;
|
||||
|
|
@ -167,7 +167,12 @@ export function resolveCppQualifiedNamespaceMember(
|
|||
allHits,
|
||||
callsite?.arity,
|
||||
callsite?.argumentTypes,
|
||||
callsite !== undefined ? { conversionRankFn: cppConversionRank } : undefined,
|
||||
callsite !== undefined
|
||||
? {
|
||||
conversionRankFn: cppConversionRank,
|
||||
conversionOnlyArgTypePrefixes: CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
if (narrowed.length === 1) return narrowed[0];
|
||||
if (narrowed.length === 0) return undefined;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { isClassLike } from '../../scope-resolution/scope/walkers.js';
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
import { cppConstraintCompatibility } from './constraint-filter.js';
|
||||
import { cppConversionRank } from './conversion-rank.js';
|
||||
import { CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES, cppConversionRank } from './conversion-rank.js';
|
||||
|
||||
interface CapturedBaseEdge {
|
||||
readonly childName: string;
|
||||
|
|
@ -309,6 +309,7 @@ function chooseOverload(
|
|||
const narrowed = narrowOverloadCandidates(candidates, callsite.arity, callsite.argumentTypes, {
|
||||
argumentTypeClasses: callsite.argumentTypeClasses,
|
||||
conversionRankFn: cppConversionRank,
|
||||
conversionOnlyArgTypePrefixes: CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES,
|
||||
constraintCompatibility: cppConstraintCompatibility,
|
||||
});
|
||||
if (narrowed.length === 1) return { kind: 'resolved', definition: narrowed[0]! };
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
import type { ScopeResolver } from '../../scope-resolution/contract/scope-resolver.js';
|
||||
import { cppProvider } from '../c-cpp.js';
|
||||
import { cppArityCompatibility } from './arity.js';
|
||||
import { cppConversionRank } from './conversion-rank.js';
|
||||
import { CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES, cppConversionRank } from './conversion-rank.js';
|
||||
import { cppMergeBindings } from './merge-bindings.js';
|
||||
import { resolveCppImportTarget } from './import-target.js';
|
||||
import { scanCppHeaderFiles } from './header-scan.js';
|
||||
|
|
@ -257,6 +257,7 @@ export const cppScopeResolver: ScopeResolver = {
|
|||
// Disambiguates `f(int)` vs `f(double)` called with `f(2.5)` by scoring
|
||||
// each candidate's conversion cost; exact match wins over standard conversion.
|
||||
conversionRankFn: cppConversionRank,
|
||||
conversionOnlyArgTypePrefixes: CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES,
|
||||
// Range-for element type inference: for (auto& user : users) → bind user to User
|
||||
populateRangeBindings: populateCppRangeBindings,
|
||||
// C++ method return-type bindings need to be visible from module scope
|
||||
|
|
|
|||
|
|
@ -654,12 +654,19 @@ function parseJsonParameterTypeClassesCapture(
|
|||
if (typeof o.pointerDepth !== 'number' || !Number.isFinite(o.pointerDepth)) {
|
||||
return undefined;
|
||||
}
|
||||
out.push({
|
||||
const shape: ParameterTypeClass = {
|
||||
base: o.base,
|
||||
cv: o.cv,
|
||||
indirection: o.indirection,
|
||||
pointerDepth: o.pointerDepth,
|
||||
});
|
||||
};
|
||||
if (Array.isArray(o.templateArguments)) {
|
||||
if (!o.templateArguments.every((x): x is string => typeof x === 'string')) {
|
||||
return undefined;
|
||||
}
|
||||
shape.templateArguments = [...o.templateArguments];
|
||||
}
|
||||
out.push(shape);
|
||||
}
|
||||
return out;
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -706,6 +706,16 @@ export interface ScopeResolver {
|
|||
*/
|
||||
readonly conversionRankFn?: ConversionRankFn;
|
||||
|
||||
/**
|
||||
* Optional per-language argument-type prefixes for conversion-only
|
||||
* argument sentinels. When ranking cannot find any viable candidate
|
||||
* for a multi-overload set containing one of these sentinels, shared
|
||||
* narrowing suppresses the ambiguous set instead of falling back to
|
||||
* arity-only candidates. Languages without such sentinels leave this
|
||||
* undefined.
|
||||
*/
|
||||
readonly conversionOnlyArgTypePrefixes?: readonly string[];
|
||||
|
||||
/**
|
||||
* Optional predicate to identify definitions with file-local linkage
|
||||
* (e.g. C `static` functions). When provided, `pickUniqueGlobalCallable`
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ export function emitFreeCallFallback(
|
|||
parsedFiles: readonly ParsedFile[],
|
||||
) => readonly SymbolDefinition[] | undefined;
|
||||
readonly conversionRankFn?: ConversionRankFn;
|
||||
readonly conversionOnlyArgTypePrefixes?: readonly string[];
|
||||
/** Optional per-language constraint hook threaded into
|
||||
* `narrowOverloadCandidates`. Drops candidates whose template
|
||||
* constraints (e.g. C++ `enable_if_t`, C++20 `requires`) provably
|
||||
|
|
@ -164,6 +165,7 @@ export function emitFreeCallFallback(
|
|||
if (fnDef === undefined) {
|
||||
fnDef = pickImplicitThisOverload(site, scopes, workspaceIndex, model, {
|
||||
conversionRankFn: options.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: options.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: options.constraintCompatibility,
|
||||
});
|
||||
fnDefFromImplicitThis = fnDef !== undefined;
|
||||
|
|
@ -191,6 +193,7 @@ export function emitFreeCallFallback(
|
|||
{
|
||||
argumentTypeClasses: site.argumentTypeClasses,
|
||||
conversionRankFn: options.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: options.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: options.constraintCompatibility,
|
||||
},
|
||||
);
|
||||
|
|
@ -277,6 +280,7 @@ export function emitFreeCallFallback(
|
|||
const narrowed = narrowOverloadCandidates(ordinary, site.arity, site.argumentTypes, {
|
||||
argumentTypeClasses: site.argumentTypeClasses,
|
||||
conversionRankFn: options.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: options.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: options.constraintCompatibility,
|
||||
});
|
||||
if (narrowed.length === 1) {
|
||||
|
|
@ -324,6 +328,7 @@ export function emitFreeCallFallback(
|
|||
const narrowed = narrowOverloadCandidates(merged, site.arity, site.argumentTypes, {
|
||||
argumentTypeClasses: site.argumentTypeClasses,
|
||||
conversionRankFn: options.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: options.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: options.constraintCompatibility,
|
||||
});
|
||||
if (narrowed.length === 1) {
|
||||
|
|
@ -380,6 +385,7 @@ export function emitFreeCallFallback(
|
|||
site.argumentTypeClasses,
|
||||
options.conversionRankFn,
|
||||
scopeDefsCache,
|
||||
options.conversionOnlyArgTypePrefixes,
|
||||
);
|
||||
}
|
||||
if (fnDef === undefined) continue;
|
||||
|
|
@ -576,6 +582,7 @@ export function pickUniqueGlobalCallable(
|
|||
callArgTypeClasses?: readonly ParameterTypeClass[],
|
||||
conversionRankFn?: ConversionRankFn,
|
||||
scopeDefsCache?: Map<string, readonly SymbolDefinition[]>,
|
||||
conversionOnlyArgTypePrefixes?: readonly string[],
|
||||
): SymbolDefinition | undefined {
|
||||
// The scope-index candidate list is a pure function of (name, callerFilePath):
|
||||
// the same-name bucket is fixed for the pass, the file-local filter depends
|
||||
|
|
@ -637,6 +644,7 @@ export function pickUniqueGlobalCallable(
|
|||
const narrowed = narrowOverloadCandidates(scopeDefs, callArity, callArgTypes, {
|
||||
argumentTypeClasses: callArgTypeClasses,
|
||||
conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes,
|
||||
});
|
||||
if (narrowed.length === 1) return narrowed[0];
|
||||
}
|
||||
|
|
@ -678,6 +686,7 @@ export function pickUniqueGlobalCallable(
|
|||
const narrowed = narrowOverloadCandidates(defs, callArity, callArgTypes, {
|
||||
argumentTypeClasses: callArgTypeClasses,
|
||||
conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes,
|
||||
});
|
||||
if (narrowed.length === 1) return narrowed[0];
|
||||
}
|
||||
|
|
@ -808,6 +817,7 @@ export function pickImplicitThisOverload(
|
|||
model: SemanticModel,
|
||||
hookCtx?: {
|
||||
readonly conversionRankFn?: ConversionRankFn;
|
||||
readonly conversionOnlyArgTypePrefixes?: readonly string[];
|
||||
readonly constraintCompatibility?: ScopeResolver['constraintCompatibility'];
|
||||
},
|
||||
): SymbolDefinition | undefined {
|
||||
|
|
@ -840,6 +850,7 @@ export function pickImplicitThisOverload(
|
|||
const candidates = narrowOverloadCandidates(overloads, site.arity, site.argumentTypes, {
|
||||
argumentTypeClasses: site.argumentTypeClasses,
|
||||
conversionRankFn: hookCtx?.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: hookCtx?.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: hookCtx?.constraintCompatibility,
|
||||
});
|
||||
if (candidates.length !== 1) return undefined;
|
||||
|
|
|
|||
|
|
@ -51,8 +51,6 @@ 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`:
|
||||
|
|
@ -85,6 +83,10 @@ export interface OverloadNarrowingHookCtx {
|
|||
/** Conversion-rank scoring fallback (step 4b). Engages when the
|
||||
* exact-type filter rejects every candidate. */
|
||||
readonly conversionRankFn?: ConversionRankFn;
|
||||
/** Per-language argument-type prefixes whose conversion-rank failures
|
||||
* should suppress genuinely ambiguous multi-overload sets instead of
|
||||
* falling back to arity-only candidates. */
|
||||
readonly conversionOnlyArgTypePrefixes?: readonly string[];
|
||||
/** Constraint filter (step 4c). Drops candidates whose template
|
||||
* guards (SFINAE `enable_if_t`, C++20 `requires`, future Rust
|
||||
* trait bounds, etc.) provably fail at the call site. Three-valued
|
||||
|
|
@ -178,7 +180,12 @@ export function narrowOverloadCandidates(
|
|||
hookCtx.argumentTypeClasses,
|
||||
);
|
||||
if (ranked.length > 0) result = ranked;
|
||||
else if (hasConversionOnlyArgType(argTypes)) result = [];
|
||||
else if (
|
||||
candidates.length > 1 &&
|
||||
hasConversionOnlyArgType(argTypes, hookCtx.conversionOnlyArgTypePrefixes)
|
||||
) {
|
||||
result = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,10 +232,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 hasConversionOnlyArgType(
|
||||
argTypes: readonly string[],
|
||||
prefixes: readonly string[] | undefined,
|
||||
): boolean {
|
||||
if (prefixes === undefined || prefixes.length === 0) return false;
|
||||
return argTypes.some((type) => prefixes.some((prefix) => type.startsWith(prefix)));
|
||||
}
|
||||
|
||||
function exactTypeSlotMatches(
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ type ReceiverBoundProviderSubset = Pick<
|
|||
| 'resolveReceiverMember'
|
||||
| 'resolveThisViaEnclosingClass'
|
||||
| 'conversionRankFn'
|
||||
| 'conversionOnlyArgTypePrefixes'
|
||||
| 'constraintCompatibility'
|
||||
| 'isStaticOnly'
|
||||
>;
|
||||
|
|
@ -519,6 +520,7 @@ export function emitReceiverBoundCalls(
|
|||
{
|
||||
argumentTypeClasses: site.argumentTypeClasses,
|
||||
conversionRankFn: provider.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: provider.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: provider.constraintCompatibility,
|
||||
},
|
||||
);
|
||||
|
|
@ -1275,6 +1277,7 @@ function pickOverload(
|
|||
const candidates = narrowOverloadCandidates(overloads, site.arity, site.argumentTypes, {
|
||||
argumentTypeClasses: site.argumentTypeClasses,
|
||||
conversionRankFn: provider.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: provider.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: provider.constraintCompatibility,
|
||||
});
|
||||
// When narrowing leaves >1 candidate that share identical normalized
|
||||
|
|
@ -1382,6 +1385,7 @@ function pickFirstNonStaticOnly(
|
|||
const candidates = narrowOverloadCandidates(overloads, site.arity, site.argumentTypes, {
|
||||
argumentTypeClasses: site.argumentTypeClasses,
|
||||
conversionRankFn: provider.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: provider.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: provider.constraintCompatibility,
|
||||
});
|
||||
// Same ambiguity handling as `pickOverload`: when normalization
|
||||
|
|
@ -1427,6 +1431,7 @@ function recordReceiverOverloadSuppression(
|
|||
const candidates = narrowOverloadCandidates(overloads, site.arity, site.argumentTypes, {
|
||||
argumentTypeClasses: site.argumentTypeClasses,
|
||||
conversionRankFn: provider.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: provider.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: provider.constraintCompatibility,
|
||||
});
|
||||
const reason: ResolutionSuppressionReason = isOverloadAmbiguousAfterNormalization(
|
||||
|
|
|
|||
|
|
@ -741,6 +741,7 @@ export function runScopeResolution(
|
|||
isCallableVisibleFromCaller: provider.isCallableVisibleFromCaller,
|
||||
resolveAdlCandidates: provider.resolveAdlCandidates,
|
||||
conversionRankFn: provider.conversionRankFn,
|
||||
conversionOnlyArgTypePrefixes: provider.conversionOnlyArgTypePrefixes,
|
||||
constraintCompatibility: provider.constraintCompatibility,
|
||||
recordResolutionOutcome,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
methodToTypeArgPosition,
|
||||
type TypeArgPosition,
|
||||
} from './shared.js';
|
||||
import { CPP_BRACED_INIT_TYPE_PREFIX } from '../languages/cpp/conversion-rank.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set(['declaration']);
|
||||
|
||||
|
|
@ -479,6 +480,8 @@ const extractForLoopBinding: ForLoopExtractor = (
|
|||
/** Infer the type of a literal AST node for C++ overload disambiguation. */
|
||||
const inferLiteralType: LiteralTypeInferrer = (node) => {
|
||||
switch (node.type) {
|
||||
case 'initializer_list':
|
||||
return inferBracedInitLiteralType(node);
|
||||
case 'number_literal': {
|
||||
const t = node.text;
|
||||
// Float suffixes
|
||||
|
|
@ -505,6 +508,23 @@ const inferLiteralType: LiteralTypeInferrer = (node) => {
|
|||
}
|
||||
};
|
||||
|
||||
function inferBracedInitLiteralType(node: SyntaxNode): string | undefined {
|
||||
const elementTypes: string[] = [];
|
||||
for (const child of node.children) {
|
||||
if (child.type === ',' || child.type === '{' || child.type === '}') continue;
|
||||
const elementType = inferLiteralType(child);
|
||||
if (elementType === undefined || elementType.startsWith(CPP_BRACED_INIT_TYPE_PREFIX)) {
|
||||
return `${CPP_BRACED_INIT_TYPE_PREFIX}unknown:${elementTypes.length + 1}`;
|
||||
}
|
||||
elementTypes.push(elementType);
|
||||
}
|
||||
if (elementTypes.length === 0) return `${CPP_BRACED_INIT_TYPE_PREFIX}unknown:0`;
|
||||
const first = elementTypes[0];
|
||||
return elementTypes.every((type) => type === first)
|
||||
? `${CPP_BRACED_INIT_TYPE_PREFIX}${first}:${elementTypes.length}`
|
||||
: `${CPP_BRACED_INIT_TYPE_PREFIX}unknown:${elementTypes.length}`;
|
||||
}
|
||||
|
||||
/** C++: detect constructor type from smart pointer factory calls (make_shared<Dog>()).
|
||||
* Extracts the template type argument as the constructor type for virtual dispatch. */
|
||||
const detectCppConstructorType: ConstructorTypeDetector = (node, classNames) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace std {
|
||||
|
|
@ -7,6 +8,8 @@ class initializer_list {};
|
|||
|
||||
template <typename T>
|
||||
class vector {};
|
||||
|
||||
class string {};
|
||||
}
|
||||
|
||||
class InitListService {
|
||||
|
|
@ -17,12 +20,21 @@ public:
|
|||
void consumeVector(std::vector<int> values) {}
|
||||
void consumeVector(int value) {}
|
||||
|
||||
void consumeScalarOrVector(std::vector<int> values) {}
|
||||
void consumeScalarOrVector(int value) {}
|
||||
|
||||
void consumeStringVectorMismatch(std::vector<int> values) {}
|
||||
void consumeStringVectorMismatch(std::string 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 consumeSingleMixed(std::initializer_list<int> values) {}
|
||||
void consumeSingleEmpty(std::initializer_list<int> values) {}
|
||||
|
||||
void callHomogeneousInitList() {
|
||||
consume({1, 2, 3});
|
||||
}
|
||||
|
|
@ -31,6 +43,14 @@ public:
|
|||
consumeVector({1, 2, 3});
|
||||
}
|
||||
|
||||
void callSingleElementScalar() {
|
||||
consumeScalarOrVector({5});
|
||||
}
|
||||
|
||||
void callStringVectorMismatch() {
|
||||
consumeStringVectorMismatch({"a", "b"});
|
||||
}
|
||||
|
||||
void callHeterogeneousInitList() {
|
||||
consumeMixed({1, 2.0});
|
||||
}
|
||||
|
|
@ -38,4 +58,12 @@ public:
|
|||
void callEmptyInitList() {
|
||||
consumeEmpty({});
|
||||
}
|
||||
|
||||
void callSingleHeterogeneousInitList() {
|
||||
consumeSingleMixed({1, 2.0});
|
||||
}
|
||||
|
||||
void callSingleEmptyInitList() {
|
||||
consumeSingleEmpty({});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1267,6 +1267,16 @@ describe('C++ braced-init-list overload disambiguation (#1899 A8 conservative)',
|
|||
]);
|
||||
});
|
||||
|
||||
it('prefers a scalar overload for single-element braced-init lists', () => {
|
||||
expect(singleTargetParameterTypes('callSingleElementScalar', 'consumeScalarOrVector')).toEqual([
|
||||
'int',
|
||||
]);
|
||||
});
|
||||
|
||||
it('rejects container overloads whose value type cannot accept the braced elements', () => {
|
||||
expect(callsFrom('callStringVectorMismatch', 'consumeStringVectorMismatch')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('suppresses heterogeneous braced-init lists instead of guessing an element type', () => {
|
||||
expect(callsFrom('callHeterogeneousInitList', 'consumeMixed')).toHaveLength(0);
|
||||
});
|
||||
|
|
@ -1274,6 +1284,14 @@ describe('C++ braced-init-list overload disambiguation (#1899 A8 conservative)',
|
|||
it('suppresses empty braced-init lists instead of guessing an element type', () => {
|
||||
expect(callsFrom('callEmptyInitList', 'consumeEmpty')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('preserves single-overload heterogeneous braced-init recall', () => {
|
||||
expect(callsFrom('callSingleHeterogeneousInitList', 'consumeSingleMixed')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('preserves single-overload empty braced-init recall', () => {
|
||||
expect(callsFrom('callSingleEmptyInitList', 'consumeSingleEmpty')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ same-arity overload cross-file and chain resolution', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import type { ParameterTypeClass, SymbolDefinition } from 'gitnexus-shared';
|
||||
import { cppConversionRank } from '../../../../src/core/ingestion/languages/cpp/conversion-rank.js';
|
||||
import {
|
||||
CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES,
|
||||
cppConversionRank,
|
||||
} from '../../../../src/core/ingestion/languages/cpp/conversion-rank.js';
|
||||
import {
|
||||
clearCppUserDefinedConversions,
|
||||
registerCppUserDefinedConversion,
|
||||
|
|
@ -104,11 +107,23 @@ 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:3', 'std::initializer_list<int>')).toBe(0);
|
||||
expect(cppConversionRank('braced-init:int:3', 'std::vector<int>')).toBe(4);
|
||||
expect(cppConversionRank('braced-init:int', 'int')).toBe(Infinity);
|
||||
});
|
||||
|
||||
it('uses element count and type before ranking container targets', () => {
|
||||
expect(cppConversionRank('braced-init:int:1', 'int')).toBe(0);
|
||||
expect(cppConversionRank('braced-init:int:1', 'std::vector<int>')).toBe(4);
|
||||
expect(cppConversionRank('braced-init:string:2', 'std::vector<int>')).toBe(Infinity);
|
||||
expect(
|
||||
cppConversionRank('braced-init:int:3', 'std::vector', value('braced-init:int:3'), {
|
||||
...value('std::vector'),
|
||||
templateArguments: ['int'],
|
||||
}),
|
||||
).toBe(4);
|
||||
});
|
||||
|
||||
it('suppresses unknown braced-init lists when conversion ranking finds no viable target', () => {
|
||||
const byIntList = mkDef(
|
||||
'f:int-list',
|
||||
|
|
@ -121,13 +136,35 @@ describe('cppConversionRank braced-init-list ranks (#1899)', () => {
|
|||
[value('std::initializer_list')],
|
||||
);
|
||||
|
||||
const result = narrowOverloadCandidates([byIntList, byDoubleList], 1, ['braced-init:unknown'], {
|
||||
argumentTypeClasses: [value('braced-init:unknown')],
|
||||
conversionRankFn: cppConversionRank,
|
||||
});
|
||||
const result = narrowOverloadCandidates(
|
||||
[byIntList, byDoubleList],
|
||||
1,
|
||||
['braced-init:unknown:2'],
|
||||
{
|
||||
argumentTypeClasses: [value('braced-init:unknown:2')],
|
||||
conversionRankFn: cppConversionRank,
|
||||
conversionOnlyArgTypePrefixes: CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('preserves single-candidate recall for unrankable braced-init lists', () => {
|
||||
const byIntList = mkDef(
|
||||
'f:int-list',
|
||||
['std::initializer_list<int>'],
|
||||
[value('std::initializer_list')],
|
||||
);
|
||||
|
||||
const result = narrowOverloadCandidates([byIntList], 1, ['braced-init:unknown:2'], {
|
||||
argumentTypeClasses: [value('braced-init:unknown:2')],
|
||||
conversionRankFn: cppConversionRank,
|
||||
conversionOnlyArgTypePrefixes: CPP_CONVERSION_ONLY_ARG_TYPE_PREFIXES,
|
||||
});
|
||||
|
||||
expect(result.map((d) => d.nodeId)).toEqual(['f:int-list']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('narrowOverloadCandidates with C++ pointer-rank sidecars (#1637)', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue