How to fix TS2322: "could be instantiated with a different subtype of constraint 'object'"? How to fix TS2322: "could be instantiated with a different subtype of constraint 'object'"? typescript typescript

How to fix TS2322: "could be instantiated with a different subtype of constraint 'object'"?


Complementing @fetzz great answer.


SHORT ANSWER

TLDR; There are two common causes for this kind of error message. You are doing the first one (see below). Along with the text, I explain in rich detail what this error message wants to convey.

CAUSE 1: In typescript, a concrete instance is not allowed to be assigned to a type parameter. Following you can see an example of the 'problem' and the 'problem solved', so you can compare the difference and see what changes:

PROBLEM

const func1 = <A extends string>(a: A = 'foo') => `hello!` // Error!const func2 = <A extends string>(a: A) => {    //stuff    a = `foo`  // Error!    //stuff}

SOLUTION

const func1 = <A extends string>(a: A) => `hello!` // okconst func2 = <A extends string>(a: A) => { //ok    //stuff    //stuff}

See in: TS Playground

CAUSE 2: Although you are not doing the below error in your code. It is also a normal circumstance where this kind of error message pops up. You should avoid doing this:

Repeat (by mistaken) the Type Parameter in a class, type, or interface.

Don't let the complexity of the below code confuse you, the only thing I want you to concentrate on is how the removing of the letter 'A' solves the problem:

PROBLEM:

type Foo<A> = {    //look the above 'A' is conflicting with the below 'A'    map: <A,B>(f: (_: A) => B) => Foo<B>}const makeFoo = <A>(a: A): Foo<A> => ({   map: f => makeFoo(f(a)) //error!})

SOLUTION:

type Foo<A> = {    // conflict removed    map: <B>(f: (_: A) => B) => Foo<B>}const makeFoo = <A>(a: A): Foo<A> => ({   map: f => makeFoo(f(a)) //ok})

See in: TS Playground


LONG ANSWER


UNDERSTANDING THE ERROR MESSAGE

Following I'll decompose each element of the error message below:

Type '{}' is not assignable to type 'P'.  '{}' is assignable to the constraint of type 'P', but 'P' could be instantiated with a different subtype of constraint'object'

WHAT IS TYPE {}

It's a type that you can assign anything except null or undefined. For example:

type A = {}const a0: A = undefined // errorconst a1: A = null // errorconst a2: A = 2 // okconst a3: A = 'hello world' //okconst a4: A = { foo: 'bar' } //ok// and so on...

See in: TS Playground


WHAT IS is not assignable

To assign is to make a variable of a particular type correspond to a particular instance. If you mismatch the type of the instance you get an error. For example:

// type string is not assignable to type number const a: number = 'hello world' //error// type number is assinable to type numberconst b: number = 2 // ok

WHAT IS A different subtype

Two types are equals: if they do not add or remove details in relation to each other.

Two types are different: if they are not equal.

Type A is a subtype of type S: if A adds detail without removing already existent detail from S.

type A and type B are different subtypes of type S: If A and B are subtypes of S, but A and B are different types. Said in other words: A and B adds detail to the type S, but they do not add the same detail.

Example: In the code below, all the following statements are true:

  1. A and D are equal types
  2. B is subtype of A
  3. E is not subtype of A
  4. B and C are different subtype of A
type A = { readonly 0: '0'}type B = { readonly 0: '0', readonly foo: 'foo'}type C = { readonly 0: '0', readonly bar: 'bar'}type D = { readonly 0: '0'}type E = { readonly 1: '1', readonly bar: 'bar'}
type A = numbertype B = 2type C = 7type D = numbertype E = `hello world`
type A = booleantype B = truetype C = falsetype D = booleantype E = number

NOTE: Structural Type

When you see in TS the use of type keyword, for instance in type A = { foo: 'Bar' } you should read: Type alias A is pointing to type structure { foo: 'Bar' }.

The general syntax is: type [type_alias_name] = [type_structure].

Typescript type system just checks against [type_structure] and not against the [type_alias_name]. That means that in TS there's no difference in terms of type checking between following: type A = { foo: 'bar } and type B = { foo: 'bar' }. For more see: Official Doc.


WHAT IS constraint of type 'X'

The Type Constraint is simply what you put on the right side of the 'extends' keyword. In the below example, the Type Constraint is 'B'.

const func = <A extends B>(a: A) => `hello!`

Reads: Type Constraint 'B' is the constraint of type 'A'


WHY THE ERROR HAPPENS

To illustrate I'll show you three cases. The only thing that will vary in each case is the Type Constraint, nothing else will change.

What I want you to notice is that the restriction that Type Constraint imposes to Type Parameter does not include different subtypes. Let's see it:

Given:

type Foo         =  { readonly 0: '0'}type SubType     =  { readonly 0: '0', readonly a: 'a'}type DiffSubType =  { readonly 0: '0', readonly b: 'b'}const foo:             Foo         = { 0: '0'}const foo_SubType:     SubType     = { 0: '0', a: 'a' }const foo_DiffSubType: DiffSubType = { 0: '0', b: 'b' }

CASE 1: NO RESTRICTION

const func = <A>(a: A) => `hello!`// call examplesconst c0 = func(undefined) // okconst c1 = func(null) // okconst c2 = func(() => undefined) // okconst c3 = func(10) // okconst c4 = func(`hi`) // okconst c5 = func({}) //okconst c6 = func(foo) // okconst c7 = func(foo_SubType) //okconst c8 = func(foo_DiffSubType) //ok

CASE 2: SOME RESTRICTION

Note below that restriction does not affect subtypes.

VERY IMPORTANT: In Typescript the Type Constraint does not restrict different subtypes

const func = <A extends Foo>(a: A) => `hello!`// call examplesconst c0 = func(undefined) // errorconst c1 = func(null) // errorconst c2 = func(() => undefined) // errorconst c3 = func(10) // errorconst c4 = func(`hi`) // errorconst c5 = func({}) // errorconst c6 = func(foo) // okconst c7 = func(foo_SubType) // ok  <-- Allowedconst c8 = func(foo_DiffSubType) // ok <-- Allowed

CASE 3: MORE CONSTRAINED

const func = <A extends SubType>(a: A) => `hello!`// call examplesconst c0 = func(undefined) // errorconst c1 = func(null) // errorconst c2 = func(() => undefined) // errorconst c3 = func(10) // errorconst c4 = func(`hi`) // errorconst c5 = func({}) // errorconst c6 = func(foo) // error <-- Restricted nowconst c7 = func(foo_SubType) // ok  <-- Still allowedconst c8 = func(foo_DiffSubType) // error <-- NO MORE ALLOWED !

See in TS playground


CONCLUSION

The function below:

const func = <A extends Foo>(a: A = foo_SubType) => `hello!` //error!

Yields this error message:

Type 'SubType' is not assignable to type 'A'.  'SubType' is assignable to the constraint of type 'A', but 'A'could be instantiated with a different subtype of constraint 'Foo'.ts(2322)

Because Typescript infers A from the function call, but there's no restriction in the language limiting you to call the function with different subtypes of 'Foo'. For instance, all function's call below are considered valid:

const c0 = func(foo)  // ok! type 'Foo' will be infered and assigned to 'A'const c1 = func(foo_SubType) // ok! type 'SubType' will be inferedconst c2 = func(foo_DiffSubType) // ok! type 'DiffSubType' will be infered

Therefore assigning a concrete type to a generic Type Parameter is incorrect because in TS the Type Parameter can always be instantiated to some arbitrary different subtype.

Solution:

Never assign a concrete type to a generic type parameter, consider it as read-only! Instead, do this:

const func = <A extends Foo>(a: A) => `hello!` //ok!

See in TS Playground


That error is warning you, that your Generic Type P can't be assigned to {}, since the Generic Type P can be a more defined, or restricted, to a particular type that can conflict with the default value.

That means that the value {} can't satisfy all the possible Types that can be used by the Generic Type P.

Let's create another example with only booleans that should be easier to understand:

interface OnlyBoolIdentityInterface<T> {  (arg: T): T;}function onlyBoolGeneric<T extends boolean>(arg: T = false): T {  return arg;}

if you define a Type that is more specific than a boolean for example:

type TrueType = true;

and if you specialised the function OnlyBoolIdentityInterface to only support true values like this:

const onlyTrueIdentity: OnlyBoolIdentityInterface<TrueType> = onlyBoolGeneric;

even if TrueType respects the constraint set by T extends boolean the default value arg: T = false is not a TrueType.

This is the situation is what the error is trying to convey to you.

So how can you fix this type of errors?

  1. Or you remove the default value
  2. Or T needs to extend the specialised type of the default param that on my example is false
  3. Or T can interfere directly with params that receive default params

For more context about this error message see the issue that suggested this error message https://github.com/Microsoft/TypeScript/issues/29049.


A bit shorter explanation.

Example that throws error:

type ObjectWithPropType<T> = {prop: T};// Mind return type - Tconst createCustomObject = <T extends ObjectWithPropType<any>>(prop: any): T => ({ prop });type CustomObj = ObjectWithProp<string> & { id: string };const customObj = createCustomObj<CustomObj>('value'); // Invalid// function will only ever return {prop: T} type.

The problem here is that the return object will only ever match the attribute prop and not any other attribute. Extending the ObjectWithPropType gives a false sense of type constraint. This example is all in all a wrong approach it was used just for illustration to show actual conflict in object attributes.

How to constrain subtype in create function:

type StringPropObject = ObjectWithPropType<string>const createCustomObject = <T>(prop: T extends ObjectWithPropType<infer U> ? U : T): ObjectWithPropType<T> => ({ prop });const stringObj = createCustomObject<StringPropObject>('test');

In this case, the function requires the argument to be a string. The object only has prop attribute and function do return required shape.