2

I am having trouble using a partial on a generic type:

interface HasName {
  name: string;
}

class Tools<T extends HasName> {
  public create: (params: Partial<T>) => T;
  createWithName = (nameArg: string) => {
    this.create({ name: nameArg })
  }
}

I expect this example to work because T extends HasName should ensure that T will have a name field, and (params: Partial<T>) should match any object with any subset of the keys of T.

However, there is a typescript compilation error on the line this.create({ name: nameArg }):

Argument of type '{ name: string; }' is not assignable to parameter of type 'Partial'.

Can someone help me understand why { name: string } is not assignable to Partial<T>, when it should be based on my logic above?

Thank you in advance.

4
  • What version of typescript are you using? I have no problems with your example using typescript 2.9.2 Commented Jun 29, 2018 at 23:23
  • 1
    @JakeHolzinger reproduces in the playground as is typescriptlang.org/play/… Commented Jun 29, 2018 at 23:36
  • I suspect that the compiler will just not try to reason about mapped types over generic parameters. In your case this should work and there really would be no difference, I'd argue it would actually be more accurate public create: (params: Partial<HasName>) => T; Commented Jun 29, 2018 at 23:38
  • @TitianCernicova-Dragomir you're right, my IDE just didn't complain about the compiler error. Using create: <T extends HasName>(params: Partial<T>) => T; works though, strange issue. Commented Jun 29, 2018 at 23:54

1 Answer 1

2

In short, TypeScript is right, but as long as you're careful with what you use for T, you should be okay to just silence the error with a cast:

this.create({ name: nameArg } as Partial<T>)

As to why TypeScript is right here, it's because you can create types that extend HasName but for which {name: string} isn't a valid object of that type.

The easiest example is just to make a type where name is more specific than just string:

interface HasSpecificName extends HasName {
    name: "alice" | "bob";
}

In which case, {name: nameArg} where nameArg is any string isn't assignable to Partial<HasSpecificName> since string isn't assignable to "alice" | "bob".

Sign up to request clarification or add additional context in comments.

5 Comments

Why does create: <T extends HasName>(params: Partial<T>) => T; work without using as?
In that case, the T in create is local to that function, and will be inferred from the type of the argument given to it. It isn't the same as the T from the definition of Tools anymore. If you're familiar with variable shadowing, this would be the same.
How is the generic type T declared at the class level different than the generic type T declared at the function level? The generic type is declared the same way, I don't understand how there is a difference, but it would be good to know how they are different.
Well both the T in Tools and the one in create would be similar in that they both extend HasName, but could differ in how they each implement HasName. For example, you could write something like const myTools: Tools<HasSpecificName> = new Tools(), and then go on to write myTools.create({name: "charlie"}) without an error, even though {name: "charlie"} isn't assignable to HasSpecificName.
Great answer! Thanks for explaining and providing the cast solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.