microsoft / microsoft/TypeScript

Surprising difference in nested calls inference from contextual type

Aperta
#56,714 0 commenti 1 reazione 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Domain: check: Type Inference Help Wanted Possible Improvement
Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
1g 19h
PR unite (30g)
117

Descrizione

🔎 Search Terms

inference nested contextual type reverse mapped type parameter

🕗 Version & Regression Information
  • This is the behavior in every version I tried
⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.4.0-dev.20231208#code/C4TwDgpgBAggxsA9gJwEoQGYB4AqBJAOzAFdgA+KAXigG8AoKKOZCAQ2AgH4AuKACgCWRUr3zDgASioUAbogEATANx0AviroKIcADasWUDMQIIBiAoeSIAtgGEW7CLkIlyfBkwcdeg8aJekUpSy8goANHQSvPBIaJjO4mQaWrr60EYmwGYWAM4QwMRgAIxYHjgxKDlQEAAeHAQKVehwKApYOcDIQgDmYbAIKOjYBBAyEMhkZBFkgtZgOjm89IysA8g5PLQejADaANJQQlAA1hAgiBhQ5Ws5ALqiFev7tyqM6mpRV485Gi0EHVAWDliDpgEUqFA8gVinxllBVrFFltGIwAO4CYAACwCwF4GCsdi8ED4vlcvA6XQI3SCFBoqgkERRUHRWMQpBxeIJ9jYHBJNNo9MZUFUEXpGiBILBKgA9NKoAA9Th0TTaPQGDKmcyQ-KFABMpUY10R1TqEAaTW0rXanR6fUeQywIzGEymdBmAjmCyW8JumyNlWFn3961+5gBEtBuohUL1sI8CMqS22zIx2L8lhs3McJKEZMhNqp-LpDOTLMxbOAHIzhJ5xL4RcFHhFH3FEGBkZlcsVdCAA

💻 Code
type ActorRef<TInput> = {
  create?: (input: TInput) => void;
};

declare function fromCreate<TInput>(
  create: (input: TInput) => void,
): ActorRef<TInput>;

declare function setup1<
  TActors extends Record<string, ActorRef<never>>,
>(impls: {
  actors?: {
    [K in keyof TActors]: TActors[K];
  };
}): TActors;

const result1 = setup1({
  actors: {
    withInput: fromCreate((input: string) => {}),
    withoutInput: fromCreate(() => {}),
  },
});

result1;
// ^? const result1: { withInput: ActorRef<string>; withoutInput: ActorRef<unknown>; }

declare function setup2<
  TActors extends Record<string, ActorRef<never>>,
>(impls: { actors?: TActors }): TActors;

const result2 = setup2({
  actors: {
    withInput: fromCreate((input: string) => {}),
    withoutInput: fromCreate(() => {}),
  },
});

result2;
// ^? const result2: { withInput: ActorRef<string>; withoutInput: ActorRef<never>; }
🙁 Actual behavior

The only difference between those 2 calls is that TActors of the outer setup call is either iterated over (with Identity-like mapped type) or not.

Yet when this "noop" iteration is involved we infer the constraint of the TInput parameter in the nested fromCreate call. When the iteration is not involved then we infer never.

🙂 Expected behavior

I'd expect both of those calls to behave identically.

Additional information about the issue
  1. without the iteration the contextual type for this inner call is ActorRef<never> whereas with the iteration it's TActors["withoutInput"]
  2. without the iteration we successfully inferTypes using InferencePriority.ReturnType from the instantiated contextualType but with the iteration we get silentNeverType for TActors and thus also silentNeverType for the whole indexed access. A silentNeverType is non-inferrable
  3. One of those calls can gather inference candidates (and thus ActorRef<never> gets inferred) and the other one is not (so it defaults to its constraint).

It might not be worth fixing this. I figured out that it might still be interesting to raise this. Maybe a rule similar to the one introduced here could be introduced somewhere? A more specific type (depending on the variance of the type parameter) could be picked up from the constraint type and the type inferred using InferencePriority.ReturnType. I'm not sure if that would work in practice though - just thinking out loud while writing, without giving this more thought 😅

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia con la riproduzione collegata in TypeScript Playground e confronta setup1 con setup2, concentrandoti sui tipi contestuali e sui tipi risultato inferiti mostrati nell’issue. Segui i percorsi menzionati inferTypes, InferencePriority.ReturnType, contextualType e silentNeverType, e rivedi la regola introdotta in PR 56506. Il lavoro è completo quando entrambe le chiamate inferiscono tipi ActorRef equivalenti senza causare regressioni nel comportamento di inferenza correlato.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
compilers
Tipo di issue
Bug
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.