Typescript: Invalid "could be instantiated with a different subtype of constraint" error

Created on 26 Dec 2019  路  11Comments  路  Source: microsoft/TypeScript


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:

Design Limitation

Most helpful comment

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});
}

All 11 comments

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

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}

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

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);
  }
}

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.

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

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

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.

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});
}

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 @dwjohnston's post on SO that pointed me here. Rough!

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.

@dwjohnston What about this approach?
@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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dlaberge picture dlaberge  路  3Comments

kyasbal-1994 picture kyasbal-1994  路  3Comments

bgrieder picture bgrieder  路  3Comments

seanzer picture seanzer  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments