How to hint to typescript that a string is consistent across multiple indexing operations

  Kiến thức lập trình

Minimal example of the problem:


const a = {
    'x': {},
    'y': 'y',
};
const b = {
    'x': {},
    'y': 'y',
};
for (const p of ['x', 'y'] as const) {
    a[p] = b[p]; // error string | {} is not assignable to {} & string
}
for (const p of ['x', 'y'] as const) {
    switch (p) {
        case 'x':
            a[p] = b[p]; // no error
            break;
        case 'y':
            a[p] = b[p]; // no error
            break;
    }
}

Typescript sees the type of p as 'x' | 'y' but doesn’t correlate the fact that a and b are being indexed by the same value.

Is there a way to… distribute? the union operation so that essentially the typed expression becomes goes from a['x'|'y'] = b['x'|'y'] to a['x'] = b['x'] | a['y'] = b['y']

I’ve tried generic functions and a host of attempts at coercing the types of the inputs but short of asserting as any nothing seems to work.

I’d like to avoid type as assertions (especially to any) as it adds one more area where type analysis is likely to fail, but I feel like I don’t have any other options other than generating code during build but that’s a rather large additional build step I’d rather not add for something that feels small like this.

A switch statement isn’t a feasible alternative since the actual number of keys is much larger than 2 and is liable to change so this would involve adding many sections of duplicated code throughout my codebase.

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

1

Theme wordpress giá rẻ Theme wordpress giá rẻ Thiết kế website Kho Theme wordpress Kho Theme WP Theme WP

LEAVE A COMMENT