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.