Skip to content

Surprising difference in nested calls inference from contextual type #56714

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

Open
Andarist opened this issue Dec 8, 2023 Β· 0 comments
Open

Surprising difference in nested calls inference from contextual type #56714

Andarist opened this issue Dec 8, 2023 Β· 0 comments
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

@Andarist
Copy link
Contributor

Andarist commented Dec 8, 2023

πŸ”Ž 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 πŸ˜…

@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 Dec 14, 2023
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Dec 14, 2023
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

2 participants