'Can there be a type constraint on a function's return type?
For example, how would you type a function that returns an array of objects that are known to have at least one property of a specific type but the objects may or may not also have other properties?
I tried the following type constraint:
/**
* Return an array of elements that have property `a` of type `1`.
* The elements in the array may or may not also have other properties.
*/
function arrFunc<T extends { a: 1 }>(): T[] {
return [ { a: 1, b: 2 }, { a: 1 } ]
}
which doesn't work because for some reason T is allowed to be instantiated
with an arbitrary type unrelated to the constraint. Why is it allowed, i.e. why
does the type constraint not constrain the type?
I could make a generic type with a default type for a function to achieve the desired typing, but it is not very nice since I can't in-line the type and the function declaration, and I also have to write the constraint twice -- once for the actual constraint and second time for the default value.
type TFunc<A extends { a: 1 } = { a: 1 }> = () => A[];
const arrFunc: TFunc = () => {
return [ { a: 1 }, { a: 1, b: 2 } ];
}
Is it not possible to have a type constraint on a return value like you can on arguments?
Solution 1:[1]
There can, but it makes things difficult.
function arrFunc<T extends { a: 1 }>(): T[] { ... }
This is a perfectly valid function declaration with a perfectly valid type. But there can never be interesting values of that type. That's because the caller decides the generics. So when someone calls arrFunc, they pick a type T which extends {a: 1} and then it's the function's job to find an array of that specific T. That is, from inside the function, we have to work with every possible T we could ever encounter, not just the one we'd like to.
Now, "every possible T" in this case is every subtype of { a: 1 }. That includes { a: 1 } itself, it includes { a: 1, b: number }, it includes { a: never }, and (pathologically) it includes never. So if someone calls arrFunc<never>, our function needs to be prepared to handle that. And there aren't many lists whose elements are of type never. Specifically, there's one, and we call it [], the empty list. So the valid implementations for your function are
// Return the only valid value
function arrFunc0<T extends { a: 1 }>(): T[] { return []; }
// Throw an exception
function arrFunc1<T extends { a: 1 }>(): T[] { throw "Oops!"; }
// Loop forever
function arrFunc2<T extends { a: 1 }>(): T[] { return arrFunc2<T>(); }
// Cast through any
function arrFunc3<T extends { a: 1 }>(): T[] { return "lol" as any as T[] }
The first is the only "legitimate" function, in the sense that all of the others either bottom out or circumvent the type checker altogether.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Silvio Mayolo |
