mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
Merge branch 'main' into main
This commit is contained in:
commit
e5e704740e
14 changed files with 405 additions and 5 deletions
|
|
@ -53,6 +53,10 @@ export interface SymbolDefinition {
|
|||
* `ScopeResolver.constraintCompatibility` hook during overload narrowing.
|
||||
* Absent for symbols that have no constraints (the common case). */
|
||||
templateConstraints?: unknown;
|
||||
/** True when the producing language marked this callable as explicit.
|
||||
* Currently used by C++ overload ranking to exclude explicit constructors
|
||||
* from implicit user-defined conversion candidates. */
|
||||
isExplicit?: boolean;
|
||||
/** Links Method/Constructor/Property to owning Class/Struct/Trait nodeId */
|
||||
ownerId?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,13 @@ export function emitCppScopeCaptures(
|
|||
JSON.stringify(arity.parameterTypeClasses),
|
||||
);
|
||||
}
|
||||
if (hasExplicitSpecifier(fnNode)) {
|
||||
grouped['@declaration.is-explicit'] = syntheticCapture(
|
||||
'@declaration.is-explicit',
|
||||
fnNode,
|
||||
'true',
|
||||
);
|
||||
}
|
||||
|
||||
// Detect static storage class (file-local linkage)
|
||||
if (hasStaticStorageClass(fnNode)) {
|
||||
|
|
@ -1542,6 +1549,20 @@ function extractDeclaratorLeafName(node: SyntaxNode): string | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a C++ declaration has an `explicit` specifier. Tree-sitter-cpp
|
||||
* exposes `explicit` as a direct keyword child on constructor declarations in
|
||||
* current grammar builds; the bounded text prefix keeps this resilient across
|
||||
* small grammar shape differences without scanning whole function bodies.
|
||||
*/
|
||||
function hasExplicitSpecifier(node: SyntaxNode): boolean {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child !== null && child.text === 'explicit') return true;
|
||||
}
|
||||
return /\bexplicit\b/.test(node.text.slice(0, 128));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a C++ function_definition or declaration has `static` storage class.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
* - rank 2: standard conversion (arithmetic, nullptr -> T*, T* -> bool,
|
||||
* T* -> void*)
|
||||
* - rank 3: nullptr -> bool (kept worse than nullptr -> T*)
|
||||
* - rank 4: ellipsis conversion (worst viable)
|
||||
* - rank 4: user-defined conversion (one-step, conservative)
|
||||
* - rank 5: ellipsis conversion (worst viable)
|
||||
* - Infinity: mismatch (string -> int, user types, unsupported shapes)
|
||||
*
|
||||
* This function is intentionally C++-specific. Other languages may define
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
*/
|
||||
|
||||
import type { ParameterTypeClass } from 'gitnexus-shared';
|
||||
import { hasCppUserDefinedConversion } from './user-defined-conversions.js';
|
||||
|
||||
/** Set of normalized arithmetic types that support implicit conversion. */
|
||||
const ARITHMETIC = new Set(['int', 'double', 'char', 'bool']);
|
||||
|
|
@ -34,7 +36,8 @@ const INTEGRAL_PROMOTION = new Map([
|
|||
* Return the conversion rank from `argType` to `paramType`.
|
||||
*
|
||||
* @returns 0 for exact match, 1 for integral promotion, 2 for standard
|
||||
* conversion, 3 for nullptr -> bool, 4 for ellipsis, Infinity
|
||||
* conversion, 3 for nullptr -> bool, 4 for user-defined conversion,
|
||||
* 5 for ellipsis, Infinity
|
||||
* for mismatch.
|
||||
*/
|
||||
export function cppConversionRank(
|
||||
|
|
@ -46,13 +49,14 @@ export function cppConversionRank(
|
|||
if (argType === paramType) {
|
||||
return exactShapeCompatible(argTypeClass, paramTypeClass) ? 0 : Infinity;
|
||||
}
|
||||
if (paramType === '...') return 4;
|
||||
if (paramType === '...') return 5;
|
||||
if (INTEGRAL_PROMOTION.get(argType) === paramType) return 1;
|
||||
if (ARITHMETIC.has(argType) && ARITHMETIC.has(paramType)) return 2;
|
||||
if (argType === 'null' && isPointer(paramTypeClass)) return 2;
|
||||
if (argType === 'null' && paramType === 'bool') return 3;
|
||||
if (isPointer(argTypeClass) && paramType === 'bool') return 2;
|
||||
if (isPointer(argTypeClass) && isPointer(paramTypeClass) && paramType === 'void') return 2;
|
||||
if (hasCppUserDefinedConversion(argType, paramType)) return 4;
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -225,6 +225,12 @@ const CPP_SCOPE_QUERY = `
|
|||
declarator: (function_declarator
|
||||
declarator: (field_identifier) @declaration.name))) @declaration.method
|
||||
|
||||
;; Constructor prototype in class body: User(int id);
|
||||
(field_declaration_list
|
||||
(declaration
|
||||
declarator: (function_declarator
|
||||
declarator: (identifier) @declaration.name)) @declaration.method)
|
||||
|
||||
;; Method prototype with reference return: User& getRef();
|
||||
(field_declaration
|
||||
declarator: (reference_declarator
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ import {
|
|||
} from './inline-namespaces.js';
|
||||
import { populateCppRangeBindings } from './range-bindings.js';
|
||||
import { cppConstraintCompatibility } from './constraint-filter.js';
|
||||
import {
|
||||
clearCppUserDefinedConversions,
|
||||
populateCppUserDefinedConversions,
|
||||
} from './user-defined-conversions.js';
|
||||
|
||||
/**
|
||||
* C++ `ScopeResolver` registered in `SCOPE_RESOLVERS` and consumed by
|
||||
|
|
@ -61,6 +65,7 @@ export const cppScopeResolver: ScopeResolver = {
|
|||
clearCppDependentBases();
|
||||
clearCppAdlState();
|
||||
clearCppInlineNamespaces();
|
||||
clearCppUserDefinedConversions();
|
||||
return scanCppHeaderFiles(repoPath);
|
||||
},
|
||||
|
||||
|
|
@ -110,6 +115,10 @@ export const cppScopeResolver: ScopeResolver = {
|
|||
// by ADL (U2 of plan 2026-05-13-001) to identify each argument type's
|
||||
// associated namespace for Koenig lookup.
|
||||
populateCppAssociatedNamespaces(parsed);
|
||||
// Build conservative one-step user-defined conversion facts for
|
||||
// overload ranking (#1631): implicit converting constructors only,
|
||||
// with no chaining or conversion-operator handling.
|
||||
populateCppUserDefinedConversions(parsed);
|
||||
},
|
||||
|
||||
// Resolve recorded template-class → dependent-base simple names to
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
import type { ParsedFile, SymbolDefinition } from 'gitnexus-shared';
|
||||
import type { ScopeId } from 'gitnexus-shared';
|
||||
import { normalizeCppParamType } from './arity-metadata.js';
|
||||
|
||||
const userDefinedConversions = new Set<string>();
|
||||
const pendingUserDefinedConversions: PendingUserDefinedConversion[] = [];
|
||||
const classIdentitiesBySimpleName = new Map<string, Set<string>>();
|
||||
|
||||
interface PendingUserDefinedConversion {
|
||||
readonly argType: string;
|
||||
readonly paramType: string;
|
||||
readonly ownerClassName: string;
|
||||
}
|
||||
|
||||
export function clearCppUserDefinedConversions(): void {
|
||||
userDefinedConversions.clear();
|
||||
pendingUserDefinedConversions.length = 0;
|
||||
classIdentitiesBySimpleName.clear();
|
||||
}
|
||||
|
||||
export function hasCppUserDefinedConversion(argType: string, paramType: string): boolean {
|
||||
return userDefinedConversions.has(conversionKey(argType, paramType));
|
||||
}
|
||||
|
||||
export function populateCppUserDefinedConversions(parsed: ParsedFile): void {
|
||||
const scopesById = new Map<ScopeId, (typeof parsed.scopes)[number]>();
|
||||
for (const scope of parsed.scopes) scopesById.set(scope.id, scope);
|
||||
|
||||
for (const classScope of parsed.scopes) {
|
||||
if (classScope.kind !== 'Class') continue;
|
||||
const classDef = classScope.ownedDefs.find(isClassLike);
|
||||
if (classDef !== undefined) recordClassIdentity(classDef);
|
||||
}
|
||||
|
||||
for (const classScope of parsed.scopes) {
|
||||
if (classScope.kind !== 'Class') continue;
|
||||
const classDef = classScope.ownedDefs.find(isClassLike);
|
||||
if (classDef === undefined) continue;
|
||||
const className = normalizedSimpleName(classDef);
|
||||
if (className === '') continue;
|
||||
|
||||
const methodDefs = collectClassMethodDefs(classScope.id, parsed, scopesById);
|
||||
for (const def of methodDefs) {
|
||||
const simpleName = simpleNameOf(def);
|
||||
if (simpleName === className && def.parameterTypes?.length === 1) {
|
||||
if (def.isExplicit === true) continue;
|
||||
registerPendingCppUserDefinedConversion(def.parameterTypes[0], className, className);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rebuildCppUserDefinedConversions();
|
||||
}
|
||||
|
||||
export function registerCppUserDefinedConversion(argType: string, paramType: string): void {
|
||||
if (argType === '' || paramType === '') return;
|
||||
if (argType === paramType) return;
|
||||
userDefinedConversions.add(conversionKey(argType, paramType));
|
||||
}
|
||||
|
||||
function collectClassMethodDefs(
|
||||
classScopeId: ScopeId,
|
||||
parsed: ParsedFile,
|
||||
scopesById: ReadonlyMap<ScopeId, (typeof parsed.scopes)[number]>,
|
||||
): SymbolDefinition[] {
|
||||
const methods: SymbolDefinition[] = [];
|
||||
const classScope = scopesById.get(classScopeId);
|
||||
if (classScope === undefined) return methods;
|
||||
|
||||
for (const def of classScope.ownedDefs) {
|
||||
if (isCallableMember(def)) methods.push(def);
|
||||
}
|
||||
for (const scope of parsed.scopes) {
|
||||
if (scope.parent !== classScopeId) continue;
|
||||
if (scope.kind === 'Class') continue;
|
||||
for (const def of scope.ownedDefs) {
|
||||
if (isCallableMember(def)) methods.push(def);
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
function conversionKey(argType: string, paramType: string): string {
|
||||
return `${argType}\0${paramType}`;
|
||||
}
|
||||
|
||||
function registerPendingCppUserDefinedConversion(
|
||||
argType: string,
|
||||
paramType: string,
|
||||
ownerClassName: string,
|
||||
): void {
|
||||
if (argType === '' || paramType === '') return;
|
||||
if (argType === paramType) return;
|
||||
pendingUserDefinedConversions.push({ argType, paramType, ownerClassName });
|
||||
}
|
||||
|
||||
function rebuildCppUserDefinedConversions(): void {
|
||||
userDefinedConversions.clear();
|
||||
for (const conversion of pendingUserDefinedConversions) {
|
||||
if (isAmbiguousClassName(conversion.ownerClassName)) continue;
|
||||
userDefinedConversions.add(conversionKey(conversion.argType, conversion.paramType));
|
||||
}
|
||||
}
|
||||
|
||||
function recordClassIdentity(def: SymbolDefinition): void {
|
||||
const simpleName = normalizedSimpleName(def);
|
||||
if (simpleName === '') return;
|
||||
const identities = classIdentitiesBySimpleName.get(simpleName) ?? new Set<string>();
|
||||
identities.add(normalizedQualifiedClassName(def));
|
||||
classIdentitiesBySimpleName.set(simpleName, identities);
|
||||
}
|
||||
|
||||
function isAmbiguousClassName(simpleName: string): boolean {
|
||||
return (classIdentitiesBySimpleName.get(simpleName)?.size ?? 0) > 1;
|
||||
}
|
||||
|
||||
function normalizedQualifiedClassName(def: SymbolDefinition): string {
|
||||
const qualifiedName = def.qualifiedName ?? simpleNameOf(def);
|
||||
if (qualifiedName === '' || !qualifiedName.includes('.')) return `${def.filePath}:${def.nodeId}`;
|
||||
return qualifiedName
|
||||
.split('.')
|
||||
.map((part) => normalizeCppParamType(part))
|
||||
.join('.');
|
||||
}
|
||||
|
||||
function normalizedSimpleName(def: SymbolDefinition): string {
|
||||
return normalizeCppParamType(simpleNameOf(def));
|
||||
}
|
||||
|
||||
function simpleNameOf(def: SymbolDefinition): string {
|
||||
return def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
||||
}
|
||||
|
||||
function isClassLike(def: SymbolDefinition): boolean {
|
||||
return def.type === 'Class' || def.type === 'Struct' || def.type === 'Interface';
|
||||
}
|
||||
|
||||
function isCallableMember(def: SymbolDefinition): boolean {
|
||||
return def.type === 'Method' || def.type === 'Constructor';
|
||||
}
|
||||
|
|
@ -574,6 +574,7 @@ function buildDefFromDeclarationMatch(
|
|||
const declaredType = match['@declaration.field-type']?.text;
|
||||
const returnType = match['@declaration.return-type']?.text;
|
||||
const templateConstraints = parseJsonCapture(match['@declaration.template-constraints']);
|
||||
const isExplicit = parseBooleanCapture(match['@declaration.is-explicit']);
|
||||
|
||||
return {
|
||||
nodeId: makeDefId(filePath, anchor.range, type, nameCap.text),
|
||||
|
|
@ -588,6 +589,7 @@ function buildDefFromDeclarationMatch(
|
|||
...(returnType !== undefined ? { returnType } : {}),
|
||||
...(templateArguments !== undefined ? { templateArguments } : {}),
|
||||
...(templateConstraints !== undefined ? { templateConstraints } : {}),
|
||||
...(isExplicit === true ? { isExplicit: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -610,6 +612,13 @@ function parseIntCapture(cap: { readonly text: string } | undefined): number | u
|
|||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
function parseBooleanCapture(cap: { readonly text: string } | undefined): boolean | undefined {
|
||||
if (cap === undefined) return undefined;
|
||||
if (cap.text === 'true') return true;
|
||||
if (cap.text === 'false') return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseJsonParameterTypeClassesCapture(
|
||||
cap: { readonly text: string } | undefined,
|
||||
): ParameterTypeClass[] | undefined {
|
||||
|
|
@ -1079,6 +1088,7 @@ const KNOWN_SUB_TAGS: ReadonlySet<string> = new Set<string>([
|
|||
'@declaration.parameter-types',
|
||||
'@declaration.parameter-type-classes',
|
||||
'@declaration.template-constraints',
|
||||
'@declaration.is-explicit',
|
||||
]);
|
||||
|
||||
/**
|
||||
|
|
|
|||
15
gitnexus/test/fixtures/lang-resolution/cpp-overload-udc-namespace-collision/lib.cpp
vendored
Normal file
15
gitnexus/test/fixtures/lang-resolution/cpp-overload-udc-namespace-collision/lib.cpp
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#include "lib.h"
|
||||
|
||||
namespace alpha {
|
||||
|
||||
Other::Other(int value) {}
|
||||
void Service::f(Token value) {}
|
||||
void Service::f(Other value) {}
|
||||
|
||||
} // namespace alpha
|
||||
|
||||
namespace beta {
|
||||
|
||||
Token::Token(int value) {}
|
||||
|
||||
} // namespace beta
|
||||
31
gitnexus/test/fixtures/lang-resolution/cpp-overload-udc-namespace-collision/lib.h
vendored
Normal file
31
gitnexus/test/fixtures/lang-resolution/cpp-overload-udc-namespace-collision/lib.h
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
namespace alpha {
|
||||
|
||||
class Token {};
|
||||
|
||||
class Other {
|
||||
public:
|
||||
Other(int value);
|
||||
};
|
||||
|
||||
class Service {
|
||||
public:
|
||||
void f(Token value);
|
||||
void f(Other value);
|
||||
|
||||
void run() {
|
||||
f(42);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace alpha
|
||||
|
||||
namespace beta {
|
||||
|
||||
class Token {
|
||||
public:
|
||||
Token(int value);
|
||||
};
|
||||
|
||||
} // namespace beta
|
||||
14
gitnexus/test/fixtures/lang-resolution/cpp-overload-user-defined-conversion/lib.cpp
vendored
Normal file
14
gitnexus/test/fixtures/lang-resolution/cpp-overload-user-defined-conversion/lib.cpp
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#include "lib.h"
|
||||
|
||||
Wrap::Wrap(int value) {}
|
||||
WrapA::WrapA(int value) {}
|
||||
WrapB::WrapB(int value) {}
|
||||
ExplicitWrap::ExplicitWrap(int value) {}
|
||||
|
||||
void Service::f(Wrap value) {}
|
||||
void Service::f(double value) {}
|
||||
void Service::g(Wrap value) {}
|
||||
void Service::h(WrapA value) {}
|
||||
void Service::h(WrapB value) {}
|
||||
void Service::e(Wrap value) {}
|
||||
void Service::e(ExplicitWrap value) {}
|
||||
39
gitnexus/test/fixtures/lang-resolution/cpp-overload-user-defined-conversion/lib.h
vendored
Normal file
39
gitnexus/test/fixtures/lang-resolution/cpp-overload-user-defined-conversion/lib.h
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
class Wrap {
|
||||
public:
|
||||
Wrap(int value);
|
||||
};
|
||||
|
||||
class WrapA {
|
||||
public:
|
||||
WrapA(int value);
|
||||
};
|
||||
|
||||
class WrapB {
|
||||
public:
|
||||
WrapB(int value);
|
||||
};
|
||||
|
||||
class ExplicitWrap {
|
||||
public:
|
||||
explicit ExplicitWrap(int value);
|
||||
};
|
||||
|
||||
class Service {
|
||||
public:
|
||||
void f(Wrap value);
|
||||
void f(double value);
|
||||
void g(Wrap value);
|
||||
void h(WrapA value);
|
||||
void h(WrapB value);
|
||||
void e(Wrap value);
|
||||
void e(ExplicitWrap value);
|
||||
|
||||
void run() {
|
||||
f(42);
|
||||
g(42);
|
||||
h(42);
|
||||
e(42);
|
||||
}
|
||||
};
|
||||
|
|
@ -1983,6 +1983,71 @@ describe('C++ overload resolution — pointer/nullptr/ellipsis ranks (#1637)', (
|
|||
});
|
||||
});
|
||||
|
||||
describe('C++ overload resolution — user-defined conversion rank (#1631)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cpp-overload-user-defined-conversion'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('f(42) resolves to f(double) because standard conversion beats constructor UDC', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const fCalls = calls.filter((c) => c.source === 'run' && c.target === 'f');
|
||||
|
||||
expect(fCalls.length).toBe(1);
|
||||
const target = result.graph.getNode(fCalls[0].rel.targetId);
|
||||
expect(target?.properties.parameterTypes).toEqual(['double']);
|
||||
});
|
||||
|
||||
it('g(42) keeps a single constructor UDC viable when no standard conversion overload exists', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const gCalls = calls.filter((c) => c.source === 'run' && c.target === 'g');
|
||||
|
||||
expect(gCalls.length).toBe(1);
|
||||
const target = result.graph.getNode(gCalls[0].rel.targetId);
|
||||
expect(target?.properties.parameterTypes).toEqual(['Wrap']);
|
||||
});
|
||||
|
||||
it('h(42) emits zero CALLS edges when two single-step constructor UDCs tie', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const hCalls = calls.filter((c) => c.source === 'run' && c.target === 'h');
|
||||
|
||||
expect(hCalls.length).toBe(0);
|
||||
});
|
||||
|
||||
it('e(42) ignores the explicit-constructor overload and keeps the implicit UDC viable', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const eCalls = calls.filter((c) => c.source === 'run' && c.target === 'e');
|
||||
|
||||
expect(eCalls.length).toBe(1);
|
||||
const target = result.graph.getNode(eCalls[0].rel.targetId);
|
||||
expect(target?.properties.parameterTypes).toEqual(['Wrap']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('C++ overload resolution — UDC namespace collision guard (#1631)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'cpp-overload-udc-namespace-collision'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('does not let beta::Token(int) tie the valid alpha::Other(int) conversion', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const fCalls = calls.filter((c) => c.source === 'run' && c.target === 'f');
|
||||
|
||||
expect(fCalls.length).toBe(1);
|
||||
const target = result.graph.getNode(fCalls[0].rel.targetId);
|
||||
expect(target?.properties.parameterTypes).toEqual(['Other']);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// U3: anonymous-namespace symbols MUST NOT leak across translation units
|
||||
// (full-pipeline integration test; unit-level coverage exists separately)
|
||||
|
|
|
|||
|
|
@ -313,6 +313,14 @@ const LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES: Readonly<Record<string, Readonly
|
|||
'g(1, 2) resolves to fixed-arity g(int, int), not g(int, ...)',
|
||||
"h(1, 'a') resolves to h(int, double), not h(int, ...)",
|
||||
'k(1, 2, 3) keeps the ellipsis overload viable when it is the only match',
|
||||
// User-defined conversion ranking (#1631) builds on the C++
|
||||
// conversion-rank hook and the registry-primary C++ owner sidecars.
|
||||
// Legacy DAG has no user-defined-conversion sidecar or ranking path.
|
||||
'f(42) resolves to f(double) because standard conversion beats constructor UDC',
|
||||
'g(42) keeps a single constructor UDC viable when no standard conversion overload exists',
|
||||
'h(42) emits zero CALLS edges when two single-step constructor UDCs tie',
|
||||
'e(42) ignores the explicit-constructor overload and keeps the implicit UDC viable',
|
||||
'does not let beta::Token(int) tie the valid alpha::Other(int) conversion',
|
||||
// The legacy DAG path lacks the SFINAE / `requires`-clause aware
|
||||
// overload filter (issue #1579). The two `process<T>` overloads
|
||||
// guarded by mutually-exclusive `enable_if_t` predicates collapse
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
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 {
|
||||
clearCppUserDefinedConversions,
|
||||
registerCppUserDefinedConversion,
|
||||
} from '../../../../src/core/ingestion/languages/cpp/user-defined-conversions.js';
|
||||
import { narrowOverloadCandidates } from '../../../../src/core/ingestion/scope-resolution/passes/overload-narrowing.js';
|
||||
|
||||
const value = (base: string): ParameterTypeClass => ({
|
||||
|
|
@ -40,6 +44,10 @@ const mkDef = (
|
|||
parameterTypeClasses: [...parameterTypeClasses],
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearCppUserDefinedConversions();
|
||||
});
|
||||
|
||||
describe('cppConversionRank pointer/nullptr/ellipsis ranks (#1637)', () => {
|
||||
it('ranks nullptr -> T* ahead of nullptr -> bool', () => {
|
||||
expect(cppConversionRank('null', 'int', value('null'), pointer('int'))).toBe(2);
|
||||
|
|
@ -57,7 +65,33 @@ describe('cppConversionRank pointer/nullptr/ellipsis ranks (#1637)', () => {
|
|||
});
|
||||
|
||||
it('ranks ellipsis as the worst viable conversion', () => {
|
||||
expect(cppConversionRank('int', '...', value('int'), ellipsis())).toBe(4);
|
||||
expect(cppConversionRank('int', '...', value('int'), ellipsis())).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cppConversionRank user-defined conversion ranks (#1631)', () => {
|
||||
it('ranks registered one-step user-defined conversions after standard conversions', () => {
|
||||
clearCppUserDefinedConversions();
|
||||
registerCppUserDefinedConversion('int', 'Wrap');
|
||||
|
||||
expect(cppConversionRank('int', 'Wrap', value('int'), value('Wrap'))).toBe(4);
|
||||
expect(cppConversionRank('int', 'double', value('int'), value('double'))).toBe(2);
|
||||
});
|
||||
|
||||
it('keeps tied user-defined conversion candidates ambiguous', () => {
|
||||
clearCppUserDefinedConversions();
|
||||
registerCppUserDefinedConversion('int', 'WrapA');
|
||||
registerCppUserDefinedConversion('int', 'WrapB');
|
||||
|
||||
const byWrapA = mkDef('h:WrapA', ['WrapA'], [value('WrapA')]);
|
||||
const byWrapB = mkDef('h:WrapB', ['WrapB'], [value('WrapB')]);
|
||||
|
||||
const result = narrowOverloadCandidates([byWrapA, byWrapB], 1, ['int'], {
|
||||
argumentTypeClasses: [value('int')],
|
||||
conversionRankFn: cppConversionRank,
|
||||
});
|
||||
|
||||
expect(result.map((d) => d.nodeId)).toEqual(['h:WrapA', 'h:WrapB']);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue