Skip to content

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

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
vmajsuk opened this issue Dec 26, 2019 · 13 comments
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@vmajsuk
Copy link

vmajsuk commented Dec 26, 2019

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:

@DanielRosenwasser
Copy link
Member

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

@vmajsuk
Copy link
Author

vmajsuk commented Dec 28, 2019

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}

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Jan 13, 2020
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jan 13, 2020

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

@mrcoles
Copy link

mrcoles commented Mar 11, 2020

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

@dwjohnston
Copy link

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.

swyxio added a commit to typescript-cheatsheets/react that referenced this issue Apr 19, 2020
* Update excluding-props.md

Fix the final HOC snippet by adding type coercion. See this Github issue: microsoft/TypeScript#35858

I also change the return type from ReactNode to JSX.Element - see: https://stackoverflow.com/questions/54905376/type-error-jsx-element-type-null-undefined-is-not-a-constructor-functi


TS Playground for this fix here: https://www.typescriptlang.org/play/?strictFunctionTypes=false&jsx=1&ssl=1&ssc=1&pln=47&pc=49#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgIilQ3wG4AoczAVwDsNgJa4B3YGACwHkXakoAFBF78AXHADOMKMFoBzAJRwA3uThwiMalGY16MRswA8AFThIAHjCS0AJhJVxhfKOKkz5cAL4A+AWvU4AGFcSD5aGHFkdBgAOhDwJhsYEwBPMCRTHwClVUCNJC0dOD0GJjgBMBwwCXEuEHZTABoCZ358HyVxACkAZQANWIBRABskEGSVAPyAehmAQTgYdKQ4NAh+NEM4YAc+NDQkCQkUKFS4ACMkNBRqCVW0jN60GTB4Ww2JWgByeABrWjCJYcFDwTireqNEwtfBtKAdOAAahUcPEsXRXjgAF44CZpoF1rQpHA+CwAArVBw45RwdGxKoQGotOHeOAoBwmCj5dSabTGBJhSbKOmkimMiSYmY+LmBLwyuXkLyUZYZYKgsU1bFTdQjYAANyO4lo1BAVygMtRkmksjkFAVpQM5SCoIENN1BokzJEUG84mdMA1ElyAV5xX8+SMtn12W5KnwBCVsYAskhhOJlO6jl4WjwXOm4YnAkYZlG9TG4Ao7ZRCcTc0hbP6tWxOHXBPgJCxUhZ8AoBP7K5QjI3MxIscoAJyYuFY9ud7twKWkBczYG7SQcCDUEa2S6rTCyJDkIx1huguAjseTpzemcdrvxxfL1cOCQbrc7kEGtlLFZDKA4KAjxPYd9SOS9JWlJ9ODXV9N23XcSgPShyBVVYABEIDkQNtRJFAJjca15ACS13BtRUqDoMpmAwuRXVoPCkC9FwvHEGjA2DHlCj5OBI2jOAAHUIAgTB03oiZszgVt829Lxi1LbIlRreATxopt2G4b0BFne9exogdB1UsSkBnfcPnjadtPnR85mfdc4J3K5EL4ICRFsQyGJM4AzOvFxbznB9IJs6CXzfeDP1WFAfwyP8AJcvg3Mw3CJk87zrJXYK7PfBD9z4IA

* Update excluding-props.md

* Run prettier

* Update excluding-props.md

Co-authored-by: swyx <shawnthe1@gmail.com>
@inad9300
Copy link

inad9300 commented May 17, 2020

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

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

@inad9300
Copy link

inad9300 commented Jun 30, 2020

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.

@ro7584
Copy link

ro7584 commented Jul 1, 2020

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

@dminkovsky
Copy link

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!

@artola
Copy link

artola commented Nov 10, 2020

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

@anlauren
Copy link

anlauren commented Dec 26, 2020

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?

@microsoft microsoft locked as resolved and limited conversation to collaborators Mar 2, 2021
@RyanCavanaugh
Copy link
Member

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

# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests