Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Issue with resolving generic type in Typescript #164

Closed
martinschnurer opened this issue Jun 28, 2020 · 4 comments
Closed

Issue with resolving generic type in Typescript #164

martinschnurer opened this issue Jun 28, 2020 · 4 comments

Comments

@martinschnurer
Copy link

immutability-helper version: 3.0.2

Function updates a parent (id) of some entity. The entity can be Group or Task. Both of these entities have attribute inGroup: string. However, when I tried to define these entities like generics, a typescript error was thrown.

Query

const updateEntityGroup = <T = GroupsMap | TasksMap>(
  entity: T,
  entityId: string,
  parentId: string,
): T => {
  return update(entity, {
    [entityId]: {
      inGroup: parentId,
    },
  });
};

Error:

Argument of type '{ [x: string]: { inGroup: string; }; }' is not assignable to parameter of type 'Spec<T, never>'.
  Property '$apply' is missing in type '{ [x: string]: { inGroup: string; }; }' but required in type '{ $apply: (v: T) => T; }'.ts(2345)

index.d.ts(18, 5): '$apply' is declared here.

Both GroupsMap and TasksMap are maps such as:

  type GroupMap = {
   [id: string]: Group
}

type TasksMap = {
  [id: string]: Task
}

Both Task a Group has attribute inGroup: string.

When I defined update query like this, there was no error, but I lost what type would be returned from the function, which would be very helpful in my case:

const updateEntityGroup = (
  entity: GroupsMap | TasksMap,
  entityId: string,
  parentId: string,
): GroupsMap | TasksMap => {
  return update(entity, {
    [entityId]: {
      inGroup: parentId,
    },
  });
};

Appreciate any help. Thanks

@nathanvogel
Copy link

I also had Property '$apply' is missing in type error, but my query was at fault. Using a static index instead of a dynamic [entityId] one made TS throw a much more helpful error. Also, the OP's query doesn't look like it's correct (no $ commands).

@kolodny
Copy link
Owner

kolodny commented Dec 23, 2020

I suspect this is due to the generic, when I change all instances of T to just GroupsMap | TasksMap it appears to work

@reneegyllensvaan
Copy link

The OP's example does not look like a valid spec since it's missing a $set wrap, but I debugged some issues with a coworker and the typings in general don't seem like they handle generics well. For example:

interface A {a: number; b: number};
function foo<T extends A>(v: T): T {
    return update<T>(v, {a: {$set: 10}});
}

The big conditional type in the first clause of the union in the Spec type seems to resolve to never, and so it complains about the a key. The same function properly resolves if it's changed to be (v: A) => A.

I'm pretty new to typescript and don't fully grok where they try to be sound and where they don't, but I don't know how you'd model this properly without a cascade of variance issues. Updating T[K] with something we know extends A[K] won't be valid (T does not necessarily extend A, so T[K] can be a subtype of A[K]). This relationship can't be properly modeled as long as update is typed as T => T.

We ended up working around it with type assertions, but I suppose specifically for discriminated unions you can make it generic across the discriminant instead, so the union that's passed to update is still properly known as A | B, so:

interface A {
    kind: 'A';
    a: number;
}
interface B {
    kind: 'B';
    a: number;
    b: number;
}
type Foo = A | B;

// Generic on the discriminant instead of the full object type:
function foo<Kind extends 'A' | 'B'>(v: Foo & {kind: Kind}): Foo & {kind: Kind} {
    return update(v, {a: {$set: 10}});
}

@kolodny
Copy link
Owner

kolodny commented Dec 19, 2021

Closing for now, feel free to reopen if more action can be done here

@kolodny kolodny closed this as completed Dec 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants