最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - Why is generic not infered when specifying the function parameter - Stack Overflow

programmeradmin0浏览0评论

I have the following typing :

declare function linkedSignal<S, D>(options: {
  source: () => S;
  computation: (source: NoInfer<S>, previous?: {source: NoInfer<S>; value: NoInfer<D>}) => D;
}): D;

When using it this way :

linkedSignal({
    source: () => 3,
    computation: (source, previous) => {
        return 3;
    },
})

D is infered as unknown when previous (the 2nd parameter) is defined, even though the computation function returns a deterministic type.

Can you explain why this is happening and how we could improve the typing so the generic is correctly inferred.

Playground

I have the following typing :

declare function linkedSignal<S, D>(options: {
  source: () => S;
  computation: (source: NoInfer<S>, previous?: {source: NoInfer<S>; value: NoInfer<D>}) => D;
}): D;

When using it this way :

linkedSignal({
    source: () => 3,
    computation: (source, previous) => {
        return 3;
    },
})

D is infered as unknown when previous (the 2nd parameter) is defined, even though the computation function returns a deterministic type.

Can you explain why this is happening and how we could improve the typing so the generic is correctly inferred.

Playground

Share Improve this question edited Nov 19, 2024 at 22:28 Matthieu Riegler asked Nov 19, 2024 at 22:22 Matthieu RieglerMatthieu Riegler 56.1k27 gold badges146 silver badges198 bronze badges 2
  • It's essentially ms/TS#47599; when you have contextual typing and generics that depend on each other, TS has a hard time inferring. You could try to change to something like this version which should at least allow the return type to be inferred, but that contextual type still won't work, because of the dependency. If this matters you're probably just going to have to annotate things even if you "shouldn't have to". Does that fully address the q? If so I'll write an a or find a duplicate; if not, what's missing? – jcalz Commented Nov 19, 2024 at 23:32
  • Yeah that's probably it, we're hitting a TS limitation. – Matthieu Riegler Commented Nov 19, 2024 at 23:58
Add a comment  | 

2 Answers 2

Reset to default 0

NoInfer doesn't stop TS from infering, it just infers the generic as unknown which is happening in your example. Since the most reliable source of guessing the type is at previous.value it is what gets recognized as your D.

It's obviously a bit out of context, so it's hard to say if it may help, but in this piece of code the type of value doesn't seem that important. You are interested in what's returned. Removing the generic D from that definition makes TS infer the return type as you wish it did.

In general, for a function of the form

(source: NoInfer<S>, previous?: {source: NoInfer<S>; value: NoInfer<D>}) => D

the return type depends on the input type, because both involve the generic type parameter D. Telling TypeScript not to infer D from the type of value doesn't change this. If you assign a function like (source, previous) => ⋯ where you don't annotate the parameters but rely on contextual typing, TypeScript will decide it needs to defer evaluation of the function type until it knows D, at which point it's too late. Syntactically at least, D depends on the type of previous, which depends on D. That's circular and TypeScript gives up.

Now, it may well be that the function body's return value doesn't actually depend on previous, in which case it's theoretically possible for TypeScript to inspect the function body and determine the return type without needing to know the type of previous. You are imagining that the circularity can be broken inside the implementation of the callback function.

But this does not happen. It's effectively a design limitation of TypeScript. See microsoft/TypeScript#47599 for the wider issue where TypeScript could theoretically improve inference of generic type arguments in the presence of context-sensitive functions. There has been some progress here, as more scenarios are supported, but the fundamental limitation will always remain in some form. TypeScript's inference algorithm does not perform so-called "full unification", as discussed in microsoft/TypeScript#30134, and this is unlikely to change.

That means you either need to work around the problem or give up. For example, if your function actually doesn't depend on previous and you're not actually using previous, then maybe don't make it a parameter?

const foo = linkedSignal({
  //  ^? const foo: number
  source: () => 3,
  computation: (source) => {
    return 3;
  },
})

Or you can include it and annotate its type to the extent you care about it:

const foo= linkedSignal({
  //  ^? const foo: number
  source: () => 3,
  computation: (source, previous?: { source: number }) => {
    return 3;
  },
})

Or you could try to change your call signatures so the circularity is moved over so that the failure doesn't prevent D from being inferred, such as adding a new type parameter constrained to D:

declare function linkedSignal<S, D, E extends D = D>(options: {
  source: () => S;
  computation: (source: NoInfer<S>, previous?: { source: NoInfer<S>; value: E }) => D;
}): D;

const foo = linkedSignal({
  //    ^? const foo: number;
  source: () => 3,
  computation: (source, previous) => { 
    // ---------------> ^? {source: number, value: unknown} 
发布评论

评论列表(0)

  1. 暂无评论