Skip to content

Commit 02440d1

Browse files
Fix inlay hints for rest tuple tail mapping
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent b19a9da commit 02440d1

File tree

1 file changed

+107
-48
lines changed

1 file changed

+107
-48
lines changed

src/services/inlayHints.ts

Lines changed: 107 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,14 @@ import {
109109
PrefixUnaryExpression,
110110
PropertyDeclaration,
111111
QuotePreference,
112-
SignatureDeclarationBase,
113-
skipParentheses,
114-
some,
115-
Symbol,
116-
SymbolFlags,
117-
SyntaxKind,
112+
SignatureDeclarationBase,
113+
Signature,
114+
signatureHasRestParameter,
115+
skipParentheses,
116+
some,
117+
Symbol,
118+
SymbolFlags,
119+
SyntaxKind,
118120
TemplateLiteralLikeNode,
119121
textSpanIntersectsWith,
120122
tokenToString,
@@ -292,44 +294,43 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
292294
}
293295
}
294296

295-
function visitCallOrNewExpression(expr: CallExpression | NewExpression) {
296-
const args = expr.arguments;
297-
if (!args || !args.length) {
298-
return;
299-
}
300-
301-
const signature = checker.getResolvedSignature(expr);
302-
if (signature === undefined) return;
303-
304-
let signatureParamPos = 0;
305-
for (const originalArg of args) {
306-
const arg = skipParentheses(originalArg);
307-
if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) {
308-
signatureParamPos++;
309-
continue;
310-
}
311-
312-
let spreadArgs = 0;
313-
if (isSpreadElement(arg)) {
314-
const spreadType = checker.getTypeAtLocation(arg.expression);
315-
if (checker.isTupleType(spreadType)) {
316-
const { elementFlags, fixedLength } = (spreadType as TupleTypeReference).target;
317-
if (fixedLength === 0) {
318-
continue;
319-
}
320-
const firstOptionalIndex = findIndex(elementFlags, f => !(f & ElementFlags.Required));
321-
const requiredArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
322-
if (requiredArgs > 0) {
323-
spreadArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
324-
}
325-
}
326-
}
327-
328-
const identifierInfo = checker.getParameterIdentifierInfoAtPosition(signature, signatureParamPos);
329-
signatureParamPos = signatureParamPos + (spreadArgs || 1);
330-
if (identifierInfo) {
331-
const { parameter, parameterName, isRestParameter: isFirstVariadicArgument } = identifierInfo;
332-
const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName);
297+
function visitCallOrNewExpression(expr: CallExpression | NewExpression) {
298+
const args = expr.arguments;
299+
if (!args || !args.length) {
300+
return;
301+
}
302+
303+
const signature = checker.getResolvedSignature(expr);
304+
if (signature === undefined) return;
305+
306+
const argumentSpans = args.map(arg => getArgumentSpan(skipParentheses(arg)));
307+
let totalArgumentPositions = 0;
308+
for (const span of argumentSpans) {
309+
totalArgumentPositions += span;
310+
}
311+
312+
const nonRestParamCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0);
313+
const restTupleInfo = getRestTupleInfo(signature, nonRestParamCount, totalArgumentPositions);
314+
315+
let signatureParamPos = 0;
316+
for (let argIndex = 0; argIndex < args.length; argIndex++) {
317+
const originalArg = args[argIndex];
318+
const arg = skipParentheses(originalArg);
319+
const spreadArgs = argumentSpans[argIndex];
320+
if (spreadArgs === 0) {
321+
continue;
322+
}
323+
if (shouldShowLiteralParameterNameHintsOnly(preferences) && !isHintableLiteral(arg)) {
324+
signatureParamPos += spreadArgs;
325+
continue;
326+
}
327+
328+
const parameterPos = getAdjustedParameterPosition(signatureParamPos, restTupleInfo);
329+
const identifierInfo = checker.getParameterIdentifierInfoAtPosition(signature, parameterPos);
330+
signatureParamPos = signatureParamPos + (spreadArgs || 1);
331+
if (identifierInfo) {
332+
const { parameter, parameterName, isRestParameter: isFirstVariadicArgument } = identifierInfo;
333+
const isParameterNameNotSameAsArgument = preferences.includeInlayParameterNameHintsWhenArgumentMatchesName || !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName);
333334
if (!isParameterNameNotSameAsArgument && !isFirstVariadicArgument) {
334335
continue;
335336
}
@@ -339,10 +340,68 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
339340
continue;
340341
}
341342

342-
addParameterHints(name, parameter, originalArg.getStart(), isFirstVariadicArgument);
343-
}
344-
}
345-
}
343+
addParameterHints(name, parameter, originalArg.getStart(), isFirstVariadicArgument);
344+
}
345+
}
346+
347+
function getArgumentSpan(arg: Expression) {
348+
if (isSpreadElement(arg)) {
349+
const spreadType = checker.getTypeAtLocation(arg.expression);
350+
if (checker.isTupleType(spreadType)) {
351+
const { elementFlags, fixedLength } = (spreadType as TupleTypeReference).target;
352+
if (fixedLength === 0) {
353+
return 0;
354+
}
355+
const firstOptionalIndex = findIndex(elementFlags, f => !(f & ElementFlags.Required));
356+
const requiredArgs = firstOptionalIndex < 0 ? fixedLength : firstOptionalIndex;
357+
if (requiredArgs > 0) {
358+
return requiredArgs;
359+
}
360+
}
361+
}
362+
return 1;
363+
}
364+
365+
function getRestTupleInfo(signature: Signature, paramCount: number, totalPositions: number) {
366+
if (!signatureHasRestParameter(signature)) {
367+
return undefined;
368+
}
369+
const restParameter = signature.parameters[paramCount];
370+
if (!restParameter) {
371+
return undefined;
372+
}
373+
const restType = checker.getTypeOfSymbol(restParameter);
374+
if (!checker.isTupleType(restType)) {
375+
return undefined;
376+
}
377+
const elementFlags = (restType as TupleTypeReference).target.elementFlags;
378+
const restStartIndex = findIndex(elementFlags, f => !!(f & ElementFlags.Variable));
379+
if (restStartIndex < 0) {
380+
return undefined;
381+
}
382+
const restTailCount = elementFlags.length - restStartIndex - 1;
383+
const restPositionsTotal = Math.max(0, totalPositions - paramCount);
384+
return { restStartIndex, restTailCount, restPositionsTotal, paramCount };
385+
}
386+
387+
function getAdjustedParameterPosition(signatureParamPos: number, restInfo?: { restStartIndex: number; restTailCount: number; restPositionsTotal: number; paramCount: number; }) {
388+
if (!restInfo || signatureParamPos < restInfo.paramCount) {
389+
return signatureParamPos;
390+
}
391+
const restPosition = signatureParamPos - restInfo.paramCount;
392+
if (restPosition < restInfo.restStartIndex) {
393+
return signatureParamPos;
394+
}
395+
if (restInfo.restTailCount > 0 && restInfo.restPositionsTotal >= restInfo.restTailCount) {
396+
const tailStart = restInfo.restPositionsTotal - restInfo.restTailCount;
397+
if (restPosition >= tailStart) {
398+
const tailIndex = restPosition - tailStart;
399+
return restInfo.paramCount + restInfo.restStartIndex + 1 + tailIndex;
400+
}
401+
}
402+
return restInfo.paramCount + restInfo.restStartIndex;
403+
}
404+
}
346405

347406
function identifierOrAccessExpressionPostfixMatchesParameterName(expr: Expression, parameterName: __String) {
348407
if (isIdentifier(expr)) {

0 commit comments

Comments
 (0)