Skip to content

Infer operator returning container type instead of applied genericΒ #48772

Closed

Description

Bug Report

πŸ”Ž Search Terms

infer, conditional type, generic, alias

πŸ•— Version & Regression Information

  • This changed between versions v4.1.5 and v4.2.3

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

type BaseContext<TConfig> = {
  foo: number;
  config: TConfig;
};

type FullContext<TConfig, TContext> = TContext & BaseContext<TConfig>;

type ExtractAdditionalContext<TContext extends FullContext<any, any>> =
  TContext extends FullContext<any, infer TAdditionalContext>
    ? TAdditionalContext
    : never;

type Config = Record<string, string>;
type AdditionalContext = { customValue: string };

type Context = FullContext<Config, AdditionalContext>;
type ExtractedAdditionalContext = ExtractAdditionalContext<Context>;

declare const additionalContext: AdditionalContext;
const test1: ExtractedAdditionalContext = additionalContext;

πŸ™ Actual behavior

I expected ExtractedAdditionalContext to be AdditionalContext or { customValue: string }, and for test1 to compile without error.

πŸ™‚ Expected behavior

ExtractedAdditionalContext is AdditionalContext & BaseContext<Config> which is the same type as Context. test1 results in a compiler error of:

Type 'AdditionalContext' is not assignable to type 'AdditionalContext & BaseContext<Config>'.
 Type 'AdditionalContext' is missing the following properties from type 'BaseContext<Config>': foo, config

Additional notes

When I remove the config property from BaseContext the issue is resolved.

Activity

  1. RyanCavanaugh commented on Apr 19, 2022

    @RyanCavanaugh
    Member

    Bisected to #42284

  2. RyanCavanaugh commented on Apr 19, 2022

    @RyanCavanaugh
    Member

    If you keep substituting in the expansions of each type and see the operation being performed, this is a correct inference -- as in many cases, there are multiple equally correct inferences that can be made, and as a result of the linked PR we switched from a certain correct inference to a different correct inference. In general it's not safe to assume that inferring onto a T & U will reliably "break apart" a source type which was also constructed through a T & U - this can happen, but is not guaranteed to.

  3. kschat commented on Apr 19, 2022

    @kschat
    Author

    Thanks for the quick reply! I can appreciate how this is a correct inference, but it's certainly surprising. I'm sure a case exists, but I'm having a hard time imaging a time where I'd want the current result.

    I guess it's partially surprising because I wrote FullContext<any, infer TAdditionalContext>, not infer TContext & BaseContext<any>. The syntax suggests that I'm asking the compiler to tell me what was provided to the second generic of the type. Clearly that's not how the infer keyword works, but that begs the question -- can we have that feature? :)

  4. fatcerberus commented on Apr 20, 2022

    @fatcerberus

    The syntax suggests that I'm asking the compiler to tell me what was provided to the second generic of the type.

    Yeah, inference is structural, not nominal, so the type system is always working backwards from the output of the generic to figure out what should go in the infer position. It's a bit like solving for x in f(x, y) = z given fixed y and z - the solution isn't guaranteed to be unique, depending on what f does with its arguments.

    Clearly that's not how the infer keyword works, but that begs the question -- can we have that feature? :)

    I'm not a TypeScript team member, but from what I do know about how the type system works I'm going to say probably not - in many cases the compiler doesn't even know which type alias a type came from, so infer always has to be done structurally.

  5. RyanCavanaugh commented on Apr 20, 2022

    @RyanCavanaugh
    Member

    Nothing's impossible but in general we're not keen to spend time tweaking things in the grey areas of inference - there are always going to be cases where there's a structural inference going on here, so making 6 out of 10 cases work as opposed to 4 out of 10 cases doesn't really change the situation much.

  6. kschat commented on Apr 21, 2022

    @kschat
    Author

    Yeah, inference is structural, not nominal, so the type system is always working backwards from the output of the generic to figure out what should go in the infer position. It's a bit like solving for x in f(x, y) = z given fixed y and z - the solution isn't guaranteed to be unique, depending on what f does with its arguments.

    Thanks, I was missing this context and it explains why this is so difficult.

    Nothing's impossible but in general we're not keen to spend time tweaking things in the grey areas of inference

    That's reasonable and I understand why the team takes that position. I'll close this issue, thanks for you time.

  7. kschat commented on Apr 21, 2022

    @kschat
    Author

    Dave Campion (@dpchamps) came up with a good solution to my problem that I'll post here for others. Instead of inferring the generic, we can omit the keys in BaseContext from FullContext. This can omit too much if AdditionalContext contains keys that are also in BaseContext, but that's not a problem in my case. Here's a working example.

  8. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions