Skip to content

Type inference works incorrectly when using spread operator over an object property #34558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dmytro-gokun opened this issue Oct 18, 2019 · 9 comments

Comments

@dmytro-gokun
Copy link

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

@dmytro-gokun
Copy link
Author

@AviVahl Wow, that was unexpected :). Do you know of any official work-around (apart of cast)?

@AviVahl
Copy link

AviVahl commented Oct 19, 2019

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

@fatcerberus
Copy link

fatcerberus commented Oct 20, 2019

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.

@dmytro-gokun
Copy link
Author

@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?

@dmytro-gokun
Copy link
Author

@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

@dmytro-gokun
Copy link
Author

But all this is totally unrelated to the original question i've asked and i'm just ruminating on my task at hand :)

@AviVahl
Copy link

AviVahl commented Oct 20, 2019

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants