Skip to content
Permalink
Browse files

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<typeof Enum>
('a': EnumT); // error instead of StrT ~> StrT
```

Reviewed By: avikchaudhuri

Differential Revision: D6232529

fbshipit-source-id: d24339d7a7a607e24a9312d637f83d5c92a97a15
  • Loading branch information...
mroch authored and facebook-github-bot committed Nov 9, 2017
1 parent 42ba05d commit 7c3390f7dcf886b0b39acfa505446614641ecb92
Showing with 51 additions and 3 deletions.
  1. +16 −2 src/typing/flow_js.ml
  2. +1 −0 tests/values/.flowconfig
  3. +15 −0 tests/values/object_types.js
  4. +19 −1 tests/values/values.exp
@@ -2801,15 +2801,29 @@ 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. *)
let ts = SMap.fold (fun key prop ts ->
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. *)
@@ -1,2 +1,3 @@
[options]
unsafe.enable_getters_and_setters=true
no_flowlib=false
@@ -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<typeof FrozenSuite>
('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
@@ -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

0 comments on commit 7c3390f

Please sign in to comment.
You can’t perform that action at this time.