TypeScript Version: 3.5.1 and up
Code
class Test {
layouts = {
contours: {
'visibility': 'visible',
'line-join': 'round',
'line-cap': 'round'
},
museums: {
'visibility': 'visible'
}
};
toggleLayer(evt: { value: 'contours' | 'museums' }) {
this.layouts[evt.value] = { // <== COMPILATION ERROR HERE
...this.layouts[evt.value],
visibility: this.layouts[evt.value].visibility === 'visible' ? 'none' : 'visible'
};
}
}
Expected behavior: Code compiles
Actual behavior: Code produces a compilation error. Version < 3.5 works fine
Playground Link: http://www.typescriptlang.org/play/index.html?ts=3.5.1#code/MYGwhgzhAEAqCmEAu0DeAoaXrgJ4HsBXJGAXjU2yuHwDskiAnCALgqo+wHIA3ASwh8ARnxB8kuLm14DhIeFwA0lTlS5ja8ALQArfH1pToXRkVoATJStVZ1B7cDAAHIybOXrVAL7KbAW0IIeEI-VnYbbn5BETEJVyi5BU9sL2svAG50awYAcxz5ABkwXHhGAAp4HiQ2VGgeMBBCeGkaeiYILmgAH2MAoJCO6C8ASnDOJAALAQA6PCISAG1KpGn6xvgAXWhyWoB6XegAHlJyAGEAeQBZAAUASQKAQVhb84A5aABRACUv86-oAAS3w+yU403Bkxmc2IECWVVWDSaG18ESwCRi4lwbEhEFmxXmsOWCPWG1WsgxEm2J2M6PknQA-MZaHQFNBpLSkjYMml0F4gA
has to do with this PR: https://github.com/microsoft/TypeScript/pull/30769
here's a simpler example: http://www.typescriptlang.org/play/#code/MYewdgzgLgBCBGArGBeGBvAsAKBjAhgFwY554QgC2ApgNbUCexA5BTTAG74A2ArtcwA0pGAF9huGPGJZJefGBBQAFtQBOAaUYs21Tj37MRonKIDcOHABNqwbvjV7QkWPQYBBFvmYXsNuw5O4NAwbu4gagBCXswwAD4wzPA+ltgIiADaYQC6qHBIWYzu2b7phR65aOgwAHR1ZTlivjgNRRGRlfmZYe0lMAD0-TDqahEtBT1RndV1Na0evU0D-SNj2EA
@AviVahl Wow, that was unexpected :). Do you know of any official work-around (apart of
I'm not even sure this is a bug. I mean, my intuition (aka heuristics of my brain) tells me this should be allowed:
obj[key] = obj[key]
but then, when I think about the error itself, it refers to the types, and obj[key] as a target is an interesting one... especially when key has a union type.
What type should be allowed to be set on obj[key] without knowing whats on the right side of the assignment? That's probably why an intersection is more sound in this case.
I believe that this is a case where using generics is the correct solution:
http://www.typescriptlang.org/play/index.html#code/MYGwhgzhAEAqCmEAu0DeAoaXrgJ4HsBXJGAXjU2yuHwDskiAnCALgqo+wHIA3ASwh8ARnxB8kuLm14DhIeFwA0lTlS5ja8ALQArfH1pToXRkVoATJStVZ1B7cDAAHIybOXrVAL7KbAW0IIeEI-VnYbbn5BETEJVyi5BU9sL2svAG50awYAcxz5ABkwXHhGAB4AaWh4AA8keAsYLhp6JgguaAAfYwCgkPaAPgAKeB4kNlRoHjAQQng2Kq8ASnDOJAALAQA6PCISAG1RpC3p2fgAXWhySYB6G+gy0nIAYQB5AFkABQBJAoBBWDfV4AOWgAFEAEoQ14Q6AACUhYOSnC2qI2212xAghzGJxmc3OvgiWASMXEuDY6IgO2Ke2xRzxZ3OJ1kZIkVyexlJ8g6AH5jLQ6ApoNJuUkbBk0ugvEA
What type should be allowed to be set on obj[key] without knowing whats on the right side of the assignment? That's probably why an intersection is more sound in this case.
Yes, you've hit the nail on the head, this was in fact the exact rationale behind the relevant part of #30769. If key is of a union type, and the compiler is only looking at the types (which it is), we don't know which property will be written through obj[key], so the only thing known to be safe is the intersection of all the types involved.
As for why the case of obj1[key] = obj2[key] (which is trivially typesafe if obj1 and obj2 have the same type) doesn't work, see #31445.
@AviVahl Okay, this is a clever solution, even though i'm not sure why it works :).
In any case, if we think a bit further and try to extend my original example to work for arbitrary number of elements in "layouts" property, this solution also does not work (since it enumerates the keys explicitly).
So, i've tried to build a generic solution here, by adding some typedefs etc.:
http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAaglgZzgIzgGzqKBeKByAN0RTQjygB98A7Ae2rIG4AoZua4CAJwDMBDAMbR4SZKSgBvZlBlQio9JhAAuWMVQZQLAL4s2HbvyFrRpAMK00pAcDj1J02QG0A1hBVQEwLuwDmAXVUREggdPQE0PgQEKAAVCC8HWShIkFoAV2AEIPVzS2tbe1wpZOSBemAMrmyk0rrCdUVQPFUG0zIAGkc65LwMBgBaACtadhb8LgzqABM8Lp7SvvYIAYE+MHG8SfSZvG7S7XmegFt0hAh045qShdk2lCaQTfkQvYXtfd1WZMrfX1IADJ8EDcAAU5SsEBsdmoOXaFkh0PoHSgbg8aNoPBMIQRBRhAEpanUIXj6K53P4cETbgA6OkkqGFajkkD+I63OSNTQeBlI5lo-w0l4aJQ4bC4e5iMhQAD8NHo0tawtIbx6X2SH32PFotFBhJudWAAAtEDTfv8IECQVxQcbTakMlkUXhyhwqgg8PiWBrmNogA
But this does not compile either. I have a feeling i'm missing something very trivial here. Can someone give me hand?
There are several possible ways to go, depending on the API and behavior you are looking for.
Here's one:
http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAaglgZzgIzgGzqKBeKByAN0RTQjygB98A7Ae2rIG4AoZua4CAJwDMBDAMbR4SZKSgBvZlBlQio9JhAAuWMVQZQLAL4tmA+gmBQ0fELQCuwBDknTZBjpa4JVU2R-zyUi0HlWE6qTkfDaORgA09p74GAwAtABWtOz++FyW1AAmeFExMnhxEPECfGBpeBkW2XjRUNp5sgC2FggQFk2udvle6r4gFd5iZFChUOHAddrM0-qmCDYAKhBG3bKm5lZdAEoQBlxZADxGXOwA5hFqoqQAfLYbltZ6HsC0Z2ekADJm3AAUBmhSAJgHB6KpdvsjidzpcRCQIDdLgBrCAqKAo8w8KCgSC0LEPLYASjWMQBQJB9AA2hiALq2dw9WQAOhZZL2FOo1NRNMajLkfU0aLZwNBnNpTKG-Rw2FwgWuIwA-DR6CMAkNgnUPLopqwPDxaLRfsSGTFgAALRBM17vL4-Li-c2WgnWS54RyvCwuPCElhambMIA
I've extracted layouts and let the compiler infer its type. It is later used in keyof typeof layouts.
Note the as const to tell the compiler to not expand the type of that value to string.
@AviVahl Well, your last suggestion would not work well if 'layuots' object is dynamic, e.g. layers might be added/removed on the fly. That what i was trying in achieve in my prev attempt by having 'key' as an arbitrary string here:
interface VisibleCollection {
[key: string]: Visible;
};
Actually, that attempt boils down to an error here:
http://www.typescriptlang.org/play/index.html#code/JYOwLgpgTgZghgYwgAgGrAM7AEYBsUDeAUMqcnAFzLYD2N+cIA3EQL4tEI0gZjIBuVdFjwoAvMmJlyVMFACuEADQky2KgEYATAGY2TIA
But all this is totally unrelated to the original question i've asked and i'm just ruminating on my task at hand :)
It can either be static, which means key types can be inferred as a union, or it's dynamic (any string key is fine), in which case you can have the values type-checked to a specific structure .
The error in that last link is a separate issue. Depending on the type you want, also can be solved in several ways:
interface Visible {
a: boolean;
[otherKeys: string]: number | boolean
};
Probably better to keep further discussion in stack overflow (for future developers).
Most helpful comment
Yes, you've hit the nail on the head, this was in fact the exact rationale behind the relevant part of #30769. If
keyis of a union type, and the compiler is only looking at the types (which it is), we don't know which property will be written throughobj[key], so the only thing known to be safe is the intersection of all the types involved.As for why the case of
obj1[key] = obj2[key](which is trivially typesafe ifobj1andobj2have the same type) doesn't work, see #31445.