Issue
I have the following code snippet :
// Library code
type Forced = {
name: string;
}
const funcExample = <T extends Forced,>(
values: T[],
defaultValue: (name: string) => T,
): T[] => {
// Do some operations...
const result = values.map( (value) => {
// Yay, I can access my .name attribute on a Generic.
// That's what I want
if (value.name === "wrong_condition_that_leads_to_default") {
const res = defaultValue(value.name)
return res;
}
const res2 = {...value}
return res2;
})
return result;
}
// User code
type Animal = {
name: string,
parent: string,
}
const animals: Animal[] = [
{name: "A", parent: "Y"},
{name: "B", parent: "X"},
{name: "C", parent: "Z"},
]
const myVar = funcExample(
animals,
(name) => {
const t = {
name: name,
parent: "foo",
};
return t;
}
)
// Type of myVar is...
// const myVar: {
// name: string;
// parent: string;
// }[]
// I want it to be...
// const myVar: Animal[]
I have found a solution that consists of calling my generic funcExample
specifying the correct type in the callback, such as...
const myVar = funcExample(
animals,
(name) => {
const t: Animal = {
name: name,
parent: "foo",
};
return t;
}
)
// Now the type is great, automaticly.
// const myVar: Animal[]
Is there a way of having it mapped automaticly ?
The problem I want to solve today is that I want typescript to make the following error impossible to happen : Defining a callback that doesn't respect the signature I have provided in my defaultValue: (name: string) => T
type signature.
EDIT :
For instance, this wouldn't throw an error if I do not implement the key parent: string
. But I want it to throw an error.
const myVar = funcExample(
animals,
(name) => {
const t = {
name: name,
};
return t;
}
)
Solution
The problem is that T
is currently being inferred from the defaultValue
argument, which means that if you return a Forced
then TS will infer that T
is Forced
instead of something narrower like Animal
, regardless of what is passed in as the values
argument.
You want T
to be inferred only from the values
argument and not from the defaulValue
argument. In other words, in the type (name: string) => T
of defaultValue
, you want T
to be a non-inferential type parameter usage. This is the subject of the feature request microsoft/TypeScript#14829.
Currently there is no built-in way to do this, but the GitHub issue presents various user-implemented approaches that work for at least some use cases. One way to do it is to "lower the priority" of the relevant type parameter usage by intersecting it with the empty object type {}
, as mentioned in this comment. That gives you
const funcExample = <T extends Forced>(
values: T[],
defaultValue: (name: string) => (T & {}),
): T[] => ⋯
Or, you could introduce a new type parameter U
constrained to T
, and use U
as a non-inferential version of T
, as mentioned in this comment:
const funcExample = <T extends Forced, U extends T>(
values: T[],
defaultValue: (name: string) => U,
): T[] =>
There are other approaches too, but I'll stop there; interested parties should consult microsoft/TypeScript#14829. Anyway, both of those work for your examples:
const myVarGood = funcExample(
animals,
(name) => {
return { name: name, parent: "foo", };
}
);
// const myVarGood: Animal[]
Now the output is inferred as Animal[]
. And you'll get an error in the desired place as well:
const myVarBad = funcExample(
animals,
(name) => { // error!
return { name: name, };
}
)
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.