newtests/tuples/test.js (412 lines of code) (raw):
/*
* @flow
*/
import type {Suite} from "flow-dev-tools/src/test/Suite";
const {suite, test} = require('flow-dev-tools/src/test/Tester');
module.exports = (suite(({addFile, addFiles, addCode}) => [
test('$ReadOnlyArray<T> is the supertype for all tuples', [
addCode(`
function tupleLength(tup: $ReadOnlyArray<mixed>): number {
// $ReadOnlyArray can use Array.prototype properties that don't mutate
// it
return tup.length;
}
// Array literals with known types can flow to $ReadOnlyArray
tupleLength([1,2,3]);
tupleLength(["a", "b", "c"]);
// Arrays can flow to $ReadOnlyArray
tupleLength(([1, 2, 3]: Array<number>));
// Tuple types can flow to $ReadOnlyArray
tupleLength(([1,2,3]: [1,2,3]));
// $ReadOnlyArray can flow to $ReadOnlyArray
tupleLength(([1,2,3]: $ReadOnlyArray<number>));
`).noNewErrors(),
addCode(`
const elemCheck =
(tup: $ReadOnlyArray<number>): $ReadOnlyArray<string> => tup;
`).newErrors(
`
test.js:22
22: (tup: $ReadOnlyArray<number>): $ReadOnlyArray<string> => tup;
^^^ Cannot return \`tup\` because number [1] is incompatible with string [2] in array element. [incompatible-return]
References:
22: (tup: $ReadOnlyArray<number>): $ReadOnlyArray<string> => tup;
^^^^^^ [1]
22: (tup: $ReadOnlyArray<number>): $ReadOnlyArray<string> => tup;
^^^^^^ [2]
`,
),
addCode(`
const tupleToArray = (tup: $ReadOnlyArray<number>): Array<number> => tup;
`).newErrors(
`
test.js:26
26: const tupleToArray = (tup: $ReadOnlyArray<number>): Array<number> => tup;
^^^ Cannot return \`tup\` because read-only array type [1] is incompatible with array type [2]. [incompatible-return]
References:
26: const tupleToArray = (tup: $ReadOnlyArray<number>): Array<number> => tup;
^^^^^^^^^^^^^^^^^^^^^^ [1]
26: const tupleToArray = (tup: $ReadOnlyArray<number>): Array<number> => tup;
^^^^^^^^^^^^^ [2]
`,
),
addCode(`
const arrayMethods = (tup: $ReadOnlyArray<number>): void => tup.push(123);
`).newErrors(
`
test.js:30
30: const arrayMethods = (tup: $ReadOnlyArray<number>): void => tup.push(123);
^^^^ Cannot call \`tup.push\` because property \`push\` is missing in \`$ReadOnlyArray\` [1]. [prop-missing]
References:
30: const arrayMethods = (tup: $ReadOnlyArray<number>): void => tup.push(123);
^^^^^^^^^^^^^^^^^^^^^^ [1]
`,
),
]),
test('You can use the $ReadOnlyArray functions', [
addCode(
`function foo(x: [1,2]): string { return x.length; }`
).newErrors(
`
test.js:3
3: function foo(x: [1,2]): string { return x.length; }
^^^^^^^^ Cannot return \`x.length\` because length \`2\` (number) of tuple [1] is incompatible with string [2]. [incompatible-return]
References:
3: function foo(x: [1,2]): string { return x.length; }
^^^^^ [1]
3: function foo(x: [1,2]): string { return x.length; }
^^^^^^ [2]
`,
),
]),
test('The ref that $ReadOnlyArray functions provide is a $ReadOnlyArray', [
addCode(`
function foo(tup: [1,2], arr: Array<number>): void {
tup.forEach((value, index, readOnlyRef) => {
readOnlyRef.push(123);
(readOnlyRef[0]: 1);
});
arr.forEach((value, index, writeableRef) => {
writeableRef.push(123);
});
}
`).newErrors(
`
test.js:6
6: readOnlyRef.push(123);
^^^^ Cannot call \`readOnlyRef.push\` because property \`push\` is missing in \`$ReadOnlyArray\` [1]. [prop-missing]
References:
753: forEach<This>(callbackfn: (this : This, value: T, index: number, array: $ReadOnlyArray<T>) => mixed, thisArg: This): void;
^^^^^^^^^^^^^^^^^ [1]. See lib: [LIB] core.js:753
test.js:7
7: (readOnlyRef[0]: 1);
^^^^^^^^^^^^^^ Cannot cast \`readOnlyRef[0]\` to number literal \`1\` because number literal \`2\` [1] is incompatible with number literal \`1\` [2]. [incompatible-cast]
References:
4: function foo(tup: [1,2], arr: Array<number>): void {
^ [1]
7: (readOnlyRef[0]: 1);
^ [2]
`,
),
]),
test('You can\'t use Array functions', [
addCode(
`function foo(x: [1,2]): number { return x.unshift(); }`
).newErrors(
`
test.js:3
3: function foo(x: [1,2]): number { return x.unshift(); }
^^^^^^^ Cannot call \`x.unshift\` because property \`unshift\` is missing in \`$ReadOnlyArray\` [1]. [prop-missing]
References:
3: function foo(x: [1,2]): number { return x.unshift(); }
^^^^^ [1]
`,
),
]),
test('Arity is enforced bidirectionally', [
addCode(`
function foo(x: [1, 2]): [1] { return x; }
function bar(x: [1]): [1, 2] { return x; }
`).newErrors(
`
test.js:4
4: function foo(x: [1, 2]): [1] { return x; }
^ Cannot return \`x\` because tuple type [1] has an arity of 2 but tuple type [2] has an arity of 1. [invalid-tuple-arity]
References:
4: function foo(x: [1, 2]): [1] { return x; }
^^^^^^ [1]
4: function foo(x: [1, 2]): [1] { return x; }
^^^ [2]
test.js:5
5: function bar(x: [1]): [1, 2] { return x; }
^ Cannot return \`x\` because tuple type [1] has an arity of 1 but tuple type [2] has an arity of 2. [invalid-tuple-arity]
References:
5: function bar(x: [1]): [1, 2] { return x; }
^^^ [1]
5: function bar(x: [1]): [1, 2] { return x; }
^^^^^^ [2]
`,
)
]),
test('The empty array literal is a 0-tuple', [
addCode(`
const foo: [] = [];
(foo: [1]);
`).newErrors(
`
test.js:5
5: (foo: [1]);
^^^ Cannot cast \`foo\` to tuple type because tuple type [1] has an arity of 0 but tuple type [2] has an arity of 1. [invalid-tuple-arity]
References:
4: const foo: [] = [];
^^ [1]
5: (foo: [1]);
^^^ [2]
`,
),
]),
test('It is an error to access a tuple out of bounds', [
addCode('function foo(x: [1,2]): number { return x[2]; }')
.newErrors(
`
test.js:3
3: function foo(x: [1,2]): number { return x[2]; }
^^^^ Cannot get \`x[2]\` because tuple type [1] only has 2 elements, so index 2 is out of bounds. [invalid-tuple-index]
References:
3: function foo(x: [1,2]): number { return x[2]; }
^^^^^ [1]
`,
)
.because('Out of bounds access causes an error'),
addCode('function foo(x: [1,2]): number { return x[-1]; }')
.newErrors(
`
test.js:5
5: function foo(x: [1,2]): number { return x[-1]; }
^^^ Cannot declare \`foo\` [1] because the name is already bound. [name-already-bound]
References:
3: function foo(x: [1,2]): number { return x[2]; }
^^^ [1]
test.js:5
5: function foo(x: [1,2]): number { return x[-1]; }
^^^^^ Cannot get \`x[-1]\` because tuple type [1] only has 2 elements, so index -1 is out of bounds. [invalid-tuple-index]
References:
5: function foo(x: [1,2]): number { return x[-1]; }
^^^^^ [1]
`,
),
]),
test('Unknown key access returns the general type', [
addCode('function foo(x: [1], y: number): string { return x[y]; }')
.newErrors(
`
test.js:3
3: function foo(x: [1], y: number): string { return x[y]; }
^^^^ Cannot return \`x[y]\` because number literal \`1\` [1] is incompatible with string [2]. [incompatible-return]
References:
3: function foo(x: [1], y: number): string { return x[y]; }
^ [1]
3: function foo(x: [1], y: number): string { return x[y]; }
^^^^^^ [2]
`,
),
]),
test('Array literals with known elements can flow to tuples', [
addCode(`
const arr = [1,2,3];
(arr: [1,2,3]);
`).noNewErrors(),
addCode(`
(arr: [1,2]);
(arr: [1,2,3,4]);
`).newErrors(
`
test.js:9
9: (arr: [1,2]);
^^^ Cannot cast \`arr\` to tuple type because array literal [1] has an arity of 3 but tuple type [2] has an arity of 2. [invalid-tuple-arity]
References:
4: const arr = [1,2,3];
^^^^^^^ [1]
9: (arr: [1,2]);
^^^^^ [2]
test.js:10
10: (arr: [1,2,3,4]);
^^^ Cannot cast \`arr\` to tuple type because array literal [1] has an arity of 3 but tuple type [2] has an arity of 4. [invalid-tuple-arity]
References:
4: const arr = [1,2,3];
^^^^^^^ [1]
10: (arr: [1,2,3,4]);
^^^^^^^^^ [2]
`,
).because('Arity is enforced'),
]),
test('Array literals without known elements cannot flow to tuples', [
addCode(`
function foo(arr: Array<number>): [number, number] { return arr; }
`).newErrors(
`
test.js:4
4: function foo(arr: Array<number>): [number, number] { return arr; }
^^^ Cannot return \`arr\` because array type [1] has an unknown number of elements, so is incompatible with tuple type [2]. [invalid-tuple-arity]
References:
4: function foo(arr: Array<number>): [number, number] { return arr; }
^^^^^^^^^^^^^ [1]
4: function foo(arr: Array<number>): [number, number] { return arr; }
^^^^^^^^^^^^^^^^ [2]
`,
),
]),
test('Tuples cannot flow to arrays at the moment', [
addCode(`
function foo(arr: [1,2]): Array<number> { return arr; }
`).newErrors(
`
test.js:4
4: function foo(arr: [1,2]): Array<number> { return arr; }
^^^ Cannot return \`arr\` because tuple type [1] is incompatible with array type [2]. [incompatible-return]
References:
4: function foo(arr: [1,2]): Array<number> { return arr; }
^^^^^ [1]
4: function foo(arr: [1,2]): Array<number> { return arr; }
^^^^^^^^^^^^^ [2]
`,
).because('Tuples are subtypes of arrays'),
]),
test('Destructuring tuple should eat the first few elements', [
addCode(`
const tup: [1,2,3,4] = [1,2,3,4];
const [a, b, ...rest] = tup;
(a: 10);
(b: 20);
(rest: [3,40]);
`).newErrors(
`
test.js:6
6: (a: 10);
^ Cannot cast \`a\` to number literal \`10\` because number literal \`1\` [1] is incompatible with number literal \`10\` [2]. [incompatible-cast]
References:
4: const tup: [1,2,3,4] = [1,2,3,4];
^ [1]
6: (a: 10);
^^ [2]
test.js:7
7: (b: 20);
^ Cannot cast \`b\` to number literal \`20\` because number literal \`2\` [1] is incompatible with number literal \`20\` [2]. [incompatible-cast]
References:
4: const tup: [1,2,3,4] = [1,2,3,4];
^ [1]
7: (b: 20);
^^ [2]
test.js:8
8: (rest: [3,40]);
^^^^ Cannot cast \`rest\` to tuple type because number literal \`4\` [1] is incompatible with number literal \`40\` [2] in index 1. [incompatible-cast]
References:
4: const tup: [1,2,3,4] = [1,2,3,4];
^ [1]
8: (rest: [3,40]);
^^ [2]
`,
),
]),
test('$TupleMap should still work as a lower bound', [
addCode(`
function foo(arr: $TupleMap<[number, number], number => string>): [1, 2] {
return arr;
}
`).newErrors(
`
test.js:5
5: return arr;
^^^ Cannot return \`arr\` because string [1] is incompatible with number literal \`1\` [2] in index 0. [incompatible-return]
References:
4: function foo(arr: $TupleMap<[number, number], number => string>): [1, 2] {
^^^^^^ [1]
4: function foo(arr: $TupleMap<[number, number], number => string>): [1, 2] {
^ [2]
test.js:5
5: return arr;
^^^ Cannot return \`arr\` because string [1] is incompatible with number literal \`2\` [2] in index 1. [incompatible-return]
References:
4: function foo(arr: $TupleMap<[number, number], number => string>): [1, 2] {
^^^^^^ [1]
4: function foo(arr: $TupleMap<[number, number], number => string>): [1, 2] {
^ [2]
`,
),
]),
test('$TupleMap should still work as a upper bound', [
addCode(`
function foo(arr: [number, number]): $TupleMap<[number, number], number => string> {
return arr;
}
`).newErrors(
`
test.js:5
5: return arr;
^^^ Cannot return \`arr\` because number [1] is incompatible with string [2] in index 0. [incompatible-return]
References:
4: function foo(arr: [number, number]): $TupleMap<[number, number], number => string> {
^^^^^^ [1]
4: function foo(arr: [number, number]): $TupleMap<[number, number], number => string> {
^^^^^^ [2]
test.js:5
5: return arr;
^^^ Cannot return \`arr\` because number [1] is incompatible with string [2] in index 1. [incompatible-return]
References:
4: function foo(arr: [number, number]): $TupleMap<[number, number], number => string> {
^^^^^^ [1]
4: function foo(arr: [number, number]): $TupleMap<[number, number], number => string> {
^^^^^^ [2]
`,
),
]),
test('Tuple elements cannot be subtyped', [
addCode(`
function foo(tup: [1, 2]): [number, number] {
return tup;
}
`).newErrors(
`
test.js:5
5: return tup;
^^^ Cannot return \`tup\` because number literal \`1\` [1] is incompatible with number [2] in index 0. [incompatible-return]
References:
4: function foo(tup: [1, 2]): [number, number] {
^ [1]
4: function foo(tup: [1, 2]): [number, number] {
^^^^^^ [2]
test.js:5
5: return tup;
^^^ Cannot return \`tup\` because number literal \`2\` [1] is incompatible with number [2] in index 1. [incompatible-return]
References:
4: function foo(tup: [1, 2]): [number, number] {
^ [1]
4: function foo(tup: [1, 2]): [number, number] {
^^^^^^ [2]
`,
),
]),
test('Fresh array -> tuple can subtype', [
addCode(`
var arr: [number | string] = [123];
`).noNewErrors(),
]),
test('instanceof Array works for tuples', [
addCode(`
function foo(tup: ?[number, number]): number {
if (tup instanceof Array) {
return tup[3];
} else {
return 123;
}
}
`)
.newErrors(
`
test.js:6
6: return tup[3];
^^^^^^ Cannot get \`tup[3]\` because tuple type [1] only has 2 elements, so index 3 is out of bounds. [invalid-tuple-index]
References:
4: function foo(tup: ?[number, number]): number {
^^^^^^^^^^^^^^^^ [1]
`,
),
]),
]): Suite);