英文:
TypeScript conditional type extends keyof but still cannot be used to index type
问题
I wrote custom types in TypeScript using the template literal types to dynamically type the getters / setters of fields. The types look like this (Playground Link):
export type Getters<T> = {
[K in `get${Capitalize<keyof T & string>}`]: K extends `get${infer S}` ? (
S extends keyof T ? () => T[S] : (
Uncapitalize<S> extends keyof T ? () => T[Uncapitalize<S>] : never
)
) : never;
};
export type Setters<T> = {
[K in `set${Capitalize<keyof T & string>}`]: K extends `set${infer S}` ? (
S extends keyof T ? (newValue: T[S]) => void : (
Uncapitalize<S> extends keyof T ? (newValue: T[Uncapitalize<S>]) => void : never
)
) : never;
};
However, I face a type error line 12 (highlighted above), in the Setters<T>
type definition, saying:
Type 'Uncapitalize
' cannot be used to index type 'T'.
I'm a bit confused because I checked that Uncapitalize<S> extends keyof T
just before, and while I might understand why there still is a type error (I don't know but maybe generic types can be different and two calls to Something<T>
in a row might return a different type?), what really surprises me is that it worked a few lines above, in the Getters<T>
type definition... The main question that comes to my mind now is: What is the difference between them? Why is one throwing a type error and not the other?
EDIT (I forgot to mention it the first time): I am aware that I can duplicate the type check inside the type of the parameter (as mentioned by @AntonKastritskiy in his answer), but I would like to avoid this repetition, and furthermore understand what happens here.
By the way, maybe there is a better way to do this (especially to keep a reference to the current keyof T
being iterated, rather than needing to "deconstruct" the generated getter/setter name), but I tried my best to do it alone, as a kind of "exercise," but now I'm open to other improvement suggestions, if any. 😊
英文:
I wrote custom types in TypeScript using the template literal types to dynamically type the getters / setters of fields. The types look like this (Playground Link):
export type Getters<T> = {
[K in `get${Capitalize<keyof T & string>}`]: K extends `get${infer S}` ? (
S extends keyof T ? () => T[S] : (
Uncapitalize<S> extends keyof T ? () => T[Uncapitalize<S>] : never
)
) : never;
};
export type Setters<T> = {
[K in `set${Capitalize<keyof T & string>}`]: K extends `set${infer S}` ? (
S extends keyof T ? (newValue: T[S]) => void : (
Uncapitalize<S> extends keyof T ? (newValue: T[Uncapitalize<S>]) => void : never
// ^^^^^^^^^^^^^^^^^^
)
) : never;
};
However, I face a type error line 12 (highlighted above), in the Setters<T>
type definition, saying:
> Type 'Uncapitalize<S>' cannot be used to index type 'T'.
I'm a bit confused because I checked that Uncapitalize<S> extends keyof T
just before, and while I might understand why there still is a type error (I don't know but maybe generic types can be different and two calls to Something<T>
in a row might return a different type?), what really surprise me is that it worked a few lines above, in the Getters<T>
type definition... The main question that come to my mind now is: What is the difference between them? Why is one throwing a type error and not the other?
EDIT (I forgot to mention it the first time): I am aware that I can duplicate the type check inside the type of the parameter (as mentioned by @AntonKastritskiy in his answer), but I would like to avoid this repetition, and furthermore understand what happens here
By the way, maybe there is a better way to do this (especially to keep a reference to the current keyof T
being iterated, rather than needing to "deconstruct" the generated getter/setter name), but I tried my best to do it alone, as a kind of "exercise", but now I'm open to other improvement suggestion, if any 😊
答案1
得分: 1
I do not know the exact answer to your question. The error only seems to reproduce when the type is used for a parameter but not the return type. You can remove the error by adding one more check to ensure that Uncapitalised<T>
is a keyof T in the parameters, ie
export type Setters<T> = {
[K in `set${Capitalize<keyof T & string>}`]: K extends `set${infer S}` ? (
S extends keyof T ? (newValue: T[S]) => void : (
Uncapitalize<S> extends keyof T ? (newValue: Uncapitalize<S> extends keyof T ? T[Uncapitalize<S>] : never) => void : never
)
) : never;
};
PS: I want to point out that even though the code tries to handle a case when a field can start with an uppercase letter. This still won't handle a case when two cases differ by the casing of their first letter. For example:
type Foo = Setter<{a: number, A: string}> // {setA: string}
英文:
I do not know the exact answer to your question. The error only seem to reproduce when the type is used for a parameter but not the return type. You can remove the error by adding one more check to ensure that Uncapitalised<T> is a keyof T in the parameters, ie
export type Setters<T> = {
[K in `set${Capitalize<keyof T & string>}`]: K extends `set${infer S}` ? (
S extends keyof T ? (newValue: T[S]) => void : (
Uncapitalize<S> extends keyof T ? (newValue: Uncapitalize<S> extends keyof T ? T[Uncapitalize<S>] : never) => void : never
)
) : never;
};
PS: I want to point out that even though the code tries to handle a case when a field can start with an uppercase letter. This still won't handle a case when two cases differ by the casing of their first letter. For example:
type Foo = Setter<{a: number, A: string}> // {setA: string}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论