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);