Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions analysis/reanalyze/src/arnold.ml
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,8 @@ module Compile = struct
|> List.map (function Typedtree.Tdict_entry (_, e) | Tdict_spread e ->
expression ~ctx e)
|> Command.unordered_sequence
| Texp_spread (_, parts) ->
parts |> List.map (expression ~ctx) |> Command.unordered_sequence
| Texp_assert _ -> Command.nothing
| Texp_try (e, cases, finally) -> (
let c_e = e |> expression ~ctx in
Expand Down
2 changes: 2 additions & 0 deletions analysis/reanalyze/src/side_effects.ml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ let rec expr_no_side_effects (expr : Typedtree.expression) =
| Typedtree.Tdict_entry (_, e) -> e |> expr_no_side_effects
(* Spreading a dict reads its properties *)
| Tdict_spread _ -> false)
(* A call of the runtime's [spread] *)
| Texp_spread _ -> false
| Texp_ifthenelse (e1, e2, eo) ->
e1 |> expr_no_side_effects && e2 |> expr_no_side_effects
&& eo |> expr_opt_no_side_effects
Expand Down
12 changes: 12 additions & 0 deletions analysis/src/completion_front_end.ml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@ let rec expr_to_context_path_inner ~(in_jsx_context : bool)
| [] -> None
| exp :: _ -> expr_to_context_path ~in_jsx_context exp))
| Pexp_dict _ -> Some (CPDict None)
| Pexp_spread (kind, _) ->
(* Typed like the runtime call it compiles to, as when the parser
represented it as [Pexp_apply] *)
Some
(CPApply
( CPId
{
path = [Ast_spread.module_name kind; "spread"];
completion_context = Value;
loc = e.pexp_loc;
},
[Nolabel] ))
| Pexp_ident {txt = Lident "->"} -> None
| Pexp_ident {txt; loc} ->
Some
Expand Down
17 changes: 17 additions & 0 deletions analysis/src/dump_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,23 @@ and print_expr_item expr ~pos ~indentation =
^ "\n"
^ add_indentation indentation
^ ")"
| Pexp_spread (kind, elements) ->
(match kind with
| Spread_array -> "Pexp_spread(array,\n"
| Spread_list -> "Pexp_spread(list,\n")
^ (elements
|> List.map (fun element ->
add_indentation (indentation + 1)
^
match element with
| Parsetree.Pspread_item expr ->
print_expr_item expr ~pos ~indentation:(indentation + 1)
| Pspread expr ->
"..." ^ print_expr_item expr ~pos ~indentation:(indentation + 1))
|> String.concat "\n")
^ "\n"
^ add_indentation indentation
^ ")"
| Pexp_match (match_expr, cases) ->
"Pexp_match("
^ print_expr_item match_expr ~pos ~indentation:0
Expand Down
7 changes: 4 additions & 3 deletions analysis/src/hint.ml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ let inlay ~source ~kind_file ~pos ~max_length ~full ~state ~debug =
match expr.pexp_desc with
| Pexp_constant _ | Pexp_tuple _ | Pexp_record _ | Pexp_variant _
| Pexp_apply _ | Pexp_match _ | Pexp_construct _ | Pexp_ifthenelse _
| Pexp_ternary _ | Pexp_array _ | Pexp_dict _ | Pexp_ident _ | Pexp_try _
| Pexp_object_get _ | Pexp_object_set _ | Pexp_field _ | Pexp_open _
| Pexp_fun _ | Pexp_template _ | Pexp_tagged_template _ ->
| Pexp_ternary _ | Pexp_array _ | Pexp_dict _ | Pexp_spread _
| Pexp_ident _ | Pexp_try _ | Pexp_object_get _ | Pexp_object_set _
| Pexp_field _ | Pexp_open _ | Pexp_fun _ | Pexp_template _
| Pexp_tagged_template _ ->
push vb.pvb_pat.ppat_loc Type
| _ -> ())
| {pvb_pat = {ppat_desc = Ppat_tuple _}} -> process_pattern vb.pvb_pat
Expand Down
8 changes: 0 additions & 8 deletions analysis/src/semantic_tokens.ml
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,6 @@ let command ~debug ~emitter ~source ~kind_file =
(* Don't emit semantic tokens for identifiers not present in source code *)
let should_emit =
match lid with
(* Collection spreads (`...`) are converted to primitive spread calls
with an `@res.spread` decorator. *)
| Ldot (Lident ("Primitive_array" | "Primitive_list"), "spread") ->
let has_spread_attr =
e.pexp_attributes
|> List.exists (fun ({Location.txt}, _) -> txt = "res.spread")
in
not has_spread_attr
(* Array access (`arr[index]`) is converted to `Array.get` *)
| Ldot (Lident "Array", "get") -> false
(* Array mutation (`arr[index]`) is converted to `Array.set` *)
Expand Down
1 change: 1 addition & 0 deletions analysis/src/utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ let identify_pexp pexp =
| Pexp_jsx_element _ -> "Pexp_jsx_element"
| Pexp_regexp _ -> "Pexp_regexp"
| Pexp_dict _ -> "Pexp_dict"
| Pexp_spread _ -> "Pexp_spread"
| Pexp_template _ -> "Pexp_template"
| Pexp_tagged_template _ -> "Pexp_tagged_template"

Expand Down
1 change: 1 addition & 0 deletions changes/operand-line-comments.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Keep a line comment after an item or argument in the operand of `switch`, `try`, `if`, `while` or a loop next to that item when formatting, instead of moving it to the end of the operand.
1 change: 1 addition & 0 deletions changes/spread-literal-errors.polish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Report a spread of the wrong collection type, such as a list spread into an array, as a mismatch of the spread instead of an array item.
1 change: 1 addition & 0 deletions changes/spread-literal-formatting.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix the formatter dropping spreads of array and list literals, attributes on literals with spreads, and comments in list literals with spreads.
1 change: 1 addition & 0 deletions changes/spread-literal-node.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Represent array and list literals with spreads with a dedicated parsetree node.
3 changes: 2 additions & 1 deletion compiler/core/lam_analysis.ml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ let rec no_side_effects (lam : Lambda.t) : bool =
(* A spread reads the source's properties, which may run getters or
proxy traps *)
| Pdict_spread | Pjs_object_get _ | Pjs_object_set _ | Pdebugger
| Pjs_fn_method
(* Runtime calls, like the [Lapply] they were parsed into *)
| Parray_spread | Plist_spread | Pjs_fn_method
(* Await promise *)
| Pawait
(* TODO *)
Expand Down
2 changes: 2 additions & 0 deletions compiler/core/lam_compile_primitive.ml
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,8 @@ let translate output_prefix loc (cxt : Lam_compile_context.t)
Some (Js_op.Lit txt, expr)
| _ -> None))
| _ -> assert false)
| Parray_spread -> E.runtime_call Primitive_modules.array "spread" args
| Plist_spread -> E.runtime_call Primitive_modules.list "spread" args
| Pdict_spread ->
(* {...target, ...source}, with the rows of dict literals in place *)
E.obj_entries
Expand Down
1 change: 1 addition & 0 deletions compiler/ml/ast_helper.ml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ module Exp = struct
let setfield ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_setfield (a, b, c))
let array ?loc ?attrs a = mk ?loc ?attrs (Pexp_array a)
let dict ?loc ?attrs a = mk ?loc ?attrs (Pexp_dict a)
let spread ?loc ?attrs kind a = mk ?loc ?attrs (Pexp_spread (kind, a))
let ifthenelse ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_ifthenelse (a, b, c))
let ternary ?loc ?attrs condition consequent alternate =
mk ?loc ?attrs (Pexp_ternary (condition, consequent, alternate))
Expand Down
2 changes: 2 additions & 0 deletions compiler/ml/ast_helper.mli
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ module Exp : sig
?loc:loc -> ?attrs:attrs -> expression -> lid -> expression -> expression
val array : ?loc:loc -> ?attrs:attrs -> expression list -> expression
val dict : ?loc:loc -> ?attrs:attrs -> dict_entry list -> expression
val spread :
?loc:loc -> ?attrs:attrs -> spread_kind -> spread_element list -> expression
val ifthenelse :
?loc:loc ->
?attrs:attrs ->
Expand Down
5 changes: 5 additions & 0 deletions compiler/ml/ast_iterator.ml
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,11 @@ module E = struct
sub.expr sub e
| Pdict_spread e -> sub.expr sub e)
entries
| Pexp_spread (_, elements) ->
List.iter
(function
| Pspread_item e | Pspread e -> sub.expr sub e)
elements
| Pexp_ifthenelse (e1, e2, e3) ->
sub.expr sub e1;
sub.expr sub e2;
Expand Down
7 changes: 7 additions & 0 deletions compiler/ml/ast_mapper.ml
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,13 @@ module E = struct
Pdict_entry (map_loc sub key, sub.expr sub e)
| Pdict_spread e -> Pdict_spread (sub.expr sub e))
entries)
| Pexp_spread (kind, elements) ->
spread ~loc ~attrs kind
(List.map
(function
| Pspread_item e -> Pspread_item (sub.expr sub e)
| Pspread e -> Pspread (sub.expr sub e))
elements)
| Pexp_ifthenelse (e1, e2, e3) ->
ifthenelse ~loc ~attrs (sub.expr sub e1) (sub.expr sub e2)
(map_opt (sub.expr sub) e3)
Expand Down
54 changes: 54 additions & 0 deletions compiler/ml/ast_mapper_from0.ml
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,52 @@ module E = struct
| None -> None)
| _ -> Option.map rows (dict_rows ~marker:Ast_dict.literal_marker e)

(* The kind and elements of an array or list literal with spreads encoded as
a call, see [Ast_mapper_to0.spread_call] *)
let spread_elements (e : expression) =
let kind_of_module_name name =
if name = Primitive_modules.array then Some Pt.Spread_array
else if name = Primitive_modules.list then Some Pt.Spread_list
else None
in
let generated (e : expression) =
e.pexp_loc.loc_ghost && e.pexp_attributes = []
in
let rec list_part (e : expression) =
match e.pexp_desc with
| Pexp_construct
( {txt = Lident "::"},
Some {pexp_desc = Pexp_tuple [item; tail]; pexp_attributes = []} )
when generated e ->
`Item item :: list_part tail
| Pexp_construct ({txt = Lident "[]"}, None) when generated e -> []
| _ -> [`Spread e]
in
let array_part (e : expression) =
match e.pexp_desc with
| Pexp_array items when generated e ->
List.map (fun item -> `Item item) items
| _ -> [`Spread e]
in
match e.pexp_desc with
| Pexp_apply
( {
pexp_desc = Pexp_ident {txt = Ldot (Lident module_name, "spread")};
pexp_attributes = [({txt}, PStr [])];
},
[
( Asttypes.Noloc.Nolabel,
{pexp_desc = Pexp_array parts; pexp_attributes = []} );
] )
when txt = Ast_mapper_to0.spread_marker -> (
match kind_of_module_name module_name with
| Some Pt.Spread_array ->
Some (Pt.Spread_array, List.concat_map array_part parts)
| Some Pt.Spread_list ->
Some (Pt.Spread_list, List.concat_map list_part parts)
| None -> None)
| _ -> None

let extract_for_of_attribute attrs =
List.find_map
(function
Expand Down Expand Up @@ -932,6 +978,14 @@ module E = struct
Pt.Pdict_entry (map_loc sub key, sub.expr sub value)
| `Spread spread -> Pt.Pdict_spread (sub.expr sub spread))
entries)
| Pexp_apply _ when Option.is_some (spread_elements e) ->
let kind, elements = Option.get (spread_elements e) in
spread ~loc ~attrs kind
(List.map
(function
| `Item item -> Pt.Pspread_item (sub.expr sub item)
| `Spread spread -> Pt.Pspread (sub.expr sub spread))
elements)
| Pexp_apply
( {pexp_desc = Pexp_ident {txt = Longident.Lident "#="}},
[
Expand Down
25 changes: 25 additions & 0 deletions compiler/ml/ast_mapper_to0.ml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,26 @@ let remove_ppx_context_string_attr attrs =

let map_loc sub {loc; txt} = {loc = sub.location sub loc; txt}

(* v0 has no array or list literals with spreads, [Pexp_spread]: they're the
calls of [Primitive_array.spread] and [Primitive_list.spread] they used to be
parsed into, whose [spread] carries the [res.spread] marker. The argument is
an array of the [Ast_spread.parts]; [Ast_mapper_from0] decodes the parts
with ghost locations back into the items. *)
let spread_marker = "res.spread"

let spread_call ~loc ~attrs kind elements =
Ast_helper.Exp.apply ~loc ~attrs
(Ast_helper.Exp.ident ~loc
~attrs:[(Location.mknoloc spread_marker, PStr [])]
(Location.mkloc
(Longident.Ldot
(Longident.Lident (Ast_spread.module_name kind), "spread"))
loc))
[
( Asttypes.Nolabel,
Ast_helper.Exp.array ~loc (Ast_spread.parts kind elements) );
]

(* Internal Parsetree0 bridge metadata; public res.* attributes pass through. *)
let record_rest_attr_name = "_res.record_rest"
let constructor_args_attr_name = "_res.constructor_args"
Expand Down Expand Up @@ -704,6 +724,11 @@ module E = struct
into *)
sub.expr sub
(Ast_dict.to_calls ~loc:e.pexp_loc ~attrs:e.pexp_attributes entries)
| Pexp_spread (kind, elements) ->
(* v0 has no spread literals: they're the calls they used to be parsed
into *)
sub.expr sub
(spread_call ~loc:e.pexp_loc ~attrs:e.pexp_attributes kind elements)
| Pexp_ifthenelse (e1, e2, e3) ->
ifthenelse ~loc ~attrs (sub.expr sub e1) (sub.expr sub e2)
(map_opt (sub.expr sub) e3)
Expand Down
79 changes: 79 additions & 0 deletions compiler/ml/ast_spread.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
(* Array and list literals with spreads, [Pexp_spread]: [[a, ...b]] and
[list{...a, b}]. They compile to calls of the runtime's
[Primitive_array.spread] and [Primitive_list.spread], whose argument is an
array of parts:
- for arrays, the spreads, and array literals of the items between them;
- for lists, the items before each spread consed onto it
([a :: b :: spread]), and a list literal of the items after the last one.
The type checker types a literal as its parts, so the items keep the errors
of array and list literals. The array literals and [::] and [[]]
constructors built from the items have ghost locations, like any node the
compiler makes up; on the v0 PPX wire they also tell the items apart from
array and list literals that are spread, see [Ast_mapper_to0.spread_call]. *)

open Parsetree

let module_name = function
| Spread_array -> Primitive_modules.array
| Spread_list -> Primitive_modules.list

(* The items before each spread with that spread, and the items after the last
spread *)
let groups (elements : spread_element list) :
(expression list * expression option) list =
let flush items acc =
match items with
| [] -> acc
| _ -> (List.rev items, None) :: acc
in
let rec loop items acc = function
| [] -> List.rev (flush items acc)
| Pspread_item item :: rest -> loop (item :: items) acc rest
| Pspread spread :: rest ->
loop [] ((List.rev items, Some spread) :: acc) rest
in
loop [] [] elements

let ghost_span (first : expression) (last : expression) =
{
Location.loc_start = first.pexp_loc.loc_start;
loc_end = last.pexp_loc.loc_end;
loc_ghost = true;
}

(* The parts in the argument of [Primitive_array.spread] or
[Primitive_list.spread], each an array or list *)
let parts kind elements =
let construct ~loc name args =
Ast_helper.Exp.construct ~loc
(Location.mkloc (Longident.Lident name) loc)
(Location.mkloc args loc)
in
List.concat_map
(fun (items, spread) ->
match (kind, items, List.rev items) with
| _, [], _ | _, _, [] -> Option.to_list spread
| Spread_array, first :: _, last :: _ ->
Ast_helper.Exp.array ~loc:(ghost_span first last) items
:: Option.to_list spread
| Spread_list, _, last :: _ ->
let tail =
match spread with
| Some spread -> spread
| None ->
construct
~loc:
{
last.pexp_loc with
loc_start = last.pexp_loc.loc_end;
loc_ghost = true;
}
"[]" []
in
[
List.fold_right
(fun item tail ->
construct ~loc:(ghost_span item tail) "::" [item; tail])
items tail;
])
(groups elements)
5 changes: 5 additions & 0 deletions compiler/ml/depend.ml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ let rec add_expr bv exp =
(function
| Pdict_entry (_, e) | Pdict_spread e -> add_expr bv e)
entries
| Pexp_spread (_, elements) ->
List.iter
(function
| Pspread_item e | Pspread e -> add_expr bv e)
elements
| Pexp_ifthenelse (e1, e2, opte3) ->
add_expr bv e1;
add_expr bv e2;
Expand Down
6 changes: 6 additions & 0 deletions compiler/ml/lambda.ml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ type primitive =
| Ptemplate of Asttypes.template_segment list
(* {...target, ...sources} *)
| Pdict_spread
(* [Primitive_array.spread(parts)] and [Primitive_list.spread(parts)], the
concatenation of an array of arrays or lists *)
| Parray_spread
| Plist_spread

and comparison = Ceq | Cneq | Clt | Cgt | Cle | Cge

Expand Down Expand Up @@ -486,6 +490,8 @@ let eq_primitive_approx (lhs : primitive) (rhs : primitive) =
| Pmakelist
(* dict primitives *)
| Pmakedict | Pdict_spread | Pdict_has
(* spread primitives *)
| Parray_spread | Plist_spread
(* promise *)
| Pawait
(* etc *)
Expand Down
4 changes: 4 additions & 0 deletions compiler/ml/lambda.mli
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ type primitive =
| Ptemplate of template_segment list
(* {...target, ...sources} *)
| Pdict_spread
(* [Primitive_array.spread(parts)] and [Primitive_list.spread(parts)], the
concatenation of an array of arrays or lists *)
| Parray_spread
| Plist_spread

and comparison = Ceq | Cneq | Clt | Cgt | Cle | Cge

Expand Down
Loading
Loading