Typescript Tupleの実態を知りたくてcompilerのコードを読んだ時のメモ
タイトルの通り。TypescriptにはTupleがあると知って、どうやって普通の配列と区別しているのか知りたくてcompilerのコードを読んだ時のメモ。 TS compilerのコードを読むのは初めてなので、間違っているかもしれない。変なことを書いていたらコメント等で教えてもらえると嬉しいです。
結論
TS compilerがsignatureを見ているところで、signatureの型が複数あったら(= Tupleだったら)SyntaxKind.TupleType
というTuple用のメンバをnodeに設定して TupleTypeNode
という型を使っっている。普通の配列は ArrayTypeNode
になるので区別できる。
ちなみにJSにはTupleは用意されていないので、TSでTupleを書いてもtranspileされたJSのコードは普通の配列と同じ見た目をしている。
const thisIsTuple: [number, string, boolean] = [1, "1", true];
↓これ(ts)がこう(js)なる
const thisIsTuple = [1, "1", true];
読んだコードのメモ
function getNewParametersForCombinedSignature(signatureDeclarations: (MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration)[]): NodeArray<ParameterDeclaration> { const lastSig = signatureDeclarations[signatureDeclarations.length - 1]; if (isFunctionLikeDeclaration(lastSig) && lastSig.body) { // Trim away implementation signature arguments (they should already be compatible with overloads, but are likely less precise to guarantee compatability with the overloads) signatureDeclarations = signatureDeclarations.slice(0, signatureDeclarations.length - 1); } return factory.createNodeArray([ factory.createParameterDeclaration( /*modifiers*/ undefined, factory.createToken(SyntaxKind.DotDotDotToken), "args", /*questionToken*/ undefined, factory.createUnionTypeNode(map(signatureDeclarations, convertSignatureParametersToTuple)) ) ]); }
function convertSignatureParametersToTuple(decl: MethodSignature | MethodDeclaration | CallSignatureDeclaration | ConstructorDeclaration | ConstructSignatureDeclaration | FunctionDeclaration): TupleTypeNode { const members = map(decl.parameters, convertParameterToNamedTupleMember); return setEmitFlags(factory.createTupleTypeNode(members), some(members, m => !!length(getSyntheticLeadingComments(m))) ? EmitFlags.None : EmitFlags.SingleLine); }
function convertParameterToNamedTupleMember(p: ParameterDeclaration): NamedTupleMember { Debug.assert(isIdentifier(p.name)); // This is checked during refactoring applicability checking const result = setTextRange(factory.createNamedTupleMember( p.dotDotDotToken, p.name, p.questionToken, p.type || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) ), p); const parameterDocComment = p.symbol && p.symbol.getDocumentationComment(checker); if (parameterDocComment) { const newComment = displayPartsToString(parameterDocComment); if (newComment.length) { setSyntheticLeadingComments(result, [{ text: `* ${newComment.split("\n").map(c => ` * ${c}`).join("\n")} `, kind: SyntaxKind.MultiLineCommentTrivia, pos: -1, end: -1, hasTrailingNewLine: true, hasLeadingNewline: true, }]); } } return result; }
// @api function createTupleTypeNode(elements: readonly (TypeNode | NamedTupleMember)[]) { const node = createBaseNode<TupleTypeNode>(SyntaxKind.TupleType); node.elements = createNodeArray(parenthesizerRules().parenthesizeElementTypesOfTupleType(elements)); node.transformFlags = TransformFlags.ContainsTypeScript; return node; }
// @api function createNamedTupleMember(dotDotDotToken: DotDotDotToken | undefined, name: Identifier, questionToken: QuestionToken | undefined, type: TypeNode) { const node = createBaseDeclaration<NamedTupleMember>(SyntaxKind.NamedTupleMember); node.dotDotDotToken = dotDotDotToken; node.name = name; node.questionToken = questionToken; node.type = type; node.transformFlags = TransformFlags.ContainsTypeScript; node.jsDoc = undefined; // initialized by parser (JsDocContainer) node.jsDocCache = undefined; // initialized by parser (JsDocContainer) return node; }
// @api function createUnionTypeNode(types: readonly TypeNode[]): UnionTypeNode { return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, types, parenthesizerRules().parenthesizeConstituentTypesOfUnionType) as UnionTypeNode; }
function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: readonly TypeNode[], parenthesize: (nodes: readonly TypeNode[]) => readonly TypeNode[]) { const node = createBaseNode<UnionTypeNode | IntersectionTypeNode>(kind); node.types = factory.createNodeArray(parenthesize(types)); node.transformFlags = TransformFlags.ContainsTypeScript; return node; }
// @api function createNodeArray<T extends Node>(elements?: readonly T[], hasTrailingComma?: boolean): NodeArray<T> { if (elements === undefined || elements === emptyArray) { elements = []; } else if (isNodeArray(elements)) { if (hasTrailingComma === undefined || elements.hasTrailingComma === hasTrailingComma) { // Ensure the transform flags have been aggregated for this NodeArray if (elements.transformFlags === undefined) { aggregateChildrenFlags(elements as MutableNodeArray<T>); } Debug.attachNodeArrayDebugInfo(elements); return elements; } // This *was* a `NodeArray`, but the `hasTrailingComma` option differs. Recreate the // array with the same elements, text range, and transform flags but with the updated // value for `hasTrailingComma` const array = elements.slice() as MutableNodeArray<T>; array.pos = elements.pos; array.end = elements.end; array.hasTrailingComma = hasTrailingComma; array.transformFlags = elements.transformFlags; Debug.attachNodeArrayDebugInfo(array); return array; } // Since the element list of a node array is typically created by starting with an empty array and // repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for // small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation. const length = elements.length; const array = (length >= 1 && length <= 4 ? elements.slice() : elements) as MutableNodeArray<T>; array.pos = -1; array.end = -1; array.hasTrailingComma = !!hasTrailingComma; array.transformFlags = TransformFlags.None; aggregateChildrenFlags(array); Debug.attachNodeArrayDebugInfo(array); return array; }
function parenthesizeConstituentTypesOfUnionType(members: readonly TypeNode[]): NodeArray<TypeNode> { return factory.createNodeArray(sameMap(members, parenthesizeConstituentTypeOfUnionType)); }
function parenthesizeConstituentTypeOfUnionType(type: TypeNode) { switch (type.kind) { case SyntaxKind.UnionType: // Not strictly necessary, but a union containing a union should have been flattened case SyntaxKind.IntersectionType: // Not strictly necessary, but makes generated output more readable and avoids breaks in DT tests return factory.createParenthesizedType(type); } return parenthesizeCheckTypeOfConditionalType(type); }
case SyntaxKind.TupleType: return emitTupleType(node as TupleTypeNode); case SyntaxKind.NamedTupleMember: return emitNamedTupleMember(node as NamedTupleMember);
function emitTupleType(node: TupleTypeNode) { emitTokenWithComment(SyntaxKind.OpenBracketToken, node.pos, writePunctuation, node); const flags = getEmitFlags(node) & EmitFlags.SingleLine ? ListFormat.SingleLineTupleTypeElements : ListFormat.MultiLineTupleTypeElements; emitList(node, node.elements, flags | ListFormat.NoSpaceIfEmpty, parenthesizer.parenthesizeElementTypeOfTupleType); emitTokenWithComment(SyntaxKind.CloseBracketToken, node.elements.end, writePunctuation, node); }
function emitNamedTupleMember(node: NamedTupleMember) { emit(node.dotDotDotToken); emit(node.name); emit(node.questionToken); emitTokenWithComment(SyntaxKind.ColonToken, node.name.end, writePunctuation, node); writeSpace(); emit(node.type); }