Skip to content

mapped types break the generic constraintsΒ #46807

Closed

Description

@Bigfish8

Bug Report

πŸ”Ž Search Terms

generic

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

see playground

πŸ™ Actual behavior

no generic constraints

πŸ™‚ Expected behavior

correctly generic constraints

Activity

  1. MartinJohns commented on Nov 15, 2021

    @MartinJohns
    Contributor

    Your code has no constraint, and nothing is broken. The type for T is just inferred to be string | number.

  2. Bigfish8 commented on Nov 15, 2021

    @Bigfish8
    Author

    Thanks for your reply, but why the type for T in first case is not string | number.

  3. tadhgmister commented on Nov 15, 2021

    @tadhgmister

    huh, it looks like it isn't detecting the properties are co-variant in the first case, instead the generic only resolves based on the first property in the declaration. playground

    type Shape1<T> = {
        foo: T,
        bar: T
    }
    type Shape2<T> = {
        bar: T,
        foo: T,
    }
    
    declare function retriveShape1<T>(arg: Shape1<T>):T
    declare function retriveShape2<T>(arg: Shape2<T>):T
    
    
    retriveShape1({ // using vs1 that has `foo` defined first...
        foo: 'text', 
        bar: 1 // produces error on 'bar' saying it should match `foo`
    })
    retriveShape2({ // using vs2 that has `bar` defined first....
        foo: 'text', // produces an error on `foo` saying it should match `bar`.
        bar: 1
    })

    Shouldn't these both just have the generic resolve to number | string since they are co-variant or is something else going on?

  4. RyanCavanaugh commented on Nov 16, 2021

    @RyanCavanaugh
    Member

    This is the intended behavior, though admittedly it's not necessarily obvious why.

    Generic inference can reject inference results that are sound. For example, if you have

    declare function fn<T>(a: T, b: T): void;

    the call

    fn(0, "hello");

    is rejected even though we could have inferred the type argument

    fn<string | number>(0, "hello");

    (side note: please don't use "1" and 1 as your example values, it's confusing πŸ˜…).

    This is done to make generics useful, since otherwise there'd be no way to write a function that rejects the call fn(0, "hello"), even though this is one of the prime raison d'Γͺtres of generics in the first place.

    However, this logic has to eventually stop somewhere, since the function

    declare function fn<T>(arg: T[]): void;

    "should" be callable with a heterogeneous array:

    fn([1, "two"]);

    The question is then, given some call, are we looking at something that looks more like "two arguments with linked types", or are we looking at something that's more like "a legitimately heterogenous array" ? For an object type with simple properties, it's the former. For a mapped type, it's the latter.

    Side note: there are no constraints whatsoever in this example.

  5. Bigfish8 commented on Nov 17, 2021

    @Bigfish8
    Author

    Very appreciated for your detailed reply!

  6. locked as resolved and limited conversation to collaborators on Oct 21, 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