microsoft / microsoft/TypeScript

Explicit target type breaks generic parameter inference for function call with intersection return type

Open
#61,467 1 comment 0 reactions 0 assignees View on GitHub
Domain: check: Type Inference Help Wanted Possible Improvement
Dominant language
Go
Stars
111k
Forks
14.3k
PR merge metrics
PR metrics pending

Description

### 🔎 Search Terms

explicit target type breaks generic parameter inference function call intersection return type contextual typing

### 🕗 Version & Regression Information

- This is the behavior in every version I tried, and I reviewed the FAQ for entries about "explicit"

### ⏯ Playground Link

https://www.typescriptlang.org/play/?strictBuiltinIteratorReturn=true#code/GYVwdgxgLglg9mABMAjAHgIKIKYA8rZgAmAzogNbYCecwiAEgCoCyAMgKIA22AtoVIwCGAcwBygvs0EAHADSIAQgD4AFIIBciDPIBGmhQEpNTNl178hYidinSA2hgC6iAGSLEAbwCwAKESIAJ2woEACkAHkdACtsaAA6QRISGGEwFSI4CBA+MCg4iCDBAjMcqDUDXQMAbl8AX19QSFgEZAAmTBx8QlJPWvllNU1tRD1FIy1Xd28-QODQiOjYvMTk1LVKmp96n18Ael3ERgALGDJgQRhOMh1YwRASbEOAZUQYMGBsALIFRABeBhYrAUICgUAQJX4kw8CAgnBgEHImhUBj+SkQADc4DAiLVXmASARBERELR3P9oZA4QikSjfmjMdjthAEATEAQCShjIDgaDwdxSlCYVTEYhkaiMVicX9kCgVAAiHQgsFgOXyCmw+EixrQeBpAweWq1ap7A7HU6IADucAC5DI2HRhDZRzgIGERydjzeHwCQWJUCo0k9ZCgR0eJGsiESHs60ipMCgbIDj0EOjgDt8zPxCfZUFa-nzBcLReLJdL-1Q8sVvJVaqFms02uaeoNRs2+0OJzIVpt11u90ewkIn3hiGkggC1gIX0jQUQwhgDqQeFj8PjnCoGZZ2ewBIAzFy2DzlRDcoLKfXRbT6ZLceX0AqlQhVZ469TL+KGTjVA-q8-1cKG3AHUEGRFtjR8dszS7a1bRGPsHhnAI4AtZAgKbMhx0eMA4ATQRgA+aBsGJYBrUQEg4D4WZEgQTcszZHcoAAFgPIFHzAE8Ezcf8LzFOkJUZaUKx-ZU-1fEVeLRMC21NTtLRg3sIDuBCvU+QgIEebtYJuUFPkteNnRBRNA0QHgZFo1kcwAVhYo8+XMU8uLEmkPxvQTWnSTJsn4fJCmKfl+ErNi5QqF9zzfRtdVAw1wKAA

### 💻 Code

```ts
function f1(a: A, b: B): HTMLElementTagNameMap[A] & B {
return Object.assign(document.createElement(a), b);
}
function f2
(a: A, b: B): A & B {
return Object.assign(a, b);
}

// This fails because TS infers B = HTMLButtonElement & {onclick: () => void} instead of B = {onclick: () => void}
const test1: HTMLButtonElement & {onclick: () => void} = f1("button", {onclick: function(){}});
// This works even though the inferred type is the same as the explicit type above
const test2 = f1("button", {onclick: function(){}});
// This works because generic parameters are given explicitly
const test3: HTMLButtonElement & {onclick: () => void} = f1<"button", {onclick: () => void}>("button", {onclick: function(){}});
// This works because arrow functions are not affected for some reason
const test4: HTMLButtonElement & {onclick: () => void} = f1("button", {onclick: () => {}});
// This works because inference works better without type map
const test5: HTMLButtonElement & {onclick: () => void} = f2(document.createElement("button"), {onclick: function(){}});
```

### 🙁 Actual behavior

`test1` shows an error:
> Argument of type '{ onclick: (this: GlobalEventHandlers) => void; }' is not assignable to parameter of type 'HTMLButtonElement & { onclick: () => void; }'.

This is because TS sees that `test1` is explicitly typed `HTMLButtonElement & {onclick: () => void}`, and so it erroneously infers the generic type parameters of `f1` as `B = HTMLButtonElement & {onclick: () => void}`, even though the correct inference is `B = {onclick: () => void}`.

More interestingly, `B` is inferred correctly if the explicit target type is omitted, as demonstrated with `test2`. However, the inferred type of `test2` is the same as the explicit type of `test1`!

### 🙂 Expected behavior

If a program is valid with an inferred type, then it should also be valid when that type is turned explicit.

### Additional information about the issue

The code snippet also demonstrates that this issue is somehow related to using mapped types and `function(){}` instead of `() => {}`. While curious, I think the main issue is that `test1` and `test2` behave differently.

Contributor guide

Open the contributing guide

Research direction

Start with the linked TypeScript Playground repro and compare test1 through test5, focusing on generic parameter inference for f1 and its intersection return type. Trace the checker behavior responsible for contextual typing of function(){}, then use the reported examples as regression cases. Done means test1 is accepted consistently with test2 without breaking the other inference cases.

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
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.