Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
01b10ab
destructure record rest elements - fixes #8311
tsnobip Mar 28, 2026
f353016
support type with parameter for record rest
tsnobip Mar 31, 2026
6108216
simplify parsing of record rest
tsnobip Mar 31, 2026
f9c6325
update record spread error message
tsnobip Mar 31, 2026
906df32
improve error message of superfluous fields in rest
tsnobip Mar 31, 2026
77ccbcf
improve error message of non optional rest field already matched
tsnobip Mar 31, 2026
453d89f
add a warning when rest record would be empty
tsnobip Mar 31, 2026
23ea968
add fixture tests for error/warning messages
tsnobip Mar 31, 2026
0841f1d
add changelog
tsnobip Mar 31, 2026
aaecac9
address comments (parsetree0 PPX roundtrips, nested rest, etc)
tsnobip Apr 1, 2026
ca77e9c
support rest of inline record
tsnobip Apr 1, 2026
42c6f9c
check rest field types, fix matching & invalid field identifier
tsnobip Apr 1, 2026
68e0ebb
fix rest of private type and analysis
tsnobip Apr 1, 2026
83a7006
use runtime field names for rest
tsnobip Apr 1, 2026
e667382
support record type alias in rest
tsnobip Apr 2, 2026
049aaed
fix compiler crash when spreading the whole record
tsnobip Apr 7, 2026
b47e43c
disallow rest spreading on packed modules
tsnobip Apr 7, 2026
b092cb8
add tests for record rest with namespaced type
tsnobip Apr 10, 2026
fbc1735
make sure rest is used and move logic to its own files
tsnobip Apr 10, 2026
53f864c
stop ignoring _rest in a few more places
tsnobip Apr 19, 2026
e0c41c8
format
tsnobip Jun 16, 2026
bcc6c80
improve output (compile to JS destructuring)
tsnobip Jun 16, 2026
88378e1
update changelog
tsnobip Jun 17, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

- Add a first-class `taggedTemplate<'param, 'output>` builtin type and the `TaggedTemplate` stdlib module (`TaggedTemplate.make`). Tagged-template tags are now tracked through the type system, so they emit real JS tagged-template syntax across module boundaries, when passed as first-class values, and when constructed at runtime by a factory (e.g. `postgres`). https://github.com/rescript-lang/rescript/pull/8461
- Make mutation of private record mutable fields a configurable warning instead of a hard error. https://github.com/rescript-lang/rescript/pull/8366
- Add support for pattern matching/destructuring of record rest. https://github.com/rescript-lang/rescript/pull/8317

#### :bug: Bug fix

Expand Down
2 changes: 1 addition & 1 deletion analysis/reanalyze/src/dead_value.ml
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ let collect_pattern ~config ~refs :
fun super self pat ->
let pos_from = pat.Typedtree.pat_loc.loc_start in
(match pat.pat_desc with
| Typedtree.Tpat_record (cases, _clodsedFlag) ->
| Typedtree.Tpat_record (cases, _clodsedFlag, _rest) ->
cases
|> List.iter (fun (_loc, {Types.lbl_loc = {loc_start = pos_to}}, _pat, _) ->
if !Config.analyze_types then
Expand Down
2 changes: 1 addition & 1 deletion analysis/src/completion_front_end.ml
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
(NPolyvariantPayload {item_num = 0; constructor_name = txt}
:: pattern_path)
?context_path p
| Ppat_record (fields, _) ->
| Ppat_record (fields, _, _rest) ->
Ext_list.iter fields (fun {lid = fname; x = p} ->
match fname with
| {Location.txt = Longident.Lident fname} ->
Expand Down
4 changes: 2 additions & 2 deletions analysis/src/completion_patterns.ml
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ and traverse_pattern (pat : Parsetree.pattern) ~pattern_path ~loc_has_cursor
[Completable.NTupleItem {item_num}] @ pattern_path)
~result_from_found_item_num:(fun item_num ->
[Completable.NTupleItem {item_num = item_num + 1}] @ pattern_path)
| Ppat_record ([], _) ->
| Ppat_record ([], _, _rest) ->
(* Empty fields means we're in a record body `{}`. Complete for the fields. *)
some_if_has_cursor
("", [Completable.NRecordBody {seen_fields = []}] @ pattern_path)
"Ppat_record(empty)"
| Ppat_record (fields, _) -> (
| Ppat_record (fields, _, _rest) -> (
let field_with_cursor = ref None in
let field_with_pat_hole = ref None in
Ext_list.iter fields (fun {lid = fname; x = f} ->
Expand Down
18 changes: 17 additions & 1 deletion analysis/src/dump_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ let print_core_type typ ~pos =
| Ptyp_variant _ -> "Ptyp_variant(<unimplemented>)"
| _ -> "<unimplemented_ptyp_desc>"

let print_record_pattern_rest rest ~pos =
(rest.Parsetree.rest_name |> print_loc_denominator_loc ~pos)
^ rest.rest_name.txt
^
match rest.rest_type with
| Some core_type -> " as " ^ print_core_type core_type ~pos
| None -> ""

let rec print_pattern pattern ~pos ~indentation =
print_attributes pattern.Parsetree.ppat_attributes
^ (pattern.ppat_loc |> print_loc_denominator ~pos)
Expand Down Expand Up @@ -101,7 +109,7 @@ let rec print_pattern pattern ~pos ~indentation =
| None -> ""
| Some pat -> "," ^ print_pattern pat ~pos ~indentation)
^ ")"
| Ppat_record (fields, _) ->
| Ppat_record (fields, _, rest) ->
"Ppat_record(\n"
^ add_indentation (indentation + 1)
^ "fields:\n"
Expand All @@ -112,6 +120,14 @@ let rec print_pattern pattern ~pos ~indentation =
^ ": "
^ print_pattern pat ~pos ~indentation:(indentation + 2))
|> String.concat "\n")
^ (match rest with
| None -> ""
| Some rest ->
"\n"
^ add_indentation (indentation + 1)
^ "rest:\n"
^ add_indentation (indentation + 2)
^ print_record_pattern_rest rest ~pos)
^ "\n"
^ add_indentation indentation
^ ")"
Expand Down
7 changes: 5 additions & 2 deletions analysis/src/hint.ml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ let inlay ~source ~kind_file ~pos ~max_length ~full ~state ~debug =
let rec process_pattern (pat : Parsetree.pattern) =
match pat.ppat_desc with
| Ppat_tuple pl -> pl |> List.iter process_pattern
| Ppat_record (fields, _) ->
Ext_list.iter fields (fun {x = p} -> process_pattern p)
| Ppat_record (fields, _, rest) -> (
Ext_list.iter fields (fun {x = p} -> process_pattern p);
match rest with
| Some {rest_name; _} -> push rest_name.loc Type
| None -> ())
| Ppat_array fields -> fields |> List.iter process_pattern
| Ppat_var {loc} -> push loc Type
| _ -> ()
Expand Down
23 changes: 21 additions & 2 deletions analysis/src/process_cmt.ml
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,27 @@ let rec for_structure_item ~(env : Shared_types.Env.t) ~(exported : Exported.t)
| Tpat_tuple pats | Tpat_array pats | Tpat_construct (_, _, pats) ->
pats |> List.iter (fun p -> handle_pattern [] p)
| Tpat_or (p, _, _) -> handle_pattern [] p
| Tpat_record (items, _) ->
items |> List.iter (fun (_, _, p, _) -> handle_pattern [] p)
| Tpat_record (record_items, _, rest) -> (
record_items |> List.iter (fun (_, _, p, _) -> handle_pattern [] p);
match rest with
| None -> ()
| Some rest ->
let declared =
add_declared ~name:rest.rest_name
~stamp:(Ident.binding_time rest.rest_ident)
~env ~extent:rest.rest_name.loc ~item:rest.rest_type []
(Exported.add exported Exported.Value)
Stamps.add_value
in
items :=
{
Module.kind = Module.Value declared.item;
name = declared.name.txt;
docstring = declared.docstring;
deprecated = declared.deprecated;
loc = declared.extent_loc;
}
:: !items)
| Tpat_variant (_, Some p, _) -> handle_pattern [] p
| Tpat_variant (_, None, _) | Tpat_any | Tpat_constant _ -> ()
in
Expand Down
22 changes: 16 additions & 6 deletions analysis/src/process_extra.ml
Original file line number Diff line number Diff line change
Expand Up @@ -378,22 +378,32 @@ let pat ~(file : File.t) ~env ~extra (iter : Tast_iterator.iterator)
| Tpackage (path, _, _) -> Some path
| _ -> None
in
let add_for_pattern stamp name =
let add_for_declared_pattern ~stamp ~name ~extent ~item ~attributes =
if Stamps.find_value file.stamps stamp = None then (
let declared =
Process_attributes.new_declared ~name ~stamp ~module_path:NotVisible
~extent:pattern.pat_loc ~item:pattern.pat_type false
pattern.pat_attributes
~extent ~item false attributes
in
Stamps.add_value file.stamps stamp declared;
add_reference ~extra stamp name.loc;
add_loc_item extra name.loc
(Typed (name.txt, pattern.pat_type, Definition (stamp, Value))))
(Typed (name.txt, item, Definition (stamp, Value))))
in
let add_for_pattern stamp name =
add_for_declared_pattern ~stamp ~name ~extent:pattern.pat_loc
~item:pattern.pat_type ~attributes:pattern.pat_attributes
in
(* Log.log("Entering pattern " ++ Utils.showLocation(pat_loc)); *)
(match pattern.pat_desc with
| Tpat_record (items, _) ->
add_for_record ~env ~extra ~record_type:pattern.pat_type items
| Tpat_record (items, _, rest) -> (
add_for_record ~env ~extra ~record_type:pattern.pat_type items;
match rest with
| None -> ()
| Some rest ->
add_for_declared_pattern
~stamp:(Ident.binding_time rest.rest_ident)
~name:rest.rest_name ~extent:rest.rest_name.loc ~item:rest.rest_type
~attributes:pattern.pat_attributes)
| Tpat_construct (lident, constructor, _) ->
add_for_constructor ~env ~extra pattern.pat_type lident constructor
| Tpat_alias (_inner, ident, name) -> (
Expand Down
10 changes: 7 additions & 3 deletions analysis/src/semantic_tokens.ml
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,13 @@ let command ~debug ~emitter ~source ~kind_file =
| Ppat_construct ({txt = Lident ("true" | "false")}, _) ->
(* Don't emit true or false *)
Ast_iterator.default_iterator.pat iterator p
| Ppat_record (cases, _) ->
| Ppat_record (cases, _, rest) ->
Ext_list.iter cases (fun {lid = label} ->
emitter |> emit_record_label ~label ~debug);
(match rest with
| Some {rest_name = {txt = id; loc}; _} when is_lowercase_id id ->
emitter |> emit_variable ~id ~debug ~loc
| _ -> ());
Ast_iterator.default_iterator.pat iterator p
| Ppat_construct (name, _) ->
emitter |> emit_variant ~name ~debug;
Expand Down Expand Up @@ -490,7 +494,7 @@ let command ~debug ~emitter ~source ~kind_file =
in
let {Res_driver.parsetree = structure; diagnostics} = parser ~source in
if debug then
Printf.printf "structure items:%d diagnostics:%d \n"
Printf.printf "structure items:%d diagnostics:%d\n"
(List.length structure) (List.length diagnostics);
iterator.structure iterator structure |> ignore)
else
Expand All @@ -499,7 +503,7 @@ let command ~debug ~emitter ~source ~kind_file =
in
let {Res_driver.parsetree = signature; diagnostics} = parser ~source in
if debug then
Printf.printf "signature items:%d diagnostics:%d \n"
Printf.printf "signature items:%d diagnostics:%d\n"
(List.length signature) (List.length diagnostics);
iterator.signature iterator signature |> ignore

Expand Down
3 changes: 2 additions & 1 deletion analysis/src/signature_help.ml
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,8 @@ let signature_help ~debug ~source ~kind_file ~pos
match tuple_item_with_cursor with
| None -> -1
| Some i -> i)
| `ConstructorPat (_, {ppat_desc = Ppat_record (fields, _)}) -> (
| `ConstructorPat (_, {ppat_desc = Ppat_record (fields, _, _rest)})
-> (
let field_name_with_cursor =
fields
|> List.find_map
Expand Down
2 changes: 1 addition & 1 deletion analysis/src/xform.ml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ module If_then_else = struct
in
match list_to_pat ~item_to_pat items with
| None -> None
| Some pat_items -> Some (mk_pat (Ppat_record (pat_items, Closed))))
| Some pat_items -> Some (mk_pat (Ppat_record (pat_items, Closed, None))))
| Pexp_record (_, Some _) -> None
| _ -> None

Expand Down
4 changes: 2 additions & 2 deletions compiler/common/pattern_printer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ let untype typed =
| Tpat_variant (label, p_opt, _row_desc) ->
let arg = Option.map loop p_opt in
mkpat (Ppat_variant (label, arg))
| Tpat_record (subpatterns, closed_flag) ->
| Tpat_record (subpatterns, closed_flag, _rest) ->
let fields, saw_optional_rewrite =
List.fold_right
(fun (_, lbl, p, opt) (fields, saw_optional_rewrite) ->
Expand All @@ -97,7 +97,7 @@ let untype typed =
subpatterns ([], false)
in
let closed_flag = if saw_optional_rewrite then Closed else closed_flag in
mkpat (Ppat_record (fields, closed_flag))
mkpat (Ppat_record (fields, closed_flag, None))
| Tpat_array lst -> mkpat (Ppat_array (List.map loop lst))
in
loop typed
Expand Down
38 changes: 37 additions & 1 deletion compiler/core/j.ml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ and property_map = (property_name * expression) list
and length_object = Js_op.length_object
and delim = External_arg_spec.delim = DNone | DStarJ | DNoQuotes | DBackQuotes

and record_rest_field = {
record_rest_label: string;
record_rest_ident: ident option;
}

and object_rest_param = {
object_rest_fields: record_rest_field list;
object_rest_rest: ident;
}

and param = Ident_param of ident | Object_rest_param of object_rest_param

and expression_desc =
| Length of expression * length_object
| Is_null_or_undefined of expression (** where we use a trick [== null ] *)
Expand Down Expand Up @@ -132,7 +144,7 @@ and expression_desc =
| Var of vident
| Fun of {
is_method: bool;
params: ident list;
params: param list;
body: block;
env: Js_fun_env.t;
return_unit: bool;
Expand Down Expand Up @@ -165,6 +177,7 @@ and expression_desc =
| Null
| Await of expression
| Spread of expression
| Record_rest of record_rest_field list * expression

and for_ident_expression = expression
(* pure*)
Expand Down Expand Up @@ -327,6 +340,9 @@ and deps_program = {
finish_ident_expression;
property_map;
length_object;
record_rest_field;
object_rest_param;
param;
(* for_ident; *)
required_modules;
case_clause;
Expand All @@ -337,3 +353,23 @@ FIXME: customize for each code generator
for each code generator, we can provide a white-list
so that we can achieve the optimal
*)

let record_rest_field_idents fields =
List.filter_map (fun {record_rest_ident} -> record_rest_ident) fields

let object_rest_param_idents {object_rest_fields; object_rest_rest} =
object_rest_rest :: record_rest_field_idents object_rest_fields

let param_idents = function
| Ident_param id -> [id]
| Object_rest_param param -> object_rest_param_idents param

let params_idents params = List.concat_map param_idents params

let params_as_idents params =
let rec aux acc = function
| [] -> Some (List.rev acc)
| Ident_param id :: rest -> aux (id :: acc) rest
| Object_rest_param _ :: _ -> None
in
aux [] params
15 changes: 14 additions & 1 deletion compiler/core/js_analyzer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ type idents_stats = {
let add_defined_idents (x : idents_stats) ident =
x.defined_idents <- Set_ident.add x.defined_idents ident

let add_record_rest_field_idents stats fields =
List.iter
(fun (field : J.record_rest_field) ->
match field.record_rest_ident with
| None -> ()
| Some ident -> add_defined_idents stats ident)
fields

(* Assume that functions already calculated closure correctly
Maybe in the future, we should add a dirty flag, to mark the calcuated
closure is correct or not
Expand All @@ -46,6 +54,9 @@ let free_variables (stats : idents_stats) =
(fun self st ->
add_defined_idents stats st.ident;
match st.value with
| Some {expression_desc = Record_rest (fields, source)} ->
add_record_rest_field_idents stats fields;
self.expression self source
| None -> ()
| Some v -> self.expression self v);
ident =
Expand Down Expand Up @@ -118,6 +129,7 @@ let rec no_side_effect_expression_desc (x : J.expression_desc) =
| FlatCall _ | Call _ | New _ | Raw_js_code _ (* actually true? *) -> false
| Await _ -> false
| Spread _ -> false
| Record_rest _ -> false

and no_side_effect (x : J.expression) =
no_side_effect_expression_desc x.expression_desc
Expand Down Expand Up @@ -230,7 +242,8 @@ let rec eq_expression ({expression_desc = x0} : J.expression)
| _ -> false)
| Length _ | Is_null_or_undefined _ | String_append _ | Typeof _ | Js_not _
| Js_bnot _ | In _ | Cond _ | FlatCall _ | New _ | Fun _ | Raw_js_code _
| Array _ | Caml_block_tag _ | Object _ | Tagged_template _ | Await _ ->
| Array _ | Caml_block_tag _ | Object _ | Tagged_template _ | Await _
| Record_rest _ ->
false
| Spread _ -> false

Expand Down
Loading
Loading