newtests/shadow/test.js (317 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('derived object reads must be compatible with prototype writes', [
addCode(`
var proto = {};
var o = Object.create(proto);
`).noNewErrors(),
// read installs a shadow property on `proto`
addCode(`(o.p: string);`).noNewErrors(),
addCode(`proto.p = 0;`).
newErrors(
`
test.js:8
8: (o.p: string);
^^^ Cannot cast \`o.p\` to string because number [1] is incompatible with string [2]. [incompatible-cast]
References:
10: proto.p = 0;
^ [1]
8: (o.p: string);
^^^^^^ [2]
`,
),
]),
test('derived object reads are independent until prototype is constrained', [
addCode(`
var proto = {};
var a = Object.create(proto);
var b = Object.create(proto);
(a.p: string);
(b.p: number);
`).noNewErrors(),
addCode(`proto.p = true;`)
.newErrors(
`
test.js:7
7: (a.p: string);
^^^ Cannot cast \`a.p\` to string because boolean [1] is incompatible with string [2]. [incompatible-cast]
References:
11: proto.p = true;
^^^^ [1]
7: (a.p: string);
^^^^^^ [2]
test.js:8
8: (b.p: number);
^^^ Cannot cast \`b.p\` to number because boolean [1] is incompatible with number [2]. [incompatible-cast]
References:
11: proto.p = true;
^^^^ [1]
8: (b.p: number);
^^^^^^ [2]
`,
),
]),
test('derived object writes are independent until prototype is constrained', [
addCode(`
var proto = {};
var a = Object.create(proto);
var b = Object.create(proto);
a.p = 0;
b.p = "";
console.log("havoc definite refinements");
`).noNewErrors(),
// *not* string ~> void
addCode(`(a.p: void);`)
.newErrors(
`
test.js:12
12: (a.p: void);
^^^ Cannot cast \`a.p\` to undefined because number [1] is incompatible with undefined [2]. [incompatible-cast]
References:
7: a.p = 0;
^ [1]
12: (a.p: void);
^^^^ [2]
`,
),
// *not* number ~> void
addCode(`(b.p: void);`)
.newErrors(
`
test.js:14
14: (b.p: void);
^^^ Cannot cast \`b.p\` to undefined because string [1] is incompatible with undefined [2]. [incompatible-cast]
References:
8: b.p = "";
^^ [1]
14: (b.p: void);
^^^^ [2]
`,
),
// TODO: This should add more errors
addCode(`proto.p = true;`)
.newErrors(
`
test.js:12
12: (a.p: void);
^^^ Cannot cast \`a.p\` to undefined because string [1] is incompatible with undefined [2]. [incompatible-cast]
References:
8: b.p = "";
^^ [1]
12: (a.p: void);
^^^^ [2]
test.js:12
12: (a.p: void);
^^^ Cannot cast \`a.p\` to undefined because boolean [1] is incompatible with undefined [2]. [incompatible-cast]
References:
16: proto.p = true;
^^^^ [1]
12: (a.p: void);
^^^^ [2]
test.js:14
14: (b.p: void);
^^^ Cannot cast \`b.p\` to undefined because number [1] is incompatible with undefined [2]. [incompatible-cast]
References:
7: a.p = 0;
^ [1]
14: (b.p: void);
^^^^ [2]
test.js:14
14: (b.p: void);
^^^ Cannot cast \`b.p\` to undefined because boolean [1] is incompatible with undefined [2]. [incompatible-cast]
References:
16: proto.p = true;
^^^^ [1]
14: (b.p: void);
^^^^ [2]
`,
),
]),
test('derived object subtyping -- write before read', [
addCode(`
var proto = {};
proto.p = 0;
`).noNewErrors(),
addCode(`var o: {p: string} = Object.create(proto);`)
.noNewErrors(),
]),
test('derived object subtyping -- read before write', [
addCode(`
var proto = {};
`).noNewErrors(),
/* NOTE: Shadow reads for ObjT -> ObjT are currently strict, which is
inconsistent with GetPropT/MethodT. It would be confusing if this
didn't error, though: var o: { p: string } = {} */
addCode(`var o: {p: string} = Object.create(proto);`)
.noNewErrors(),
addCode(`proto.p = 0;`)
.noNewErrors(),
]),
/* Because shadow operations execute on failure, a builtin or import can cause
lookups to build up as upper bounds of an tvar. Once the tvar is resolved,
the various lookups shouldn't clobber each other. */
test('racy read before write', [
addFile('proto.js'),
addCode(`
import proto from './proto';
var o = Object.create(proto);
if (Math.random() < 0.5) {
(o.p: number);
} else {
(o.p: string);
}
`),
addCode(`o.p = true;`)
.newErrors(
`
test.js:7
7: (o.p: number);
^^^ Cannot cast \`o.p\` to number because boolean [1] is incompatible with number [2]. [incompatible-cast]
References:
13: o.p = true;
^^^^ [1]
7: (o.p: number);
^^^^^^ [2]
test.js:9
9: (o.p: string);
^^^ Cannot cast \`o.p\` to string because boolean [1] is incompatible with string [2]. [incompatible-cast]
References:
13: o.p = true;
^^^^ [1]
9: (o.p: string);
^^^^^^ [2]
`,
),
]),
test('racy read before write 2', [
addFile('proto.js'),
addCode(`
import proto from './proto';
var o = Object.create(proto);
function f() {
(o.p: void);
}
`).noNewErrors(),
addCode(`
if (Math.random() < 0.5) {
o.p = 0;
} else {
o.p = "";
}
`).newErrors(
`
test.js:7
7: (o.p: void);
^^^ Cannot cast \`o.p\` to undefined because number [1] is incompatible with undefined [2]. [incompatible-cast]
References:
13: o.p = 0;
^ [1]
7: (o.p: void);
^^^^ [2]
test.js:7
7: (o.p: void);
^^^ Cannot cast \`o.p\` to undefined because string [1] is incompatible with undefined [2]. [incompatible-cast]
References:
15: o.p = "";
^^ [1]
7: (o.p: void);
^^^^ [2]
`,
),
]),
test('racy read before write 3', [
addFile('proto.js'),
addCode(`
import lib_proto from './proto';
var proto = Object.create(lib_proto);
var a = Object.create(proto);
var b = Object.create(proto);
if (Math.random() < 0.5) {
(a.p: number);
} else {
(b.p: string);
}
`).noNewErrors(),
addCode(`proto.p = true;`)
.newErrors(
`
test.js:9
9: (a.p: number);
^^^ Cannot cast \`a.p\` to number because boolean [1] is incompatible with number [2]. [incompatible-cast]
References:
15: proto.p = true;
^^^^ [1]
9: (a.p: number);
^^^^^^ [2]
test.js:11
11: (b.p: string);
^^^ Cannot cast \`b.p\` to string because boolean [1] is incompatible with string [2]. [incompatible-cast]
References:
15: proto.p = true;
^^^^ [1]
11: (b.p: string);
^^^^^^ [2]
`,
),
]),
test('racy write before read', [
addFile('proto.js'),
addCode(`
import proto from './proto';
let o = Object.create(proto);
if (Math.random() < 0.5) {
o.p = 0;
} else {
o.p = "";
}
`).noNewErrors(),
addCode(`(o.p: void);`)
.newErrors(
`
test.js:13
13: (o.p: void);
^^^ Cannot cast \`o.p\` to undefined because number [1] is incompatible with undefined [2]. [incompatible-cast]
References:
7: o.p = 0;
^ [1]
13: (o.p: void);
^^^^ [2]
test.js:13
13: (o.p: void);
^^^ Cannot cast \`o.p\` to undefined because string [1] is incompatible with undefined [2]. [incompatible-cast]
References:
9: o.p = "";
^^ [1]
13: (o.p: void);
^^^^ [2]
`,
),
]),
test('racy write before read 2', [
addFile('proto.js'),
addCode(`
import proto from './proto';
var o = Object.create(proto);
function f() {
o.p = true;
}
`).noNewErrors(),
addCode(`
if (Math.random() < 0.5) {
(o.p: number); // error: void ~> number
} else {
(o.p: string); // error: void ~> string
}
`).newErrors(
`
test.js:13
13: (o.p: number); // error: void ~> number
^^^ Cannot cast \`o.p\` to number because boolean [1] is incompatible with number [2]. [incompatible-cast]
References:
7: o.p = true;
^^^^ [1]
13: (o.p: number); // error: void ~> number
^^^^^^ [2]
test.js:15
15: (o.p: string); // error: void ~> string
^^^ Cannot cast \`o.p\` to string because boolean [1] is incompatible with string [2]. [incompatible-cast]
References:
7: o.p = true;
^^^^ [1]
15: (o.p: string); // error: void ~> string
^^^^^^ [2]
`,
),
]),
]): Suite);