shufflingb
shufflingb

Reputation: 1947

How to create a Flow Union runtime refinement without embedding literals

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

Answers (2)

shufflingb
shufflingb

Reputation: 1947

One approach that appears to works is to use the const object which defines the allowed values, to:

  1. Generate a union type using the $keys utility.
  2. Use that union type to create a map object where the keys are the desired input (our case strings) and the values are "maybe"s of the type that needs refining.

Here's the example from earlier reworked so that it:

  • Sets the type up as we'd expect to allow either "foo" or "baa" but nothing else.
  • Detects when a string is suitably refined so that it only contains "foo" or "baa".
  • Detects when a string might contain something else other than what's expected.

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

Jordan Brown
Jordan Brown

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

Related Questions