From 7c3390f7dcf886b0b39acfa505446614641ecb92 Mon Sep 17 00:00:00 2001 From: Marshall Roch Date: Thu, 9 Nov 2017 12:45:43 -0800 Subject: [PATCH] make $Values on a frozen object return singleton literals Summary: on a frozen object, StrT, NumT and BoolT values can't be changed so we can treat them as singletons. this makes this pattern work: ``` const Enum = Object.freeze({ X: 'x', Y: 'y', }); type EnumT = $Values ('a': EnumT); // error instead of StrT ~> StrT ``` Reviewed By: avikchaudhuri Differential Revision: D6232529 fbshipit-source-id: d24339d7a7a607e24a9312d637f83d5c92a97a15 --- src/typing/flow_js.ml | 18 ++++++++++++++++-- tests/values/.flowconfig | 1 + tests/values/object_types.js | 15 +++++++++++++++ tests/values/values.exp | 20 +++++++++++++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/typing/flow_js.ml b/src/typing/flow_js.ml index eebb7babbee..c4e24c9b221 100644 --- a/src/typing/flow_js.ml +++ b/src/typing/flow_js.ml @@ -2801,7 +2801,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace = (* values *) (**********) - | DefT (_, ObjT { props_tmap = tmap; dict_t; _ }), GetValuesT (reason, values) -> + | DefT (_, ObjT { props_tmap = tmap; dict_t; flags; _ }), GetValuesT (reason, values) -> (* Find all of the props. *) let props = Context.find_props cx tmap in (* Get the read type for all readable properties and discard the rest. *) @@ -2809,7 +2809,21 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace = match Property.read_t prop with (* Don't include the type for the internal "$call" property if one exists. *) - | Some t when key != "$call" -> t :: ts + | Some t when key != "$call" -> + let t = if flags.frozen then + match t with + | DefT (t_reason, StrT (Literal (_, lit))) -> + let t_reason = replace_reason_const (RStringLit lit) t_reason in + DefT (t_reason, SingletonStrT lit) + | DefT (t_reason, NumT (Literal (_, lit))) -> + let t_reason = replace_reason_const (RNumberLit (snd lit)) t_reason in + DefT (t_reason, SingletonNumT lit) + | DefT (t_reason, BoolT (Some lit)) -> + let t_reason = replace_reason_const (RBooleanLit lit) t_reason in + DefT (t_reason, SingletonBoolT lit) + | _ -> t + else t in + t :: ts | _ -> ts ) props [] in (* If the object has a dictionary value then add that to our types. *) diff --git a/tests/values/.flowconfig b/tests/values/.flowconfig index 4557c794115..d67f006ba42 100644 --- a/tests/values/.flowconfig +++ b/tests/values/.flowconfig @@ -1,2 +1,3 @@ [options] unsafe.enable_getters_and_setters=true +no_flowlib=false diff --git a/tests/values/object_types.js b/tests/values/object_types.js index 1b47c3b788e..f0fb2598178 100644 --- a/tests/values/object_types.js +++ b/tests/values/object_types.js @@ -80,3 +80,18 @@ magicTrick('DIAMONDS'); // Error: 'DIAMONDS' is a key, but not a value. magicTrick('Magic'); // Error: 'Magic' is not a value. magicTrick(('Diamonds': string)); // Error: the `string` type is to general and // not a value. + +// same as Suite above, but uses Object.freeze instead of needing an +// annotation. +const FrozenSuite = Object.freeze({ + DIAMONDS: 'Diamonds', + CLUBS: 'Clubs', + HEARTS: 'Hearts', + SPADES: 'Spades', +}); +type FrozenSuiteEnum = $Values +('Diamonds': FrozenSuiteEnum); // ok +(DIAMONDS: FrozenSuiteEnum); // ok +('DIAMONDS': FrozenSuiteEnum); // Error: 'DIAMONDS' is a key, but not a value. +('Magic': FrozenSuiteEnum); // Error: 'Magic' is not a value. +(('Diamonds': string): FrozenSuiteEnum); // Error: `string` is too general diff --git a/tests/values/values.exp b/tests/values/values.exp index a1d693b6903..d7d3d442493 100644 --- a/tests/values/values.exp +++ b/tests/values/values.exp @@ -366,5 +366,23 @@ Error: object_types.js:81 64: function magicTrick(suite: SuiteEnum) { ^^^^^^^^^ string enum +Error: object_types.js:95 + 95: ('DIAMONDS': FrozenSuiteEnum); // Error: 'DIAMONDS' is a key, but not a value. + ^^^^^^^^^^ string. This type is incompatible with + 95: ('DIAMONDS': FrozenSuiteEnum); // Error: 'DIAMONDS' is a key, but not a value. + ^^^^^^^^^^^^^^^ string enum + +Error: object_types.js:96 + 96: ('Magic': FrozenSuiteEnum); // Error: 'Magic' is not a value. + ^^^^^^^ string. This type is incompatible with + 96: ('Magic': FrozenSuiteEnum); // Error: 'Magic' is not a value. + ^^^^^^^^^^^^^^^ string enum + +Error: object_types.js:97 + 97: (('Diamonds': string): FrozenSuiteEnum); // Error: `string` is too general + ^^^^^^ string. This type is incompatible with + 97: (('Diamonds': string): FrozenSuiteEnum); // Error: `string` is too general + ^^^^^^^^^^^^^^^ string enum + -Found 36 errors +Found 39 errors