Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type 'string' cannot be used as an indexed type #33808

Closed
ppoulard opened this issue Oct 4, 2019 · 3 comments
Closed

Type 'string' cannot be used as an indexed type #33808

ppoulard opened this issue Oct 4, 2019 · 3 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@ppoulard
Copy link

ppoulard commented Oct 4, 2019

Hi,

I'm working on TS types, and got this error :
error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type

Here is the story : I intend to promisify a type, that is to say take all the members of an interface and turn their type to a Promise, except for undefined.

So far so good, this works very well, and when I generate a proxy, all the expected behaviour works with this :

export type Promisify<T, S = any> = {

    [P in keyof T]:                                         // for each property P in the type T
        T[P] extends (...args: infer A) => undefined        // if it is a function(args) -> undefined
            ? (this: S, ...args: A) => void                 // turn it to function(args) -> void
            : T[P] extends (...args: infer A) => infer R    // if it is a function(args) -> whatever
                ? (this: S, ...args: A) => Promise<R>       // turn it to function(args) -> Promise<whatever>
                : Promise<T[P]>;                            // else turn the field to a Promise
};

With interface Hello { sayHello(user: string): void } I get a promisified type that would contain the method signature sayHello(user: string): Promise<void>.
Usage : await proxy.sayHello('Bob')

Then I intend to let the user to 'depromisify' a member, in the case the return value would be Promise<void> :

export const NOACK = Symbol()

type NoAckPromisify<T, S = any> = {

    [P in keyof T]:                                        
        T[P] extends (...args: infer A) => undefined
            ? (this: S, ...args: A) => void
            : T[P] extends (...args: infer A) => void
                ? ((this: S, ...args: A) => Promise<void>)
                    & { NOACK : (this: S, ...args: A) => void }
                : T[P] extends (...args: infer A) => infer R
                    ? (this: S, ...args: A) => Promise<R>
                    : Promise<T[P]>;
};

The compiler doesn't complain on the type definition, but when I use it : proxy.sayHello[NOACK]('Bob');
I got :

error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '((this: any, msg: string) => Promise<void>) & { NOACK: (this: any, msg: string) => void; }'.
      No index signature with a parameter of type 'string' was found on type '((this: any, msg: string) => Promise<void>) & { NOACK: (this: any, msg: string) => void; }'.

    21             proxy.sayHello[NOACK]('Bob');

I tried the workaround explained here #24587
that is to say to cast the symbol to a string (the message I have) but it still doesn't work.

I'm using
"ts-node": "^8.3.0",
"typescript": "^3.6.3"

Any idea ?
Thanks !

@ahejlsberg ahejlsberg added the Question An issue which isn't directly actionable in code label Oct 4, 2019
@nmain
Copy link

nmain commented Oct 4, 2019

You are missing [ ] around NOACK in your definition of NoAckPromisify<,>. Without them, it's just defining a property with key "NOACK".

@ppoulard
Copy link
Author

ppoulard commented Oct 4, 2019

It seems that T[P] extends (...args: infer A) => void capture every return type and the alternative is never reached :(

Therefore, I push the test later, and this definition is doing the job :

type NoAckPromisify<T, S = any> = {

    [P in keyof T]:                                        
        T[P] extends (...args: infer A) => undefined
            ? (this: S, ...args: A) => void
            : T[P] extends ((...args: infer A) => infer R)            // else if f():R 
                ? ((this: S, ...args: A) => Promise<R>)               // return f():P<R>
                    & (R extends void                                 //      & if R is void
                        ? { [NOACK] : (this: S, ...args: A) => void } //         [NOACK]:f():void
                        : (this: S, ...args: A) => Promise<R>)        //          f():P<R>
                : Promise<T[P]>;                                      // P<R>
};

However when the 'not void' case is evaluated, the result is (this: S, ...args: A) => Promise<R> & (this: S, ...args: A) => Promise<R>

I tried 'never' in the alternative but it collapses to nothing.

Do you think there is a better way ?
Thanks

@typescript-bot
Copy link
Collaborator

This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants