This is a consequence of a type safety improvement to indexed access types introduced in TypeScript 3.5, as implemented by microsoft/TypeScript#30769. In general it is unsafe to allow a[p] = b[q] where a and b are of the same object type and where p and q are of the same union type, since if p and q turn out to be different elements of the union, you might be writing an incompatible value. In your case you are doing a[p] = b[p], but from the type system's perspective that's the same thing; all it sees is that you are writing and reading an object property whose key is a union type "x" | "y", which is unsafe in general. It doesn't pay attention to the fact that if you're using the exact same value p in both places, then it has to be safe.
So since TypeScript 3.5 this has been a pain point. There is a request to fix this for when you are reading or writing the "same" property; see microsoft/TypeScript#32693. And, fortunately, according to this comment it looks like this will be fixed for the case here where you're literally using the same identifier (like p) as the key. Not sure when that will happen, though... the issue seems to be on the Backlog and not slated for a particular release of TypeScript. So it could be a while.
Until then it should be possible to refactor to a generic function, since one place they still allow the older pre-TS-3.5 unsafe access is when you are using generic type. This is mentioned in a comment on #30769:
One rule we've always had is that any given type (and, by extension, any T[K] for identical T and K) is by definition assignable to itself, and that's the basic unsoundness we permit
So if we introduce this indirection:
function copyProp<T, K extends keyof T>(dst: T, src: T, key: K) {
dst[key] = src[key];
}
That compiles just fine, and now we can use it:
for (const p of ['x', 'y'] as const) {
copyProp(a, b, p);
}
which also compiles without error. It's annoying, but at least there is a solution/workaround that works for now, at least until a fix for #32693 is released.
One last thought about wishing this could be fixed in general so you could avoid switch statements. A while ago I opened a feature request microsoft/TypeScript#25051 to allow for "opt-in distributive control flow analysis" where you could say something like type switch (p) {...} and have the compiler evaluate the enclosed code block once for each element of the union type of p, and if each pass succeeded, then the whole thing would succeed. The compiler feasibly can't do that kind of multi-pass analysis for each union-typed expression it encounters, but I was hoping we could at least have some syntax to ask for it in specific cases. Alas, it is not to be (and was closed as a duplicate of one of the several issues it would address), but when I see this issue I become wistful and think of what might have been.... Sigh...
Okay, hope that helps; good luck!
Playground link to code