Typescript 3.7 Partial and is not assignable to type never/undefined

This is a known breaking change introduced in TypeScript 3.5 to prevent unsound writes to indexed access types. It has had some good effects by catching actual bugs, and it's had some unfortunate effects by falsely warning on perfectly safe assignments, as you can see.

The simplest way to get around this is to use a type assertion:

(extract as any)[key] = input[key];
(target as any)[key] = source[key];

There are safer assertions than any, but they are more complicated to express.


If you want to avoid type assertions, you'll need to use some workarounds. For extract(), it suffices to use a generic callback function inside forEach(). The compiler sees the assignment as being both from and to a value of the identical generic type Partial<A>[K], which it allows:

function extract(input: B, keys: Array<keyof A>): Partial<A> {
    const extract: Partial<A> = {};
    keys.forEach(<K extends keyof A>(key: K) => {
        extract[key] = input[key];
    });
    return extract;
}

For assign() that target[key] = source[key] won't work even with a generic key of type K. You're reading from a generic type NonNullable<Partial<A>[K]> and writing to a different generic type B[K]. (I mean "different" in the sense that the compiler doesn't represent them identically; of course they are the same type when you evaluate them.) We can get back the identical type by widening the target variable to Partial<A> (which is fine because every B is also a Partial<A>, if you squint and don't think about mutations).

So I'd do it like this:

function assign(target: B, source: Partial<A>): void {
    const keys = Object.keys(source) as Array<keyof A>;
    const widerTarget: Partial<A> = target;
    keys.forEach(<K extends keyof A>(key: K) => {
        if (typeof source[key] !== "undefined") { // need this check
            widerTarget[key] = source[key];
        }
    });
}

Oh and I added that undefined check because assign(test, { x: "new", y: undefined }) is allowed; the language doesn't really distinguish missing from undefined.


Anyway, those will work as desired. Personally I'd probably just use a type assertion and move on.

Okay, hope that helps; good luck!

Link to code

Tags:

Typescript