Search code examples
typescripttypescript-types

TypeScript: Narrow type inference fails when destructuring array in function


I have the following object with a const assertion:

const foo = {
  bar: ['a', 'b'],
} as const;

My goal is to write a function that updates the bar array and correctly infers the new type.

I can achieve the intended result when I pass foo.bar into the function:

type Bar = Readonly<string[]>;

function update<T extends Bar>(arr: T) {
  return {
    bar: [...arr, 'c'],
  } as const;
}

const updatedFoo = update(foo.bar);

// Inferred type is as expected:
//
// const updatedFoo: {
//  readonly bar: readonly ["a", "b", "c"];
// }

But I cannot get this to work when I pass in foo itself:

type Foo = Readonly<{ bar: Bar }>;

function update2<T extends Foo>(obj: T) {
  return {
    bar: [...obj.bar, 'c'],
  } as const;
}

const updatedFoo2 = update2(foo);

// Inferred type is too wide:
//
// const updatedFoo2: {
//   readonly bar: readonly [...string[], "c"];
// }

How can I rewrite update2 to correctly infer the type of bar as readonly ["a", "b", "c"]?


Solution

  • The simplest solution would probably to continue using T to represent the type of bar. We can give the parameter obj an object type having a property bar of type T.

    function update2<T extends Bar>(obj: { bar: T }) {
      return {
        bar: [...obj.bar, 'c'],
      } as const;
    }
    
    const updatedFoo2 = update2(foo);
    
    // const updatedFoo2: {
    //     readonly bar: readonly ["a", "b", "c"];
    // }
    

    If you want to use the whole obj type as T, you will have to use a type assertion.

    function update2<T extends Foo>(obj: T) {
      return {
        bar: [...obj.bar as T["bar"], 'c'],
      } as const;
    }
    

    Playground