Skip to content
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

Mapping using distributive object type fails when key is generated in for loop #50147

Closed
willocho opened this issue Aug 2, 2022 · 5 comments
Closed
Labels
Unactionable There isn't something we can do with this issue

Comments

@willocho
Copy link

willocho commented Aug 2, 2022

Bug Report

🔎 Search Terms

Mapped index with enum index
typescript dynamic generic type enum loop

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about distributive object types

⏯ Playground Link

Playground link with relevant code

💻 Code

https://www.typescriptlang.org/play?strictPropertyInitialization=false&jsx=0&ts=4.7.4&ssl=60&ssc=2&pln=27&pc=1#code/KYOwrgtgBAygLgQzsACgJwPYAdhrgTygG8AoKcqAQQCEYoBeKAIhpiYBoyKARAUQDEGzPvw5dyAeQBGAK2ABjOEKYTqAKV4BhACpMSAXxIkCOWImTpsuAgFkEWLAEsQAcyGkKUANpZMOPITOZkioftb4ALoAXFAAqgDOwPGU2vg4ADwIIPgAfAZG8gA2CPHxUKk4dg7OLprFpVCOEFiFwBCgcGXwIZb+tvZOrsTi3t0WYQEAdKzRcYnJFcDpUhgYrVk5ANwjXmOhVlMiswlJKWlL4BBSuFs7e73hk9JyisfzZxlECDGX12ibUCkMRWa2AWX0t0MJCKJTKJwW53S2hyHgoWDAUkKjnkUAAJsAAGYIMCFOAANQQhTAwBi2m2UJhDXhiyqgxc6QA0sBCMAAB7IEC4rrmfZ9XKo8jozHYqAAa25MS5+G2nilWJxADdKdTaedWTU6rCvEqIl4AOT4okk8na4BmiIqtEY9VQRDy+IUqnAIQACg1usqAwN9Xixu5pothOJpM91PtAEoGDliGhgHAwGgQFANfp6UYAPT57iE5yOOCODBZgCMJELcxqroAFo4ypbS+XK1AXI4NUkoFkoLhMGgm0hguMDgQoPIsmalNcoGBErjXRhGoK+VAqsy9UHXLX867zlv7PEd4HqkNGKQ6xQvPLAln7hMCG8kiy9+yH3k6-pdiKHgCCICyLEsQDLCsswAJgPBIGzgZtWzAiDO27XsygHLAEDQBB2mQEc4DXN0kljb0WygEAMCUWFHBcEAEExb1CIo4BezQYxjyqM95g-S83GvHYH3XcdRXCN94l4tl0m-fJoRDKAAHUy0bKpKHiGw2j+YZVWdGUIHsDSrlwGIuPPYB9VcbYRnkSt4jgNAwEUDA0B9fSsEMv4TNPMyLJcRMJU8JsW0mNyPNwIRQs03BHXIKEdOlHEEJbAB9IlHEKeIfXjALPAJZyfVaJQyiCZ9J3wbKRkCihC14NBhygDB5HkDMykbXBgEqqqgviEKDKitAvAw4UehfSJJmIj1bR9JKesioyBqGkTANfSYtS9eNOtikY4qdBLuuSgB3ZzZXiQ7lOSmc7JqLKcooPKXMKqBiqfADRoqrrAsepRGEW0qxRij6oELbREKgI60BOzbAp9dIfM-dITGADACVdHIchm3r3P6rw4AieNxoQd1SOmxDMbCgbcdW20No+wxPEMfQgA

//Enum
enum StateProperty {
    ABS = "ABS",
    DEF = "DEF",
    Object = "OBJECT"
}

//Type definition for mapping type
type StatePropertyMapping = {
    [property in StateProperty]: UsesAType<any>
}

//Actual type mappings
class TypeMappingClass implements StatePropertyMapping {
    [StateProperty.ABS]: UsesAType<boolean>;
    [StateProperty.DEF]: UsesAType<number>;
    [StateProperty.Object]: UsesAType<{a: number; b: boolean}>;
}

class UsesAType<T>{
    public defaultValue: T;
}

class UsesTypeMapping<Key extends StateProperty>{
    public key: Key;
    public value: TypeMappingClass[Key]['defaultValue']; //This is a separate class that wants to know the type mapping for 'Key'
    public takesValue = (v: TypeMappingClass[Key]['defaultValue']) => {return v};
}

//Definition 1
// Using this definition gives an error that 'StateProperty can't be used to index MapUsesTypeMapping'
// type MapsUsesTypeMapping = {
//    [key in StateProperty]: UsesTypeMapping<key>
// }[StateProperty]

//Definition 2
//Using this definition gives an error that 'parameter to takesValue is not assignable to never'
type MapsUsesTypeMapping = {
    [key in StateProperty]: UsesTypeMapping<key>
}

class WithMapAsMember {
    public mapMember: MapsUsesTypeMapping;

    constructor(mapMember: MapsUsesTypeMapping) {
        this.mapMember = mapMember;
    }

    public this_fails(){
        for(let s in StateProperty){
            //Error occurs here
            this.mapMember[s as StateProperty].takesValue(this.mapMember[s as StateProperty].value)
        }
    }

    public this_works_with_casting(){
        for(let s in StateProperty){
            let t = s as StateProperty;
            //This works with definition 2 only
            (<UsesTypeMapping<typeof t>>this.mapMember[t]).takesValue(this.mapMember[t].value)
        }
    }
}

🙁 Actual behavior

If I try to setup the mapping using an enum, the compiler is unable to infer the type mapping based of the union type when the value is generated at runtime (i.e. in a loop)

🙂 Expected behavior

I expect that the compiler will be able to infer the type that takesValue accepts when mapMember is indexed using a specific state property, similar to how it is described in this issue: #47109

@jcalz
Copy link
Contributor

jcalz commented Aug 3, 2022

for (let s in StateProperty) and s as StateProperty doesn't make much sense unless the keys and values of the StateProperty enum are identical, and they aren't. Even if they were, it's sketchy to do this. You should maybe be doing something like for (let s of Object.values(StateProperty)) instead.

Anyway I'm not seeing this as a bug... the way #47109 works is to use generics, and you're not using generics. If you think this should be possible without generics then maybe this is a re-reporting of #30581?

Anyway my refactoring here to use the solution in #47109 would be

Object.values(StateProperty).forEach(
    <S extends StateProperty>(s: S) => {
        this.mapMember[s].takesValue(this.mapMember[s].value)
    }
);

and this is quite close to what you're doing. Is there some version of this which isn't easily translated into a generic forEach() callback?

@willocho
Copy link
Author

willocho commented Aug 3, 2022

No what I'm working on should be easily translated to the forEach callback.

Could you help me understand why it's necessary to typecast the 's' parameter to <S extends StateProperty>(s: S) => ...? I don't understand how the compiler doesn't already know that s is of type StateProperty.

@jcalz
Copy link
Contributor

jcalz commented Aug 3, 2022

There's no typecasting going on there. It's a generic callback function. It's the same as

const that = this;
  
// this is a generic callback function, no type assertions or "casting"
function cb<S extends StateProperty>(s: S) { 
    that.mapMember[s].takesValue(that.mapMember[s].value)
}
   
Object.values(StateProperty).forEach(cb);

@RyanCavanaugh RyanCavanaugh added the Unactionable There isn't something we can do with this issue label Aug 4, 2022
@RyanCavanaugh
Copy link
Member

All this behavior looks right to me. Relevant thread - https://twitter.com/SeaRyanC/status/1544414378383925250

@willocho
Copy link
Author

willocho commented Aug 8, 2022

Thanks for the comments! Closing

@willocho willocho closed this as completed Aug 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Unactionable There isn't something we can do with this issue
Projects
None yet
Development

No branches or pull requests

3 participants