Skip to content

Commit 42e7c9e

Browse files
authored
flambda-backend: Add [@no_mutable_implied_modalities] (#2716)
1 parent 4aaba97 commit 42e7c9e

File tree

9 files changed

+179
-16
lines changed

9 files changed

+179
-16
lines changed

parsing/builtin_attributes.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ let builtin_attrs =
109109
; "only_generative_effects"; "ocaml.only_generative_effects"
110110
; "error_message"; "ocaml.error_message"
111111
; "layout_poly"; "ocaml.layout_poly"
112+
; "no_mutable_implied_modalities"; "ocaml.no_mutable_implied_modalities"
112113
]
113114

114115
(* nroberts: When we upstream the builtin-attribute whitelisting, we shouldn't
@@ -634,6 +635,9 @@ let parse_standard_implementation_attributes attr =
634635
flambda_oclassic_attribute attr;
635636
zero_alloc_attribute attr
636637

638+
let has_no_mutable_implied_modalities attrs =
639+
has_attribute ["ocaml.no_mutable_implied_modalities";"no_mutable_implied_modalities"] attrs
640+
637641
let has_local_opt attrs =
638642
has_attribute ["ocaml.local_opt"; "local_opt"] attrs
639643

parsing/builtin_attributes.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ val has_boxed: Parsetree.attributes -> bool
178178
val parse_standard_interface_attributes : Parsetree.attribute -> unit
179179
val parse_standard_implementation_attributes : Parsetree.attribute -> unit
180180

181+
val has_no_mutable_implied_modalities: Parsetree.attributes -> bool
181182
val has_local_opt: Parsetree.attributes -> bool
182183
val has_layout_poly: Parsetree.attributes -> bool
183184
val has_curry: Parsetree.attributes -> bool

testsuite/tests/typing-modes/mutable.ml

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,147 @@
11
(* TEST
2-
flags = "-extension unique";
2+
flags = "-extension unique -w +53";
33
expect;
44
*)
55

66
(* This file tests the typing around mutable() logic. *)
77

8-
(* For legacy compatibility, [mutable] implies [global] [shared] and [many].
9-
Therefore, the effect of mutable in isolation is not testable yet. *)
8+
(* By default, mutable implies [global many shared] modalities *)
9+
type r = {mutable s : string}
10+
let foo (local_ s) = local_ {s}
11+
[%%expect{|
12+
type r = { mutable s : string; }
13+
Line 2, characters 29-30:
14+
2 | let foo (local_ s) = local_ {s}
15+
^
16+
Error: This value escapes its region.
17+
|}]
18+
19+
(* [@no_mutable_implied_modalities] disables those implied modalities, and
20+
allows us to test [mutable] alone *)
21+
22+
(* Note the attribute is not printed back, which might be confusing.
23+
Considering this is a short-term workaround, let's not worry too much. *)
24+
type 'a r = {mutable s : 'a [@no_mutable_implied_modalities]}
25+
[%%expect{|
26+
type 'a r = { mutable s : 'a; }
27+
|}]
28+
29+
(* We can now construct a local record using a local field. *)
30+
let foo (local_ s) = local_ {s}
31+
[%%expect{|
32+
val foo : local_ 'a -> local_ 'a r = <fun>
33+
|}]
34+
35+
(* Mutation needs to be global *)
36+
let foo (local_ r) =
37+
r.s <- (local_ "hello")
38+
[%%expect{|
39+
Line 2, characters 9-25:
40+
2 | r.s <- (local_ "hello")
41+
^^^^^^^^^^^^^^^^
42+
Error: This value escapes its region.
43+
|}]
44+
45+
let foo (local_ r) = ref r.s
46+
[%%expect{|
47+
Line 1, characters 25-28:
48+
1 | let foo (local_ r) = ref r.s
49+
^^^
50+
Error: This value escapes its region.
51+
|}]
52+
53+
let foo (local_ r) =
54+
r.s <- "hello"
55+
[%%expect{|
56+
val foo : local_ string r -> unit = <fun>
57+
|}]
1058

11-
(* CR zqian: add test for mutable when mutable is decoupled from modalities. *)
59+
(* We can still add modalities explicitly. Of course, the print-back is
60+
confusing. *)
61+
type r' = {mutable s' : string @@ global [@no_mutable_implied_modalities]}
62+
[%%expect{|
63+
type r' = { mutable global_ s' : string; }
64+
|}]
65+
66+
let foo (local_ s') = local_ {s'}
67+
[%%expect{|
68+
Line 1, characters 30-32:
69+
1 | let foo (local_ s') = local_ {s'}
70+
^^
71+
Error: This value escapes its region.
72+
|}]
73+
74+
(* mutable defaults to mutable(legacy = nonportable), so currently we can't construct a
75+
portable record (ignoring mode-crossing). *)
76+
let foo (s @ portable) = ({s} : _ @@ portable)
77+
[%%expect{|
78+
Line 1, characters 26-29:
79+
1 | let foo (s @ portable) = ({s} : _ @@ portable)
80+
^^^
81+
Error: This value is nonportable but expected to be portable.
82+
|}]
83+
84+
(* For monadic axes, mutable defaults to mutable(min). So currently we can't
85+
write a [contended] value to a mutable field. *)
86+
let foo (r @ uncontended) (s @ contended) = r.s <- s
87+
[%%expect{|
88+
Line 1, characters 51-52:
89+
1 | let foo (r @ uncontended) (s @ contended) = r.s <- s
90+
^
91+
Error: This value is contended but expected to be uncontended.
92+
|}]
93+
94+
module M : sig
95+
type t = { mutable s : string [@no_mutable_implied_modalities] }
96+
end = struct
97+
type t = { mutable s : string }
98+
end
99+
[%%expect{|
100+
Lines 3-5, characters 6-3:
101+
3 | ......struct
102+
4 | type t = { mutable s : string }
103+
5 | end
104+
Error: Signature mismatch:
105+
Modules do not match:
106+
sig type t = { mutable s : string; } end
107+
is not included in
108+
sig type t = { mutable s : string; } end
109+
Type declarations do not match:
110+
type t = { mutable s : string; }
111+
is not included in
112+
type t = { mutable s : string; }
113+
Fields do not match:
114+
mutable s : string;
115+
is not the same as:
116+
mutable s : string;
117+
The second is empty and the first is shared.
118+
|}]
119+
120+
module M : sig
121+
type t = { mutable s : string }
122+
end = struct
123+
type t = { mutable s : string [@no_mutable_implied_modalities] }
124+
end
125+
[%%expect{|
126+
Lines 3-5, characters 6-3:
127+
3 | ......struct
128+
4 | type t = { mutable s : string [@no_mutable_implied_modalities] }
129+
5 | end
130+
Error: Signature mismatch:
131+
Modules do not match:
132+
sig type t = { mutable s : string; } end
133+
is not included in
134+
sig type t = { mutable s : string; } end
135+
Type declarations do not match:
136+
type t = { mutable s : string; }
137+
is not included in
138+
type t = { mutable s : string; }
139+
Fields do not match:
140+
mutable s : string;
141+
is not the same as:
142+
mutable s : string;
143+
The second is global_ and the first is not.
144+
|}]
12145

13146
type r =
14147
{ f : string -> string;

testsuite/tests/warnings/w53.compilers.reference

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ File "w53.ml", line 12, characters 4-5:
88
^
99
Warning 32 [unused-value-declaration]: unused value h.
1010

11+
File "w53.ml", line 9, characters 24-53:
12+
9 | type r0 = {s : string [@no_mutable_implied_modalities]} (* rejected *)
13+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
Warning 53 [misplaced-attribute]: the "no_mutable_implied_modalities" attribute cannot appear in this context
15+
1116
File "w53.ml", line 12, characters 14-20:
1217
12 | let h x = x [@inline] (* rejected *)
1318
^^^^^^

testsuite/tests/warnings/w53.ml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
55
66
7-
8-
9-
107
*)
118

9+
type r0 = {s : string [@no_mutable_implied_modalities]} (* rejected *)
10+
type r1 = {mutable s : string [@no_mutable_implied_modalities]} (* accepted *)
11+
1212
let h x = x [@inline] (* rejected *)
1313
let h x = x [@ocaml.inline] (* rejected *)
1414

typing/printtyp.ml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,11 +1309,11 @@ let tree_of_modality (t : Mode.Modality.t) =
13091309
Some (Ogf_legacy Ogf_global)
13101310
| _ -> Option.map (fun x -> Ogf_new x) (tree_of_modality_new t)
13111311

1312-
let tree_of_modalities mutability t =
1312+
let tree_of_modalities ~has_mutable_implied_modalities t =
13131313
let l = Mode.Modality.Value.to_list t in
13141314
(* CR zqian: decouple mutable and modalities *)
13151315
let l =
1316-
if Types.is_mutable mutability then
1316+
if has_mutable_implied_modalities then
13171317
List.filter (fun m -> not @@ Typemode.is_mutable_implied_modality m) l
13181318
else
13191319
l
@@ -1502,7 +1502,8 @@ and tree_of_labeled_typlist mode tyl =
15021502
List.map (fun (label, ty) -> label, tree_of_typexp mode Alloc.Const.legacy ty) tyl
15031503

15041504
and tree_of_typ_gf {ca_type=ty; ca_modalities=gf; _} =
1505-
(tree_of_typexp Type Alloc.Const.legacy ty, tree_of_modalities Immutable gf)
1505+
(tree_of_typexp Type Alloc.Const.legacy ty,
1506+
tree_of_modalities ~has_mutable_implied_modalities:false gf)
15061507

15071508
(** We are on the RHS of an arrow type, where [ty] is the return type, and [m]
15081509
is the return mode. This function decides the printed modes on [ty].
@@ -1689,7 +1690,15 @@ let tree_of_label l =
16891690
mut
16901691
| Immutable -> Om_immutable
16911692
in
1692-
let ld_modalities = tree_of_modalities l.ld_mutable l.ld_modalities in
1693+
let has_mutable_implied_modalities =
1694+
if is_mutable l.ld_mutable then
1695+
not (Builtin_attributes.has_no_mutable_implied_modalities l.ld_attributes)
1696+
else
1697+
false
1698+
in
1699+
let ld_modalities =
1700+
tree_of_modalities ~has_mutable_implied_modalities l.ld_modalities
1701+
in
16931702
(Ident.name l.ld_id, mut, tree_of_typexp Type l.ld_type, ld_modalities)
16941703
16951704
let tree_of_constructor_arguments = function

typing/typedecl.ml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,15 @@ let transl_labels ~new_var_jkind ~allow_unboxed env univars closed lbls kloc =
426426
| Immutable -> Immutable
427427
| Mutable -> Mutable Mode.Alloc.Comonadic.Const.legacy
428428
in
429-
let modalities = Typemode.transl_modalities mut modalities in
429+
let has_mutable_implied_modalities =
430+
if Types.is_mutable mut then
431+
not (Builtin_attributes.has_no_mutable_implied_modalities attrs)
432+
else
433+
false
434+
in
435+
let modalities =
436+
Typemode.transl_modalities ~has_mutable_implied_modalities modalities
437+
in
430438
let arg = Ast_helper.Typ.force_poly arg in
431439
let cty = transl_simple_type ~new_var_jkind env ?univars ~closed Mode.Alloc.Const.legacy arg in
432440
{ld_id = Ident.create_local name.txt;
@@ -463,7 +471,10 @@ let transl_types_gf ~new_var_jkind ~allow_unboxed
463471
env loc univars closed cal kloc =
464472
let mk arg =
465473
let cty = transl_simple_type ~new_var_jkind env ?univars ~closed Mode.Alloc.Const.legacy arg.pca_type in
466-
let gf = Typemode.transl_modalities Immutable arg.pca_modalities in
474+
let gf =
475+
Typemode.transl_modalities ~has_mutable_implied_modalities:false
476+
arg.pca_modalities
477+
in
467478
{ca_modalities = gf; ca_type = cty; ca_loc = arg.pca_loc}
468479
in
469480
let tyl_gfl = List.map mk cal in

typing/typemode.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,10 @@ let is_mutable_implied_modality m =
115115
(* polymorphic equality suffices for now. *)
116116
List.mem m mutable_implied_modalities
117117

118-
let transl_modalities mut modalities =
118+
let transl_modalities ~has_mutable_implied_modalities modalities =
119119
let modalities = List.map transl_modality modalities in
120120
let modalities =
121-
if Types.is_mutable mut
121+
if has_mutable_implied_modalities
122122
then modalities @ mutable_implied_modalities
123123
else modalities
124124
in

typing/typemode.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ val transl_alloc_mode : Jane_syntax.Mode_expr.t -> Mode.Alloc.Const.t
77

88
(** Interpret mode syntax as modalities *)
99
val transl_modalities :
10-
Types.mutability ->
10+
has_mutable_implied_modalities:bool ->
1111
Parsetree.modality Location.loc list ->
1212
Mode.Modality.Value.t
1313

0 commit comments

Comments
 (0)