Reputation: 1947
Hello kind Stackoverflow folks,
I'm trying to create a function to guard off code from being executed at run-time with an incorrect Flow type present.
My understanding is that the way to do this at run-time is by refining, or checking, that the type matches what is required and using Flow to keep an eye that no cases are missed along the way.
A simple case is where I have a string input that I would like to confirm matches to a enum/Union type. I have this working as I would expect with literals e.g.
/* @flow */
type typeFooOrBaa = "foo"| "baa"
const catchType = (toCheck: string): void => {
// Working check
if (toCheck === "foo" || toCheck === "baa") {
// No Flow errors
const checkedValue: typeFooOrBaa = toCheck
// ... do something with the checkedValue
}
};
Try it over here
Naturally, I would like to avoid embedding literals.
One of the things I've tried is the equivalent object key test, which doesn't work :-( e.g.
/* @flow */
type typeFooOrBaa = "foo"| "baa"
const fooOrBaaObj = {"foo": 1, "baa": 2}
const catchType = (toCheck: string): void => {
// Non working check
if (fooOrBaaObj[toCheck]) {
/*
The next assignment generates the following Flow error
Cannot assign `toCheck` to `checkedVariable` because: Either string [1] is incompatible
with string literal `foo` [2]. Or string [1] is incompatible with string literal `baa` [3].",
"type"
*/
const checkedVariable: typeFooOrBaa = toCheck
}
};
Try it over here
Is it possible to achieve something like this without having to go down the full flow-runtime route? If so how is it best done?
Thanks for your help.
Upvotes: 0
Views: 264
Reputation: 1947
One approach that appears to works is to use the const object which defines the allowed values, to:
Here's the example from earlier reworked so that it:
Credit to @vkurchatkin for his answer that helped me crack this (finally).
/* @flow */
// Example of how to persuade Flow to detect safe adequately refined usage of a Union type
// at runtime and its unsafe, inadequately refined counterparts.
const fooOrBaaObj = {foo: 'foo', baa: 'baa'}
type typeFooOrBaa = $Keys<typeof fooOrBaaObj>
// NB: $Keys used inorder for the type definition to avoid aliasing typeFooOrBaa === string
// which allows things like below to correctly spot problems.
//const testFlowSpotsBadDefition: typeFooOrBaa = "make_flow_barf"
const fooOrBaaMap: { [key: string]: ?typeFooOrBaa } = fooOrBaaObj;
// NB: Use of the "?" maybe signifier in the definition a essential to inform Flow that indexing into
// the map "might" produce a "null". Without it the subsequent correct detection of unsafe
// unrefined variables fails.
const catchType = (toCheck: string): void => {
const myValue = fooOrBaaMap[toCheck];
if (myValue) {
// Detects refined safe usage
const checkedVariable: typeFooOrBaa = myValue
}
// Uncommenting the following line correctly causes Flow to flag the unsafe type. Must have the
// "?" in the map defininiton to get Flow to spot this.
//const testFlowSpotsUnrefinedUsage: typeFooOrBaa = myValue
}
Have a play with it over here
Upvotes: 2
Reputation: 638
You can type the object as {[fooOrBaa]: number}
, but flow will not enforce that all members of fooOrBaa
exist in the object.
Upvotes: 0