microsoft / microsoft/TypeScript
Interface that extends another no longer constrains types like the original
まだ誰も着手していません。
- 主要言語
- Go
- スター
- 111k
- フォーク
- 14.3k
- 平均マージ
- 2日 4時間
- マージ済み PR(30日)
- 132
説明
🔎 Search Terms
interface
interface constrain
extend interface
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about interfaces.
⏯ Playground Link
💻 Code
/** SETUP ========================== */
function type<T>(): T { return null as unknown as T; }
type EventMap = Record<string, Event>;
type Listener<TEvent extends Event> = ((evt: TEvent) => void) | { handleEvent(object: TEvent): void };
export interface TypedEventEmitter<TEventMap extends EventMap> {
addEventListener<TEventType extends keyof TEventMap>(
type: TEventType,
listener: Listener<TEventMap[TEventType]>,
options?: AddEventListenerOptions | boolean,
): void;
removeEventListener<TEventType extends keyof TEventMap>(
type: TEventType,
listener: Listener<TEventMap[TEventType]>,
options?: EventListenerOptions | boolean,
): void;
}
/** END SETUP ========================== */
/**
* ✅ Sanity test
*/
type<TypedEventEmitter<{ foo: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ✅
// @ts-expect-error
type<TypedEventEmitter<{ bar: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ✅
/**
* ✅ Alias of the original type
*/
type CoolEventEmitter<TEventMap extends EventMap> = TypedEventEmitter<TEventMap>;
type<CoolEventEmitter<{ foo: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ✅
// @ts-expect-error
type<CoolEventEmitter<{ bar: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ✅
/**
* ❌ Interface that simply extends the original type
*/
interface CoolInterfaceEventEmitter<TEventMap extends EventMap> extends TypedEventEmitter<TEventMap> { }
type<CoolInterfaceEventEmitter<{ foo: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ✅
// @ts-expect-error
type<CoolInterfaceEventEmitter<{ bar: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ❌
/**
* ❌ Interface that extends the original type and adds stuff
*/
interface CoolInterfacePlusDispatchEventEmitter<TEventMap extends EventMap> extends TypedEventEmitter<TEventMap> {
dispatchEvent<TEventType extends keyof TEventMap>(ev: TEventMap[TEventType]): void;
}
type<CoolInterfacePlusDispatchEventEmitter<{ foo: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ✅
// @ts-expect-error
type<CoolInterfacePlusDispatchEventEmitter<{ bar: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ❌
/**
* ✅ Copy pasting the code instead of extending the interface works
*/
interface CopyPastedEventEmitter<TEventMap extends EventMap> {
addEventListener<TEventType extends keyof TEventMap>(
type: TEventType,
listener: Listener<TEventMap[TEventType]>,
options?: AddEventListenerOptions | boolean,
): void;
removeEventListener<TEventType extends keyof TEventMap>(
type: TEventType,
listener: Listener<TEventMap[TEventType]>,
options?: EventListenerOptions | boolean,
): void;
dispatchEvent<TEventType extends keyof TEventMap>(ev: TEventMap[TEventType]): void;
}
type<CopyPastedEventEmitter<{ foo: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ✅
// @ts-expect-error
type<CopyPastedEventEmitter<{ bar: Event }>>() satisfies TypedEventEmitter<{ foo: Event }>; // ✅
🙁 Actual behavior
As soon as the original interface is extended – whether the extender adds properties, does not add properties, uses the type parameter, or doesn't use the type parameter – I seem to lose the ability to constrain types in the same way as the original interface can.
🙂 Expected behavior
I would expect an interface that extends another to constrain types in exactly the same way as the original.
Additional information about the issue
No response
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
リンクされた TypeScript Playground と issue 内のコードを使って動作を再現し、元の interface、type alias、そして拡張する interface を比較してください。ジェネリック制約に対する型チェックの経路を追跡し、回帰テストを追加してください。拡張する interface が元の制約を保持し、既存の @ts-expect-error のケースが期待どおりに動作すれば完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- typescript
- 領域
- compilers
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 停滞
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 38/100