Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
nuiva opened this issue Mar 23, 2025 · 1 comment
Labels
Help Wanted You can do this Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases
Milestone

Comments

@nuiva
Copy link

nuiva commented Mar 23, 2025

🔎 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

function f1<A extends keyof HTMLElementTagNameMap, B>(a: A, b: B): HTMLElementTagNameMap[A] & B {
  return Object.assign(document.createElement(a), b);
}
function f2<A extends {}, B>(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.

@jcalz
Copy link
Contributor

jcalz commented Mar 23, 2025

No idea why inference from contextual return type would take precedence in that case, and no idea if this is a bug, design limitation, or intended behavior... but as a workaround I'd make the return type NoInfer<HTMLElementTagNameMap[A] & B>.

@RyanCavanaugh RyanCavanaugh added Help Wanted You can do this Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases labels Mar 27, 2025
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Mar 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Help Wanted You can do this Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases
Projects
None yet
Development

No branches or pull requests

3 participants