microsoft / microsoft/TypeScript
Intersection of merged declarations - function types behave inconsistently
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.4k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 117
Description
🔎 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
💻 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 withMyArrayExtension<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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the linked TypeScript Playground reproduction and compare the merged Foo and Bar declarations with the union call behavior. Read the discussion of related issue #19064, then trace the type-checking path responsible for merged function types and unions. Done means the union call result and ReturnType agree and preserve the specific shuffle method.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100