microsoft / microsoft/TypeScript

Interface that extends another no longer constrains types like the original

Abierto
#60,008 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Bug Domain: check: Type Inference Help Wanted
Lenguaje dominante
Go
Estrellas
111k
Forks
14.3k
Merge medio
2 d 4 h
PR fusionados (30 d)
132

Descripción

🔎 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

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Reproduce el comportamiento usando el TypeScript Playground enlazado y el código del issue, comparando la interfaz original, el alias de tipo y las interfaces que extienden. Sigue la ruta de comprobación de tipos para las restricciones genéricas y añade una prueba de regresión; se considera terminado cuando una interfaz que extiende conserva la restricción original y los casos existentes de @ts-expect-error se comportan como se espera.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
typescript
Área
compilers
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Estancado
Claridad
Bastante claro
Aptitud para principiantes
38/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.