This seems to be a limitation of TypeScript when the --strictNullChecks compiler option is off, but I have not found an existing issue in GitHub about it. It is almost universally recommended to turn on --strictNullChecks, so there's not anywhere near as much coverage in GitHub and the documentation of how things behave when it's off.
My analysis here is that a few things are interacting poorly:
When --strictNullChecks is off, null and undefined are considered assignable to almost any type. This almost makes them bottom types like the never type. But, for whatever reason, they are not assignable to the never type. This is a crucial difference (and the thing I might think of as a bug or limitation). Only never is assignable to never:
declare let never: never;
declare let any: any;
declare let v: never;
v = undefined; // error!
v = null; // error
v = any; // error
v = never; // okay
When --strictNullChecks is off, null and undefined are absorbed into unions (like never):
type Foo = string | undefined;
// type Foo = string
When you try to assign a value to obj[key] for a key of a union type, TypeScript requires that the value be assignable to the intersection of the value types for each key in the union, as implemented in microsoft/TypeScript#30769:
interface Bar {
x: number | string;
y: string | boolean;
}
const bar: Bar = { x: 3, y: true };
for (const key of ['x', 'y'] as const) {
bar[key] = 1; // error! number is not assignable to string
bar[key] = "okay"; // okay
}
Putting this all together:
In this code:
for (const key of ['a', 'b'] as const) {
data[key] = undefined;
}
Because data is of type Partial<IData>, with --strictNullChecks on, this intersection would be (number | undefined) & (string | undefined) which evaluates to just undefined. So undefined is the only safe value you can assign to data[key] when key is only known to be "a" or "b". And the above assignment compiles with no error.
But with --strictNullChecks off, (number | undefined) & (string | undefined) collapses to number & string which is just never. And undefined is, for whatever reason, not assignable to never. And the above assignment gives you the error you're facing.
Blah.
So, how can you proceed? Assuming you don't want to turn on --strictNullChecks (which, again, is almost universally recommended), and you don't want to file an issue in GitHub about it (which would probably end up being marked as "Working as Intended" or "Design Limitation", but maybe possibly would be marked as "Bug" which would be low on anyone's priority list to fix), then you can work around it via type assertion. You can just say "undefined is assignable to never here" and the compiler will accept it:
for (const key of ['a', 'b'] as const) {
data[key] = undefined as never; // okay
}
Is this type safe? Well, it's not really any less type safe than any other handling of undefined when --strictNullChecks is off. Type assertions inherently let you do unsafe things, so you are taking some responsibility for safety away from the compiler. But there's not much better that you can do here, unfortunately.
(Well, you know, except for turning on --strictNullChecks. And all of the --strict suite of compiler features, while you're at it. The --strict option is a "standard" level of type which is used in a lot of code bases; if you have a question about TS and --strict or one of its sub-features is not enabled, you will find fewer resources to help you. And the --strict suite is composed of features which have, in real world code bases, solved more problems than they've caused. Features which tend to make things worse for the average code base are kept out, like --noUncheckedIndexedAccess and --exactOptionalPropertyTypes).
Playground link to code