Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7004,11 +7004,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
for (let i = 0; i < tupleConstituentNodes.length; i++) {
const flags = (type.target as TupleType).elementFlags[i];
const labeledElementDeclaration = labeledElementDeclarations?.[i];
const label = labeledElementDeclaration && getTupleElementLabel(type as TupleTypeReference, labeledElementDeclaration);

if (labeledElementDeclaration) {
if (label) {
tupleConstituentNodes[i] = factory.createNamedTupleMember(
flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined,
factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel(labeledElementDeclaration))),
factory.createIdentifier(unescapeLeadingUnderscores(label)),
flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined,
flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) :
tupleConstituentNodes[i],
Expand Down Expand Up @@ -12954,7 +12955,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) {
const associatedNamesMap = new Map<__String, number>();
return map(type.target.labeledElementDeclarations, (labeledElement, i) => {
const name = getTupleElementLabel(labeledElement, i, restName);
const name = getTupleElementLabel(type, labeledElement, i, restName);
const prevCounter = associatedNamesMap.get(name);
if (prevCounter === undefined) {
associatedNamesMap.set(name, 1);
Expand Down Expand Up @@ -35297,13 +35298,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type;
}

function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String;
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String;
function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) {
function getTupleElementLabel(tupleType: TupleTypeReference, d: ParameterDeclaration | NamedTupleMember): __String | undefined;
function getTupleElementLabel(tupleType: TupleTypeReference, d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String;
function getTupleElementLabel(tupleType: TupleTypeReference, d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) {
if (!d) {
return `${restParameterName}_${index}` as __String;
}
Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names
Debug.assert(!isBindingPattern(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names with them
if (d.name.kind === SyntaxKind.TemplateLiteralType) {
const label = instantiateType(getTypeFromTypeNode(d.name), tupleType.mapper);
return label.flags & TypeFlags.StringLiteral
? escapeLeadingUnderscores((label as StringLiteralType).value)
: typeof index === "number"
? `${restParameterName}_${index}` as __String
: undefined;
}
if (d.name.kind === SyntaxKind.NoSubstitutionTemplateLiteral) {
return escapeLeadingUnderscores(d.name.text);
}
return d.name.escapedText;
}

Expand All @@ -35315,9 +35327,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const restParameter = signature.parameters[paramCount] || unknownSymbol;
const restType = overrideRestType || getTypeOfSymbol(restParameter);
if (isTupleType(restType)) {
const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations;
const associatedNames = restType.target.labeledElementDeclarations;
const index = pos - paramCount;
return getTupleElementLabel(associatedNames?.[index], index, restParameter.escapedName);
return getTupleElementLabel(restType, associatedNames?.[index], index, restParameter.escapedName);
}
return restParameter.escapedName;
}
Expand Down Expand Up @@ -35345,13 +35357,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

const restType = getTypeOfSymbol(restParameter);
if (isTupleType(restType)) {
const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations;
const associatedNames = restType.target.labeledElementDeclarations;
const index = pos - paramCount;
const associatedName = associatedNames?.[index];
const isRestTupleElement = !!associatedName?.dotDotDotToken;

if (associatedName) {
Debug.assert(isIdentifier(associatedName.name));
// TODO: figure out the non-identifier case here
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MariaSolOs what kind of inlay hint interactivity is expected out of a parameter name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected behaviour is to be able to click on an argument of a function call and have that be a "go to definition" to the respective parameter in the function signature.

if (associatedName && isIdentifier(associatedName.name)) {
return { parameter: associatedName.name, parameterName: associatedName.name.escapedText, isRestParameter: isRestTupleElement };
}

Expand Down Expand Up @@ -35380,7 +35392,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const restParameter = signature.parameters[paramCount] || unknownSymbol;
const restType = getTypeOfSymbol(restParameter);
if (isTupleType(restType)) {
const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations;
const associatedNames = restType.target.labeledElementDeclarations;
const index = pos - paramCount;
return associatedNames && associatedNames[index];
}
Expand Down Expand Up @@ -39580,6 +39592,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (node.type.kind === SyntaxKind.RestType) {
grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_rest_with_a_before_the_name_rather_than_before_the_type);
}
if (node.name.kind === SyntaxKind.TemplateLiteralType) {
checkSourceElement(node.name);
}
checkSourceElement(node.type);
getTypeFromTypeNode(node);
}
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ import {
NamedImportBindings,
NamedImports,
NamedTupleMember,
NamedTupleMemberName,
NamespaceExport,
NamespaceExportDeclaration,
NamespaceImport,
Expand Down Expand Up @@ -2499,7 +2500,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) {
function createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: NamedTupleMemberName, questionToken: QuestionToken | undefined, type: TypeNode) {
const node = createBaseDeclaration<NamedTupleMember>(SyntaxKind.NamedTupleMember);
node.dotDotDotToken = dotDotDotToken;
node.name = name;
Expand All @@ -2512,7 +2513,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) {
function updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: NamedTupleMemberName, questionToken: QuestionToken | undefined, type: TypeNode) {
return node.dotDotDotToken !== dotDotDotToken
|| node.name !== name
|| node.questionToken !== questionToken
Expand Down
23 changes: 18 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4404,23 +4404,36 @@ namespace Parser {
return type;
}

function isNextTokenColonOrQuestionColon() {
return nextToken() === SyntaxKind.ColonToken || (token() === SyntaxKind.QuestionToken && nextToken() === SyntaxKind.ColonToken);
function isTokenColonOrQuestionColon() {
return token() === SyntaxKind.ColonToken || (token() === SyntaxKind.QuestionToken && nextToken() === SyntaxKind.ColonToken);
}

function isTupleElementName() {
if (token() === SyntaxKind.DotDotDotToken) {
return tokenIsIdentifierOrKeyword(nextToken()) && isNextTokenColonOrQuestionColon();
nextToken();
}
if (token() === SyntaxKind.TemplateHead) {
parseTemplateType();
return isTokenColonOrQuestionColon();
}
if (tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.NoSubstitutionTemplateLiteral) {
nextToken();
return isTokenColonOrQuestionColon();
}
return tokenIsIdentifierOrKeyword(token()) && isNextTokenColonOrQuestionColon();
return false;
}

function parseTupleElementNameOrTupleElementType() {
// TODO: optimize this so we don't have to do lookahead
if (lookAhead(isTupleElementName)) {
const pos = getNodePos();
const hasJSDoc = hasPrecedingJSDocComment();
const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
const name = parseIdentifierName();
const name = token() === SyntaxKind.NoSubstitutionTemplateLiteral
? (parseLiteralLikeNode(token()) as NoSubstitutionTemplateLiteral)
: token() === SyntaxKind.TemplateHead
? parseTemplateType()
: parseIdentifierName();
const questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
parseExpected(SyntaxKind.ColonToken);
const type = parseTupleElementType();
Expand Down
8 changes: 5 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2222,10 +2222,12 @@ export interface TupleTypeNode extends TypeNode {
readonly elements: NodeArray<TypeNode | NamedTupleMember>;
}

export type NamedTupleMemberName = Identifier | NoSubstitutionTemplateLiteral | TemplateLiteralTypeNode;

export interface NamedTupleMember extends TypeNode, Declaration, JSDocContainer {
readonly kind: SyntaxKind.NamedTupleMember;
readonly dotDotDotToken?: Token<SyntaxKind.DotDotDotToken>;
readonly name: Identifier;
readonly name: NamedTupleMemberName;
readonly questionToken?: Token<SyntaxKind.QuestionToken>;
readonly type: TypeNode;
}
Expand Down Expand Up @@ -8379,8 +8381,8 @@ export interface NodeFactory {
updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode;
createTupleTypeNode(elements: readonly (TypeNode | NamedTupleMember)[]): TupleTypeNode;
updateTupleTypeNode(node: TupleTypeNode, elements: readonly (TypeNode | NamedTupleMember)[]): TupleTypeNode;
createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode): NamedTupleMember;
updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode): NamedTupleMember;
createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: NamedTupleMemberName, questionToken: QuestionToken | undefined, type: TypeNode): NamedTupleMember;
updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: NamedTupleMemberName, questionToken: QuestionToken | undefined, type: TypeNode): NamedTupleMember;
createOptionalTypeNode(type: TypeNode): OptionalTypeNode;
updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode;
createRestTypeNode(type: TypeNode): RestTypeNode;
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ import {
NamedExports,
NamedImports,
NamedImportsOrExports,
NamedTupleMemberName,
NamespaceExport,
NamespaceImport,
NewExpression,
Expand Down Expand Up @@ -10434,3 +10435,8 @@ export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralT
}
return Debug.fail();
}

/** @internal */
export function isNamedTupleMemberName(node: Node): node is NamedTupleMemberName {
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.TemplateLiteralType || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral;
}
3 changes: 2 additions & 1 deletion src/compiler/visitorPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
isModuleReference,
isNamedExportBindings,
isNamedImportBindings,
isNamedTupleMemberName,
isObjectLiteralElementLike,
isOptionalChain,
isParameter,
Expand Down Expand Up @@ -913,7 +914,7 @@ const visitEachChildTable: VisitEachChildTable = {
return context.factory.updateNamedTupleMember(
node,
tokenVisitor ? nodeVisitor(node.dotDotDotToken, tokenVisitor, isDotDotDotToken) : node.dotDotDotToken,
Debug.checkDefined(nodeVisitor(node.name, visitor, isIdentifier)),
Debug.checkDefined(nodeVisitor(node.name, visitor, isNamedTupleMemberName)),
tokenVisitor ? nodeVisitor(node.questionToken, tokenVisitor, isQuestionToken) : node.questionToken,
Debug.checkDefined(nodeVisitor(node.type, visitor, isTypeNode)),
);
Expand Down
7 changes: 4 additions & 3 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5196,10 +5196,11 @@ declare namespace ts {
readonly kind: SyntaxKind.TupleType;
readonly elements: NodeArray<TypeNode | NamedTupleMember>;
}
type NamedTupleMemberName = Identifier | NoSubstitutionTemplateLiteral | TemplateLiteralTypeNode;
interface NamedTupleMember extends TypeNode, Declaration, JSDocContainer {
readonly kind: SyntaxKind.NamedTupleMember;
readonly dotDotDotToken?: Token<SyntaxKind.DotDotDotToken>;
readonly name: Identifier;
readonly name: NamedTupleMemberName;
readonly questionToken?: Token<SyntaxKind.QuestionToken>;
readonly type: TypeNode;
}
Expand Down Expand Up @@ -7932,8 +7933,8 @@ declare namespace ts {
updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode;
createTupleTypeNode(elements: readonly (TypeNode | NamedTupleMember)[]): TupleTypeNode;
updateTupleTypeNode(node: TupleTypeNode, elements: readonly (TypeNode | NamedTupleMember)[]): TupleTypeNode;
createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode): NamedTupleMember;
updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode): NamedTupleMember;
createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: NamedTupleMemberName, questionToken: QuestionToken | undefined, type: TypeNode): NamedTupleMember;
updateNamedTupleMember(node: NamedTupleMember, dotDotDotToken: DotDotDotToken | undefined, name: NamedTupleMemberName, questionToken: QuestionToken | undefined, type: TypeNode): NamedTupleMember;
createOptionalTypeNode(type: TypeNode): OptionalTypeNode;
updateOptionalTypeNode(node: OptionalTypeNode, type: TypeNode): OptionalTypeNode;
createRestTypeNode(type: TypeNode): RestTypeNode;
Expand Down
40 changes: 40 additions & 0 deletions tests/baselines/reference/instantiableTupleLabels1.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
instantiableTupleLabels1.ts(4,27): error TS5087: A labeled tuple element is declared as rest with a '...' before the name, rather than before the type.
instantiableTupleLabels1.ts(15,24): error TS2322: Type 'unknown' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
instantiableTupleLabels1.ts(18,46): error TS2322: Type 'T1' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
instantiableTupleLabels1.ts(18,71): error TS2322: Type 'T2' is not assignable to type 'string | number | bigint | boolean | null | undefined'.


==== instantiableTupleLabels1.ts (4 errors) ====
type T1 = [`wow`: boolean];
type T2 = [number, `wow`: boolean];
type T3 = [number, ...`wow`: boolean[]];
type T4 = [number, `wow`: ...boolean[]]; // error
~~~~~~~~~~~~
!!! error TS5087: A labeled tuple element is declared as rest with a '...' before the name, rather than before the type.

type Prefix = 'pre';

type T5 = [`${Prefix}wow`: boolean];
type T6 = [number, `${Prefix}wow`: boolean];
type T7 = [number, ...`${Prefix}wow`: boolean[]];

type T8 = [number, `${never}wontfly`: boolean]; // no label displayed
type T9 = [number, `${any}wontfly`: boolean]; // no label displayed
type T11 = [number, `${"a" | "b"}wontfly`: boolean]; // no label displayed
type T12 = [number, `${unknown}wontfly`: boolean]; // error
~~~~~~~
!!! error TS2322: Type 'unknown' is not assignable to type 'string | number | bigint | boolean | null | undefined'.

type MakeTuple1<T1 extends string, T2 extends string> = [number, `second-${T1}`: string, ...`rest-${T2}`: boolean[]];
type MakeTuple2<T1, T2> = [number, `second-${T1}`: string, ...`rest-${T2}`: boolean[]]; // error
~~
!!! error TS2322: Type 'T1' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
!!! related TS2208 instantiableTupleLabels1.ts:18:17: This type parameter might need an `extends string | number | bigint | boolean | null | undefined` constraint.
~~
!!! error TS2322: Type 'T2' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
!!! related TS2208 instantiableTupleLabels1.ts:18:21: This type parameter might need an `extends string | number | bigint | boolean | null | undefined` constraint.

type T13 = MakeTuple1<"awesome", "tail">;
type T14 = MakeTuple1<any, "tail">;
type T15 = MakeTuple1<"a" | "b", "tail">;

Loading