DEV Community

Pragmatic Maciej
Pragmatic Maciej

Posted on • Edited on

Advanced TypeScript Exercises - Question 2

In this question I will ask you, why TS fails here. And I can say there is a valid reason why such construct is wrong, its not a language bug. Can you spot why, and what is example type which proves TypeScript rightly prevents such code to compile?

type User = {
  id: number;
  kind: string;
};

function makeCustomer<T extends User>(u: T): T {
  // Below error, why?
  return {
    id: u.id,
    kind: 'customer'
  }
}
Enter fullscreen mode Exit fullscreen mode

You can start playing with this code here - Playground link.
Post your answers in comments. Have fun! Answer will be published soon!

This series is just starting. If you want to know about new exciting questions from advanced TypeScript please follow me on dev.to and twitter.

Top comments (6)

Collapse
 
nombrekeff profile image
Keff • Edited

Cool, it took me a little to figure this one out. I've made a Gist to hold the answer as to not spoil other people!

Gist Here

Anser From Gist:

PD: I don't know if it's the correct answer, but it's what I would have done :)

I think it fails because we always expect the output type to be equal to the generic type, although we always return a User.

Typescript tells us that if we pass in a generic type, for example:

createCustomer<{  id: number, kind: string, other: number }>({ 
  id: 1, 
  kind: 'customer' 
});
// >> This gives an error, "Property 'other' is missing in type"
Enter fullscreen mode Exit fullscreen mode

If the above Generic is passed, the return type expects to also contain other property.

So to solve this we could:

  • Pass in all additional parameters to return (preferred)
function makeCustomer<T extends User>(u: T): T {
  return {
    ...u,
    id: u.id,
    kind: 'customer',
  };
}
Enter fullscreen mode Exit fullscreen mode
  • Set the return type always to User
function makeCustomer<T extends User>(u: T): User {
  return {
    id: u.id,
    kind: 'customer',
  };
}
Enter fullscreen mode Exit fullscreen mode
  • Mark the output objects as T
function makeCustomer<T extends User>(u: T): T {
  return {
    id: u.id,
    kind: 'customer',
  } as T;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dwjohnston profile image
David Johnston

I think it fails because we always expect the output type to be equal to the generic type, although we always return a User.

Ah, of course.

Mark the output objects as T

I think this is a bad option, as it's possible that you would be now expecting an extra field to be there, that no longer exists.

Collapse
 
nombrekeff profile image
Keff

I think this is a bad option, as it's possible that you would be now expecting an extra field to be there, that no longer exists.

Agreed, this is why I marked the first option as preferred :P

Collapse
 
macsikora profile image
Pragmatic Maciej

Mangola, link is broken please fix it :)

Collapse
 
nombrekeff profile image
Keff • Edited

Ohh sorry, fixed, I shared the edit link

Collapse
 
mateiadrielrafael profile image
Matei Adriel

I think u could also do a ...u inside the return