Intersection of merged declarations - function types behave inconsistently

Aberta
#57,319 5 comentários 0 reações 0 responsáveis Ver no GitHub

Ninguém assumiu esta issue ainda.

Avaliação

Dificuldade
5/5
Tempo estimado
Mais de uma semana
Facilidade para iniciantes
35/100
Tipo de issue
Bug
Clareza
Claramente especificada
Status de atividade
Estagnada
Stack de tecnologia
typescript
Domínio
compilers

Direção de pesquisa

Comece executando a reprodução vinculada no TypeScript Playground e compare as declarações mescladas de Foo e Bar com o comportamento de chamadas de union. Leia a discussão da issue relacionada #19064 e, em seguida, rastreie o caminho de verificação de tipos responsável pelos tipos de função mesclados e pelas unions. Está concluído quando o resultado da chamada de union e ReturnType concordarem e preservarem o método shuffle específico.

Escrita pelo modelo de indexação a partir do texto da issue.

Descrição

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.

Linguagem predominante
Go
Estrelas
111k
Forks
14.4k
Merge médio
1d 19h
PRs com merge (30d)
117

Guia de contribuição

Abrir o guia de contribuição

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de microsoft/TypeScript

Todas as issues de microsoft/TypeScript

Issues semelhantes

Mais issues de Go

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.