Home > Software design >  String extends Function?
String extends Function?

Time:03-08

I ran into a typing problem after upgrading a library. This is the distilled form of the bug:

class Bar {
  id: string = '';
}

declare type ExcludeFunctions<T, K extends keyof T> = T[K] extends Function ? never : (K extends symbol ? never : K);


declare type Baz<T> = {
  [K in keyof T as ExcludeFunctions<T, K>]?: T[K];
};


class Base<T extends Bar> {

  public async foo(input: Baz<T>) {
   //Property 'id' does not exist on type 'Baz<T>'.
    input.id = 'x';
  }
}

Why doesn't Baz have property 'id'? It looks like "string extends Function" evaluates to true, because when I remove this condition the error disappears. But string clearly doesn't extend Function. What is going on here?

CodePudding user response:

I am fairly sure that this is a limitation with generics and the fact that you are losing context when you say that T in Base<T> must extend Bar. When you pass the generic into Baz, it is using mapped types so it cannot compute through the Bar part of T because T could be anything that extends Bar. You can fix this error by changing the type of input to specifically compute Baz<Bar> to Baz<T> & Baz<Bar>:

class Bar {
  id: string = '';
}

type Baz<T> = {
  [K in keyof T as T[K] extends Function ? never : K]?: T[K];
};

class Base<T extends Bar> {

  public async foo(input: Baz<T> & Baz<Bar>) {
    type Debug = Baz<T>;
    //   ^? type Debug = { [K in keyof T as T[K] extends Function ? never : K]?: T[K] | undefined; }

    input.id = 'x'; // no error!
  }
}

Also note how Baz<T & Bar> does not work because that is the same thing as saying Baz<T> because T must extend Bar.

P.S. You shouldn't use declare type as types are already in an ambient context.

TypeScript Playground Link

  • Related