Refinements are a frequently used aspect of many type systems. They are so
ingrained in the way that we program and even the way that we think you might
not even notice them.
In the code below, value can either be "A"
or "B"
.
1
2
3
4
5
6
|
function method(value: "A" | "B") {
if (value === "A") {
}
}
|
{"value":"// @flow\nfunction method(value: \"A\" | \"B\") {\n if (value === \"A\") {\n // value is \"A\"\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":9,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":18,"end":24},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":24,"end":25},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":25,"end":30},{"type":"T_COLON","context":"type","value":":","line":2,"start":30,"end":31},{"type":"T_STRING","context":"type","value":"\"A\"","line":2,"start":32,"end":35},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":36,"end":37},{"type":"T_STRING","context":"type","value":"\"B\"","line":2,"start":38,"end":41},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":41,"end":42},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":43,"end":44},{"type":"T_IF","context":"normal","value":"if","line":3,"start":47,"end":49},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":50,"end":51},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":3,"start":51,"end":56},{"type":"T_STRICT_EQUAL","context":"normal","value":"===","line":3,"start":57,"end":60},{"type":"T_STRING","context":"normal","value":"\"A\"","line":3,"start":61,"end":64},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":64,"end":65},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":66,"end":67},{"type":"Line","context":"comment","value":"// value is \"A\"","line":4,"start":72,"end":87},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":90,"end":91},{"type":"T_RCURLY","context":"normal","value":"}","line":6,"start":92,"end":93}],"errors":[]}
Inside of the if block we know that value must be "A"
because that’s the only
time the if-statement will be truthy.
The ability for a static type checker to be able to tell that the value inside
the if statement must be "A"
is known as a refinement.
Next we’ll add an else block to our if statement.
1
2
3
4
5
6
7
8
|
function method(value: "A" | "B") {
if (value === "A") {
} else {
}
}
|
{"value":"// @flow\nfunction method(value: \"A\" | \"B\") {\n if (value === \"A\") {\n // value is \"A\"\n } else {\n // value is \"B\"\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":9,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":18,"end":24},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":24,"end":25},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":25,"end":30},{"type":"T_COLON","context":"type","value":":","line":2,"start":30,"end":31},{"type":"T_STRING","context":"type","value":"\"A\"","line":2,"start":32,"end":35},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":36,"end":37},{"type":"T_STRING","context":"type","value":"\"B\"","line":2,"start":38,"end":41},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":41,"end":42},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":43,"end":44},{"type":"T_IF","context":"normal","value":"if","line":3,"start":47,"end":49},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":50,"end":51},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":3,"start":51,"end":56},{"type":"T_STRICT_EQUAL","context":"normal","value":"===","line":3,"start":57,"end":60},{"type":"T_STRING","context":"normal","value":"\"A\"","line":3,"start":61,"end":64},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":64,"end":65},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":66,"end":67},{"type":"Line","context":"comment","value":"// value is \"A\"","line":4,"start":72,"end":87},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":90,"end":91},{"type":"T_ELSE","context":"normal","value":"else","line":5,"start":92,"end":96},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":97,"end":98},{"type":"Line","context":"comment","value":"// value is \"B\"","line":6,"start":103,"end":118},{"type":"T_RCURLY","context":"normal","value":"}","line":7,"start":121,"end":122},{"type":"T_RCURLY","context":"normal","value":"}","line":8,"start":123,"end":124}],"errors":[]}
Inside of the else block we know that value must be "B"
because it can only
be "A"
or "B"
and we’ve removed "A"
from the possibilities.
You can expand this even further and keep refining possibilities away:
1
2
3
4
5
6
7
8
9
10
11
12
|
function method(value: "A" | "B" | "C" | "D") {
if (value === "A") {
} else if (value === "B") {
} else if (value === "C") {
} else {
}
}
|
{"value":"// @flow\nfunction method(value: \"A\" | \"B\" | \"C\" | \"D\") {\n if (value === \"A\") {\n // value is \"A\"\n } else if (value === \"B\") {\n // value is \"B\"\n } else if (value === \"C\") {\n // value is \"C\"\n } else {\n // value is \"D\"\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":9,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":18,"end":24},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":24,"end":25},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":25,"end":30},{"type":"T_COLON","context":"type","value":":","line":2,"start":30,"end":31},{"type":"T_STRING","context":"type","value":"\"A\"","line":2,"start":32,"end":35},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":36,"end":37},{"type":"T_STRING","context":"type","value":"\"B\"","line":2,"start":38,"end":41},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":42,"end":43},{"type":"T_STRING","context":"type","value":"\"C\"","line":2,"start":44,"end":47},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":48,"end":49},{"type":"T_STRING","context":"type","value":"\"D\"","line":2,"start":50,"end":53},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":53,"end":54},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":55,"end":56},{"type":"T_IF","context":"normal","value":"if","line":3,"start":59,"end":61},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":62,"end":63},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":3,"start":63,"end":68},{"type":"T_STRICT_EQUAL","context":"normal","value":"===","line":3,"start":69,"end":72},{"type":"T_STRING","context":"normal","value":"\"A\"","line":3,"start":73,"end":76},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":76,"end":77},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":78,"end":79},{"type":"Line","context":"comment","value":"// value is \"A\"","line":4,"start":84,"end":99},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":102,"end":103},{"type":"T_ELSE","context":"normal","value":"else","line":5,"start":104,"end":108},{"type":"T_IF","context":"normal","value":"if","line":5,"start":109,"end":111},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":112,"end":113},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":5,"start":113,"end":118},{"type":"T_STRICT_EQUAL","context":"normal","value":"===","line":5,"start":119,"end":122},{"type":"T_STRING","context":"normal","value":"\"B\"","line":5,"start":123,"end":126},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":126,"end":127},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":128,"end":129},{"type":"Line","context":"comment","value":"// value is \"B\"","line":6,"start":134,"end":149},{"type":"T_RCURLY","context":"normal","value":"}","line":7,"start":152,"end":153},{"type":"T_ELSE","context":"normal","value":"else","line":7,"start":154,"end":158},{"type":"T_IF","context":"normal","value":"if","line":7,"start":159,"end":161},{"type":"T_LPAREN","context":"normal","value":"(","line":7,"start":162,"end":163},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":7,"start":163,"end":168},{"type":"T_STRICT_EQUAL","context":"normal","value":"===","line":7,"start":169,"end":172},{"type":"T_STRING","context":"normal","value":"\"C\"","line":7,"start":173,"end":176},{"type":"T_RPAREN","context":"normal","value":")","line":7,"start":176,"end":177},{"type":"T_LCURLY","context":"normal","value":"{","line":7,"start":178,"end":179},{"type":"Line","context":"comment","value":"// value is \"C\"","line":8,"start":184,"end":199},{"type":"T_RCURLY","context":"normal","value":"}","line":9,"start":202,"end":203},{"type":"T_ELSE","context":"normal","value":"else","line":9,"start":204,"end":208},{"type":"T_LCURLY","context":"normal","value":"{","line":9,"start":209,"end":210},{"type":"Line","context":"comment","value":"// value is \"D\"","line":10,"start":215,"end":230},{"type":"T_RCURLY","context":"normal","value":"}","line":11,"start":233,"end":234},{"type":"T_RCURLY","context":"normal","value":"}","line":12,"start":235,"end":236}],"errors":[]}
Refinements can also come in other forms other than testing for equality:
1
2
3
4
5
6
7
8
9
10
|
function method(value: boolean | Array<string> | Event) {
if (typeof value === "boolean") {
} else if (Array.isArray(value)) {
} else if (value instanceof Event) {
}
}
|
{"value":"// @flow\nfunction method(value: boolean | Array<string> | Event) {\n if (typeof value === \"boolean\") {\n // value is a boolean\n } else if (Array.isArray(value)) {\n // value is an Array\n } else if (value instanceof Event) {\n // value is an Event\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":9,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":18,"end":24},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":24,"end":25},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":25,"end":30},{"type":"T_COLON","context":"type","value":":","line":2,"start":30,"end":31},{"type":"T_BOOLEAN_TYPE","context":"type","value":"boolean","line":2,"start":32,"end":39},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":40,"end":41},{"type":"T_IDENTIFIER","context":"type","value":"Array","line":2,"start":42,"end":47},{"type":"T_LESS_THAN","context":"type","value":"<","line":2,"start":47,"end":48},{"type":"T_STRING_TYPE","context":"type","value":"string","line":2,"start":48,"end":54},{"type":"T_GREATER_THAN","context":"type","value":">","line":2,"start":54,"end":55},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"type","value":"Event","line":2,"start":58,"end":63},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":63,"end":64},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":65,"end":66},{"type":"T_IF","context":"normal","value":"if","line":3,"start":69,"end":71},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":72,"end":73},{"type":"T_TYPEOF","context":"normal","value":"typeof","line":3,"start":73,"end":79},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":3,"start":80,"end":85},{"type":"T_STRICT_EQUAL","context":"normal","value":"===","line":3,"start":86,"end":89},{"type":"T_STRING","context":"normal","value":"\"boolean\"","line":3,"start":90,"end":99},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":99,"end":100},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":101,"end":102},{"type":"Line","context":"comment","value":"// value is a boolean","line":4,"start":107,"end":128},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":131,"end":132},{"type":"T_ELSE","context":"normal","value":"else","line":5,"start":133,"end":137},{"type":"T_IF","context":"normal","value":"if","line":5,"start":138,"end":140},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":141,"end":142},{"type":"T_IDENTIFIER","context":"normal","value":"Array","line":5,"start":142,"end":147},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":147,"end":148},{"type":"T_IDENTIFIER","context":"normal","value":"isArray","line":5,"start":148,"end":155},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":155,"end":156},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":5,"start":156,"end":161},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":161,"end":162},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":162,"end":163},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":164,"end":165},{"type":"Line","context":"comment","value":"// value is an Array","line":6,"start":170,"end":190},{"type":"T_RCURLY","context":"normal","value":"}","line":7,"start":193,"end":194},{"type":"T_ELSE","context":"normal","value":"else","line":7,"start":195,"end":199},{"type":"T_IF","context":"normal","value":"if","line":7,"start":200,"end":202},{"type":"T_LPAREN","context":"normal","value":"(","line":7,"start":203,"end":204},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":7,"start":204,"end":209},{"type":"T_INSTANCEOF","context":"normal","value":"instanceof","line":7,"start":210,"end":220},{"type":"T_IDENTIFIER","context":"normal","value":"Event","line":7,"start":221,"end":226},{"type":"T_RPAREN","context":"normal","value":")","line":7,"start":226,"end":227},{"type":"T_LCURLY","context":"normal","value":"{","line":7,"start":228,"end":229},{"type":"Line","context":"comment","value":"// value is an Event","line":8,"start":234,"end":254},{"type":"T_RCURLY","context":"normal","value":"}","line":9,"start":257,"end":258},{"type":"T_RCURLY","context":"normal","value":"}","line":10,"start":259,"end":260}],"errors":[]}
Or you could refine on the shape of objects.
1
2
3
4
5
6
7
8
9
10
11
|
type A = { type: "A" };
type B = { type: "B" };
function method(value: A | B) {
if (value.type === "A") {
} else {
}
}
|
{"value":"// @flow\ntype A = { type: \"A\" };\ntype B = { type: \"B\" };\n\nfunction method(value: A | B) {\n if (value.type === \"A\") {\n // value is A\n } else {\n // value is B\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_TYPE","context":"normal","value":"type","line":2,"start":9,"end":13},{"type":"T_IDENTIFIER","context":"type","value":"A","line":2,"start":14,"end":15},{"type":"T_ASSIGN","context":"type","value":"=","line":2,"start":16,"end":17},{"type":"T_LCURLY","context":"type","value":"{","line":2,"start":18,"end":19},{"type":"T_TYPE","context":"normal","value":"type","line":2,"start":20,"end":24},{"type":"T_COLON","context":"type","value":":","line":2,"start":24,"end":25},{"type":"T_STRING","context":"type","value":"\"A\"","line":2,"start":26,"end":29},{"type":"T_RCURLY","context":"type","value":"}","line":2,"start":30,"end":31},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":31,"end":32},{"type":"T_TYPE","context":"normal","value":"type","line":3,"start":33,"end":37},{"type":"T_IDENTIFIER","context":"type","value":"B","line":3,"start":38,"end":39},{"type":"T_ASSIGN","context":"type","value":"=","line":3,"start":40,"end":41},{"type":"T_LCURLY","context":"type","value":"{","line":3,"start":42,"end":43},{"type":"T_TYPE","context":"normal","value":"type","line":3,"start":44,"end":48},{"type":"T_COLON","context":"type","value":":","line":3,"start":48,"end":49},{"type":"T_STRING","context":"type","value":"\"B\"","line":3,"start":50,"end":53},{"type":"T_RCURLY","context":"type","value":"}","line":3,"start":54,"end":55},{"type":"T_SEMICOLON","context":"normal","value":";","line":3,"start":55,"end":56},{"type":"T_FUNCTION","context":"normal","value":"function","line":5,"start":58,"end":66},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":5,"start":67,"end":73},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":73,"end":74},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":5,"start":74,"end":79},{"type":"T_COLON","context":"type","value":":","line":5,"start":79,"end":80},{"type":"T_IDENTIFIER","context":"type","value":"A","line":5,"start":81,"end":82},{"type":"T_BIT_OR","context":"type","value":"|","line":5,"start":83,"end":84},{"type":"T_IDENTIFIER","context":"type","value":"B","line":5,"start":85,"end":86},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":86,"end":87},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":88,"end":89},{"type":"T_IF","context":"normal","value":"if","line":6,"start":92,"end":94},{"type":"T_LPAREN","context":"normal","value":"(","line":6,"start":95,"end":96},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":6,"start":96,"end":101},{"type":"T_PERIOD","context":"normal","value":".","line":6,"start":101,"end":102},{"type":"T_TYPE","context":"normal","value":"type","line":6,"start":102,"end":106},{"type":"T_STRICT_EQUAL","context":"normal","value":"===","line":6,"start":107,"end":110},{"type":"T_STRING","context":"normal","value":"\"A\"","line":6,"start":111,"end":114},{"type":"T_RPAREN","context":"normal","value":")","line":6,"start":114,"end":115},{"type":"T_LCURLY","context":"normal","value":"{","line":6,"start":116,"end":117},{"type":"Line","context":"comment","value":"// value is A","line":7,"start":122,"end":135},{"type":"T_RCURLY","context":"normal","value":"}","line":8,"start":138,"end":139},{"type":"T_ELSE","context":"normal","value":"else","line":8,"start":140,"end":144},{"type":"T_LCURLY","context":"normal","value":"{","line":8,"start":145,"end":146},{"type":"Line","context":"comment","value":"// value is B","line":9,"start":151,"end":164},{"type":"T_RCURLY","context":"normal","value":"}","line":10,"start":167,"end":168},{"type":"T_RCURLY","context":"normal","value":"}","line":11,"start":169,"end":170}],"errors":[]}
Which also applies to nested types within objects.
1
2
3
4
5
6
|
function method(value: { prop?: string }) {
if (value.prop) {
value.prop.charAt(0);
}
}
|
{"value":"// @flow\nfunction method(value: { prop?: string }) {\n if (value.prop) {\n value.prop.charAt(0);\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":9,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":2,"start":18,"end":24},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":24,"end":25},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":2,"start":25,"end":30},{"type":"T_COLON","context":"type","value":":","line":2,"start":30,"end":31},{"type":"T_LCURLY","context":"type","value":"{","line":2,"start":32,"end":33},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":2,"start":34,"end":38},{"type":"T_PLING","context":"type","value":"?","line":2,"start":38,"end":39},{"type":"T_COLON","context":"type","value":":","line":2,"start":39,"end":40},{"type":"T_STRING_TYPE","context":"type","value":"string","line":2,"start":41,"end":47},{"type":"T_RCURLY","context":"type","value":"}","line":2,"start":48,"end":49},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":49,"end":50},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":51,"end":52},{"type":"T_IF","context":"normal","value":"if","line":3,"start":55,"end":57},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":58,"end":59},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":3,"start":59,"end":64},{"type":"T_PERIOD","context":"normal","value":".","line":3,"start":64,"end":65},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":3,"start":65,"end":69},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":69,"end":70},{"type":"T_LCURLY","context":"normal","value":"{","line":3,"start":71,"end":72},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":4,"start":77,"end":82},{"type":"T_PERIOD","context":"normal","value":".","line":4,"start":82,"end":83},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":4,"start":83,"end":87},{"type":"T_PERIOD","context":"normal","value":".","line":4,"start":87,"end":88},{"type":"T_IDENTIFIER","context":"normal","value":"charAt","line":4,"start":88,"end":94},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":94,"end":95},{"type":"T_NUMBER","context":"normal","value":"0","line":4,"start":95,"end":96},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":96,"end":97},{"type":"T_SEMICOLON","context":"normal","value":";","line":4,"start":97,"end":98},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":101,"end":102},{"type":"T_RCURLY","context":"normal","value":"}","line":6,"start":103,"end":104}],"errors":[]}
Refinement Invalidations
It is also possible to invalidate refinements, for example:
1
2
3
4
5
6
7
8
9
10
|
function otherMethod() { }
function method(value: { prop?: string }) {
if (value.prop) {
otherMethod();
value.prop.charAt(0);
}
}
|
Cannot call `value.prop.charAt` because property `charAt` is missing in undefined [1]. [incompatible-use]
{"value":"// @flow\nfunction otherMethod() { /* ... */ }\n\nfunction method(value: { prop?: string }) {\n if (value.prop) {\n otherMethod();\n // $ExpectError\n value.prop.charAt(0);\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":9,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"otherMethod","line":2,"start":18,"end":29},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":29,"end":30},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":30,"end":31},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":32,"end":33},{"type":"Block","context":"comment","value":"/* ... */","line":2,"start":34,"end":43},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":44,"end":45},{"type":"T_FUNCTION","context":"normal","value":"function","line":4,"start":47,"end":55},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":4,"start":56,"end":62},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":62,"end":63},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":4,"start":63,"end":68},{"type":"T_COLON","context":"type","value":":","line":4,"start":68,"end":69},{"type":"T_LCURLY","context":"type","value":"{","line":4,"start":70,"end":71},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":4,"start":72,"end":76},{"type":"T_PLING","context":"type","value":"?","line":4,"start":76,"end":77},{"type":"T_COLON","context":"type","value":":","line":4,"start":77,"end":78},{"type":"T_STRING_TYPE","context":"type","value":"string","line":4,"start":79,"end":85},{"type":"T_RCURLY","context":"type","value":"}","line":4,"start":86,"end":87},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":87,"end":88},{"type":"T_LCURLY","context":"normal","value":"{","line":4,"start":89,"end":90},{"type":"T_IF","context":"normal","value":"if","line":5,"start":93,"end":95},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":96,"end":97},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":5,"start":97,"end":102},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":102,"end":103},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":5,"start":103,"end":107},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":107,"end":108},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":109,"end":110},{"type":"T_IDENTIFIER","context":"normal","value":"otherMethod","line":6,"start":115,"end":126},{"type":"T_LPAREN","context":"normal","value":"(","line":6,"start":126,"end":127},{"type":"T_RPAREN","context":"normal","value":")","line":6,"start":127,"end":128},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":128,"end":129},{"type":"Line","context":"comment","value":"// $ExpectError","line":7,"start":134,"end":149},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":8,"start":154,"end":159},{"type":"T_PERIOD","context":"normal","value":".","line":8,"start":159,"end":160},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":8,"start":160,"end":164},{"type":"T_PERIOD","context":"normal","value":".","line":8,"start":164,"end":165},{"type":"T_IDENTIFIER","context":"normal","value":"charAt","line":8,"start":165,"end":171},{"type":"T_LPAREN","context":"normal","value":"(","line":8,"start":171,"end":172},{"type":"T_NUMBER","context":"normal","value":"0","line":8,"start":172,"end":173},{"type":"T_RPAREN","context":"normal","value":")","line":8,"start":173,"end":174},{"type":"T_SEMICOLON","context":"normal","value":";","line":8,"start":174,"end":175},{"type":"T_RCURLY","context":"normal","value":"}","line":9,"start":178,"end":179},{"type":"T_RCURLY","context":"normal","value":"}","line":10,"start":180,"end":181}],"errors":[{"id":"E1","messages":[{"id":"E1M1","description":"Cannot call `value.prop.charAt` because property `charAt` is missing in undefined [1]. [incompatible-use]","context":" value.prop.charAt(0);","source":"-","start":{"line":8,"column":16,"offset":165},"end":{"line":8,"column":21,"offset":171}}],"operation":null}]}
The reason for this is that we don’t know that otherMethod()
hasn’t done
something to our value. Imagine the following scenario:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
var obj = { prop: "test" };
function otherMethod() {
if (Math.random() > 0.5) {
delete obj.prop;
}
}
function method(value: { prop?: string }) {
if (value.prop) {
otherMethod();
value.prop.charAt(0);
}
}
method(obj);
|
Cannot delete `obj.prop` because undefined [1] is incompatible with string [2]. [incompatible-type]
Cannot call `value.prop.charAt` because property `charAt` is missing in undefined [1]. [incompatible-use]
{"value":"// @flow\nvar obj = { prop: \"test\" };\n\nfunction otherMethod() {\n if (Math.random() > 0.5) {\n delete obj.prop;\n }\n}\n\nfunction method(value: { prop?: string }) {\n if (value.prop) {\n otherMethod();\n // $ExpectError\n value.prop.charAt(0);\n }\n}\n\nmethod(obj);\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_VAR","context":"normal","value":"var","line":2,"start":9,"end":12},{"type":"T_IDENTIFIER","context":"normal","value":"obj","line":2,"start":13,"end":16},{"type":"T_ASSIGN","context":"normal","value":"=","line":2,"start":17,"end":18},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":19,"end":20},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":2,"start":21,"end":25},{"type":"T_COLON","context":"normal","value":":","line":2,"start":25,"end":26},{"type":"T_STRING","context":"normal","value":"\"test\"","line":2,"start":27,"end":33},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":34,"end":35},{"type":"T_SEMICOLON","context":"normal","value":";","line":2,"start":35,"end":36},{"type":"T_FUNCTION","context":"normal","value":"function","line":4,"start":38,"end":46},{"type":"T_IDENTIFIER","context":"normal","value":"otherMethod","line":4,"start":47,"end":58},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":58,"end":59},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":59,"end":60},{"type":"T_LCURLY","context":"normal","value":"{","line":4,"start":61,"end":62},{"type":"T_IF","context":"normal","value":"if","line":5,"start":65,"end":67},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":68,"end":69},{"type":"T_IDENTIFIER","context":"normal","value":"Math","line":5,"start":69,"end":73},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":73,"end":74},{"type":"T_IDENTIFIER","context":"normal","value":"random","line":5,"start":74,"end":80},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":80,"end":81},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":81,"end":82},{"type":"T_GREATER_THAN","context":"normal","value":">","line":5,"start":83,"end":84},{"type":"T_NUMBER","context":"normal","value":"0.5","line":5,"start":85,"end":88},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":88,"end":89},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":90,"end":91},{"type":"T_DELETE","context":"normal","value":"delete","line":6,"start":96,"end":102},{"type":"T_IDENTIFIER","context":"normal","value":"obj","line":6,"start":103,"end":106},{"type":"T_PERIOD","context":"normal","value":".","line":6,"start":106,"end":107},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":6,"start":107,"end":111},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":111,"end":112},{"type":"T_RCURLY","context":"normal","value":"}","line":7,"start":115,"end":116},{"type":"T_RCURLY","context":"normal","value":"}","line":8,"start":117,"end":118},{"type":"T_FUNCTION","context":"normal","value":"function","line":10,"start":120,"end":128},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":10,"start":129,"end":135},{"type":"T_LPAREN","context":"normal","value":"(","line":10,"start":135,"end":136},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":10,"start":136,"end":141},{"type":"T_COLON","context":"type","value":":","line":10,"start":141,"end":142},{"type":"T_LCURLY","context":"type","value":"{","line":10,"start":143,"end":144},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":10,"start":145,"end":149},{"type":"T_PLING","context":"type","value":"?","line":10,"start":149,"end":150},{"type":"T_COLON","context":"type","value":":","line":10,"start":150,"end":151},{"type":"T_STRING_TYPE","context":"type","value":"string","line":10,"start":152,"end":158},{"type":"T_RCURLY","context":"type","value":"}","line":10,"start":159,"end":160},{"type":"T_RPAREN","context":"normal","value":")","line":10,"start":160,"end":161},{"type":"T_LCURLY","context":"normal","value":"{","line":10,"start":162,"end":163},{"type":"T_IF","context":"normal","value":"if","line":11,"start":166,"end":168},{"type":"T_LPAREN","context":"normal","value":"(","line":11,"start":169,"end":170},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":11,"start":170,"end":175},{"type":"T_PERIOD","context":"normal","value":".","line":11,"start":175,"end":176},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":11,"start":176,"end":180},{"type":"T_RPAREN","context":"normal","value":")","line":11,"start":180,"end":181},{"type":"T_LCURLY","context":"normal","value":"{","line":11,"start":182,"end":183},{"type":"T_IDENTIFIER","context":"normal","value":"otherMethod","line":12,"start":188,"end":199},{"type":"T_LPAREN","context":"normal","value":"(","line":12,"start":199,"end":200},{"type":"T_RPAREN","context":"normal","value":")","line":12,"start":200,"end":201},{"type":"T_SEMICOLON","context":"normal","value":";","line":12,"start":201,"end":202},{"type":"Line","context":"comment","value":"// $ExpectError","line":13,"start":207,"end":222},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":14,"start":227,"end":232},{"type":"T_PERIOD","context":"normal","value":".","line":14,"start":232,"end":233},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":14,"start":233,"end":237},{"type":"T_PERIOD","context":"normal","value":".","line":14,"start":237,"end":238},{"type":"T_IDENTIFIER","context":"normal","value":"charAt","line":14,"start":238,"end":244},{"type":"T_LPAREN","context":"normal","value":"(","line":14,"start":244,"end":245},{"type":"T_NUMBER","context":"normal","value":"0","line":14,"start":245,"end":246},{"type":"T_RPAREN","context":"normal","value":")","line":14,"start":246,"end":247},{"type":"T_SEMICOLON","context":"normal","value":";","line":14,"start":247,"end":248},{"type":"T_RCURLY","context":"normal","value":"}","line":15,"start":251,"end":252},{"type":"T_RCURLY","context":"normal","value":"}","line":16,"start":253,"end":254},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":18,"start":256,"end":262},{"type":"T_LPAREN","context":"normal","value":"(","line":18,"start":262,"end":263},{"type":"T_IDENTIFIER","context":"normal","value":"obj","line":18,"start":263,"end":266},{"type":"T_RPAREN","context":"normal","value":")","line":18,"start":266,"end":267},{"type":"T_SEMICOLON","context":"normal","value":";","line":18,"start":267,"end":268}],"errors":[{"id":"E1","messages":[{"id":"E1M1","description":"Cannot delete `obj.prop` because undefined [1] is incompatible with string [2]. [incompatible-type]","context":" delete obj.prop;","source":"-","start":{"line":6,"column":12,"offset":103},"end":{"line":6,"column":19,"offset":111}}],"operation":null},{"id":"E2","messages":[{"id":"E2M1","description":"Cannot call `value.prop.charAt` because property `charAt` is missing in undefined [1]. [incompatible-use]","context":" value.prop.charAt(0);","source":"-","start":{"line":14,"column":16,"offset":238},"end":{"line":14,"column":21,"offset":244}}],"operation":null}]}
Inside of otherMethod()
we sometimes remove prop
. Flow doesn’t know if the
if (value.prop)
check is still true, so it invalidates the refinement.
There’s a straightforward way to get around this. Store the value before
calling another method and use the stored value instead. This way you can
prevent the refinement from invalidating.
1
2
3
4
5
6
7
8
9
10
|
function otherMethod() { }
function method(value: { prop?: string }) {
if (value.prop) {
var prop = value.prop;
otherMethod();
prop.charAt(0);
}
}
|
{"value":"// @flow\nfunction otherMethod() { /* ... */ }\n\nfunction method(value: { prop?: string }) {\n if (value.prop) {\n var prop = value.prop;\n otherMethod();\n prop.charAt(0);\n }\n}\n","tokens":[{"type":"Line","context":"comment","value":"// @flow","line":1,"start":0,"end":8},{"type":"T_FUNCTION","context":"normal","value":"function","line":2,"start":9,"end":17},{"type":"T_IDENTIFIER","context":"normal","value":"otherMethod","line":2,"start":18,"end":29},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":29,"end":30},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":30,"end":31},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":32,"end":33},{"type":"Block","context":"comment","value":"/* ... */","line":2,"start":34,"end":43},{"type":"T_RCURLY","context":"normal","value":"}","line":2,"start":44,"end":45},{"type":"T_FUNCTION","context":"normal","value":"function","line":4,"start":47,"end":55},{"type":"T_IDENTIFIER","context":"normal","value":"method","line":4,"start":56,"end":62},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":62,"end":63},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":4,"start":63,"end":68},{"type":"T_COLON","context":"type","value":":","line":4,"start":68,"end":69},{"type":"T_LCURLY","context":"type","value":"{","line":4,"start":70,"end":71},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":4,"start":72,"end":76},{"type":"T_PLING","context":"type","value":"?","line":4,"start":76,"end":77},{"type":"T_COLON","context":"type","value":":","line":4,"start":77,"end":78},{"type":"T_STRING_TYPE","context":"type","value":"string","line":4,"start":79,"end":85},{"type":"T_RCURLY","context":"type","value":"}","line":4,"start":86,"end":87},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":87,"end":88},{"type":"T_LCURLY","context":"normal","value":"{","line":4,"start":89,"end":90},{"type":"T_IF","context":"normal","value":"if","line":5,"start":93,"end":95},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":96,"end":97},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":5,"start":97,"end":102},{"type":"T_PERIOD","context":"normal","value":".","line":5,"start":102,"end":103},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":5,"start":103,"end":107},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":107,"end":108},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":109,"end":110},{"type":"T_VAR","context":"normal","value":"var","line":6,"start":115,"end":118},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":6,"start":119,"end":123},{"type":"T_ASSIGN","context":"normal","value":"=","line":6,"start":124,"end":125},{"type":"T_IDENTIFIER","context":"normal","value":"value","line":6,"start":126,"end":131},{"type":"T_PERIOD","context":"normal","value":".","line":6,"start":131,"end":132},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":6,"start":132,"end":136},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":136,"end":137},{"type":"T_IDENTIFIER","context":"normal","value":"otherMethod","line":7,"start":142,"end":153},{"type":"T_LPAREN","context":"normal","value":"(","line":7,"start":153,"end":154},{"type":"T_RPAREN","context":"normal","value":")","line":7,"start":154,"end":155},{"type":"T_SEMICOLON","context":"normal","value":";","line":7,"start":155,"end":156},{"type":"T_IDENTIFIER","context":"normal","value":"prop","line":8,"start":161,"end":165},{"type":"T_PERIOD","context":"normal","value":".","line":8,"start":165,"end":166},{"type":"T_IDENTIFIER","context":"normal","value":"charAt","line":8,"start":166,"end":172},{"type":"T_LPAREN","context":"normal","value":"(","line":8,"start":172,"end":173},{"type":"T_NUMBER","context":"normal","value":"0","line":8,"start":173,"end":174},{"type":"T_RPAREN","context":"normal","value":")","line":8,"start":174,"end":175},{"type":"T_SEMICOLON","context":"normal","value":";","line":8,"start":175,"end":176},{"type":"T_RCURLY","context":"normal","value":"}","line":9,"start":179,"end":180},{"type":"T_RCURLY","context":"normal","value":"}","line":10,"start":181,"end":182}],"errors":[]}