Skip to content

Commit

Permalink
object type spread
Browse files Browse the repository at this point in the history
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
  • Loading branch information
samwgoldman authored and facebook-github-bot committed Mar 6, 2017
1 parent 160dde1 commit ad443dc
Show file tree
Hide file tree
Showing 23 changed files with 1,010 additions and 62 deletions.
13 changes: 13 additions & 0 deletions src/common/utils/nel.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/parser/estree_translator.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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;
|]
)

Expand Down
2 changes: 1 addition & 1 deletion src/parser/spider_monkey_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/parser/type_parser.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
54 changes: 54 additions & 0 deletions src/typing/debug_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 1 addition & 3 deletions src/typing/flow_error.ml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ and unsupported_syntax =
| PredicateInvalidBody
| PredicateVoidReturn
| MultipleIndexers
| ObjectTypeSpread (* TODO *)
| SpreadArgument

(* decide reason order based on UB's flavor and blamability *)
Expand Down Expand Up @@ -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 *)
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit ad443dc

Please sign in to comment.