言いたいことはそれだけか

KotlinとかAndroidとかが好きです。調べたことをメモします。٩( 'ω' )و

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];

読んだコードのメモ

https://github.com/microsoft/TypeScript/blob/00dc0b6674eef3fbb3abb86f9d71705b11134446/src/services/refactors/convertOverloadListToSingleSignature.ts

    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;
    }

https://github.com/microsoft/TypeScript/blob/44e8244dd96daadfafba75d1c1c2645a42a493ca/src/compiler/factory/nodeFactory.ts

    // @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;
    }

https://github.com/microsoft/TypeScript/blob/00dc0b6674eef3fbb3abb86f9d71705b11134446/src/compiler/factory/parenthesizerRules.ts

    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);
    }

https://github.com/microsoft/TypeScript/blob/90fb764a0f76b7d15f56c08a8bb98f37e8dd046f/src/compiler/emitter.ts

                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);
    }