TypeScript Version: 3.3.0-dev.20181204
Search Terms: array length type guard spread
Code
const timeString = '09:00';
const date = new Date();
const timeParts = timeString.split(':').map((part) => parseInt(part, 10));
if (timeParts.length > 0) {
date.setHours(...timeParts);
}
Expected behavior: No errors
Actual behavior: TS2556: Expected 1-4 arguments, but got 0 or more.
Playground Link: https://www.typescriptlang.org/play/#src=const%20timeString%20%3D%20'09%3A00'%3B%0D%0Aconst%20date%20%3D%20new%20Date()%3B%0D%0Aconst%20timeParts%20%3D%20timeString.split('%3A').map((part)%20%3D%3E%20parseInt(part%2C%2010))%3B%0D%0Aif%20(timeParts.length%20%3E%200)%20%7B%0D%0A%20%20%20%20date.setHours(...timeParts)%3B%0D%0A%7D%0D%0A
Related Issues: None
You could write a helper type-guard to make sure your array is a matching tuple, for example:
function isTuple<T>(arr: T[], minLength: 1): arr is [T];
function isTuple<T>(arr: T[], minLength: 2): arr is [T, T];
function isTuple<T>(arr: T[], minLength: 3): arr is [T, T, T];
function isTuple<T>(arr: T[], minLength: 4): arr is [T, T, T, T];
function isTuple<T>(arr: T[], minLength: number) {
return arr.length >= minLength;
}
if (isTuple(timeParts, 1)) {
date.setHours(...timeParts);
}
@weswigham Is that "Awaiting More Feedback" directed towards me? Of course there are a couple of workarounds. It would just be nice for the language/type system to be smart enough by itself.
More like "we're waiting for more people to weigh in before we take decisive action in any direction"
I have the same request:
I want a convenient and easy-to-maintain way to support the future branches-exploding.
function fn0(a: any) {
return a
}
function fn1(a: any, b: any) {
return a
}
function fn2(a: any, b: any, c: any) {
return a
}
function fn3(a: any, b: any, c: any, d: any) {
return a
}
function fn4(a: any, b: any, c: any, d: any, e: any) {
return a
}
function fn(...args: any[]) {
switch (args.length) {
case 1:
return fn0(args[0])
case 2:
return fn1(args[0], args[1])
case 3:
return fn2(args[0], args[1], args[2])
case 4:
return fn3(args[0], args[1], args[2], args[3])
case 5:
return fn4(args[0], args[1], args[2], args[3], args[4])
default:
throw Error()
}
}
I expect to refactor fn to the below way:
function fn(...args: any[]) {
switch (args.length) {
case 1:
//Expected 1 arguments, but got 0 or more.
return fn0(...args)
case 2:
return fn1(...args)
case 3:
return fn2(...args)
case 4:
return fn3(...args)
case 5:
return fn4(...args)
default:
throw Error()
}
}
Same issue here:
function rec(arg1: string, ...args:string[]) {
console.log(arg1);
if (!args.length) return;
rec(...args);
}
(parameter) args: string[]
Expected at least 1 arguments, but got 0 or more.
Although in my example there is a simple workaround since i only use one type:
function recWorkaround(arg1: string, arg2?: string, ...args:string[]) {
console.log(arg1);
if (!arg2) return;
recWorkaround(arg2, ...args);
}
I could see this feature providing a lot of safety. And it's not only limited to spread use cases. The way I see it is the array type would be narrowed to a tuple having the number of elements given in the expression. It could only really work given a constant number, but that'd be good enough in a lot of cases. Here's what I think the types would be after narrowing:
if (a.length > 1) {
a; // [any, any, ...any[]]
}
if (a.length >= 1) {
a; // [any, ...any[]]
}
if (a.length === 1) {
a; // [any]
}
if (a.length === 0) {
a; // []
}
if (a.length > 1) {
a; // [any, any, ...any[]]
}
if (a.length < 0) {
a; // never
}
if (a.length === -1) {
a; // never
}
~I also may be stretching or missing something here, but it might also have the consequence of allowing compile time construction of arbitrary length tuples. For example if you were to create a function like:~
function makeArray<T extends number>(n: T) {
const newArr = new Array(n);
if (newArr.length === n) { // checker would need to use the type of `n` to get the length of the tuple type
return newArr;
}
// This should be unreachable but seems out of scope of this issue
}
const tupleOf3 = makeArray(3);
~Provided that T is a number literal type then I _believe_ that the type that could come out of makeArray(3) would be a [any, any, any].~
I was wrong, this isn't possible because the checker knows nothing of the literal being passed in while inside of makeArray.
@bopandrade
Although in my example there is a simple workaround since i only use one type:
function recWorkaround(arg1: string, arg2?: string, ...args:string[]) { console.log(arg1); if (!arg2) return; recWorkaround(arg2, ...args); }
I can call your function like this
recWorkaround(str1, undefined, str2, str3, str4)
I can call your function like this
recWorkaround(str1, undefined, str2, str3, str4)
While this is true it didnt really apply for my case. It was an internal function and this scenario wouldnt happen unless someone purposely did it. In any case, it wouldnt be hard to assert that args should only be truthy if arg2 is truthy.
it wouldnt be hard to assert that args should only be truthy if arg2 is truthy.
That would mean adding a runtime check and a runtime test.
Most helpful comment
I have the same request:
I want a convenient and easy-to-maintain way to support the future branches-exploding.
I expect to refactor
fnto the below way: