You don't need a Generic for your getName
function and can use a type annotation of Foo
for the input parameter obj
, i.e.:
const getName = (obj: Foo = foo): string => {
return obj.name;
};
This is because in Typescript every complex type (e.g. objects and arrays) is covariant in its members.
In other words: Since TypeScript knows that Foo
here has less required fields and therefore Bar
is a subtype of Foo
any element of type Bar
will be accepted by the getName
function. That is, you can safely use a Bar
anywhere a Foo
is required. (As noted by @DDomen in the comment, function parameter types behave differently)
Let me explain covariance:
Although you assign the type Foo
to obj
it will also work for getName(bar);
because Bar extends Foo
(and thereby Bar
is a subtype of Foo
).
This is allowed because of the way the Typescript engineers set up the type system. They decided that members of objects (i.e. complex types like shapes and arrays) should be allowed to receive its defined type and anything which is a subtype of that type.
In type theory this behavior can be described as shapes being covariant in its members.
Other type systems don't allow such flexibility, i.e. to use a subtype in such a situation. Such type systems would then by called invariant on members of a shape because they'd require exactly the type Foo
and would not allow for this flexibility which TypeScript allows.
Also note that Bar
does not have to explicitly extend Foo
because TypeScript is structurally typed. TypeScript compares the structure of two objects to see whether one is the sub- or supertype of the other. So instead of interface Bar extends Foo
you could also just define Bar
as:
interface Bar {
name: string;
displayName: string;
}
and getName
will still be properly typed with its parameter obj
as Foo
.
When to use generics?
You don't need a generic because you don't want to relate different parts of your function to each other. A valid use of a generic may be if you want to relate your input parameter to your output parameter.
See this TS Playground of your code.
See also this related issue concerning the error you're getting, i.e.
Type 'Foo' is not assignable to type 'T'.
'Foo' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Foo'.(2322)
<: T
– DDomen Nov 21 '21 at 17:52` (`Bar <: Foo` then `Function <: Function`, `<:` means subtype)