loading...

Advanced TypeScript Exercises - Question 2

macsikora profile image Maciej Sikora Updated on ・1 min read

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'
  }
}

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.

Posted on by:

macsikora profile

Maciej Sikora

@macsikora

I am Software Developer, currently interested in static type languages (TypeScript, Elm, Reason) mostly in the frontend land. I am available for mentoring, I can help with type systems and FP.

Discussion

pic
Editor guide
 

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"

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',
  };
}
  • Set the return type always to User
function makeCustomer<T extends User>(u: T): User {
  return {
    id: u.id,
    kind: 'customer',
  };
}
  • Mark the output objects as T
function makeCustomer<T extends User>(u: T): T {
  return {
    id: u.id,
    kind: 'customer',
  } as T;
}
 

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.

 

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

 

Mangola, link is broken please fix it :)

 

Ohh sorry, fixed, I shared the edit link

 

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