Intersection of merged declarations - function types behave inconsistently

Abierto
#57,319 5 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
35/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Estancado
Stack tecnológico
typescript
Área
compilers

Línea de trabajo

Comienza ejecutando la reproducción enlazada en TypeScript Playground y compara las declaraciones combinadas de Foo y Bar con el comportamiento de las llamadas sobre uniones. Lee la discusión del issue relacionado #19064 y, después, sigue el recorrido de comprobación de tipos responsable de los tipos de función combinados y las uniones. Se considera terminado cuando el resultado de la llamada sobre la unión y ReturnType coinciden y preservan el método shuffle específico.

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

Descripción

In Discussion Suggestion
🔎 Search Terms

declaration merging

🕗 Version & Regression Information
  • This is a type inference problem
  • This problem appears in Typescript 3.3.3, 3.5.1, 3.9.7, 4.9.5, 5.3.3 and 5.4.0-dev.20240207
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about declaration merging
⏯ Playground Link

https://www.typescriptlang.org/play?#code/C4TwDgpgBAsiCCAnRBDEBRAHsCA7AzgJYD2uAPACoB8UAvFEqiJTQGRQDeU+AFgK4AzAQBsIACgCUALigA3YoQAmUAL4AoNYVw5EAlAGNoAMWLFOUAOYRgAGUL5gkmYzRkHiLRZrqtOvYagTMy4rW3tHaVgEZDQsHAIScndPb01tCF0DaAAhFERzULsHJwYY5mTcL1U0vyyoXPyQ6yKImTgXDGw8IlI3YA9K1LVQSEDTFopwaHoAJWs+RFxJyDIggG0AIkLwjYBdGihDo4B6Y6gUfCgITEh9HEUZCos13ah2Dl5BEXFI+SUAbnUIxyeQmUzoUDmwAWSymZAam22Dj2ByOh1O50u11u90e-U8LzenE+QlEJT+ikBw3BRlB4WW00h80WDLIYiCAB8GhJEc0dvsoBiLlcbhA7hAHtx8ZVCe8Sd9yQpKeo1KJgFABKYjLgIWIOCpMWNiBIAHRI4D-NFW61WjFcEodPoDLyWkrtMpxbqJJ0pS3qNVQABGeW1uv1hu5Zr5DktNrjaLtUAdZR9g1dkXdTE9CV6TyoftV1g1IZ19D1BuFnMj5tj8dtZ3tkUdefTbWiWa6OaS0pdqigHPMyaYqd7bvbsU7PW7zvz1QDekQLRktMX9PB9AX2sktbrCbOvGIfGEygA7sREABrAA0Qb46oyiHPUjUxwAVFA1AyoAByJ4vb9QPYUC4MQ6oXEQFi4CggaiFAwBmMCP6ZhO8RTiOVDfiaaiHAACo+kCIKAP7yqIAFAQAtvYRCVIBOqIb+Pb-re6qIBAACOfCEKxyhaHB4LflwJE-DIFJ+phYgAEwAMwSRJEhqK+xwaOWhpBKa5qSCaQnbru8YYmel74GoKnCtW0YRFp-Ckj8O66XuUAGReRkmZcVZ5Op5madpEi2buGLoMgT5QHhxAEURv5Wd8AGKMQECXCB96YOEUCkHxowMc6-4mpJUlSQAnBIQA

💻 Code
type MyArrayExtension<T> = Array<T> & { shuffle(): void }

interface Foo { getList(): Array<string> }
interface Foo { getList(): MyArrayExtension<string> }
interface Bar { getList(): Array<string> }
interface Bar { getList(): MyArrayExtension<string> }

type FooListType = ReturnType<Foo["getList"]>       // as expected: string[] & {shuffle(): void;}
type BarListType = ReturnType<Bar["getList"]>       // as expected: string[] & {shuffle(): void;}
type FarListType = ReturnType<(Foo|Bar)["getList"]> // as expected: string[] & {shuffle(): void;}

let fooFn = ({} as Foo).getList;                    // { (): Array<string>; (): MyArrayExtension<string>; }
let barFn = ({} as Bar).getList;                    // { (): Array<string>; (): MyArrayExtension<string>; }
let farFn = ({} as Foo|Bar).getList;                // { (): Array<string>; (): MyArrayExtension<string>; } | { (): Array<string>; (): MyArrayExtension<string>; }
let farList: FarListType = farFn();                 // should work, but error:
/* 
Type 'string[]' is not assignable to type 'MyArrayExtension<string>'.
  Property 'shuffle' is missing in type 'string[]' but required in type '{ shuffle(): void; }'.(2322)
*/

({} as Foo).getList().shuffle();                    // works
({} as Bar).getList().shuffle();                    // works
({} as Foo|Bar).getList().shuffle();                // Error: Property 'shuffle' does not exist on type 'string[]'.(2339)
🙁 Actual behavior

There are two interfaces (Foo and Bar), that each are constituted of two merged declarations.
Each interface individually behaves such that the later declaration wins (getList(): MyArrayExtension<string> in either case). When changing the order (define the MyArrayExtension returning interfaces first), the return type accordingly is string[].
However, when intersecting Foo|Bar, the behaviour becomes inconsistent:

  • ReturnType<(Foo|Bar)["getList"]> is what I would expect (compatible with MyArrayExtension<string>), but
  • ({} as Foo|Bar).getList() is not assignable to a variable of this type
🙂 Expected behavior

My use case is the following: I have an external dependency that defines interfaces with functions that return T[].
Since I know those functions return a specific type that extends T[], I want to augment the module (using declare module) and give the functions a more specific return type.
The behaviour for non-intersected types if perfect for this. The later (e.g. provided by my declare module augmentation) wins.
However, it breaks down when intersecting two types from the external dependency, as described above.

I would expect type inference to work consistently. I think it's good to be able to override/make types more specific this way, and I think this ability should not be lost when intersecting types for which this has been done.

Additional information about the issue

@jcalz explained to me on StackOverflow that this problem is not restricted to module augmentation, but appears even when merging declarations in the same file. The problem is probably related to #19064.

Lenguaje dominante
Go
Estrellas
111k
Forks
14.4k
Merge medio
1 d 15 h
PR fusionados (30 d)
106

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.

Más de microsoft/TypeScript

Todos los issues de microsoft/TypeScript

Issues similares

Más issues de Go

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.