joost
joost

Reputation: 129

How do I typecast in an overloaded function with generics in TypeScript

In a library I'm working on, I have a method that ensures something is of the type IList, and if it isn't, it should turn it into an instance of type IList. See the code below:

1    import { IList, isList } from './list';
2
3    import Unit      from './unit';
4    import ArrayList from './array_list';
5
6    export default function factory<V,I>(obj: IList<V,I>): IList<V,I>;
7    export default function factory<V>(obj: V[]): IList<V,number>;
8    export default function factory<V>(obj: V): IList<V,number> {
9      if(isList(obj)) return obj;
10     if(Array.isArray(obj)) return new ArrayList(obj);
11     return new Unit(obj);
12   }

This method fails to compile, see the errors below:

src/factory.ts(9,30): 2322 Type 'V' is not assignable to type 'IList<V, number>'.
  Property 'has' is missing in type '{}'.
src/factory.ts(10,51): 2345 Argument of type 'V' is not assignable to parameter of type '{}[]'.
  Property 'length' is missing in type '{}'.
src/factory.ts(11,14): 2322 Type 'Unit<{}>' is not assignable to type 'IList<V, number>'.
  Types of property 'get' are incompatible.
    Type '(id: number) => {}' is not assignable to type '(id: number) => V'.
      Type '{}' is not assignable to type 'V'.

I'm unsure how to fix this: of course, I can simply state the return type of the method to be any, but this is unacceptable as it will result into typing issues elsewhere.

Does anyone know how I should continue?

Upvotes: 0

Views: 559

Answers (1)

Ryan Cavanaugh
Ryan Cavanaugh

Reputation: 221212

Remember that the implementation signature is not visible. When you have a set of overloads on a method, only the signatures that aren't the implementation are externally seen:

// Visible to callers
export default function factory<V,I>(obj: IList<V,I>): IList<V,I>;
// Visible to callers
export default function factory<V>(obj: V[]): IList<V,number>;
// *Not* visible to callers
export default function factory<V>(obj: V): IList<V,number> {
  if(isList(obj)) return obj;
  if(Array.isArray(obj)) return new ArrayList(obj);
  return new Unit(obj);
}

That last signature needs to be its own overload, like this.

// Visible to callers
export default function factory<V,I>(obj: IList<V,I>): IList<V,I>;
// Visible to callers
export default function factory<V>(obj: V[]): IList<V,number>;
// Visible to callers
export default function factory<V>(obj: V): IList<V,number>;
// Not seen by callers, so 'any' does not leak out
export default function factory(obj: any): any {
  if(isList(obj)) return obj;
  if(Array.isArray(obj)) return new ArrayList(obj);
  return new Unit(obj);
}

Since callers can't see the implementation signature anyway, it's a good time to use any because the typechecking is going to be more of a nuisance than a help.

Upvotes: 2

Related Questions