Skip to content

Commit

Permalink
[enums] Parsing of unknown members (...)
Browse files Browse the repository at this point in the history
Summary:
This adds parsing support for Flow Enums with unknown members.

The error for using this when the option is not enabled is part of the next diff.

Whether an enum has unknown members is logically part of the enum body, so I've put it as part of this AST node, though this does cause a bit of repetition.

Syntax:

```
enum E {
  A,
  B,
  ...
}
```
The `...` must be last, and cannot have a trialing comma.

Reviewed By: Hans-Halverson

Differential Revision: D24152192

fbshipit-source-id: a7194ae00d3971146e965a35a132fe050f3a06a6
  • Loading branch information
gkz authored and facebook-github-bot committed Oct 8, 2020
1 parent b49b79c commit de2296c
Show file tree
Hide file tree
Showing 65 changed files with 451 additions and 111 deletions.
84 changes: 68 additions & 16 deletions src/parser/enum_parser.ml
Expand Up @@ -26,6 +26,7 @@ end = struct
type acc = {
members: members;
seen_names: SSet.t;
has_unknown_members: bool;
}

type init =
Expand All @@ -38,7 +39,7 @@ end = struct
let empty_members =
{ boolean_members = []; number_members = []; string_members = []; defaulted_members = [] }

let empty_acc = { members = empty_members; seen_names = SSet.empty }
let empty_acc = { members = empty_members; seen_names = SSet.empty; has_unknown_members = false }

let end_of_member_init env =
match Peek.token env with
Expand Down Expand Up @@ -123,7 +124,7 @@ end = struct
let is_a_to_z c = c >= 'a' && c <= 'z'

let enum_member ~enum_name ~explicit_type acc env =
let { members; seen_names } = acc in
let { members; seen_names; _ } = acc in
let (member_loc, (id, init)) = member_raw env in
let (id_loc, { Identifier.name = member_name; _ }) = id in
(* if we parsed an empty name, something has gone wrong and we should abort analysis *)
Expand Down Expand Up @@ -184,12 +185,32 @@ end = struct
match Peek.token env with
| T_RCURLY
| T_EOF ->
{
boolean_members = List.rev acc.members.boolean_members;
number_members = List.rev acc.members.number_members;
string_members = List.rev acc.members.string_members;
defaulted_members = List.rev acc.members.defaulted_members;
}
( {
boolean_members = List.rev acc.members.boolean_members;
number_members = List.rev acc.members.number_members;
string_members = List.rev acc.members.string_members;
defaulted_members = List.rev acc.members.defaulted_members;
},
acc.has_unknown_members )
| T_ELLIPSIS ->
let loc = Peek.loc env in
Eat.token env;
(match Peek.token env with
| T_RCURLY
| T_EOF ->
()
| T_COMMA ->
Expect.token env T_COMMA;
let trailing_comma =
match Peek.token env with
| T_RCURLY
| T_EOF ->
true
| _ -> false
in
error_at env (loc, Parse_error.EnumInvalidEllipsis { trailing_comma })
| _ -> error_at env (loc, Parse_error.EnumInvalidEllipsis { trailing_comma = false }));
enum_members ~enum_name ~explicit_type { acc with has_unknown_members = true } env
| _ ->
let acc = enum_member ~enum_name ~explicit_type acc env in
(match Peek.token env with
Expand All @@ -202,14 +223,16 @@ end = struct
| _ -> Expect.token env T_COMMA);
enum_members ~enum_name ~explicit_type acc env

let string_body ~env ~enum_name ~is_explicit string_members defaulted_members comments =
let string_body
~env ~enum_name ~is_explicit ~has_unknown_members string_members defaulted_members comments =
let initialized_len = List.length string_members in
let defaulted_len = List.length defaulted_members in
let defaulted_body () =
StringBody
{
StringBody.members = StringBody.Defaulted defaulted_members;
explicitType = is_explicit;
has_unknown_members;
comments;
}
in
Expand All @@ -218,6 +241,7 @@ end = struct
{
StringBody.members = StringBody.Initialized string_members;
explicitType = is_explicit;
has_unknown_members;
comments;
}
in
Expand Down Expand Up @@ -272,7 +296,7 @@ end = struct
[]
in
Expect.token env T_LCURLY;
let members = enum_members ~enum_name ~explicit_type empty_acc env in
let (members, has_unknown_members) = enum_members ~enum_name ~explicit_type empty_acc env in
Expect.token env T_RCURLY;
let trailing =
match Peek.token env with
Expand All @@ -287,28 +311,45 @@ end = struct
match explicit_type with
| Some Enum_common.Boolean ->
BooleanBody
{ BooleanBody.members = members.boolean_members; explicitType = true; comments }
{
BooleanBody.members = members.boolean_members;
explicitType = true;
has_unknown_members;
comments;
}
| Some Enum_common.Number ->
NumberBody
{ NumberBody.members = members.number_members; explicitType = true; comments }
{
NumberBody.members = members.number_members;
explicitType = true;
has_unknown_members;
comments;
}
| Some Enum_common.String ->
string_body
~env
~enum_name
~is_explicit:true
~has_unknown_members
members.string_members
members.defaulted_members
comments
| Some Enum_common.Symbol ->
SymbolBody { SymbolBody.members = members.defaulted_members; comments }
SymbolBody
{ SymbolBody.members = members.defaulted_members; has_unknown_members; comments }
| None ->
let bools_len = List.length members.boolean_members in
let nums_len = List.length members.number_members in
let strs_len = List.length members.string_members in
let defaulted_len = List.length members.defaulted_members in
let empty () =
StringBody
{ StringBody.members = StringBody.Defaulted []; explicitType = false; comments }
{
StringBody.members = StringBody.Defaulted [];
explicitType = false;
has_unknown_members;
comments;
}
in
begin
match (bools_len, nums_len, strs_len, defaulted_len) with
Expand All @@ -318,6 +359,7 @@ end = struct
~env
~enum_name
~is_explicit:false
~has_unknown_members
members.string_members
members.defaulted_members
comments
Expand All @@ -329,7 +371,12 @@ end = struct
(loc, Parse_error.EnumBooleanMemberNotInitialized { enum_name; member_name }))
members.defaulted_members;
BooleanBody
{ BooleanBody.members = members.boolean_members; explicitType = false; comments }
{
BooleanBody.members = members.boolean_members;
explicitType = false;
has_unknown_members;
comments;
}
| (0, _, 0, _) when nums_len >= defaulted_len ->
List.iter
(fun (loc, { DefaultedMember.id = (_, { Identifier.name = member_name; _ }) }) ->
Expand All @@ -338,7 +385,12 @@ end = struct
(loc, Parse_error.EnumNumberMemberNotInitialized { enum_name; member_name }))
members.defaulted_members;
NumberBody
{ NumberBody.members = members.number_members; explicitType = false; comments }
{
NumberBody.members = members.number_members;
explicitType = false;
has_unknown_members;
comments;
}
| _ ->
error_at env (name_loc, Parse_error.EnumInconsistentMemberValues { enum_name });
empty ()
Expand Down
17 changes: 12 additions & 5 deletions src/parser/estree_translator.ml
Expand Up @@ -1032,7 +1032,7 @@ with type t = Impl.t = struct
let open Statement.EnumDeclaration in
let enum_body =
match body with
| (loc, BooleanBody { BooleanBody.members; explicitType; comments }) ->
| (loc, BooleanBody { BooleanBody.members; explicitType; has_unknown_members; comments }) ->
node
?comments
"EnumBooleanBody"
Expand All @@ -1048,8 +1048,9 @@ with type t = Impl.t = struct
node "EnumBooleanMember" loc [("id", identifier id); ("init", bool value)])
members );
("explicitType", bool explicitType);
("hasUnknownMembers", bool has_unknown_members);
]
| (loc, NumberBody { NumberBody.members; explicitType; comments }) ->
| (loc, NumberBody { NumberBody.members; explicitType; has_unknown_members; comments }) ->
node
?comments
"EnumNumberBody"
Expand All @@ -1064,8 +1065,9 @@ with type t = Impl.t = struct
[("id", identifier id); ("init", number_literal init)])
members );
("explicitType", bool explicitType);
("hasUnknownMembers", bool has_unknown_members);
]
| (loc, StringBody { StringBody.members; explicitType; comments }) ->
| (loc, StringBody { StringBody.members; explicitType; has_unknown_members; comments }) ->
let members =
match members with
| StringBody.Defaulted defaulted_members ->
Expand All @@ -1083,8 +1085,12 @@ with type t = Impl.t = struct
?comments
"EnumStringBody"
loc
[("members", array members); ("explicitType", bool explicitType)]
| (loc, SymbolBody { SymbolBody.members; comments }) ->
[
("members", array members);
("explicitType", bool explicitType);
("hasUnknownMembers", bool has_unknown_members);
]
| (loc, SymbolBody { SymbolBody.members; has_unknown_members; comments }) ->
node
?comments
"EnumSymbolBody"
Expand All @@ -1095,6 +1101,7 @@ with type t = Impl.t = struct
(fun (loc, { DefaultedMember.id }) ->
node "EnumDefaultedMember" loc [("id", identifier id)])
members );
("hasUnknownMembers", bool has_unknown_members);
]
in
node ?comments "EnumDeclaration" loc [("id", identifier id); ("body", enum_body)]
Expand Down
4 changes: 4 additions & 0 deletions src/parser/flow_ast.ml
Expand Up @@ -697,6 +697,7 @@ and Statement : sig
type 'M t = {
members: ('M BooleanLiteral.t, 'M) InitializedMember.t list;
explicitType: bool;
has_unknown_members: bool;
comments: ('M, unit) Syntax.t option;
}
[@@deriving show]
Expand All @@ -706,6 +707,7 @@ and Statement : sig
type 'M t = {
members: ('M NumberLiteral.t, 'M) InitializedMember.t list;
explicitType: bool;
has_unknown_members: bool;
comments: ('M, unit) Syntax.t option;
}
[@@deriving show]
Expand All @@ -715,6 +717,7 @@ and Statement : sig
type 'M t = {
members: ('M StringLiteral.t, 'M) members;
explicitType: bool;
has_unknown_members: bool;
comments: ('M, unit) Syntax.t option;
}

Expand All @@ -727,6 +730,7 @@ and Statement : sig
module SymbolBody : sig
type 'M t = {
members: 'M DefaultedMember.t list;
has_unknown_members: bool;
comments: ('M, unit) Syntax.t option;
}
[@@deriving show]
Expand Down
10 changes: 5 additions & 5 deletions src/parser/flow_ast_mapper.ml
Expand Up @@ -745,7 +745,7 @@ class ['loc] mapper =

method enum_boolean_body (body : 'loc Ast.Statement.EnumDeclaration.BooleanBody.t) =
let open Ast.Statement.EnumDeclaration.BooleanBody in
let { members; explicitType = _; comments } = body in
let { members; explicitType = _; has_unknown_members = _; comments } = body in
let members' = map_list this#enum_boolean_member members in
let comments' = this#syntax_opt comments in
if members == members' && comments == comments' then
Expand All @@ -755,7 +755,7 @@ class ['loc] mapper =

method enum_number_body (body : 'loc Ast.Statement.EnumDeclaration.NumberBody.t) =
let open Ast.Statement.EnumDeclaration.NumberBody in
let { members; explicitType = _; comments } = body in
let { members; explicitType = _; has_unknown_members = _; comments } = body in
let members' = map_list this#enum_number_member members in
let comments' = this#syntax_opt comments in
if members == members' && comments == comments' then
Expand All @@ -765,7 +765,7 @@ class ['loc] mapper =

method enum_string_body (body : 'loc Ast.Statement.EnumDeclaration.StringBody.t) =
let open Ast.Statement.EnumDeclaration.StringBody in
let { members; explicitType = _; comments } = body in
let { members; explicitType = _; has_unknown_members = _; comments } = body in
let members' =
match members with
| Defaulted members -> Defaulted (map_list this#enum_defaulted_member members)
Expand All @@ -779,13 +779,13 @@ class ['loc] mapper =

method enum_symbol_body (body : 'loc Ast.Statement.EnumDeclaration.SymbolBody.t) =
let open Ast.Statement.EnumDeclaration.SymbolBody in
let { members; comments } = body in
let { members; has_unknown_members = _; comments } = body in
let members' = map_list this#enum_defaulted_member members in
let comments' = this#syntax_opt comments in
if members == members' && comments == comments' then
body
else
{ members = members'; comments = comments' }
{ body with members = members'; comments = comments' }

method enum_defaulted_member (member : 'loc Ast.Statement.EnumDeclaration.DefaultedMember.t) =
let open Ast.Statement.EnumDeclaration.DefaultedMember in
Expand Down
6 changes: 6 additions & 0 deletions src/parser/parse_error.ml
Expand Up @@ -32,6 +32,7 @@ type t =
member_name: string;
}
| EnumInvalidMemberSeparator
| EnumInvalidEllipsis of { trailing_comma: bool }
| EnumNumberMemberNotInitialized of {
enum_name: string;
member_name: string;
Expand Down Expand Up @@ -231,6 +232,11 @@ module PP = struct
suggestion
enum_name
| EnumInvalidMemberSeparator -> "Enum members are separated with `,`. Replace `;` with `,`."
| EnumInvalidEllipsis { trailing_comma } ->
if trailing_comma then
"The `...` must come at the end of the enum body. Remove the trailing comma."
else
"The `...` must come after all enum members. Move it to the end of the enum body."
| EnumNumberMemberNotInitialized { enum_name; member_name } ->
Printf.sprintf
"Number enum members need to be initialized, e.g. `%s = 1,` in enum `%s`."
Expand Down

0 comments on commit de2296c

Please sign in to comment.