microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.13k stars 12.5k forks source link

TypeScript can't infer type of default parameters #59643

Open SukkaW opened 3 months ago

SukkaW commented 3 months ago

πŸ”Ž Search Terms

infer default parameters function wrapper function factory

πŸ•— Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtVNQBEQYsA3EAHgEF4QAPDEVYAZ3jQGtUcB3VANoBdADTwASgD4AFACh4CggC540gHQbYAc1YrqASngBeSfADizElAw4YlLj35ipI2fpUO++APQAqX-AAtjhgnCDA8HAYyDD4vt4A3LKyiEQk5CDSKOjYePC+0lDG8AAMYgBGxQDkVYYA3vLwYHisOBAgahA4WtINivBQrvDe3vAAegD8jQrl8iPjUwoAvvpJK0lAA

πŸ’» Code

declare function fnDerive<A extends unknown[], R>(
    fn: (...args: A) => Generator<unknown, R>,
): unknown /** mocked return */;

fnDerive(function *(a = 0, b = '') {
  console.log({
    a,
 // ^?
    b
 // ^?
  });
});

πŸ™ Actual behavior

The type of a and b is unknown.

image

πŸ™‚ Expected behavior

The type of a and b should be number and string

Additional information about the issue

I was working with a library gensync when I hit this issue, and I extracted the minimum reproduction out of it.

Note that if I don't use default parameters, typescript can infer the a and b types correctly:

declare function fnDerive<A extends unknown[], R>(
    fn: (...args: A) => Generator<unknown, R>,
): unknown /** mocked return */;

fnDerive(function *(a?: number | undefined, b?: string | undefined) {
  console.log({
    a,
 // ^?
    b
 // ^?
  });

  a ??= 0;
  b ??= '';
  console.log({
    a,
 // ^?
    b
 // ^?
  });
});
image
Andarist commented 3 months ago

This is not specific to generator functions. It's related to inferring type parameters from unannotated parameters with inferred types from initializers (TS playground):

declare function fnDerive<A extends unknown[]>(fn: (...args: A) => void): A;

const result = fnDerive(function (a = 0, b = "") {});
//    ^? const result: [a?: unknown, b?: unknown]

declare function fnDerive2<A, B>(fn: (a: A, b: B) => void): [A, B];

const result2 = fnDerive2(function (a = 0, b = "") {});
//    ^? const result2: [unknown, unknown]

It's also just related to how there is a contextual signature available here. This touched on what I improved here: https://github.com/microsoft/TypeScript/pull/56506 . This PR didn't break it anyhow when it comes to the case presented here but it also didn't improve it.

So the question here is also - how both situations should be differentiated? There are situations in which the contextual parameter type should be preferred. Perhaps it could be done based on checkMode & CheckMode.Inferential, or maybe based on the uninstantiated contextual signature when the parameter's type is a type variable.

I'd love to experiment with this but I have a long list of things to experiment with so no promises πŸ˜…

SukkaW commented 3 months ago

This is not specific to generator functions.

Updated title and search terms to reflect this~