typescripttype-inferencecomputed-properties

Object property won't infer union type created from keyof typeof computed properties


If I create a union type from the keys of an object, I can pass that type around perfectly, as long as the keys are regular strings. But if I use computed properties as keys for the object, Typescript seems to convert my union type into a simple string type when I pass a value of that type into another object. Consider the following:

const FOO = "foo";
const BAR = "bar";

const myObj = {
  [FOO]: "oof",
  [BAR]: "rab",
} as const 

type MyUnion = keyof typeof myObj; // "foo" | "bar"

const myFunc = (arg: MyUnion) => {console.log("arg: ", arg)};

const myFooOrBar: MyUnion = "foo";

myFunc(myFooOrBar);

const newObj = {
  myFooOrBar,
}

myFunc(newObj.myFooOrBar);

Typescript does not like the last line. I would expect all of this to compile no problem. I am allowed to call myFunc(myFooOrBar) without issue, but if create an object with the myFooOrBar property, Typescript complains that I cannot call myFunc(newObj.myFooOrBar) because Argument of type 'string' is not assignable to parameter of type '"foo" | "bar"'.

Here is a sandbox to see for yourself: https://playcode.io/1646157

This is not an issue if I define myObj with regular string keys like so:

const myObj = {
  "foo": "oof",
  "bar": "rab",
} as const 

I can also resolve the issue by using the as keyword and forcing the type when I create newObj:

const newObj = {
  myFooOrBar: myFooOrBar as MyUnion,
}

The second solution is more palatable for me, but not ideal, and I'm still annoyed. What is it about computed properties and object type inference that prevents them from playing nice together? Is it worth creating an issue with Typescript?


Solution

  • This has nothing to do with computed property names. Rather TypeScript widens the type of the property to string in both myObj and newObj so that you could assign other strings - unless you use as const. Your code will work when you do that for newObj as well:

    const newObj = {
      myFooOrBar,
    } as const;
    
    myFunc(newObj.myFooOrBar);
    

    (online demo)