Skip to content

Commit bc684ac

Browse files
committed
allow mutation of private record fields with @allowMutation
should be reversed at some point to become the default behavior you could opt-out with `@DisallowMutation`
1 parent 3f48a4d commit bc684ac

24 files changed

Lines changed: 219 additions & 1 deletion

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
- Rewatch: feature-gated source directories. Tag a source entry with `"feature": "<name>"` and select with `--features a,b` (or per-dep in `dependencies` / `dev-dependencies`) to include optional slices of a package's source tree at build time. Top-level `features` map supports transitive implications. https://github.com/rescript-lang/rescript/pull/8379
8484
- Rewatch: improve watch output and add `--clear-screen` option. https://github.com/rescript-lang/rescript/pull/8373
8585
- Add `Dict.assignMany`, `Dict.concat`, `Dict.concatMany`, `Dict.concatAll`, `Array.concatAll` to the stdlib. https://github.com/rescript-lang/rescript/pull/8364
86+
- Allow mutation of private record fields with @allowMutation https://github.com/rescript-lang/rescript/pull/8366
8687

8788
#### :bug: Bug fix
8889

compiler/ml/builtin_attributes.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,13 @@ let immediate =
240240
| {txt = "ocaml.immediate" | "immediate"; _}, _ -> true
241241
| _ -> false)
242242

243+
let has_allow_mutation attr =
244+
List.exists
245+
(function
246+
| {txt = "allowMutation"; _}, _ -> true
247+
| _ -> false)
248+
attr
249+
243250
(* The "ocaml.boxed (default)" and "ocaml.unboxed (default)"
244251
attributes cannot be input by the user, they are added by the
245252
compiler when applying the default setting. This is done to record

compiler/ml/builtin_attributes.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,7 @@ val explicit_arity : Parsetree.attributes -> bool
9393

9494
val immediate : Parsetree.attributes -> bool
9595

96+
val has_allow_mutation : Parsetree.attributes -> bool
97+
9698
val has_unboxed : Parsetree.attributes -> bool
9799
val has_boxed : Parsetree.attributes -> bool

compiler/ml/typecore.ml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,14 @@ let extract_concrete_record env ty =
317317
| p0, p, {type_kind = Type_record (fields, repr)} -> (p0, p, fields, repr)
318318
| _ -> raise Not_found
319319

320+
let private_record_allows_mutation env label =
321+
match extract_concrete_typedecl env label.lbl_res with
322+
| _, _, {type_kind = Type_record _; type_private = Private; type_attributes}
323+
->
324+
Builtin_attributes.has_allow_mutation type_attributes
325+
| _ -> false
326+
| exception Not_found -> false
327+
320328
let extract_concrete_variant env ty =
321329
match extract_concrete_typedecl env ty with
322330
| p0, p, {type_kind = Type_variant cstrs} -> (p0, p, cstrs)
@@ -3637,7 +3645,13 @@ and type_label_exp ~call_context create env loc ty_expected
36373645
end_def ();
36383646
(* Generalize information merged from ty_expected *)
36393647
generalize_structure ty_arg);
3640-
if label.lbl_private = Private then
3648+
let allow_private_assignment =
3649+
match call_context with
3650+
| `SetRecordField when not create ->
3651+
private_record_allows_mutation env label
3652+
| _ -> false
3653+
in
3654+
if label.lbl_private = Private && not allow_private_assignment then
36413655
if create then raise (Error (loc, env, Private_type ty_expected))
36423656
else raise (Error (lid.loc, env, Private_label (lid.txt, ty_expected)));
36433657
let arg =

compiler/ml/typedecl.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,15 @@ let transl_declaration ~type_record_as_object ~untagged_wfc env sdecl id =
387387
( sdecl.ptype_loc,
388388
Invalid_attribute
389389
"@notUndefined can only be used on abstract types" )));
390+
(if Builtin_attributes.has_allow_mutation sdecl.ptype_attributes then
391+
match (sdecl.ptype_private, sdecl.ptype_kind) with
392+
| Private, Ptype_record _ -> ()
393+
| _ ->
394+
raise
395+
(Error
396+
( sdecl.ptype_loc,
397+
Invalid_attribute
398+
"@allowMutation can only be used on private record types" )));
390399

391400
(* Bind type parameters *)
392401
reset_type_variables ();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/allowMutation_private_abstract_attribute.res:2:1-20
4+
5+
1 │ @allowMutation
6+
2 │ type t = private int
7+
3 │
8+
9+
@allowMutation can only be used on private record types
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/allowMutation_private_record_construction.res:10:30-39
4+
5+
8 │ }
6+
9 │
7+
10 │ let _item: PrivateRecord.t = {value: 1}
8+
11 │
9+
10+
Cannot create values of the private type PrivateRecord.t
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/allowMutation_private_record_immutable_field.res:11:1-21
4+
5+
9 │
6+
10 │ let item = PrivateRecord.make(1)
7+
11 │ item.name = "changed"
8+
12 │
9+
10+
The record field name is not mutable
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/allowMutation_private_record_update.res:11:16-34
4+
5+
9 │
6+
10 │ let item = PrivateRecord.make(1)
7+
11 │ let _updated = {...item, value: 2}
8+
12 │
9+
10+
Cannot create values of the private type PrivateRecord.t
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/allowMutation_private_variant_attribute.res:2:1-22
4+
5+
1 │ @allowMutation
6+
2 │ type t = private A | B
7+
3 │
8+
9+
@allowMutation can only be used on private record types

0 commit comments

Comments
 (0)