Skip to content

Commit

Permalink
[flow][enums] Update isValid Flow Enums method to use type guard
Browse files Browse the repository at this point in the history
Summary:
Changelog: [feature] Updated the `isValid` Flow Enums method to use a type guard, allowing it to refine its input to the enum type in a conditional context.

E.g.

```
enum Status {Active, Off}

const s = "Active";

if (Status.isValid(s)) {
  s as Status; // Should work
}
```

Reviewed By: panagosg7

Differential Revision: D54834841

fbshipit-source-id: 075eecd5da2ae00640a246be21818db16e470347
  • Loading branch information
gkz authored and facebook-github-bot committed Mar 13, 2024
1 parent 1a32689 commit 6355cf9
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 46 deletions.
2 changes: 1 addition & 1 deletion lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2618,7 +2618,7 @@ declare var console: {
type $EnumProto<TEnumObject, TEnum, TRepresentation> = {|
cast(this: TEnumObject, input: ?TRepresentation): void | TEnum,
getName(this: TEnumObject, input: TEnum): string,
isValid(this: TEnumObject, input: ?TRepresentation): boolean,
isValid(this: TEnumObject, input: ?TRepresentation | TEnum): input is TEnum,
members(this: TEnumObject): Iterator<TEnum>,
__proto__: null,
|}
Expand Down
4 changes: 2 additions & 2 deletions tests/autocomplete/autocomplete.exp
Original file line number Diff line number Diff line change
Expand Up @@ -2605,9 +2605,9 @@ Flags: --lsp
}
{
"label":"isValid",
"labelDetails":{"detail":": (input: ?string) => boolean"},
"labelDetails":{"detail":": (input: ?(string | E)) => input is E"},
"kind":3,
"detail":"(input: ?string) => boolean",
"detail":"(input: ?(string | E)) => input is E",
"sortText":"00000000000000000001",
"insertTextFormat":1,
"textEdit":{
Expand Down
24 changes: 14 additions & 10 deletions tests/autocomplete_jsdoc/autocomplete_jsdoc.exp
Original file line number Diff line number Diff line change
Expand Up @@ -2493,9 +2493,11 @@ Flags: --lsp
}
{
"label":"isValid",
"labelDetails":{"detail":": (input: ?string) => boolean"},
"labelDetails":{
"detail":": (input: ?(string | DefaultedStringEnum)) => input is DefaultedStringEnum"
},
"kind":3,
"detail":"(input: ?string) => boolean",
"detail":"(input: ?(string | DefaultedStringEnum)) => input is DefaultedStringEnum",
"sortText":"00000000000000000001",
"insertTextFormat":1,
"textEdit":{
Expand Down Expand Up @@ -2672,9 +2674,11 @@ Flags: --lsp
}
{
"label":"isValid",
"labelDetails":{"detail":": (input: ?string) => boolean"},
"labelDetails":{
"detail":": (input: ?(string | InitializedStringEnum)) => input is InitializedStringEnum"
},
"kind":3,
"detail":"(input: ?string) => boolean",
"detail":"(input: ?(string | InitializedStringEnum)) => input is InitializedStringEnum",
"sortText":"00000000000000000001",
"insertTextFormat":1,
"textEdit":{
Expand Down Expand Up @@ -2851,9 +2855,9 @@ Flags: --lsp
}
{
"label":"isValid",
"labelDetails":{"detail":": (input: ?number) => boolean"},
"labelDetails":{"detail":": (input: ?(number | NumberEnum)) => input is NumberEnum"},
"kind":3,
"detail":"(input: ?number) => boolean",
"detail":"(input: ?(number | NumberEnum)) => input is NumberEnum",
"sortText":"00000000000000000001",
"insertTextFormat":1,
"textEdit":{
Expand Down Expand Up @@ -3030,9 +3034,9 @@ Flags: --lsp
}
{
"label":"isValid",
"labelDetails":{"detail":": (input: ?boolean) => boolean"},
"labelDetails":{"detail":": (input: ?(boolean | BooleanEnum)) => input is BooleanEnum"},
"kind":3,
"detail":"(input: ?boolean) => boolean",
"detail":"(input: ?(boolean | BooleanEnum)) => input is BooleanEnum",
"sortText":"00000000000000000001",
"insertTextFormat":1,
"textEdit":{
Expand Down Expand Up @@ -3209,9 +3213,9 @@ Flags: --lsp
}
{
"label":"isValid",
"labelDetails":{"detail":": (input: ?symbol) => boolean"},
"labelDetails":{"detail":": (input: ?(symbol | SymbolEnum)) => input is SymbolEnum"},
"kind":3,
"detail":"(input: ?symbol) => boolean",
"detail":"(input: ?(symbol | SymbolEnum)) => input is SymbolEnum",
"sortText":"00000000000000000001",
"insertTextFormat":1,
"textEdit":{
Expand Down
66 changes: 33 additions & 33 deletions tests/enums/enums.exp
Original file line number Diff line number Diff line change
Expand Up @@ -1418,8 +1418,8 @@ References:
3| enum E {
^ [2]
<BUILTINS>/core.js:2621:3
2621| isValid(this: TEnumObject, input: ?TRepresentation): boolean,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [3]
2621| isValid(this: TEnumObject, input: ?TRepresentation | TEnum): input is TEnum,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [3]


Error ----------------------------------------------------------------------------------------- exhaustive-check.js:80:8
Expand Down Expand Up @@ -2070,12 +2070,12 @@ References:
^ [1]


Error -------------------------------------------------------------------------------------------------- methods.js:29:3
Error -------------------------------------------------------------------------------------------------- methods.js:35:3

Cannot get `E.nonExistent` because property `nonExistent` is missing in `$EnumProto` [1]. [prop-missing]

methods.js:29:3
29| E.nonExistent; // Error
methods.js:35:3
35| E.nonExistent; // Error
^^^^^^^^^^^

References:
Expand All @@ -2084,19 +2084,19 @@ References:
2618| type $EnumProto<TEnumObject, TEnum, TRepresentation> = {|
2619| cast(this: TEnumObject, input: ?TRepresentation): void | TEnum,
2620| getName(this: TEnumObject, input: TEnum): string,
2621| isValid(this: TEnumObject, input: ?TRepresentation): boolean,
2621| isValid(this: TEnumObject, input: ?TRepresentation | TEnum): input is TEnum,
2622| members(this: TEnumObject): Iterator<TEnum>,
2623| __proto__: null,
2624| |}
-^ [1]


Error -------------------------------------------------------------------------------------------------- methods.js:32:3
Error -------------------------------------------------------------------------------------------------- methods.js:38:3

Cannot call `E.nonExistent` because property `nonExistent` is missing in `$EnumProto` [1]. [prop-missing]

methods.js:32:3
32| E.nonExistent(); // Error
methods.js:38:3
38| E.nonExistent(); // Error
^^^^^^^^^^^

References:
Expand All @@ -2105,19 +2105,19 @@ References:
2618| type $EnumProto<TEnumObject, TEnum, TRepresentation> = {|
2619| cast(this: TEnumObject, input: ?TRepresentation): void | TEnum,
2620| getName(this: TEnumObject, input: TEnum): string,
2621| isValid(this: TEnumObject, input: ?TRepresentation): boolean,
2621| isValid(this: TEnumObject, input: ?TRepresentation | TEnum): input is TEnum,
2622| members(this: TEnumObject): Iterator<TEnum>,
2623| __proto__: null,
2624| |}
-^ [1]


Error -------------------------------------------------------------------------------------------------- methods.js:35:3
Error -------------------------------------------------------------------------------------------------- methods.js:41:3

An index signature declaring the expected key / value type is missing in enum `E` [1]. [incompatible-use]

methods.js:35:3
35| E['members'](); // Error
methods.js:41:3
41| E['members'](); // Error
^^^^^^^^^

References:
Expand All @@ -2126,12 +2126,12 @@ References:
^ [1]


Error -------------------------------------------------------------------------------------------------- methods.js:38:3
Error -------------------------------------------------------------------------------------------------- methods.js:44:3

Cannot call `E.A` because property `A` is missing in `$EnumProto` [1]. [prop-missing]

methods.js:38:3
38| E.A(); // Error
methods.js:44:3
44| E.A(); // Error
^

References:
Expand All @@ -2140,19 +2140,19 @@ References:
2618| type $EnumProto<TEnumObject, TEnum, TRepresentation> = {|
2619| cast(this: TEnumObject, input: ?TRepresentation): void | TEnum,
2620| getName(this: TEnumObject, input: TEnum): string,
2621| isValid(this: TEnumObject, input: ?TRepresentation): boolean,
2621| isValid(this: TEnumObject, input: ?TRepresentation | TEnum): input is TEnum,
2622| members(this: TEnumObject): Iterator<TEnum>,
2623| __proto__: null,
2624| |}
-^ [1]


Error -------------------------------------------------------------------------------------------------- methods.js:41:3
Error -------------------------------------------------------------------------------------------------- methods.js:47:3

Cannot call `E.toString` because property `toString` is missing in `$EnumProto` [1]. [prop-missing]

methods.js:41:3
41| E.toString(); // Error
methods.js:47:3
47| E.toString(); // Error
^^^^^^^^

References:
Expand All @@ -2161,44 +2161,44 @@ References:
2618| type $EnumProto<TEnumObject, TEnum, TRepresentation> = {|
2619| cast(this: TEnumObject, input: ?TRepresentation): void | TEnum,
2620| getName(this: TEnumObject, input: TEnum): string,
2621| isValid(this: TEnumObject, input: ?TRepresentation): boolean,
2621| isValid(this: TEnumObject, input: ?TRepresentation | TEnum): input is TEnum,
2622| members(this: TEnumObject): Iterator<TEnum>,
2623| __proto__: null,
2624| |}
-^ [1]


Error -------------------------------------------------------------------------------------------------- methods.js:44:1
Error -------------------------------------------------------------------------------------------------- methods.js:50:1

Cannot cast `E.getName(...)` to boolean because string [1] is incompatible with boolean [2]. [incompatible-cast]

methods.js:44:1
44| E.getName(E.B) as boolean; // Error - wrong type
methods.js:50:1
50| E.getName(E.B) as boolean; // Error - wrong type
^^^^^^^^^^^^^^

References:
<BUILTINS>/core.js:2620:45
2620| getName(this: TEnumObject, input: TEnum): string,
^^^^^^ [1]
methods.js:44:19
44| E.getName(E.B) as boolean; // Error - wrong type
methods.js:50:19
50| E.getName(E.B) as boolean; // Error - wrong type
^^^^^^^ [2]


Error -------------------------------------------------------------------------------------------------- methods.js:50:1
Error -------------------------------------------------------------------------------------------------- methods.js:56:1

Cannot call `o.cast` because object literal [1] is incompatible with enum `E` [2]. [incompatible-call]

methods.js:50:1
50| o.cast('x'); // Error
methods.js:56:1
56| o.cast('x'); // Error
^

References:
methods.js:47:11
methods.js:53:11
v
47| const o = {
48| cast: E.cast,
49| };
53| const o = {
54| cast: E.cast,
55| };
^ [1]
methods.js:3:6
3| enum E {
Expand Down
6 changes: 6 additions & 0 deletions tests/enums/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ const b: Iterable<E> = E.members();
const c: boolean = E.isValid('A');
E.isValid(maybeString);
const s: string = E.getName(E.A);
{
const x = 'A';
if (E.isValid(x)) {
x as E; // OK
}
}

// .members()
for (const x of E.members()) {
Expand Down

0 comments on commit 6355cf9

Please sign in to comment.