Objects can be created with object literals. The types of properties are fixed based on their initializers.
// @flow var o = { x: 42, foo(x) { this.x = x; } }; o.foo('hello');
$> flow
6: o.foo('hello'); ^^^^^^^^^^^^^^ call of method `foo` 4: foo(x) { this.x = x; } ^ string. This type is incompatible with 3: x: 42, ^^ number
Flow infers the type of property x
of the object to be number since it is initialized with a number
. The method call foo()
on the object writes string
to that property. As expected, running Flow produces an error.
Object types are of the form:
{ x1: T1; x2: T2; x3: T3;}
Here is an example of declaring an object type:
// @flow class Foo {} var obj: {a: boolean; b: string; c: Foo} = {a: true, b: "Hi", c: new Foo()}
Here is an example of Flow catching a problem with your object type:
// @flow class Bar {} var badObj: {a: boolean; b: string; c: Foo} = {a: true, b: "Hi", c: new Bar()}
$> flow
3: var badObj: {a: boolean; b: string; c: Foo} = {a: true, b: "Hi", c: new Bar()} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ object literal. This type is incompatible with 3: var badObj: {a: boolean; b: string; c: Foo} = {a: true, b: "Hi", c: new Bar()} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ object type
Object types can be made reusable through the use of type aliases:
// @flow type MyType = {message: string; isAwesome: boolean}; function sayHello(data: MyType) { console.log(data.message); } var mySampleData: MyType = {message: 'Hello World', isAwesome: true}; sayHello(mySampleData); sayHello({message: 'Hi', isAwesome: false});
Object types can be added together with the intersection operator, &
. See union and intersection types for details.
Object types can have optional properties. The following code shows how optional properties allow objects with missing properties to be typed.
// @flow var optObj: { a: string; b?: number } = { a: "hello" };
When optional properties are accessed, Flow tracks the fact that they could be undefined
, and reports errors when they are used as is.
optObj.b * 10 // error: undefined is incompatible with number
$> flow
1: optObj.b * 10 // error: undefined is incompatible with number ^^^^^^^^ undefined. The operand of an arithmetic operation must be a number.
One way to avoid errors is to dynamically check that an optional property exists before using it. See nullable types for details.
By default, objects are invariant with respect to their property types.
function invariance(o: {p: ?number}) { let p = o.p; o.p = null; return p; } var subtype_p: {p: number} = {p: 0}; invariance(subtype_p);
$> flow
7: invariance(subtype_p); ^^^^^^^^^ object type. This type is incompatible with the expected param type of 1: function invariance(o: {p: ?number}) { ^^^^^^^^^^^^ object type 7: invariance(subtype_p); ^^^^^^^^^ object type. This type is incompatible with the expected param type of 1: function invariance(o: {p: ?number}) { ^^^^^^^^^^^^ object type
However, properties can be annotated as covariant:
function covariance(o: {+p: ?number}) {} covariance(subtype_p);
Covariant properties can not be written:
function covariance_err(o: {+p: ?number}) { o.p = null; }
$> flow
2: o.p = null; ^ object type. Covariant property `p` incompatible with contravariant use in 2: o.p = null; ^^^ assignment of property `p`
Or contravariant:
function contravariance(o: {-p: number}) { o.p = 0; } var supertype_p: {p: ?number} = {p: null}; contravariance(supertype_p);
Contravariant properties can not be read from:
function contravariance_err(o: {-p: number}) { return o.p; }
$> flow
2: return o.p; ^ object type. Contravariant property `p` incompatible with covariant use in 2: return o.p; ^^^ property `p`
Another way of creating objects in JavaScript is by using new
on constructor functions. A constructor function is typically an open method that “initializes” some properties of this
; and a new
operation on such a function calls it on a freshly created object before returning it.
Additionally, a constructor function may set various properties on its prototype
object. These properties are typically methods, and are inherited by all objects created from that constructor function by a process known as prototype chaining.
// @flow function FuncBasedClass(x) { this.x = x; } FuncBasedClass.prototype.f = function() { return this.x; } var y = new FuncBasedClass(42); var z: number = y.f();
In this code, a new
object is created by new FuncBasedClass(42)
; this object has a property x
initialized by FuncBasedClass
with the number
passed to it. The object also responds to the f
method defined in FuncBasedClass.prototype
, so y.f()
reads y.x
and returns it. This fits with the expectation of a number
as expressed by the annotation at line 6, so this code typechecks.
Furthermore, Flow ensures that an object’s type can always be viewed as a subtype of its constructor’s prototype type
. (This is analogous to subtyping based on class inheritance.) This means that the following code typechecks:
var anObj: FuncBasedClass = new FuncBasedClass(42);
It is a common idiom in JavaScript to add properties to objects after they are created. In fact, we have already seen this idiom on several occasions above: when initializing properties of this
properties in a constructor function; when building a constructor function’s prototype
object; when building a module.exports
object; and so on.
Flow supports this idiom. As far as we know, this is a type system novelty: supporting this idiom while balancing other constraints of the type system, such as sound subtyping over objects and prototypes, can be quite tricky!
However, for a property that may be added to an object after its creation, Flow cannot guarantee the existence of that property at a particular property access operation; it can only check that its writes and reads are type- consistent. Providing such guarantees for dynamic objects would significantly complicate the analysis; this is a well-known fact (in technical terms, Flow’s analysis is heap-insensitive for strong updates).
For example, the following code typechecks:
// @flow function foo(p) { p.x = 42; } function bar(q) { return q.f(); } var o = { }; o.f = function() { return this.x; }; bar(o); foo(o);
In this code, when bar(o)
is called, o.x
is undefined; only later is it initialized by foo(o)
, but it is hard to track this fact statically.
Fortunately, though, the following code does not typecheck:
var test: string = bar(o);
$> flow
1: var test: string = bar(o); ^^^^^^ number. This type is incompatible with 1: var test: string = bar(o); ^^^^^^ string
In other words, Flow knows enough to infer that whenever the x
property of o
does exist, it is a number, so a string
should not be expected.
Unfortunately, supporting dynamically added properties means that Flow can miss errors where the programmer accesses a non-existent property by mistake. Thus, Flow also supports sealed object types, where accesses of non-existent properties are reported as errors.
When object types appear as annotations, they are considered sealed. Also, non-empty object literals are considered to have sealed object types. In fact, the only cases where an object type is not sealed are when it describes an empty object literal (to be extended by adding properties to it), an object literal with spread properties, or when it describes a map (see below).
Overall, the weaker guarantee for dynamically added properties is a small cost to pay for the huge increase in flexibility it affords. Specifically, it allows Flow to usefully type check lots of idiomatic JavaScript code, while trusting the programmer to follow the discipline of fully initializing an object before making it available, which effectively ensures that any dynamically added properties during initialization are only accessed after initialization is complete.
In any case, for most objects you can altogether avoid adding properties dynamically, in which case you get stronger guarantees. Furthermore, as described above, object type annotations are sealed, so you can always force sealing by going through an annotation (and sealing is enforced at module boundaries).
An object can be viewed as a map from string
to some value type by setting and getting its properties via bracket notation (i.e. dynamic accessors), instead of dot notation. Flow infers a precise value type for the map: in other words, if you only write number
values to a map, you will read number
values back (rather than, say, any
).
Such a map can be given a type of the form
type MapOfNumbers = { [key: string]: number }; var numbers: MapOfNumbers = { ten: 10, twenty: 20, };
where string
is the key type and number
is the value type of the map.
Viewing an object as a map does not preclude viewing it as a record. However, for such an object, the value type of the map does not interfere with the types of the properties of the record. This is potentially unsound, but we admit it because a sound design would necessarily lead to severe imprecision in the types of properties.
Object
type
This type describes “any object” and you can think of it like an any
-flavored version of an object type.
In JavaScript, everything is an object. Flow is a bit stricter and does not consider primitive types to be subtypes of Object
.
(0: Object); ("": Object); (true: Object); (null: Object); (undefined: Object);
$> flow
1: (0: Object); ^ number. This type is incompatible with 1: (0: Object); ^^^^^^ object type 2: ("": Object); ^^ string. This type is incompatible with 2: ("": Object); ^^^^^^ object type 3: (true: Object); ^^^^ boolean. This type is incompatible with 3: (true: Object); ^^^^^^ object type 4: (null: Object); ^^^^ null. This type is incompatible with 4: (null: Object); ^^^^^^ object type 5: (undefined: Object); ^^^^^^^^^ undefined. This type is incompatible with 5: (undefined: Object); ^^^^^^ object type
Many other types can be treated as objects, however. Naturally objects are compatible with Object
, but so are functions and classes.
({foo: "foo"}: Object); (function() {}: Object); (class {}: Object); ([]: Object); // Flow does not treat arrays as objects (likely to change)
$> flow
4: ([]: Object); // Flow does not treat arrays as objects (likely to change) ^^ empty array literal. This type is incompatible with 4: ([]: Object); // Flow does not treat arrays as objects (likely to change) ^^^^^^ object type
As we saw, the object type {x:string}
ensures that an object contains at least the property x
of type string
. However, {x:string}
may have other properties in addition to x
.
Sometimes we want to also make sure that x
is the only property of the object. For this purpose there are exact object types, which use {|
and |}
instead of {
and }
:
type User = { name: string, age: number }; type StrictUser = {| name: string, age: number |}; // Regular object types allow extra properties ({ name: "Foo", age: 27, foo: false }: User); // Exact object types disallow extra properties ({ name: "Foo", age: 27, foo: false }: StrictUser); // Otherwise, they behave similarly ({ name: "Foo", age: 27 }: User); ({ name: "Foo", age: 27 }: StrictUser); ({ name: "Foo" }: User); ({ name: "Foo" }: StrictUser); // Error: 'age' is missing
$> flow
7: ({ name: "Foo", age: 27, foo: false }: StrictUser); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ property `foo`. Property not found in 7: ({ name: "Foo", age: 27, foo: false }: StrictUser); ^^^^^^^^^^ object type 12: ({ name: "Foo" }: User); ^^^^ property `age`. Property not found in 12: ({ name: "Foo" }: User); ^^^^^^^^^^^^^^^ object literal 13: ({ name: "Foo" }: StrictUser); // Error: 'age' is missing ^^^^^^^^^^ property `age`. Property not found in 13: ({ name: "Foo" }: StrictUser); // Error: 'age' is missing ^^^^^^^^^^^^^^^ object literal
Exact object types are a very useful tool for helping Flow to refine unions of object types and notice typos on property names and refinements. Because {name:string}
only means “an object with at least a name
property”, Flow can’t be sure that objects of that type don’t also have other properties. For this reason, Flow won’t error if it sees an access of a property called, say, nname
because there’s no guarantee that the object doesn’t actually have a nname
property on it!
© 2013–present Facebook Inc.
Licensed under the BSD License.
https://flowtype.org/docs/objects.html