I don’t see how this is an abuse of assertions, it’s just unnecessary mutation and the caveats reveal that. For the specific case of adding a known field, you can return a value with that field added. If you create a new value derived from the old one, you also regain a stable object shape (at the expense of allocation).<p>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:<p><pre><code> function extend<T extends object, U extends object>(
value: T,
extension: U
): Omit<T, keyof U> & U {
return Object.assign(value, extension);
}
</code></pre>
The resulting type may be unwieldy, but you can tame it with a Merge type:<p><pre><code> type Merge<T> = {
[K in keyof T]: T[K];
}
// then change the return type to:
// Merge<Omit<T, keyof U> & U></code></pre>