210 lines
6.4 KiB
Plaintext
210 lines
6.4 KiB
Plaintext
![]() |
exports[`test test.js 1`] = `
|
||
|
"var x: { } = { foo: 0 };
|
||
|
var y: { foo?: string } = x; // OK in TypeScript, not OK in Flow
|
||
|
|
||
![]() |
var z: string = y.foo || \"\";
|
||
![]() |
|
||
|
var o = { };
|
||
|
y = o; // OK; we know that narrowing could not have happened
|
||
|
o.foo = 0; // future widening is constrained
|
||
|
|
||
|
function bar(config: { foo?: number }) {}
|
||
|
bar({});
|
||
![]() |
bar({foo: \"\"});
|
||
![]() |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
![]() |
var x: {} = { foo: 0 };
|
||
![]() |
var y: { foo?: string } = x;
|
||
![]() |
|
||
![]() |
// OK in TypeScript, not OK in Flow
|
||
![]() |
var z: string = y.foo || \"\";
|
||
![]() |
|
||
![]() |
var o = {};
|
||
![]() |
y = o;
|
||
![]() |
// OK; we know that narrowing could not have happened
|
||
![]() |
o.foo = 0;
|
||
![]() |
|
||
![]() |
// future widening is constrained
|
||
![]() |
function bar(config: { foo?: number }) {}
|
||
![]() |
bar({});
|
||
![]() |
bar({ foo: \"\" });
|
||
|
"
|
||
![]() |
`;
|
||
|
|
||
|
exports[`test test2.js 1`] = `
|
||
|
"var a: { foo?: string } = {};
|
||
|
a.foo = undefined; // This is not an error
|
||
|
a.foo = null; // But this is an error
|
||
|
|
||
|
var b: { foo?: ?string } = {};
|
||
|
b.foo = undefined; // This is fine
|
||
|
b.foo = null; // Also fine
|
||
|
|
||
|
var c: { foo?: string } = { foo: undefined }; // This is not an error
|
||
|
var d: { foo?: string } = { foo: null }; // But this is an error
|
||
|
|
||
|
var e: { foo?: ?string } = { foo: undefined }; // This is fine
|
||
|
var f: { foo?: ?string } = { foo: null }; // Also fine
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
var a: { foo?: string } = {};
|
||
![]() |
a.foo = undefined;
|
||
![]() |
// This is not an error
|
||
![]() |
a.foo = null;
|
||
![]() |
|
||
![]() |
// But this is an error
|
||
![]() |
var b: { foo?: ?string } = {};
|
||
![]() |
b.foo = undefined;
|
||
![]() |
// This is fine
|
||
![]() |
b.foo = null;
|
||
![]() |
|
||
![]() |
// Also fine
|
||
![]() |
var c: { foo?: string } = { foo: undefined };
|
||
![]() |
// This is not an error
|
||
![]() |
var d: { foo?: string } = { foo: null };
|
||
![]() |
|
||
![]() |
// But this is an error
|
||
![]() |
var e: { foo?: ?string } = { foo: undefined };
|
||
![]() |
// This is fine
|
||
![]() |
var f: { foo?: ?string } = { foo: null }; // Also fine
|
||
|
"
|
||
![]() |
`;
|
||
|
|
||
|
exports[`test test3.js 1`] = `
|
||
|
"// @flow
|
||
|
|
||
|
/*
|
||
|
object literals are sealed. this is simply a heuristic
|
||
![]() |
decision: most of the time, the rule gives the \'right\'
|
||
![]() |
errors.
|
||
|
|
||
|
an exception is when a literal is used as an initializer
|
||
|
for an lvalue whose type specifies optional properties
|
||
|
missing from the literal, as below.
|
||
|
|
||
|
the problem becomes visible when a property assignment
|
||
|
is then used to (legitimately) extend the object with an
|
||
![]() |
optional property - the variable\'s specific (path-
|
||
![]() |
dependent) type has become that of the literal which.
|
||
|
without adjustment, will reject the property addition.
|
||
|
|
||
|
the solution in cases where a sealed object type (as from
|
||
|
an object literal) flows to an object type with optional
|
||
|
properties, is to have the sealed type acquire the optional
|
||
|
properties.
|
||
|
*/
|
||
|
|
||
|
// x has optional property b.
|
||
|
// (note that the initializer here does not play into
|
||
![]() |
// the problem, it\'s just a placeholder. initializers
|
||
![]() |
// do not narrow the types of annotated variables as do
|
||
|
// subsequent assignments.)
|
||
|
//
|
||
|
var x: { a: number, b?: number } = { a: 0 };
|
||
|
|
||
|
// now assign an object literal lacking property b.
|
||
![]() |
// the literal\'s type is sealed and has only a at creation.
|
||
|
// but it then flows, specific ~> general, to x\'s annotation
|
||
![]() |
// type. at that point, it acquires b as an optional property.
|
||
|
//
|
||
|
x = { a: 0 };
|
||
|
|
||
|
// ...which allows this assignment to take place.
|
||
|
x.b = 1;
|
||
|
|
||
|
// T7810506
|
||
|
class A {
|
||
|
x: { a: number, b?: string };
|
||
|
foo() {
|
||
![]() |
// Something similar should happen here, but doesn\'t: the problem is
|
||
![]() |
// made explicit by adding generics (see test3_failure.js introduced by
|
||
|
// D2747512). There is a race between writing b on the object literal
|
||
|
// type and adding b as an optional property to it, since in general we
|
||
|
// cannot guarantee that the flow from the object literal to the
|
||
|
// annotation will be processed before the flow involving the
|
||
|
// access. Here we lose the race and get an error on the write.
|
||
|
this.x = { a: 123 };
|
||
![]() |
this.x.b = \'hello\';
|
||
![]() |
}
|
||
|
}
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
// @flow
|
||
![]() |
|
||
![]() |
/*
|
||
|
object literals are sealed. this is simply a heuristic
|
||
![]() |
decision: most of the time, the rule gives the \'right\'
|
||
![]() |
errors.
|
||
|
|
||
|
an exception is when a literal is used as an initializer
|
||
|
for an lvalue whose type specifies optional properties
|
||
|
missing from the literal, as below.
|
||
|
|
||
|
the problem becomes visible when a property assignment
|
||
|
is then used to (legitimately) extend the object with an
|
||
![]() |
optional property - the variable\'s specific (path-
|
||
![]() |
dependent) type has become that of the literal which.
|
||
|
without adjustment, will reject the property addition.
|
||
|
|
||
|
the solution in cases where a sealed object type (as from
|
||
|
an object literal) flows to an object type with optional
|
||
|
properties, is to have the sealed type acquire the optional
|
||
|
properties.
|
||
|
*/
|
||
![]() |
|
||
![]() |
// x has optional property b.
|
||
|
// (note that the initializer here does not play into
|
||
![]() |
// the problem, it\'s just a placeholder. initializers
|
||
![]() |
// do not narrow the types of annotated variables as do
|
||
|
// subsequent assignments.)
|
||
|
//
|
||
![]() |
var x: { a: number, b?: number } = { a: 0 };
|
||
|
|
||
![]() |
// now assign an object literal lacking property b.
|
||
![]() |
// the literal\'s type is sealed and has only a at creation.
|
||
|
// but it then flows, specific ~> general, to x\'s annotation
|
||
![]() |
// type. at that point, it acquires b as an optional property.
|
||
|
//
|
||
![]() |
x = { a: 0 };
|
||
![]() |
|
||
|
// ...which allows this assignment to take place.
|
||
![]() |
x.b = 1;
|
||
![]() |
|
||
|
// T7810506
|
||
![]() |
class A {
|
||
![]() |
x: { a: number, b?: string };
|
||
![]() |
foo() {
|
||
![]() |
// Something similar should happen here, but doesn\'t: the problem is
|
||
|
// made explicit by adding generics (see test3_failure.js introduced by
|
||
|
// D2747512). There is a race between writing b on the object literal
|
||
|
// type and adding b as an optional property to it, since in general we
|
||
|
// cannot guarantee that the flow from the object literal to the
|
||
|
// annotation will be processed before the flow involving the
|
||
|
// access. Here we lose the race and get an error on the write.
|
||
![]() |
this.x = { a: 123 };
|
||
![]() |
this.x.b = \"hello\";
|
||
![]() |
}
|
||
![]() |
}
|
||
|
"
|
||
![]() |
`;
|
||
|
|
||
|
exports[`test test3_failure.js 1`] = `
|
||
|
"// generalization of failure in test3.js
|
||
|
|
||
|
class A<O: {x: { a: number, b?: string}}> {
|
||
|
o: O;
|
||
|
foo() {
|
||
|
this.o.x = { a: 123 };
|
||
![]() |
this.o.x.b = \'hello\'; // this is a spurious error (see test3.js for details)
|
||
![]() |
}
|
||
|
}
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
![]() |
// generalization of failure in test3.js
|
||
![]() |
|
||
![]() |
class A<O: { x: { a: number, b?: string } }> {
|
||
![]() |
o: O;
|
||
|
foo() {
|
||
![]() |
this.o.x = { a: 123 };
|
||
![]() |
this.o.x.b = \"hello\"; // this is a spurious error (see test3.js for details)
|
||
![]() |
}
|
||
![]() |
}
|
||
|
"
|
||
![]() |
`;
|