From ad443dc92879ae21705d4c61b942ba2f8ad61e4d Mon Sep 17 00:00:00 2001 From: Sam Goldman Date: Mon, 6 Mar 2017 11:26:23 -0800 Subject: [PATCH] object type spread Summary: This diffs adds typing support for object type spread properties. = Overview For example, a type of the form `{...A,...B}` is an object with the properties of `A` and `B`. If some property `p` appears in both `A` and `B`, the resulting type will use the property type from `B`. That is, spreads overwrite earlier properties. = Motivation Currently, people use intersections for a similar result, but `A & B` doesn't behave the way people usually expect when the property sets are not disjoint. Furthermore, it's not possible to use intersections for exact object types, even when the property sets are disjoint. That is, no object is the inhabitant of the type `{| p: T |} & {| q: U |}`. But `{| ...A, ...B |}`, where `A` is `{| p: T |}` and `B` is `{| q: U |}` does what you expect. = Relationship with runtime object spread The rules for producing a resulting object type are derived from an idealized view of runtime object spread. That is, the type `{...A,...B}` is intended to be the type of `{...a,...b}` given `a:A` and `b:B`. At runtime, object spread is concerned with the layout of properties. Only the own properties are considered. Flow's object types don't model layout, so a value with type `{p:T}` might have an own property `p` or might get `p` from anywhere in it's prototype chain. Eventually we will change exact object types to imply all-own properties. For example, a value with the type `{|p:T|}` will definitely have an own property `p` with type `T`. The logic used in this diff assumes that exact-implies-own holds. Assuming this makes it possible to implement object type spread without waiting for the exact object work to be completed. Eventually, once exact does in fact imply own, we can use the same logic for object value spread and replace current, buggy spread implementation. = Unions A spread of unions is equal to a union of spreads. `{...A|B}` is equal to `{...A}|{...B}`. Spreading unions and non-unions leads to forking behavior. `{...A|B,...C}` is equal to `{...A,...C}|{...B,...C}`, for example. = Intersections A spread of intersections is equal to a spread of a merged intersection type. `{...{p:T}&{q:U}}` is equal to `{...{p:T,q:U}`. Intersections distribute through unions. `{...A&(B|C)}` is eequal to `{...(A&B)|(A&C)}`, for example. = Instance types Instance types add a small additional complication, because the super class's class properties represent own properties. To satisfy this, we walk up the super classes until we hit a non-instance, collecting own properties as we go. Instances are modeled as inexact objects w.r.t. spread. Reviewed By: gabelevi Differential Revision: D4268886 fbshipit-source-id: c1d6bb8d6d2b6b90c08095e8980a24bc0b6c25ff --- src/common/utils/nel.ml | 13 + src/parser/estree_translator.ml | 2 +- src/parser/spider_monkey_ast.ml | 2 +- src/parser/type_parser.ml | 2 +- src/typing/debug_js.ml | 54 +++ src/typing/flow_error.ml | 4 +- src/typing/flow_js.ml | 307 ++++++++++++++++++ src/typing/gc_js.ml | 31 ++ src/typing/graph.ml | 1 + src/typing/type.ml | 44 ++- src/typing/type_annotation.ml | 142 ++++---- src/typing/type_visitor.ml | 1 + tests/new_spread/.flowconfig | 0 tests/new_spread/new_spread.exp | 179 ++++++++++ tests/new_spread/type.js | 91 ++++++ tests/new_spread/type_any.js | 13 + tests/new_spread/type_contra.js | 13 + tests/new_spread/type_dict.js | 38 +++ tests/new_spread/type_instance.js | 11 + tests/new_spread/type_intersection.js | 28 ++ .../new_spread/type_intersection_optional.js | 65 ++++ tests/new_spread/type_optional.js | 22 ++ tests/new_spread/type_union.js | 9 + 23 files changed, 1010 insertions(+), 62 deletions(-) create mode 100644 tests/new_spread/.flowconfig create mode 100644 tests/new_spread/new_spread.exp create mode 100644 tests/new_spread/type.js create mode 100644 tests/new_spread/type_any.js create mode 100644 tests/new_spread/type_contra.js create mode 100644 tests/new_spread/type_dict.js create mode 100644 tests/new_spread/type_instance.js create mode 100644 tests/new_spread/type_intersection.js create mode 100644 tests/new_spread/type_intersection_optional.js create mode 100644 tests/new_spread/type_optional.js create mode 100644 tests/new_spread/type_union.js diff --git a/src/common/utils/nel.ml b/src/common/utils/nel.ml index 68ad23829b1..c7733004681 100644 --- a/src/common/utils/nel.ml +++ b/src/common/utils/nel.ml @@ -14,6 +14,19 @@ let iter f (x, xs) = let map f (x, xs) = (f x, List.map f xs) +let concat (xs, xss) = + let xs = to_list xs in + let xss = List.map to_list xss in + match List.concat (xs::xss) with + | [] -> failwith "impossible" + | x::xs -> (x, xs) + +let map_concat f (x, xs) = + let xss = List.map (fun x -> to_list (f x)) (x::xs) in + match List.concat xss with + | [] -> failwith "impossible" + | x::xs -> (x, xs) + let rev (x, xs) = match List.rev (x::xs) with | [] -> failwith "impossible" diff --git a/src/parser/estree_translator.ml b/src/parser/estree_translator.ml index 54c63906f0f..95979e8272c 100644 --- a/src/parser/estree_translator.ml +++ b/src/parser/estree_translator.ml @@ -1041,7 +1041,7 @@ end with type t = Impl.t) = struct and object_type_spread_property (loc, prop) = Type.Object.SpreadProperty.( node "ObjectTypeSpreadProperty" loc [| - "argument", generic_type prop.argument; + "argument", _type prop.argument; |] ) diff --git a/src/parser/spider_monkey_ast.ml b/src/parser/spider_monkey_ast.ml index 5f64827661d..28235ee41d6 100644 --- a/src/parser/spider_monkey_ast.ml +++ b/src/parser/spider_monkey_ast.ml @@ -102,7 +102,7 @@ and Type : sig module SpreadProperty : sig type t = Loc.t * t' and t' = { - argument: Loc.t * Generic.t; + argument: Type.t; } end module Indexer: sig diff --git a/src/parser/type_parser.ml b/src/parser/type_parser.ml index 2cecbfba357..8de46323907 100644 --- a/src/parser/type_parser.ml +++ b/src/parser/type_parser.ml @@ -516,7 +516,7 @@ module Type (Parse: Parser_common.PARSER) : TYPE = struct properties ~allow_static ~allow_spread ~exact env (call_prop::acc) | T_ELLIPSIS when allow_spread -> Eat.token env; - let (arg_loc, _) as argument = generic env in + let (arg_loc, _) as argument = _type env in let loc = Loc.btwn start_loc arg_loc in let property = Type.Object.(SpreadProperty (loc, { SpreadProperty. argument; diff --git a/src/typing/debug_js.ml b/src/typing/debug_js.ml index 4a3cac4baca..8de8f314af0 100644 --- a/src/typing/debug_js.ml +++ b/src/typing/debug_js.ml @@ -629,6 +629,10 @@ and _json_of_use_t_impl json_cx t = Hh_json.( "cont", JSON_Object (_json_of_cont json_cx cont); ] + | ObjSpreadT (_, _, _, tout) -> [ + "t_out", _json_of_t json_cx tout; + ] + | ReactKitT (_, React.CreateElement (t, t_out)) -> [ "config", _json_of_t json_cx t; "returnType", _json_of_t json_cx t_out; @@ -1636,6 +1640,53 @@ and dump_use_t_ (depth, tvars) cx t = spf "CreateClass (%s, %s)" (create_class tool knot) (kid tout) in + let slice (_, props, dict, {exact; _}) = + let xs = match dict with + | Some {dict_polarity=p; _} -> [(Polarity.sigil p)^"[]"] + | None -> [] + in + let xs = SMap.fold (fun k (t,_) xs -> + let opt = match t with OptionalT _ -> "?" | _ -> "" in + (k^opt)::xs + ) props xs in + let xs = String.concat "; " xs in + if exact + then spf "{|%s|}" xs + else spf "{%s}" xs + in + + let object_spread = + let open ObjectSpread in + let join = function And -> "And" | Or -> "Or" in + let resolved xs = + spf "[%s]" (String.concat "; " (List.map slice (Nel.to_list xs))) + in + let resolve = function + | Next -> "Next" + | List0 (todo, j) -> + spf "List0 ([%s], %s)" + (String.concat "; " (List.map kid (Nel.to_list todo))) + (join j) + | List (todo, done_rev, j) -> + spf "List ([%s], [%s], %s)" + (String.concat "; " (List.map kid todo)) + (String.concat "; " (List.map resolved (Nel.to_list done_rev))) + (join j) + in + let tool = function + | Resolve tool -> spf "Resolve %s" (resolve tool) + | Super (s, tool) -> spf "Super (%s, %s)" (slice s) (resolve tool) + in + let state {todo_rev; acc; make_exact} = + spf "{todo_rev=[%s]; acc=[%s]; make_exact=%b}" + (String.concat "; " (List.map kid todo_rev)) + (String.concat "; " (List.map resolved acc)) + make_exact + in + fun t s -> + spf "(%s, %s)" (tool t) (state s) + in + if depth = 0 then string_of_use_ctor t else match t with | UseT (use_op, t) -> spf "UseT (%s, %s)" (string_of_use_op use_op) (kid t) @@ -1742,6 +1793,9 @@ and dump_use_t_ (depth, tvars) cx t = (kid ptype)) t | SpecializeT (_, _, cache, args, ret) -> p ~extra:(spf "%s, [%s], %s" (specialize_cache cache) (String.concat "; " (List.map kid args)) (kid ret)) t + | ObjSpreadT (_, tool, state, arg) -> p ~extra:(spf "%s, %s" + (object_spread tool state) + (kid arg)) t | TestPropT (_, prop, ptype) -> p ~extra:(spf "(%s), %s" (propref prop) (kid ptype)) t diff --git a/src/typing/flow_error.ml b/src/typing/flow_error.ml index 569768e1e36..a03ad19c10a 100644 --- a/src/typing/flow_error.ml +++ b/src/typing/flow_error.ml @@ -168,7 +168,6 @@ and unsupported_syntax = | PredicateInvalidBody | PredicateVoidReturn | MultipleIndexers - | ObjectTypeSpread (* TODO *) | SpreadArgument (* decide reason order based on UB's flavor and blamability *) @@ -240,6 +239,7 @@ let rec error_of_msg ~trace_reasons ~op ~source_file = -> "Expected spread argument to be an iterable instead of" end | TypeAppVarianceCheckT _ -> "Expected polymorphic type instead of" + | ObjSpreadT _ -> "Cannot spread properties from" (* unreachable or unclassified use-types. until we have a mechanical way to verify that all legit use types are listed above, we can't afford to throw on a use type, so mark the error instead *) @@ -852,8 +852,6 @@ let rec error_of_msg ~trace_reasons ~op ~source_file = "Predicate functions need to return non-void." | MultipleIndexers -> "multiple indexers are not supported" - | ObjectTypeSpread -> - "object type spread is not supported" | SpreadArgument -> "A spread argument is unsupported here" in diff --git a/src/typing/flow_js.ml b/src/typing/flow_js.ml index 6f01fb4619c..fc812f26b24 100644 --- a/src/typing/flow_js.ml +++ b/src/typing/flow_js.ml @@ -2608,6 +2608,9 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace = reposition the entire union type. *) rec_flow cx trace (reposition cx ~trace (loc_of_reason reason_op) l, u) + | UnionT _, ObjSpreadT (reason_op, tool, state, tout) -> + object_spread cx trace reason_op tool state tout l + (* cases where there is no loss of precision *) (** Optimization where an union is a subset of another. Equality modulo @@ -2752,6 +2755,9 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace = | IntersectionT _, ReposLowerT (reason_op, u) -> rec_flow cx trace (reposition cx ~trace (loc_of_reason reason_op) l, u) + | IntersectionT _, ObjSpreadT (reason_op, tool, state, tout) -> + object_spread cx trace reason_op tool state tout l + (** All other pairs with an intersection lower bound come here. Before further processing, we ensure that the upper bound is concretized. See prep_try_intersection for details. **) @@ -4469,6 +4475,13 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace = let a = ArrT (reason, arrtype) in rec_flow_t cx trace (a, tout) + (**********************) + (* object type spread *) + (**********************) + + | _, ObjSpreadT (reason_op, tool, state, tout) -> + object_spread cx trace reason_op tool state tout l + (**************************************************) (* function types can be mapped over a structure *) (**************************************************) @@ -9665,6 +9678,7 @@ and continue cx trace t = function | Upper u -> rec_flow cx trace (t, u) | Lower l -> rec_flow_t cx trace (l, t) + and react_kit = React_kit.run ~add_output @@ -9683,6 +9697,299 @@ and react_kit = ~eval_destructor ~sealed_in_op +and object_spread = + let open ObjectSpread in + + let read_prop r flags x p = + let t = match Property.read_t p with + | Some t -> t + | None -> + let reason = replace_reason_const (RUnknownProperty (Some x)) r in + let t = MixedT (reason, Mixed_everything) in + t + in + t, flags.exact + in + + let read_dict r {value; dict_polarity; _} = + if Polarity.compat (dict_polarity, Positive) + then value + else + let reason = replace_reason_const (RUnknownProperty None) r in + MixedT (reason, Mixed_everything) + in + + (* Lift a pairwise function like spread2 to a function over a resolved list *) + let merge (f: slice -> slice -> slice) = + let f' (x0: resolved) (x1: resolved) = + Nel.map_concat (fun slice1 -> + Nel.map (f slice1) x0 + ) x1 + in + let rec loop x0 = function + | [] -> x0 + | x1::xs -> loop (f' x0 x1) xs + in + fun x0 (x1,xs) -> loop (f' x0 x1) xs + in + + (* Compute spread result: slice * slice -> slice *) + let spread2 reason (r1,props1,dict1,flags1) (r2,props2,dict2,flags2) = + let union t1 t2 = UnionT (reason, UnionRep.make t1 t2 []) in + let merge_props (t1, own1) (t2, own2) = + let t1, opt1 = match t1 with OptionalT t -> t, true | _ -> t1, false in + let t2, opt2 = match t2 with OptionalT t -> t, true | _ -> t2, false in + (* An own, non-optional property definitely overwrites earlier properties. + Otherwise, the type might come from either side. *) + let t, own = + if own2 && not opt2 then t2, own2 + else union t1 t2, own1 || own2 + in + (* If either property is own, the result is non-optional unless the own + property is itself optional. Non-own implies optional (see mk_object), + so we don't need to handle those cases here. *) + let opt = + if own1 && own2 then opt1 && opt2 + else own1 && opt1 || own2 && opt2 + in + let t = if opt then OptionalT t else t in + t, own + in + let props = SMap.merge (fun x p1 p2 -> + (* Treat dictionaries as optional, own properties. Dictionary reads should + * be exact. TODO: Forbid writes to indexers through the photo chain. + * Property accesses which read from dictionaries normally result in a + * non-optional result, but that leads to confusing spread results. For + * example, `p` in `{...{|p:T|},...{[]:U}` should `T|U`, not `U`. *) + let read_dict r d = OptionalT (read_dict r d), true in + (* Due to width subtyping, failing to read from an inexact object does not + imply non-existence, but rather an unknown result. *) + let unknown r = + let r = replace_reason_const (RUnknownProperty (Some x)) r in + MixedT (r, Mixed_everything), false + in + match p1, p2 with + | None, None -> None + | Some p1, Some p2 -> Some (merge_props p1 p2) + | Some p1, None -> + (match dict2 with + | Some d2 -> Some (merge_props p1 (read_dict r2 d2)) + | None -> + if flags2.exact + then Some p1 + else Some (merge_props p1 (unknown r2))) + | None, Some p2 -> + (match dict1 with + | Some d1 -> Some (merge_props (read_dict r1 d1) p2) + | None -> + if flags1.exact + then Some p2 + else Some (merge_props (unknown r1) p2)) + ) props1 props2 in + let dict = Option.merge dict1 dict2 (fun d1 d2 -> { + dict_name = None; + key = union d1.key d2.key; + value = union (read_dict r1 d1) (read_dict r2 d2); + dict_polarity = Neutral + }) in + let flags = { + frozen = flags1.frozen && flags2.frozen; + sealed = Sealed; + exact = + flags1.exact && flags2.exact && + sealed_in_op reason flags1.sealed && + sealed_in_op reason flags2.sealed; + } in + reason, props, dict, flags + in + + (* Intersect two object slices: slice * slice -> slice + * + * In general it is unsound to combine intersection types, but since spread + * makes a copy and only reads from its arguments, it is safe in this specific + * case. + * + * {...{p:T}&{q:U}} = {...{p:T,q:U}} + * {...{p:T}&{p:U}} = {...{p:T&U}} + * {...A&(B|C)} = {...{A&B)|(A&C)} + * {...(A|B)&C} = {...{A&C)|(B&C)} + *) + let intersect2 reason (r1,props1,dict1,flags1) (r2,props2,dict2,flags2) = + let intersection t1 t2 = IntersectionT (reason, InterRep.make t1 t2 []) in + let merge_props (t1, own1) (t2, own2) = + let t1, t2, opt = match t1, t2 with + | OptionalT t1, OptionalT t2 -> t1, t2, true + | OptionalT t1, t2 | t1, OptionalT t2 | t1, t2 -> t1, t2, false + in + let t = intersection t1 t2 in + let t = if opt then OptionalT t else t in + t, own1 || own2 + in + let r = + let loc = Loc.btwn (loc_of_reason r1) (loc_of_reason r2) in + mk_reason RObjectType loc + in + let props = SMap.merge (fun _ p1 p2 -> + let read_dict r d = OptionalT (read_dict r d), true in + match p1, p2 with + | None, None -> None + | Some p1, Some p2 -> Some (merge_props p1 p2) + | Some p1, None -> + (match dict2 with + | Some d2 -> Some (merge_props p1 (read_dict r2 d2)) + | None -> Some p1) + | None, Some p2 -> + (match dict1 with + | Some d1 -> Some (merge_props (read_dict r1 d1) p2) + | None -> Some p2) + ) props1 props2 in + let dict = Option.merge dict1 dict2 (fun d1 d2 -> { + dict_name = None; + key = intersection d1.key d2.key; + value = intersection (read_dict r1 d1) (read_dict r2 d2); + dict_polarity = Neutral; + }) in + let flags = { + frozen = flags1.frozen || flags2.frozen; + sealed = Sealed; + exact = flags1.exact || flags2.exact; + } in + r, props, dict, flags + in + + let spread reason = function + | x,[] -> x + | x0,x1::xs -> merge (spread2 reason) x0 (x1,xs) + in + + let mk_object cx reason ~make_exact (r, props, dict, flags) = + let props = SMap.map (fun (t, own) -> + (* Spread only copies over own properties. If `not own`, then the property + might be on a proto object instead, so make the result optional. *) + let t = match t with + | OptionalT _ -> t + | _ -> if own then t else OptionalT t + in + Field (t, Neutral) + ) props in + let id = Context.make_property_map cx props in + let proto = ObjProtoT reason in + let t = ObjT (r, mk_objecttype ~flags dict id proto) in + if make_exact then ExactT (reason, t) else t + in + + let next cx trace reason {todo_rev; acc; make_exact} tout x = + Nel.iter (fun (r,_,_,{exact;_}) -> + if make_exact && not exact + then add_output cx ~trace (FlowError.EIncompatibleWithExact (r, reason)); + ) x; + match todo_rev with + | [] -> + let t = match spread reason (Nel.rev (x, acc)) with + | x,[] -> mk_object cx reason ~make_exact x + | x0,x1::xs -> + UnionT (reason, UnionRep.make + (mk_object cx reason ~make_exact x0) + (mk_object cx reason ~make_exact x1) + (List.map (mk_object cx reason ~make_exact) xs)) + in + rec_flow_t cx trace (t, tout) + | t::todo_rev -> + let tool = Resolve Next in + let state = {todo_rev; acc = x::acc; make_exact} in + rec_flow cx trace (t, ObjSpreadT (reason, tool, state, tout)) + in + + let resolved cx trace reason tool state tout x = + match tool with + | Next -> next cx trace reason state tout x + | List0 ((t, todo), join) -> + let tool = Resolve (List (todo, Nel.one x, join)) in + rec_flow cx trace (t, ObjSpreadT (reason, tool, state, tout)) + | List (todo, done_rev, join) -> + match todo with + | [] -> + let x = match join with + | Or -> Nel.cons x done_rev |> Nel.concat + | And -> merge (intersect2 reason) x done_rev + in + next cx trace reason state tout x + | t::todo -> + let done_rev = Nel.cons x done_rev in + let tool = Resolve (List (todo, done_rev, join)) in + rec_flow cx trace (t, ObjSpreadT (reason, tool, state, tout)) + in + + let object_slice cx r id dict flags = + let props = Context.find_props cx id in + let props = SMap.mapi (read_prop r flags) props in + let dict = Option.map dict (fun d -> { + dict_name = None; + key = d.key; + value = read_dict r d; + dict_polarity = Neutral; + }) in + (r, props, dict, flags) + in + + let interface_slice cx r id = + let flags = {frozen=false; exact=false; sealed=Sealed} in + let id, dict = + let props = Context.find_props cx id in + match SMap.get "$key" props, SMap.get "$value" props with + | Some (Field (key, polarity)), Some (Field (value, polarity')) + when polarity = polarity' -> + let props = props |> SMap.remove "$key" |> SMap.remove "$value" in + let id = Context.make_property_map cx props in + let dict = {dict_name = None; key; value; dict_polarity = polarity} in + id, Some dict + | _ -> id, None + in + object_slice cx r id dict flags + in + + let resolve cx trace reason state tout tool = function + | ObjT (r, {props_tmap; dict_t; flags; _}) -> + let x = Nel.one (object_slice cx r props_tmap dict_t flags) in + resolved cx trace reason tool state tout x + | InstanceT (r, _, super, _, {fields_tmap; _}) -> + let tool = Super (interface_slice cx r fields_tmap, tool) in + rec_flow cx trace (super, ObjSpreadT (reason, tool, state, tout)) + | UnionT (_, rep) -> + let t, todo = UnionRep.members_nel rep in + let tool = Resolve (List0 (todo, Or)) in + rec_flow cx trace (t, ObjSpreadT (reason, tool, state, tout)) + | IntersectionT (_, rep) -> + let t, todo = InterRep.members_nel rep in + let tool = Resolve (List0 (todo, And)) in + rec_flow cx trace (t, ObjSpreadT (reason, tool, state, tout)) + | AnyT _ | AnyObjT _ -> + rec_flow_t cx trace (AnyT.why reason, tout) + (* Other types have reasonable spread implementations, like FunT, which + would spread its statics. Since spread is currently limited to types, an + arbitrary subset of possible types are implemented. *) + | t -> + add_output cx ~trace (FlowError.EIncompatible + (t, ObjSpreadT (reason, Resolve tool, state, tout))) + in + + let super cx trace reason state tout acc tool = function + | InstanceT (r, _, super, _, {fields_tmap; _}) -> + let slice = interface_slice cx r fields_tmap in + let acc = intersect2 reason acc slice in + let tool = Super (acc, tool) in + rec_flow cx trace (super, ObjSpreadT (reason, tool, state, tout)) + | AnyT _ | AnyObjT _ -> + rec_flow_t cx trace (AnyT.why reason, tout) + | _ -> + next cx trace reason state tout (Nel.one acc) + in + + fun cx trace reason tool state tout l -> + match tool with + | Resolve tool -> resolve cx trace reason state tout tool l + | Super (acc, tool) -> super cx trace reason state tout acc tool l + (************* end of slab **************************************************) let intersect_members cx members = diff --git a/src/typing/gc_js.ml b/src/typing/gc_js.ml index d6291544c6f..03d215d9c2e 100644 --- a/src/typing/gc_js.ml +++ b/src/typing/gc_js.ml @@ -273,6 +273,9 @@ and gc_use cx state = function | SetElemT(_, i, t) -> gc cx state i; gc cx state t | SetPropT(_, _, t) -> gc cx state t | SpecializeT (_, _, _, ts, t) -> List.iter (gc cx state) ts; gc cx state t + | ObjSpreadT (_, tool, state', t) -> + gc_object_spread cx state tool state'; + gc cx state t | SubstOnPredT (_, _, t) -> gc cx state t | SuperT (_, instance) -> gc_insttype cx state instance | TestPropT(_, _, t) -> gc cx state t @@ -386,6 +389,34 @@ and gc_pred cx state = function and gc_pred_map cx state pred_map = Key_map.iter (fun _ p -> gc_pred cx state p) pred_map +and gc_object_spread = + let open ObjectSpread in + let gc_slice cx state (_, props, dict, _) = + SMap.iter (fun _ (t, _) -> gc cx state t) props; + Option.iter dict (gc_dicttype cx state) + in + let gc_resolved cx state xs = + Nel.iter (gc_slice cx state) xs + in + let gc_resolve cx state = function + | Next -> () + | List0 (todo, _) -> + Nel.iter (gc cx state) todo + | List (todo, acc, _) -> + List.iter (gc cx state) todo; + Nel.iter (gc_resolved cx state) acc + in + let gc_tool cx state = function + | Resolve tool -> gc_resolve cx state tool + | Super (slice, tool) -> + gc_slice cx state slice; + gc_resolve cx state tool + in + fun cx state tool {todo_rev; acc; make_exact=_} -> + gc_tool cx state tool; + List.iter (gc cx state) todo_rev; + List.iter (gc_resolved cx state) acc + and gc_cont cx state = function | Lower t -> gc cx state t | Upper u -> gc_use cx state u diff --git a/src/typing/graph.ml b/src/typing/graph.ml index ada189a4ac0..23ae055c14a 100644 --- a/src/typing/graph.ml +++ b/src/typing/graph.ml @@ -353,6 +353,7 @@ and parts_of_use_t cx = function | SetElemT (_, ix, t) -> ["ix", Def ix; "t", Def t] | SetPropT (_, _, t) -> ["t", Def t] | SpecializeT (_, _, _, args, out) -> ("out", Def out) :: list_parts args +| ObjSpreadT (_, _, _, out) -> ["out", Def out] | SubstOnPredT (_, _, t) -> ["t", Def t] | SuperT _ -> [] | TestPropT (_, _, out) -> ["out", Def out] diff --git a/src/typing/type.ml b/src/typing/type.ml index f05727cadd1..76d02e7e533 100644 --- a/src/typing/type.ml +++ b/src/typing/type.ml @@ -422,7 +422,8 @@ module rec TypeTerm : sig | ReactKitT of reason * React.tool - (* toolkit for making choices, contd. (appearing only as upper bounds) *) + | ObjSpreadT of reason * ObjectSpread.tool * ObjectSpread.state * t_out + | ChoiceKitUseT of reason * choice_use_tool (* tools for preprocessing intersections *) @@ -1181,6 +1182,7 @@ and UnionRep : sig (** members in declaration order *) val members: t -> TypeTerm.t list + val members_nel: t -> TypeTerm.t * TypeTerm.t Nel.t val cons: TypeTerm.t -> t -> t @@ -1264,6 +1266,7 @@ end = struct | Some (base, _) -> Some base let members (t0, t1, ts, _) = t0::t1::ts + let members_nel (t0, t1, ts, _) = t0, (t1, ts) let cons t0 (t1, t2, ts, _) = make t0 t1 (t2::ts) @@ -1314,6 +1317,7 @@ and InterRep : sig (** member list in declaration order *) val members: t -> TypeTerm.t list + val members_nel: t -> TypeTerm.t * TypeTerm.t Nel.t (** map rep r to rep r' along type mapping f. drops history *) val map: (TypeTerm.t -> TypeTerm.t) -> t -> t @@ -1334,6 +1338,7 @@ end = struct let make t0 t1 ts = (t0, t1, ts) let members (t0, t1, ts) = t0::t1::ts + let members_nel (t0, t1, ts) = t0, (t1, ts) let map f (t0, t1, ts) = make (f t0) (f t1) (List.map f ts) @@ -1474,6 +1479,39 @@ and React : sig | CreateClass of CreateClass.tool * CreateClass.knot * TypeTerm.t_out end = React +and ObjectSpread : sig + type tool = + (* Each part of a spread must be resolved in order to compute the result *) + | Resolve of resolve + (* In order to resolve an InstanceT, all supers must also be resolved to + collect class properties, which are own. *) + | Super of slice * resolve + + and resolve = + | Next + (* Resolve each element of a union or intersection *) + | List0 of TypeTerm.t Nel.t * join + | List of TypeTerm.t list * resolved Nel.t * join + + and join = And | Or + + and state = { + todo_rev: TypeTerm.t list; + acc: resolved list; + make_exact: bool; + } + + (* A union type resolves to a resolved spread with more than one element *) + and resolved = slice Nel.t + + and slice = reason * props * dict * TypeTerm.flags + + and props = prop SMap.t + and prop = TypeTerm.t * bool (* own *) + + and dict = TypeTerm.dicttype option +end = ObjectSpread + include TypeTerm (*********************************************************) @@ -1616,6 +1654,7 @@ let any_propagating_use_t = function | SpecializeT _ | ThisSpecializeT _ | UseT (_, ClassT _) (* mk_instance ~for_type:false *) + | ObjSpreadT _ -> true (* These types have no t_out, so can't propagate anything *) @@ -1810,6 +1849,7 @@ and reason_of_use_t = function | SetElemT (reason,_,_) -> reason | SetPropT (reason,_,_) -> reason | SpecializeT(reason,_,_,_,_) -> reason + | ObjSpreadT (reason, _, _, _) -> reason | SubstOnPredT (reason, _, _) -> reason | SuperT (reason,_) -> reason | TestPropT (reason, _, _) -> reason @@ -1977,6 +2017,7 @@ and mod_reason_of_use_t f = function | SetPropT (reason, n, t) -> SetPropT (f reason, n, t) | SpecializeT(reason_op, reason_tapp, cache, ts, t) -> SpecializeT (f reason_op, reason_tapp, cache, ts, t) + | ObjSpreadT (reason, tool, state, k) -> ObjSpreadT (f reason, tool, state, k) | SubstOnPredT (reason, subst, t) -> SubstOnPredT (f reason, subst, t) | SuperT (reason, inst) -> SuperT (f reason, inst) | TestPropT (reason, n, t) -> TestPropT (f reason, n, t) @@ -2189,6 +2230,7 @@ let string_of_use_ctor = function | SetElemT _ -> "SetElemT" | SetPropT _ -> "SetPropT" | SpecializeT _ -> "SpecializeT" + | ObjSpreadT _ -> "ObjSpreadT" | SubstOnPredT _ -> "SubstOnPredT" | SuperT _ -> "SuperT" | TestPropT _ -> "TestPropT" diff --git a/src/typing/type_annotation.ml b/src/typing/type_annotation.ml index e7fa2320194..ee26adbe322 100644 --- a/src/typing/type_annotation.ml +++ b/src/typing/type_annotation.ml @@ -556,6 +556,41 @@ let rec convert cx tparams_map = Ast.Type.(function if (tparams = []) then ft else PolyT(tparams, ft) | loc, Object { Object.exact; properties } -> + let mk_object (call_props, dict, props_map) = + let props_map = match List.rev call_props with + | [] -> props_map + | [t] -> + let p = Field (t, Positive) in + SMap.add "$call" p props_map + | t0::t1::ts -> + let callable_reason = mk_reason (RCustom "callable object type") loc in + let rep = InterRep.make t0 t1 ts in + let t = IntersectionT (callable_reason, rep) in + let p = Field (t, Positive) in + SMap.add "$call" p props_map + in + (* Use the same reason for proto and the ObjT so we can walk the proto chain + and use the root proto reason to build an error. *) + let reason_desc = RObjectType in + let pmap = Context.make_property_map cx props_map in + let callable = List.exists (function + | Object.CallProperty (_, { Object.CallProperty.static; _ }) -> not static + | _ -> false + ) properties in + let proto = if callable + then FunProtoT (locationless_reason reason_desc) + else ObjProtoT (locationless_reason reason_desc) in + let flags = { + sealed = Sealed; + exact; + frozen = false; + } in + let t = ObjT (mk_reason reason_desc loc, + Flow_js.mk_objecttype ~flags dict pmap proto) in + if exact + then ExactT (mk_reason (RExactType reason_desc) loc, t) + else t + in let property loc prop props = match prop with | { Object.Property. @@ -600,65 +635,62 @@ let rec convert cx tparams_map = Ast.Type.(function Flow_js.add_output cx Flow_error.(EUnsupportedSyntax (loc, ObjectPropertyGetSet)); props - in - let call_props, dict, props_map = List.fold_left ( - fun (calls, dict, props) -> function - | Object.CallProperty (loc, { Object.CallProperty.value = (_, ft); _ }) -> - let t = convert cx tparams_map (loc, Ast.Type.Function ft) in - t::calls, dict, props - | Object.Indexer (loc, _) when dict <> None -> + let add_call c = function + | None -> Some ([c], None, SMap.empty) + | Some (cs, d, pmap) -> Some (c::cs, d, pmap) + in + let make_dict { Object.Indexer.id; key; value; variance; _ } = + Some { Type. + dict_name = Option.map id snd; + key = convert cx tparams_map key; + value = convert cx tparams_map value; + dict_polarity = polarity variance; + } + in + let add_dict loc indexer = function + | None -> Some ([], make_dict indexer, SMap.empty) + | Some (cs, None, pmap) -> Some (cs, make_dict indexer, pmap) + | Some (_, Some _, _) as o -> Flow_js.add_output cx FlowError.(EUnsupportedSyntax (loc, MultipleIndexers)); - calls, dict, props - | Object.Indexer (_, { Object.Indexer.id; key; value; variance; _ }) -> - let dict = Some { Type. - dict_name = Option.map id snd; - key = convert cx tparams_map key; - value = convert cx tparams_map value; - dict_polarity = polarity variance; - } in - calls, dict, props - | Object.Property (loc, prop) -> - calls, dict, property loc prop props - | Object.SpreadProperty (loc, _) -> - Flow_js.add_output cx - FlowError.(EUnsupportedSyntax (loc, ObjectTypeSpread)); - calls, dict, props - ) ([], None, SMap.empty) properties in - let props_map = match List.rev call_props with - | [] -> props_map - | [t] -> - let p = Field (t, Positive) in - SMap.add "$call" p props_map - | t0::t1::ts -> - let callable_reason = mk_reason (RCustom "callable object type") loc in - let rep = InterRep.make t0 t1 ts in - let t = IntersectionT (callable_reason, rep) in - let p = Field (t, Positive) in - SMap.add "$call" p props_map + o + in + let add_prop loc p = function + | None -> Some ([], None, property loc p SMap.empty) + | Some (cs, d, pmap) -> Some (cs, d, property loc p pmap) + in + let o, ts, spread = List.fold_left ( + fun (o, ts, spread) -> function + | Object.CallProperty (loc, { Object.CallProperty.value = (_, ft); _ }) -> + let t = convert cx tparams_map (loc, Ast.Type.Function ft) in + add_call t o, ts, spread + | Object.Indexer (loc, i) -> + add_dict loc i o, ts, spread + | Object.Property (loc, p) -> + add_prop loc p o, ts, spread + | Object.SpreadProperty (_, { Object.SpreadProperty.argument }) -> + let ts = match o with + | None -> ts + | Some o -> (mk_object o)::ts + in + let o = convert cx tparams_map argument in + None, o::ts, true + ) (None, [], false) properties in + let ts = match o with + | None -> ts + | Some o -> mk_object o::ts in - (* Use the same reason for proto and the ObjT so we can walk the proto chain - and use the root proto reason to build an error. *) - let reason_desc = RObjectType in - let pmap = Context.make_property_map cx props_map in - let callable = List.exists (function - | Object.CallProperty (_, { Object.CallProperty.static; _ }) -> not static - | _ -> false - ) properties in - let proto = if callable - then FunProtoT (locationless_reason reason_desc) - else ObjProtoT (locationless_reason reason_desc) in - let flags = { - sealed = Sealed; - exact; - frozen = false; - } in - let t = ObjT (mk_reason reason_desc loc, - Flow_js.mk_objecttype ~flags dict pmap proto) in - if exact - then ExactT (mk_reason (RExactType reason_desc) loc, t) - else t + (match ts with + | [] -> mk_object ([], None, SMap.empty) + | [t] when not spread -> t + | t::todo_rev -> + let open ObjectSpread in + let reason = mk_reason RObjectType loc in + let tool = Resolve Next in + let state = { todo_rev; acc = []; make_exact = exact } in + AnnotT (Flow_js.mk_tvar_where cx reason (fun tout -> + Flow_js.flow cx (t, ObjSpreadT (reason, tool, state, tout))))) | loc, Exists -> (* Do not evaluate existential type variables when map is non-empty. This diff --git a/src/typing/type_visitor.ml b/src/typing/type_visitor.ml index e7dc50c9ecd..9a2009640cf 100644 --- a/src/typing/type_visitor.ml +++ b/src/typing/type_visitor.ml @@ -274,6 +274,7 @@ class ['a] t = object(self) | SetElemT (_, _, _) | SetPropT (_, _, _) | SpecializeT (_,_, _, _, _) + | ObjSpreadT _ | SubstOnPredT _ | SuperT (_, _) | TestPropT (_, _, _) diff --git a/tests/new_spread/.flowconfig b/tests/new_spread/.flowconfig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/new_spread/new_spread.exp b/tests/new_spread/new_spread.exp new file mode 100644 index 00000000000..255a199d84b --- /dev/null +++ b/tests/new_spread/new_spread.exp @@ -0,0 +1,179 @@ +type.js:13 + 13: (o1: {p:T}); // error: o1.p is optional + ^^ object type. This type is incompatible with + 13: (o1: {p:T}); // error: o1.p is optional + ^^^^^ object type + Property `p` is incompatible: + 10: type O1 = {...{p:T}}; + ^ undefined. This type is incompatible with + 13: (o1: {p:T}); // error: o1.p is optional + ^ T + +type.js:16 + 16: ({p:y}: O1); // error: y ~> T + ^^^^^ object literal. This type is incompatible with + 16: ({p:y}: O1); // error: y ~> T + ^^ object type + Property `p` is incompatible: + 16: ({p:y}: O1); // error: y ~> T + ^ U. This type is incompatible with + 10: type O1 = {...{p:T}}; + ^ T + +type.js:23 + 23: (o2: {p:T}); // error: o2.p is optional + ^^ object type. This type is incompatible with + 23: (o2: {p:T}); // error: o2.p is optional + ^^^^^ object type + Property `p` is incompatible: + 20: type O2 = {...{p?:T}}; + ^ undefined. This type is incompatible with + 23: (o2: {p:T}); // error: o2.p is optional + ^ T + +type.js:26 + 26: ({p:y}: O2); // error: y ~> T + ^^^^^ object literal. This type is incompatible with + 26: ({p:y}: O2); // error: y ~> T + ^^ object type + Property `p` is incompatible: + 26: ({p:y}: O2); // error: y ~> T + ^ U. This type is incompatible with + 20: type O2 = {...{p?:T}}; + ^ T + +type.js:30 + 30: type O3 = {|...{p:T}|}; // error: spread result is not exact + ^^^^^ object type. Inexact type is incompatible with exact type + 30: type O3 = {|...{p:T}|}; // error: spread result is not exact + ^^^^^^^^^^^^ object type + +type.js:37 + 37: ({}: O4); // error: property `p` not found + ^^ property `p`. Property not found in + 37: ({}: O4); // error: property `p` not found + ^^ object literal + +type.js:39 + 39: ({p:y}: O4); // error: y ~> T + ^^^^^ object literal. This type is incompatible with + 39: ({p:y}: O4); // error: y ~> T + ^^ object type + Property `p` is incompatible: + 39: ({p:y}: O4); // error: y ~> T + ^ U. This type is incompatible with + 33: type O4 = {...{|p:T|}}; + ^ T + +type.js:47 + 47: (nil: O5); // error: property `p` not found + ^^ property `p`. Property not found in + 7: declare var nil: {||}; + ^^^^ object type + +type.js:49 + 49: ({p:y}: O5); // error: y ~> T + ^^^^^ object literal. This type is incompatible with + 49: ({p:y}: O5); // error: y ~> T + ^^ object type + Property `p` is incompatible: + 49: ({p:y}: O5); // error: y ~> T + ^ U. This type is incompatible with + 43: type O5 = {|...{|p:T|}|}; + ^ T + +type.js:50 + 50: ({p:x,q:y}: O5); // error: additional property `q` found + ^^^^^^^^^ property `q`. Property not found in + 50: ({p:x,q:y}: O5); // error: additional property `q` found + ^^ object type + +type.js:56 + 56: ({}: O6); // error: property `p` not found + ^^ property `p`. Property not found in + 56: ({}: O6); // error: property `p` not found + ^^ object literal + +type.js:57 + 57: ({p:x}: O6); // error: x ~> U + ^^^^^ object literal. This type is incompatible with + 57: ({p:x}: O6); // error: x ~> U + ^^ object type + Property `p` is incompatible: + 57: ({p:x}: O6); // error: x ~> U + ^ T. This type is incompatible with + 53: type O6 = {...{p:T},...{|p:U|}}; + ^ U + +type.js:62 + 62: type O7 = {|...{p:T},...{|p:U|}|}; // error: spread result is not exact + ^^^^^ object type. Inexact type is incompatible with exact type + 62: type O7 = {|...{p:T},...{|p:U|}|}; // error: spread result is not exact + ^^^^^^^^^^^^^^^^^^^^^^^ object type + +type.js:68 + 68: (o8.p: T); // error: U ~> T + ^^^^ U. This type is incompatible with + 68: (o8.p: T); // error: U ~> T + ^ T + +type.js:69 + 69: (o8.p: U); // error: T ~> U + ^^^^ T. This type is incompatible with + 69: (o8.p: U); // error: T ~> U + ^ U + +type.js:75 + 75: (o9.p: T); // error: o9.p is optional + ^^^^ undefined. This type is incompatible with + 75: (o9.p: T); // error: o9.p is optional + ^ T + +type_contra.js:6 + 6: (o1: {p?:T}); // error: unknown ~> T + ^^ object type. This type is incompatible with + 6: (o1: {p?:T}); // error: unknown ~> T + ^^^^^^ object type + Property `p` is incompatible: + 3: type O1 = {...{-p:T}}; + ^^^^^^ property `p` of unknown type. This type is incompatible with + 6: (o1: {p?:T}); // error: unknown ~> T + ^ T + +type_contra.js:7 + 7: (o1.p: T); // errors: undefined ~> T, unknown ~> T + ^^^^ property `p` of unknown type. This type is incompatible with + 7: (o1.p: T); // errors: undefined ~> T, unknown ~> T + ^ T + +type_contra.js:7 + 7: (o1.p: T); // errors: undefined ~> T, unknown ~> T + ^^^^ undefined. This type is incompatible with + 7: (o1.p: T); // errors: undefined ~> T, unknown ~> T + ^ T + +type_contra.js:9 + 9: type O2 = {...{-[string]:T}}; + ^^^^^^^^^^^^^ computed property of unknown type. This type is incompatible with + 12: (o2: {[string]:T}); // error: unknown ~> T + ^ T + +type_contra.js:13 + 13: (o2.p: T); // errors: unknown ~> T + ^^^^ computed property of unknown type. This type is incompatible with + 13: (o2.p: T); // errors: unknown ~> T + ^ T + +type_intersection.js:15 + 15: ({p: new A}: O2); // error: A ~> B + ^^^^^^^^^^ object literal. This type is incompatible with + 15: ({p: new A}: O2); // error: A ~> B + ^^ object type + Property `p` is incompatible: + 15: ({p: new A}: O2); // error: A ~> B + ^^^^^ A. This type is incompatible with + 11: type O2 = {...{p:A}&{p:B}}; + ^ B + + +Found 22 errors diff --git a/tests/new_spread/type.js b/tests/new_spread/type.js new file mode 100644 index 00000000000..2aeee01b4b7 --- /dev/null +++ b/tests/new_spread/type.js @@ -0,0 +1,91 @@ +declare class T {} +declare var x: T; + +declare class U {} +declare var y: U; + +declare var nil: {||}; + +// inexact: `p` may be non-own +type O1 = {...{p:T}}; +declare var o1: O1; +(o1: {p?:T}); // ok +(o1: {p:T}); // error: o1.p is optional +({}: O1); // ok +({p:x}: O1); // ok +({p:y}: O1); // error: y ~> T +({p:x,q:y}: O1); // ok + +// inexact: optional `p`, if own, must be `T` +type O2 = {...{p?:T}}; +declare var o2: O2; +(o2: {p?:T}); // ok +(o2: {p:T}); // error: o2.p is optional +({}: O2); // ok +({p:x}: O2); // ok +({p:y}: O2); // error: y ~> T +({p:x,q:y}: O2); // ok + +// can't make exact from inexact +type O3 = {|...{p:T}|}; // error: spread result is not exact + +// exact +type O4 = {...{|p:T|}}; +declare var o4: O4; +(o4: {p:T}); // ok +(o4: {|p:T|}); // error: not exact +({}: O4); // error: property `p` not found +({p:x}: O4); // ok +({p:y}: O4); // error: y ~> T +({p:x,q:y}: O4); // ok + +// can make exact from exact +type O5 = {|...{|p:T|}|}; +declare var o5: O5; +(o5: {p:T}); // ok +(o5: {|p:T|}); // ok +(nil: O5); // error: property `p` not found +({p:x}: O5); // ok +({p:y}: O5); // error: y ~> T +({p:x,q:y}: O5); // error: additional property `q` found + +// inexact p + exact p +type O6 = {...{p:T},...{|p:U|}}; +declare var o6: O6; +(o6: {p:U}); // ok +({}: O6); // error: property `p` not found +({p:x}: O6); // error: x ~> U +({p:y}: O6); // ok +({p:y,q:x}: O6); // ok + +// inexact p + exact p ~> exact +type O7 = {|...{p:T},...{|p:U|}|}; // error: spread result is not exact + +// exact p + inexact p +type O8 = {...{|p:T|},...{p:U}}; +declare var o8: O8; +(o8: {p:T|U}); // ok +(o8.p: T); // error: U ~> T +(o8.p: U); // error: T ~> U + +// inexact p + exact q +type O9 = {...{p:T},...{|q:U|}}; +declare var o9: O9; +(o9: {p?:T,q:U}); +(o9.p: T); // error: o9.p is optional +(o9.q: U); // ok + +// exact p + inexact q +type O10 = {...{|p:T|},...{q:U}}; +declare var o10: O10; +(o10: {p:mixed, q?: U}); // ok + +// inexact p + inexact q +type O11 = {...{p:T},...{q:U}}; +declare var o11: O11; +(o11: {p:mixed, q: mixed}); // ok + +// exact + exact +type O12 = {...{|p:T|},...{|q:U|}}; +declare var o12: O12; +(o12: {p:T,q:U}); // ok diff --git a/tests/new_spread/type_any.js b/tests/new_spread/type_any.js new file mode 100644 index 00000000000..4ec21b722d1 --- /dev/null +++ b/tests/new_spread/type_any.js @@ -0,0 +1,13 @@ +type O1 = {...any}; +var o1: O1 = (0: mixed); // ok +(o1: empty); // ok + +type O2 = {...Object}; +var o2: O2 = (0: mixed); // ok +(o2: empty); // ok + +declare var Base: any; +declare class Derived extends Base {} +type O3 = {...Derived}; +var o3: O3 = (0: mixed); // ok +(o3: empty) // ok diff --git a/tests/new_spread/type_contra.js b/tests/new_spread/type_contra.js new file mode 100644 index 00000000000..b6ffd4b6640 --- /dev/null +++ b/tests/new_spread/type_contra.js @@ -0,0 +1,13 @@ +declare class T {}; + +type O1 = {...{-p:T}}; +declare var o1: O1; +(o1: {p?:mixed}); // ok +(o1: {p?:T}); // error: unknown ~> T +(o1.p: T); // errors: undefined ~> T, unknown ~> T + +type O2 = {...{-[string]:T}}; +declare var o2: O2; +(o2: {[string]:mixed}); // ok +(o2: {[string]:T}); // error: unknown ~> T +(o2.p: T); // errors: unknown ~> T diff --git a/tests/new_spread/type_dict.js b/tests/new_spread/type_dict.js new file mode 100644 index 00000000000..3ecd63e8ba6 --- /dev/null +++ b/tests/new_spread/type_dict.js @@ -0,0 +1,38 @@ +declare class T {} +declare class U {} + +declare var o1: {...{[string]:T},...{p:U}}; +(o1: {p?:T|U,[string]:T}); // ok + +declare var o2: {...{p:T},...{[string]:U}}; +(o2: {p?:T|U,[string]:U}); // ok + +declare var o3: {...{[string]:T},...{[string]:U}}; +(o3: {[string]:T|U}); // ok + +declare var o4: {...{|[string]:T|},...{p:U}}; +(o4: {p?:T|U,[string]:T}); // ok + +declare var o5: {...{|p:T|},...{[string]:U}}; +(o5: {p:T|U,[string]:U}); // ok + +declare var o6: {...{|[string]:T|},...{[string]:U}}; +(o6: {[string]:T|U}); // ok + +declare var o7: {...{[string]:T},...{|p:U|}}; +(o7: {p:U,[string]:T}); // ok + +declare var o8: {...{p:T},...{|[string]:U|}}; +(o8: {p?:T|U,[string]:U}); // ok + +declare var o9: {...{[string]:T},...{|[string]:U|}}; +(o9: {[string]:T|U}); // ok + +declare var o10: {...{|[string]:T|},...{|p:U|}}; +(o10: {|p:U,[string]:T|}); // ok + +declare var o11: {...{|p :T|},...{|[string]:U|}}; +(o11: {|p:T|U,[string]:U|}); // ok + +declare var o12: {...{|[string]:T|},...{|[string]:U|}}; +(o12: {|[string]:T|U|}); // ok diff --git a/tests/new_spread/type_instance.js b/tests/new_spread/type_instance.js new file mode 100644 index 00000000000..dd8d4f37d1b --- /dev/null +++ b/tests/new_spread/type_instance.js @@ -0,0 +1,11 @@ +class A {+p: string|number} +class B extends A {p: number} + +type O1 = {...B}; +declare var o1: O1; +(o1: {p?:number}); // ok + +declare class C {[string]:number} +type O2 = {...C}; +declare var o2: O2; +(o2: {[string]:number}); // ok diff --git a/tests/new_spread/type_intersection.js b/tests/new_spread/type_intersection.js new file mode 100644 index 00000000000..1895418e90c --- /dev/null +++ b/tests/new_spread/type_intersection.js @@ -0,0 +1,28 @@ +declare class T {} +declare class U {} + +declare class A {} +declare class B extends A {} + +type O1 = {...{p:T}&{q:U}}; +declare var o1: O1; +(o1: {p?:T,q?:U}); // ok + +type O2 = {...{p:A}&{p:B}}; +declare var o2: O2; +(o2: {p?:B}); // ok +({p: new B}: O2); // ok +({p: new A}: O2); // error: A ~> B + +type O3 = {...{p:A}&{[string]:B}}; +declare var o3: O3; +(o3: {p:B,[string]:B});// ok: A&B = B +(o3.q: B); // ok + +type O4 = {...{[string]:A}&{p:B}}; +declare var o4: O4; +(o4: {p:B,[string]:A}); // ok: A&B = B + +type O5 = {...{[string]:A}&{[string]:B}}; +declare var o5: O5; +(o5: {[string]:B}); // ok: A&B = B diff --git a/tests/new_spread/type_intersection_optional.js b/tests/new_spread/type_intersection_optional.js new file mode 100644 index 00000000000..1e1092034cc --- /dev/null +++ b/tests/new_spread/type_intersection_optional.js @@ -0,0 +1,65 @@ +declare class T {} +declare class U {} + +declare var o1: {...{p:T}&{p:U}}; +(o1: {p?:T&U}); // ok + +declare var o2: {...{p?:T}&{p:U}}; +(o2: {p?:T&U}); // ok + +declare var o3: {...{p:T}&{p?:U}}; +(o3: {p?:T&U}); // ok + +declare var o4: {...{p?:T}&{p?:U}}; +(o4: {p?:T&U}); // ok + +declare var o5: {...{|p:T|}&{p:U}}; +(o5: {p:T&U}); // ok + +declare var o6: {...{|p?:T|}&{p:U}}; +(o6: {p:T&U}); // ok + +declare var o7: {...{|p:T|}&{p?:U}}; +(o7: {p:T&U}); // ok + +declare var o8: {...{|p?:T|}&{p?:U}}; +(o8: {p?:T&U}); // ok + +declare var o9: {...{p:T}&{|p:U|}}; +(o9: {p:T&U}); // ok + +declare var o10: {...{p?:T}&{|p:U|}}; +(o10: {p:T&U}); // ok + +declare var o11: {...{p:T}&{|p?:U|}}; +(o11: {p:T&U}); // ok + +declare var o12: {...{p?:T}&{|p?:U|}}; +(o12: {p?:T&U}); // ok + +declare var o13: {...{|p:T|}&{|p:U|}}; +(o13: {|p:T&U|}); // ok + +declare var o14: {...{|p?:T|}&{|p:U|}}; +(o14: {|p:T&U|}); // ok + +declare var o15: {...{|p:T|}&{|p?:U|}}; +(o15: {|p:T&U|}); // ok + +declare var o16: {...{|p?:T|}&{|p?:U|}}; +(o16: {|p?:T&U|}); // ok + +declare var o17: {...{p:T}&{q:U}}; +(o17: {p?:T,q?:U}); // ok + +declare var o18: {...{p?:T}&{q:U}}; +(o18: {p?:T,q?:U}); // ok + +declare var o19: {...{p:T}&{q?:U}}; +(o19: {p?:T,q?:U}); // ok + +declare var o20: {...{p?:T}&{q?:U}}; +(o20: {p?:T,q?:U}); // ok + +declare var o21: {...{|p:T|}&{q:U}}; +(o21: {p:T,q?:U}); // ok diff --git a/tests/new_spread/type_optional.js b/tests/new_spread/type_optional.js new file mode 100644 index 00000000000..5fa828e3f2f --- /dev/null +++ b/tests/new_spread/type_optional.js @@ -0,0 +1,22 @@ +declare class T {} +declare class U {} + +declare var a: {...{ p :T },...{ p :U }}; (a: { p?:T|U }); +declare var b: {...{ p?:T },...{ p :U }}; (b: { p?:T|U }); +declare var c: {...{ p :T },...{ p?:U }}; (c: { p?:T|U }); +declare var d: {...{ p?:T },...{ p?:U }}; (d: { p?:T|U }); + +declare var e: {...{|p :T|},...{ p :U }}; (e: { p :T|U }); +declare var f: {...{|p?:T|},...{ p :U }}; (f: { p?:T|U }); +declare var g: {...{|p :T|},...{ p?:U }}; (g: { p :T|U }); +declare var h: {...{|p?:T|},...{ p?:U }}; (h: { p?:T|U }); + +declare var i: {...{ p :T },...{|p :U|}}; (i: { p : U }); +declare var j: {...{ p?:T },...{|p :U|}}; (j: { p : U }); +declare var k: {...{ p :T },...{|p?:U|}}; (k: { p?:T|U }); +declare var l: {...{ p?:T },...{|p?:U|}}; (l: { p?:T|U }); + +declare var m: {...{|p :T|},...{|p :U|}}; (m: {|p : U|}); +declare var n: {...{|p?:T|},...{|p :U|}}; (n: {|p : U|}); +declare var o: {...{|p :T|},...{|p?:U|}}; (o: {|p :T|U|}); +declare var p: {...{|p?:T|},...{|p?:U|}}; (p: {|p?:T|U|}); diff --git a/tests/new_spread/type_union.js b/tests/new_spread/type_union.js new file mode 100644 index 00000000000..21a8619e582 --- /dev/null +++ b/tests/new_spread/type_union.js @@ -0,0 +1,9 @@ +declare class T {} +declare var x: T; + +declare class U {} +declare var y: U; + +type O1 = {...{p:T}|{q:U}}; +declare var o1: O1; +(o1: {p?:T}|{q?:U}); // ok