microsoft / microsoft/TypeScript

Can't dynamically map type from class private field

オープン
#44,633 コメント 5 件 リアクション 1 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

Awaiting More Feedback Suggestion
主要言語
Go
スター
111k
フォーク
14.4k
平均マージ
1日 19時間
マージ済み PR(30日)
117

説明

Suggestion

Support private fields in mapped types.

🔍 Search Terms

typescript, private field, mapped types

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Include information about private fields types inside class interface.

📃 Motivating Example

TypeScript have dynamic mapped types which is very powerful feature. But currently can't used with private fields. So its hard to migrate to those.

💻 Use Cases

Working example without private field:

interface Runnable {
    (): void;
}

interface Consumer<T> {
    (data: T): void;
}

class EventEmitter<T> {
    emit(data: T): void {
        return void 0;
    }

    subscribe(consumer: Consumer<T>): Runnable {
        const unsubscribe = () => void 0;

        return unsubscribe;
    }
}

class Host {
    private readonly events = {
        init: new EventEmitter<Record<string, number>>(),
        contextUpdate: new EventEmitter<Record<string, string>>(),
        inactivity: new EventEmitter<boolean>(),
    } as const;

    on<T extends HostEventType>(
        type: T,
        consumer: HostEventConsumer<T>,
    ): Runnable {
        return this.events[type].subscribe(consumer);
    }
}

type HostEventType = keyof Host['events'];

type HostEventConsumer<T extends HostEventType> =
    Host['events'][T] extends EventEmitter<infer U> ? Consumer<U> : never;

const host = new Host();

// Here data is mapped to Record<string, number>
host.on('init', (data) => console.log({ data }));

Not working example with private field (expecting to work in future):

interface Runnable {
    (): void;
}

interface Consumer<T> {
    (data: T): void;
}

class EventEmitter<T> {
    emit(data: T): void {
        return void 0;
    }

    subscribe(consumer: Consumer<T>): Runnable {
        const unsubscribe = () => void 0;

        return unsubscribe;
    }
}

class Host {
    readonly #events = {
        init: new EventEmitter<Record<string, number>>(),
        contextUpdate: new EventEmitter<Record<string, string>>(),
        inactivity: new EventEmitter<boolean>(),
    } as const;

    on<T extends HostEventType>(
        type: T,
        consumer: HostEventConsumer<T>,
    ): Runnable {
        return this.#events[type].subscribe(consumer);
    }
}

type HostEventType = keyof Host['#events'];

type HostEventConsumer<T extends HostEventType> =
    Host['#events'][T] extends EventEmitter<infer U> ? Consumer<U> : never;

const host = new Host();

// Here data should be mapped to Record<string, number>
host.on('init', (data) => console.log({ data }));

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

issue にある動作する TypeScript の例と動作しない TypeScript の例から始め、keyof と indexed access が private #events フィールドをどのように扱うかに焦点を当てます。private フィールドに対する意図された mapped-type の動作を特定し、両方の例に照らして検証します。private フィールドの例で、JavaScript のランタイム動作を変更せずに、示されているとおりにイベント payload の型が推論されれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。