Home > OS >  Template literal inference and narrowing in Typescript
Template literal inference and narrowing in Typescript

Time:12-21

I have the following code:

export type EntityType = "foo" | "bar";
export interface Foo {
  id: `foo-${string}`;
}
export interface Bar {
  id: `bar-${string}`;
}

export type Ent<T extends EntityType> = T extends "foo" ? Foo : Bar;
export const resolve = <T extends EntityType>(id: `${T}-${string}`): Ent<T> => {
  if (id.startsWith("foo-")) {
    return { id: "foo-a" };
  }
  if (id.startsWith("bar-")) {
    return { id: "bar-a" };
  }
  throw new Error(`Unsupported entity type ${id}`);
};

The return statements are throwing an error that says: Type '{ id: "foo-a"; }' is not assignable to type 'Ent<T>'

This happens because TS can't infer that .startsWith("foo-") actually matches the Foo interface, which is understandable.

Is there a way to hint to the compiler so it can correctly infer and narrow down the expected return type? Right now this only works if I cast to any:

if (id.startsWith("foo-")) {
  return { id: "foo-a" } as any;
}

CodePudding user response:

Perhaps you can try

if (id.startsWith("foo-")) {
    return { id: "foo-a" } as Foo as Ent<T>;
}
if (id.startsWith("bar-")) {
    return { id: "bar-a" } as Bar as Ent<T>;
}
  • Related