Misusing TypeScript assertion functions for fun and profit
v5.chriskrycho.com
v5.chriskrycho.com
I raised an issue and Ryan from the TS team gave a good explanation of why this is so, and why it's unlikely to change in the near future (performance penalty of doing the analysis required), but I thought I'd mention it in case anyone is looking to use this feature and might hit the same issues. The TS issue is https://github.com/microsoft/TypeScript/issues/47945 if you'd like to see more details.
For the general case of extend, all of the same applies. To avoid `never`, you can use Omit or perhaps Exclude to handle overwritten field types. Applying all of that to this simple extend example:
function extend<T extends object, U extends object>(
value: T,
extension: U
): Omit<T, keyof U> & U {
return Object.assign(value, extension);
}
The resulting type may be unwieldy, but you can tame it with a Merge type: type Merge<T> = {
[K in keyof T]: T[K];
}
// then change the return type to:
// Merge<Omit<T, keyof U> & U>Yes! The article says as much in both the introduction and the caveats at the end! The only reason to reach for this is: cases where for some reason you cannot change the existing API.
As for the `extend` type: you’re right that can make it "work" that way by stomping the existing field, which matches the actual behavior of the thing at runtime, but it also requires you to write it as an assignment to get that behavior, which necessarily perturbs any existing uses of the code. Instead of this:
extend(prev, { someValue });
You have to write this: let result = extend(prev, { someValue });
The whole point here is to avoid doing that in the case of an API which you need to represent and cannot require consumers to change. That’s often the undesirable reality that library maintainers find themselves in, especially when converting an old JavaScript library to TypeScript.My point in saying so in that statement wasn’t to focus on the unnecessary mutation! It was to focus on the perfectly legitimate use case for assertion types in attempting to address it.
All of the rest is a fair point, and I probably did lean too hard on the mutation point. My intent in taking it further wasn’t to belabor that point, but to show that the merge type is solvable.
In hindsight I should have kept the assertion signature and focused on the solution to `never` (and the resulting unwieldy type), because I think that could be genuinely useful for many people.
Also in hindsight, I should have taken the time to say that I have attempted the exact same solution to the same problem for a different stateful API (in my case, Node vm.*Module initialization states), and reached the same conclusions.
My intent wasn’t to criticize. Nor to dispute anything in the article other than doubting the legitimacy of the approach. Apologies for failing to communicate that effectively.