Skip to content

Commit

Permalink
Sugar for exact object types
Browse files Browse the repository at this point in the history
Summary:
Adds support for `{| ... |}` exact object type declaration syntax.
Given an object type `{ a: X, b: Y, ... }`, `{| a: X, b: Y, ... |}`
is synonymous with `$Exact<{ a: X, b: Y, ... }>`.

Note that `$Exact<X>` is still the only way to express the exact
version of an aliased object type `X`.

Reviewed By: avikchaudhuri

Differential Revision: D3714440

fbshipit-source-id: df4ffa43a35397445880f6315c42b44edf60a527
  • Loading branch information
Basil Hosmer authored and Facebook Github Bot 6 committed Aug 17, 2016
1 parent b11bbbc commit c710c40
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 36 deletions.
19 changes: 6 additions & 13 deletions newtests/exact/prop_test2.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,29 @@
/***
* more prop tests on unions of objects.
* to see why property existence tests don't work to refine inexact types,
* take a look at checkFlag_err() below.
* more prop tests on unions of objects,
* using {| ... |} annotation syntax
*/

type StringFlag = {
type Flag = {|
type: "string",
name: string,
description: string,
argName: string,
aliases?: Array<string>,
default?: string,
};

type BoolFlag = {
|} | {|
type: "boolean",
name: string,
description: string,
aliases?: Array<string>,
};

type EnumFlag = {
|} | {|
type: "enum",
name: string,
description: string,
argName: string,
validValues: Array<string>,
aliases?: Array<string>,
default?: string,
};

type Flag = $Exact<StringFlag> | $Exact<BoolFlag> | $Exact<EnumFlag>;
|};

function checkFlag_ok(flag: Flag): string {
if (flag.default) {
Expand Down
1 change: 1 addition & 0 deletions src/parser/estree_translator.ml
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,7 @@ end with type t = Impl.t) = struct

and object_type (loc, o) = Type.Object.(
node "ObjectTypeAnnotation" loc [|
"exact", bool o.exact;
"properties", array_of_list object_type_property o.properties;
"indexers", array_of_list object_type_indexer o.indexers;
"callProperties", array_of_list object_type_call_property o.callProperties;
Expand Down
6 changes: 6 additions & 0 deletions src/parser/lexer_flow.mll
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ module Token = struct
(* Syntax *)
| T_LCURLY
| T_RCURLY
| T_LCURLYBAR
| T_RCURLYBAR
| T_LPAREN
| T_RPAREN
| T_LBRACKET
Expand Down Expand Up @@ -211,6 +213,8 @@ module Token = struct
| T_AWAIT -> "T_AWAIT"
| T_LCURLY -> "T_LCURLY"
| T_RCURLY -> "T_RCURLY"
| T_LCURLYBAR -> "T_LCURLYBAR"
| T_RCURLYBAR -> "T_RCURLYBAR"
| T_LPAREN -> "T_LPAREN"
| T_RPAREN -> "T_RPAREN"
| T_LBRACKET -> "T_LBRACKET"
Expand Down Expand Up @@ -1091,6 +1095,8 @@ and type_token env = parse
| "]" { env, T_RBRACKET }
| "{" { env, T_LCURLY }
| "}" { env, T_RCURLY }
| "{|" { env, T_LCURLYBAR }
| "|}" { env, T_RCURLYBAR }
| "(" { env, T_LPAREN }
| ")" { env, T_RPAREN }
| "..." { env, T_ELLIPSIS }
Expand Down
51 changes: 32 additions & 19 deletions src/parser/parser_flow.ml
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,10 @@ end = struct
loc, Type.Exists
| T_LESS_THAN -> _function env
| T_LPAREN -> function_or_group env
| T_LCURLY ->
let loc, o = _object env in
loc, Type.Object o
| T_LCURLY
| T_LCURLYBAR ->
let loc, o = _object env ~allow_exact:true in
loc, Type.Object o
| T_TYPEOF ->
let start_loc = Peek.loc env in
Expect.token env T_TYPEOF;
Expand Down Expand Up @@ -465,27 +466,35 @@ end = struct
static;
})

in let semicolon env =
in let semicolon exact env =
match Peek.token env with
| T_COMMA | T_SEMICOLON -> Eat.token env
| T_RCURLY -> ()
| T_RCURLYBAR when exact -> ()
| T_RCURLY when not exact -> ()
| _ -> error_unexpected env

in let rec properties ~allow_static env (acc, indexers, callProperties) =
in let rec properties ~allow_static ~exact env
(acc, indexers, callProperties) =
let start_loc = Peek.loc env in
let static = allow_static && Expect.maybe env T_STATIC in
match Peek.token env with
| T_EOF
| T_RCURLY -> List.rev acc, List.rev indexers, List.rev callProperties
| T_EOF ->
List.rev acc, List.rev indexers, List.rev callProperties
| T_RCURLYBAR when exact ->
List.rev acc, List.rev indexers, List.rev callProperties
| T_RCURLY when not exact ->
List.rev acc, List.rev indexers, List.rev callProperties
| T_LBRACKET ->
let indexer = indexer_property env start_loc static in
semicolon env;
properties allow_static env (acc, indexer::indexers, callProperties)
semicolon exact env;
properties allow_static exact env
(acc, indexer::indexers, callProperties)
| T_LESS_THAN
| T_LPAREN ->
let call_prop = call_property env start_loc static in
semicolon env;
properties allow_static env (acc, indexers, call_prop::callProperties)
semicolon exact env;
properties allow_static exact env
(acc, indexers, call_prop::callProperties)
| _ ->
let static, (_, key) = match static, Peek.token env with
| true, T_COLON ->
Expand All @@ -507,17 +516,20 @@ end = struct
| T_LESS_THAN
| T_LPAREN -> method_property env start_loc static key
| _ -> property env start_loc static key in
semicolon env;
properties allow_static env (property::acc, indexers, callProperties)
semicolon exact env;
properties allow_static exact env
(property::acc, indexers, callProperties)

in fun ?(allow_static=false) env ->
in fun ?(allow_static=false) ?(allow_exact=false) env ->
let exact = allow_exact && Peek.token env = T_LCURLYBAR in
let start_loc = Peek.loc env in
Expect.token env T_LCURLY;
Expect.token env (if exact then T_LCURLYBAR else T_LCURLY);
let properties, indexers, callProperties =
properties ~allow_static env ([], [], []) in
properties ~allow_static ~exact env ([], [], []) in
let end_loc = Peek.loc env in
Expect.token env T_RCURLY;
Expect.token env (if exact then T_RCURLYBAR else T_RCURLY);
Loc.btwn start_loc end_loc, Type.Object.({
exact;
properties;
indexers;
callProperties
Expand Down Expand Up @@ -644,7 +656,8 @@ end = struct
let type_parameter_declaration =
wrap (type_parameter_declaration ~allow_default:false)
let type_parameter_instantiation = wrap type_parameter_instantiation
let _object ?(allow_static=false) env = wrap (_object ~allow_static) env
let _object ?(allow_static=false) env =
wrap (_object ~allow_static ~allow_exact:false) env
let function_param_list = wrap function_param_list
let annotation = wrap annotation
let annotation_opt = wrap annotation_opt
Expand Down
1 change: 1 addition & 0 deletions src/parser/spider_monkey_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ and Type : sig
}
end
type t = {
exact: bool;
properties: Property.t list;
indexers: Indexer.t list;
callProperties: CallProperty.t list;
Expand Down
3 changes: 3 additions & 0 deletions src/parser/test/custom_ast_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ def("DeclareFunction")

def("Function")
.field("predicate", or(def("Predicate"), null), defaults["null"])

def("ObjectTypeAnnotation")
.field("exact", Boolean)
3 changes: 3 additions & 0 deletions src/parser/test/esprima_test_runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ function handleSpecialObjectCompare(esprima, flow, env) {
esprima.typeAnnotation = null;
}
break;
case 'ObjectTypeAnnotation':
esprima.exact = esprima.exact || false;
break;
case 'ObjectTypeProperty':
case 'ObjectTypeIndexer':
case 'ObjectTypeCallProperty':
Expand Down
16 changes: 15 additions & 1 deletion src/parser/test/hardcoded_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ module.exports = {
'key.name': 'numVal',
'value.type': 'NumberTypeAnnotation',
}
]
],
}
}
}
Expand Down Expand Up @@ -826,6 +826,20 @@ module.exports = {
}
}
},
'Exact object types': {
'var obj: {| x: number, y: string |}': { // no trailing comma
'body.0.declarations.0.id.typeAnnotation.typeAnnotation': {
'type': 'ObjectTypeAnnotation',
'exact': true,
},
},
'var obj: {| x: number, y: string, |}': { // trailing comma
'body.0.declarations.0.id.typeAnnotation.typeAnnotation': {
'type': 'ObjectTypeAnnotation',
'exact': true,
},
}
},
'Tuples': {
'var a : [] = [];': {
'body.0.declarations.0.id.typeAnnotation.typeAnnotation': {
Expand Down
2 changes: 1 addition & 1 deletion src/typing/class_sig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ let extract_mixins _cx =
let mk_interface cx loc reason structural self = Ast.Statement.(
fun { Interface.
typeParameters;
body = (_, { Ast.Type.Object.properties; indexers; callProperties });
body = (_, { Ast.Type.Object.properties; indexers; callProperties; _ });
extends;
mixins;
_;
Expand Down
4 changes: 2 additions & 2 deletions src/typing/type_annotation.ml
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ let rec convert cx tparams_map = Ast.Type.(function
in
if (tparams = []) then ft else PolyT(tparams, ft)

| loc, Object { Object.properties; indexers; callProperties; } ->
| loc, Object { Object.exact; properties; indexers; callProperties; } ->
let props_map = List.fold_left (
fun props_map (loc, { Object.Property.key; value; optional; _ }) ->
(match key with
Expand Down Expand Up @@ -513,7 +513,7 @@ let rec convert cx tparams_map = Ast.Type.(function
let proto = MixedT (reason_of_string reason_desc, Mixed_everything) in
let flags = {
sealed = if sealed then Sealed else UnsealedInFile (Loc.source loc);
exact = not sealed;
exact = not sealed || exact;
frozen = false;
} in
ObjT (mk_reason reason_desc loc,
Expand Down

0 comments on commit c710c40

Please sign in to comment.