2

A bit hard to explain it in the title but basically, I'd like to be able to declare a type array of fixed length strings from 1 to N:

interface Command {
  [key: string]: (args: [string, ...string[]]) => boolean;
}

const cmd: Command = {
  TEST: (args: [string, string]) => args[0] === args[1],
  TEST2: (args: [string]) => args[0] === 'hello'
}

so this doesn't work as [string, string] is different from string[]:

Type '(args: [string]) => boolean' is not assignable to type '(args: [string, ...string[]]) => boolean'.

A solution could be to define like all kind of arguments:

interface Command {
  [key: string]: (args: [string] | [string, string] | [string, string, string]) => boolean;
}

But a bit too verbose for something simple (agreed it can be encapsulated in an interface), anyway is there another elegant solution to this that I'm not seeing?

Thanks,

2
  • No it's not possible. Typescript doesn't supports types with specified string length. But there maybe something that can help you, check out this answer: stackoverflow.com/a/54832231/7616528 Commented Feb 19, 2020 at 0:00
  • Your "verbose" solution doesn't work either, right? It gives the same error. I'm not really sure what you want Command to look like. Can you show how you would plan to use a value c of type Command? Say, you have the function c.f... are you allowed to call it with c.f(["hello"])? Can you call it with c.f(["hello","goodbye"])? Commented Feb 19, 2020 at 2:14

1 Answer 1

2

I suspect that you actually need Command to be generic, so that its properties can actually be used. If you have a function type like (arg: [string] | [string, string]) => boolean, then you cannot implement it with a (arg: [string]) => boolean or with a (arg: [string, string]) => boolean. Function types vary contravariantly in their arguments' types. You can widen, but not narrow, the type of a function argument. Unless you want to require that all methods of type Command must accept every possible string array of length one or more, you have to do something with generics.

Here's a possible type for Command, along with a helper function asCommand() which lets the compiler infer the proper value of T given a value of type Command<T> without forcing you to write it out yourself:

type Command<T> = { [K in keyof T]: (args: T[K]) => boolean }

const asCommand = <T extends Record<keyof T, [string, ...string[]]>>(
    cmd: Command<T>
) => cmd;

Then, your cmd constant can be declared like this with no errors:

const cmd = asCommand({
    TEST: (args: [string, string]) => args[0] === args[1],
    TEST2: (args: [string]) => args[0] === 'hello'
});

and the compiler remembers that cmd.TEST takes a pair and cmd.TEST2 takes a one-tuple:

cmd.TEST(["", ""]); // okay
cmd.TEST([]); // error
cmd.TEST([""]); // error
cmd.TEST(["", "", ""]); // error

cmd.TEST2([""]); // okay
cmd.TEST2([]); // error
cmd.TEST2(["", ""]); // error
cmd.TEST2(["", "", ""]); // error

And you're not allowed to give a property that accepts only zero-length tuples:

const badCmd = asCommand({
    OOPS: (args: []) => false, // error!
})

I hope that gives you some direction; good luck!

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.