microsoft / microsoft/TypeScript
Inconsistent type compatibility for a type with a call signature and and index signature
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
TypeScript Version: 2.9.0-dev
Search Terms: index signature intersection
Code
type INOk = {
(): string;
[name: string] : number;
}
type IOk = {
(): string;
} & {
[name: string] : number;
}
declare let val:(() => "") & { foo: number; }
let ok: IOk = val; // This works
let nok: INOk = val; // This does not
Expected behavior:
Both IOk and INOk have the same public structure, they both have a call signature and are indexable, both assign statements should be valid.
Actual behavior:
The second assign statement fails with the message Index signature is missing in type '(() => "") & { foo: number; }'.
Playground Link: link
Related Issues: https://github.com/Microsoft/TypeScript/issues/15300
Notes
Looking at the checker code, it appears that for IOk type compatibility is checked for each constituent of the intersection type, so we will have :
isRelatedTo (typeof val, IOk) =
isRealtedTo(typeof val, () => "")) // == True, since val has a call signature
( // Above equal to
isRelatedTo(() => "", () => "") // == True
||
isRelatedTo({ foo: number; }, () => "") // == False but it's does not matter
)
&&
isRealtedTo(typeof val, { [name: string] : number }) // = True since the { foo: number} part of typeof val has an inferable index (ie isObjectTypeWithInferableIndex( { foo: number} ) will return true)
( // Above equal to
isRelatedTo(() => "", { [name: string] : number }) // == False but does not matter
||
isRelatedTo({ foo: number; }, { [name: string] : number }) // == True,
)
While for INOk the relation is checked directly, since INOk can't be split into constituents and we have
isRelatedTo (typeof val, INOk) =
isRelatedTo(() => "", INOk) // == False INOk has index, but ()=> "" does not
||
isRelatedTo({ foo: number; }, INOk) // == False, No compatible call signature
So then the checker falls back to structural checking (recursiveTypeRelatedTo) but this fails as well because when checking for index compatibility (inside indexTypesRelatedTo), it decides that the typeof value ((() => "") & { foo: number; }) does not have an inferable index (isObjectTypeWithInferableIndex returns false because the intersection type does not have a symbol and even if it did the condition for inferable index checks that the type does not have a call signature (!typeHasCallOrConstructSignatures(type)))
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
Reproduce the example with TypeScript 2.9.0-dev or the current playground, then inspect the checker paths named in the issue: isRelatedTo, recursiveTypeRelatedTo, indexTypesRelatedTo, and isObjectTypeWithInferableIndex. Compare compatibility handling for the intersection type and the type with combined call and index signatures; done means both assignments are accepted consistently without weakening unrelated checks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100