What is a subtype?
A type like number
, boolean
, or string
describes a set of possible
values. A number
describes every possible number, so a single number
(such as 42
) would be a subtype of the number
type.
If we want to know whether one type is the subtype of another, we need to look at
all the possible values for both types and figure out if the other has a
subset of the values.
For example, if we had a TypeA
which described the numbers 1 through 3, and
a TypeB
which described the numbers 1 through 5: TypeA
would be considered
a subtype of TypeB
, because TypeA
is a subset of TypeB
.
1
2
|
type TypeA = 1 | 2 | 3;
type TypeB = 1 | 2 | 3 | 4 | 5;
|
{"value":"type TypeA = 1 | 2 | 3;\ntype TypeB = 1 | 2 | 3 | 4 | 5;\n","tokens":[{"type":"T_TYPE","context":"normal","value":"type","line":1,"start":0,"end":4},{"type":"T_IDENTIFIER","context":"type","value":"TypeA","line":1,"start":5,"end":10},{"type":"T_ASSIGN","context":"type","value":"=","line":1,"start":11,"end":12},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"1","line":1,"start":13,"end":14},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":15,"end":16},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"2","line":1,"start":17,"end":18},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":19,"end":20},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"3","line":1,"start":21,"end":22},{"type":"T_SEMICOLON","context":"type","value":";","line":1,"start":22,"end":23},{"type":"T_TYPE","context":"normal","value":"type","line":2,"start":24,"end":28},{"type":"T_IDENTIFIER","context":"type","value":"TypeB","line":2,"start":29,"end":34},{"type":"T_ASSIGN","context":"type","value":"=","line":2,"start":35,"end":36},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"1","line":2,"start":37,"end":38},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":39,"end":40},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"2","line":2,"start":41,"end":42},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":43,"end":44},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"3","line":2,"start":45,"end":46},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":47,"end":48},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"4","line":2,"start":49,"end":50},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":51,"end":52},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"5","line":2,"start":53,"end":54},{"type":"T_SEMICOLON","context":"type","value":";","line":2,"start":54,"end":55}],"errors":[]}
Consider a TypeLetters
which described the strings: “A”, “B”, “C”, and a
TypeNumbers
which described the numbers: 1, 2, 3. Neither of them would
be a subtype of the other, as they each contain a completely different set of
values.
1
2
|
type TypeLetters = "A" | "B" | "C";
type TypeNumbers = 1 | 2 | 3;
|
{"value":"type TypeLetters = \"A\" | \"B\" | \"C\";\ntype TypeNumbers = 1 | 2 | 3;\n","tokens":[{"type":"T_TYPE","context":"normal","value":"type","line":1,"start":0,"end":4},{"type":"T_IDENTIFIER","context":"type","value":"TypeLetters","line":1,"start":5,"end":16},{"type":"T_ASSIGN","context":"type","value":"=","line":1,"start":17,"end":18},{"type":"T_STRING","context":"type","value":"\"A\"","line":1,"start":19,"end":22},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":23,"end":24},{"type":"T_STRING","context":"type","value":"\"B\"","line":1,"start":25,"end":28},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":29,"end":30},{"type":"T_STRING","context":"type","value":"\"C\"","line":1,"start":31,"end":34},{"type":"T_SEMICOLON","context":"type","value":";","line":1,"start":34,"end":35},{"type":"T_TYPE","context":"normal","value":"type","line":2,"start":36,"end":40},{"type":"T_IDENTIFIER","context":"type","value":"TypeNumbers","line":2,"start":41,"end":52},{"type":"T_ASSIGN","context":"type","value":"=","line":2,"start":53,"end":54},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"1","line":2,"start":56,"end":57},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":59,"end":60},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"2","line":2,"start":62,"end":63},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":65,"end":66},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"3","line":2,"start":68,"end":69},{"type":"T_SEMICOLON","context":"type","value":";","line":2,"start":69,"end":70}],"errors":[]}
Finally, if we had a TypeA
which described the numbers 1 through 3, and a
TypeB
which described the numbers 3 through 5. Neither of them would be a
subtype of the other. Even though they both have 3 and describe numbers, they
each have some unique items.
1
2
|
type TypeA = 1 | 2 | 3;
type TypeB = 3 | 4 | 5;
|
{"value":"type TypeA = 1 | 2 | 3;\ntype TypeB = 3 | 4 | 5;\n","tokens":[{"type":"T_TYPE","context":"normal","value":"type","line":1,"start":0,"end":4},{"type":"T_IDENTIFIER","context":"type","value":"TypeA","line":1,"start":5,"end":10},{"type":"T_ASSIGN","context":"type","value":"=","line":1,"start":11,"end":12},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"1","line":1,"start":13,"end":14},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":15,"end":16},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"2","line":1,"start":17,"end":18},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":19,"end":20},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"3","line":1,"start":21,"end":22},{"type":"T_SEMICOLON","context":"type","value":";","line":1,"start":22,"end":23},{"type":"T_TYPE","context":"normal","value":"type","line":2,"start":24,"end":28},{"type":"T_IDENTIFIER","context":"type","value":"TypeB","line":2,"start":29,"end":34},{"type":"T_ASSIGN","context":"type","value":"=","line":2,"start":35,"end":36},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"3","line":2,"start":45,"end":46},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":47,"end":48},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"4","line":2,"start":49,"end":50},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":51,"end":52},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"5","line":2,"start":53,"end":54},{"type":"T_SEMICOLON","context":"type","value":";","line":2,"start":54,"end":55}],"errors":[]}
When are subtypes used?
Most of the work that Flow does is comparing types against one another.
For example, in order to know if you are calling a function correctly, Flow
needs to compare the arguments you are passing with the parameters the
function expects.
This often means figuring out if the value you are passing in is a subtype of
the value you are expecting.
So if I write a function that expects the numbers 1 through 5, any subtype of
that set will be acceptable.
1
2
3
4
5
6
7
8
9
10
11
|
function f(param: 1 | 2 | 3 | 4 | 5) {
}
declare var oneOrTwo: 1 | 2;
declare var fiveOrSix: 5 | 6;
f(oneOrTwo);
f(fiveOrSix);
|
Cannot call `f` with `fiveOrSix` bound to `param` because number literal `6` [1] is incompatible with enum [2].
{"value":"// @flow\nfunction f(param: 1 | 2 | 3 | 4 | 5) {\n // ...\n}\n\ndeclare var oneOrTwo: 1 | 2; // Subset of the input parameters type.\ndeclare var fiveOrSix: 5 | 6; // Not a subset of the input parameters type.\n\nf(oneOrTwo); // Works!\n// $ExpectError\nf(fiveOrSix); // Error!\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":"f","line":2,"start":18,"end":19},{"type":"T_LPAREN","context":"normal","value":"(","line":2,"start":19,"end":20},{"type":"T_IDENTIFIER","context":"normal","value":"param","line":2,"start":20,"end":25},{"type":"T_COLON","context":"type","value":":","line":2,"start":25,"end":26},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"1","line":2,"start":27,"end":28},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":29,"end":30},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"2","line":2,"start":31,"end":32},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":33,"end":34},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"3","line":2,"start":35,"end":36},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":37,"end":38},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"4","line":2,"start":39,"end":40},{"type":"T_BIT_OR","context":"type","value":"|","line":2,"start":41,"end":42},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"5","line":2,"start":43,"end":44},{"type":"T_RPAREN","context":"normal","value":")","line":2,"start":44,"end":45},{"type":"T_LCURLY","context":"normal","value":"{","line":2,"start":46,"end":47},{"type":"Line","context":"comment","value":"// ...","line":3,"start":50,"end":56},{"type":"T_RCURLY","context":"normal","value":"}","line":4,"start":57,"end":58},{"type":"T_DECLARE","context":"normal","value":"declare","line":6,"start":60,"end":67},{"type":"T_VAR","context":"normal","value":"var","line":6,"start":68,"end":71},{"type":"T_IDENTIFIER","context":"normal","value":"oneOrTwo","line":6,"start":72,"end":80},{"type":"T_COLON","context":"type","value":":","line":6,"start":80,"end":81},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"1","line":6,"start":82,"end":83},{"type":"T_BIT_OR","context":"type","value":"|","line":6,"start":84,"end":85},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"2","line":6,"start":87,"end":88},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":88,"end":89},{"type":"Line","context":"comment","value":"// Subset of the input parameters type.","line":6,"start":90,"end":129},{"type":"T_DECLARE","context":"normal","value":"declare","line":7,"start":130,"end":137},{"type":"T_VAR","context":"normal","value":"var","line":7,"start":138,"end":141},{"type":"T_IDENTIFIER","context":"normal","value":"fiveOrSix","line":7,"start":142,"end":151},{"type":"T_COLON","context":"type","value":":","line":7,"start":151,"end":152},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"5","line":7,"start":153,"end":154},{"type":"T_BIT_OR","context":"type","value":"|","line":7,"start":155,"end":156},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"6","line":7,"start":157,"end":158},{"type":"T_SEMICOLON","context":"normal","value":";","line":7,"start":158,"end":159},{"type":"Line","context":"comment","value":"// Not a subset of the input parameters type.","line":7,"start":160,"end":205},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":9,"start":207,"end":208},{"type":"T_LPAREN","context":"normal","value":"(","line":9,"start":208,"end":209},{"type":"T_IDENTIFIER","context":"normal","value":"oneOrTwo","line":9,"start":209,"end":217},{"type":"T_RPAREN","context":"normal","value":")","line":9,"start":217,"end":218},{"type":"T_SEMICOLON","context":"normal","value":";","line":9,"start":218,"end":219},{"type":"Line","context":"comment","value":"// Works!","line":9,"start":220,"end":229},{"type":"Line","context":"comment","value":"// $ExpectError","line":10,"start":230,"end":245},{"type":"T_IDENTIFIER","context":"normal","value":"f","line":11,"start":246,"end":247},{"type":"T_LPAREN","context":"normal","value":"(","line":11,"start":247,"end":248},{"type":"T_IDENTIFIER","context":"normal","value":"fiveOrSix","line":11,"start":248,"end":257},{"type":"T_RPAREN","context":"normal","value":")","line":11,"start":257,"end":258},{"type":"T_SEMICOLON","context":"normal","value":";","line":11,"start":258,"end":259},{"type":"Line","context":"comment","value":"// Error!","line":11,"start":260,"end":269}],"errors":[{"id":"E1","messages":[{"id":"E1M1","description":"Cannot call `f` with `fiveOrSix` bound to `param` because number literal `6` [1] is incompatible with enum [2].","context":"f(fiveOrSix); // Error!","source":"-","start":{"line":11,"column":3,"offset":248},"end":{"line":11,"column":11,"offset":257}}],"operation":null}]}
Subtypes of complex types
Flow needs to compare more than just sets of primitive values, it also needs to
be able to compare objects, functions, and every other type that appears in the
language.
Subtypes of objects
You can start to compare two objects by their keys. If one object contains all
the keys of another object, then it may be a subtype.
For example, if we had an ObjectA
which contained the key foo
, and an
ObjectB
which contained the keys foo
and bar
. Then it’s possible that
ObjectB
is a subtype of ObjectA
.
1
2
3
4
5
6
|
type ObjectA = { foo: string };
type ObjectB = { foo: string, bar: number };
let objectB: ObjectB = { foo: 'test', bar: 42 };
let objectA: ObjectA = objectB;
|
{"value":"// @flow\ntype ObjectA = { foo: string };\ntype ObjectB = { foo: string, bar: number };\n\nlet objectB: ObjectB = { foo: 'test', bar: 42 };\nlet objectA: ObjectA = objectB; // Works!\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":"ObjectA","line":2,"start":14,"end":21},{"type":"T_ASSIGN","context":"type","value":"=","line":2,"start":22,"end":23},{"type":"T_LCURLY","context":"type","value":"{","line":2,"start":24,"end":25},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":2,"start":26,"end":29},{"type":"T_COLON","context":"type","value":":","line":2,"start":29,"end":30},{"type":"T_STRING_TYPE","context":"type","value":"string","line":2,"start":31,"end":37},{"type":"T_RCURLY","context":"type","value":"}","line":2,"start":38,"end":39},{"type":"T_SEMICOLON","context":"type","value":";","line":2,"start":39,"end":40},{"type":"T_TYPE","context":"normal","value":"type","line":3,"start":41,"end":45},{"type":"T_IDENTIFIER","context":"type","value":"ObjectB","line":3,"start":46,"end":53},{"type":"T_ASSIGN","context":"type","value":"=","line":3,"start":54,"end":55},{"type":"T_LCURLY","context":"type","value":"{","line":3,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":3,"start":58,"end":61},{"type":"T_COLON","context":"type","value":":","line":3,"start":61,"end":62},{"type":"T_STRING_TYPE","context":"type","value":"string","line":3,"start":63,"end":69},{"type":"T_COMMA","context":"type","value":",","line":3,"start":69,"end":70},{"type":"T_IDENTIFIER","context":"normal","value":"bar","line":3,"start":71,"end":74},{"type":"T_COLON","context":"type","value":":","line":3,"start":74,"end":75},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":3,"start":76,"end":82},{"type":"T_RCURLY","context":"type","value":"}","line":3,"start":83,"end":84},{"type":"T_SEMICOLON","context":"type","value":";","line":3,"start":84,"end":85},{"type":"T_LET","context":"normal","value":"let","line":5,"start":87,"end":90},{"type":"T_IDENTIFIER","context":"normal","value":"objectB","line":5,"start":91,"end":98},{"type":"T_COLON","context":"type","value":":","line":5,"start":98,"end":99},{"type":"T_IDENTIFIER","context":"type","value":"ObjectB","line":5,"start":100,"end":107},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":108,"end":109},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":110,"end":111},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":5,"start":112,"end":115},{"type":"T_COLON","context":"normal","value":":","line":5,"start":115,"end":116},{"type":"T_STRING","context":"normal","value":"'test'","line":5,"start":117,"end":123},{"type":"T_COMMA","context":"normal","value":",","line":5,"start":123,"end":124},{"type":"T_IDENTIFIER","context":"normal","value":"bar","line":5,"start":125,"end":128},{"type":"T_COLON","context":"normal","value":":","line":5,"start":128,"end":129},{"type":"T_NUMBER","context":"normal","value":"42","line":5,"start":130,"end":132},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":133,"end":134},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":134,"end":135},{"type":"T_LET","context":"normal","value":"let","line":6,"start":136,"end":139},{"type":"T_IDENTIFIER","context":"normal","value":"objectA","line":6,"start":140,"end":147},{"type":"T_COLON","context":"type","value":":","line":6,"start":147,"end":148},{"type":"T_IDENTIFIER","context":"type","value":"ObjectA","line":6,"start":149,"end":156},{"type":"T_ASSIGN","context":"normal","value":"=","line":6,"start":157,"end":158},{"type":"T_IDENTIFIER","context":"normal","value":"objectB","line":6,"start":159,"end":166},{"type":"T_SEMICOLON","context":"normal","value":";","line":6,"start":166,"end":167},{"type":"Line","context":"comment","value":"// Works!","line":6,"start":168,"end":177}],"errors":[]}
But we also need to compare the types of the values. If both objects had a key
foo
but one was a number
and the other was a string
, then one would not
be the subtype of the other.
1
2
3
4
5
6
7
|
type ObjectA = { foo: string };
type ObjectB = { foo: number, bar: number };
let objectB: ObjectB = { foo: 1, bar: 2 };
let objectA: ObjectA = objectB;
|
Cannot assign `objectB` to `objectA` because number [1] is incompatible with string [2] in property `foo`.
{"value":"// @flow\ntype ObjectA = { foo: string };\ntype ObjectB = { foo: number, bar: number };\n\nlet objectB: ObjectB = { foo: 1, bar: 2 };\n// $ExpectError\nlet objectA: ObjectA = objectB; // Error!\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":"ObjectA","line":2,"start":14,"end":21},{"type":"T_ASSIGN","context":"type","value":"=","line":2,"start":22,"end":23},{"type":"T_LCURLY","context":"type","value":"{","line":2,"start":24,"end":25},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":2,"start":26,"end":29},{"type":"T_COLON","context":"type","value":":","line":2,"start":29,"end":30},{"type":"T_STRING_TYPE","context":"type","value":"string","line":2,"start":31,"end":37},{"type":"T_RCURLY","context":"type","value":"}","line":2,"start":38,"end":39},{"type":"T_SEMICOLON","context":"type","value":";","line":2,"start":39,"end":40},{"type":"T_TYPE","context":"normal","value":"type","line":3,"start":41,"end":45},{"type":"T_IDENTIFIER","context":"type","value":"ObjectB","line":3,"start":46,"end":53},{"type":"T_ASSIGN","context":"type","value":"=","line":3,"start":54,"end":55},{"type":"T_LCURLY","context":"type","value":"{","line":3,"start":56,"end":57},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":3,"start":58,"end":61},{"type":"T_COLON","context":"type","value":":","line":3,"start":61,"end":62},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":3,"start":63,"end":69},{"type":"T_COMMA","context":"type","value":",","line":3,"start":69,"end":70},{"type":"T_IDENTIFIER","context":"normal","value":"bar","line":3,"start":71,"end":74},{"type":"T_COLON","context":"type","value":":","line":3,"start":74,"end":75},{"type":"T_NUMBER_TYPE","context":"type","value":"number","line":3,"start":76,"end":82},{"type":"T_RCURLY","context":"type","value":"}","line":3,"start":83,"end":84},{"type":"T_SEMICOLON","context":"type","value":";","line":3,"start":84,"end":85},{"type":"T_LET","context":"normal","value":"let","line":5,"start":87,"end":90},{"type":"T_IDENTIFIER","context":"normal","value":"objectB","line":5,"start":91,"end":98},{"type":"T_COLON","context":"type","value":":","line":5,"start":98,"end":99},{"type":"T_IDENTIFIER","context":"type","value":"ObjectB","line":5,"start":100,"end":107},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":108,"end":109},{"type":"T_LCURLY","context":"normal","value":"{","line":5,"start":110,"end":111},{"type":"T_IDENTIFIER","context":"normal","value":"foo","line":5,"start":112,"end":115},{"type":"T_COLON","context":"normal","value":":","line":5,"start":115,"end":116},{"type":"T_NUMBER","context":"normal","value":"1","line":5,"start":117,"end":118},{"type":"T_COMMA","context":"normal","value":",","line":5,"start":118,"end":119},{"type":"T_IDENTIFIER","context":"normal","value":"bar","line":5,"start":120,"end":123},{"type":"T_COLON","context":"normal","value":":","line":5,"start":123,"end":124},{"type":"T_NUMBER","context":"normal","value":"2","line":5,"start":125,"end":126},{"type":"T_RCURLY","context":"normal","value":"}","line":5,"start":127,"end":128},{"type":"T_SEMICOLON","context":"normal","value":";","line":5,"start":128,"end":129},{"type":"Line","context":"comment","value":"// $ExpectError","line":6,"start":130,"end":145},{"type":"T_LET","context":"normal","value":"let","line":7,"start":146,"end":149},{"type":"T_IDENTIFIER","context":"normal","value":"objectA","line":7,"start":150,"end":157},{"type":"T_COLON","context":"type","value":":","line":7,"start":157,"end":158},{"type":"T_IDENTIFIER","context":"type","value":"ObjectA","line":7,"start":159,"end":166},{"type":"T_ASSIGN","context":"normal","value":"=","line":7,"start":167,"end":168},{"type":"T_IDENTIFIER","context":"normal","value":"objectB","line":7,"start":169,"end":176},{"type":"T_SEMICOLON","context":"normal","value":";","line":7,"start":176,"end":177},{"type":"Line","context":"comment","value":"// Error!","line":7,"start":178,"end":187}],"errors":[{"id":"E1","messages":[{"id":"E1M1","description":"Cannot assign `objectB` to `objectA` because number [1] is incompatible with string [2] in property `foo`.","context":"let objectA: ObjectA = objectB; // Error!","source":"-","start":{"line":7,"column":24,"offset":169},"end":{"line":7,"column":30,"offset":176}}],"operation":null}]}
If these values on the object happen to be other objects, we would have to
compare those against one another. We need to compare every value
recursively until we can decide if we have a subtype or not.
Subtypes of functions
Subtyping rules for functions are more complicated. So far, we’ve seen that A
is a subtype of B
if B
contains all possible values for A
. For functions,
it’s not clear how this relationship would apply. To simplify things, you can think
of a function type A
as being a subtype of a function type B
if functions of type
A
can be used wherever a function of type B
is expected.
Let’s say we have a function type and a few functions. Which of the functions can
be used safely in code that expects the given function type?
1
2
3
4
5
|
type FuncType = (1 | 2) => "A" | "B";
let f1: (1 | 2) => "A" | "B" | "C" = (x) =>
let f2: (1 | null) => "A" | "B" = (x) =>
let f3: (1 | 2 | 3) => "A" = (x) =>
|
{"value":"type FuncType = (1 | 2) => \"A\" | \"B\";\n\nlet f1: (1 | 2) => \"A\" | \"B\" | \"C\" = (x) => /* ... */\nlet f2: (1 | null) => \"A\" | \"B\" = (x) => /* ... */\nlet f3: (1 | 2 | 3) => \"A\" = (x) => /* ... */\n","tokens":[{"type":"T_TYPE","context":"normal","value":"type","line":1,"start":0,"end":4},{"type":"T_IDENTIFIER","context":"type","value":"FuncType","line":1,"start":5,"end":13},{"type":"T_ASSIGN","context":"type","value":"=","line":1,"start":14,"end":15},{"type":"T_LPAREN","context":"type","value":"(","line":1,"start":16,"end":17},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"1","line":1,"start":17,"end":18},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":19,"end":20},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"2","line":1,"start":21,"end":22},{"type":"T_RPAREN","context":"type","value":")","line":1,"start":22,"end":23},{"type":"T_ARROW","context":"type","value":"=>","line":1,"start":24,"end":26},{"type":"T_STRING","context":"type","value":"\"A\"","line":1,"start":27,"end":30},{"type":"T_BIT_OR","context":"type","value":"|","line":1,"start":31,"end":32},{"type":"T_STRING","context":"type","value":"\"B\"","line":1,"start":33,"end":36},{"type":"T_SEMICOLON","context":"type","value":";","line":1,"start":36,"end":37},{"type":"T_LET","context":"normal","value":"let","line":3,"start":39,"end":42},{"type":"T_IDENTIFIER","context":"normal","value":"f1","line":3,"start":43,"end":45},{"type":"T_COLON","context":"type","value":":","line":3,"start":45,"end":46},{"type":"T_LPAREN","context":"type","value":"(","line":3,"start":47,"end":48},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"1","line":3,"start":48,"end":49},{"type":"T_BIT_OR","context":"type","value":"|","line":3,"start":50,"end":51},{"type":"T_NUMBER_SINGLETON_TYPE","context":"type","value":"2","line":3,"start":52,"end":53},{"type":"T_RPAREN","context":"type","value":")","line":3,"start":53,"end":54},{"type":"T_ARROW","context":"type","value":"=>","line":3,"start":55,"end":57},{"type":"T_STRING","context":"type","value":"\"A\"","line":3,"start":58,"end":61},{"type":"T_BIT_OR","context":"type","value":"|","line":3,"start":62,"end":63},{"type":"T_STRING","context":"type","value":"\"B\"","line":3,"start":64,"end":67},{"type":"T_BIT_OR","context":"type","value":"|","line":3,"start":68,"end":69},{"type":"T_STRING","context":"type","value":"\"C\"","line":3,"start":70,"end":73},{"type":"T_ASSIGN","context":"normal","value":"=","line":3,"start":74,"end":75},{"type":"T_LPAREN","context":"normal","value":"(","line":3,"start":76,"end":77},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":3,"start":77,"end":78},{"type":"T_RPAREN","context":"normal","value":")","line":3,"start":78,"end":79},{"type":"T_ARROW","context":"normal","value":"=>","line":3,"start":80,"end":82},{"type":"Block","context":"comment","value":"/* ... */","line":3,"start":83,"end":92},{"type":"T_LET","context":"normal","value":"let","line":4,"start":93,"end":96},{"type":"T_IDENTIFIER","context":"normal","value":"f2","line":4,"start":97,"end":99},{"type":"T_COLON","context":"normal","value":":","line":4,"start":99,"end":100},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":101,"end":102},{"type":"T_NUMBER","context":"normal","value":"1","line":4,"start":102,"end":103},{"type":"T_BIT_OR","context":"normal","value":"|","line":4,"start":104,"end":105},{"type":"T_NULL","context":"normal","value":"null","line":4,"start":106,"end":110},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":110,"end":111},{"type":"T_ARROW","context":"normal","value":"=>","line":4,"start":112,"end":114},{"type":"T_STRING","context":"normal","value":"\"A\"","line":4,"start":115,"end":118},{"type":"T_BIT_OR","context":"normal","value":"|","line":4,"start":119,"end":120},{"type":"T_STRING","context":"normal","value":"\"B\"","line":4,"start":121,"end":124},{"type":"T_ASSIGN","context":"normal","value":"=","line":4,"start":125,"end":126},{"type":"T_LPAREN","context":"normal","value":"(","line":4,"start":127,"end":128},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":4,"start":128,"end":129},{"type":"T_RPAREN","context":"normal","value":")","line":4,"start":129,"end":130},{"type":"T_ARROW","context":"normal","value":"=>","line":4,"start":131,"end":133},{"type":"Block","context":"comment","value":"/* ... */","line":4,"start":134,"end":143},{"type":"T_LET","context":"normal","value":"let","line":5,"start":144,"end":147},{"type":"T_IDENTIFIER","context":"normal","value":"f3","line":5,"start":148,"end":150},{"type":"T_COLON","context":"normal","value":":","line":5,"start":150,"end":151},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":152,"end":153},{"type":"T_NUMBER","context":"normal","value":"1","line":5,"start":153,"end":154},{"type":"T_BIT_OR","context":"normal","value":"|","line":5,"start":155,"end":156},{"type":"T_NUMBER","context":"normal","value":"2","line":5,"start":157,"end":158},{"type":"T_BIT_OR","context":"normal","value":"|","line":5,"start":159,"end":160},{"type":"T_NUMBER","context":"normal","value":"3","line":5,"start":161,"end":162},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":162,"end":163},{"type":"T_ARROW","context":"normal","value":"=>","line":5,"start":164,"end":166},{"type":"T_STRING","context":"normal","value":"\"A\"","line":5,"start":167,"end":170},{"type":"T_ASSIGN","context":"normal","value":"=","line":5,"start":171,"end":172},{"type":"T_LPAREN","context":"normal","value":"(","line":5,"start":173,"end":174},{"type":"T_IDENTIFIER","context":"normal","value":"x","line":5,"start":174,"end":175},{"type":"T_RPAREN","context":"normal","value":")","line":5,"start":175,"end":176},{"type":"T_ARROW","context":"normal","value":"=>","line":5,"start":177,"end":179},{"type":"Block","context":"comment","value":"/* ... */","line":5,"start":180,"end":189}],"errors":[]}
f1
can return a value that FuncType
never does, so code that relies on FuncType
might not be safe if f1
is used. Its type is not a subtype of FuncType
.
f2
can’t handle all the argument values that FuncType
does, so code that relies on
FuncType
can’t safely use f2
. Its type is also not a subtype of FuncType
.
f3
can accept all the argument values that FuncType
does, and only returns
values that FuncType
does, so its type is a subtype of FuncType
.
In general, the function subtyping rule is this: A function type B
is a subtype
of a function type A
if and only if B
’s inputs are a superset of A
’s, and B
’s outputs
are a subset of A
’s. The subtype must accept at least the same inputs as its parent,
and must return at most the same outputs.
The decision of which direction to apply the subtyping rule on inputs and outputs is
governed by variance, which is the topic of the next section.