The next release of Flow, 0.34, will include a few important changes to object
types:
- property variance,
- invariant-by-default dictionary types,
- covariant-by-default method types,
- and more flexible getters and setters.
What is Variance?
Defining the subtype relationship between types is a core responsibility of Flow
as a type system. These relationships are determined either directly for
simple types or, for complex types, defined in terms of their parts.
Variance describes the subtyping relationship for complex types as it relates
to the subtyping relationships of their parts.
For example, Flow directly encodes the knowledge that string
is a subtype of
?string
. Intuitively, a string
type contains string values while a ?string
type contains null
, undefined
, and also string values, so membership in the
former naturally implies membership in the later.
The subtype relationships between two function types is not as direct. Rather,
it is derived from the subtype relationships between the functions’ parameter
and return types.
Let’s see how this works for two simple function types:
1
2
|
type F1 = (x: P1) => R1;
type F2 = (x: P2) => R2;
|
{"value":"type F1 = (x: P1) => R1;\ntype F2 = (x: P2) => R2;\n","tokens":[{"type":"T_TYPE","context":"normal","value":"type","line":1,"start":0,"end":4},{"type":"T_IDENTIFIER","context":"type","value":"F1","line":1,"start":5,"end":7},{"type":"T_ASSIGN","context":"type","value":"=","line":1,"start":8,"end":9},{"type":"T_LPAREN","context":"type","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_IDENTIFIER","context":"type","value":"P1","line":1,"start":14,"end":16},{"type":"T_RPAREN","context":"type","value":")","line":1,"start":16,"end":17},{"type":"T_ARROW","context":"type","value":"=>","line":1,"start":18,"end":20},{"type":"T_IDENTIFIER","context":"type","value":"R1","line":1,"start":21,"end":23},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":23,"end":24},{"type":"T_TYPE","context":"normal","value":"type","line":2,"start":25,"end":29},{"type":"T_IDENTIFIER","context":"type","value":"F2","line":2,"start":30,"end":32},{"type":"T_ASSIGN","context":"type","value":"=","line":2,"start":33,"end":34},{"type":"T_LPAREN","context":"type","value":"(","line":2,"start":35,"end":36},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":2,"start":36,"end":37},{"type":"T_COLON","context":"type","value":":","line":2,"start":37,"end":38},{"type":"T_IDENTIFIER","context":"type","value":"P2","line":2,"start":39,"end":41},{"type":"T_RPAREN","context":"type","value":")","line":2,"start":41,"end":42},{"type":"T_ARROW","context":"type","value":"=>","line":2,"start":43,"end":45},{"type":"T_IDENTIFIER","context":"type","value":"R2","line":2,"start":46,"end":48},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":48,"end":49}],"errors":[]}
Whether F2
is a subtype of F1
depends on the relationships between P1
and
P2
and R1
and R2
. Let’s use the notation B <: A
to mean B
is a
subtype of A
.
It turns out that F2 <: F1
if P1 <: P2
and R2 <: R1
. Notice that the
relationship for parameters is reversed? In technical terms, we can say that
function types are “contravariant” with respect to their parameter types and
“covariant” with respect to their return types.
Let’s look at an example:
1
2
3
|
function f(callback: (x: string) => ?number): number {
return callback("hi") || 0;
}
|
{"value":"function f(callback: (x: string) => ?number): number {\n return callback(\"hi\") || 0;\n}\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"callback","line":1,"start":11,"end":19},{"type":"T_COLON","context":"type","value":":","line":1,"start":19,"end":20},{"type":"T_LPAREN","context":"type","value":"(","line":1,"start":21,"end":22},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":1,"start":22,"end":23},{"type":"T_COLON","context":"type","value":":","line":1,"start":23,"end":24},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":25,"end":31},{"type":"T_RPAREN","context":"type","value":")","line":1,"start":31,"end":32},{"type":"T_ARROW","context":"type","value":"=>","line":1,"start":33,"end":35},{"type":"T_PLING","context":"type","value":"?","line":1,"start":36,"end":37},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":1,"start":37,"end":43},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":43,"end":44},{"type":"T_COLON","context":"type","value":":","line":1,"start":44,"end":45},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":1,"start":46,"end":52},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":53,"end":54},{"type":"T_RETURN","context":"normal","value":"return","line":2,"start":57,"end":63},{"type":"T_IDENTIFIER","context":"normal","value":"callback","line":2,"start":64,"end":72},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":72,"end":73},{"type":"T_STRING","context":"normal","value":"\"hi\"","line":2,"start":73,"end":77},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":77,"end":78},{"type":"T_OR","context":"normal","value":"||","line":2,"start":79,"end":81},{"type":"T_NUMBER","context":"normal","value":"0","line":2,"start":82,"end":83},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":83,"end":84},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":85,"end":86}],"errors":[]}
What kinds of functions can we pass to f
? Based on the subtyping rule above,
then we can pass a function whose parameter type is a supertype of string
and
whose return type is a subtype of ?number
.
1
2
3
4
|
function g(x: ?string): number {
return x ? x.length : 0;
}
f(g);
|
{"value":"function g(x: ?string): number {\n return x ? x.length : 0;\n}\nf(g);\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"g","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_PLING","context":"type","value":"?","line":1,"start":14,"end":15},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":15,"end":21},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":21,"end":22},{"type":"T_COLON","context":"type","value":":","line":1,"start":22,"end":23},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":1,"start":24,"end":30},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":31,"end":32},{"type":"T_RETURN","context":"normal","value":"return","line":2,"start":35,"end":41},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":2,"start":42,"end":43},{"type":"T_PLING","context":"normal","value":"?","line":2,"start":44,"end":45},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":2,"start":46,"end":47},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":47,"end":48},{"type":"T_IDENTIFIER","context":"normal","value":"length","line":2,"start":48,"end":54},{"type":"T_COLON","context":"normal","value":":","line":2,"start":55,"end":56},{"type":"T_NUMBER","context":"normal","value":"0","line":2,"start":57,"end":58},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":58,"end":59},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":60,"end":61},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":4,"start":62,"end":63},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":63,"end":64},{"type":"T_IDENTIFIER","context":"normal","value":"g","line":4,"start":64,"end":65},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":65,"end":66},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":66,"end":67}],"errors":[]}
The body of f
will only ever pass string
values into g
, which is safe
because g
takes at least string
by taking ?string
. Conversely, g
will
only ever return number
values to f
, which is safe because f
handles at
least number
by handling ?number
.
One convenient way to remember when something is covariant vs. contravariant is
to think about “input” and “output.”
Parameters are in an input position, often called a “negative” position.
Complex types are contravariant in their input positions.
Return is an output position, often called a “positive” position. Complex
types are covariant in their output positions.
Property Invariance
Just as function types are composed of parameter and return types, so too are
object types composed of property types. Thus, the subtyping relationship
between objects is derived from the subtyping relationships of their
properties.
However, unlike functions which have input parameters and an output return,
object properties can be read and written. That is, properties are both input
and output.
Let’s see how this works for two simple object types:
1
2
|
type O1 = {p: T1};
type O2 = {p: T2};
|
{"value":"type O1 = {p: T1};\ntype O2 = {p: T2};\n","tokens":[{"type":"T_TYPE","context":"normal","value":"type","line":1,"start":0,"end":4},{"type":"T_IDENTIFIER","context":"type","value":"O1","line":1,"start":5,"end":7},{"type":"T_ASSIGN","context":"type","value":"=","line":1,"start":8,"end":9},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_IDENTIFIER","context":"type","value":"T1","line":1,"start":14,"end":16},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":16,"end":17},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":17,"end":18},{"type":"T_TYPE","context":"normal","value":"type","line":2,"start":19,"end":23},{"type":"T_IDENTIFIER","context":"type","value":"O2","line":2,"start":24,"end":26},{"type":"T_ASSIGN","context":"type","value":"=","line":2,"start":27,"end":28},{"type":"T_LCURLY","context":"type","value":"{","line":2,"start":29,"end":30},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":30,"end":31},{"type":"T_COLON","context":"type","value":":","line":2,"start":31,"end":32},{"type":"T_IDENTIFIER","context":"type","value":"T2","line":2,"start":33,"end":35},{"type":"T_RCURLY","context":"type","value":"}","line":2,"start":35,"end":36},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":36,"end":37}],"errors":[]}
As with function types, whether O2
is a subtype of O1
depends on the
relationship between its parts, T1
and T2
.
Here it turns out that O2 <: O1
if T2 <: T1
and T1 <: T2
. In technical
terms, object types are “invariant” with respect to their property types.
Let’s look at an example:
1
2
3
4
5
6
7
8
9
10
11
12
|
function f(o: {p: ?string}): void {
let len: number;
if (o.p) {
len = o.p.length;
} else {
len = 0;
}
o.p = null;
}
|
{"value":"function f(o: {p: ?string}): void {\n // We can read p from o\n let len: number;\n if (o.p) {\n len = o.p.length;\n } else {\n len = 0;\n }\n\n // We can also write into p\n o.p = null;\n}\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":14,"end":15},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":15,"end":16},{"type":"T_COLON","context":"type","value":":","line":1,"start":16,"end":17},{"type":"T_PLING","context":"type","value":"?","line":1,"start":18,"end":19},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":19,"end":25},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":25,"end":26},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":26,"end":27},{"type":"T_COLON","context":"type","value":":","line":1,"start":27,"end":28},{"type":"T_VOID_TYPE","context":"type","value":"void","line":1,"start":29,"end":33},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":34,"end":35},{"type":"Line","context":"comment","value":"// We can read p from o","line":2,"start":38,"end":61},{"type":"T_LET","context":"normal","value":"let","line":3,"start":64,"end":67},{"type":"T_IDENTIFIER","context":"normal","value":"len","line":3,"start":68,"end":71},{"type":"T_COLON","context":"type","value":":","line":3,"start":71,"end":72},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":3,"start":73,"end":79},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":79,"end":80},{"type":"T_IF","context":"normal","value":"if","line":4,"start":83,"end":85},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":86,"end":87},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":4,"start":87,"end":88},{"type":"T_PERIOD","context":"normal","value":".","line":4,"start":88,"end":89},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":4,"start":89,"end":90},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":90,"end":91},{"type":"T_LCURLY","context":"normal","value":"{","line":4,"start":92,"end":93},{"type":"T_IDENTIFIER","context":"normal","value":"len","line":5,"start":98,"end":101},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":102,"end":103},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":5,"start":104,"end":105},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":105,"end":106},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":5,"start":106,"end":107},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":107,"end":108},{"type":"T_IDENTIFIER","context":"normal","value":"length","line":5,"start":108,"end":114},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":114,"end":115},{"type":"T_RCURLY","context":"normal","value":"}","line":6,"start":118,"end":119},{"type":"T_ELSE","context":"normal","value":"else","line":6,"start":120,"end":124},{"type":"T_LCURLY","context":"normal","value":"{","line":6,"start":125,"end":126},{"type":"T_IDENTIFIER","context":"normal","value":"len","line":7,"start":131,"end":134},{"type":"T_ASSIGN","context":"normal","value":"=","line":7,"start":135,"end":136},{"type":"T_NUMBER","context":"normal","value":"0","line":7,"start":137,"end":138},{"type":"T_SEMICOLON","context":"normal","value":";","line":7,"start":138,"end":139},{"type":"T_RCURLY","context":"normal","value":"}","line":8,"start":142,"end":143},{"type":"Line","context":"comment","value":"// We can also write into p","line":10,"start":147,"end":174},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":11,"start":177,"end":178},{"type":"T_PERIOD","context":"normal","value":".","line":11,"start":178,"end":179},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":11,"start":179,"end":180},{"type":"T_ASSIGN","context":"normal","value":"=","line":11,"start":181,"end":182},{"type":"T_NULL","context":"normal","value":"null","line":11,"start":183,"end":187},{"type":"T_SEMICOLON","context":"normal","value":";","line":11,"start":187,"end":188},{"type":"T_RCURLY","context":"normal","value":"}","line":12,"start":189,"end":190}],"errors":[]}
What kinds of objects can we pass into f
, then? If we try to pass in an
object with a subtype property, we get an error:
1
2
|
var o1: {p: string} = {p: ""};
f(o1);
|
{"value":"var o1: {p: string} = {p: \"\"};\nf(o1);\n","tokens":[{"type":"T_VAR","context":"normal","value":"var","line":1,"start":0,"end":3},{"type":"T_IDENTIFIER","context":"normal","value":"o1","line":1,"start":4,"end":6},{"type":"T_COLON","context":"type","value":":","line":1,"start":6,"end":7},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":8,"end":9},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":9,"end":10},{"type":"T_COLON","context":"type","value":":","line":1,"start":10,"end":11},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":12,"end":18},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":18,"end":19},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":20,"end":21},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":22,"end":23},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":23,"end":24},{"type":"T_COLON","context":"normal","value":":","line":1,"start":24,"end":25},{"type":"T_STRING","context":"normal","value":"\"\"","line":1,"start":26,"end":28},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":28,"end":29},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":29,"end":30},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":2,"start":31,"end":32},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":32,"end":33},{"type":"T_IDENTIFIER","context":"normal","value":"o1","line":2,"start":33,"end":35},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":35,"end":36},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":36,"end":37}],"errors":[]}
function f(o: {p: ?string}) {}
^ null. This type is incompatible with
var o1: {p: string} = {p: ""};
^ string
function f(o: {p: ?string}) {}
^ undefined. This type is incompatible with
var o1: {p: string} = {p: ""};
^ string
Flow has correctly identified an error here. If the body of f
writes null
into o.p
, then o1.p
would no longer have type string
.
If we try to pass an object with a supertype property, we again get an error:
1
2
|
var o2: {p: ?(string|number)} = {p: 0};
f(o2);
|
{"value":"var o2: {p: ?(string|number)} = {p: 0};\nf(o2);\n","tokens":[{"type":"T_VAR","context":"normal","value":"var","line":1,"start":0,"end":3},{"type":"T_IDENTIFIER","context":"normal","value":"o2","line":1,"start":4,"end":6},{"type":"T_COLON","context":"type","value":":","line":1,"start":6,"end":7},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":8,"end":9},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":9,"end":10},{"type":"T_COLON","context":"type","value":":","line":1,"start":10,"end":11},{"type":"T_PLING","context":"type","value":"?","line":1,"start":12,"end":13},{"type":"T_LPAREN","context":"type","value":"(","line":1,"start":13,"end":14},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":14,"end":20},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":20,"end":21},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":1,"start":21,"end":27},{"type":"T_RPAREN","context":"type","value":")","line":1,"start":27,"end":28},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":28,"end":29},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":30,"end":31},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":32,"end":33},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":33,"end":34},{"type":"T_COLON","context":"normal","value":":","line":1,"start":34,"end":35},{"type":"T_NUMBER","context":"normal","value":"0","line":1,"start":36,"end":37},{"type":"T_RCURLY","context":"normal","value":"}","line":1,"start":37,"end":38},{"type":"T_SEMICOLON","context":"normal","value":";","line":1,"start":38,"end":39},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":2,"start":40,"end":41},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":41,"end":42},{"type":"T_IDENTIFIER","context":"normal","value":"o2","line":2,"start":42,"end":44},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":44,"end":45},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":45,"end":46}],"errors":[]}
var o1: {p: ?(string|number)} = {p: ""};
^ number. This type is incompatible with
function f(o: {p: ?string}) {}
^ string
Again, Flow correctly identifies an error, because if f
tried to read p
from o
, it would find a number.
Property Variance
So objects have to be invariant with respect to their property types because
properties can be read from and written to. But just because you can read and
write, doesn’t mean you always do.
Consider a function that gets the length of an nullable string property:
1
2
3
|
function f(o: {p: ?string}): number {
return o.p ? o.p.length : 0;
}
|
{"value":"function f(o: {p: ?string}): number {\n return o.p ? o.p.length : 0;\n}\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":14,"end":15},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":15,"end":16},{"type":"T_COLON","context":"type","value":":","line":1,"start":16,"end":17},{"type":"T_PLING","context":"type","value":"?","line":1,"start":18,"end":19},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":19,"end":25},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":25,"end":26},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":26,"end":27},{"type":"T_COLON","context":"type","value":":","line":1,"start":27,"end":28},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":1,"start":29,"end":35},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":36,"end":37},{"type":"T_RETURN","context":"normal","value":"return","line":2,"start":40,"end":46},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":2,"start":47,"end":48},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":48,"end":49},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":49,"end":50},{"type":"T_PLING","context":"normal","value":"?","line":2,"start":51,"end":52},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":2,"start":53,"end":54},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":54,"end":55},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":55,"end":56},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"normal","value":"length","line":2,"start":57,"end":63},{"type":"T_COLON","context":"normal","value":":","line":2,"start":64,"end":65},{"type":"T_NUMBER","context":"normal","value":"0","line":2,"start":66,"end":67},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":67,"end":68},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":69,"end":70}],"errors":[]}
We never write into o.p
, so we should be able to pass in an object where the
type of property p
is a subtype of ?string
. Until now, this wasn’t possible
in Flow.
With property variance, you can explicitly annotate object properties as being
covariant and contravariant. For example, we can rewrite the above function:
1
2
3
4
5
6
|
function f(o: {+p: ?string}): number {
return o.p ? o.p.length : 0;
}
var o: {p: string} = {p: ""};
f(o);
|
{"value":"function f(o: {+p: ?string}): number {\n return o.p ? o.p.length : 0;\n}\n\nvar o: {p: string} = {p: \"\"};\nf(o); // no type error!\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":14,"end":15},{"type":"T_PLUS","context":"type","value":"+","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":16,"end":17},{"type":"T_COLON","context":"type","value":":","line":1,"start":17,"end":18},{"type":"T_PLING","context":"type","value":"?","line":1,"start":19,"end":20},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":20,"end":26},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":26,"end":27},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":27,"end":28},{"type":"T_COLON","context":"type","value":":","line":1,"start":28,"end":29},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":1,"start":30,"end":36},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":37,"end":38},{"type":"T_RETURN","context":"normal","value":"return","line":2,"start":41,"end":47},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":2,"start":48,"end":49},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":49,"end":50},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":50,"end":51},{"type":"T_PLING","context":"normal","value":"?","line":2,"start":52,"end":53},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":2,"start":54,"end":55},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":55,"end":56},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":56,"end":57},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":57,"end":58},{"type":"T_IDENTIFIER","context":"normal","value":"length","line":2,"start":58,"end":64},{"type":"T_COLON","context":"normal","value":":","line":2,"start":65,"end":66},{"type":"T_NUMBER","context":"normal","value":"0","line":2,"start":67,"end":68},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":68,"end":69},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":70,"end":71},{"type":"T_VAR","context":"normal","value":"var","line":5,"start":73,"end":76},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":5,"start":77,"end":78},{"type":"T_COLON","context":"type","value":":","line":5,"start":78,"end":79},{"type":"T_LCURLY","context":"type","value":"{","line":5,"start":80,"end":81},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":5,"start":81,"end":82},{"type":"T_COLON","context":"type","value":":","line":5,"start":82,"end":83},{"type":"T_STRING_TYPE","context":"type","value":"string","line":5,"start":84,"end":90},{"type":"T_RCURLY","context":"type","value":"}","line":5,"start":90,"end":91},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":92,"end":93},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":94,"end":95},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":5,"start":95,"end":96},{"type":"T_COLON","context":"normal","value":":","line":5,"start":96,"end":97},{"type":"T_STRING","context":"normal","value":"\"\"","line":5,"start":98,"end":100},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":100,"end":101},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":101,"end":102},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":6,"start":103,"end":104},{"type":"T_LPAREN","context":"normal","value":"(","line":6,"start":104,"end":105},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":6,"start":105,"end":106},{"type":"T_RPAREN","context":"normal","value":")","line":6,"start":106,"end":107},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":107,"end":108},{"type":"Line","context":"comment","value":"// no type error!","line":6,"start":109,"end":126}],"errors":[]}
It’s crucial that covariant properties only ever appear in output positions. It
is an error to write to a covariant property:
1
2
3
|
function f(o: {+p: ?string}) {
o.p = null;
}
|
{"value":"function f(o: {+p: ?string}) {\n o.p = null;\n}\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":14,"end":15},{"type":"T_PLUS","context":"type","value":"+","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":16,"end":17},{"type":"T_COLON","context":"type","value":":","line":1,"start":17,"end":18},{"type":"T_PLING","context":"type","value":"?","line":1,"start":19,"end":20},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":20,"end":26},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":26,"end":27},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":27,"end":28},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":29,"end":30},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":2,"start":33,"end":34},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":34,"end":35},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":35,"end":36},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":37,"end":38},{"type":"T_NULL","context":"normal","value":"null","line":2,"start":39,"end":43},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":43,"end":44},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":45,"end":46}],"errors":[]}
o.p = null;
^ object type. Covariant property `p` incompatible with contravariant use in
o.p = null;
^ assignment of property `p`
Conversely, if a function only ever writes to a property, we can annotate the
property as contravariant. This might come up in a function that initializes an
object with default values, for example.
1
2
3
4
5
|
function g(o: {-p: string}): void {
o.p = "default";
}
var o: {p: ?string} = {p: null};
g(o);
|
{"value":"function g(o: {-p: string}): void {\n o.p = \"default\";\n}\nvar o: {p: ?string} = {p: null};\ng(o);\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"g","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":14,"end":15},{"type":"T_MINUS","context":"type","value":"-","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":16,"end":17},{"type":"T_COLON","context":"type","value":":","line":1,"start":17,"end":18},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":19,"end":25},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":25,"end":26},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":26,"end":27},{"type":"T_COLON","context":"type","value":":","line":1,"start":27,"end":28},{"type":"T_VOID_TYPE","context":"type","value":"void","line":1,"start":29,"end":33},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":34,"end":35},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":2,"start":38,"end":39},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":39,"end":40},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":40,"end":41},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":42,"end":43},{"type":"T_STRING","context":"normal","value":"\"default\"","line":2,"start":44,"end":53},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":53,"end":54},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":55,"end":56},{"type":"T_VAR","context":"normal","value":"var","line":4,"start":57,"end":60},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":4,"start":61,"end":62},{"type":"T_COLON","context":"type","value":":","line":4,"start":62,"end":63},{"type":"T_LCURLY","context":"type","value":"{","line":4,"start":64,"end":65},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":4,"start":65,"end":66},{"type":"T_COLON","context":"type","value":":","line":4,"start":66,"end":67},{"type":"T_PLING","context":"type","value":"?","line":4,"start":68,"end":69},{"type":"T_STRING_TYPE","context":"type","value":"string","line":4,"start":69,"end":75},{"type":"T_RCURLY","context":"type","value":"}","line":4,"start":75,"end":76},{"type":"T_ASSIGN","context":"normal","value":"=","line":4,"start":77,"end":78},{"type":"T_LCURLY","context":"normal","value":"{","line":4,"start":79,"end":80},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":4,"start":80,"end":81},{"type":"T_COLON","context":"normal","value":":","line":4,"start":81,"end":82},{"type":"T_NULL","context":"normal","value":"null","line":4,"start":83,"end":87},{"type":"T_RCURLY","context":"normal","value":"}","line":4,"start":87,"end":88},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":88,"end":89},{"type":"T_IDENTIFIER","context":"normal","value":"g","line":5,"start":90,"end":91},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":91,"end":92},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":5,"start":92,"end":93},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":93,"end":94},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":94,"end":95}],"errors":[]}
Contravariant properties can only ever appear in input positions. It is an
error to read from a contravariant property:
1
2
3
|
function f(o: {-p: string}) {
o.p.length;
}
|
{"value":"function f(o: {-p: string}) {\n o.p.length;\n}\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":14,"end":15},{"type":"T_MINUS","context":"type","value":"-","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":1,"start":16,"end":17},{"type":"T_COLON","context":"type","value":":","line":1,"start":17,"end":18},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":19,"end":25},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":25,"end":26},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":26,"end":27},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":28,"end":29},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":2,"start":32,"end":33},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":33,"end":34},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":34,"end":35},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":35,"end":36},{"type":"T_IDENTIFIER","context":"normal","value":"length","line":2,"start":36,"end":42},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":42,"end":43},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":44,"end":45}],"errors":[]}
o.p.length;
^ object type. Contravariant property `p` incompatible with covariant use in
o.p.length;
^ property `p`
Invariant-by-default Dictionary Types
The object type {[key: string]: ?number}
describes an object that can be used
as a map. We can read any property and Flow will infer the result type as
?number
. We can also write null
or undefined
or number
into any
property.
In Flow 0.33 and earlier, these dictionary types were treated covariantly by
the type system. For example, Flow accepted the following code:
1
2
3
4
5
|
function f(o: {[key: string]: ?number}) {
o.p = null;
}
declare var o: {p: number};
f(o);
|
{"value":"function f(o: {[key: string]: ?number}) {\n o.p = null;\n}\ndeclare var o: {p: number};\nf(o);\n","tokens":[{"type":"T_FUNCTION","context":"normal","value":"function","line":1,"start":0,"end":8},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":1,"start":9,"end":10},{"type":"T_LPAREN","context":"normal","value":"(","line":1,"start":10,"end":11},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":11,"end":12},{"type":"T_COLON","context":"type","value":":","line":1,"start":12,"end":13},{"type":"T_LCURLY","context":"type","value":"{","line":1,"start":14,"end":15},{"type":"T_LBRACKET","context":"type","value":"[","line":1,"start":15,"end":16},{"type":"T_IDENTIFIER","context":"type","value":"key","line":1,"start":16,"end":19},{"type":"T_COLON","context":"type","value":":","line":1,"start":19,"end":20},{"type":"T_STRING_TYPE","context":"type","value":"string","line":1,"start":21,"end":27},{"type":"T_RBRACKET","context":"type","value":"]","line":1,"start":27,"end":28},{"type":"T_COLON","context":"type","value":":","line":1,"start":28,"end":29},{"type":"T_PLING","context":"type","value":"?","line":1,"start":30,"end":31},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":1,"start":31,"end":37},{"type":"T_RCURLY","context":"type","value":"}","line":1,"start":37,"end":38},{"type":"T_RPAREN","context":"normal","value":")","line":1,"start":38,"end":39},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":40,"end":41},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":2,"start":44,"end":45},{"type":"T_PERIOD","context":"normal","value":".","line":2,"start":45,"end":46},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":2,"start":46,"end":47},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":48,"end":49},{"type":"T_NULL","context":"normal","value":"null","line":2,"start":50,"end":54},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":54,"end":55},{"type":"T_RCURLY","context":"normal","value":"}","line":3,"start":56,"end":57},{"type":"T_DECLARE","context":"normal","value":"declare","line":4,"start":58,"end":65},{"type":"T_VAR","context":"normal","value":"var","line":4,"start":66,"end":69},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":4,"start":70,"end":71},{"type":"T_COLON","context":"type","value":":","line":4,"start":71,"end":72},{"type":"T_LCURLY","context":"type","value":"{","line":4,"start":73,"end":74},{"type":"T_IDENTIFIER","context":"normal","value":"p","line":4,"start":74,"end":75},{"type":"T_COLON","context":"type","value":":","line":4,"start":75,"end":76},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":4,"start":77,"end":83},{"type":"T_RCURLY","context":"type","value":"}","line":4,"start":83,"end":84},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":84,"end":85},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":5,"start":86,"end":87},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":87,"end":88},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":5,"start":88,"end":89},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":89,"end":90},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":90,"end":91}],"errors":[]}
This is unsound because f
can overwrite property p
with null
. In Flow
0.34, dictionaries are invariant, like named properties. The same code now
results in the following type error:
function f(o: {[key: string]: ?number}) {}
^ null. This type is incompatible with
declare var o: {p: number};
^ number
function f(o: {[key: string]: ?number}) {}
^ undefined. This type is incompatible with
declare var o: {p: number};
^ number
Covariant and contravariant dictionaries can be incredibly useful, though. To
support this, the same syntax used to support variance for named properties can
be used for dictionaries as well.
function f(o: {+[key: string]: ?number}) {}
declare var o: {p: number};
f(o); // no type error!
Covariant-by-default Method Types
ES6 gave us a shorthand way to write object properties which are functions.
1
2
3
4
5
|
var o = {
m(x) {
return x * 2
}
}
|
{"value":"var o = {\n m(x) {\n return x * 2\n }\n}\n","tokens":[{"type":"T_VAR","context":"normal","value":"var","line":1,"start":0,"end":3},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":4,"end":5},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":6,"end":7},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":8,"end":9},{"type":"T_IDENTIFIER","context":"normal","value":"m","line":2,"start":12,"end":13},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":13,"end":14},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":2,"start":14,"end":15},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":15,"end":16},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":17,"end":18},{"type":"T_RETURN","context":"normal","value":"return","line":3,"start":23,"end":29},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":3,"start":30,"end":31},{"type":"T_MULT","context":"normal","value":"*","line":3,"start":32,"end":33},{"type":"T_NUMBER","context":"normal","value":"2","line":3,"start":34,"end":35},{"type":"T_RCURLY","context":"normal","value":"}","line":4,"start":38,"end":39},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":40,"end":41}],"errors":[]}
Flow now interprets properties which use this shorthand method syntax as
covariant by default. This means it is an error to write to the property m
.
If you don’t want covariance, you can use the long form syntax:
1
2
3
4
5
|
var o = {
m: function(x) {
return x * 2;
}
}
|
{"value":"var o = {\n m: function(x) {\n return x * 2;\n }\n}\n","tokens":[{"type":"T_VAR","context":"normal","value":"var","line":1,"start":0,"end":3},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":1,"start":4,"end":5},{"type":"T_ASSIGN","context":"normal","value":"=","line":1,"start":6,"end":7},{"type":"T_LCURLY","context":"normal","value":"{","line":1,"start":8,"end":9},{"type":"T_IDENTIFIER","context":"normal","value":"m","line":2,"start":12,"end":13},{"type":"T_COLON","context":"normal","value":":","line":2,"start":13,"end":14},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":15,"end":23},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":23,"end":24},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":2,"start":24,"end":25},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":25,"end":26},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":27,"end":28},{"type":"T_RETURN","context":"normal","value":"return","line":3,"start":33,"end":39},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":3,"start":40,"end":41},{"type":"T_MULT","context":"normal","value":"*","line":3,"start":42,"end":43},{"type":"T_NUMBER","context":"normal","value":"2","line":3,"start":44,"end":45},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":45,"end":46},{"type":"T_RCURLY","context":"normal","value":"}","line":4,"start":49,"end":50},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":51,"end":52}],"errors":[]}
More Flexible Getters and Setters
In Flow 0.33 and earlier, getters and setters had to agree exactly on their
return type and parameter type, respectively. Flow 0.34 lifts that restriction.
This means you can write code like the following:
1
2
3
4
5
6
7
8
9
10
11
|
declare var x: string;
var o = {
get x(): string {
return x;
},
set x(value: ?string) {
x = value || "default";
}
}
|
{"value":"// @flow\ndeclare var x: string;\n\nvar o = {\n get x(): string {\n return x;\n },\n set x(value: ?string) {\n x = value || \"default\";\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_DECLARE","context":"normal","value":"declare","line":2,"start":9,"end":16},{"type":"T_VAR","context":"normal","value":"var","line":2,"start":17,"end":20},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":2,"start":21,"end":22},{"type":"T_COLON","context":"type","value":":","line":2,"start":22,"end":23},{"type":"T_STRING_TYPE","context":"type","value":"string","line":2,"start":24,"end":30},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":30,"end":31},{"type":"T_VAR","context":"normal","value":"var","line":4,"start":33,"end":36},{"type":"T_IDENTIFIER","context":"normal","value":"o","line":4,"start":37,"end":38},{"type":"T_ASSIGN","context":"normal","value":"=","line":4,"start":39,"end":40},{"type":"T_LCURLY","context":"normal","value":"{","line":4,"start":41,"end":42},{"type":"T_IDENTIFIER","context":"normal","value":"get","line":5,"start":45,"end":48},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":5,"start":49,"end":50},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":50,"end":51},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":51,"end":52},{"type":"T_COLON","context":"type","value":":","line":5,"start":52,"end":53},{"type":"T_STRING_TYPE","context":"type","value":"string","line":5,"start":54,"end":60},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":61,"end":62},{"type":"T_RETURN","context":"normal","value":"return","line":6,"start":67,"end":73},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":6,"start":74,"end":75},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":75,"end":76},{"type":"T_RCURLY","context":"normal","value":"}","line":7,"start":79,"end":80},{"type":"T_COMMA","context":"normal","value":",","line":7,"start":80,"end":81},{"type":"T_IDENTIFIER","context":"normal","value":"set","line":8,"start":84,"end":87},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":8,"start":88,"end":89},{"type":"T_LPAREN","context":"normal","value":"(","line":8,"start":89,"end":90},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":8,"start":90,"end":95},{"type":"T_COLON","context":"type","value":":","line":8,"start":95,"end":96},{"type":"T_PLING","context":"type","value":"?","line":8,"start":97,"end":98},{"type":"T_STRING_TYPE","context":"type","value":"string","line":8,"start":98,"end":104},{"type":"T_RPAREN","context":"normal","value":")","line":8,"start":104,"end":105},{"type":"T_LCURLY","context":"normal","value":"{","line":8,"start":106,"end":107},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":9,"start":112,"end":113},{"type":"T_ASSIGN","context":"normal","value":"=","line":9,"start":114,"end":115},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":9,"start":116,"end":121},{"type":"T_OR","context":"normal","value":"||","line":9,"start":122,"end":124},{"type":"T_STRING","context":"normal","value":"\"default\"","line":9,"start":125,"end":134},{"type":"T_SEMICOLON","context":"normal","value":";","line":9,"start":134,"end":135},{"type":"T_RCURLY","context":"normal","value":"}","line":10,"start":138,"end":139},{"type":"T_RCURLY","context":"normal","value":"}","line":11,"start":140,"end":141}],"errors":[]}