Skip to content

Commit 3ff1fba

Browse files
avikchaudhuriAvik Chaudhuri
authored andcommitted
spread of exact = exact
Summary: Spreading a property always leads to returning an unsealed object, but we can consider the result sealed unless we are spreading an explicitly unsealed object, say, {}. Exactness is determined by the conjunction of flags.exact and Obj_type.sealed_in_op ... flags.sealed, so this fix ensures that we consider more objects exact. Reviewed By: samwgoldman Differential Revision: D7336715 fbshipit-source-id: 40a7b2c01fafd1f0af64689268448a93d14828c8
1 parent 888abc8 commit 3ff1fba

File tree

12 files changed

+231
-58
lines changed

12 files changed

+231
-58
lines changed

src/typing/debug_js.ml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1436,7 +1436,7 @@ and json_of_obj_assign_kind json_cx =
14361436

14371437
and json_of_obj_assign_kind_impl _json_cx kind = Hh_json.JSON_String (
14381438
match kind with
1439-
| ObjAssign -> "normal"
1439+
| ObjAssign _ -> "normal"
14401440
| ObjSpreadAssign -> "spread"
14411441
)
14421442

@@ -2752,3 +2752,7 @@ let dump_flow_error =
27522752
spf "EInvalidPrototype (%s)" (dump_reason cx reason)
27532753
| EExperimentalOptionalChaining loc ->
27542754
spf "EExperimentalOptionalChaining (%s)" (string_of_loc loc)
2755+
| EInexactSpread (reason, reason_op) ->
2756+
spf "EInexactSpread (%s, %s)"
2757+
(dump_reason cx reason)
2758+
(dump_reason cx reason_op)

src/typing/flow_error.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ type error_message =
153153
}
154154
| EInvalidPrototype of reason
155155
| EExperimentalOptionalChaining of Loc.t
156+
| EInexactSpread of reason * reason
156157

157158
and binding_error =
158159
| ENameAlreadyBound
@@ -365,6 +366,7 @@ let util_use_op_of_msg nope util = function
365366
| ESketchyNullLint {kind=_; loc=_; null_loc=_; falsy_loc=_}
366367
| EInvalidPrototype (_)
367368
| EExperimentalOptionalChaining (_)
369+
| EInexactSpread _
368370
-> nope
369371

370372
(* Rank scores for signals of different strength on an x^2 scale so that greater
@@ -1936,3 +1938,12 @@ let rec error_of_msg ~trace_reasons ~source_file =
19361938
code "esproposal.optional_chaining=enable"; text " into the ";
19371939
code "[options]"; text " section of your "; code ".flowconfig"; text ".";
19381940
]
1941+
1942+
| EInexactSpread (reason, reason_op) ->
1943+
mk_error (loc_of_reason reason) [
1944+
text "Cannot determine the type of "; ref reason_op; text " because ";
1945+
text "it contains a spread of inexact "; ref reason; text ". ";
1946+
text "Being inexact, "; ref reason;
1947+
text " might be missing the types of some properties that are being copied. ";
1948+
text "Perhaps you could make it exact?"
1949+
]

src/typing/flow_js.ml

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,7 +2356,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
23562356
let desc = if use_desc then Some (desc_of_reason reason_op) else None in
23572357
rec_flow cx trace (reposition cx ~trace loc ?desc l, u)
23582358

2359-
| DefT (_, MaybeT t), ObjAssignFromT (_, _, _, ObjAssign) ->
2359+
| DefT (_, MaybeT t), ObjAssignFromT (_, _, _, ObjAssign _) ->
23602360
(* This isn't correct, but matches the existing incorrectness of spreads
23612361
* today. In particular, spreading `null` and `void` become {}. The wrong
23622362
* part is that spreads should distribute through unions, so `{...?T}`
@@ -2386,7 +2386,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
23862386
reposition the entire optional type. *)
23872387
rec_flow cx trace (reposition_reason cx ~trace reason ~use_desc l, u)
23882388

2389-
| DefT (_, OptionalT t), ObjAssignFromT (_, _, _, ObjAssign) ->
2389+
| DefT (_, OptionalT t), ObjAssignFromT (_, _, _, ObjAssign _) ->
23902390
(* This isn't correct, but matches the existing incorrectness of spreads
23912391
* today. In particular, spreading `null` and `void` become {}. The wrong
23922392
* part is that spreads should distribute through unions, so `{...?T}`
@@ -3345,7 +3345,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
33453345

33463346
(* ObjT LB ~> $Exact<UB>. make exact if exact and unsealed *)
33473347
| DefT (_, ObjT { flags; _ }), UseT (use_op, ExactT (r, t)) ->
3348-
if flags.exact && sealed_in_op r flags.sealed
3348+
if flags.exact && (Obj_type.sealed_in_op r flags.sealed)
33493349
then rec_flow cx trace (t, MakeExactT (r, Lower (use_op, l)))
33503350
else begin
33513351
let reasons = FlowError.ordered_reasons (reason_of_t l, r) in
@@ -3441,7 +3441,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
34413441
let lookup_default = match l with
34423442
| DefT (_, ObjT { flags; _ })
34433443
when flags.exact ->
3444-
if sealed_in_op reason_op flags.sealed then
3444+
if Obj_type.sealed_in_op reason_op flags.sealed then
34453445
let r = replace_reason_const (RMissingProperty name) reason_op in
34463446
Some (DefT (r, VoidT), lookup_default)
34473447
else
@@ -4277,7 +4277,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
42774277

42784278
| (_, UseT (_, ShapeT (o))) ->
42794279
let reason = reason_of_t o in
4280-
rec_flow cx trace (l, ObjAssignFromT (reason, o, Locationless.AnyT.t, ObjAssign))
4280+
rec_flow cx trace (l, ObjAssignFromT (reason, o, Locationless.AnyT.t, default_obj_assign_kind))
42814281

42824282
| DefT (_, AnyT), ObjTestT (reason_op, _, u) ->
42834283
rec_flow_t cx trace (AnyT.why reason_op, u)
@@ -4772,8 +4772,8 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
47724772
(* objects can be assigned, i.e., their properties can be set in bulk *)
47734773
(**********************************************************************)
47744774

4775-
| proto, ObjAssignToT (reason, from, t, kind) ->
4776-
rec_flow cx trace (from, ObjAssignFromT (reason, proto, t, kind))
4775+
| to_obj, ObjAssignToT (reason, from_obj, t, kind) ->
4776+
rec_flow cx trace (from_obj, ObjAssignFromT (reason, to_obj, t, kind))
47774777

47784778
(** When some object-like type O1 flows to
47794779
ObjAssignFromT(_,O2,X,ObjAssign), the properties of O1 are copied to
@@ -4787,8 +4787,8 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
47874787
avoid this race, we make O2 flow to ObjAssignToT(_,O1,X,ObjAssign);
47884788
when O2 is resolved, we make the switch. **)
47894789

4790-
| DefT (lreason, ObjT { props_tmap = mapr; _ }),
4791-
ObjAssignFromT (reason_op, proto, t, ObjAssign) ->
4790+
| DefT (lreason, ObjT { props_tmap = mapr; flags; dict_t; _ }),
4791+
ObjAssignFromT (reason_op, to_obj, t, ObjAssign error_flags) ->
47924792
let props_to_skip = ["$call"] in
47934793
Context.iter_props cx mapr (fun x p ->
47944794
if not (List.mem x props_to_skip) then (
@@ -4804,7 +4804,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
48044804
| Some t ->
48054805
let propref = Named (reason_prop, x) in
48064806
let t = filter_optional cx ~trace reason_prop t in
4807-
rec_flow cx trace (proto, SetPropT (
4807+
rec_flow cx trace (to_obj, SetPropT (
48084808
unknown_use, reason_prop, propref, Normal, t, None
48094809
))
48104810
| None ->
@@ -4813,10 +4813,15 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
48134813
))
48144814
)
48154815
);
4816-
rec_flow_t cx trace (proto, t)
4816+
if dict_t <> None then rec_flow_t cx trace (DefT (reason_op, AnyObjT), t)
4817+
else begin
4818+
if error_flags.assert_exact && not flags.exact
4819+
then add_output cx ~trace (FlowError.EInexactSpread (lreason, reason_op));
4820+
rec_flow_t cx trace (to_obj, t)
4821+
end
48174822

48184823
| DefT (lreason, InstanceT (_, _, _, { fields_tmap; methods_tmap; _ })),
4819-
ObjAssignFromT (reason_op, proto, t, ObjAssign) ->
4824+
ObjAssignFromT (reason_op, to_obj, t, ObjAssign _) ->
48204825
let fields_pmap = Context.find_props cx fields_tmap in
48214826
let methods_pmap = Context.find_props cx methods_tmap in
48224827
let pmap = SMap.union fields_pmap methods_pmap in
@@ -4826,7 +4831,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
48264831
match Property.read_t p with
48274832
| Some t ->
48284833
let propref = Named (reason_op, x) in
4829-
rec_flow cx trace (proto, SetPropT (
4834+
rec_flow cx trace (to_obj, SetPropT (
48304835
unknown_use, reason_op, propref, Normal, t, None
48314836
))
48324837
| None ->
@@ -4835,24 +4840,24 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
48354840
))
48364841
)
48374842
);
4838-
rec_flow_t cx trace (proto, t)
4843+
rec_flow_t cx trace (to_obj, t)
48394844

48404845
(* AnyObjT has every prop, each one typed as `any`, so spreading it into an
48414846
existing object destroys all of the keys, turning the result into an
4842-
AnyObjT as well. TODO: wait for `proto` to be resolved, and then call
4847+
AnyObjT as well. TODO: wait for `to_obj` to be resolved, and then call
48434848
`SetPropT (_, _, _, AnyT, _)` on all of its props. *)
4844-
| DefT (_, AnyObjT), ObjAssignFromT (reason, _, t, ObjAssign) ->
4849+
| DefT (_, AnyObjT), ObjAssignFromT (reason, _, t, ObjAssign _) ->
48454850
rec_flow_t cx trace (DefT (reason, AnyObjT), t)
48464851

4847-
| ObjProtoT _, ObjAssignFromT (_, proto, t, ObjAssign) ->
4848-
rec_flow_t cx trace (proto, t)
4852+
| ObjProtoT _, ObjAssignFromT (_, to_obj, t, ObjAssign _) ->
4853+
rec_flow_t cx trace (to_obj, t)
48494854

48504855
(* Object.assign semantics *)
4851-
| DefT (_, (NullT | VoidT)), ObjAssignFromT (_, proto, tout, ObjAssign) ->
4852-
rec_flow_t cx trace (proto, tout)
4856+
| DefT (_, (NullT | VoidT)), ObjAssignFromT (_, to_obj, tout, ObjAssign _) ->
4857+
rec_flow_t cx trace (to_obj, tout)
48534858

48544859
(* {...mixed} is the equivalent of {...{[string]: mixed}} *)
4855-
| DefT (reason, MixedT _), ObjAssignFromT (_, _, _, ObjAssign) ->
4860+
| DefT (reason, MixedT _), ObjAssignFromT (_, _, _, ObjAssign _) ->
48564861
let dict = {
48574862
dict_name = None;
48584863
key = StrT.make reason;
@@ -4871,16 +4876,16 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
48714876
| ArrayAT (elemt, None)
48724877
| ROArrayAT (elemt) ->
48734878
(* Object.assign(o, ...Array<x>) -> Object.assign(o, x) *)
4874-
rec_flow cx trace (elemt, ObjAssignFromT (r, o, t, ObjAssign))
4879+
rec_flow cx trace (elemt, ObjAssignFromT (r, o, t, default_obj_assign_kind))
48754880
| TupleAT (_, ts)
48764881
| ArrayAT (_, Some ts) ->
48774882
(* Object.assign(o, ...[x,y,z]) -> Object.assign(o, x, y, z) *)
48784883
List.iter (fun from ->
4879-
rec_flow cx trace (from, ObjAssignFromT (r, o, t, ObjAssign))
4884+
rec_flow cx trace (from, ObjAssignFromT (r, o, t, default_obj_assign_kind))
48804885
) ts
48814886
| EmptyAT ->
48824887
(* Object.assign(o, ...EmptyAT) -> Object.assign(o, empty) *)
4883-
rec_flow cx trace (DefT (arr_r, EmptyT), ObjAssignFromT (r, o, t, ObjAssign))
4888+
rec_flow cx trace (DefT (arr_r, EmptyT), ObjAssignFromT (r, o, t, default_obj_assign_kind))
48844889
end
48854890

48864891
(*************************)
@@ -4898,7 +4903,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
48984903
(* Remove shadow properties from rest result *)
48994904
let props = SMap.filter (fun x _ -> not (is_internal_name x)) props in
49004905
let proto = ObjProtoT reason in
4901-
let sealed = sealed_in_op reason flags.sealed in
4906+
let sealed = Obj_type.sealed_in_op reason flags.sealed in
49024907
(* A rest result can not be exact if the source object is unsealed,
49034908
because we may not have seen all the writes yet. *)
49044909
let exact = sealed && flags.exact in
@@ -4923,7 +4928,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
49234928
let o = Tvar.mk_where cx reason_op (fun tvar ->
49244929
rec_flow cx trace (
49254930
obj_inst,
4926-
ObjAssignFromT (reason_op, obj_super, tvar, ObjAssign)
4931+
ObjAssignFromT (reason_op, obj_super, tvar, default_obj_assign_kind)
49274932
)
49284933
) in
49294934

@@ -4958,6 +4963,9 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
49584963
| DefT (_, AnyT), ObjSealT (reason, tout) ->
49594964
rec_flow_t cx trace (AnyT.why reason, tout)
49604965

4966+
| DefT (_, AnyObjT), ObjSealT (reason, tout) ->
4967+
rec_flow_t cx trace (DefT (reason, AnyObjT), tout)
4968+
49614969
(*************************)
49624970
(* objects can be frozen *)
49634971
(*************************)
@@ -4988,7 +4996,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
49884996
| _ -> ());
49894997
perform_lookup_action cx trace propref p reason_obj reason_op action
49904998
| None ->
4991-
let strict = match sealed_in_op reason_op o.flags.sealed, strict with
4999+
let strict = match Obj_type.sealed_in_op reason_op o.flags.sealed, strict with
49925000
| false, ShadowRead (strict, ids) ->
49935001
ShadowRead (strict, Nel.cons o.props_tmap ids)
49945002
| false, ShadowWrite ids ->
@@ -5303,7 +5311,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
53035311
(***********************************************)
53045312

53055313
| DefT (_, FunT (_, t, _)), SetPropT (_, reason_op, Named (_, "prototype"), _, tin, _) ->
5306-
rec_flow cx trace (tin, ObjAssignFromT (reason_op, t, Locationless.AnyT.t, ObjAssign))
5314+
rec_flow cx trace (tin, ObjAssignFromT (reason_op, t, Locationless.AnyT.t, default_obj_assign_kind))
53075315

53085316
(*********************************)
53095317
(* ... and their prototypes read *)
@@ -6446,7 +6454,7 @@ and flow_obj_to_obj cx trace ~use_op (lreason, l_obj) (ureason, u_obj) =
64466454
((reason_prop, lreason), lreason, Some s, Some use_op))
64476455
| _ ->
64486456
(* otherwise, look up the property in the prototype *)
6449-
let strict = match sealed_in_op ureason lflags.sealed, ldict with
6457+
let strict = match Obj_type.sealed_in_op ureason lflags.sealed, ldict with
64506458
| false, None -> ShadowRead (Some lreason, Nel.one lflds)
64516459
| true, None -> Strict lreason
64526460
| _ -> NonstrictReturning (None, None)
@@ -6804,10 +6812,6 @@ and flow_type_args cx trace ~use_op lreason ureason pmap tmap1 tmap2 =
68046812

68056813
and inherited_method x = x <> "constructor" && x <> "$call"
68066814

6807-
and sealed_in_op reason_op = function
6808-
| Sealed -> true
6809-
| UnsealedInFile source -> source <> (Loc.source (loc_of_reason reason_op))
6810-
68116815
(* dispatch checks to verify that lower satisfies the structural
68126816
requirements given in the tuple. *)
68136817
and structural_subtype cx trace ?(use_op=unknown_use) lower reason_struct
@@ -7455,7 +7459,7 @@ and spread_objects cx reason those =
74557459
and chain_objects cx ?trace reason this those =
74567460
let result = List.fold_left (fun result that ->
74577461
let that, kind = match that with
7458-
| Arg t -> t, ObjAssign
7462+
| Arg t -> t, default_obj_assign_kind
74597463
| SpreadArg t ->
74607464
(* If someone does Object.assign({}, ...Array<obj>) we can treat it like
74617465
Object.assign({}, obj). *)
@@ -8266,7 +8270,7 @@ and read_obj_prop cx trace ~use_op o propref reason_obj reason_op tout =
82668270
match propref with
82678271
| Named _ ->
82688272
let strict =
8269-
if sealed_in_op reason_op o.flags.sealed
8273+
if Obj_type.sealed_in_op reason_op o.flags.sealed
82708274
then Strict reason_obj
82718275
else ShadowRead (None, Nel.one o.props_tmap)
82728276
in
@@ -8298,7 +8302,7 @@ and write_obj_prop cx trace ~use_op o propref reason_obj reason_op tin prop_t =
82988302
| None ->
82998303
match propref with
83008304
| Named (reason_prop, prop) ->
8301-
let sealed = sealed_in_op reason_op o.flags.sealed in
8305+
let sealed = Obj_type.sealed_in_op reason_op o.flags.sealed in
83028306
if sealed && o.flags.exact
83038307
then
83048308
add_output cx ~trace (FlowError.EPropNotFound
@@ -8699,7 +8703,7 @@ and prop_exists_test_generic
86998703
(lreason, reason), Some key, Property.polarity p, Read, unknown_use
87008704
))
87018705
)
8702-
| None when flags.exact && sealed_in_op (reason_of_t result) flags.sealed ->
8706+
| None when flags.exact && Obj_type.sealed_in_op (reason_of_t result) flags.sealed ->
87038707
(* prop is absent from exact object type *)
87048708
if sense
87058709
then ()
@@ -10571,7 +10575,7 @@ and react_kit cx trace ~use_op reason_op l u =
1057110575
~mk_instance
1057210576
~string_key
1057310577
~mk_type_destructor
10574-
~sealed_in_op
10578+
~sealed_in_op:Obj_type.sealed_in_op
1057510579
~union_of_ts
1057610580
~filter_maybe
1057710581
cx trace ~use_op reason_op l u
@@ -10872,8 +10876,8 @@ and object_kit =
1087210876
sealed = Sealed;
1087310877
exact =
1087410878
flags1.exact && flags2.exact &&
10875-
sealed_in_op reason flags1.sealed &&
10876-
sealed_in_op reason flags2.sealed;
10879+
Obj_type.sealed_in_op reason flags1.sealed &&
10880+
Obj_type.sealed_in_op reason flags2.sealed;
1087710881
} in
1087810882
reason, props, dict, flags
1087910883
in
@@ -11099,7 +11103,7 @@ and object_kit =
1109911103
let flags = {
1110011104
frozen = false;
1110111105
sealed = Sealed;
11102-
exact = flags1.exact && sealed_in_op reason flags1.sealed;
11106+
exact = flags1.exact && Obj_type.sealed_in_op reason flags1.sealed;
1110311107
} in
1110411108
let id = Context.make_property_map cx props in
1110511109
let proto = ObjProtoT r1 in
@@ -11230,8 +11234,8 @@ and object_kit =
1123011234
sealed = Sealed;
1123111235
exact =
1123211236
config_flags.exact && defaults_flags.exact &&
11233-
sealed_in_op reason config_flags.sealed &&
11234-
sealed_in_op reason defaults_flags.sealed;
11237+
Obj_type.sealed_in_op reason config_flags.sealed &&
11238+
Obj_type.sealed_in_op reason defaults_flags.sealed;
1123511239
} in
1123611240
props, dict, flags
1123711241
(* Otherwise turn our slice props map into an object props. *)
@@ -11251,7 +11255,7 @@ and object_kit =
1125111255
let flags = {
1125211256
frozen = true;
1125311257
sealed = Sealed;
11254-
exact = config_flags.exact && sealed_in_op reason config_flags.sealed;
11258+
exact = config_flags.exact && Obj_type.sealed_in_op reason config_flags.sealed;
1125511259
} in
1125611260
props, dict, flags
1125711261
in

src/typing/obj_type.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ let mk_with_proto cx reason
1919

2020
let mk cx reason =
2121
mk_with_proto cx reason (ObjProtoT reason)
22+
23+
and sealed_in_op reason_op = function
24+
| Sealed -> true
25+
| UnsealedInFile source -> source <> (Loc.source (Reason.loc_of_reason reason_op))

0 commit comments

Comments
 (0)