microsoft / microsoft/TypeScript

Interface that extends another no longer constrains types like the original

Ouverte
#60,008 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Bug Domain: check: Type Inference Help Wanted
Langage dominant
Go
Étoiles
111k
Forks
14.3k
Merge moyen
1 j 19 h
PR mergées (30 j)
117

Description

🔎 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

https://www.typescriptlang.org/play/?#code/PQKhAIGUFEBUFUAK4C8b0c1z4TAFABmArgHYDGALgJYD2p4lAngA4CmAPLAHwAUAlAC5wscAG9wAJzaVikhqWIAbJeACGAZ3BkA1qVoB3BppEBucAF98zduGgA3NqUoBZNS1TgASm3K1JACYcGpSS1KQA5gA0do7O3KbWrGzgADLUIU5sklwOTpTgbAAelE4BWnnxnry8bPaUwrCVlPyo3OD2tNQBrQA+4uAAFmqkAUpszby0AEYAVr4NIs1CHV0Blon4xSz+BeGlkoRq5CmwyQHN0AC21JQHuXGu7oUlZRWPbiztYvjgf+oBC6PdKZUjZB75M62YqlUZaHRsJi0QhLD7uPi-f5YmxsRrNKFsKKYrF-JQZWHZYQgik5JpolgAbTpkOSAF1uESSf9aCwaPQNAB+YQAQUBzWpWUkAHleXRSFp+tNaLRxiNOf8Vp1uoksdIrrRHOLyZKIc4CS9YeVwAikSjmc5PhiuX8cXjHgT1VyyaDKWljWDac1Pkz8WyOcSSTy+fKhbF8hKAzLowrwEqVWw1RHNWtElZQBBoAA5AAiUDgSFQ2Cr1ZQuAI+HzvwggFByKAjW5MRhsEJNgg4rjnS43O7giSEZXCZqWbh8VoaNQ0DSEajdkSDx7XW73McTuPOafmYDAcDN-AN48AAUoGgAtNsFnfJJJ-El2AP2ED8puRzkJNM1JIk6PNOs7gPOi7LquBKfs437buA460EB+QHuAR4nmeja4Ce4DCmSJjIowgwpP41AROEaiqDivavikADCypKEOW7gvaTweDCbx7ux7S1tBzE-qaPGbP2DEqgJ8GIch+4WDOAhgQuGSQVo-EbsOkm7lOsmHsep4Rs6BmGUZxkmaZJnoVet73lQj7PpItEcGJTFqSxv6pgB0kFLJoHgUpK4qeuX7qaOCGacB2lobpmFgE24CADLk4AAJLONkRwnERC5gdQVwsEonacXCREkWE5GkJRjDJDR+ypcc9GMclBxpRMLmCWxnwWlxQboh1hWqUFrlCY6AxWA5TkNTVJwSSFUncSB8m+Uu-lrh+U1uTNWkJJFGH6WZu17ftB1GRZ153kU7A2dkdmjfVKWHLVq0cH+Hmzd582KYtUGBbBwVrWFKERehcXRSAsUJeNd3pZQwwFAVVpQ8VZEUVRyTqKMAJWiExCEIQVW3U14BjXjtWIEoxAaMWGQsAu5CDA9bXPLD7z5ENjPLWwMGUHBrFdV84gRgElPU7TjxCearM2oR9NfLU9husz7ghu6bLZtq+AjaJN2NcTpPk4LlA0w9O5IS9clzu9ylsxzXO-cbG06dth2O07zsu38x1WWdD6XS+GsquDTUk2TFMaFT+vC-1glPYBJs+ebS19d9A1G55qGA8DsWtgxLCdlTIThBERXgH4AQpOEmRqOshGw-nhfVRDKQGP4OgaLjWvpVnTCIJopRWz9g0M68hU898EYVxzCbc0r0KD1aEt2sPvA7S6yRy2aySeiS3o0lS-qT-LjJsQS7Ib1iUZyoKIpisCu-SrK-LgIqjEZqQG8qwEOr-HqBrNfGN+iyj4tESSwXkvCq7BV6UA9KArekod4+kDPSRWLJ2DH1AWffksYjTwKTOfB+qYn6ZixG-D+fwBYhyFs0f+09LTwiAfPekfA6gQODIfZWwgtTvzVmeDW2cu6ZF7knUKttwqmwUhBeOX1OZ92Tibe2elXYKMUftd2p1zqUFsj7ZIjkeSd27uzQ27lo4bVjuIz6K0WoaWEf9Ta6FmxAA

💻 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

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Reproduisez le comportement à l’aide du TypeScript Playground fourni en lien et du code présent dans l’issue, en comparant l’interface d’origine, l’alias de type et les interfaces d’extension. Suivez le chemin de vérification des types pour les contraintes génériques et ajoutez un test de régression ; le travail est terminé lorsqu’une interface d’extension conserve la contrainte d’origine et que les cas @ts-expect-error existants se comportent comme prévu.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
typescript
Domaine
compilers
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
38/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.