Flow: Support $Values<typeof ['string', 'enum']>

Created on 24 Jul 2017  路  3Comments  路  Source: facebook/flow

String enums continue to be incredibly tedious in Flow. Using type unions of string literals only goes so far because you can't actually assert that a given string is a valid member of the enum without repeating yourself. Ideally, what I would like to write is something like this:

const VALID = ['string', 'enum'];
type ValidType = $Values<typeof VALID>;

function readString(filename: string): ValidType {
  const content = fs.readFileSync(filename); // `content` is just `string` for now
  invariant(VALID.includes(content)) // `content` is now proven to be of ValidType
  return content;
}
refinements tuple

Most helpful comment

I have the same issue and the only way to overcome this is to dupe the values twice which brings the burden of maintaining these two values now.

It's quite frustrating that I have to fight the Flow each time I need to share constants between JS and Flow, this greatly decreases my productivity.

All 3 comments

This solution isn't necessarily less tedious, but it does allow you to assert a given string is a valid member of an enum without repetition:

const VALID = {
  string: '',
  enum: '',
};
type ValidType = $Keys<typeof VALID>;
const VALID_VALUES: Array<ValidType> = Object.keys(VALID);

function readString(filename: string): ValidType {
  const content = fs.readFileSync(filename); // `content` is just `string` for now
  const validatedContent = VALID_VALUES.find(value => value === content);
  invariant(validatedContent) // `content` is now proven to be of ValidType
  return validated;
}

Example here

@rsolomon Ah, that's a neat trick, making VALID_VALUES an Array<ValidType> and then "laundering" the type of content through VALID_VALUES.find(). Could write a small helper for that:

function ensureEnum<E>(value: string, enums: Array<E>): E {
  const literal = enums.find((candidate) => candidate === value);
  invariant(literal);
  return literal;
}

So then we have

const VALID = {
  string: '',
  enum: '',
};
type ValidType = $Keys<typeof VALID>;
const VALID_VALUES: Array<ValidType> = Object.keys(VALID);

function readString(filename: string): ValidType {
  const content = readFileContents(filename); // `content` is just `string` for now
  return ensureEnum(content, VALID_VALUES);
}

Still quite cluttered. Could push VALID_VALUES into the helper as well if performance/GC garbage isn't a concern:

function ensureEnum<E: Object>(value: string, enums: E): $Keys<E> {
  const valids: Array<$Keys<E>> = Object.keys(enums);
  const literal = valids.find((candidate) => candidate === value);
  invariant(literal);
  return literal;
}

In any case we have to do an Array.find(). It's really too bad that we have to add to the runtime to make Flow happy. In practice it probably won't matter much, but the principle of it does bother me. Plus, string enums are SUCH a common thing in JS.

I have the same issue and the only way to overcome this is to dupe the values twice which brings the burden of maintaining these two values now.

It's quite frustrating that I have to fight the Flow each time I need to share constants between JS and Flow, this greatly decreases my productivity.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pelotom picture pelotom  路  3Comments

mmollaverdi picture mmollaverdi  路  3Comments

bennoleslie picture bennoleslie  路  3Comments

Beingbook picture Beingbook  路  3Comments

ghost picture ghost  路  3Comments