Skip to content

Type inference when using extend in a generic is not consistentΒ #59993

Closed as not planned

Description

πŸ”Ž Search Terms

type inference when extend condition for a generic as const narrow inconsistent

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtVIEYAeAFQD4AKADwC54SBKOkgbgCgA3WeKA+AXkIUA5AAkQECDmEMW8APTz4AZwwwsqAOZs2oSLAQp02PEgBMpeCCoYQqYMpVqNmyrXpN67LjB5mB5iLiktKyCkoARMFSEfBEALTx8ADuABYAniqpOMgQwPAYqQjWtvbwYKlQWgiFCMlQmRjpAA4IWI4aiCAwcMAA-PCiOMnwwDjwAJLwyMo1ReV4wFgm+ABGyBjwALZQANZtm53dvTyOqupafTp60HBIaJi4+IgAzJYldg5OF5rwAD7wVDILarbpuZieVicbhQF4BV5BCRSGRyRTwKJInCxBJJQrteD42oqKBbBBQRxEqCrHAcEAAGng602ymyIyg01mYHJdQykxSOTyKSqmzxjlBlQ4uBg13At0MDxWSAALO8bJ8zs4tABtAC64I8zG8MKV8KVFC1Ykxwh1YTR5xcuriiRSvOUyGAoFQGAgmWWBMcqBwh1QXR6IHy5IWqFUV10soM92MTyQAFZVaUvtScBAQFVdfrGIbob4oCn4SnzYgoBBZjbUUoqzWQI60TiXZlI2AcMovUgcL4szmqmKNoCg33fPatB18KKOQgubM+kA

πŸ’» Code

declare function f1<T>(x: T): T;
var a1 = f1('Hello'); // string

declare function f2<T extends string>(x: T): T;
var a2 = f2('Hello'); // "Hello" <-- why should the extend change the way type is inferred? How do I use the condition but make it inferred as string?

declare function f3<T extends string | number>(x: T): T;
var a3 = f3('Hello'); // "Hello" <-- this is the same as the above, but show a usecase why I would want this behavior

declare function f4<T extends string[]>(x: T): T;
var a4 = f4(['Hello']); // string[] <-- why suddently it is not inferred as const?

declare function f5<T extends boolean[]>(x: T): T;
var a5 = f5([false]); // false[] // <-- why as cosnt for booleans but not for strings in this use case?

πŸ™ Actual behavior

The type inference is not consistent in the above cases, and it's unclear to me (a) what is the logic behind this, and (b) how do I accomplish a different behavior if I need it. More specifically:

  • Why adding extends string means that the type is now inferred as const?
  • Why using an array changes the behavior back to not using as const?
  • Why extends string[] and extends boolean[] behaves so differently? Why booleans gets the const treatment while strings don't?

πŸ™‚ Expected behavior

Since there is a dedicated feature when we want as const for generics, I think that it should be inferred not as const in all of these use cases. This offers the maximum flexibility for consumers as they can pick the behavior they want.

I would also be happy for any work around for this. Let's take this example:

function f<T extends string | number>(value: T): T;

How do I make it so that:

  1. f('somestring') is inferred as string
  2. f<'blue' | 'red'>('red') is inferred as `red'

Also - how do I solve the issue with arrays? Specifically, if I have the function:

function f<T extends any[]>(value: T): T;

Can I make it so that f(['string', true]) will be inferred as [string, boolean] instead of [string, true]?

Additional information about the issue

No response

Activity

  1. RyanCavanaugh commented on Sep 18, 2024

    @RyanCavanaugh
    Member

    I'm confused because you know about const but aren't using it where doing so would give you the result you want.

    Inference is presented with a set of candidates plus a constraint and tries to figure out what you wanted. This talk goes into some examples - it doesn't talk about literals but you can see from that talk why inference makes some decisions but not others.

    Why adding extends string means that the type is now inferred as const?

    Because if you wanted the function to return string when passed "hello", you wouldn't have written the constraint in the first place.

    Why using an array changes the behavior back to not using as const?

    Because the prior argument no longer applies (e.g. you might have a subtype of array that you're specializing to)

    Why booleans gets the const treatment while strings don't?

    Booleans are a union but strings aren't

  2. ShacharHarshuv commented on Sep 18, 2024

    @ShacharHarshuv
    Author

    I'm confused because you know about const but aren't using it where doing so would give you the result you want.

    I'm not sure how I'm supposed to get the result I want using const, as it seems to be the opposite of what I want.

    Imagine this example: function f<T extends number | string>(value: T): T. How do I make it infer f('hello') as string, f(1) as number, and f<'red' | 'blue'>('red') as 'red' | 'blue' ?
    In that case, there is a reason to the constraint - I do want to narrow number | string but I want to narrow it to either number or string.

    The only solution I was able to figure out is using overloads instead:

    function f(value: string): string;
    function f(value: number): number;
    function f(value: string | number): string | number;

    However, this doesn't work for more complicated examples. Consider this:

    function f<T extends (object | boolean)[]>(value: T): T;

    I would expect f([{name: 'hello', true}]) to be inferred as ({name: 'hello'}|boolean)[] but it is inferred as ({name: 'hello'}|true)[].

    Is it possible to type in a way that this would work?

  3. typescript-bot commented on Sep 21, 2024

    @typescript-bot
    Contributor

    This issue has been marked as "Question" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  4. 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

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions