Felipe
Felipe

Reputation: 494

2322: is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint

Would someone know why addChildrenDefaultValue works but addChildrenDefaultValue2 throws

Type '(Omit<T, "children"> & { children: ObjectWithChildren<TestObject>[]; })[]' is not assignable to type 'ObjectWithChildren<T>[]'.
  Type 'Omit<T, "children"> & { children: ObjectWithChildren<TestObject>[]; }' is not assignable to type 'ObjectWithChildren<T>'.
    Type 'Omit<T, "children"> & { children: ObjectWithChildren<TestObject>[]; }' is not assignable to type 'T'.
      'Omit<T, "children"> & { children: ObjectWithChildren<TestObject>[]; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'TestObject'.(2322)

? Is there a better way to do this? (I'm using extends cause in my use case it extends more types)


interface TestObject{
  name: string;
  children?: TestObject[] | null;
}

type ObjectWithChildren<T> = T & {children: ObjectWithChildren<T>[]};

function addChildrenDefaultValue(
  taxonomy: TestObject[]
): ObjectWithChildren<TestObject>[] {
  return taxonomy.map(({children, ...props}) => {
    return {...props, children: addChildrenDefaultValue(children ?? [])};
  });
}

function addChildrenDefaultValue2<T extends TestObject>(
  taxonomy: T[]
): ObjectWithChildren<T>[] {
  return taxonomy.map(({children, ...props}) => {
    return {...props, children: addChildrenDefaultValue2(children ?? [])};
  });
}

Upvotes: 1

Views: 354

Answers (2)

Tim
Tim

Reputation: 2912

The reason it throws an error is two fold.

  1. Because your interface references itself, the type T has children of type TestObject[] and not T[] like you were likely wanting. This makes the types potentially incompatible (T could be many things, but TestObject cannot).

  2. The use of map in that way (with a spread, and then the property redefined) is omitting children, and then re-adding it with a non-optional definition. So the type of the recursive type needs to reflect that.

type TestObject<TChild> = {
  name: string;
  children?: TChild[] | null;
}

type ObjectWithChildren<T extends TestObject<T>> = Omit<T, "children"> & 
{children: ObjectWithChildren<T>[]};

function addChildrenDefaultValue2<T extends TestObject<T>>(
  taxonomy: T[]
): ObjectWithChildren<T>[] {
  return taxonomy.map(({children, ...props}) => {
    return {...props, children: addChildrenDefaultValue2<T>(children ?? [])};
  });
}

Upvotes: 1

Shivam Singla
Shivam Singla

Reputation: 2201

Consider the following type

type TestObjectExtended = TestObject & {
    age: number
}

The type of children property in TestObjectExtended is TestObject[] | null | undefined. So, in the function addChildrenDefaultValue2, property children of T will have same type - TestObject[] | null | undefined.

Now in the callback/mapper of map function, children has type TestObject[] | null | undefined instead of T[] | null | undefined. In the recursive call of addChildrenDefaultValue2, children necessarily does not satisfy the constraint of the function as it is TestObject[] (if not undefined/null) but expected is T[].

QED

Upvotes: 1

Related Questions