Skip to content
Merged
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
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ The Makefile’s targets build on each other in this order:

- **We are NOT bound by OCaml compatibility** - The ReScript compiler originated as a fork of the OCaml compiler, but we maintain our own AST and can make breaking changes. Focus on what's best for ReScript's JavaScript compilation target.

- **Never modify `parsetree0.ml`** - Existing PPX (parser extensions) rely on this frozen v0 version. When changing `parsetree.ml`, always update the mapping modules `ast_mapper_from0.ml` and `ast_mapper_to0.ml` to maintain PPX compatibility while allowing the main parsetree to evolve
- **Never modify `parsetree0.ml`** - Existing PPX (parser extensions) rely on this frozen v0 version. When changing `parsetree.ml`, always update the mapping modules `ast_mapper_from0.ml` and `ast_mapper_to0.ml` to maintain PPX compatibility while allowing the main parsetree to evolve. **Test the bridge with the existing infra** — do not build new harnesses for this:
- Add a source fixture exercising the new syntax to `tests/syntax_tests/data/ast-mapping/`. Every file there is run through `res_parser -test-ast-conversion` (which round-trips the parsetree through the frozen v0 AST before printing) as part of `make test-syntax`; the printed output must match the snapshot in its `expected/` directory.
- For exact-identity invariants (locations, attributes) or the v0 wire shape itself, add cases to `tests/ounit_tests/ounit_ast_mapper0_tests.ml`, which tests `ast_mapper_to0`/`ast_mapper_from0` directly.

- **Missing test coverage** - Always add tests for syntax, lambda, and end-to-end behavior

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
- Remove the deprecated `Js` namespace and its runtime modules. https://github.com/rescript-lang/rescript/pull/8531
- Move Belt into the separately installed `@rescript/belt` package. Projects using Belt must install the package and list it in their `rescript.json` dependencies. https://github.com/rescript-lang/rescript/pull/8554
- Correct the structured function details produced by `rescript-tools doc` and exposed by `RescriptTools.Docgen`: parameters now retain labels and optionality, nested functions, tuples, variables, and generic arguments retain their type structure, return types are identified correctly, and non-function values no longer receive fake function details. This changes the published docgen detail schema. https://github.com/rescript-lang/rescript/pull/8576
- Make object-field mutability part of the type. A property has one type for reading and writing. Assignment requires `@set`, except on an inferred open row, where assignment makes the field settable. Private rows are not inferred open rows, so a field in `type t = private {.."x": int}` is writable only when annotated with `@set`. Coercions never grant or widen write capability. Previously, getter and setter types were tracked independently, allowing a property to be written at a different type than it was read and allowing writes through a value coerced to a type without `@set`. https://github.com/rescript-lang/rescript/pull/8597
- Remove the undocumented object-field attribute forms `@get` (bare or with a `null`/`undefined`/`nullable` payload) and `@set({no_get: ...})` on object types. Only bare `@set` marks a field settable; nullable getter types are written directly (`null<t>`, `undefined<t>`, `nullable<t>`). https://github.com/rescript-lang/rescript/pull/8597

#### :eyeglasses: Spec Compliance

Expand All @@ -30,6 +32,7 @@

#### :bug: Bug fix

- Object typing errors now describe fields directly: assigning to a field without `@set` reports that the field is not settable and suggests the annotation, and missing-property errors name the field instead of a phantom `"x#="` member. https://github.com/rescript-lang/rescript/pull/8597
- Fix signature inclusion rejecting equivalent object externals after type-alias expansion. https://github.com/rescript-lang/rescript/pull/8581
- Fix externals whose result type is an alias of `unit` so they use the same unit-return behavior as externals declared to return `unit`. https://github.com/rescript-lang/rescript/pull/8581
- Fix dynamic imports of external bindings that require FFI argument or result conversions, including `@variadic`, `@unwrap`, polymorphic variant encodings, `@as` phantom arguments, optional labeled arguments, and `@return` wrappers. The imported value now applies the same conversions as a direct external call. https://github.com/rescript-lang/rescript/pull/8582
Expand Down Expand Up @@ -57,6 +60,7 @@

#### :house: Internal

- Rework the object-type representation end to end: object rows are plain field chains carrying a per-field mutability state (no phantom setter members), object literals are typed directly and property access and assignment are first-class AST and Lambda nodes shared between the Lambda and JS pipelines, and dead class-system remnants (the field-presence lattice, the class-abbreviation memo on object types, method-send typing) are removed. https://github.com/rescript-lang/rescript/pull/8597
- Upgrade the development toolchain and primary CI builds to OCaml 5.5 while retaining OCaml 5.0 as the minimum supported version. https://github.com/rescript-lang/rescript/pull/8589
- Upgrade the vendored Flow parser from 0.267.0 to 0.320.0, the final release of the OCaml implementation. https://github.com/rescript-lang/rescript/pull/8588
- Vendor the Flow parser 0.267.0 sources used by the compiler, removing the external `flow_parser` dependency and establishing a maintained baseline for future OCaml upgrades. https://github.com/rescript-lang/rescript/pull/8587
Expand Down
13 changes: 10 additions & 3 deletions analysis/reanalyze/src/arnold.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1003,9 +1003,16 @@ module Compile = struct
| Texp_for_await_of (_id, _pat, e1, e2) ->
let open Command in
expression ~ctx e1 +++ expression ~ctx e2
| Texp_send _ ->
not_implemented "Texp_send";
assert false
| Texp_object_literal fields ->
(* Fields are emitted and evaluated in source order *)
fields
|> List.map (fun (_name, e) -> e |> expression ~ctx)
|> Command.sequence
| Texp_object_get (e, _) -> e |> expression ~ctx
| Texp_object_set (e1, _, e2) ->
(* Receiver first, then the assigned value *)
let open Command in
expression ~ctx e1 +++ expression ~ctx e2
| Texp_letmodule _ ->
not_implemented "Texp_letmodule";
assert false
Expand Down
5 changes: 4 additions & 1 deletion analysis/reanalyze/src/side_effects.ml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ let rec expr_no_side_effects (expr : Typedtree.expression) =
e1 |> expr_no_side_effects && e2 |> expr_no_side_effects
&& e3 |> expr_no_side_effects
| Texp_for_of _ | Texp_for_await_of _ -> false
| Texp_send _ -> false
| Texp_object_literal fields ->
fields |> List.for_all (fun (_name, e) -> e |> expr_no_side_effects)
| Texp_object_get _ -> false
| Texp_object_set _ -> false
Comment thread
cristianoc marked this conversation as resolved.
| Texp_letexception (_ec, e) -> e |> expr_no_side_effects
| Texp_pack _ -> false
| Texp_extension_constructor _ when true -> true
Expand Down
16 changes: 11 additions & 5 deletions analysis/src/completion_front_end.ml
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ let rec expr_to_context_path_inner ~(in_jsx_context : bool)
expr_loc = e1.pexp_loc;
in_jsx = in_jsx_context;
})
| Pexp_send (e1, {txt}) -> (
| Pexp_object_get (e1, {txt}) -> (
match expr_to_context_path ~in_jsx_context e1 with
| None -> None
| Some contex_path -> Some (CPObj (contex_path, txt)))
Expand Down Expand Up @@ -1230,8 +1230,8 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
if expr.pexp_loc |> Loc.has_pos ~pos:pos_no_white && !result = None then (
set_found ();
match expr.pexp_desc with
| Pexp_extension ({txt = "obj"}, PStr [str_item]) ->
Ast_iterator.default_iterator.structure_item iterator str_item
| Pexp_object_literal fields ->
List.iter (fun (_, e) -> iterator.expr iterator e) fields
| Pexp_extension ({txt}, _) -> set_result (CextensionNode txt)
| Pexp_constant _ -> set_result Cnone
| Pexp_ident lid ->
Expand Down Expand Up @@ -1580,7 +1580,8 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
|> iterate_fn_arguments ~is_pipe:false ~args ~iterator;
reset_current_ctx_path old_ctx_path)
| Some arg_completable -> set_result arg_completable)
| Pexp_send (lhs, {txt; loc}) -> (
| (Pexp_object_get (lhs, {txt; loc}) as object_access)
| (Pexp_object_set (lhs, {txt; loc}, _) as object_access) -> (
(* e["txt"]
If the string for txt is not closed, it could go over several lines.
Only take the first like to represent the label *)
Expand All @@ -1596,7 +1597,12 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
((l, c + 1), (l, c + 1 + String.length label))
in
if debug then
Printf.printf "Pexp_send %s%s e:%s\n" label
Printf.printf "%s %s%s e:%s\n"
(match object_access with
| Pexp_object_get _ -> "Pexp_object_get"
| Pexp_object_set _ -> "Pexp_object_set"
| _ -> assert false)
label
(Range.to_string label_range)
(Loc.to_string lhs.pexp_loc);
if
Expand Down
4 changes: 2 additions & 2 deletions analysis/src/hint.ml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ let inlay ~source ~kind_file ~pos ~max_length ~full ~state ~debug =
pexp_desc =
( Pexp_constant _ | Pexp_tuple _ | Pexp_record _ | Pexp_variant _
| Pexp_apply _ | Pexp_match _ | Pexp_construct _ | Pexp_ifthenelse _
| Pexp_array _ | Pexp_ident _ | Pexp_try _ | Pexp_send _
| Pexp_field _ | Pexp_open _ | Pexp_fun _ );
| Pexp_array _ | Pexp_ident _ | Pexp_try _ | Pexp_object_get _
| Pexp_object_set _ | Pexp_field _ | Pexp_open _ | Pexp_fun _ );
};
} ->
push vb.pvb_pat.ppat_loc Type
Expand Down
15 changes: 0 additions & 15 deletions analysis/src/references.ml
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,6 @@ let get_loc_item ~full ~pos ~debug =
heuristic for: [Props, x], give loc of `x`";
if debug then Printf.printf "n1:%s n2:%s\n" (name_of li1) (name_of li2);
Some li2
| [
({loc_type = Typed (_, _, LocalReference _)} as li1);
({
loc_type = Typed (_, _, GlobalReference ("Js_OO", ["unsafe_downgrade"], _));
} as li2);
li3;
]
(* For older compiler 9.0 or earlier *)
when li1.loc = li2.loc && li2.loc = li3.loc ->
(* Not currently testable on 9.1.4 *)
log 6
"heuristic for JSX and compiler combined:\n\
~x becomes Js_OO.unsafe_downgrade(Props)#x\n\
heuristic for: [Props, unsafe_downgrade, x], give loc of `x`";
Some li3
| [
({loc_type = Typed (_, _, LocalReference (_, Value))} as li1);
({loc_type = Typed (_, _, Definition (_, Value))} as li2);
Expand Down
9 changes: 9 additions & 0 deletions analysis/src/semantic_tokens.ml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ let command ~debug ~emitter ~source ~kind_file =
Printf.printf "Binary operator %s %s\n" op (Loc.to_string loc);
emitter |> emit_from_loc ~loc ~type_:Operator;
Ast_iterator.default_iterator.expr iterator e
| Pexp_object_literal fields ->
fields
|> List.iter (fun ((s : string Asttypes.loc), _) ->
if not (Utils.is_first_char_uppercase s.txt) then
emitter
|> emit_record_label
~label:{Asttypes.txt = Longident.Lident s.txt; loc = s.loc}
~debug);
Ast_iterator.default_iterator.expr iterator e
| Pexp_record (cases, _) ->
Ext_list.filter_map cases (fun {lid} ->
match lid.txt with
Expand Down
18 changes: 10 additions & 8 deletions analysis/src/type_utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ let rec has_tvar (ty : Types.type_expr) : bool =
List.exists (fun ({typ} : Types.arg) -> has_tvar typ) params || has_tvar ret
| Ttuple tyl -> List.exists has_tvar tyl
| Tconstr (_, tyl, _) -> List.exists has_tvar tyl
| Tobject (ty, _) -> has_tvar ty
| Tfield (_, _, ty1, ty2) -> has_tvar ty1 || has_tvar ty2
| Tobject ty -> has_tvar ty
| Tfield {typ = ty1; rest = ty2} -> has_tvar ty1 || has_tvar ty2
| Tnil -> false
| Tlink ty -> has_tvar ty
| Tsubst ty -> has_tvar ty
Expand Down Expand Up @@ -156,8 +156,9 @@ let instantiate_type ~type_params ~type_args (t : Types.type_expr) =
loop ret );
}
| Ttuple tl -> {t with desc = Ttuple (tl |> List.map loop)}
| Tobject (t, r) -> {t with desc = Tobject (loop t, r)}
| Tfield (n, k, t1, t2) -> {t with desc = Tfield (n, k, loop t1, loop t2)}
| Tobject t -> {t with desc = Tobject (loop t)}
| Tfield f ->
{t with desc = Tfield {f with typ = loop f.typ; rest = loop f.rest}}
| Tpoly (t, []) -> loop t
| Tpoly (t, tl) -> {t with desc = Tpoly (loop t, tl |> List.map loop)}
| Tpackage (p, l, tl) ->
Expand Down Expand Up @@ -217,8 +218,9 @@ let instantiate_type2 ?(type_arg_context : type_arg_context option)
loop ret );
}
| Ttuple tl -> {t with desc = Ttuple (tl |> List.map loop)}
| Tobject (t, r) -> {t with desc = Tobject (loop t, r)}
| Tfield (n, k, t1, t2) -> {t with desc = Tfield (n, k, loop t1, loop t2)}
| Tobject t -> {t with desc = Tobject (loop t)}
| Tfield f ->
{t with desc = Tfield {f with typ = loop f.typ; rest = loop f.rest}}
| Tpoly (t, []) -> loop t
| Tpoly (t, tl) -> {t with desc = Tpoly (loop t, tl |> List.map loop)}
| Tpackage (p, l, tl) ->
Expand Down Expand Up @@ -270,7 +272,7 @@ let rec extract_object_type ~state ~env ~package (t : Types.type_expr) =
match t.desc with
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) ->
extract_object_type ~state ~env ~package t1
| Tobject (t_obj, _) -> Some (env, t_obj)
| Tobject t_obj -> Some (env, t_obj)
| Tconstr (path, type_args, _) -> (
match References.dig_constructor ~state ~env ~package path with
| Some (env, {item = {decl = {type_manifest = Some t1; type_params}}}) ->
Expand Down Expand Up @@ -1334,7 +1336,7 @@ let remove_current_module_if_needed ~env_completion_is_made_from completion_path

let rec get_obj_fields (texp : Types.type_expr) =
match texp.desc with
| Tfield (name, _, t1, t2) ->
| Tfield {name; typ = t1; rest = t2} ->
let fields = t2 |> get_obj_fields in
(name, t1) :: fields
| Tlink te | Tsubst te | Tpoly (te, []) -> te |> get_obj_fields
Expand Down
4 changes: 3 additions & 1 deletion analysis/src/utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ let identify_pexp pexp =
| Pexp_for_await_of _ -> "Pexp_for_await_of"
| Pexp_constraint _ -> "Pexp_constraint"
| Pexp_coerce _ -> "Pexp_coerce"
| Pexp_send _ -> "Pexp_send"
| Pexp_object_get _ -> "Pexp_object_get"
| Pexp_object_set _ -> "Pexp_object_set"
| Pexp_object_literal _ -> "Pexp_object_literal"
| Pexp_letmodule _ -> "Pexp_letmodule"
| Pexp_letexception _ -> "Pexp_letexception"
| Pexp_assert _ -> "Pexp_assert"
Expand Down
1 change: 0 additions & 1 deletion compiler/core/lam.ml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ module Types = struct
| Lfor_of of ident * t * t
| Lfor_await_of of ident * t * t
| Lassign of ident * t
(* | Lsend of Lam_compat.meth_kind * t * t * t list * Location.t *)
end

include Types
Expand Down
1 change: 0 additions & 1 deletion compiler/core/lam.mli
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ and t = private
| Lfor_await_of of ident * t * t
| Lassign of ident * t

(* | Lsend of Lambda.meth_kind * t * t * t list * Location.t *)
(* | Levent of t * Lambda.lambda_event
[Levent] in the branch hurt pattern match,
we should use record for trivial debugger info
Expand Down
5 changes: 2 additions & 3 deletions compiler/core/lam_analysis.ml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ let rec no_side_effects (lam : Lam.t) : bool =
(* A tagged template invokes its tag at runtime, so it always has side
effects. *)
| Ptagged_template | Pjs_apply | Pjs_runtime_apply | Pjs_call _ | Pinit_mod
| Pupdate_mod | Pjs_unsafe_downgrade _ | Pdebugger | Pjs_fn_method
| Pupdate_mod | Pjs_object_get _ | Pjs_object_set _ | Pdebugger
| Pjs_fn_method
(* Await promise *)
| Pawait
(* TODO *)
Expand Down Expand Up @@ -123,7 +124,6 @@ let rec no_side_effects (lam : Lam.t) : bool =
| Lfor _ -> false
| Lfor_of _ | Lfor_await_of _ -> false
| Lassign _ -> false (* actually it depends ... *)
(* | Lsend _ -> false *)
| Lapply
{
ap_func = Lprim {primitive = Pfield (_, Fld_module {name = "from_fun"})};
Expand Down Expand Up @@ -186,7 +186,6 @@ let rec size (lam : Lam.t) =
| Lfor_of _ | Lfor_await_of _ -> really_big ()
| Lassign (_, v) -> 1 + size v
(* This is side effectful, be careful *)
(* | Lsend _ -> really_big () *)
with Too_big_to_inline -> 1000

and size_constant x =
Expand Down
60 changes: 5 additions & 55 deletions compiler/core/lam_compile.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1405,50 +1405,6 @@ let compile output_prefix =
Js_output.make
(aux lambda_cxt
{lambda_cxt with continuation = EffectCall new_return_type})
(* Note that in [Texp_apply] for [%sendcache] the cache might not be used
see {!CamlinternalOO.send_meth} and {!Translcore.transl_exp0} the branch
[Texp_apply] when [public_send ], args are simply dropped

reference
[js_of_ocaml]
1. GETPUBMET
2. GETDYNMET
3. GETMETHOD
[ocaml]
Lsend (bytegen.ml)
For the object layout refer to [camlinternalOO/create_object]
{[
let create_object table =
(* XXX Appel de [obj_block] *)
let obj = mark_ocaml_object @@ Obj.new_block Obj.object_tag table.size in
(* XXX Appel de [caml_modify] *)
Obj.set_field obj 0 (Obj.repr table.methods);
Obj.obj (set_id obj)

let create_object_opt obj_0 table =
if (Obj.magic obj_0 : bool) then obj_0 else begin
(* XXX Appel de [obj_block] *)
let obj = mark_ocaml_object @@ Obj.new_block Obj.object_tag table.size in
(* XXX Appel de [caml_modify] *)
Obj.set_field obj 0 (Obj.repr table.methods);
Obj.obj (set_id obj)
end
]}
it's a block with tag [248], the first field is [table.methods] which is an array
{[
type table =
{ mutable size: int;
mutable methods: closure array;
mutable methods_by_name: meths;
mutable methods_by_label: labs;
mutable previous_states:
(meths * labs * (label * item) list * vars *
label list * string list) list;
mutable hidden_meths: (label * item) list;
mutable vars: vars;
mutable initializers: (obj -> unit) list }
]}
*)
and compile_ifthenelse (predicate : Lam.t) (t_branch : Lam.t)
(f_branch : Lam.t) (lambda_cxt : Lam_compile_context.t) =
match
Expand Down Expand Up @@ -1765,11 +1721,8 @@ let compile output_prefix =
check the arity of fn before wrapping it
we need mark something that such eta-conversion can not be simplified in some cases
*)
| {
primitive = Pjs_unsafe_downgrade {name = property; setter = false};
args = [obj];
} -> (
(* getter {[ x #. height ]} *)
| {primitive = Pjs_object_get property; args = [obj]} -> (
(* property read: obj["height"] *)
match
compile_lambda {lambda_cxt with continuation = NeedValue Not_tail} obj
with
Expand All @@ -1785,11 +1738,8 @@ let compile output_prefix =
in
Js_output.output_of_block_and_expression lambda_cxt.continuation blocks
ret)
| {
primitive = Pjs_unsafe_downgrade {name = property; setter = true};
args = [obj; setter_val];
} -> (
(* setter {[ x ## method_call ]} *)
| {primitive = Pjs_object_set property; args = [obj; setter_val]} -> (
(* property write: obj["height"] = v *)
let need_value_no_return_cxt =
{lambda_cxt with continuation = NeedValue Not_tail}
in
Expand All @@ -1812,7 +1762,7 @@ let compile output_prefix =
| Some (obj_code, obj) ->
cont obj_block arg_block (Some obj_code)
(E.seq (E.assign (E.dot (E.var obj) property) value) E.unit)))
| {primitive = Pjs_unsafe_downgrade _; args} -> assert false
| {primitive = Pjs_object_get _ | Pjs_object_set _; args} -> assert false
| {primitive = Pjs_fn_method; args = args_lambda} -> (
match args_lambda with
| [Lfunction {params; body; attr = {return_unit; async}; loc}] ->
Expand Down
2 changes: 1 addition & 1 deletion compiler/core/lam_compile_primitive.ml
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ let translate output_prefix loc (cxt : Lam_compile_context.t)
| Pis_undefined -> E.is_undef (Ext_list.singleton_exn args)
| Pis_null_undefined -> E.is_null_undefined (Ext_list.singleton_exn args)
| Ptypeof -> E.typeof (Ext_list.singleton_exn args)
| Pjs_unsafe_downgrade _ | Pdebugger | Pjs_fn_method ->
| Pjs_object_get _ | Pjs_object_set _ | Pdebugger | Pjs_fn_method ->
assert false (* already handled by {!Lam_compile} *)
| Pstringadd -> (
match args with
Expand Down
Loading
Loading