Skip to content

Invalid "could be instantiated with a different subtype of constraint" error #35858

Closed

Description

TypeScript Version: 3.7.2, also tested in nightly (3.8.0-dev.20191224)

Search Terms:
"is assignable to the constraint of type", "could be instantiated with a different subtype of constraint"

Code

function test<T extends {accepted: boolean}>(cb: (value: T) => void) {
  return (data: Omit<T, 'accepted'>) => cb({...data, accepted: true});
}

Expected behavior:
No error

Actual behavior:

Argument of type 'Pick<T, Exclude<keyof T, "accepted">> & { accepted: true; }' is not assignable to parameter of type 'T'.
  'Pick<T, Exclude<keyof T, "accepted">> & { accepted: true; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{ accepted: boolean; }'.

Playground Link: http://www.typescriptlang.org/play/?ts=3.8.0-dev.20191224&ssl=1&ssc=1&pln=4&pc=1#code/GYVwdgxgLglg9mABFApgZygHgCqJQD1TABM1EBvAQwghQAdViAuRAIzjgBsVKwBfAHwAKCKxZCAbpU4gULbAEpEAXgGIJcGMSXkAUIkQAnFFBCGkQ4pSiUWAeQC2MLNgA0iAOTVaDFMQ8CSqqIokLkAHSRVjbu3vSMLFCGsnwKANy6fLpAA

Related Issues:

Activity

  1. DanielRosenwasser commented on Dec 26, 2019

    @DanielRosenwasser
    Member

    T could be instantiated with { accepted: false; }, couldn't it?

  2. vadzimmaisiuk commented on Dec 28, 2019

    @vadzimmaisiuk
    Author

    T could be instantiated with { accepted: false; }, couldn't it?

    1. I believe that's not the reason, changing accepted: boolean to accepted: true still gives the same error: http://www.typescriptlang.org/play/?ts=3.8.0-dev.20191224&ssl=4&ssc=1&pln=1&pc=1#code/GYVwdgxgLglg9mABFApgZygHgCqJQD1TABM1EBvAQwghQAdViAuZAJxBQF8A+ACggBGLXgDdKAGw4tsASkQBebohFwYxOeQBQiRKxRQQrJL2KUolFgHkAtjCzYANIgDk1WgxTFn3OYsSDecgA6ENNzJzd6RhYodi4ZAG5NTk0gA

    2. Even if it was the reason, this would be incorrect from types point of view:
      cb: (v: T) => void, T extends { accepted: boolean } -> we should be able to call cb with argument of type T & {accepted: true}

  3. RyanCavanaugh commented on Jan 13, 2020

    @RyanCavanaugh
    Member

    TS isn't capable of the higher-order reasoning needed to understand that Exclude<T, k> & { [k]: T[k] } is equivalent to T. You can only make that determination through understanding what Exclude and & actually do at a higher level, but from TS's point of view, Exclude is just a type alias

  4. mrcoles commented on Mar 11, 2020

    @mrcoles

    I find this usage of Omit to be really useful for HOCs, and found it unintuitive that Typescript runs into this design limitation.

    In this example, is there any better approach than just doing a type assertion, e.g.,

    function test<T extends {accepted: boolean}>(cb: (value: T) => void) {
      return (data: Omit<T, 'accepted'>) => {
        const newData = {...data, accepted: true} as T;
        return cb(newData);
      }
    }
  5. dwjohnston commented on Apr 17, 2020

    @dwjohnston

    Is this here the same problem?

    type MyType = {
      somethingFromHoc: string; 
    }
    
    type DerivedType<T extends {}, U extends T> = Omit<U, keyof T>; 
    
    const anotherThing = <P extends MyType>(input: DerivedType<MyType, P>): P =>{
      return {
        ...input,
        somethingFromHoc: "hello"
      };j 
    }
    

    https://www.typescriptlang.org/play/?ssl=15&ssc=1&pln=1&pc=1#code/C4TwDgpgBAsiAq5oF4oG8BQUoGcD2AthMABYCWAdgOYBiAToQBJ4DGAXLsHZVQNxQYAvhgyhIUACIRuANwgATRJAA88KBAAewCBXk50ggDRQAquq069UeAD4oqAPIEywZSeMBrCCDwAzazb8Iix4FDjAUACGFHik0vDk1PZQygAK5tq6+nBKEDYAFJRgAK7AHFKyCrnKOUjGqTYAlBzpyDaY2HTExXQU6FjYUAB0I0WlhgPY+ESkPPRMrBwARCQQADZreEsDgrwAVgLCIkA

    Is easily solved with an as P assertion.

  6. added a commit that references this issue on Apr 19, 2020
  7. inad9300 commented on May 17, 2020

    @inad9300

    I would like to expose a simpler example for which an unexpected error is emitted too, that perhaps helps in tracking down the issue here:

    /*
    Type '"foo"' is not assignable to type 'T'. '"foo"' is assignable to the constraint
    of type 'T', but 'T' could be instantiated with a different subtype of constraint
    '"foo" | "bar"'.
    */
    function f<T extends 'foo' | 'bar'>(t: T = 'foo') {}

    (Playground link.)

  8. jakajancar commented on Jun 24, 2020

    @jakajancar

    Daniel M. (@inad9300)
    I'm pretty sure you want just:

    function f(t: 'foo' | 'bar' = 'foo') {}
    

    Otherwise what happens if T is instantiated with 'bar'?

    function f<'bar'>(t: 'bar' = 'foo')
    

    The error message is super accurate here.

  9. inad9300 commented on Jun 30, 2020

    @inad9300

    Inlining the type as in your first snippet would not allow type reuse, unfortunately.

    Perhaps what should happen is, an error should be issued when the conflicting situation occurs, and not before. With the overall goal of avoiding having to provide generics explicitly as much as possible, I'd be typically writing things like f() or f('bar'). In this case, TypeScript reporting this error before it actually happens hinders type inference, which is one of the best features of the language.

  10. ro7584 commented on Jul 1, 2020

    @ro7584

    There's another solution if you don't want to use as to casting
    Thanks for my workmate😍

    Playground Link

    function test<T>(cb: (value: T & { accepted: boolean }) => void) {
      return (data: T) => cb({...data, accepted: true});
    }
  11. dminkovsky commented on Jul 20, 2020

    @dminkovsky

    This confused me for a long time.

    I expected this should work, but it did not.

    But this did.

    See how the only difference is the extracted props. After messing around for a long time I suspected it was the extracted props, then saw David Johnston (@dwjohnston)'s post on SO that pointed me here. Rough!

  12. artola commented on Nov 10, 2020

    @artola

    Is this here the same problem?

    type MyType = {
      somethingFromHoc: string; 
    }
    
    type DerivedType<T extends {}, U extends T> = Omit<U, keyof T>; 
    
    const anotherThing = <P extends MyType>(input: DerivedType<MyType, P>): P =>{
      return {
        ...input,
        somethingFromHoc: "hello"
      };j 
    }
    

    https://www.typescriptlang.org/play/?ssl=15&ssc=1&pln=1&pc=1#code/C4TwDgpgBAsiAq5oF4oG8BQUoGcD2AthMABYCWAdgOYBiAToQBJ4DGAXLsHZVQNxQYAvhgyhIUACIRuANwgATRJAA88KBAAewCBXk50ggDRQAquq069UeAD4oqAPIEywZSeMBrCCDwAzazb8Iix4FDjAUACGFHik0vDk1PZQygAK5tq6+nBKEDYAFJRgAK7AHFKyCrnKOUjGqTYAlBzpyDaY2HTExXQU6FjYUAB0I0WlhgPY+ESkPPRMrBwARCQQADZreEsDgrwAVgLCIkA

    Is easily solved with an as P assertion.

    David Johnston (@dwjohnston) What about this approach?
    Ryan Cavanaugh (@RyanCavanaugh) Could you please if this is an acceptable way?

    function withOwner(owner: string) {
      return function <T extends { owner: string }>(
        Component: React.ComponentType<T>
      ): React.ComponentType<Omit<T, "owner"> & {owner?: never}> {
        return function (props) {
          const newProps = { ...props, owner };
          return <Component {...newProps} />;
        };
      };
    }

    https://www.typescriptlang.org/play?strictFunctionTypes=false&jsx=1#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgIilQ3wG4AoczAVwDsNgJa4B3YGACwHkXakoAFBF78AXHADOMKMFoBzAJRwA3uThwiMalGY16MRswA8AFThIAHjCS0AJhJVxhfKOKkz5cAL4A+AWvU4AGFcSD5aGHFkdBgAOhDwJhsYEwBPMCRTHwCFKOI4hLDktIyjLhB2UwAaAmd+fB84ADIVOqgAfnE+ADd+XxUA9U1tXToGJjgBMBwwCSVVQMC0Jik4PhYABRmHAF5HWIPpiFmatu8KRaGkLR04I0KkiJUD2PWt44kvOAB6HwvArz-QHkLyUGDpJDBFAwd6zOB7BZwAA2wF6Ei61BAACN+P82m5pLI5BRgXpxswgtCBMpkaikBJTiIoN5xJSYdt5gFhrd-IsjLZUdlLip8ARQcKALJIYTiZQotFeGo8FyytriwJGb4C7pCuAKEmUZa0VbKpC2Nnw1jsbhMgT4CQsVIWfAKARs-WUe7Q2lonbKACcXzaO3tjudPz+P2+cE4wAcEg4EGoSNscBxcEwsiQ5DKInN3vl9L9gacTJDDqdot+pCjMY4cckieTqY4KF6cBQMYhAFEoDgoDnTfn4IWJMWvtXa7H402U2nIZm+JRyOCMnAACIQOSwhyI2goEBIAkeOQBfGSQnyEFUMYGCabuTU-eHxkuLziB87zlXG7GbWNAB1CAIEwWVnyQRU4FNVxWiZLxNX-a8jRNPMH0tNhOGgu0K2dV0Hw9T00PAkNM1sCBRWDUNKwjGtvmjadGyTOd00XbNcz4WwiIPJASOAMiKLLKjw0nOi6wbBMmJbNtIU7VckF7ftB1Qrc1m43j+Joqd6xnST5wzLMgA

  13. anlauren commented on Dec 26, 2020

    @anlauren

    I was digging into that Omit problem because it is quite a limitation to use generic types. I am not sure if it is related, but even without using omit i am discovering something that is not consistent.
    This, does not seem to generate any error:

    type A = {
        "id": string
    }
    
    function test<T extends A>(a: T): T {
        a.id = "other"
        return a
    }

    even if i use test with a more specific subtype of A

    type B = {
        "id": "test"
    }
    
    const b : B = {"id": "test"}
    
    const c = test<B>(b) // here c is of type B, but... does not have the key "id": "test"!

    Which is even going to generate the an error if i then compare the "id" key:

    c.id === "other"; // generates : "This condition will always return 'false' since the types '"test"' and '"other"' have no overlap."

    Not sure if I should raise another issue with this, but it is the same problem with than with Omit. Typescript should not let you call a function with generics if they contain a constant. Or maybe i am missing something basic here?

  14. locked as resolved and limited conversation to collaborators on Mar 2, 2021
  15. RyanCavanaugh commented on Mar 2, 2021

    @RyanCavanaugh
    Member

    Locking since this is garnering a lot of off-topic discussion not really related to the original post

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

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions